commit c891bb7105ab0581c86e4db202dc871c16aa6ea9 Author: geos_one Date: Sun Aug 10 01:34:16 2025 +0200 New upstream version 8.1.0 diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..ee6b12d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,35 @@ +--- +name: Bug report +about: Create a report to help us improve BeeGFS +title: '' +labels: bug, new +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is, which component (mgmtd, meta, storage, client, others or any combination) is affected and what effects (error messages, performance degradation, crashes, ...) can be observed. + +**Describe the system** +A short description of the system BeeGFS is running on that covers all information relevant to the issue. For example (but not limited to): +1. Number and type of BeeGFS services and their distribution across physical machines +2. Information about the network interconnect (technology, protocols, topology) +3. Information about underlying file systems and storage hardware +4. Operating system and proprietary driver information (versions, RDMA drivers) +5. Software that is accessing BeeGFS + +**To Reproduce** +Steps to reproduce the behavior: +1. Set configuration option "x" to "y" for component "c" +2. Start component "c" on machine "m" +3. Run application "a" with parameters "p" +4. Observe behavior "b" + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Log messages, error outputs** +If available, add relevant log files (anonymize if necessary), error messages, `perf` measurements, `strace` outputs, core dumps and everything else you have collected and can share publicly. + +**Additional context** +Add any other context not covered above. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..b80bdec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest a feature or improvement for BeeGFS +title: '' +labels: enhancement, new +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context about the feature request here. diff --git a/BUILD.txt b/BUILD.txt new file mode 120000 index 0000000..42061c0 --- /dev/null +++ b/BUILD.txt @@ -0,0 +1 @@ +README.md \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..cf949f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,140 @@ +cmake_minimum_required(VERSION 3.7) + + +project( + BeeGFS + LANGUAGES CXX C +) + +set(BEEGFS_VERSION "" CACHE STRING "Defaults to current git version.") +if(BEEGFS_VERSION STREQUAL "") + execute_process( + COMMAND git describe --match *.* --abbrev=10 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE BEEGFS_VERSION_FROM_GIT + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + if(BEEGFS_VERSION_FROM_GIT STREQUAL "") + message(FATAL_ERROR "Cannot determine BeeGFS version. Specify with `cmake -DBEEGFS_VERSION=...`") + endif() + + set(BEEGFS_VERSION ${BEEGFS_VERSION_FROM_GIT} CACHE STRING "Defaults to current git version" FORCE) +endif() + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBEEGFS_VERSION=\\\"${BEEGFS_VERSION}\\\"") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DBEEGFS_VERSION=\\\"${BEEGFS_VERSION}\\\"") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wunused-variable -Woverloaded-virtual -Wno-unused-parameter -Wuninitialized -Wno-missing-field-initializers") + +set(BEEGFS_DEBUG OFF CACHE BOOL "Build with debug information.") +if(BEEGFS_DEBUG) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DBEEGFS_DEBUG=1 -Werror") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -DBEEGFS_DEBUG=1") +endif() + +set(BEEGFS_INSTRUMENTATION "" CACHE STRING "Instrumentation for testing.") +if(BEEGFS_INSTRUMENTATION STREQUAL "") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3") +elseif(BEEGFS_INSTRUMENTATION STREQUAL "coverage") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} --coverage -O0") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage -O0") +elseif(BEEGFS_INSTRUMENTATION STREQUAL "address") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -O0") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +elseif(BEEGFS_INSTRUMENTATION STREQUAL "thread") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=thread") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=thread -O0") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +elseif(BEEGFS_INSTRUMENTATION STREQUAL "undefined") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -O0") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O0") +elseif(BEEGFS_INSTRUMENTATION STREQUAL "iwyu") + if(NOT DEFINED CMAKE_CXX_INCLUDE_WHAT_YOU_USE) + set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "include-what-you-use") + endif() + list(APPEND CMAKE_CXX_INCLUDE_WHAT_YOU_USE "-Xiwyu" "--mapping_file=${CMAKE_SOURCE_DIR}/iwyu-mappings.imp") +else() + message(FATAL_ERROR "Invalid instrumentation.") +endif() + +set(BEEGFS_SKIP_TESTS OFF CACHE BOOL "Skip building and running tests.") +set(BEEGFS_SKIP_CLIENT OFF CACHE BOOL "Skip building the kernel module.") +set(BEEGFS_KERNELDIR "" CACHE PATH "Path to kernel for kernel module (optional).") +set(BEEGFS_OFEDDIR "" CACHE PATH "Path to OFED for kernel module (optional).") + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb3") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -ggdb3") + +set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lrt") + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +include_directories(common/source) +include_directories(SYSTEM thirdparty/source/boost) +include_directories(SYSTEM thirdparty/source/nu/include) +include_directories(thirdparty/source/gtest/googletest/include) + +if(NOT BEEGFS_SKIP_TESTS) + enable_testing() + option(INSTALL_GMOCK OFF) + option(INSTALL_GTEST OFF) + add_subdirectory("thirdparty/source/gtest") +endif() + +set(CMAKE_INSTALL_PREFIX "/") + +add_subdirectory("beeond") +# add_subdirectory("client_devel") +# add_subdirectory("client_module") +add_subdirectory("common") +add_subdirectory("event_listener") +add_subdirectory("fsck") +add_subdirectory("meta") +add_subdirectory("mon") +add_subdirectory("storage") +add_subdirectory("utils") + +add_custom_target( + dkms-install + COMMAND dkms install "beegfs/${BEEGFS_VERSION}" +) + +add_custom_target( + dkms-uninstall + COMMAND dkms remove "beegfs/${BEEGFS_VERSION}" --all +) + + +### Packaging settings ### + +set(CPACK_PACKAGE_CONTACT "BeeGFS Maintainers ") +set(CPACK_PACKAGE_VENDOR "ThinkparQ GmbH") + +string(REGEX REPLACE "^([^.]+)\\.([^-]+)-([^-]+)(-.*)?$" "\\1" CPACK_PACKAGE_VERSION_MAJOR "${BEEGFS_VERSION}") +string(REGEX REPLACE "^([^.]+)\\.([^-]+)-([^-]+)(-.*)?$" "\\2" CPACK_PACKAGE_VERSION_MINOR "${BEEGFS_VERSION}") +string(REGEX REPLACE "^([^.]+)\\.([^-]+)-([^-]+)(-.*)?$" "\\3" CPACK_PACKAGE_VERSION_PATCH "${BEEGFS_VERSION}") + +set(CPACK_PACKAGING_INSTALL_PREFIX "/") + +# silence cpack warnings about non relocatable package. +set(CPACK_PACKAGE_RELOCATABLE OFF) +set(CPACK_GENERATOR "DEB" "RPM") + +# enable creation of separate packages +set(CPACK_DEB_COMPONENT_INSTALL ON) +set(CPACK_RPM_COMPONENT_INSTALL ON) + +set(CPACK_STRIP_FILES OFF) + +# cpack tries to create these directories with nonstandard / +# conflicting permissions in some versions. solve by assuming that +# these directories already exist. +set(CPACK_RPM_EXCLUDE_FROM_AUTO_FILELIST_ADDITION "/sbin;/usr/sbin") + +include(CPack) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..4ec54ac --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,118 @@ +# BeeGFS Code of Conduct + +## Our Pledge + +We as BeeGFS contributors and maintainers pledge to make participation in our community a +harassment-free experience for everyone, regardless of age, body size, visible or invisible +disability, ethnicity, sex characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or +sexual identity and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and +healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the + experience +* Focusing on what is best not just for us as individuals, but for the overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email address, without their + explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Enforcement Responsibilities + +The BeeGFS community managers are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in response to any behavior +that they deem inappropriate, threatening, offensive, or harmful. + +The BeeGFS community managers have the right and responsibility to remove, edit, or reject comments, +commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of +Conduct, and will communicate reasons for moderation decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when an individual is +officially representing the community in public spaces. Examples of representing our community +include using an official email address, posting via an official social media account, or acting as +an appointed representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community +managers responsible for enforcement at +[community-managers@thinkparq.com](mailto:community-managers@thinkparq.com). All complaints will be +reviewed and investigated promptly and fairly. + +All community managers are obligated to respect the privacy and security of the reporter of any +incident. + +## Enforcement Guidelines + +Community managers will follow these Community Impact Guidelines in determining the consequences for +any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or +unwelcome in the community. + +**Consequence**: A private, written warning from community managers, providing clarity around the +nature of the violation and an explanation of why the behavior was inappropriate. A public apology +may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series of actions. + +**Consequence**: A warning with consequences for continued behavior. No interaction with the people +involved, including unsolicited interaction with those enforcing the Code of Conduct, for a +specified period of time. This includes avoiding interactions in community spaces as well as +external channels like social media. Violating these terms may lead to a temporary or permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including sustained inappropriate +behavior. + +**Consequence**: A temporary ban from any sort of interaction or public communication with the +community for a specified period of time. No public or private interaction with the people involved, +including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this +period. Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community standards, including +sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement +of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at +[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla +CoC]. + +For answers to common questions about this code of conduct, see the FAQ at +[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at +[https://www.contributor-covenant.org/translations][translations]. + +[homepage]: https://www.contributor-covenant.org +[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html +[Mozilla CoC]: https://github.com/mozilla/diversity +[FAQ]: https://www.contributor-covenant.org/faq +[translations]: https://www.contributor-covenant.org/translations diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b02ce2e --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,66 @@ +# Contributing +Thank you for your interest in contributing to `beegfs`! 🎉 + +We appreciate that you want to take the time to contribute. Please follow these steps before +submitting your PR. + +# Contents + +- [ThinkParQ Contributor License Agreement (CLA)](#thinkparq-contributor-license-agreement-cla) +- [Creating a Pull Request](#creating-a-pull-request) +- [Our Commitment](#our-commitment) + +# ThinkParQ Contributor License Agreement (CLA) + +Before contributions can be accepted we must have a signed CLA on file for all contributor(s): + +* Download and fill out and sign the ThinkParQ CLA found at: + https://www.beegfs.io/docs/ThinkParQ_CLA.pdf. +* Email your signed copy to . + + +# Creating a Pull Request + +1. Please search [existing issues](https://github.com/ThinkParQ/beegfs/issues) to determine if an + issue already exists for what you intend to contribute. +2. If the issue does not exist, [create a new + one](https://github.com/ThinkParQ/beegfs/issues/new) that explains the bug or feature request. + * Let us know in the issue that you plan on creating a pull request for it. This helps us to keep + track of the pull request and avoid any duplicate efforts. +3. Before creating a pull request, write up a brief proposal in the issue describing what your + change would be and how it would work so that others can comment. + * It's better to wait for feedback from the maintainers before writing code. We don't have an + SLA for our feedback, but we will do our best to respond in a timely manner (at a minimum, to + give you an idea if you're on the right track and that you should proceed, or not). +4. When ready refer to the guidelines and process for submitting a [Pull + Request](https://github.com/ThinkParQ/beegfs-go/wiki/Pull-Requests). + + +# Our Commitment +While we truly appreciate your efforts on pull requests and will accept contributions as often as we +can, we **cannot** commit to accepting all PRs. Here are a few reasons why a PR may be rejected: + +* There are many factors involved in integrating new code into this project including: + * Adding appropriate unit and end-to-end test coverage for new/changed functionality. + * Ensuring adherence with ThinkParQ and industry standards around security and licensing. + * Validating new functionality doesn't raise long-term maintainability and/or supportability + concerns. + * Verifying changes fit with the current and/or planned architecture. + * etc. + + In other words, while your bug fix or feature may be perfect as a standalone patch, we have to + ensure the changes also work in all use cases, supported, configurations, and across our support + matrix. + +* The BeeGFS development team must plan resources to integrate your code into our code base and CI + platform, and depending on the complexity of your PR, we may or may not have the resources + available to make it happen in a timely fashion. We'll do our best, but typically the earliest + changes can be merged into the master branch is with our next formal release, unless they resolve + a critical bug or security vulnerability. + +* Sometimes a PR doesn't fit into our future plans or conflicts with other items on the roadmap. + It's possible that a PR you submit doesn't align with our upcoming plans, thus we won't be able to + use it. It's not personal and why we highly recommend submitting an issue with your proposed + changes so we can provide feedback before you expend significant effort on development. + +Thank you for considering to contribute to `beegfs`! \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..e360d51 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,48 @@ +BeeGFS END USER LICENSE AGREEMENT +================================= + +Copyright (c) 2009 Fraunhofer ITWM, 2022 ThinkParQ GmbH + +Use of the provided software and libraries is governed by the BeeGFS End User License Agreement +found at https://www.beegfs.io/docs/BeeGFS_EULA.txt. + +THIRD PARTY LICENSES +==================== + +Paul Hsieh OLD BSD license +-------------------------- + +The files `common/source/common/toolkit/BufferTk.cpp` and `client_module/source/common/toolkit/ +HashTk.c` contain a hash implementation distributed under the following license: + +Copyright (c) 2010, Paul Hsieh All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted +provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this list of + conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, this list of + conditions and the following disclaimer in the documentation and/or other materials provided + with the distribution. + + Neither my name, Paul Hsieh, nor the names of any other contributors to the code use may + be used to endorse or promote products derived from this software without specific prior + written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR SEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING ANY +WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +See http://www.azillionmonkeys.com/qed/hash.html and http://www.azillionmonkeys.com/qed/weblicense.html. + +Other third party code +---------------------- + +The directory `thirdparty` contains code distributed under the terms of the included license files. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9a6d97c --- /dev/null +++ b/Makefile @@ -0,0 +1,198 @@ +# all variables used by package builds must be exported. +export BEEGFS_DEBUG +export USER_CXXFLAGS +export USER_LDFLAGS +export BEEGFS_DEBUG_RDMA +export BEEGFS_DEBUG_IP + +export KDIR +export KSRCDIR + +BEEGFS_THIRDPARTY_OPTIONAL = +export BEEGFS_THIRDPARTY_OPTIONAL + +WITHOUT_COMM_DEBUG = true + +# if version is not set, derive from current git. +# debian epoch for unversioned builds (usually dev builds) is 19 because it was 18 previously. +# versioned builds (usually from tags) are set to epoch 20, allowing upgrades from all previous versions. +ifndef BEEGFS_VERSION + BEEGFS_VERSION := $(shell git describe --tags --match '*.*' --abbrev=10) + BEEGFS_EPOCH := 19 +else + BEEGFS_EPOCH := 20 +endif + +# underscores are not allowed in version strings, because they are not valid semver +ifneq (,$(findstring _,$(BEEGFS_VERSION))) + $(error Underscores not allowed in versions. BEEGFS_VERSION is $(BEEGFS_VERSION)) +endif + +# dashes in semver for pre-releases should be converted to tildes for package managers in the +# distributions we support +BEEGFS_VERSION_RPM := $(subst -,~,$(BEEGFS_VERSION)) +BEEGFS_VERSION_DEB := $(BEEGFS_EPOCH):$(subst -,~,$(BEEGFS_VERSION)) +export BEEGFS_VERSION + +ifneq ($(NVFS_INCLUDE_PATH),) +BEEGFS_NVFS=1 +endif +export BEEGFS_NVFS + +PREFIX ?= /opt/beegfs +DESTDIR ?= + +DAEMONS := meta storage mon +UTILS := fsck event_listener $(if $(WITHOUT_COMM_DEBUG),,comm_debug) + +# exclude components with no runnable tests from `test'. +DO_NOT_TEST := thirdparty event_listener + +ALL_COMPONENTS := thirdparty common $(DAEMONS) $(UTILS) +TIDY_COMPONENTS := $(filter-out thirdparty event_listener, $(ALL_COMPONENTS)) + +all: daemons utils client + +.PHONY: daemons +daemons: $(patsubst %,%-all,$(DAEMONS)) + @ + +.PHONY: utils +utils: $(patsubst %,%-all,$(UTILS)) + @ + +.PHONY: $(patsubst %,%-all,$(DAEMONS) $(UTILS)) +$(patsubst %,%-all,$(DAEMONS) $(UTILS)): common-all + $(MAKE) -C $(subst -all,,$@)/build all + +.PHONY: common-all common +common-all: thirdparty + $(MAKE) -C common/build all + +.PHONY: thirdparty +thirdparty: + $(MAKE) -C thirdparty/build all $(BEEGFS_THIRDPARTY_OPTIONAL) + +.PHONY: client +client: + $(MAKE) -C client_module/build + +.PHONY: tidy +tidy: $(addsuffix -tidy,$(TIDY_COMPONENTS)) + @ + +define tidy_component +.PHONY: $1-tidy +$1-tidy: + +$(MAKE) -C $1/build tidy +endef + +$(foreach C,$(TIDY_COMPONENTS),$(eval $(call tidy_component,$(C)))) + +_tested_components := $(filter-out $(DO_NOT_TEST),$(ALL_COMPONENTS)) + +.PHONY: test +test: $(patsubst %,%-test,$(_tested_components)) + @ + +define test_component +.PHONY: $1-test +$1-test: $1-all + cd $1/build && ./test-runner --compiler +endef + +$(foreach C,$(_tested_components),$(eval $(call test_component,$(C)))) + +.PHONY: install +install: daemons-install utils-install common-install client-install event_listener-install + @ + +define install_component +.PHONY: $1-install +$1-install: $1-all + install -t $(DESTDIR)/$(PREFIX)/$2 -D \ + $1/build/beegfs-$$(or $$(install_name),$1) +endef + +comm_debug-install: install_name=comm-debug + +$(foreach D,$(DAEMONS),$(eval $(call install_component,$D,sbin))) +$(foreach U,$(filter-out event_listener,$(UTILS)),$(eval $(call install_component,$U,sbin))) + +.PHONY: daemons-install +daemons-install: $(patsubst %,%-install,$(DAEMONS)) + @ + +.PHONY: utils-install +utils-install: $(patsubst %,%-install,$(UTILS)) + @ + +.PHONY: common-install +common-install: common-all + install -t $(DESTDIR)/$(PREFIX)/lib -D \ + common/build/libbeegfs_ib.so + +.PHONY: client-install +client-install: client + install -t $(DESTDIR)/$(PREFIX)/lib/modules/$(KVER)/kernel/beegfs -D \ + client_module/build/beegfs.ko + +## Overriding previous generic rule due to non-matching executable name +.PHONY: event_listener-intsall +event_listener-install: event_listener-all + install -t $(DESTDIR)/$(PREFIX)/sbin -D \ + event_listener/build/beegfs-event-listener + +.PHONY: clean +clean: $(patsubst %,%-clean,$(ALL_COMPONENTS)) client-clean + @ + +.PHONY: $(patsubst %,%-clean,$(ALL_COMPONENTS)) +$(patsubst %,%-clean,$(ALL_COMPONENTS)): + $(MAKE) -C $(subst -clean,,$@)/build clean + +.PHONY: client-clean +client-clean: + $(MAKE) -C client_module/build clean + +# use DEBUILD_OPTS to pass more options to debuild, eg +# DEBUILD_OPTS='-j32 --prepend-path=/usr/lib/ccache' +# for greater concurrency during build and ccache support +.PHONY: package-deb +package-deb: clean + [ '$(PACKAGE_DIR)' ] || { echo need a PACKAGE_DIR >&2; false; } + ! [ -d '$(PACKAGE_DIR)' ] || { echo choose a new directory for PACKAGE_DIR, please >&2; false; } + mkdir -p '$(PACKAGE_DIR)' + cd '$(PACKAGE_DIR)' && \ + dpkg-source -I '-I$(PACKAGE_DIR)' -z1 -b '$(dir $(realpath $(firstword $(MAKEFILE_LIST))))' && \ + rm -rf build && \ + dpkg-source -x *.dsc build && \ + ( \ + cd build; \ + sed -i -e 's/beegfs (.*)/beegfs ($(BEEGFS_VERSION_DEB))/' debian/changelog; \ + sed -i -e 's/@DATE/$(shell date -R)/' debian/changelog; \ + debuild -eBEEGFS_\* $(DEBUILD_OPTS) -us -uc -b 2>&1 | grep -Ev "dir-or-file-in-opt" \ + ) && \ + rm -rf build *.dsc *.tar.gz \ + # Replace tilde in package filename with hypens. + # Github release action and api substitutes tilde (~) with dot (.) in file names when uploaded to Github packages. + find -type f -name "*~*.deb" -exec bash -c 'mv "$$1" "$${1//\~/-}"' _ {} \; + +# use RPMBUILD_OPTS to pass more options to rpmuild, eg +# RPMBUILD_OPTS='-D "MAKE_CONCURRENCY 32"' +# for greater concurrency during build +.PHONY: package-rpm +package-rpm: clean + [ '$(PACKAGE_DIR)' ] || { echo need a PACKAGE_DIR >&2; false; } + ! [ -d '$(PACKAGE_DIR)' ] || { echo choose a new directory for PACKAGE_DIR, please >&2; false; } + mkdir -p $(PACKAGE_DIR)/SOURCES + tar --exclude $(PACKAGE_DIR) --exclude .git --exclude .ccache \ + -cf $(PACKAGE_DIR)/SOURCES/beegfs-$(BEEGFS_VERSION_RPM).tar . + rpmbuild --clean -bb beegfs.spec \ + --define '_topdir $(abspath $(PACKAGE_DIR))' \ + --define 'EPOCH $(BEEGFS_EPOCH)' \ + --define 'BEEGFS_VERSION $(BEEGFS_VERSION_RPM)' \ + $(RPMBUILD_OPTS) \ + # Replace tilde in package filename with hypens. + # Github release action and api substitutes tilde (~) with dot (.) in file names when uploaded to Github packages. + find $(PACKAGE_DIR)/RPMS -type f -name "*~*.rpm" -exec bash -c 'mv "$$1" "$${1//\~/-}"' _ {} \; diff --git a/README.md b/README.md new file mode 100644 index 0000000..441cd71 --- /dev/null +++ b/README.md @@ -0,0 +1,185 @@ +# BeeGFS Parallel File System +BeeGFS (formerly FhGFS) is the leading parallel cluster file system, developed with a strong focus +on performance and designed for very easy installation and management. If I/O intensive workloads +are your problem, BeeGFS is the solution. + +Homepage: https://www.beegfs.io + +Documentation: https://doc.beegfs.io/ + +# Getting Started with BeeGFS + +## How do I download BeeGFS? + +If you don't need/want to build BeeGFS from sources, prebuilt packages for both x86 and ARM [are +available](https://www.beegfs.io/c/download/) for many popular Linux distributions. + +## How do I build BeeGFS from sources? + +Prior to BeeGFS 8, all development happened in a private Git repository, with the source code for +each release squashed into a single commit in the public Git repository. As part of BeeGFS 8, the +opportunity came up to rewrite some components and make the full history of the new components +public. For this to happen the source code is split across these repositories: + +* `beegfs` - The main public repository containing all original C/C++ components, notably the + Metadata and Storage services along with the Client kernel module and file system checker. +* `beegfs-rust` - New BeeGFS components written in Rust, notably the Management service. +* `beegfs-go` - New BeeGFS components written in Go, notably the BeeGFS command-line tool (CTL). +* `protobuf` - Common protocol buffer and gRPC service definitions along with generated library code + to interact with new BeeGFS services from multiple languages including C++, Go, Rust, etc. + * For Go, comprehensive libraries for fully managing BeeGFS can be found in `beegfs-go`. + +It is only necessary to clone the repo(s) containing the component(s) you wish to modify or build +from sources. If you wanted to build everything you would need to clone all three repositories: + +* `git clone git@github.com:ThinkParQ/beegfs.git` +* `git clone git@github.com:ThinkParQ/beegfs-rust.git` +* `git clone git@github.com:ThinkParQ/beegfs-go.git` + +Then refer to each repositories' README for directions on how to get started including installing any +prerequisites and building packaged or unpackaged binaries for the components provided by that repo. + +Note: It is not necessary to clone the `protobuf` repo to build BeeGFS from sources. This is only +needed to modify the protocol buffers or develop an application that integrates with BeeGFS. + +# Getting Started with BeeGFS C/C++ Components (this repo) + +## Prerequisites +Before building BeeGFS, install the following dependency packages: + +### Red Hat / CentOS +``` +$ yum install libuuid-devel libibverbs-devel librdmacm-devel libattr-devel redhat-rpm-config \ + rpm-build xfsprogs-devel zlib-devel gcc-c++ gcc \ + redhat-lsb-core unzip libcurl-devel elfutils-libelf-devel kernel-devel \ + libblkid-devel libnl3-devel +``` + +The `elfutils-libelf-devel` and `kernel-devel` packages can be omitted if you don't intend to +build the client module. + +On RHEL releases older than 8, the additional `devtoolset-7` package is also required, +which provides a newer compiler version. The installation steps are outlined here. +Please consult the documentation of your distribution for details. + + 1. Install a package with repository for your system: + - On CentOS, install package centos-release-scl available in CentOS repository: + ``` + $ sudo yum install centos-release-scl + ``` + - On RHEL, enable RHSCL repository for you system: + ``` + $ sudo yum-config-manager --enable rhel-server-rhscl-7-rpms + ``` + 2. Install the collection: + ``` + $ sudo yum install devtoolset-7 + ``` + + 3. Start using software collections: + ``` + $ scl enable devtoolset-7 bash + ``` + 4. Follow the instructions below to build BeeGFS. + +### Debian and Ubuntu + +#### Option 1: Semi-automatic installation of build dependencies + +Install required utilities: +``` +$ apt install --no-install-recommends devscripts equivs +``` +Automatically install build dependencies: +``` +$ mk-build-deps --install debian/control +``` + +#### Option 2: Manual installation of build dependencies + +Run this command to install the required packages: +``` +$ sudo apt install build-essential autoconf automake pkg-config devscripts debhelper \ + libtool libattr1-dev xfslibs-dev lsb-release kmod librdmacm-dev libibverbs-dev \ + default-jdk zlib1g-dev libssl-dev libcurl4-openssl-dev libblkid-dev uuid-dev \ + libnl-3-200 libnl-3-dev libnl-genl-3-200 libnl-route-3-200 libnl-route-3-dev dh-dkms +``` +Note: If you have an older Debian system you might have to install the `module-init-tools` +package instead of `kmod`. You also have the choice between the openssl, nss, or gnutls version +of `libcurl-dev`. Choose the one you prefer. On Debian versions older than 12, replace `dh-dkms` +by `dkms`. + +## Building Packages + +### For development systems + +BeeGFS comes with a Makefile capable of building packages for the system on which it is executed. +These include all services, the client module and utilities. + +To build RPM packages, run +``` + $ make package-rpm PACKAGE_DIR=packages +``` +You may also enable parallel execution with +``` + $ make package-rpm PACKAGE_DIR=packages RPMBUILD_OPTS="-D 'MAKE_CONCURRENCY '" +``` +where `` is the number of concurrent processes. + +For DEB packages use this command: +``` + $ make package-deb PACKAGE_DIR=packages +``` +Or start with `` jobs running in parallel: +``` + $ make package-deb PACKAGE_DIR=packages DEBUILD_OPTS="-j" +``` + +This will generate individual packages for each service (management, meta-data, storage) +as well as the client kernel module and administration tools. + +The above examples use `packages` as the output folder for packages, which must not exist +and will be created during the build process. +You may specify any other non-existent directory instead. + +Note, however, that having `PACKAGE_DIR` on a NFS or similar network share may slow down +the build process significantly. + +### For production systems, or from source snapshots + +By default the packaging system generates version numbers suitable only for development +packages. Packages intended for installation on production systems must be built differently. +All instructions to build development packages (as given above) apply, but additionally the +package version must be explicitly set. This is done by passing `BEEGFS_VERSION=` +in the make command line, e.g. +``` + $ make package-deb PACKAGE_DIR=packages DEBUILD_OPTS="-j" BEEGFS_VERSION=7.1.4-local1 +``` + +Setting the version explicitly is required to generate packages that can be easily upgraded +with the system package manager. + + +## Building without packaging +To build the complete project without generating any packages, +simply run +``` +$ make +``` + +The sub-projects have individual make targets, for example `storage-all`, +`meta-all`, etc. + +To speed things you can use the `-j` option of `make`. +Additionally, the build system supports `distcc`: +``` +$ make DISTCC=distcc +``` + +# Setup Instructions +Detailed guides on how to configure BeeGFS can be found at +[doc.beegfs.io](https://doc.beegfs.io/latest/index.html) + +# Share your thoughts +Of course, we are curious about what you are doing with the BeeGFS sources, so +don't forget to drop us a note... diff --git a/SUPPORT.md b/SUPPORT.md new file mode 100644 index 0000000..73d93c4 --- /dev/null +++ b/SUPPORT.md @@ -0,0 +1,137 @@ +Support for BeeGFS +================== + +- [Overview](#overview) +- [Documentation](#documentation) +- [Community Support](#community-support) +- [Enterprise Support](#enterprise-support) +- [Support Lifecycle and Policies](#support-lifecycle-and-policies) + - [BeeGFS Versioning](#beegfs-versioning) + - [Release Lifecycle](#release-lifecycle) + - [End-of-Life (EOL)](#end-of-life-eol) + - [End-of-Support (EOS)](#end-of-support-eos) + - [Example Scenarios](#example-scenarios) + +# Overview +The BeeGFS community edition is available free of charge and shares a common codebase with our +[enterprise edition](https://www.beegfs.io/c/enterprise/enterprise-features/). The enterprise +edition includes support alongside additional features, and is how we fund ongoing BeeGFS +development. Regardless of whether you use the paid or community edition, we welcome [GitHub +issues](https://github.com/ThinkParQ/beegfs/issues) for bugs and feature requests from everyone in +our community. + +**Please note:** GitHub issues are not the place for general questions on how to deploy or use +BeeGFS. Refer to the resources below. If you are unsure if your issue is a bug or "working as +designed," feel free to open an issue, and we will politely direct you to community support options +if necessary. + +Thank you for being part of the BeeGFS community! + +# Documentation + +* [BeeGFS Documentation Site](https://doc.beegfs.io). +* [Building BeeGFS from sources](README.md) (for developers). + * Check out the [contributing guide](CONTRIBUTING.md) if you'd like to contribute to BeeGFS! + +# Community Support + +BeeGFS is fortunate to have an active community of users who are often willing to help with general +questions on designing, deploying, and otherwise administering BeeGFS. + +* [BeeGFS Google Groups](https://groups.google.com/g/fhgfs-user). +* [GitHub Discussions](https://github.com/ThinkParQ/beegfs/discussions). + +While our team moderates these forums to ensure posts are on-topic and meet our [Code of +Conduct](code-of-conduct.md), we generally do not actively respond to posts. However, we are always +keeping an eye out for ways to improve BeeGFS and our documentation. + +# Enterprise Support + +The BeeGFS [enterprise edition](https://www.beegfs.io/c/enterprise/enterprise-features/) offers: + +- Dedicated support from BeeGFS experts. +- Access to features not available in the community edition such as storage pools and quotas. +- Assistance with designing, deploying, and managing BeeGFS. + +If you are a current BeeGFS enterprise customer with a valid support contract, please contact our +support team via the channels specified in your support contract documentation. + +If you are interested in enterprise support, professional services, or learning more about the +additional features of the enterprise edition, please contact us at +[info@thinkparq.com](mailto:info@thinkparq.com). + +# Support Lifecycle and Policies + +## BeeGFS Versioning +Starting with BeeGFS 8, we will more strictly adhere to [Semantic Versioning](https://semver.org/) +to ensure a predictable and consistent support lifecycle. BeeGFS releases follow a MAJOR.MINOR.PATCH +versioning scheme, where: + +* Major releases (e.g., 8.x) introduce breaking changes and new features. +* Minor releases (e.g., 8.1, 8.2) add functionality in a backward-compatible manner. + * Note: Enabling new functionality typically requires all components to be on the same version + otherwise older components may see errors about unknown functionality. +* Patch releases (e.g., 8.0.1, 8.1.2) contain only backward-compatible bug fixes and security + updates. + +## Release Lifecycle + +* Latest Major Release (e.g., BeeGFS 8.x) + * Actively maintained with bug fixes, security patches, and feature enhancements. + * Receives support for new operating systems, kernels, and DOCA/OFED versions. + * New deployments are recommended to use this version to ensure continued support without + requiring a major upgrade. +* Previous Major Releases (e.g., BeeGFS 7.x) + * Receive critical security and stability fixes until the release reaches EOL. + * Receive best effort support for new operating systems, kernels, and DOCA/OFED versions until the + release reaches EOL. + * New deployments are strongly discouraged from using these versions. +* Minor and Patch Releases (e.g., 8.1.0, 8.0.1) + * To receive bug fixes and security patches, users must upgrade to the latest available patch or + minor version within their major release, as fixes will not be backported to older minor + versions. + +## End-of-Life (EOL) + +* A BeeGFS release reaches EOL when it no longer receives regular updates, including: + * Bug fixes + * Security patches + * Support for new OS/kernel/DOCA/OFED versions. +* New deployments should not be performed using a release that has reached EOL. +* Users with active support contracts for systems originally deployed on that version may still + receive best-effort patches if they cannot upgrade to the latest major version due to + incompatibilities. + * Patches made after a release reaches EOL will typically be private unless they address a + security vulnerability that affects all users. +* EOL timing: When a new major BeeGFS is made available, adoption trends, stability, and customer + feedback are considered before announcing the EOL date for the previous major release. Generally + the EOL date will be no less than 12 months from the release date of the new major version. + +## End-of-Support (EOS) + +* When a BeeGFS release reaches EOS it is considered obsolete and no further updates will be + made—even for customers with a support contract. +* EOS timing: Depending on customer demand and extended support contracts EOS will typically follow + EOL by 12-24 months. +* ThinkParQ support will assist all customers holding a valid support contract regardless if the + BeeGFS version has reached EOS. + +If support identifies a software issue in an EOS release, the recommended action will be to upgrade +to a supported version where the issue is (or can be) resolved. Support will assist as needed to +define a recommended upgrade path. + +## Example Scenarios + +* A user with/without support running BeeGFS 8.0.0 finds a bug before BeeGFS 7 reaches EOL: + * If critical, it may be fixed in a patch release (8.0.1). + * If less severe or found/fixed close to the next scheduled release, it may be fixed in a minor + release (8.1.0). + * If this bug also affects BeeGFS 7, it would be backported to the latest 7.x patch release (e.g., + 7.4.x). +* A user with support running BeeGFS 7 after it reaches EOL finds a bug impacting performance or + stability: + * If the bug affects BeeGFS 8 and has not yet been fixed, a fix will be made in the latest 8.x + release and the user advised to upgrade if possible. + * If the user is unable to upgrade to BeeGFS 8 due to incompatibilities in their OS/kernel/etc., + the feasibility of patching BeeGFS 7 will be evaluated and decided on a case-by-case basis + working in conjunction with the user to identify the best possible solution. diff --git a/beegfs.spec b/beegfs.spec new file mode 100644 index 0000000..6affe97 --- /dev/null +++ b/beegfs.spec @@ -0,0 +1,631 @@ +%define VER %(echo '%{BEEGFS_VERSION}' | cut -d - -f 1) + +%define BEEGFS_MAJOR_VERSION %(echo '%{BEEGFS_VERSION}' | cut -d . -f 1) +%define CLIENT_DIR /opt/beegfs/src/client/client_module_%{BEEGFS_MAJOR_VERSION} +%define CLIENT_COMPAT_DIR /opt/beegfs/src/client/client_compat_module_%{BEEGFS_MAJOR_VERSION} + +%define is_sles %(test -f /etc/os-release && grep -q "openSUSE" /etc/os-release || test -f /etc/SUSEConnect && echo 1 || echo 0) + +%if %is_sles +%define distver %(release="`rpm -qf --queryformat='%%{VERSION}' /etc/os-release 2> /dev/null | tr . : | sed s/:.*$//g`" ; if test $? != 0 ; then release="" ; fi ; echo "$release") +%define RELEASE sles%{distver} + +%else +%if %{defined ?dist} +%define RELEASE %(tr -d . <<< %{?dist}) + +%else +%define RELEASE generic + +%endif +%endif + +%define post_package() if [ "$1" = 1 ] \ +then \ + output=$(systemctl is-system-running 2> /dev/null) \ + if [ "$?" == 127 ] \ + then \ + chkconfig %1 on \ + elif [ "$?" == 0 ] || ( [ "$output" != "offline" ] && [ "$output" != "unknown" ] ) \ + then \ + systemctl enable %1.service \ + else \ + chkconfig %1 on \ + fi \ +fi + +%define preun_package() if [ "$1" = 0 ] \ +then \ + output=$(systemctl is-system-running 2> /dev/null) \ + if [ "$?" == 127 ] \ + then \ + chkconfig %1 off \ + elif [ "$?" == 0 ] || ( [ "$output" != "offline" ] && [ "$output" != "unknown" ] ) \ + then \ + systemctl disable %1.service \ + else \ + chkconfig %1 off \ + fi \ +fi + + +Name: beegfs +Summary: BeeGFS parallel file system +License: BeeGFS EULA +Version: %{VER} +Release: %{RELEASE} +URL: http://www.beegfs.io +Source: beegfs-%{BEEGFS_VERSION}.tar +Vendor: ThinkParQ GmbH +BuildRoot: %{_tmppath}/beegfs-root +Epoch: %{EPOCH} + +%description + +Distribution of the BeeGFS parallel filesystem. + +%clean +rm -rf %{buildroot} + +%prep +%setup -c + +%define make_j %{?MAKE_CONCURRENCY:-j %{MAKE_CONCURRENCY}} + +%build + +export BEEGFS_VERSION=%{BEEGFS_VERSION} +export WITHOUT_COMM_DEBUG=1 +export BEEGFS_NVFS=1 + +make %make_j daemons utils + +%install + +export BEEGFS_VERSION=%{BEEGFS_VERSION} +export WITHOUT_COMM_DEBUG=1 + + +# makefiles need some adjustments still +mkdir -p \ + ${RPM_BUILD_ROOT}/opt/beegfs/sbin \ + ${RPM_BUILD_ROOT}/opt/beegfs/lib \ + ${RPM_BUILD_ROOT}/usr/bin \ + ${RPM_BUILD_ROOT}/usr/include \ + ${RPM_BUILD_ROOT}/sbin \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client-devel/examples/ \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils-devel/examples/beegfs-event-listener/build \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils-devel/examples/beegfs-event-listener/source \ + ${RPM_BUILD_ROOT}/etc/bash_completion.d + + +########## +########## libbeegfs-ib files +########## + +install -D common/build/libbeegfs_ib.so \ + ${RPM_BUILD_ROOT}/opt/beegfs/lib/libbeegfs_ib.so + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/libbeegfs-ib/copyright + +########## +########## daemons, utils +########## + +make DESTDIR=${RPM_BUILD_ROOT} daemons-install utils-install + +########## +########## common directories for extra files +########## +mkdir -p \ + ${RPM_BUILD_ROOT}/etc/beegfs \ + ${RPM_BUILD_ROOT}/etc/init.d \ + ${RPM_BUILD_ROOT}/opt/beegfs/scripts/grafana + +########## +########## meta extra files +########## + +cp -a meta/build/dist/etc/*.conf ${RPM_BUILD_ROOT}/etc/beegfs + +#install systemd unit description +install -D -m644 meta/build/dist/usr/lib/systemd/system/beegfs-meta.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-meta.service +install -D -m644 meta/build/dist/usr/lib/systemd/system/beegfs-meta@.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-meta@.service + +install -D meta/build/dist/sbin/beegfs-setup-meta \ + ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beegfs-setup-meta + +install -D meta/build/dist/etc/default/beegfs-meta ${RPM_BUILD_ROOT}/etc/default/beegfs-meta + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-meta/copyright + +########## +########## storage extra files +########## + +cp -a storage/build/dist/etc/*.conf ${RPM_BUILD_ROOT}/etc/beegfs/ + +#install systemd unit description +install -D -m644 storage/build/dist/usr/lib/systemd/system/beegfs-storage.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-storage.service +install -D -m644 storage/build/dist/usr/lib/systemd/system/beegfs-storage@.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-storage@.service + +install -D storage/build/dist/sbin/beegfs-setup-storage \ + ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beegfs-setup-storage + +install -D storage/build/dist/etc/default/beegfs-storage ${RPM_BUILD_ROOT}/etc/default/beegfs-storage + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-storage/copyright + +########## +########## mon extra files +########## + +install -D -m644 mon/build/dist/etc/beegfs-mon.conf ${RPM_BUILD_ROOT}/etc/beegfs +install -D -m600 mon/build/dist/etc/beegfs-mon.auth ${RPM_BUILD_ROOT}/etc/beegfs + +#install systemd unit description +install -D -m644 mon/build/dist/usr/lib/systemd/system/beegfs-mon.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-mon.service +install -D -m644 mon/build/dist/usr/lib/systemd/system/beegfs-mon@.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-mon@.service + +install -D mon/build/dist/etc/default/beegfs-mon ${RPM_BUILD_ROOT}/etc/default/beegfs-mon +cp -a mon/scripts/grafana/* ${RPM_BUILD_ROOT}/opt/beegfs/scripts/grafana/ + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-mon/copyright + +########## +########## mon-grafana +########## + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-mon-grafana/copyright + +########## +########## utils +########## + +cp -a utils/scripts/fsck.beegfs ${RPM_BUILD_ROOT}/sbin/ + +ln -s /opt/beegfs/sbin/beegfs-fsck ${RPM_BUILD_ROOT}/usr/bin/beegfs-fsck + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils/copyright + +########## +########## utils-devel +########## + +cp -a event_listener/include/* ${RPM_BUILD_ROOT}/usr/include/ +cp -a event_listener/build/Makefile \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils-devel/examples/beegfs-event-listener/build/ +cp -a event_listener/source/beegfs-event-listener.cpp \ + event_listener/source/beegfs-file-event-log.cpp \ + event_listener/source/seqpacket-reader-new-protocol.cpp \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils-devel/examples/beegfs-event-listener/source/ + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-utils-devel/copyright + +########## +########## client +########## + +make -C client_module/build %make_j \ + RELEASE_PATH=${RPM_BUILD_ROOT}/opt/beegfs/src/client KDIR="%{KDIR}" V=1 \ + prepare_release +cp client_module/build/dist/etc/*.conf ${RPM_BUILD_ROOT}/etc/beegfs/ +cp client_module/build/dist/etc/beegfs-client-build.mk ${RPM_BUILD_ROOT}/etc/beegfs/beegfs-client-build.mk + + +# compat files +cp -a ${RPM_BUILD_ROOT}/%{CLIENT_DIR} ${RPM_BUILD_ROOT}/%{CLIENT_COMPAT_DIR} + +echo beegfs-%{BEEGFS_MAJOR_VERSION} | tr -d . > ${RPM_BUILD_ROOT}/%{CLIENT_COMPAT_DIR}/build/beegfs.fstype + +# we use the redhat script for all rpm distros, as we now provide our own +# daemon() and killproc() function library (derived from redhat) +install -D client_module/build/dist/sbin/beegfs-client.init ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beegfs-client +%if !%is_sles +ln -s /opt/beegfs/sbin/beegfs-client ${RPM_BUILD_ROOT}/etc/init.d/beegfs-client +%endif +#install systemd unit description +install -D -m644 client_module/build/dist/usr/lib/systemd/system/beegfs-client.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-client.service + +install -D -m644 client_module/build/dist/usr/lib/systemd/system/beegfs-client@.service \ + ${RPM_BUILD_ROOT}/usr/lib/systemd/system/beegfs-client@.service + + +install -D client_module/build/dist/etc/default/beegfs-client ${RPM_BUILD_ROOT}/etc/default/beegfs-client + +install -D client_module/scripts/etc/beegfs/lib/init-multi-mode.beegfs-client \ + ${RPM_BUILD_ROOT}/etc/beegfs/lib/init-multi-mode.beegfs-client + +install -D client_module/build/dist/sbin/beegfs-setup-client \ + ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beegfs-setup-client + +install -D client_module/build/dist/sbin/mount.beegfs \ + ${RPM_BUILD_ROOT}/sbin/mount.beegfs + +install -D client_module/build/dist/etc/beegfs-client-mount-hook.example \ + ${RPM_BUILD_ROOT}/etc/beegfs/beegfs-client-mount-hook.example + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client/copyright + +########## +########## client-dkms +########## + +cp client_module/build/dist/etc/*.conf ${RPM_BUILD_ROOT}/etc/beegfs/ + +mkdir -p ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER} + +cp -r client_module/build ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER} +cp -r client_module/source ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER} +cp -r client_module/include ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER} + +rm -Rf ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER}/build/dist + + +install -D client_module/build/dist/sbin/beegfs-setup-client \ + ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beegfs-setup-client +install -D client_module/build/dist/sbin/mount.beegfs \ + ${RPM_BUILD_ROOT}/sbin/mount.beegfs + +sed -e 's/__VERSION__/%{VER}/g' -e 's/__NAME__/beegfs/g' -e 's/__MODNAME__/beegfs/g' \ + < client_module/dkms.conf.in \ + > ${RPM_BUILD_ROOT}/usr/src/beegfs-%{VER}/dkms.conf + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client-dkms/copyright + +########## +########## client-compat +########## + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client-compat/copyright + +########## +########## client-devel +########## + +cp -a client_devel/include/beegfs \ + ${RPM_BUILD_ROOT}/usr/include/ +cp -a client_module/include/uapi/* \ + ${RPM_BUILD_ROOT}/usr/include/beegfs/ +sed -i '~s~uapi/beegfs_client~beegfs/beegfs_client~g' \ + ${RPM_BUILD_ROOT}/usr/include/beegfs/*.h +cp -a client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/* \ + ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client-devel/examples/ + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beegfs-client-devel/copyright + +########## +########## beeond +########## + +install -D beeond/source/beeond ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beeond +install -D beeond/source/beeond-cp ${RPM_BUILD_ROOT}/opt/beegfs/sbin/beeond-cp +cp beeond/scripts/lib/* ${RPM_BUILD_ROOT}/opt/beegfs/lib/ +ln -s /opt/beegfs/sbin/beeond ${RPM_BUILD_ROOT}/usr/bin/beeond +ln -s /opt/beegfs/sbin/beeond-cp ${RPM_BUILD_ROOT}/usr/bin/beeond-cp + +install -D debian/copyright ${RPM_BUILD_ROOT}/usr/share/doc/beeond/copyright + +%package -n libbeegfs-ib + +Summary: BeeGFS InfiniBand support +Group: Software/Other +Buildrequires: librdmacm-devel, libibverbs-devel +BuildRequires: libnl3-devel +Provides: libbeegfs-ib = %{VER} + +%description -n libbeegfs-ib +This package contains support libraries for InfiniBand. + +%files -n libbeegfs-ib +%license /usr/share/doc/libbeegfs-ib/copyright +/opt/beegfs/lib/libbeegfs_ib.so + + + +%package meta + +Summary: BeeGFS meta server daemon +Group: Software/Other +BuildRequires: libnl3-devel +Provides: beegfs-meta = %{VER} + +%description meta +This package contains the BeeGFS meta server binaries. + +%post meta +%post_package beegfs-meta + +%preun meta +%preun_package beegfs-meta + +%files meta +%defattr(-,root,root) +%license /usr/share/doc/beegfs-meta/copyright +%config(noreplace) /etc/beegfs/beegfs-meta.conf +%config(noreplace) /etc/default/beegfs-meta +/opt/beegfs/sbin/beegfs-meta +/opt/beegfs/sbin/beegfs-setup-meta +/usr/lib/systemd/system/beegfs-meta.service +/usr/lib/systemd/system/beegfs-meta@.service + + +%package storage + +Summary: BeeGFS storage server daemon +Group: Software/Other +BuildRequires: libnl3-devel +Provides: beegfs-storage = %{VER} + +%description storage +This package contains the BeeGFS storage server binaries. + +%post storage +%post_package beegfs-storage + +%preun storage +%preun_package beegfs-storage + +%files storage +%defattr(-,root,root) +%license /usr/share/doc/beegfs-storage/copyright +%config(noreplace) /etc/beegfs/beegfs-storage.conf +%config(noreplace) /etc/default/beegfs-storage +/opt/beegfs/sbin/beegfs-storage +/opt/beegfs/sbin/beegfs-setup-storage +/usr/lib/systemd/system/beegfs-storage.service +/usr/lib/systemd/system/beegfs-storage@.service + + + +%package mon + +Summary: BeeGFS mon server daemon +Group: Software/Other +BuildRequires: libnl3-devel +Provides: beegfs-mon = %{VER} + +%description mon +This package contains the BeeGFS mon server binaries. + +%post mon +%post_package beegfs-mon + +%preun mon +%preun_package beegfs-mon + +%files mon +%defattr(-,root,root) +%license /usr/share/doc/beegfs-mon/copyright +%config(noreplace) /etc/beegfs/beegfs-mon.conf +%config(noreplace) /etc/beegfs/beegfs-mon.auth +%config(noreplace) /etc/default/beegfs-mon +/opt/beegfs/sbin/beegfs-mon +/usr/lib/systemd/system/beegfs-mon.service +/usr/lib/systemd/system/beegfs-mon@.service + + + +%package mon-grafana + +Summary: BeeGFS mon dashboards for Grafana +Group: Software/Other +BuildArch: noarch +Provides: beegfs-mon-grafana = %{VER} + +%description mon-grafana +This package contains the BeeGFS mon dashboards to display monitoring data in Grafana. + +The default dashboard setup requires both Grafana, and InfluxDB. + +%files mon-grafana +%license /usr/share/doc/beegfs-mon-grafana/copyright +%defattr(-,root,root) +/opt/beegfs/scripts/grafana/ + + + +%package utils + +Summary: BeeGFS utilities +Group: Software/Other +BuildRequires: libnl3-devel +Provides: beegfs-utils = %{VER} + +%description utils +This package contains BeeGFS utilities. + +%files utils +%defattr(-,root,root) +%license /usr/share/doc/beegfs-utils/copyright +%attr(0755, root, root) /opt/beegfs/sbin/beegfs-fsck +/usr/bin/beegfs-fsck +/sbin/fsck.beegfs +/opt/beegfs/sbin/beegfs-event-listener + + +%package utils-devel + +Summary: BeeGFS utils devel files +Group: Software/Other +BuildArch: noarch +Provides: beegfs-utils-devel = %{VER} + +%description utils-devel +This package contains BeeGFS utils development files and examples. + +%files utils-devel +%defattr(-,root,root) +%license /usr/share/doc/beegfs-utils-devel/copyright +/usr/include/beegfs/beegfs_file_event_log.hpp +/usr/include/beegfs/seqpacket-reader-new-protocol.hpp +/usr/share/doc/beegfs-utils-devel/examples/beegfs-event-listener/* + + + +%package client + +Summary: BeeGFS client kernel module +License: GPL v2 +Group: Software/Other +BuildArch: noarch +%if %is_sles +Requires: make, gcc, kernel-default-devel, elfutils +%else +Requires: make, gcc +Recommends: kernel-devel, elfutils-libelf-devel +%endif +Conflicts: beegfs-client-dkms +Provides: beegfs-client = %{VER} + +%description client +This package contains scripts, config and source files to build and +start beegfs-client. + +%post client +%post_package beegfs-client + +# make the script to run autobuild +mkdir -p /var/lib/beegfs/client +touch /var/lib/beegfs/client/force-auto-build + +%preun client +%preun_package beegfs-client + +%files client +%defattr(-,root,root) +%license /usr/share/doc/beegfs-client/copyright +%config(noreplace) /etc/beegfs/beegfs-client-autobuild.conf +%config(noreplace) /etc/beegfs/beegfs-client-mount-hook.example +%config(noreplace) /etc/beegfs/beegfs-client.conf +%config(noreplace) /etc/beegfs/beegfs-mounts.conf +%dir /etc/beegfs/lib/ +%config(noreplace) /etc/beegfs/lib/init-multi-mode.beegfs-client +%config(noreplace) /etc/default/beegfs-client +/opt/beegfs/sbin/beegfs-client +%if !%is_sles +/etc/init.d/beegfs-client +%endif +/opt/beegfs/sbin/beegfs-setup-client +/sbin/mount.beegfs +/usr/lib/systemd/system/beegfs-client.service +/usr/lib/systemd/system/beegfs-client@.service +%{CLIENT_DIR} + +%postun client +rm -rf /lib/modules/*/updates/fs/beegfs_autobuild + +%package client-dkms + +Summary: BeeGFS client kernel module (DKMS version) +License: GPL v2 +Group: Software/Other +BuildArch: noarch +%if %is_sles +Requires: make, dkms, kernel-default-devel, elfutils +%else +Requires: make, dkms +Recommends: kernel-devel, elfutils-libelf-devel +%endif +Conflicts: beegfs-client +Provides: beegfs-client = %{VER} + +%description client-dkms +This package contains scripts, config and source files to build and +start beegfs-client. It uses DKMS to build the kernel module. + +%post client-dkms +dkms install beegfs/%{VER} + +%preun client-dkms +dkms remove beegfs/%{VER} --all + +%files client-dkms +%defattr(-,root,root) +%license /usr/share/doc/beegfs-client-dkms/copyright +%config(noreplace) /etc/beegfs/beegfs-client.conf +%config(noreplace) /etc/beegfs/beegfs-client-build.mk +/sbin/mount.beegfs +/usr/src/beegfs-%{VER} + + + +%package client-compat + +Summary: BeeGFS client compat module, allows to run two different client versions. +License: GPL v2 +Group: Software/Other +%if %is_sles +Requires: make, gcc, kernel-default-devel, elfutils +%else +Requires: make, gcc +Recommends: kernel-devel, elfutils-libelf-devel +%endif +BuildArch: noarch +Provides: beegfs-client-compat = %{VER} + +%description client-compat +This package allows to build and to run a compatbility beegfs-client kernel module +on a system that has a newer beegfs-client version installed. + +%files client-compat +%license /usr/share/doc/beegfs-client-compat/copyright +%defattr(-,root,root) +%{CLIENT_COMPAT_DIR} + + + +%package client-devel + +Summary: BeeGFS client devel files +Group: Software/Other +BuildArch: noarch +Provides: beegfs-client-devel = %{VER} + +%description client-devel +This package contains BeeGFS client development files. + +%files client-devel +%defattr(-,root,root) +%license /usr/share/doc/beegfs-client-devel/copyright +%dir /usr/include/beegfs +/usr/include/beegfs/beegfs.h +/usr/include/beegfs/beegfs_client.h +/usr/include/beegfs/beegfs_ioctl.h +/usr/include/beegfs/beegfs_ioctl_functions.h +/usr/share/doc/beegfs-client-devel/examples/createFileWithStripePattern.cpp +/usr/share/doc/beegfs-client-devel/examples/getStripePatternOfFile.cpp + + + +%package -n beeond + +Summary: BeeOND +Group: Software/Other +# The dependecies used to be on FULL_VERSION=%{EPOCH}:%{VER}-%{RELEASE}, which +# doesn't work with BeeGFS 8, because the distribution independent packages +# can not append an OS RELEASE. We also don't need to depend on the EPOCH, the +# semantic version should be enough, so we just depend on %{VER} now. +Requires: beegfs-tools = %{VER}, beegfs-mgmtd = %{VER}, beegfs-meta = %{VER}, beegfs-storage = %{VER}, beegfs-client = %{VER}, libbeegfs-license = %{VER}, psmisc +BuildArch: noarch +Provides: beeond = %{VER} + +%description -n beeond +This package contains BeeOND. + +%files -n beeond +%defattr(-,root,root) +%license /usr/share/doc/beeond/copyright +/opt/beegfs/sbin/beeond +/usr/bin/beeond +/opt/beegfs/sbin/beeond-cp +/usr/bin/beeond-cp +/opt/beegfs/lib/beeond-lib +/opt/beegfs/lib/beegfs-ondemand-stoplocal diff --git a/beeond/CMakeLists.txt b/beeond/CMakeLists.txt new file mode 100644 index 0000000..59b5a58 --- /dev/null +++ b/beeond/CMakeLists.txt @@ -0,0 +1,11 @@ +install( + PROGRAMS "beegfs-ondemand-stoplocal" "beeond-lib" + DESTINATION "usr/share/beeond" + COMPONENT "beeond" +) + +install( + PROGRAMS "beeond" "beeond-cp" + DESTINATION "usr/bin" + COMPONENT "beeond" +) diff --git a/beeond/scripts/lib/beegfs-ondemand-stoplocal b/beeond/scripts/lib/beegfs-ondemand-stoplocal new file mode 100644 index 0000000..37b8aef --- /dev/null +++ b/beeond/scripts/lib/beegfs-ondemand-stoplocal @@ -0,0 +1,400 @@ +#!/bin/bash + +# beegfs-ondemand-stoplocal +# This file contains helper functions to stop BeeOND services locally on one node. +# This is meant to be sourced from another script (i.e. beeond) + + +# Checks the return code of the last command that has been executed. If the code is !=0, indicating +# an error, it prints a message and sets an error flag. +# Parameters: +# * The return code of the last command +# * A string containing a hint on what was being done that could have caused the error. It is +# used for the error message. +# Modifies: +# ERROR: Is set to "true" when an error was encountered. +sl_checkerror() +{ + if [ "${1}" != 0 ] + then + echo "ERROR: There was a problem ${2} on host $(hostname)" + ERROR="true" + fi +} + +# Prints an info message if the QUIET variable is not set. +# Parameter: +# A string (the message). It is prefixed with INFO when printed. +# Checks: +# QUIET: If "true", nothing is printed. +sl_print_info() +{ + local MESSAGE=${1} + if [ "${QUIET}" != "true" ] + then + echo "INFO: ${MESSAGE}" + fi +} + +# unmounts tmpfs mounts listed in the status file +sl_unmount_tmpfs() +{ + local SERVICE MOUNTPOINT _ + IFS=, + while read -r _ SERVICE MOUNTPOINT _ _ + do + if [ "${SERVICE}" != "tmpfs" ] + then + continue + fi + + sl_print_info "Unmounting tmpfs at ${MOUNTPOINT}" + + if [ "${CLEANUP}" != "true" ] + then + fuser -k "${MOUNTPOINT}" + umount -l "${MOUNTPOINT}" + + sl_checkerror $? "unmounting tmpfs" + else + fuser -k "${MOUNTPOINT}" 2>/dev/null + umount -l "${MOUNTPOINT}" 2>/dev/null + true + fi + done < "${STATUSFILE}" + unset IFS +} + +# Unmounts all local mounts listed in the status file +sl_unmount_local_mounts() +{ + local SERVICE MOUNTPOINT _ + IFS=, + while read -r _ SERVICE MOUNTPOINT _ _ + do + if [ "${SERVICE}" != "${CLIENTSERVICE}" ] + then + continue + fi + + sl_print_info "Unmounting ${MOUNTPOINT}" + if [ "${CLEANUP}" != "true" ] + then + fuser -k "${MOUNTPOINT}" # no "sl_checkerror" after this, becuase fuser also returns + # non-zero when there are no processes accessing the file system + umount -l "${MOUNTPOINT}" + sl_checkerror $? "unmounting the ondemand file system" + else + fuser -k "${MOUNTPOINT}" 2>/dev/null + umount -l "${MOUNTPOINT}" 2>/dev/null + true # reset error code before next invocation of sl_checkerror + fi + done < "${STATUSFILE}" + unset IFS + + # try to remove the client module - this is allowed to fail, because we might have a "normal" + # beegfs mount somewhere in the system. + rmmod beegfs 2>/dev/null || true +} + +# sends a SIGTERM to a process, then waits until the process is stopped or appriximately 10 seconds +# have passed. +# Parameter: +# The PID of the proces +# Returns: +# 0 if process was stopped within 10 seconds, 1 if it wasn't, 255 if initial kill returned an +# error. +sl_kill_check() +{ + local PID=$1 + + if ! kill "$PID" + then + return 255 + fi + + for ((i=0; i<100; i++)) + do + if kill -0 "$PID" 2>/dev/null + then + sleep 0.1 + else + return 0 + fi + done + + return 1 +} + +# stops all services listed in the status file except for clients +sl_stop_services() +{ + local SERVICE DATAPATH PIDFILE _ + IFS=, + while read -r _ SERVICE DATAPATH _ PIDFILE + do + if [ "${PIDFILE}" != "-" ] # pidfile is "-" for beegfs-client and tmpfs, because it is not + # a process + then + if [ -e "${PIDFILE}" ] + then + PID=$(cat "${PIDFILE}") + sl_kill_check "${PID}" + RES=$? + if [ $RES -eq 1 ] + then + echo "ERROR: ${SERVICE} did not stop within 10 seconds (PID ${PID})." + ERROR="true" + elif [ $RES -eq 255 ] + then + echo "ERROR: ${SERVICE} does not seem to be running any more (PID ${PID})." + fi + else + if [ "${CLEANUP}" != "true" ] + then + echo "ERROR: PID file ${PIDFILE} does not exist on host $(hostname)" + ERROR="true" + fi + fi + + # delete data... + if [ "${DELETE_DATA}" = "true" ] + then + if [ "${DATAPATH}" != "-" ] + then + sl_print_info "Deleting stored data; Data path: ${DATAPATH}" + rm -rf "${DATAPATH}" + sl_checkerror $? "deleting ${DATAPATH}" + fi + fi + + # delete preferredMds and preferredTarget files + rm -f "${PREFERRED_MDS_FILE}" + sl_checkerror $? "deleting ${PREFERRED_MDS_FILE}" + rm -f "${PREFERRED_TARGET_FILE}" + sl_checkerror $? "deleting ${PREFERRED_TARGET_FILE}" + fi + done < "${STATUSFILE}" + unset IFS + + # unmount tempfs if it was used + sl_unmount_tmpfs +} + +# deletes the logfiles listed in the status file if ERROR is set to false +# If the log directory is empty afterwards, it is also deleted +sl_delete_logfiles() +{ + local LOGFILE # declare it here, because the last LOGFILE path is needed to delete the directory + # after the loop + + # delete log files + if [ "${ERROR}" != "true" ] # if we haven't encountered an error yet. + then + # delete log files + local SERVICE LOGFILE _ + IFS=, + while read -r _ SERVICE _ LOGFILE _ + do + if [ "${ONLY_UNMOUNT}" = "true" ] && [ "${SERVICE}" != "${CLIENTSERVICE}" ] + then continue; fi + if [ "${ONLY_STOP_SERVER}" = "true" ] && [ "${SERVICE}" = "${CLIENTSERVICE}" ] + then continue; fi + if [ "${LOGFILE}" != "-" ] + then + sl_print_info "Deleting log file ${LOGFILE}" + rm -f "${LOGFILE}" 2>/dev/null # beegfs-client does not (always) generate a logfile. + # in this case rm gives an error message, but we don't + # want to see it. - for the same reason no sl_checkerror + # here + fi + done < "${STATUSFILE}" + unset IFS + + # delete log directory if empty + local LOG_DIR + LOG_DIR=$(dirname "${LOGFILE}") + if [ "${LOG_DIR}" != "." ] && [ ! "$(ls -A "${LOG_DIR}")" ] + then + echo "Deleting log directory ${LOG_DIR}" + rmdir "${LOG_DIR}" + sl_checkerror $? "deleting ${LOG_DIR}" + fi + else + sl_print_info "Not deleting log files because of a previous error." + fi +} + +# The "main" stoplocal function. From here, the functions to unmount the file system and stop the +# services are called. If there was no error, sl_delete_logfiles is called, and the status file is +# also removed. +# Checks the following variables: +# STATUSFILE The location of the status file +# ONLY_STOP_SERVER If "true", the umount_local_mounts step is skipped, and status file is not +# removed. +# ONLY_UNMOUNT If "true", the stop_services step is skipped, and status file is not +# removed. +# Modifies: +# ERROR Is set to "true" (and an error message is printed to %2) if an error is +# encountered in any step. +stoplocal() +{ + sl_print_info "Using status file ${STATUSFILE}" + + # do the actual shutdown process + + # unmount the file system (skip this step if we only want to stop the server) + if [ "${ONLY_STOP_SERVER}" != "true" ] + then + sl_unmount_local_mounts + fi + + # stop the services (skip this step if we only got asked to unmount the file system) + if [ "${ONLY_UNMOUNT}" != "true" ] + then + sl_stop_services + fi + + # delete the logfiles + if [ "${ERROR}" != "true" ] && [ "${DELETE_LOGS}" = "true" ] + then + sl_delete_logfiles + fi + + + # delete the status file (only if a full shutdown was requested) + if [ "${ONLY_UNMOUNT}" != "true" ] && [ "${ONLY_STOP_SERVER}" != "true" ] + then + rm -f "${STATUSFILE}" + sl_checkerror $? "deleting the status file" + fi +} + +# the user interface / main entry point to stoplocal +# Options: +# -i FILENAME => Status information filename +# (DEFAULT: ${DEFAULT_STATUSFILE}) +# -d => Delete BeeGFS data on disks +# -L => Delete log files after successful shutdown +# -q => Suppress \"INFO\" messages, only print \"ERROR\"s +# -c => "Cleanup": Remove remaining processes and directories of a +# potentially unsuccessful shutdown of an earlier beeond +# instance. This switch silences the error message when a status +# information file is not found or an unmount command fails; +# instead, a message is printed (if \"INFO\" messages are not +# suppressed) when a status file DOES exist, because this means +# there actually was an instance before that is now being +# cleaned up. +# -u => ONLY unmount the file systems(*) +# -s => ONLY stop non-client services(*) +# +# (*) Options -u and -s are mutually exclusive +# If -u or -s are given, the status file is not deleted. +do_stoplocal() +{ + local DEFAULT_STATUSFILE=/tmp/beeond.tmp + local CLIENTSERVICE=beegfs-client + local DELETE_DATA="false" + local DELETE_LOGS="false" + local ONLY_UNMOUNT="false" + local ONLY_STOP_SERVER="false" + local PREFERRED_MDS_FILE=/tmp/preferredMds.fod + local PREFERRED_TARGET_FILE=/tmp/preferredTarget.fod + local QUIET="false" + + local ERROR="false" + local STATUSFILE="${DEFAULT_STATUSFILE}" + + local OPTIND=1 + local OPTARG="" + while getopts ":i:dLusqc" opt "$@" + do + case $opt in + i) + STATUSFILE=${OPTARG} + ;; + d) + DELETE_DATA="true" + ;; + L) + DELETE_LOGS="true" + ;; + u) + if [ "${ONLY_STOP_SERVER}" = "true" ] + then + echo "ERROR: Options -s and -${OPTARG} are mutually exclusive" >&2 + if declare -f -F print_usage_and_exit >/dev/null + then print_usage_and_exit; fi + return 1 + fi + ONLY_UNMOUNT="true" + ;; + s) + if [ "${ONLY_UNMOUNT}" = "true" ] + then + echo "ERROR: Options -u and -${OPTARG} are mutually exclusive" >&2 + if declare -f -F print_usage_and_exit >/dev/null + then print_usage_and_exit; fi + return 1 + fi + ONLY_STOP_SERVER="true" + ;; + q) + QUIET="true" + ;; + c) + CLEANUP="true" + ;; + \?) + echo "ERROR: invalid option -${OPTARG}" >&2 + if declare -f -F print_usage_and_exit >/dev/null + then print_usage_and_exit; fi + return 1 + ;; + :) + echo "ERROR: Option -${OPTARG} requires an argument" >&2 + if declare -f -F print_usage_and_exit >/dev/null + then print_usage_and_exit; fi + return 1 + ;; + esac + done + + # if statusfile can't be found, print a message and exit. + if [ ! -f ${STATUSFILE} ] + then + # only print message when we're not doing a cleanup run. + if [ "${CLEANUP}" != "true" ] + then + echo "ERROR: Status file ${STATUSFILE} not found." >&2 + + # If the user has specified a status file, just give a brief error message and exit. + # If the user has not specified a status file, give the full usage info - maybe the user + # didn't know how to specify a status file. + if [ "${STATUSFILE}" = "${DEFAULT_STATUSFILE}" ] + then + if declare -f -F "print_usage_and_exit" >/dev/null + then print_usage_and_exit; fi + fi + + return 1 + else + return 0 # return 0 if we're doing a cleanup so that pdsh doesn't complain + fi + fi + + # if we're doing a cleanup run, inform the user that a status file was found. + if [ "${CLEANUP}" = "true" ] + then + sl_print_info "Status file found." + fi + + stoplocal + + if [ "${ERROR}" = "true" ] + then + return 1 + else + return 0 + fi +} diff --git a/beeond/scripts/lib/beeond-lib b/beeond/scripts/lib/beeond-lib new file mode 100644 index 0000000..b894530 --- /dev/null +++ b/beeond/scripts/lib/beeond-lib @@ -0,0 +1,392 @@ +#!/bin/bash + +# This file contains some functions used across all of the BeeOND scripts. + +BEEOND_FILENAME_PREFIX=".beeond_" +BEEOND_COPY_FILE_LIST="${BEEOND_FILENAME_PREFIX}files_copy" +BEEOND_COPY_SCAN_LIST="${BEEOND_FILENAME_PREFIX}scan_list" # List of dirs that have to be scanned. +BEEOND_COPY_DIR_LIST="${BEEOND_FILENAME_PREFIX}dirs_copy" +BEEOND_START_FILE_LIST="${BEEOND_FILENAME_PREFIX}files_start" +BEEOND_END_FILE_LIST="${BEEOND_FILENAME_PREFIX}files_end" +BEEOND_END_UPDATED_FILE_LIST="${BEEOND_FILENAME_PREFIX}files_end_updated" +BEEOND_START_DIR_LIST="${BEEOND_FILENAME_PREFIX}dirs_start" +BEEOND_END_DIR_LIST="${BEEOND_FILENAME_PREFIX}dirs_end" +BEEOND_END_UPDATED_DIR_LIST="${BEEOND_FILENAME_PREFIX}dirs_end_updated" +BEEOND_SESSION_FILE="${BEEOND_FILENAME_PREFIX}session" + +BEEOND_BATCH_SIZE=20 + +beeond_print_error() +{ + echo "ERROR: ${1}" >&2 + echo "" +} + +beeond_print_error_and_exit() +{ + beeond_print_error "${1}" >&2 + exit 1 +} + +beeond_print_info() +{ + local MESSAGE="${1}" + if [ "${QUIET}" != "true" ] + then + echo "INFO: ${MESSAGE}" + fi +} + +# Saves the session info file which contains the paths of the global store and the node file. +beeond_save_session_info() +{ + local NODEFILE="${1}" + local GLOBAL_PATH="${2}" + + if ! printf "NodeFile=%q\nGlobalPath=%q\n" "${NODEFILE}" "${GLOBAL_PATH}" \ + > "${LOCAL_PATH}/${BEEOND_SESSION_FILE}" + then + beeond_print_error_and_exit "Could not write to session file." + fi +} + +# Generate the list of files in the beeond folder. +beeond_generate_file_list() +{ + local LOCAL_PATH="${1}" + local LISTFILE="${2}" + local REFERENCE_FILE="${3}" + + + pushd "${LOCAL_PATH}" + if [ "${REFERENCE_FILE}" = "" ] + then # No reference file - just generate the full list (e.g. on startup). + + beeond_print_info "Generating file list ${LISTFILE}..." + + find . ! -path ./${BEEOND_FILENAME_PREFIX}\* \( -type f -or -type l \)\ + -exec bash -c 'printf "%q\n" "$0"' {} \; \ + | grep -v ^\\$ | sort > "${LISTFILE}" + + # The grep statement filters out file names with newlines in them. While they are technically + # legal they would cause problems later on due to the way the script run by parallel handles + # the arguments. + + else # Reference file given: Compare timestamps. + + beeond_print_info \ + "Generating file list ${LISTFILE}. Timestamp reference: ${REFERENCE_FILE}..." + + find . ! -path ./${BEEOND_FILENAME_PREFIX}\* \( -type f -or -type l \) \ + \( -cnewer "${REFERENCE_FILE}" -or -newer "${REFERENCE_FILE}" \) \ + -exec bash -c 'printf "%q\n" "$0"' {} \; \ + | grep -v ^\\$ | sort > "${LISTFILE}" + + fi + popd +} + +# Generate list of directories - this is necessary in case directories were created during the +# session and need to be created on the global store as well. +beeond_generate_directory_list() +{ + local LOCAL_PATH="${1}" + local LISTFILE="${2}" + local REFERENCE_FILE="${3}" + + pushd "${LOCAL_PATH}" + if [ "${REFERENCE_FILE}" = "" ] + then # No reference file - just generate the full list (e.g. on startup). + + beeond_print_info "Generating directory list ${LISTFILE}..." + + find . ! -path . -type d \ + -exec bash -c 'printf "%q\n" "$0"' {} \; \ + | grep -v ^\\$ | sort > "${LISTFILE}" + + else # Reference file given: Compare timestamps. + + beeond_print_info \ + "Generating directory list ${LISTFILE}. Timestamp reference: ${REFERENCE_FILE}..." + + find . ! -path . -type d \ + \( -cnewer "${REFERENCE_FILE}" -or -newer "${REFERENCE_FILE}" \) \ + -exec bash -c 'printf "%q\n" "$0"' {} \; \ + | grep -v ^\\$ | sort > "${LISTFILE}" + + fi + popd +} + +# Generate copy file list. If we do a parallel copy, we can't just list the paths to all the files +# to be copied, because we have to "flatten" the folder hierarchy (e.g. when the user says +# "copy dir/subfir/file_a anotherdir/file_b target_dir" we want to end up with +# target_dir/file_a and target_dir/file_b. To achieve this, we just save an explicit target path +# to each source file. We also have to keep a list of directories we encounter because we want to +# create them before we start copying. +beeond_generate_copy_lists() +{ + local TARGET="${1}" + local NODE_LIST="${2}" + local CONCURRENCY="${3}" + shift 3 + + # Note: We do relative path expansion here, (and not directly in the do_... functions) + # because this is the first time we iterate over the source list. + + # Expand target path. + if [ ! "${TARGET:0:1}" = "/" ] + then + TARGET="${PWD}/${TARGET}" + fi + + # Delete possibly left over file lists. + rm -f "${TARGET}/${BEEOND_COPY_SCAN_LIST}" \ + "${TARGET}/${BEEOND_COPY_DIR_LIST}" \ + "${TARGET}/${BEEOND_COPY_FILE_LIST}" + + # Generate lists: A list of files which can be used directly, and a list of directories which + # have to be scanned first. + for ENTRY in "$@" + do + # Expand path if it's relative. + if [ ! "${ENTRY:0:1}" = "/" ] + then + ENTRY="${PWD}/${ENTRY}" + fi + + beeond_print_info "Path to scan: ${ENTRY}" + if [ -d "${ENTRY}" ]; then + printf "%q\n" "${ENTRY}" >> "${TARGET}/${BEEOND_COPY_SCAN_LIST}" + elif [ -f "${ENTRY}" ]; then + printf "%q\n" >> "${TARGET}/${BEEOND_COPY_FILE_LIST}" + else + beeond_print_error_and_exit "File or directory does not exist: ${ENTRY}" + fi + done + + beeond_print_info "Scanning sources..." + + < "${TARGET}/${BEEOND_COPY_SCAN_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read DIR; do \ + cd \"\${DIR}\"; \ + find . -type d -exec bash -c \\\'printf \"%s/%q\n\" \"\$0\" \"\$1\"\' \"\`basename \"\${DIR}\"\`\" \{\} \; \ + | grep -v ^\\$; \ + done; + " \ + | sort > "${TARGET}/${BEEOND_COPY_DIR_LIST}" + + < "${TARGET}/${BEEOND_COPY_SCAN_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read DIR; do \ + cd \"\${DIR}\"; \ + find . \( -type f -or -type l \) -exec bash -c \ + \\\'printf \"%q %q\n\" \"\${PWD}/\$0\" \"${TARGET}/\`basename \"\${PWD}\"\`/\$0\"\' \{\} \; \ + | grep -v ^\\$; \ + done; \ + " \ + | sort > "${TARGET}/${BEEOND_COPY_FILE_LIST}" +} + +# Parallel copy of the files from a previously generated file list to the target directory. +# First, the directory structure is generated, then the files are copied into it. +beeond_parallel_copy() +{ + local TARGET="${1}" + local NODE_LIST="${2}" + local CONCURRENCY="${3}" + + # Expand target path. + if [ ! "${TARGET:0:1}" = "/" ] + then + TARGET="${PWD}/${TARGET}" + fi + + beeond_print_info "Generating target directory structure..." + + < "${TARGET}/${BEEOND_COPY_DIR_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read DIR; do \ + mkdir -pv \"${TARGET}/\${DIR}\"; \ + done; \ + " + + beeond_print_info "Copying files..." + + < "${TARGET}/${BEEOND_COPY_FILE_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read -a LINE; do \ + cp -av \"\${LINE[0]}\" \"\${LINE[1]}\"; \ + done; \ + " + + # Delete temporary files. +# rm "${TARGET}/${BEEOND_COPY_SCAN_LIST}" \ +# "${TARGET}/${BEEOND_COPY_DIR_LIST}" \ +# "${TARGET}/${BEEOND_COPY_FILE_LIST}" +} + +# Remove all files from the global store that have been deleted during the session. +beeond_remove_removed_files() +{ + local GLOBAL_PATH="${1}" + local LOCAL_PATH="${2}" + local NODE_LIST="${3}" + local CONCURRENCY="${4}" + + beeond_print_info "Deleting files:" + comm -23 "${LOCAL_PATH}/${BEEOND_START_FILE_LIST}" "${LOCAL_PATH}/${BEEOND_END_FILE_LIST}" | \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read FILE; do \ + rm -v \"${GLOBAL_PATH}/\${FILE}\"; \ + done; \ + " + + beeond_print_info "Deleting directories:" + comm -23 "${LOCAL_PATH}/${BEEOND_START_DIR_LIST}" "${LOCAL_PATH}/${BEEOND_END_DIR_LIST}" | \ + tac | \ + xargs -I{} rmdir -v "${GLOBAL_PATH}/{}" + # Not being done in parallel to avoid deleting a subdirectory before its parent (this is also + # the reason the list is inverted (tac)). +} + +# Copy back all files to the global store that have been updated during the session. +beeond_copy_updated_files() +{ + local GLOBAL_PATH="${1}" + local LOCAL_PATH="${2}" + local NODE_LIST="${3}" + local CONCURRENCY="${4}" + + beeond_print_info "Creating new directories:" + + < "${LOCAL_PATH}/${BEEOND_END_DIR_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read DIR; do \ + mkdir -pv \"${GLOBAL_PATH}/\${DIR}\"; \ + done; \ + " + + beeond_print_info "Copying back changed files:" + + < "${LOCAL_PATH}/${BEEOND_END_UPDATED_FILE_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read FILE; do \ + cp -uv \"${LOCAL_PATH}/\${FILE}\" \"${GLOBAL_PATH}/\${FILE}\"; \ + done; \ + " + + # Copy files into updated directories. (When a directory is renamed or files + # are moved to a directory, the files in it don't have their timestamp + # updated. Therefore, we need to check all the updated directories again). + + beeond_print_info "Copying back changed files (updated dirs):" + + pushd "${LOCAL_PATH}" + < "${LOCAL_PATH}/${BEEOND_END_UPDATED_DIR_LIST}" \ + xargs -I{} find {} -maxdepth 1 \( -type f -or -type l \) | \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read FILE; do \ + cp -uv \"${LOCAL_PATH}/\${FILE}\" \"\`dirname \"${GLOBAL_PATH}/\${FILE}\"\`\"; \ + done; \ + " + popd +} + +# Stage in process: Copy all files from the global store to the local store. +beeond_stage_in() +{ + local GLOBAL_PATH="${1}" + local LOCAL_PATH="${2}" + local NODE_LIST="${3}" + local CONCURRENCY="${4}" + + # Generate list of files that have to be copied. + beeond_generate_file_list "${GLOBAL_PATH}" "${LOCAL_PATH}/${BEEOND_START_FILE_LIST}" + beeond_generate_directory_list "${GLOBAL_PATH}" "${LOCAL_PATH}/${BEEOND_START_DIR_LIST}" + + beeond_print_info "Creating directory structure..." + < "${LOCAL_PATH}/${BEEOND_START_DIR_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read DIR; do \ + mkdir -pv \"${LOCAL_PATH}/\${DIR}\"; \ + touch --reference=\"${GLOBAL_PATH}/\${DIR}\" \"${LOCAL_PATH}/\${DIR}\"; \ + done; \ + " + + beeond_print_info "Copying files to local directory..." + if ! < "${LOCAL_PATH}/${BEEOND_START_FILE_LIST}" \ + ${PARALLEL} -S "${NODE_LIST}" -j"${CONCURRENCY}" --pipe --recend "\n" \ + -N${BEEOND_BATCH_SIZE} --controlmaster --will-cite \ + " \ + while read FILE; do \ + cp -av \"${GLOBAL_PATH}/\${FILE}\" \"\`dirname \"${LOCAL_PATH}/\${FILE}\"\`\"/; \ + done; + " + then + beeond_print_error "Stage-in copy did not succeed. Data is incompletely staged in." + fi + + # Generate list of files and dirs that were actually copied to keep track if the user deletes + # files during a session. (re-generate list here, so that if something went wrong during the + # stage-in copy, we don't start deleting stuff from the global store by accident). + beeond_generate_file_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_START_FILE_LIST}" + beeond_generate_directory_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_START_DIR_LIST}" +} + +# Stage out process: remove all the files that have been removed during the beeond session, +# and copy back the files which have been changed. +beeond_stage_out() +{ + local GLOBAL_PATH="${1}" + local LOCAL_PATH="${2}" + local NODE_LIST="${3}" + local CONCURRENCY="${4}" + + beeond_print_info "Nodes for parallel stage out: ${NODE_LIST}." + + beeond_generate_file_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_END_FILE_LIST}" + beeond_generate_file_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_END_UPDATED_FILE_LIST}" \ + "${BEEOND_START_FILE_LIST}" + + beeond_generate_directory_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_END_DIR_LIST}" + beeond_generate_directory_list "${LOCAL_PATH}" "${LOCAL_PATH}/${BEEOND_END_UPDATED_DIR_LIST}" \ + "${BEEOND_START_DIR_LIST}" + + beeond_remove_removed_files "${GLOBAL_PATH}" "${LOCAL_PATH}" "${NODE_LIST}" "${CONCURRENCY}" + + beeond_copy_updated_files "${GLOBAL_PATH}" "${LOCAL_PATH}" "${NODE_LIST}" "${CONCURRENCY}" +} + +# Parallel copy process: copy all files and directories (recursively) into the target directory. +beeond_copy() +{ + local NODE_LIST="${1}" + local CONCURRENCY="${2}" + shift 2 + + beeond_print_info "Nodes for parallel copy: ${NODE_LIST}; Concurrency: ${CONCURRENCY}" + + beeond_generate_copy_lists "${@:$#}" "${NODE_LIST}" "${CONCURRENCY}" "${@:1:$#-1}" + beeond_parallel_copy "${@:$#}" "${NODE_LIST}" "${CONCURRENCY}" + +} diff --git a/beeond/source/beeond b/beeond/source/beeond new file mode 100755 index 0000000..76035aa --- /dev/null +++ b/beeond/source/beeond @@ -0,0 +1,1606 @@ +#!/bin/bash + +CURRENTTIME=$(date +%Y%m%d-%H%M%S) + +MGMTD_BIN=beegfs-mgmtd +META_BIN=beegfs-meta +STORAGE_BIN=beegfs-storage +CLIENT_BIN=beegfs-client # not really a binary, but name of config, init, etc. +CTL_BIN=beegfs + +DEFAULT_LOG_PATH=/var/log +LOG_PATH=${DEFAULT_LOG_PATH} +STORAGE_LOG=${STORAGE_BIN}_${CURRENTTIME}.log +META_LOG=${META_BIN}_${CURRENTTIME}.log +CLIENT_LOG=${CLIENT_BIN}_${CURRENTTIME}.log + +STORAGE_CFG_NAME=${STORAGE_BIN}.conf +META_CFG_NAME=${META_BIN}.conf +MGMTD_CFG_NAME=${MGMTD_BIN}.toml +CLIENT_CFG_NAME=${CLIENT_BIN}.conf + +META_NUMID_FILE=nodeNumID +TARGET_NUMID_FILE=targetNumID + +PREFERRED_MDS_FILE=/tmp/preferredMds.fod +PREFERRED_TARGET_FILE=/tmp/preferredTarget.fod + +DEFAULT_STATUSFILE=/var/tmp/beeond.tmp +STATUSFILE=${DEFAULT_STATUSFILE} + +NUM_META_SERVER=1 +NUM_STORAGE_SERVER=0 + +BEEGFS_BIN_PATH=/opt/beegfs/sbin + +DEFAULT_MGMTD_GRPC_PORT=8010 +DEFAULT_PORT_SHIFT=1000 + +SSH="ssh" +SSH_PARAMS=( -qq -oNumberOfPasswordPrompts=0 -oStrictHostKeyChecking=no -n ) +DEFAULT_PDSH_PATH=$(which pdsh 2>/dev/null) +PDSH_RCMD="ssh" + +# source helper script +ABSOLUTE_PATH=$(dirname "$(readlink -e "$0")") # using readlink, because somone might be calling + # this script using a symlink +if [ -e "${ABSOLUTE_PATH}/../lib/beegfs-ondemand-stoplocal" ] +then + BEEOND_STOPLOCAL="${ABSOLUTE_PATH}/../lib/beegfs-ondemand-stoplocal" +else + BEEOND_STOPLOCAL="${ABSOLUTE_PATH}/../scripts/lib/beegfs-ondemand-stoplocal" +fi + +#shellcheck source=scripts/lib/beegfs-ondemand-stoplocal +source "${BEEOND_STOPLOCAL}" + +# print usage +print_usage_and_exit() +{ + echo "" + echo "BeeOND - BeeGFS OnDemand (http://www.beegfs.com)" + echo "" + echo "DESCRIPTION:" + echo " Script to set up or shut down a BeeGFS setup on the fly." + echo "" + echo " Creates a new BeeGFS file system on a set of hosts. All necessary services" + echo " are automatically started and the file system is mounted. In the same way," + echo " the file system can be unmounted again and the services will be shut down." + echo " Optionally, the contents of the file system can be deleted." + echo "" + echo " This script can be used e.g. to automatically create a temporary scratch file" + echo " system for cluster nodes during a compute job, and to remove it after the job" + echo " is finished." + echo "" + echo "USAGE: $(basename "$0") " + echo "" + echo "ACTIONS:" + echo " The first argument to $(basename "$0") is considered to be an action that the" + echo " script should perform." + echo "" + echo " The following actions are available:" + echo "" + echo " start:" + echo " Start the file system on a number of nodes, specified by the node file." + echo " The necessary services will be started and the newly created file system" + echo " will be mounted at the specified mount point. Information about the" + echo " running file system are stored in a status file on each node." + echo "" + echo " Mandatory arguments:" + echo " -n FILENAME => Node file with line-separated hostnames." + echo " -d PATH => Path for BeeGFS data on servers." + echo " -c PATH => Mount point for BeeGFS clients." + echo "" + echo " Optional arguments:" + echo " -i FILENAME => Status information file name." + echo " Default: ${DEFAULT_STATUSFILE}" + echo " -F => Remove contents of data path before starting services." + echo " This is useful if the processes and status file of a" + echo " previous beeond session are gone, but the" + echo " data is still there." + echo " -m NUM => Number of metadata servers to start. Default: 1" + echo " -s NUM => Number of storage servers to start." + echo " Default: Number of hosts." + echo " -p NUM => Network port shift. The standard BeeGFS network port" + echo " numbers are shifted by this number. Useful in order to" + echo " have several BeeGFS instances running on the same node." + echo " Default: ${DEFAULT_PORT_SHIFT}" + echo " -f PATH => Directory containing additional beegfs config files." + echo " There can be one file for each service as well as the client." + echo " They must be named in the form beegfs-.conf, where " + echo " can be meta, storage, mgmtd or client." + echo " Only the options specified within the files are" + echo " set/overwritten, the rest of the defaults will not be" + echo " touched and still be applied. The directory and the " + echo " files need to be present on every node." + echo " -L PATH => Log file directory. If necessary, the directory will be" + echo " created. Default: ${DEFAULT_LOG_PATH}" + echo " -l => Prefer local storage nodes." + echo " -P => Use pdsh for parallel startup. If this option is not" + echo " given, ssh is used to start up the services on the nodes" + echo " sequentially." + echo " -b PATH => Path to the pdsh binary. Default: " + echo " -r => Use tmpfs for beegfs storage and metadata." + echo " Note: On older Linux versions, tmpfs does not support" + echo " extended attributes. If you get an error message" + echo " from beegfs_meta reading \"Failed to store root" + echo " directory\" you have to provide an additional config" + echo " file beegfs-meta.conf containing the line" + echo " storeUseExtendedAttribs = false" + echo " -k => enable storage target mirroring" + echo " Note: Needs an even number of storage servers (-s)." + echo " -j => enable metadata server mirroring" + echo " Note: Needs an even number of metadata servers (-m)." + echo " -q => Suppress INFO messages, only print ERRORs." + echo " -t FILE => Use FILE to define multiple storage targets and assign" + echo " them to storage pools. The file needs to be in the" + echo " following format:" + echo "" + echo " pool_1:/path/to/target_1,/path/to/target_2,..." + echo " pool_2:/path/to/target_3,/path/to/target_4,..." + echo " ..." + echo "" + echo " pool_n is the name of the storage pool, the comma separated" + echo " list after the colon are the paths to the target directories" + echo " that shall be part of this pool." + echo " The lines can't contain whitespaces. BeeOND will look for" + echo " these directories and add them as a storage target on all" + echo " nodes where they exist. To avoid having unwanted targets" + echo " in a pool, make sure each of the specified paths only" + echo " exists on nodes where they are actually mounted on the" + echo " desired storage medium." + echo " BeeOND will then assign the targets to the corresponding" + echo " storage pools and create a directory for each pool" + echo " on the root level of the BeeGFS mount." + echo " This option can only be used together with -F." + echo " -T => Don't create and assign the pool directories when using -t." + echo " -G => The base gRPC port (before port shifting) that the mgmtd" + echo " uses in this BeeOND instance. Defaults to 8010 and only needs" + echo " to be supplied if mgmtd is configured via configuration file" + echo " (see -f) to use a base gRPC port other than 8010." + echo "" + echo " Arguments that require a configuration directory (option -f) that is available" + echo " on all nodes and contains the required files (see option descriptions):" + echo " -C => Enable connection authentication. Requires a \"conn.auth\"" + echo " file in the configuration directory." + echo " -E => Enable TLS encryption between the mgmtmd and the command" + echo " line configuration tool. Requires \"cert.pem\" and" + echo " \"key.pem\" files in the configuration directory." + echo " -H => Enable enterprise features. This mode is required by all" + echo " other modes that enable enterprise features and requires a" + echo " \"license.pem\" file in the configuration directory." + echo "" + echo " stop:" + echo " Stop the file system on a number of nodes, specified by the node file." + echo " Use the information from the status file to unmount a file system on a" + echo " number of nodes specified by the node file, and shut down the services." + echo "" + echo " Mandatory arguments:" + echo " -n FILENAME => Node file." + echo "" + echo " Optional arguments:" + echo " -i FILENAME => Status information file name." + echo " Default: ${DEFAULT_STATUSFILE}" + echo " -d => Delete BeeGFS data on disks." + echo " -L => Delete log files after successful shutdown." + echo " -c => \"Cleanup\": Remove remaining processes and directories" + echo " of a potentially unsuccessful shutdown of an earlier" + echo " beeond instance. This switch silences the error" + echo " message when a status information file is not found on a" + echo " node or an unmount command fails; instead, a message is" + echo " printed (if \"INFO\" messages are not suppressed) when a" + echo " status file DOES exist, because this means there" + echo " actually was an instance before that is now being" + echo " cleaned up." + echo " -P => Use pdsh for parallel shutdown. If this option is not" + echo " given, ssh is used to unmount the file system and stop" + echo " the services on all nodes sequentially." + echo " -b PATH => Path to the pdsh binary. Default: ${DEFAULT_PDSH_PATH}" + echo " -q => Suppress INFO messages, only print ERRORs." + echo "" + echo " stoplocal:" + echo " Stop the file system on the local host only. This is recommended only as" + echo " an emergency measure, e.g. after a host encountered an error during the" + echo " distributed shutdown procedure. Uses the information from the status file" + echo " to unmount the file system and stop the services on the local host only." + echo "" + echo " Optional arguments:" + echo " -i FILENAME => Status information file." + echo " Default: ${DEFAULT_STATUSFILE}" + echo " -d => Delete BeeGFS data on disks." + echo " -L => Delete log files after successful shutdown. If the log" + echo " directory is empty afterwards, it will also be removed." + echo " -c => \"Cleanup\": Remove remaining processes and directories" + echo " of a potentially unsuccessful shutdown of an earlier" + echo " beeond instance. This switch silences the error" + echo " message when the status information file is not found or" + echo " the unmount command fails; instead, a message is printed" + echo " (if \"INFO\" messages are not suppressed) when a status" + echo " file DOES exist, because this means there actually was" + echo " an instance before that is now being cleaned up." + echo " -q => Suppress INFO messages, only print ERRORs." + echo " -u => ONLY unmount the file system." + echo " (Cannot be used in combination with \"-s\".)" + echo " -s => ONLY stop non-client services. (*)" + echo " (Cannot be used in combination with \"-u\".)" + echo "" + echo "EXAMPLES:" + echo " Start a beeond instance on the nodes given in nodefile, using the data" + echo " directory /data/beeond and the client mountpoint /mnt/beeond via pdsh" + echo " for parallel startup:" + echo " $(basename "$0") start -n nodefile -d /data/beeond -c /mnt/beeond -P" + echo "" + echo " Stop the file system:" + echo " $(basename "$0") stop -n nodefile -P -L -d" + echo "" + exit 1 +} + +### internal functions for general usage ### +print_error() +{ + echo "ERROR: ${1}" >&2 + echo "" +} + +print_error_and_exit() +{ + print_error "${1}" + exit 1 +} + +print_info() +{ + local MESSAGE=${1} + if [ "${QUIET}" != "true" ] + then + echo "INFO: ${MESSAGE}" + fi +} + +check_pdsh() +{ + #an array is passed here, so this makes parameter passing a bit more complex + local HOSTS=$1 + + print_info "Checking PDSH availability on the following hosts: ${HOSTS}" + + # execute cmd + test -e "${PDSH}" &&\ + ${PDSH} -R ${PDSH_RCMD} -S -w "${HOSTS}" \ + "test \${SHELL} = '/bin/bash' || exit 2" + RES=$? + + if [ $RES -eq 2 ] + then + print_error_and_exit "One or more hosts don't use /bin/bash as default shell." + elif [ $RES -ne 0 ] + then + print_info "pdsh does not seem to work on all nodes. Disabling pdsh and using ssh instead" + USE_PDSH=false + + # We have to repeat the reachability check using conventional SSH before continuing. + IFS=, + for HOST in ${HOSTS} + do + check_reachability "${HOST}" + done + unset IFS + return + fi + + ${PDSH} -R ${PDSH_RCMD} -S -w "${HOSTS}" \ + "if [ -e ${BEEOND_STOPLOCAL} ]; then true; else exit 2; fi" || \ + print_error_and_exit "Unable to find BeeOND helper program on one or more nodes. +Please make sure BeeOND is installed on all machines." +} + +execute_ssh_cmd() +{ + local HOST="$1" + local CMD="$2" + + # error checks + if [ "${HOST}" = "" ] || [ "${CMD}" = "" ] + then + print_error_and_exit "Internal function 'execute_ssh_cmd' was called without a host or \ +without a command" + fi + + # execute cmd + ${SSH} "${SSH_PARAMS[@]}" "${HOST}" "${CMD}" +} + +execute_pdsh_cmd() +{ + local HOSTS="$1" # comma-separated list + local CMD="$2" + local CONTINUE_ON_ERROR="$3" + local TMPTIME + TMPTIME=$(date +%Y%m%d-%H%M%S) + local TMPFAILFILE="/tmp/beegfs.pdsh_fail.${TMPTIME}" + + # error checks + if [ "${HOSTS}" = "" ] || [ "${CMD}" = "" ] + then + print_error_and_exit "Internal function 'execute_pdsh_cmd' was called without a host or \ +without a command" + fi + + # execute cmd + if ! ${PDSH} -R ${PDSH_RCMD} -S -w "${HOSTS}" "${CMD} || (touch ${TMPFAILFILE} && false)" + then + # pdsh returned non-zero, so there must have been an error on at least one node + # (-S returns the greatest return value of all nodes). + # the executed line created a file on the failing node + # now we have to look on each node for this file if we are interested which node failed + # for now, we do not do that; only abort and leave it to the user to investigate pdsh output + if [ "${CONTINUE_ON_ERROR}" = "true" ] + then + print_error "Execution of a command failed. Please see pdsh output for more information." + ERROR="true" + else + print_error_and_exit "Execution of a command failed. Please see pdsh output for more \ +information." + fi + fi +} + +check_reachability() +{ + local HOST="$1" + + # error checks + if [ "${HOST}" = "" ] + then + print_error_and_exit "Internal function 'check_reachability' was called without a hostname" + fi + + print_info "Checking reachability of host ${HOST}" + + execute_ssh_cmd "${HOST}" "test \${SHELL} = '/bin/bash'" + RES=$? + if [ $RES -eq 255 ] + then + print_error_and_exit "Host is unreachable via ssh: ${HOST}" + elif [ $RES -eq 1 ] + then + print_error_and_exit "Host doesn't use /bin/bash as default shell: ${HOST}" + elif [ $RES -ne 0 ] + then + print_error_and_exit "Error contacting host: ${HOST}" + fi + + execute_ssh_cmd "${HOST}" "test -e ${BEEOND_STOPLOCAL}" || \ + print_error_and_exit "Could not find BeeOND helper program on host: ${HOST} +Please make sure BeeOND is installed on all machines." +} + +check_hostfile() +{ + # hostfile set? + if [ "${HOSTFILE}" = "" ] + then + print_error_and_exit "Node file undefined" + fi + + # does it exist + if [ ! -f "${HOSTFILE}" ] + then + print_error_and_exit "Node file does not exist: ${HOSTFILE}" + fi +} + +check_datapath() +{ + if [ "${DATA_PATH}" = "" ] + then + print_error_and_exit "Path for BeeGFS data undefined" + fi +} + +check_mountpoint() +{ + if [ "${MOUNTPOINT}" = "" ] + then + print_error_and_exit "Path for client mountpoint undefined" + fi +} + +check_statusfile() +{ + # checks for every node: + # - whether the statusfile already exists (maybe a session is already running) + # - whether the statusfile can be created (if not, we can't continue) + + local HOSTS=$1 + + if [ "${HOSTS}" = "" ] + then + print_error_and_exit "Internal function 'check_statusfile' was called without a hostname" + fi + + local CHECK_CMD="[ ! -e \"${STATUSFILE}\" ]" + local TOUCH_CMD="touch \"${STATUSFILE}\"" + + if [ "${USE_PDSH}" = "true" ] + then + # see if statusfile already exists + if ! ${PDSH} -R ${PDSH_RCMD} -S -w "${HOSTS}" "${CHECK_CMD} || (echo \"Statusfile already exists.\" && false)" + then + print_error_and_exit "Statusfile ${STATUSFILE} on one ore more hosts already exists. \ +Maybe a session is already running or the previous session was not properly \ +shut down." + fi + + # touch statusfile on every host, to make sure the file can be accessed + if ! ${PDSH} -R ${PDSH_RCMD} -S -w "${HOSTS}" "${TOUCH_CMD}" + then + print_error_and_exit "Could not create status file ${STATUSFILE} on one ore more hosts." + fi + else + IFS=, + for HOST in ${HOSTS} + do + # see if statusfile already exists + if ! ${SSH} "${SSH_PARAMS[@]}" "${HOST}" "${CHECK_CMD}" + then + print_error_and_exit "Status file ${STATUSFILE} on host ${HOST} already exists. \ +Maybe a session is already running or the previous session was not properly \ +shut down." + fi + done + + for HOST in ${HOSTS} + do + if ! ${SSH} "${SSH_PARAMS[@]}" "${HOST}" "${TOUCH_CMD}" + then + print_error_and_exit "Could not create status file ${STATUSFILE} on host ${HOST}" + fi + done + unset IFS + fi +} + +create_log_path() +{ + local HOSTS + HOSTS=$(IFS=,; echo "$*") # turn argument list into comma-separated string for PDSH + + if [ "${HOSTS}" = "" ] + then + print_error_and_exit "Internal function 'create_log_path' was called without a host." + fi + + # if the path doesn't exist, it's created. If it already exists, nothing happens + CMD="mkdir -p \"${LOG_PATH}\"" + + if [ "${USE_PDSH}" = "true" ] + then + execute_pdsh_cmd "${HOSTS}" "${CMD}" "false" + else + # no pdsh: do it manually with a loop + IFS=, + for HOST in ${HOSTS} + do + if ! execute_ssh_cmd "${HOST}" "${CMD}" + then + print_error_and_exit "Could not create log path ${LOG_PATH} on host ${HOST}" + fi + done + unset IFS + fi +} + +### internal functions for beegfs-ondemand start ### + +start_tmpfs() +{ + local HOSTS=$1 + local DATAPATH=$2 + + # error checks + if [ "${HOSTS}" = "" ] || [ "${DATAPATH}" = "" ] + then + print_error_and_exit "Internal function 'start_tmpfs' called without all needes parameters" + fi + + CMD="mkdir -p ${DATAPATH} && mount -t tmpfs tmpfs ${DATAPATH}" + + if [ "${USE_PDSH}" = "true" ] + then + print_info "Starting tempfs on the following hosts: ${HOSTS}" + + execute_pdsh_cmd "${HOSTS}" "${CMD}" "false" + + IFS=',' + for HOST in ${HOSTS} + do + if [ "${HOST}" = "" ]; then continue; fi + add_to_status_file "${HOST}" tmpfs "${DATAPATH}" - - + done + unset IFS + else + # no pdsh => do it manually with ssh loop + print_info "Starting tmpfs mounts" + + # for each host, start server + IFS=, + for HOST in ${HOSTS} + do + print_info "Starting tmpfs on host: ${HOST}" + + if ! execute_ssh_cmd "${HOST}" "${CMD}" + then + print_error_and_exit "Unable to start tmpfs on host: ${HOST}" + else + add_to_status_file "${HOST}" tmpfs "${DATAPATH}" "-" "-" + fi + done + unset IFS + fi +} + +start_meta_servers() +{ + local HOSTS=$1 # comma seperated + local DATAPATH=$2 + local MGMTD=$3 + local PORT_SHIFT=$4 # port shift can be empty! + local CFG_PATH=$5 # may be empty + local CFG_FILE=${CFG_PATH}/${META_CFG_NAME} + + local LOGFILE=${LOG_PATH}/${META_LOG} + local PIDFILE=/var/run/${META_BIN}-${CURRENTTIME}.pid + + # error checks + if [ "${HOSTS}" = "" ] || [ "${MGMTD}" = "" ] || [ "${DATAPATH}" = "" ] + then + print_error_and_exit "Internal function 'start_meta_servers_ssh' was called without all \ +needed parameters" + fi + + DATAPATH=${DATAPATH}/${META_BIN} + + PARAMS="sysMgmtdHost=${MGMTD} storeMetaDirectory=${DATAPATH} logStdFile=${LOGFILE} \ + ${CONNAUTH_LEGACY} runDaemonized=true pidFile=${PIDFILE}" + + if [ "${PORT_SHIFT}" != "" ] + then + PARAMS="${PARAMS} connPortShift=${PORT_SHIFT}" + fi + + CMD="PARAMS=\"${PARAMS}\"; \ +if [ -n \"${CFG_PATH}\" ] && [ -e \"${CFG_FILE}\" ]; then \ + PARAMS=\"\${PARAMS} cfgFile=${CFG_FILE}\"; fi; \ +if [ \"${CLEAR_DATA}\" = \"true\" ]; then \ + rm -rf ${DATAPATH}; fi; \ +${BEEGFS_BIN_PATH}/${META_BIN} \${PARAMS}" + + if [ "${USE_PDSH}" = "true" ] + then + print_info "Starting ${META_BIN} processes on the following hosts: ${HOSTS}" + print_info "Metadata server log: ${LOGFILE}" + + execute_pdsh_cmd "${HOSTS}" "${CMD}" "false" + + if [ "${PREFER_LOCAL}" = "true" ] + then + # create the preferred MDS file (actually just a symlink to the node ID file) + execute_pdsh_cmd "${HOSTS}" "rm -f ${PREFERRED_MDS_FILE}; \ + ln -s ${DATAPATH}/${META_NUMID_FILE} ${PREFERRED_MDS_FILE}" "false" + fi + + + execute_pdsh_cmd "${HOSTS}" "echo %h,${META_BIN},${DATAPATH},${LOGFILE},${PIDFILE} >> ${STATUSFILE}" "false" + + else + # no pdsh => do it manually with ssh loop + print_info "Starting ${META_BIN} processes" + print_info "Metadata server log: ${LOGFILE}" + + # for each host, start server + IFS=, + for HOST in ${HOSTS} + do + print_info "Starting ${META_BIN} on host: ${HOST}" + if ! execute_ssh_cmd "${HOST}" "${CMD}" + then + print_error_and_exit "Unable to start ${META_BIN} on host: ${HOST}" + else + add_to_status_file "${HOST}" "${META_BIN}" "${DATAPATH}" "${LOGFILE}" "${PIDFILE}" + if [ "${PREFER_LOCAL}" = "true" ] + then + # create the preferred MDS file (actually just a symlink to the node ID file) + execute_ssh_cmd "${HOST}" "rm -f ${PREFERRED_MDS_FILE}; \ + ln -s ${DATAPATH}/${META_NUMID_FILE} ${PREFERRED_MDS_FILE}" + fi + fi + done + unset IFS + fi + + if [ "${QUIET}" != "true" ] + then + echo "" + fi +} + +start_storage_servers() +{ + local HOSTS=$1 + local DATAPATH=$2 + local MGMTD=$3 + local PORT_SHIFT=$4 # port shift can be empty! + local CFG_PATH=$5 # may be empty + local CFG_FILE=${CFG_PATH}/${STORAGE_CFG_NAME} + + local LOGFILE=${LOG_PATH}/${STORAGE_LOG} + local PIDFILE=/var/run/${STORAGE_BIN}-${CURRENTTIME}.pid + + # error checks + if [ "${HOSTS}" = "" ] || [ "${MGMTD}" = "" ] || [ "${DATAPATH}" = "" ] + then + print_error_and_exit "Internal function 'start_storage_servers' was called without all \ +needed parameters" + fi + + DATAPATH=${DATAPATH}/${STORAGE_BIN} + + PARAMS="sysMgmtdHost=${MGMTD} logStdFile=${LOGFILE} runDaemonized=true pidFile=${PIDFILE} ${CONNAUTH_LEGACY}" + + if [ "${PORT_SHIFT}" != "" ] + then + PARAMS="${PARAMS} connPortShift=${PORT_SHIFT}" + fi + + if [ "${TARGETFILE}" != "" ] + then + local ALL_TARGETS + ALL_TARGETS=$(get_all_targets_from_targetfile) + + CMD="while read T; do \ + if [ -d \"\${T}\" ] ; then EXISTING_TARGETS=\"\${EXISTING_TARGETS}\${T},\"; \ + if [ \"${CLEAR_DATA}\" = \"true\" ]; then \ + rm -rf \"${T}/*\"; \ + fi; + fi ; \ + done < <(echo \"${ALL_TARGETS}\" | tr ',' '\n' ); \ + PARAMS=\"${PARAMS} storeStorageDirectory=\${EXISTING_TARGETS}\"; \ + if [ -n \"${CFG_PATH}\" ] && [ -e \"${CFG_FILE}\" ]; then \ + PARAMS=\"\${PARAMS} cfgFile=${CFG_FILE}\"; fi; \ + ${BEEGFS_BIN_PATH}/${STORAGE_BIN} \${PARAMS}" + else + CMD="PARAMS=\"${PARAMS} storeStorageDirectory=${DATAPATH}\"; \ + if [ -n \"${CFG_PATH}\" ] && [ -e \"${CFG_FILE}\" ]; then \ + PARAMS=\"\${PARAMS} cfgFile=${CFG_FILE}\"; fi; \ + if [ \"${CLEAR_DATA}\" = \"true\" ]; then \ + rm -rf ${DATAPATH}; fi; \ + ${BEEGFS_BIN_PATH}/${STORAGE_BIN} \${PARAMS}" + fi + + + if [ "${USE_PDSH}" = "true" ] + then + print_info "Starting ${STORAGE_BIN} processes on the following hosts: ${HOSTS}" + # trailing ',' removed + print_info "Storage server log: ${LOGFILE}" + + execute_pdsh_cmd "${HOSTS}" "${CMD}" "false" + + if [ "${PREFER_LOCAL}" = "true" ] + then + # create the preferred target file (actually just a symlink to the target ID file) + execute_pdsh_cmd "${HOSTS}" "rm -f ${PREFERRED_TARGET_FILE}; \ + ln -s ${DATAPATH}/${TARGET_NUMID_FILE} ${PREFERRED_TARGET_FILE}" "false" + fi + + execute_pdsh_cmd "${HOSTS}" "echo %h,${STORAGE_BIN},${DATAPATH},${LOGFILE},${PIDFILE} >> ${STATUSFILE}" "false" + + else + # no pdsh => do it manually with ssh loop + print_info "Starting ${STORAGE_BIN} processes" + print_info "Storage server log: ${LOGFILE}" + + # for each host, start server + IFS=, + for HOST in ${HOSTS} + do + print_info "Starting ${STORAGE_BIN} on host: ${HOST}" + if ! execute_ssh_cmd "${HOST}" "${CMD}" + then + print_error_and_exit "Unable to start ${STORAGE_BIN} on host: ${HOST}" + else + add_to_status_file "${HOST}" "${STORAGE_BIN}" "${DATAPATH}" "${LOGFILE}" "${PIDFILE}" + if [ "${PREFER_LOCAL}" = "true" ] + then + # create the preferred target file (actually just a symlink to the target ID file) + execute_ssh_cmd "${HOST}" "rm -f ${PREFERRED_TARGET_FILE}; \ + ln -s ${DATAPATH}/${TARGET_NUMID_FILE} ${PREFERRED_TARGET_FILE}" + fi + fi + done + unset IFS + fi + + if [ "${TARGETFILE}" != "" ] + then + create_storage_pools "${HOSTS}" + fi + + if [ "${QUIET}" != "true" ] + then + echo "" + fi +} + +create_storage_pools() +{ + local HOSTS=$1 + + if [ "${TARGETFILE}" != "" ] + then + while read LINE + do + IFS=: read POOL TARGETS <<< "${LINE}" + TARGETS=$(echo "${TARGETS}" | tr -d "[:space:]") + + TARGET_IDS= + while read HOST + do + CMD="echo \"${TARGETS}\" | tr ',' '\n' | \ + while read T; do if [ -f \"\${T}/${TARGET_NUMID_FILE}\" ]; then \ + echo -n \"\$(cat \"\${T}/targetNumID\") \"; fi; done" + HOST_TARGETS=$(execute_ssh_cmd "${HOST}" "${CMD}") + for TARGET_ID in $HOST_TARGETS + do + TARGET_IDS="${TARGET_IDS:+$TARGET_IDS,}storage:$TARGET_ID" + done + done < <(echo "${HOSTS}" | tr ',' '\n') + + if [ "$POOL" == "default" ] || [ "$POOL" == "Default" ] + then + "${CTL_BIN}" ${CTL_GLOBAL_PARAMS} \ + pool set-alias storage:1 "${POOL}" > /dev/null + else + # create pool with collected ids + "${CTL_BIN}" ${CTL_GLOBAL_PARAMS} \ + pool create "${POOL}" --targets "${TARGET_IDS}" > /dev/null + fi + done < <(grep -v "^$" "${TARGETFILE}" | grep -v "^\s*\#") + fi +} + +assign_storage_pool_dirs() +{ + local POOLS + POOLS=$("${CTL_BIN}" ${CTL_GLOBAL_PARAMS} \ + pool list --columns alias | grep -v '^\s*$'| tail -n+2) + + while read LINE + do + read ALIAS <<< "${LINE}" + + "${CTL_BIN}" ${CTL_GLOBAL_PARAMS} \ + entry create dir --mount=none "/${ALIAS}" > /dev/null + + "${CTL_BIN}" ${CTL_GLOBAL_PARAMS} \ + entry set --mount=none --pool "${ALIAS}" "/${ALIAS}" &> /dev/null + + done < <(echo "${POOLS}") +} + +check_targetfile() +{ + local CHECK1 + local CHECK2 + local LINE_REGEX + + LINE_REGEX='^\w+:([\w/_.-]+,?)+\s*$' + + CHECK1=$(grep -i -P "${LINE_REGEX}" "${TARGETFILE}") + CHECK2=$(grep -i -P -v "${LINE_REGEX}" "${TARGETFILE}" | grep -v "^\s*\#" | \ + grep -v "^$") + + if [ "${CHECK1}" == "" ] || [ "${CHECK2}" != "" ] + then + print_error_and_exit "${TARGETFILE} contains invalid entries or is empty." + fi + + CHECK1=$(grep -v "^$" "${TARGETFILE}" | grep -v "^\s*\#" | \ + tr -d ' ' | awk -F ':' '{print $1}' | sort | uniq -i -d) + + if [ "${CHECK1}" != "" ] + then + print_error_and_exit "${TARGETFILE} contains non-unique pool names." + fi + + CHECK1=$(get_all_targets_from_targetfile | tr ',' '\n' | sort | uniq -i -d) + + if [ "${CHECK1}" != "" ] + then + print_error_and_exit "${TARGETFILE} contains non-unique target paths." + fi + + if [ "${CLEAR_DATA}" != "true" ] + then + print_error_and_exit "Using storage pools requires the -F option \ +to make sure no old data is left." + fi + + if [ "${STORAGE_MIRROR}" == "true" ] + then + print_error_and_exit "Using storage pools doesn't support storage mirroring (-k)." + fi +} + +get_all_targets_from_targetfile() +{ + local ALL_TARGETS + + while read LINE + do + IFS=: read POOL TARGETS <<< ${LINE} + while read T + do + T=$(echo "${T}" | tr -d "[:space:]") + ALL_TARGETS="${ALL_TARGETS}${T}," + done < <(echo "${TARGETS}" | tr ',' '\n') + done < <(grep -v "^$" "${TARGETFILE}" | grep -v "^\s*\#") + echo "$ALL_TARGETS" +} + +start_mgmtd() +{ + local HOST=$1 + local DATAPATH=$2 + local PORT_SHIFT=$3 # port shift can be empty! + local CFG_PATH=$4 # may be empty + local CFG_FILE=${CFG_PATH}/${MGMTD_CFG_NAME} + + local PIDFILE=/var/run/${MGMTD_BIN}-${CURRENTTIME}.pid + + # error checks + if [ "${HOST}" = "" ] || [ "${DATAPATH}" = "" ] + then + print_error_and_exit "Internal function 'start_mgmtd' was called without all needed \ +parameters" + fi + + DATAPATH=${DATAPATH}/${MGMTD_BIN} + DBPATH=${DATAPATH}/beegfs-mgmtd.sqlite3 + + # start server + print_info "Starting ${MGMTD_BIN} processes" + + print_info "Starting ${MGMTD_BIN} on host: ${HOST}" + + PARAMS="--db-file ${DBPATH} --daemonize true --daemonize-pid-file ${PIDFILE} ${TLS_DISABLE} ${TLS_CERT_FILE} ${TLS_KEY_FILE} ${CONNAUTH_FLAG} ${LICENSE_FILE}" + + if [ "${PORT_SHIFT}" != "" ] + then + PARAMS="${PARAMS} --port-shift ${PORT_SHIFT}" + fi + + CMD="PARAMS=\"${PARAMS}\"; \ +if [ -n \"${CFG_PATH}\" ] && [ -e \"${CFG_FILE}\" ]; then \ + PARAMS=\"\${PARAMS} --config-file ${CFG_FILE}\"; fi; \ +if [ \"${CLEAR_DATA}\" = \"true\" ]; then \ + rm -rf ${DATAPATH}; fi; \ +${BEEGFS_BIN_PATH}/${MGMTD_BIN} --init --db-file ${DBPATH}; \ +${BEEGFS_BIN_PATH}/${MGMTD_BIN} \${PARAMS}" + + if ! execute_ssh_cmd "${HOST}" "${CMD}" + then + print_error_and_exit "Unable to start ${MGMTD_BIN} on host: ${HOST}" + else + add_to_status_file "${HOST}" "${MGMTD_BIN}" "${DATAPATH}" "-" "${PIDFILE}" + fi + + if [ "${QUIET}" != "true" ] + then + echo "" + fi +} + +start_clients() +{ + local HOSTS=$1 + local MGMTD=$2 + local MOUNTPOINT=$3 + local PORT_SHIFT=$4 # port shift can be empty! + local CFG_PATH=$5 # may be empty + local CLIENT_CFG_FILE=${CFG_PATH}/${CLIENT_CFG_NAME} + + local LOGFILE=${LOG_PATH}/${CLIENT_LOG} + + # error checks + if [ "${HOSTS}" = "" ] || [ "${MGMTD}" = "" ] || [ "${MOUNTPOINT}" = "" ] + then + print_error_and_exit "Internal function 'start_clients_ssh' was called without all \ +needed parameters" + fi + + MODPROBE_CMD="modprobe beegfs" + REBUILD_CMD="/etc/init.d/${CLIENT_BIN} rebuild" + + MOUNT_PARAMS="-osysMgmtdHost=${MGMTD},${CONNAUTH_LEGACY// /,}" + + if [ "${PORT_SHIFT}" != "" ] + then + MOUNT_PARAMS="${MOUNT_PARAMS},connPortShift=${PORT_SHIFT}" + fi + + MOUNT_CMD="PARAMS=\"${MOUNT_PARAMS}\"; if [ -n \"${CFG_PATH}\" ] && \ +[ -e \"${CLIENT_CFG_FILE}\" ]; then PARAMS=\"\${PARAMS},cfgFile=${CLIENT_CFG_FILE}\"; fi; \ +if [ \"${PREFER_LOCAL}\" = \"true\" ] && [ -e \"${PREFERRED_MDS_FILE}\" ]; \ +then PARAMS=\"\${PARAMS},tunePreferredMetaFile=${PREFERRED_MDS_FILE}\"; fi; \ +if [ \"${PREFER_LOCAL}\" = \"true\" ] && [ -e \"${PREFERRED_TARGET_FILE}\" ]; \ +then PARAMS=\"\${PARAMS},tunePreferredStorageFile=${PREFERRED_TARGET_FILE}\"; fi; \ +mkdir -p ${MOUNTPOINT} && ${MODPROBE_CMD} && mount -t beegfs beegfs_ondemand ${MOUNTPOINT} \${PARAMS}" + + if [ "${USE_PDSH}" = "true" ] + then + # trailing ',' removed in output + print_info "Starting ${CLIENT_BIN} processes on the following hosts: ${HOSTS}" + print_info "Client log: ${LOGFILE}" + + execute_pdsh_cmd "${HOSTS}" "echo %h,${CLIENT_BIN},${MOUNTPOINT},${LOGFILE},- >> ${STATUSFILE}" "false" + + execute_pdsh_cmd "${HOSTS}" "${MODPROBE_CMD} || ${REBUILD_CMD}" "false" + execute_pdsh_cmd "${HOSTS}" "${MOUNT_CMD}" "false" + + if [ "${PREFER_LOCAL}" = "true" ] #set target count to 1 + then + CTL_CMD="${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + entry set --num-targets 1 --chunk-size 512ki ${MOUNTPOINT} > /dev/null" + execute_pdsh_cmd "${HOSTS}" "${CTL_CMD}" "false" + fi + else + # no pdsh => do it manually with ssh loop + + print_info "Starting ${CLIENT_BIN} processes" + print_info "Client log: ${LOGFILE}" + + # for each host, start client + IFS=, + for HOST in ${HOSTS} + do + print_info "Starting ${CLIENT_BIN} on host: ${HOST}" + + if ! execute_ssh_cmd "${HOST}" "${MODPROBE_CMD}" + then + print_info "Module beegfs could not be loaded on host: ${HOST}. Trying to recompile \ +from source." + execute_ssh_cmd "${HOST}" "${REBUILD_CMD}" + fi + + if ! execute_ssh_cmd "${HOST}" "${MOUNT_CMD}" + then + print_error_and_exit "Unable to start BeeGFS client on host: ${HOST}" + else + # NOTE : mountpoint as data path + add_to_status_file "${HOST}" "${CLIENT_BIN}" "${MOUNTPOINT}" "${LOGFILE}" "-" + + if [ "${PREFER_LOCAL}" = "true" ] #set target count to 1 + then + CTL_CMD="${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + entry set --num-targets 1 --chunk-size 512ki ${MOUNTPOINT} > /dev/null" + execute_ssh_cmd "${HOST}" "${CTL_CMD}" + fi + + fi + done + unset IFS + fi + + if [ "${QUIET}" != "true" ] + then + echo "" + fi +} + +add_to_status_file() +{ + local HOST=$1 + local SERVICE=$2 + local DATAPATH=$3 + local LOGFILE=$4 + local PIDFILE=$5 + + # error checks + if [ "${HOST}" = "" ] || [ "${SERVICE}" = "" ] || [ "${LOGFILE}" = "" ] || [ "${PIDFILE}" = "" ] + then + print_error_and_exit "Internal function 'add_to_status_file' was called without all \ +needed parameters" + fi + + INFO="${HOST},${SERVICE},${DATAPATH},${LOGFILE},${PIDFILE}" + execute_ssh_cmd "${HOST}" "echo ${INFO} >> ${STATUSFILE}" +} + +### internal functions for beegfs-ondemand stop ### + +# build the argument string for the "stoplocal" function +make_stoplocal_args() +{ + local STOPLOCAL_ARGS=" -q" # quiet + if [ "${DELETE_DATA}" = "true" ] + then + STOPLOCAL_ARGS="${STOPLOCAL_ARGS} -d" # delete data + fi + + if [ "${DELETE_LOGS}" = "true" ] + then + STOPLOCAL_ARGS="${STOPLOCAL_ARGS} -L" # delete logs + fi + + if [ "${CLEANUP}" = "true" ] + then + STOPLOCAL_ARGS="${STOPLOCAL_ARGS} -c" # don't complain about missing files (from properly shut + fi # down beegfs-ondemand instances) + + echo "${STOPLOCAL_ARGS}" +} + +stop_procs() +{ + local HOSTS=$1 + local DELETE_DATA=$2 + local DELETE_LOGS=$3 + + # prepare command for remote script + STOPSERVERSCMD="source ${BEEOND_STOPLOCAL}; \ + do_stoplocal -s -i ${STATUSFILE} $(make_stoplocal_args)" + + # issue the stop server command via ssh/pdsh + if [ "${USE_PDSH}" = "true" ] + then + print_info "Stopping remaining processes on the following hosts: ${HOSTS}" + + execute_pdsh_cmd "${HOSTS}" "${STOPSERVERSCMD}" "true" + else + # ssh mode - launch command for each host separately + IFS=, + for HOST in ${HOSTS} + do + print_info "Stopping remaining processes on host: ${HOST}" + execute_ssh_cmd "${HOST}" "${STOPSERVERSCMD}" + done + unset IFS + fi + + # delete the statusfile + local DELETESTATUSFILECMD="rm -f ${STATUSFILE}" + + if [ "${USE_PDSH}" = "true" ] + then + print_info "Deleting status file on hosts: ${HOSTS}" + + execute_pdsh_cmd "${HOSTS}" "${DELETESTATUSFILECMD}" "true" + else + # ssh mode - launch command for each host separately + IFS=, + for HOST in ${HOSTS} + do + print_info "Deleting status file on host: ${HOST}" + execute_ssh_cmd "${HOST}" "${DELETESTATUSFILECMD}" + done + unset IFS + fi +} + +unmount_clients() +{ + HOSTS=$1 + # prepare command for remote script + local UMOUNTCMD + UMOUNTCMD="source ${BEEOND_STOPLOCAL}; \ + do_stoplocal -u -i ${STATUSFILE} $(make_stoplocal_args)" + + if [ "${USE_PDSH}" = "true" ] + then + print_info "Unmounting file system on the following hosts: ${HOSTS}" + + execute_pdsh_cmd "${HOSTS}" "${UMOUNTCMD}" "true" + else + # ssh mode - launch command for each host separately + IFS=, + for HOST in ${HOSTS} + do + print_info "Unmounting file system on host: ${HOST}" + execute_ssh_cmd "${HOST}" "${UMOUNTCMD}" + done + unset IFS + fi +} + +# Blocks until all targets are online/good +# Parameters: nodetype numNodes +wait_online_good() +{ + local NODE_TYPE=$1 + local NUM_NODES=$2 + + #first, wait for the correct number of *nodes* to become available. + echo -n "Waiting until all nodes have registered with mgmtd..." + local CTLCMD="${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + node list --node-type ${NODE_TYPE}" + while [ ! "$(${CTLCMD} | head -n-1 | tail -n+2 | wc -l)" = "${NUM_NODES}" ] + do + echo -n "." + sleep 1 + done + echo + + # now wait for all *targets* to become online/good. (also works for metadata servers, since they + # are internally treated as one target per server) + echo -n "Waiting for all nodes/targets to be online and in sync..." + local CTLCMD="${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + target list --node-type ${NODE_TYPE} --state --columns reachability,consistency" + while [ ! "$(${CTLCMD} | head -n-1 | tail -n+2 | grep "Online\s\+Good" -c)" = "$(${CTLCMD} | head -n-1 | tail -n+2 | wc -l)" ] + do + echo -n "." + sleep 1 + done + echo + + # and now, we wait until all targets have reported their available space and inodes. It should be + # good enough to only check space, because both will be reported at the same time. Without this, + # the automatic mirror group creation might fail, because it compares target sizes and free + # inodes. + echo -n "Waiting for all nodes/targets to report their available space..." + local CTLCMD="${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + target list --node-type ${NODE_TYPE} --raw --columns space" + while [ ! "$(${CTLCMD} | head -n-1 | tail -n+2 | grep -s '^-\s\+$' | wc -l)" == 0 ] + do + echo -n "." + sleep 1 + done + echo + +} + +### main functions ### +do_start() +{ + CLEAR_DATA="false" + USE_PDSH="false" + PREFER_LOCAL="false" + QUIET="false" + USE_TMPFS="false" + STORAGE_MIRROR="false" + META_MIRROR="false" + PORT_SHIFT=${DEFAULT_PORT_SHIFT} + ASSIGN_STORAGE_POOL_DIRS=true + CONNAUTH_FLAG="--auth-disable" + CONNAUTH_LEGACY="connDisableAuthentication=true" + TLS_DISABLE="--tls-disable" + TLS_CERT_FILE="" + TLS_KEY_FILE="" + LICENSE_FILE="" + MGMTD_GRPC_PORT=${DEFAULT_MGMTD_GRPC_PORT} + + while getopts ":c:d:f:Fi:m:n:p:G:lL:Pb:s:qrkjt:TCEH" opt; do + case $opt in + n) + HOSTFILE=${OPTARG} + ;; + d) + DATA_PATH=${OPTARG} + ;; + F) + CLEAR_DATA="true" + ;; + c) + MOUNTPOINT=${OPTARG} + ;; + i) + STATUSFILE=${OPTARG} + ;; + L) + LOG_PATH=${OPTARG} + ;; + m) + if ! [[ ${OPTARG} =~ ^[0-9]+$ ]]; + then + print_error_and_exit "number of metadata servers must be numeric"; + fi + NUM_META_SERVER=${OPTARG} + ;; + p) + if ! [[ ${OPTARG} =~ ^[0-9]+$ ]]; + then + print_error_and_exit "port shift must be numeric"; + fi + PORT_SHIFT=${OPTARG} + ;; + G) + if ! [[ ${OPTARG} =~ ^[0-9]+$ ]]; + then + print_error_and_exit "management gRPC port must be numeric"; + fi + MGMTD_GRPC_PORT=${OPTARG} + ;; + P) + USE_PDSH="true" + ;; + b) + PDSH=${OPTARG} + ;; + s) + if ! [[ ${OPTARG} =~ ^[0-9]+$ ]]; + then + print_error_and_exit "number of storage servers must be numeric"; + fi + NUM_STORAGE_SERVER=$OPTARG + ;; + f) + if ! [[ -d ${OPTARG} ]]; then + print_error_and_exit "The -f option expects a path to a directory: ${OPTARG}" + fi + CONFIGPATH=${OPTARG} + ;; + l) + PREFER_LOCAL="true" + ;; + q) + QUIET="true" + ;; + r) + USE_TMPFS="true" + ;; + k) + if [[ -z ${LICENSE_FILE} ]] ; then + print_error_and_exit "To use mirroring, licensing (option -H) must be configured before the option -k" + fi + STORAGE_MIRROR="true" + ;; + j) + if [[ -z ${LICENSE_FILE} ]] ; then + print_error_and_exit "To use mirroring, licensing (option -H) must be configured before the option -j" + fi + META_MIRROR="true" + ;; + t) + if [[ -z ${LICENSE_FILE} ]] ; then + print_error_and_exit "To use storage pools, licensing (option -H) must be configured before the option -t" + fi + TARGETFILE=${OPTARG} + ;; + T) + ASSIGN_STORAGE_POOL_DIRS="false" + ;; + C) + if ! [[ -f ${CONFIGPATH}/conn.auth ]] ; then + print_error_and_exit "To use connection authentication, a config path (option -f) that contains a \"conn.auth\" file and is available on all nodes needs to be specified before the option -C" + fi + CONNAUTH_FLAG="--auth-file ${CONFIGPATH}/conn.auth" + CONNAUTH_LEGACY="connAuthFile=${CONFIGPATH}/conn.auth connDisableAuthentication=false" + ;; + E) + if ! [[ -f ${CONFIGPATH}/cert.pem && -f ${CONFIGPATH}/key.pem ]] ; then + print_error_and_exit "To use TLS encryption, a config path (option -f) that contains a \"cert.pem\" and a \"key.pem\" file and is available on all nodes needs to be specified before the option -E" + fi + TLS_DISABLE="--tls-disable=false" + TLS_CERT_FILE="--tls-cert-file ${CONFIGPATH}/cert.pem" + TLS_KEY_FILE="--tls-key-file ${CONFIGPATH}/key.pem" + ;; + H) + if ! [[ -f ${CONFIGPATH}/license.pem ]] ; then + print_error_and_exit "To use enterprise features, a config path (option -f) that contains a \"license.pem\" file and is available on all nodes needs to be specified before the option -H" + fi + LICENSE_FILE="--license-cert-file ${CONFIGPATH}/license.pem" + ;; + \?) + echo "ERROR: invalid option: -${OPTARG}" >&2 + print_usage_and_exit + ;; + :) + echo "ERROR: Option -${OPTARG} requires an argument" >&2 + print_usage_and_exit + ;; + esac + done + + if [ "${USE_PDSH}" = "true" ] + then + PDSH=${PDSH:-${DEFAULT_PDSH_PATH}} + + if [ -z "${PDSH}" ]; then + echo "Unable to autodetect pdsh. Please specify using the -b option." + exit 1 + fi + fi + + check_hostfile + check_datapath + check_mountpoint + + if [ "${STORAGE_MIRROR}" = "true" ] && [ "${PREFER_LOCAL}" = "true" ] + then + print_error_and_exit "Options -k and -l are mutually exclusive." + fi + + if [ "${TARGETFILE}" != "" ] + then + check_targetfile + fi + + + print_info "Using status information file: ${STATUSFILE}" + + NODECOUNT=$(grep -v '^$' ${HOSTFILE} | uniq | wc -l) #ignore empty lines + NODES=( $(grep -v '^$' ${HOSTFILE} | uniq) ) #store as array and ignore empty lines + + # make list of all nodes first - needed for clients and tmpfs mounts + ALLNODES=$(IFS=,; echo "${NODES[*]}") + + if [ "${USE_PDSH}" = "true" ] + then + # check all nodes for reachability and working PDSH + check_pdsh "${ALLNODES}" + else + # check reachability of all nodes + for HOST in "${NODES[@]}" + do + check_reachability "${HOST}" + done + fi + + check_statusfile "${ALLNODES}" + + # if the number of meta servers given is 0 or greater than node count, start it on all hosts + if [ ${NUM_META_SERVER} -eq 0 ] || [ ${NUM_META_SERVER} -gt "${NODECOUNT}" ] + then + NUM_META_SERVER=${NODECOUNT} + print_info "Number of metadata servers automatically set to ${NUM_META_SERVER}" + fi + + # if the number of storage servers given is 0 or greater than node count, start it on hosts + if [ ${NUM_STORAGE_SERVER} -eq 0 ] || [ ${NUM_STORAGE_SERVER} -gt "${NODECOUNT}" ] + then + NUM_STORAGE_SERVER=${NODECOUNT} + print_info "Number of storage servers automatically set to ${NUM_STORAGE_SERVER}" + fi + + # create the log path on all nodes if it doesn't exist yet + # without an existing logfile path, the server won't start up + create_log_path "${NODES[@]}" + + # take the first host as master host + MASTERHOST=${NODES[0]} + + # delete STATUS_FILE + execute_ssh_cmd "${MASTERHOST}" "rm -f ${STATUSFILE}" + + # mount tmpfs + if [ "${USE_TMPFS}" = "true" ] + then + start_tmpfs "${ALLNODES}" "${DATA_PATH}" + fi + + # MASTERHOST is also mgmtd host + MGMTD=${MASTERHOST} + + # The gRPC port for MGMTD, important for CTL + MGMTD_GRPC_PORT=$((MGMTD_GRPC_PORT+PORT_SHIFT)) + + # Combine variables relevant to CTL into one + CTL_GLOBAL_PARAMS="--mgmtd-addr ${MGMTD}:${MGMTD_GRPC_PORT} ${TLS_DISABLE} ${TLS_CERT_FILE} ${CONNAUTH_FLAG}" + + # port shift and config path may be empty, but that's ok + start_mgmtd "${MGMTD}" "${DATA_PATH}" "${PORT_SHIFT}" "${CONFIGPATH}" + + # take the first NUM_STORAGE_SERVER as storage servers + STORAGENODES=$(IFS=,; echo "${NODES[*]:0:${NUM_STORAGE_SERVER}}") + + # port shift and config path may be empty, but that's ok + start_storage_servers "${STORAGENODES}" "${DATA_PATH}" "${MGMTD}" "${PORT_SHIFT}" "${CONFIGPATH}" + + # take the first NUM_META_SERVER as metadata servers + METANODES=$(IFS=,; echo "${NODES[*]:0:${NUM_META_SERVER}}") + + # port shift and config path may be empty, but that's ok + start_meta_servers "${METANODES}" "${DATA_PATH}" "${MGMTD}" "${PORT_SHIFT}" "${CONFIGPATH}" + + # give the management daemon some time to get all information from servers + wait_online_good storage "${NUM_STORAGE_SERVER}" + wait_online_good meta "${NUM_META_SERVER}" + + # enable mirroring + if [ "${STORAGE_MIRROR}" = "true" ] + then + if ! ${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + mirror autocreate storage > /dev/null + then + print_error_and_exit "Unable to create storage target buddy mirror groups." + fi + + # all metadata servers need to know about the storage mirror groups + sleep 8 + fi + + if [ "${META_MIRROR}" = "true" ] + then + if ! ${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + mirror autocreate meta > /dev/null + then + print_error_and_exit "Unable to create metadata server buddy mirror groups." + fi + + # all metadata servers need to know about the newly created mirror groups + sleep 8 + + if ! ${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + mirror init --yes > /dev/null + then + print_error_and_exit "Unable to enable metadata mirroring." + fi + + # wait for initial resync + wait_online_good meta "${NUM_META_SERVER}" + fi + + # take all hosts as client + # port shift and config path may be empty, but that's ok + start_clients "${ALLNODES}" "${MGMTD}" "${MOUNTPOINT}" "${PORT_SHIFT}" "${CONFIGPATH}" + + if [ "${ASSIGN_STORAGE_POOL_DIRS}" = "true" ] && [ "${TARGETFILE}" != "" ] + then + assign_storage_pool_dirs + fi + + if [ "${STORAGE_MIRROR}" = "true" ] + then + if ! ${CTL_BIN} ${CTL_GLOBAL_PARAMS} \ + entry set --chunk-size 512ki --num-targets 4 --pattern mirrored "${MOUNTPOINT}" > /dev/null + then + print_error_and_exit "Unable to enable mirroring pattern." + fi + fi + + echo " ****************************************************************************** " + echo "* BeeOND setup finished successfully! To configure the \`beegfs\` command line" + echo "* utility to talk to the BeeOND mgmtd service, some additional configuration" + echo "* might be necessary." + echo "*" + echo "* If there is more than one BeeGFS mounted on the node that runs \`beegfs\`," + echo "* the correct mgmtd will need to be configured by using" + echo "* --mgmtd-addr \"${MGMTD}:${MGMTD_GRPC_PORT}\" or" + echo "* export BEEGFS_MGMTD_ADDR=\"${MGMTD}:${MGMTD_GRPC_PORT}\"" + if ! [[ -z ${CONNAUTH_FLAG} ]] + then + echo "*" + echo "* To configure \`beegfs\` to use the correct connection authentication file," + echo "* please use" + echo "* --auth-file \"${CONFIGPATH}/conn.auth\" or" + echo "* export BEEGFS_AUTH_FILE=\"${CONFIGPATH}/conn.auth\"" + fi + if ! [[ -z ${TLS_DISABLE} ]] + then + echo "*" + echo "* To configure \`beegfs\` to use TLS encryption when talking to mgmtd, use" + echo "* --tls-cert-file \"${CONFIGPATH}/cert.pem\" or" + echo "* export BEEGFS_TLS_CERT_FILE=\"${CONFIGPATH}/cert.pem\"" + fi + echo " ****************************************************************************** " +} + +do_stop() +{ + DELETE_DATA="false" + USE_PDSH="false" + DELETE_LOGS="false" + QUIET="false" + CLEANUP="false" + + while getopts "di:n:Pb:Lcq" opt; do + case $opt in + n) + HOSTFILE=${OPTARG} + ;; + i) + STATUSFILE=${OPTARG} + ;; + d) + DELETE_DATA="true" + ;; + c) + CLEANUP="true" + ;; + P) + USE_PDSH="true" + ;; + b) + PDSH=${OPTARG} + ;; + L) + DELETE_LOGS="true" + ;; + q) + QUIET="true" + ;; + \?) + echo "ERROR: invalid option: -${OPTARG}" >&2 + print_usage_and_exit + ;; + :) + echo "ERROR: Option -${OPTARG} requires an argument" >&2 + print_usage_and_exit + ;; + esac + done + + if [ "${USE_PDSH}" = "true" ] + then + PDSH=${PDSH:-${DEFAULT_PDSH_PATH}} + + if [ -z "${PDSH}" ]; then + echo "Unable to autodetect pdsh. Please specify using the -b option." + exit 1 + fi + fi + + check_hostfile + + print_info "Using status information file: ${STATUSFILE}" + + NODES=( $(grep -v '^$' ${HOSTFILE} | uniq) ) #store as array and ignore empty lines + ALLNODES=$(IFS=,; echo "${NODES[*]}") + + if [ "${USE_PDSH}" = "true" ] + then + # check all nodes for reachability and working PDSH + check_pdsh "${ALLNODES}" + else + # check reachability of all nodes + for HOST in ${NODES[*]} + do + check_reachability "${HOST}" + done + fi + + # take the first host as master host + MASTERHOST=${NODES[0]} + + ALLNODES=$(IFS=,; echo "${NODES[*]}") + + # read status file on master host and stop all servers + unmount_clients "${ALLNODES}" + + # read status file on master host and stop all servers + stop_procs "${ALLNODES}" ${DELETE_DATA} ${DELETE_LOGS} +} + +# print help if no arguments given +if [ $# -eq 0 ] ; then + print_usage_and_exit +fi + + +# parse arguments +ACTION=$1 + +if [ "${ACTION}" = "start" ] +then + shift + do_start "$@" +elif [ "${ACTION}" = "stop" ] +then + shift + ERROR="false" # store if we encountered an error, so that we can return a statuscode + # (because the stop function does not abort on error) + do_stop "$@" + if [ "${ERROR}" = "true" ] + then + exit 1 + fi; +elif [ "${ACTION}" = "stoplocal" ] +then + shift + do_stoplocal "$@" + exit $? +else + print_usage_and_exit +fi diff --git a/beeond/source/beeond-cp b/beeond/source/beeond-cp new file mode 100755 index 0000000..9663b16 --- /dev/null +++ b/beeond/source/beeond-cp @@ -0,0 +1,354 @@ +#!/bin/bash + +# Source helper script. +ABSOLUTE_PATH=$(dirname "$(readlink -e "$0")") # using readlink, because somone might be calling + # this script using a symlink + +if [ -e "${ABSOLUTE_PATH}/../lib/beeond-lib" ] +then + BEEOND_LIB="${ABSOLUTE_PATH}/../lib/beeond-lib" +else + BEEOND_LIB="${ABSOLUTE_PATH}/../scripts/lib/beeond-lib" +fi + +#shellcheck source=scripts/lib/beeond-lib +source "${BEEOND_LIB}" +PARALLEL="${ABSOLUTE_PATH}/../thirdparty/parallel/parallel" + +# Print usage. +print_usage_and_exit() +{ + echo "" + echo "BeeOND copy (http://www.beegfs.com)" + echo "" + echo "DESCRIPTION:" + echo " BeeGFS OnDemand copying/staging system." + echo "" + echo "USAGE: $(basename "$0") " + echo "" + echo "ACTIONS:" + echo " The first argument to $(basename "$0") is considered to be an action that the script" + echo " should perform." + echo "" + echo " The following actions are available:" + echo "" + echo " stagein: (EXPERIMENTAL)" + echo " Stage a complete directory from the global storage in to BeeOND." + echo "" + echo " Mandatory arguments:" + echo " -n FILENAME => File containing the list of nodes where the parallel" + echo " copy should be performed. All nodes must have access to" + echo " the global and local directories." + echo " -g PATH => Directory on global storage. (*)" + echo " -l PATH => Directory on local (BeeOND) storage. (*)" + echo "" + echo " Notes:" + echo " (*) Global and local directories have to be specified in form of an" + echo " absolute path." + echo "" + echo " stageout: (EXPERIMENTAL)" + echo " Stage a complete directory out from BeeOND to the global storage." + echo " Only changes will be staged out of the local directory and committed to" + echo " the global directory." + echo "" + echo " Mandatory arguments:" + echo " -l PATH => Local directory." + echo "" + echo " Notes:" + echo " The contents will be completely synchronized, i.e. deleted files on " + echo " BeeOND will get deleted on global storage, too." + echo "" + echo " copy:" + echo " Perform a parallel copy of a set of files or folders." + echo " Files will be copied into the target directory, folders will be copied" + echo " recursively. The copy process is parallelized across the set of nodes" + echo " specified in the nodefile." + echo "" + echo " Mandatory arguments:" + echo " -n FILENAME => File containing the list of nodes where the parallel" + echo " copy should be performed. All nodes must have access to" + echo " the sources and the target directory." + echo "" + echo " Notes:" + echo " Further command line arguments are consdiered source directory or file" + echo " names. The last command line argument specifies the target directory" + echo "" + echo "EXAMPLES:" + echo " Stage data from /mnt/beegfs-global/dataset in to BeeOND mounted at /mnt/beeond," + echo " using the nodes given in /tmp/nodefile:" + echo " beeond-cp stagein -n /tmp/nodefile -g /mnt/beegfs-global/dataset -l /mnt/beeond" + echo "" + echo " Stage out modified data from BeeOND mounted at /mnt/beeond to the global " + echo " storage:" + echo " beeond-cp stageout -n /tmp/nodefile -g /mnt/beegfs-global/dataset -l /mnt/beeond" + echo "" + echo " Recursively copy the directories dir_1 and dir_2 to /mnt/beegfs, using the nodes" + echo " in /tmp/nodefile:" + echo " beeond-cp copy -n /tmp/nodefile dir_1 dir_2 /mnt/beegfs" + echo "" + echo "NOTE:" + echo " BeeOND copy uses GNU Parallel -" + echo " When using programs that use GNU Parallel to process data for publication" + echo " please cite:" + echo " O. Tange (2011): GNU Parallel - The Command-Line Power Tool," + echo " ;login: The USENIX Magazine, February 2011:42-47." + echo "" + echo " SSH is used to log into the nodes specified in the nodefile. Please make" + echo " sure your SSH configuration allows for enough concurrent sessions and pending" + echo " logins. You might have to (ask your admin to) raise the MaxSessions and" + echo " MaxStartups settings in the sshd_config file." + echo "" + echo " Also please make sure you have the access rights needed to write to the" + echo " global store. Otherwise the stage-out might fail. Note that the access rights" + echo " in the BeeOND local store do not necessarily reflect those in the global" + echo " store." + + exit 1 +} + +### main functions +do_start() +{ + local NODEFILE="${1}" + local GLOBAL_PATH="${2}" + local LOCAL_PATH="${3}" + + beeond_print_info "BeeOND startup..." + + beeond_print_info "nodefile: ${NODEFILE}" + beeond_print_info "global path: ${GLOBAL_PATH}" + beeond_print_info "local path: ${LOCAL_PATH}" + + MISSING_PARAM=0 + if [ "${NODEFILE}" = "" ] + then + beeond_print_error "No nodefile specified." + MISSING_PARAM=1 + fi + if [ "${GLOBAL_PATH}" = "" ] + then + beeond_print_error "Global path not specified." + MISSING_PARAM=1 + fi + if [ "${LOCAL_PATH}" = "" ] + then + beeond_print_error "Local path not specified." + MISSING_PARAM=1 + fi + + # Expand relative path to nodefile. + if [ ! "${NODEFILE:0:1}" = "/" ] + then + NODEFILE="${PWD}/${NODEFILE}" + fi + + if [ ! -e "${NODEFILE}" ] + then + beeond_print_error_and_exit "Node file does not exist." + fi + + # The paths to the global and local directory have to be specified as absolute paths to prevent + # user errors (like copying a lot of files to ~/mnt/beeond). + if [ ! "${GLOBAL_PATH:0:1}" = "/" ] || [ ! "${LOCAL_PATH:0:1}" = "/" ] + then + beeond_print_error_and_exit "Global path and local path have to be absolute." + fi + + [ "${MISSING_PARAM}" = "1" ] && exit 1 + + # Make sure target directory is empty before starting. + if [ -e "${LOCAL_PATH}" ] + then + [ -d "${LOCAL_PATH}" ] \ + || beeond_print_error_and_exit "Target path is not a directory." + + find "${LOCAL_PATH}" -maxdepth 0 -type d -empty | read -r _ \ + || beeond_print_error_and_exit "Target directory is not empty." + else + mkdir -p "${LOCAL_PATH}" \ + || beeond_print_error_and_exit "Cannot create target directory." + fi + + local CONCURRENCY=$(( $(wc -l < "${NODEFILE}") )) + beeond_print_info "Concurrency: ${CONCURRENCY}" + + beeond_print_info "Writing session information." + beeond_save_session_info "${NODEFILE}" "${GLOBAL_PATH}" + + beeond_print_info "Starting stage-in..." + NODES=( $(grep -v '^$' "${NODEFILE}" | uniq) ) # Store as array and ignore empty lines. + NODELIST=$(IFS=,; echo "${NODES[*]}") # turn argument list into comma-separated string for PDSH + + beeond_stage_in "${GLOBAL_PATH}" "${LOCAL_PATH}" "${NODELIST}" ${CONCURRENCY} + + beeond_print_info "Done." +} + +do_stop() +{ + local LOCAL_PATH="${1}" + + if [ "${LOCAL_PATH}" = "" ] + then + beeond_print_error_and_exit "No path specified." + fi + + # Expand relative local path. + # Note: Don't have to ensure that it's an absolute path here: We confirm it's a BeeOND instance + # by looking for the session info file. + if [ ! "${LOCAL_PATH:0:1}" = "/" ] + then + LOCAL_PATH="${PWD}/${LOCAL_PATH}" + fi + + # Read parameters from session info file. + NODEFILE=$(grep NodeFile "${LOCAL_PATH}/${BEEOND_SESSION_FILE}" | cut -d = -f 2-) + GLOBAL_PATH=$(grep GlobalPath "${LOCAL_PATH}/${BEEOND_SESSION_FILE}" | cut -d = -f 2-) + + if [ "${NODEFILE}" = "" ] + then + beeond_print_error "Error reading node file name from session file." + MISSING_PARAM=1 + fi + if [ "${GLOBAL_PATH}" = "" ] + then + beeond_print_error "Error reading global path from session file." + MISSING_PARAM=1 + fi + + [ "${MISSING_PARAM}" = "1" ] && exit 1 + + if [ ! -e "${NODEFILE}" ] + then + beeond_print_error_and_exit "Node file does not exist." + fi + + beeond_print_info "BeeOND shutdown..." + + beeond_print_info "nodefile: ${NODEFILE}" + beeond_print_info "global path: ${GLOBAL_PATH}" + beeond_print_info "local path: ${LOCAL_PATH}" + + NODES=( $(grep -v '^$' "${NODEFILE}" | uniq) ) # Store as array and ignore empty lines. + NODELIST=$(IFS=,; echo "${NODES[*]}") + + local CONCURRENCY=$(( $(wc -l < "${NODEFILE}") )) + beeond_print_info "Concurrency: ${CONCURRENCY}" + + beeond_stage_out "${GLOBAL_PATH}" "${LOCAL_PATH}" "${NODELIST}" ${CONCURRENCY} + + beeond_print_info "Done." +} + +do_copy() +{ + local NODEFILE="${1}" + shift + + beeond_print_info "BeeOND copy..." + + if [ "${NODEFILE}" = "" ] + then + beeond_print_error "No nodefile specified." + fi + + # Expand relative path to nodefile. + if [ ! "${NODEFILE:0:1}" = "/" ] + then + NODEFILE="${PWD}/${NODEFILE}" + fi + + if [ ! -e "${NODEFILE}" ] + then + beeond_print_error_and_exit "Node file does not exist." + fi + + NODES=( $(grep -v '^$' "${NODEFILE}" | uniq) ) # Store as array and ignore empty lines. + NODELIST=$(IFS=,; echo "${NODES[*]}") + + local CONCURRENCY=$(( $(wc -l < "${NODEFILE}") )) + beeond_print_info "Concurrency: ${CONCURRENCY}" + + beeond_copy "${NODELIST}" "${CONCURRENCY}" "$@" +} + +# Print help if no arguments given. +if [ $# -eq 0 ] ; then + print_usage_and_exit +fi + +# Do it. +ACTION="${1}" + +if [ "${ACTION}" = "stagein" ] +then + shift + while getopts ":n:g:l:" opt; do + case $opt in + n) + NODEFILE="${OPTARG}" + ;; + g) + GLOBAL_PATH="${OPTARG}" + ;; + l) + LOCAL_PATH="${OPTARG}" + ;; + \?) + beeond_print_error_and_exit "Invalid option: -${OPTARG}." + ;; + :) + beeond_print_error_and_exit "Option -${OPTARG} requires an argument." + ;; + esac + done + + do_start "${NODEFILE}" "${GLOBAL_PATH}" "${LOCAL_PATH}" +elif [ "${ACTION}" = "stageout" ] +then + shift + while getopts ":l:" opt; do + case $opt in + l) + LOCAL_PATH="${OPTARG}" + ;; + \?) + beeond_print_error_and_exit "Invalid option: -${OPTARG}." + ;; + :) + beeond_print_error_and_exit "Option -${OPTARG} requires an argument." + ;; + esac + done + + do_stop "${LOCAL_PATH}" +elif [ "${ACTION}" = "copy" ] +then + shift + + # Nodefile has to be given as the only command line argument. + if getopts ":n:" opt + then + if [ "$opt" = "n" ] + then + NODEFILE="${OPTARG}" + else + beeond_print_error_and_exit "Invalid option: -${opt}" + fi + + else + beeond_print_error_and_exit "No nodefile specified." + fi + + # All following command line arguments are file or directory names, specifying the sources and + # the target of the copy ancion + shift # shift out -n parameter + shift # shift out name of node file + + do_copy "${NODEFILE}" "$@" +elif [ "${ACTION}" = "info" ] +then + do_print_info +else + print_usage_and_exit +fi diff --git a/build/Makefile b/build/Makefile new file mode 100644 index 0000000..c3bc71f --- /dev/null +++ b/build/Makefile @@ -0,0 +1,598 @@ +##### BASE MAKEFILE +# +# This makefile is intended to be a basis on which new makefiles are written. +# It is divided into five parts: +# * help text generation +# * path for subproject locations and thirparty include/library paths +# * default values for tools (like CXX) and compiler/linker flags +# * description of known libraries that may be used in the build +# * functions that define build artifacts +# +# library handling will be described in detail later. +# +# we have three functions that defined build artifacts: +# * build-executable +# * build-static-library +# * build-dynamic-library +# +# they all have the same, basic signature: +# (name: string, sources: list, usedLibraries: list) +# +# $name determines the name given to the output file (for executables) or the library name given +# to the output library (for libraries). +# +# $sources must be a list of source files (allowed types are .cpp and .c), relative to the directory +# of the Makefile you are writing (see example below). +# +# $usedLibraries is a (possibly empty) list of libraries as defined in the KNOWN LIBRARIES section +# below. they will be automatically added to compiler and linker invocations as needed. +# +# EXAMPLE: +# +# imagine you have a project with a directory structure like this: +# - /build +# - Makefile +# - /source +# - /lib +# - ... +# - /exe +# - ... +# +# you want your Makefile to build a shared library from /source/lib and an executable from +# /source/exe. the you could write your Makefile like this: +# +# include ,../../build/Makefile +# +# # will generate a libFancy.so in /build, uses libz +# $(call build-static-library,\ +# Fancy,\ +# $(shell find ../source/lib -iname '*.cpp'),\ +# z) +# +# # this registers libFancy as a shared library. see details below (KNOWN LIBRARIES). +# $(call define-dep-lib, Fancy, -I ../source/lib/include, -L . -l Fancy) +# +# # will generate an executable file Run in /build, uses libFancy +# $(call build-executable,\ +# Run,\ +# $(shell find ../source/exe -iname '*.cpp'),\ +# Fancy) +# +# # a dependency must be added between Run and Fancy, to ensure that Fancy is built first. +# Run: | libFancy.so + +override V := $(if $V,,@) + +all: + +define HELP_ARGS_GENERIC + @echo ' BEEGFS_DEBUG=1 Enables debug information and symbols' + @echo ' BEEGFS_DEBUG_OPT=1 Enables internal debug code, but compiled' + @echo ' with optimizations' + @echo ' BEEGFS_DEBUG_IP=1 Enables low-level debug of network sendto' + @echo ' and recvfrom' + @echo ' CXX= Specifies a c++ compiler' + @echo ' DISTCC=distcc Enables the usage of distcc' + @echo ' V=1 Print command lines of tool invocations' + @echo ' BEEGFS_COMMON_PATH= Path to the common directory' + @echo ' BEEGFS_THIRDPARTY_PATH=' + @echo ' Path to the thirdparty directory' + @echo ' BEEGFS_USE_PCH=1 Enables use of precompiled headers' +endef +define HELP_TARGETS_GENERIC + @echo ' all (default) build only' + @echo ' clean delete compiled files' + @echo ' help print this help message' +endef + +help: + @echo 'Optional arguments:' + $(HELP_ARGS_GENERIC) + $(HELP_ARGS_SPECIFIC) + @echo + @echo 'Targets:' + $(HELP_TARGETS_GENERIC) + $(HELP_TARGETS_SPECIFIC) + +root_dir := $(realpath $(dir $(lastword $(MAKEFILE_LIST)))/..) +build_dir := $(realpath $(dir $(firstword $(MAKEFILE_LIST)))) + +BEEGFS_COMMON_PATH ?= $(root_dir)/common +BEEGFS_COMMON_PCH_H ?= $(BEEGFS_COMMON_PATH)/source/common/pch.h +BEEGFS_THIRDPARTY_PATH ?= $(root_dir)/thirdparty +BEEGFS_COMM_DEBUG_PATH ?= $(root_dir)/comm_debug +BEEGFS_FSCK_PATH ?= $(root_dir)/fsck +BEEGFS_EVENT_LISTENER_PATH ?= $(root_dir)/event_listener +BEEGFS_DEVEL_INCLUDE_PATH ?= $(root_dir)/client_devel/include +BEEGFS_CLIENT_PATH ?= $(root_dir)/client_module/include + +BOOST_INC_PATH ?= $(BEEGFS_THIRDPARTY_PATH)/source/boost +NU_INC_PATH ?= $(BEEGFS_THIRDPARTY_PATH)/source/nu/include + +GTEST_INC_PATH ?= $(BEEGFS_THIRDPARTY_PATH)/source/gtest/googletest/include +GTEST_LIB_PATH ?= $(BEEGFS_THIRDPARTY_PATH)/build + +ifneq ($(target_arch),) + STRIP := $(target_arch)-strip + AR := $(target_arch)-ar + CC := $(target_arch)-gcc + CXX := $(target_arch)-g++ +endif + +SHELL := /bin/bash +STRIP ?= strip +CXX ?= g++ +AR ?= ar +CLANG_TIDY ?= clang-tidy + +# if -T is supported by ar, use it. thin archives are quicker to create and maintain. +ifeq ($(shell ar -TM 2>&1 <<<""),) +AR += -T +endif + +CXXFLAGS = \ + -std=c++17 -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 \ + -isystem $(BOOST_INC_PATH) -isystem $(NU_INC_PATH) \ + -pthread \ + -fno-strict-aliasing -fmessage-length=0 \ + -Wall -Wextra -Wunused-variable -Woverloaded-virtual -Wno-unused-parameter -Wuninitialized \ + -Wno-missing-field-initializers \ + -Winvalid-pch \ + -ggdb3 \ + $(USER_CXXFLAGS) +CXXFLAGS_RELEASE = -O3 -Wuninitialized +CXXFLAGS_DEBUG = -O1 -D_FORTIFY_SOURCE=2 \ + -DBEEGFS_DEBUG -DDEBUG_READWRITE -DDEBUG_MUTEX_LOCKING -DDEBUG_REFCOUNT \ + -DDEBUG_BACKTRACE -DLOG_DEBUG_MESSAGES +CXXFLAGS_COVERAGE = --coverage -O1 -fno-inline + +LDFLAGS = -std=c++1y \ + -rdynamic \ + -pthread -lrt \ + -Wl,-rpath,'$$ORIGIN/../lib' \ + $(USER_LDFLAGS) +LDFLAGS_COVERAGE = --coverage + +ifneq ($(BEEGFS_DEBUG),) +CXXFLAGS += $(CXXFLAGS_DEBUG) +else ifneq ($(BEEGFS_COVERAGE),) +CXXFLAGS += $(CXXFLAGS_COVERAGE) +LDFLAGS += $(LDFLAGS_COVERAGE) +else +CXXFLAGS += $(CXXFLAGS_RELEASE) +endif + + +CXXFLAGS += -DBEEGFS_VERSION="\"$(BEEGFS_VERSION)\"" + +ifeq ($(BEEGFS_NVFS),1) +CXXFLAGS += -DBEEGFS_NVFS +endif + +ifeq ($(BEEGFS_DEBUG_RDMA),1) +CXXFLAGS += -DBEEGFS_DEBUG_RDMA +endif + +ifneq ($(BEEGFS_DEBUG_IP),) +CXXFLAGS += -DBEEGFS_DEBUG_IP +endif + +###### KNOWN LIBRARIES +# +# this is our local registry of known libraries, both our own and thirdparty libraries. +# +# currently supports building with our own libraries common and client-devel. +# thirdparty packages supported are: +# * dl (from system) +# +# to define new libraries, add appriopriate calls to define-dep-lib in this section. +# the signature of define-dep-lib is: +# (name: string, CXXFLAGS: string, libraryPath: string[, LDFLAGS: string]) +# +# $name is used to identify the library within this registry, and is otherwise unused. +# +# $CXXFLAGS will be added to the set of CXXFLAGS used when compiling files from projects that use +# this library (see example at the beginning of this file for reference). +# +# $libraryPath is the path (absolute or relative) to the library. glob patterns are allowed, and in +# fact required if you want to link a dynamic library. +# +# $LDFLAGS will be added to the set of LDFLAGS used when linking shared libraries or executables +# that use this library. if the library you want to link is static, LDFLAGS need not be set - the +# library will be added to the list of archives. +# +# +# to define libraries that are taken from the system, use define-dep-lib-system. it will use its +# arguments to ask pkgconfig for the correct compiler and linker flags. then signature of +# define-dep-lib-system is: +# (name: string, pkgconfigID: string) +# +# $name is used only to identify the library. +# +# $pkgconfigID is the identifier pkgconfig will be asked about. +define resolve-dep-cflags +$(if $(filter undefined,$(origin _DEP_LIB_CXX[$(strip $1)])),\ + $(error I have no CXXFLAGS for $1!),\ + $(_DEP_LIB_CXX[$(strip $1)])) +endef +define resolve-dep-ldflags +$(if $(filter undefined,$(origin _DEP_LIB_LD[$(strip $1)])),\ + $(error I have no LDFLAGS for $1!),\ + $(_DEP_LIB_LD[$(strip $1)])) +endef +define resolve-dep-deps +$(if $(filter undefined,$(origin _DEP_LIB_DEPS[$(strip $1)])),\ + $(error I have no library dependencies for $1!),\ + $(_DEP_LIB_DEPS[$(strip $1)])) +endef +define define-dep-lib +$(strip + $(eval _DEP_LIB_CXX[$(strip $1)] = $(strip $2)) + $(eval _DEP_LIB_LD[$(strip $1)] = $(strip $(or $4, $3))) + $(eval _DEP_LIB_DEPS[$(strip $1)] = $(strip $3)) + $(eval $(strip $3): )) +endef +define define-dep-lib-system +$(strip + $(if $(shell pkg-config --exists $2 || echo fail), + $(error Could not find library $2 in system!)) + $(call define-dep-lib, + $1, + $(shell pkg-config --cflags $2), + $(shell pkg-config --libs $2))) +endef + +# don't bother to search the system for libraries if we only run `make clean' +# this also has the nice side effect that libraries needn't be present in the system +# when `make clean' is run - if we override dependency resolution to not do anything. +ifeq ($(strip $(MAKECMDGOALS)),clean) + resolve-dep-cflags := + resolve-dep-ldflags := + resolve-dep-deps := +else + $(call define-dep-lib, common,\ + -I $(BEEGFS_COMMON_PATH)/source \ + -I $(BEEGFS_CLIENT_PATH), \ + -ldl $(BEEGFS_COMMON_PATH)/build/libbeegfs-common.a) + + $(call define-dep-lib, client-devel,\ + -I $(BEEGFS_DEVEL_INCLUDE_PATH),) + + $(call define-dep-lib, dl, , , -ldl) + + $(call define-dep-lib, rdma, , -lrdmacm -libverbs) + + $(call define-dep-lib, gtest,\ + -isystem $(GTEST_INC_PATH)/include,\ + $(GTEST_LIB_PATH)/libgtest.a) + + $(call define-dep-lib, cassandra, -I$(BEEGFS_THIRDPARTY_PATH)/source/datastax) + + $(call define-dep-lib-system, curl, libcurl) + $(call define-dep-lib-system, z, zlib) + + $(call define-dep-lib, blkid, , , -lblkid) + $(call define-dep-lib, uuid, , , -luuid) + $(call define-dep-lib-system, nl3-route, libnl-route-3.0) +endif + + +clean: + @$(RM) -rf $(if $V,,-v) $(CLEANUP_FILES) + + +NONDEP_MAKEFILES = $(filter-out %.d,$(MAKEFILE_LIST)) + +%.autogen.h: + @# Generate a proxy-header for precompilation. + @true > $@ # truncate file + @echo '/* Autogenerated precompiled header (PCH)' >> $@ + @echo ' * For each set of compiler options we need to compile ' >> $@ + @echo ' * a PCH separately. To realize that, we create ' >> $@ + @echo ' * proxy PCHs like this file and compile these. */' >> $@ + @echo '#include "$(BEEGFS_COMMON_PCH_H)"' >> $@ + +%.h.gch: %.h + @echo "[CXX] $@" + $V$(CXX) $(CXXFLAGS) -c $< -E -MMD -MP -MF$<.d -MT$@ -o/dev/null + $V$(DISTCC) $(CXX) $(CXXFLAGS) -o$@ -c $(realpath $<) + +%.cpp.o: %.cpp $(NONDEP_MAKEFILES) + @echo "[CXX] $<" + $V$(DISTCC) $(CXX) $(CXXFLAGS) -MMD -MF$<.d -MT$<.o -MP -o $<.o -c $(realpath $<) + +%.c.o: %.c $(NONDEP_MAKEFILES) + @echo "[CXX] $<" + $V$(DISTCC) $(CXX) $(CXXFLAGS) -MMD -MF$<.d -MT$<.o -MP -o$<.o -c $(realpath $<) + +# first partial list: checks we want to exclude on a general basis right now +# second list: very minor problems we don't want to be bugged about yet, but want to fix over time +# third list: definite errors, fix as soon as possible +define -clang_tidy_checks +-clang-analyzer-alpha.deadcode.UnreachableCode +-fuchsia-default-arguments +-fuchsia-overloaded-operator +-google-build-using-namespace +-google-readability-braces-around-statements +-google-readability-casting +-google-readability-namespace-comments +-hicpp-braces-around-statements +-llvm-header-guard +-llvm-include-order +-llvm-namespace-comment +-readability-braces-around-statements +-readability-implicit-bool-conversion + +-android-cloexec-accept +-android-cloexec-creat +-android-cloexec-dup +-android-cloexec-epoll-create +-android-cloexec-fopen +-android-cloexec-open +-boost-use-to-string +-bugprone-branch-clone +-bugprone-exception-escape +-bugprone-narrowing-conversions +-bugprone-sizeof-expression +-cert-oop54-cpp +-clang-analyzer-core.CallAndMessage +-clang-analyzer-core.uninitialized.Assign +-clang-analyzer-cplusplus.NewDeleteLeaks +-clang-analyzer-deadcode.DeadStores +-clang-analyzer-optin.cplusplus.UninitializedObject +-clang-diagnostic-deprecated-copy +-cppcoreguidelines-avoid-c-arrays +-cppcoreguidelines-avoid-goto +-cppcoreguidelines-avoid-magic-numbers +-cppcoreguidelines-init-variables +-cppcoreguidelines-interfaces-global-init +-cppcoreguidelines-macro-usage +-cppcoreguidelines-narrowing-conversions +-cppcoreguidelines-non-private-member-variables-in-classes +-cppcoreguidelines-pro-bounds-array-to-pointer-decay +-cppcoreguidelines-pro-bounds-constant-array-index +-cppcoreguidelines-pro-bounds-pointer-arithmetic +-cppcoreguidelines-pro-type-const-cast +-cppcoreguidelines-pro-type-cstyle-cast +-cppcoreguidelines-pro-type-member-init +-cppcoreguidelines-pro-type-reinterpret-cast +-cppcoreguidelines-pro-type-static-cast-downcast +-cppcoreguidelines-pro-type-union-access +-cppcoreguidelines-pro-type-vararg +-cppcoreguidelines-special-member-functions +-fuchsia-default-arguments-calls +-fuchsia-default-arguments-declarations +-fuchsia-statically-constructed-objects +-google-default-arguments +-google-explicit-constructor +-google-readability-avoid-underscore-in-googletest-name +-google-readability-redundant-smartptr-get +-google-readability-todo +-google-runtime-int +-google-runtime-references +-hicpp-avoid-c-arrays +-hicpp-avoid-goto +-hicpp-deprecated-headers +-hicpp-explicit-conversions +-hicpp-member-init +-hicpp-multiway-paths-covered +-hicpp-no-array-decay +-hicpp-no-assembler +-hicpp-no-malloc +-hicpp-signed-bitwise +-hicpp-special-member-functions +-hicpp-uppercase-literal-suffix +-hicpp-use-auto +-hicpp-use-emplace +-hicpp-use-equals-default +-hicpp-use-equals-delete +-hicpp-use-noexcept +-hicpp-use-nullptr +-hicpp-vararg +-llvm-qualified-auto +-misc-misplaced-widening-cast +-misc-move-constructor-init +-misc-non-private-member-variables-in-classes +-misc-redundant-expression +-misc-string-compare +-misc-unused-parameters +-modernize-avoid-bind +-modernize-avoid-c-arrays +-modernize-loop-convert +-modernize-make-shared +-modernize-make-unique +-modernize-pass-by-value +-modernize-raw-string-literal +-modernize-replace-random-shuffle +-modernize-return-braced-init-list +-modernize-use-auto +-modernize-use-default +-modernize-use-default-member-init +-modernize-use-nodiscard +-modernize-use-emplace +-modernize-use-equals-default +-modernize-use-equals-delete +-modernize-use-noexcept +-modernize-use-nullptr +-modernize-use-trailing-return-type +-modernize-use-using +-performance-faster-string-find +-performance-inefficient-string-concatenation +-performance-unnecessary-copy-initialization +-performance-unnecessary-value-param +-readability-const-return-type +-readability-convert-member-functions-to-static +-readability-else-after-return +-readability-implicit-bool-cast +-readability-isolate-declaration +-readability-magic-numbers +-readability-make-member-function-const +-readability-named-parameter +-readability-non-const-parameter +-readability-qualified-auto +-readability-redundant-control-flow +-readability-redundant-smartptr-get +-readability-redundant-string-cstr +-readability-string-compare +-readability-uppercase-literal-suffix + +-cert-err58-cpp +-cert-err60-cpp +-cert-msc30-c +-cert-msc50-cpp +-clang-analyzer-alpha.cplusplus.VirtualCall +-clang-analyzer-core.DivideZero +-clang-analyzer-optin.cplusplus.VirtualCall +-cppcoreguidelines-no-malloc +-cppcoreguidelines-owning-memory +-readability-misleading-indentation +endef +clang_tidy_checks := *,$(shell echo $(strip $(-clang_tidy_checks)) | tr ' ' ',') + +# make-tidy-rule +# +# add a rule to invoke clang-tidy for the given source files +# arguments: +# #1: identifier for library/executable +# #2: source files +# #3: include paths and defines +make-tidy-rule = $(eval $(call -make-tidy-rule,$(strip $1),$2,$3)) +define -specific-tidy-rule +$1: + @echo "[TIDY] $2" + $V$(CLANG_TIDY) -checks='$(clang_tidy_checks)' -quiet $2 -- $$(CXXFLAGS) $3 +endef +define -make-tidy-rule +.PHONY: tidy +tidy: tidy-$1 + +.PHONY: tidy-$1 $(foreach file,$2,$(file).tidy-$1) +tidy-$1: $(foreach file,$2,$(file).tidy-$1) + +$(foreach file,$2,$(eval $(call -specific-tidy-rule,$(file).tidy-$1,$(file),$3))) +endef + +define -build-pch-fragment +ifneq ($(BEEGFS_USE_PCH),) +# .o files depend on precompiled headers. +$(addsuffix .o,$2): CXXFLAGS += -include $(1).autogen.h +$(addsuffix .o,$2): $(1).autogen.h.gch +endif +# not behind conditional -- let's delete these always +CLEANUP_FILES += $(1).autogen.h $(1).autogen.h.gch +CLEANUP_FILES += $(1).autogen.h.d # "dependency" files that get produced when compiling the header +endef + + +# build-executable +# +# define a new executable for the build +# arguments: +# #1: name of the executable +# #2: sources +# #3: required libraries +# #4: include directories +build-executable = $(eval $(call -build-executable-fragment,$(strip $1),$2,$3,$4)) +define -build-executable-fragment +all: $1 + +$(call -build-pch-fragment,$1,$2) + +CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2) + +$(addsuffix .o .tidy-%,$2): CXXFLAGS += \ + $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4) + +$1: LDFLAGS += \ + -Wl,--start-group $(foreach lib,$3,$(call resolve-dep-ldflags,$(lib))) -Wl,--end-group + +$1: $(addsuffix .o,$2) $(foreach lib,$3,$(call resolve-dep-deps,$(lib))) + @echo "[LD] $$@" + $$V$$(CXX) -o $$@ $(addsuffix .o,$2) $$(LDFLAGS) + +-include $(addsuffix .d,$2) + +$(call make-tidy-rule, $1, $2,\ + $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)) +endef + +# build-test +# +# build a test runner from specified test files. acts much like build-executable, except that it +# includes gtest in the build as a non-library .a +# #1: name if the test executable +# #2: sources +# #3: required libraries (not including gtest) +# #4: included directories (not including gtest) +define build-test +$(call build-executable, $1, $2, $3, $4 -isystem $(GTEST_INC_PATH)) + +$1: LDFLAGS += $(GTEST_LIB_PATH)/libgtest.a +endef + +# build-static-library +# +# define a new (static) library for the build +# arguments: +# #1: name of the library +# #2: sources +# #3: required libraries +# #4: include directories +build-static-library = $(eval $(call -build-static-library-fragment,lib$(strip $1).a,$2,$3,$4)) +define -build-static-library-fragment +all: $1 + +$(call -build-pch-fragment,$1,$2) + +CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2) + +$(addsuffix .o .tidy-%,$2): CXXFLAGS += \ + $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4) + +$(build_dir)/$1: $(addsuffix .o,$2) + @echo "[AR] $1" + @rm -f $$@ + $$V$(AR) -rcs $$@ $$^ + +$1: $(build_dir)/$1 + +-include $(addsuffix .d,$2) + +$(call make-tidy-rule, $1, $2,\ + $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)) +endef + +# build-shared-library +# +# define a new (shared) library for the build +# arguments: +# #1: name of the library +# #2: sources +# #3: required libraries +# #4: include directories +build-shared-library = $(eval $(call -build-shared-library-fragment,lib$(strip $1).so,$2,$3,$4)) +define -build-shared-library-fragment +all: $1 + +$(call -build-pch-fragment,$1,$2) + +CLEANUP_FILES += $1 $(addsuffix .o,$2) $(addsuffix .d,$2) + +$(addsuffix .o .tidy-%,$2): CXXFLAGS += \ + -fPIC $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4) + +$1: LDFLAGS += \ + -Wl,--start-group $(foreach lib,$3,$(call resolve-dep-ldflags,$(lib))) -Wl,--end-group + +$(build_dir)/$1: $(addsuffix .o,$2) $(foreach lib,$3,$(call resolve-dep-deps,$(lib))) + @echo "[LD] $1" + $$V$$(CXX) -shared -o $$@ $(addsuffix .o,$2) \ + -Wl,--whole-archive $$(LDFLAGS) -Wl,--no-whole-archive + +$1: $(build_dir)/$1 + +-include $(addsuffix .d,$2) + +$(call make-tidy-rule, $1, $2,\ + $(foreach lib,$3,$(call resolve-dep-cflags,$(lib))) $(addprefix -I, $4)) +endef diff --git a/build/filter-requires.sh b/build/filter-requires.sh new file mode 100755 index 0000000..16d496f --- /dev/null +++ b/build/filter-requires.sh @@ -0,0 +1,3 @@ +#!/bin/sh + +$* | sed -e '/libibverbs|librdmacm/ d' diff --git a/build/make-deb-common b/build/make-deb-common new file mode 100644 index 0000000..10b9734 --- /dev/null +++ b/build/make-deb-common @@ -0,0 +1,28 @@ +export TOP_PID=$$ + +function die() { + echo "${*}" >&2 + kill $TOP_PID +} + +function getVersion() { + dpkg-query -f '${Version}' -W "${1}" 2>/dev/null || die "Package ${1} is not installed." +} + +function isNewerVersion() { + local v=$(getVersion "${1}") + dpkg --compare-versions "${v}" \>= "${2}" +} + +function runDebuild() { +# build the package and supress lintian warnings. Lintian in Lenny cannot +# do that itself yet +# NOTE: package not signed yet! (-us -uc)` +yes | debuild -us -uc 2>&1 | egrep -v "dir-or-file-in-opt | file-in-unusual-dir" + +if $(isNewerVersion devscripts 2.16.10); then + yes | debuild -- clean +else + yes | debuild clean +fi +} diff --git a/client_devel/CMakeLists.txt b/client_devel/CMakeLists.txt new file mode 100644 index 0000000..7aafeed --- /dev/null +++ b/client_devel/CMakeLists.txt @@ -0,0 +1,11 @@ +install( + FILES "include/beegfs/beegfs.h" "include/beegfs/beegfs_ioctl.h" "include/beegfs/beegfs_ioctl_functions.h" + DESTINATION "usr/include/beegfs" + COMPONENT "client-devel" +) + +install( + FILES "build/dist/usr/share/doc/beegfs-client-devel/examples/createFileWithStripePattern.cpp" + DESTINATION "usr/share/doc/beegfs/examples/createFileWithStripePattern" + COMPONENT "client-devel" +) diff --git a/client_devel/build/Makefile b/client_devel/build/Makefile new file mode 100644 index 0000000..8c568e1 --- /dev/null +++ b/client_devel/build/Makefile @@ -0,0 +1,34 @@ +BEEGFS_COMMON_PATH ?= ../../common/ + +ifneq ($(BEEGFS_VERSION),) +BEEGFS_EXTRA_FLAGS += 'BEEGFS_VERSION="$(BEEGFS_VERSION)"' +endif + +ifneq ($(BEEGFS_DEBUG),) +BEEGFS_EXTRA_FLAGS += 'BEEGFS_DEBUG=$(BEEGFS_DEBUG)' +endif + + +all: + +clean: + +help: + @echo 'Optional Arguments:' + @echo ' BEEGFS_DEBUG=1:' + @echo ' Enables debug information and symbols.' + @echo ' CXX=:' + @echo ' Specifies a c++ compiler.' + @echo ' BEEGFS_COMMON_PATH=:' + @echo ' Path to the common directory.' + @echo + @echo 'Targets:' + @echo ' all (default) - build only' + @echo ' help - print this help message' + + +# Include dependency files +ifneq ($(DEPENDENCY_FILES),) +include $(DEPENDENCY_FILES) +endif + diff --git a/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/createFileWithStripePattern.cpp b/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/createFileWithStripePattern.cpp new file mode 100644 index 0000000..d83e18b --- /dev/null +++ b/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/createFileWithStripePattern.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include +#include +#include + + + +static const mode_t MODE_FLAG = S_IRWXU | S_IRGRP | S_IROTH; +static const unsigned numtargets = 8; +static const unsigned chunksize = 1048576; // 1 Mebibyte + + +int main(int argc, char** argv) +{ + // check if a path to the file is provided + if(argc != 2) + { + std::cout << "Usage: " << argv[0] << " $PATH_TO_FILE" << std::endl; + exit(-1); + } + + std::string file(argv[1]); + std::string fileName(basename(argv[1]) ); + std::string parentDirectory(dirname(argv[1]) ); + + // check if we got a file name from the given path + if(fileName.empty() ) + { + std::cout << "Can not get file name from given path: " << file << std::endl; + exit(-1); + } + + // check if we got the parent directory path from the given path + if(parentDirectory.empty() ) + { + std::cout << "Can not get parent directory path from given path: " << file << std::endl; + exit(-1); + } + + // open the directory to get a directory stream + DIR* parentDir = opendir(parentDirectory.c_str() ); + if(parentDir == NULL) + { + std::cout << "Can not get directory stream of directory: " << parentDirectory + << " errno: " << errno << std::endl; + exit(-1); + } + + // get a fd of the parent directory + int fd = dirfd(parentDir); + if(fd == -1) + { + std::cout << "Can not get fd from directory: " << parentDirectory + << " errno: " << errno << std::endl; + exit(-1); + } + + // check if the parent directory is located on a BeeGFS, because the striping API works only on + // BeeGFS (Results of BeeGFS ioctls on other file systems are undefined.) + bool isBeegfs = beegfs_testIsBeeGFS(fd); + if(!isBeegfs) + { + std::cout << "The given file is not located on an BeeGFS: " << file << std::endl; + exit(-1); + } + + // create the file with the given stripe pattern + bool isFileCreated = beegfs_createFile(fd, fileName.c_str(), MODE_FLAG, numtargets, chunksize); + if(isFileCreated) + { + std::cout << "File successful created: " << file << std::endl; + } + else + { + std::cout << "Can not create file: " << file << " errno: " << errno << std::endl; + exit(-1); + } +} diff --git a/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/getStripePatternOfFile.cpp b/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/getStripePatternOfFile.cpp new file mode 100644 index 0000000..9b6b08b --- /dev/null +++ b/client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/getStripePatternOfFile.cpp @@ -0,0 +1,108 @@ +#include + +#include +#include +#include + + + +static const mode_t MODE_FLAG = S_IRWXU | S_IRGRP | S_IROTH; +static const int OPEN_FLAGS = O_RDWR; + + +int main(int argc, char** argv) +{ + // check if a path to the file is provided + if(argc != 2) + { + std::cout << "Usage: " << argv[0] << " $PATH_TO_FILE" << std::endl; + exit(-1); + } + + std::string file(argv[1]); + + // open the provided file + int fd = open(file.c_str(), OPEN_FLAGS, MODE_FLAG); + if(fd == -1) + { + std::cout << "Open: can not open file: " << file << " errno: " << errno << std::endl; + exit(-1); + } + + // check if the file is located on a BeeGFS, because the striping API works only on a BeeGFS + // (Results of BeeGFS ioctls on other file systems are undefined.) + bool isBeegfs = beegfs_testIsBeeGFS(fd); + if(!isBeegfs) + { + std::cout << "The given file is not located on an BeeGFS: " << file << std::endl; + exit(-1); + } + + unsigned outPatternType = 0; + unsigned outChunkSize = 0; + uint16_t outNumTargets = 0; + + // retrive the stripe pattern of the file and print them to the console + bool stripeInfoRetVal = beegfs_getStripeInfo(fd, &outPatternType, &outChunkSize, &outNumTargets); + if(stripeInfoRetVal) + { + std::string patternType; + switch(outPatternType) + { + case BEEGFS_STRIPEPATTERN_RAID0: + patternType = "RAID0"; + break; + case BEEGFS_STRIPEPATTERN_RAID10: + patternType = "RAID10"; + break; + case BEEGFS_STRIPEPATTERN_BUDDYMIRROR: + patternType = "BUDDYMIRROR"; + break; + default: + patternType = "INVALID"; + } + std::cout << "Stripe pattern of file: " << file << std::endl; + std::cout << "+ Type: " << patternType << std::endl; + std::cout << "+ Chunksize: " << outChunkSize << " Byte" << std::endl; + std::cout << "+ Number of storage targets: " << outNumTargets << std::endl; + std::cout << "+ Storage targets:" << std::endl; + + // get the targets which are used for the file and print them to the console + for (int targetIndex = 0; targetIndex < outNumTargets; targetIndex++) + { + struct BeegfsIoctl_GetStripeTargetV2_Arg outTargetInfo; + + bool stripeTargetRetVal = beegfs_getStripeTargetV2(fd, targetIndex, &outTargetInfo); + if(stripeTargetRetVal) + { + if(outPatternType == BEEGFS_STRIPEPATTERN_BUDDYMIRROR) + { + std::cout << " + " << outTargetInfo.targetOrGroup + << " @ " << outTargetInfo.primaryTarget + << " @ " << outTargetInfo.primaryNodeAlias + << " [ID: "<< outTargetInfo.primaryNodeID << "]" << std::endl; + std::cout << " + " << outTargetInfo.targetOrGroup + << " @ " << outTargetInfo.secondaryTarget + << " @ " << outTargetInfo.secondaryNodeAlias + << " [ID: "<< outTargetInfo.secondaryNodeID << "]" << std::endl; + } + else + { + std::cout << " + " << outTargetInfo.targetOrGroup + << " @ " << outTargetInfo.primaryNodeAlias + << " [ID: "<< outTargetInfo.primaryNodeID << "]" << std::endl; + } + } + else + { + std::cout << "Can not get stripe targets of file: " << file << std::endl; + exit(-1); + } + } + } + else + { + std::cout << "Can not get stripe info of file: " << file << std::endl; + exit(-1); + } +} diff --git a/client_devel/include/beegfs/beegfs.h b/client_devel/include/beegfs/beegfs.h new file mode 100644 index 0000000..a618048 --- /dev/null +++ b/client_devel/include/beegfs/beegfs.h @@ -0,0 +1,6 @@ +#ifndef __BEEGFS_H__ +#define __BEEGFS_H__ + +#include + +#endif /* __BEEGFS_H__ */ diff --git a/client_devel/include/beegfs/beegfs_ioctl.h b/client_devel/include/beegfs/beegfs_ioctl.h new file mode 100644 index 0000000..8cf787e --- /dev/null +++ b/client_devel/include/beegfs/beegfs_ioctl.h @@ -0,0 +1,11 @@ +#ifndef __BEEGFS_IOCTL_H__ +#define __BEEGFS_IOCTL_H__ + +#include +#include +#include +#include +#include + + +#endif /* __BEEGFS_IOCTL_H__ */ diff --git a/client_devel/include/beegfs/beegfs_ioctl_functions.h b/client_devel/include/beegfs/beegfs_ioctl_functions.h new file mode 100644 index 0000000..4aff7a8 --- /dev/null +++ b/client_devel/include/beegfs/beegfs_ioctl_functions.h @@ -0,0 +1,335 @@ +#ifndef __BEEGFS_IOCTL_FUNCTIONS_H__ +#define __BEEGFS_IOCTL_FUNCTIONS_H__ + +#include +#include +#include +#include +#include + +#ifndef __cplusplus +#include +#endif + +#define BEEGFS_API_MAJOR_VERSION 1 // major version number of the API, different major version + // are incompatible +#define BEEGFS_API_MINOR_VERSION 1 // minor version number of the API, the minor versions of the + // same major version are backward compatible + +#define beegfs_api_version_check() { return beegfs_checkApiVersion(); } // backward compatibility + +static inline bool beegfs_getConfigFile(int fd, char** outCfgFile); +static inline bool beegfs_getRuntimeConfigFile(int fd, char** outCfgFile); +static inline bool beegfs_testIsBeeGFS(int fd); +static inline bool beegfs_getMountID(int fd, char** outMountID); +static inline bool beegfs_getStripeInfo(int fd, unsigned* outPatternType, unsigned* outChunkSize, + uint16_t* outNumTargets); +static inline bool beegfs_getStripeTarget(int fd, uint16_t targetIndex, uint16_t* outTargetNumID, + uint16_t* outNodeNumID, char** outNodeStrID); +static inline bool beegfs_getStripeTargetV2(int fd, uint32_t targetIndex, + struct BeegfsIoctl_GetStripeTargetV2_Arg* outTargetInfo); +static inline bool beegfs_createFile(int fd, const char* filename, mode_t mode, + unsigned numtargets, unsigned chunksize); +static inline bool beegfs_getInodeID(int fd, const char* entryID, uint64_t* outInodeID); +static inline bool beegfs_getEntryInfo(int fd, uint32_t* ownerID, char* parentEntryID, + char* entryID, int* entryType, int* featureFlags); +static inline bool beegfs_checkApiVersion(const unsigned required_major_version, + const unsigned required_minor_version); +static inline bool beegfs_pingNode(int fd, struct BeegfsIoctl_PingNode_Arg* ping); + + +/** + * Get the path to the client config file of an active BeeGFS mountpoint. + * + * @param fd filedescriptor pointing to file or dir inside BeeGFS mountpoint. + * @param outCfgFile buffer for config file path; will be malloc'ed and needs to be free'd by + * caller if success was returned. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getConfigFile(int fd, char** outCfgFile) +{ + struct BeegfsIoctl_GetCfgFile_Arg getCfgFile; + getCfgFile.length = BEEGFS_IOCTL_CFG_MAX_PATH; + + int res = ioctl(fd, BEEGFS_IOC_GET_CFG_FILE, &getCfgFile); + if(res) + return false; + + *outCfgFile = strndup(getCfgFile.path, BEEGFS_IOCTL_CFG_MAX_PATH); + if(!*outCfgFile) + return false; + + return true; +} + +/** + * Get the path to the client runtime config file in procfs. + * + * @param fd filedescriptor pointing to file or dir inside BeeGFS mountpoint. + * @param outCfgFile buffer for config file path; will be malloc'ed and needs to be free'd by + * caller if success was returned. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getRuntimeConfigFile(int fd, char** outCfgFile) +{ + struct BeegfsIoctl_GetCfgFile_Arg getCfgFile; + getCfgFile.length = BEEGFS_IOCTL_CFG_MAX_PATH; + + int res = ioctl(fd, BEEGFS_IOC_GET_RUNTIME_CFG_FILE, &getCfgFile); + if(res) + return false; + + *outCfgFile = strndup(getCfgFile.path, BEEGFS_IOCTL_CFG_MAX_PATH); + if(!*outCfgFile) + return false; + + return true; +} + +/** + * Test if the underlying file system is a BeeGFS. + * + * @param fd filedescriptor pointing to some file or dir that should be checked for whether it is + * located inside a BeeGFS mount. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_testIsBeeGFS(int fd) +{ + char testArray[sizeof(BEEGFS_IOCTL_TEST_STRING)]; + +#ifdef BEEGFS_DEBUG + // just calm valgrind; it does not detect that the array is initialized by the ioctl + memset(testArray, 0, sizeof(BEEGFS_IOCTL_TEST_STRING) ); +#endif + + int ioctlRes = ioctl(fd, BEEGFS_IOC_TEST_IS_BEEGFS, testArray); + if(ioctlRes) + return false; + + int memCmpRes = memcmp(testArray, BEEGFS_IOCTL_TEST_STRING, sizeof(BEEGFS_IOCTL_TEST_STRING) ); + if(memCmpRes) + { // ioctl was accepted by underlying fs, but buffer wasn't filled correctly + errno = EPROTO; + return false; // verification through buffer failed, probably just not a beegfs + } + + return true; +} + +/** + * Get the mountID aka clientID aka nodeID of client mount aka sessionID. + * + * @param fd filedescriptor pointing to some file or dir that should be checked for whether it is + * located inside a BeeGFS mount. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getMountID(int fd, char** outMountID) +{ + char mountIDBuf[BEEGFS_IOCTL_MOUNTID_BUFLEN]; + +#ifdef BEEGFS_DEBUG + // just calm valgrind; it does not detect that the array is initialized by the ioctl + memset(mountIDBuf, 0, sizeof(mountIDBuf) ); +#endif + + int ioctlRes = ioctl(fd, BEEGFS_IOC_GET_MOUNTID, mountIDBuf); + if(ioctlRes) + return false; + + *outMountID = strndup(mountIDBuf, sizeof(mountIDBuf) ); + if(!*outMountID) + return false; + + return true; +} + +/** + * Get the stripe info of a file. + * + * @param fd filedescriptor pointing to some file inside a BeeGFS mount. + * @param outPatternType type of stripe pattern (BEEGFS_STRIPEPATTERN_...) + * @param outChunkSize chunk size for striping. + * @param outNumTargets number of targets for striping. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getStripeInfo(int fd, unsigned* outPatternType, unsigned* outChunkSize, + uint16_t* outNumTargets) +{ + struct BeegfsIoctl_GetStripeInfo_Arg getStripeInfo; + + int res = ioctl(fd, BEEGFS_IOC_GET_STRIPEINFO, &getStripeInfo); + if(res) + return false; + + *outPatternType = getStripeInfo.outPatternType; + *outChunkSize = getStripeInfo.outChunkSize; + *outNumTargets = getStripeInfo.outNumTargets; + + return true; +} + +/** + * Get the stripe target of a file (with 0-based index). + * + * @param fd filedescriptor pointing to some file inside a BeeGFS mount. + * @param targetIndex index of target that should be retrieved (start with 0 and then call this + * again with index up to "*outNumTargets-1" to retrieve remaining targets). + * @param outTargetNumID numeric ID of target at given index. + * @param outNodeNumID numeric ID to node to which this target is assigned. + * @param outNodeAlias alias (formerly string ID) of the node to which this target is assigned; + * buffer will be alloc'ed and needs to be free'd by caller if success is returned. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getStripeTarget(int fd, uint16_t targetIndex, uint16_t* outTargetNumID, + uint16_t* outNodeNumID, char** outNodeAlias) +{ + struct BeegfsIoctl_GetStripeTarget_Arg getStripeTarget; + + getStripeTarget.targetIndex = targetIndex; + + int res = ioctl(fd, BEEGFS_IOC_GET_STRIPETARGET, &getStripeTarget); + if(res) + return false; + + *outTargetNumID = getStripeTarget.outTargetNumID; + *outNodeNumID = getStripeTarget.outNodeNumID; + + *outNodeAlias = strndup(getStripeTarget.outNodeAlias, BEEGFS_IOCTL_CFG_MAX_PATH); + if(!*outNodeAlias) + return false; + + return true; +} + +/** + * Get the stripe target of a file (with 0-based index). + * + * @param fd filedescriptor pointing to some file inside a BeeGFS mount. + * @param targetIndex index of target that should be retrieved (start with 0 and then call this + * again with index up to "*outNumTargets-1" to retrieve remaining targets). + * @param outTargetInfo pointer to struct that will be filled with information about the selected + * stripe target + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_getStripeTargetV2(int fd, uint32_t targetIndex, + struct BeegfsIoctl_GetStripeTargetV2_Arg* outTargetInfo) +{ + memset(outTargetInfo, 0, sizeof(*outTargetInfo)); + + outTargetInfo->targetIndex = targetIndex; + + return ioctl(fd, BEEGFS_IOC_GET_STRIPETARGET_V2, outTargetInfo) == 0; +} + +/** + * Create a new regular file with stripe hints. + * + * As the stripe pattern cannot be changed when a file is already created, this is an exclusive + * create, so it will return an error if the file already existed. + * + * @param fd filedescriptor pointing to parent directory for the new file. + * @param filename name of created file. + * @param mode permission bits of new file (i.e. symbolic constants like S_IRWXU or 0644). + * @param numtargets desired number of storage targets for striping; 0 for directory default; ~0 to + * use all available targets. + * @param chunksize chunksize per storage target for striping in bytes; 0 for directory default; + * must be 2^n >= 64KiB. + * @return true on success, false on error (in which case errno will be set). + */ +bool beegfs_createFile(int fd, const char* filename, mode_t mode, unsigned numtargets, + unsigned chunksize) +{ + struct BeegfsIoctl_MkFileWithStripeHints_Arg createFileArg; + + createFileArg.filename = filename; + createFileArg.mode = mode; + + createFileArg.numtargets = numtargets; + createFileArg.chunksize = chunksize; + + int res = ioctl(fd, BEEGFS_IOC_MKFILE_STRIPEHINTS, &createFileArg); + if(res) + return false; + + return true; +} + +bool beegfs_getInodeID(int fd, const char* entryID, uint64_t* outInodeID) +{ + struct BeegfsIoctl_GetInodeID_Arg getInodeIDArg; + + getInodeIDArg.entryID[BEEGFS_IOCTL_ENTRYID_MAXLEN] = '\0'; + strncpy(getInodeIDArg.entryID, entryID, BEEGFS_IOCTL_ENTRYID_MAXLEN); + + if(ioctl(fd, BEEGFS_IOC_GETINODEID, &getInodeIDArg)) + { + *outInodeID = 0; + return false; + } + + *outInodeID = getInodeIDArg.inodeID; + return true; +} + +/** + * Get entryInfo data for given file. + * + * @param fd filedescriptor pointing to some file inside a BeeGFS mount. + * @param ownerID pointer to an uint32_t in which the ownerID shall be stored + * @param parentEntryID pointer to a buffer for the parent entryID. The buffer must + * be at least BEEGFS_IOCTL_ENTRYID_MAXLEN + 1 bytes long. + * @param entryID pointer to a buffer for the entryID. The buffer must + * be at least BEEGFS_IOCTL_ENTRYID_MAXLEN + 1 bytes long. + * @param entryType pointer to an int in which the entryType shall be stored + * @param featureFlags pointer to an int in which the feature flags shall be stored + * @return success/failure + */ +bool beegfs_getEntryInfo(int fd, uint32_t* ownerID, char* parentEntryID, + char* entryID, int* entryType, int* featureFlags) +{ + struct BeegfsIoctl_GetEntryInfo_Arg arg; + + if(ioctl(fd, BEEGFS_IOC_GETENTRYINFO, &arg)) + { + return false; + } + + *ownerID = arg.ownerID; + strncpy(parentEntryID, arg.parentEntryID, BEEGFS_IOCTL_ENTRYID_MAXLEN + 1); + strncpy(entryID, arg.entryID, BEEGFS_IOCTL_ENTRYID_MAXLEN + 1); + *entryType = arg.entryType; + *featureFlags = arg.featureFlags; + + return true; +} + +/** + * Checks if the required API version of the application is compatible to current API version + * + * @param required_major_version the required major API version of the user application + * @param required_minor_version the minimal required minor API version of the user application + * @return true if the required version and the API version are compatible, if not false is returned + */ +bool beegfs_checkApiVersion(const unsigned required_major_version, + const unsigned required_minor_version) +{ + if(required_major_version != BEEGFS_API_MAJOR_VERSION) + return false; + + if(required_minor_version > BEEGFS_API_MINOR_VERSION) + return false; + + return true; +} + +static inline bool beegfs_pingNode(int fd, struct BeegfsIoctl_PingNode_Arg* inoutPing) +{ + if(ioctl(fd, BEEGFS_IOC_PINGNODE, inoutPing)) + { + return false; + } + + return true; +} + +#endif /* __BEEGFS_IOCTL_FUNCTIONS_H__ */ diff --git a/client_module/CMakeLists.txt b/client_module/CMakeLists.txt new file mode 100644 index 0000000..c697f1e --- /dev/null +++ b/client_module/CMakeLists.txt @@ -0,0 +1,102 @@ +if(NOT BEEGFS_SKIP_CLIENT) + include(ExternalProject) + + ExternalProject_Add( + client-module + BUILD_IN_SOURCE ON + URL "${CMAKE_CURRENT_SOURCE_DIR}" + CONFIGURE_COMMAND "" + BUILD_COMMAND make -C build -j $(nproc) "KDIR=${BEEGFS_KERNELDIR}" "OFED_INCLUDE_PATH=${BEEGFS_OFEDDIR}" + INSTALL_COMMAND "" + ) +endif() + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/build/dkms.conf.client" + "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.client" +) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/build/dkms.conf.compat" + "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.compat" +) + +configure_file( + + "${CMAKE_CURRENT_SOURCE_DIR}/build/postinst.in" + "${CMAKE_CURRENT_BINARY_DIR}/postinst" +) + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/build/prerm.in" + "${CMAKE_CURRENT_BINARY_DIR}/prerm" +) + +install( + DIRECTORY "" + DESTINATION "usr/src/beegfs-${BEEGFS_VERSION}" + COMPONENT "client" + USE_SOURCE_PERMISSIONS + PATTERN "CMakeLists.txt" EXCLUDE + PATTERN "dkms.conf.*" EXCLUDE +) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.client" + DESTINATION "usr/src/beegfs-${BEEGFS_VERSION}" + RENAME "dkms.conf" + COMPONENT "client" +) + +install( + FILES "build/dist/etc/beegfs-client.conf" + DESTINATION "etc/beegfs" + COMPONENT "client" +) + +install( + DIRECTORY "" + DESTINATION "usr/src/beegfs-compat-${BEEGFS_VERSION}" + COMPONENT "client-compat" + USE_SOURCE_PERMISSIONS + PATTERN "CMakeLists.txt" EXCLUDE + PATTERN "dkms.conf.*" EXCLUDE +) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.compat" + DESTINATION "usr/src/beegfs-compat-${BEEGFS_VERSION}" + RENAME "dkms.conf" + COMPONENT "client-compat" +) + +# Debian package settings +set(CPACK_DEBIAN_CLIENT_PACKAGE_DEPENDS "dkms" PARENT_SCOPE) + +set( + CPACK_DEBIAN_CLIENT_PACKAGE_CONTROL_EXTRA + "${CMAKE_CURRENT_BINARY_DIR}/prerm;${CMAKE_CURRENT_BINARY_DIR}/postinst;${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.client" + PARENT_SCOPE +) + +# currently no dkms in compat package +# set( +# CPACK_DEBIAN_CLIENT-COMPAT_PACKAGE_CONTROL_EXTRA +# "${CMAKE_CURRENT_BINARY_DIR}/dkms.conf.compat" +# PARENT_SCOPE +# ) + +# RPM package settings +set(CPACK_RPM_CLIENT_PACKAGE_REQUIRES "dkms" PARENT_SCOPE) + +set( + CPACK_RPM_PRE_UNINSTALL_SCRIPT_FILE + "${CMAKE_CURRENT_BINARY_DIR}/prerm" + PARENT_SCOPE +) + +set( + CPACK_RPM_POST_INSTALL_SCRIPT_FILE + "${CMAKE_CURRENT_BINARY_DIR}/postinst" + PARENT_SCOPE +) diff --git a/client_module/build/AutoRebuild.mk b/client_module/build/AutoRebuild.mk new file mode 100644 index 0000000..03e144c --- /dev/null +++ b/client_module/build/AutoRebuild.mk @@ -0,0 +1,52 @@ +# Automatic rebuild of BeeGFS client modules on kernel change. + + +AUTO_REBUILD_KVER_FILE := auto_rebuild_kernel.ver +AUTO_REBUILD_KVER_CURRENT = $(shell uname -srvmpi) + +AUTO_REBUILD_LOG_PREFIX := "BeeGFS Client Auto-Rebuild:" + +# config file (keys) +AUTO_REBUILD_CONF_FILE := /etc/beegfs/beegfs-client-autobuild.conf +AUTO_REBUILD_CONF_ENABLED_KEY := buildEnabled +AUTO_REBUILD_CONF_BUILDARGS_KEY := buildArgs + +# config file (values) +AUTO_REBUILD_CONF_ENABLED = $(shell \ + grep -s "^\s*$(AUTO_REBUILD_CONF_ENABLED_KEY)\s*=" "$(AUTO_REBUILD_CONF_FILE)" | \ + cut --delimiter="=" --fields="2-" ) +AUTO_REBUILD_CONF_BUILDARGS = $(shell \ + grep "^\s*$(AUTO_REBUILD_CONF_BUILDARGS_KEY)\s*=" "$(AUTO_REBUILD_CONF_FILE)" | \ + cut --delimiter="=" --fields="2-" ) + +# add build dependency if rebuild has been enabled in config file +ifeq ($(AUTO_REBUILD_CONF_ENABLED), true) +AUTO_REBUILD_CONFIGURED_DEPS := auto_rebuild_install +endif + +# environment variables (intentionally commented out here; just to mention +# them somewhere) +# - AUTO_REBUILD_KVER_STORED: internally for target auto_rebuild + + +auto_rebuild: + $(MAKE) auto_rebuild_clean $(AUTO_REBUILD_CONF_BUILDARGS) + $(MAKE) $(AUTO_REBUILD_CONF_BUILDARGS) + +# checked rebuild and install +auto_rebuild_install: auto_rebuild + $(MAKE) install $(AUTO_REBUILD_CONF_BUILDARGS) + + +# run checked rebuild and install if enabled in config file +auto_rebuild_configured: $(AUTO_REBUILD_CONFIGURED_DEPS) + @ /bin/true + + +auto_rebuild_clean: clean + @ /bin/true + + +auto_rebuild_help: + @echo 'No Auto-Rebuild Arguments defined.' + diff --git a/client_module/build/KernelFeatureDetection.mk b/client_module/build/KernelFeatureDetection.mk new file mode 100644 index 0000000..9fa1455 --- /dev/null +++ b/client_module/build/KernelFeatureDetection.mk @@ -0,0 +1,334 @@ +# All detected features are included in "KERNEL_FEATURE_DETECTION" + +# parameters: +# $1: name to define when grep finds something +# $2: grep flags and expression +# $3: input files in linux source tree +define define_if_matches +$(eval \ + KERNEL_FEATURE_DETECTION += $$(shell \ + grep -q -s $2 $(addprefix ${KSRCDIR_PRUNED_HEAD}/include/linux/,$3) \ + && echo "-D$(strip $1)")) +endef + +ifneq ($(OFED_INCLUDE_PATH),) +OFED_DETECTION_PATH := $(OFED_INCLUDE_PATH) +else +OFED_DETECTION_PATH := ${KSRCDIR_PRUNED_HEAD}/include +endif + + +# Find out whether rdma_create_id function has qp_type argument. +# This is tricky because the function declaration spans multiple lines. +# Note: Was introduced in vanilla 3.0 +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sA2 "struct rdma_cm_id \*rdma_create_id(" ${OFED_DETECTION_PATH}/rdma/rdma_cm.h 2>&1 \ + | grep -qs "ib_qp_type qp_type);" \ + && echo "-DOFED_HAS_RDMA_CREATE_QPTYPE") + +# Find out whether rdma_set_service_type function has been declared. +# Note: Was introduced in vanilla 2.6.24 +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs "rdma_set_service_type(struct rdma_cm_id \*id, int tos)" \ + ${OFED_DETECTION_PATH}/rdma/rdma_cm.h \ + && echo "-DOFED_HAS_SET_SERVICE_TYPE") + +# Find out whether ib_create_cq function has cq_attr argument +# This is tricky because the function declaration spans multiple lines. +# Note: Was introduced in vanilla 4.2 +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sA4 "struct ib_cq \*ib_create_cq(struct ib_device \*device," ${OFED_DETECTION_PATH}/rdma/ib_verbs.h 2>&1 \ + | grep -qs "const struct ib_cq_init_attr \*cq_attr);" \ + && echo "-DOFED_HAS_IB_CREATE_CQATTR") + +# Find out whether rdma_reject function has reason argument +# This is tricky because the function declaration spans multiple lines. +# Note: Was introduced in MLNX OFED 5.1 +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sA1 "int rdma_reject(" ${OFED_DETECTION_PATH}/rdma/rdma_cm.h 2>&1 \ + | grep -qs "u8 reason);" \ + && echo "-DOFED_RDMA_REJECT_NEEDS_REASON") + +# kernels >=v4.4 expect a netns argument for rdma_create_id +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs "struct rdma_cm_id \*rdma_create_id.struct net \*net," \ + ${OFED_DETECTION_PATH}/rdma/rdma_cm.h \ + && echo "-DOFED_HAS_NETNS") + +# kernels >=v4.4 split up ib_send_wr into a lot of other structs +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs -F "struct ib_atomic_wr {" \ + ${OFED_DETECTION_PATH}/rdma/ib_verbs.h \ + && echo "-DOFED_SPLIT_WR") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs -F "IB_PD_UNSAFE_GLOBAL_RKEY" \ + ${OFED_DETECTION_PATH}/rdma/ib_verbs.h \ + && echo "-DOFED_UNSAFE_GLOBAL_RKEY") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs -F "ib_get_dma_mr" \ + ${OFED_DETECTION_PATH}/rdma/ib_verbs.h \ + && echo "-DOFED_IB_GET_DMA_MR") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qs -F "static inline void ib_destroy_cq" \ + ${OFED_DETECTION_PATH}/rdma/ib_verbs.h \ + && echo "-DOFED_IB_DESTROY_CQ_IS_VOID") + +# Find out whether the kernel has a scsi/fc_compat.h file, which defines +# vlan_dev_vlan_id. +# Note: We need this, because some kernels (e.g. RHEL 5.9's 2.6.18) forgot this +# include in their rdma headers, leading to implicit function declarations. +$(call define_if_matches, KERNEL_HAS_SCSI_FC_COMPAT, "vlan_dev_vlan_id", scsi/fc_compat.h) + +# Find out whether the kernel has a ihold function. +# Note: Was added in vanilla 2.6.37, but RedHat adds it to their 2.6.32. +$(call define_if_matches, KERNEL_HAS_IHOLD, ' ihold(struct inode.*)', fs.h) + +# Find out whether the kernel has fsync start and end range arguments. +# Note: fsync start and end were added in vanilla 3.1, but SLES11SP3 adds it to its 3.0 kernel. +$(call define_if_matches, KERNEL_HAS_FSYNC_RANGE, \ + -F "int (*fsync) (struct file *, loff_t, loff_t, int datasync);", fs.h) + +# Find out whether the kernel has define struct dentry_operations *s_d_op +# in struct super_block. If it has it used that to check if the file system +# needs to revalidate dentries. +$(call define_if_matches, KERNEL_HAS_S_D_OP, -F "struct dentry_operations *s_d_op;", fs.h) + +# Find out whether the kernel has d_materialise_unique() to +# add dir dentries. +# +# Note: d_materialise_unique was added in vanilla 2.6.19 (backported to rhel5 +# 2.6.18) and got merged into d_splice_alias in vanilla 3.19. +$(call define_if_matches, KERNEL_HAS_D_MATERIALISE_UNIQUE, \ + -F "d_materialise_unique(struct dentry *, struct inode *)", dcache.h) + +# Find out whether the kernel has a PDE_DATA method. +# +# Note: This method was added in vanilla linux-3.10 +$(call define_if_matches, KERNEL_HAS_PDE_DATA, -F "PDE_DATA(const struct inode *)", proc_fs.h) + +# Find out whether the kernel has i_uid_read +# +# Note: added to 3.5 +$(call define_if_matches, KERNEL_HAS_I_UID_READ, "i_uid_read", fs.h) + +# Find out whether the kernel has atomic_open +# +# Note: added to 3.5 +$(call define_if_matches, KERNEL_HAS_ATOMIC_OPEN, "atomic_open", fs.h) + +# Find out whether the kernel used umode_t +# +# Note: added to 3.3 +$(call define_if_matches, KERNEL_HAS_UMODE_T, -E "(\*mkdir).*umode_t", fs.h) + +# Find out if the kernel has a file_inode() method. +$(call define_if_matches, KERNEL_HAS_FILE_INODE, " file_inode(.*)", fs.h) + +# Find out whether the kernel has a strnicmp function. +# +# Note: strnicmp was switched to strncasecmp in linux-4.0. strncasecmp existed +# before, but was wrong, so we only use strncasecmp if strnicmp doesn't exist. +$(call define_if_matches, KERNEL_HAS_STRNICMP, "strnicmp", string.h) + +# Find out whether the kernel has BDI_CAP_MAP_COPY defined. +$(call define_if_matches, KERNEL_HAS_BDI_CAP_MAP_COPY, "define BDI_CAP_MAP_COPY", backing-dev.h) + +# Find out whether xattr_handler** s_xattr in super_block is const. +$(call define_if_matches, KERNEL_HAS_CONST_XATTR_CONST_PTR_HANDLER, \ + -F "const struct xattr_handler * const *s_xattr;", fs.h) + +$(call define_if_matches, KERNEL_HAS_CONST_XATTR_HANDLER, \ + -F "const struct xattr_handler **s_xattr;", fs.h) + +# Find out whether xattr_handler functions need a dentry* (otherwise they need an inode*). +# Note: grepping for "(*set).struct..." instead of "(*set)(struct..." because make complains about +# the missing ")" otherwise. +$(call define_if_matches, KERNEL_HAS_DENTRY_XATTR_HANDLER, \ + "int (\*set).struct dentry \*dentry", xattr.h) + +# address_space.assoc_mapping went away in vanilla 3.8, but SLES11 backports that change +$(call define_if_matches, KERNEL_HAS_ADDRSPACE_ASSOC_MAPPING, -F "assoc_mapping", fs.h) + +# current_umask() was added in 2.6.30 +$(call define_if_matches, KERNEL_HAS_CURRENT_UMASK, -F "current_umask", fs.h) + +# super_operations.show_options was changed to struct dentry* in 3.3 +$(call define_if_matches, KERNEL_HAS_SHOW_OPTIONS_DENTRY, -F "int (*show_options)(struct seq_file *, struct dentry *);", fs.h) + +# xattr handlers >=v4.4 also receive pointer to struct xattr_handler +KERNEL_FEATURE_DETECTION += $(shell \ + grep -s -F "int (*get)" ${KSRCDIR_PRUNED_HEAD}/include/linux/xattr.h \ + | grep -q -s -F "const struct xattr_handler *" \ + && echo "-DKERNEL_HAS_XATTR_HANDLER_PTR_ARG -DKERNEL_HAS_DENTRY_XATTR_HANDLER") + +# 4.5 introduces name in xattr_handler, which can be used instead of prefix +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA1 "struct xattr_handler {" ${KSRCDIR_PRUNED_HEAD}/include/linux/xattr.h \ + | grep -qsF "const char *name;" \ + && echo "-DKERNEL_HAS_XATTR_HANDLER_NAME") + +# locks_lock_inode_wait is used for flock since 4.4 (before flock_lock_file_wait was used) +# since 6.3 locks_lock_inode_wait moved from file fs.h to filelock.h +$(call define_if_matches, KERNEL_HAS_LOCKS_FILELOCK_INODE_WAIT, -F "static inline int locks_lock_inode_wait(struct inode *inode, struct file_lock *fl)", filelock.h) + +$(call define_if_matches, KERNEL_HAS_LOCKS_LOCK_INODE_WAIT, -F "static inline int locks_lock_inode_wait(struct inode *inode, struct file_lock *fl)", fs.h) + +# get_link() replaces follow_link() in 4.5 +$(call define_if_matches, KERNEL_HAS_GET_LINK, -F "const char * (*get_link) (struct dentry *, struct inode *, struct delayed_call *);", fs.h) + +$(call define_if_matches, KERNEL_HAS_I_MMAP_LOCK, -F "i_mmap_lock_read", fs.h) +$(call define_if_matches, KERNEL_HAS_I_MMAP_RWSEM, -F "i_mmap_rwsem", fs.h) +$(call define_if_matches, KERNEL_HAS_I_MMAP_MUTEX, -F "i_mmap_mutex", fs.h) +$(call define_if_matches, KERNEL_HAS_I_MMAP_RBTREE, -P "struct rb_root\s+i_mmap", fs.h) +$(call define_if_matches, KERNEL_HAS_I_MMAP_CACHED_RBTREE, -P "struct rb_root_cached\s+i_mmap", fs.h) +$(call define_if_matches, KERNEL_HAS_I_MMAP_NONLINEAR, -F "i_mmap_nonlinear", fs.h) + +$(call define_if_matches, KERNEL_HAS_INODE_LOCK, "static inline void inode_lock", fs.h) + +#=linux-4.8 +$(call define_if_matches, KERNEL_HAS_PAGE_ENDIO, \ + -F "void page_endio(struct page *page, bool is_write, int err);", pagemap.h) + +# kernels <= 2.7.27 use remove_suid, others use file_remove_suid. +# except linux-3.10.0-514.el7, which uses file_remove_privs. +$(call define_if_matches, KERNEL_HAS_FILE_REMOVE_SUID, \ + -F "int file_remove_suid(struct file *);", fs.h) +$(call define_if_matches, KERNEL_HAS_FILE_REMOVE_PRIVS, \ + -F "int file_remove_privs(struct file *);", fs.h) + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA1 "sock_recvmsg" ${KSRCDIR_PRUNED_HEAD}/include/linux/net.h \ + | grep -qsF "size_t size" \ + && echo "-DKERNEL_HAS_RECVMSG_SIZE") + +$(call define_if_matches, KERNEL_HAS_MEMDUP_USER, "memdup_user", string.h) +$(call define_if_matches, KERNEL_HAS_FAULTATTR_DNAME, -F "struct dentry *dname", fault-inject.h) +$(call define_if_matches, KERNEL_HAS_SYSTEM_UTSNAME, "system_utsname", utsname.h) +$(call define_if_matches, KERNEL_HAS_SOCK_CREATE_KERN_NS, "sock_create_kern.struct net", net.h) +$(call define_if_matches, KERNEL_HAS_SOCK_SENDMSG_NOLEN, "sock_sendmsg.*msg.;", net.h) +$(call define_if_matches, KERNEL_HAS_IOV_ITER_INIT_DIR, "iov_iter_init.*direction", uio.h) +$(call define_if_matches, KERNEL_HAS_ITER_KVEC, "ITER_KVEC", uio.h) +$(call define_if_matches, KERNEL_HAS_IOV_ITER_TYPE, "iov_iter_type", uio.h) +$(call define_if_matches, KERNEL_HAS_ITER_BVEC, "ITER_BVEC", uio.h) +$(call define_if_matches, KERNEL_HAS_ITER_PIPE, "ITER_PIPE", uio.h) +$(call define_if_matches, KERNEL_HAS_IOV_ITER_IS_PIPE, "iov_iter_is_pipe", uio.h) +$(call define_if_matches, KERNEL_HAS_ITER_IS_IOVEC, "iter_is_iovec", uio.h) +$(call define_if_matches, KERNEL_HAS_IOV_ITER_IOVEC, "iov_iter_iovec", uio.h) +# iov_iter_iovec removed from 6.4 kernel and used iter_iov_addr & iter_iov_len macro's +$(call define_if_matches, KERNEL_HAS_ITER_IOV_ADDR, "iter_iov_addr", uio.h) +$(call define_if_matches, KERNEL_HAS_GET_SB_NODEV, "get_sb_nodev", fs.h) +$(call define_if_matches, KERNEL_HAS_GENERIC_FILE_LLSEEK_UNLOCKED, "generic_file_llseek_unlocked", \ + fs.h) +$(call define_if_matches, KERNEL_HAS_SET_NLINK, "set_nlink", fs.h) +$(call define_if_matches, KERNEL_HAS_DENTRY_PATH_RAW, "dentry_path_raw", dcache.h) +$(call define_if_matches, KERNEL_HAS_FSYNC_DENTRY, -P "(\*fsync).*dentry", fs.h) +$(call define_if_matches, KERNEL_HAS_ITER_FILE_SPLICE_WRITE, "iter_file_splice_write", fs.h) +$(call define_if_matches, KERNEL_HAS_ITER_GENERIC_FILE_SENDFILE, "generic_file_sendfile", fs.h) +$(call define_if_matches, KERNEL_HAS_ITERATE_DIR, "iterate_dir", fs.h) +$(call define_if_matches, KERNEL_HAS_ENCODE_FH_INODE, -P "\(\*encode_fh\).struct inode", exportfs.h) +$(call define_if_matches, KERNEL_HAS_D_DELETE_CONST_ARG, \ + -F "int (*d_delete)(const struct dentry *);", dcache.h) +$(call define_if_matches, KERNEL_HAS_FILE_F_VFSMNT, -P "struct vfsmount\s*\*f_vfsmnt", fs.h) +$(call define_if_matches, KERNEL_HAS_POSIX_ACL_XATTR_USERNS_ARG, \ + -P "posix_acl_from_xattr.struct user_namespace", posix_acl_xattr.h) +$(call define_if_matches, KERNEL_HAS_D_MAKE_ROOT, d_make_root, dcache.h) +$(call define_if_matches, KERNEL_HAS_GENERIC_WRITE_CHECKS_ITER, \ + -P "generic_write_checks.*iov_iter", fs.h) +$(call define_if_matches, KERNEL_HAS_INVALIDATEPAGE_RANGE, \ + -P "void \(\*invalidatepage\) \(struct page \*. unsigned int. unsigned int\);", fs.h) +$(call define_if_matches, KERNEL_HAS_PERMISSION_2, \ + -P "int \(\*permission\) \(struct inode \*. int\);", fs.h) +$(call define_if_matches, KERNEL_HAS_PERMISSION_FLAGS, \ + -P "int \(\*permission\) \(struct inode \*. int. unsigned int\);", fs.h) +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA5 "kmem_cache_create" ${KSRCDIR_PRUNED_HEAD}/include/linux/slab.h \ + | grep -qsF "void (*)(void *, struct kmem_cache *, unsigned long)" \ + && echo "-DKERNEL_HAS_KMEMCACHE_CACHE_FLAGS_CTOR") +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA5 "kmem_cache_create" ${KSRCDIR_PRUNED_HEAD}/include/linux/slab.h \ + | grep -qsF "void (*)(struct kmem_cache *, void *)" \ + && echo "-DKERNEL_HAS_KMEMCACHE_CACHE_CTOR") +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA5 "kmem_cache_create" ${KSRCDIR_PRUNED_HEAD}/include/linux/slab.h \ + | grep -qsxP "\s+void \(\*\)\(.*?\)," \ + && echo "-DKERNEL_HAS_KMEMCACHE_DTOR") +$(call define_if_matches, KERNEL_HAS_SB_BDI, -F "struct backing_dev_info *s_bdi", fs.h) +$(call define_if_matches, KERNEL_HAS_BDI_SETUP_AND_REGISTER, "bdi_setup_and_register", \ + backing-dev.h) +$(call define_if_matches, KERNEL_HAS_FOLLOW_LINK_COOKIE, \ + -P "const char \* \(\*follow_link\) \(struct dentry \*. void \*\*\);", fs.h) +$(call define_if_matches, KERNEL_HAS_FSYNC_2, \ + -F "int (*fsync) (struct file *, int datasync);", fs.h) +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA20 "struct address_space {" ${KSRCDIR_PRUNED_HEAD}/include/linux/fs.h \ + | grep -qsP "struct backing_dev_info *backing_dev_info;" \ + && echo "-DKERNEL_HAS_ADDRESS_SPACE_BDI") +$(call define_if_matches, KERNEL_HAS_COPY_FROM_ITER, "copy_from_iter", uio.h) +$(call define_if_matches, KERNEL_HAS_ALLOC_WORKQUEUE, "alloc_workqueue", workqueue.h) +$(call define_if_matches, KERNEL_HAS_WQ_RESCUER, "WQ_RESCUER", workqueue.h) +$(call define_if_matches, KERNEL_HAS_WAIT_QUEUE_ENTRY_T, "wait_queue_entry_t", wait.h) +$(call define_if_matches, KERNEL_HAS_CURRENT_FS_TIME, "current_fs_time", fs.h) +$(call define_if_matches, KERNEL_HAS_64BIT_TIMESTAMPS, "struct timespec64 ia_atime;", fs.h) +$(call define_if_matches, KERNEL_HAS_SB_NODIRATIME, "SB_NODIRATIME", fs.h) + +$(call define_if_matches, KERNEL_HAS_GENERIC_GETXATTR, "generic_getxattr", xattr.h) + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sA1 "(*rename) " $(KSRCDIR_PRUNED_HEAD)/include/linux/fs.h \ + | grep -qsF "unsigned int" \ + && echo "-DKERNEL_HAS_RENAME_FLAGS") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qsF "static inline ino_t parent_ino" $(KSRCDIR_PRUNED_HEAD)/include/linux/fs.h \ + && echo "-DKERNEL_HAS_PARENT_INO") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -qsF "define SLAB_MEM_SPREAD" $(KSRCDIR_PRUNED_HEAD)/include/linux/slab.h \ + && echo "-DKERNEL_HAS_SLAB_MEM_SPREAD") + +$(call define_if_matches, KERNEL_ACCESS_OK_WANTS_TYPE, "define access_ok(type, addr, size)" \ + $(KSRCDIR_PRUNED_HEAD)/include/asm-generic/uaccess.h) + +$(call define_if_matches, KERNEL_SPIN_RELEASE_HAS_3_ARGUMENTS, "\#define spin_release(l, n, i)", lockdep.h) + +$(call define_if_matches, KERNEL_HAS_NEW_PDE_DATA, "pde_data", proc_fs.h) + +# From linux-5.18, .readpages, .invalidatepage, .set_page_dirty, .launder_page +# are replaced by.readahead, .invalidate_folio, .dirty_folio, launder_folio +#$(call define_if_matches, KERNEL_HAS_READAHEAD, -F "void (*readahead)", fs.h) +$(call define_if_matches, KERNEL_HAS_FOLIO, -F "bool (*dirty_folio)", fs.h) + +$(call define_if_matches, KERNEL_HAS_READ_FOLIO, -F "int (*read_folio)", fs.h) + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA1 "int (*writepage_t)" ${KSRCDIR_PRUNED_HEAD}/include/linux/writeback.h \ + | grep -qsF "struct folio *" \ + && echo "-DKERNEL_WRITEPAGE_HAS_FOLIO") + +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA1 "int (*write_begin)" ${KSRCDIR_PRUNED_HEAD}/include/linux/fs.h \ + | grep -qsF "unsigned flags" \ + && echo "-DKERNEL_WRITE_BEGIN_HAS_FLAGS") + +# Matching: int posix_acl_chmod(struct user_namespace *, struct dentry *, umode_t) +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sF "int posix_acl_chmod" ${KSRCDIR_PRUNED_HEAD}/include/linux/posix_acl.h \ + | grep -qs "struct user_namespace\s*.*struct dentry" \ + && echo "-DKERNEL_HAS_POSIX_ACL_CHMOD_NS_DENTRY") + +# linux-6.0 has iov_iter_get_pages2 +$(call define_if_matches, KERNEL_HAS_IOV_ITER_GET_PAGES2, "iov_iter_get_pages2", uio.h) + +$(call define_if_matches, KERNEL_HAS_GET_RANDOM_INT, "get_random_int", random.h) + +# Detect if write_begin() uses struct folio** +KERNEL_FEATURE_DETECTION += $(shell \ + grep -sFA2 "int (*write_begin)" ${KSRCDIR_PRUNED_HEAD}/include/linux/fs.h \ + | grep -qs "struct folio **" \ + && echo "-DKERNEL_WRITE_BEGIN_USES_FOLIO") diff --git a/client_module/build/Makefile b/client_module/build/Makefile new file mode 100644 index 0000000..17dbfc2 --- /dev/null +++ b/client_module/build/Makefile @@ -0,0 +1,309 @@ + +# This is the BeeGFS client makefile. +# It creates the client kernel module (beegfs.ko). +# +# Use "make help" to find out about configuration options. +# +# Note: This is the Makefile for internal use, there is a separate Release.mk +# file for release packages (to handle the closed source tree properly). + +TARGET ?= beegfs + +export TARGET +export OFED_INCLUDE_PATH +export BEEGFS_NO_RDMA + +BEEGFS_DKMS_BUILD=0 +ifdef KERNELRELEASE +BEEGFS_DKMS_BUILD=1 +endif + +ifeq ($(BEEGFS_DKMS_BUILD),1) +-include /etc/beegfs/beegfs-client-build.mk +endif +-include Version.mk + +ifeq ($(obj),) +BEEGFS_BUILDDIR := $(shell pwd) +else +BEEGFS_BUILDDIR := $(obj) +endif + +ifeq ($(KRELEASE),) +KRELEASE := $(shell uname -r) +endif + +ifneq ($(BEEGFS_NO_RDMA),) +BEEGFS_CFLAGS += -DBEEGFS_NO_RDMA +else + +$(info $$OFED_INCLUDE_PATH = [${OFED_INCLUDE_PATH}]) + +ifneq ($(OFED_INCLUDE_PATH),) +BEEGFS_CFLAGS += -I$(OFED_INCLUDE_PATH) +export KBUILD_EXTRA_SYMBOLS += $(OFED_INCLUDE_PATH)/../Module.symvers +endif +endif + +# The following section deals with the auto-detection of the kernel +# build directory (KDIR) + +# Guess KDIR based on running kernel. +# - "/usr/src/linux-headers-*" for Ubuntu +# - "/usr/src/kernels/*" for RHEL +# - "/lib/modules/*/build" for Debian, SLES +ifeq ($(KDIR),) +override KDIR = \ + /lib/modules/$(KRELEASE)/build \ + /lib/modules/default/build \ + /usr/src/linux-headers-$(KRELEASE) \ + /usr/src/linux-headers-default \ + /usr/src/kernels/$(KRELEASE) \ + /usr/src/kernels/default +endif + +# Prune the KDIR list down to paths that exist and have an +# /include/linux/version.h file +# Note: linux-3.7 moved version.h to generated/uapi/linux/version.h +test_dir = $(shell [ -e $(dir)/include/linux/version.h -o \ + -e $(dir)/include/generated/uapi/linux/version.h ] && echo $(dir) ) +KDIR_PRUNED := $(foreach dir, $(KDIR), $(test_dir) ) + +# We use the first valid entry of the pruned KDIR list +KDIR_PRUNED_HEAD := $(firstword $(KDIR_PRUNED) ) + + +# The following section deals with the auto-detection of the kernel +# source path (KSRCDIR) which is required e.g. for KERNEL_FEATURE_DETECTION. + +# Guess KSRCDIR based on KDIR +# (This is usually KDIR or KDIR/../source, so you can specify multiple +# directories here as a space-separated list) +ifeq ($(KSRCDIR),) + +# Note: "KSRCDIR += $(KDIR)/../source" is not working here +# because of the symlink ".../build"), so we do it with substring +# replacement + +KSRCDIR := $(subst /build,/source, $(KDIR_PRUNED_HEAD) ) +KSRCDIR += $(KDIR) +endif + +# Prune the KSRCDIR list down to paths that exist and contain an +# include/linux/fs.h file +test_dir = $(shell [ -e $(dir)/include/linux/fs.h ] && echo $(dir) ) +KSRCDIR_PRUNED := $(foreach dir, $(KSRCDIR), $(test_dir) ) + +# We use the first valid entry of the pruned KSRCDIR list +KSRCDIR_PRUNED_HEAD := $(firstword $(KSRCDIR_PRUNED) ) + +ifeq ($(BEEGFS_NO_RDMA),) +# OFED +ifneq ($(OFED_INCLUDE_PATH),) + +BEEGFS_CFLAGS += -I$(OFED_INCLUDE_PATH) + +module: $(OFED_INCLUDE_PATH)/rdma/rdma_cm.h +$(OFED_INCLUDE_PATH)/rdma/rdma_cm.h: + $(error OFED_INCLUDE_PATH not valid: $(OFED_INCLUDE_PATH)) +endif +endif + +# Include kernel feature auto-detectors +include KernelFeatureDetection.mk + +KMOD_INST_DIR ?= $(DESTDIR)/lib/modules/$(KRELEASE)/updates/fs/beegfs + +# Prepare CFLAGS: +# (Note: "-Wsign-compare" included in "-Wextra", but must be explicit here, +# because kernel Makefile adds "-Wno-sign-compare" by default. But we can't +# make it permanent here, because it generates a lot of warnings from kernel +# includes.) +BEEGFS_CFLAGS := $(BUILD_ARCH) $(KERNEL_FEATURE_DETECTION) \ + -I$(BEEGFS_BUILDDIR)/../source \ + -I$(BEEGFS_BUILDDIR)/../include \ + -Wextra -Wno-sign-compare -Wno-empty-body -Wno-unused-parameter -Wno-missing-field-initializers \ + -DBEEGFS_MODULE_NAME_STR='\"$(TARGET)\"' + +# Update 2022-12: BeeGFS module source code had already switched to -std=gnu99, +# but now the kernel has caught up with us - kernel moved from gnu89 to +# gnu11. +# So we're switching from gnu99 to gnu11, to not break the build with the newer +# kernels. We still need to specify this flag because that would break the +# client module build with older kernels, where gnu89 is the default. +BEEGFS_CFLAGS += -std=gnu11 + +ifeq ($(shell echo | gcc -Wtype-limits -E - >/dev/null 2>&1 && echo 1),1) + BEEGFS_CFLAGS += -Wno-type-limits +endif + +# -O0 would be better, but is not allowed by kernel includes (will not work) +BEEGFS_CFLAGS_DEBUG := -O1 -ggdb3 -rdynamic -fno-inline -DBEEGFS_DEBUG \ + -DLOG_DEBUG_MESSAGES -DDEBUG_REFCOUNT -DBEEGFS_LOG_CONN_ERRORS +BEEGFS_CFLAGS_RELEASE := -Wuninitialized + +ifeq ($(BEEGFS_DEBUG),) +BEEGFS_CFLAGS += $(BEEGFS_CFLAGS_RELEASE) +else +BEEGFS_CFLAGS += $(BEEGFS_CFLAGS_DEBUG) +endif + +ifeq ($(BEEGFS_NO_RDMA),) +# NVFS +ifneq ($(NVFS_INCLUDE_PATH),) +$(NVFS_INCLUDE_PATH)/nvfs-dma.h: + $(error NVFS_INCLUDE_PATH missing nvfs-dma.h: $(NVFS_INCLUDE_PATH)) +$(NVFS_INCLUDE_PATH)/config-host.h: + $(error NVFS_INCLUDE_PATH missing config-host.h: $(NVFS_INCLUDE_PATH)) +$(NVIDIA_INCLUDE_PATH)/nv-p2p.h: + $(error NVIDIA_INCLUDE_PATH missing nv-p2p.h: $(NVIDIA_INCLUDE_PATH)) +module: $(NVFS_INCLUDE_PATH)/nvfs-dma.h $(NVFS_INCLUDE_PATH)/config-host.h \ + $(NVIDIA_INCLUDE_PATH)/nv-p2p.h + +BEEGFS_CFLAGS += -DBEEGFS_NVFS +BEEGFS_CFLAGS += -I$(NVFS_INCLUDE_PATH) -I$(NVIDIA_INCLUDE_PATH) +endif + +endif + +# if path to strip command was not given, use default +# (alternative strip is important when cross-compiling) +ifeq ($(STRIP),) +STRIP=strip +endif + +BEEGFS_CFLAGS += '-DBEEGFS_VERSION=\"$(BEEGFS_VERSION)\"' + +# Prepare RELEASE_PATH extension +ifneq ($(RELEASE_PATH),) +RELEASE_PATH_CLIENT := $(RELEASE_PATH)/client_module_$(shell echo '$(BEEGFS_VERSION)' | cut -d. -f1) +endif + + +all: module + @ /bin/true + +module: $(TARGET_ALL_DEPS) +ifeq ($(KDIR_PRUNED_HEAD),) + $(error Linux kernel build directory not found. Please check if\ + the kernel module development packages are installed for the current kernel\ + version. (RHEL: kernel-devel; SLES: kernel-default-devel; Debian: linux-headers)) +endif + +ifeq ($(KSRCDIR_PRUNED_HEAD),) + $(error Linux kernel source directory not found. Please check if\ + the kernel module development packages are installed for the current kernel\ + version. (RHEL: kernel-devel; SLES: kernel-default-devel; Debian: linux-headers)) +endif + + @echo "Building beegfs client module" + $(MAKE) -C $(KDIR_PRUNED_HEAD) "M=$(BEEGFS_BUILDDIR)/../source" \ + "EXTRA_CFLAGS=$(BEEGFS_CFLAGS) $(EXTRA_CFLAGS)" modules + + @cp ../source/$(TARGET).ko . + @ cp ${TARGET}.ko ${TARGET}-unstripped.ko + @ ${STRIP} --strip-debug ${TARGET}.ko; + +coccicheck: + $(MAKE) -C $(KDIR_PRUNED_HEAD) "M=$(BEEGFS_BUILDDIR)" coccicheck MODE=report \ + M=$(BEEGFS_BUILDDIR)/../source KBUILD_EXTMOD="$(BEEGFS_BUILDDIR)/../source" + + +include AutoRebuild.mk # adds auto_rebuild targets + +prepare_release: +ifeq ($(RELEASE_PATH),) + $(error RELEASE_PATH not defined) +endif + + @ echo "Creating release directory:" $(RELEASE_PATH_CLIENT) + mkdir --parents $(RELEASE_PATH_CLIENT)/build \ + $(RELEASE_PATH_CLIENT)/source \ + $(RELEASE_PATH_CLIENT)/include + + @ echo "Storing beegfs version:" $(BEEGFS_VERSION) + echo "BEEGFS_VERSION =" $(BEEGFS_VERSION) > $(RELEASE_PATH_CLIENT)/build/Version.mk + + @ echo "Copying beegfs client release files to" $(RELEASE_PATH_CLIENT) "..." + cp Makefile $(RELEASE_PATH_CLIENT)/build/Makefile + cp KernelFeatureDetection.mk $(RELEASE_PATH_CLIENT)/build/ + cp AutoRebuild.mk $(RELEASE_PATH_CLIENT)/build/ + cp feature-detect.sh $(RELEASE_PATH_CLIENT)/build/ + cp ../source/Makefile $(RELEASE_PATH_CLIENT)/source/ + + find ../source -mount -name '*.h' -type f | \ + xargs -I ’{}’ cp --parents ’{}’ $(RELEASE_PATH_CLIENT)/build + find ../source -mount -name '*.c' -type f | \ + xargs -I ’{}’ cp --parents ’{}’ $(RELEASE_PATH_CLIENT)/build + find ../include -mount -name '*.h' -type f | \ + xargs -I ’{}’ cp --parents ’{}’ $(RELEASE_PATH_CLIENT)/build + +# When used for development where the full BeeGFS source is available, this install target handles +# ensuring the mount.beegfs script is installed. If the mount.script is not present in the source, +# for example if the install target was invoked via the BeeGFS client service and AutoRebuild.mk, it +# just checks the script already exists at /sbin/mount.beegfs since the script should have been +# installed by the package manager. If it does not exist an error is returned as this is likely a +# bug elsewhere related to installing the script, or somebody is trying to use this Makefile in an +# unsupported/unexpected manner and further investigation is required. +install: + install -D -m 644 $(TARGET).ko $(KMOD_INST_DIR)/$(TARGET).ko + @if [ -f dist/sbin/mount.beegfs ]; then \ + install -D -m 755 dist/sbin/mount.beegfs /sbin/mount.beegfs; \ + echo "Info: Installed mount script at /sbin/mount.beegfs."; \ + elif [ ! -f /sbin/mount.beegfs ]; then \ + echo "Error: mount.beegfs does not already exist at /sbin/mount.beegfs (this is likely a bug elsewhere)."; \ + exit 1; \ + fi + depmod -a $(KRELEASE) + +clean: + rm -f *~ .${TARGET}??* + rm -f .*.cmd *.mod.c *.mod.o *.o *.ko *.ko.unsigned + rm -f ../source/Module*.symvers ../source/modules.order ../source/Module.markers + rm -f Module*.symvers modules.order Module.markers + rm -f $(AUTO_REBUILD_KVER_FILE) + rm -rf .tmp_versions/ + find ../source/ -mount -name '*.o' -type f -delete + find ../source/ -mount -name '.*.o.cmd' -type f -delete + find ../source/ -mount -name '.*.o.d' -type f -delete + find ../source/ -mount -name '*.gcno' -type f -delete + +help: + @echo "This makefile creates the kernel module: $(TARGET) (beegfs-client)" + @echo ' ' + @echo 'client Arguments (required):' + @echo ' RELEASE_PATH= (Target: prepare_release)' + @echo ' The path to the client release directory.' + @echo ' ' + @echo 'client Arguments (optional):' + @echo ' KRELEASE=: Kernel release' + @echo ' (The output of "uname -r" will be used if undefined.' + @echo ' This option is useful when building for a kernel different' + @echo ' from the one currently running (e.g. in a chroot).)' + @echo ' KDIR=: Kernel build directory.' + @echo ' (Will be guessed based on running kernel or KRELEASE if undefined.)' + @echo ' KSRCDIR=: Kernel source directory containing the kernel include ' + @echo ' directory. (Will be guessed based on KDIR if undefined.)' + @echo ' BEEGFS_DEBUG=1:' + @echo ' Enables file sytem debug log messages etc.' + @echo ' TARGET=' + @echo ' Set a different module and file system name.' + @echo ' ' + @echo 'Infiniband (RDMA) arguments (optional):' + @echo ' OFED_INCLUDE_PATH=:' + @echo ' Path to OpenFabrics Enterpise Distribution kernel include directory, e.g.' + @echo ' "/usr/src/openib/include". (If not defined, the standard kernel headers' + @echo ' will be used.)' + @echo '' + @echo 'NVIDIA GPUDirect Storage (GDS) arguments (optional):' + @echo ' NVFS_INCLUDE_PATH=:' + @echo ' Path to directory that contains nvfs-dma.h. If not defined, GDS support is' + @echo ' disabled.' + @echo ' NVIDIA_INCLUDE_PATH=:' + @echo ' Path to NVIDIA driver source. Required when NVFS_INCLUDE_PATH is specifed.' + @echo '' + @echo 'Targets:' + @echo ' all (default) - build only' + @echo ' install - install the kernel modules' + @echo ' clean - delete previously compiled files' + @echo ' prepare_release - build and copy files into the RELEASE_PATH directory' diff --git a/client_module/build/dist/etc/beegfs-client-autobuild.conf b/client_module/build/dist/etc/beegfs-client-autobuild.conf new file mode 100644 index 0000000..37233bb --- /dev/null +++ b/client_module/build/dist/etc/beegfs-client-autobuild.conf @@ -0,0 +1,92 @@ +# This is a config file for the automatic build process of BeeGFS client kernel +# modules. +# http://www.beegfs.com + + +# +# --- Section: [Notes] --- +# + +# General Notes +# ============= +# To force a rebuild of the client modules: +# $ /etc/init.d/beegfs-client rebuild +# +# To see a list of available build arguments: +# $ make help -C /opt/beegfs/src/client/client_module_${BEEGFS_MAJOR_VERSION}/build +# +# Help example for BeeGFS 2015.03 release: +# $ make help -C /opt/beegfs/src/client/client_module_2015.03/build + + +# RDMA Support Notes +# ================== +# If you installed InfiniBand kernel modules from OpenFabrics OFED, then also +# define the correspsonding header include path by adding +# "OFED_INCLUDE_PATH=" to the "buildArgs", where usually is +# "/usr/src/openib/include" or "/usr/src/ofa_kernel/default/include" for +# Mellanox OFED. +# +# OFED headers are automatically detected even if OFED_INCLUDE_PATH is not +# defined. To build the client without RDMA support, define BEEGFS_NO_RDMA=1. +# + + +# NVIDIA GPUDirect Storage Support Notes +# ================== +# If you want to build BeeGFS with NVIDIA GPUDirect Storage support, add +# "NVFS_INCLUDE_PATH=" to the "buildArgs" below, where path is the directory +# that contains nvfs-dma.h. This is usually the nvidia-fs source directory: +# /usr/src/nvidia-fs-VERSION. +# +# If config-host.h is not present in NVFS_INCLUDE_PATH, execute the configure +# script. Example: +# $ cd /usr/src/nvidia-fs-2.13.5 +# $ ./configure +# +# NVIDIA_INCLUDE_PATH must be defined and point to the NVIDIA driver source: +# /usr/src/nvidia-VERSION/nvidia +# +# OFED_INCLUDE_PATH must be defined and point to Mellanox OFED. +# + +# +# --- Section: [Build Settings] --- +# + +# Build Settings +# ============== +# These are the arguments for the client module "make" command. +# +# Note: Quotation marks and equal signs can be used without escape characters +# here. +# +# Example1: +# buildArgs=-j8 +# +# Example2 (see "RDMA Support Notes" above): +# buildArgs=-j8 OFED_INCLUDE_PATH=/usr/src/openib/include +# +# Example3 (see "NVIDIA GPUDirect Storage Support Notes" above): +# buildArgs=-j8 OFED_INCLUDE_PATH=/usr/src/ofa_kernel/default/include \ +# NVFS_INCLUDE_PATH=/usr/src/nvidia-fs-2.13.5 \ +# NVIDIA_INCLUDE_PATH=/usr/src/nvidia-520.61.05/nvidia +# +# Default: +# buildArgs=-j8 + +buildArgs=-j8 + + +# Turn Autobuild on/off +# ===================== +# Controls whether modules will be built on "/etc/init.d/beegfs-client start". +# +# Note that even if autobuild is enabled here, the modules will only be built +# if no beegfs kernel module for the current kernel version exists in +# "/lib/modules//updates/". +# +# Default: +# buildEnabled=true + +buildEnabled=true diff --git a/client_module/build/dist/etc/beegfs-client-build.mk b/client_module/build/dist/etc/beegfs-client-build.mk new file mode 100644 index 0000000..ab7f5bb --- /dev/null +++ b/client_module/build/dist/etc/beegfs-client-build.mk @@ -0,0 +1,19 @@ +# BeeGFS client module DKMS build configuration +# This file is only used when building via DKMS. +# The module needs to be rebuilt after this file has been changed. + +# If using thirdparty OFED specify the path to the installation here. +# Examples: +#OFED_INCLUDE_PATH=/usr/src/ofa_kernel/default/include +#OFED_INCLUDE_PATH=/usr/src/openib/include +# To disable RDMA support, define BEEGFS_NO_RDMA +#BEEGFS_NO_RDMA=1 +# If building nvidia-fs support, specify path to nvfs-dma.h. +# This directory must also have config-host.h, which is created +# by the nvidia-fs configure script. +# Example: +#NVFS_INCLUDE_PATH=/usr/src/nvidia-fs-2.13.5 +# If building nvidia-fs support, specify path to NVIDIA driver +# source. +# Example: +#NVIDIA_INCLUDE_PATH=/usr/src/nvidia-520.61.05/nvidia diff --git a/client_module/build/dist/etc/beegfs-client-mount-hook.example b/client_module/build/dist/etc/beegfs-client-mount-hook.example new file mode 100755 index 0000000..9a868b7 --- /dev/null +++ b/client_module/build/dist/etc/beegfs-client-mount-hook.example @@ -0,0 +1,37 @@ +#!/bin/bash -e +# BeeGFS client mount hook script + +action="${1}" +mountpoint="${2}" + +# THIS IS AN EXAMPLE SCRIPT. +# Copy and modify it, and remove the following line: +exit 1 + +if [ ! -d "${mountpoint}" ] +then + echo "${0}: Mount point does not exist: ${mountpoint}" + exit 1 +fi + +case "${action}" in + + pre-mount) + ;; + + post-mount) + mount -o bind "${mountpoint}/foo" "${mountpoint}/bar" + ;; + + pre-unmount) + umount "${mountpoint}/bar" + ;; + + post-unmount) + ;; + + *) + echo "${0}: Unrecognized option supplied to client mount hook: ${action}" + exit 1 + ;; +esac diff --git a/client_module/build/dist/etc/beegfs-client.conf b/client_module/build/dist/etc/beegfs-client.conf new file mode 100644 index 0000000..feea750 --- /dev/null +++ b/client_module/build/dist/etc/beegfs-client.conf @@ -0,0 +1,755 @@ +# This is a config file for BeeGFS clients. +# http://www.beegfs.com + + +# --- [Table of Contents] --- +# +# 1) Settings +# 2) Mount Options +# 3) Basic Settings Documentation +# 4) Advanced Settings Documentation + + +# +# --- Section 1.1: [Basic Settings] --- +# + +sysMgmtdHost = + + +# +# --- Section 1.2: [Advanced Settings] --- +# + +connAuthFile = /etc/beegfs/conn.auth +connDisableAuthentication = false +connClientPort = 8004 +connMgmtdPort = 8008 +connPortShift = 0 + +connCommRetrySecs = 600 +connFallbackExpirationSecs = 900 +connInterfacesFile = +connRDMAInterfacesFile = +connMaxInternodeNum = 12 +connMaxConcurrentAttempts = 0 +connNetFilterFile = + +connUseRDMA = true +connTCPFallbackEnabled = true +connTCPRcvBufSize = 0 +connUDPRcvBufSize = 0 +connRDMABufNum = 70 +connRDMABufSize = 8192 +connRDMAFragmentSize = page +connRDMATypeOfService = 0 +connTcpOnlyFilterFile = + +logClientID = false +logLevel = 3 + +quotaEnabled = false + +sysCacheInvalidationVersion = true +sysCreateHardlinksAsSymlinks = false +sysMountSanityCheckMS = 11000 +sysSessionCheckOnClose = false +sysSyncOnClose = false +sysTargetOfflineTimeoutSecs = 900 +sysUpdateTargetStatesSecs = 30 +sysXAttrsEnabled = false + +tuneFileCacheType = buffered +tunePreferredMetaFile = +tunePreferredStorageFile = +tuneRemoteFSync = true +tuneUseGlobalAppendLocks = false +tuneUseGlobalFileLocks = false + + +# +# --- Section 1.3: [Enterprise Features] --- +# +# See end-user license agreement for definition and usage limitations of +# enterprise features. +# + +sysACLsEnabled = false + + +# +# --- Section 2: [Mount Options] --- +# + +# Valid mount options are: +# cfgFile, logLevel, connPortShift, connMgmtdPort, +# sysMgmtdHost, sysMountSanityCheckMS, connInterfacesList. +# +# Use the mount option "cfgFile" to specify a different config file +# for the beegfs client. +# Example: +# $ /bin/mount -t beegfs beegfs_nodev /beegfs -ocfgFile=/etc/anotherconfig.conf +# +# Use the mount option "connInterfacesList" to pass the list of interfaces names. +# These interfaces names should be space-separated. +# Example: +# $ /bin/mount -t beegfs beegfs_nodev /beegfs \ +# -ocfgFile=/etc/anotherconfig.conf,connInterfacesList='ib0 eth0' +# +# Mount options override the corresponding config file values. +# Example: +# $ /bin/mount -ocfgFile=/etc/anotherconfig.conf,logLevel=3 ... + + +# +# --- Section 3: [Basic Settings Documentation] --- +# + +# [sysMgmtdHost] +# Hostname (or IP) of the host running the management service. +# (See also "connMgmtdPort".) +# Default: + + +# +# --- Section 4: [Advanced Settings Documentation] --- +# + +# +# --- Section 4.1: [Connections & Communication] --- +# + +# [connAuthFile] +# The path to a file that contains a shared secret for connection based +# authentication. Only peers that use the same shared secret will be able to +# connect. +# Default: + +# [connDisableAuthentication] +# If set to true, explicitly disables connection authentication and allow the +# service to run without a connAuthFile. Running BeeGFS without connection +# authentication is considered insecure and is not recommended. +# Default: false + +# [connClientPort] +# The UDP port of the client. +# Default: 8004 + +# [connMgmtdPort] +# The UDP and TCP port of the management node. +# Default: 8008 + +# [connPortShift] +# Shifts all following UDP and TCP ports according to the specified value. +# Intended to make port configuration easier in case you do not want to +# configure each port individually. +# Default: 0 + +# [connCommRetrySecs] +# The time (in seconds) for retries in case a network communication fails +# (e.g. if a server is down). After this time, the I/O operation will fail +# and the calling process will receive an error. +# Note: Set this value to 0 for infinite retries. In this case, a process +# accessing the file system will block until the corresponding server +# becomes available (or until it is interrupted by a signal). +# Default: 600 + +# [connFallbackExpirationSecs] +# The time in seconds after which a connection to a fallback interface expires. +# When a fallback connection expires, the system will try to establish a new +# connection to the other hosts primary interface (falling back to another +# interface again if necessary). +# Note: The priority of node interfaces can be configured using the +# "connInterfacesFile" parameter. +# Default: 900 + +# [connInterfacesFile] +# The path to a text file that specifies the names of the interfaces, which +# may be used for communication by other nodes. One interface per line. The +# line number also defines the priority of an interface. +# Example: "ib0" in the first line, "eth0" in the second line. +# Values: This setting is optional. If unspecified, all available interfaces +# will be published and priorities will be assigned automatically. +# Note: This has no influence on outgoing connections. The information is sent +# to other hosts to inform them about possible communication paths. +# Default: + +# [connInterfacesList] +# Comma-separated list of interface names. Performs the same function as +# connInterfacesFile. +# If use as the mount option "connInterfacesList" to pass the list of interfaces +# names then it override the corresponding config file/list values.. +# The interfaces names should be space-separated. +# Example: +# $ /bin/mount -t beegfs beegfs_nodev /beegfs \ +# -ocfgFile=/etc/anotherconfig.conf,connInterfacesList='ib0 eth0' +# +# Default: + +# [connRDMAInterfacesFile] +# The path to a text file that specifies the names of IPoIB interfaces, which +# may be used for outbound RDMA communication with other nodes. One interface +# per line. These interfaces must be RDMA-capable NICs. +# +# All storage and metadata servers must be IP-reachable from each specified +# interface. +# +# Specifying interfaces in this file limits which RDMA NICs are used for outbound +# RDMA. Specifying multiple interfaces allows the client to use multiple RDMA +# interfaces for outbound communication. +# +# Example: "ib0" in the first line, "ib1" in the second line. +# Values: This setting is optional. When none are specified, the client will use +# the first client host interface that can reach the remote node via IPoIB, +# as decided by rdma_cm. When multiple interfaces are specified, the client +# round-robins creation of outbound RDMA connections across the specified +# interfaces. +# Default: + +# [connMaxInternodeNum] +# The maximum number of simultaneous connections to the same node. +# Default: 12 + +# [connMaxConcurrentAttempts] +# The maximum number of simultaneous connection attempts. This may help in case +# establishing new connections keeps failing and produces fallbacks. +# It may happen particularly when using RDMA in an Omni-Path setup. If you +# don't have failing connection attempts, tuning this option might still lead +# to a faster connection process. This option is experimental, so there is no +# experience with different values. Setting it to 0 disables it, which means +# concurrent connection attempts are not limited. +# Default: 0 + +# [connNetFilterFile] +# The path to a text file that specifies allowed IP subnets, which may be used +# for outgoing communication. One subnet per line in classless notation (IP +# address and number of significant bits). +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# This value is optional. If unspecified, all addresses are allowed for +# outgoing communication. +# Default: + +# [connUseRDMA] +# Enables the use of Remote Direct Memory Access (RDMA) for InfiniBand or RoCE. +# For this setting to be effective, OFED ibverbs support also has to be enabled +# at compile time of the beegfs client modules (typically via +# beegfs-client-autobuild.conf). +# Default: true + +# [connTCPFallbackEnabled] +# Enables fallback from RDMA to TCP sockets when there is a problem connecting +# via RDMA to a storage or meta node. +# Default: true + +# [connTCPRcvBufSize], [connUDPRcvBufSize] +# Sets the size for TCP and UDP socket receive buffers (SO_RCVBUF). The maximum +# allowed value is determined by sysctl net.core.rmem_max. This value is ignored +# if it is less than the default value determined by net.core.rmem_default. +# For legacy reasons, the default value 0 indicates that the buffer size is set +# to connRDMABufNum * connRDMABufSize. +# -1 indicates that the buffer size should be left at the system default. +# Default: 0 + +# [connRDMABufNum], [connRDMABufSize], [connRDMAFragmentSize] +# InfiniBand RDMA buffer settings. +# connRDMABufSize is the maximum size of a buffer (in bytes) that will be sent +# over the network; connRDMABufNum is the number of available buffers that can +# be in flight for a single connection. These client settings are also applied +# on the server side for each connection. Ideally, the largest, commonly used +# filesytem chunksize should be < connRDMABufNum * connRDMABufSize to achieve +# the best performance. +# +# The minimum usable value for connRDMABufNum is 3, which is required by the +# BeeGFS RDMA protocol. Lower values will immediately result in communication +# failures. +# +# connRDMAFragmentSize determines how contiguous memory is allocated per +# buffer. If connRDMAFragmentSize=4096 and connRDMABufSize=8192, each buffer +# is allocated in 2 regions of 4096 contiguous bytes. Less fragmentation +# improves performance. The value "none" indicates that buffers will not be +# fragmented, resulting in allocation of contiguous regions of +# connRDMABufSize. The value "page" uses the Linux PAGE_SIZE as the +# fragmentation value. +# The reason for using fragmentation is that large allocations +# are more likely to fail if there is a shortage of heap memory. The minimum +# fragmentation value is PAGE_SIZE. Using larger values (or 0) should improve +# performance and allows for larger values of connRDMABufSize. +# +# Note: RAM usage per connection is connRDMABufSize x connRDMABufNum x 2. Keep +# resulting RAM usage (x connMaxInternodeNum x number_of_clients) on the +# server in mind when increasing these values. +# Default: 70, 8192, page + +# [connRDMAMetaBufNum], [connRDMAMetaBufSize], [connRDMAMetaFragmentSize] +# InfiniBand RDMA buffer settings for connections to beegfs-meta. +# These settings behave in the same way as connRDMABufNum, connRDMABufSize +# and connRDMAFragmentize except that they are used for connections to +# beegfs-meta. +# Metadata messages are usuallly small and do not require the large amount +# of buffer space that is typically configured for connections to +# beegfs-storage. One exception to this would be if large extended attributes +# are added to files. +# connRDMAMetaBufNum = "default" indicates that connRDMABufNum should be used. +# connRDMAMetaBufSize = "default" indicates that connRDMABufSize should be used. +# connRDMAMetaFragmentSize = "default" indicates that connRDMAFragmentSize +# should be used. +# The minimum value for connRDMAMetaBufNum is 3. +# Default: default, default, default + +# [connRDMATypeOfService] +# InfiniBand provides the option to set a type of service for an application. +# This type of service can be used by your subnet manager to provide Quality of +# Service functionality (e.g. setting different service levels). +# In openSM the service type will be mapped to the parameter qos-class, which +# can be handled in your QoS configuration. +# See +# www.openfabrics.org/downloads/OFED/ofed-1.4/OFED-1.4-docs/ +# QoS_management_in_OpenSM.txt +# for more information on how to configure openSM for QoS. +# This parameter sets the type of service for all outgoing connections of this +# daemon. +# Default: 0 (Max: 255) + +# [connRDMAKeyType] +# In RDMA, an "rkey" is used to provide an access token for a peer to access +# local memory regions that are registered for RDMA. Historically, +# BeeGFS used either a "DMA key" or an "unsafe global rkey" depending upon +# whether or not "unsafe global rkey" is supported by the operating system. +# This is now selectable. "DMA key" is not supported on kernel >= 4.9 +# unless MOFED is installed. If an unsupported option is specified, there +# will be warnings in syslog and RDMA connections will not be established. +# Use of "unsafe global rkey" is preferred, but generates a syslog message +# every time an RDMA connection is established: "enabling unsafe global rkey". +# Neither option is considered "safe" because they both provide access +# to all DMA mapped memory for a given connection. This technique is +# used to provide better performance for small I/O requests. +# "register" uses a memory registration per connection to provide an rkey. +# "register" is not compatible with NVIDIA GPUDirect Storage. +# Specify "dma" or "register" to squelch the syslog warning. +# Values: "global" (unsafe global rkey), "dma" (DMA key), "register" +# (memory registration) +# Default: "global" + +# [connTcpOnlyFilterFile] +# The path to a text file that specifies IP address ranges to which no RDMA +# connection should be established. This is useful e.g. for environments where +# all hosts support RDMA, but some hosts cannot connect via RDMA to some other +# hosts. +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. +# Default: + +# [connMessagingTimeouts] +# These constants are used to set some of the connection timeouts for sending +# and receiving data between services in the cluster. They used to be hard-coded +# (CONN_LONG_TIMEOUT, CONN_MEDIUM_TIMEOUT and CONN_SHORT_TIMEOUT) but are now +# made configurable for experimentation purposes. +# This option takes three integer values of milliseconds, separated by a comma +# in the order long, medium, short. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 600000,90000,30000 + +# [connRDMATimeouts] +# These constants are used to set some of the timeouts for sending and receiving +# data between services in the cluster via RDMA. They used to be +# hard-coded IBVSOCKET_CONN_TIMEOUT_MS, IBVSOCKET_COMPLETION_TIMEOUT_MS, +# IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS, +# IBVSOCKET_FLOWCONTROL_ONRECV_TIMEOUT_MS and a 10000 literal for poll timeout +# but are now made configurable for experimentation purposes. +# This option takes five integer values of milliseconds, separated by a comma +# in the order connectMS, completionMS, flowSendMS, flowRecvMS and pollMS. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 5000,300000,180000,180000,10000 + + +# --- Section 4.2: [Logging] --- +# + +# [logClientID] +# Defines whether the ClientID should appear in each log line. +# This is mainly helpful if BeeGFS is mounted multiple times on this machine. +# Default: false + +# [logLevel] +# Defines the amount of log messages. The higher this level, the more detailed +# the log messages will be. +# Level 3 will print connection messages, level 4 will print syscall messages, +# level 5 will print debug messages. +# Note: Levels above 3 might decrease performance. +# Default: 3 (Max: 5) + +# +# --- Section 4.3: [Quota Settings] --- +# + +# [quotaEnabled] +# Enables user and group quota support by transferring extra user data to the +# servers. This uses quota information of the underlying file systems on the +# storage servers, which needs to be enabled by the server administrator. +# Note: In the first implementation, only quota monitoring is available. +# Note: Get quota information with "beegfs-ctl --getquota". +# Note: If this option is true, performance might be slightly decreased due to +# extra information tracking. +# Default: false + + +# +# --- Section 4.4: [System Settings] --- +# + +# [sysCacheInvalidationVersion] +# +# Enable the client to invalidate its cache and reload the inode of a file when +# the version parameter of the file on metadata changes due to internal metadata +# operations such as stripe pattern change. This is done by comparing the +# version parameters on client and metadata on the first lookup after internal +# metadata changes. If the versions differ, the client invalidates the cache and +# reloads the inode. +# Note: If this option is set to true, performance may be decreased. +# Default: true + +# [sysCreateHardlinksAsSymlinks] +# Create a symlink when an application tries to create a hardlink for files in +# different directories. +# Default: false + +# [sysMountSanityCheckMS] +# Perform some basic checks during mount (e.g. whether the client helper daemon +# and storage servers are reachable). Mounting will fail if a problem is +# detected. +# Values: Set the time (in ms) you want to spend waiting for the servers +# (especially the management daemon) to respond. Use 0 to disable all checks +# and allow mounting even if no servers are reachable. +# Default: 11000 + +# [sysSessionCheckOnClose] +# Checks for a valid session on the storage servers when a file is closed. If +# this option is set to true, a potential cache loss from a crash of a storage +# server can be detected. This will be reported to the user application as a +# close() error code. +# Note: There is also a session check included in all read/write/fsync messages, +# which is independent of this setting. +# Note: If this option is set to true, more network messages are required on +# close(), so performance will decreased. +# Default: false + +# [sysSessionChecksEnabled] +# Enable session checks in read/write/fsync operations to be able to detect +# server crashes that could have caused a loss of server side caches. Disabling +# these checks is useful in certain system configurations to be able to cleanly +# resume I/O after a server crash/unclean failover. +# WARNING: Disabling session checks can lead to undetected cache loss and +# therefore silent data corruption on the storage servers. Only disable the +# checks if absolutely necessary and if there are measures in place to prevent +# cache loss (synchronous mounts, battery backed caches) on the storage servers. +# Default: true + +# [sysSyncOnClose] +# Sync file contents on close. If this option is set to true, the storage +# servers will flush the write cache of a file to disk immediately when it is +# closed by the application. If this option is set to false, the write cache +# will be flushed to disk asynchronously after a few seconds. +# Note: If this option is true, performance will be decreased. +# Default: false + +# [sysTargetOfflineTimeoutSecs] +# Timeout until all storage targets and metadata nodes are considered offline +# when no target state updates can be fetched from the management server. +# If this value is 0, targets will never be set to offline due to an +# unreachable management node and will stay in state probably-offline. +# Note: This must be at least twice as large as the value of +# sysTargetOfflineTimoutSecs in the server config files. +# Values: time in seconds +# Default: 900 + +# [sysUpdateTargetStatesSecs] +# Interval in which the states of the storage targets are checked. +# Note: This must be significantly shorter than the sysTargetOfflineTimeoutSecs +# value set in the server (recommendation: maximum 1/3 of it). +# Values: time in seconds +# Default: 30 + +# [sysXAttrsEnabled] +# Enable extended attributes (also know as EAs/xattrs). +# Default: false + +# [sysXAttrsCheckCapabilities] +# Check inodes for existing "security.capability" extended attribute and +# optionally cache to reduce metadata requests and increase write performance. +# The Linux kernel uses a security mechanism that automatically removes +# setuid/setgid bits and capabilities from files when they are changed. This is +# done to prevent users from executing binaries with elevated privileges that +# were changed after the privileges were originally set. That mechanism requires +# that, by default, the kernel has to check each file for existing capabilities +# on every write which leads to a large overhead in metadata RPCs to fetch the +# "security.capability" extended attribute. To optimize this, Linux allows file +# systems to set a flag (S_NOSEC) on the file, which short-circuits these +# checks. +# This configuration option configures the file system mount to allow the +# client to either always check, check once and cache that flag after a first +# lookup of the extended attribute returns an empty result, or set the flag on +# inode creation and never check. The flag will automatically be cleared when +# capabilities are modified on this client. It will, however, currently not +# be cleared when a different client modifies capabilities or sets setuid/setgid +# bits, which can lead to capabilities not being cleared, even after the file is +# written to. If this is a concern, this option should be set to "always". +# As long as BeeGFS is mounted using the "nosuid" mount option (which is +# recommended and the default setting), elevating privileges via setuid/setgid +# bits and capabilities is disabled and it is safe to set this option to "never". +# Possible values: +# always (always check for security xattr, never cache the result) +# cache (check for security xattr once, then cache) +# never (mark new inodes immediately, never check security xattr) +# Default: never + +# [sysBypassFileAccessCheckOnMeta] +# Allow this client to bypass file access restrictions enforced by metadata +# servers. When enabled, the client is permitted to open files even if access +# restrictions are currently in place (e.g., file marked read-only, +# write-locked, or fully restricted). This setting is primarily intended for +# specialized clients in controlled environments, such as HSM (Hierarchical +# Storage Management) systems that need to restore file data, or backup/recovery +# tools that require access to otherwise locked files. +# Note: This setting only affects metadata-level access checks and has no effect +# on other security or permission mechanisms. +# Default: false + + +# [sysACLsEnabled] +# Allow the creation and enforcement of Access Control Lists (ACLs). +# Note: Only works if sysXAttrsEnabled=true. +# Note: Requires at least Linux kernel version 3.2. +# Note: Enabling this setting can affect metadata performance. +# Default: false + +# [sysFileEventLogMask] +# Specifies which file system events shall be logged by the metadata servers. If +# unset, this client doesn't send log events at all. This can either be "none" +# or a comma separated list of event types to log. The following event types +# (and any comma-separated combination) are possible: +# "flush" (explicit data flushes on files), "close" (close writable file), +# "trunc" (file truncation), "setattr" (set attributes), +# "link-op" (create,mkdir,mknod,create symlink,create hardlink,rmdir,unlink, rename), +# "read" (deprecated, see notes) or "open-read" (file opened in read-only mode), +# "open-write" (file opened in write only mode), +# "open-readwrite" (file opened for both read and write), +# Note: If a client opens a file multiple times, "close" will only generate an +# event if the last fd is closed. If events for each fd shall be generated, +# "flush" needs to be used. However, "flush" might have a small performance +# impact. Also note that "read" is deprecated for file opening in read-only mode, +# but it is still allowed for backward compatibility. It is recommended to use +# "open-read" instead of "read" for clarity and consistency. +# Example: sysFileEventLogMask = close,trunc,setattr,link-op,open-read +# Default: none + +# [sysRenameEbusyAsXdev] +# Changes the semantics of rename() to return an EXDEV error if a file could not +# be moved because it is in use (instead of the default EBUSY). Applications and +# tools like mv can handle EXDEV and fall back to copy/unlink for the files. +# This is mostly useful for NFS exports, where files may not be closed by the +# server until after the last open file handle has been closed by clients. This +# can cause spurious EBUSY errors in clients that close a file and rename it +# immediately afterwards. +# Default: false + + +# +# --- Section 4.5: [Tuning] --- +# + +# [tuneFileCacheType] +# Sets the file read/write cache type. +# Values: "none" (disable client caching), "buffered" (use a pool of small +# static buffers for write-back and read-ahead), "native" (use the kernel +# pagecache), "paged" (experimental, deprecated). +# Note: The cache protocols are currently non-coherent (but caches are +# automatically flushed when a file is closed). +# Note: When client and servers are running on the same machine, "paged" mode +# contains the typical potential for memory allocation deadlocks (also known +# from local NFS server mounts). So do not use "paged" mode for clients that +# run on a metadata or storage server machine. +# Default: buffered + +# [tunePreferredMetaFile], [tunePreferredStorageFile] +# Path to a text file that contains the numeric IDs of preferred storage targets +# and metadata servers. These will be preferred when the client creates new file +# system entries. This is useful e.g. to take advantage of data locality in the +# case of multiple data centers. If unspecified, all available targets and +# servers will be used equally. +# Usage: One targetID per line for storage servers, one nodeID per line for +# metadata servers. +# Note: TargetIDs and nodeIDs can be queried with the beegfs-ctl tool. +# Default: + +# [tuneRemoteFSync] +# Controls whether fsync() syscalls from a user application should only be +# executed on the client to transfer data from the client cache to server +# cache (=false); or also on the servers to flush the server's cached file +# data to the disks (=true). +# Default: true + +# [tuneUseGlobalAppendLocks] +# Controls whether files opened in append mode should be protected by locks on +# the local machine only (=false) or globally on the servers (=true). +# Default: false + +# [tuneUseGlobalFileLocks] +# Controls whether application advisory file locks via flock() and fcntl() +# should be checked for conflicts on the local machine only (=false) or +# globally on the servers (=true). +# Default: false + +# [tuneCoherentBuffers] +# Enables or disables coherence between the buffers used by +# tuneFileCacheType=buffered and the page cache. +# If a file is concurrently accessed via mmap() regions and read()/write() +# system calls, the buffers used by tuneFileCacheType=buffered and the page +# cache used by mmap() may go out of sync - changes made in an mmap()ed region +# may not be visible to read() calls immediately, or changes made by write() +# calls may not be immediately reflected in mmap()ed regions. +# Many programs that use both methods of accessing a file assume that +# read()/write() and mmap() present the same view of the file, if this is not +# the case, those programs may not work correctly. Programs that have been +# observed to misbehave with non-coherent buffers are, for example, git and +# some in-memory database applications. +# When this option is enabled, files that are currently mmap()ed will behave as +# though they had been opened with tuneFileCacheType=none +# Default: true + + +# +# --- Section 5: [Expert options] +# + +# [connUnmountRetries] +# Retry communications during unmount. +# If this option is set to `true` and a communication error occurs during +# unmont, for example due to a transient network fault, the unsuccessful +# communications will be retried normally. When set to `false` they will not be +# retried; this leads to a quicker unmount, but resources allocated to current +# client will not be freed for a few hours. +# Default: true + +# [tuneFileCacheBufSize] +# When using buffered mode: maximum size of the (contiguous) data cache for an +# open file. +# When using native mode: threshold for direct operations. If a read() or +# write() passes a buffer size larger than tuneFileCacheBufSize the client will +# bypass the page cache and send/receive the data directly to/from the storage +# servers. +# Default: 524288 (512KiB) + +# [tuneFileCacheBufNum] +# When using buffered mode: maximum number of file caches to preallocate +# for the mount. When a file is opened a cache is allocated, up to this number. +# If the maximum number of caches is reached no cache is allocated and all +# read/write operations for the file go to the storage servers directly. +# Default: 4*(number of CPUs) + +# [tunePageCacheValidityMS] +# Maximum lifetime of cached data in the page cache. +# In buffered mode the page cache is used for mmap(), in native mode the page +# cache is used for all data. Data in the page cache that was not yet written +# to the storage server is written after at most this time, data that was read +# but not modified is discarded. +# Default: 2000000000 (approx. 23 days) + +# [tuneDirSubentryCacheValidityMS] +# Validity time of directory attribute data, in milliseconds. +# Attributes of directories (eg stat() data) that have been loaded from the +# metadata servers are assumed to be valid for this amount of time without +# requiring a refresh. Once the time has passed the next access will cause a +# refresh. +# Default: 1000 + +# [tuneFileSubentryCacheValidityMS] +# Validity time of file attribute data, in milliseconds. +# Attributes of files (eg stat() data) that have been loaded from the metadata +# servers are assumed to be valid for this amount of time without requiring a +# refresh. Once the time has passed the next access will cause a refresh. +# Default: 0 + +# [tuneENOENTCacheValidityMS] +# Validity time of the non-existing file in milliseconds. +# A negative result of a stat call indicating "No such file or directory" +# (ENOENT) is assumed to be valid for this amount of time without requiring a +# new request to the meta server. Once the time has passed the next access will +# cause a refresh. +# Default: 0 + +# [tunePathBufSize] +# Size of buffers used for constructing paths. +# Whenever a full path must be constructed (eg for log messages) a preallocated +# buffer of this size will be used. +# Default: 4096 + +# [tunePathBufNum] +# Number of path buffers for path construction. +# Determines how many path buffers are preallocated during mount. If no buffers +# are available for an operation the operation must wait for another thread to +# free enough buffers. +# Default: 8 + +# [tuneMsgBufSize] +# Size of buffers used for messaging. +# Messages sent and received by the client (except logging messages) use +# buffers preallocated at mount time. Buffers are allocated with the size +# given here. +# Default: 65536 + +# [tuneMsgBufNum] +# Number of message buffers. +# During mount this many message buffers are preallocated. If an operation +# requires communication with a server but all buffers are used, the operation +# must wait until a buffer is released. +# Default: 4*(number of CPUs) + 1 + +# [tuneRefreshOnGetAttr] +# If set to `true`, file attributes will be loaded from the server on each call +# to fstat(). When set to `false` a call to fstat() may return stale +# information for files that are not currently open; this can happen mainly +# when NFS exports are used. +# Default: false + +# [tuneInodeBlockBits] +# Sets the block size of file on the mountpoint to 2**tuneInodeBlockBits. +# Default: 19 (512KiB) + +# [tuneEarlyCloseResponse] +# Request close responses from the metadata server before the file is fully closed. +# This may improve close() performance, but closed files may be accounted as +# open for a short time after close() has returned. Files accounted as open +# cannot be moved. +# Default: false + +# [tuneUseBufferedAppend] +# Used only buffered mode. If set, writes to files opened with O_APPEND will be +# cached. Ignored unless tuneUseGlobalAppendLocks is also set. +# Default: true + +# [tuneStatFsCacheSecs] +# Validity time of statfs() results, in seconds. +# Results of statfs(), once queried from the storage servers, will be cached +# for this amount of time. +# Default: 10 + +# [sysInodeIDStyle] +# Sets the hash function used to compute inode numbers from metadata IDs. +# The *32 options produce 32 bit inodes numbers, the *64 variants produce 64 +# bit inode numbers. +# Possible values: +# hash32 +# hash64 +# md4hash32 +# md4hash64 +# Default: md4hash64 diff --git a/client_module/build/dist/etc/beegfs-mounts.conf b/client_module/build/dist/etc/beegfs-mounts.conf new file mode 100644 index 0000000..ff805cc --- /dev/null +++ b/client_module/build/dist/etc/beegfs-mounts.conf @@ -0,0 +1 @@ +/mnt/beegfs /etc/beegfs/beegfs-client.conf diff --git a/client_module/build/dist/etc/default/beegfs-client b/client_module/build/dist/etc/default/beegfs-client new file mode 100644 index 0000000..7ece8e9 --- /dev/null +++ b/client_module/build/dist/etc/default/beegfs-client @@ -0,0 +1,26 @@ +# BeeGFS client service configuration. + +# Set to "NO" to disable start of the BeeGFS client via the init script. +START_SERVICE="YES" + +# Set to "YES" if you want to start multiple clients with different +# configuration files on this machine. +# +# Create a subdirectory with the ending ".d" in "/etc/beegfs/" for every config +# file. The subdirectory name will be used to identify a particular client +# instance for init script start/stop. +# +# Note: The original config file in /etc/beegfs will not be used when multi-mode +# is enabled. +# +# Example: /etc/beegfs/scratch.d/beegfs-client.conf +# $ /etc/init.d/beegfs-client start scratch +MULTI_MODE="NO" + +# Mount hook will be executed before any mount or unmount operation, +# and additionally after the operation succeeded. +# The first argument passed is either 'pre-mount', 'post-mount', 'pre-unmount', +# or 'post-unmount'. +# The second argument is the mount point. This can be useful to set up bind +# mounts on top of a BeeGFS, for example. +#MOUNT_HOOK=/etc/beegfs/beegfs-client-mount-hook.example diff --git a/client_module/build/dist/sbin/beegfs-client.init b/client_module/build/dist/sbin/beegfs-client.init new file mode 100644 index 0000000..287a388 --- /dev/null +++ b/client_module/build/dist/sbin/beegfs-client.init @@ -0,0 +1,439 @@ +#!/bin/bash + +# NOTE: We expclicitly use "bash" here, as rc.status is not shell compliant +# and will complain with the message below, if "/bin/sh" is given +# +# /etc/rc.status: line 43: test: -eq: unary operator expected +# /etc/rc.status: line 44: test: -eq: unary operator expected +# + +set -e + +# +### BEGIN INIT INFO +# Provides: beegfs-client +# Required-Start: +# Required-Stop: +# Should-Start: $network $local_fs $syslog $time beegfs-helperd beegfs-mgmtd beegfs-meta beegfs-storage openib openibd rdma opensmd opensm $named slapd autofs ypbind nscd nslcd sshd +# Should-Stop: $network $local_fs $syslog $time beegfs-helperd beegfs-mgmtd beegfs-meta beegfs-storage openib openibd rdma opensmd opensm $named slapd autofs ypbind nscd nslcd sshd +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# chkconfig: 35 99 5 +# Short-Description: BeeGFS Client +# Description: Start BeeGFS Client +### END INIT INFO + +SERVICE_NAME="BeeGFS Client" + +# Check for missing binaries (stale symlinks should not happen) +# Note: Special treatment of stop for LSB conformance +CLIENT_MOD=beegfs + +SYSCONFIG_FILE=/etc/default/beegfs-client +BEEGFS_MOUNT_CONF="/etc/beegfs/beegfs-mounts.conf" +FORCE_AUTO_BUILD="/var/lib/beegfs/client/force-auto-build" +SUBSYS=/var/lock/subsys/beegfs-client + +AUTOBUILD_CONF="/etc/beegfs/beegfs-client-autobuild.conf" +AUTOBUILD_CONF_SAVED="/var/lib/beegfs/client/beegfs-client-autobuild.conf.old" + +CLIENT_SRC_PATH="/opt/beegfs/src/client" + +SELINUX_OPT="" + +DEFAULT_FS_TYPE="beegfs" +BUILD_FSTYPE_FILE="beegfs.fstype" + +# we add "/usr/sbin" & co because "su" doesn't automatically add them +# on some systems. +EXTRA_PATH="/sbin:/usr/sbin/:/bin:/usr/bin:/usr/local/bin:/usr/local/sbin" +PATH="$EXTRA_PATH:$PATH" + +# source function library +. /etc/beegfs/lib/init-multi-mode.beegfs-client + +# Return values acc. to LSB for all commands but status: +# 0 - success +# 1 - generic or unspecified error +# 2 - invalid or excess argument(s) +# 3 - unimplemented feature (e.g. "reload") +# 4 - user had insufficient privileges +# 5 - program is not installed +# 6 - program is not configured +# 7 - program is not running +# 8--199 - reserved (8--99 LSB, 100--149 distrib, 150--199 appl) +# +# Note that starting an already running service, stopping +# or restarting a not-running service as well as the restart +# with force-reload (in case signaling is not supported) are +# considered a success. + +handle_selinux() +{ + selinuxenabled 2>/dev/null || return + + # we do not support SELINUX right now and it only causes trouble, so + # we try to deactivate it + SELINUX_OPT="fscontext=system_u:object_r:tmp_t:s0" + + echo + echo "WARNING: SELINUX IS ENABLED. BeeGFS might not work!" + echo " Before you consider to contact BeeGFS support, please try to" + echo " disable selinux (Usually in /etc/selinux/config)!" + echo +} + + +rmmod_beegfs() +{ + for module in `lsmod |egrep "^beegfs" | sed -e 's/\s.*$//g'`; do rmmod $module; done +} + +# Build beegfs. The autobuild will install modules to +# /lib/modules/`uname -r`/updates/fs/beegfs_autobuild +# if the build was successful and it will also run depmod. +build_beegfs() +{ + echo "- BeeGFS module autobuild" + + mkdir -p `dirname $SUBSYS` + + # locked section (to avoid build problems when this script is called from + # multiple processes concurrently) + ( + # (note: fd 16 is a more or less arbitrary number) + flock 16 || echo "WARNING: flock for build failed (continuing without lock...)" >&2 + + + for dir in ${CLIENT_SRC_PATH}/*; do + + set +e + echo $dir | grep opentk >/dev/null 2>&1 + [ $? -ne 0 ] || continue # we ignore opentk + + # ingore paths with missing Makfile + [ -f ${dir}/build/Makefile ] || continue + + + if [ -f ${dir}/build/${BUILD_FSTYPE_FILE} ]; then + fstype=`cat ${dir}/build/${BUILD_FSTYPE_FILE}` + TARGET_PARAM="TARGET=${fstype}" + else + TARGET_PARAM="" + fi + + set -e + + MAKE_ARGS="KMOD_INST_DIR=/lib/modules/$(uname -r)/updates/fs/beegfs_autobuild" + make -C ${dir}/build auto_rebuild_configured ${MAKE_ARGS} ${TARGET_PARAM} --silent + make -C ${dir}/build clean ${MAKE_ARGS} --silent # always clean up + + done + + set -e # ensure -e here (continue conditions above didn't restore it) + + install -D $AUTOBUILD_CONF $AUTOBUILD_CONF_SAVED + ) 16>$SUBSYS.init-autobuild + + + # we do not want to delete the $FORCE_AUTO_BUILD file here yet, + # as we do not test here, if the modules we just built will load at all +} + +# Test if the user updated the build config. If so, we touch +# the $FORCE_AUTO_BUILD, which will trigger a rebuild +test_updated_autobuild_conf() +{ + set +e + RC=0 + # diskless installations might not have those files + if [ -e $AUTOBUILD_CONF -a -e $AUTOBUILD_CONF_SAVED ]; then + diff $AUTOBUILD_CONF $AUTOBUILD_CONF_SAVED >/dev/null 2>&1 + RC=$? + fi + [ $RC -eq 0 ] || touch $FORCE_AUTO_BUILD + set -e +} + +# Test and warn if user specified OFED_INCLUDE_PATH but appears to use in-tree +# drivers or other way around. +warn_on_ofed_mismatch() +{ + set +e + + modinfo ib_core > /dev/null 2>&1 + if [ $? -ne 0 ]; then + # "modinfo ib_core" not working => cancel (because the user + # might have special mechanisms to load ib modules). + set -e + return + fi + + # ibverbs enabled => check if include path set or not + + grep '^buildArgs.*OFED_INCLUDE_PATH' $AUTOBUILD_CONF > /dev/null 2>&1 + if [ $? -eq 0 ]; then + # ofed include path specified => warn if in-tree modules used + modinfo ib_core | grep 'filename:.*updates/' > /dev/null 2>&1 + if [ $? -ne 0 ]; then + echo "WARNING: You probably should not specify OFED_INCLUDE_PATH in $AUTOBUILD_CONF" + fi + else + # no ofed include path specified => warn if out-of-tree modules + modinfo ib_core | grep 'filename:.*updates/' > /dev/null 2>&1 + if [ $? -eq 0 ]; then + echo "WARNING: You probably need to specify" \ + "OFED_INCLUDE_PATH in $AUTOBUILD_CONF" + fi + fi + + set -e +} + +source ${SYSCONFIG_FILE} +if [ "${MULTI_MODE}" = "YES" -o "${MULTI_MODE}" = "yes" ]; then + set +e + init_multi_mode $1 $2 + exit $? +fi + +RETVAL=0 +case "$1" in + start) + if [ -f "${SYSCONFIG_FILE}" ]; then + if [ "${START_SERVICE}" = "NO" -o "${START_SERVICE}" = "no" ]; then + echo "${SERVICE_NAME} not set to be started" + exit 0 + fi + fi + + set +e + handle_selinux + set -e + + echo "Starting ${SERVICE_NAME}: " + + test_updated_autobuild_conf + + set +e + echo "- Loading BeeGFS modules" + if [ -f "$FORCE_AUTO_BUILD" ]; then + # a new version was installed or the user updated the + # auto-build config, so we force a rebuild + rmmod_beegfs + rc=1 + else + modprobe $CLIENT_MOD + rc=$? + fi + + if [ $rc -ne 0 ]; then + set -e + build_beegfs + modprobe $CLIENT_MOD || (warn_on_ofed_mismatch && false) + rm -f $FORCE_AUTO_BUILD + fi + set -e + + echo "- Mounting directories from $BEEGFS_MOUNT_CONF" + + mkdir -p `dirname $SUBSYS` + touch $SUBSYS + + OLDIFS="$IFS" + IFS=$'\n' + file=`tr -d '\r' < $BEEGFS_MOUNT_CONF` # read all lines at once and remove CR from dos files + for line in $file; do + if [ -z "$line" ]; then + continue # ignore empty line + elif echo "$line" | grep -qs "^\s*#" ; then + continue # ignore shell style comments + fi + + mnt=`echo $line | awk '{print $1}'` + cfg=`echo $line | awk '{print $2}'` + fstype=`echo $line | awk '{print $3}'` + extra_mount_opts=`echo $line | awk '{print $4}'` + + if [ -z "$mnt" -o -z "$cfg" ]; then + echo "Invalid config line: \"$line\"" + continue + fi + + if [ -z "$fstype" ]; then + fstype=${DEFAULT_FS_TYPE} + fi + + set +e + + mount -t ${fstype} | grep "${mnt} " >/dev/null 2>&1 + if [ $? -eq 0 ]; then + # already mounted + set -e + continue + fi + + set -e + + # mkdir required for admon-based installation + if [ ! -e ${mnt} ]; then + mkdir -p ${mnt} + fi + + exec_mount_hook pre-mount "${mnt}" + + mount -t ${fstype} beegfs_nodev ${mnt} \ + -ocfgFile=${cfg},_netdev,nosuid,${SELINUX_OPT},${extra_mount_opts} + + exec_mount_hook post-mount "${mnt}" + + done + + RETVAL=$? + IFS="$OLDIFS" + ;; + stop) + echo "Shutting down ${SERVICE_NAME}: " + + RETVAL=$? + echo "- Unmounting directories from $BEEGFS_MOUNT_CONF" + OLDIFS="$IFS" + IFS=$'\n' + file=`cat $BEEGFS_MOUNT_CONF` # we have to read all lines at once + for line in $file; do + if [ -z "$line" ]; then + continue # ignore empty line + elif echo "$line" | grep -qs "^\s*#" ; then + continue # ignore shell style comments + fi + + mnt=`echo $line | awk '{print $1}'` + cfg=`echo $line | awk '{print $2}'` + if [ -z "$mnt" -o -z "$cfg" ]; then + echo "Invalid config line: \"$line\"" + continue + fi + + exec_mount_hook pre-unmount "${mnt}" + + set +e + res=`umount ${mnt} 2>&1` + if [ $? -ne 0 ]; then + # umount failed, ignore the failure if not mounted at all + echo $res | grep "not mounted" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + # Is mounted, abort. + echo "umount failed: $res" + exit 1 + fi + fi + set -e + + exec_mount_hook post-unmount "${mnt}" + + done + IFS="$OLDIFS" + + echo "- Unloading modules" + set +e + res=`rmmod_beegfs` + if [ $? -ne 0 ]; then + # rmmod failed, ignore it if the module is not loaded at all + echo $res | grep "does not exist in" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + # Is loaded, check if a BeeGFS is still mounted (all from the list are unmounted, so + # it must be a BeeGFS, which was manually mounted) + STILL_MOUNTED=$(mount -t beegfs | wc -l) + if [ ${STILL_MOUNTED} -eq 0 ]; then + # no more BeeGFS instances, no reason why rmmod should be failing, abort + echo "rmmod failed: $res" + exit 1 + else + # BeeGFS instances mounted, so failure to rmmod is normal + echo "WARNING: Unloading BeeGFS modules failed. There are still mounted BeeGFS" \ + "instances, which do not seem to be managed by the init-script mechanism" \ + "(beegfs-mounts.conf)." + fi + fi + fi + RETVAL=$? + set -e + + if [ $RETVAL -eq 0 ]; then rm -f $SUBSYS; fi + # Remember status and be verbose + ;; + restart) + ## Stop the service and regardless of whether it was + ## running or not, start it again. + $0 stop + $0 start + RETVAL=$? + # Remember status and be quiet + ;; + rebuild) + # Just rebuild modules. The user needs to call "restart" to make use + # of those new modules. + build_beegfs + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + rm -f $FORCE_AUTO_BUILD + fi + ;; + try-restart|condrestart) + ## Do a restart only if the service was active before. + ## Note: try-restart is now part of LSB (as of 1.9). + ## RH has a similar command named condrestart. + set +e + $0 status + if test $? = 0 + then + set -e + $0 restart + RETVAL=$? + else + set -e + RETVAL=7 + fi + ;; + status) + # Return value is slightly different for the status command: + # 0 - service up and running + # 1 - service dead, but /var/run/ pid file exists + # 2 - service dead, but /var/lock/ lock file exists + # 3 - service not running (unused) + # 4 - service status unknown :-( + # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.) + set +e + + echo -n "Checking for service $SERVICE_NAME: " + + lsmod | grep $CLIENT_MOD >/dev/null 2>&1 + if [ $? -eq 0 ] + then + echo "is running..." + + mount -t beegfs | grep beegfs >/dev/null 2>&1 + if [ $? -eq 0 ]; then + mount -t beegfs | cut "-d " -f1-3 + echo + RETVAL=0 + else + echo ">> No active beegfs mounts detected." + echo + RETVAL=4 + fi + else + echo "is stopped." + echo + RETVAL=3 + fi + + set -e + ;; + *) + echo "Usage: $0 {start|stop|status|restart|rebuild|condrestart|try-restart}" + exit 3 + ;; + esac +exit $RETVAL + diff --git a/client_module/build/dist/sbin/beegfs-setup-client b/client_module/build/dist/sbin/beegfs-setup-client new file mode 100755 index 0000000..0b6000b --- /dev/null +++ b/client_module/build/dist/sbin/beegfs-setup-client @@ -0,0 +1,128 @@ +#!/bin/bash + +# License: BeeGFS EULA + +# constant definitions +# (for configurables see below) + +DEFAULT_CFG_PATH="/etc/beegfs/beegfs-client.conf" +MGMTD_CFG_KEY="sysMgmtdHost" +QUOTA_ENABLED_CFG_KEY="quotaEnabled" + +print_usage() +{ + echo + echo "DESCRIPTION: Initialize or update the beegfs-client config file." + echo + echo "USAGE: `basename $0` [options]" + echo + echo " Recommended Options:" + echo + echo " -m - Hostname (or IP address) of management server." + echo " (Will be stored in client config file.)" + echo + echo " Other Options:" + echo + echo " -C - Do not update client config file." + echo + echo " -c - Path to client config file." + echo " (Default: ${DEFAULT_CFG_PATH})" + echo + echo " -f - Force actions, ignore warnings." + echo + echo " -h - Print this help." + echo + echo " -q - Enable quota support in config file." + echo " (Default: Quota support disabled)" + echo + echo "EXAMPLES:" + echo " * Example 1) Set \"storage01\" as management daemon host in config file:" + echo " $ `basename $0` -m storage01" + echo +} + +# update config file (if enabled) +update_config_file() +{ + # check if config file is defined + + if [ -z "${CFG_PATH}" ]; then + return 0 + fi + + echo "Updating config file: ${CFG_PATH}" + + if [ ! -f "${CFG_PATH}" ]; then + echo " * ERROR: Config file not found: ${CFG_PATH}" + exit 1 + fi + + if [ -n "${MGMTD_HOST}" ]; then + echo " * Setting management host: ${MGMTD_HOST}" + sed -i "s/\(^${MGMTD_CFG_KEY}.*=\).*/\1 ${MGMTD_HOST}/" ${CFG_PATH} + fi + + if [ -n "${QUOTA_ENABLED}" ]; then + echo " * Setting quota enabled: ${QUOTA_ENABLED}" + sed -i "s/\(^${QUOTA_ENABLED_CFG_KEY}.*=\).*/\1 ${QUOTA_ENABLED}/" ${CFG_PATH} + fi +} + +################## end of function definitions ############## + + +# configurable values and their defaults +# (for constants see above) + +CFG_PATH="$DEFAULT_CFG_PATH" # empty path means "don't update cfg file" +FORCE_ACTIONS="" +MGMTD_HOST="" +QUOTA_ENABLED="" + +# parse command line arguments +# (see print_usage() for description of parameters) + +while getopts "Cc:fhm:q" opt; do + case $opt in + C) + CFG_PATH="" + ;; + c) + CFG_PATH="$OPTARG" + ;; + f) + FORCE_ACTIONS="1" + ;; + h) + print_usage + exit 0 + ;; + m) + MGMTD_HOST="$OPTARG" + ;; + q) + QUOTA_ENABLED="true" + ;; + *) + echo "ERROR: Invalid argument" >&2 + print_usage + exit 1 + ;; + esac +done + +set -e + +# don't do anything if no arguments are provided + +if [ $# -eq 0 ]; then + print_usage + exit 1 +fi + +# update config file + +update_config_file + + +echo "All done." diff --git a/client_module/build/dist/sbin/mount.beegfs b/client_module/build/dist/sbin/mount.beegfs new file mode 100755 index 0000000..982c803 --- /dev/null +++ b/client_module/build/dist/sbin/mount.beegfs @@ -0,0 +1,94 @@ +#!/usr/bin/env bash + +# BeeGFS Client Mount Utility / External Mount Helper +# If placed in /sbin, this script is called automatically by `mount` whenever a filesystem with type +# `beegfs` is being mounted. It's main purpose is to do name resolution of the management hostname +# as the kernel module can't do that by itself. The hostname is taken from either the mount option +# string itself ('sysMgmtdHost=') or from the config file given by the mount options ('cfgFile='). + +function usage() { + echo "Usage: $(basename "$0") [-sfnv] [-o options]" + exit 1 +} + +SPEC="$1" +shift +DIR="$1" +shift + +[[ -n "$SPEC" && -n "$DIR" ]] || usage + +FLAGS= +OPTIONS= + +# Read in all possible flags (see `man mount 8`) +while getopts 'hsfnvo:t:' FLAG; do + case "$FLAG" in + s|f|n|v) + FLAGS+=("-$FLAG") + ;; + o) + OPTIONS="$OPTARG" + ;; + h|*) + usage + ;; + esac +done +shift "$((OPTIND -1))" + +# If there is a config file available and sysMgmtdHost is set, load the management hostname from the +# config file +CFG_FILE=$(echo "$OPTIONS" | grep -oP 'cfgFile=[^,]+' | cut -d= -f2) + +if [[ -n "$CFG_FILE" ]]; then + [[ -f "$CFG_FILE" ]] || { + echo "Can not open config file '$CFG_FILE'" + exit 1 + } + + H=$(grep -oP '^\s*sysMgmtdHost\s*=\s*\S+\s*$' "$CFG_FILE" | tr -d ' ' | cut -d= -f2) + if [[ $H == *$'\n'* ]]; then + echo "Multiple definitions of 'sysMgmtdHost' found in '$CFG_FILE'." + exit 1 + elif [[ -n "$H" ]]; then + HOST_NAME="$H" + fi +fi + +# If it has explicitly been set as a mount option, use that (overrides config file). +HOST_NAME_OPTION=$(echo "$OPTIONS" | grep -oP 'sysMgmtdHost=[^,]+' | cut -d= -f2) +if [[ -n "$HOST_NAME_OPTION" ]]; then + HOST_NAME="$HOST_NAME_OPTION" +fi + +# A management host must be provided +[[ -n "$HOST_NAME" ]] || { + echo "Can not determine management host - neither through a mount option ('sysMgmtdHost') nor through \ +a client config file ('cfgFile')." + exit 1 +} + +# Resolve management address +MGMTD_ADDR=$(getent ahostsv4 "$HOST_NAME" | cut -f1 -d' ' | head -n1) + +# If resolve fails, error out +[[ -n "$MGMTD_ADDR" ]] || { + echo "Can not resolve management host address using hostname '$HOST_NAME'" + exit 1 +} + +# If the host argument was given, replace it with the resolved address, otherwise append +if [[ -n "$HOST_NAME_OPTION" ]]; then + # shellcheck disable=SC2001 + OPTIONS=$(echo "$OPTIONS" | sed "s/sysMgmtdHost=[^,]\\+/sysMgmtdHost=$MGMTD_ADDR/g") +else + OPTIONS="$OPTIONS,sysMgmtdHost=$MGMTD_ADDR" +fi + +# Take the part behind the "." as fstype (typically "beegfs"). This allows multiple mount utilities +# and client modules being installed at the same time. +FS_TYPE=$(basename "$0" | sed 's/^.*\.//') + +set -ex +mount --internal -t "$FS_TYPE" --source "$SPEC" --target "$DIR" ${FLAGS[*]} -o"$OPTIONS" diff --git a/client_module/build/dist/usr/lib/systemd/system/beegfs-client.service b/client_module/build/dist/usr/lib/systemd/system/beegfs-client.service new file mode 100644 index 0000000..dbcb4e1 --- /dev/null +++ b/client_module/build/dist/usr/lib/systemd/system/beegfs-client.service @@ -0,0 +1,21 @@ +[Unit] +Description=Start BeeGFS Client +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=local-fs.target time-sync.target beegfs-mgmtd.service \ +#beegfs-meta.service beegfs-storage.service openib.service openibd.service rdma.service \ +#opensmd.service opensm.service nss-lookup.target nss-user-lookup.target slapd.service \ +#autofs.service ypbind.service nscd.service nslcd.service sshd.service +After=network-online.target local-fs.target time-sync.target \ +beegfs-mgmtd.service beegfs-meta.service beegfs-storage.service openib.service openibd.service \ +rdma.service opensmd.service opensm.service nss-lookup.target nss-user-lookup.target \ +slapd.service autofs.service ypbind.service nscd.service nslcd.service sshd.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/opt/beegfs/sbin/beegfs-client start +ExecStop=/opt/beegfs/sbin/beegfs-client stop + +[Install] +WantedBy=multi-user.target diff --git a/client_module/build/dist/usr/lib/systemd/system/beegfs-client@.service b/client_module/build/dist/usr/lib/systemd/system/beegfs-client@.service new file mode 100644 index 0000000..4fda10b --- /dev/null +++ b/client_module/build/dist/usr/lib/systemd/system/beegfs-client@.service @@ -0,0 +1,21 @@ +[Unit] +Description=Start BeeGFS Client +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=local-fs.target time-sync.target beegfs-mgmtd.service \ +#beegfs-meta.service beegfs-storage.service openib.service openibd.service rdma.service \ +#opensmd.service opensm.service nss-lookup.target nss-user-lookup.target slapd.service \ +#autofs.service ypbind.service nscd.service nslcd.service sshd.service +After=network-online.target local-fs.target time-sync.target \ +beegfs-mgmtd.service beegfs-meta.service beegfs-storage.service openib.service openibd.service \ +rdma.service opensmd.service opensm.service nss-lookup.target nss-user-lookup.target \ +slapd.service autofs.service ypbind.service nscd.service nslcd.service sshd.service + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=/opt/beegfs/sbin/beegfs-client start %I +ExecStop=/opt/beegfs/sbin/beegfs-client stop %I + +[Install] +WantedBy=multi-user.target diff --git a/client_module/build/feature-detect.sh b/client_module/build/feature-detect.sh new file mode 100755 index 0000000..90a8d88 --- /dev/null +++ b/client_module/build/feature-detect.sh @@ -0,0 +1,478 @@ +#!/bin/bash + +set -e + +# NOTE, we're using the argument array as part of this string here. As is +# often the case in shell scripts (especially when used with Makefiles) the +# quoting here is not correct in the sense that it can be broken for example +# with arguments that contain spaces. But it is expected to work with our input +# data. + +CFLAGS="-D__KERNEL__ $LINUXINCLUDE $* -DKBUILD_BASENAME=\"beegfs\" -DKBUILD_MODNAME=\"beegfs\"" + +_generate_includes() { + for i in "$@"; do + echo "#include <$i>" + done +} + +_marker_if_compiles() { + local marker=$1 + shift + + if $CC $CFLAGS -x c -o /dev/null -c - 2>/dev/null + then + echo -D$marker + fi +} + +_check_type_input() { + local tp=$1 + shift 1 + + _generate_includes "$@" + + cat <= 4.20 issues a warning (including stacktrace) if it _is_ set. +# +# We can't detect this from the headers, only by looking at the source. +# BUT: The source is not guaranteed to be available when building. +# +# We have tried to check for this "feature" by looking at the Linux version +# as reported by the LINUX_VERSION_CODE macro. +# +# BUT: Some non-vanilla < 4.20 kernels have backported the >= 4.20 +# functionality to < 4.20 kernels. So relying on the version code doesn't +# work either. +# +# What we're doing here now is our current best attempt to detect how the API +# must be used. We're checking something that is not really related to the +# actual function, but which seems to give the right results. +check_function \ + iov_iter_is_kvec 'bool(const struct iov_iter *)' \ + KERNEL_HAS_IOV_ITER_KVEC_NO_TYPE_FLAG_IN_DIRECTION \ + linux/uio.h + +# print_stack_trace() found on older Linux kernels < 5.2 +check_function \ + print_stack_trace 'void (struct stack_trace *trace, int spaces)' \ + KERNEL_HAS_PRINT_STACK_TRACE \ + linux/stacktrace.h + +# Starting from kernel 5.2, print_stack_trace() is demoted to tools/ directory +# so not available to us. +# Starting from kernel 5.16 print_stack_trace() is removed completely, but +# stack_trace_print() can be used instead. +# Notably, the identifier stack_trace_print() existed even in older Linux +# versions, but with a different signature and different functionality. +check_function \ + stack_trace_print 'void (unsigned long *trace, unsigned int nr_entries, int spaces)' \ + KERNEL_HAS_STACK_TRACE_PRINT \ + linux/stacktrace.h \ + +check_type 'struct file_lock_core' KERNEL_HAS_FILE_LOCK_CORE linux/filelock.h + +check_type 'struct proc_ops' KERNEL_HAS_PROC_OPS linux/proc_fs.h + +check_type sockptr_t KERNEL_HAS_SOCKPTR_T linux/sockptr.h + +check_function \ + sock_setsockopt 'int (struct socket *, int, int, sockptr_t, unsigned int)' \ + KERNEL_HAS_SOCK_SETSOCKOPT_SOCKPTR_T_PARAM \ + net/sock.h + +check_type time64_t KERNEL_HAS_TIME64 linux/ktime.h +check_function ktime_get_ts64 'void (struct timespec64 *)' KERNEL_HAS_KTIME_GET_TS64 linux/ktime.h +check_function ktime_get_real_ts64 'void (struct timespec64 *)' KERNEL_HAS_KTIME_GET_REAL_TS64 linux/ktime.h +check_function ktime_get_coarse_real_ts64 'void (struct timespec64 *)' KERNEL_HAS_KTIME_GET_COARSE_REAL_TS64 linux/ktime.h + +# latest kernel from 6.3 changes moved to timekeeping.h +check_function \ + ktime_get_ts64 "void (struct timespec64 *ts)" \ + KERNEL_HAS_KTIME_GET_TS64 \ + linux/timekeeping.h +check_function \ + ktime_get_real_ts64 "void (struct timespec64 *tv)" \ + KERNEL_HAS_KTIME_GET_REAL_TS64 \ + linux/timekeeping.h +check_function \ + ktime_get_coarse_real_ts64 "void (struct timespec64 *ts)" \ + KERNEL_HAS_KTIME_GET_COARSE_REAL_TS64 \ + linux/timekeeping.h + +check_function \ + generic_file_splice_read "ssize_t (struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int)" \ + KERNEL_HAS_GENERIC_FILE_SPLICE_READ \ + linux/fs.h + +check_function \ + generic_permission "int (struct inode *, int)" \ + KERNEL_HAS_GENERIC_PERMISSION_2 \ + linux/fs.h + +check_function \ + generic_permission "int (struct inode *, int, int (*check_acl)(struct inode *, int)" \ + KERNEL_HAS_GENERIC_PERMISSION_4 \ + linux/fs.h + +check_function \ + setattr_prepare "int (struct mnt_idmap *, struct dentry *, struct iattr *)" \ + KERNEL_HAS_SETATTR_PREPARE \ + linux/fs.h + +check_function \ + setattr_prepare "int (struct dentry *dentry, struct iattr *attr)" \ + KERNEL_HAS_SETATTR_PREPARE \ + linux/fs.h + +check_function \ + setattr_prepare "int (struct user_namespace *, struct dentry *, struct iattr *)" \ + KERNEL_HAS_SETATTR_PREPARE \ + linux/fs.h + +check_struct_field \ + inode_operations::get_acl \ + KERNEL_HAS_GET_ACL \ + linux/fs.h + +check_struct_field_type \ + inode_operations::get_acl "struct posix_acl* (*)(struct mnt_idmap *, struct dentry *, int)" \ + KERNEL_HAS_POSIX_GET_ACL_IDMAP \ + linux/fs.h + +check_struct_field_type \ + inode_operations::get_acl "struct posix_acl* (*)(struct user_namespace *, struct dentry *, int)" \ + KERNEL_HAS_POSIX_GET_ACL_NS \ + linux/fs.h + +check_symbol \ + "extern const struct xattr_handler posix_acl_default_xattr_handler;" \ + KERNEL_HAS_POSIX_ACL_DEFAULT_XATTR_HANDLER \ + linux/posix_acl_xattr.h + +check_struct_field_type \ + inode_operations::get_acl "struct posix_acl* (*)(struct inode *, int, bool)" \ + KERNEL_POSIX_GET_ACL_HAS_RCU \ + linux/fs.h + +check_struct_field_type \ + inode_operations::get_inode_acl "struct posix_acl* (*)(struct inode *, int, bool)" \ + KERNEL_HAS_GET_INODE_ACL \ + linux/fs.h + +check_struct_field \ + inode_operations::set_acl \ + KERNEL_HAS_SET_ACL \ + linux/fs.h + +check_struct_field_type \ + inode_operations::set_acl "int (*)(struct user_namespace *, struct inode *, struct posix_acl *, int)" \ + KERNEL_HAS_SET_ACL_NS_INODE \ + linux/fs.h + +check_struct_field_type \ + inode_operations::set_acl "int (*)(struct mnt_idmap *, struct dentry *, struct posix_acl *, int)" \ + KERNEL_HAS_SET_ACL_DENTRY \ + linux/fs.h + +check_struct_field_type \ + inode_operations::set_acl "int (*)(struct user_namespace *, struct dentry *, struct posix_acl *, int)" \ + KERNEL_HAS_SET_ACL_DENTRY \ + linux/fs.h + +check_function \ + vfs_create "int (struct user_namespace *, struct inode *, struct dentry *, umode_t, bool)" \ + KERNEL_HAS_USER_NS_MOUNTS \ + linux/fs.h + +check_function \ + vfs_create "int (struct mnt_idmap *, struct inode *, struct dentry *, umode_t, bool)" \ + KERNEL_HAS_IDMAPPED_MOUNTS \ + linux/fs.h + +check_struct_field_type \ + file_operations::iterate "int (*)(struct file *, struct dir_context *)" \ + KERNEL_HAS_FOPS_ITERATE \ + linux/fs.h + +check_struct_field_type \ + xattr_handler::set "int (*)(const struct xattr_handler *, struct dentry *, struct inode *, const char *, const void *, size_t, int)" \ + KERNEL_HAS_XATTR_HANDLERS_INODE_ARG \ + linux/xattr.h + +check_struct_field_type \ + xattr_handler::set "int (*)(const struct xattr_handler *, struct user_namespace *, struct dentry *, struct inode *, const char *, const void *, size_t, int)" \ + KERNEL_HAS_XATTR_HANDLERS_INODE_ARG \ + linux/xattr.h + +check_struct_field_type \ + xattr_handler::set "int (*)(const struct xattr_handler *, struct mnt_idmap *, struct dentry *, struct inode *, const char *, const void *, size_t, int)" \ + KERNEL_HAS_XATTR_HANDLERS_INODE_ARG \ + linux/xattr.h + +check_struct_field \ + thread_info::cpu \ + KERNEL_HAS_CPU_IN_THREAD_INFO \ + linux/thread_info.h + +check_function \ + generic_fillattr "void (struct mnt_idmap *, u32, struct inode *, struct kstat *)" \ + KERNEL_HAS_GENERIC_FILLATTR_REQUEST_MASK \ + linux/fs.h + + + +# Kernel 6.5 introduced getters and setters for struct inode's ctime field + +check_function \ + inode_get_ctime "struct timespec64 (const struct inode *inode)" \ + KERNEL_HAS_INODE_GET_SET_CTIME \ + linux/fs.h + +# Kernel 6.6 introduced more getters and setters, also for atime and mtime + +check_function \ + inode_get_mtime "struct timespec64 (const struct inode *inode)" \ + KERNEL_HAS_INODE_GET_SET_CTIME_MTIME_ATIME \ + linux/fs.h + +# From Linux kernel 6.12 onward, unaligned.h has been moved from to include path + +check_header \ + linux/unaligned.h \ + KERNEL_HAS_LINUX_UNALIGNED_H + +# we have to communicate with the calling makefile somehow. since we can't really use the return +# code of this script, we'll echo a special string at the end of our output for the caller to +# detect and remove again. +# this string has to be something that will, on its own, never be a valid compiler option. so let's +# choose something really, really unlikely like +echo "--~~success~~--" diff --git a/client_module/dkms.conf.in b/client_module/dkms.conf.in new file mode 100644 index 0000000..32f8856 --- /dev/null +++ b/client_module/dkms.conf.in @@ -0,0 +1,8 @@ +PACKAGE_NAME="__NAME__" +PACKAGE_VERSION="__VERSION__" +BUILT_MODULE_NAME[0]="__MODNAME__" +BUILT_MODULE_LOCATION[0]="build/" +DEST_MODULE_LOCATION[0]="/kernel/fs/beegfs/" +MAKE[0]="make -C build module 'KDIR=$kernel_source_dir' TARGET=__MODNAME__ BEEGFS_VERSION=__VERSION__" +CLEAN="make -C build clean" +AUTOINSTALL="yes" \ No newline at end of file diff --git a/client_module/include/uapi/beegfs_client.h b/client_module/include/uapi/beegfs_client.h new file mode 100644 index 0000000..b128c78 --- /dev/null +++ b/client_module/include/uapi/beegfs_client.h @@ -0,0 +1,300 @@ +#ifndef _BEEGFS_CLIENT_H_INCLUDED +#define _BEEGFS_CLIENT_H_INCLUDED + +#define BEEGFS_IOCTL_CFG_MAX_PATH 4096 // just an arbitrary value, has to be identical in user space +#define BEEGFS_IOCTL_TEST_STRING "_FhGFS_" /* copied to user space by BEEGFS_IOC_TEST_IS_FHGFS to + to confirm an FhGFS mount */ +#define BEEGFS_IOCTL_TEST_BUFLEN 6 /* note: char[6] is actually the wrong size for the + BEEGFS_IOCTL_TEST_STRING that is exchanged, but that is no problem + in this particular case and so we keep it for compatibility */ +#define BEEGFS_IOCTL_MOUNTID_BUFLEN 256 +#define BEEGFS_IOCTL_NODEALIAS_BUFLEN 256 // The alias (formerly string ID) buffer length. +#define BEEGFS_IOCTL_NODETYPE_BUFLEN 16 +#define BEEGFS_IOCTL_FILENAME_MAXLEN 256 // max supported filename len (incl terminating zero) + +// entryID string is made of three 32 bit values in hexadecimal form plus two dashes +// (see common/toolkit/StorageTk.h) +#define BEEGFS_IOCTL_ENTRYID_MAXLEN 26 + +// stripe pattern types +#define BEEGFS_STRIPEPATTERN_INVALID 0 +#define BEEGFS_STRIPEPATTERN_RAID0 1 +#define BEEGFS_STRIPEPATTERN_RAID10 2 +#define BEEGFS_STRIPEPATTERN_BUDDYMIRROR 3 + +#define BEEGFS_IOCTL_PING_MAX_COUNT 10 +#define BEEGFS_IOCTL_PING_MAX_INTERVAL 2000 +#define BEEGFS_IOCTL_PING_NODE_BUFLEN 64 +#define BEEGFS_IOCTL_PING_SOCKTYPE_BUFLEN 8 + +/* + * General notes: + * - the _IOR() macro is for ioctls that read information, _IOW refers to ioctls that write or make + * modifications (e.g. file creation). + * + * - _IOR(type, number, data_type) meanings: + * - note: _IOR() encodes all three values (type, number, data_type size) into the request number + * - type: 8 bit driver-specific number to identify the driver if there are multiple drivers + * listening to the same fd (e.g. such as the TCP and IP layers). + * - number: 8 bit integer command number, so different numbers for different routines. + * - data_type: the data type (size) to be exchanged with the driver (though this number can + * also rather be seen as a command number subversion, because the actual number given here is + * not really exchanged unless the drivers' ioctl handler explicity does the exchange). + */ + +#define BEEGFS_IOCTYPE_ID 'f' + +#define BEEGFS_IOCNUM_GETVERSION_OLD 1 // value from FS_IOC_GETVERSION in linux/fs.h +#define BEEGFS_IOCNUM_GETVERSION 3 +#define BEEGFS_IOCNUM_GET_CFG_FILE 20 +#define BEEGFS_IOCNUM_CREATE_FILE 21 +#define BEEGFS_IOCNUM_TEST_IS_FHGFS 22 +#define BEEGFS_IOCNUM_TEST_IS_BEEGFS 22 +#define BEEGFS_IOCNUM_GET_RUNTIME_CFG_FILE 23 +#define BEEGFS_IOCNUM_GET_MOUNTID 24 +#define BEEGFS_IOCNUM_GET_STRIPEINFO 25 +#define BEEGFS_IOCNUM_GET_STRIPETARGET 26 +#define BEEGFS_IOCNUM_MKFILE_STRIPEHINTS 27 +#define BEEGFS_IOCNUM_CREATE_FILE_V2 28 +#define BEEGFS_IOCNUM_CREATE_FILE_V3 29 +#define BEEGFS_IOCNUM_GETINODEID 30 +#define BEEGFS_IOCNUM_GETENTRYINFO 31 +#define BEEGFS_IOCNUM_PINGNODE 32 + +#define BEEGFS_IOC_GETVERSION _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GETVERSION, long) +#define BEEGFS_IOC32_GETVERSION _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GETVERSION, int) +#define BEEGFS_IOC_GET_CFG_FILE _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_CFG_FILE, struct BeegfsIoctl_GetCfgFile_Arg) +#define BEEGFS_IOC_CREATE_FILE _IOW( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_CREATE_FILE, struct BeegfsIoctl_MkFile_Arg) +#define BEEGFS_IOC_CREATE_FILE_V2 _IOW( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_CREATE_FILE_V2, struct BeegfsIoctl_MkFileV2_Arg) +#define BEEGFS_IOC_CREATE_FILE_V3 _IOW( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_CREATE_FILE_V3, struct BeegfsIoctl_MkFileV3_Arg) +#define BEEGFS_IOC_TEST_IS_FHGFS _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_TEST_IS_FHGFS, char[BEEGFS_IOCTL_TEST_BUFLEN]) +#define BEEGFS_IOC_TEST_IS_BEEGFS _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_TEST_IS_BEEGFS, char[BEEGFS_IOCTL_TEST_BUFLEN]) +#define BEEGFS_IOC_GET_RUNTIME_CFG_FILE _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_RUNTIME_CFG_FILE, struct BeegfsIoctl_GetCfgFile_Arg) +#define BEEGFS_IOC_GET_MOUNTID _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_MOUNTID, char[BEEGFS_IOCTL_MOUNTID_BUFLEN]) +#define BEEGFS_IOC_GET_STRIPEINFO _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_STRIPEINFO, struct BeegfsIoctl_GetStripeInfo_Arg) +#define BEEGFS_IOC_GET_STRIPETARGET _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_STRIPETARGET, struct BeegfsIoctl_GetStripeTarget_Arg) +#define BEEGFS_IOC_GET_STRIPETARGET_V2 _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GET_STRIPETARGET, struct BeegfsIoctl_GetStripeTargetV2_Arg) +#define BEEGFS_IOC_MKFILE_STRIPEHINTS _IOW( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_MKFILE_STRIPEHINTS, struct BeegfsIoctl_MkFileWithStripeHints_Arg) +#define BEEGFS_IOC_GETINODEID _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GETINODEID, struct BeegfsIoctl_GetInodeID_Arg) +#define BEEGFS_IOC_GETENTRYINFO _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_GETENTRYINFO, struct BeegfsIoctl_GetEntryInfo_Arg) +#define BEEGFS_IOC_PINGNODE _IOR( \ + BEEGFS_IOCTYPE_ID, BEEGFS_IOCNUM_PINGNODE, struct BeegfsIoctl_PingNode_Arg) + + +/* used to return the client config file path using an IOCTL */ +struct BeegfsIoctl_GetCfgFile_Arg +{ + char path[BEEGFS_IOCTL_CFG_MAX_PATH]; // (out-value) where the result path will be stored + int length; /* (in-value) length of path buffer (unused, because it's + after a fixed-size path buffer anyways) */ +}; + +/* used to pass information for file creation */ +struct BeegfsIoctl_MkFile_Arg +{ + uint16_t ownerNodeID; // owner node of the parent dir + + const char* parentParentEntryID; // entryID of the parent of the parent (=> the grandparentID) + int parentParentEntryIDLen; + + const char* parentEntryID; // entryID of the parent + int parentEntryIDLen; + + const char* parentName; // name of parent dir + int parentNameLen; + + // file information + const char* entryName; // file name we want to create + int entryNameLen; + int fileType; // see linux/fs.h or man 3 readdir, DT_UNKNOWN, DT_FIFO, ... + + const char* symlinkTo; // Only must be set for symlinks. The name a symlink is supposed to point to + int symlinkToLen; // Length of the symlink name + + int mode; // mode (permission) of the new file + + // user ID and group only will be used, if the current user is root + uid_t uid; // user ID + gid_t gid; // group ID + + int numTargets; // number of targets in prefTargets array (without final 0 element) + uint16_t* prefTargets; // array of preferred targets (additional last element must be 0) + int prefTargetsLen; // raw byte length of prefTargets array (including final 0 element) +}; + +struct BeegfsIoctl_MkFileV2_Arg +{ + uint32_t ownerNodeID; // owner node/group of the parent dir + + const char* parentParentEntryID; // entryID of the parent of the parent (=> the grandparentID) + int parentParentEntryIDLen; + + const char* parentEntryID; // entryID of the parent + int parentEntryIDLen; + int parentIsBuddyMirrored; + + const char* parentName; // name of parent dir + int parentNameLen; + + // file information + const char* entryName; // file name we want to create + int entryNameLen; + int fileType; // see linux/fs.h or man 3 readdir, DT_UNKNOWN, DT_FIFO, ... + + char* symlinkTo; // Only must be set for symlinks. The name a symlink is supposed to point to + int symlinkToLen; // Length of the symlink name + + int mode; // mode (permission) of the new file + + // user ID and group only will be used, if the current user is root + uid_t uid; // user ID + gid_t gid; // group ID + + int numTargets; // number of targets in prefTargets array (without final 0 element) + uint16_t* prefTargets; // array of preferred targets (additional last element must be 0) + int prefTargetsLen; // raw byte length of prefTargets array (including final 0 element) +}; + +struct BeegfsIoctl_MkFileV3_Arg +{ + uint32_t ownerNodeID; // owner node/group of the parent dir + + const char* parentParentEntryID; // entryID of the parent of the parent (=> the grandparentID) + int parentParentEntryIDLen; + + const char* parentEntryID; // entryID of the parent + int parentEntryIDLen; + int parentIsBuddyMirrored; + + const char* parentName; // name of parent dir + int parentNameLen; + + // file information + const char* entryName; // file name we want to create + int entryNameLen; + int fileType; // see linux/fs.h or man 3 readdir, DT_UNKNOWN, DT_FIFO, ... + + const char* symlinkTo; // Only must be set for symlinks. The name a symlink is supposed to point to + int symlinkToLen; // Length of the symlink name + + int mode; // mode (permission) of the new file + + // user ID and group only will be used, if the current user is root + uid_t uid; // user ID + gid_t gid; // group ID + + int numTargets; // number of targets in prefTargets array (without final 0 element) + uint16_t* prefTargets; // array of preferred targets (additional last element must be 0) + int prefTargetsLen; // raw byte length of prefTargets array (including final 0 element) + + uint16_t storagePoolId; // if set, this is used to override the pool id of the parent dir +}; + +/* used to get the stripe info of a file */ +struct BeegfsIoctl_GetStripeInfo_Arg +{ + unsigned outPatternType; // (out-value) stripe pattern type (STRIPEPATTERN_...) + unsigned outChunkSize; // (out-value) chunksize for striping + uint16_t outNumTargets; // (out-value) number of stripe targets of given file +}; + +/* used to get the stripe target of a file */ +struct BeegfsIoctl_GetStripeTarget_Arg +{ + uint16_t targetIndex; // index of the target that should be queried (0-based) + + uint16_t outTargetNumID; // (out-value) numeric ID of target with given index + uint16_t outNodeNumID; // (out-value) numeric ID of node to which this target is mapped + char outNodeAlias[BEEGFS_IOCTL_NODEALIAS_BUFLEN]; /* (out-value) alias (formerly string ID) of the node + to which this target is mapped */ +}; + +struct BeegfsIoctl_GetStripeTargetV2_Arg +{ + /* inputs */ + uint32_t targetIndex; + + /* outputs */ + uint32_t targetOrGroup; // target ID if the file is not buddy mirrored, otherwise mirror group ID + + uint32_t primaryTarget; // target ID != 0 if buddy mirrored + uint32_t secondaryTarget; // target ID != 0 if buddy mirrored + + uint32_t primaryNodeID; // node ID of target (if unmirrored) or primary target (if mirrored) + uint32_t secondaryNodeID; // node ID of secondary target, or 0 if unmirrored + + char primaryNodeAlias[BEEGFS_IOCTL_NODEALIAS_BUFLEN]; + char secondaryNodeAlias[BEEGFS_IOCTL_NODEALIAS_BUFLEN]; +}; + +/* used to pass information for file creation with stripe hints */ +struct BeegfsIoctl_MkFileWithStripeHints_Arg +{ + const char* filename; // file name we want to create + unsigned mode; // mode (access permission) of the new file + + unsigned numtargets; // number of desired targets, 0 for directory default + unsigned chunksize; // in bytes, must be 2^n >= 64Ki, 0 for directory default +}; + +struct BeegfsIoctl_GetInodeID_Arg +{ + // input + char entryID[BEEGFS_IOCTL_ENTRYID_MAXLEN + 1]; + + // output + uint64_t inodeID; + +}; + +struct BeegfsIoctl_GetEntryInfo_Arg +{ + uint32_t ownerID; + char parentEntryID[BEEGFS_IOCTL_ENTRYID_MAXLEN + 1]; + char entryID[BEEGFS_IOCTL_ENTRYID_MAXLEN + 1]; + int entryType; + int featureFlags; +}; + +struct BeegfsIoctl_PingNode_Arg_Params +{ + uint32_t nodeId; + char nodeType[BEEGFS_IOCTL_NODETYPE_BUFLEN]; + unsigned count; + unsigned interval; +}; + +struct BeegfsIoctl_PingNode_Arg_Results +{ + char outNode[BEEGFS_IOCTL_PING_NODE_BUFLEN]; + unsigned outSuccess; + unsigned outErrors; + unsigned outTotalTime; + unsigned outPingTime[BEEGFS_IOCTL_PING_MAX_COUNT]; + char outPingType[BEEGFS_IOCTL_PING_MAX_COUNT][BEEGFS_IOCTL_PING_SOCKTYPE_BUFLEN]; +}; + +struct BeegfsIoctl_PingNode_Arg +{ + struct BeegfsIoctl_PingNode_Arg_Params params; + struct BeegfsIoctl_PingNode_Arg_Results results; +}; + + +#endif diff --git a/client_module/scripts/etc/beegfs/lib/init-multi-mode.beegfs-client b/client_module/scripts/etc/beegfs/lib/init-multi-mode.beegfs-client new file mode 100755 index 0000000..f0738bb --- /dev/null +++ b/client_module/scripts/etc/beegfs/lib/init-multi-mode.beegfs-client @@ -0,0 +1,367 @@ +#!/bin/sh + +function exec_mount_hook() +{ + if [ -n "$MOUNT_HOOK" ] + then + set +e + /bin/sh -c "${MOUNT_HOOK} ${1} \"${2}\"" + set -e + fi +} + +function init_multi_mode() +{ + ACTION=$1 + CONFIG=$2 + ERROR=0 + + if [ -z $ACTION ] + then + echo "Usage: $0 {start|stop|status|restart|rebuild|condrestart|try-restart} [CONFIGURATION_NAME]" + exit 3 + fi + + if [ -z $CONFIG ] + then + case "$ACTION" in + rebuild) + # Just rebuild modules. The user needs to call "restart" to make use + # of those new modules. + build_beegfs + ERROR=$? + if [ $ERROR -eq 0 ]; then + rm -f $FORCE_AUTO_BUILD + fi + ;; + restart) + do_central_loop stop + do_central_loop start + ERROR=$? + ;; + try-restart|condrestart) + ## Do a restart only if the service was active before. + ## Note: try-restart is now part of LSB (as of 1.9). + ## RH has a similar command named condrestart. + do_central_loop status + if test $? = 0 + then + $0 restart + ERROR=$? + else + ERROR=1 + fi + ;; + *) + do_central_loop $ACTION + ERROR=$? + ;; + esac + else + BEEGFS_MOUNT_CONF="/etc/beegfs/${CONFIG}.d/beegfs-mounts.conf" + + if [ -e "/etc/beegfs/${CONFIG}.d" ] + then + init_multi_mode_single_configuration $ACTION $CONFIG + ERROR=$? + else + echo "Configuration folder /etc/beegfs/${CONFIG}.d doesn't exist." + ERROR=1 + fi + fi + return $ERROR +} + +do_central_loop() +{ + ACTION=$1 + LOOP_ERROR=0 + + for CONFIG_FOLDER in $( ls -d /etc/beegfs/*.d ) + do + if [ -r ${CONFIG_FOLDER}/beegfs-client.conf ] + then + CONFIG=$( basename $CONFIG_FOLDER .d ) + BEEGFS_MOUNT_CONF="${CONFIG_FOLDER}/beegfs-mounts.conf" + + init_multi_mode_single_configuration $ACTION $CONFIG + + if [ $? -ne 0 ] + then + LOOP_ERROR=1 + fi + fi + done + + return $LOOP_ERROR +} + +function init_multi_mode_single_configuration() +{ + ACTION=$1 + CONFIG=$2 + + if [ -z $ACTION ] + then + echo "Usage: $0 {start|stop|status|restart|rebuild|condrestart|try-restart} [CONFIGURATION_NAME]" + exit 3 + fi + + RETVAL=0 + case "$1" in + start) + if [ -f "${SYSCONFIG_FILE}" ] + then + source ${SYSCONFIG_FILE} + if [ "${START_SERVICE}" = "NO" -o "${START_SERVICE}" = "no" ] + then + echo "BeeGFS Client not set to be started" + return 0 + fi + fi + + handle_selinux + + echo "Starting BeeGFS Client (${CONFIG}): " + + test_updated_autobuild_conf + set +e #methode test_updated_autobuild_conf set -e + + echo "- Loading BeeGFS modules" + if [ -f "$FORCE_AUTO_BUILD" ]; then + # a new version was installed or the user updated the + # auto-build config, so we force a rebuild + rmmod $CLIENT_MOD 2>/dev/null + rc=1 + else + modprobe $CLIENT_MOD + rc=$? + fi + + if [ $rc -ne 0 ] + then + set -e + build_beegfs + modprobe $CLIENT_MOD || (warn_on_ofed_mismatch && false) + rm -f $FORCE_AUTO_BUILD + set +e + fi + + echo "- Mounting directories from $BEEGFS_MOUNT_CONF" + + mkdir -p /var/lock/subsys/ >/dev/null 2>&1 + touch $SUBSYS >/dev/null 2>&1 + + if [ $? -ne 0 ] + then + echo "Couldn't create lock file." + return 1 + fi + + OLDIFS="$IFS" + IFS=$'\n' + + if [ -r $BEEGFS_MOUNT_CONF ] + then + file=`tr -d '\r' < $BEEGFS_MOUNT_CONF` # read all lines at once and remove CR from dos files + else + echo "Couldn't read configuration file: $BEEGFS_MOUNT_CONF" + IFS="$OLDIFS" + return 1 + fi + + for line in $file; do + if [ -z "$line" ]; then + continue # ignore empty line + elif echo "$line" | grep -qs "^\s*#" ; then + continue # ignore shell style comments + fi + + mnt=`echo $line | awk '{print $1}'` + cfg=`echo $line | awk '{print $2}'` + extra_mount_opts=`echo $line | awk '{print $3}'` + + if [ -z "$mnt" -o -z "$cfg" ]; then + echo "Invalid config line: \"$line\"" + RETVAL=1 + continue + fi + + mount -t beegfs | grep "${mnt} " >/dev/null 2>&1 + if [ $? -eq 0 ]; then + # already mounted + continue + fi + + # mkdir required for admon + if [ ! -e ${mnt} ] + then + mkdir -p ${mnt} + if [ $? -ne 0 ] + then + echo "Couldn't create mountpoint folder: ${mnt}" + RETVAL=1 + continue + fi + fi + + + exec_mount_hook pre-mount "${mnt}" + + mount -t beegfs beegfs_${CONFIG} ${mnt} \ + -ocfgFile=${cfg},_netdev,${SELINUX_OPT},${extra_mount_opts} + if [ $? -ne 0 ] + then + echo "Couldn't mount ${mnt}." + RETVAL=1 + continue + fi + + exec_mount_hook post-mount "${mnt}" + + done + + IFS="$OLDIFS" + ;; + stop) + echo "Shutting down BeeGFS Client (${CONFIG}): " + + RETVAL=0 + echo "- Unmounting directories from $BEEGFS_MOUNT_CONF" + + OLDIFS="$IFS" + IFS=$'\n' + + if [ -r $BEEGFS_MOUNT_CONF ] + then + file=`cat $BEEGFS_MOUNT_CONF` # we have to read all lines at once + else + echo "Couldn't read configuration file: $BEEGFS_MOUNT_CONF" + IFS="$OLDIFS" + return 1 + fi + + for line in $file + do + if [ -z "$line" ]; then + continue # ignore empty line + fi + + mnt=`echo $line | awk '{print $1}'` + cfg=`echo $line | awk '{print $2}'` + if [ -z "$mnt" -o -z "$cfg" ]; then + echo "Invalid config line: \"$line\"" + RETVAL=1 + continue + fi + + exec_mount_hook pre-unmount "${mnt}" + + res=`umount ${mnt} 2>&1` + if [ $? -ne 0 ] + then + # umount failed, ignore the failure if not mounted at all + echo $res | grep "not mounted" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + # Is mounted, abort. + echo "umount failed: $res" + RETVAL=1 + continue + fi + fi + + exec_mount_hook post-unmount "${mnt}" + + done + + IFS="$OLDIFS" + + # tests if other beegfs mounts exists + mount -t beegfs | grep beegfs >/dev/null 2>&1 + if [ $? -ne 0 ] + then + echo "- Unloading modules" + + res=`rmmod $CLIENT_MOD 2>&1` + if [ $? -ne 0 ]; then + # rmmod failed, ignore it if the module is not loaded at all + echo $res | grep "does not exist in" >/dev/null 2>&1 + if [ $? -ne 0 ]; then + # Is loaded, abort + echo "rmmod failed: $res" + RETVAL=1 + fi + fi + fi + + if [ $RETVAL -eq 0 ]; then rm -f $SUBSYS; fi + # Remember status and be verbose + ;; + status) + # Return value is slightly different for the status command: + # 0 - service up and running + # 1 - service dead, but /var/run/ pid file exists + # 2 - service dead, but /var/lock/ lock file exists + # 3 - service not running (unused) + # 4 - service status unknown :-( + # 5--199 reserved (5--99 LSB, 100--149 distro, 150--199 appl.) + + echo -n "Checking for service BeeGFS Client (${CONFIG}): " + + lsmod | grep $CLIENT_MOD >/dev/null 2>&1 + if [ $? -eq 0 ] + then + echo "is running..." + mount -t beegfs | grep beegfs_${CONFIG} >/dev/null 2>&1 + if [ $? -eq 0 ]; then + mount -t beegfs | grep beegfs_${CONFIG} | cut "-d " -f1-3 + echo + RETVAL=0 + else + echo ">> No mounts for the configuration ${CONFIG} mounted." + echo + RETVAL=4 + fi + else + echo "is stopped." + echo + RETVAL=3 + fi + ;; + restart) + ## Stop the service and regardless of whether it was + ## running or not, start it again. + $0 stop ${CONFIG} + $0 start ${CONFIG} + RETVAL=$? + # Remember status and be quiet + ;; + rebuild) + # Just rebuild modules. The user needs to call "restart" to make use + # of those new modules. + build_beegfs + RETVAL=$? + if [ $RETVAL -eq 0 ]; then + rm -f $FORCE_AUTO_BUILD + fi + ;; + try-restart|condrestart) + ## Do a restart only if the service was active before. + ## Note: try-restart is now part of LSB (as of 1.9). + ## RH has a similar command named condrestart. + $0 status ${CONFIG} + if test $? = 0 + then + $0 restart ${CONFIG} + RETVAL=$? + else + RETVAL=7 + fi + ;; + *) + echo "Usage: $0 {start|stop|status|restart|rebuild|condrestart|try-restart} [CONFIGURATION_NAME]" + exit 3 + ;; + esac + return $RETVAL +} diff --git a/client_module/source/Makefile b/client_module/source/Makefile new file mode 100644 index 0000000..6ece734 --- /dev/null +++ b/client_module/source/Makefile @@ -0,0 +1,199 @@ +# NOTE(jstimpfle, 2022-04): I changed the interface of the feature-detect.sh +# script to take some arguments explicitly from the command-line, as opposed to +# implicitly from the environment. +# +# The reason is that we hit a case where there was a string literal in +# KBUILD_CFLAGS (generated in Linux kernel Makefiles), and since this variable +# is normally used as part of shell commands contained in Makefiles this +# string literal is wrapped in a pair of single quotes, +# like this '"string literal"'. + +BEEGFS_FEATURE_DETECTION := $(shell $(dir $(lastword $(MAKEFILE_LIST)))/../build/feature-detect.sh $(KBUILD_CFLAGS) $(KBUILD_CPPFLAGS)) + +ifneq ($(lastword $(BEEGFS_FEATURE_DETECTION)),--~~success~~--) + $(error feature detection reported an error) +else + BEEGFS_FEATURE_DETECTION := $(filter-out --~~success~~--,$(BEEGFS_FEATURE_DETECTION)) + $(info feature detection gives: $(BEEGFS_FEATURE_DETECTION)) +endif + +# ccflags-y was introduced in 2.6.24, earlier kernels use EXTRA_CFLAGS for the same purpose +ifeq ($(origin ccflags-y),file) +ccflags-y += $(BEEGFS_FEATURE_DETECTION) +else +# the client makefile sets this already +override EXTRA_CFLAGS += $(BEEGFS_FEATURE_DETECTION) +endif + +kernel-version = $(shell printf "%02d%02d" $(VERSION) $(PATCHLEVEL)) +kernel-if-version-or-later = $(shell [ $(kernel-version) -ge $(1) ] && echo $(2)) + +obj-m += ${TARGET}.o + +SOURCES := \ + fault-inject/fault-inject.c \ + os/iov_iter.c \ + os/atomic64.c \ + os/OsCompat.c \ + os/OsDeps.c \ + net/message/NetMessageFactory.c \ + net/filesystem/FhgfsOpsCommKit.c \ + net/filesystem/FhgfsOpsRemoting.c \ + net/filesystem/FhgfsOpsCommKitVec.c \ + common/system/System.c \ + common/net/sock/Socket.c \ + common/net/sock/NicAddress.c \ + common/net/sock/NetworkInterfaceCard.c \ + common/net/sock/NicAddressList.c \ + common/net/sock/RDMASocket.c \ + common/net/sock/ibv/IBVSocket.c \ + common/net/sock/ibv/IBVBuffer.c \ + common/net/sock/ibv/No_IBVSocket.c \ + common/net/sock/StandardSocket.c \ + common/net/message/control/PeerInfoMsg.c \ + common/net/message/SimpleIntMsg.c \ + common/net/message/NetMessage.c \ + common/net/message/session/locking/FLockEntryMsg.c \ + common/net/message/session/locking/FLockAppendMsg.c \ + common/net/message/session/locking/LockGrantedMsgEx.c \ + common/net/message/session/locking/FLockRangeMsg.c \ + common/net/message/session/GetFileVersionRespMsg.c \ + common/net/message/session/rw/ReadLocalFileV2Msg.c \ + common/net/message/session/rw/ReadLocalFileRDMAMsg.c \ + common/net/message/session/rw/WriteLocalFileMsg.c \ + common/net/message/session/rw/WriteLocalFileRDMAMsg.c \ + common/net/message/session/FSyncLocalFileMsg.c \ + common/net/message/session/GetFileVersionMsg.c \ + common/net/message/session/opening/OpenFileMsg.c \ + common/net/message/session/opening/OpenFileRespMsg.c \ + common/net/message/session/opening/CloseFileMsg.c \ + common/net/message/session/BumpFileVersion.c \ + common/net/message/SimpleInt64Msg.c \ + common/net/message/SimpleUInt16Msg.c \ + common/net/message/SimpleStringMsg.c \ + common/net/message/SimpleIntStringMsg.c \ + common/net/message/nodes/RegisterNodeMsg.c \ + common/net/message/nodes/RegisterNodeRespMsg.c \ + common/net/message/nodes/HeartbeatRequestMsgEx.c \ + common/net/message/nodes/MapTargetsMsgEx.c \ + common/net/message/nodes/GetTargetMappingsRespMsg.c \ + common/net/message/nodes/HeartbeatMsgEx.c \ + common/net/message/nodes/RemoveNodeMsgEx.c \ + common/net/message/nodes/GetStatesAndBuddyGroupsMsg.c \ + common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.c \ + common/net/message/nodes/RefreshTargetStatesMsgEx.c \ + common/net/message/nodes/GetNodesRespMsg.c \ + common/net/message/nodes/SetMirrorBuddyGroupMsgEx.c \ + common/net/message/SimpleMsg.c \ + common/net/message/storage/moving/RenameMsg.c \ + common/net/message/storage/StatStoragePathRespMsg.c \ + common/net/message/storage/creating/HardlinkMsg.c \ + common/net/message/storage/creating/MkFileMsg.c \ + common/net/message/storage/creating/MkDirMsg.c \ + common/net/message/storage/creating/UnlinkFileMsg.c \ + common/net/message/storage/creating/MkDirRespMsg.c \ + common/net/message/storage/creating/RmDirMsg.c \ + common/net/message/storage/creating/MkFileRespMsg.c \ + common/net/message/storage/attribs/StatRespMsg.c \ + common/net/message/storage/attribs/ListXAttrRespMsg.c \ + common/net/message/storage/attribs/SetAttrMsg.c \ + common/net/message/storage/attribs/ListXAttrMsg.c \ + common/net/message/storage/attribs/RefreshEntryInfoMsg.c \ + common/net/message/storage/attribs/StatMsg.c \ + common/net/message/storage/attribs/GetXAttrRespMsg.c \ + common/net/message/storage/attribs/GetXAttrMsg.c \ + common/net/message/storage/attribs/RemoveXAttrMsg.c \ + common/net/message/storage/attribs/SetXAttrMsg.c \ + common/net/message/storage/lookup/LookupIntentRespMsg.c \ + common/net/message/storage/lookup/LookupIntentMsg.c \ + common/net/message/storage/TruncFileMsg.c \ + common/net/message/storage/listing/ListDirFromOffsetRespMsg.c \ + common/net/message/storage/listing/ListDirFromOffsetMsg.c \ + common/threading/Thread.c \ + common/toolkit/Serialization.c \ + common/toolkit/MessagingTk.c \ + common/toolkit/HashTk.c \ + common/toolkit/ackstore/WaitAckMap.c \ + common/toolkit/ackstore/AcknowledgmentStore.c \ + common/toolkit/ackstore/AckStoreMap.c \ + common/toolkit/Random.c \ + common/toolkit/tree/IntMap.c \ + common/toolkit/tree/StrCpyMap.c \ + common/toolkit/tree/PointerRBTree.c \ + common/toolkit/NodesTk.c \ + common/toolkit/SocketTk.c \ + common/toolkit/MetadataTk.c \ + common/toolkit/StringTk.c \ + common/toolkit/ListTk.c \ + common/Types.c \ + common/nodes/Node.c \ + common/nodes/TargetMapper.c \ + common/nodes/MirrorBuddyGroup.c \ + common/nodes/NumNodeID.c \ + common/nodes/TargetStateStore.c \ + common/nodes/NodeTree.c \ + common/nodes/MirrorBuddyGroupMapper.c \ + common/nodes/NodeConnPool.c \ + common/storage/StorageErrors.c \ + common/storage/StatData.c \ + common/storage/striping/BuddyMirrorPattern.c \ + common/storage/striping/SimplePattern.c \ + common/storage/striping/StripePattern.c \ + common/storage/striping/Raid10Pattern.c \ + common/storage/striping/Raid0Pattern.c \ + common/storage/PathInfo.c \ + common/storage/RdmaInfo.c \ + common/storage/Nvfs.c \ + common/storage/EntryInfo.c \ + common/storage/StoragePoolId.c \ + common/storage/FileEvent.c \ + program/Main.c \ + toolkit/FhgfsChunkPageVec.c \ + toolkit/StatFsCache.c \ + toolkit/NoAllocBufferStore.c \ + toolkit/InodeRefStore.c \ + toolkit/BitStore.c \ + components/DatagramListener.c \ + components/InternodeSyncer.c \ + components/Flusher.c \ + components/worker/RWPagesWork.c \ + components/AckManager.c \ + nodes/NodeStoreEx.c \ + filesystem/ProcFs.c \ + filesystem/FhgfsOpsPages.c \ + filesystem/FhgfsOpsFile.c \ + filesystem/FhgfsOpsInode.c \ + filesystem/FhgfsOpsFileNative.c \ + filesystem/FhgfsInode.c \ + filesystem/helper/IoctlHelper.c \ + filesystem/FsFileInfo.c \ + filesystem/ProcFsHelper.c \ + filesystem/FhgfsOpsSuper.c \ + filesystem/FhgfsOps_versions.c \ + filesystem/FhgfsXAttrHandlers.c \ + filesystem/FhgfsOpsDir.c \ + filesystem/FhgfsOpsIoctl.c \ + filesystem/FhgfsOpsExport.c \ + filesystem/FhgfsOpsHelper.c \ + app/App.c \ + app/log/Logger.c \ + app/config/MountConfig.c \ + app/config/Config.c + +${TARGET}-y := $(patsubst %.c,%.o,$(SOURCES)) + +ifdef BEEGFS_NO_RDMA +ccflags-y += -DBEEGFS_NO_RDMA +endif + +ifneq ($(OFED_INCLUDE_PATH),) + +# RHEL/CentOS 8 + MLNX 4.7 requires extra flags (bug?) +ccflags-y += $(call kernel-if-version-or-later, 0418, -DHAVE_BITS_H) + +NOSTDINC_FLAGS+=-I$(OFED_INCLUDE_PATH) -I$(OFED_INCLUDE_PATH)/uapi +ifeq ($(shell [ -f $(OFED_INCLUDE_PATH)/linux/compat-2.6.h ] && echo 1),1) +ccflags-y += -include $(OFED_INCLUDE_PATH)/linux/compat-2.6.h +endif + +endif diff --git a/client_module/source/app/App.c b/client_module/source/app/App.c new file mode 100644 index 0000000..f854723 --- /dev/null +++ b/client_module/source/app/App.c @@ -0,0 +1,1010 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// REMOVEME +#include + +#include + +#ifndef BEEGFS_VERSION + #error BEEGFS_VERSION undefined +#endif + +/** + * @param mountConfig belongs to the app afterwards (and will automatically be destructed + * by the app) + */ +void App_init(App* this, MountConfig* mountConfig) +{ + this->mountConfig = mountConfig; + + this->appResult = APPCODE_NO_ERROR; + + this->connRetriesEnabled = true; + this->netBenchModeEnabled = false; + + this->cfg = NULL; + this->netFilter = NULL; + this->tcpOnlyFilter = NULL; + this->logger = NULL; + this->fsUUID = NULL; + StrCpyList_init(&this->allowedInterfaces); + StrCpyList_init(&this->allowedRDMAInterfaces); + UInt16List_init(&this->preferredMetaNodes); + UInt16List_init(&this->preferredStorageTargets); + this->cacheBufStore = NULL; + this->pathBufStore = NULL; + this->msgBufStore = NULL; + this->ackStore = NULL; + this->inodeRefStore = NULL; + this->statfsCache = NULL; + this->localNode = NULL; + this->mgmtNodes = NULL; + this->metaNodes = NULL; + this->storageNodes = NULL; + this->targetMapper = NULL; + this->storageBuddyGroupMapper = NULL; + this->metaBuddyGroupMapper = NULL; + this->targetStateStore = NULL; + this->metaStateStore = NULL; + + this->dgramListener = NULL; + this->internodeSyncer = NULL; + this->ackManager = NULL; + this->flusher = NULL; + + this->fileInodeOps = NULL; + this->symlinkInodeOps = NULL; + this->dirInodeOps = NULL; + this->specialInodeOps = NULL; + + Mutex_init(&this->nicListMutex); + Mutex_init(&this->fsUUIDMutex); + +#ifdef BEEGFS_DEBUG + Mutex_init(&this->debugCounterMutex); + + this->numRPCs = 0; + this->numRemoteReads = 0; + this->numRemoteWrites = 0; +#endif // BEEGFS_DEBUG + +} + +void App_uninit(App* this) +{ + SAFE_DESTRUCT(this->flusher, Flusher_destruct); + SAFE_DESTRUCT(this->ackManager, AckManager_destruct); + SAFE_DESTRUCT(this->internodeSyncer, InternodeSyncer_destruct); + SAFE_DESTRUCT(this->dgramListener, DatagramListener_destruct); + + SAFE_DESTRUCT(this->storageNodes, NodeStoreEx_destruct); + SAFE_DESTRUCT(this->metaNodes, NodeStoreEx_destruct); + SAFE_DESTRUCT(this->mgmtNodes, NodeStoreEx_destruct); + + if(this->logger) + Logger_setAllLogLevels(this->logger, LOG_NOTHING); // disable logging + + SAFE_DESTRUCT(this->localNode, Node_put); + SAFE_DESTRUCT(this->targetMapper, TargetMapper_destruct); + SAFE_DESTRUCT(this->storageBuddyGroupMapper, MirrorBuddyGroupMapper_destruct); + SAFE_DESTRUCT(this->metaBuddyGroupMapper, MirrorBuddyGroupMapper_destruct); + SAFE_DESTRUCT(this->metaStateStore, TargetStateStore_destruct); + SAFE_DESTRUCT(this->targetStateStore, TargetStateStore_destruct); + SAFE_DESTRUCT(this->statfsCache, StatFsCache_destruct); + SAFE_DESTRUCT(this->inodeRefStore, InodeRefStore_destruct); + SAFE_DESTRUCT(this->ackStore, AcknowledgmentStore_destruct); + SAFE_DESTRUCT(this->msgBufStore, NoAllocBufferStore_destruct); + SAFE_DESTRUCT(this->pathBufStore, NoAllocBufferStore_destruct); + SAFE_DESTRUCT(this->cacheBufStore, NoAllocBufferStore_destruct); + UInt16List_uninit(&this->preferredStorageTargets); + UInt16List_uninit(&this->preferredMetaNodes); + StrCpyList_uninit(&this->allowedInterfaces); + StrCpyList_uninit(&this->allowedRDMAInterfaces); + SAFE_DESTRUCT(this->logger, Logger_destruct); + SAFE_DESTRUCT(this->tcpOnlyFilter, NetFilter_destruct); + SAFE_DESTRUCT(this->netFilter, NetFilter_destruct); + SAFE_DESTRUCT(this->cfg, Config_destruct); + + AtomicInt_uninit(&this->lockAckAtomicCounter); + + SAFE_DESTRUCT(this->mountConfig, MountConfig_destruct); + + ListTk_kfreeNicAddressListElems(&this->rdmaNicList); + NicAddressList_uninit(&this->rdmaNicList); + + kfree(this->fileInodeOps); + kfree(this->symlinkInodeOps); + kfree(this->dirInodeOps); + kfree(this->specialInodeOps); + + Mutex_uninit(&this->nicListMutex); + +#ifdef BEEGFS_DEBUG + Mutex_uninit(&this->debugCounterMutex); +#endif // BEEGFS_DEBUG +} + +int App_run(App* this) +{ + + printk_fhgfs(KERN_INFO, "Built " +#ifdef BEEGFS_NVFS + "with" +#else + "without" +#endif + " NVFS RDMA support.\n"); + +// init data objects & storage + + if(!__App_initDataObjects(this, this->mountConfig) ) + { + printk_fhgfs(KERN_WARNING, + "Configuration error: Initialization of common objects failed. " + "(Log file may provide additional information.)\n"); + this->appResult = APPCODE_INVALID_CONFIG; + return this->appResult; + } + + if(!__App_initInodeOperations(this) ) + { + printk_fhgfs(KERN_WARNING, "Initialization of inode operations failed."); + this->appResult = APPCODE_INITIALIZATION_ERROR; + return this->appResult; + } + + + // init components + + if(!__App_initComponents(this) ) + { + printk_fhgfs(KERN_WARNING, "Component initialization error. " + "(Log file may provide additional information.)\n"); + this->appResult = APPCODE_INITIALIZATION_ERROR; + return this->appResult; + } + + __App_logInfos(this); + + + // start components + + __App_startComponents(this); + + // Note: We wait some ms for the node downloads here because the kernel would like to + // check the properties of the root directory directly after mount. + + InternodeSyncer_waitForMgmtInit(this->internodeSyncer, 1000); + + if(!__App_mountServerCheck(this) ) + { // mount check failed => cancel mount + printk_fhgfs(KERN_WARNING, "Mount sanity check failed. Canceling mount. " + "(Log file may provide additional information. Check can be disabled with " + "sysMountSanityCheckMS=0 in the config file.)\n"); + this->appResult = APPCODE_INITIALIZATION_ERROR; + + return this->appResult; + } + + // mark: mount succeeded if we got here! + + return this->appResult; +} + +void App_stop(App* this) +{ + const char* logContext = "App (stop)"; + + // note: be careful, because we don't know what has been succesfully initialized! + + // note: this can also be called from App_run() to cancel a mount! + + if(this->cfg) + { + __App_stopComponents(this); + + if(!Config_getConnUnmountRetries(this->cfg) ) + this->connRetriesEnabled = false; + + // wait for termination + __App_joinComponents(this); + + if(this->logger) + Logger_log(this->logger, 1, logContext, "All components stopped."); + } +} + +bool __App_initDataObjects(App* this, MountConfig* mountConfig) +{ + const char* logContext = "App (init data objects)"; + + char* interfacesFilename; + char* rdmaInterfacesFilename; + char* preferredMetaFile; + char* preferredStorageFile; + size_t numCacheBufs; + size_t cacheBufSize; + size_t numPathBufs; + size_t pathBufsSize; + size_t numMsgBufs; + size_t msgBufsSize; + char* interfacesList; + + AtomicInt_init(&this->lockAckAtomicCounter, 0); + + this->cfg = Config_construct(mountConfig); + if(!this->cfg) + return false; + + + if(!StringTk_hasLength(Config_getSysMgmtdHost(this->cfg) ) ) + { + printk_fhgfs(KERN_WARNING, "Management host undefined\n"); + return false; + } + + { // load net filter (required before any connection can be made, incl. local conns) + const char* netFilterFile = Config_getConnNetFilterFile(this->cfg); + + this->netFilter = NetFilter_construct(netFilterFile); + if(!this->netFilter) + return false; + } + + { // load tcp-only filter (required before any connection can be made, incl. local conns) + const char* tcpOnlyFilterFile = Config_getConnTcpOnlyFilterFile(this->cfg); + + this->tcpOnlyFilter = NetFilter_construct(tcpOnlyFilterFile); + if(!this->tcpOnlyFilter) + return false; + } + + // logger (+ hostname as initial clientID, real ID will be set below) + this->logger = Logger_construct(this, this->cfg); + if(Config_getLogClientID(this->cfg) ) + { + const char* hostnameCopy = System_getHostnameCopy(); + Logger_setClientID(this->logger, hostnameCopy); + kfree(hostnameCopy); + } + + + // load allowed interface list + interfacesList = Config_getConnInterfacesList(this->cfg); + if (StringTk_hasLength(interfacesList) ) + StringTk_explode(interfacesList, ',', &this->allowedInterfaces); + else + { + // load allowed interface file + interfacesFilename = Config_getConnInterfacesFile(this->cfg); + if(strlen(interfacesFilename) && + !Config_loadStringListFile(interfacesFilename, &this->allowedInterfaces) ) + { + Logger_logErrFormatted(this->logger, logContext, + "Unable to load configured interfaces file: %s", interfacesFilename); + return false; + } + } + + // load allowed RDMA interface file + rdmaInterfacesFilename = Config_getConnRDMAInterfacesFile(this->cfg); + if(strlen(rdmaInterfacesFilename) && + !Config_loadStringListFile(rdmaInterfacesFilename, &this->allowedRDMAInterfaces) ) + { + Logger_logErrFormatted(this->logger, logContext, + "Unable to load configured RDMA interfaces file: %s", rdmaInterfacesFilename); + return false; + } + + // load preferred nodes files + preferredMetaFile = Config_getTunePreferredMetaFile(this->cfg); + if(strlen(preferredMetaFile) && + !Config_loadUInt16ListFile(this->cfg, preferredMetaFile, &this->preferredMetaNodes) ) + { + Logger_logErrFormatted(this->logger, logContext, + "Unable to load configured preferred meta nodes file: %s", preferredMetaFile); + return false; + } + + preferredStorageFile = Config_getTunePreferredStorageFile(this->cfg); + if(strlen(preferredStorageFile) && + !Config_loadUInt16ListFile(this->cfg, preferredStorageFile, &this->preferredStorageTargets) ) + { + Logger_logErrFormatted(this->logger, logContext, + "Unable to load configured preferred storage nodes file: %s", preferredStorageFile); + return false; + } + + // init stores, queues etc. + + this->targetMapper = TargetMapper_construct(); + + this->storageBuddyGroupMapper = MirrorBuddyGroupMapper_construct(); + + this->metaBuddyGroupMapper = MirrorBuddyGroupMapper_construct(); + + this->targetStateStore = TargetStateStore_construct(); + + this->metaStateStore = TargetStateStore_construct(); + + this->mgmtNodes = NodeStoreEx_construct(this, NODETYPE_Mgmt); + + this->metaNodes = NodeStoreEx_construct(this, NODETYPE_Meta); + + this->storageNodes = NodeStoreEx_construct(this, NODETYPE_Storage); + + if(!__App_initLocalNodeInfo(this) ) + return false; + + if(Config_getLogClientID(this->cfg) ) + { // set real clientID + NodeString alias; + Node_copyAlias(this->localNode, &alias); + Logger_setClientID(this->logger, alias.buf); + } + + // prealloc buffers + + switch(Config_getTuneFileCacheTypeNum(this->cfg) ) + { + case FILECACHETYPE_Buffered: + numCacheBufs = Config_getTuneFileCacheBufNum(this->cfg); + break; + + default: + numCacheBufs = 0; + break; + } + + cacheBufSize = Config_getTuneFileCacheBufSize(this->cfg); + + this->cacheBufStore = NoAllocBufferStore_construct(numCacheBufs, cacheBufSize); + if(!this->cacheBufStore) + return false; + + numPathBufs = Config_getTunePathBufNum(this->cfg); + pathBufsSize = Config_getTunePathBufSize(this->cfg); + + this->pathBufStore = NoAllocBufferStore_construct(numPathBufs, pathBufsSize); + if(!this->pathBufStore) + return false; + + numMsgBufs = Config_getTuneMsgBufNum(this->cfg); + msgBufsSize = Config_getTuneMsgBufSize(this->cfg); + + this->msgBufStore = NoAllocBufferStore_construct(numMsgBufs, msgBufsSize); + + this->ackStore = AcknowledgmentStore_construct(); + + this->inodeRefStore = InodeRefStore_construct(); + + this->statfsCache = StatFsCache_construct(this); + + return true; +} + +/** + * Initialized the inode_operations structs depending on what features have been enabled in + * the config. + */ +bool __App_initInodeOperations(App* this) +{ + Config* cfg = App_getConfig(this); + + this->fileInodeOps = os_kzalloc(sizeof(struct inode_operations) ); + this->symlinkInodeOps = os_kzalloc(sizeof(struct inode_operations) ); + this->dirInodeOps = os_kzalloc(sizeof(struct inode_operations) ); + this->specialInodeOps = os_kzalloc(sizeof(struct inode_operations) ); + + if (!this->fileInodeOps || !this->symlinkInodeOps || + !this->dirInodeOps || !this->specialInodeOps) + { + SAFE_KFREE(this->fileInodeOps); + SAFE_KFREE(this->symlinkInodeOps); + SAFE_KFREE(this->dirInodeOps); + SAFE_KFREE(this->specialInodeOps); + + return false; + } + + this->fileInodeOps->getattr = FhgfsOps_getattr; + this->fileInodeOps->permission = FhgfsOps_permission; + this->fileInodeOps->setattr = FhgfsOps_setattr; + +#ifdef KERNEL_HAS_GENERIC_READLINK + this->symlinkInodeOps->readlink = generic_readlink; // default is fine for us currently +#endif +#ifdef KERNEL_HAS_GET_LINK + this->symlinkInodeOps->get_link = FhgfsOps_get_link; +#else + this->symlinkInodeOps->follow_link = FhgfsOps_follow_link; + this->symlinkInodeOps->put_link = FhgfsOps_put_link; +#endif + this->symlinkInodeOps->getattr = FhgfsOps_getattr; + this->symlinkInodeOps->permission = FhgfsOps_permission; + this->symlinkInodeOps->setattr = FhgfsOps_setattr; + +#ifdef KERNEL_HAS_ATOMIC_OPEN + #ifdef BEEGFS_ENABLE_ATOMIC_OPEN + this->dirInodeOps->atomic_open = FhgfsOps_atomicOpen; + #endif // BEEGFS_ENABLE_ATOMIC_OPEN +#endif + this->dirInodeOps->lookup = FhgfsOps_lookupIntent; + this->dirInodeOps->create = FhgfsOps_createIntent; + this->dirInodeOps->link = FhgfsOps_link; + this->dirInodeOps->unlink = FhgfsOps_unlink; + this->dirInodeOps->mknod = FhgfsOps_mknod; + this->dirInodeOps->symlink = FhgfsOps_symlink; + this->dirInodeOps->mkdir = FhgfsOps_mkdir; + this->dirInodeOps->rmdir = FhgfsOps_rmdir; + this->dirInodeOps->rename = FhgfsOps_rename; + this->dirInodeOps->getattr = FhgfsOps_getattr; + this->dirInodeOps->permission = FhgfsOps_permission; + this->dirInodeOps->setattr = FhgfsOps_setattr; + + this->specialInodeOps->setattr = FhgfsOps_setattr; + + if (Config_getSysXAttrsEnabled(cfg) ) + { + this->fileInodeOps->listxattr = FhgfsOps_listxattr; + this->dirInodeOps->listxattr = FhgfsOps_listxattr; + +#ifdef KERNEL_HAS_GENERIC_GETXATTR + this->fileInodeOps->getxattr = generic_getxattr; + this->fileInodeOps->removexattr = FhgfsOps_removexattr; + this->fileInodeOps->setxattr = generic_setxattr; + + this->dirInodeOps->getxattr = generic_getxattr; + this->dirInodeOps->removexattr = FhgfsOps_removexattr; + this->dirInodeOps->setxattr = generic_setxattr; +#endif + + if (Config_getSysACLsEnabled(cfg) ) + { +#if defined(KERNEL_HAS_SET_ACL) || defined(KERNEL_HAS_SET_ACL_DENTRY) + this->fileInodeOps->set_acl = FhgfsOps_set_acl; + this->dirInodeOps->set_acl = FhgfsOps_set_acl; + /* Note: symlinks don't have ACLs + * The get_acl() operation was introduced as get_inode_acl() in the struct inode_operations in Linux 6.2 + */ +#if defined(KERNEL_HAS_GET_ACL) + this->fileInodeOps->get_acl = FhgfsOps_get_acl; + this->dirInodeOps->get_acl = FhgfsOps_get_acl; +#endif +#if defined(KERNEL_HAS_GET_INODE_ACL) + this->fileInodeOps->get_inode_acl = FhgfsOps_get_inode_acl; + this->dirInodeOps->get_inode_acl = FhgfsOps_get_inode_acl; +#endif +#else + Logger_logErr(this->logger, "Init inode operations", + "ACLs activated in config, but the signature inode->i_op->set_acl()" + "not supported on this kernel version."); + return false; +#endif // KERNEL_HAS_SET_ACL or KERNEL_HAS_SET_ACL_DENTRY + } + } + + return true; +} + +/** + * Retrieve NICs for the local node. + * + * @param nicList an uninitialized NicAddressList. Caller is responsible for + * memory management. + */ +void App_cloneLocalNicList(App* this, NicAddressList* nicList) +{ + Node_cloneNicList(this->localNode, nicList); +} + +void App_updateLocalInterfaces(App* this, NicAddressList* nicList) +{ + NicAddressList rdmaNicList; + NicListCapabilities nicCaps; + NodeStoreEx* stores[] = { + this->mgmtNodes, this->metaNodes, this->storageNodes, NULL + }; + int i; + + App_findAllowedRDMAInterfaces(this, nicList, &rdmaNicList); + + Mutex_lock(&this->nicListMutex); // L O C K + + ListTk_kfreeNicAddressListElems(&this->rdmaNicList); + NicAddressList_uninit(&this->rdmaNicList); + ListTk_cloneNicAddressList(&rdmaNicList, &this->rdmaNicList, true); + + Mutex_unlock(&this->nicListMutex); // U N L O C K + + NIC_supportedCapabilities(nicList, &nicCaps); + Node_updateInterfaces(this->localNode, Node_getPortUDP(this->localNode), + Node_getPortTCP(this->localNode), nicList); + Node_updateLocalInterfaces(this->localNode, nicList, &nicCaps, NULL); + + for (i = 0; stores[i] != NULL; ++i) + { + NodeStoreEx* store = stores[i]; + Node* node; + + for (node = NodeStoreEx_referenceFirstNode(store); node != NULL; + node = NodeStoreEx_referenceNextNodeAndReleaseOld(store, node)) + { + int nodeType = Node_getNodeType(node); + Node_updateLocalInterfaces(node, nicList, &nicCaps, + nodeType == NODETYPE_Meta || nodeType == NODETYPE_Storage? &rdmaNicList : NULL); + } + + } + ListTk_kfreeNicAddressListElems(&rdmaNicList); + NicAddressList_uninit(&rdmaNicList); +} + +/** + * Retrieve file system UUID for the mounted BeeGFS. + * + * @return a pointer to a copy of the file system UUID. Must be freed by the caller. + */ +char* App_cloneFsUUID(App* this) +{ + char* fsUUID; + Mutex_lock(&this->fsUUIDMutex); + fsUUID = StringTk_strDup(this->fsUUID); + Mutex_unlock(&this->fsUUIDMutex); + + return fsUUID; +} + +/** + * Update file system UUID for the mounted BeeGFS. + * + * @param fsUUID will be cloned and stored in the app object so original pointer can be freed. + */ +void App_updateFsUUID(App* this, const char* fsUUID) +{ + Mutex_lock(&this->fsUUIDMutex); + this->fsUUID = StringTk_strDup(fsUUID); + Mutex_unlock(&this->fsUUIDMutex); +} + +/** + * Populate nicList with the allowed and available interfaces. + * + * @param nicList uninitialized list to populate. Caller is responsible for memory + * management + * @return true if an allowed and available NIC was found. nicList is initialized and + * contains elements. + */ +bool App_findAllowedInterfaces(App* this, NicAddressList* nicList) +{ + NicAddressList tmpNicList; + bool useRDMA = Config_getConnUseRDMA(this->cfg); + bool result; + + NicAddressList_init(&tmpNicList); + NIC_findAll(&this->allowedInterfaces, useRDMA, false, &tmpNicList); + + if(!NicAddressList_length(&tmpNicList) ) + { + result = false; + goto exit; + } + + // created sorted tmpNicList clone + ListTk_cloneSortNicAddressList(&tmpNicList, nicList, &this->allowedInterfaces); + + result = true; + +exit: + if (result) + ListTk_kfreeNicAddressListElems(&tmpNicList); + NicAddressList_uninit(&tmpNicList); + return result; +} + +void App_cloneLocalRDMANicList(App* this, NicAddressList* rdmaNicList) +{ + Mutex_lock(&this->nicListMutex); // L O C K + ListTk_cloneNicAddressList(&this->rdmaNicList, rdmaNicList, true); + Mutex_unlock(&this->nicListMutex); // U N L O C K +} + +void App_findAllowedRDMAInterfaces(App* this, NicAddressList* nicList, NicAddressList* rdmaNicList) +{ + const char* logContext = "App (find RDMA interfaces)"; + bool useRDMA = Config_getConnUseRDMA(this->cfg); + if (useRDMA && StrCpyList_length(&this->allowedRDMAInterfaces) > 0) + { + NicAddressList tmpList; + + NicAddressList_init(&tmpList); + NIC_findAll(&this->allowedRDMAInterfaces, true, true, &tmpList); + + if(!NicAddressList_length(&tmpList) ) + { + Logger_logErr(this->logger, logContext, "Couldn't find any filtered and usable outbound RDMA NIC, using any"); + NicAddressList_init(rdmaNicList); + } + else + { + // created sorted rdmaNicList clone + ListTk_cloneSortNicAddressList(&tmpList, rdmaNicList, &this->allowedRDMAInterfaces); + } + ListTk_kfreeNicAddressListElems(&tmpList); + NicAddressList_uninit(&tmpList); + } + else + { + NicAddressList_init(rdmaNicList); + } +} + +bool __App_initLocalNodeInfo(App* this) +{ + const char* logContext = "App (init local node info)"; + + NicAddressList nicList; + char* hostname; + Time now; + pid_t currentPID; + char* generatedAlias; + char* alias; + + if (!App_findAllowedInterfaces(this, &nicList)) + { + Logger_logErr(this->logger, logContext, "Couldn't find any usable NIC"); + // required by App_uninit() + NicAddressList_init(&this->rdmaNicList); + return false; + } + + App_findAllowedRDMAInterfaces(this, &nicList, &this->rdmaNicList); + + // prepare clientID and create localNode + Time_setToNowReal(&now); + hostname = System_getHostnameCopy(); + currentPID = current->pid; + + // Generate the client alias (formerly nodeID): + generatedAlias = StringTk_kasprintf("c%llX-%llX-%s", + (uint64_t)currentPID, (uint64_t)now.tv_sec, hostname); + + // Truncate the generatedAlias at 32 characters before using it as the nodeID since it will be + // used as this client's alias by the management, and aliases are limited to 32 characters. + alias = os_kmalloc(33); + strncpy(alias, generatedAlias, 32); + alias[32] = '\0'; + + // note: numeric ID gets initialized with 0; will be set by management later in InternodeSyncer + this->localNode = Node_construct(this, alias, (NumNodeID){0}, + Config_getConnClientPort(this->cfg), 0, &nicList, NULL); + + // clean up + kfree(generatedAlias); + kfree(alias); + kfree(hostname); + + // delete nicList elems + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + + return true; +} + +bool __App_initComponents(App* this) +{ + const char* logContext = "App (init components)"; + + Logger_log(this->logger, Log_DEBUG, logContext, "Initializing components..."); + + this->dgramListener = + DatagramListener_construct(this, this->localNode, Config_getConnClientPort(this->cfg) ); + if(!this->dgramListener) + { + Logger_logErr(this->logger, logContext, + "Initialization of DatagramListener component failed"); + return false; + } + + this->internodeSyncer = InternodeSyncer_construct(this); // (requires dgramlis) + + this->ackManager = AckManager_construct(this); + + this->flusher = Flusher_construct(this); + + Logger_log(this->logger, Log_DEBUG, logContext, "Components initialized."); + + return true; +} + +void __App_startComponents(App* this) +{ + const char* logContext = "App (start components)"; + + Logger_log(this->logger, Log_DEBUG, logContext, "Starting up components..."); + + Thread_start( (Thread*)this->dgramListener); + + Thread_start( (Thread*)this->internodeSyncer); + Thread_start( (Thread*)this->ackManager); + + if(Config_getTuneFileCacheTypeNum(this->cfg) == FILECACHETYPE_Buffered) + Thread_start( (Thread*)this->flusher); + + Logger_log(this->logger, Log_DEBUG, logContext, "Components running."); +} + +void __App_stopComponents(App* this) +{ + const char* logContext = "App (stop components)"; + + if(this->logger) + Logger_log(this->logger, Log_WARNING, logContext, "Stopping components..."); + + if(this->flusher && Config_getTuneFileCacheTypeNum(this->cfg) == FILECACHETYPE_Buffered) + Thread_selfTerminate( (Thread*)this->flusher); + if(this->ackManager) + Thread_selfTerminate( (Thread*)this->ackManager); + if(this->internodeSyncer) + Thread_selfTerminate( (Thread*)this->internodeSyncer); + + if(this->dgramListener) + Thread_selfTerminate( (Thread*)this->dgramListener); +} + +void __App_joinComponents(App* this) +{ + const char* logContext = "App (join components)"; + + if(this->logger) + Logger_log(this->logger, 4, logContext, "Waiting for components to self-terminate..."); + + + if(Config_getTuneFileCacheTypeNum(this->cfg) == FILECACHETYPE_Buffered) + __App_waitForComponentTermination(this, (Thread*)this->flusher); + + __App_waitForComponentTermination(this, (Thread*)this->ackManager); + __App_waitForComponentTermination(this, (Thread*)this->internodeSyncer); + + __App_waitForComponentTermination(this, (Thread*)this->dgramListener); +} + +/** + * @param component the thread that we're waiting for via join(); may be NULL (in which case this + * method returns immediately) + */ +void __App_waitForComponentTermination(App* this, Thread* component) +{ + const char* logContext = "App (wait for component termination)"; + + const int timeoutMS = 2000; + + bool isTerminated; + + if(!component) + return; + + isTerminated = Thread_timedjoin(component, timeoutMS); + if(!isTerminated) + { // print message to show user which thread is blocking + if(this->logger) + Logger_logFormatted(this->logger, 2, logContext, + "Still waiting for this component to stop: %s", Thread_getName(component) ); + + Thread_join(component); + + if(this->logger) + Logger_logFormatted(this->logger, 2, logContext, + "Component stopped: %s", Thread_getName(component) ); + } +} + +void __App_logInfos(App* this) +{ + const char* logContext = "App_logInfos"; + NodeString alias; + size_t nicListStrLen = 1024; + char* nicListStr = os_kmalloc(nicListStrLen); + char* extendedNicListStr = os_kmalloc(nicListStrLen); + NicAddressList nicList; + NicAddressListIter nicIter; + + Node_cloneNicList(this->localNode, &nicList); + + // print software version (BEEGFS_VERSION) + Logger_logFormatted(this->logger, 1, logContext, "BeeGFS Client Version: %s", BEEGFS_VERSION); + + // print debug version info + LOG_DEBUG(this->logger, 1, logContext, "--DEBUG VERSION--"); + + // print local clientID + Node_copyAlias(this->localNode, &alias); + Logger_logFormatted(this->logger, 2, logContext, "ClientID: %s", alias.buf); + + // list usable network interfaces + NicAddressListIter_init(&nicIter, &nicList); + nicListStr[0] = 0; + extendedNicListStr[0] = 0; + + for( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + const char* nicTypeStr; + char* nicAddrStr; + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + char* tmpStr = os_kmalloc(nicListStrLen); + + if(nicAddr->nicType == NICADDRTYPE_RDMA) + nicTypeStr = "RDMA"; + else + if(nicAddr->nicType == NICADDRTYPE_STANDARD) + nicTypeStr = "TCP"; + else + nicTypeStr = "Unknown"; + + // short NIC info + snprintf(tmpStr, nicListStrLen, "%s%s(%s) ", nicListStr, nicAddr->name, nicTypeStr); + strcpy(nicListStr, tmpStr); // tmpStr to avoid writing to a buffer that we're reading + // from at the same time + + // extended NIC info + nicAddrStr = NIC_nicAddrToString(nicAddr); + snprintf(tmpStr, nicListStrLen, "%s\n+ %s", extendedNicListStr, nicAddrStr); + strcpy(extendedNicListStr, tmpStr); // tmpStr to avoid writing to a buffer that we're + // reading from at the same time + kfree(nicAddrStr); + + SAFE_KFREE(tmpStr); + } + + Logger_logFormatted(this->logger, 2, logContext, "Usable NICs: %s", nicListStr); + Logger_logFormatted(this->logger, 4, logContext, "Extended list of usable NICs: %s", + extendedNicListStr); + + // print net filters + if(NetFilter_getNumFilterEntries(this->netFilter) ) + { + Logger_logFormatted(this->logger, Log_WARNING, logContext, "Net filters: %d", + (int)NetFilter_getNumFilterEntries(this->netFilter) ); + } + + if(NetFilter_getNumFilterEntries(this->tcpOnlyFilter) ) + { + Logger_logFormatted(this->logger, Log_WARNING, logContext, "TCP-only filters: %d", + (int)NetFilter_getNumFilterEntries(this->tcpOnlyFilter) ); + } + + // print preferred nodes and targets + + if(UInt16List_length(&this->preferredMetaNodes) ) + { + Logger_logFormatted(this->logger, Log_WARNING, logContext, "Preferred metadata servers: %d", + (int)UInt16List_length(&this->preferredMetaNodes) ); + } + + if(UInt16List_length(&this->preferredStorageTargets) ) + { + Logger_logFormatted(this->logger, Log_WARNING, logContext, "Preferred storage targets: %d", + (int)UInt16List_length(&this->preferredStorageTargets) ); + } + + // Print a warning message if user has enabled ACLs, but no XAttrs (in that case, XAttrs have + // been auto-enabled which contradicts the config file.) + if (Config_getSysXAttrsImplicitlyEnabled(this->cfg) ) + { + Logger_log(this->logger, Log_WARNING, logContext, + "WARNING: ACLs are enabled, but XAttrs are not. " + "XAttrs are needed to store ACLs, so they have automatically been enabled. " + "Please check configuration."); + } + + // clean up + SAFE_KFREE(nicListStr); + SAFE_KFREE(extendedNicListStr); + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + +} + +/** + * Perform some basic sanity checks to deny mount in case of an error. + */ +bool __App_mountServerCheck(App* this) +{ + const char* logContext = "Mount sanity check"; + Config* cfg = this->cfg; + + unsigned waitMS = Config_getSysMountSanityCheckMS(cfg); + + bool retVal = false; + bool retriesEnabledOrig = this->connRetriesEnabled; + bool mgmtInitDone; + FhgfsOpsErr statRootErr; + fhgfs_stat statRootDetails; + FhgfsOpsErr statStorageErr; + int64_t storageSpaceTotal; + int64_t storageSpaceFree; + + + if(!waitMS) + return true; // mount check disabled + + + this->connRetriesEnabled = false; // NO _ R E T R I E S + + // wait for management init + + mgmtInitDone = InternodeSyncer_waitForMgmtInit(this->internodeSyncer, waitMS); + if(!mgmtInitDone) + { + Logger_logErr(this->logger, logContext, "Waiting for management server initialization " + "timed out. Are the server settings correct? Is the management daemon running?"); + + goto error_resetRetries; + } + + // check root metadata server + + statRootErr = FhgfsOpsRemoting_statRoot(this, &statRootDetails); + if(statRootErr != FhgfsOpsErr_SUCCESS) + { + Logger_logErrFormatted(this->logger, logContext, "Retrieval of root directory entry " + "failed. Are all metadata servers running and registered at the management daemon? " + "(Error: %s)", FhgfsOpsErr_toErrString(statRootErr) ); + + goto error_resetRetries; + } + + // check storage servers + + statStorageErr = FhgfsOpsRemoting_statStoragePath(this, false, + &storageSpaceTotal, &storageSpaceFree); + + if(statStorageErr != FhgfsOpsErr_SUCCESS) + { + Logger_logErrFormatted(this->logger, logContext, "Retrieval of storage server free space " + "info failed. Are the storage servers running and registered at the management daemon? " + "Did you remove a storage target directory on a server? (Error: %s)", + FhgfsOpsErr_toErrString(statStorageErr) ); + + goto error_resetRetries; + } + + retVal = true; + + +error_resetRetries: + this->connRetriesEnabled = retriesEnabledOrig; // R E S E T _ R E T R I E S + + return retVal; +} + +/** + * Note: This is actually a Program version, not an App version, but we have it here now because + * app provides a stable closed source part and we want this version to be fixed at compile time. + */ +const char* App_getVersionStr(void) +{ + return BEEGFS_VERSION; +} diff --git a/client_module/source/app/App.h b/client_module/source/app/App.h new file mode 100644 index 0000000..74363d6 --- /dev/null +++ b/client_module/source/app/App.h @@ -0,0 +1,436 @@ +#ifndef APP_H_ +#define APP_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// program return codes +#define APPCODE_NO_ERROR 0 +#define APPCODE_PROGRAM_ERROR 1 +#define APPCODE_INVALID_CONFIG 2 +#define APPCODE_INITIALIZATION_ERROR 3 +#define APPCODE_RUNTIME_ERROR 4 + +// forward declarations +struct Config; +struct Logger; +struct DatagramListener; +struct InternodeSyncer; +struct AckManager; +struct Flusher; +struct Node; +struct NodeStoreEx; +struct TargetMapper; +struct MirrorBuddyGroupMapper; +struct TargetStateStore; +struct NoAllocBufferStore; +struct AcknowledgmentStore; +struct NetFilter; +struct InodeRefStore; +struct StatFsCache; + + + +struct App; +typedef struct App App; + + +extern void App_init(App* this, MountConfig* mountConfig); +extern void App_uninit(App* this); + +extern int App_run(App* this); +extern void App_stop(App* this); + +extern bool __App_initDataObjects(App* this, MountConfig* mountConfig); +extern bool __App_initInodeOperations(App* this); +extern bool __App_initLocalNodeInfo(App* this); +extern bool __App_initComponents(App* this); +extern void __App_startComponents(App* this); +extern void __App_stopComponents(App* this); +extern void __App_joinComponents(App* this); +extern void __App_waitForComponentTermination(App* this, Thread* component); + +extern void __App_logInfos(App* this); + +extern bool __App_mountServerCheck(App* this); + +extern bool App_findAllowedInterfaces(App* this, NicAddressList* nicList); +extern void App_findAllowedRDMAInterfaces(App* this, NicAddressList* nicList, NicAddressList* rdmaNicList); + +// external getters & setters +extern const char* App_getVersionStr(void); +extern void App_updateLocalInterfaces(App* app, NicAddressList* nicList); +extern char* App_cloneFsUUID(App* this); +extern void App_updateFsUUID(App* this, const char* fsUUID); +extern void App_cloneLocalNicList(App* this, NicAddressList* nicList); +extern void App_cloneLocalRDMANicList(App* this, NicAddressList* rdmaNicList); + +// inliners +static inline struct Logger* App_getLogger(App* this); +static inline struct Config* App_getConfig(App* this); +static inline struct MountConfig* App_getMountConfig(App* this); +static inline struct NetFilter* App_getNetFilter(App* this); +static inline struct NetFilter* App_getTcpOnlyFilter(App* this); +static inline NicAddressList* App_getLocalRDMANicListLocked(App* this); +/** + * Called when access to the nicList is required but doesn't + * want the overhead of a clone operation. This locks the internel nicListMutex. + * App_unlockNicList must later be invoked. + */ +static inline void App_lockNicList(App* this); +/** + * Release the lock on nicListMutex acquired by App_lockNicList. + */ +static inline void App_unlockNicList(App* this); +static inline UInt16List* App_getPreferredStorageTargets(App* this); +static inline UInt16List* App_getPreferredMetaNodes(App* this); +static inline struct Node* App_getLocalNode(App* this); +static inline struct NodeStoreEx* App_getMgmtNodes(App* this); +static inline struct NodeStoreEx* App_getMetaNodes(App* this); +static inline struct NodeStoreEx* App_getStorageNodes(App* this); +static inline struct TargetMapper* App_getTargetMapper(App* this); +static inline struct MirrorBuddyGroupMapper* App_getStorageBuddyGroupMapper(App* this); +static inline struct MirrorBuddyGroupMapper* App_getMetaBuddyGroupMapper(App* this); +static inline struct TargetStateStore* App_getTargetStateStore(App* this); +static inline struct TargetStateStore* App_getMetaStateStore(App* this); +static inline struct NoAllocBufferStore* App_getCacheBufStore(App* this); +static inline struct NoAllocBufferStore* App_getPathBufStore(App* this); +static inline struct NoAllocBufferStore* App_getMsgBufStore(App* this); +static inline struct AcknowledgmentStore* App_getAckStore(App* this); +static inline struct InodeRefStore* App_getInodeRefStore(App* this); +static inline struct StatFsCache* App_getStatFsCache(App* this); +static inline struct DatagramListener* App_getDatagramListener(App* this); +static inline struct InternodeSyncer* App_getInternodeSyncer(App* this); +static inline struct AckManager* App_getAckManager(App* this); +static inline AtomicInt* App_getLockAckAtomicCounter(App* this); +static inline bool App_getConnRetriesEnabled(App* this); +static inline void App_setConnRetriesEnabled(App* this, bool connRetriesEnabled); +static inline bool App_getNetBenchModeEnabled(App* this); +static inline void App_setNetBenchModeEnabled(App* this, bool netBenchModeEnabled); + +static inline struct inode_operations* App_getFileInodeOps(App* this); +static inline struct inode_operations* App_getSymlinkInodeOps(App* this); +static inline struct inode_operations* App_getDirInodeOps(App* this); +static inline struct inode_operations* App_getSpecialInodeOps(App* this); + +#ifdef BEEGFS_DEBUG +static inline size_t App_getNumRPCs(App* this); +static inline void App_incNumRPCs(App* this); +static inline size_t App_getNumRemoteReads(App* this); +static inline void App_incNumRemoteReads(App* this); +static inline size_t App_getNumRemoteWrites(App* this); +static inline void App_incNumRemoteWrites(App* this); +#endif // BEEGFS_DEBUG + + +struct App +{ + int appResult; + MountConfig* mountConfig; + + struct Config* cfg; + struct Logger* logger; + const char* fsUUID; + Mutex fsUUIDMutex; + + struct NetFilter* netFilter; // empty filter means "all nets allowed" + struct NetFilter* tcpOnlyFilter; // for IPs which allow only plain TCP (no RDMA etc) + StrCpyList allowedInterfaces; // empty list means "all interfaces accepted" + StrCpyList allowedRDMAInterfaces; // empty list means "all interfaces eligible" + UInt16List preferredMetaNodes; // empty list means "no preferred nodes => use any" + UInt16List preferredStorageTargets; // empty list means "no preferred nodes => use any" + // rdmaNicList contains the addresses of specific RDMA NICs to use for outbound RDMA. + // This is only populated when the configuration specifies a list of interfaces. If this + // list is empty, any RDMA NIC on the client may be used for outbound RDMA. + // allowedRDMAInterfaces contains the interface names used to populate this list. + NicAddressList rdmaNicList; + Mutex nicListMutex; + + struct Node* localNode; + struct NodeStoreEx* mgmtNodes; + struct NodeStoreEx* metaNodes; + struct NodeStoreEx* storageNodes; + + struct TargetMapper* targetMapper; + struct MirrorBuddyGroupMapper* storageBuddyGroupMapper; + struct MirrorBuddyGroupMapper* metaBuddyGroupMapper; + struct TargetStateStore* targetStateStore; // map storage targets IDs to a state + struct TargetStateStore* metaStateStore; // map mds targets (i.e. nodeIDs) to a state + + struct NoAllocBufferStore* cacheBufStore; // for buffered cache mode + struct NoAllocBufferStore* pathBufStore; // for dentry path lookups + struct NoAllocBufferStore* msgBufStore; // for MessagingTk request/response + struct AcknowledgmentStore* ackStore; + struct InodeRefStore* inodeRefStore; + struct StatFsCache* statfsCache; + + struct DatagramListener* dgramListener; + struct InternodeSyncer* internodeSyncer; + struct AckManager* ackManager; + struct Flusher* flusher; + + AtomicInt lockAckAtomicCounter; // used by remoting to generate unique lockAckIDs + volatile bool connRetriesEnabled; // changed at umount and via procfs + bool netBenchModeEnabled; // changed via procfs to disable server-side disk read/write + + // Inode operations. Since the members of the structs depend on runtime config opts, we need + // one copy of each struct per App object. + struct inode_operations* fileInodeOps; + struct inode_operations* symlinkInodeOps; + struct inode_operations* dirInodeOps; + struct inode_operations* specialInodeOps; + +#ifdef BEEGFS_DEBUG + Mutex debugCounterMutex; // this is the closed tree, so we don't have atomics here (but doesn't + // matter since this is debug info and not performance critical) + + size_t numRPCs; + size_t numRemoteReads; + size_t numRemoteWrites; +#endif // BEEGFS_DEBUG + +}; + + +struct Logger* App_getLogger(App* this) +{ + return this->logger; +} + +struct Config* App_getConfig(App* this) +{ + return this->cfg; +} + +struct MountConfig* App_getMountConfig(App* this) +{ + return this->mountConfig; +} + +struct NetFilter* App_getNetFilter(App* this) +{ + return this->netFilter; +} + +struct NetFilter* App_getTcpOnlyFilter(App* this) +{ + return this->tcpOnlyFilter; +} + +UInt16List* App_getPreferredMetaNodes(App* this) +{ + return &this->preferredMetaNodes; +} + +UInt16List* App_getPreferredStorageTargets(App* this) +{ + return &this->preferredStorageTargets; +} + +void App_lockNicList(App* this) +{ + Mutex_lock(&this->nicListMutex); // L O C K +} + +void App_unlockNicList(App* this) +{ + Mutex_unlock(&this->nicListMutex); // U N L O C K +} + +NicAddressList* App_getLocalRDMANicListLocked(App* this) +{ + return &this->rdmaNicList; +} + +struct Node* App_getLocalNode(App* this) +{ + return this->localNode; +} + +struct NodeStoreEx* App_getMgmtNodes(App* this) +{ + return this->mgmtNodes; +} + +struct NodeStoreEx* App_getMetaNodes(App* this) +{ + return this->metaNodes; +} + +struct NodeStoreEx* App_getStorageNodes(App* this) +{ + return this->storageNodes; +} + +struct TargetMapper* App_getTargetMapper(App* this) +{ + return this->targetMapper; +} + +struct MirrorBuddyGroupMapper* App_getStorageBuddyGroupMapper(App* this) +{ + return this->storageBuddyGroupMapper; +} + +struct MirrorBuddyGroupMapper* App_getMetaBuddyGroupMapper(App* this) +{ + return this->metaBuddyGroupMapper; +} + +struct TargetStateStore* App_getTargetStateStore(App* this) +{ + return this->targetStateStore; +} + +struct TargetStateStore* App_getMetaStateStore(App* this) +{ + return this->metaStateStore; +} + +struct NoAllocBufferStore* App_getCacheBufStore(App* this) +{ + return this->cacheBufStore; +} + +struct NoAllocBufferStore* App_getPathBufStore(App* this) +{ + return this->pathBufStore; +} + +struct NoAllocBufferStore* App_getMsgBufStore(App* this) +{ + return this->msgBufStore; +} + +struct AcknowledgmentStore* App_getAckStore(App* this) +{ + return this->ackStore; +} + +struct InodeRefStore* App_getInodeRefStore(App* this) +{ + return this->inodeRefStore; +} + +struct StatFsCache* App_getStatFsCache(App* this) +{ + return this->statfsCache; +} + +struct DatagramListener* App_getDatagramListener(App* this) +{ + return this->dgramListener; +} + +struct InternodeSyncer* App_getInternodeSyncer(App* this) +{ + return this->internodeSyncer; +} + +struct AckManager* App_getAckManager(App* this) +{ + return this->ackManager; +} + +AtomicInt* App_getLockAckAtomicCounter(App* this) +{ + return &this->lockAckAtomicCounter; +} + +bool App_getConnRetriesEnabled(App* this) +{ + return this->connRetriesEnabled; +} + +void App_setConnRetriesEnabled(App* this, bool connRetriesEnabled) +{ + this->connRetriesEnabled = connRetriesEnabled; +} + +bool App_getNetBenchModeEnabled(App* this) +{ + return this->netBenchModeEnabled; +} + +void App_setNetBenchModeEnabled(App* this, bool netBenchModeEnabled) +{ + this->netBenchModeEnabled = netBenchModeEnabled; +} + +struct inode_operations* App_getFileInodeOps(App* this) +{ + return this->fileInodeOps; +} + +struct inode_operations* App_getSymlinkInodeOps(App* this) +{ + return this->symlinkInodeOps; +} + +struct inode_operations* App_getDirInodeOps(App* this) +{ + return this->dirInodeOps; +} + +struct inode_operations* App_getSpecialInodeOps(App* this) +{ + return this->specialInodeOps; +} + +#ifdef BEEGFS_DEBUG + + size_t App_getNumRPCs(App* this) + { + return this->numRPCs; + } + + void App_incNumRPCs(App* this) + { + Mutex_lock(&this->debugCounterMutex); + this->numRPCs++; + Mutex_unlock(&this->debugCounterMutex); + } + + size_t App_getNumRemoteReads(App* this) + { + return this->numRemoteReads; + } + + void App_incNumRemoteReads(App* this) + { + Mutex_lock(&this->debugCounterMutex); + this->numRemoteReads++; + Mutex_unlock(&this->debugCounterMutex); + } + + size_t App_getNumRemoteWrites(App* this) + { + return this->numRemoteWrites; + } + + void App_incNumRemoteWrites(App* this) + { + Mutex_lock(&this->debugCounterMutex); + this->numRemoteWrites++; + Mutex_unlock(&this->debugCounterMutex); + } + +#else // BEEGFS_DEBUG + + #define App_incNumRPCs(this) + #define App_incNumRemoteReads(this) + #define App_incNumRemoteWrites(this) + +#endif // BEEGFS_DEBUG + + +#endif /*APP_H_*/ diff --git a/client_module/source/app/config/Config.c b/client_module/source/app/config/Config.c new file mode 100644 index 0000000..b928bd7 --- /dev/null +++ b/client_module/source/app/config/Config.c @@ -0,0 +1,1557 @@ +#include +#include +#include +#include +#include "Config.h" + +#include +#include + + +#define CONFIG_DEFAULT_CFGFILENAME "/etc/beegfs/beegfs-client.conf" +#define CONFIG_FILE_COMMENT_CHAR '#' +#define CONFIG_FILE_MAX_LINE_LENGTH 1024 +#define CONFIG_ERR_BUF_LENGTH 1024 +#define CONFIG_AUTHFILE_READSIZE 1024 // max amount of data that we read from auth file +#define CONFIG_AUTHFILE_MINSIZE 4 // at least 2, because we compute two 32bit hashes + +#define CONFIG_CONN_RDMA_BUFNUM_MIN 3 // required by the IBVSocket logic and protocol +#define CONFIG_CONN_RDMA_NONE_STR "none" +#define CONFIG_CONN_RDMA_DEFAULT_STR "default" +#define CONFIG_CONN_RDMA_PAGE_STR "page" +#define CONFIG_CONN_RDMA_DEFAULT -1 + +#define FILECACHETYPE_NONE_STR "none" +#define FILECACHETYPE_BUFFERED_STR "buffered" +#define FILECACHETYPE_PAGED_STR "paged" +#define FILECACHETYPE_NATIVE_STR "native" + +#define LOGGERTYPE_SYSLOG_STR "syslog" + +#define EVENTLOGMASK_NONE "none" +#define EVENTLOGMASK_FLUSH "flush" +#define EVENTLOGMASK_TRUNC "trunc" +#define EVENTLOGMASK_SETATTR "setattr" +#define EVENTLOGMASK_CLOSE "close" +#define EVENTLOGMASK_LINK_OP "link-op" +#define EVENTLOGMASK_READ "read" +#define EVENTLOGMASK_OPEN_READ "open-read" +#define EVENTLOGMASK_OPEN_WRITE "open-write" +#define EVENTLOGMASK_OPEN_READ_WRITE "open-readwrite" + + +#define IGNORE_CONFIG_VALUE(compareStr) /* to be used in applyConfigMap() */ \ + if(!strcmp(keyStr, compareStr) ) \ + ; \ + else + + + +static bool __Config_readLineFromFile(struct file* cfgFile, + char* buf, size_t bufLen, bool* outEndOfFile); + + + +static size_t Config_fs_read(struct file *file, char *buf, size_t size, loff_t *pos) +{ + size_t readRes; + +#if defined(KERNEL_HAS_KERNEL_READ) + readRes = kernel_read(file, buf, size, pos); +#else + + WITH_PROCESS_CONTEXT { + readRes = vfs_read(file, buf, size, pos); + } + +#endif + + return readRes; +} + +static bool assignKeyIfNotZero(const char* key, const char* strVal, int* const intVal) { + const int tempVal = StringTk_strToInt(strVal); + if (tempVal == 0 || intVal == NULL) { + return false; + } + + *intVal = tempVal; + return true; +} + +/** + * @param mountConfig will be copied (not owned by this object) + */ +bool Config_init(Config* this, MountConfig* mountConfig) +{ + // init configurable strings + this->cfgFile = NULL; + this->connInterfacesFile = NULL; + this->connRDMAInterfacesFile = NULL; + this->connNetFilterFile = NULL; + this->connAuthFile = NULL; + this->connMessagingTimeouts = NULL; + this->connRDMATimeouts = NULL; + this->connTcpOnlyFilterFile = NULL; + this->tunePreferredMetaFile = NULL; + this->tunePreferredStorageFile = NULL; + this->tuneFileCacheType = NULL; + this->sysMgmtdHost = NULL; + this->sysInodeIDStyle = NULL; + this->connInterfacesList = NULL; + this->connRDMAKeyType = NULL; + + return _Config_initConfig(this, mountConfig); +} + +Config* Config_construct(MountConfig* mountConfig) +{ + Config* this = kmalloc(sizeof(Config), GFP_NOFS); + + if(!this || + !Config_init(this, mountConfig) ) + { + kfree(this); + return NULL; + } + + return this; +} + +void Config_uninit(Config* this) +{ + SAFE_KFREE(this->cfgFile); + SAFE_KFREE(this->connInterfacesFile); + SAFE_KFREE(this->connRDMAInterfacesFile); + SAFE_KFREE(this->connNetFilterFile); + SAFE_KFREE(this->connAuthFile); + SAFE_KFREE(this->connTcpOnlyFilterFile); + SAFE_KFREE(this->connMessagingTimeouts); + SAFE_KFREE(this->connRDMATimeouts); + SAFE_KFREE(this->tunePreferredMetaFile); + SAFE_KFREE(this->tunePreferredStorageFile); + SAFE_KFREE(this->tuneFileCacheType); + SAFE_KFREE(this->sysMgmtdHost); + SAFE_KFREE(this->sysInodeIDStyle); + SAFE_KFREE(this->connInterfacesList); + SAFE_KFREE(this->connRDMAKeyType); + + StrCpyMap_uninit(&this->configMap); +} + +void Config_destruct(Config* this) +{ + Config_uninit(this); + + kfree(this); +} + +bool _Config_initConfig(Config* this, MountConfig* mountConfig) +{ + struct in_addr ipAddr; + + StrCpyMap_init(&this->configMap); + + // load and apply args to see whether we have a cfgFile + _Config_loadDefaults(this); + __Config_loadFromMountConfig(this, mountConfig); + + if(!_Config_applyConfigMap(this) ) + goto error; + + if(this->cfgFile && strlen(this->cfgFile) ) + { // there is a config file specified + // start over again and include the config file this time + StrCpyMap_clear(&this->configMap); + + _Config_loadDefaults(this); + if(!__Config_loadFromFile(this, this->cfgFile) ) + goto error; + + __Config_loadFromMountConfig(this, mountConfig); + + if(!_Config_applyConfigMap(this) ) + goto error; + + if (this->connMaxInternodeNum > 0xffff) + goto error; + } + + if(!SocketTk_getHostByAddrStr(this->sysMgmtdHost, &ipAddr)) { + printk_fhgfs(KERN_WARNING, "Management address '%s' is not an IPv4 address\n", this->sysMgmtdHost); + goto error; + } + + return __Config_initImplicitVals(this); + +error: + StrCpyMap_uninit(&this->configMap); + return false; +} + +StrCpyMapIter _Config_eraseFromConfigMap(Config* this, StrCpyMapIter* iter) +{ + char* nextKey; + StrCpyMapIter nextIter; + + nextIter = *iter; + StrCpyMapIter_next(&nextIter); + + if(StrCpyMapIter_end(&nextIter) ) + { // no next element in the map + StrCpyMap_erase(&this->configMap, StrCpyMapIter_key(iter) ); + return nextIter; + } + + nextKey = StrCpyMapIter_key(&nextIter); + StrCpyMap_erase(&this->configMap, StrCpyMapIter_key(iter) ); + + return StrCpyMap_find(&this->configMap, nextKey); +} + +void _Config_loadDefaults(Config* this) +{ + /** + * **IMPORTANT**: Don't forget to add new values also to the BEEGFS_ONLINE_CFG and BEEGFS_FSCK + * ignored config values! + */ + + _Config_configMapRedefine(this, "cfgFile", ""); + + _Config_configMapRedefine(this, "logLevel", "3"); + _Config_configMapRedefine(this, "logClientID", "false"); + + _Config_configMapRedefine(this, "connPortShift", "0"); + + // To be able to merge these with the legacy settings later, we set them to -1 here. Otherwise it + // is impossible to detect if they have actually been set or just loaded the default. + // The actual default values are applied during the post processing in applyConfigMap. + _Config_configMapRedefine(this, "connClientPort", "-1"); // 8004 + _Config_configMapRedefine(this, "connMgmtdPort", "-1"); // 8008 + + _Config_configMapRedefine(this, "connUseRDMA", "true"); + _Config_configMapRedefine(this, "connTCPFallbackEnabled", "true"); + _Config_configMapRedefine(this, "connMaxInternodeNum", "8"); + _Config_configMapRedefine(this, "connInterfacesFile", ""); + _Config_configMapRedefine(this, "connInterfacesList", ""); + _Config_configMapRedefine(this, "connRDMAInterfacesFile", ""); + _Config_configMapRedefine(this, "connFallbackExpirationSecs", "900"); + _Config_configMapRedefine(this, "connCommRetrySecs", "600"); + _Config_configMapRedefine(this, "connUnmountRetries", "true"); + _Config_configMapRedefine(this, "connTCPRcvBufSize", "0"); + _Config_configMapRedefine(this, "connUDPRcvBufSize", "0"); + _Config_configMapRedefine(this, "connRDMABufSize", "8192"); + _Config_configMapRedefine(this, "connRDMAFragmentSize", CONFIG_CONN_RDMA_PAGE_STR); + _Config_configMapRedefine(this, "connRDMABufNum", "70"); + _Config_configMapRedefine(this, "connRDMAMetaBufSize", CONFIG_CONN_RDMA_DEFAULT_STR); + _Config_configMapRedefine(this, "connRDMAMetaFragmentSize", CONFIG_CONN_RDMA_DEFAULT_STR); + _Config_configMapRedefine(this, "connRDMAMetaBufNum", CONFIG_CONN_RDMA_DEFAULT_STR); + _Config_configMapRedefine(this, "connRDMATypeOfService", "0"); + _Config_configMapRedefine(this, "connRDMAKeyType", RDMAKEYTYPE_UNSAFE_GLOBAL_STR); + _Config_configMapRedefine(this, "connNetFilterFile", ""); + _Config_configMapRedefine(this, "connMaxConcurrentAttempts", "0"); + _Config_configMapRedefine(this, "connAuthFile", ""); + _Config_configMapRedefine(this, "connDisableAuthentication", "false"); + _Config_configMapRedefine(this, "connTcpOnlyFilterFile", ""); + + /* connMessagingTimeouts: default to zero, indicating that constants + * specified in Common.h are used. + */ + _Config_configMapRedefine(this, "connMessagingTimeouts", "0,0,0"); + // connRDMATimeouts: zero values are interpreted as the defaults specified in IBVSocket.c + _Config_configMapRedefine(this, "connRDMATimeouts", "0,0,0,0,0"); + + _Config_configMapRedefine(this, "tunePreferredMetaFile", ""); + _Config_configMapRedefine(this, "tunePreferredStorageFile", ""); + _Config_configMapRedefine(this, "tuneFileCacheType", FILECACHETYPE_BUFFERED_STR); + _Config_configMapRedefine(this, "tuneFileCacheBufSize", "524288"); + _Config_configMapRedefine(this, "tuneFileCacheBufNum", "0"); + _Config_configMapRedefine(this, "tunePageCacheValidityMS", "2000000000"); + _Config_configMapRedefine(this, "tuneDirSubentryCacheValidityMS", "1000"); + _Config_configMapRedefine(this, "tuneFileSubentryCacheValidityMS", "0"); + _Config_configMapRedefine(this, "tuneENOENTCacheValidityMS", "0"); + _Config_configMapRedefine(this, "tunePathBufSize", "4096"); + _Config_configMapRedefine(this, "tunePathBufNum", "8"); + _Config_configMapRedefine(this, "tuneMsgBufSize", "65536"); + _Config_configMapRedefine(this, "tuneMsgBufNum", "0"); + _Config_configMapRedefine(this, "tuneRemoteFSync", "true"); + _Config_configMapRedefine(this, "tuneUseGlobalFileLocks", "false"); + _Config_configMapRedefine(this, "tuneRefreshOnGetAttr", "false"); + _Config_configMapRedefine(this, "tuneInodeBlockBits", "19"); + _Config_configMapRedefine(this, "tuneEarlyCloseResponse", "false"); + _Config_configMapRedefine(this, "tuneUseGlobalAppendLocks", "false"); + _Config_configMapRedefine(this, "tuneUseBufferedAppend", "true"); + _Config_configMapRedefine(this, "tuneStatFsCacheSecs", "10"); + _Config_configMapRedefine(this, "tuneCoherentBuffers", "true"); + + _Config_configMapRedefine(this, "sysMgmtdHost", ""); + _Config_configMapRedefine(this, "sysInodeIDStyle", INODEIDSTYLE_DEFAULT); + _Config_configMapRedefine(this, "sysCacheInvalidationVersion", "true"); + _Config_configMapRedefine(this, "sysCreateHardlinksAsSymlinks", "false"); + _Config_configMapRedefine(this, "sysMountSanityCheckMS", "11000"); + _Config_configMapRedefine(this, "sysSyncOnClose", "false"); + _Config_configMapRedefine(this, "sysSessionCheckOnClose", "false"); + _Config_configMapRedefine(this, "sysSessionChecksEnabled", "true"); + + _Config_configMapRedefine(this, "sysUpdateTargetStatesSecs", "30"); + _Config_configMapRedefine(this, "sysTargetOfflineTimeoutSecs", "900"); + // Note: The default here is intentionally set to double the value from the server config. + // This ensures that the servers push their state twice during one Mgmtd InternodeSyncer run, + // but the client only needs to fetch the states once during that period. + + _Config_configMapRedefine(this, "sysXAttrsEnabled", "false"); + _Config_configMapRedefine(this, "sysXAttrsCheckCapabilities", "never"); + _Config_configMapRedefine(this, "sysACLsEnabled", "false"); + _Config_configMapRedefine(this, "sysBypassFileAccessCheckOnMeta", "false"); + + _Config_configMapRedefine(this, "quotaEnabled", "false"); + _Config_configMapRedefine(this, "sysFileEventLogMask", EVENTLOGMASK_NONE); + _Config_configMapRedefine(this, "sysRenameEbusyAsXdev", "false"); + + _Config_configMapRedefine(this, "remapConnectionFailureStatus", "0"); +} + +bool _Config_applyConfigMap(Config* this) +{ + // IMPORTANT: Don't forget to add new values also to the fsck ignored config values! + + // Deprecated separate port settings. These are post processed below and merged into the new + // combined settings. + int connClientPortUDP = -1; + int connMgmtdPortUDP = -1; + int connMgmtdPortTCP = -1; + + // Scan the whole config map + StrCpyMapIter iter = StrCpyMap_begin(&this->configMap); + while(!StrCpyMapIter_end(&iter) ) + { + char* keyStr = StrCpyMapIter_key(&iter); + char* valueStr = StrCpyMapIter_value(&iter); + + if(!strcmp(keyStr, "cfgFile") ) + { + SAFE_KFREE(this->cfgFile); + this->cfgFile = StringTk_strDup(valueStr ); + } + else + if(!strcmp(keyStr, "sysMgmtdHost") ) + { + SAFE_KFREE(this->sysMgmtdHost); + this->sysMgmtdHost = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "logLevel") ) + this->logLevel = StringTk_strToInt(valueStr); + else + IGNORE_CONFIG_VALUE("logNoDate") + if(!strcmp(keyStr, "logClientID") ) + this->logClientID = StringTk_strToBool(valueStr); + else + IGNORE_CONFIG_VALUE("logStdFile") + IGNORE_CONFIG_VALUE("logNumLines") + IGNORE_CONFIG_VALUE("logNumRotatedFiles") + if(!strcmp(keyStr, "connPortShift") ) + this->connPortShift = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "connClientPort") ) + { + if (!assignKeyIfNotZero(keyStr, valueStr, &this->connClientPort)) + goto bad_config_elem; + } + else + if(!strcmp(keyStr, "connMgmtdPort") ) + { + if (!assignKeyIfNotZero(keyStr, valueStr, &this->connMgmtdPort)) + goto bad_config_elem; + } + else + if(!strcmp(keyStr, "connClientPortUDP") ) + { + if (!assignKeyIfNotZero(keyStr, valueStr, &connClientPortUDP)) + goto bad_config_elem; + } + else + if(!strcmp(keyStr, "connMgmtdPortTCP") ) + { + if (!assignKeyIfNotZero(keyStr, valueStr, &connMgmtdPortTCP)) + goto bad_config_elem; + } + else + if(!strcmp(keyStr, "connMgmtdPortUDP") ) + { + if (!assignKeyIfNotZero(keyStr, valueStr, &connMgmtdPortUDP)) + goto bad_config_elem; + } + else + if(!strcmp(keyStr, "connUseRDMA") ) + this->connUseRDMA = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "connTCPFallbackEnabled") ) + this->connTCPFallbackEnabled = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "connRDMATypeOfService") ) + this->connRDMATypeOfService = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "connMaxInternodeNum") ) + this->connMaxInternodeNum = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "connInterfacesFile") ) + { + SAFE_KFREE(this->connInterfacesFile); + this->connInterfacesFile = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "connInterfacesList") ) + { + SAFE_KFREE(this->connInterfacesList); + this->connInterfacesList = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "connRDMAInterfacesFile") ) + { + SAFE_KFREE(this->connRDMAInterfacesFile); + this->connRDMAInterfacesFile = StringTk_strDup(valueStr); + } + else + IGNORE_CONFIG_VALUE("connNonPrimaryExpiration") + if(!strcmp(keyStr, "connFallbackExpirationSecs") ) + this->connFallbackExpirationSecs = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "connCommRetrySecs") ) + this->connCommRetrySecs = StringTk_strToUInt(valueStr); + else + IGNORE_CONFIG_VALUE("connNumCommRetries") // auto-generated based on connCommRetrySecs + if(!strcmp(keyStr, "connUnmountRetries") ) + this->connUnmountRetries = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "connTCPRcvBufSize") ) + this->connTCPRcvBufSize = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "connUDPRcvBufSize") ) + this->connUDPRcvBufSize = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "connRDMABufSize") ) + this->connRDMABufSize = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "connRDMAFragmentSize") ) + { + if (!strcmp(valueStr, CONFIG_CONN_RDMA_PAGE_STR)) + this->connRDMAFragmentSize = PAGE_SIZE; + else if (!strcmp(valueStr, CONFIG_CONN_RDMA_NONE_STR)) + this->connRDMAFragmentSize = 0; + else + this->connRDMAFragmentSize = StringTk_strToUInt(valueStr); + } + else + if(!strcmp(keyStr, "connRDMABufNum") ) + this->connRDMABufNum = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "connRDMAMetaBufSize") ) + { + if (!strcmp(valueStr, CONFIG_CONN_RDMA_DEFAULT_STR)) + this->connRDMAMetaBufSize = CONFIG_CONN_RDMA_DEFAULT; + else + this->connRDMAMetaBufSize = StringTk_strToInt(valueStr); + } + else + if(!strcmp(keyStr, "connRDMAMetaFragmentSize") ) + { + if (!strcmp(valueStr, CONFIG_CONN_RDMA_PAGE_STR)) + this->connRDMAMetaFragmentSize = PAGE_SIZE; + else if (!strcmp(valueStr, CONFIG_CONN_RDMA_NONE_STR)) + this->connRDMAMetaFragmentSize = 0; + else if (!strcmp(valueStr, CONFIG_CONN_RDMA_DEFAULT_STR)) + this->connRDMAMetaFragmentSize = CONFIG_CONN_RDMA_DEFAULT; + else + this->connRDMAMetaFragmentSize = StringTk_strToInt(valueStr); + } + else + if(!strcmp(keyStr, "connRDMAMetaBufNum") ) + { + if (!strcmp(valueStr, CONFIG_CONN_RDMA_DEFAULT_STR)) + this->connRDMAMetaBufNum = CONFIG_CONN_RDMA_DEFAULT; + else + this->connRDMAMetaBufNum = StringTk_strToUInt(valueStr); + } + else + if(!strcmp(keyStr, "connRDMAKeyType") ) + { + SAFE_KFREE(this->connRDMAKeyType); + this->connRDMAKeyType = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "connNetFilterFile") ) + { + SAFE_KFREE(this->connNetFilterFile); + this->connNetFilterFile = StringTk_strDup(valueStr); + } + else + if (!strcmp(keyStr, "connMaxConcurrentAttempts")) + { + this->connMaxConcurrentAttempts = StringTk_strToUInt(valueStr); + } + else + if(!strcmp(keyStr, "connAuthFile") ) + { + SAFE_KFREE(this->connAuthFile); + this->connAuthFile = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "connDisableAuthentication") ) + this->connDisableAuthentication = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "connTcpOnlyFilterFile") ) + { + SAFE_KFREE(this->connTcpOnlyFilterFile); + this->connTcpOnlyFilterFile = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "connMessagingTimeouts")) + { + int cfgValCount = 3; // count value in config file in order of long, medium and short + + StrCpyList connMsgTimeoutList; + StrCpyListIter iter; + + SAFE_KFREE(this->connMessagingTimeouts); + this->connMessagingTimeouts = StringTk_strDup(valueStr); + + StrCpyList_init(&connMsgTimeoutList); + StringTk_explode(valueStr, ',', &connMsgTimeoutList); + + StrCpyListIter_init(&iter, &connMsgTimeoutList); + + if (StrCpyList_length(&connMsgTimeoutList) == cfgValCount) + { + this->connMsgLongTimeout = StringTk_strToInt(StrCpyListIter_value(&iter)) > 0 ? + StringTk_strToInt(StrCpyListIter_value(&iter)) : CONN_LONG_TIMEOUT; + StrCpyListIter_next(&iter); + + this->connMsgMediumTimeout = StringTk_strToInt(StrCpyListIter_value(&iter)) > 0 ? + StringTk_strToInt(StrCpyListIter_value(&iter)) : CONN_MEDIUM_TIMEOUT; + StrCpyListIter_next(&iter); + + this->connMsgShortTimeout = StringTk_strToInt(StrCpyListIter_value(&iter)) > 0 ? + StringTk_strToInt(StrCpyListIter_value(&iter)) : CONN_SHORT_TIMEOUT; + } + else + { + StrCpyList_uninit(&connMsgTimeoutList); + goto bad_config_elem; + } + StrCpyList_uninit(&connMsgTimeoutList); + } + else + if(!strcmp(keyStr, "connRDMATimeouts")) + { + StrCpyList connRDMATimeoutList; + int* cfgVals[] = { + &this->connRDMATimeoutConnect, + &this->connRDMATimeoutCompletion, + &this->connRDMATimeoutFlowSend, + &this->connRDMATimeoutFlowRecv, + &this->connRDMATimeoutPoll + }; + bool badVals = false; + + SAFE_KFREE(this->connRDMATimeouts); + this->connRDMATimeouts = StringTk_strDup(valueStr); + + StrCpyList_init(&connRDMATimeoutList); + StringTk_explode(valueStr, ',', &connRDMATimeoutList); + + if (StrCpyList_length(&connRDMATimeoutList) == sizeof(cfgVals) / sizeof(int*)) + { + StrCpyListIter iter; + int idx; + + StrCpyListIter_init(&iter, &connRDMATimeoutList); + for (idx = 0; !StrCpyListIter_end(&iter); ++idx, StrCpyListIter_next(&iter)) + { + *cfgVals[idx] = StringTk_strToInt(StrCpyListIter_value(&iter)); + } + } + else + { + badVals = true; + } + + StrCpyList_uninit(&connRDMATimeoutList); + if (badVals) + goto bad_config_elem; + } + else + IGNORE_CONFIG_VALUE("debugFindOtherNodes") + IGNORE_CONFIG_VALUE("tuneNumWorkers") + IGNORE_CONFIG_VALUE("tuneNumRetryWorkers") + if(!strcmp(keyStr, "tunePreferredMetaFile") ) + { + SAFE_KFREE(this->tunePreferredMetaFile); + this->tunePreferredMetaFile = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "tunePreferredStorageFile") ) + { + SAFE_KFREE(this->tunePreferredStorageFile); + this->tunePreferredStorageFile = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "tuneFileCacheType") ) + { + SAFE_KFREE(this->tuneFileCacheType); + this->tuneFileCacheType = StringTk_strDup(valueStr); + } + else + IGNORE_CONFIG_VALUE("tunePagedIOBufSize") + IGNORE_CONFIG_VALUE("tunePagedIOBufNum") + if(!strcmp(keyStr, "tuneFileCacheBufSize") ) + this->tuneFileCacheBufSize = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "tuneFileCacheBufNum") ) + this->tuneFileCacheBufNum = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "tunePageCacheValidityMS") ) + this->tunePageCacheValidityMS= StringTk_strToUInt(valueStr); + else + IGNORE_CONFIG_VALUE("tuneAttribCacheValidityMS") + if(!strcmp(keyStr, "tuneDirSubentryCacheValidityMS") ) + this->tuneDirSubentryCacheValidityMS = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "tuneFileSubentryCacheValidityMS") ) + this->tuneFileSubentryCacheValidityMS = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "tuneENOENTCacheValidityMS") ) + this->tuneENOENTCacheValidityMS = StringTk_strToUInt(valueStr); + else + IGNORE_CONFIG_VALUE("tuneMaxWriteWorks") + IGNORE_CONFIG_VALUE("tuneMaxReadWorks") + IGNORE_CONFIG_VALUE("tuneAllowMultiSetWrite") + IGNORE_CONFIG_VALUE("tuneAllowMultiSetRead") + if(!strcmp(keyStr, "tunePathBufSize") ) + this->tunePathBufSize = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "tunePathBufNum") ) + this->tunePathBufNum = StringTk_strToInt(valueStr); + else + IGNORE_CONFIG_VALUE("tuneMaxReadWriteNum") + IGNORE_CONFIG_VALUE("tuneMaxReadWriteNodesNum") + if(!strcmp(keyStr, "tuneMsgBufSize") ) + this->tuneMsgBufSize = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "tuneMsgBufNum") ) + this->tuneMsgBufNum = StringTk_strToInt(valueStr); + else + if(!strcmp(keyStr, "tuneRemoteFSync") ) + this->tuneRemoteFSync = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneUseGlobalFileLocks") ) + this->tuneUseGlobalFileLocks = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneRefreshOnGetAttr") ) + this->tuneRefreshOnGetAttr = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneInodeBlockBits") ) + this->tuneInodeBlockBits = StringTk_strToUInt(valueStr); + else + IGNORE_CONFIG_VALUE("tuneInodeBlockSize") // auto-generated based on tuneInodeBlockBits + IGNORE_CONFIG_VALUE("tuneMaxClientMirrorSize") // was removed, kept here for compat + if(!strcmp(keyStr, "tuneEarlyCloseResponse") ) + this->tuneEarlyCloseResponse = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneUseGlobalAppendLocks") ) + this->tuneUseGlobalAppendLocks = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneUseBufferedAppend") ) + this->tuneUseBufferedAppend = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "tuneStatFsCacheSecs") ) + this->tuneStatFsCacheSecs = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "tuneCoherentBuffers") ) + this->tuneCoherentBuffers = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysInodeIDStyle") ) + { + SAFE_KFREE(this->sysInodeIDStyle); + this->sysInodeIDStyle = StringTk_strDup(valueStr); + } + else + if(!strcmp(keyStr, "sysCacheInvalidationVersion") ) + this->sysCacheInvalidationVersion = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysCreateHardlinksAsSymlinks") ) + this->sysCreateHardlinksAsSymlinks = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysMountSanityCheckMS") ) + this->sysMountSanityCheckMS = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "sysSyncOnClose") ) + this->sysSyncOnClose = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysSessionCheckOnClose") ) + this->sysSessionCheckOnClose = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysSessionChecksEnabled") ) + this->sysSessionChecksEnabled = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysUpdateTargetStatesSecs") ) + this->sysUpdateTargetStatesSecs = StringTk_strToUInt(valueStr); + else + if(!strcmp(keyStr, "sysTargetOfflineTimeoutSecs") ) + { + this->sysTargetOfflineTimeoutSecs = StringTk_strToUInt(valueStr); + if (this->sysTargetOfflineTimeoutSecs < 30) + { + printk_fhgfs(KERN_WARNING, "Invalid argument for sysTargetOfflineTimeoutSecs: %s " + "(must be at least 30)\n", valueStr); + return false; + } + } + else + if(!strcmp(keyStr, "sysXAttrsEnabled") ) + this->sysXAttrsEnabled = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysXAttrsCheckCapabilities") ) + { + if (!strcmp(valueStr, CHECKCAPABILITIES_ALWAYS_STR)) + this->sysXAttrsCheckCapabilities = CHECKCAPABILITIES_Always; + else if (!strcmp(valueStr, CHECKCAPABILITIES_CACHE_STR)) + this->sysXAttrsCheckCapabilities = CHECKCAPABILITIES_Cache; + else if (!strcmp(valueStr, CHECKCAPABILITIES_NEVER_STR)) + this->sysXAttrsCheckCapabilities = CHECKCAPABILITIES_Never; + } + else + if(!strcmp(keyStr, "sysBypassFileAccessCheckOnMeta")) + this->sysBypassFileAccessCheckOnMeta = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "sysACLsEnabled") ) + this->sysACLsEnabled = StringTk_strToBool(valueStr); + else + if (!strcmp(keyStr, "sysRenameEbusyAsXdev")) + this->sysRenameEbusyAsXdev = StringTk_strToBool(valueStr); + else + if(!strcmp(keyStr, "quotaEnabled") ) + this->quotaEnabled = StringTk_strToBool(valueStr); + else if (!strcmp(keyStr, "sysFileEventLogMask")) + { + if (!strcmp(valueStr, EVENTLOGMASK_NONE)) + this->eventLogMask = EventLogMask_NONE; + else + { + StrCpyList parts; + StrCpyListIter it; + + StrCpyList_init(&parts); + StringTk_explode(valueStr, ',', &parts); + + this->eventLogMask = 0; + + StrCpyListIter_init(&it, &parts); + while (!StrCpyListIter_end(&it)) + { + if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_FLUSH)) + this->eventLogMask |= EventLogMask_FLUSH; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_TRUNC)) + this->eventLogMask |= EventLogMask_TRUNC; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_SETATTR)) + this->eventLogMask |= EventLogMask_SETATTR; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_CLOSE)) + this->eventLogMask |= EventLogMask_CLOSE; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_LINK_OP)) + this->eventLogMask |= EventLogMask_LINK_OP; + else if ((!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_READ)) || + ((!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_OPEN_READ)))) + this->eventLogMask |= EventLogMask_OPEN_READ; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_OPEN_WRITE)) + this->eventLogMask |= EventLogMask_OPEN_WRITE; + else if (!strcmp(StrCpyListIter_value(&it), EVENTLOGMASK_OPEN_READ_WRITE)) + this->eventLogMask |= EventLogMask_OPEN_READ_WRITE; + else + { + StrCpyList_uninit(&parts); + goto bad_config_elem; + } + + StrCpyListIter_next(&it); + } + + StrCpyList_uninit(&parts); + } + } + else if(!strcmp(keyStr, "remapConnectionFailureStatus")) + this->remapConnectionFailureStatus = StringTk_strToUInt(valueStr); + else if(!strcmp(keyStr, "logType")) + printk_fhgfs(KERN_INFO, "Ignoring obsolete config argument 'logType'\n"); + else if(!strcmp(keyStr, "logHelperdIP")) + printk_fhgfs(KERN_INFO, "Ignoring obsolete config argument 'logHelperdIP'\n"); + else if(!strcmp(keyStr, "connHelperdPortTCP")) + printk_fhgfs(KERN_INFO, "Ignoring obsolete config argument 'connHelperdPortTCP'\n"); + else + { + StrCpyMapIter_next(&iter); + continue; + } + + iter = _Config_eraseFromConfigMap(this, &iter); + continue; + +bad_config_elem: + printk_fhgfs(KERN_WARNING, "The config argument '%s' is invalid\n", keyStr ); + return false; + } // end of while loop + + if(this->connClientPort == -1) { + if(connClientPortUDP != -1) { + this->connClientPort = connClientPortUDP; + printk_fhgfs(KERN_INFO, "Using deprecated config argument 'connClientPortUDP'\n"); + } else { + this->connClientPort = 8004; + } + } else if(connClientPortUDP != -1) { + printk_fhgfs(KERN_WARNING, "Deprecated config argument 'connClientPortUDP' set along with the new \ +'connClientPort' setting. Please use only the new setting.\n"); + return false; + } + + if(this->connMgmtdPort == -1) { + if(connMgmtdPortUDP != -1 && connMgmtdPortTCP != -1 && connMgmtdPortTCP != connMgmtdPortUDP) { + printk_fhgfs(KERN_WARNING, "Deprecated config arguments 'connMgmtdPortUDP' and \ +'connMgmtdPortTCP' set to different values, which is no longer allowed. Please use the new 'connMgmtdPort' \ +setting instead.\n"); + return false; + } + + if(connMgmtdPortUDP != -1) { + printk_fhgfs(KERN_INFO, "Using deprecated config argument 'connMgmtdPortUDP'\n"); + this->connMgmtdPort = connMgmtdPortUDP; + } + + if(connMgmtdPortTCP != -1) { + printk_fhgfs(KERN_INFO, "Using deprecated config argument 'connMgmtdPortTCP'\n"); + this->connMgmtdPort = connMgmtdPortTCP; + } + + if(this->connMgmtdPort == -1) { + this->connMgmtdPort = 8008; + } + } else if(connMgmtdPortUDP != -1 || connMgmtdPortTCP != -1) { + printk_fhgfs(KERN_WARNING, "Deprecated config argument 'connMgmtdPortUDP/TCP' set along with the new \ +'connMgmtdPort' setting. Please use only the new setting.\n"); + return false; + } + + return true; +} + +void _Config_configMapRedefine(Config* this, char* keyStr, const char* valueStr) +{ + StrCpyMapIter iter; + + iter = StrCpyMap_find(&this->configMap, keyStr); + + if(!StrCpyMapIter_end(&iter) ) + StrCpyMap_erase(&this->configMap, keyStr); + + + StrCpyMap_insert(&this->configMap, keyStr, valueStr); +} + +void __Config_addLineToConfigMap(Config* this, char* line) +{ + int divPos; + char* divPosStr; + char* param; + char* value; + char* trimParam; + char* trimValue; + + divPosStr = strchr(line, '='); + + if(!divPosStr) + { + char* trimCopy = StringTk_trimCopy(line); + _Config_configMapRedefine(this, trimCopy, ""); + kfree(trimCopy); + + return; + } + + divPos = divPosStr - line; + + param = StringTk_subStr(line, divPos); + value = StringTk_subStr(&divPosStr[1], strlen(&divPosStr[1]) ); + + trimParam = StringTk_trimCopy(param); + trimValue = StringTk_trimCopy(value); + + _Config_configMapRedefine(this, trimParam, trimValue); + + kfree(param); + kfree(value); + + kfree(trimParam); + kfree(trimValue); +} + +void __Config_loadFromMountConfig(Config* this, MountConfig* mountConfig) +{ + // string args + + if(mountConfig->cfgFile) + _Config_configMapRedefine(this, "cfgFile", mountConfig->cfgFile); + + if(mountConfig->logStdFile) + _Config_configMapRedefine(this, "logStdFile", mountConfig->logStdFile); + + if(mountConfig->sysMgmtdHost) + _Config_configMapRedefine(this, "sysMgmtdHost", mountConfig->sysMgmtdHost); + + if(mountConfig->tunePreferredMetaFile) + _Config_configMapRedefine(this, "tunePreferredMetaFile", mountConfig->tunePreferredMetaFile); + + if(mountConfig->tunePreferredStorageFile) + _Config_configMapRedefine(this, "tunePreferredStorageFile", + mountConfig->tunePreferredStorageFile); + + if(mountConfig->connInterfacesList) + { + char* delimiter = mountConfig->connInterfacesList; + + for(size_t listLength = strlen(mountConfig->connInterfacesList); + listLength ; ++delimiter,--listLength) + { + if(*delimiter == ' ') + *delimiter = ','; + } + _Config_configMapRedefine(this, "connInterfacesList", mountConfig->connInterfacesList); + } + + if(mountConfig->connAuthFile) + _Config_configMapRedefine(this, "connAuthFile", + mountConfig->connAuthFile); + + if(mountConfig->connDisableAuthentication) + _Config_configMapRedefine(this, "connDisableAuthentication", + mountConfig->connDisableAuthentication); + + // integer args + + if(mountConfig->logLevelDefined) + { + char* valueStr = StringTk_intToStr(mountConfig->logLevel); + _Config_configMapRedefine(this, "logLevel", valueStr); + kfree(valueStr); + } + + if(mountConfig->connPortShiftDefined) + { + char* valueStr = StringTk_uintToStr(mountConfig->connPortShift); + _Config_configMapRedefine(this, "connPortShift", valueStr); + kfree(valueStr); + } + + if(mountConfig->connMgmtdPortDefined) + { + char* valueStr = StringTk_uintToStr(mountConfig->connMgmtdPort); + _Config_configMapRedefine(this, "connMgmtdPort", valueStr); + kfree(valueStr); + } + + if(mountConfig->sysMountSanityCheckMSDefined) + { + char* valueStr = StringTk_uintToStr(mountConfig->sysMountSanityCheckMS); + _Config_configMapRedefine(this, "sysMountSanityCheckMS", valueStr); + kfree(valueStr); + } + +} + +bool __Config_loadFromFile(struct Config* this, const char* filename) +{ + bool retVal = true; + + struct file* cfgFile; + char* line; + char* trimLine; + int currentLineNum; // 1-based (just for error logging) + + //printk_fhgfs_debug(KERN_INFO, "Attempting to read config file: '%s'\n", filename); + cfgFile = filp_open(filename, (O_RDONLY), 0); + if(IS_ERR(cfgFile) ) + { + printk_fhgfs(KERN_WARNING, "Failed to open config file: '%s'\n", filename); + return false; + } + + line = os_kmalloc(CONFIG_FILE_MAX_LINE_LENGTH); + + + for(currentLineNum=1; ; currentLineNum++) + { + bool endOfFile; + bool readRes; + + readRes = __Config_readLineFromFile(cfgFile, line, CONFIG_FILE_MAX_LINE_LENGTH, &endOfFile); + + // stop on end of file + if(endOfFile) + break; + + if(!readRes) + { // error occurred + printk_fhgfs(KERN_WARNING, "Error occurred while reading the config file " + "(line: %d, file: '%s')\n", currentLineNum, filename); + + retVal = false; + + break; + } + + + // trim the line and add it to the config, ignore comment lines + trimLine = StringTk_trimCopy(line); + if(strlen(trimLine) && (trimLine[0] != CONFIG_FILE_COMMENT_CHAR) ) + __Config_addLineToConfigMap(this, trimLine); + + kfree(trimLine); + } + + // clean up + kfree(line); + + WITH_PROCESS_CONTEXT { + filp_close(cfgFile, NULL); + } + + return retVal; +} + +/** + * Reads each line of a file into a separate string and appends it to outList. + * + * All strings are trimmed and empty lines are not added to the list. + */ +bool Config_loadStringListFile(const char* filename, StrCpyList* outList) +{ + bool retVal = true; + + struct file* listFile; + char* line; + char* trimLine; + + //printk_fhgfs(KERN_INFO, "Attempting to read configured list file: '%s'\n", filename); + listFile = filp_open(filename, (O_RDONLY), 0); + if(IS_ERR(listFile) ) + { + printk_fhgfs(KERN_WARNING, "Failed to open (config) list file: '%s'\n", filename); + return false; + } + + line = os_kmalloc(CONFIG_FILE_MAX_LINE_LENGTH); + + + for( ; ; ) + { + bool endOfFile; + bool readRes; + + readRes = __Config_readLineFromFile(listFile, line, CONFIG_FILE_MAX_LINE_LENGTH, + &endOfFile); + + // stop on end of file + if(endOfFile) + break; + + if(!readRes) + { // error occurred + printk_fhgfs(KERN_WARNING, "Error occurred while reading a (config) list file: " + "'%s'\n", filename); + + retVal = false; + + break; + } + + // trim the line and add it to the nodes list, ignore comment lines + trimLine = StringTk_trimCopy(line); + if(strlen(trimLine) && (trimLine[0] != CONFIG_FILE_COMMENT_CHAR) ) + StrCpyList_append(outList, trimLine); + + kfree(trimLine); + + } + + // clean up + kfree(line); + + WITH_PROCESS_CONTEXT { + filp_close(listFile, NULL); + } + + return retVal; +} + +/** + * Wrapper for loadStringListFile() to read file into a UInt16List. + */ +bool Config_loadUInt16ListFile(struct Config* this, const char* filename, UInt16List* outList) +{ + StrCpyList strList; + StrCpyListIter iter; + bool loadRes; + + StrCpyList_init(&strList); + + loadRes = Config_loadStringListFile(filename, &strList); + if(!loadRes) + goto cleanup_and_exit; + + StrCpyListIter_init(&iter, &strList); + + for( ; !StrCpyListIter_end(&iter); StrCpyListIter_next(&iter) ) + { + char* currentLine = StrCpyListIter_value(&iter); + + UInt16List_append(outList, StringTk_strToUInt(currentLine) ); + } + +cleanup_and_exit: + StrCpyList_uninit(&strList); + + return loadRes; +} + +/** + * Note: Cuts off lines that are longer than the buffer + * + * @param outEndOfFile true if end of file reached + * @return false on file io error + */ +bool __Config_readLineFromFile(struct file* cfgFile, + char* buf, size_t bufLen, bool* outEndOfFile) +{ + size_t numRead; + bool endOfLine; + bool erroroccurred; + + *outEndOfFile = false; + endOfLine = false; + erroroccurred = false; + + + for(numRead = 0; numRead < (bufLen-1); numRead++) + { + char charBuf; + + ssize_t readRes = Config_fs_read(cfgFile, &charBuf, 1, &cfgFile->f_pos); + + if( (readRes > 0) && (charBuf == '\n') ) + { // end of line + endOfLine = true; + break; + } + else + if(readRes > 0) + { // any normal character + buf[numRead] = charBuf; + } + else + if(readRes == 0) + { // end of file + *outEndOfFile = true; + break; + } + else + { // error occurred + printk_fhgfs(KERN_WARNING, "Failed to read from file at line position: %lld\n", + (long long)numRead); + + erroroccurred = true; + break; + } + + } + + buf[numRead] = 0; // add terminating zero + + // read the rest of the line if it is longer than the buffer + while(!endOfLine && !(*outEndOfFile) && !erroroccurred) + { + char charBuf; + + ssize_t readRes = Config_fs_read(cfgFile, &charBuf, 1, &cfgFile->f_pos); + if( (readRes > 0) && (charBuf == '\n') ) + endOfLine = true; + if(readRes == 0) + *outEndOfFile = true; + else + if(readRes < 0) + erroroccurred = true; + } + + + + return !erroroccurred; + +} + +/* + * Set val to defVal if val == condVal. + * return true if val was assigned + */ +static bool __Config_setIfEqualInt(int* val, int condVal, int defVal) +{ + if (*val == condVal) + { + *val = defVal; + return true; + } + return false; +} + +/* + * Ensure val is at least minVal. + */ +static void __Config_ensureMinInt(int* val, int minVal, const char* name) +{ + if (*val < minVal) + { + *val = minVal; + printk_fhgfs(KERN_WARNING, "%s is too low, setting to %d\n", name, minVal); + } +} + +/** + * Init values that are not directly set by the user, but computed from values that the user + * has set. + */ +bool __Config_initImplicitVals(Config* this) +{ + __Config_initConnNumCommRetries(this); + __Config_initTuneFileCacheTypeNum(this); + __Config_initSysInodeIDStyleNum(this); + __Config_initConnRDMAKeyTypeNum(this); + + // tuneMsgBufNum + if(!this->tuneMsgBufNum) + this->tuneMsgBufNum = (num_online_cpus() * 4) + 1; + + // tuneFileCacheBufNum + if(!this->tuneFileCacheBufNum) + this->tuneFileCacheBufNum = MAX(num_online_cpus() * 4, 4); + + // tuneInodeBlockSize + this->tuneInodeBlockSize = (1 << this->tuneInodeBlockBits); + + /* tuneUseBufferedAppend: allow only in combination with global locks + (locally locked code paths are currently not prepared to work with buffering) */ + if(!this->tuneUseGlobalAppendLocks) + this->tuneUseBufferedAppend = false; + + __Config_ensureMinInt(&this->connRDMABufNum, CONFIG_CONN_RDMA_BUFNUM_MIN, "connRDMABufNum"); + __Config_ensureMinInt(&this->connRDMABufSize, PAGE_SIZE, "connRDMABufSize"); + if (!__Config_setIfEqualInt(&this->connRDMAFragmentSize, 0, this->connRDMABufSize)) + __Config_ensureMinInt(&this->connRDMAFragmentSize, PAGE_SIZE, "connRDMAFragmentSize"); + + if (!__Config_setIfEqualInt(&this->connRDMAMetaBufNum, CONFIG_CONN_RDMA_DEFAULT, this->connRDMABufNum)) + __Config_ensureMinInt(&this->connRDMAMetaBufNum, CONFIG_CONN_RDMA_BUFNUM_MIN, "connRDMAMetaBufNum"); + + if (!__Config_setIfEqualInt(&this->connRDMAMetaBufSize, CONFIG_CONN_RDMA_DEFAULT, this->connRDMABufSize)) + __Config_ensureMinInt(&this->connRDMAMetaBufSize, PAGE_SIZE, "connRDMAMetaBufSize"); + + if (!__Config_setIfEqualInt(&this->connRDMAMetaFragmentSize, 0, this->connRDMAMetaBufSize)) + if (!__Config_setIfEqualInt(&this->connRDMAMetaFragmentSize, CONFIG_CONN_RDMA_DEFAULT, this->connRDMAFragmentSize)) + __Config_ensureMinInt(&this->connRDMAMetaFragmentSize, PAGE_SIZE, "connRDMAMetaFragmentSize"); + + if (this->connTCPRcvBufSize == 0) + { + /* 0 indicates that legacy behavior should be preserved. Legacy behavior used RDMA + settings for TCP bufsize. */ + this->connTCPRcvBufSize = this->connRDMABufNum * this->connRDMABufSize; + } + + if (this->connUDPRcvBufSize == 0) + { + /* 0 indicates that legacy behavior should be preserved. Legacy behavior used RDMA + settings for UDP bufsize. */ + this->connUDPRcvBufSize = this->connRDMABufNum * this->connRDMABufSize; + } + + // Automatically enable XAttrs if ACLs have been enabled + if (this->sysACLsEnabled && !this->sysXAttrsEnabled) + { + this->sysXAttrsEnabled = true; + this->sysXAttrsImplicitlyEnabled = true; + } + else + { + this->sysXAttrsImplicitlyEnabled = false; + } + + // connAuthHash + return __Config_initConnAuthHash(this, this->connAuthFile, &this->connAuthHash); +} + +void __Config_initConnNumCommRetries(Config* this) +{ + // note: keep in sync with MessagingTk_getRetryWaitMS() + + unsigned retrySecs = this->connCommRetrySecs; + unsigned retryMS = retrySecs * 1000; + + unsigned numRetries; + + if(retrySecs <= 60) + { // 12 x 5sec retries during 1st min + numRetries = (retryMS + (5000-1) ) / 5000; + } + else + if(retrySecs <= 300) + { // 12 x 20sec retries during 2nd to 5th min + numRetries = (retryMS + (20000-1) - 60000) / 20000; // without 1st min + numRetries += 12; // add 1st min + } + else + { // 60 sec retries after 5th min + numRetries = (retryMS + (60000-1) - (60000*5) ) / 60000; // without first 5 mins + numRetries += 12; // add 1st min + numRetries += 12; // add 2nd to 5th min + } + + this->connNumCommRetries = numRetries; +} + +void __Config_initTuneFileCacheTypeNum(Config* this) +{ + const char* valueStr = this->tuneFileCacheType; + + if(!strcasecmp(valueStr, FILECACHETYPE_NATIVE_STR)) + this->tuneFileCacheTypeNum = FILECACHETYPE_Native; + else + if(!strcasecmp(valueStr, FILECACHETYPE_BUFFERED_STR)) + this->tuneFileCacheTypeNum = FILECACHETYPE_Buffered; + else + if(!strcasecmp(valueStr, FILECACHETYPE_PAGED_STR)) + this->tuneFileCacheTypeNum = FILECACHETYPE_Paged; + else + this->tuneFileCacheTypeNum = FILECACHETYPE_None; +} + +const char* Config_fileCacheTypeNumToStr(FileCacheType cacheType) +{ + switch(cacheType) + { + case FILECACHETYPE_Native: + return FILECACHETYPE_NATIVE_STR; + case FILECACHETYPE_Buffered: + return FILECACHETYPE_BUFFERED_STR; + case FILECACHETYPE_Paged: + return FILECACHETYPE_PAGED_STR; + + default: + return FILECACHETYPE_NONE_STR; + } +} + +void __Config_initSysInodeIDStyleNum(Config* this) +{ + const char* valueStr = this->sysInodeIDStyle; + + if(!strcasecmp(valueStr, INODEIDSTYLE_HASH64HSIEH_STR)) + this->sysInodeIDStyleNum = INODEIDSTYLE_Hash64HSieh; + else + if(!strcasecmp(valueStr, INODEIDSTYLE_HASH32HSIEH_STR)) + this->sysInodeIDStyleNum = INODEIDSTYLE_Hash32Hsieh; + else + if(!strcasecmp(valueStr, INODEIDSTYLE_HASH64MD4_STR)) + this->sysInodeIDStyleNum = INODEIDSTYLE_Hash64MD4; + else + if(!strcasecmp(valueStr, INODEIDSTYLE_HASH32MD4_STR)) + this->sysInodeIDStyleNum = INODEIDSTYLE_Hash32MD4; + else + { // default + printk_fhgfs(KERN_INFO, "Unknown Inode-Hash: %s. Defaulting to hash64md4.\n", valueStr); + this->sysInodeIDStyleNum = INODEIDSTYLE_Default; + } +} + +const char* Config_inodeIDStyleNumToStr(InodeIDStyle inodeIDStyle) +{ + switch(inodeIDStyle) + { + case INODEIDSTYLE_Hash64HSieh: + return INODEIDSTYLE_HASH64HSIEH_STR; + + case INODEIDSTYLE_Hash32MD4: + return INODEIDSTYLE_HASH32MD4_STR; + + case INODEIDSTYLE_Hash64MD4: + return INODEIDSTYLE_HASH64MD4_STR; + + default: + return INODEIDSTYLE_HASH32HSIEH_STR; + } +} + +const char* Config_eventLogMaskToStr(enum EventLogMask mask) +{ +#define ELM_PART_OPEN_READ(Prefix) \ + (mask & EventLogMask_OPEN_READ \ + ? Prefix "," EVENTLOGMASK_OPEN_READ \ + : Prefix) +#define ELM_PART_OPEN_WRITE(Prefix) \ + (mask & EventLogMask_OPEN_WRITE \ + ? ELM_PART_OPEN_READ(Prefix "," EVENTLOGMASK_OPEN_WRITE) \ + : ELM_PART_OPEN_READ(Prefix)) +#define ELM_PART_OPEN_READ_WRITE(Prefix) \ + (mask & EventLogMask_OPEN_READ_WRITE \ + ? ELM_PART_OPEN_WRITE(Prefix "," EVENTLOGMASK_OPEN_READ_WRITE) \ + : ELM_PART_OPEN_WRITE(Prefix)) +#define ELM_PART_FLUSH(Prefix) \ + (mask & EventLogMask_FLUSH \ + ? ELM_PART_OPEN_READ_WRITE(Prefix "," EVENTLOGMASK_FLUSH) \ + : ELM_PART_OPEN_READ_WRITE(Prefix)) +#define ELM_PART_TRUNC(Prefix) \ + (mask & EventLogMask_TRUNC \ + ? ELM_PART_FLUSH(Prefix "," EVENTLOGMASK_TRUNC) \ + : ELM_PART_FLUSH(Prefix)) +#define ELM_PART_SETATTR(Prefix) \ + (mask & EventLogMask_SETATTR \ + ? ELM_PART_TRUNC(Prefix "," EVENTLOGMASK_SETATTR) \ + : ELM_PART_TRUNC(Prefix)) +#define ELM_PART_CLOSE(Prefix) \ + (mask & EventLogMask_CLOSE \ + ? ELM_PART_SETATTR(Prefix "," EVENTLOGMASK_CLOSE) \ + : ELM_PART_SETATTR(Prefix)) +#define ELM_PART_LINK_OP(Prefix) \ + (mask & EventLogMask_LINK_OP \ + ? ELM_PART_CLOSE(Prefix "," EVENTLOGMASK_LINK_OP) \ + : ELM_PART_CLOSE(Prefix)) + // If new event types are added here, the return below must be updated. + if (mask == EventLogMask_NONE) + return EVENTLOGMASK_NONE; + else + return ELM_PART_LINK_OP("") + 1; +} + +void __Config_initConnRDMAKeyTypeNum(Config* this) +{ + const char* valueStr = this->connRDMAKeyType; + + if(!strcasecmp(valueStr, RDMAKEYTYPE_UNSAFE_DMA_STR)) + this->connRDMAKeyTypeNum = RDMAKEYTYPE_UnsafeDMA; + else if(!strcasecmp(valueStr, RDMAKEYTYPE_REGISTER_STR)) + this->connRDMAKeyTypeNum = RDMAKEYTYPE_Register; + else + this->connRDMAKeyTypeNum = RDMAKEYTYPE_UnsafeGlobal; +} + +const char* Config_rdmaKeyTypeNumToStr(RDMAKeyType keyType) +{ + switch(keyType) + { + case RDMAKEYTYPE_Register: + return RDMAKEYTYPE_REGISTER_STR; + case RDMAKEYTYPE_UnsafeDMA: + return RDMAKEYTYPE_UNSAFE_DMA_STR; + default: + return RDMAKEYTYPE_UNSAFE_GLOBAL_STR; + } +} + +const char* Config_checkCapabilitiesTypeToStr(CheckCapabilities checkCapabilities) +{ + switch(checkCapabilities) + { + case CHECKCAPABILITIES_Cache: + return CHECKCAPABILITIES_CACHE_STR; + case CHECKCAPABILITIES_Never: + return CHECKCAPABILITIES_NEVER_STR; + default: + return CHECKCAPABILITIES_ALWAYS_STR; + } +} + +/** + * Generate connection authentication hash based on contents of given authentication file. + * + * @param outConnAuthHash will be set to 0 if file is not defined + * @return true on success or unset file, false on error + */ +bool __Config_initConnAuthHash(Config* this, char* connAuthFile, uint64_t* outConnAuthHash) +{ + struct file* fileHandle; + char* buf; + ssize_t readRes; + + + if (this->connDisableAuthentication) + { + *outConnAuthHash = 0; + return true; // connAuthFile explicitly disabled => no hash to be generated + } + + // Connection authentication not explicitly disabled, so bail if connAuthFile is not configured + if(!connAuthFile || !StringTk_hasLength(connAuthFile)) + { + printk_fhgfs(KERN_WARNING, "No connAuthFile configured. Using BeeGFS without connection authentication is considered insecure and is not recommended. If you really want or need to run BeeGFS without connection authentication, please set connDisableAuthentication to true."); + return false; + } + + + // open file... + + fileHandle = filp_open(connAuthFile, O_RDONLY, 0); + if(IS_ERR(fileHandle) ) + { + printk_fhgfs(KERN_WARNING, "Failed to open auth file: '%s'\n", connAuthFile); + return false; + } + + // read file... + + buf = os_kmalloc(CONFIG_AUTHFILE_READSIZE); + if(unlikely(!buf) ) + { + printk_fhgfs(KERN_WARNING, "Failed to alloc mem for auth file reading: '%s'\n", connAuthFile); + + WITH_PROCESS_CONTEXT { + filp_close(fileHandle, NULL); + } + + return false; + } + + readRes = Config_fs_read(fileHandle, buf, CONFIG_AUTHFILE_READSIZE, &fileHandle->f_pos); + + WITH_PROCESS_CONTEXT { + filp_close(fileHandle, NULL); + } + + + if(readRes < 0) + { + printk_fhgfs(KERN_WARNING, "Unable to read auth file: '%s'\n", connAuthFile); + return false; + } + + // empty authFile is probably unintended, so treat it as error + if(!readRes || (readRes < CONFIG_AUTHFILE_MINSIZE) ) + { + printk_fhgfs(KERN_WARNING, "Auth file is empty or too small: '%s'\n", connAuthFile); + return false; + } + + + // Calculate Hash + if(HashTk_authHash(buf, readRes, outConnAuthHash) != 0) { + return false; + } + + // clean up + kfree(buf); + + + return true; +} + diff --git a/client_module/source/app/config/Config.h b/client_module/source/app/config/Config.h new file mode 100644 index 0000000..51592ce --- /dev/null +++ b/client_module/source/app/config/Config.h @@ -0,0 +1,758 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#include +#include +#include +#include +#include +#include +#include + + +struct Config; +typedef struct Config Config; + +enum FileCacheType; +typedef enum FileCacheType FileCacheType; + +enum InodeIDStyle; +typedef enum InodeIDStyle InodeIDStyle; + +enum RDMAKeyType; +typedef enum RDMAKeyType RDMAKeyType; + +enum CheckCapabilities; +typedef enum CheckCapabilities CheckCapabilities; + +enum EventLogMask +{ + EventLogMask_NONE = 0, + EventLogMask_FLUSH = 1, + EventLogMask_TRUNC = 2, + EventLogMask_SETATTR = 4, + EventLogMask_CLOSE = 8, + EventLogMask_LINK_OP = 16, + EventLogMask_OPEN_READ = 32, + EventLogMask_OPEN_WRITE = 64, + EventLogMask_OPEN_READ_WRITE = 128 +}; + + +extern __must_check bool Config_init(Config* this, MountConfig* mountConfig); +extern Config* Config_construct(MountConfig* mountConfig); +extern void Config_uninit(Config* this); +extern void Config_destruct(Config* this); + +extern bool _Config_initConfig(Config* this, MountConfig* mountConfig); +extern StrCpyMapIter _Config_eraseFromConfigMap(Config* this, StrCpyMapIter* iter); +extern void _Config_loadDefaults(Config* this); +extern bool _Config_applyConfigMap(Config* this); +extern void _Config_configMapRedefine(Config* this, char* keyStr, const char* valueStr); +extern void __Config_addLineToConfigMap(Config* this, char* line); + +extern void __Config_loadFromMountConfig(Config* this, MountConfig* mountConfig); +extern bool __Config_loadFromFile(struct Config* this, const char* filename); +extern bool Config_loadStringListFile(const char* filename, + StrCpyList* outList); +extern bool Config_loadUInt16ListFile(struct Config* this, const char* filename, + UInt16List* outList); + +extern bool __Config_initImplicitVals(Config* this); +extern void __Config_initConnNumCommRetries(Config* this); +extern void __Config_initTuneFileCacheTypeNum(Config* this); +void __Config_initSysInodeIDStyleNum(Config* this); +bool __Config_initConnAuthHash(Config* this, char* connAuthFile, uint64_t* outConnAuthHash); +void __Config_initConnRDMAKeyTypeNum(Config* this); + +// conversion +const char* Config_fileCacheTypeNumToStr(FileCacheType cacheType); +const char* Config_inodeIDStyleNumToStr(InodeIDStyle inodeIDStyle); +const char* Config_eventLogMaskToStr(enum EventLogMask mask); +const char* Config_rdmaKeyTypeNumToStr(RDMAKeyType keyType); +const char* Config_checkCapabilitiesTypeToStr(CheckCapabilities checkCapabilities); + +// getters & setters +static inline char* Config_getCfgFile(Config* this); +static inline int Config_getLogLevel(Config* this); +static inline bool Config_getLogClientID(Config* this); +static inline bool Config_getConnUseRDMA(Config* this); +static inline bool Config_getConnTCPFallbackEnabled(Config* this); +static inline int Config_getConnClientPort(Config* this); +static inline int Config_getConnMgmtdPort(Config* this); +static inline int Config_getConnMgmtdGrpcPort(Config* this); +static inline void Config_setConnMgmtdGrpcPort(Config* this, int port); +static inline unsigned Config_getConnMaxInternodeNum(Config* this); +static inline char* Config_getConnInterfacesFile(Config* this); +static inline char* Config_getConnInterfacesList(Config* this); +static inline char* Config_getConnRDMAInterfacesFile(Config* this); +static inline unsigned Config_getConnFallbackExpirationSecs(Config* this); +static inline unsigned Config_getConnNumCommRetries(Config* this); +static inline unsigned Config_getConnCommRetrySecs(Config* this); +static inline bool Config_getConnUnmountRetries(Config* this); +static inline int Config_getConnTCPRcvBufSize(Config* this); +static inline int Config_getConnUDPRcvBufSize(Config* this); +static inline unsigned Config_getConnRDMABufSize(Config* this); +static inline unsigned Config_getConnRDMAFragmentSize(Config* this); +static inline unsigned Config_getConnRDMABufNum(Config* this); +static inline unsigned Config_getConnRDMAMetaBufSize(Config* this); +static inline unsigned Config_getConnRDMAMetaBufNum(Config* this); +static inline unsigned Config_getConnRDMAMetaFragmentSize(Config* this); +static inline char* Config_getConnRDMAKeyType(Config* this); +static inline RDMAKeyType Config_getConnRDMAKeyTypeNum(Config* this); +static inline int Config_getConnRDMATypeOfService(Config* this); +static inline unsigned Config_getRemapConnectionFailureStatus(Config* this); +static inline void Config_setRemapConnectionFailureStatus(Config* this, unsigned status); +static inline char* Config_getConnNetFilterFile(Config* this); +static inline unsigned Config_getConnMaxConcurrentAttempts(Config* this); +static inline char* Config_getConnAuthFile(Config* this); +static inline bool Config_getConnDisableAuthentication(Config* this); +static inline uint64_t Config_getConnAuthHash(Config* this); +static inline char* Config_getConnTcpOnlyFilterFile(Config* this); +static inline char* Config_getTunePreferredMetaFile(Config* this); +static inline char* Config_getTunePreferredStorageFile(Config* this); +static inline char* Config_getTuneFileCacheType(Config* this); +static inline FileCacheType Config_getTuneFileCacheTypeNum(Config* this); +static inline int Config_getTuneFileCacheBufSize(Config* this); +static inline int Config_getTuneFileCacheBufNum(Config* this); +static inline int Config_getTunePathBufSize(Config* this); +static inline int Config_getTunePathBufNum(Config* this); +static inline int Config_getTuneMsgBufSize(Config* this); +static inline int Config_getTuneMsgBufNum(Config* this); +static inline unsigned Config_getTunePageCacheValidityMS(Config* this); +static inline unsigned Config_getTuneDirSubentryCacheValidityMS(Config* this); +static inline unsigned Config_getTuneFileSubentryCacheValidityMS(Config* this); +static inline unsigned Config_getTuneENOENTCacheValidityMS(Config* this); +static inline bool Config_getTuneRemoteFSync(Config* this); +static inline bool Config_getTuneUseGlobalFileLocks(Config* this); +static inline bool Config_getTuneRefreshOnGetAttr(Config* this); +static inline void Config_setTuneRefreshOnGetAttr(Config* this); +static inline unsigned Config_getTuneInodeBlockBits(Config* this); +static inline unsigned Config_getTuneInodeBlockSize(Config* this); +static inline bool Config_getTuneEarlyCloseResponse(Config* this); +static inline bool Config_getTuneUseGlobalAppendLocks(Config* this); +static inline bool Config_getTuneUseBufferedAppend(Config* this); +static inline unsigned Config_getTuneStatFsCacheSecs(Config* this); +static inline bool Config_getTuneCoherentBuffers(Config* this); + +static inline char* Config_getSysMgmtdHost(Config* this); +static inline char* Config_getSysInodeIDStyle(Config* this); +static inline InodeIDStyle Config_getSysInodeIDStyleNum(Config* this); +static inline bool Config_getSysCacheInvalidationVersion(Config* this); +static inline bool Config_getSysCreateHardlinksAsSymlinks(Config* this); +static inline unsigned Config_getSysMountSanityCheckMS(Config* this); +static inline bool Config_getSysSyncOnClose(Config* this); +static inline bool Config_getSysSessionCheckOnClose(Config* this); +static inline bool Config_getSysSessionChecksEnabled(Config* this); +static inline unsigned Config_getSysUpdateTargetStatesSecs(Config* this); +static inline unsigned Config_getSysTargetOfflineTimeoutSecs(Config* this); +static inline bool Config_getSysXAttrsEnabled(Config* this); +static inline CheckCapabilities Config_getSysXAttrsCheckCapabilities (Config* this); +static inline bool Config_getSysACLsEnabled(Config* this); +static inline bool Config_getSysXAttrsImplicitlyEnabled(Config* this); +static inline bool Config_getSysBypassFileAccessCheckOnMeta(Config* this); + +static inline bool Config_getQuotaEnabled(Config* this); +static inline char* Config_getConnMessagingTimeouts(Config* this); +static inline char* Config_getConnRDMATimeouts(Config* this); +static inline int Config_getConnRDMATimeoutConnect(Config* this); +static inline int Config_getConnRDMATimeoutCompletion(Config* this); +static inline int Config_getConnRDMATimeoutFlowSend(Config* this); +static inline int Config_getConnRDMATimeoutFlowRecv(Config* this); +static inline int Config_getConnRDMATimeoutPoll(Config* this); + + +enum FileCacheType +{ FILECACHETYPE_None = 0, FILECACHETYPE_Buffered = 1, FILECACHETYPE_Paged = 2, + FILECACHETYPE_Native = 3}; + + +#define INODEIDSTYLE_HASH32HSIEH_STR "hash32" +#define INODEIDSTYLE_HASH64HSIEH_STR "hash64" +#define INODEIDSTYLE_HASH32MD4_STR "md4hash32" +#define INODEIDSTYLE_HASH64MD4_STR "md4hash64" +#define INODEIDSTYLE_DEFAULT INODEIDSTYLE_HASH64MD4_STR + +enum InodeIDStyle +{ + INODEIDSTYLE_Hash32Hsieh = 0, // hsieh32 + INODEIDSTYLE_Hash32MD4, // half-md4 + INODEIDSTYLE_Hash64HSieh, // hsieh32 + INODEIDSTYLE_Hash64MD4 // half-md4 +}; +#define INODEIDSTYLE_Default INODEIDSTYLE_Hash64MD4 + +#define RDMAKEYTYPE_UNSAFE_GLOBAL_STR "global" +#define RDMAKEYTYPE_UNSAFE_DMA_STR "dma" +#define RDMAKEYTYPE_REGISTER_STR "register" + +enum RDMAKeyType +{ + RDMAKEYTYPE_UnsafeGlobal = 0, + RDMAKEYTYPE_UnsafeDMA, + RDMAKEYTYPE_Register +}; + +#define CHECKCAPABILITIES_ALWAYS_STR "always" +#define CHECKCAPABILITIES_CACHE_STR "cache" +#define CHECKCAPABILITIES_NEVER_STR "never" + +enum CheckCapabilities +{ + CHECKCAPABILITIES_Always = 0, + CHECKCAPABILITIES_Cache, + CHECKCAPABILITIES_Never +}; + +struct Config +{ + // configurables + char* cfgFile; + + int logLevel; + bool logClientID; + + int connPortShift; // shifts all UDP and TCP ports + int connClientPort; + int connMgmtdPort; + int connMgmtdGrpcPort; // pulled from mgmtd and not meant to be configured by the user + bool connUseRDMA; + bool connTCPFallbackEnabled; + unsigned connMaxInternodeNum; + char* connInterfacesFile; + char* connInterfacesList; + char* connRDMAInterfacesFile; + unsigned connFallbackExpirationSecs; + unsigned connNumCommRetries; // auto-computed from connCommRetrySecs + unsigned connCommRetrySecs; + bool connUnmountRetries; + int connTCPRcvBufSize; + int connUDPRcvBufSize; + int connRDMABufSize; + int connRDMAFragmentSize; + int connRDMABufNum; + int connRDMAMetaBufSize; + int connRDMAMetaFragmentSize; + int connRDMAMetaBufNum; + int connRDMATypeOfService; + char* connRDMAKeyType; + RDMAKeyType connRDMAKeyTypeNum; + char* connNetFilterFile; // allowed IP addresses (all IPs allowed, if empty) + unsigned connMaxConcurrentAttempts; + char* connAuthFile; + bool connDisableAuthentication; + uint64_t connAuthHash; // implicitly set based on hash of connAuthFile contents + char* connTcpOnlyFilterFile; // allow only plain TCP (no RDMA etc) to these IPs + + char* connMessagingTimeouts; + int connMsgLongTimeout; + int connMsgMediumTimeout; + int connMsgShortTimeout; // connection (response) timeouts in ms + // note: be careful here, because servers not + // responding for >30secs under high load is nothing + // unusual, so never use connMsgShortTimeout for + // IO-related operations. + char* connRDMATimeouts; + int connRDMATimeoutConnect; + int connRDMATimeoutCompletion; + int connRDMATimeoutFlowSend; + int connRDMATimeoutFlowRecv; + int connRDMATimeoutPoll; + + char* tunePreferredMetaFile; + char* tunePreferredStorageFile; + char* tuneFileCacheType; + FileCacheType tuneFileCacheTypeNum; // auto-generated based on tuneFileCacheType + int tuneFileCacheBufSize; + int tuneFileCacheBufNum; // 0 means automatic setting + unsigned tunePageCacheValidityMS; + unsigned tuneDirSubentryCacheValidityMS; + unsigned tuneFileSubentryCacheValidityMS; + unsigned tuneENOENTCacheValidityMS; + int tunePathBufSize; + int tunePathBufNum; + int tuneMsgBufSize; + int tuneMsgBufNum; // 0 means automatic setting + bool tuneRemoteFSync; + bool tuneUseGlobalFileLocks; // false means local flock/fcntl locks + bool tuneRefreshOnGetAttr; // false means don't refresh on getattr + unsigned tuneInodeBlockBits; // bitshift for optimal io size seen by stat() (2^n) + unsigned tuneInodeBlockSize; // auto-generated based on tuneInodeBlockBits + bool tuneEarlyCloseResponse; // don't wait for chunk files close result + bool tuneUseGlobalAppendLocks; // false means local append locks + bool tuneUseBufferedAppend; // false disables buffering of append writes + unsigned tuneStatFsCacheSecs; // 0 disables caching of free space info from servers + bool tuneCoherentBuffers; // try to keep buffer cache and page cache coherent + + char* sysMgmtdHost; + char* sysInodeIDStyle; + InodeIDStyle sysInodeIDStyleNum; // auto-generated based on sysInodeIDStyle + bool sysCacheInvalidationVersion; + bool sysCreateHardlinksAsSymlinks; + unsigned sysMountSanityCheckMS; + bool sysSyncOnClose; + bool sysSessionCheckOnClose; + bool sysSessionChecksEnabled; + unsigned sysUpdateTargetStatesSecs; + unsigned sysTargetOfflineTimeoutSecs; + + bool sysXAttrsEnabled; + CheckCapabilities sysXAttrsCheckCapabilities; + bool sysACLsEnabled; + bool sysXAttrsImplicitlyEnabled; // True when XAttrs have not been enabled in the config file + // but have been enabled by __Config_initImplicitVals + // because ACLs are enabled in the config and XAs are needed + // to store the ACLs. + bool sysBypassFileAccessCheckOnMeta; // bypass file access check on meta server + + bool quotaEnabled; + enum EventLogMask eventLogMask; + + /* workaround for rename of closed files on nfs */ + bool sysRenameEbusyAsXdev; + + + // internals + StrCpyMap configMap; + + // testing + unsigned remapConnectionFailureStatus; +}; + +char* Config_getCfgFile(Config* this) +{ + return this->cfgFile; +} + +int Config_getLogLevel(Config* this) +{ + return this->logLevel; +} + +bool Config_getLogClientID(Config* this) +{ + return this->logClientID; +} + +int Config_getConnClientPort(Config* this) +{ + return this->connClientPort ? (this->connClientPort + this->connPortShift) : 0; +} + +int Config_getConnMgmtdPort(Config* this) +{ + return this->connMgmtdPort ? (this->connMgmtdPort + this->connPortShift) : 0; +} + +int Config_getConnMgmtdGrpcPort(Config* this) +{ + // not adding port shift here because connMgmtdGrpcPort is pulled from mgmtd and already shifted + return this->connMgmtdGrpcPort ? this->connMgmtdGrpcPort : 0; +} + +void Config_setConnMgmtdGrpcPort(Config* this, int port) +{ + this->connMgmtdGrpcPort = port; +} + +bool Config_getConnUseRDMA(Config* this) +{ + return this->connUseRDMA; +} + +bool Config_getConnTCPFallbackEnabled(Config* this) +{ + return this->connTCPFallbackEnabled; +} + +unsigned Config_getConnMaxInternodeNum(Config* this) +{ + return this->connMaxInternodeNum; +} + +char* Config_getConnInterfacesFile(Config* this) +{ + return this->connInterfacesFile; +} + +char* Config_getConnInterfacesList(Config* this) +{ + return this->connInterfacesList; +} + +char* Config_getConnRDMAInterfacesFile(Config* this) +{ + return this->connRDMAInterfacesFile; +} + +unsigned Config_getConnFallbackExpirationSecs(Config* this) +{ + return this->connFallbackExpirationSecs; +} + +unsigned Config_getConnNumCommRetries(Config* this) +{ + return this->connNumCommRetries; +} + +unsigned Config_getConnCommRetrySecs(Config* this) +{ + return this->connCommRetrySecs; +} + +bool Config_getConnUnmountRetries(Config* this) +{ + return this->connUnmountRetries; +} + +int Config_getConnTCPRcvBufSize(Config* this) +{ + return this->connTCPRcvBufSize; +} + +int Config_getConnUDPRcvBufSize(Config* this) +{ + return this->connUDPRcvBufSize; +} + +unsigned Config_getConnRDMABufSize(Config* this) +{ + return (unsigned) this->connRDMABufSize; +} + +unsigned Config_getConnRDMAFragmentSize(Config* this) +{ + return (unsigned) this->connRDMAFragmentSize; +} + +unsigned Config_getConnRDMABufNum(Config* this) +{ + return (unsigned) this->connRDMABufNum; +} + +unsigned Config_getConnRDMAMetaBufSize(Config* this) +{ + return (unsigned) this->connRDMAMetaBufSize; +} + +unsigned Config_getConnRDMAMetaFragmentSize(Config* this) +{ + return (unsigned) this->connRDMAMetaFragmentSize; +} + +unsigned Config_getConnRDMAMetaBufNum(Config* this) +{ + return (unsigned) this->connRDMAMetaBufNum; +} + +int Config_getConnRDMATypeOfService(Config* this) +{ + return this->connRDMATypeOfService; +} + +char* Config_getConnRDMAKeyType(Config* this) +{ + return this->connRDMAKeyType; +} + +RDMAKeyType Config_getConnRDMAKeyTypeNum(Config* this) +{ + return this->connRDMAKeyTypeNum; +} + +unsigned Config_getRemapConnectionFailureStatus(Config* this) +{ + return this->remapConnectionFailureStatus; +} + +void Config_setRemapConnectionFailureStatus(Config* this, unsigned status) +{ + this->remapConnectionFailureStatus = status; +} + +char* Config_getConnNetFilterFile(Config* this) +{ + return this->connNetFilterFile; +} + +unsigned Config_getConnMaxConcurrentAttempts(Config* this) +{ + return this->connMaxConcurrentAttempts; +} + +char* Config_getConnAuthFile(Config* this) +{ + return this->connAuthFile; +} + +bool Config_getConnDisableAuthentication(Config* this) +{ + return this->connDisableAuthentication; +} + +uint64_t Config_getConnAuthHash(Config* this) +{ + return this->connAuthHash; +} + +char* Config_getConnTcpOnlyFilterFile(Config* this) +{ + return this->connTcpOnlyFilterFile; +} + +char* Config_getTunePreferredMetaFile(Config* this) +{ + return this->tunePreferredMetaFile; +} + +char* Config_getTunePreferredStorageFile(Config* this) +{ + return this->tunePreferredStorageFile; +} + +char* Config_getTuneFileCacheType(Config* this) +{ + return this->tuneFileCacheType; +} + +FileCacheType Config_getTuneFileCacheTypeNum(Config* this) +{ + return this->tuneFileCacheTypeNum; +} + +int Config_getTuneFileCacheBufSize(Config* this) +{ + return this->tuneFileCacheBufSize; +} + +int Config_getTuneFileCacheBufNum(Config* this) +{ + return this->tuneFileCacheBufNum; +} + +int Config_getTunePathBufSize(Config* this) +{ + return this->tunePathBufSize; +} + +int Config_getTunePathBufNum(Config* this) +{ + return this->tunePathBufNum; +} + +int Config_getTuneMsgBufSize(Config* this) +{ + return this->tuneMsgBufSize; +} + +int Config_getTuneMsgBufNum(Config* this) +{ + return this->tuneMsgBufNum; +} + +unsigned Config_getTunePageCacheValidityMS(Config* this) +{ + return this->tunePageCacheValidityMS; +} + +unsigned Config_getTuneDirSubentryCacheValidityMS(Config* this) +{ + return this->tuneDirSubentryCacheValidityMS; +} + +unsigned Config_getTuneFileSubentryCacheValidityMS(Config* this) +{ + return this->tuneFileSubentryCacheValidityMS; +} + +unsigned Config_getTuneENOENTCacheValidityMS(Config* this) +{ + return this->tuneENOENTCacheValidityMS; +} + +bool Config_getTuneRemoteFSync(Config* this) +{ + return this->tuneRemoteFSync; +} + +bool Config_getTuneUseGlobalFileLocks(Config* this) +{ + return this->tuneUseGlobalFileLocks; +} + +bool Config_getTuneRefreshOnGetAttr(Config* this) +{ + return this->tuneRefreshOnGetAttr; +} + +bool Config_getTuneCoherentBuffers(Config* this) +{ + return this->tuneCoherentBuffers; +} + +/** + * Special function to automatically enable TuneRefreshOnGetAttr, e.g. for NFS exports. + * + * Note: We do not use any locks here assuming the right value will propate to all cores rather + * soon. + */ +void Config_setTuneRefreshOnGetAttr(Config* this) +{ + this->tuneRefreshOnGetAttr = true; + + // do a memory barrier, so that other CPUs get the new value as soon as possible + smp_wmb(); +} + + +unsigned Config_getTuneInodeBlockBits(Config* this) +{ + return this->tuneInodeBlockBits; +} + +unsigned Config_getTuneInodeBlockSize(Config* this) +{ + return this->tuneInodeBlockSize; +} + +bool Config_getTuneEarlyCloseResponse(Config* this) +{ + return this->tuneEarlyCloseResponse; +} + +bool Config_getTuneUseGlobalAppendLocks(Config* this) +{ + return this->tuneUseGlobalAppendLocks; +} + +bool Config_getTuneUseBufferedAppend(Config* this) +{ + return this->tuneUseBufferedAppend; +} + +unsigned Config_getTuneStatFsCacheSecs(Config* this) +{ + return this->tuneStatFsCacheSecs; +} + +char* Config_getSysMgmtdHost(Config* this) +{ + return this->sysMgmtdHost; +} + +char* Config_getSysInodeIDStyle(Config* this) +{ + return this->sysInodeIDStyle; +} + +InodeIDStyle Config_getSysInodeIDStyleNum(Config* this) +{ + return this->sysInodeIDStyleNum; +} + +bool Config_getSysCacheInvalidationVersion(Config* this) +{ + return this->sysCacheInvalidationVersion; +} + +bool Config_getSysCreateHardlinksAsSymlinks(Config* this) +{ + return this->sysCreateHardlinksAsSymlinks; +} + +unsigned Config_getSysMountSanityCheckMS(Config* this) +{ + return this->sysMountSanityCheckMS; +} + +bool Config_getSysSyncOnClose(Config* this) +{ + return this->sysSyncOnClose; +} + +bool Config_getSysSessionCheckOnClose(Config* this) +{ + return this->sysSessionCheckOnClose; +} + +bool Config_getSysSessionChecksEnabled(Config* this) +{ + return this->sysSessionChecksEnabled; +} + +unsigned Config_getSysUpdateTargetStatesSecs(Config* this) +{ + return this->sysUpdateTargetStatesSecs; +} + +unsigned Config_getSysTargetOfflineTimeoutSecs(Config* this) +{ + return this->sysTargetOfflineTimeoutSecs; +} + +bool Config_getSysXAttrsEnabled(Config* this) +{ + return this->sysXAttrsEnabled; +} + +CheckCapabilities Config_getSysXAttrsCheckCapabilities(Config* this) +{ + return this->sysXAttrsCheckCapabilities; +} + +bool Config_getSysACLsEnabled(Config* this) +{ + return this->sysACLsEnabled; +} + +bool Config_getSysXAttrsImplicitlyEnabled(Config* this) +{ + return this->sysXAttrsImplicitlyEnabled; +} + +bool Config_getSysBypassFileAccessCheckOnMeta(Config* this) +{ + return this->sysBypassFileAccessCheckOnMeta; +} + +bool Config_getQuotaEnabled(Config* this) +{ + return this->quotaEnabled; +} + +char* Config_getConnMessagingTimeouts(Config* this) +{ + return this->connMessagingTimeouts; +} + +char* Config_getConnRDMATimeouts(Config* this) +{ + return this->connRDMATimeouts; +} + +int Config_getConnRDMATimeoutConnect(Config* this) +{ + return this->connRDMATimeoutConnect; +} + +int Config_getConnRDMATimeoutCompletion(Config* this) +{ + return this->connRDMATimeoutCompletion; +} + +int Config_getConnRDMATimeoutFlowSend(Config* this) +{ + return this->connRDMATimeoutFlowSend; +} + +int Config_getConnRDMATimeoutFlowRecv(Config* this) +{ + return this->connRDMATimeoutFlowRecv; +} + +int Config_getConnRDMATimeoutPoll(Config* this) +{ + return this->connRDMATimeoutPoll; +} + +#endif /*CONFIG_H_*/ diff --git a/client_module/source/app/config/MountConfig.c b/client_module/source/app/config/MountConfig.c new file mode 100644 index 0000000..5cae58f --- /dev/null +++ b/client_module/source/app/config/MountConfig.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include + +#include + + +enum { + /* Mount options that take string arguments */ + Opt_cfgFile, + Opt_logStdFile, + Opt_sysMgmtdHost, + Opt_tunePreferredMetaFile, + Opt_tunePreferredStorageFile, + + Opt_connInterfacesList, + Opt_connAuthFile, + Opt_connDisableAuthentication, + + /* Mount options that take integer arguments */ + Opt_logLevel, + Opt_connPortShift, + Opt_connMgmtdPort, + Opt_sysMountSanityCheckMS, + + /* Mount options that take no arguments */ + Opt_grpid, + + Opt_err +}; + + +static match_table_t fhgfs_mount_option_tokens = +{ + /* Mount options that take string arguments */ + { Opt_cfgFile, "cfgFile=%s" }, + { Opt_logStdFile, "logStdFile=%s" }, + { Opt_sysMgmtdHost, "sysMgmtdHost=%s" }, + { Opt_tunePreferredMetaFile, "tunePreferredMetaFile=%s" }, + { Opt_tunePreferredStorageFile, "tunePreferredStorageFile=%s" }, + + { Opt_connInterfacesList, "connInterfacesList=%s" }, + { Opt_connAuthFile, "connAuthFile=%s" }, + { Opt_connDisableAuthentication, "connDisableAuthentication=%s" }, + + /* Mount options that take integer arguments */ + { Opt_logLevel, "logLevel=%d" }, + { Opt_connPortShift, "connPortShift=%d" }, + { Opt_connMgmtdPort, "connMgmtdPort=%u" }, + { Opt_sysMountSanityCheckMS, "sysMountSanityCheckMS=%u" }, + + { Opt_grpid, "grpid" }, + + { Opt_err, NULL } +}; + + + +bool MountConfig_parseFromRawOptions(MountConfig* this, char* mountOptions) +{ + char* currentOption; + + if(!mountOptions) + { + printk_fhgfs_debug(KERN_INFO, "Mount options = \n"); + return true; + } + + + printk_fhgfs_debug(KERN_INFO, "Mount options = '%s'\n", mountOptions); + + while( (currentOption = strsep(&mountOptions, ",") ) != NULL) + { + substring_t args[MAX_OPT_ARGS]; + int tokenID; + + if(!*currentOption) + continue; // skip empty string + + tokenID = match_token(currentOption, fhgfs_mount_option_tokens, args); + + switch(tokenID) + { + /* Mount options that take STRING arguments */ + + case Opt_cfgFile: + { + SAFE_KFREE(this->cfgFile); + + this->cfgFile = match_strdup(args);// (string kalloc'ed => needs kfree later) + } break; + + case Opt_logStdFile: + { + SAFE_KFREE(this->logStdFile); + + this->logStdFile = match_strdup(args); // (string kalloc'ed => needs kfree later) + } break; + + case Opt_sysMgmtdHost: + { + SAFE_KFREE(this->sysMgmtdHost); + + this->sysMgmtdHost = match_strdup(args); // (string kalloc'ed => needs kfree later) + } break; + + case Opt_tunePreferredMetaFile: + { + SAFE_KFREE(this->tunePreferredMetaFile); + + this->tunePreferredMetaFile = match_strdup(args); // (string kalloc'ed => needs kfree later) + } break; + + case Opt_tunePreferredStorageFile: + { + SAFE_KFREE(this->tunePreferredStorageFile); + + this->tunePreferredStorageFile = match_strdup(args); // (string kalloc'ed => needs kfree later) + } break; + + case Opt_connInterfacesList: + { + SAFE_KFREE(this->connInterfacesList); + + this->connInterfacesList = match_strdup(args); + } break; + + case Opt_connAuthFile: + { + SAFE_KFREE(this->connAuthFile); + + this->connAuthFile = match_strdup(args); + } break; + + case Opt_connDisableAuthentication: + { + SAFE_KFREE(this->connDisableAuthentication); + + this->connDisableAuthentication = match_strdup(args); + } break; + + /* Mount options that take INTEGER arguments */ + + case Opt_logLevel: + { + if(match_int(args, &this->logLevel) ) + goto err_exit_invalid_option; + + this->logLevelDefined = true; + } break; + + case Opt_connPortShift: + { + if(match_int(args, &this->connPortShift) ) + goto err_exit_invalid_option; + + this->connPortShiftDefined = true; + } break; + + case Opt_connMgmtdPort: + { + if(match_int(args, &this->connMgmtdPort) ) + goto err_exit_invalid_option; + + this->connMgmtdPortDefined = true; + } break; + + case Opt_sysMountSanityCheckMS: + { + if(match_int(args, &this->sysMountSanityCheckMS) ) + goto err_exit_invalid_option; + + this->sysMountSanityCheckMSDefined = true; + } break; + + case Opt_grpid: + this->grpid = true; + break; + + default: + goto err_exit_unknown_option; + } + } + + return true; + + +err_exit_unknown_option: + printk_fhgfs(KERN_WARNING, "Unknown mount option: '%s'\n", currentOption); + return false; + +err_exit_invalid_option: + printk_fhgfs(KERN_WARNING, "Invalid mount option: '%s'\n", currentOption); + return false; +} + +void MountConfig_showOptions(MountConfig* this, struct seq_file* sf) +{ + if (this->cfgFile) + seq_printf(sf, ",cfgFile=%s", this->cfgFile); + + if (this->logStdFile) + seq_printf(sf, ",logStdFile=%s", this->logStdFile); + + if (this->sysMgmtdHost) + seq_printf(sf, ",sysMgmtdHost=%s", this->sysMgmtdHost); + + if (this->tunePreferredMetaFile) + seq_printf(sf, ",tunePreferredMetaFile=%s", this->tunePreferredMetaFile); + + if (this->tunePreferredStorageFile) + seq_printf(sf, ",tunePreferredStorageFile=%s", this->tunePreferredStorageFile); + + if (this->connInterfacesList) + seq_printf(sf, ",connInterfacesList=%s", this->connInterfacesList); + + if (this->connAuthFile) + seq_printf(sf, ",connAuthFile=%s", this->connInterfacesList); + + if (this->connDisableAuthentication) + seq_printf(sf, ",connDisableAuthentication=%s", this->connInterfacesList); + + if (this->logLevelDefined) + seq_printf(sf, ",logLevel=%d", this->logLevel); + + if (this->connPortShiftDefined) + seq_printf(sf, ",connPortShift=%d", this->connPortShift); + + if (this->connMgmtdPortDefined) + seq_printf(sf, ",connMgmtdPort=%u", this->connMgmtdPort); + + if (this->sysMountSanityCheckMSDefined) + seq_printf(sf, ",sysMountSanityCheckMS=%u", this->sysMountSanityCheckMS); + + if (this->grpid) + seq_printf(sf, ",grpid"); +} diff --git a/client_module/source/app/config/MountConfig.h b/client_module/source/app/config/MountConfig.h new file mode 100644 index 0000000..d15789d --- /dev/null +++ b/client_module/source/app/config/MountConfig.h @@ -0,0 +1,80 @@ +#ifndef OPEN_MOUNTCONFIG_H_ +#define OPEN_MOUNTCONFIG_H_ + +#include + +#include + +struct MountConfig; +typedef struct MountConfig MountConfig; + + +static inline void MountConfig_init(MountConfig* this); +static inline MountConfig* MountConfig_construct(void); +static inline void MountConfig_uninit(MountConfig* this); +static inline void MountConfig_destruct(MountConfig* this); + +extern bool MountConfig_parseFromRawOptions(MountConfig* this, char* mountOptions); +extern void MountConfig_showOptions(MountConfig* this, struct seq_file* sf); + + +struct MountConfig +{ + char* cfgFile; + char* logStdFile; + char* sysMgmtdHost; + char* tunePreferredMetaFile; + char* tunePreferredStorageFile; + + bool logLevelDefined; // true if the value has been specified + bool connPortShiftDefined; // true if the value has been specified + bool connMgmtdPortDefined; // true if the value has been specified + bool sysMountSanityCheckMSDefined; // true if the value has been specified + + int logLevel; + unsigned connPortShift; + unsigned connMgmtdPort; + unsigned sysMountSanityCheckMS; + char* connInterfacesList; + char* connAuthFile; + char* connDisableAuthentication; + + bool grpid; +}; + + +void MountConfig_init(MountConfig* this) +{ + memset(this, 0, sizeof(*this) ); +} + +struct MountConfig* MountConfig_construct(void) +{ + struct MountConfig* this = (MountConfig*)os_kmalloc(sizeof(*this) ); + + MountConfig_init(this); + + return this; +} + +void MountConfig_uninit(MountConfig* this) +{ + SAFE_KFREE(this->cfgFile); + SAFE_KFREE(this->logStdFile); + SAFE_KFREE(this->sysMgmtdHost); + SAFE_KFREE(this->tunePreferredMetaFile); + SAFE_KFREE(this->tunePreferredStorageFile); + SAFE_KFREE(this->connInterfacesList); + SAFE_KFREE(this->connAuthFile); + SAFE_KFREE(this->connDisableAuthentication); +} + +void MountConfig_destruct(MountConfig* this) +{ + MountConfig_uninit(this); + + kfree(this); +} + + +#endif /*OPEN_MOUNTCONFIG_H_*/ diff --git a/client_module/source/app/log/Logger.c b/client_module/source/app/log/Logger.c new file mode 100644 index 0000000..7f10ec2 --- /dev/null +++ b/client_module/source/app/log/Logger.c @@ -0,0 +1,331 @@ +#include +#include +#include +#include +#include +#include +#include "Logger.h" + + +#define LOG_TOPIC_GENERAL_STR "general" +#define LOG_TOPIC_CONN_STR "conn" +#define LOG_TOPIC_COMMKIT_STR "commkit" +#define LOG_TOPIC_UNKNOWN_STR "" /* for unknown/invalid log topics */ + + + +void Logger_init(Logger* this, App* app, Config* cfg) +{ + int i; + + this->app = app; + + for(i=0; i < LogTopic_LAST; i++) + this->logLevels[i] = Config_getLogLevel(cfg); + + this->logFormattedBuf = (char*)os_kmalloc(LOGGER_LOGBUF_SIZE); + this->logContextBuf = (char*)os_kmalloc(LOGGER_LOGBUF_SIZE); + + this->clientID = NULL; + + Mutex_init(&this->outputMutex); + + + // Note: The follwing guys exist to avoid deadlocks that would occur when log messages are + // created (by the same thread) while we're already trying to send a log message to the + // helper daemon (e.g. the messages of the NodeConnPool). Such messages will be discarded. + this->currentOutputPID = LOGGER_PID_NOCURRENTOUTPUT; + Mutex_init(&this->multiLockMutex); +} + +Logger* Logger_construct(App* app, Config* cfg) +{ + Logger* this = (Logger*)os_kmalloc(sizeof(Logger) ); + + Logger_init(this, app, cfg); + + return this; +} + +void Logger_uninit(Logger* this) +{ + SAFE_KFREE(this->clientID); + + SAFE_KFREE(this->logContextBuf); + SAFE_KFREE(this->logFormattedBuf); + + Mutex_uninit(&this->multiLockMutex); + Mutex_uninit(&this->outputMutex); +} + +void Logger_destruct(Logger* this) +{ + Logger_uninit(this); + + kfree(this); +} + +/** + * Just print a log message with formatting similar to printk(). + * + * @param level LogLevel_... value + * @param context the context from which this msg was printed (e.g. the calling function). + * @param msg the log message with formatting, e.g. "%s". + */ +void Logger_logFormatted(Logger* this, LogLevel level, const char* context, const char* msgFormat, + ...) +{ + // note: cannot be inlined because of variable arg list + + va_list ap; + + if(level > this->logLevels[LogTopic_GENERAL]) + return; + + va_start(ap, msgFormat); + + __Logger_logTopFormattedGranted(this, LogTopic_GENERAL, level, context, msgFormat, ap); + + va_end(ap); +} + +void Logger_logTopFormatted(Logger* this, LogTopic logTopic, LogLevel level, const char* context, + const char* msgFormat, ...) +{ + // note: cannot be inlined because of variable arg list + + va_list ap; + + if(level > this->logLevels[logTopic]) + return; + + va_start(ap, msgFormat); + + __Logger_logTopFormattedGranted(this, logTopic, level, context, msgFormat, ap); + + va_end(ap); +} + +/** + * Log with EntryID + * + * Note: This takes an EntryInfo read-lock. Must be used only if there is no risk of deadlock. + */ +void Logger_logTopFormattedWithEntryID(struct inode* inode, LogTopic logTopic, LogLevel level, + const char* logContext, const char* msgFormat, ...) +{ + char* newMsg; + App* app = FhgfsOps_getApp(inode->i_sb); + Logger* log = App_getLogger(app); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + const EntryInfo* entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + va_list ap; + + FhgfsInode_entryInfoReadLock(fhgfsInode); // L O C K entryInfo + + va_start(ap, msgFormat); + + newMsg = os_kmalloc(LOGGER_LOGBUF_SIZE); + if (newMsg) + snprintf(newMsg, LOGGER_LOGBUF_SIZE, "entryID: %s %s ", EntryInfo_getEntryID(entryInfo), + msgFormat); + else // malloc failed. Likely an out memory situation, we still try to print msgFormat + newMsg = (char*)msgFormat; + + Logger_logTopFormattedVA(log, logTopic, level, logContext, newMsg, ap); + va_end(ap); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // U N L O C K entryInfo + + if(newMsg != msgFormat) + kfree(newMsg); +} + + + + +/** + * Just print a log message. Similar to Logger_logFormatted(), but take a va_list already + */ +void Logger_logFormattedVA(Logger* this, LogLevel level, const char* context, const char* msgFormat, + va_list ap) +{ + if(level > this->logLevels[LogTopic_GENERAL]) + return; + + __Logger_logTopFormattedGranted(this, LogTopic_GENERAL, level, context, msgFormat, ap); +} + +/** + * Just print a log message. Similar to Logger_logTopFormatted(), but take a va_list already + */ +void Logger_logTopFormattedVA(Logger* this, LogTopic logTopic, LogLevel level, const char* context, + const char* msgFormat, va_list ap) +{ + if(level > this->logLevels[logTopic]) + return; + + __Logger_logTopFormattedGranted(this, logTopic, level, context, msgFormat, ap); +} + +void Logger_logErrFormatted(Logger* this, const char* context, const char* msgFormat, ...) +{ + // note: cannot be inlined because of variable arg list + + va_list ap; + + va_start(ap, msgFormat); + + __Logger_logTopFormattedGranted(this, LogTopic_GENERAL, Log_ERR, context, msgFormat, ap); + + va_end(ap); +} + +void Logger_logTopErrFormatted(Logger* this, LogTopic logTopic, const char* context, + const char* msgFormat, ...) +{ + // note: cannot be inlined because of variable arg list + + va_list ap; + + va_start(ap, msgFormat); + + __Logger_logTopFormattedGranted(this, logTopic, Log_ERR, context, msgFormat, ap); + + va_end(ap); +} + +/** + * Prints a message to the standard log. + * + * @param level log level (Log_... value) + * @param msg the message + */ +void __Logger_logTopFormattedGranted(Logger* this, LogTopic logTopic, LogLevel level, + const char* context, const char* msgFormat, va_list args) +{ + if(__Logger_checkThreadMultiLock(this) ) + { + // this thread is already trying to log a message. trying to lock outputMutex would deadlock. + // => discard this message + + return; + } + + Mutex_lock(&this->outputMutex); + + __Logger_setCurrentOutputPID(this, current->pid); // grab currentOutputPID + + + // evaluate msgFormat + vsnprintf(this->logFormattedBuf, LOGGER_LOGBUF_SIZE, msgFormat, args); + + // extend context + if(this->clientID) + snprintf(this->logContextBuf, LOGGER_LOGBUF_SIZE, "%s: %s", this->clientID, context); + else + snprintf(this->logContextBuf, LOGGER_LOGBUF_SIZE, "%s", context); + + printk_fhgfs(KERN_INFO, "%s: %s\n", this->logContextBuf, this->logFormattedBuf); + + __Logger_setCurrentOutputPID(this, LOGGER_PID_NOCURRENTOUTPUT); // release currentOutputPID + + Mutex_unlock(&this->outputMutex); +} + + +/** + * Note: Call this before locking the outputMutex (because it exists to avoid dead-locking). + * + * @return true if the currentOutputPID is set to the current thread and logging cannot continue; + */ +bool __Logger_checkThreadMultiLock(Logger* this) +{ + bool retVal = false; + + Mutex_lock(&this->multiLockMutex); + + if(this->currentOutputPID == current->pid) + { // we alread own the outputPID (=> we already own the outputMutex) + retVal = true; + } + + Mutex_unlock(&this->multiLockMutex); + + return retVal; +} + +/** + * Note: Call this only after the thread owns the outputMutex to avoid "stealing". + */ +void __Logger_setCurrentOutputPID(Logger* this, pid_t pid) +{ + Mutex_lock(&this->multiLockMutex); + + this->currentOutputPID = pid; + + Mutex_unlock(&this->multiLockMutex); +} + + +/** + * Returns a pointer to the static string representation of a log topic (or "" for unknown/ + * invalid log topic numbers. + */ +const char* Logger_getLogTopicStr(LogTopic logTopic) +{ + switch(logTopic) + { + case LogTopic_GENERAL: + return LOG_TOPIC_GENERAL_STR; + + case LogTopic_CONN: + return LOG_TOPIC_CONN_STR; + + case LogTopic_COMMKIT: + return LOG_TOPIC_COMMKIT_STR; + + default: + return LOG_TOPIC_UNKNOWN_STR; + } +} + +/** + * Returns the log topic number from a string (not case-sensitive). + * + * @return false if string didn't match any known log topic. + */ +bool Logger_getLogTopicFromStr(const char* logTopicStr, LogTopic* outLogTopic) +{ + int i; + + for(i=0; i < LogTopic_LAST; i++) + { + const char* currentLogTopicStr = Logger_getLogTopicStr( (LogTopic)i); + + if(!strcasecmp(logTopicStr, currentLogTopicStr)) + { + *outLogTopic = i; + return true; + } + } + + // (note: we carefully set outLogTopic to "general" to not risk leaving it undefined) + *outLogTopic = LogTopic_GENERAL; + return false; +} + +/** + * Shortcut to retrieve the level of LogTopic_GENERAL in old code. + * New code should use _getLogTopicLevel() instead. + */ +LogLevel Logger_getLogLevel(Logger* this) +{ + return this->logLevels[LogTopic_GENERAL]; +} + +LogLevel Logger_getLogTopicLevel(Logger* this, LogTopic logTopic) +{ + return this->logLevels[logTopic]; +} + diff --git a/client_module/source/app/log/Logger.h b/client_module/source/app/log/Logger.h new file mode 100644 index 0000000..9f7be16 --- /dev/null +++ b/client_module/source/app/log/Logger.h @@ -0,0 +1,244 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define LOGGER_LOGBUF_SIZE 1000 /* max log message length */ +#define LOGGER_PID_NOCURRENTOUTPUT 0 /* pid value if outputMutex not locked */ + + +#ifdef LOG_DEBUG_MESSAGES + + #define LOG_DEBUG(logger, level, contextStr, msgStr) \ + do { Logger_log(logger, level, contextStr, msgStr); } while(0) + + #define LOG_DEBUG_TOP(logger, logTopic, level, contextStr, msgStr) \ + do { Logger_logTop(logger, logTopic, level, contextStr, msgStr); } while(0) + + #define LOG_DEBUG_FORMATTED(logger, level, contextStr, msgStr, ...) \ + do { Logger_logFormatted(logger, level, contextStr, msgStr, ## __VA_ARGS__); } while(0) + + #define LOG_DEBUG_TOP_FORMATTED(logger, logTopic, level, contextStr, msgStr, ...) \ + do { Logger_logTopFormatted(logger, logTopic, level, contextStr, msgStr, ## __VA_ARGS__); } \ + while(0) + +#else + + #define LOG_DEBUG(logger, level, contextStr, msgStr) + #define LOG_DEBUG_TOP(logger, logTopic, level, contextStr, msgStr) + #define LOG_DEBUG_FORMATTED(logger, level, contextStr, msgStr, ...) + #define LOG_DEBUG_TOP_FORMATTED(logger, logTopic, level, contextStr, msgStr, ...) + +#endif // LOG_DEBUG_MESSAGES + +#define Logger_logFormattedWithEntryID(inode, level, logContext, msgFormat, ...) \ + Logger_logTopFormattedWithEntryID(inode, LogTopic_GENERAL, level, logContext, msgFormat, \ + ##__VA_ARGS__) + +// forward declarations... + +struct Logger; +typedef struct Logger Logger; + +struct Node; + +enum LogLevel; +typedef enum LogLevel LogLevel; +enum LogTopic; +typedef enum LogTopic LogTopic; + + + +extern void Logger_init(Logger* this, App* app, Config* cfg); +extern Logger* Logger_construct(App* app, Config* cfg); +extern void Logger_uninit(Logger* this); +extern void Logger_destruct(Logger* this); + +__attribute__((format(printf, 4, 5))) +extern void Logger_logFormatted(Logger* this, LogLevel level, const char* context, + const char* msgFormat, ...); +__attribute__((format(printf, 5, 6))) +extern void Logger_logTopFormattedWithEntryID(struct inode* inode, LogTopic logTopic, + LogLevel level, const char* logContext, const char* msgFormat, ...); +__attribute__((format(printf, 5, 6))) +extern void Logger_logTopFormatted(Logger* this, LogTopic logTopic, LogLevel level, + const char* context, const char* msgFormat, ...); +extern void Logger_logFormattedVA(Logger* this, LogLevel level, const char* context, + const char* msgFormat, va_list ap); +extern void Logger_logTopFormattedVA(Logger* this, LogTopic logTopic, LogLevel level, + const char* context, const char* msgFormat, va_list ap); +__attribute__((format(printf, 3, 4))) +extern void Logger_logErrFormatted(Logger* this, const char* context, const char* msgFormat, ...); +__attribute__((format(printf, 4, 5))) +extern void Logger_logTopErrFormatted(Logger* this, LogTopic logTopic, const char* context, + const char* msgFormat, ...); + +extern LogLevel Logger_getLogLevel(Logger* this); +extern LogLevel Logger_getLogTopicLevel(Logger* this, LogTopic logTopic); + +extern void __Logger_logTopFormattedGranted(Logger* this, LogTopic logTopic, LogLevel level, + const char* context, const char* msgFormat, va_list args); + +extern bool __Logger_checkThreadMultiLock(Logger* this); +extern void __Logger_setCurrentOutputPID(Logger* this, pid_t pid); + + +// static +extern const char* Logger_getLogTopicStr(LogTopic logTopic); +extern bool Logger_getLogTopicFromStr(const char* logTopicStr, LogTopic* logTopic); + +// getters & setters +static inline void Logger_setClientID(Logger* this, const char* clientID); +static inline void Logger_setAllLogLevels(Logger* this, LogLevel logLevel); +static inline void Logger_setLogTopicLevel(Logger* this, LogTopic logTopic, LogLevel logLevel); + +// inliners +static inline void Logger_log(Logger* this, LogLevel level, const char* context, const char* msg); +static inline void Logger_logTop(Logger* this, LogTopic logTopic, LogLevel level, + const char* context, const char* msg); +static inline void Logger_logErr(Logger* this, const char* context, const char* msg); +static inline void Logger_logTopErr(Logger* this, LogTopic logTopic, const char* context, + const char* msg); + + +enum LogLevel +{ + LOG_NOTHING=-1, + Log_ERR=0, /* system error */ + Log_CRITICAL=1, /* something the users should definitely know about */ + Log_WARNING=2, /* things that indicate or are related to a problem */ + Log_NOTICE=3, /* things that could help finding problems */ + Log_DEBUG=4, /* things that are only useful during debugging, often logged with LOG_DEBUG() */ + Log_SPAM=5 /* things that are typically too detailed even during normal debugging, + very often with LOG_DEBUG() */ +}; + +/** + * Note: When you add a new log topic, you must also update these places: + * 1) Logger_getLogTopicStr() + * 2) ProcFsHelper_{read/write}_logLevels() + */ +enum LogTopic +{ + LogTopic_GENERAL=0, /* everything that is not assigned to a more specific log topic */ + LogTopic_CONN, /* connects and disconnects */ + LogTopic_COMMKIT, /* CommKitVec */ + + LogTopic_LAST /* not valid, just exists to define the LogLevelsArray size */ +}; + +typedef signed char LogTopicLevels[LogTopic_LAST]; /* array for per-topic log levels, see LogTopic/ + LogLevel. Note: Type is actually type LogLevel, but we use char here because we also allow + "-1" to disable a level. */ + + +/** + * This is the general logger class. + */ +struct Logger +{ + // configurables + LogTopicLevels logLevels; // per-topic log levels + + // internals + App* app; + + Mutex outputMutex; + + Mutex multiLockMutex; // to avoid multiple locking of the outputMutex by the same thread + pid_t currentOutputPID; // pid of outputMutex holder (see LOGGER_PID_NOCURRENTOUTPUT) + + char* logFormattedBuf; // for logging functions with variable argument list + char* logContextBuf; // for extended context logging + + char* clientID; // only set if clientID logging is enabled +}; + + +/** + * Note: Copies the clientID. + */ +void Logger_setClientID(Logger* this, const char* clientID) +{ + SAFE_KFREE(this->clientID); // free old clientID + + this->clientID = StringTk_strDup(clientID); +} + +/** + * Note: This is intended to be used during app destruction to disable logging by setting the levels + * to "-1". + * + * @param logLevel LogLevel_... or "-1" to disable. + */ +void Logger_setAllLogLevels(Logger* this, LogLevel logLevel) +{ + int i; + + for(i=0; i < LogTopic_LAST; i++) + this->logLevels[i] = logLevel; +} + +/** + * @param logLevel LogLevel_... or "-1" to disable. + */ +void Logger_setLogTopicLevel(Logger* this, LogTopic logTopic, LogLevel logLevel) +{ + this->logLevels[logTopic] = logLevel; +} + +/** + * Log msg for LogTopic_GENERAL. + * + * @param level LogLevel_... value + * @param context the context from which this msg was printed (e.g. the calling function). + * @param msg the log message + */ +void Logger_log(Logger* this, LogLevel level, const char* context, const char* msg) +{ + Logger_logFormatted(this, level, context, "%s", msg); +} + +/** + * Log msg for a certain log topic. + * + * @param level LogLevel_... value + * @param context the context from which this msg was printed (e.g. the calling function). + * @param msg the log message + */ +void Logger_logTop(Logger* this, LogTopic logTopic, LogLevel level, const char* context, + const char* msg) +{ + Logger_logTopFormatted(this, logTopic, level, context, "%s", msg); +} + +/** + * Log error msg for LogTopic_GENERAL. + */ +void Logger_logErr(Logger* this, const char* context, const char* msg) +{ + Logger_logErrFormatted(this, context, "%s", msg); +} + +/** + * Log error msg for a certain log topic. + */ +void Logger_logTopErr(Logger* this, LogTopic logTopic, const char* context, const char* msg) +{ + Logger_logTopErrFormatted(this, logTopic, context, "%s", msg); +} + + + + + + +#endif /*LOGGER_H_*/ diff --git a/client_module/source/common/Common.h b/client_module/source/common/Common.h new file mode 100644 index 0000000..830048f --- /dev/null +++ b/client_module/source/common/Common.h @@ -0,0 +1,490 @@ +#ifndef COMMON_H_ +#define COMMON_H_ + +#include +#include +#include +#include /* for TASK_COMM_LEN */ +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERNEL_HAS_LINUX_FILELOCK_H +#include +#endif + +#ifdef KERNEL_HAS_LINUX_STDARG_H +#include +#else +#include +#endif + +#include +#include +#include +#include + +#ifdef KERNEL_HAS_SCHED_SIG_H +#include +#endif + +#include +#include + +/** + * NOTE: These timeouts can now be overridden by the connMessagingTimeouts + * option in the configuration file. If that option is unset or set to <=0, we + * still default to these constants. + */ +#define CONN_LONG_TIMEOUT 600000 +#define CONN_MEDIUM_TIMEOUT 90000 +#define CONN_SHORT_TIMEOUT 30000 + +#ifndef MIN +#define MIN(a, b) \ + ( ( (a) < (b) ) ? (a) : (b) ) +#endif +#ifndef MAX +#define MAX(a, b) \ + ( ( (a) < (b) ) ? (b) : (a) ) +#endif + +#define SAFE_VFREE(p) \ + do{ if(p) {vfree(p); (p)=NULL;} } while(0) + +#define SAFE_VFREE_NOSET(p) \ + do{ if(p) vfree(p); } while(0) + +#define SAFE_KFREE(p) \ + do{ if(p) {kfree(p); (p)=NULL;} } while(0) + +#define SAFE_KFREE_NOSET(p) \ + do{ if(p) kfree(p); } while(0) + +#define SAFE_DESTRUCT(p, destructor) \ + do{if(p) {destructor(p); (p)=NULL;} } while(0) + +#define SAFE_DESTRUCT_NOSET(p, destructor) \ + do{if(p) destructor(p); } while(0) + + +// typically used for optional out-args +#define SAFE_ASSIGN(destPointer, sourceValue) \ + do{ if(destPointer) {*(destPointer) = (sourceValue);} } while(0) + + +// module name + +#ifndef BEEGFS_MODULE_NAME_STR + #define BEEGFS_MODULE_NAME_STR "beegfs" +#endif + +#define BEEGFS_THREAD_NAME_PREFIX_STR BEEGFS_MODULE_NAME_STR "_" + +// a printk-version that adds the module name and comm name +#define printk_fhgfs(levelStr, fmtStr, ...) \ + printk(levelStr BEEGFS_MODULE_NAME_STR ": " "%s(%u): " fmtStr, current->comm, \ + (unsigned)current->pid, ## __VA_ARGS__) + +// for interrupt handling routines (does not print "current") +#define printk_fhgfs_ir(levelStr, fmtStr, ...) \ + printk(levelStr BEEGFS_MODULE_NAME_STR ": " fmtStr, ## __VA_ARGS__) + + +// dumps stack in case of a buggy condition +#define BEEGFS_BUG_ON(condition, msgStr) \ + do { \ + if(unlikely(condition) ) { \ + printk_fhgfs(KERN_ERR, "%s:%d: BUG: %s (dumping stack...)\n", \ + __func__, __LINE__, msgStr); \ + dump_stack(); \ + } \ + } while(0) + + + +#ifdef LOG_DEBUG_MESSAGES + +#define printk_fhgfs_debug(levelStr, fmtStr, ...) \ + printk(levelStr BEEGFS_MODULE_NAME_STR ": " "%s(%u): " fmtStr, current->comm, \ + (unsigned)current->pid, ## __VA_ARGS__) + + +#define printk_fhgfs_ir_debug(levelStr, fmtStr, ...) \ + printk_fhgfs_ir(levelStr, fmtStr, ## __VA_ARGS__) + +#define BEEGFS_BUG_ON_DEBUG(condition, msgStr) BEEGFS_BUG_ON(condition, msgStr) + +#else // !LOG_DEBUG_MESSAGES + +#define printk_fhgfs_debug(levelStr, fmtStr, ...) \ + do { /* nothing */ } while(0) + +#define printk_fhgfs_ir_debug(levelStr, fmtStr, ...) \ + do { /* nothing */ } while(0) + +#define BEEGFS_BUG_ON_DEBUG(condition, msgStr) \ + do { /* nothing */ } while(0) + +#endif // LOG_DEBUG_MESSAGES + +#ifdef BEEGFS_OPENTK_LOG_CONN_ERRORS + #define printk_fhgfs_connerr(levelStr, fmtStr, ...) \ + printk_fhgfs(levelStr, fmtStr, ## __VA_ARGS__) +#else + #define printk_fhgfs_connerr(levelStr, fmtStr, ...) /* logging of conn errors disabled */ +#endif // BEEGFS_OPENTK_LOG_CONN_ERRORS + + +// this macro mutes warnings about unused variables +#define IGNORE_UNUSED_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0) + +// this macro mutes warnings about unsused variables that are only used in debug build +#ifdef BEEGFS_DEBUG +#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ /* do nothing */ } while(0) +#else +#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0) +#endif + + + +//////////////////////////////////////////////////////// +/* set_fs() / get_fs() macro hackery. + * + * set_fs() and get_fs() have disappeared with Linux kernel 5.10. + * For older kernels, we employ some macros to make their use less of a hassle. + */ +//////////////////////////////////////////////////////// + +#define BEEGFS_CONCAT_(x, y) x ## y +#define BEEGFS_CONCAT(x, y) BEEGFS_CONCAT_(x, y) +#define BEEGFS_UNIQUE_NAME(prefix) BEEGFS_CONCAT(prefix, __LINE__) + + +// Lifted from Linux 5.10 +#if __has_attribute(__fallthrough__) +#define BEEGFS_FALLTHROUGH __attribute__((__fallthrough__)) +#else +#define BEEGFS_FALLTHROUGH do {} while (0) /* FALLTHROUGH */ +#endif + + +/* Preprocessor hack to add statements that are executed on scope cleanup. + * A for-loop that runs exactly 1 time is misused to execute the cleanup + * statement. An assertion ensures that we didn't break from the inner loop, + * to ensure the cleanup statement is executed. */ + +#define BEEGFS_FOR_SCOPE_(begin_stmt, end_stmt, name) \ + for (int name = 0; !name; ({BUG_ON(!name);})) \ + for (begin_stmt; !name++; end_stmt) + +#define BEEGFS_FOR_SCOPE(begin_stmt, end_stmt) \ + BEEGFS_FOR_SCOPE_(begin_stmt, end_stmt, BEEGFS_UNIQUE_NAME(scope)) + +#ifdef KERNEL_HAS_GET_FS + +static inline mm_segment_t BEEGFS_BEGIN_PROCESS_CONTEXT(void) +{ + mm_segment_t out = get_fs(); + set_fs(KERNEL_DS); + return out; +} + +static inline void BEEGFS_END_PROCESS_CONTEXT(mm_segment_t *backup) +{ + set_fs(*backup); +} + +#define WITH_PROCESS_CONTEXT \ + BEEGFS_FOR_SCOPE( \ + mm_segment_t segment = BEEGFS_BEGIN_PROCESS_CONTEXT(), \ + BEEGFS_END_PROCESS_CONTEXT(&segment)) + +#else + +#define WITH_PROCESS_CONTEXT + +#endif + +//////////////////////////////////////////////////////// + + + + + +// in 4.13 wait_queue_t got renamed to wait_queue_entry_t +#if defined(KERNEL_HAS_WAIT_QUEUE_ENTRY_T) + typedef wait_queue_entry_t wait_queue_t; +#endif + +#if defined(KERNEL_HAS_64BIT_TIMESTAMPS) +static inline struct timespec64 current_fs_time(struct super_block *sb) +{ + struct timespec64 now; +#if defined(KERNEL_HAS_KTIME_GET_COARSE_REAL_TS64) + ktime_get_coarse_real_ts64(&now); + return now; +#elif defined(KERNEL_HAS_KTIME_GET_REAL_TS64) + ktime_get_real_ts64(&now); + return timespec64_trunc(now, sb->s_time_gran); +#else + now = current_kernel_time64(); + return timespec64_trunc(now, sb->s_time_gran); +#endif +} +#elif !defined(KERNEL_HAS_CURRENT_FS_TIME) +static inline struct timespec current_fs_time(struct super_block *sb) +{ + struct timespec now = current_kernel_time(); + return timespec_trunc(now, sb->s_time_gran); +} +#endif + +/* Defined by and already included by one of the headers, so + * no KernelFeatureDetection.mk detection required. + * Note: Not in OsCompat.h, as OsCompat depends on Common.h. */ +#ifndef _LINUX_UIDGID_H + typedef unsigned kuid_t; + typedef unsigned kgid_t; + + #define from_kuid(a, b) (b) + #define from_kgid(a, b) (b) + #define make_kuid(a, b) (b) + #define make_kgid(a, b) (b) +#endif + +#ifndef swap + #define swap(a, b) do { typeof(a) __tmp = (a); (a) = (b); (b) = __tmp; } while (0) +#endif + +#undef BEEGFS_RDMA +#ifndef BEEGFS_NO_RDMA +#if defined(CONFIG_INFINIBAND) || defined(CONFIG_INFINIBAND_MODULE) +#define BEEGFS_RDMA 1 +#endif +#endif + +static inline unsigned FhgfsCommon_getCurrentUserID(void); +static inline unsigned FhgfsCommon_getCurrentGroupID(void); + + +unsigned FhgfsCommon_getCurrentUserID(void) +{ + return from_kuid(&init_user_ns, current_fsuid()); +} + +unsigned FhgfsCommon_getCurrentGroupID(void) +{ + return from_kgid(&init_user_ns, current_fsgid()); +} + +// Helper function for getting file pointer +static inline struct file * FhgfsCommon_getFileLock(struct file_lock *fileLock) +{ +#if defined(KERNEL_HAS_FILE_LOCK_CORE) + return fileLock->c.flc_file; +#else + return fileLock->fl_file; +#endif +} +// Helper function to get PID from file lock +static inline pid_t FhgfsCommon_getFileLockPID(struct file_lock *fileLock) +{ +#if defined(KERNEL_HAS_FILE_LOCK_CORE) + return fileLock->c.flc_pid; +#else + return fileLock->fl_pid; +#endif +} + +// Helper function to get lock type +static inline unsigned char FhgfsCommon_getFileLockType(struct file_lock *flock) +{ +#if defined(KERNEL_HAS_FILE_LOCK_CORE) + return flock->c.flc_type; +#else + return flock->fl_type; +#endif +} + +// Helper function to get lock flags +static inline unsigned int FhgfsCommon_getFileLockFlags(struct file_lock *fileLock) +{ +#if defined(KERNEL_HAS_FILE_LOCK_CORE) + return fileLock->c.flc_flags; +#else + return fileLock->fl_flags; +#endif +} + + +/* + * Debug definitions: + * - LOG_DEBUG_MESSAGES: Enables logging of some extra debug messages that will not be + * available otherwise. + * - DEBUG_REFCOUNT: Enables debugging of ObjectReferencer::refCount. Error messages will + * be logged if refCount is less than zero. + */ + +/** + * BEEGFS_KFREE_LIST() - destroys and kfree()s all element of a list, leaving an empty list + * + * expands to ``BEEGFS_KFREE_LIST_DTOR(List, ElemType, Member, (void))``, ie no destructor is + * called. + */ +#define BEEGFS_KFREE_LIST(List, ElemType, Member) \ + BEEGFS_KFREE_LIST_DTOR(List, ElemType, Member, (void)) +/** + * BEEGFS_KFREE_LIST_DTOR() - destroys and kfree()s all element of a list, leaving an empty list + * @List the &struct list_head to free + * @ElemType type of elements contained in the list + * @Member name of the &struct list_head in @ElemType that links to the next element of the list + * @Dtor for each element of the list, ``Dtor(entry)`` is evaluated before the entry is freed + */ +#define BEEGFS_KFREE_LIST_DTOR(List, ElemType, Member, Dtor) \ + do { \ + ElemType* entry; \ + ElemType* n; \ + list_for_each_entry_safe(entry, n, List, Member) \ + { \ + Dtor(entry); \ + kfree(entry); \ + } \ + INIT_LIST_HEAD(List); \ + } while (0) + +/** + * Generic key comparison function for integer types and pointers, to be used by the + * RBTREE_FUNCTIONS as KeyCmp. + */ +#define BEEGFS_RB_KEYCMP_LT_INTEGRAL(lhs, rhs) \ + ( lhs < rhs ? -1 : (lhs == rhs ? 0 : 1) ) + +/** + * BEEGFS_KFREE_RBTREE() - destroys and kfree()s all elements an rbtree + * + * expands to ``BEEGFS_KFREE_RBTREE_DTOR(TreeRoot, ElemType, Member, (void))``, ie no destructor is + * called. + */ +#define BEEGFS_KFREE_RBTREE(TreeRoot, ElemType, Member) \ + BEEGFS_KFREE_RBTREE_DTOR(TreeRoot, ElemType, Member, (void)) +/** + * BEEGFS_KFREE_RBTREE_DTOR() - destroys and kfree()s all elements an rbtree, leaving an empty tree + * @TreeRoot &struct rb_root to free + * @ElemType type of elements contained in the tree + * @Member name of the &struct rb_node in @ElemType that links to further entries in the tree + * @Dtor for each element of the tree, ``Dtor(elem)`` is evaluated before the entry is freed + */ +#define BEEGFS_KFREE_RBTREE_DTOR(TreeRoot, ElemType, Member, Dtor) \ + do { \ + ElemType* pos; \ + ElemType* n; \ + rbtree_postorder_for_each_entry_safe(pos, n, TreeRoot, Member) \ + { \ + Dtor(pos); \ + kfree(pos); \ + } \ + *(TreeRoot) = RB_ROOT; \ + } while (0) + +/** + * BEEGFS_RBTREE_FUNCTIONS() - defines a default set of rbtree functions + * @Access access modifier of generated functions + * @NS namespace prefix for all defined functions + * @RootTy type of the struct that contains the tree we are building functions for + * @RootNode name of the &struct rb_root of our tree in @RootTy + * @KeyTy type of the trees sort key + * @ElemTy type of the tree element. must contain a field of type @KeyTy + * @ElemKey name of the key member in @ElemTy (must be of type @KeyTy) + * @ElemNode node of the &struct rb_node of our tree in @ElemTy + * @KeyCmp function or macro used to compare to @KeyTy values for tree ordering + * + * This macro declares a number of functions with names prefixed by @NS: + * + * * ``Access ElemTy* NS##_find(const RootTy*, KeyTy key)`` finds the @ElemTy with key ``key`` and + * returns it. If none exists, %NULL is returned. + * * ``Access bool NS##_insert(RootTy*, ElemTy* data)`` inserts the element ``data`` into the tree + * if no element with the same key exists and return %true. If an element with the same key does + * exist, returns %false. + * * ``Access ElemTy* NS##_insertOrReplace(RootTy*, ElemTy* data)`` inserts ``data`` into the tree + * if no element with the same key exists or replaced the existing item with the same key. If no + * item existed %NULL is returned, otherwise the pointer to the previous element is returned. + * This is analogous to how std::map::operator[] works in C++. + * * ``access void NS##_erase(RootTy*, ElemTy* data)`` removes ``data`` from the tree. ``data`` is + * not freed or otherwise processed, this function only removes the item and rebalances the tree + * if necessary. + */ +#define BEEGFS_RBTREE_FUNCTIONS(Access, NS, RootTy, RootNode, KeyTy, ElemTy, ElemKey, ElemNode, \ + KeyCmp) \ + __attribute__((unused)) \ + Access ElemTy* NS##_find(const RootTy* root, KeyTy key) \ + { \ + struct rb_node* node = root->RootNode.rb_node; \ + while (node) \ + { \ + ElemTy* data = rb_entry(node, ElemTy, ElemNode); \ + int cmp = KeyCmp(key, data->ElemKey); \ + \ + if (cmp < 0) \ + node = node->rb_left; \ + else if (cmp > 0) \ + node = node->rb_right; \ + else \ + return data; \ + } \ + return NULL; \ + } \ + __attribute__((unused)) \ + Access bool NS##_insert(RootTy* root, ElemTy* data) \ + { \ + struct rb_node** new = &root->RootNode.rb_node; \ + struct rb_node* parent = NULL; \ + while (*new) \ + { \ + ElemTy* cur = container_of(*new, ElemTy, ElemNode); \ + int cmp = KeyCmp(data->ElemKey, cur->ElemKey); \ + \ + parent = *new; \ + if (cmp < 0) \ + new = &(*new)->rb_left; \ + else if (cmp > 0) \ + new = &(*new)->rb_right; \ + else \ + return false; \ + } \ + rb_link_node(&data->ElemNode, parent, new); \ + rb_insert_color(&data->ElemNode, &root->RootNode); \ + return true; \ + } \ + __attribute__((unused)) \ + Access ElemTy* NS##_insertOrReplace(RootTy* root, ElemTy* data) \ + { \ + ElemTy* existing; \ + if (NS##_insert(root, data)) \ + return NULL; \ + \ + existing = NS##_find(root, data->ElemKey); \ + rb_replace_node(&existing->ElemNode, &data->ElemNode, &root->RootNode); \ + return existing; \ + } \ + __attribute__((unused)) \ + Access void NS##_erase(RootTy* root, ElemTy* data) \ + { \ + rb_erase(&data->ElemNode, &root->RootNode); \ + } + +#define BEEGFS_RBTREE_FOR_EACH_ENTRY(Pos, Root, Node) \ + for (Pos = rb_entry_safe(rb_first(Root), typeof(*Pos), Node); \ + Pos; \ + Pos = rb_entry_safe(rb_next(&Pos->Node), typeof(*Pos), Node)) + +/* version number of both the network protocol and the on-disk data structures that are versioned. + * must be kept in sync with userspace. */ +#define BEEGFS_DATA_VERSION ((uint32_t)0) + +#endif /*COMMON_H_*/ diff --git a/client_module/source/common/FhgfsTypes.h b/client_module/source/common/FhgfsTypes.h new file mode 100644 index 0000000..6e218b9 --- /dev/null +++ b/client_module/source/common/FhgfsTypes.h @@ -0,0 +1,35 @@ +#ifndef OPEN_FHGFSTYPES_H_ +#define OPEN_FHGFSTYPES_H_ + +#include +#include + +#include + +struct fhgfs_sockaddr_in +{ + struct in_addr addr; + __be16 port; +}; +typedef struct fhgfs_sockaddr_in fhgfs_sockaddr_in; + + + +struct fhgfs_stat +{ + umode_t mode; + unsigned int nlink; + uid_t uid; + gid_t gid; + loff_t size; + uint64_t blocks; + Time atime; + Time mtime; + Time ctime; // attrib change time (not creation time) + unsigned int metaVersion; +}; +typedef struct fhgfs_stat fhgfs_stat; + + + +#endif /* OPEN_FHGFSTYPES_H_ */ diff --git a/client_module/source/common/Types.c b/client_module/source/common/Types.c new file mode 100644 index 0000000..b9861fa --- /dev/null +++ b/client_module/source/common/Types.c @@ -0,0 +1,30 @@ +#include "Types.h" + +SERDES_DEFINE_SERIALIZERS_SIMPLE(, TargetMapping, struct TargetMapping, + (targetID, , Serialization, UShort), + (nodeID, &, NumNodeID, )) +SERDES_DEFINE_LIST_SERIALIZERS(, TargetMappingList, struct TargetMapping, + TargetMapping, (void), _list, false) + + +SERDES_DEFINE_SERIALIZERS_SIMPLE(, TargetPoolMapping, struct TargetPoolMapping, + (targetID, , Serialization, UShort), + (poolID, &, StoragePoolId, )) +SERDES_DEFINE_LIST_SERIALIZERS(, TargetPoolMappingList, struct TargetPoolMapping, + TargetPoolMapping, (void), _list, false) + + +SERDES_DEFINE_SERIALIZERS_SIMPLE(, BuddyGroupMapping, struct BuddyGroupMapping, + (groupID, , Serialization, UShort), + (primaryTargetID, , Serialization, UShort), + (secondaryTargetID, , Serialization, UShort)) +SERDES_DEFINE_LIST_SERIALIZERS(, BuddyGroupMappingList, struct BuddyGroupMapping, + BuddyGroupMapping, (void), _list, false) + + +SERDES_DEFINE_SERIALIZERS_SIMPLE(, TargetStateMapping, struct TargetStateMapping, + (targetID, , Serialization, UShort), + (reachabilityState, , TargetReachabilityState, ), + (consistencyState, , TargetConsistencyState, )) +SERDES_DEFINE_LIST_SERIALIZERS(, TargetStateMappingList, struct TargetStateMapping, + TargetStateMapping, (void), _list, false) diff --git a/client_module/source/common/Types.h b/client_module/source/common/Types.h new file mode 100644 index 0000000..4e04c53 --- /dev/null +++ b/client_module/source/common/Types.h @@ -0,0 +1,105 @@ +#ifndef BEEGFS_TYPES_H_ +#define BEEGFS_TYPES_H_ + +#include +#include + +struct TargetMapping +{ + uint16_t targetID; + NumNodeID nodeID; + +/* private: */ + union { + struct rb_node _node; /* for use by TargetMapper */ + struct list_head _list; /* for use by de/serialized lists */ + }; +}; + +SERDES_DECLARE_SERIALIZERS(TargetMapping, struct TargetMapping) +SERDES_DECLARE_LIST_SERIALIZERS(TargetMappingList, struct TargetMapping) + +/* make sure to keep this in sync with TargetState in common lib */ +enum TargetReachabilityState +{ + TargetReachabilityState_ONLINE, + TargetReachabilityState_POFFLINE, // probably offline + TargetReachabilityState_OFFLINE +}; + +typedef enum TargetReachabilityState TargetReachabilityState; + +SERDES_DEFINE_ENUM_SERIALIZERS(TargetReachabilityState, enum TargetReachabilityState, + uint8_t, UInt8) + +enum TargetConsistencyState +{ + TargetConsistencyState_GOOD, + TargetConsistencyState_NEEDS_RESYNC, + TargetConsistencyState_BAD +}; + +typedef enum TargetConsistencyState TargetConsistencyState; + +SERDES_DEFINE_ENUM_SERIALIZERS(TargetConsistencyState, enum TargetConsistencyState, uint8_t, UInt8) + +struct CombinedTargetState +{ + TargetReachabilityState reachabilityState; + TargetConsistencyState consistencyState; +}; + +typedef struct CombinedTargetState CombinedTargetState; + +struct TargetStateInfo +{ + uint16_t targetID; + struct CombinedTargetState state; + +/* private */ + struct rb_node _node; +}; + +typedef struct TargetStateInfo TargetStateInfo; + +struct TargetPoolMapping +{ + uint16_t targetID; + StoragePoolId poolID; + +/* private: */ + struct list_head _list; /* for de/serialized lists */ +}; + +SERDES_DECLARE_SERIALIZERS(TargetPoolMapping, struct TargetPoolMapping) +SERDES_DECLARE_LIST_SERIALIZERS(TargetPoolMappingList, struct TargetPoolMapping) + + +struct BuddyGroupMapping +{ + uint16_t groupID; + uint16_t primaryTargetID; + uint16_t secondaryTargetID; + +/* private: */ + struct list_head _list; /* for de/serialized lists */ +}; + +SERDES_DECLARE_SERIALIZERS(BuddyGroupMapping, struct BuddyGroupMapping) +SERDES_DECLARE_LIST_SERIALIZERS(BuddyGroupMappingList, struct BuddyGroupMapping) + + +struct TargetStateMapping +{ + uint16_t targetID; + TargetReachabilityState reachabilityState; + TargetConsistencyState consistencyState; + +/* private: */ + struct list_head _list; /* for de/serialized lists */ +}; + +SERDES_DECLARE_SERIALIZERS(TargetStateMapping, struct TargetStateMapping) +SERDES_DECLARE_LIST_SERIALIZERS(TargetStateMappingList, struct TargetStateMapping) + +#endif diff --git a/client_module/source/common/net/message/NetMessage.c b/client_module/source/common/net/message/NetMessage.c new file mode 100644 index 0000000..f0d2cd9 --- /dev/null +++ b/client_module/source/common/net/message/NetMessage.c @@ -0,0 +1,149 @@ +#include "NetMessage.h" + +/** + * Processes this message. + * + * Note: Some messages might be received over a datagram socket, so the response + * must be atomic (=> only a single sendto()-call) + * + * @param fromAddr must be NULL for stream sockets + * @return false on error + */ +bool NetMessage_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + // Note: Has to be implemented appropriately by derived classes. + // Empty implementation provided here for invalid messages and other messages + // that don't require this way of processing (e.g. some response messages). + + return false; +} + +/** + * Returns all feature flags that are supported by this message. Defaults to "none", so this + * method needs to be overridden by derived messages that actually support header feature + * flags. + * + * @return combination of all supported feature flags + */ +unsigned NetMessage_getSupportedHeaderFeatureFlagsMask(NetMessage* this) +{ + return 0; +} + +/** + * Reads the (common) header part of a message from a buffer. + * + * Note: Message type will be set to NETMSGTYPE_Invalid if deserialization fails. + */ +void __NetMessage_deserializeHeader(DeserializeCtx* ctx, NetMessageHeader* outHeader) +{ + size_t totalLength = ctx->length; + uint64_t prefix = 0; + // check min buffer length + + if(unlikely(ctx->length < NETMSG_HEADER_LENGTH) ) + { + outHeader->msgType = NETMSGTYPE_Invalid; + return; + } + + // message length + Serialization_deserializeUInt(ctx, &outHeader->msgLength); + + // verify contained msg length + if(unlikely(outHeader->msgLength != totalLength) ) + { + outHeader->msgType = NETMSGTYPE_Invalid; + return; + } + + // feature flags + Serialization_deserializeUShort(ctx, &outHeader->msgFeatureFlags); + Serialization_deserializeUInt8(ctx, &outHeader->msgCompatFeatureFlags); + Serialization_deserializeUInt8(ctx, &outHeader->msgFlags); + + // check message prefix + Serialization_deserializeUInt64(ctx, &prefix); + if (prefix != NETMSG_PREFIX) + { + outHeader->msgType = NETMSGTYPE_Invalid; + return; + } + + if (outHeader->msgFlags & ~(MSGHDRFLAG_BUDDYMIRROR_SECOND | MSGHDRFLAG_IS_SELECTIVE_ACK | + MSGHDRFLAG_HAS_SEQUENCE_NO)) + { + outHeader->msgType = NETMSGTYPE_Invalid; + return; + } + + // message type + Serialization_deserializeUShort(ctx, &outHeader->msgType); + + // targetID + Serialization_deserializeUShort(ctx, &outHeader->msgTargetID); + + // userID + Serialization_deserializeUInt(ctx, &outHeader->msgUserID); + + Serialization_deserializeUInt64(ctx, &outHeader->msgSequence); + Serialization_deserializeUInt64(ctx, &outHeader->msgSequenceDone); +} + + +/** + * Writes the (common) header part of a message to a buffer. + * + * Note the min required size for the buf parameter! Message-specific data can be stored + * from &buf[NETMSG_HEADER_LENGTH] on. + * The msg->msgPrefix field is ignored and will always be stored correctly in the buffer. + * + * @param buf min size is NETMSG_HEADER_LENGTH + * @return false on error (e.g. bufLen too small), true otherwise + */ +void __NetMessage_serializeHeader(NetMessage* this, SerializeCtx* ctx, bool zeroLengthField) +{ + // message length + Serialization_serializeUInt(ctx, zeroLengthField ? 0 : NetMessage_getMsgLength(this) ); + + // feature flags + Serialization_serializeUShort(ctx, this->msgHeader.msgFeatureFlags); + Serialization_serializeChar(ctx, this->msgHeader.msgCompatFeatureFlags); + Serialization_serializeChar(ctx, this->msgHeader.msgFlags); + + // message prefix + Serialization_serializeUInt64(ctx, NETMSG_PREFIX); + + // message type + Serialization_serializeUShort(ctx, this->msgHeader.msgType); + + // targetID + Serialization_serializeUShort(ctx, this->msgHeader.msgTargetID); + + // userID + Serialization_serializeUInt(ctx, this->msgHeader.msgUserID); + + Serialization_serializeUInt64(ctx, this->msgHeader.msgSequence); + Serialization_serializeUInt64(ctx, this->msgHeader.msgSequenceDone); +} + +/** + * Dummy function for deserialize pointers + */ +bool _NetMessage_deserializeDummy(NetMessage* this, DeserializeCtx* ctx) +{ + printk_fhgfs(KERN_INFO, "Bug: Deserialize function called, although it should not\n"); + dump_stack(); + + return true; +} + +/** + * Dummy function for serialize pointers + */ +void _NetMessage_serializeDummy(NetMessage* this, SerializeCtx* ctx) +{ + printk_fhgfs(KERN_INFO, "Bug: Serialize function called, although it should not\n"); + dump_stack(); +} diff --git a/client_module/source/common/net/message/NetMessage.h b/client_module/source/common/net/message/NetMessage.h new file mode 100644 index 0000000..840435d --- /dev/null +++ b/client_module/source/common/net/message/NetMessage.h @@ -0,0 +1,259 @@ +#ifndef NETMESSAGE_H_ +#define NETMESSAGE_H_ + +#include +#include +#include +#include +#include "NetMessageTypes.h" + +/** + * Note: This "class" is not meant to be instantiated directly (consider it to be abstract). + * It contains some "virtual" methods, as you can see in the struct NetMessage. Only the virtual + * Method processIncoming(..) has a default implementation. + * Derived classes have a destructor with a NetMessage-Pointer (instead of the real type) + * because of the generic virtual destructor signature. + * Derived classes normally have two constructors: One has no arguments and is used for + * deserialization. The other one is the standard constructor. + */ + +// common message constants +// ======================== +#define NETMSG_PREFIX ((0x42474653ULL << 32) + BEEGFS_DATA_VERSION) +#define NETMSG_MIN_LENGTH NETMSG_HEADER_LENGTH +#define NETMSG_HEADER_LENGTH 40 /* length of the header (see struct NetMessageHeader) */ +#define NETMSG_MAX_MSG_SIZE 65536 // 64kB +#define NETMSG_MAX_PAYLOAD_SIZE ((unsigned)(NETMSG_MAX_MSG_SIZE - NETMSG_HEADER_LENGTH)) + +#define NETMSG_DEFAULT_USERID (~0) // non-zero to avoid mixing up with root userID + + +// forward declaration +struct App; + + +struct NetMessageHeader; +typedef struct NetMessageHeader NetMessageHeader; +struct NetMessage; +typedef struct NetMessage NetMessage; + +struct NetMessageOps; + +static inline void NETMESSAGE_FREE(NetMessage* this); + +static inline void NetMessage_init(NetMessage* this, unsigned short msgType, + const struct NetMessageOps* ops); + +extern void __NetMessage_deserializeHeader(DeserializeCtx* ctx, struct NetMessageHeader* outHeader); +extern void __NetMessage_serializeHeader(NetMessage* this, SerializeCtx* ctx, bool zeroLengthField); + +extern void _NetMessage_serializeDummy(NetMessage* this, SerializeCtx* ctx); +extern bool _NetMessage_deserializeDummy(NetMessage* this, DeserializeCtx* ctx); + + +static inline unsigned NetMessage_extractMsgLengthFromBuf(const char* recvBuf); +static inline bool NetMessage_serialize(NetMessage* this, char* buf, size_t bufLen); +static inline bool NetMessage_checkHeaderFeatureFlagsCompat(NetMessage* this); + +// virtual functions +extern bool NetMessage_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); +extern unsigned NetMessage_getSupportedHeaderFeatureFlagsMask(NetMessage* this); + +// getters & setters +static inline unsigned short NetMessage_getMsgType(NetMessage* this); +static inline unsigned NetMessage_getMsgHeaderFeatureFlags(NetMessage* this); +static inline bool NetMessage_isMsgHeaderFeatureFlagSet(NetMessage* this, unsigned flag); +static inline void NetMessage_addMsgHeaderFeatureFlag(NetMessage* this, unsigned flag); +static inline unsigned NetMessage_getMsgLength(NetMessage* this); +static inline void NetMessage_setMsgHeaderUserID(NetMessage* this, unsigned userID); +static inline void NetMessage_setMsgHeaderTargetID(NetMessage* this, uint16_t userID); + +static inline void _NetMessage_setMsgType(NetMessage* this, unsigned short msgType); + + +#define MSGHDRFLAG_BUDDYMIRROR_SECOND (0x01) +#define MSGHDRFLAG_IS_SELECTIVE_ACK (0x02) +#define MSGHDRFLAG_HAS_SEQUENCE_NO (0x04) + +struct NetMessageHeader +{ + unsigned msgLength; // in bytes + uint16_t msgFeatureFlags; // feature flags for derived messages (depend on msgType) + uint8_t msgCompatFeatureFlags; /* for derived messages, similar to msgFeatureFlags, but + "compat" because there is no check whether receiver + understands these flags, so they might be ignored. */ + uint8_t msgFlags; +// char* msgPrefix; // NETMSG_PREFIX_STR (8 bytes) + unsigned short msgType; // the type of payload, defined as NETMSGTYPE_x + uint16_t msgTargetID; // targetID (not groupID) for per-target workers on storage server + unsigned msgUserID; // system user ID for per-user msg queues, stats etc. + uint64_t msgSequence; // for retries, 0 if not present + uint64_t msgSequenceDone; // a sequence number that has been fully processed, or 0 +}; + +struct NetMessageOps +{ + void (*serializePayload) (NetMessage* this, SerializeCtx* ctx); + bool (*deserializePayload) (NetMessage* this, DeserializeCtx* ctx); + + bool (*processIncoming) (NetMessage* this, struct App* app, fhgfs_sockaddr_in* fromAddr, + struct Socket* sock, char* respBuf, size_t bufLen); + unsigned (*getSupportedHeaderFeatureFlagsMask) (NetMessage* this); + + void (*release)(NetMessage* this); + + + // not strictly operations, but these are common to all messages and do not warrant their own + // functions + bool supportsSequenceNumbers; +}; + +struct NetMessage +{ + struct NetMessageHeader msgHeader; + + const struct NetMessageOps* ops; +}; + + +void NetMessage_init(NetMessage* this, unsigned short msgType, const struct NetMessageOps* ops) +{ + memset(this, 0, sizeof(*this) ); // clear function pointers etc. + + // this->msgLength = 0; // zero'ed by memset + // this->msgFeatureFlags = 0; // zero'ed by memset + + this->msgHeader.msgType = msgType; + + // needs to be set to actual ID by some async flushers etc + this->msgHeader.msgUserID = FhgfsCommon_getCurrentUserID(); + + // this->msgTargetID = 0; // zero'ed by memset + + this->ops = ops; +} + +#define NETMESSAGE_CONSTRUCT(TYPE) \ + ({ \ + TYPE* msg = os_kmalloc(sizeof(TYPE)); \ + TYPE##_init(msg); \ + (NetMessage*)msg; \ + }) + +static inline void NETMESSAGE_FREE(NetMessage* msg) +{ + if (msg->ops->release) + msg->ops->release(msg); + + kfree(msg); +} + + +/** + * recvBuf must be at least NETMSG_MIN_LENGTH long + */ +unsigned NetMessage_extractMsgLengthFromBuf(const char* recvBuf) +{ + unsigned msgLength; + DeserializeCtx ctx = { recvBuf, sizeof(msgLength) }; + + Serialization_deserializeUInt(&ctx, &msgLength); + + return msgLength; +} + +bool NetMessage_serialize(NetMessage* this, char* buf, size_t bufLen) +{ + SerializeCtx ctx = { + .data = buf, + }; + + if(unlikely(bufLen < NetMessage_getMsgLength(this) ) ) + return false; + + __NetMessage_serializeHeader(this, &ctx, false); + this->ops->serializePayload(this, &ctx); + + return true; +} + +/** + * Check if the msg sender has set an incompatible feature flag. + * + * @return false if an incompatible feature flag was set + */ +bool NetMessage_checkHeaderFeatureFlagsCompat(NetMessage* this) +{ + unsigned unsupportedFlags = ~(this->ops->getSupportedHeaderFeatureFlagsMask(this) ); + if(unlikely(this->msgHeader.msgFeatureFlags & unsupportedFlags) ) + return false; // an unsupported flag was set + + return true; +} + +unsigned short NetMessage_getMsgType(NetMessage* this) +{ + return this->msgHeader.msgType; +} + +unsigned NetMessage_getMsgHeaderFeatureFlags(NetMessage* this) +{ + return this->msgHeader.msgFeatureFlags; +} + +/** + * Test flag. (For convenience and readability.) + * + * @return true if given flag is set. + */ +bool NetMessage_isMsgHeaderFeatureFlagSet(NetMessage* this, unsigned flag) +{ + return (this->msgHeader.msgFeatureFlags & flag) != 0; +} + +/** + * Add another flag without clearing the previously set flags. + * + * Note: The receiver will reject this message if it doesn't know the given feature flag. + */ +void NetMessage_addMsgHeaderFeatureFlag(NetMessage* this, unsigned flag) +{ + this->msgHeader.msgFeatureFlags |= flag; +} + +unsigned NetMessage_getMsgLength(NetMessage* this) +{ + if(!this->msgHeader.msgLength) + { + SerializeCtx ctx = { NULL, 0 }; + + __NetMessage_serializeHeader(this, &ctx, true); + this->ops->serializePayload(this, &ctx); + + this->msgHeader.msgLength = ctx.length; + } + + return this->msgHeader.msgLength; +} + +void NetMessage_setMsgHeaderUserID(NetMessage* this, unsigned userID) +{ + this->msgHeader.msgUserID = userID; +} + +/** + * @param targetID this has to be an actual targetID (not a groupID). + */ +void NetMessage_setMsgHeaderTargetID(NetMessage* this, uint16_t targetID) +{ + this->msgHeader.msgTargetID = targetID; +} + +void _NetMessage_setMsgType(NetMessage* this, unsigned short msgType) +{ + this->msgHeader.msgType = msgType; +} + + +#endif /*NETMESSAGE_H_*/ diff --git a/client_module/source/common/net/message/NetMessageTypes.h b/client_module/source/common/net/message/NetMessageTypes.h new file mode 100644 index 0000000..b51e841 --- /dev/null +++ b/client_module/source/common/net/message/NetMessageTypes.h @@ -0,0 +1,273 @@ +#ifndef NETMESSAGETYPES_H_ +#define NETMESSAGETYPES_H_ + +/* This file MUST be kept in sync with the corresponding fhgfs_common file! + See fhgfs_common/source/common/net/message/NetMessageTypes.h */ + +// invalid messages +#define NETMSGTYPE_Invalid 0 + +// nodes messages +#define NETMSGTYPE_RemoveNode 1013 +#define NETMSGTYPE_RemoveNodeResp 1014 +#define NETMSGTYPE_GetNodes 1017 +#define NETMSGTYPE_GetNodesResp 1018 +#define NETMSGTYPE_HeartbeatRequest 1019 +#define NETMSGTYPE_Heartbeat 1020 +#define NETMSGTYPE_GetNodeCapacityPools 1021 +#define NETMSGTYPE_GetNodeCapacityPoolsResp 1022 +#define NETMSGTYPE_MapTargets 1023 +#define NETMSGTYPE_MapTargetsResp 1024 +#define NETMSGTYPE_GetTargetMappings 1025 +#define NETMSGTYPE_GetTargetMappingsResp 1026 +#define NETMSGTYPE_UnmapTarget 1027 +#define NETMSGTYPE_UnmapTargetResp 1028 +#define NETMSGTYPE_GenericDebug 1029 +#define NETMSGTYPE_GenericDebugResp 1030 +#define NETMSGTYPE_GetClientStats 1031 +#define NETMSGTYPE_GetClientStatsResp 1032 +#define NETMSGTYPE_RefreshCapacityPools 1035 +#define NETMSGTYPE_StorageBenchControlMsg 1037 +#define NETMSGTYPE_StorageBenchControlMsgResp 1038 +#define NETMSGTYPE_RegisterNode 1039 +#define NETMSGTYPE_RegisterNodeResp 1040 +#define NETMSGTYPE_RegisterTarget 1041 +#define NETMSGTYPE_RegisterTargetResp 1042 +#define NETMSGTYPE_SetMirrorBuddyGroup 1045 +#define NETMSGTYPE_SetMirrorBuddyGroupResp 1046 +#define NETMSGTYPE_GetMirrorBuddyGroups 1047 +#define NETMSGTYPE_GetMirrorBuddyGroupsResp 1048 +#define NETMSGTYPE_GetTargetStates 1049 +#define NETMSGTYPE_GetTargetStatesResp 1050 +#define NETMSGTYPE_RefreshTargetStates 1051 +#define NETMSGTYPE_GetStatesAndBuddyGroups 1053 +#define NETMSGTYPE_GetStatesAndBuddyGroupsResp 1054 +#define NETMSGTYPE_SetTargetConsistencyStates 1055 +#define NETMSGTYPE_SetTargetConsistencyStatesResp 1056 +#define NETMSGTYPE_ChangeTargetConsistencyStates 1057 +#define NETMSGTYPE_ChangeTargetConsistencyStatesResp 1058 +#define NETMSGTYPE_PublishCapacities 1059 +#define NETMSGTYPE_RemoveBuddyGroup 1060 +#define NETMSGTYPE_RemoveBuddyGroupResp 1061 +#define NETMSGTYPE_GetTargetConsistencyStates 1062 +#define NETMSGTYPE_GetTargetConsistencyStatesResp 1063 + + +// storage messages +#define NETMSGTYPE_MkDir 2001 +#define NETMSGTYPE_MkDirResp 2002 +#define NETMSGTYPE_RmDir 2003 +#define NETMSGTYPE_RmDirResp 2004 +#define NETMSGTYPE_MkFile 2005 +#define NETMSGTYPE_MkFileResp 2006 +#define NETMSGTYPE_UnlinkFile 2007 +#define NETMSGTYPE_UnlinkFileResp 2008 +#define NETMSGTYPE_UnlinkLocalFile 2011 +#define NETMSGTYPE_UnlinkLocalFileResp 2012 +#define NETMSGTYPE_Stat 2015 +#define NETMSGTYPE_StatResp 2016 +#define NETMSGTYPE_GetChunkFileAttribs 2017 +#define NETMSGTYPE_GetChunkFileAttribsResp 2018 +#define NETMSGTYPE_TruncFile 2019 +#define NETMSGTYPE_TruncFileResp 2020 +#define NETMSGTYPE_TruncLocalFile 2021 +#define NETMSGTYPE_TruncLocalFileResp 2022 +#define NETMSGTYPE_Rename 2023 +#define NETMSGTYPE_RenameResp 2024 +#define NETMSGTYPE_SetAttr 2025 +#define NETMSGTYPE_SetAttrResp 2026 +#define NETMSGTYPE_ListDirFromOffset 2029 +#define NETMSGTYPE_ListDirFromOffsetResp 2030 +#define NETMSGTYPE_StatStoragePath 2031 +#define NETMSGTYPE_StatStoragePathResp 2032 +#define NETMSGTYPE_SetLocalAttr 2033 +#define NETMSGTYPE_SetLocalAttrResp 2034 +#define NETMSGTYPE_FindOwner 2035 +#define NETMSGTYPE_FindOwnerResp 2036 +#define NETMSGTYPE_MkLocalDir 2037 +#define NETMSGTYPE_MkLocalDirResp 2038 +#define NETMSGTYPE_RmLocalDir 2039 +#define NETMSGTYPE_RmLocalDirResp 2040 +#define NETMSGTYPE_MovingFileInsert 2041 +#define NETMSGTYPE_MovingFileInsertResp 2042 +#define NETMSGTYPE_MovingDirInsert 2043 +#define NETMSGTYPE_MovingDirInsertResp 2044 +#define NETMSGTYPE_GetEntryInfo 2045 +#define NETMSGTYPE_GetEntryInfoResp 2046 +#define NETMSGTYPE_SetDirPattern 2047 +#define NETMSGTYPE_SetDirPatternResp 2048 +#define NETMSGTYPE_GetHighResStats 2051 +#define NETMSGTYPE_GetHighResStatsResp 2052 +#define NETMSGTYPE_MkFileWithPattern 2053 +#define NETMSGTYPE_MkFileWithPatternResp 2054 +#define NETMSGTYPE_RefreshEntryInfo 2055 +#define NETMSGTYPE_RefreshEntryInfoResp 2056 +#define NETMSGTYPE_RmDirEntry 2057 +#define NETMSGTYPE_RmDirEntryResp 2058 +#define NETMSGTYPE_LookupIntent 2059 +#define NETMSGTYPE_LookupIntentResp 2060 +#define NETMSGTYPE_FindLinkOwner 2063 +#define NETMSGTYPE_FindLinkOwnerResp 2064 +#define NETMSGTYPE_MirrorMetadata 2067 +#define NETMSGTYPE_MirrorMetadataResp 2068 +#define NETMSGTYPE_SetMetadataMirroring 2069 +#define NETMSGTYPE_SetMetadataMirroringResp 2070 +#define NETMSGTYPE_Hardlink 2071 +#define NETMSGTYPE_HardlinkResp 2072 +#define NETMSGTYPE_SetQuota 2075 +#define NETMSGTYPE_SetQuotaResp 2076 +#define NETMSGTYPE_SetExceededQuota 2077 +#define NETMSGTYPE_SetExceededQuotaResp 2078 +#define NETMSGTYPE_RequestExceededQuota 2079 +#define NETMSGTYPE_RequestExceededQuotaResp 2080 +#define NETMSGTYPE_UpdateDirParent 2081 +#define NETMSGTYPE_UpdateDirParentResp 2082 +#define NETMSGTYPE_ResyncLocalFile 2083 +#define NETMSGTYPE_ResyncLocalFileResp 2084 +#define NETMSGTYPE_StartStorageTargetResync 2085 +#define NETMSGTYPE_StartStorageTargetResyncResp 2086 +#define NETMSGTYPE_StorageResyncStarted 2087 +#define NETMSGTYPE_StorageResyncStartedResp 2088 +#define NETMSGTYPE_ListChunkDirIncremental 2089 +#define NETMSGTYPE_ListChunkDirIncrementalResp 2090 +#define NETMSGTYPE_RmChunkPaths 2091 +#define NETMSGTYPE_RmChunkPathsResp 2092 +#define NETMSGTYPE_GetStorageResyncStats 2093 +#define NETMSGTYPE_GetStorageResyncStatsResp 2094 +#define NETMSGTYPE_SetLastBuddyCommOverride 2095 +#define NETMSGTYPE_SetLastBuddyCommOverrideResp 2096 +#define NETMSGTYPE_GetQuotaInfo 2097 +#define NETMSGTYPE_GetQuotaInfoResp 2098 +#define NETMSGTYPE_SetStorageTargetInfo 2099 +#define NETMSGTYPE_SetStorageTargetInfoResp 2100 +#define NETMSGTYPE_ListXAttr 2101 +#define NETMSGTYPE_ListXAttrResp 2102 +#define NETMSGTYPE_GetXAttr 2103 +#define NETMSGTYPE_GetXAttrResp 2104 +#define NETMSGTYPE_RemoveXAttr 2105 +#define NETMSGTYPE_RemoveXAttrResp 2106 +#define NETMSGTYPE_SetXAttr 2107 +#define NETMSGTYPE_SetXAttrResp 2108 +#define NETMSGTYPE_GetDefaultQuota 2109 +#define NETMSGTYPE_GetDefaultQuotaResp 2110 +#define NETMSGTYPE_SetDefaultQuota 2111 +#define NETMSGTYPE_SetDefaultQuotaResp 2112 +#define NETMSGTYPE_ResyncSessionStore 2113 +#define NETMSGTYPE_ResyncSessionStoreResp 2114 +#define NETMSGTYPE_ResyncRawInodes 2115 +#define NETMSGTYPE_ResyncRawInodesResp 2116 +#define NETMSGTYPE_GetMetaResyncStats 2117 +#define NETMSGTYPE_GetMetaResyncStatsResp 2118 +#define NETMSGTYPE_MoveFileInode 2119 +#define NETMSGTYPE_MoveFileInodeResp 2120 +#define NETMSGTYPE_UnlinkLocalFileInode 2121 +#define NETMSGTYPE_UnlinkLocalFileInodeResp 2122 +#define NETMSGTYPE_SetFilePattern 2123 +#define NETMSGTYPE_SetFilePatternResp 2124 +#define NETMSGTYPE_CpChunkPaths 2125 +#define NETMSGTYPE_CpChunkPathsResp 2126 +#define NETMSGTYPE_ChunkBalance 2127 +#define NETMSGTYPE_ChunkBalanceResp 2128 +#define NETMSGTYPE_StripePatternUpdate 2129 +#define NETMSGTYPE_StripePatternUpdateResp 2130 +#define NETMSGTYPE_SetFileState 2131 +#define NETMSGTYPE_SetFileStateResp 2132 + +// session messages +#define NETMSGTYPE_OpenFile 3001 +#define NETMSGTYPE_OpenFileResp 3002 +#define NETMSGTYPE_CloseFile 3003 +#define NETMSGTYPE_CloseFileResp 3004 +#define NETMSGTYPE_OpenLocalFile 3005 +#define NETMSGTYPE_OpenLocalFileResp 3006 +#define NETMSGTYPE_CloseChunkFile 3007 +#define NETMSGTYPE_CloseChunkFileResp 3008 +#define NETMSGTYPE_WriteLocalFile 3009 +#define NETMSGTYPE_WriteLocalFileResp 3010 +#define NETMSGTYPE_FSyncLocalFile 3013 +#define NETMSGTYPE_FSyncLocalFileResp 3014 +#define NETMSGTYPE_ReadLocalFileV2 3019 +#define NETMSGTYPE_RefreshSession 3021 +#define NETMSGTYPE_RefreshSessionResp 3022 +#define NETMSGTYPE_LockGranted 3023 +#define NETMSGTYPE_FLockEntry 3025 +#define NETMSGTYPE_FLockEntryResp 3026 +#define NETMSGTYPE_FLockRange 3027 +#define NETMSGTYPE_FLockRangeResp 3028 +#define NETMSGTYPE_FLockAppend 3029 +#define NETMSGTYPE_FLockAppendResp 3030 +#define NETMSGTYPE_AckNotify 3031 +#define NETMSGTYPE_AckNotifyResp 3032 +#define NETMSGTYPE_BumpFileVersion 3033 +#define NETMSGTYPE_BumpFileVersionResp 3034 +#define NETMSGTYPE_GetFileVersion 3035 +#define NETMSGTYPE_GetFileVersionResp 3036 +#ifdef BEEGFS_NVFS +#define NETMSGTYPE_WriteLocalFileRDMA 3037 +#define NETMSGTYPE_WriteLocalFileRDMAResp 3038 +#define NETMSGTYPE_ReadLocalFileRDMA 3039 +#define NETMSGTYPE_ReadLocalFileRDMAResp 3040 +#endif + +// control messages +#define NETMSGTYPE_SetChannelDirect 4001 +#define NETMSGTYPE_Ack 4003 +#define NETMSGTYPE_Dummy 4005 +#define NETMSGTYPE_AuthenticateChannel 4007 +#define NETMSGTYPE_GenericResponse 4009 +#define NETMSGTYPE_PeerInfo 4011 + +// mon messages +#define NETMSGTYPE_GetNodesFromRootMetaNode 6001 +#define NETMSGTYPE_SendNodesList 6002 +#define NETMSGTYPE_RequestMetaData 6003 +#define NETMSGTYPE_RequestStorageData 6004 +#define NETMSGTYPE_RequestMetaDataResp 6005 +#define NETMSGTYPE_RequestStorageDataResp 6006 + +// fsck messages +#define NETMSGTYPE_RetrieveDirEntries 7001 +#define NETMSGTYPE_RetrieveDirEntriesResp 7002 +#define NETMSGTYPE_RetrieveInodes 7003 +#define NETMSGTYPE_RetrieveInodesResp 7004 +#define NETMSGTYPE_RetrieveChunks 7005 +#define NETMSGTYPE_RetrieveChunksResp 7006 +#define NETMSGTYPE_RetrieveFsIDs 7007 +#define NETMSGTYPE_RetrieveFsIDsResp 7008 +#define NETMSGTYPE_DeleteDirEntries 7009 +#define NETMSGTYPE_DeleteDirEntriesResp 7010 +#define NETMSGTYPE_CreateDefDirInodes 7011 +#define NETMSGTYPE_CreateDefDirInodesResp 7012 +#define NETMSGTYPE_FixInodeOwnersInDentry 7013 +#define NETMSGTYPE_FixInodeOwnersInDentryResp 7014 +#define NETMSGTYPE_FixInodeOwners 7015 +#define NETMSGTYPE_FixInodeOwnersResp 7016 +#define NETMSGTYPE_LinkToLostAndFound 7017 +#define NETMSGTYPE_LinkToLostAndFoundResp 7018 +#define NETMSGTYPE_DeleteChunks 7019 +#define NETMSGTYPE_DeleteChunksResp 7020 +#define NETMSGTYPE_CreateEmptyContDirs 7021 +#define NETMSGTYPE_CreateEmptyContDirsResp 7022 +#define NETMSGTYPE_UpdateFileAttribs 7023 +#define NETMSGTYPE_UpdateFileAttribsResp 7024 +#define NETMSGTYPE_UpdateDirAttribs 7025 +#define NETMSGTYPE_UpdateDirAttribsResp 7026 +#define NETMSGTYPE_RemoveInodes 7027 +#define NETMSGTYPE_RemoveInodesResp 7028 +#define NETMSGTYPE_RecreateFsIDs 7031 +#define NETMSGTYPE_RecreateFsIDsResp 7032 +#define NETMSGTYPE_RecreateDentries 7033 +#define NETMSGTYPE_RecreateDentriesResp 7034 +#define NETMSGTYPE_FsckModificationEvent 7035 +#define NETMSGTYPE_FsckSetEventLogging 7036 +#define NETMSGTYPE_FsckSetEventLoggingResp 7037 +#define NETMSGTYPE_FetchFsckChunkList 7038 +#define NETMSGTYPE_FetchFsckChunkListResp 7039 +#define NETMSGTYPE_AdjustChunkPermissions 7040 +#define NETMSGTYPE_AdjustChunkPermissionsResp 7041 +#define NETMSGTYPE_MoveChunkFile 7042 +#define NETMSGTYPE_MoveChunkFileResp 7043 +#define NETMSGTYPE_CheckAndRepairDupInode 7044 +#define NETMSGTYPE_CheckAndRepairDupInodeResp 7045 + +#endif /*NETMESSAGETYPES_H_*/ diff --git a/client_module/source/common/net/message/SimpleInt64Msg.c b/client_module/source/common/net/message/SimpleInt64Msg.c new file mode 100644 index 0000000..eadc42e --- /dev/null +++ b/client_module/source/common/net/message/SimpleInt64Msg.c @@ -0,0 +1,22 @@ +#include "SimpleInt64Msg.h" + +const struct NetMessageOps SimpleInt64Msg_Ops = { + .serializePayload = SimpleInt64Msg_serializePayload, + .deserializePayload = SimpleInt64Msg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleInt64Msg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SimpleInt64Msg* thisCast = (SimpleInt64Msg*)this; + + Serialization_serializeInt64(ctx, thisCast->value); +} + +bool SimpleInt64Msg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SimpleInt64Msg* thisCast = (SimpleInt64Msg*)this; + + return Serialization_deserializeInt64(ctx, &thisCast->value); +} diff --git a/client_module/source/common/net/message/SimpleInt64Msg.h b/client_module/source/common/net/message/SimpleInt64Msg.h new file mode 100644 index 0000000..a6169a1 --- /dev/null +++ b/client_module/source/common/net/message/SimpleInt64Msg.h @@ -0,0 +1,46 @@ +#ifndef SIMPLEINT64MSG_H_ +#define SIMPLEINT64MSG_H_ + +#include "NetMessage.h" + +struct SimpleInt64Msg; +typedef struct SimpleInt64Msg SimpleInt64Msg; + +static inline void SimpleInt64Msg_init(SimpleInt64Msg* this, unsigned short msgType); +static inline void SimpleInt64Msg_initFromValue(SimpleInt64Msg* this, unsigned short msgType, + int64_t value); + +// virtual functions +extern void SimpleInt64Msg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleInt64Msg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline int64_t SimpleInt64Msg_getValue(SimpleInt64Msg* this); + +struct SimpleInt64Msg +{ + NetMessage netMessage; + + int64_t value; +}; + +extern const struct NetMessageOps SimpleInt64Msg_Ops; + +void SimpleInt64Msg_init(SimpleInt64Msg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleInt64Msg_Ops); +} + +void SimpleInt64Msg_initFromValue(SimpleInt64Msg* this, unsigned short msgType, int64_t value) +{ + SimpleInt64Msg_init(this, msgType); + + this->value = value; +} + +int64_t SimpleInt64Msg_getValue(SimpleInt64Msg* this) +{ + return this->value; +} + +#endif /*SIMPLEINT64MSG_H_*/ diff --git a/client_module/source/common/net/message/SimpleIntMsg.c b/client_module/source/common/net/message/SimpleIntMsg.c new file mode 100644 index 0000000..c591076 --- /dev/null +++ b/client_module/source/common/net/message/SimpleIntMsg.c @@ -0,0 +1,22 @@ +#include "SimpleIntMsg.h" + +const struct NetMessageOps SimpleIntMsg_Ops = { + .serializePayload = SimpleIntMsg_serializePayload, + .deserializePayload = SimpleIntMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleIntMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SimpleIntMsg* thisCast = (SimpleIntMsg*)this; + + Serialization_serializeInt(ctx, thisCast->value); +} + +bool SimpleIntMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SimpleIntMsg* thisCast = (SimpleIntMsg*)this; + + return Serialization_deserializeInt(ctx, &thisCast->value); +} diff --git a/client_module/source/common/net/message/SimpleIntMsg.h b/client_module/source/common/net/message/SimpleIntMsg.h new file mode 100644 index 0000000..b0b4627 --- /dev/null +++ b/client_module/source/common/net/message/SimpleIntMsg.h @@ -0,0 +1,48 @@ +#ifndef SIMPLEINTMSG_H_ +#define SIMPLEINTMSG_H_ + +#include "NetMessage.h" + +struct SimpleIntMsg; +typedef struct SimpleIntMsg SimpleIntMsg; + +static inline void SimpleIntMsg_init(SimpleIntMsg* this, unsigned short msgType); +static inline void SimpleIntMsg_initFromValue(SimpleIntMsg* this, unsigned short msgType, + int value); + +// virtual functions +extern void SimpleIntMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleIntMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline int SimpleIntMsg_getValue(SimpleIntMsg* this); + + +struct SimpleIntMsg +{ + NetMessage netMessage; + + int value; +}; + +extern const struct NetMessageOps SimpleIntMsg_Ops; + +void SimpleIntMsg_init(SimpleIntMsg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleIntMsg_Ops); +} + +void SimpleIntMsg_initFromValue(SimpleIntMsg* this, unsigned short msgType, int value) +{ + SimpleIntMsg_init(this, msgType); + + this->value = value; +} + +int SimpleIntMsg_getValue(SimpleIntMsg* this) +{ + return this->value; +} + + +#endif /*SIMPLEINTMSG_H_*/ diff --git a/client_module/source/common/net/message/SimpleIntStringMsg.c b/client_module/source/common/net/message/SimpleIntStringMsg.c new file mode 100644 index 0000000..7a4c48e --- /dev/null +++ b/client_module/source/common/net/message/SimpleIntStringMsg.c @@ -0,0 +1,31 @@ +#include "SimpleIntStringMsg.h" + +const struct NetMessageOps SimpleIntStringMsg_Ops = { + .serializePayload = SimpleIntStringMsg_serializePayload, + .deserializePayload = SimpleIntStringMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleIntStringMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SimpleIntStringMsg* thisCast = (SimpleIntStringMsg*)this; + + Serialization_serializeInt(ctx, thisCast->intValue); + Serialization_serializeStr(ctx, thisCast->strValueLen, thisCast->strValue); +} + +bool SimpleIntStringMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SimpleIntStringMsg* thisCast = (SimpleIntStringMsg*)this; + + // intValue + if(!Serialization_deserializeInt(ctx, &thisCast->intValue) ) + return false; + + // strValue + if(!Serialization_deserializeStr(ctx, &thisCast->strValueLen, &thisCast->strValue) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/SimpleIntStringMsg.h b/client_module/source/common/net/message/SimpleIntStringMsg.h new file mode 100644 index 0000000..af34785 --- /dev/null +++ b/client_module/source/common/net/message/SimpleIntStringMsg.h @@ -0,0 +1,69 @@ +#ifndef SIMPLEINTSTRINGMSG_H_ +#define SIMPLEINTSTRINGMSG_H_ + +#include "NetMessage.h" + +struct SimpleIntStringMsg; +typedef struct SimpleIntStringMsg SimpleIntStringMsg; + +static inline void SimpleIntStringMsg_init(SimpleIntStringMsg* this, unsigned short msgType); +static inline void SimpleIntStringMsg_initFromValue(SimpleIntStringMsg* this, unsigned short msgType, + int intValue, const char* strValue); + +// virtual functions +extern void SimpleIntStringMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleIntStringMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline int SimpleIntStringMsg_getIntValue(SimpleIntStringMsg* this); +static inline const char* SimpleIntStringMsg_getStrValue(SimpleIntStringMsg* this); + + +/** + * Simple message containing an integer value and a string (e.g. int error code and human-readable + * explantion with more details as string). + */ +struct SimpleIntStringMsg +{ + NetMessage netMessage; + + int intValue; + + const char* strValue; + unsigned strValueLen; +}; + +extern const struct NetMessageOps SimpleIntStringMsg_Ops; + +void SimpleIntStringMsg_init(SimpleIntStringMsg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleIntStringMsg_Ops); +} + +/** + * @param strValue just a reference, so don't free or modify it while this msg is used. + */ +void SimpleIntStringMsg_initFromValue(SimpleIntStringMsg* this, unsigned short msgType, + int intValue, const char* strValue) +{ + SimpleIntStringMsg_init(this, msgType); + + this->intValue = intValue; + + this->strValue = strValue; + this->strValueLen = strlen(strValue); +} + +int SimpleIntStringMsg_getIntValue(SimpleIntStringMsg* this) +{ + return this->intValue; +} + +const char* SimpleIntStringMsg_getStrValue(SimpleIntStringMsg* this) +{ + return this->strValue; +} + + + +#endif /* SIMPLEINTSTRINGMSG_H_ */ diff --git a/client_module/source/common/net/message/SimpleMsg.c b/client_module/source/common/net/message/SimpleMsg.c new file mode 100644 index 0000000..629a4be --- /dev/null +++ b/client_module/source/common/net/message/SimpleMsg.c @@ -0,0 +1,19 @@ +#include "SimpleMsg.h" + +const struct NetMessageOps SimpleMsg_Ops = { + .serializePayload = SimpleMsg_serializePayload, + .deserializePayload = SimpleMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + // nothing to be done here for simple messages +} + +bool SimpleMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + // nothing to be done here for simple messages + return true; +} diff --git a/client_module/source/common/net/message/SimpleMsg.h b/client_module/source/common/net/message/SimpleMsg.h new file mode 100644 index 0000000..edbfa99 --- /dev/null +++ b/client_module/source/common/net/message/SimpleMsg.h @@ -0,0 +1,33 @@ +#ifndef SIMPLEMSG_H_ +#define SIMPLEMSG_H_ + +#include "NetMessage.h" + +/** + * Note: Simple messages are defined by the header (resp. the msgType) only and + * require no additional data + */ + +struct SimpleMsg; +typedef struct SimpleMsg SimpleMsg; + +static inline void SimpleMsg_init(SimpleMsg* this, unsigned short msgType); + +// virtual functions +extern void SimpleMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + + +struct SimpleMsg +{ + NetMessage netMessage; +}; + +extern const struct NetMessageOps SimpleMsg_Ops; + +void SimpleMsg_init(SimpleMsg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleMsg_Ops); +} + +#endif /*SIMPLEMSG_H_*/ diff --git a/client_module/source/common/net/message/SimpleStringMsg.c b/client_module/source/common/net/message/SimpleStringMsg.c new file mode 100644 index 0000000..6a7938d --- /dev/null +++ b/client_module/source/common/net/message/SimpleStringMsg.c @@ -0,0 +1,22 @@ +#include "SimpleStringMsg.h" + +const struct NetMessageOps SimpleStringMsg_Ops = { + .serializePayload = SimpleStringMsg_serializePayload, + .deserializePayload = SimpleStringMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleStringMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SimpleStringMsg* thisCast = (SimpleStringMsg*)this; + + Serialization_serializeStr(ctx, thisCast->valueLen, thisCast->value); +} + +bool SimpleStringMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SimpleStringMsg* thisCast = (SimpleStringMsg*)this; + + return Serialization_deserializeStr(ctx, &thisCast->valueLen, &thisCast->value); +} diff --git a/client_module/source/common/net/message/SimpleStringMsg.h b/client_module/source/common/net/message/SimpleStringMsg.h new file mode 100644 index 0000000..46f728e --- /dev/null +++ b/client_module/source/common/net/message/SimpleStringMsg.h @@ -0,0 +1,48 @@ +#ifndef SIMPLESTRINGMSG_H_ +#define SIMPLESTRINGMSG_H_ + +#include "NetMessage.h" + +struct SimpleStringMsg; +typedef struct SimpleStringMsg SimpleStringMsg; + +static inline void SimpleStringMsg_init(SimpleStringMsg* this, unsigned short msgType); +static inline void SimpleStringMsg_initFromValue(SimpleStringMsg* this, unsigned short msgType, + const char* value); + +// virtual functions +extern void SimpleStringMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleStringMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline const char* SimpleStringMsg_getValue(SimpleStringMsg* this); + +struct SimpleStringMsg +{ + NetMessage netMessage; + + const char* value; + unsigned valueLen; +}; + +extern const struct NetMessageOps SimpleStringMsg_Ops; + +void SimpleStringMsg_init(SimpleStringMsg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleStringMsg_Ops); +} + +void SimpleStringMsg_initFromValue(SimpleStringMsg* this, unsigned short msgType, const char* value) +{ + SimpleStringMsg_init(this, msgType); + + this->value = value; + this->valueLen = strlen(value); +} + +const char* SimpleStringMsg_getValue(SimpleStringMsg* this) +{ + return this->value; +} + +#endif /* SIMPLESTRINGMSG_H_ */ diff --git a/client_module/source/common/net/message/SimpleUInt16Msg.c b/client_module/source/common/net/message/SimpleUInt16Msg.c new file mode 100644 index 0000000..d5d3bea --- /dev/null +++ b/client_module/source/common/net/message/SimpleUInt16Msg.c @@ -0,0 +1,22 @@ +#include "SimpleUInt16Msg.h" + +const struct NetMessageOps SimpleUInt16Msg_Ops = { + .serializePayload = SimpleUInt16Msg_serializePayload, + .deserializePayload = SimpleUInt16Msg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void SimpleUInt16Msg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SimpleUInt16Msg* thisCast = (SimpleUInt16Msg*)this; + + Serialization_serializeUShort(ctx, thisCast->value); +} + +bool SimpleUInt16Msg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SimpleUInt16Msg* thisCast = (SimpleUInt16Msg*)this; + + return Serialization_deserializeUShort(ctx, &thisCast->value); +} diff --git a/client_module/source/common/net/message/SimpleUInt16Msg.h b/client_module/source/common/net/message/SimpleUInt16Msg.h new file mode 100644 index 0000000..d411856 --- /dev/null +++ b/client_module/source/common/net/message/SimpleUInt16Msg.h @@ -0,0 +1,48 @@ +#ifndef SIMPLEUINT16MSG_H_ +#define SIMPLEUINT16MSG_H_ + +#include "NetMessage.h" + +struct SimpleUInt16Msg; +typedef struct SimpleUInt16Msg SimpleUInt16Msg; + +static inline void SimpleUInt16Msg_init(SimpleUInt16Msg* this, unsigned short msgType); +static inline void SimpleUInt16Msg_initFromValue(SimpleUInt16Msg* this, unsigned short msgType, + uint16_t value); + +// virtual functions +extern void SimpleUInt16Msg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool SimpleUInt16Msg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline uint16_t SimpleUInt16Msg_getValue(SimpleUInt16Msg* this); + + +struct SimpleUInt16Msg +{ + NetMessage netMessage; + + uint16_t value; +}; + +extern const struct NetMessageOps SimpleUInt16Msg_Ops; + +void SimpleUInt16Msg_init(SimpleUInt16Msg* this, unsigned short msgType) +{ + NetMessage_init(&this->netMessage, msgType, &SimpleUInt16Msg_Ops); +} + +void SimpleUInt16Msg_initFromValue(SimpleUInt16Msg* this, unsigned short msgType, uint16_t value) +{ + SimpleUInt16Msg_init(this, msgType); + + this->value = value; +} + +uint16_t SimpleUInt16Msg_getValue(SimpleUInt16Msg* this) +{ + return this->value; +} + + +#endif /* SIMPLEUINT16MSG_H_ */ diff --git a/client_module/source/common/net/message/control/AckMsgEx.h b/client_module/source/common/net/message/control/AckMsgEx.h new file mode 100644 index 0000000..9126a70 --- /dev/null +++ b/client_module/source/common/net/message/control/AckMsgEx.h @@ -0,0 +1,40 @@ +#ifndef ACKMSGEX_H_ +#define ACKMSGEX_H_ + +#include "../SimpleStringMsg.h" + + +struct AckMsgEx; +typedef struct AckMsgEx AckMsgEx; + +static inline void AckMsgEx_init(AckMsgEx* this); +static inline void AckMsgEx_initFromValue(AckMsgEx* this, + const char* value); + +// getters & setters +static inline const char* AckMsgEx_getValue(AckMsgEx* this); + + +struct AckMsgEx +{ + SimpleStringMsg simpleStringMsg; +}; + + +void AckMsgEx_init(AckMsgEx* this) +{ + SimpleStringMsg_init( (SimpleStringMsg*)this, NETMSGTYPE_Ack); +} + +void AckMsgEx_initFromValue(AckMsgEx* this, const char* value) +{ + SimpleStringMsg_initFromValue( (SimpleStringMsg*)this, NETMSGTYPE_Ack, value); +} + +const char* AckMsgEx_getValue(AckMsgEx* this) +{ + return SimpleStringMsg_getValue( (SimpleStringMsg*)this); +} + + +#endif /* ACKMSGEX_H_ */ diff --git a/client_module/source/common/net/message/control/AuthenticateChannelMsg.h b/client_module/source/common/net/message/control/AuthenticateChannelMsg.h new file mode 100644 index 0000000..58eda38 --- /dev/null +++ b/client_module/source/common/net/message/control/AuthenticateChannelMsg.h @@ -0,0 +1,32 @@ +#ifndef AUTHENTICATECHANNELMSG_H_ +#define AUTHENTICATECHANNELMSG_H_ + +#include + + +struct AuthenticateChannelMsg; +typedef struct AuthenticateChannelMsg AuthenticateChannelMsg; + + +static inline void AuthenticateChannelMsg_init(AuthenticateChannelMsg* this); +static inline void AuthenticateChannelMsg_initFromValue(AuthenticateChannelMsg* this, + uint64_t authHash); + + +struct AuthenticateChannelMsg +{ + SimpleInt64Msg simpleInt64Msg; +}; + + +void AuthenticateChannelMsg_init(AuthenticateChannelMsg* this) +{ + SimpleInt64Msg_init( (SimpleInt64Msg*)this, NETMSGTYPE_AuthenticateChannel); +} + +void AuthenticateChannelMsg_initFromValue(AuthenticateChannelMsg* this, uint64_t authHash) +{ + SimpleInt64Msg_initFromValue( (SimpleInt64Msg*)this, NETMSGTYPE_AuthenticateChannel, authHash); +} + +#endif /* AUTHENTICATECHANNELMSG_H_ */ diff --git a/client_module/source/common/net/message/control/GenericResponseMsg.h b/client_module/source/common/net/message/control/GenericResponseMsg.h new file mode 100644 index 0000000..6d7dc25 --- /dev/null +++ b/client_module/source/common/net/message/control/GenericResponseMsg.h @@ -0,0 +1,65 @@ +#ifndef GENERICRESPONSEMSG_H_ +#define GENERICRESPONSEMSG_H_ + +#include + + +/** + * Note: Remember to keep this in sync with userspace common lib. + */ +enum GenericRespMsgCode +{ + GenericRespMsgCode_TRYAGAIN = 0, /* requestor shall try again in a few seconds */ + GenericRespMsgCode_INDIRECTCOMMERR = 1, /* indirect communication error and requestor should + try again (e.g. msg forwarding to other server failed) */ + GenericRespMsgCode_NEWSEQNOBASE = 2, /* client has restarted its seq# sequence. server provides + the new starting seq#. */ +}; +typedef enum GenericRespMsgCode GenericRespMsgCode; + + +struct GenericResponseMsg; +typedef struct GenericResponseMsg GenericResponseMsg; + + +static inline void GenericResponseMsg_init(GenericResponseMsg* this); + +// getters & setters +static inline GenericRespMsgCode GenericResponseMsg_getControlCode(GenericResponseMsg* this); +static inline const char* GenericResponseMsg_getLogStr(GenericResponseMsg* this); + + +/** + * A generic response that can be sent as a reply to any message. This special control message will + * be handled internally by the requestors MessageTk::requestResponse...() method. + * + * This is intended to submit internal information (like asking for a communication retry) to the + * requestors MessageTk through the control code. So the MessageTk caller on the requestor side + * will never actually see this GenericResponseMsg. + * + * The message string is intended to provide additional human-readable information like why this + * control code was submitted instead of the actually expected response msg. + */ +struct GenericResponseMsg +{ + SimpleIntStringMsg simpleIntStringMsg; +}; + + +void GenericResponseMsg_init(GenericResponseMsg* this) +{ + SimpleIntStringMsg_init( (SimpleIntStringMsg*)this, NETMSGTYPE_GenericResponse); +} + +GenericRespMsgCode GenericResponseMsg_getControlCode(GenericResponseMsg* this) +{ + return (GenericRespMsgCode)SimpleIntStringMsg_getIntValue( (SimpleIntStringMsg*)this); +} + +const char* GenericResponseMsg_getLogStr(GenericResponseMsg* this) +{ + return SimpleIntStringMsg_getStrValue( (SimpleIntStringMsg*)this); +} + + +#endif /* GENERICRESPONSEMSG_H_ */ diff --git a/client_module/source/common/net/message/control/PeerInfoMsg.c b/client_module/source/common/net/message/control/PeerInfoMsg.c new file mode 100644 index 0000000..896b74c --- /dev/null +++ b/client_module/source/common/net/message/control/PeerInfoMsg.c @@ -0,0 +1,29 @@ +#include "PeerInfoMsg.h" + +static void PeerInfoMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + PeerInfoMsg* thisCast = (PeerInfoMsg*)this; + + Serialization_serializeUInt(ctx, thisCast->type); + NumNodeID_serialize(ctx, &thisCast->id); +} + +static bool PeerInfoMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + PeerInfoMsg* thisCast = (PeerInfoMsg*)this; + + unsigned type = 0; + bool result = + Serialization_deserializeUInt(ctx, &type) + && NumNodeID_deserialize(ctx, &thisCast->id); + + thisCast->type = type; + return result; +} + +const struct NetMessageOps PeerInfoMsg_Ops = { + .serializePayload = PeerInfoMsg_serializePayload, + .deserializePayload = PeerInfoMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; diff --git a/client_module/source/common/net/message/control/PeerInfoMsg.h b/client_module/source/common/net/message/control/PeerInfoMsg.h new file mode 100644 index 0000000..ee0b6b1 --- /dev/null +++ b/client_module/source/common/net/message/control/PeerInfoMsg.h @@ -0,0 +1,28 @@ +#ifndef COMMON_NET_MESSAGE_CONTROL_PEERINFOMSG_H +#define COMMON_NET_MESSAGE_CONTROL_PEERINFOMSG_H + +#include +#include + +struct PeerInfoMsg; +typedef struct PeerInfoMsg PeerInfoMsg; + +struct PeerInfoMsg +{ + NetMessage netMessage; + + NodeType type; + NumNodeID id; +}; + +extern const struct NetMessageOps PeerInfoMsg_Ops; + +static inline void PeerInfoMsg_init(PeerInfoMsg* this, NodeType type, NumNodeID id) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_PeerInfo, &PeerInfoMsg_Ops); + + this->type = type; + this->id = id; +} + +#endif diff --git a/client_module/source/common/net/message/control/SetChannelDirectMsg.h b/client_module/source/common/net/message/control/SetChannelDirectMsg.h new file mode 100644 index 0000000..56cb92d --- /dev/null +++ b/client_module/source/common/net/message/control/SetChannelDirectMsg.h @@ -0,0 +1,29 @@ +#ifndef SETCHANNELDIRECTMSG_H_ +#define SETCHANNELDIRECTMSG_H_ + +#include + + +struct SetChannelDirectMsg; +typedef struct SetChannelDirectMsg SetChannelDirectMsg; + +static inline void SetChannelDirectMsg_init(SetChannelDirectMsg* this); +static inline void SetChannelDirectMsg_initFromValue(SetChannelDirectMsg* this, int value); + +struct SetChannelDirectMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void SetChannelDirectMsg_init(SetChannelDirectMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_SetChannelDirect); +} + +void SetChannelDirectMsg_initFromValue(SetChannelDirectMsg* this, int value) +{ + SimpleIntMsg_initFromValue( (SimpleIntMsg*)this, NETMSGTYPE_SetChannelDirect, value); +} + +#endif /*SETCHANNELDIRECTMSG_H_*/ diff --git a/client_module/source/common/net/message/helperd/GetHostByNameMsg.c b/client_module/source/common/net/message/helperd/GetHostByNameMsg.c new file mode 100644 index 0000000..0814e60 --- /dev/null +++ b/client_module/source/common/net/message/helperd/GetHostByNameMsg.c @@ -0,0 +1,29 @@ +#include +#include +#include "GetHostByNameMsg.h" + +const struct NetMessageOps GetHostByNameMsg_Ops = { + .serializePayload = GetHostByNameMsg_serializePayload, + .deserializePayload = GetHostByNameMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void GetHostByNameMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + GetHostByNameMsg* thisCast = (GetHostByNameMsg*)this; + + // hostname + Serialization_serializeStr(ctx, thisCast->hostnameLen, thisCast->hostname); +} + +bool GetHostByNameMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetHostByNameMsg* thisCast = (GetHostByNameMsg*)this; + + // hostname + if(!Serialization_deserializeStr(ctx, &thisCast->hostnameLen, &thisCast->hostname) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/helperd/GetHostByNameMsg.h b/client_module/source/common/net/message/helperd/GetHostByNameMsg.h new file mode 100644 index 0000000..c99f0d6 --- /dev/null +++ b/client_module/source/common/net/message/helperd/GetHostByNameMsg.h @@ -0,0 +1,45 @@ +#ifndef GETHOSTBYNAMEMSG_H_ +#define GETHOSTBYNAMEMSG_H_ + +#include + + +struct GetHostByNameMsg; +typedef struct GetHostByNameMsg GetHostByNameMsg; + +static inline void GetHostByNameMsg_init(GetHostByNameMsg* this); +static inline void GetHostByNameMsg_initFromHostname(GetHostByNameMsg* this, + const char* hostname); + +// virtual functions +extern void GetHostByNameMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool GetHostByNameMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +struct GetHostByNameMsg +{ + NetMessage netMessage; + + unsigned hostnameLen; + const char* hostname; +}; + +extern const struct NetMessageOps GetHostByNameMsg_Ops; + +void GetHostByNameMsg_init(GetHostByNameMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetHostByName, &GetHostByNameMsg_Ops); +} + +/** + * @param hostname just a reference, so do not free it as long as you use this object! + */ +void GetHostByNameMsg_initFromHostname(GetHostByNameMsg* this, + const char* hostname) +{ + GetHostByNameMsg_init(this); + + this->hostname = hostname; + this->hostnameLen = strlen(hostname); +} + +#endif /*GETHOSTBYNAMEMSG_H_*/ diff --git a/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.c b/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.c new file mode 100644 index 0000000..3ba8440 --- /dev/null +++ b/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.c @@ -0,0 +1,21 @@ +#include +#include +#include "GetHostByNameRespMsg.h" + +const struct NetMessageOps GetHostByNameRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetHostByNameRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool GetHostByNameRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetHostByNameRespMsg* thisCast = (GetHostByNameRespMsg*)this; + + // hostAddr + if(!Serialization_deserializeStr(ctx, &thisCast->hostAddrLen, &thisCast->hostAddr) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.h b/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.h new file mode 100644 index 0000000..c630f36 --- /dev/null +++ b/client_module/source/common/net/message/helperd/GetHostByNameRespMsg.h @@ -0,0 +1,39 @@ +#ifndef GETHOSTBYNAMERESPMSG_H_ +#define GETHOSTBYNAMERESPMSG_H_ + +#include + + +struct GetHostByNameRespMsg; +typedef struct GetHostByNameRespMsg GetHostByNameRespMsg; + +static inline void GetHostByNameRespMsg_init(GetHostByNameRespMsg* this); + +// virtual functions +extern bool GetHostByNameRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline const char* GetHostByNameRespMsg_getHostAddr(GetHostByNameRespMsg* this); + + +struct GetHostByNameRespMsg +{ + NetMessage netMessage; + + unsigned hostAddrLen; + const char* hostAddr; +}; + +extern const struct NetMessageOps GetHostByNameRespMsg_Ops; + +void GetHostByNameRespMsg_init(GetHostByNameRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetHostByNameResp, &GetHostByNameRespMsg_Ops); +} + +const char* GetHostByNameRespMsg_getHostAddr(GetHostByNameRespMsg* this) +{ + return this->hostAddr; +} + +#endif /*GETHOSTBYNAMERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/helperd/LogMsg.c b/client_module/source/common/net/message/helperd/LogMsg.c new file mode 100644 index 0000000..95cebea --- /dev/null +++ b/client_module/source/common/net/message/helperd/LogMsg.c @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include "LogMsg.h" + + +const struct NetMessageOps LogMsg_Ops = { + .serializePayload = LogMsg_serializePayload, + .deserializePayload = LogMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void LogMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + LogMsg* thisCast = (LogMsg*)this; + + // level + Serialization_serializeInt(ctx, thisCast->level); + + // threadID + Serialization_serializeInt(ctx, thisCast->threadID); + + // threadName + Serialization_serializeStr(ctx, thisCast->threadNameLen, thisCast->threadName); + + // context + Serialization_serializeStr(ctx, thisCast->contextLen, thisCast->context); + + // logMsg + Serialization_serializeStr(ctx, thisCast->logMsgLen, thisCast->logMsg); +} + +bool LogMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + LogMsg* thisCast = (LogMsg*)this; + + // level + if(!Serialization_deserializeInt(ctx, &thisCast->level) ) + return false; + + // threadID + if(!Serialization_deserializeInt(ctx, &thisCast->threadID) ) + return false; + + // threadName + if(!Serialization_deserializeStr(ctx, &thisCast->threadNameLen, &thisCast->threadName) ) + return false; + + // context + if(!Serialization_deserializeStr(ctx, &thisCast->contextLen, &thisCast->context) ) + return false; + + // logMsg + if(!Serialization_deserializeStr(ctx, &thisCast->logMsgLen, &thisCast->logMsg) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/helperd/LogMsg.h b/client_module/source/common/net/message/helperd/LogMsg.h new file mode 100644 index 0000000..233e656 --- /dev/null +++ b/client_module/source/common/net/message/helperd/LogMsg.h @@ -0,0 +1,62 @@ +#ifndef LOGMSG_H_ +#define LOGMSG_H_ + +#include + + +struct LogMsg; +typedef struct LogMsg LogMsg; + +static inline void LogMsg_init(LogMsg* this); +static inline void LogMsg_initFromEntry(LogMsg* this, int level, + int threadID, const char* threadName, const char* context, const char* logMsg); + +// virtual functions +extern void LogMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool LogMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + + +struct LogMsg +{ + NetMessage netMessage; + + int level; + int threadID; + unsigned threadNameLen; + const char* threadName; + unsigned contextLen; + const char* context; + unsigned logMsgLen; + const char* logMsg; +}; + +extern const struct NetMessageOps LogMsg_Ops; + +void LogMsg_init(LogMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_Log, &LogMsg_Ops); +} + +/** + * @param context just a reference, so do not free it as long as you use this object! + * @param logMsg just a reference, so do not free it as long as you use this object! + */ +void LogMsg_initFromEntry(LogMsg* this, int level, int threadID, const char* threadName, + const char* context, const char* logMsg) +{ + LogMsg_init(this); + + this->level = level; + this->threadID = threadID; + + this->threadName = threadName; + this->threadNameLen = strlen(threadName); + + this->context = context; + this->contextLen = strlen(context); + + this->logMsg = logMsg; + this->logMsgLen = strlen(logMsg); +} + +#endif /*LOGMSG_H_*/ diff --git a/client_module/source/common/net/message/helperd/LogRespMsg.h b/client_module/source/common/net/message/helperd/LogRespMsg.h new file mode 100644 index 0000000..53d7fa5 --- /dev/null +++ b/client_module/source/common/net/message/helperd/LogRespMsg.h @@ -0,0 +1,32 @@ +#ifndef LOGRESPMSG_H_ +#define LOGRESPMSG_H_ + +#include + + +struct LogRespMsg; +typedef struct LogRespMsg LogRespMsg; + +static inline void LogRespMsg_init(LogRespMsg* this); + +// getters & setters +static inline int LogRespMsg_getValue(LogRespMsg* this); + +struct LogRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void LogRespMsg_init(LogRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_LogResp); +} + +int LogRespMsg_getValue(LogRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + +#endif /*LOGRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h b/client_module/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h new file mode 100644 index 0000000..8cf6d9b --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h @@ -0,0 +1,21 @@ +#ifndef GETMIRRORBUDDYGROUPSMSG_H +#define GETMIRRORBUDDYGROUPSMSG_H + +#include + +struct GetMirrorBuddyGroupsMsg; +typedef struct GetMirrorBuddyGroupsMsg GetMirrorBuddyGroupsMsg; + +static inline void GetMirrorBuddyGroupsMsg_init(GetMirrorBuddyGroupsMsg* this, NodeType nodeType); + +struct GetMirrorBuddyGroupsMsg +{ + SimpleIntMsg simpleIntMsg; +}; + +void GetMirrorBuddyGroupsMsg_init(GetMirrorBuddyGroupsMsg* this, NodeType nodeType) +{ + SimpleIntMsg_initFromValue( (SimpleIntMsg*)this, NETMSGTYPE_GetMirrorBuddyGroups, nodeType); +} + +#endif /* GETMIRRORBUDDYGROUPSMSG_H */ diff --git a/client_module/source/common/net/message/nodes/GetNodesMsg.h b/client_module/source/common/net/message/nodes/GetNodesMsg.h new file mode 100644 index 0000000..ce4f49b --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetNodesMsg.h @@ -0,0 +1,32 @@ +#ifndef GETNODESMSG_H_ +#define GETNODESMSG_H_ + +#include + + +struct GetNodesMsg; +typedef struct GetNodesMsg GetNodesMsg; + +static inline void GetNodesMsg_init(GetNodesMsg* this); +static inline void GetNodesMsg_initFromValue(GetNodesMsg* this, int nodeType); + +struct GetNodesMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void GetNodesMsg_init(GetNodesMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_GetNodes); +} + +/** + * @param nodeType NODETYPE_... + */ +void GetNodesMsg_initFromValue(GetNodesMsg* this, int nodeType) +{ + SimpleIntMsg_initFromValue( (SimpleIntMsg*)this, NETMSGTYPE_GetNodes, nodeType); +} + +#endif /* GETNODESMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/GetNodesRespMsg.c b/client_module/source/common/net/message/nodes/GetNodesRespMsg.c new file mode 100644 index 0000000..76fab7a --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetNodesRespMsg.c @@ -0,0 +1,30 @@ +#include "GetNodesRespMsg.h" + +const struct NetMessageOps GetNodesRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetNodesRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + + +bool GetNodesRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetNodesRespMsg* thisCast = (GetNodesRespMsg*)this; + + // nodeList + if(!Serialization_deserializeNodeListPreprocess(ctx, &thisCast->rawNodeList) ) + return false; + + // rootNumID + if(!NumNodeID_deserialize(ctx, &thisCast->rootNumID) ) + return false; + + // rootIsBuddyMirrored + if(!Serialization_deserializeBool(ctx, &thisCast->rootIsBuddyMirrored) ) + return false; + + return true; +} + + diff --git a/client_module/source/common/net/message/nodes/GetNodesRespMsg.h b/client_module/source/common/net/message/nodes/GetNodesRespMsg.h new file mode 100644 index 0000000..8a5cf15 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetNodesRespMsg.h @@ -0,0 +1,62 @@ +#ifndef GETNODESRESPMSG_H_ +#define GETNODESRESPMSG_H_ + +#include +#include + +/* + * note: serialization not implemented + */ +struct GetNodesRespMsg; +typedef struct GetNodesRespMsg GetNodesRespMsg; + +static inline void GetNodesRespMsg_init(GetNodesRespMsg* this); + +// virtual functions +extern bool GetNodesRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// inliners +static inline void GetNodesRespMsg_parseNodeList(App* app, GetNodesRespMsg* this, + NodeList* outNodeList); + +// getters & setters +static inline NumNodeID GetNodesRespMsg_getRootNumID(GetNodesRespMsg* this); +static inline bool GetNodesRespMsg_getRootIsBuddyMirrored(GetNodesRespMsg* this); + + +struct GetNodesRespMsg +{ + NetMessage netMessage; + + NumNodeID rootNumID; + bool rootIsBuddyMirrored; + + // for deserialization + RawList rawNodeList; +}; + +extern const struct NetMessageOps GetNodesRespMsg_Ops; + +void GetNodesRespMsg_init(GetNodesRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetNodesResp, &GetNodesRespMsg_Ops); + + this->rootNumID = (NumNodeID){0}; +} + +void GetNodesRespMsg_parseNodeList(App* app, GetNodesRespMsg* this, NodeList* outNodeList) +{ + Serialization_deserializeNodeList(app, &this->rawNodeList, outNodeList); +} + +NumNodeID GetNodesRespMsg_getRootNumID(GetNodesRespMsg* this) +{ + return this->rootNumID; +} + +bool GetNodesRespMsg_getRootIsBuddyMirrored(GetNodesRespMsg* this) +{ + return this->rootIsBuddyMirrored; +} + +#endif /* GETNODESRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.c b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.c new file mode 100644 index 0000000..dcc38ce --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.c @@ -0,0 +1,27 @@ +#include "GetStatesAndBuddyGroupsMsg.h" + +static void GetStatesAndBuddyGroupsMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + GetStatesAndBuddyGroupsMsg* thisCast = (GetStatesAndBuddyGroupsMsg*)this; + + Serialization_serializeInt(ctx, thisCast->nodeType); + NumNodeID_serialize(ctx, &thisCast->requestedByClientID); +} + +static bool GetStatesAndBuddyGroupsMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetStatesAndBuddyGroupsMsg* thisCast = (GetStatesAndBuddyGroupsMsg*)this; + + bool result = + Serialization_deserializeInt(ctx, (int32_t*)&thisCast->nodeType) + && NumNodeID_deserialize(ctx, &thisCast->requestedByClientID); + + return result; +} + +const struct NetMessageOps GetStatesAndBuddyGroupsMsg_Ops = { + .serializePayload = GetStatesAndBuddyGroupsMsg_serializePayload, + .deserializePayload = GetStatesAndBuddyGroupsMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; diff --git a/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h new file mode 100644 index 0000000..6481847 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h @@ -0,0 +1,34 @@ +#ifndef GETSTATESANDBUDDYGROUPSMSG_H_ +#define GETSTATESANDBUDDYGROUPSMSG_H_ + +#include +#include "common/nodes/NumNodeID.h" +#include + +struct GetStatesAndBuddyGroupsMsg; +typedef struct GetStatesAndBuddyGroupsMsg GetStatesAndBuddyGroupsMsg; + + +static inline void GetStatesAndBuddyGroupsMsg_init(GetStatesAndBuddyGroupsMsg* this, + NodeType nodeType, NumNodeID requestedByClientID); + + +struct GetStatesAndBuddyGroupsMsg +{ + NetMessage netMessage; + + NodeType nodeType; + NumNodeID requestedByClientID; +}; + +extern const struct NetMessageOps GetStatesAndBuddyGroupsMsg_Ops; + +void GetStatesAndBuddyGroupsMsg_init(GetStatesAndBuddyGroupsMsg* this, NodeType nodeType, NumNodeID requestedByClientID) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetStatesAndBuddyGroups, &GetStatesAndBuddyGroupsMsg_Ops); + + this->nodeType = nodeType; + this->requestedByClientID = requestedByClientID; +} + +#endif /* GETSTATESANDBUDDYGROUPSMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.c b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.c new file mode 100644 index 0000000..e928244 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.c @@ -0,0 +1,33 @@ +#include "GetStatesAndBuddyGroupsRespMsg.h" + +static void GetStatesAndBuddyGroupsRespMsg_release(NetMessage* msg) +{ + GetStatesAndBuddyGroupsRespMsg* this = container_of(msg, struct GetStatesAndBuddyGroupsRespMsg, + netMessage); + + BEEGFS_KFREE_LIST(&this->groups, struct BuddyGroupMapping, _list); + BEEGFS_KFREE_LIST(&this->states, struct TargetStateMapping, _list); +} + +const struct NetMessageOps GetStatesAndBuddyGroupsRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetStatesAndBuddyGroupsRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .release = GetStatesAndBuddyGroupsRespMsg_release, +}; + +bool GetStatesAndBuddyGroupsRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetStatesAndBuddyGroupsRespMsg* thisCast = (GetStatesAndBuddyGroupsRespMsg*)this; + + if (!BuddyGroupMappingList_deserialize(ctx, &thisCast->groups)) + return false; + + if (!TargetStateMappingList_deserialize(ctx, &thisCast->states)) + return false; + + return true; +} + + diff --git a/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h new file mode 100644 index 0000000..379eeee --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h @@ -0,0 +1,43 @@ +#ifndef GETSTATESANDBUDDYGROUPSRESPMSG_H_ +#define GETSTATESANDBUDDYGROUPSRESPMSG_H_ + +#include +#include +#include + + +struct GetStatesAndBuddyGroupsRespMsg; +typedef struct GetStatesAndBuddyGroupsRespMsg GetStatesAndBuddyGroupsRespMsg; + +static inline void GetStatesAndBuddyGroupsRespMsg_init(GetStatesAndBuddyGroupsRespMsg* this); + +// virtual functions +extern bool GetStatesAndBuddyGroupsRespMsg_deserializePayload(NetMessage* this, + DeserializeCtx* ctx); + +/** + * This message carries two maps: + * 1) buddyGroupID -> primaryTarget, secondaryTarget + * 2) targetID -> targetReachabilityState, targetConsistencyState + * + * Note: This message can only be received/deserialized (send/serialization not implemented). + */ +struct GetStatesAndBuddyGroupsRespMsg +{ + NetMessage netMessage; + + struct list_head groups; /* struct BuddyGroupMapping */ + struct list_head states; /* struct TargetStateMapping */ +}; +extern const struct NetMessageOps GetStatesAndBuddyGroupsRespMsg_Ops; + +void GetStatesAndBuddyGroupsRespMsg_init(GetStatesAndBuddyGroupsRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetStatesAndBuddyGroupsResp, + &GetStatesAndBuddyGroupsRespMsg_Ops); + + INIT_LIST_HEAD(&this->groups); + INIT_LIST_HEAD(&this->states); +} + +#endif /* GETSTATESANDBUDDYGROUPSRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/GetTargetMappingsMsg.h b/client_module/source/common/net/message/nodes/GetTargetMappingsMsg.h new file mode 100644 index 0000000..d6a6e47 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetTargetMappingsMsg.h @@ -0,0 +1,24 @@ +#ifndef GETTARGETMAPPINGSMSG_H_ +#define GETTARGETMAPPINGSMSG_H_ + +#include "../SimpleMsg.h" + + +struct GetTargetMappingsMsg; +typedef struct GetTargetMappingsMsg GetTargetMappingsMsg; + +static inline void GetTargetMappingsMsg_init(GetTargetMappingsMsg* this); + + +struct GetTargetMappingsMsg +{ + SimpleMsg simpleMsg; +}; + + +void GetTargetMappingsMsg_init(GetTargetMappingsMsg* this) +{ + SimpleMsg_init( (SimpleMsg*)this, NETMSGTYPE_GetTargetMappings); +} + +#endif /* GETTARGETMAPPINGSMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.c b/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.c new file mode 100644 index 0000000..e06ba86 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.c @@ -0,0 +1,28 @@ +#include "GetTargetMappingsRespMsg.h" + +#include + +static void GetTargetMappingsRespMsg_release(NetMessage* this) +{ + GetTargetMappingsRespMsg* thisCast = (GetTargetMappingsRespMsg*)this; + + BEEGFS_KFREE_LIST(&thisCast->mappings, struct TargetMapping, _list); +} + +bool GetTargetMappingsRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetTargetMappingsRespMsg* thisCast = (GetTargetMappingsRespMsg*)this; + + if (!TargetMappingList_deserialize(ctx, &thisCast->mappings)) + return false; + + return true; +} + +const struct NetMessageOps GetTargetMappingsRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetTargetMappingsRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .release = GetTargetMappingsRespMsg_release, +}; diff --git a/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.h b/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.h new file mode 100644 index 0000000..62c6e57 --- /dev/null +++ b/client_module/source/common/net/message/nodes/GetTargetMappingsRespMsg.h @@ -0,0 +1,38 @@ +#ifndef GETTARGETMAPPINGSRESPMSG_H_ +#define GETTARGETMAPPINGSRESPMSG_H_ + +#include +#include + +/** + * Note: This message can only be received/deserialized (send/serialization not implemented) + */ + + +struct GetTargetMappingsRespMsg; +typedef struct GetTargetMappingsRespMsg GetTargetMappingsRespMsg; + +static inline void GetTargetMappingsRespMsg_init(GetTargetMappingsRespMsg* this); + +// virtual functions +extern bool GetTargetMappingsRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + + +struct GetTargetMappingsRespMsg +{ + NetMessage netMessage; + + struct list_head mappings; /* TargetMapping */ +}; + +extern const struct NetMessageOps GetTargetMappingsRespMsg_Ops; + +void GetTargetMappingsRespMsg_init(GetTargetMappingsRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetTargetMappingsResp, + &GetTargetMappingsRespMsg_Ops); + + INIT_LIST_HEAD(&this->mappings); +} + +#endif /* GETTARGETMAPPINGSRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/HeartbeatMsgEx.c b/client_module/source/common/net/message/nodes/HeartbeatMsgEx.c new file mode 100644 index 0000000..b3ea933 --- /dev/null +++ b/client_module/source/common/net/message/nodes/HeartbeatMsgEx.c @@ -0,0 +1,257 @@ +#include +#include +#include +#include +#include +#include +#include +#include "HeartbeatMsgEx.h" + +const struct NetMessageOps HeartbeatMsgEx_Ops = { + .serializePayload = HeartbeatMsgEx_serializePayload, + .deserializePayload = HeartbeatMsgEx_deserializePayload, + .processIncoming = __HeartbeatMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void HeartbeatMsgEx_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + HeartbeatMsgEx* thisCast = (HeartbeatMsgEx*)this; + + // instanceVersion + Serialization_serializeUInt64(ctx, thisCast->instanceVersion); + + // nicListVersion + Serialization_serializeUInt64(ctx, thisCast->nicListVersion); + + // nodeType + Serialization_serializeInt(ctx, thisCast->nodeType); + + // nodeID + Serialization_serializeStr(ctx, thisCast->nodeIDLen, thisCast->nodeID); + + // ackID + Serialization_serializeStrAlign4(ctx, thisCast->ackIDLen, thisCast->ackID); + + // nodeNumID + NumNodeID_serialize(ctx, &thisCast->nodeNumID); + + // rootNumID + NumNodeID_serialize(ctx, &thisCast->rootNumID); + + // rootIsBuddyMirrored + Serialization_serializeBool(ctx, thisCast->rootIsBuddyMirrored); + + // portUDP + Serialization_serializeUShort(ctx, thisCast->portUDP); + + // portTCP + Serialization_serializeUShort(ctx, thisCast->portTCP); + + // nicList + Serialization_serializeNicList(ctx, thisCast->nicList); + + // machineUUID + Serialization_serializeStr(ctx, thisCast->machineUUIDLen, thisCast->machineUUID); +} + +bool HeartbeatMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + HeartbeatMsgEx* thisCast = (HeartbeatMsgEx*)this; + + // instanceVersion + if(!Serialization_deserializeUInt64(ctx, &thisCast->instanceVersion) ) + return false; + + // nicListVersion + if(!Serialization_deserializeUInt64(ctx, &thisCast->nicListVersion) ) + return false; + + // nodeType + if(!Serialization_deserializeInt(ctx, &thisCast->nodeType) ) + return false; + + // nodeID + if(!Serialization_deserializeStr(ctx, &thisCast->nodeIDLen, &thisCast->nodeID) ) + return false; + + // ackID + if(!Serialization_deserializeStrAlign4(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + // nodeNumID + if(!NumNodeID_deserialize(ctx, &thisCast->nodeNumID) ) + return false; + + // rootNumID + if(!NumNodeID_deserialize(ctx, &thisCast->rootNumID) ) + return false; + + // rootIsBuddyMirrored + if(!Serialization_deserializeBool(ctx, &thisCast->rootIsBuddyMirrored) ) + return false; + + // portUDP + if(!Serialization_deserializeUShort(ctx, &thisCast->portUDP) ) + return false; + + // portTCP + if(!Serialization_deserializeUShort(ctx, &thisCast->portTCP) ) + return false; + + // nicList + if(!Serialization_deserializeNicListPreprocess(ctx, &thisCast->rawNicList) ) + return false; + + // machineUUID + if(!Serialization_deserializeStr(ctx, &thisCast->machineUUIDLen, &thisCast->machineUUID) ) + return false; + + return true; +} + +bool __HeartbeatMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Heartbeat incoming"; + + HeartbeatMsgEx* thisCast = (HeartbeatMsgEx*)this; + + NicAddressList nicList; + Node* node; + NodeConnPool* connPool; + Node* localNode; + NicAddressList localNicList; + NicListCapabilities localNicCaps; + NodeStoreEx* nodes = NULL; + bool isNodeNew; + NumNodeID nodeNumID; + int nodeType; + + // check if nodeNumID is set + nodeNumID = HeartbeatMsgEx_getNodeNumID(thisCast); + if(NumNodeID_isZero(&nodeNumID)) + { // shouldn't happen: this node would need to register first to get a nodeNumID assigned + Logger_logFormatted(log, Log_WARNING, logContext, + "Rejecting heartbeat of node without numeric ID: %s (Type: %s)", + HeartbeatMsgEx_getNodeID(thisCast), + Node_nodeTypeToStr(HeartbeatMsgEx_getNodeType(thisCast) ) ); + + goto ack_resp; + } + + // find the corresponding node store for this node type + + nodeType = HeartbeatMsgEx_getNodeType(thisCast); + switch(nodeType) + { + case NODETYPE_Meta: + nodes = App_getMetaNodes(app); break; + + case NODETYPE_Storage: + nodes = App_getStorageNodes(app); break; + + case NODETYPE_Mgmt: + nodes = App_getMgmtNodes(app); break; + + default: + { + const char* nodeID = HeartbeatMsgEx_getNodeID(thisCast); + + Logger_logErrFormatted(log, logContext, "Invalid node type: %d (%s); ID: %s", + nodeType, Node_nodeTypeToStr(nodeType), nodeID); + + goto ack_resp; + } break; + } + + // construct node + + NicAddressList_init(&nicList); + + HeartbeatMsgEx_parseNicList(thisCast, &nicList); + + App_lockNicList(app); + node = Node_construct(app, + HeartbeatMsgEx_getNodeID(thisCast), HeartbeatMsgEx_getNodeNumID(thisCast), + HeartbeatMsgEx_getPortUDP(thisCast), HeartbeatMsgEx_getPortTCP(thisCast), &nicList, + nodeType == NODETYPE_Meta || nodeType == NODETYPE_Storage? App_getLocalRDMANicListLocked(app) : NULL); + // (will belong to the NodeStore => no destruct() required) + App_unlockNicList(app); + + Node_setNodeAliasAndType(node, NULL, nodeType); + + // set local nic capabilities + + localNode = App_getLocalNode(app); + Node_cloneNicList(localNode, &localNicList); + connPool = Node_getConnPool(node); + + NIC_supportedCapabilities(&localNicList, &localNicCaps); + NodeConnPool_setLocalNicCaps(connPool, &localNicCaps); + + // add node to store (or update it) + + isNodeNew = NodeStoreEx_addOrUpdateNode(nodes, &node); + if(isNodeNew) + { + bool supportsRDMA = NIC_supportsRDMA(&nicList); + + Logger_logFormatted(log, Log_WARNING, logContext, + "New node: %s %s [ID: %hu]; %s", + Node_nodeTypeToStr(nodeType), + HeartbeatMsgEx_getNodeID(thisCast), + HeartbeatMsgEx_getNodeNumID(thisCast).value, + (supportsRDMA ? "RDMA; " : "") ); + } + + __HeartbeatMsgEx_processIncomingRoot(thisCast, app); + +ack_resp: + // send ack + MsgHelperAck_respondToAckRequest(app, HeartbeatMsgEx_getAckID(thisCast), fromAddr, sock, + respBuf, bufLen); + + // clean-up + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + ListTk_kfreeNicAddressListElems(&localNicList); + NicAddressList_uninit(&localNicList); + + return true; +} + +/** + * Handles the contained root information. + */ +void __HeartbeatMsgEx_processIncomingRoot(HeartbeatMsgEx* this, App* app) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Heartbeat incoming (root)"; + + NodeStoreEx* metaNodes; + bool setRootRes; + NodeOrGroup rootOwner = this->rootIsBuddyMirrored + ? NodeOrGroup_fromGroup(this->rootNumID.value) + : NodeOrGroup_fromNode(this->rootNumID); + NumNodeID rootNumID = HeartbeatMsgEx_getRootNumID(this); + + // check whether root info is defined + if( (HeartbeatMsgEx_getNodeType(this) != NODETYPE_Meta) || (NumNodeID_isZero(&rootNumID))) + return; + + // try to apply the contained root info + + metaNodes = App_getMetaNodes(app); + + setRootRes = NodeStoreEx_setRootOwner(metaNodes, rootOwner, false); + + if(setRootRes) + { // found the very first root + Logger_logFormatted(log, Log_CRITICAL, logContext, "Root (by Heartbeat): %hu", + HeartbeatMsgEx_getRootNumID(this).value ); + } + +} + diff --git a/client_module/source/common/net/message/nodes/HeartbeatMsgEx.h b/client_module/source/common/net/message/nodes/HeartbeatMsgEx.h new file mode 100644 index 0000000..a5ba002 --- /dev/null +++ b/client_module/source/common/net/message/nodes/HeartbeatMsgEx.h @@ -0,0 +1,152 @@ +#ifndef HEARTBEATMSG_H_ +#define HEARTBEATMSG_H_ + +#include +#include + + +struct HeartbeatMsgEx; +typedef struct HeartbeatMsgEx HeartbeatMsgEx; + +static inline void HeartbeatMsgEx_init(HeartbeatMsgEx* this); +static inline void HeartbeatMsgEx_initFromNodeData(HeartbeatMsgEx* this, + const char* nodeID, NumNodeID nodeNumID, int nodeType, NicAddressList* nicList); + +extern void __HeartbeatMsgEx_processIncomingRoot(HeartbeatMsgEx* this, struct App* app); + +// virtual functions +extern void HeartbeatMsgEx_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool HeartbeatMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __HeartbeatMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// inliners +static inline void HeartbeatMsgEx_parseNicList(HeartbeatMsgEx* this, + NicAddressList* outNicList); + +// getters & setters +static inline const char* HeartbeatMsgEx_getNodeID(HeartbeatMsgEx* this); +static inline NumNodeID HeartbeatMsgEx_getNodeNumID(HeartbeatMsgEx* this); +static inline int HeartbeatMsgEx_getNodeType(HeartbeatMsgEx* this); +static inline NumNodeID HeartbeatMsgEx_getRootNumID(HeartbeatMsgEx* this); +static inline const char* HeartbeatMsgEx_getAckID(HeartbeatMsgEx* this); +static inline void HeartbeatMsgEx_setPorts(HeartbeatMsgEx* this, + uint16_t portUDP, uint16_t portTCP); +static inline uint16_t HeartbeatMsgEx_getPortUDP(HeartbeatMsgEx* this); +static inline uint16_t HeartbeatMsgEx_getPortTCP(HeartbeatMsgEx* this); + + +struct HeartbeatMsgEx +{ + NetMessage netMessage; + + unsigned nodeIDLen; + const char* nodeID; + int nodeType; + NumNodeID nodeNumID; + NumNodeID rootNumID; // 0 means unknown/undefined + bool rootIsBuddyMirrored; + uint64_t instanceVersion; // not used currently + uint64_t nicListVersion; // not used currently + uint16_t portUDP; // 0 means "undefined" + uint16_t portTCP; // 0 means "undefined" + unsigned ackIDLen; + const char* ackID; + const char* machineUUID; + unsigned machineUUIDLen; + + // for serialization + NicAddressList* nicList; // not owned by this object + + // for deserialization + RawList rawNicList; +}; + +extern const struct NetMessageOps HeartbeatMsgEx_Ops; + +void HeartbeatMsgEx_init(HeartbeatMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_Heartbeat, &HeartbeatMsgEx_Ops); +} + +/** + * @param nodeID just a reference, so do not free it as long as you use this object + * @param nicList just a reference, so do not free it as long as you use this object + */ +void HeartbeatMsgEx_initFromNodeData(HeartbeatMsgEx* this, + const char* nodeID, NumNodeID nodeNumID, int nodeType, NicAddressList* nicList) +{ + HeartbeatMsgEx_init(this); + + this->nodeID = nodeID; + this->nodeIDLen = strlen(nodeID); + this->nodeNumID = nodeNumID; + + this->nodeType = nodeType; + + this->rootNumID = (NumNodeID){0}; // 0 means undefined/unknown + this->rootIsBuddyMirrored = false; + + this->instanceVersion = 0; // reserverd for future use + + this->nicListVersion = 0; // reserverd for future use + this->nicList = nicList; + + this->portUDP = 0; // 0 means "undefined" + this->portTCP = 0; // 0 means "undefined" + + this->ackID = ""; + this->ackIDLen = 0; + + this->machineUUID = ""; // not currently needed on the client + this->machineUUIDLen = 0; +} + +void HeartbeatMsgEx_parseNicList(HeartbeatMsgEx* this, NicAddressList* outNicList) +{ + Serialization_deserializeNicList(&this->rawNicList, outNicList); +} + +const char* HeartbeatMsgEx_getNodeID(HeartbeatMsgEx* this) +{ + return this->nodeID; +} + +NumNodeID HeartbeatMsgEx_getNodeNumID(HeartbeatMsgEx* this) +{ + return this->nodeNumID; +} + +int HeartbeatMsgEx_getNodeType(HeartbeatMsgEx* this) +{ + return this->nodeType; +} + +NumNodeID HeartbeatMsgEx_getRootNumID(HeartbeatMsgEx* this) +{ + return this->rootNumID; +} + +const char* HeartbeatMsgEx_getAckID(HeartbeatMsgEx* this) +{ + return this->ackID; +} + +void HeartbeatMsgEx_setPorts(HeartbeatMsgEx* this, uint16_t portUDP, uint16_t portTCP) +{ + this->portUDP = portUDP; + this->portTCP = portTCP; +} + +uint16_t HeartbeatMsgEx_getPortUDP(HeartbeatMsgEx* this) +{ + return this->portUDP; +} + +uint16_t HeartbeatMsgEx_getPortTCP(HeartbeatMsgEx* this) +{ + return this->portTCP; +} + + +#endif /*HEARTBEATMSG_H_*/ diff --git a/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.c b/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.c new file mode 100644 index 0000000..4c0d2d7 --- /dev/null +++ b/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.c @@ -0,0 +1,65 @@ +#include +#include +#include +#include +#include "HeartbeatMsgEx.h" +#include "HeartbeatRequestMsgEx.h" + + +const struct NetMessageOps HeartbeatRequestMsgEx_Ops = { + .serializePayload = SimpleMsg_serializePayload, + .deserializePayload = SimpleMsg_deserializePayload, + .processIncoming = __HeartbeatRequestMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool __HeartbeatRequestMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "HeartbeatRequest incoming"; + + Config* cfg = App_getConfig(app); + Node* localNode = App_getLocalNode(app); + NodeString alias; + NumNodeID localNodeNumID = Node_getNumID(localNode); + NicAddressList nicList; + + HeartbeatMsgEx hbMsg; + unsigned respLen; + bool serializeRes; + ssize_t sendRes; + + Node_cloneNicList(localNode, &nicList); + Node_copyAlias(localNode, &alias); + HeartbeatMsgEx_initFromNodeData(&hbMsg, alias.buf, localNodeNumID, NODETYPE_Client, &nicList); + HeartbeatMsgEx_setPorts(&hbMsg, Config_getConnClientPort(cfg), 0); + + respLen = NetMessage_getMsgLength( (NetMessage*)&hbMsg); + serializeRes = NetMessage_serialize( (NetMessage*)&hbMsg, respBuf, bufLen); + if(unlikely(!serializeRes) ) + { + Logger_logErrFormatted(log, logContext, "Unable to serialize response"); + goto err_uninit; + } + + if(fromAddr) + { // datagram => sync via dgramLis send method + DatagramListener* dgramLis = App_getDatagramListener(app); + sendRes = DatagramListener_sendto_kernel(dgramLis, respBuf, respLen, 0, fromAddr); + } + else + sendRes = Socket_sendto_kernel(sock, respBuf, respLen, 0, NULL); + + if(unlikely(sendRes <= 0) ) + Logger_logErrFormatted(log, logContext, "Send error. ErrCode: %lld", (long long)sendRes); + +err_uninit: + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + + return true; +} + + diff --git a/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.h b/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.h new file mode 100644 index 0000000..f20b84f --- /dev/null +++ b/client_module/source/common/net/message/nodes/HeartbeatRequestMsgEx.h @@ -0,0 +1,30 @@ +#ifndef HEARTBEATREQUESTMSGEX_H_ +#define HEARTBEATREQUESTMSGEX_H_ + +#include "../SimpleMsg.h" + + +struct HeartbeatRequestMsgEx; +typedef struct HeartbeatRequestMsgEx HeartbeatRequestMsgEx; + +static inline void HeartbeatRequestMsgEx_init(HeartbeatRequestMsgEx* this); + +// virtual functions +extern bool __HeartbeatRequestMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + + +struct HeartbeatRequestMsgEx +{ + SimpleMsg simpleMsg; +}; + +extern const struct NetMessageOps HeartbeatRequestMsgEx_Ops; + +void HeartbeatRequestMsgEx_init(HeartbeatRequestMsgEx* this) +{ + SimpleMsg_init(&this->simpleMsg, NETMSGTYPE_HeartbeatRequest); + this->simpleMsg.netMessage.ops = &HeartbeatRequestMsgEx_Ops; +} + +#endif /* HEARTBEATREQUESTMSGEX_H_ */ diff --git a/client_module/source/common/net/message/nodes/MapTargetsMsgEx.c b/client_module/source/common/net/message/nodes/MapTargetsMsgEx.c new file mode 100644 index 0000000..d4625d8 --- /dev/null +++ b/client_module/source/common/net/message/nodes/MapTargetsMsgEx.c @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include +#include +#include +#include "MapTargetsMsgEx.h" + + +static void MapTargetsMsgEx_release(NetMessage* msg) +{ + MapTargetsMsgEx* this = container_of(msg, struct MapTargetsMsgEx, netMessage); + + BEEGFS_KFREE_LIST(&this->poolMappings, struct TargetPoolMapping, _list); +} + +const struct NetMessageOps MapTargetsMsgEx_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = MapTargetsMsgEx_deserializePayload, + .processIncoming = __MapTargetsMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .release = MapTargetsMsgEx_release, +}; + +bool MapTargetsMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + MapTargetsMsgEx* thisCast = (MapTargetsMsgEx*)this; + + // targets + if (!TargetPoolMappingList_deserialize(ctx, &thisCast->poolMappings)) + return false; + + // nodeID + if(!NumNodeID_deserialize(ctx, &thisCast->nodeID) ) + return false; + + // ackID + if(!Serialization_deserializeStr(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + return true; +} + +bool __MapTargetsMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "MapTargetsMsg incoming"; + + MapTargetsMsgEx* thisCast = (MapTargetsMsgEx*)this; + + const char* peer; + + TargetMapper* targetMapper = App_getTargetMapper(app); + NumNodeID nodeID = MapTargetsMsgEx_getNodeID(thisCast); + struct TargetPoolMapping* mapping; + + + peer = fromAddr ? + SocketTk_ipaddrToStr(fromAddr->addr) : StringTk_strDup(Socket_getPeername(sock) ); + LOG_DEBUG_FORMATTED(log, 4, logContext, "Received a MapTargetsMsg from: %s", peer); + kfree(peer); + + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + + list_for_each_entry(mapping, &thisCast->poolMappings, _list) + { + bool wasNewTarget = TargetMapper_mapTarget(targetMapper, mapping->targetID, nodeID); + if(wasNewTarget) + { + LOG_DEBUG_FORMATTED(log, Log_WARNING, logContext, "Mapping target %hu => node %hu", + mapping->targetID, nodeID.value); + } + } + + // send ack + MsgHelperAck_respondToAckRequest(app, MapTargetsMsgEx_getAckID(thisCast), fromAddr, sock, + respBuf, bufLen); + + return true; +} + diff --git a/client_module/source/common/net/message/nodes/MapTargetsMsgEx.h b/client_module/source/common/net/message/nodes/MapTargetsMsgEx.h new file mode 100644 index 0000000..757d7aa --- /dev/null +++ b/client_module/source/common/net/message/nodes/MapTargetsMsgEx.h @@ -0,0 +1,60 @@ +#ifndef MAPTARGETSMSGEX_H_ +#define MAPTARGETSMSGEX_H_ + +#include +#include +#include + +/** + * Note: Only the receive/deserialize part of this message is implemented (so it cannot be sent). + * Note: Processing only sends response when ackID is set (no MapTargetsRespMsg implemented). + */ + +struct MapTargetsMsgEx; +typedef struct MapTargetsMsgEx MapTargetsMsgEx; + +static inline void MapTargetsMsgEx_init(MapTargetsMsgEx* this); + +// virtual functions +extern bool MapTargetsMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __MapTargetsMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// getters & setters +static inline NumNodeID MapTargetsMsgEx_getNodeID(MapTargetsMsgEx* this); +static inline const char* MapTargetsMsgEx_getAckID(MapTargetsMsgEx* this); + + +struct MapTargetsMsgEx +{ + NetMessage netMessage; + + NumNodeID nodeID; + unsigned ackIDLen; + const char* ackID; + + struct list_head poolMappings; /* struct TargetPoolMapping */ +}; + +extern const struct NetMessageOps MapTargetsMsgEx_Ops; + +void MapTargetsMsgEx_init(MapTargetsMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_MapTargets, &MapTargetsMsgEx_Ops); + + INIT_LIST_HEAD(&this->poolMappings); +} + +NumNodeID MapTargetsMsgEx_getNodeID(MapTargetsMsgEx* this) +{ + return this->nodeID; +} + +const char* MapTargetsMsgEx_getAckID(MapTargetsMsgEx* this) +{ + return this->ackID; +} + + + +#endif /* MAPTARGETSMSGEX_H_ */ diff --git a/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.c b/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.c new file mode 100644 index 0000000..d4fc6ae --- /dev/null +++ b/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.c @@ -0,0 +1,53 @@ +#include +#include +#include +#include + +#include "RefreshTargetStatesMsgEx.h" + + +const struct NetMessageOps RefreshTargetStatesMsgEx_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = RefreshTargetStatesMsgEx_deserializePayload, + .processIncoming = __RefreshTargetStatesMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool RefreshTargetStatesMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + RefreshTargetStatesMsgEx* thisCast = (RefreshTargetStatesMsgEx*)this; + + // ackID + if(!Serialization_deserializeStr(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + return true; +} + +bool __RefreshTargetStatesMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "RefreshTargetStatesMsg incoming"; + + RefreshTargetStatesMsgEx* thisCast = (RefreshTargetStatesMsgEx*)this; + + const char* peer; + + InternodeSyncer* internodeSyncer = App_getInternodeSyncer(app); + InternodeSyncer_setForceTargetStatesUpdate(internodeSyncer); + + peer = fromAddr ? + SocketTk_ipaddrToStr(fromAddr->addr) : StringTk_strDup(Socket_getPeername(sock) ); + LOG_DEBUG_FORMATTED(log, 4, logContext, "Received a RefreshTargetStatesMsg from: %s", peer); + kfree(peer); + + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + + // send ack + MsgHelperAck_respondToAckRequest(app, RefreshTargetStatesMsgEx_getAckID(thisCast), fromAddr, + sock, respBuf, bufLen); + + return true; +} diff --git a/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.h b/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.h new file mode 100644 index 0000000..f91d93c --- /dev/null +++ b/client_module/source/common/net/message/nodes/RefreshTargetStatesMsgEx.h @@ -0,0 +1,47 @@ +#ifndef REFRESHTARGETSTATESMSGEX_H_ +#define REFRESHTARGETSTATESMSGEX_H_ + +#include +#include + +/** + * Note: Only the receive/deserialize part of this message is implemented (so it cannot be sent). + * Note: Processing only sends response when ackID is set (no RefreshTargetStatesRespMsg + * implemented). + */ + +struct RefreshTargetStatesMsgEx; +typedef struct RefreshTargetStatesMsgEx RefreshTargetStatesMsgEx; + +static inline void RefreshTargetStatesMsgEx_init(RefreshTargetStatesMsgEx* this); + +// virtual functions +extern bool RefreshTargetStatesMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __RefreshTargetStatesMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// getters & setters +static inline const char* RefreshTargetStatesMsgEx_getAckID(RefreshTargetStatesMsgEx* this); + + +struct RefreshTargetStatesMsgEx +{ + NetMessage netMessage; + + unsigned ackIDLen; + const char* ackID; +}; + +extern const struct NetMessageOps RefreshTargetStatesMsgEx_Ops; + +void RefreshTargetStatesMsgEx_init(RefreshTargetStatesMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RefreshTargetStates, &RefreshTargetStatesMsgEx_Ops); +} + +const char* RefreshTargetStatesMsgEx_getAckID(RefreshTargetStatesMsgEx* this) +{ + return this->ackID; +} + +#endif /* REFRESHTARGETSTATESMSGEX_H_ */ diff --git a/client_module/source/common/net/message/nodes/RegisterNodeMsg.c b/client_module/source/common/net/message/nodes/RegisterNodeMsg.c new file mode 100644 index 0000000..399b619 --- /dev/null +++ b/client_module/source/common/net/message/nodes/RegisterNodeMsg.c @@ -0,0 +1,46 @@ +#include "RegisterNodeMsg.h" + +const struct NetMessageOps RegisterNodeMsg_Ops = { + .serializePayload = RegisterNodeMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void RegisterNodeMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RegisterNodeMsg* thisCast = (RegisterNodeMsg*)this; + + // instanceVersion + Serialization_serializeUInt64(ctx, thisCast->instanceVersion); + + // nicListVersion + Serialization_serializeUInt64(ctx, thisCast->nicListVersion); + + // nodeID + Serialization_serializeStr(ctx, thisCast->nodeIDLen, thisCast->nodeID); + + // nicList + Serialization_serializeNicList(ctx, thisCast->nicList); + + // nodeType + Serialization_serializeInt(ctx, thisCast->nodeType); + + // nodeNumID + NumNodeID_serialize(ctx, &thisCast->nodeNumID); + + // rootNumID + NumNodeID_serialize(ctx, &thisCast->rootNumID); + + // rootIsBuddyMirrored + Serialization_serializeBool(ctx, thisCast->rootIsBuddyMirrored); + + // portUDP + Serialization_serializeUShort(ctx, thisCast->portUDP); + + // portTCP + Serialization_serializeUShort(ctx, thisCast->portTCP); + + // machineUUID + Serialization_serializeStr(ctx, thisCast->machineUUIDLen, thisCast->machineUUID); +} diff --git a/client_module/source/common/net/message/nodes/RegisterNodeMsg.h b/client_module/source/common/net/message/nodes/RegisterNodeMsg.h new file mode 100644 index 0000000..18da24b --- /dev/null +++ b/client_module/source/common/net/message/nodes/RegisterNodeMsg.h @@ -0,0 +1,78 @@ +#ifndef REGISTERNODEMSG_H +#define REGISTERNODEMSG_H + +#include +#include +#include + +struct RegisterNodeMsg; +typedef struct RegisterNodeMsg RegisterNodeMsg; + +static inline void RegisterNodeMsg_init(RegisterNodeMsg* this); +static inline void RegisterNodeMsg_initFromNodeData(RegisterNodeMsg* this, const char* nodeID, + const NumNodeID nodeNumID, const int nodeType, NicAddressList* nicList, const uint16_t portUDP); + +// virtual functions +extern void RegisterNodeMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +struct RegisterNodeMsg +{ + NetMessage netMessage; + + const char* nodeID; // not owned by this object + unsigned nodeIDLen; + NumNodeID nodeNumID; + + NumNodeID rootNumID; + bool rootIsBuddyMirrored; + + int nodeType; + + NicAddressList* nicList; // not owned by this object + + uint16_t portUDP; + uint16_t portTCP; + + uint64_t instanceVersion; // not used currently + uint64_t nicListVersion; // not used currently + + const char* machineUUID; + unsigned machineUUIDLen; +}; + +extern const struct NetMessageOps RegisterNodeMsg_Ops; + +void RegisterNodeMsg_init(RegisterNodeMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RegisterNode, &RegisterNodeMsg_Ops); +} + +/** + * @param nodeID just a reference, so do not free it as long as you use this object + * @param nicList just a reference, so do not free it as long as you use this object + */ +void RegisterNodeMsg_initFromNodeData(RegisterNodeMsg* this, const char* nodeID, + const NumNodeID nodeNumID, const int nodeType, + NicAddressList* nicList, const uint16_t portUDP) +{ + RegisterNodeMsg_init(this); + + this->nodeID = nodeID; + this->nodeIDLen = strlen(nodeID); + this->nodeNumID = nodeNumID; + this->nodeType = nodeType; + this->nicList = nicList; + this->portUDP = portUDP; + + // not used in client, but general RegisterNodeMsg has it and expects it to be serialized + this->rootNumID = (NumNodeID){0}; + this->rootIsBuddyMirrored = false; + + this->nicListVersion = 0; // undefined + this->instanceVersion = 0; // undefined + this->portTCP = 0; // undefined + this->machineUUID = ""; // not currently needed on the client + this->machineUUIDLen = 0; +} + +#endif /*REGISTERNODEMSG_H*/ diff --git a/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.c b/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.c new file mode 100644 index 0000000..0afbea6 --- /dev/null +++ b/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.c @@ -0,0 +1,28 @@ +#include "RegisterNodeRespMsg.h" + +const struct NetMessageOps RegisterNodeRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, // serialization not implemented + .deserializePayload = RegisterNodeRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool RegisterNodeRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + RegisterNodeRespMsg* thisCast = (RegisterNodeRespMsg*)this; + + // nodeNumID + if(!NumNodeID_deserialize(ctx, &thisCast->nodeNumID) ) + return false; + + // GRPC Port + if(!Serialization_deserializeUShort(ctx, &thisCast->grpcPort) ) + return false; + + // fsUUID + if(!Serialization_deserializeStr(ctx, &thisCast->fsUUIDLen, + &thisCast->fsUUID) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.h b/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.h new file mode 100644 index 0000000..52d5159 --- /dev/null +++ b/client_module/source/common/net/message/nodes/RegisterNodeRespMsg.h @@ -0,0 +1,49 @@ +#ifndef REGISTERNODERESPMSG_H +#define REGISTERNODERESPMSG_H + +#include + +struct RegisterNodeRespMsg; +typedef struct RegisterNodeRespMsg RegisterNodeRespMsg; + +static inline void RegisterNodeRespMsg_init(RegisterNodeRespMsg* this); +static inline NumNodeID RegisterNodeRespMsg_getNodeNumID(RegisterNodeRespMsg* this); +static inline int RegisterNodeRespMsg_getGrpcPort(RegisterNodeRespMsg* this); +static inline const char* RegisterNodeRespMsg_getFsUUID(RegisterNodeRespMsg* this); + +// virtual functions +extern bool RegisterNodeRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +struct RegisterNodeRespMsg +{ + NetMessage netMessage; + + NumNodeID nodeNumID; + unsigned short grpcPort; + unsigned fsUUIDLen; + const char* fsUUID; +}; + +extern const struct NetMessageOps RegisterNodeRespMsg_Ops; + +void RegisterNodeRespMsg_init(RegisterNodeRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RegisterNodeResp, &RegisterNodeRespMsg_Ops); +} + +NumNodeID RegisterNodeRespMsg_getNodeNumID(RegisterNodeRespMsg* this) +{ + return this->nodeNumID; +} + +int RegisterNodeRespMsg_getGrpcPort(RegisterNodeRespMsg* this) +{ + return this->grpcPort; +} + +const char* RegisterNodeRespMsg_getFsUUID(RegisterNodeRespMsg* this) +{ + return this->fsUUID; +} + +#endif /*REGISTERNODERESPMSG_H*/ diff --git a/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.c b/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.c new file mode 100644 index 0000000..f754b4e --- /dev/null +++ b/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.c @@ -0,0 +1,126 @@ +#include +#include +#include +#include +#include +#include +#include "RemoveNodeRespMsg.h" +#include "RemoveNodeMsgEx.h" + + +const struct NetMessageOps RemoveNodeMsgEx_Ops = { + .serializePayload = RemoveNodeMsgEx_serializePayload, + .deserializePayload = RemoveNodeMsgEx_deserializePayload, + .processIncoming = __RemoveNodeMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void RemoveNodeMsgEx_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RemoveNodeMsgEx* thisCast = (RemoveNodeMsgEx*)this; + + // nodeType + Serialization_serializeShort(ctx, thisCast->nodeType); + + // nodeNumID + NumNodeID_serialize(ctx, &thisCast->nodeNumID); + + // ackID + Serialization_serializeStr(ctx, thisCast->ackIDLen, thisCast->ackID); +} + +bool RemoveNodeMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + RemoveNodeMsgEx* thisCast = (RemoveNodeMsgEx*)this; + + // nodeType + if(!Serialization_deserializeShort(ctx, &thisCast->nodeType) ) + return false; + + // nodeNumID + if(!NumNodeID_deserialize(ctx, &thisCast->nodeNumID) ) + return false; + + // ackID + if(!Serialization_deserializeStr(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + return true; +} + +bool __RemoveNodeMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "RemoveNodeMsg incoming"; + + RemoveNodeMsgEx* thisCast = (RemoveNodeMsgEx*)this; + + const char* peer; + NodeType nodeType = (NodeType)RemoveNodeMsgEx_getNodeType(thisCast); + NumNodeID nodeID = RemoveNodeMsgEx_getNodeNumID(thisCast); + + RemoveNodeRespMsg respMsg; + unsigned respLen; + bool serializeRes; + ssize_t sendRes; + + peer = fromAddr ? + SocketTk_ipaddrToStr(fromAddr->addr) : StringTk_strDup(Socket_getPeername(sock) ); + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, + "Received a RemoveNodeMsg from: %s; Node: %s %hu", + peer, Node_nodeTypeToStr(nodeType), nodeID.value); + kfree(peer); + + + if(nodeType == NODETYPE_Meta) + { + NodeStoreEx* nodes = App_getMetaNodes(app); + if(NodeStoreEx_deleteNode(nodes, nodeID) ) + Logger_logFormatted(log, Log_WARNING, logContext, "Removed metadata node: %hu", nodeID.value); + } + else + if(nodeType == NODETYPE_Storage) + { + NodeStoreEx* nodes = App_getStorageNodes(app); + if(NodeStoreEx_deleteNode(nodes, nodeID) ) + Logger_logFormatted(log, Log_WARNING, logContext, "Removed storage node: %hu", nodeID.value); + } + else + { // should never happen + Logger_logFormatted(log, Log_WARNING, logContext, + "Received removal request for invalid node type: %d (%s)", + (int)nodeType, Node_nodeTypeToStr(nodeType) ); + } + + + // send response + if(!MsgHelperAck_respondToAckRequest(app, RemoveNodeMsgEx_getAckID(thisCast), + fromAddr, sock, respBuf, bufLen) ) + { + RemoveNodeRespMsg_initFromValue(&respMsg, 0); + + respLen = NetMessage_getMsgLength( (NetMessage*)&respMsg); + serializeRes = NetMessage_serialize( (NetMessage*)&respMsg, respBuf, bufLen); + if(unlikely(!serializeRes) ) + { + Logger_logErrFormatted(log, logContext, "Unable to serialize response"); + goto err_resp_uninit; + } + + if(fromAddr) + { // datagram => sync via dgramLis send method + DatagramListener* dgramLis = App_getDatagramListener(app); + sendRes = DatagramListener_sendto_kernel(dgramLis, respBuf, respLen, 0, fromAddr); + } + else + sendRes = Socket_sendto_kernel(sock, respBuf, respLen, 0, NULL); + + if(unlikely(sendRes <= 0) ) + Logger_logErrFormatted(log, logContext, "Send error. ErrCode: %lld", (long long)sendRes); + } + +err_resp_uninit: + return true; +} + diff --git a/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.h b/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.h new file mode 100644 index 0000000..452857b --- /dev/null +++ b/client_module/source/common/net/message/nodes/RemoveNodeMsgEx.h @@ -0,0 +1,74 @@ +#ifndef REMOVENODEMSGEX_H_ +#define REMOVENODEMSGEX_H_ + +#include + + +struct RemoveNodeMsgEx; +typedef struct RemoveNodeMsgEx RemoveNodeMsgEx; + +static inline void RemoveNodeMsgEx_init(RemoveNodeMsgEx* this); +static inline void RemoveNodeMsgEx_initFromNodeData(RemoveNodeMsgEx* this, + NumNodeID nodeNumID, NodeType nodeType); + +// virtual functions +extern void RemoveNodeMsgEx_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern bool RemoveNodeMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __RemoveNodeMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// getters & setters +static inline NumNodeID RemoveNodeMsgEx_getNodeNumID(RemoveNodeMsgEx* this); +static inline NodeType RemoveNodeMsgEx_getNodeType(RemoveNodeMsgEx* this); +static inline const char* RemoveNodeMsgEx_getAckID(RemoveNodeMsgEx* this); + + +struct RemoveNodeMsgEx +{ + NetMessage netMessage; + + NumNodeID nodeNumID; + int16_t nodeType; + unsigned ackIDLen; + const char* ackID; +}; + +extern const struct NetMessageOps RemoveNodeMsgEx_Ops; + +void RemoveNodeMsgEx_init(RemoveNodeMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RemoveNode, &RemoveNodeMsgEx_Ops); +} + +/** + * @param nodeID just a reference, so do not free it as long as you use this object! + */ +void RemoveNodeMsgEx_initFromNodeData(RemoveNodeMsgEx* this, + NumNodeID nodeNumID, NodeType nodeType) +{ + RemoveNodeMsgEx_init(this); + + this->nodeNumID = nodeNumID; + + this->nodeType = (int16_t)nodeType; + + this->ackID = ""; + this->ackIDLen = 0; +} + +NumNodeID RemoveNodeMsgEx_getNodeNumID(RemoveNodeMsgEx* this) +{ + return this->nodeNumID; +} + +NodeType RemoveNodeMsgEx_getNodeType(RemoveNodeMsgEx* this) +{ + return (NodeType)this->nodeType; +} + +const char* RemoveNodeMsgEx_getAckID(RemoveNodeMsgEx* this) +{ + return this->ackID; +} + +#endif /* REMOVENODEMSGEX_H_ */ diff --git a/client_module/source/common/net/message/nodes/RemoveNodeRespMsg.h b/client_module/source/common/net/message/nodes/RemoveNodeRespMsg.h new file mode 100644 index 0000000..3ed8496 --- /dev/null +++ b/client_module/source/common/net/message/nodes/RemoveNodeRespMsg.h @@ -0,0 +1,29 @@ +#ifndef REMOVENODERESPMSG_H_ +#define REMOVENODERESPMSG_H_ + +#include + + +struct RemoveNodeRespMsg; +typedef struct RemoveNodeRespMsg RemoveNodeRespMsg; + +static inline void RemoveNodeRespMsg_init(RemoveNodeRespMsg* this); +static inline void RemoveNodeRespMsg_initFromValue(RemoveNodeRespMsg* this, int value); + +struct RemoveNodeRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void RemoveNodeRespMsg_init(RemoveNodeRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_RemoveNodeResp); +} + +void RemoveNodeRespMsg_initFromValue(RemoveNodeRespMsg* this, int value) +{ + SimpleIntMsg_initFromValue( (SimpleIntMsg*)this, NETMSGTYPE_RemoveNodeResp, value); +} + +#endif /* REMOVENODERESPMSG_H_ */ diff --git a/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.c b/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.c new file mode 100644 index 0000000..9e56fb2 --- /dev/null +++ b/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.c @@ -0,0 +1,103 @@ +#include +#include +#include + +#include "SetMirrorBuddyGroupMsgEx.h" + +const struct NetMessageOps SetMirrorBuddyGroupMsgEx_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = SetMirrorBuddyGroupMsgEx_deserializePayload, + .processIncoming = __SetMirrorBuddyGroupMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool SetMirrorBuddyGroupMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + SetMirrorBuddyGroupMsgEx* thisCast = (SetMirrorBuddyGroupMsgEx*)this; + + // nodeType + if (!Serialization_deserializeInt(ctx, &thisCast->nodeType) ) + return false; + + // primaryTargetID + if (!Serialization_deserializeUShort(ctx, &thisCast->primaryTargetID) ) + return false; + + // secondaryTargetID + if (!Serialization_deserializeUShort(ctx, &thisCast->secondaryTargetID) ) + return false; + + // buddyGroupID + if (!Serialization_deserializeUShort(ctx, &thisCast->buddyGroupID) ) + return false; + + // allowUpdate + if (!Serialization_deserializeBool(ctx, &thisCast->allowUpdate) ) + return false; + + // ackID + if (!Serialization_deserializeStr(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + return true; +} + +bool __SetMirrorBuddyGroupMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + MirrorBuddyGroupMapper* mirrorBuddyGroupMapper; + TargetMapper* targetMapper = NULL; + const char* logContext = "SetMirrorBuddyGroupMsg Incoming"; + FhgfsOpsErr addGroupResult; + + SetMirrorBuddyGroupMsgEx* thisCast = (SetMirrorBuddyGroupMsgEx*)this; + + const char* peer; + + uint16_t primaryTargetID = SetMirrorBuddyGroupMsgEx_getPrimaryTargetID(thisCast); + uint16_t secondaryTargetID = SetMirrorBuddyGroupMsgEx_getSecondaryTargetID(thisCast); + uint16_t buddyGroupID = SetMirrorBuddyGroupMsgEx_getBuddyGroupID(thisCast); + bool allowUpdate = SetMirrorBuddyGroupMsgEx_getAllowUpdate(thisCast); + + NodeType nodeType = SetMirrorBuddyGroupMsgEx_getNodeType(thisCast); + + switch (nodeType) + { + case NODETYPE_Storage: + mirrorBuddyGroupMapper = App_getStorageBuddyGroupMapper(app); + targetMapper = App_getTargetMapper(app); + break; + + case NODETYPE_Meta: + mirrorBuddyGroupMapper = App_getMetaBuddyGroupMapper(app); + break; + + default: + Logger_logErr(log, logContext, "Node type mismatch"); + return false; + } + + addGroupResult = MirrorBuddyGroupMapper_addGroup(mirrorBuddyGroupMapper, app->cfg, targetMapper, + buddyGroupID, primaryTargetID, secondaryTargetID, allowUpdate); + + if (addGroupResult == FhgfsOpsErr_SUCCESS) + Logger_logFormatted(log, Log_NOTICE, logContext, "Added mirror group: Node type: %s; " + "Group ID: %hu; Primary target ID: %hu; Secondary target ID: %hu.", + Node_nodeTypeToStr(nodeType), buddyGroupID, primaryTargetID, secondaryTargetID); + else + Logger_logFormatted(log, Log_WARNING, logContext, "Error adding mirror buddy group: " + "Node type: %s, Group ID: %hu; Primary target ID: %hu; Secondary target ID: %hu; " + "Error: %s", + Node_nodeTypeToStr(nodeType), buddyGroupID, primaryTargetID, secondaryTargetID, + FhgfsOpsErr_toErrString(addGroupResult) ); + + peer = fromAddr ? + SocketTk_ipaddrToStr(fromAddr->addr) : StringTk_strDup(Socket_getPeername(sock) ); + + // send Ack + MsgHelperAck_respondToAckRequest(app, SetMirrorBuddyGroupMsgEx_getAckID(thisCast), fromAddr, + sock, respBuf, bufLen); + + return true; +} diff --git a/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.h b/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.h new file mode 100644 index 0000000..5e8b57e --- /dev/null +++ b/client_module/source/common/net/message/nodes/SetMirrorBuddyGroupMsgEx.h @@ -0,0 +1,77 @@ +#ifndef SETMIRRORBUDDYGROUPMSGEX_H_ +#define SETMIRRORBUDDYGROUPMSGEX_H_ + +#include + + +struct SetMirrorBuddyGroupMsgEx; +typedef struct SetMirrorBuddyGroupMsgEx SetMirrorBuddyGroupMsgEx; + +static inline void SetMirrorBuddyGroupMsgEx_init(SetMirrorBuddyGroupMsgEx* this); + +// virtual functions +extern bool SetMirrorBuddyGroupMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __SetMirrorBuddyGroupMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// getters and setters +static inline NodeType SetMirrorBuddyGroupMsgEx_getNodeType(SetMirrorBuddyGroupMsgEx* this); +static inline uint16_t SetMirrorBuddyGroupMsgEx_getPrimaryTargetID(SetMirrorBuddyGroupMsgEx* this); +static inline uint16_t SetMirrorBuddyGroupMsgEx_getSecondaryTargetID(SetMirrorBuddyGroupMsgEx* this); +static inline uint16_t SetMirrorBuddyGroupMsgEx_getBuddyGroupID(SetMirrorBuddyGroupMsgEx* this); +static inline bool SetMirrorBuddyGroupMsgEx_getAllowUpdate(SetMirrorBuddyGroupMsgEx* this); +static inline const char* SetMirrorBuddyGroupMsgEx_getAckID(SetMirrorBuddyGroupMsgEx* this); + +struct SetMirrorBuddyGroupMsgEx +{ + NetMessage netMessage; + + int nodeType; + uint16_t primaryTargetID; + uint16_t secondaryTargetID; + uint16_t buddyGroupID; + bool allowUpdate; + + unsigned ackIDLen; + const char* ackID; +}; + +extern const struct NetMessageOps SetMirrorBuddyGroupMsgEx_Ops; + +void SetMirrorBuddyGroupMsgEx_init(SetMirrorBuddyGroupMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_SetMirrorBuddyGroup, + &SetMirrorBuddyGroupMsgEx_Ops); +} + +static inline NodeType SetMirrorBuddyGroupMsgEx_getNodeType(SetMirrorBuddyGroupMsgEx* this) +{ + return (NodeType)this->nodeType; +} + +static inline uint16_t SetMirrorBuddyGroupMsgEx_getPrimaryTargetID(SetMirrorBuddyGroupMsgEx* this) +{ + return this->primaryTargetID; +} + +static inline uint16_t SetMirrorBuddyGroupMsgEx_getSecondaryTargetID(SetMirrorBuddyGroupMsgEx* this) +{ + return this->secondaryTargetID; +} + +static inline uint16_t SetMirrorBuddyGroupMsgEx_getBuddyGroupID(SetMirrorBuddyGroupMsgEx* this) +{ + return this->buddyGroupID; +} + +static inline bool SetMirrorBuddyGroupMsgEx_getAllowUpdate(SetMirrorBuddyGroupMsgEx* this) +{ + return this->allowUpdate; +} + +const char* SetMirrorBuddyGroupMsgEx_getAckID(SetMirrorBuddyGroupMsgEx* this) +{ + return this->ackID; +} + +#endif /* SETMIRRORBUDDYGROUPMSGEX_H_ */ diff --git a/client_module/source/common/net/message/session/BumpFileVersion.c b/client_module/source/common/net/message/session/BumpFileVersion.c new file mode 100644 index 0000000..68ce8ad --- /dev/null +++ b/client_module/source/common/net/message/session/BumpFileVersion.c @@ -0,0 +1,19 @@ +#include "BumpFileVersion.h" + +static void BumpFileVersionMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + struct BumpFileVersionMsg* msg = container_of(this, struct BumpFileVersionMsg, netMessage); + + EntryInfo_serialize(ctx, msg->entryInfo); + + if (this->msgHeader.msgFeatureFlags & BUMPFILEVERSIONMSG_FLAG_HASEVENT) + FileEvent_serialize(ctx, msg->fileEvent); +} + +const struct NetMessageOps BumpFileVersionMsg_Ops = { + .serializePayload = BumpFileVersionMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; diff --git a/client_module/source/common/net/message/session/BumpFileVersion.h b/client_module/source/common/net/message/session/BumpFileVersion.h new file mode 100644 index 0000000..2fff7f2 --- /dev/null +++ b/client_module/source/common/net/message/session/BumpFileVersion.h @@ -0,0 +1,38 @@ +#ifndef BUMPFILEVERSIONMSG_H_ +#define BUMPFILEVERSIONMSG_H_ + +#include +#include +#include + +#define BUMPFILEVERSIONMSG_FLAG_PERSISTENT 1 /* change persistent file version number too */ +#define BUMPFILEVERSIONMSG_FLAG_HASEVENT 2 /* has a loggable event attached */ + +/** + * Note: This message supports only serialization, deserialization is not implemented. + */ +struct BumpFileVersionMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfo; + const struct FileEvent* fileEvent; +}; +extern const struct NetMessageOps BumpFileVersionMsg_Ops; + +static inline void BumpFileVersionMsg_init(struct BumpFileVersionMsg* this, + const EntryInfo* entryInfo, bool persistent, const struct FileEvent* fileEvent) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_BumpFileVersion, &BumpFileVersionMsg_Ops); + + this->entryInfo = entryInfo; + this->fileEvent = fileEvent; + + if (persistent) + this->netMessage.msgHeader.msgFeatureFlags |= BUMPFILEVERSIONMSG_FLAG_PERSISTENT; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= BUMPFILEVERSIONMSG_FLAG_HASEVENT; +} + +#endif diff --git a/client_module/source/common/net/message/session/BumpFileVersionResp.h b/client_module/source/common/net/message/session/BumpFileVersionResp.h new file mode 100644 index 0000000..726826e --- /dev/null +++ b/client_module/source/common/net/message/session/BumpFileVersionResp.h @@ -0,0 +1,19 @@ +#ifndef BUMPFILEVERSIONRESPMSG_H_ +#define BUMPFILEVERSIONRESPMSG_H_ + +#include "../SimpleIntMsg.h" + +typedef struct BumpFileVersionRespMsg BumpFileVersionRespMsg; + +struct BumpFileVersionRespMsg +{ + SimpleIntMsg base; +}; + + +static inline void BumpFileVersionRespMsg_init(struct BumpFileVersionRespMsg* this) +{ + SimpleIntMsg_init(&this->base, NETMSGTYPE_BumpFileVersionResp); +} + +#endif diff --git a/client_module/source/common/net/message/session/FSyncLocalFileMsg.c b/client_module/source/common/net/message/session/FSyncLocalFileMsg.c new file mode 100644 index 0000000..faf6dc9 --- /dev/null +++ b/client_module/source/common/net/message/session/FSyncLocalFileMsg.c @@ -0,0 +1,23 @@ +#include "FSyncLocalFileMsg.h" + + +const struct NetMessageOps FSyncLocalFileMsg_Ops = { + .serializePayload = FSyncLocalFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void FSyncLocalFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + FSyncLocalFileMsg* thisCast = (FSyncLocalFileMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // targetID + Serialization_serializeUShort(ctx, thisCast->targetID); +} diff --git a/client_module/source/common/net/message/session/FSyncLocalFileMsg.h b/client_module/source/common/net/message/session/FSyncLocalFileMsg.h new file mode 100644 index 0000000..c868785 --- /dev/null +++ b/client_module/source/common/net/message/session/FSyncLocalFileMsg.h @@ -0,0 +1,60 @@ +#ifndef FSYNCLOCALFILEMSG_H_ +#define FSYNCLOCALFILEMSG_H_ + +#include + + +#define FSYNCLOCALFILEMSG_FLAG_NO_SYNC 1 /* if a sync is not needed */ +#define FSYNCLOCALFILEMSG_FLAG_SESSION_CHECK 2 /* if session check should be done */ +#define FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + + +struct FSyncLocalFileMsg; +typedef struct FSyncLocalFileMsg FSyncLocalFileMsg; + +static inline void FSyncLocalFileMsg_init(FSyncLocalFileMsg* this); +static inline void FSyncLocalFileMsg_initFromSession(FSyncLocalFileMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID); + +// virtual functions +extern void FSyncLocalFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + + +/** + * Note: This message supports only serialization, deserialization is not implemented. + */ +struct FSyncLocalFileMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + uint16_t targetID; +}; +extern const struct NetMessageOps FSyncLocalFileMsg_Ops; + +void FSyncLocalFileMsg_init(FSyncLocalFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_FSyncLocalFile, &FSyncLocalFileMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ +void FSyncLocalFileMsg_initFromSession(FSyncLocalFileMsg* this, NumNodeID clientNumID, + const char* fileHandleID, uint16_t targetID) +{ + FSyncLocalFileMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; +} + +#endif /*FSYNCLOCALFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/session/FSyncLocalFileRespMsg.h b/client_module/source/common/net/message/session/FSyncLocalFileRespMsg.h new file mode 100644 index 0000000..7e5265b --- /dev/null +++ b/client_module/source/common/net/message/session/FSyncLocalFileRespMsg.h @@ -0,0 +1,33 @@ +#ifndef FSYNCLOCALFILERESPMSG_H_ +#define FSYNCLOCALFILERESPMSG_H_ + +#include "../SimpleInt64Msg.h" + + +struct FSyncLocalFileRespMsg; +typedef struct FSyncLocalFileRespMsg FSyncLocalFileRespMsg; + +static inline void FSyncLocalFileRespMsg_init(FSyncLocalFileRespMsg* this); + +// getters & setters +static inline int64_t FSyncLocalFileRespMsg_getValue(FSyncLocalFileRespMsg* this); + + +struct FSyncLocalFileRespMsg +{ + SimpleInt64Msg simpleInt64Msg; +}; + + +void FSyncLocalFileRespMsg_init(FSyncLocalFileRespMsg* this) +{ + SimpleInt64Msg_init( (SimpleInt64Msg*)this, NETMSGTYPE_FSyncLocalFileResp); +} + +int64_t FSyncLocalFileRespMsg_getValue(FSyncLocalFileRespMsg* this) +{ + return SimpleInt64Msg_getValue( (SimpleInt64Msg*)this); +} + + +#endif /*FSYNCLOCALFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/session/GetFileVersionMsg.c b/client_module/source/common/net/message/session/GetFileVersionMsg.c new file mode 100644 index 0000000..5972627 --- /dev/null +++ b/client_module/source/common/net/message/session/GetFileVersionMsg.c @@ -0,0 +1,15 @@ +#include "GetFileVersionMsg.h" + +static void GetFileVersionMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + struct GetFileVersionMsg* msg = container_of(this, struct GetFileVersionMsg, netMessage); + + EntryInfo_serialize(ctx, msg->entryInfo); +} + +const struct NetMessageOps GetFileVersionMsg_Ops = { + .serializePayload = GetFileVersionMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; diff --git a/client_module/source/common/net/message/session/GetFileVersionMsg.h b/client_module/source/common/net/message/session/GetFileVersionMsg.h new file mode 100644 index 0000000..d4e1e5d --- /dev/null +++ b/client_module/source/common/net/message/session/GetFileVersionMsg.h @@ -0,0 +1,26 @@ +#ifndef GETFILEVERSIONMSG_H_ +#define GETFILEVERSIONMSG_H_ + +#include +#include + +/** + * Note: This message supports only serialization, deserialization is not implemented. + */ +struct GetFileVersionMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfo; +}; +extern const struct NetMessageOps GetFileVersionMsg_Ops; + +static inline void GetFileVersionMsg_init(struct GetFileVersionMsg* this, + const EntryInfo* entryInfo) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetFileVersion, &GetFileVersionMsg_Ops); + + this->entryInfo = entryInfo; +} + +#endif diff --git a/client_module/source/common/net/message/session/GetFileVersionRespMsg.c b/client_module/source/common/net/message/session/GetFileVersionRespMsg.c new file mode 100644 index 0000000..19008d2 --- /dev/null +++ b/client_module/source/common/net/message/session/GetFileVersionRespMsg.c @@ -0,0 +1,22 @@ +#include "GetFileVersionRespMsg.h" + +static bool GetFileVersionRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + struct GetFileVersionRespMsg* msg = container_of(this, struct GetFileVersionRespMsg, base); + + int result; + + if (!Serialization_deserializeInt(ctx, &result) || + !Serialization_deserializeUInt(ctx, &msg->version)) + return false; + + msg->result = result; + return true; +} + +const struct NetMessageOps GetFileVersionRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetFileVersionRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; diff --git a/client_module/source/common/net/message/session/GetFileVersionRespMsg.h b/client_module/source/common/net/message/session/GetFileVersionRespMsg.h new file mode 100644 index 0000000..d82afe7 --- /dev/null +++ b/client_module/source/common/net/message/session/GetFileVersionRespMsg.h @@ -0,0 +1,24 @@ +#ifndef GETFILEVERSIONRESPMSG_H_ +#define GETFILEVERSIONRESPMSG_H_ + +#include +#include + +typedef struct GetFileVersionRespMsg GetFileVersionRespMsg; + +struct GetFileVersionRespMsg +{ + NetMessage base; + + FhgfsOpsErr result; + uint32_t version; +}; +extern const struct NetMessageOps GetFileVersionRespMsg_Ops; + + +static inline void GetFileVersionRespMsg_init(struct GetFileVersionRespMsg* this) +{ + NetMessage_init(&this->base, NETMSGTYPE_GetFileVersionResp, &GetFileVersionRespMsg_Ops); +} + +#endif diff --git a/client_module/source/common/net/message/session/locking/FLockAppendMsg.c b/client_module/source/common/net/message/session/locking/FLockAppendMsg.c new file mode 100644 index 0000000..1df45bd --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockAppendMsg.c @@ -0,0 +1,35 @@ +#include "FLockAppendMsg.h" + + +const struct NetMessageOps FLockAppendMsg_Ops = { + .serializePayload = FLockAppendMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void FLockAppendMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + FLockAppendMsg* thisCast = (FLockAppendMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // clientFD + Serialization_serializeInt64(ctx, thisCast->clientFD); + + // ownerPID + Serialization_serializeInt(ctx, thisCast->ownerPID); + + // lockTypeFlags + Serialization_serializeInt(ctx, thisCast->lockTypeFlags); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // lockAckID + Serialization_serializeStrAlign4(ctx, thisCast->lockAckIDLen, thisCast->lockAckID); +} diff --git a/client_module/source/common/net/message/session/locking/FLockAppendMsg.h b/client_module/source/common/net/message/session/locking/FLockAppendMsg.h new file mode 100644 index 0000000..e324973 --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockAppendMsg.h @@ -0,0 +1,73 @@ +#ifndef FLOCKAPPENDMSG_H_ +#define FLOCKAPPENDMSG_H_ + +#include +#include + +/** + * This message is for serialiazation (outgoing) only, deserialization is not implemented!! + */ + +struct FLockAppendMsg; +typedef struct FLockAppendMsg FLockAppendMsg; + +static inline void FLockAppendMsg_init(FLockAppendMsg* this); +static inline void FLockAppendMsg_initFromSession(FLockAppendMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int64_t clientFD, int ownerPID, + int lockTypeFlags, const char* lockAckID); + +// virtual functions +extern void FLockAppendMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct FLockAppendMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + const EntryInfo* entryInfoPtr; // not owned by this object + int64_t clientFD; /* client-wide unique identifier (typically not really the fd number) + * corresponds to 'struct file_lock* fileLock->fl_file on the client side' + */ + + int ownerPID; // pid on client (just informative, because shared on fork() ) + int lockTypeFlags; // ENTRYLOCKTYPE_... + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; +}; + +extern const struct NetMessageOps FLockAppendMsg_Ops; + +void FLockAppendMsg_init(FLockAppendMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_FLockAppend, &FLockAppendMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void FLockAppendMsg_initFromSession(FLockAppendMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int64_t clientFD, int ownerPID, + int lockTypeFlags, const char* lockAckID) +{ + FLockAppendMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->entryInfoPtr = entryInfo; + + this->clientFD = clientFD; + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); +} + +#endif /* FLOCKAPPENDMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/FLockAppendRespMsg.h b/client_module/source/common/net/message/session/locking/FLockAppendRespMsg.h new file mode 100644 index 0000000..e3310c8 --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockAppendRespMsg.h @@ -0,0 +1,27 @@ +#ifndef FLOCKAPPENDRESPMSG_H_ +#define FLOCKAPPENDRESPMSG_H_ + +#include +#include + +/** + * This message is for deserialiazation (incoming) only, serialization is not implemented!! + */ + +struct FLockAppendRespMsg; +typedef struct FLockAppendRespMsg FLockAppendRespMsg; + +static inline void FLockAppendRespMsg_init(FLockAppendRespMsg* this); + +struct FLockAppendRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void FLockAppendRespMsg_init(FLockAppendRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_FLockAppendResp); +} + +#endif /* FLOCKAPPENDRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/FLockEntryMsg.c b/client_module/source/common/net/message/session/locking/FLockEntryMsg.c new file mode 100644 index 0000000..f56695b --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockEntryMsg.c @@ -0,0 +1,36 @@ +#include "FLockEntryMsg.h" + + +const struct NetMessageOps FLockEntryMsg_Ops = { + .serializePayload = FLockEntryMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void FLockEntryMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + FLockEntryMsg* thisCast = (FLockEntryMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // clientFD + Serialization_serializeInt64(ctx, thisCast->clientFD); + + // ownerPID + Serialization_serializeInt(ctx, thisCast->ownerPID); + + // lockTypeFlags + Serialization_serializeInt(ctx, thisCast->lockTypeFlags); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // lockAckID + Serialization_serializeStrAlign4(ctx, thisCast->lockAckIDLen, thisCast->lockAckID); +} diff --git a/client_module/source/common/net/message/session/locking/FLockEntryMsg.h b/client_module/source/common/net/message/session/locking/FLockEntryMsg.h new file mode 100644 index 0000000..6dd6dce --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockEntryMsg.h @@ -0,0 +1,73 @@ +#ifndef FLOCKENTRYMSG_H_ +#define FLOCKENTRYMSG_H_ + +#include +#include + +/** + * This message is for serialiazation (outgoing) only, deserialization is not implemented!! + */ + +struct FLockEntryMsg; +typedef struct FLockEntryMsg FLockEntryMsg; + +static inline void FLockEntryMsg_init(FLockEntryMsg* this); +static inline void FLockEntryMsg_initFromSession(FLockEntryMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int64_t clientFD, int ownerPID, + int lockTypeFlags, const char* lockAckID); + +// virtual functions +extern void FLockEntryMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct FLockEntryMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + const EntryInfo* entryInfoPtr; // not owned by this object + int64_t clientFD; /* client-wide unique identifier (typically not really the fd number) + * corresponds to 'struct file_lock* fileLock->fl_file on the client side' + */ + + int ownerPID; // pid on client (just informative, because shared on fork() ) + int lockTypeFlags; // ENTRYLOCKTYPE_... + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; +}; + +extern const struct NetMessageOps FLockEntryMsg_Ops; + +void FLockEntryMsg_init(FLockEntryMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_FLockEntry, &FLockEntryMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void FLockEntryMsg_initFromSession(FLockEntryMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int64_t clientFD, int ownerPID, + int lockTypeFlags, const char* lockAckID) +{ + FLockEntryMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->entryInfoPtr = entryInfo; + + this->clientFD = clientFD; + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); +} + +#endif /* FLOCKENTRYMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/FLockEntryRespMsg.h b/client_module/source/common/net/message/session/locking/FLockEntryRespMsg.h new file mode 100644 index 0000000..026b4ad --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockEntryRespMsg.h @@ -0,0 +1,27 @@ +#ifndef FLOCKENTRYRESPMSG_H_ +#define FLOCKENTRYRESPMSG_H_ + +#include +#include + +/** + * This message is for deserialiazation (incoming) only, serialization is not implemented!! + */ + +struct FLockEntryRespMsg; +typedef struct FLockEntryRespMsg FLockEntryRespMsg; + +static inline void FLockEntryRespMsg_init(FLockEntryRespMsg* this); + +struct FLockEntryRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void FLockEntryRespMsg_init(FLockEntryRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_FLockEntryResp); +} + +#endif /* FLOCKENTRYRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/FLockRangeMsg.c b/client_module/source/common/net/message/session/locking/FLockRangeMsg.c new file mode 100644 index 0000000..d651ab4 --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockRangeMsg.c @@ -0,0 +1,39 @@ +#include "FLockRangeMsg.h" + + +const struct NetMessageOps FLockRangeMsg_Ops = { + .serializePayload = FLockRangeMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void FLockRangeMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + FLockRangeMsg* thisCast = (FLockRangeMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // start + Serialization_serializeUInt64(ctx, thisCast->start); + + // end + Serialization_serializeUInt64(ctx, thisCast->end); + + // ownerPID + Serialization_serializeInt(ctx, thisCast->ownerPID); + + // lockTypeFlags + Serialization_serializeInt(ctx, thisCast->lockTypeFlags); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // lockAckID + Serialization_serializeStrAlign4(ctx, thisCast->lockAckIDLen, thisCast->lockAckID); +} diff --git a/client_module/source/common/net/message/session/locking/FLockRangeMsg.h b/client_module/source/common/net/message/session/locking/FLockRangeMsg.h new file mode 100644 index 0000000..171fe3c --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockRangeMsg.h @@ -0,0 +1,73 @@ +#ifndef FLOCKRANGEMSG_H_ +#define FLOCKRANGEMSG_H_ + +#include +#include + +/** + * This message is for serialiazation (outgoing) only, deserialization is not implemented!! + */ + +struct FLockRangeMsg; +typedef struct FLockRangeMsg FLockRangeMsg; + +static inline void FLockRangeMsg_init(FLockRangeMsg* this); +static inline void FLockRangeMsg_initFromSession(FLockRangeMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, + int ownerPID, int lockTypeFlags, uint64_t start, uint64_t end, const char* lockAckID); + +// virtual functions +extern void FLockRangeMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct FLockRangeMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + const EntryInfo* entryInfoPtr; // not owned by this object + int ownerPID; // pid on client (just informative, because shared on fork() ) + int lockTypeFlags; // ENTRYLOCKTYPE_... + uint64_t start; + uint64_t end; + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; +}; + +extern const struct NetMessageOps FLockRangeMsg_Ops; + +void FLockRangeMsg_init(FLockRangeMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_FLockRange, &FLockRangeMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void FLockRangeMsg_initFromSession(FLockRangeMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, + int ownerPID, int lockTypeFlags, uint64_t start, uint64_t end, const char* lockAckID) +{ + FLockRangeMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->entryInfoPtr = entryInfo; + + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->start = start; + this->end = end; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); +} + +#endif /* FLOCKRANGEMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/FLockRangeRespMsg.h b/client_module/source/common/net/message/session/locking/FLockRangeRespMsg.h new file mode 100644 index 0000000..ea1d437 --- /dev/null +++ b/client_module/source/common/net/message/session/locking/FLockRangeRespMsg.h @@ -0,0 +1,27 @@ +#ifndef FLOCKRANGERESPMSG_H_ +#define FLOCKRANGERESPMSG_H_ + +#include +#include + +/** + * This message is for deserialiazation (incoming) only, serialization is not implemented!! + */ + +struct FLockRangeRespMsg; +typedef struct FLockRangeRespMsg FLockRangeRespMsg; + +static inline void FLockRangeRespMsg_init(FLockRangeRespMsg* this); + +struct FLockRangeRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void FLockRangeRespMsg_init(FLockRangeRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_FLockRangeResp); +} + +#endif /* FLOCKRANGERESPMSG_H_ */ diff --git a/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.c b/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.c new file mode 100644 index 0000000..2230c3e --- /dev/null +++ b/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.c @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include "LockGrantedMsgEx.h" + +const struct NetMessageOps LockGrantedMsgEx_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = LockGrantedMsgEx_deserializePayload, + .processIncoming = __LockGrantedMsgEx_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool LockGrantedMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + LockGrantedMsgEx* thisCast = (LockGrantedMsgEx*)this; + + // lockAckID + if(!Serialization_deserializeStrAlign4(ctx, + &thisCast->lockAckIDLen, &thisCast->lockAckID) ) + return false; + + // ackID + if(!Serialization_deserializeStrAlign4(ctx, &thisCast->ackIDLen, &thisCast->ackID) ) + return false; + + // granterNodeID + if(!NumNodeID_deserialize(ctx, &thisCast->granterNodeID) ) + return false; + + return true; +} + +bool __LockGrantedMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen) +{ + const char* logContext = "LockGranted incoming"; + Logger* log = App_getLogger(app); + AcknowledgmentStore* ackStore = App_getAckStore(app); + AckManager* ackManager = App_getAckManager(app); + + LockGrantedMsgEx* thisCast = (LockGrantedMsgEx*)this; + bool gotLockWaiter; + + #ifdef LOG_DEBUG_MESSAGES + const char* peer; + + peer = fromAddr ? + SocketTk_ipaddrToStr(fromAddr->addr) : StringTk_strDup(Socket_getPeername(sock) ); + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "Received a AckMsg from: %s", peer); + + kfree(peer); + #endif // LOG_DEBUG_MESSAGES + + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + + gotLockWaiter = AcknowledgmentStore_receivedAck( + ackStore, LockGrantedMsgEx_getLockAckID(thisCast) ); + + /* note: other than for standard acks, we send a ack repsonse to lock-grants only if someone was + still registered to wait for the lock-grant. (we do this to make our handling of interrupts + during lock waits easier, because we don't need lock canceling now.) */ + + if(gotLockWaiter) + { // lock waiter registered => send ack + const char* ackID = LockGrantedMsgEx_getAckID(thisCast); + NumNodeID granterNodeID = LockGrantedMsgEx_getGranterNodeID(thisCast); + + MsgHelperAck_respondToAckRequest(app, LockGrantedMsgEx_getAckID(thisCast), fromAddr, sock, + respBuf, bufLen); + + AckManager_addAckToQueue(ackManager, granterNodeID, ackID); + } + + return true; +} + diff --git a/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.h b/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.h new file mode 100644 index 0000000..6a37ad7 --- /dev/null +++ b/client_module/source/common/net/message/session/locking/LockGrantedMsgEx.h @@ -0,0 +1,62 @@ +#ifndef LOCKGRANTEDMSGEX_H_ +#define LOCKGRANTEDMSGEX_H_ + +#include + + +/** + * This message is for deserialization (incoming) only, serialization is not implemented! + */ + + +struct LockGrantedMsgEx; +typedef struct LockGrantedMsgEx LockGrantedMsgEx; + +static inline void LockGrantedMsgEx_init(LockGrantedMsgEx* this); + +// virtual functions +extern bool LockGrantedMsgEx_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern bool __LockGrantedMsgEx_processIncoming(NetMessage* this, struct App* app, + fhgfs_sockaddr_in* fromAddr, struct Socket* sock, char* respBuf, size_t bufLen); + +// getters & setters +static inline const char* LockGrantedMsgEx_getLockAckID(LockGrantedMsgEx* this); +static inline const char* LockGrantedMsgEx_getAckID(LockGrantedMsgEx* this); +static inline NumNodeID LockGrantedMsgEx_getGranterNodeID(LockGrantedMsgEx* this); + + +struct LockGrantedMsgEx +{ + NetMessage netMessage; + + unsigned lockAckIDLen; + const char* lockAckID; + unsigned ackIDLen; + const char* ackID; + NumNodeID granterNodeID; +}; + +extern const struct NetMessageOps LockGrantedMsgEx_Ops; + +void LockGrantedMsgEx_init(LockGrantedMsgEx* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_LockGranted, &LockGrantedMsgEx_Ops); +} + +const char* LockGrantedMsgEx_getLockAckID(LockGrantedMsgEx* this) +{ + return this->lockAckID; +} + +const char* LockGrantedMsgEx_getAckID(LockGrantedMsgEx* this) +{ + return this->ackID; +} + +NumNodeID LockGrantedMsgEx_getGranterNodeID(LockGrantedMsgEx* this) +{ + return this->granterNodeID; +} + + +#endif /* LOCKGRANTEDMSGEX_H_ */ diff --git a/client_module/source/common/net/message/session/opening/CloseFileMsg.c b/client_module/source/common/net/message/session/opening/CloseFileMsg.c new file mode 100644 index 0000000..3dff4c3 --- /dev/null +++ b/client_module/source/common/net/message/session/opening/CloseFileMsg.c @@ -0,0 +1,30 @@ +#include "CloseFileMsg.h" + + +const struct NetMessageOps CloseFileMsg_Ops = { + .serializePayload = CloseFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void CloseFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + CloseFileMsg* thisCast = (CloseFileMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + // maxUsedNodeIndex + Serialization_serializeInt(ctx, thisCast->maxUsedNodeIndex); + + if (this->msgHeader.msgFeatureFlags & CLOSEFILEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/session/opening/CloseFileMsg.h b/client_module/source/common/net/message/session/opening/CloseFileMsg.h new file mode 100644 index 0000000..373f003 --- /dev/null +++ b/client_module/source/common/net/message/session/opening/CloseFileMsg.h @@ -0,0 +1,75 @@ +#ifndef CLOSEFILEMSG_H_ +#define CLOSEFILEMSG_H_ + +#include +#include +#include + + +#define CLOSEFILEMSG_FLAG_EARLYRESPONSE 1 /* send response before chunk files close */ +#define CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS 2 /* cancel append locks of this file handle */ +#define CLOSEFILEMSG_FLAG_HAS_EVENT 8 /* contains file event logging information */ + +/** + * This message supports sending (serialization) only, i.e. no deserialization implemented!! + */ + +struct CloseFileMsg; +typedef struct CloseFileMsg CloseFileMsg; + +static inline void CloseFileMsg_init(CloseFileMsg* this); +static inline void CloseFileMsg_initFromSession(CloseFileMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int maxUsedNodeIndex, + const struct FileEvent* fileEvent); + +// virtual functions +extern void CloseFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct CloseFileMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + + unsigned fileHandleIDLen; + const char* fileHandleID; + int maxUsedNodeIndex; + + // for serialization + const EntryInfo* entryInfoPtr; // not owned by this object + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps CloseFileMsg_Ops; + +void CloseFileMsg_init(CloseFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_CloseFile, &CloseFileMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void CloseFileMsg_initFromSession(CloseFileMsg* this, NumNodeID clientNumID, + const char* fileHandleID, const EntryInfo* entryInfo, int maxUsedNodeIndex, + const struct FileEvent* fileEvent) +{ + CloseFileMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->entryInfoPtr = entryInfo; + + this->maxUsedNodeIndex = maxUsedNodeIndex; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= CLOSEFILEMSG_FLAG_HAS_EVENT; +} + +#endif /*CLOSEFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/session/opening/CloseFileRespMsg.h b/client_module/source/common/net/message/session/opening/CloseFileRespMsg.h new file mode 100644 index 0000000..95d6154 --- /dev/null +++ b/client_module/source/common/net/message/session/opening/CloseFileRespMsg.h @@ -0,0 +1,32 @@ +#ifndef CLOSEFILERESPMSG_H_ +#define CLOSEFILERESPMSG_H_ + +#include + + +struct CloseFileRespMsg; +typedef struct CloseFileRespMsg CloseFileRespMsg; + +static inline void CloseFileRespMsg_init(CloseFileRespMsg* this); + +// getters & setters +static inline int CloseFileRespMsg_getValue(CloseFileRespMsg* this); + +struct CloseFileRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void CloseFileRespMsg_init(CloseFileRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_CloseFileResp); +} + +int CloseFileRespMsg_getValue(CloseFileRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + +#endif /*CLOSEFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/session/opening/OpenFileMsg.c b/client_module/source/common/net/message/session/opening/OpenFileMsg.c new file mode 100644 index 0000000..3ccf483 --- /dev/null +++ b/client_module/source/common/net/message/session/opening/OpenFileMsg.c @@ -0,0 +1,27 @@ +#include "OpenFileMsg.h" + + +const struct NetMessageOps OpenFileMsg_Ops = { + .serializePayload = OpenFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void OpenFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + OpenFileMsg* thisCast = (OpenFileMsg*)this; + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + if (this->msgHeader.msgFeatureFlags & OPENFILEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/session/opening/OpenFileMsg.h b/client_module/source/common/net/message/session/opening/OpenFileMsg.h new file mode 100644 index 0000000..c98c765 --- /dev/null +++ b/client_module/source/common/net/message/session/opening/OpenFileMsg.h @@ -0,0 +1,65 @@ +#ifndef OPENFILEMSG_H_ +#define OPENFILEMSG_H_ + +#include +#include +#include + + +#define OPENFILEMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define OPENFILEMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ +#define OPENFILEMSG_FLAG_BYPASS_ACCESS_CHECK 4 /* bypass file access checks on metadata server */ + +struct OpenFileMsg; +typedef struct OpenFileMsg OpenFileMsg; + +static inline void OpenFileMsg_init(OpenFileMsg* this); +static inline void OpenFileMsg_initFromSession(OpenFileMsg* this, + NumNodeID clientNumID, const EntryInfo* entryInfo, unsigned accessFlags, + const struct FileEvent* fileEvent); + +// virtual functions +extern void OpenFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct OpenFileMsg +{ + NetMessage netMessage; + + NumNodeID clientNumID; + unsigned sessionIDLen; + const EntryInfo* entryInfoPtr; // not owned by this object + unsigned accessFlags; + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps OpenFileMsg_Ops; + +void OpenFileMsg_init(OpenFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_OpenFile, &OpenFileMsg_Ops); +} + +/** + * @param sessionID just a reference, so do not free it as long as you use this object! + * @param entryInfoPtr just a reference, so do not free it as long as you use this object! + * @param accessFlags OPENFILE_ACCESS_... flags + */ +void OpenFileMsg_initFromSession(OpenFileMsg* this, + NumNodeID clientNumID, const EntryInfo* entryInfo, unsigned accessFlags, + const struct FileEvent* fileEvent) +{ + OpenFileMsg_init(this); + + this->clientNumID = clientNumID; + + this->entryInfoPtr = entryInfo; + + this->accessFlags = accessFlags; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= OPENFILEMSG_FLAG_HAS_EVENT; +} + +#endif /*OPENFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/session/opening/OpenFileRespMsg.c b/client_module/source/common/net/message/session/opening/OpenFileRespMsg.c new file mode 100644 index 0000000..bd608ee --- /dev/null +++ b/client_module/source/common/net/message/session/opening/OpenFileRespMsg.c @@ -0,0 +1,37 @@ +#include "OpenFileRespMsg.h" + + +const struct NetMessageOps OpenFileRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = OpenFileRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool OpenFileRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + OpenFileRespMsg* thisCast = (OpenFileRespMsg*)this; + + // result + if(!Serialization_deserializeInt(ctx, &thisCast->result) ) + return false; + + // fileHandleID + if(!Serialization_deserializeStrAlign4(ctx, &thisCast->fileHandleIDLen, + &thisCast->fileHandleID) ) + return false; + + // pathInfo + if (!PathInfo_deserialize(ctx, &thisCast->pathInfo) ) + return false; + + // stripePattern + if(!StripePattern_deserializePatternPreprocess(ctx, + &thisCast->patternStart, &thisCast->patternLength) ) + return false; + + if (!Serialization_deserializeUInt(ctx, &thisCast->fileVersion)) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/session/opening/OpenFileRespMsg.h b/client_module/source/common/net/message/session/opening/OpenFileRespMsg.h new file mode 100644 index 0000000..31cad7a --- /dev/null +++ b/client_module/source/common/net/message/session/opening/OpenFileRespMsg.h @@ -0,0 +1,76 @@ +#ifndef OPENFILERESPMSG_H_ +#define OPENFILERESPMSG_H_ + +#include +#include +#include +#include + + +struct OpenFileRespMsg; +typedef struct OpenFileRespMsg OpenFileRespMsg; + +static inline void OpenFileRespMsg_init(OpenFileRespMsg* this); + +// virtual functions +extern bool OpenFileRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// inliners +static inline StripePattern* OpenFileRespMsg_createPattern(OpenFileRespMsg* this); + +// getters & setters +static inline int OpenFileRespMsg_getResult(OpenFileRespMsg* this); +static inline const char* OpenFileRespMsg_getFileHandleID(OpenFileRespMsg* this); +static inline const PathInfo* OpenFileRespMsg_getPathInfo(OpenFileRespMsg* this); + +struct OpenFileRespMsg +{ + NetMessage netMessage; + + int result; + unsigned fileHandleIDLen; + const char* fileHandleID; + + PathInfo pathInfo; + + uint32_t fileVersion; + + // for serialization + StripePattern* pattern; // not owned by this object! + + // for deserialization + const char* patternStart; + uint32_t patternLength; +}; + +extern const struct NetMessageOps OpenFileRespMsg_Ops; + +void OpenFileRespMsg_init(OpenFileRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_OpenFileResp, &OpenFileRespMsg_Ops); +} + +/** + * @return must be deleted by the caller; might be of type STRIPEPATTERN_Invalid + */ +StripePattern* OpenFileRespMsg_createPattern(OpenFileRespMsg* this) +{ + return StripePattern_createFromBuf(this->patternStart, this->patternLength); +} + +int OpenFileRespMsg_getResult(OpenFileRespMsg* this) +{ + return this->result; +} + +const char* OpenFileRespMsg_getFileHandleID(OpenFileRespMsg* this) +{ + return this->fileHandleID; +} + +const PathInfo* OpenFileRespMsg_getPathInfo(OpenFileRespMsg* this) +{ + return &this->pathInfo; +} + +#endif /*OPENFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.c b/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.c new file mode 100644 index 0000000..5250af6 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.c @@ -0,0 +1,39 @@ +#ifdef BEEGFS_NVFS +#include "ReadLocalFileRDMAMsg.h" + +const struct NetMessageOps ReadLocalFileRDMAMsg_Ops = { + .serializePayload = ReadLocalFileRDMAMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void ReadLocalFileRDMAMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + ReadLocalFileRDMAMsg* thisCast = (ReadLocalFileRDMAMsg*)this; + + // offset + Serialization_serializeInt64(ctx, thisCast->offset); + + // count + Serialization_serializeInt64(ctx, thisCast->count); + + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // pathInfo + PathInfo_serialize(ctx, thisCast->pathInfoPtr); + + // targetID + Serialization_serializeUShort(ctx, thisCast->targetID); + + // RDMA info + RdmaInfo_serialize(ctx, thisCast->rdmap); +} +#endif /* BEEGFS_NVFS */ diff --git a/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h b/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h new file mode 100644 index 0000000..fe1e85e --- /dev/null +++ b/client_module/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h @@ -0,0 +1,71 @@ +#ifndef READLOCALFILERDMAMSG_H_ +#define READLOCALFILERDMAMSG_H_ +#ifdef BEEGFS_NVFS +#include +#include +#include +#include "ReadLocalFileV2Msg.h" + + +struct ReadLocalFileRDMAMsg; +typedef struct ReadLocalFileRDMAMsg ReadLocalFileRDMAMsg; + +static inline void ReadLocalFileRDMAMsg_init(ReadLocalFileRDMAMsg* this); +static inline void ReadLocalFileRDMAMsg_initFromSession(ReadLocalFileRDMAMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfoPtr, + unsigned accessFlags, int64_t offset, int64_t count, RdmaInfo *rdmap); + +// virtual functions +extern void ReadLocalFileRDMAMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct ReadLocalFileRDMAMsg +{ + NetMessage netMessage; + + int64_t offset; + int64_t count; + unsigned accessFlags; + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + PathInfo* pathInfoPtr; + uint16_t targetID; + RdmaInfo *rdmap; +}; + +extern const struct NetMessageOps ReadLocalFileRDMAMsg_Ops; + +void ReadLocalFileRDMAMsg_init(ReadLocalFileRDMAMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ReadLocalFileRDMA, &ReadLocalFileRDMAMsg_Ops); +} + +/** + * @param sessionID just a reference, so do not free it as long as you use this object! + */ +void ReadLocalFileRDMAMsg_initFromSession(ReadLocalFileRDMAMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfoPtr, + unsigned accessFlags, int64_t offset, int64_t count, RdmaInfo *rdmap) +{ + ReadLocalFileRDMAMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfoPtr; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; + + this->rdmap = rdmap; +} + +#endif /* BEEGFS_NVFS */ +#endif /*READLOCALFILERDMAMSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.c b/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.c new file mode 100644 index 0000000..682c707 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.c @@ -0,0 +1,35 @@ +#include "ReadLocalFileV2Msg.h" + + +const struct NetMessageOps ReadLocalFileV2Msg_Ops = { + .serializePayload = ReadLocalFileV2Msg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void ReadLocalFileV2Msg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + ReadLocalFileV2Msg* thisCast = (ReadLocalFileV2Msg*)this; + + // offset + Serialization_serializeInt64(ctx, thisCast->offset); + + // count + Serialization_serializeInt64(ctx, thisCast->count); + + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // pathInfo + PathInfo_serialize(ctx, thisCast->pathInfoPtr); + + // targetID + Serialization_serializeUShort(ctx, thisCast->targetID); +} diff --git a/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.h b/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.h new file mode 100644 index 0000000..69b8055 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/ReadLocalFileV2Msg.h @@ -0,0 +1,71 @@ +#ifndef READLOCALFILEV2MSG_H_ +#define READLOCALFILEV2MSG_H_ + +#include +#include + + +#define READLOCALFILEMSG_FLAG_SESSION_CHECK 1 /* if session check infos should be done */ +#define READLOCALFILEMSG_FLAG_DISABLE_IO 2 /* disable read syscall for net bench */ +#define READLOCALFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + + +struct ReadLocalFileV2Msg; +typedef struct ReadLocalFileV2Msg ReadLocalFileV2Msg; + +static inline void ReadLocalFileV2Msg_init(ReadLocalFileV2Msg* this); +static inline void ReadLocalFileV2Msg_initFromSession(ReadLocalFileV2Msg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfoPtr, + unsigned accessFlags, int64_t offset, int64_t count); + +// virtual functions +extern void ReadLocalFileV2Msg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct ReadLocalFileV2Msg +{ + NetMessage netMessage; + + int64_t offset; + int64_t count; + unsigned accessFlags; + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + PathInfo* pathInfoPtr; + uint16_t targetID; +}; + +extern const struct NetMessageOps ReadLocalFileV2Msg_Ops; + +void ReadLocalFileV2Msg_init(ReadLocalFileV2Msg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ReadLocalFileV2, &ReadLocalFileV2Msg_Ops); +} + +/** + * @param sessionID just a reference, so do not free it as long as you use this object! + */ +void ReadLocalFileV2Msg_initFromSession(ReadLocalFileV2Msg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfoPtr, + unsigned accessFlags, int64_t offset, int64_t count) +{ + ReadLocalFileV2Msg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfoPtr; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; +} + +#endif /*READLOCALFILEV2MSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.c b/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.c new file mode 100644 index 0000000..94d35f8 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.c @@ -0,0 +1,44 @@ +#include "WriteLocalFileMsg.h" + + +const struct NetMessageOps WriteLocalFileMsg_Ops = { + .serializePayload = WriteLocalFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void WriteLocalFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + WriteLocalFileMsg* thisCast = (WriteLocalFileMsg*)this; + + // offset + Serialization_serializeInt64(ctx, thisCast->offset); + + // count + Serialization_serializeInt64(ctx, thisCast->count); + + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + if(NetMessage_isMsgHeaderFeatureFlagSet(this, WRITELOCALFILEMSG_FLAG_USE_QUOTA)) + { + // userID + Serialization_serializeUInt(ctx, thisCast->userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->groupID); + } + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // pathInfo + PathInfo_serialize(ctx, thisCast->pathInfo); + + // targetID + Serialization_serializeUShort(ctx, thisCast->targetID); +} diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.h b/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.h new file mode 100644 index 0000000..07ef6f4 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileMsg.h @@ -0,0 +1,99 @@ +#ifndef WRITELOCALFILEMSG_H_ +#define WRITELOCALFILEMSG_H_ + +#include +#include +#include +#include +#include +#include + + +#define WRITELOCALFILEMSG_FLAG_SESSION_CHECK 1 /* if session check should be done */ +#define WRITELOCALFILEMSG_FLAG_USE_QUOTA 2 /* if msg contains quota info */ +#define WRITELOCALFILEMSG_FLAG_DISABLE_IO 4 /* disable write syscall for net bench mode */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR 8 /* given targetID is a buddymirrorgroup ID */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 16 /* secondary of group, otherwise primary */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD 32 /* forward msg to secondary */ + + +struct WriteLocalFileMsg; +typedef struct WriteLocalFileMsg WriteLocalFileMsg; + + +static inline void WriteLocalFileMsg_init(WriteLocalFileMsg* this); +static inline void WriteLocalFileMsg_initFromSession(WriteLocalFileMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfo, + unsigned accessFlags, int64_t offset, int64_t count); + +// virtual functions +extern void WriteLocalFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +// getters & setters +static inline void WriteLocalFileMsg_setUserdataForQuota(WriteLocalFileMsg* this, unsigned userID, + unsigned groupID); + + +/** + * Note: This message supports only serialization, deserialization is not implemented. + */ +struct WriteLocalFileMsg +{ + NetMessage netMessage; + + int64_t offset; + int64_t count; + unsigned accessFlags; + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + uint16_t targetID; + PathInfo* pathInfo; + unsigned userID; + unsigned groupID; +}; + +extern const struct NetMessageOps WriteLocalFileMsg_Ops; + +void WriteLocalFileMsg_init(WriteLocalFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_WriteLocalFile, &WriteLocalFileMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ +void WriteLocalFileMsg_initFromSession(WriteLocalFileMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfo, + unsigned accessFlags, + int64_t offset, int64_t count) +{ + WriteLocalFileMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + this->pathInfo = pathInfo; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; +} + +void WriteLocalFileMsg_setUserdataForQuota(WriteLocalFileMsg* this, unsigned userID, + unsigned groupID) +{ + NetMessage* thisCast = (NetMessage*) this; + + NetMessage_addMsgHeaderFeatureFlag(thisCast, WRITELOCALFILEMSG_FLAG_USE_QUOTA); + + this->userID = userID; + this->groupID = groupID; +} + +#endif /*WRITELOCALFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.c b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.c new file mode 100644 index 0000000..9828f32 --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.c @@ -0,0 +1,49 @@ +#ifdef BEEGFS_NVFS +#include "WriteLocalFileRDMAMsg.h" +#include "WriteLocalFileMsg.h" + +const struct NetMessageOps WriteLocalFileRDMAMsg_Ops = { + .serializePayload = WriteLocalFileRDMAMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void WriteLocalFileRDMAMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + WriteLocalFileRDMAMsg* thisCast = (WriteLocalFileRDMAMsg*)this; + + // offset + Serialization_serializeInt64(ctx, thisCast->offset); + + // count + Serialization_serializeInt64(ctx, thisCast->count); + + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + if(NetMessage_isMsgHeaderFeatureFlagSet(this, WRITELOCALFILEMSG_FLAG_USE_QUOTA)) + { + // userID + Serialization_serializeUInt(ctx, thisCast->userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->groupID); + } + + // fileHandleID + Serialization_serializeStrAlign4(ctx, thisCast->fileHandleIDLen, thisCast->fileHandleID); + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + + // pathInfo + PathInfo_serialize(ctx, thisCast->pathInfo); + + // targetID + Serialization_serializeUShort(ctx, thisCast->targetID); + + // RDMA info + RdmaInfo_serialize(ctx, thisCast->rdmap); +} +#endif /* BEEGFS_NVFS */ diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h new file mode 100644 index 0000000..dafaf6e --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h @@ -0,0 +1,104 @@ +#ifndef WRITELOCALFILERDMAMSG_H_ +#define WRITELOCALFILERDMAMSG_H_ +#ifdef BEEGFS_NVFS + +#include +#include +#include +#include +#include +#include +#include + + +#define WRITELOCALFILERDMAMSG_FLAG_SESSION_CHECK 1 /* if session check should be done */ +#define WRITELOCALFILERDMAMSG_FLAG_USE_QUOTA 2 /* if msg contains quota info */ +#define WRITELOCALFILERDMAMSG_FLAG_DISABLE_IO 4 /* disable write syscall for net bench mode */ +#define WRITELOCALFILERDMAMSG_FLAG_BUDDYMIRROR 8 /* given targetID is a buddymirrorgroup ID */ +#define WRITELOCALFILERDMAMSG_FLAG_BUDDYMIRROR_SECOND 16 /* secondary of group, otherwise primary */ +#define WRITELOCALFILERDMAMSG_FLAG_BUDDYMIRROR_FORWARD 32 /* forward msg to secondary */ + + +struct WriteLocalFileRDMAMsg; +typedef struct WriteLocalFileRDMAMsg WriteLocalFileRDMAMsg; + + +static inline void WriteLocalFileRDMAMsg_init(WriteLocalFileRDMAMsg* this); +static inline void WriteLocalFileRDMAMsg_initFromSession(WriteLocalFileRDMAMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfo, + unsigned accessFlags, int64_t offset, int64_t count, RdmaInfo *rdmap); + +// virtual functions +extern void WriteLocalFileRDMAMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +// getters & setters +static inline void WriteLocalFileRDMAMsg_setUserdataForQuota(WriteLocalFileRDMAMsg* this, unsigned userID, + unsigned groupID); + + +/** + * Note: This message supports only serialization, deserialization is not implemented. + */ +struct WriteLocalFileRDMAMsg +{ + NetMessage netMessage; + + int64_t offset; + int64_t count; + unsigned accessFlags; + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + uint16_t targetID; + PathInfo* pathInfo; + unsigned userID; + unsigned groupID; + RdmaInfo *rdmap; +}; + +extern const struct NetMessageOps WriteLocalFileRDMAMsg_Ops; + +void WriteLocalFileRDMAMsg_init(WriteLocalFileRDMAMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_WriteLocalFileRDMA, &WriteLocalFileRDMAMsg_Ops); +} + +/** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ +void WriteLocalFileRDMAMsg_initFromSession(WriteLocalFileRDMAMsg* this, + NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, PathInfo* pathInfo, + unsigned accessFlags, int64_t offset, int64_t count, RdmaInfo *rdmap) +{ + WriteLocalFileRDMAMsg_init(this); + + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + this->pathInfo = pathInfo; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; + + this->rdmap = rdmap; +} + +void WriteLocalFileRDMAMsg_setUserdataForQuota(WriteLocalFileRDMAMsg* this, unsigned userID, + unsigned groupID) +{ + NetMessage* thisCast = (NetMessage*) this; + + NetMessage_addMsgHeaderFeatureFlag(thisCast, WRITELOCALFILERDMAMSG_FLAG_USE_QUOTA); + + this->userID = userID; + this->groupID = groupID; +} + +#endif /* BEEGFS_NVFS */ +#endif /*WRITELOCALFILERDMAMSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h new file mode 100644 index 0000000..5c82d2d --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h @@ -0,0 +1,34 @@ +#ifndef WRITELOCALFILERDMARESPMSG_H_ +#define WRITELOCALFILERDMARESPMSG_H_ +#ifdef BEEGFS_NVFS + +#include + + +struct WriteLocalFileRDMARespMsg; +typedef struct WriteLocalFileRDMARespMsg WriteLocalFileRDMARespMsg; + +static inline void WriteLocalFileRDMARespMsg_init(WriteLocalFileRDMARespMsg* this); + +// getters & setters +static inline int64_t WriteLocalFileRDMARespMsg_getValue(WriteLocalFileRDMARespMsg* this); + +struct WriteLocalFileRDMARespMsg +{ + SimpleInt64Msg simpleInt64Msg; +}; + + +void WriteLocalFileRDMARespMsg_init(WriteLocalFileRDMARespMsg* this) +{ + SimpleInt64Msg_init( (SimpleInt64Msg*)this, NETMSGTYPE_WriteLocalFileRDMAResp); +} + +int64_t WriteLocalFileRDMARespMsg_getValue(WriteLocalFileRDMARespMsg* this) +{ + return SimpleInt64Msg_getValue( (SimpleInt64Msg*)this); +} + + +#endif /* BEEGFS_NVFS */ +#endif /*WRITELOCALFILERDMARESPMSG_H_*/ diff --git a/client_module/source/common/net/message/session/rw/WriteLocalFileRespMsg.h b/client_module/source/common/net/message/session/rw/WriteLocalFileRespMsg.h new file mode 100644 index 0000000..4c1abcf --- /dev/null +++ b/client_module/source/common/net/message/session/rw/WriteLocalFileRespMsg.h @@ -0,0 +1,32 @@ +#ifndef WRITELOCALFILERESPMSG_H_ +#define WRITELOCALFILERESPMSG_H_ + +#include + + +struct WriteLocalFileRespMsg; +typedef struct WriteLocalFileRespMsg WriteLocalFileRespMsg; + +static inline void WriteLocalFileRespMsg_init(WriteLocalFileRespMsg* this); + +// getters & setters +static inline int64_t WriteLocalFileRespMsg_getValue(WriteLocalFileRespMsg* this); + +struct WriteLocalFileRespMsg +{ + SimpleInt64Msg simpleInt64Msg; +}; + + +void WriteLocalFileRespMsg_init(WriteLocalFileRespMsg* this) +{ + SimpleInt64Msg_init( (SimpleInt64Msg*)this, NETMSGTYPE_WriteLocalFileResp); +} + +int64_t WriteLocalFileRespMsg_getValue(WriteLocalFileRespMsg* this) +{ + return SimpleInt64Msg_getValue( (SimpleInt64Msg*)this); +} + + +#endif /*WRITELOCALFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/StatStoragePathMsg.h b/client_module/source/common/net/message/storage/StatStoragePathMsg.h new file mode 100644 index 0000000..c4a37c4 --- /dev/null +++ b/client_module/source/common/net/message/storage/StatStoragePathMsg.h @@ -0,0 +1,33 @@ +#ifndef STATSTORAGEPATHMSG_H_ +#define STATSTORAGEPATHMSG_H_ + +#include "../SimpleUInt16Msg.h" + + +struct StatStoragePathMsg; +typedef struct StatStoragePathMsg StatStoragePathMsg; + +static inline void StatStoragePathMsg_init(StatStoragePathMsg* this); +static inline void StatStoragePathMsg_initFromTarget(StatStoragePathMsg* this, + uint16_t targetID); + +struct StatStoragePathMsg +{ + SimpleUInt16Msg simpleUInt16Msg; +}; + + +void StatStoragePathMsg_init(StatStoragePathMsg* this) +{ + SimpleUInt16Msg_init( (SimpleUInt16Msg*)this, NETMSGTYPE_StatStoragePath); +} + +/** + * @param targetID only used for storage servers, ignored for other nodes (but may not be NULL!) + */ +void StatStoragePathMsg_initFromTarget(StatStoragePathMsg* this, uint16_t targetID) +{ + SimpleUInt16Msg_initFromValue( (SimpleUInt16Msg*)this, NETMSGTYPE_StatStoragePath, targetID); +} + +#endif /*STATSTORAGEPATHMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/StatStoragePathRespMsg.c b/client_module/source/common/net/message/storage/StatStoragePathRespMsg.c new file mode 100644 index 0000000..ac4e317 --- /dev/null +++ b/client_module/source/common/net/message/storage/StatStoragePathRespMsg.c @@ -0,0 +1,36 @@ +#include "StatStoragePathRespMsg.h" + + +const struct NetMessageOps StatStoragePathRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = StatStoragePathRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool StatStoragePathRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + StatStoragePathRespMsg* thisCast = (StatStoragePathRespMsg*)this; + + // result + if(!Serialization_deserializeInt(ctx, &thisCast->result) ) + return false; + + // sizeTotal + if(!Serialization_deserializeInt64(ctx, &thisCast->sizeTotal) ) + return false; + + // sizeFree + if(!Serialization_deserializeInt64(ctx, &thisCast->sizeFree) ) + return false; + + // inodesTotal + if(!Serialization_deserializeInt64(ctx, &thisCast->inodesTotal) ) + return false; + + // inodesFree + if(!Serialization_deserializeInt64(ctx, &thisCast->inodesFree) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/storage/StatStoragePathRespMsg.h b/client_module/source/common/net/message/storage/StatStoragePathRespMsg.h new file mode 100644 index 0000000..4ecf132 --- /dev/null +++ b/client_module/source/common/net/message/storage/StatStoragePathRespMsg.h @@ -0,0 +1,37 @@ +#ifndef STATSTORAGEPATHRESPMSG_H_ +#define STATSTORAGEPATHRESPMSG_H_ + +#include + +/** + * Only supports deserialization. Serialization is not impl'ed. + */ + +struct StatStoragePathRespMsg; +typedef struct StatStoragePathRespMsg StatStoragePathRespMsg; + +static inline void StatStoragePathRespMsg_init(StatStoragePathRespMsg* this); + +// virtual functions +extern bool StatStoragePathRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + + +struct StatStoragePathRespMsg +{ + NetMessage netMessage; + + int result; + int64_t sizeTotal; + int64_t sizeFree; + int64_t inodesTotal; + int64_t inodesFree; +}; + +extern const struct NetMessageOps StatStoragePathRespMsg_Ops; + +void StatStoragePathRespMsg_init(StatStoragePathRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_StatStoragePathResp, &StatStoragePathRespMsg_Ops); +} + +#endif /*STATSTORAGEPATHRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/TruncFileMsg.c b/client_module/source/common/net/message/storage/TruncFileMsg.c new file mode 100644 index 0000000..c404fc4 --- /dev/null +++ b/client_module/source/common/net/message/storage/TruncFileMsg.c @@ -0,0 +1,23 @@ +#include "TruncFileMsg.h" + + +const struct NetMessageOps TruncFileMsg_Ops = { + .serializePayload = TruncFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void TruncFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + TruncFileMsg* thisCast = (TruncFileMsg*)this; + + // filesize + Serialization_serializeInt64(ctx, thisCast->filesize); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + if (this->msgHeader.msgFeatureFlags & TRUNCFILEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/TruncFileMsg.h b/client_module/source/common/net/message/storage/TruncFileMsg.h new file mode 100644 index 0000000..0be19d5 --- /dev/null +++ b/client_module/source/common/net/message/storage/TruncFileMsg.h @@ -0,0 +1,60 @@ +#ifndef TRUNCFILEMSG_H_ +#define TRUNCFILEMSG_H_ + +#include +#include +#include + + +#define TRUNCFILEMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define TRUNCFILEMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ + + +struct TruncFileMsg; +typedef struct TruncFileMsg TruncFileMsg; + +static inline void TruncFileMsg_init(TruncFileMsg* this); +static inline void TruncFileMsg_initFromEntryInfo(TruncFileMsg* this, int64_t filesize, + const EntryInfo* entryInfo, const struct FileEvent* fileEvent); + +// virtual functions +extern void TruncFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + + +struct TruncFileMsg +{ + NetMessage netMessage; + + int64_t filesize; + + // for serialization + const EntryInfo* entryInfoPtr; // not owned by this object! + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps TruncFileMsg_Ops; + +void TruncFileMsg_init(TruncFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_TruncFile, &TruncFileMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void TruncFileMsg_initFromEntryInfo(TruncFileMsg* this, int64_t filesize, + const EntryInfo* entryInfo, const struct FileEvent* fileEvent) +{ + TruncFileMsg_init(this); + + this->filesize = filesize; + + this->entryInfoPtr = entryInfo; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= TRUNCFILEMSG_FLAG_HAS_EVENT; +} + +#endif /*TRUNCFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/TruncFileRespMsg.h b/client_module/source/common/net/message/storage/TruncFileRespMsg.h new file mode 100644 index 0000000..c94926a --- /dev/null +++ b/client_module/source/common/net/message/storage/TruncFileRespMsg.h @@ -0,0 +1,33 @@ +#ifndef TRUNCFILERESPMSG_H_ +#define TRUNCFILERESPMSG_H_ + +#include + + +struct TruncFileRespMsg; +typedef struct TruncFileRespMsg TruncFileRespMsg; + +static inline void TruncFileRespMsg_init(TruncFileRespMsg* this); + +// getters & setters +static inline int TruncFileRespMsg_getValue(TruncFileRespMsg* this); + +struct TruncFileRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void TruncFileRespMsg_init(TruncFileRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_TruncFileResp); +} + +int TruncFileRespMsg_getValue(TruncFileRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + + +#endif /*TRUNCFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.c b/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.c new file mode 100644 index 0000000..6aff953 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.c @@ -0,0 +1,17 @@ +#include "GetXAttrMsg.h" + +const struct NetMessageOps GetXAttrMsg_Ops = { + .serializePayload = GetXAttrMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void GetXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + GetXAttrMsg* thisCast = (GetXAttrMsg*)this; + + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + Serialization_serializeStr(ctx, strlen(thisCast->name), thisCast->name); + Serialization_serializeInt(ctx, thisCast->size); +} diff --git a/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.h b/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.h new file mode 100644 index 0000000..b53121b --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/GetXAttrMsg.h @@ -0,0 +1,44 @@ +#ifndef GETXATTRMSG_H_ +#define GETXATTRMSG_H_ + +#include +#include + +struct GetXAttrMsg; +typedef struct GetXAttrMsg GetXAttrMsg; + +static inline void GetXAttrMsg_init(GetXAttrMsg* this); +static inline void GetXAttrMsg_initFromEntryInfoNameAndSize(GetXAttrMsg* this, + const EntryInfo* entryInfo, const char* name, ssize_t size); + +// virtual functions +extern void GetXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct GetXAttrMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; + + const char* name; + int size; +}; + +extern const struct NetMessageOps GetXAttrMsg_Ops; + +void GetXAttrMsg_init(GetXAttrMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetXAttr, &GetXAttrMsg_Ops); +} + +void GetXAttrMsg_initFromEntryInfoNameAndSize(GetXAttrMsg* this, const EntryInfo* entryInfo, + const char* name, ssize_t size) +{ + GetXAttrMsg_init(this); + this->entryInfoPtr = entryInfo; + this->name = name; + this->size = size; +} + +#endif /*GETXATTRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.c b/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.c new file mode 100644 index 0000000..82d9a24 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.c @@ -0,0 +1,32 @@ +#include + +#include "GetXAttrRespMsg.h" + +const struct NetMessageOps GetXAttrRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = GetXAttrRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool GetXAttrRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + GetXAttrRespMsg* thisCast = (GetXAttrRespMsg*)this; + + const char** valueBufPtr = (const char**)&thisCast->valueBuf; + + { + unsigned valueFieldLen; + if (!Serialization_deserializeCharArray(ctx, &thisCast->valueBufLen, + valueBufPtr, &valueFieldLen) ) + return false; + } + + if (!Serialization_deserializeInt(ctx, &thisCast->size) ) + return false; + + if (!Serialization_deserializeInt(ctx, &thisCast->returnCode) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.h b/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.h new file mode 100644 index 0000000..8d82b82 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/GetXAttrRespMsg.h @@ -0,0 +1,51 @@ +#ifndef GETXATTRRESPMSG_H_ +#define GETXATTRRESPMSG_H_ + +#include + +struct GetXAttrRespMsg; +typedef struct GetXAttrRespMsg GetXAttrRespMsg; + +static inline void GetXAttrRespMsg_init(GetXAttrRespMsg* this); + +// virtual functions +extern bool GetXAttrRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters and setters +static inline char* GetXAttrRespMsg_getValueBuf(GetXAttrRespMsg* this); +static inline int GetXAttrRespMsg_getReturnCode(GetXAttrRespMsg* this); +static inline int GetXAttrRespMsg_getSize(GetXAttrRespMsg* this); + +struct GetXAttrRespMsg +{ + NetMessage netMessage; + + unsigned valueBufLen; // only used for deserialization + char* valueBuf; + int size; + int returnCode; +}; + +extern const struct NetMessageOps GetXAttrRespMsg_Ops; + +void GetXAttrRespMsg_init(GetXAttrRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_GetXAttrResp, &GetXAttrRespMsg_Ops); +} + +char* GetXAttrRespMsg_getValueBuf(GetXAttrRespMsg* this) +{ + return this->valueBuf; +} + +int GetXAttrRespMsg_getReturnCode(GetXAttrRespMsg* this) +{ + return this->returnCode; +} + +int GetXAttrRespMsg_getSize(GetXAttrRespMsg* this) +{ + return this->size; +} + +#endif /*GETXATTRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.c b/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.c new file mode 100644 index 0000000..ee661d9 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.c @@ -0,0 +1,16 @@ +#include "ListXAttrMsg.h" + +const struct NetMessageOps ListXAttrMsg_Ops = { + .serializePayload = ListXAttrMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void ListXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + ListXAttrMsg* thisCast = (ListXAttrMsg*)this; + + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + Serialization_serializeInt(ctx, thisCast->size); +} diff --git a/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.h b/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.h new file mode 100644 index 0000000..aee6c57 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/ListXAttrMsg.h @@ -0,0 +1,44 @@ +#ifndef LISTXATTRMSG_H_ +#define LISTXATTRMSG_H_ + +#include +#include + +struct ListXAttrMsg; +typedef struct ListXAttrMsg ListXAttrMsg; + +static inline void ListXAttrMsg_init(ListXAttrMsg* this); +static inline void ListXAttrMsg_initFromEntryInfoAndSize(ListXAttrMsg* this, + const EntryInfo* entryInfo, ssize_t size); + +// virtual functions +extern void ListXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct ListXAttrMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; // not owned by this object + int size; // buffer size for the list buffer of the listxattr call +}; + +extern const struct NetMessageOps ListXAttrMsg_Ops; + +void ListXAttrMsg_init(ListXAttrMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ListXAttr, &ListXAttrMsg_Ops); +} + +/** + * @param entryID just a reference, so do not free it as long as you use this object! + */ +void ListXAttrMsg_initFromEntryInfoAndSize(ListXAttrMsg* this, const EntryInfo* entryInfo, + ssize_t size) +{ + ListXAttrMsg_init(this); + this->entryInfoPtr = entryInfo; + this->size = size; +} + +#endif /*LISTXATTRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.c b/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.c new file mode 100644 index 0000000..c332cd7 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.c @@ -0,0 +1,35 @@ +#include + +#include "ListXAttrRespMsg.h" + +const struct NetMessageOps ListXAttrRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = ListXAttrRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool ListXAttrRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + ListXAttrRespMsg* thisCast = (ListXAttrRespMsg*)this; + + { // value + RawList valueField; + + if(!Serialization_deserializeStrCpyVecPreprocess(ctx, &valueField) ) + return false; + + thisCast->valueElemNum = valueField.elemCount; + thisCast->valueBuf = (char*) valueField.data; + } + + // size + if (!Serialization_deserializeInt(ctx, &thisCast->size) ) + return false; + + // returnCode + if (!Serialization_deserializeInt(ctx, &thisCast->returnCode) ) + return false; + + return true; +} diff --git a/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.h b/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.h new file mode 100644 index 0000000..95e86bc --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/ListXAttrRespMsg.h @@ -0,0 +1,51 @@ +#ifndef LISTXATTRRESPMSG_H_ +#define LISTXATTRRESPMSG_H_ + +#include + +struct ListXAttrRespMsg; +typedef struct ListXAttrRespMsg ListXAttrRespMsg; + +static inline void ListXAttrRespMsg_init(ListXAttrRespMsg* this); + +// virtual functions +extern bool ListXAttrRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters and setters +static inline char* ListXAttrRespMsg_getValueBuf(ListXAttrRespMsg* this); +static inline int ListXAttrRespMsg_getReturnCode(ListXAttrRespMsg* this); +static inline int ListXAttrRespMsg_getSize(ListXAttrRespMsg* this); + +struct ListXAttrRespMsg +{ + NetMessage netMessage; + + unsigned valueElemNum; + char* valueBuf; + int size; + int returnCode; +}; + +extern const struct NetMessageOps ListXAttrRespMsg_Ops; + +void ListXAttrRespMsg_init(ListXAttrRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ListXAttrResp, &ListXAttrRespMsg_Ops); +} + +char* ListXAttrRespMsg_getValueBuf(ListXAttrRespMsg* this) +{ + return this->valueBuf; +} + +int ListXAttrRespMsg_getReturnCode(ListXAttrRespMsg* this) +{ + return this->returnCode; +} + +int ListXAttrRespMsg_getSize(ListXAttrRespMsg* this) +{ + return this->size; +} + +#endif /*LISTXATTRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.c b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.c new file mode 100644 index 0000000..b4a84ff --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.c @@ -0,0 +1,18 @@ +#include "RefreshEntryInfoMsg.h" + + +const struct NetMessageOps RefreshEntryInfoMsg_Ops = { + .serializePayload = RefreshEntryInfoMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void RefreshEntryInfoMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RefreshEntryInfoMsg* thisCast = (RefreshEntryInfoMsg*)this; + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); +} diff --git a/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h new file mode 100644 index 0000000..17bed72 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h @@ -0,0 +1,42 @@ +#ifndef REFRESHENTRYINFO_H_ +#define REFRESHENTRYINFO_H_ + +#include +#include + + +struct RefreshEntryInfoMsg; +typedef struct RefreshEntryInfoMsg RefreshEntryInfoMsg; + +static inline void RefreshEntryInfoMsg_init(RefreshEntryInfoMsg* this); +static inline void RefreshEntryInfoMsg_initFromEntryInfo(RefreshEntryInfoMsg* this, const EntryInfo* entryInfo); + +// virtual functions +extern void RefreshEntryInfoMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct RefreshEntryInfoMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; // not owned by this object +}; + +extern const struct NetMessageOps RefreshEntryInfoMsg_Ops; + +void RefreshEntryInfoMsg_init(RefreshEntryInfoMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RefreshEntryInfo, &RefreshEntryInfoMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void RefreshEntryInfoMsg_initFromEntryInfo(RefreshEntryInfoMsg* this, const EntryInfo* entryInfo) +{ + RefreshEntryInfoMsg_init(this); + + this->entryInfoPtr = entryInfo; +} + +#endif /*REFRESHENTRYINFO_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h new file mode 100644 index 0000000..61fc455 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h @@ -0,0 +1,31 @@ +#ifndef REFRESHENTRYINFORESPMSG_H_ +#define REFRESHENTRYINFORESPMSG_H_ + +#include + + +struct RefreshEntryInfoRespMsg; +typedef struct RefreshEntryInfoRespMsg RefreshEntryInfoRespMsg; + +static inline void RefreshEntryInfoRespMsg_init(RefreshEntryInfoRespMsg* this); + +// getters & setters +static inline int RefreshEntryInfoRespMsg_getResult(RefreshEntryInfoRespMsg* this); + +struct RefreshEntryInfoRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void RefreshEntryInfoRespMsg_init(RefreshEntryInfoRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_RefreshEntryInfoResp); +} + +int RefreshEntryInfoRespMsg_getResult(RefreshEntryInfoRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + +#endif /*REFRESHENTRYINFORESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.c b/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.c new file mode 100644 index 0000000..4d5660e --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.c @@ -0,0 +1,17 @@ +#include "RemoveXAttrMsg.h" + +const struct NetMessageOps RemoveXAttrMsg_Ops = { + .serializePayload = RemoveXAttrMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void RemoveXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RemoveXAttrMsg* thisCast = (RemoveXAttrMsg*)this; + + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + Serialization_serializeStr(ctx, strlen(thisCast->name), thisCast->name); +} diff --git a/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.h b/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.h new file mode 100644 index 0000000..9d0faef --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RemoveXAttrMsg.h @@ -0,0 +1,40 @@ +#ifndef REMOVEXATTRMSG_H_ +#define REMOVEXATTRMSG_H_ + +#include +#include + +struct RemoveXAttrMsg; +typedef struct RemoveXAttrMsg RemoveXAttrMsg; + +static inline void RemoveXAttrMsg_init(RemoveXAttrMsg* this); +static inline void RemoveXAttrMsg_initFromEntryInfoAndName(RemoveXAttrMsg* this, + const EntryInfo* entryInfo, const char* name); + +// virtual functions +extern void RemoveXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +struct RemoveXAttrMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; + const char* name; +}; + +extern const struct NetMessageOps RemoveXAttrMsg_Ops; + +void RemoveXAttrMsg_init(RemoveXAttrMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RemoveXAttr, &RemoveXAttrMsg_Ops); +} + +void RemoveXAttrMsg_initFromEntryInfoAndName(RemoveXAttrMsg* this, const EntryInfo* entryInfo, + const char* name) +{ + RemoveXAttrMsg_init(this); + this->entryInfoPtr = entryInfo; + this->name = name; +} + +#endif /*REMOVEXATTRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h b/client_module/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h new file mode 100644 index 0000000..c7e70f8 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h @@ -0,0 +1,27 @@ +#ifndef REMOVEXATTRRESPMSG_H_ +#define REMOVEXATTRRESPMSG_H_ + +#include + +struct RemoveXAttrRespMsg; +typedef struct RemoveXAttrRespMsg RemoveXAttrRespMsg; + +static inline void RemoveXAttrRespMsg_init(RemoveXAttrRespMsg* this); + +struct RemoveXAttrRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + +void RemoveXAttrRespMsg_init(RemoveXAttrRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_RemoveXAttrResp); +} + +// getters & setters +static inline int RemoveXAttrRespMsg_getValue(RemoveXAttrRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + +#endif /*REMOVEXATTRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/SetAttrMsg.c b/client_module/source/common/net/message/storage/attribs/SetAttrMsg.c new file mode 100644 index 0000000..f15a959 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetAttrMsg.c @@ -0,0 +1,39 @@ +#include "SetAttrMsg.h" + + +const struct NetMessageOps SetAttrMsg_Ops = { + .serializePayload = SetAttrMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void SetAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SetAttrMsg* thisCast = (SetAttrMsg*)this; + + // validAttribs + Serialization_serializeInt(ctx, thisCast->validAttribs); + + // mode + Serialization_serializeInt(ctx, thisCast->attribs.mode); + + // modificationTimeSecs + Serialization_serializeInt64(ctx, thisCast->attribs.modificationTimeSecs); + + // lastAccessTimeSecs + Serialization_serializeInt64(ctx, thisCast->attribs.lastAccessTimeSecs); + + // userID + Serialization_serializeUInt(ctx, thisCast->attribs.userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->attribs.groupID); + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + if (this->msgHeader.msgFeatureFlags & SETATTRMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/attribs/SetAttrMsg.h b/client_module/source/common/net/message/storage/attribs/SetAttrMsg.h new file mode 100644 index 0000000..98dea2f --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetAttrMsg.h @@ -0,0 +1,63 @@ +#ifndef SETATTRMSG_H_ +#define SETATTRMSG_H_ + +#include +#include +#include +#include + + +#define SETATTRMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define SETATTRMSG_FLAG_BUDDYMIRROR_SECOND 2 /* secondary of group, otherwise primary */ +#define SETATTRMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ + + +struct SetAttrMsg; +typedef struct SetAttrMsg SetAttrMsg; + +static inline void SetAttrMsg_init(SetAttrMsg* this); +static inline void SetAttrMsg_initFromEntryInfo(SetAttrMsg* this, const EntryInfo* entryInfo, + int validAttribs, SettableFileAttribs* attribs, const struct FileEvent* fileEvent); + +// virtual functions +extern void SetAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct SetAttrMsg +{ + NetMessage netMessage; + + int validAttribs; + SettableFileAttribs attribs; + + // for serialization + const EntryInfo* entryInfoPtr; + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps SetAttrMsg_Ops; + +void SetAttrMsg_init(SetAttrMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_SetAttr, &SetAttrMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param validAttribs a combination of SETATTR_CHANGE_...-Flags + */ +void SetAttrMsg_initFromEntryInfo(SetAttrMsg* this, const EntryInfo* entryInfo, int validAttribs, + SettableFileAttribs* attribs, const struct FileEvent* fileEvent) +{ + SetAttrMsg_init(this); + + this->entryInfoPtr = entryInfo; + this->validAttribs = validAttribs; + this->attribs = *attribs; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= SETATTRMSG_FLAG_HAS_EVENT; +} + +#endif /*SETATTRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/SetAttrRespMsg.h b/client_module/source/common/net/message/storage/attribs/SetAttrRespMsg.h new file mode 100644 index 0000000..b0fce6b --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetAttrRespMsg.h @@ -0,0 +1,31 @@ +#ifndef SETATTRRESPMSG_H_ +#define SETATTRRESPMSG_H_ + +#include + + +struct SetAttrRespMsg; +typedef struct SetAttrRespMsg SetAttrRespMsg; + +static inline void SetAttrRespMsg_init(SetAttrRespMsg* this); + +// getters & setters +static inline int SetAttrRespMsg_getValue(SetAttrRespMsg* this); + +struct SetAttrRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void SetAttrRespMsg_init(SetAttrRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_SetAttrResp); +} + +int SetAttrRespMsg_getValue(SetAttrRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + +#endif /*SETATTRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.c b/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.c new file mode 100644 index 0000000..d07ffd8 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.c @@ -0,0 +1,19 @@ +#include "SetXAttrMsg.h" + +const struct NetMessageOps SetXAttrMsg_Ops = { + .serializePayload = SetXAttrMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void SetXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + SetXAttrMsg* thisCast = (SetXAttrMsg*)this; + + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + Serialization_serializeStr(ctx, strlen(thisCast->name), thisCast->name); + Serialization_serializeCharArray(ctx, thisCast->size, thisCast->value); + Serialization_serializeInt(ctx, thisCast->flags); +} diff --git a/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.h b/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.h new file mode 100644 index 0000000..49832e9 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetXAttrMsg.h @@ -0,0 +1,48 @@ +#ifndef SETXATTRMSG_H_ +#define SETXATTRMSG_H_ + +#include +#include + +struct SetXAttrMsg; +typedef struct SetXAttrMsg SetXAttrMsg; + +static inline void SetXAttrMsg_init(SetXAttrMsg* this); +static inline void SetXAttrMsg_initFromEntryInfoNameValueAndSize(SetXAttrMsg* this, + const EntryInfo* entryInfo, const char* name, const char* value, const size_t size, + const int flags); + +// virtual functions +extern void SetXAttrMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +struct SetXAttrMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; + + const char* name; + const char* value; + size_t size; + int flags; +}; + +extern const struct NetMessageOps SetXAttrMsg_Ops; + +void SetXAttrMsg_init(SetXAttrMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_SetXAttr, &SetXAttrMsg_Ops); +} + +void SetXAttrMsg_initFromEntryInfoNameValueAndSize(SetXAttrMsg* this, const EntryInfo* entryInfo, + const char* name, const char* value, const size_t size, const int flags) +{ + SetXAttrMsg_init(this); + this->entryInfoPtr = entryInfo; + this->name = name; + this->value = value; + this->size = size; + this->flags = flags; +} + +#endif /*SETXATTRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/SetXAttrRespMsg.h b/client_module/source/common/net/message/storage/attribs/SetXAttrRespMsg.h new file mode 100644 index 0000000..16b83f4 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/SetXAttrRespMsg.h @@ -0,0 +1,27 @@ +#ifndef SETXATTRRESPMSG_H_ +#define SETXATTRRESPMSG_H_ + +#include + +struct SetXAttrRespMsg; +typedef struct SetXAttrRespMsg SetXAttrRespMsg; + +static inline void SetXAttrRespMsg_init(SetXAttrRespMsg* this); + +struct SetXAttrRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + +void SetXAttrRespMsg_init(SetXAttrRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_SetXAttrResp); +} + +// getters & setters +static inline int SetXAttrRespMsg_getValue(SetXAttrRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + +#endif /*SETXATTRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/StatMsg.c b/client_module/source/common/net/message/storage/attribs/StatMsg.c new file mode 100644 index 0000000..dec262e --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/StatMsg.c @@ -0,0 +1,22 @@ +#include "StatMsg.h" + + +const struct NetMessageOps StatMsg_Ops = { + .serializePayload = StatMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = StatMsg_getSupportedHeaderFeatureFlagsMask, +}; + +void StatMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + StatMsg* thisCast = (StatMsg*)this; + + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); +} + +unsigned StatMsg_getSupportedHeaderFeatureFlagsMask(NetMessage* this) +{ + return STATMSG_FLAG_GET_PARENTINFO; +} diff --git a/client_module/source/common/net/message/storage/attribs/StatMsg.h b/client_module/source/common/net/message/storage/attribs/StatMsg.h new file mode 100644 index 0000000..805d183 --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/StatMsg.h @@ -0,0 +1,55 @@ +#ifndef STATMSG_H_ +#define STATMSG_H_ + +#include +#include +#include + +#define STATMSG_FLAG_GET_PARENTINFO 1 /* caller wants to have parentOwnerNodeID and parentEntryID */ + + +struct StatMsg; +typedef struct StatMsg StatMsg; + +static inline void StatMsg_init(StatMsg* this); +static inline void StatMsg_initFromEntryInfo(StatMsg* this, const EntryInfo* entryInfo); +static inline void StatMsg_addParentInfoRequest(StatMsg* this); + +// virtual functions +extern void StatMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); +extern unsigned StatMsg_getSupportedHeaderFeatureFlagsMask(NetMessage* this); + + +struct StatMsg +{ + NetMessage netMessage; + + const EntryInfo* entryInfoPtr; // not owed by this object +}; + +extern const struct NetMessageOps StatMsg_Ops; + +void StatMsg_init(StatMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_Stat, &StatMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ +void StatMsg_initFromEntryInfo(StatMsg* this, const EntryInfo* entryInfo) +{ + StatMsg_init(this); + + this->entryInfoPtr = entryInfo; +} + +void StatMsg_addParentInfoRequest(StatMsg* this) +{ + NetMessage* netMsg = (NetMessage*) this; + + NetMessage_addMsgHeaderFeatureFlag(netMsg, STATMSG_FLAG_GET_PARENTINFO); +} + + +#endif /*STATMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/attribs/StatRespMsg.c b/client_module/source/common/net/message/storage/attribs/StatRespMsg.c new file mode 100644 index 0000000..6817dff --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/StatRespMsg.c @@ -0,0 +1,44 @@ +#include "StatRespMsg.h" + + +const struct NetMessageOps StatRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = StatRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = StatRespMsg_getSupportedHeaderFeatureFlagsMask, +}; + +bool StatRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + StatRespMsg* thisCast = (StatRespMsg*)this; + + // result + if(!Serialization_deserializeInt(ctx, &thisCast->result) ) + return false; + + // statData + if(!StatData_deserialize(ctx, &thisCast->statData) ) + return false; + + if(NetMessage_isMsgHeaderFeatureFlagSet(this, STATRESPMSG_FLAG_HAS_PARENTINFO) ) + { + { // parentEntryID + unsigned strLen; + + if (!Serialization_deserializeStrAlign4(ctx, &strLen, &thisCast->parentEntryID) ) + return false; + } + + // parentNodeID + if(!NumNodeID_deserialize(ctx, &thisCast->parentNodeID) ) + return false; + } + + + return true; +} + +unsigned StatRespMsg_getSupportedHeaderFeatureFlagsMask(NetMessage* this) +{ + return STATRESPMSG_FLAG_HAS_PARENTINFO; +} diff --git a/client_module/source/common/net/message/storage/attribs/StatRespMsg.h b/client_module/source/common/net/message/storage/attribs/StatRespMsg.h new file mode 100644 index 0000000..e73405b --- /dev/null +++ b/client_module/source/common/net/message/storage/attribs/StatRespMsg.h @@ -0,0 +1,75 @@ +#ifndef STATRESPMSG_H_ +#define STATRESPMSG_H_ + +#include +#include + + +#define STATRESPMSG_FLAG_HAS_PARENTINFO 1 /* msg includes parentOwnerNodeID and + parentEntryID */ + + +struct StatRespMsg; +typedef struct StatRespMsg StatRespMsg; + +static inline void StatRespMsg_init(StatRespMsg* this); +static inline void StatRespMsg_getParentInfo(StatRespMsg* this, NumNodeID* outParentNodeID, + char** outParentEntryID); + +// virtual functions +extern bool StatRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); +extern unsigned StatRespMsg_getSupportedHeaderFeatureFlagsMask(NetMessage* this); + +// getters & setters +static inline int StatRespMsg_getResult(StatRespMsg* this); +static inline StatData* StatRespMsg_getStatData(StatRespMsg* this); + +struct StatRespMsg +{ + NetMessage netMessage; + + int result; + StatData statData; + + const char* parentEntryID; + NumNodeID parentNodeID; +}; + +extern const struct NetMessageOps StatRespMsg_Ops; + +void StatRespMsg_init(StatRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_StatResp, &StatRespMsg_Ops); + + NumNodeID_set(&this->parentNodeID, 0); + this->parentEntryID = NULL; +} + +int StatRespMsg_getResult(StatRespMsg* this) +{ + return this->result; +} + + +StatData* StatRespMsg_getStatData(StatRespMsg* this) +{ + return &this->statData; +} + +/** + * Get parentInfo + * + * Note: outParentEntryID is a string copy + */ +void StatRespMsg_getParentInfo(StatRespMsg* this, NumNodeID* outParentNodeID, char** outParentEntryID) +{ + if (outParentNodeID) + { + *outParentNodeID = this->parentNodeID; + + if (likely(outParentEntryID) ) + *outParentEntryID = StringTk_strDup(this->parentEntryID); + } +} + +#endif /*STATRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/HardlinkMsg.c b/client_module/source/common/net/message/storage/creating/HardlinkMsg.c new file mode 100644 index 0000000..857f9ff --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/HardlinkMsg.c @@ -0,0 +1,33 @@ +#include "HardlinkMsg.h" + + +const struct NetMessageOps HardlinkMsg_Ops = { + .serializePayload = HardlinkMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void HardlinkMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + HardlinkMsg* thisCast = (HardlinkMsg*)this; + + // fromInfo + EntryInfo_serialize(ctx, thisCast->fromInfo); + + // toDirInfo + EntryInfo_serialize(ctx, thisCast->toDirInfo); + + // toName + Serialization_serializeStrAlign4(ctx, thisCast->toNameLen, thisCast->toName); + + // fromDirInfo + EntryInfo_serialize(ctx, thisCast->fromDirInfo); + + // fromName + Serialization_serializeStrAlign4(ctx, thisCast->fromNameLen, thisCast->fromName); + + if (this->msgHeader.msgFeatureFlags & HARDLINKMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/creating/HardlinkMsg.h b/client_module/source/common/net/message/storage/creating/HardlinkMsg.h new file mode 100644 index 0000000..f199586 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/HardlinkMsg.h @@ -0,0 +1,80 @@ +#ifndef HARDLINKMSG_H_ +#define HARDLINKMSG_H_ + +#include +#include +#include +#include + +#define HARDLINKMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ + +/* This is the HardlinkMsg class, deserialization is not implemented, as the client is not + * supposed to deserialize the message. There is also only a basic constructor, if a full + * constructor is needed, it can be easily added later on */ + +struct HardlinkMsg; +typedef struct HardlinkMsg HardlinkMsg; + +static inline void HardlinkMsg_init(HardlinkMsg* this); +static inline void HardlinkMsg_initFromEntryInfo(HardlinkMsg* this, const EntryInfo* fromDirInfo, + const char* fromName, unsigned fromLen, const EntryInfo* fromInfo, const EntryInfo* toDirInfo, + const char* toName, unsigned toNameLen, const struct FileEvent* fileEvent); + +// virtual functions +extern void HardlinkMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct HardlinkMsg +{ + NetMessage netMessage; + + // for serialization + + const char* fromName; // not owned by this object! + const EntryInfo* fromInfo; // not owned by this object! + + unsigned fromNameLen; + const EntryInfo* fromDirInfo; // not owned by this object! + + const char* toName; // not owned by this object! + unsigned toNameLen; + const EntryInfo* toDirInfo; // not owned by this object! + + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps HardlinkMsg_Ops; + +void HardlinkMsg_init(HardlinkMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_Hardlink, &HardlinkMsg_Ops); +} + +/** + * @param fromName just a reference, so do not free it as long as you use this object! + * @param fromDirInfo just a reference, so do not free it as long as you use this object! + * @param toName just a reference, so do not free it as long as you use this object! + * @param toDirInfo just a reference, so do not free it as long as you use this object! + */ +void HardlinkMsg_initFromEntryInfo(HardlinkMsg* this, const EntryInfo* fromDirInfo, + const char* fromName, unsigned fromLen, const EntryInfo* fromInfo, const EntryInfo* toDirInfo, + const char* toName, unsigned toNameLen, const struct FileEvent* fileEvent) +{ + HardlinkMsg_init(this); + + this->fromName = fromName; + this->fromInfo = fromInfo; + + this->fromNameLen = fromLen; + this->fromDirInfo = fromDirInfo; + + this->toName = toName; + this->toNameLen = toNameLen; + this->toDirInfo = toDirInfo; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= HARDLINKMSG_FLAG_HAS_EVENT; +} + +#endif /*HARDLINKMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/HardlinkRespMsg.h b/client_module/source/common/net/message/storage/creating/HardlinkRespMsg.h new file mode 100644 index 0000000..4e0caa0 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/HardlinkRespMsg.h @@ -0,0 +1,32 @@ +#ifndef HARDLINKRESPMSG_H_ +#define HARDLINKRESPMSG_H_ + +#include + + +struct HardlinkRespMsg; +typedef struct HardlinkRespMsg HardlinkRespMsg; + +static inline void HardlinkRespMsg_init(HardlinkRespMsg* this); + +// getters & setters +static inline int HardlinkRespMsg_getValue(HardlinkRespMsg* this); + +struct HardlinkRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void HardlinkRespMsg_init(HardlinkRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_HardlinkResp); +} + +int HardlinkRespMsg_getValue(HardlinkRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + +#endif /*HARDLINKRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/MkDirMsg.c b/client_module/source/common/net/message/storage/creating/MkDirMsg.c new file mode 100644 index 0000000..359d126 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkDirMsg.c @@ -0,0 +1,39 @@ +#include "MkDirMsg.h" + + +const struct NetMessageOps MkDirMsg_Ops = { + .serializePayload = MkDirMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void MkDirMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + MkDirMsg* thisCast = (MkDirMsg*)this; + + // userID + Serialization_serializeUInt(ctx, thisCast->userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->groupID); + + // mode + Serialization_serializeInt(ctx, thisCast->mode); + + // umask + Serialization_serializeInt(ctx, thisCast->umask); + + // parentInfo + EntryInfo_serialize(ctx, thisCast->parentInfo); + + // newDirName + Serialization_serializeStrAlign4(ctx, thisCast->newDirNameLen, thisCast->newDirName); + + // preferredNodes + Serialization_serializeUInt16List(ctx, thisCast->preferredNodes); + + if (this->msgHeader.msgFeatureFlags & MKDIRMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/creating/MkDirMsg.h b/client_module/source/common/net/message/storage/creating/MkDirMsg.h new file mode 100644 index 0000000..5fe064e --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkDirMsg.h @@ -0,0 +1,80 @@ +#ifndef MKDIRMSG_H_ +#define MKDIRMSG_H_ + +#include +#include +#include + + +/** + * this message supports only serialization, deserialization is not implemented. + */ + +#define MKDIRMSG_FLAG_NOMIRROR 1 /* do not use mirror setting from parent + * (i.e. do not mirror) */ +#define MKDIRMSG_FLAG_BUDDYMIRROR_SECOND 2 /* if this message goes to a buddy group, this + * indicates, that it was sent to secondary of group */ +#define MKDIRMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ + +struct MkDirMsg; +typedef struct MkDirMsg MkDirMsg; + +static inline void MkDirMsg_init(MkDirMsg* this); +static inline void MkDirMsg_initFromEntryInfo(MkDirMsg* this, const EntryInfo* parentInfo, + struct CreateInfo* createInfo); + +// virtual functions +extern void MkDirMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + + +struct MkDirMsg +{ + NetMessage netMessage; + + unsigned userID; + unsigned groupID; + int mode; + int umask; + + // for serialization + const EntryInfo* parentInfo; // not owned by this object! + + const char* newDirName; + unsigned newDirNameLen; + + UInt16List* preferredNodes; // not owned by this object! + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps MkDirMsg_Ops; + +void MkDirMsg_init(MkDirMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_MkDir, &MkDirMsg_Ops); +} + +/** + * @param path just a reference, so do not free it as long as you use this object! + */ +void MkDirMsg_initFromEntryInfo(MkDirMsg* this, const EntryInfo* parentInfo, + struct CreateInfo* createInfo) +{ + MkDirMsg_init(this); + + this->parentInfo = parentInfo; + this->newDirName = createInfo->entryName; + this->newDirNameLen = strlen(createInfo->entryName); + + this->userID = createInfo->userID; + this->groupID = createInfo->groupID; + this->mode = createInfo->mode; + this->umask = createInfo->umask; + this->preferredNodes = createInfo->preferredMetaTargets; + this->fileEvent = createInfo->fileEvent; + + if (createInfo->fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= MKDIRMSG_FLAG_HAS_EVENT; +} + +#endif /*MKDIRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/MkDirRespMsg.c b/client_module/source/common/net/message/storage/creating/MkDirRespMsg.c new file mode 100644 index 0000000..b1ce142 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkDirRespMsg.c @@ -0,0 +1,26 @@ +#include "MkDirRespMsg.h" +#include + +const struct NetMessageOps MkDirRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = MkDirRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool MkDirRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + MkDirRespMsg* thisCast = (MkDirRespMsg*)this; + + // result + if(!Serialization_deserializeUInt(ctx, &thisCast->result) ) + return false; + + if ( (FhgfsOpsErr)thisCast->result == FhgfsOpsErr_SUCCESS) + { // entryInfo, empty object if result != FhgfsOpsErr_SUCCESS, deserialization would fail then + if (!EntryInfo_deserialize(ctx, &thisCast->entryInfo) ) + return false; + } + + return true; +} diff --git a/client_module/source/common/net/message/storage/creating/MkDirRespMsg.h b/client_module/source/common/net/message/storage/creating/MkDirRespMsg.h new file mode 100644 index 0000000..8926a51 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkDirRespMsg.h @@ -0,0 +1,47 @@ +#ifndef MKDIRRESPMSG_H_ +#define MKDIRRESPMSG_H_ + +#include +#include + +struct MkDirRespMsg; +typedef struct MkDirRespMsg MkDirRespMsg; + +static inline void MkDirRespMsg_init(MkDirRespMsg* this); + +// virtual functions +extern bool MkDirRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline int MkDirRespMsg_getResult(MkDirRespMsg* this); +static inline const EntryInfo* MkDirRespMsg_getEntryInfo(MkDirRespMsg* this); + + +struct MkDirRespMsg +{ + NetMessage netMessage; + + int result; + + // for deserialization + EntryInfo entryInfo; +}; + +extern const struct NetMessageOps MkDirRespMsg_Ops; + +void MkDirRespMsg_init(MkDirRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_MkDirResp, &MkDirRespMsg_Ops); +} + +int MkDirRespMsg_getResult(MkDirRespMsg* this) +{ + return this->result; +} + +const EntryInfo* MkDirRespMsg_getEntryInfo(MkDirRespMsg* this) +{ + return &this->entryInfo; +} + +#endif /*MKDIRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/MkFileMsg.c b/client_module/source/common/net/message/storage/creating/MkFileMsg.c new file mode 100644 index 0000000..28a2674 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkFileMsg.c @@ -0,0 +1,56 @@ +#include "MkFileMsg.h" + + +const struct NetMessageOps MkFileMsg_Ops = { + .serializePayload = MkFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void MkFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + MkFileMsg* thisCast = (MkFileMsg*)this; + + // userID + Serialization_serializeUInt(ctx, thisCast->userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->groupID); + + // mode + Serialization_serializeInt(ctx, thisCast->mode); + + // umask + Serialization_serializeInt(ctx, thisCast->umask); + + // optional stripe hints + if(NetMessage_isMsgHeaderFeatureFlagSet( (NetMessage*)this, MKFILEMSG_FLAG_STRIPEHINTS) ) + { + // numtargets + Serialization_serializeUInt(ctx, thisCast->numtargets); + + // chunksize + Serialization_serializeUInt(ctx, thisCast->chunksize); + } + + // optional storage pool ID to overide settings in parent directory + if(NetMessage_isMsgHeaderFeatureFlagSet( (NetMessage*)this, MKFILEMSG_FLAG_STORAGEPOOLID) ) + { + // storagePoolId + StoragePoolId_serialize(ctx, &thisCast->storagePoolId); + } + + // parentInfoPtr + EntryInfo_serialize(ctx, thisCast->parentInfoPtr); + + // newFileName + Serialization_serializeStrAlign4(ctx, thisCast->newFileNameLen, thisCast->newFileName); + + // preferredTargets + Serialization_serializeUInt16List(ctx, thisCast->preferredTargets); + + if (this->msgHeader.msgFeatureFlags & MKFILEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/creating/MkFileMsg.h b/client_module/source/common/net/message/storage/creating/MkFileMsg.h new file mode 100644 index 0000000..77d8775 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkFileMsg.h @@ -0,0 +1,116 @@ +#ifndef MKFILEMSG_H_ +#define MKFILEMSG_H_ + +#include +#include +#include +#include + + +#define MKFILEMSG_FLAG_STRIPEHINTS 1 /* msg contains extra stripe hints */ +#define MKFILEMSG_FLAG_STORAGEPOOLID 2 /* msg contains a storage pool ID to override parent */ +#define MKFILEMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ + +struct MkFileMsg; +typedef struct MkFileMsg MkFileMsg; + +static inline void MkFileMsg_init(MkFileMsg* this); +static inline void MkFileMsg_initFromEntryInfo(MkFileMsg* this, const EntryInfo* parentInfo, + struct CreateInfo* createInfo); + +// virtual functions +extern void MkFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +// getters & setters +static inline void MkFileMsg_setStripeHints(MkFileMsg* this, + unsigned numtargets, unsigned chunksize); +static inline void MkFileMsg_setStoragePoolId(MkFileMsg* this, StoragePoolId StoragePoolId); + + +/* + * note: this message supports serialization only, deserialization is not implemented + */ +struct MkFileMsg +{ + NetMessage netMessage; + + unsigned userID; + unsigned groupID; + int mode; + int umask; + + unsigned numtargets; + unsigned chunksize; + + // if this is set, the storage pool of the parent directory is ignored, and this pool is used + // instead + StoragePoolId storagePoolId; + + // for serialization + const EntryInfo* parentInfoPtr; + const char* newFileName; + unsigned newFileNameLen; + UInt16List* preferredTargets; // not owned by this object! + const struct FileEvent* fileEvent; + + // for deserialization + EntryInfo entryInfo; + unsigned prefNodesElemNum; + const char* prefNodesListStart; + unsigned prefNodesBufLen; +}; + +extern const struct NetMessageOps MkFileMsg_Ops; + +void MkFileMsg_init(MkFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_MkFile, &MkFileMsg_Ops); +} + +/** + * @param parentInfo just a reference, so do not free it as long as you use this object! + */ +void MkFileMsg_initFromEntryInfo(MkFileMsg* this, const EntryInfo* parentInfo, + struct CreateInfo* createInfo) +{ + MkFileMsg_init(this); + + this->parentInfoPtr = parentInfo; + this->newFileName = createInfo->entryName; + this->newFileNameLen = strlen(createInfo->entryName); + this->userID = createInfo->userID; + this->groupID = createInfo->groupID; + this->mode = createInfo->mode; + this->umask = createInfo->umask; + this->preferredTargets = createInfo->preferredStorageTargets; + this->fileEvent = createInfo->fileEvent; + + if (createInfo->fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= MKFILEMSG_FLAG_HAS_EVENT; + + if (createInfo->storagePoolId.value != STORAGEPOOLID_INVALIDPOOLID) + MkFileMsg_setStoragePoolId(this, createInfo->storagePoolId); +} + +/** + * Note: Adds MKFILEMSG_FLAG_STRIPEHINTS. + */ +void MkFileMsg_setStripeHints(MkFileMsg* this, unsigned numtargets, unsigned chunksize) +{ + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)this, MKFILEMSG_FLAG_STRIPEHINTS); + + this->numtargets = numtargets; + this->chunksize = chunksize; +} + +/** + * Note: Adds MKFILEMSG_FLAG_STORAGEPOOLID. + */ +void MkFileMsg_setStoragePoolId(MkFileMsg* this, StoragePoolId StoragePoolId) +{ + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)this, MKFILEMSG_FLAG_STORAGEPOOLID); + + this->storagePoolId = StoragePoolId; +} + +#endif /*MKFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/MkFileRespMsg.c b/client_module/source/common/net/message/storage/creating/MkFileRespMsg.c new file mode 100644 index 0000000..9cf2a66 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkFileRespMsg.c @@ -0,0 +1,27 @@ +#include +#include "MkFileRespMsg.h" + + +const struct NetMessageOps MkFileRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = MkFileRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool MkFileRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + MkFileRespMsg* thisCast = (MkFileRespMsg*)this; + + // result + if(!Serialization_deserializeUInt(ctx, &thisCast->result) ) + return false; + + if ( (FhgfsOpsErr)thisCast->result == FhgfsOpsErr_SUCCESS) + { // entryInfo, empty object if result != FhgfsOpsErr_SUCCESS, deserialization would fail then + if (!EntryInfo_deserialize(ctx, &thisCast->entryInfo) ) + return false; + } + + return true; +} diff --git a/client_module/source/common/net/message/storage/creating/MkFileRespMsg.h b/client_module/source/common/net/message/storage/creating/MkFileRespMsg.h new file mode 100644 index 0000000..0863baa --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/MkFileRespMsg.h @@ -0,0 +1,45 @@ +#ifndef MKFILERESPMSG_H_ +#define MKFILERESPMSG_H_ + +#include +#include + +struct MkFileRespMsg; +typedef struct MkFileRespMsg MkFileRespMsg; + +static inline void MkFileRespMsg_init(MkFileRespMsg* this); + +// virtual functions +extern bool MkFileRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// getters & setters +static inline int MkFileRespMsg_getResult(MkFileRespMsg* this); +static inline const EntryInfo* MkFileRespMsg_getEntryInfo(MkFileRespMsg* this); + + +struct MkFileRespMsg +{ + NetMessage netMessage; + + int result; + EntryInfo entryInfo; +}; + +extern const struct NetMessageOps MkFileRespMsg_Ops; + +void MkFileRespMsg_init(MkFileRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_MkFileResp, &MkFileRespMsg_Ops); +} + +int MkFileRespMsg_getResult(MkFileRespMsg* this) +{ + return this->result; +} + +const EntryInfo* MkFileRespMsg_getEntryInfo(MkFileRespMsg* this) +{ + return &this->entryInfo; +} + +#endif /*MKFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/RmDirMsg.c b/client_module/source/common/net/message/storage/creating/RmDirMsg.c new file mode 100644 index 0000000..d0a23e8 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/RmDirMsg.c @@ -0,0 +1,24 @@ +#include "RmDirMsg.h" + + +const struct NetMessageOps RmDirMsg_Ops = { + .serializePayload = RmDirMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void RmDirMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RmDirMsg* thisCast = (RmDirMsg*)this; + + // parentInfo + EntryInfo_serialize(ctx, thisCast->parentInfo); + + // delDirName + Serialization_serializeStrAlign4(ctx, thisCast->delDirNameLen, thisCast->delDirName); + + if (this->msgHeader.msgFeatureFlags & RMDIRMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/creating/RmDirMsg.h b/client_module/source/common/net/message/storage/creating/RmDirMsg.h new file mode 100644 index 0000000..f350888 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/RmDirMsg.h @@ -0,0 +1,62 @@ +#ifndef RMDIRMSG_H_ +#define RMDIRMSG_H_ + +#include +#include +#include +#include + +#define RMDIRMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +struct RmDirMsg; +typedef struct RmDirMsg RmDirMsg; + +static inline void RmDirMsg_init(RmDirMsg* this); +static inline void RmDirMsg_initFromEntryInfo(RmDirMsg* this, const EntryInfo* parentInfo, + const char* delDirName, const struct FileEvent* fileEvent); + +// virtual functions +extern void RmDirMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +// inliners + +struct RmDirMsg +{ + NetMessage netMessage; + + // for serialization + const EntryInfo* parentInfo; // not owned by this object! + const char* delDirName; // not owned by this object! + unsigned delDirNameLen; + const struct FileEvent* fileEvent; + + // for deserialization +}; + +extern const struct NetMessageOps RmDirMsg_Ops; + +void RmDirMsg_init(RmDirMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_RmDir, &RmDirMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param delDirName just a reference, so do not free it as long as you use this object! + */ +void RmDirMsg_initFromEntryInfo(RmDirMsg* this, const EntryInfo* parentInfo, + const char* delDirName, const struct FileEvent* fileEvent) +{ + RmDirMsg_init(this); + + this->parentInfo = parentInfo; + + this->delDirName = delDirName; + this->delDirNameLen = strlen(delDirName); + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= RMDIRMSG_FLAG_HAS_EVENT; +} + +#endif /*RMDIRMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/RmDirRespMsg.h b/client_module/source/common/net/message/storage/creating/RmDirRespMsg.h new file mode 100644 index 0000000..e5d3bd9 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/RmDirRespMsg.h @@ -0,0 +1,33 @@ +#ifndef RMDIRRESPMSG_H_ +#define RMDIRRESPMSG_H_ + +#include + + +struct RmDirRespMsg; +typedef struct RmDirRespMsg RmDirRespMsg; + +static inline void RmDirRespMsg_init(RmDirRespMsg* this); + +// getters & setters +static inline int RmDirRespMsg_getValue(RmDirRespMsg* this); + +struct RmDirRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void RmDirRespMsg_init(RmDirRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_RmDirResp); +} + +int RmDirRespMsg_getValue(RmDirRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + + +#endif /*RMDIRRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.c b/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.c new file mode 100644 index 0000000..f6c3c6a --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.c @@ -0,0 +1,24 @@ +#include "UnlinkFileMsg.h" + + +const struct NetMessageOps UnlinkFileMsg_Ops = { + .serializePayload = UnlinkFileMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void UnlinkFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + UnlinkFileMsg* thisCast = (UnlinkFileMsg*)this; + + // parentInf + EntryInfo_serialize(ctx, thisCast->parentInfoPtr); + + // delFileName + Serialization_serializeStrAlign4(ctx, thisCast->delFileNameLen, thisCast->delFileName); + + if (this->msgHeader.msgFeatureFlags & UNLINKFILEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.h b/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.h new file mode 100644 index 0000000..bc24475 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/UnlinkFileMsg.h @@ -0,0 +1,57 @@ +#ifndef UNLINKFILEMSG_H_ +#define UNLINKFILEMSG_H_ + +#include +#include +#include + +#define UNLINKFILEMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +struct UnlinkFileMsg; +typedef struct UnlinkFileMsg UnlinkFileMsg; + +static inline void UnlinkFileMsg_init(UnlinkFileMsg* this); +static inline void UnlinkFileMsg_initFromEntryInfo(UnlinkFileMsg* this, + const EntryInfo* parentInfo, const char* delFileName, const struct FileEvent* fileEvent); + +// virtual functions +extern void UnlinkFileMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct UnlinkFileMsg +{ + NetMessage netMessage; + + // for serialization + const EntryInfo* parentInfoPtr; // not owned by this object! + const char* delFileName; // file name to be delete, not owned by this object + unsigned delFileNameLen; + + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps UnlinkFileMsg_Ops; + +void UnlinkFileMsg_init(UnlinkFileMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_UnlinkFile, &UnlinkFileMsg_Ops); +} + +/** + * @param path just a reference, so do not free it as long as you use this object! + */ +void UnlinkFileMsg_initFromEntryInfo(UnlinkFileMsg* this, const EntryInfo* parentInfo, + const char* delFileName, const struct FileEvent* fileEvent) +{ + UnlinkFileMsg_init(this); + + this->parentInfoPtr = parentInfo; + this->delFileName = delFileName; + this->delFileNameLen = strlen(delFileName); + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= UNLINKFILEMSG_FLAG_HAS_EVENT; +} + +#endif /*UNLINKFILEMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/creating/UnlinkFileRespMsg.h b/client_module/source/common/net/message/storage/creating/UnlinkFileRespMsg.h new file mode 100644 index 0000000..c469ff5 --- /dev/null +++ b/client_module/source/common/net/message/storage/creating/UnlinkFileRespMsg.h @@ -0,0 +1,33 @@ +#ifndef UNLINKFILERESPMSG_H_ +#define UNLINKFILERESPMSG_H_ + +#include + + +struct UnlinkFileRespMsg; +typedef struct UnlinkFileRespMsg UnlinkFileRespMsg; + +static inline void UnlinkFileRespMsg_init(UnlinkFileRespMsg* this); + +// getters & setters +static inline int UnlinkFileRespMsg_getValue(UnlinkFileRespMsg* this); + +struct UnlinkFileRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void UnlinkFileRespMsg_init(UnlinkFileRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_UnlinkFileResp); +} + +int UnlinkFileRespMsg_getValue(UnlinkFileRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + + +#endif /*UNLINKFILERESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.c b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.c new file mode 100644 index 0000000..769fb7d --- /dev/null +++ b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.c @@ -0,0 +1,26 @@ +#include "ListDirFromOffsetMsg.h" + + +const struct NetMessageOps ListDirFromOffsetMsg_Ops = { + .serializePayload = ListDirFromOffsetMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +void ListDirFromOffsetMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + ListDirFromOffsetMsg* thisCast = (ListDirFromOffsetMsg*)this; + + // serverOffset + Serialization_serializeInt64(ctx, thisCast->serverOffset); + + // maxOutNames + Serialization_serializeUInt(ctx, thisCast->maxOutNames); + + // EntryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + + // filterDots + Serialization_serializeBool(ctx, thisCast->filterDots); +} diff --git a/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h new file mode 100644 index 0000000..98a2b07 --- /dev/null +++ b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h @@ -0,0 +1,64 @@ +#ifndef LISTDIRFROMOFFSETMSG_H_ +#define LISTDIRFROMOFFSETMSG_H_ + +#include +#include +#include + +/** + * This message supports only serialization. (deserialization not implemented) + */ + +struct ListDirFromOffsetMsg; +typedef struct ListDirFromOffsetMsg ListDirFromOffsetMsg; + +static inline void ListDirFromOffsetMsg_init(ListDirFromOffsetMsg* this); +static inline void ListDirFromOffsetMsg_initFromEntryInfo(ListDirFromOffsetMsg* this, + const EntryInfo* entryInfo, int64_t serverOffset, unsigned maxOutNames, bool filterDots); + +// virtual functions +extern void ListDirFromOffsetMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + + +struct ListDirFromOffsetMsg +{ + NetMessage netMessage; + + int64_t serverOffset; + unsigned maxOutNames; + bool filterDots; + + // for serialization + const EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; +}; + +extern const struct NetMessageOps ListDirFromOffsetMsg_Ops; + +void ListDirFromOffsetMsg_init(ListDirFromOffsetMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ListDirFromOffset, &ListDirFromOffsetMsg_Ops); +} + +/** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param filterDots true if you don't want "." and ".." in the result list. + */ +void ListDirFromOffsetMsg_initFromEntryInfo(ListDirFromOffsetMsg* this, const EntryInfo* entryInfo, + int64_t serverOffset, unsigned maxOutNames, bool filterDots) +{ + ListDirFromOffsetMsg_init(this); + + this->entryInfoPtr = entryInfo; + + this->serverOffset = serverOffset; + + this->maxOutNames = maxOutNames; + + this->filterDots = filterDots; +} + +#endif /*LISTDIRFROMOFFSETMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.c b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.c new file mode 100644 index 0000000..ebfe877 --- /dev/null +++ b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.c @@ -0,0 +1,53 @@ +#include "ListDirFromOffsetRespMsg.h" + + +const struct NetMessageOps ListDirFromOffsetRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = ListDirFromOffsetRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool ListDirFromOffsetRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + const char* logContext = "ListDirFromOffsetRespMsg deserialization"; + ListDirFromOffsetRespMsg* thisCast = (ListDirFromOffsetRespMsg*)this; + + // newServerOffset + if(!Serialization_deserializeInt64(ctx, &thisCast->newServerOffset) ) + return false; + + // serverOffsets + if(!Serialization_deserializeInt64CpyVecPreprocess(ctx, &thisCast->serverOffsetsList) ) + return false; + + // result + if(!Serialization_deserializeInt(ctx, &thisCast->result) ) + return false; + + // entryTypes + if(!Serialization_deserializeUInt8VecPreprocess(ctx, &thisCast->entryTypesList) ) + return false; + + // entryIDs + if (!Serialization_deserializeStrCpyVecPreprocess(ctx, &thisCast->entryIDsList) ) + return false; + + // names + if(!Serialization_deserializeStrCpyVecPreprocess(ctx, &thisCast->namesList) ) + return false; + + // sanity check for equal list lengths + if(unlikely( + (thisCast->entryTypesList.elemCount != thisCast->namesList.elemCount) || + (thisCast->entryTypesList.elemCount != thisCast->entryIDsList.elemCount) || + (thisCast->entryTypesList.elemCount != thisCast->serverOffsetsList.elemCount) ) ) + { + printk_fhgfs(KERN_INFO, "%s: Sanity check failed (number of list elements does not match)!\n", + logContext); + return false; + } + + + return true; +} diff --git a/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h new file mode 100644 index 0000000..8adb162 --- /dev/null +++ b/client_module/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h @@ -0,0 +1,90 @@ +#ifndef LISTDIRFROMOFFSETRESPMSG_H_ +#define LISTDIRFROMOFFSETRESPMSG_H_ + +#include +#include + + +struct ListDirFromOffsetRespMsg; +typedef struct ListDirFromOffsetRespMsg ListDirFromOffsetRespMsg; + +static inline void ListDirFromOffsetRespMsg_init(ListDirFromOffsetRespMsg* this); + +// virtual functions +extern bool ListDirFromOffsetRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +// inliners +static inline void ListDirFromOffsetRespMsg_parseNames(ListDirFromOffsetRespMsg* this, + StrCpyVec* outNames); +static inline void ListDirFromOffsetRespMsg_parseEntryIDs(ListDirFromOffsetRespMsg* this, + StrCpyVec* outEntryIDs); +static inline void ListDirFromOffsetRespMsg_parseEntryTypes(ListDirFromOffsetRespMsg* this, + UInt8Vec* outEntryTypes); +static inline void ListDirFromOffsetRespMsg_parseServerOffsets(ListDirFromOffsetRespMsg* this, + Int64CpyVec* outServerOffsets); + +// getters & setters +static inline int ListDirFromOffsetRespMsg_getResult(ListDirFromOffsetRespMsg* this); +static inline uint64_t ListDirFromOffsetRespMsg_getNewServerOffset(ListDirFromOffsetRespMsg* this); + + +/** + * Note: Only deserialization supported, no serialization. + */ +struct ListDirFromOffsetRespMsg +{ + NetMessage netMessage; + + int result; + + int64_t newServerOffset; + + // for deserialization + RawList namesList; + RawList entryTypesList; + RawList entryIDsList; + RawList serverOffsetsList; +}; + +extern const struct NetMessageOps ListDirFromOffsetRespMsg_Ops; + +void ListDirFromOffsetRespMsg_init(ListDirFromOffsetRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_ListDirFromOffsetResp, + &ListDirFromOffsetRespMsg_Ops); +} + +void ListDirFromOffsetRespMsg_parseEntryIDs(ListDirFromOffsetRespMsg* this, StrCpyVec* outEntryIDs) +{ + Serialization_deserializeStrCpyVec(&this->entryIDsList, outEntryIDs); +} + +void ListDirFromOffsetRespMsg_parseNames(ListDirFromOffsetRespMsg* this, StrCpyVec* outNames) +{ + Serialization_deserializeStrCpyVec(&this->namesList, outNames); +} + +void ListDirFromOffsetRespMsg_parseEntryTypes(ListDirFromOffsetRespMsg* this, + UInt8Vec* outEntryTypes) +{ + Serialization_deserializeUInt8Vec(&this->entryTypesList, outEntryTypes); +} + +void ListDirFromOffsetRespMsg_parseServerOffsets(ListDirFromOffsetRespMsg* this, + Int64CpyVec* outServerOffsets) +{ + Serialization_deserializeInt64CpyVec(&this->serverOffsetsList, outServerOffsets); +} + +int ListDirFromOffsetRespMsg_getResult(ListDirFromOffsetRespMsg* this) +{ + return this->result; +} + +uint64_t ListDirFromOffsetRespMsg_getNewServerOffset(ListDirFromOffsetRespMsg* this) +{ + return this->newServerOffset; +} + + +#endif /*LISTDIRFROMOFFSETRESPMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.c b/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.c new file mode 100644 index 0000000..f6f72b7 --- /dev/null +++ b/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.c @@ -0,0 +1,62 @@ +#include "LookupIntentMsg.h" + + +const struct NetMessageOps LookupIntentMsg_Ops = { + .serializePayload = LookupIntentMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void LookupIntentMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + LookupIntentMsg* thisCast = (LookupIntentMsg*)this; + + // intentFlags + Serialization_serializeInt(ctx, thisCast->intentFlags); + + // parentInfo + EntryInfo_serialize(ctx, thisCast->parentInfoPtr); + + // entryName + Serialization_serializeStrAlign4(ctx, thisCast->entryNameLen, thisCast->entryName); + + if (thisCast->intentFlags & LOOKUPINTENTMSG_FLAG_REVALIDATE) + { + //metadata version + Serialization_serializeUInt(ctx, thisCast->metaVersion); + // entryInfo + EntryInfo_serialize(ctx, thisCast->entryInfoPtr); + } + + if(thisCast->intentFlags & LOOKUPINTENTMSG_FLAG_OPEN) + { + // accessFlags + Serialization_serializeUInt(ctx, thisCast->accessFlags); + + // clientNumID + NumNodeID_serialize(ctx, &thisCast->clientNumID); + } + + if(thisCast->intentFlags & LOOKUPINTENTMSG_FLAG_CREATE) + { + // userID + Serialization_serializeUInt(ctx, thisCast->userID); + + // groupID + Serialization_serializeUInt(ctx, thisCast->groupID); + + // mode + Serialization_serializeInt(ctx, thisCast->mode); + + // umask + Serialization_serializeInt(ctx, thisCast->umask); + + // preferredTargets + Serialization_serializeUInt16List(ctx, thisCast->preferredTargets); + + if (this->msgHeader.msgFeatureFlags & LOOKUPINTENTMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); + } +} diff --git a/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.h b/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.h new file mode 100644 index 0000000..0524f1d --- /dev/null +++ b/client_module/source/common/net/message/storage/lookup/LookupIntentMsg.h @@ -0,0 +1,168 @@ +#ifndef LOOKUPINTENTMSG_H_ +#define LOOKUPINTENTMSG_H_ + +#include +#include +#include + + +/** + * This message supports only serialization; deserialization is not implemented! + */ + + +/* intentFlags as payload + Keep these flags in sync with the userspace msg flags: + fhgfs_common/source/common/net/message/storage/LookupIntentMsg.h */ +#define LOOKUPINTENTMSG_FLAG_REVALIDATE 1 /* revalidate entry, cancel if invalid */ +#define LOOKUPINTENTMSG_FLAG_CREATE 2 /* create file */ +#define LOOKUPINTENTMSG_FLAG_CREATEEXCLUSIVE 4 /* exclusive file creation */ +#define LOOKUPINTENTMSG_FLAG_OPEN 8 /* open file */ +#define LOOKUPINTENTMSG_FLAG_STAT 16 /* stat file */ + + +// feature flags as header flags +#define LOOKUPINTENTMSG_FLAG_USE_QUOTA 1 /* if the message contains quota information */ +#define LOOKUPINTENTMSG_FLAG_BUDDYMIRROR 2 +#define LOOKUPINTENTMSG_FLAG_BUDDYMIRROR_SECOND 4 +#define LOOKUPINTENTMSG_FLAG_HAS_EVENT 8 /* contains file event logging information */ + + +struct LookupIntentMsg; +typedef struct LookupIntentMsg LookupIntentMsg; + + +static inline void LookupIntentMsg_init(LookupIntentMsg* this); +static inline void LookupIntentMsg_initFromName(LookupIntentMsg* this, const EntryInfo* parentInfo, + const char* entryName); +static inline void LookupIntentMsg_initFromEntryInfo(LookupIntentMsg* this, + const EntryInfo* parentInfo, const char* entryName, const EntryInfo* entryInfo, uint32_t metaVersion); + +// virtual functions +extern void LookupIntentMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + +// inliners +static inline void LookupIntentMsg_addIntentCreate(LookupIntentMsg* this, + unsigned userID, unsigned groupID, int mode, int umask, UInt16List* preferredTargets, + const struct FileEvent* fileEvent); +static inline void LookupIntentMsg_addIntentCreateExclusive(LookupIntentMsg* this); +static inline void LookupIntentMsg_addIntentOpen(LookupIntentMsg* this, NumNodeID clientNumID, + unsigned accessFlags); +static inline void LookupIntentMsg_addIntentStat(LookupIntentMsg* this); + + +struct LookupIntentMsg +{ + NetMessage netMessage; + + int intentFlags; // combination of LOOKUPINTENTMSG_FLAG_... + + const char* entryName; // (lookup data), set for all but revalidate + unsigned entryNameLen; // (lookup data), set for all but revalidate + + const EntryInfo* entryInfoPtr; // (revalidation data) + + unsigned userID; // (file creation data) + unsigned groupID; // (file creation data) + int mode; // file creation mode permission bits (file creation data) + int umask; // umask of the context (file creation data) + const struct FileEvent* fileEvent; + + NumNodeID clientNumID; // (file open data) + unsigned accessFlags; // OPENFILE_ACCESS_... flags (file open data) + + // for serialization + const EntryInfo* parentInfoPtr; // not owned by this object (lookup/open/creation data) + UInt16List* preferredTargets; // not owned by this object! (file creation data) + uint32_t metaVersion; +}; + +extern const struct NetMessageOps LookupIntentMsg_Ops; + +void LookupIntentMsg_init(LookupIntentMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_LookupIntent, &LookupIntentMsg_Ops); + this->fileEvent = NULL; +} + +/** + * This just prepares the basic lookup. Use the additional addIntent...() methods to do + * more than just the lookup. + * + * @param parentEntryID just a reference, so do not free it as long as you use this object! + * @param entryName just a reference, so do not free it as long as you use this object! + */ +void LookupIntentMsg_initFromName(LookupIntentMsg* this, const EntryInfo* parentInfo, + const char* entryName) +{ + LookupIntentMsg_init(this); + + this->intentFlags = 0; + + this->parentInfoPtr = parentInfo; + + this->entryName = entryName; + this->entryNameLen = strlen(entryName); +} + +/** + * Initialize from entryInfo - supposed to be used for revalidate intent + */ +void LookupIntentMsg_initFromEntryInfo(LookupIntentMsg* this, const EntryInfo* parentInfo, + const char* entryName, const EntryInfo* entryInfo, uint32_t metaVersion) +{ + LookupIntentMsg_init(this); + + this->intentFlags = LOOKUPINTENTMSG_FLAG_REVALIDATE; + + this->parentInfoPtr = parentInfo; + + this->entryName = entryName; + this->entryNameLen = strlen(entryName); + + this->entryInfoPtr = entryInfo; + this->metaVersion = metaVersion; +} + +void LookupIntentMsg_addIntentCreate(LookupIntentMsg* this, + unsigned userID, unsigned groupID, int mode, int umask, UInt16List* preferredTargets, + const struct FileEvent* fileEvent) +{ + this->intentFlags |= LOOKUPINTENTMSG_FLAG_CREATE; + + this->userID = userID; + this->groupID = groupID; + this->mode = mode; + this->umask = umask; + this->preferredTargets = preferredTargets; + this->fileEvent = fileEvent; + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= LOOKUPINTENTMSG_FLAG_HAS_EVENT; +} + +void LookupIntentMsg_addIntentCreateExclusive(LookupIntentMsg* this) +{ + this->intentFlags |= LOOKUPINTENTMSG_FLAG_CREATEEXCLUSIVE; +} + +/** + * @param accessFlags OPENFILE_ACCESS_... flags + */ +void LookupIntentMsg_addIntentOpen(LookupIntentMsg* this, NumNodeID clientNumID, + unsigned accessFlags) +{ + this->intentFlags |= LOOKUPINTENTMSG_FLAG_OPEN; + + this->clientNumID = clientNumID; + + this->accessFlags = accessFlags; +} + +void LookupIntentMsg_addIntentStat(LookupIntentMsg* this) +{ + this->intentFlags |= LOOKUPINTENTMSG_FLAG_STAT; +} + + + +#endif /* LOOKUPINTENTMSG_H_ */ diff --git a/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.c b/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.c new file mode 100644 index 0000000..bdce094 --- /dev/null +++ b/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.c @@ -0,0 +1,90 @@ +#include "LookupIntentRespMsg.h" + + +const struct NetMessageOps LookupIntentRespMsg_Ops = { + .serializePayload = _NetMessage_serializeDummy, + .deserializePayload = LookupIntentRespMsg_deserializePayload, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, +}; + +bool LookupIntentRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx) +{ + LookupIntentRespMsg* thisCast = (LookupIntentRespMsg*)this; + + // pre-initialize + thisCast->lookupResult = FhgfsOpsErr_INTERNAL; + thisCast->createResult = FhgfsOpsErr_INTERNAL; + + // responseFlags + if(!Serialization_deserializeInt(ctx, &thisCast->responseFlags) ) + return false; + + // lookupResult + if(!Serialization_deserializeUInt(ctx, &thisCast->lookupResult) ) + return false; + + if(thisCast->responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) + { + // statResult + if(!Serialization_deserializeInt(ctx, &thisCast->statResult) ) + return false; + + // StatData + if(!StatData_deserialize(ctx, &thisCast->statData) ) + return false; + } + + if(thisCast->responseFlags & LOOKUPINTENTRESPMSG_FLAG_REVALIDATE) + { + // revalidateResult + if(!Serialization_deserializeInt(ctx, &thisCast->revalidateResult) ) + return false; + } + + if(thisCast->responseFlags & LOOKUPINTENTRESPMSG_FLAG_CREATE) + { + // createResult + if(!Serialization_deserializeInt(ctx, &thisCast->createResult) ) + return false; + } + + if(thisCast->responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) + { + // openResult + if(!Serialization_deserializeInt(ctx, &thisCast->openResult) ) + return false; + + // fileHandleID + if(!Serialization_deserializeStrAlign4(ctx, + &thisCast->fileHandleIDLen, &thisCast->fileHandleID) ) + return false; + + // stripePattern + if(!StripePattern_deserializePatternPreprocess(ctx, + &thisCast->patternStart, &thisCast->patternLength) ) + return false; + + // PathInfo + if(!PathInfo_deserialize(ctx, &thisCast->pathInfo) ) + return false; + } + + // only try to decode the EntryInfo if either of those was successful + if ( (thisCast->lookupResult == FhgfsOpsErr_SUCCESS) || + (thisCast->createResult == FhgfsOpsErr_SUCCESS) ) + { // entryInfo + if (unlikely(!EntryInfo_deserialize(ctx, &thisCast->entryInfo) ) ) + { + printk_fhgfs(KERN_INFO, + "EntryInfo deserialization failed. LookupResult: %d; CreateResult %d\n", + thisCast->lookupResult, thisCast->createResult); + return false; + } + } + + + return true; +} + + diff --git a/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.h b/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.h new file mode 100644 index 0000000..a04a9c4 --- /dev/null +++ b/client_module/source/common/net/message/storage/lookup/LookupIntentRespMsg.h @@ -0,0 +1,69 @@ +#ifndef LOOKUPINTENTRESPMSG_H_ +#define LOOKUPINTENTRESPMSG_H_ + +#include +#include +#include +#include +#include +#include +#include + +/** + * This message supports only deserialization; serialization is not implemented! + */ + + +/* Keep these flags in sync with the userspace msg flags: + fhgfs_common/source/common/net/message/storage/LookupIntentRespMsg.h */ + +#define LOOKUPINTENTRESPMSG_FLAG_REVALIDATE 1 /* revalidate entry, cancel if invalid */ +#define LOOKUPINTENTRESPMSG_FLAG_CREATE 2 /* create file response */ +#define LOOKUPINTENTRESPMSG_FLAG_OPEN 4 /* open file response */ +#define LOOKUPINTENTRESPMSG_FLAG_STAT 8 /* stat file response */ + + +struct LookupIntentRespMsg; +typedef struct LookupIntentRespMsg LookupIntentRespMsg; + + +static inline void LookupIntentRespMsg_init(LookupIntentRespMsg* this); + +// virtual functions +extern bool LookupIntentRespMsg_deserializePayload(NetMessage* this, DeserializeCtx* ctx); + +struct LookupIntentRespMsg +{ + NetMessage netMessage; + + int responseFlags; // combination of LOOKUPINTENTRESPMSG_FLAG_... + + int lookupResult; + + EntryInfo entryInfo; // only deserialized if either lookup or create was successful + + int statResult; + StatData statData; + + int revalidateResult; // FhgfsOpsErr_SUCCESS if still valid, any other value otherwise + + int createResult; // FhgfsOpsErr_... + + int openResult; + unsigned fileHandleIDLen; + const char* fileHandleID; + + // for deserialization + const char* patternStart; // (open file data) + uint32_t patternLength; // (open file data) + PathInfo pathInfo; +}; + +extern const struct NetMessageOps LookupIntentRespMsg_Ops; + +void LookupIntentRespMsg_init(LookupIntentRespMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_LookupIntentResp, &LookupIntentRespMsg_Ops); +} + +#endif /* LOOKUPINTENTRESPMSG_H_ */ diff --git a/client_module/source/common/net/message/storage/moving/RenameMsg.c b/client_module/source/common/net/message/storage/moving/RenameMsg.c new file mode 100644 index 0000000..c103b70 --- /dev/null +++ b/client_module/source/common/net/message/storage/moving/RenameMsg.c @@ -0,0 +1,33 @@ +#include "RenameMsg.h" + + +const struct NetMessageOps RenameMsg_Ops = { + .serializePayload = RenameMsg_serializePayload, + .deserializePayload = _NetMessage_deserializeDummy, + .processIncoming = NetMessage_processIncoming, + .getSupportedHeaderFeatureFlagsMask = NetMessage_getSupportedHeaderFeatureFlagsMask, + .supportsSequenceNumbers = true, +}; + +void RenameMsg_serializePayload(NetMessage* this, SerializeCtx* ctx) +{ + RenameMsg* thisCast = (RenameMsg*)this; + + //entryType + Serialization_serializeUInt(ctx, thisCast->entryType); + + // fromDirInfo + EntryInfo_serialize(ctx, thisCast->fromDirInfo); + + // toDirInfo + EntryInfo_serialize(ctx, thisCast->toDirInfo); + + // oldName + Serialization_serializeStrAlign4(ctx, thisCast->oldNameLen, thisCast->oldName); + + // newName + Serialization_serializeStrAlign4(ctx, thisCast->newNameLen, thisCast->newName); + + if (this->msgHeader.msgFeatureFlags & RENAMEMSG_FLAG_HAS_EVENT) + FileEvent_serialize(ctx, thisCast->fileEvent); +} diff --git a/client_module/source/common/net/message/storage/moving/RenameMsg.h b/client_module/source/common/net/message/storage/moving/RenameMsg.h new file mode 100644 index 0000000..00eb018 --- /dev/null +++ b/client_module/source/common/net/message/storage/moving/RenameMsg.h @@ -0,0 +1,78 @@ +#ifndef RENAMEMSG_H_ +#define RENAMEMSG_H_ + +#include +#include +#include +#include + +#define RENAMEMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +/* This is the RenameMsg class, deserialization is not implemented, as the client is not supposed to + deserialize the message. There is also only a basic constructor, if a full constructor is + needed, it can be easily added later on */ + +struct RenameMsg; +typedef struct RenameMsg RenameMsg; + +static inline void RenameMsg_init(RenameMsg* this); +static inline void RenameMsg_initFromEntryInfo(RenameMsg* this, const char* oldName, + unsigned oldLen, DirEntryType entryType, const EntryInfo* fromDirInfo, const char* newName, + unsigned newLen, const EntryInfo* toDirInfo, const struct FileEvent* fileEvent); + +// virtual functions +extern void RenameMsg_serializePayload(NetMessage* this, SerializeCtx* ctx); + + +struct RenameMsg +{ + NetMessage netMessage; + + // for serialization + + const char* oldName; // not owned by this object! + unsigned oldNameLen; + DirEntryType entryType; + const EntryInfo* fromDirInfo; // not owned by this object! + + const char* newName; // not owned by this object! + unsigned newNameLen; + const EntryInfo* toDirInfo; // not owned by this object! + + const struct FileEvent* fileEvent; +}; + +extern const struct NetMessageOps RenameMsg_Ops; + +void RenameMsg_init(RenameMsg* this) +{ + NetMessage_init(&this->netMessage, NETMSGTYPE_Rename, &RenameMsg_Ops); +} + +/** + * @param oldName just a reference, so do not free it as long as you use this object! + * @param fromDirInfo just a reference, so do not free it as long as you use this object! + * @param newName just a reference, so do not free it as long as you use this object! + * @param toDirInfo just a reference, so do not free it as long as you use this object! + */ +void RenameMsg_initFromEntryInfo(RenameMsg* this, const char* oldName, unsigned oldLen, + DirEntryType entryType, const EntryInfo* fromDirInfo, const char* newName, unsigned newLen, + const EntryInfo* toDirInfo, const struct FileEvent* fileEvent) +{ + RenameMsg_init(this); + + this->oldName = oldName; + this->oldNameLen = oldLen; + this->entryType = entryType; + this->fromDirInfo = fromDirInfo; + + this->newName = newName; + this->newNameLen = newLen; + this->toDirInfo = toDirInfo; + this->fileEvent = fileEvent; + + if (fileEvent) + this->netMessage.msgHeader.msgFeatureFlags |= RENAMEMSG_FLAG_HAS_EVENT; +} + +#endif /*RENAMEMSG_H_*/ diff --git a/client_module/source/common/net/message/storage/moving/RenameRespMsg.h b/client_module/source/common/net/message/storage/moving/RenameRespMsg.h new file mode 100644 index 0000000..b6809fc --- /dev/null +++ b/client_module/source/common/net/message/storage/moving/RenameRespMsg.h @@ -0,0 +1,32 @@ +#ifndef RENAMERESPMSG_H_ +#define RENAMERESPMSG_H_ + +#include + + +struct RenameRespMsg; +typedef struct RenameRespMsg RenameRespMsg; + +static inline void RenameRespMsg_init(RenameRespMsg* this); + +// getters & setters +static inline int RenameRespMsg_getValue(RenameRespMsg* this); + +struct RenameRespMsg +{ + SimpleIntMsg simpleIntMsg; +}; + + +void RenameRespMsg_init(RenameRespMsg* this) +{ + SimpleIntMsg_init( (SimpleIntMsg*)this, NETMSGTYPE_RenameResp); +} + +int RenameRespMsg_getValue(RenameRespMsg* this) +{ + return SimpleIntMsg_getValue( (SimpleIntMsg*)this); +} + + +#endif /*RENAMERESPMSG_H_*/ diff --git a/client_module/source/common/net/msghelpers/MsgHelperAck.h b/client_module/source/common/net/msghelpers/MsgHelperAck.h new file mode 100644 index 0000000..73ebb0a --- /dev/null +++ b/client_module/source/common/net/msghelpers/MsgHelperAck.h @@ -0,0 +1,59 @@ +#ifndef MSGHELPERACK_H_ +#define MSGHELPERACK_H_ + +#include +#include +#include +#include + + +// inliners +static inline bool MsgHelperAck_respondToAckRequest(App* app, const char* ackID, + fhgfs_sockaddr_in* fromAddr, Socket* sock, char* respBuf, size_t bufLen); + + +/** + * Note: This will only send a response if an ackID has been set. + * + * @return true if ackID was set + */ +bool MsgHelperAck_respondToAckRequest(App* app, const char* ackID, + fhgfs_sockaddr_in* fromAddr, Socket* sock, char* respBuf, size_t bufLen) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Ack response"; + + AckMsgEx respMsg; + unsigned respLen; + bool serializeRes; + ssize_t sendRes; + + if(!StringTk_hasLength(ackID) ) + return false; + + AckMsgEx_initFromValue(&respMsg, ackID); + + respLen = NetMessage_getMsgLength( (NetMessage*)&respMsg); + serializeRes = NetMessage_serialize( (NetMessage*)&respMsg, respBuf, bufLen); + if(unlikely(!serializeRes) ) + { + Logger_logErrFormatted(log, logContext, "Unable to serialize response"); + goto err_uninit; + } + + if(fromAddr) + { // datagram => sync via dgramLis send method + DatagramListener* dgramLis = App_getDatagramListener(app); + sendRes = DatagramListener_sendto_kernel(dgramLis, respBuf, respLen, 0, fromAddr); + } + else + sendRes = Socket_sendto_kernel(sock, respBuf, respLen, 0, NULL); + + if(unlikely(sendRes <= 0) ) + Logger_logErrFormatted(log, logContext, "Send error. ErrCode: %lld", (long long)sendRes); + +err_uninit: + return true; +} + +#endif /* MSGHELPERACK_H_ */ diff --git a/client_module/source/common/net/sock/NetworkInterfaceCard.c b/client_module/source/common/net/sock/NetworkInterfaceCard.c new file mode 100644 index 0000000..5450d34 --- /dev/null +++ b/client_module/source/common/net/sock/NetworkInterfaceCard.c @@ -0,0 +1,261 @@ +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define NIC_STRING_LEN 1024 + + +static bool __NIC_fillNicAddress(struct net_device* dev, NicAddrType_t nicType, + NicAddress* outAddr); + + +void NIC_findAll(StrCpyList* allowedInterfaces, bool useRDMA, bool onlyRDMA, + NicAddressList* outList) +{ + // find standard TCP/IP interfaces + __NIC_findAllTCP(allowedInterfaces, outList); + + // find RDMA interfaces (based on TCP/IP interfaces query results) + if(useRDMA && RDMASocket_rdmaDevicesExist() ) + { + NicAddressList tcpInterfaces; + + NicAddressList_init(&tcpInterfaces); + + __NIC_findAllTCP(allowedInterfaces, &tcpInterfaces); + + __NIC_filterInterfacesForRDMA(&tcpInterfaces, outList); + + ListTk_kfreeNicAddressListElems(&tcpInterfaces); + NicAddressList_uninit(&tcpInterfaces); + } + + if (onlyRDMA) + { + NicAddressListIter nicIter; + NicAddressListIter_init(&nicIter, outList); + while (!NicAddressListIter_end(&nicIter)) + { + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + if (nicAddr->nicType != NICADDRTYPE_RDMA) + { + nicIter = NicAddressListIter_remove(&nicIter); + kfree(nicAddr); + } + else + NicAddressListIter_next(&nicIter); + } + } +} + +void __NIC_findAllTCP(StrCpyList* allowedInterfaces, NicAddressList* outList) +{ + struct net_device *dev; + + // find standard TCP/IP interfaces + + // foreach network device + for (dev = first_net_device(&init_net); dev; dev = next_net_device(dev)) + { + NicAddress* nicAddr = (NicAddress*)os_kmalloc(sizeof(NicAddress) ); + ssize_t metricByListPos = 0; + + if(!nicAddr) + { + printk_fhgfs(KERN_WARNING, "%s:%d: memory allocation failed. size: %zu\n", + __func__, __LINE__, sizeof(*nicAddr) ); + return; + } + + if(__NIC_fillNicAddress(dev, NICADDRTYPE_STANDARD, nicAddr) && + (!StrCpyList_length(allowedInterfaces) || + ListTk_listContains(nicAddr->name, allowedInterfaces, &metricByListPos) ) ) + { + NicAddressList_append(outList, nicAddr); + } + else + { // netdevice rejected => clean up + kfree(nicAddr); + } + } +} + +bool __NIC_fillNicAddress(struct net_device* dev, NicAddrType_t nicType, NicAddress* outAddr) +{ + struct ifreq ifr; + struct in_device* in_dev; + struct in_ifaddr *ifa; + +#ifdef BEEGFS_RDMA + outAddr->ibdev = NULL; +#endif + // name + strncpy(outAddr->name, dev->name, IFNAMSIZ); + + + // SIOCGIFFLAGS: + // get interface flags + ifr.ifr_flags = dev_get_flags(dev); + + if(ifr.ifr_flags & IFF_LOOPBACK) + return false; // loopback interface => skip + + ifr.ifr_hwaddr.sa_family = dev->type; + + // select which hardware types to process + // (on Linux see /usr/include/linux/if_arp.h for the whole list) + switch(ifr.ifr_hwaddr.sa_family) + { + case ARPHRD_LOOPBACK: + return false; + default: + break; + } + + + // copy nicType + outAddr->nicType = nicType; + + // ip address + // note: based on inet_gifconf in /net/ipv4/devinet.c + + in_dev = __in_dev_get_rtnl(dev); + if(!in_dev) + { + printk_fhgfs_debug(KERN_NOTICE, "found interface without in_dev: %s\n", dev->name); + return false; + } + + ifa = in_dev->ifa_list; + if(!ifa) + { + printk_fhgfs_debug(KERN_NOTICE, "found interface without ifa_list: %s\n", dev->name); + return false; + } + + outAddr->ipAddr.s_addr = ifa->ifa_local; // ip address + + // code to read multiple addresses + /* + for (; ifa; ifa = ifa->ifa_next) + { + (*(struct sockaddr_in *)&ifr.ifr_addr).sin_family = AF_INET; + (*(struct sockaddr_in *)&ifr.ifr_addr).sin_addr.s_addr = + ifa->ifa_local; + } + */ + + return true; +} + +/** + * @return static string (not alloc'ed, so don't free it). + */ +const char* NIC_nicTypeToString(NicAddrType_t nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: return "RDMA"; + case NICADDRTYPE_STANDARD: return "TCP"; + + default: return ""; + } +} + +/** + * @return string will be kalloced and must be kfreed later + */ +char* NIC_nicAddrToString(NicAddress* nicAddr) +{ + char* nicAddrStr; + char ipStr[NICADDRESS_IP_STR_LEN]; + const char* typeStr; + + nicAddrStr = (char*)os_kmalloc(NIC_STRING_LEN); + + NicAddress_ipToStr(nicAddr->ipAddr, ipStr); + + if(nicAddr->nicType == NICADDRTYPE_RDMA) + typeStr = "RDMA"; + else + if(nicAddr->nicType == NICADDRTYPE_STANDARD) + typeStr = "TCP"; + else + typeStr = "Unknown"; + + snprintf(nicAddrStr, NIC_STRING_LEN, "%s[ip addr: %s; type: %s]", nicAddr->name, ipStr, typeStr); + + return nicAddrStr; +} + +bool NIC_supportsRDMA(NicAddressList* nicList) +{ + bool rdmaSupported = false; + + NicAddressListIter iter; + NicAddressListIter_init(&iter, nicList); + + for( ; !NicAddressListIter_end(&iter); NicAddressListIter_next(&iter) ) + { + if(NicAddressListIter_value(&iter)->nicType == NICADDRTYPE_RDMA) + { + rdmaSupported = true; + break; + } + } + + return rdmaSupported; +} + +void NIC_supportedCapabilities(NicAddressList* nicList, NicListCapabilities* outCapabilities) +{ + outCapabilities->supportsRDMA = NIC_supportsRDMA(nicList); +} + + +/** + * Checks a list of TCP/IP interfaces for RDMA-capable interfaces. + */ +void __NIC_filterInterfacesForRDMA(NicAddressList* nicList, NicAddressList* outList) +{ + // Note: This works by binding an RDMASocket to each IP of the passed list. + + NicAddressListIter iter; + NicAddressListIter_init(&iter, nicList); + + for( ; !NicAddressListIter_end(&iter); NicAddressListIter_next(&iter) ) + { + RDMASocket rdmaSock; + Socket* sock = (Socket*)&rdmaSock; + NicAddress* nicAddr = NicAddressListIter_value(&iter); + bool bindRes; + + if(!RDMASocket_init(&rdmaSock, nicAddr->ipAddr, NULL) ) + continue; + + bindRes = sock->ops->bindToAddr(sock, nicAddr->ipAddr, 0); + + if(bindRes) + { // we've got an RDMA-capable interface => append it to outList + NicAddress* nicAddrCopy = os_kmalloc(sizeof(NicAddress) ); + + *nicAddrCopy = *nicAddr; + +#ifdef BEEGFS_RDMA + nicAddrCopy->ibdev = rdmaSock.ibvsock.cm_id->device; +#endif + nicAddrCopy->nicType = NICADDRTYPE_RDMA; + + NicAddressList_append(outList, nicAddrCopy); + } + + sock->ops->uninit(sock); + } +} diff --git a/client_module/source/common/net/sock/NetworkInterfaceCard.h b/client_module/source/common/net/sock/NetworkInterfaceCard.h new file mode 100644 index 0000000..1856a9a --- /dev/null +++ b/client_module/source/common/net/sock/NetworkInterfaceCard.h @@ -0,0 +1,25 @@ +#ifndef NETWORKINTERFACECARD_H_ +#define NETWORKINTERFACECARD_H_ + +#include +#include +#include +#include +#include + + +extern void NIC_findAll(StrCpyList* allowedInterfaces, bool useRDMA, bool onlyRDMA, + NicAddressList* outList); + +extern const char* NIC_nicTypeToString(NicAddrType_t nicType); +extern char* NIC_nicAddrToString(NicAddress* nicAddr); + +extern bool NIC_supportsRDMA(NicAddressList* nicList); +extern void NIC_supportedCapabilities(NicAddressList* nicList, + NicListCapabilities* outCapabilities); + +extern void __NIC_findAllTCP(StrCpyList* allowedInterfaces, NicAddressList* outList); +extern void __NIC_filterInterfacesForRDMA(NicAddressList* list, NicAddressList* outList); + + +#endif /*NETWORKINTERFACECARD_H_*/ diff --git a/client_module/source/common/net/sock/NicAddress.c b/client_module/source/common/net/sock/NicAddress.c new file mode 100644 index 0000000..1abb7af --- /dev/null +++ b/client_module/source/common/net/sock/NicAddress.c @@ -0,0 +1,34 @@ +#include +#include + +/** + * @return true if lhs (left-hand side) is preferred compared to rhs + */ +bool NicAddress_preferenceComp(const NicAddress* lhs, const NicAddress* rhs) +{ + // compares the preference of NICs + // returns true if lhs is preferred compared to rhs + + unsigned lhsHostOrderIP; + unsigned rhsHostOrderIP; + + // prefer RDMA NICs + if( (lhs->nicType == NICADDRTYPE_RDMA) && (rhs->nicType != NICADDRTYPE_RDMA) ) + return true; + if( (rhs->nicType == NICADDRTYPE_RDMA) && (lhs->nicType != NICADDRTYPE_RDMA) ) + return false; + + // no bandwidth in client NicAddress +// // prefer higher bandwidth +// if(lhs->bandwidth > rhs->bandwidth) +// return true; +// if(lhs->bandwidth < rhs->bandwidth) +// return false; + + // prefer higher ipAddr + lhsHostOrderIP = ntohl(lhs->ipAddr.s_addr); + rhsHostOrderIP = ntohl(rhs->ipAddr.s_addr); + + // this is the original IP-order version + return lhsHostOrderIP > rhsHostOrderIP; +} diff --git a/client_module/source/common/net/sock/NicAddress.h b/client_module/source/common/net/sock/NicAddress.h new file mode 100644 index 0000000..ca24f66 --- /dev/null +++ b/client_module/source/common/net/sock/NicAddress.h @@ -0,0 +1,70 @@ +#ifndef NICADDRESS_H_ +#define NICADDRESS_H_ + +#include +#include + +#define NICADDRESS_IP_STR_LEN 16 + + +enum NicAddrType; +typedef enum NicAddrType NicAddrType_t; + +struct NicAddress; +typedef struct NicAddress NicAddress; + +struct NicListCapabilities; +typedef struct NicListCapabilities NicListCapabilities; + +struct ib_device; + +extern bool NicAddress_preferenceComp(const NicAddress* lhs, const NicAddress* rhs); + +// inliners +static inline void NicAddress_ipToStr(struct in_addr ipAddr, char* outStr); +static inline bool NicAddress_equals(NicAddress* this, NicAddress* other); + + +enum NicAddrType +{ + NICADDRTYPE_STANDARD = 0, + // removed: NICADDRTYPE_SDP = 1, + NICADDRTYPE_RDMA = 2 +}; + +struct NicAddress +{ + struct in_addr ipAddr; + NicAddrType_t nicType; + char name[IFNAMSIZ]; +#ifdef BEEGFS_RDMA + struct ib_device *ibdev; +#endif +}; + +struct NicListCapabilities +{ + bool supportsRDMA; +}; + + + + +/** + * @param outStr must be at least NICADDRESS_STR_LEN bytes long + */ +void NicAddress_ipToStr(struct in_addr ipAddr, char* outStr) +{ + u8* ipArray = (u8*)&ipAddr.s_addr; + + sprintf(outStr, "%u.%u.%u.%u", ipArray[0], ipArray[1], ipArray[2], ipArray[3]); +} + +bool NicAddress_equals(NicAddress* this, NicAddress* other) +{ + return (this->ipAddr.s_addr == other->ipAddr.s_addr) && + (this->nicType == other->nicType) && + !strncmp(this->name, other->name, IFNAMSIZ); +} + +#endif /*NICADDRESS_H_*/ diff --git a/client_module/source/common/net/sock/NicAddressList.c b/client_module/source/common/net/sock/NicAddressList.c new file mode 100644 index 0000000..9df12b4 --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressList.c @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#if 0 +#include +#include +#include +#include +#endif + +bool NicAddressList_equals(NicAddressList* this, NicAddressList* other) +{ + bool result = false; + + if (NicAddressList_length(this) == NicAddressList_length(other)) + { + PointerListIter thisIter; + PointerListIter otherIter; + + PointerListIter_init(&thisIter, (PointerList*) this); + PointerListIter_init(&otherIter, (PointerList*) other); + + for (result = true; + result == true && !PointerListIter_end(&thisIter) && !PointerListIter_end(&otherIter); + PointerListIter_next(&thisIter), PointerListIter_next(&otherIter)) + { + result = NicAddress_equals((NicAddress*) PointerListIter_value(&thisIter), + (NicAddress*) PointerListIter_value(&otherIter)); + } + } + + return result; +} diff --git a/client_module/source/common/net/sock/NicAddressList.h b/client_module/source/common/net/sock/NicAddressList.h new file mode 100644 index 0000000..d424b54 --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressList.h @@ -0,0 +1,45 @@ +#ifndef NICADDRESSLIST_H_ +#define NICADDRESSLIST_H_ + +#include +#include +#include +#include "NicAddress.h" + +struct NicAddressList; +typedef struct NicAddressList NicAddressList; + +static inline void NicAddressList_init(NicAddressList* this); +static inline void NicAddressList_uninit(NicAddressList* this); +static inline void NicAddressList_append(NicAddressList* this, NicAddress* nicAddress); +static inline size_t NicAddressList_length(NicAddressList* this); + +extern bool NicAddressList_equals(NicAddressList* this, NicAddressList* other); + +struct NicAddressList +{ + struct PointerList pointerList; +}; + + +void NicAddressList_init(NicAddressList* this) +{ + PointerList_init( (PointerList*)this); +} + +void NicAddressList_uninit(NicAddressList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void NicAddressList_append(NicAddressList* this, NicAddress* nicAddress) +{ + PointerList_append( (PointerList*)this, nicAddress); +} + +size_t NicAddressList_length(NicAddressList* this) +{ + return PointerList_length( (PointerList*)this); +} + +#endif /*NICADDRESSLIST_H_*/ diff --git a/client_module/source/common/net/sock/NicAddressListIter.h b/client_module/source/common/net/sock/NicAddressListIter.h new file mode 100644 index 0000000..240f73a --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressListIter.h @@ -0,0 +1,59 @@ +#ifndef NICADDRESSLISTITER_H_ +#define NICADDRESSLISTITER_H_ + +#include +#include "NicAddressList.h" + +struct NicAddressListIter; +typedef struct NicAddressListIter NicAddressListIter; + +static inline void NicAddressListIter_init(NicAddressListIter* this, NicAddressList* list); +static inline void NicAddressListIter_next(NicAddressListIter* this); +static inline NicAddress* NicAddressListIter_value(NicAddressListIter* this); +static inline bool NicAddressListIter_end(NicAddressListIter* this); +static inline NicAddressListIter NicAddressListIter_remove(NicAddressListIter* this); + + +struct NicAddressListIter +{ + PointerListIter pointerListIter; +}; + + +void NicAddressListIter_init(NicAddressListIter* this, NicAddressList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void NicAddressListIter_next(NicAddressListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +NicAddress* NicAddressListIter_value(NicAddressListIter* this) +{ + return (NicAddress*)PointerListIter_value( (PointerListIter*)this); +} + +bool NicAddressListIter_end(NicAddressListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + +/** + * note: the current iterator becomes invalid after the call (use the returned iterator) + * @return the new iterator that points to the element just behind the erased one + */ +NicAddressListIter NicAddressListIter_remove(NicAddressListIter* this) +{ + NicAddressListIter newIter = *this; + + NicAddressListIter_next(&newIter); // the new iter that will be returned + + PointerListIter_remove( (PointerListIter*)this); + + return newIter; +} + + +#endif /*NICADDRESSLISTITER_H_*/ diff --git a/client_module/source/common/net/sock/NicAddressStats.h b/client_module/source/common/net/sock/NicAddressStats.h new file mode 100644 index 0000000..585608d --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressStats.h @@ -0,0 +1,137 @@ +#ifndef NICADDRESSSTATS_H_ +#define NICADDRESSSTATS_H_ + +#include +#include +#ifdef BEEGFS_RDMA +#include +#endif + +struct NicAddressStats; +typedef struct NicAddressStats NicAddressStats; + +static inline void NicAddressStats_init(NicAddressStats* this, NicAddress* nic); +static inline void NicAddressStats_uninit(NicAddressStats* this); +/** + * Called when an associated NIC has gone down. This indicates + * that this particular statistic should not be considered for load balancing. + */ +static inline void NicAddressStats_invalidate(NicAddressStats* this); +/** + * Called when an associated NIC has come online. This updates the internal NicAddress + * and indicates that this particular statistic should be considered for load balancing. + */ +static inline void NicAddressStats_setValid(NicAddressStats* this, NicAddress* nic); +static inline int NicAddressStats_comparePriority(NicAddressStats* this, NicAddressStats* o, + int numa); +static inline void NicAddressStats_updateUsed(NicAddressStats* this); +static inline void NicAddressStats_updateLastError(NicAddressStats* this); +static inline bool NicAddressStats_lastErrorExpired(NicAddressStats* this, Time* now, + int expirationSecs); +static inline bool NicAddressStats_usable(NicAddressStats* this, int maxConns); + +struct NicAddressStats +{ + NicAddress nic; + int established; + int available; + Time used; + Time lastError; + /** + * nicValid indicates if the NicAddress can be used for connections. + * This may be tracking stats for a device that has gone offline. + */ + bool nicValid; +}; + +void NicAddressStats_init(NicAddressStats* this, NicAddress* nic) +{ + this->nic = *nic; + this->established = 0; + this->available = 0; + this->nicValid = true; + Time_initZero(&this->used); + Time_initZero(&this->lastError); +} + +void NicAddressStats_uninit(NicAddressStats* this) +{ +} + +void NicAddressStats_invalidate(NicAddressStats* this) +{ + this->nicValid = false; +#ifdef BEEGFS_RDMA + this->nic.ibdev = NULL; +#endif +} + +void NicAddressStats_setValid(NicAddressStats* this, NicAddress* nic) +{ + this->nicValid = true; + this->nic = *nic; +} + +/* + * Compare the priority of this and o. + * + * Return value is < 0 if this has higher priority, > 0 if o has higher priority. + */ +int NicAddressStats_comparePriority(NicAddressStats* this, NicAddressStats* o, + int numa) +{ + int rc; + +#ifdef BEEGFS_RDMA + // device on the same numa node as current thread has higher priority + if (likely(this->nic.ibdev && o->nic.ibdev)) + { + int thisNode = this->nic.ibdev->dma_device->numa_node; + int oNode = o->nic.ibdev->dma_device->numa_node; + if (thisNode != oNode) + { + if (thisNode == numa) + return -1; + if (oNode == numa) + return 1; + } + } +#endif + + // device with more available connections has higher priority + rc = o->available - this->available; + if (rc != 0) + return rc; + // device with less established connections has higher priority + rc = this->established - o->established; + if (rc != 0) + return rc; + // device used less recently has higher priority + return Time_compare(&this->used, &o->used); +} + +void NicAddressStats_updateUsed(NicAddressStats* this) +{ + Time_setToNow(&this->used); +} + +void NicAddressStats_updateLastError(NicAddressStats* this) +{ + Time_setToNow(&this->lastError); +} + +bool NicAddressStats_lastErrorExpired(NicAddressStats* this, Time* now, int expirationSecs) +{ + return Time_elapsedSinceMS(now, &this->lastError) >= (expirationSecs * 1000); +} + +bool NicAddressStats_usable(NicAddressStats* this, int maxConns) +{ +#ifdef BEEGFS_RDMA + if (unlikely(!this->nic.ibdev)) + return false; +#endif + return this->available > 0 || this->established < maxConns; +} + +#endif /*NICADDRESSSTATS_H_*/ diff --git a/client_module/source/common/net/sock/NicAddressStatsList.h b/client_module/source/common/net/sock/NicAddressStatsList.h new file mode 100644 index 0000000..36d46d2 --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressStatsList.h @@ -0,0 +1,42 @@ +#ifndef NICADDRESSSTATSLIST_H_ +#define NICADDRESSSTATSLIST_H_ + +#include +#include +#include "NicAddressStats.h" + +struct NicAddressStatsList; +typedef struct NicAddressStatsList NicAddressStatsList; + +static inline void NicAddressStatsList_init(NicAddressStatsList* this); +static inline void NicAddressStatsList_uninit(NicAddressStatsList* this); +static inline void NicAddressStatsList_append(NicAddressStatsList* this, NicAddressStats* stats); +static inline size_t NicAddressStatsList_length(NicAddressStatsList* this); + +struct NicAddressStatsList +{ + PointerList pointerList; +}; + +void NicAddressStatsList_init(NicAddressStatsList* this) +{ + PointerList_init( (PointerList*)this); +} + +void NicAddressStatsList_uninit(NicAddressStatsList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void NicAddressStatsList_append(NicAddressStatsList* this, struct NicAddressStats* stats) +{ + PointerList_append( (PointerList*)this, stats); +} + +static inline size_t NicAddressStatsList_length(NicAddressStatsList* this) +{ + return PointerList_length( (PointerList*)this); +} + + +#endif /*NICADDRESSSTATSLIST_H_*/ diff --git a/client_module/source/common/net/sock/NicAddressStatsListIter.h b/client_module/source/common/net/sock/NicAddressStatsListIter.h new file mode 100644 index 0000000..c6fd6da --- /dev/null +++ b/client_module/source/common/net/sock/NicAddressStatsListIter.h @@ -0,0 +1,56 @@ +#ifndef NICADDRESSSTATSLISTITER_H_ +#define NICADDRESSSTATSLISTITER_H_ + +#include +#include "NicAddressStatsList.h" + +struct NicAddressStatsListIter; +typedef struct NicAddressStatsListIter NicAddressStatsListIter; + +static inline void NicAddressStatsListIter_init(NicAddressStatsListIter* this, NicAddressStatsList* list); +static inline void NicAddressStatsListIter_next(NicAddressStatsListIter* this); +static inline NicAddressStats* NicAddressStatsListIter_value(NicAddressStatsListIter* this); +static inline bool NicAddressStatsListIter_end(NicAddressStatsListIter* this); +static inline NicAddressStatsListIter NicAddressStatsListIter_remove(NicAddressStatsListIter* this); + +struct NicAddressStatsListIter +{ + PointerListIter pointerListIter; +}; + +void NicAddressStatsListIter_init(NicAddressStatsListIter* this, NicAddressStatsList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void NicAddressStatsListIter_next(NicAddressStatsListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +NicAddressStats* NicAddressStatsListIter_value(NicAddressStatsListIter* this) +{ + return (struct NicAddressStats*)PointerListIter_value( (PointerListIter*)this); +} + +bool NicAddressStatsListIter_end(NicAddressStatsListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + +/** + * note: the current iterator becomes invalid after the call (use the returned iterator) + * @return the new iterator that points to the element just behind the erased one + */ +NicAddressStatsListIter NicAddressStatsListIter_remove(NicAddressStatsListIter* this) +{ + NicAddressStatsListIter newIter = *this; + + NicAddressStatsListIter_next(&newIter); // the new iter that will be returned + + PointerListIter_remove( (PointerListIter*)this); + + return newIter; +} + +#endif /*NICADDRESSSTATSLISTITER_H_*/ diff --git a/client_module/source/common/net/sock/PooledSocket.h b/client_module/source/common/net/sock/PooledSocket.h new file mode 100644 index 0000000..b4d0672 --- /dev/null +++ b/client_module/source/common/net/sock/PooledSocket.h @@ -0,0 +1,143 @@ +#ifndef POOLEDSOCKET_H_ +#define POOLEDSOCKET_H_ + +#include +#include + + +struct PooledSocket; +typedef struct PooledSocket PooledSocket; +struct ConnectionList; +typedef struct ConnectionList ConnectionList; + + +static inline void _PooledSocket_init(PooledSocket* this, NicAddrType_t nicType); +static inline void _PooledSocket_uninit(Socket* this); + +// inliners +static inline bool PooledSocket_getHasExpired(PooledSocket* this, unsigned expireSecs); + +// getters & setters +static inline bool PooledSocket_isAvailable(PooledSocket* this); +static inline void PooledSocket_setAvailable(PooledSocket* this, bool available); +static inline bool PooledSocket_getHasActivity(PooledSocket* this); +static inline void PooledSocket_setHasActivity(PooledSocket* this); +static inline void PooledSocket_resetHasActivity(PooledSocket* this); +static inline bool PooledSocket_getHasExpirationTimer(PooledSocket* this); +static inline void PooledSocket_setExpireTimeStart(PooledSocket* this); +static inline NicAddrType_t PooledSocket_getNicType(PooledSocket* this); +static inline ConnectionList* PooledSocket_getPool(PooledSocket* this); +static inline PointerListElem* PooledSocket_getPoolElem(PooledSocket* this); +static inline void PooledSocket_setPool(PooledSocket* this, ConnectionList* pool, + PointerListElem* poolElem); + + +/** + * This class provides special extensions for sockets in a NodeConnPool. + */ +struct PooledSocket +{ + Socket socket; + ConnectionList* pool; + PointerListElem* poolElem; + bool available; // == !acquired + bool hasActivity; // true if channel was not idle (part of channel class in fhgfs_common) + bool closeOnRelease; /* release must close socket. used for signal handling */ + Time expireTimeStart; // 0 means "doesn't expire", otherwise time when conn was established + NicAddrType_t nicType; // same as the interface for which this conn was established +}; + + +void _PooledSocket_init(PooledSocket* this, NicAddrType_t nicType) +{ + _Socket_init( (Socket*)this); + + this->available = false; + this->hasActivity = true; // initially active to avoid immediate disconnection + this->closeOnRelease = false; + Time_initZero(&this->expireTimeStart); + this->nicType = nicType; + this->pool = NULL; + this->poolElem = NULL; +} + +void _PooledSocket_uninit(Socket* this) +{ + _Socket_uninit(this); +} + +/** + * Tests whether this socket is set to expire and whether its expire time has been exceeded. + * + * @param expireSecs the time in seconds after which an expire-enabled socket expires. + * @return true if this socket has expired. + */ +bool PooledSocket_getHasExpired(PooledSocket* this, unsigned expireSecs) +{ + if(likely(Time_getIsZero(&this->expireTimeStart) ) ) + return false; + + if(Time_elapsedMS(&this->expireTimeStart) > (expireSecs*1000) ) // "*1000" for milliseconds + return true; + + return false; +} + +bool PooledSocket_isAvailable(PooledSocket* this) +{ + return this->available; +} + +void PooledSocket_setAvailable(PooledSocket* this, bool available) +{ + this->available = available; +} + +bool PooledSocket_getHasActivity(PooledSocket* this) +{ + return this->hasActivity; +} + +void PooledSocket_setHasActivity(PooledSocket* this) +{ + this->hasActivity = true; +} + +void PooledSocket_resetHasActivity(PooledSocket* this) +{ + this->hasActivity = false; +} + +bool PooledSocket_getHasExpirationTimer(PooledSocket* this) +{ + return !Time_getIsZero(&this->expireTimeStart); +} + +void PooledSocket_setExpireTimeStart(PooledSocket* this) +{ + Time_setToNow(&this->expireTimeStart); +} + +NicAddrType_t PooledSocket_getNicType(PooledSocket* this) +{ + return this->nicType; +} + +void PooledSocket_setPool(PooledSocket* this, ConnectionList* pool, + PointerListElem* poolElem) +{ + this->pool = pool; + this->poolElem = poolElem; +} + +ConnectionList* PooledSocket_getPool(PooledSocket* this) +{ + return this->pool; +} + +PointerListElem* PooledSocket_getPoolElem(PooledSocket* this) +{ + return this->poolElem; +} + +#endif /*POOLEDSOCKET_H_*/ diff --git a/client_module/source/common/net/sock/RDMASocket.c b/client_module/source/common/net/sock/RDMASocket.c new file mode 100644 index 0000000..962ab21 --- /dev/null +++ b/client_module/source/common/net/sock/RDMASocket.c @@ -0,0 +1,233 @@ +#include +#include + +#include +#include + + +// Note: These are historical defaults designed for SDR IB and do not provide +// the best performance for current IB fabrics. Ideally, buf_size should be +// configured as the largest chunksize used by the filesystem and buf_num +// will be 3. It would be ideal to take buf_num down to 1, but the current +// protocol requires at least 3 buffers. +// buf_num=64; buf_size=4*1024 (=> 512kB per socket for send and recv) + +#define RDMASOCKET_DEFAULT_BUF_NUM (128) // moved to config +#define RDMASOCKET_DEFAULT_BUF_SIZE (4*1024) // moved to config +#define RDMASOCKET_DEFAULT_FRAGMENT_SIZE RDMASOCKET_DEFAULT_BUF_SIZE // moved to config +#define RDMASOCKET_DEFAULT_KEY_TYPE RDMAKEYTYPE_UnsafeGlobal + +static const struct SocketOps rdmaOps = { + .uninit = _RDMASocket_uninit, + + .connectByIP = _RDMASocket_connectByIP, + .bindToAddr = _RDMASocket_bindToAddr, + .listen = _RDMASocket_listen, + .shutdown = _RDMASocket_shutdown, + .shutdownAndRecvDisconnect = _RDMASocket_shutdownAndRecvDisconnect, + + .sendto = _RDMASocket_sendto, + .recvT = _RDMASocket_recvT, +}; + +bool RDMASocket_init(RDMASocket* this, struct in_addr src, NicAddressStats* nicStats) +{ + Socket* thisBase = (Socket*)this; + + // init super class + _PooledSocket_init( (PooledSocket*)this, NICADDRTYPE_RDMA); + + thisBase->ops = &rdmaOps; + + // normal init part + + thisBase->sockType = NICADDRTYPE_RDMA; + + this->commCfg.bufNum = RDMASOCKET_DEFAULT_BUF_NUM; + this->commCfg.bufSize = RDMASOCKET_DEFAULT_BUF_SIZE; + this->commCfg.fragmentSize = RDMASOCKET_DEFAULT_FRAGMENT_SIZE; + this->commCfg.keyType = RDMASocket_toIBVSocketKeyType(RDMASOCKET_DEFAULT_KEY_TYPE); + + if(!IBVSocket_init(&this->ibvsock, src, nicStats) ) + goto err_ibv; + + return true; + +err_ibv: + _PooledSocket_uninit(&this->pooledSocket.socket); + return false; +} + +RDMASocket* RDMASocket_construct(struct in_addr src, NicAddressStats *nicStats) +{ + RDMASocket* this = kmalloc(sizeof(*this), GFP_NOFS); + + if(!this || + !RDMASocket_init(this, src, nicStats) ) + { + kfree(this); + return NULL; + } + + return this; +} + +void _RDMASocket_uninit(Socket* this) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + IBVSocket_uninit(&thisCast->ibvsock); + _PooledSocket_uninit(this); +} + +bool RDMASocket_rdmaDevicesExist(void) +{ +#ifdef BEEGFS_RDMA + return true; +#else + return false; +#endif +} + +bool _RDMASocket_connectByIP(Socket* this, struct in_addr ipaddress, unsigned short port) +{ + // note: does not set the family type to the one of this socket. + + RDMASocket* thisCast = (RDMASocket*)this; + + bool connRes; + + connRes = IBVSocket_connectByIP(&thisCast->ibvsock, ipaddress, port, &thisCast->commCfg); + + if(!connRes) + { + // note: this message would flood the log if hosts are unreachable on the primary interface + + //char* ipStr = SocketTk_ipaddrToStr(ipaddress); + //printk_fhgfs(KERN_WARNING, "RDMASocket failed to connect to %s.\n", ipStr); + //kfree(ipStr); + + return false; + } + + // connected + + // set peername if not done so already (e.g. by connect(hostname) ) + if(!this->peername[0]) + { + SocketTk_endpointAddrToStrNoAlloc(this->peername, SOCKET_PEERNAME_LEN, ipaddress, port); + this->peerIP = ipaddress; + } + + return true; +} + +bool _RDMASocket_bindToAddr(Socket* this, struct in_addr ipaddress, unsigned short port) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + bool bindRes; + + bindRes = IBVSocket_bindToAddr(&thisCast->ibvsock, ipaddress, port); + if(!bindRes) + { + //printk_fhgfs_debug(KERN_INFO, "Failed to bind RDMASocket.\n"); // debug in + return false; + } + + this->boundPort = port; + + return true; +} + +bool _RDMASocket_listen(Socket* this) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + bool listenRes; + + listenRes = IBVSocket_listen(&thisCast->ibvsock); + if(!listenRes) + { + printk_fhgfs(KERN_WARNING, "Failed to set RDMASocket to listening mode.\n"); + return false; + } + + snprintf(this->peername, SOCKET_PEERNAME_LEN, "Listen(Port: %u)", this->boundPort); + + return true; +} + +bool _RDMASocket_shutdown(Socket* this) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + bool shutRes = IBVSocket_shutdown(&thisCast->ibvsock); + if(!shutRes) + { + printk_fhgfs_debug(KERN_INFO, "RDMASocket failed to send shutdown.\n"); + return false; + } + + return true; +} + +/** + * Note: The RecvDisconnect-part is currently not implemented, so this is equal to the + * normal shutdown() method. + */ +bool _RDMASocket_shutdownAndRecvDisconnect(Socket* this, int timeoutMS) +{ + return this->ops->shutdown(this); +} + + +/** + * @return -ETIMEDOUT on timeout + */ +ssize_t _RDMASocket_recvT(Socket* this, struct iov_iter* iter, int flags, int timeoutMS) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + ssize_t retVal; + + retVal = IBVSocket_recvT(&thisCast->ibvsock, iter, flags, timeoutMS); + + return retVal; +} + +/** + * Note: This is a connection-based socket type, so to and tolen are ignored. + * + * @param flags ignored + */ +ssize_t _RDMASocket_sendto(Socket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *to) +{ + RDMASocket* thisCast = (RDMASocket*)this; + + ssize_t retVal; + + retVal = IBVSocket_send(&thisCast->ibvsock, iter, flags); + + return retVal; +} + +/** + * Register for polling (=> this method does not call schedule() !). + * + * Note: Call this only once with finishPoll==true (=> non-blocking) or multiple times with + * finishPoll==true in the last call from the current thread (for cleanup). + * Note: It's safe to call this multiple times with finishPoll==true. + * + * @param events the event flags you are interested in (POLL...) + * @param finishPoll true for cleanup if you don't call poll again from this thread; (it's also ok + * to set this to true if you call poll only once and want to avoid blocking) + * @return mask revents mask (like poll() => POLL... flags), but only the events you requested or + * error events + */ +unsigned long RDMASocket_poll(RDMASocket* this, short events, bool finishPoll) +{ + return IBVSocket_poll(&this->ibvsock, events, finishPoll); +} + diff --git a/client_module/source/common/net/sock/RDMASocket.h b/client_module/source/common/net/sock/RDMASocket.h new file mode 100644 index 0000000..97eda70 --- /dev/null +++ b/client_module/source/common/net/sock/RDMASocket.h @@ -0,0 +1,133 @@ +#ifndef OPEN_RDMASOCKET_H_ +#define OPEN_RDMASOCKET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct ib_device; +struct ib_mr; +struct RDMASocket; +typedef struct RDMASocket RDMASocket; +struct NicAddressStats; +typedef struct NicAddressStats NicAddressStats; + + +extern __must_check bool RDMASocket_init(RDMASocket* this, struct in_addr srcIpAddr, NicAddressStats* nicStats); +extern RDMASocket* RDMASocket_construct(struct in_addr srcIpAddr, NicAddressStats* nicStats); +extern void _RDMASocket_uninit(Socket* this); + +extern bool RDMASocket_rdmaDevicesExist(void); + +extern bool _RDMASocket_connectByIP(Socket* this, struct in_addr ipaddress, + unsigned short port); +extern bool _RDMASocket_bindToAddr(Socket* this, struct in_addr ipaddress, + unsigned short port); +extern bool _RDMASocket_listen(Socket* this); +extern bool _RDMASocket_shutdown(Socket* this); +extern bool _RDMASocket_shutdownAndRecvDisconnect(Socket* this, int timeoutMS); + +extern ssize_t _RDMASocket_recvT(Socket* this, struct iov_iter* iter, int flags, + int timeoutMS); +extern ssize_t _RDMASocket_sendto(Socket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *to); + +extern unsigned long RDMASocket_poll(RDMASocket* this, short events, bool finishPoll); + +// inliners +static inline struct ib_device* RDMASocket_getDevice(RDMASocket* this); +static inline unsigned RDMASocket_getRkey(RDMASocket* this); +static inline bool RDMASocket_isRkeyGlobal(RDMASocket* this); + +static inline void RDMASocket_setBuffers(RDMASocket* this, unsigned bufNum, unsigned bufSize, + unsigned fragmentSize, RDMAKeyType keyType); +static inline void RDMASocket_setTimeouts(RDMASocket* this, int connectMS, + int completionMS, int flowSendMS, int flowRecvMS, int pollMS); +static inline void RDMASocket_setTypeOfService(RDMASocket* this, int typeOfService); +static inline void RDMASocket_setConnectionFailureStatus(RDMASocket* this, unsigned value); +static inline bool RDMASocket_registerMr(RDMASocket* this, struct ib_mr* mr, int access); +static inline IBVSocketKeyType RDMASocket_toIBVSocketKeyType(RDMAKeyType keyType); + +struct RDMASocket +{ + PooledSocket pooledSocket; + + IBVSocket ibvsock; + + IBVCommConfig commCfg; +}; + +unsigned RDMASocket_getRkey(RDMASocket *this) +{ + return IBVSocket_getRkey(&this->ibvsock); +} + +bool RDMASocket_isRkeyGlobal(RDMASocket* this) +{ + return this->commCfg.keyType != IBVSOCKETKEYTYPE_Register; +} + +struct ib_device* RDMASocket_getDevice(RDMASocket *this) +{ + return IBVSocket_getDevice(&this->ibvsock); +} + +IBVSocketKeyType RDMASocket_toIBVSocketKeyType(RDMAKeyType keyType) +{ + switch (keyType) + { + case RDMAKEYTYPE_UnsafeDMA: + return IBVSOCKETKEYTYPE_UnsafeDMA; + case RDMAKEYTYPE_Register: + return IBVSOCKETKEYTYPE_Register; + default: + return IBVSOCKETKEYTYPE_UnsafeGlobal; + } +} + +/** + * Note: Only has an effect for unconnected sockets. + */ +void RDMASocket_setBuffers(RDMASocket* this, unsigned bufNum, unsigned bufSize, + unsigned fragmentSize, RDMAKeyType keyType) +{ + this->commCfg.bufNum = bufNum; + this->commCfg.bufSize = bufSize; + this->commCfg.fragmentSize = fragmentSize; + this->commCfg.keyType = RDMASocket_toIBVSocketKeyType(keyType); +} + +void RDMASocket_setTimeouts(RDMASocket* this, int connectMS, + int completionMS, int flowSendMS, int flowRecvMS, int pollMS) +{ + IBVSocket_setTimeouts(&this->ibvsock, connectMS, completionMS, flowSendMS, + flowRecvMS, pollMS); +} + +/** + * Note: Only has an effect for unconnected sockets. + */ +void RDMASocket_setTypeOfService(RDMASocket* this, int typeOfService) +{ + IBVSocket_setTypeOfService(&this->ibvsock, typeOfService); +} + +/** + * Note: Only has an effect for unconnected sockets. + */ +void RDMASocket_setConnectionFailureStatus(RDMASocket* this, unsigned value) +{ + IBVSocket_setConnectionFailureStatus(&this->ibvsock, value); +} + +bool RDMASocket_registerMr(RDMASocket* this, struct ib_mr* mr, int access) +{ + return !IBVSocket_registerMr(&this->ibvsock, mr, access); +} + +#endif /*OPEN_RDMASOCKET_H_*/ diff --git a/client_module/source/common/net/sock/Socket.c b/client_module/source/common/net/sock/Socket.c new file mode 100644 index 0000000..91013f8 --- /dev/null +++ b/client_module/source/common/net/sock/Socket.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +void _Socket_init(Socket* this) +{ + memset(this, 0, sizeof(*this) ); + + this->sockType = NICADDRTYPE_STANDARD; + this->boundPort = -1; +} + +void _Socket_uninit(Socket* this) +{ +} + + +bool Socket_bind(Socket* this, unsigned short port) +{ + struct in_addr ipAddr = { INADDR_ANY }; + return this->ops->bindToAddr(this, ipAddr, port); +} + +bool Socket_bindToAddr(Socket* this, struct in_addr ipAddr, unsigned short port) +{ + return this->ops->bindToAddr(this, ipAddr, port); +} diff --git a/client_module/source/common/net/sock/Socket.h b/client_module/source/common/net/sock/Socket.h new file mode 100644 index 0000000..d970012 --- /dev/null +++ b/client_module/source/common/net/sock/Socket.h @@ -0,0 +1,194 @@ +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include +#include +#include +#include +#include +#include + + +#define SOCKET_PEERNAME_LEN 24 + +/* + * This is an abstract class. + */ + + +struct Socket; +typedef struct Socket Socket; + + +extern void _Socket_init(Socket* this); +extern void _Socket_uninit(Socket* this); + +extern bool Socket_bind(Socket* this, unsigned short port); +extern bool Socket_bindToAddr(Socket* this, struct in_addr ipAddr, unsigned short port); + + + +struct SocketOps +{ + void (*uninit)(Socket* this); + + bool (*connectByIP)(Socket* this, struct in_addr ipaddress, unsigned short port); + bool (*bindToAddr)(Socket* this, struct in_addr ipaddress, unsigned short port); + bool (*listen)(Socket* this); + bool (*shutdown)(Socket* this); + bool (*shutdownAndRecvDisconnect)(Socket* this, int timeoutMS); + + ssize_t (*sendto)(Socket* this, struct iov_iter* iter, int flags, fhgfs_sockaddr_in *to); + ssize_t (*recvT)(Socket* this, struct iov_iter* iter, int flags, int timeoutMS); +}; + +struct Socket +{ + NicAddrType_t sockType; + char peername[SOCKET_PEERNAME_LEN]; + struct in_addr peerIP; + int boundPort; + + const struct SocketOps* ops; + + struct { + struct list_head _list; + short _events; + short revents; + } poll; +}; + + +static inline NicAddrType_t Socket_getSockType(Socket* this) +{ + return this->sockType; +} + +static inline char* Socket_getPeername(Socket* this) +{ + return this->peername; +} + +static inline struct in_addr Socket_getPeerIP(Socket* this) +{ + return this->peerIP; +} + +/** + * Calls the virtual uninit method and kfrees the object. + */ +static inline void Socket_virtualDestruct(Socket* this) +{ + this->ops->uninit(this); + kfree(this); +} + +static inline ssize_t Socket_recvT(Socket* this, struct iov_iter *iter, + size_t length, int flags, int timeoutMS) +{ + // TODO: implementation function should accept length as well. + struct iov_iter copy = *iter; + iov_iter_truncate(©, length); + + { + ssize_t nread = this->ops->recvT(this, ©, flags, timeoutMS); + + if (nread >= 0) + { + // TODO: currently some parts of the project expect that we advance + // the iov_iter. But as it turns out, advancing here does not mesh + // well with how iov_iter is supposed to be used. A problem can be + // observed when advancing an iov_iter of type ITER_PIPE. This will + // result in mutation of external state (struct pipe_inode_info). IOW + // we can't just make a copy of any iov_iter and advance that in + // isolation. + // + // That means, the code should be changed such that we advance only in + // the outermost layers of the beegfs client module. + + iov_iter_advance(iter, nread); + } + + return nread; + } +} + +static inline ssize_t Socket_recvT_kernel(Socket* this, void *buffer, + size_t length, int flags, int timeoutMS) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buffer, length, READ); + return this->ops->recvT(this, iter, flags, timeoutMS); +} + +/** + * Receive with timeout, extended version with numReceivedBeforeError. + * + * note: this uses a soft timeout that is being reset after each received data packet. + * + * @param outNumReceivedBeforeError number of bytes received before returning (also set in case of + * an error, e.g. timeout); given value will only be increased and is intentionally not set to 0 + * initially. + * @return -ETIMEDOUT on timeout. + */ +static inline ssize_t Socket_recvExactTEx(Socket* this, struct iov_iter *iter, size_t len, int flags, int timeoutMS, + size_t* outNumReceivedBeforeError) +{ + ssize_t missingLen = len; + + do + { + ssize_t recvRes = this->ops->recvT(this, iter, flags, timeoutMS); + + if(unlikely(recvRes <= 0) ) + return recvRes; + + missingLen -= recvRes; + *outNumReceivedBeforeError += recvRes; + + } while(missingLen); + + // all received if we got here + return len; +} + +static inline ssize_t Socket_recvExactTEx_kernel(Socket* this, void *buf, size_t len, int flags, int timeoutMS, + size_t* outNumReceivedBeforeError) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, len, READ); + return Socket_recvExactTEx(this, iter, len, flags, timeoutMS, outNumReceivedBeforeError); +} + +/** + * Receive with timeout. + * + * @return -ETIMEDOUT on timeout. + */ +static inline ssize_t Socket_recvExactT(Socket* this, struct iov_iter *iter, size_t len, int flags, int timeoutMS) +{ + size_t numReceivedBeforeError; + + return Socket_recvExactTEx(this, iter, len, flags, timeoutMS, &numReceivedBeforeError); +} +static inline ssize_t Socket_recvExactT_kernel(Socket* this, void *buf, size_t len, int flags, int timeoutMS) +{ + size_t numReceivedBeforeError; + + return Socket_recvExactTEx_kernel(this, buf, len, flags, timeoutMS, &numReceivedBeforeError); +} + + + +static inline ssize_t Socket_sendto_kernel(Socket *this, const void *buf, size_t len, int flags, + fhgfs_sockaddr_in *to) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, len, WRITE); + return this->ops->sendto(this, iter, flags, to); +} + +static inline ssize_t Socket_send_kernel(Socket *this, const void *buf, size_t len, int flags) +{ + return Socket_sendto_kernel(this, buf, len, flags, NULL); +} + + +#endif /*SOCKET_H_*/ diff --git a/client_module/source/common/net/sock/StandardSocket.c b/client_module/source/common/net/sock/StandardSocket.c new file mode 100644 index 0000000..b111084 --- /dev/null +++ b/client_module/source/common/net/sock/StandardSocket.c @@ -0,0 +1,660 @@ +#include +#include +#include +#include + +#include +#include + + +#define SOCKET_LISTEN_BACKLOG 32 +#define SOCKET_SHUTDOWN_RECV_BUF_LEN 32 +#define STANDARDSOCKET_CONNECT_TIMEOUT_MS 5000 + +static const struct SocketOps standardOps = { + .uninit = _StandardSocket_uninit, + + .connectByIP = _StandardSocket_connectByIP, + .bindToAddr = _StandardSocket_bindToAddr, + .listen = _StandardSocket_listen, + .shutdown = _StandardSocket_shutdown, + .shutdownAndRecvDisconnect = _StandardSocket_shutdownAndRecvDisconnect, + + .sendto = _StandardSocket_sendto, + .recvT = _StandardSocket_recvT, +}; + +#ifdef KERNEL_HAS_SKWQ_HAS_SLEEPER +# define __sock_has_sleeper(wq) (skwq_has_sleeper(wq)) +#else +# define __sock_has_sleeper(wq) (wq_has_sleeper(wq)) +#endif + +#if defined(KERNEL_HAS_SK_SLEEP) && !defined(KERNEL_HAS_SK_HAS_SLEEPER) +static inline int sk_has_sleeper(struct sock* sk) +{ + return sk->sk_sleep && waitqueue_active(sk->sk_sleep); +} +#endif + +#if defined(KERNEL_WAKE_UP_SYNC_KEY_HAS_3_ARGUMENTS) +# define __wake_up_sync_key_m(wq, state, key) __wake_up_sync_key(wq, state, key) +#else +# define __wake_up_sync_key_m(wq, state, key) __wake_up_sync_key(wq, state, 1, key) +#endif + + +/* unlike linux sock_def_readable, this will also wake TASK_KILLABLE threads. we need this + * for SocketTk_poll, which wants to wait for fatal signals only. */ +#ifdef KERNEL_HAS_SK_DATA_READY_2 +static void sock_readable(struct sock *sk, int len) +#else +static void sock_readable(struct sock *sk) +#endif +{ +#ifdef KERNEL_HAS_SK_SLEEP + read_lock(&sk->sk_callback_lock); + if (sk_has_sleeper(sk)) + { + __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL, + (void*) (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)); + } + read_unlock(&sk->sk_callback_lock); +#else + struct socket_wq *wq; + + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + if (__sock_has_sleeper(wq)) + { + __wake_up_sync_key_m(&wq->wait, TASK_NORMAL, + (void*) (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND)); + } + rcu_read_unlock(); +#endif +} + +/* sock_def_write_space will also not wake uninterruptible threads. additionally, in newer kernels + * it uses refcount_t for an optimization we will do not need: linux does not want to wake up + * many writers if many of them cannot make progress. we have only a single writer. */ +static void sock_write_space(struct sock *sk) +{ +#ifdef KERNEL_HAS_SK_SLEEP + read_lock(&sk->sk_callback_lock); + + if (sk_has_sleeper(sk)) + { + __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL, + (void*) (POLLOUT | POLLWRNORM | POLLWRBAND)); + } + + read_unlock(&sk->sk_callback_lock); +#else + struct socket_wq *wq; + + rcu_read_lock(); + + wq = rcu_dereference(sk->sk_wq); + if (__sock_has_sleeper(wq)) + __wake_up_sync_key_m(&wq->wait, TASK_NORMAL, (void*) (POLLOUT | POLLWRNORM | POLLWRBAND)); + + rcu_read_unlock(); +#endif +} + +/* sock_def_wakeup, which is called for disconnects, has the same problem. */ +static void sock_wakeup(struct sock *sk) +{ +#ifdef KERNEL_HAS_SK_SLEEP + read_lock(&sk->sk_callback_lock); + if (sk_has_sleeper(sk)) + wake_up_all(sk->sk_sleep); + read_unlock(&sk->sk_callback_lock); +#else + struct socket_wq *wq; + + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + if (__sock_has_sleeper(wq)) + wake_up_all(&wq->wait); + rcu_read_unlock(); +#endif +} + +/* as does sock_def_error_report */ +static void sock_error_report(struct sock *sk) +{ +#ifdef KERNEL_HAS_SK_SLEEP + read_lock(&sk->sk_callback_lock); + if (sk_has_sleeper(sk)) + __wake_up_sync_key_m(sk->sk_sleep, TASK_NORMAL, (void*) (POLLERR)); + read_unlock(&sk->sk_callback_lock); +#else + struct socket_wq *wq; + + rcu_read_lock(); + wq = rcu_dereference(sk->sk_wq); + if (__sock_has_sleeper(wq)) + __wake_up_sync_key_m(&wq->wait, TASK_NORMAL, (void*) (POLLERR)); + rcu_read_unlock(); +#endif +} + + +bool StandardSocket_init(StandardSocket* this, int domain, int type, int protocol) +{ + Socket* thisBase = (Socket*)this; + + NicAddrType_t nicType = NICADDRTYPE_STANDARD; + + // init super class + _PooledSocket_init( (PooledSocket*)this, nicType); + + thisBase->ops = &standardOps; + + // normal init part + + this->sock = NULL; + + this->sockDomain = domain; + + return _StandardSocket_initSock(this, domain, type, protocol); +} + +StandardSocket* StandardSocket_construct(int domain, int type, int protocol) +{ + StandardSocket* this = kmalloc(sizeof(*this), GFP_NOFS); + + if(!this || + !StandardSocket_init(this, domain, type, protocol) ) + { + kfree(this); + return NULL; + } + + return this; +} + +StandardSocket* StandardSocket_constructUDP(void) +{ + return StandardSocket_construct(PF_INET, SOCK_DGRAM, 0); +} + +StandardSocket* StandardSocket_constructTCP(void) +{ + return StandardSocket_construct(PF_INET, SOCK_STREAM, 0); +} + +void _StandardSocket_uninit(Socket* this) +{ + StandardSocket* thisCast = (StandardSocket*)this; + + _PooledSocket_uninit(this); + + if(thisCast->sock) + sock_release(thisCast->sock); +} + +bool _StandardSocket_initSock(StandardSocket* this, int domain, int type, int protocol) +{ + int createRes; + + // prepare/create socket +#ifndef KERNEL_HAS_SOCK_CREATE_KERN_NS + createRes = sock_create_kern(domain, type, protocol, &this->sock); +#else + createRes = sock_create_kern(&init_net, domain, type, protocol, &this->sock); +#endif + if(createRes < 0) + { + //printk_fhgfs(KERN_WARNING, "Failed to create socket\n"); + return false; + } + + __StandardSocket_setAllocMode(this, GFP_NOFS); + this->sock->sk->sk_data_ready = sock_readable; + this->sock->sk->sk_write_space = sock_write_space; + this->sock->sk->sk_state_change = sock_wakeup; + this->sock->sk->sk_error_report = sock_error_report; + + return true; +} + +void __StandardSocket_setAllocMode(StandardSocket* this, gfp_t flags) +{ + this->sock->sk->sk_allocation = flags; +} + + +/** + * Use this to change socket options. + * Note: Behaves (almost) like user-space setsockopt. + * + * @return 0 on success, error code otherwise (=> different from userspace version) + */ +int _StandardSocket_setsockopt(StandardSocket* this, int level, + int optname, char* optval, int optlen) +{ + struct socket *sock = this->sock; + + #if defined(KERNEL_HAS_SOCK_SETSOCKOPT_SOCKPTR_T_PARAM) + + sockptr_t ptr = KERNEL_SOCKPTR(optval); + + if (level == SOL_SOCKET) + return sock_setsockopt(sock, level, optname, ptr, optlen); + else + return sock->ops->setsockopt(sock, level, optname, ptr, optlen); + + #elif defined(KERNEL_HAS_GET_FS) + + char __user *ptr = (char __user __force *) optval; + int r; + + WITH_PROCESS_CONTEXT + if (level == SOL_SOCKET) + r = sock_setsockopt(sock, level, optname, ptr, optlen); + else + r = sock->ops->setsockopt(sock, level, optname, ptr, optlen); + return r; + + #else + #error need set_fs()/get_fs() if sockptr_t is not available. + #endif + + // unreachable + BUG(); +} + +bool StandardSocket_setSoKeepAlive(StandardSocket* this, bool enable) +{ + int val = (enable ? 1 : 0); + + int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_KEEPALIVE, (char *) &val, sizeof val); + + return r == 0; +} + +bool StandardSocket_setSoBroadcast(StandardSocket* this, bool enable) +{ + int val = (enable ? 1 : 0); + + int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_BROADCAST, (char *) &val, sizeof val); + + return r == 0; +} + +int StandardSocket_getSoRcvBuf(StandardSocket* this) +{ + //TODO: should this be READ_ONCE()? There are different uses in the Linux kernel + return this->sock->sk->sk_rcvbuf; +} + +/** + * Note: Increase only (buffer will not be set to a smaller value). + * + * @return false on error, true otherwise (decrease skipping is not an error) + */ +bool StandardSocket_setSoRcvBuf(StandardSocket* this, int size) +{ + int origBufLen = StandardSocket_getSoRcvBuf(this); + + if (origBufLen >= size) + { + // we don't decrease buf sizes (but this is not an error) + return true; + } + else + { + /* note: according to socket(7) man page, the value given to setsockopt() + * is doubled and the doubled value is returned by getsockopt() + * + * update 2022-05-13: the kernel doubles the value passed to + * setsockopt(SO_RCVBUF) to allow for bookkeeping overhead. Halving the + * value is probably "not correct" but it's been this way since 2010 and + * changing it will potentially do more harm than good at this point. + */ + + int val = size/2; + + int r = _StandardSocket_setsockopt(this, SOL_SOCKET, SO_RCVBUF, (char *) + &val, sizeof val); + + if(r != 0) + printk_fhgfs_debug(KERN_INFO, "%s: setSoRcvBuf error: %d;\n", __func__, r); + + return r == 0; + } +} + +bool StandardSocket_setTcpNoDelay(StandardSocket* this, bool enable) +{ + int val = (enable ? 1 : 0); + + int r = _StandardSocket_setsockopt(this, SOL_TCP, TCP_NODELAY, (char*) &val, sizeof val); + + return r == 0; +} + +bool StandardSocket_setTcpCork(StandardSocket* this, bool enable) +{ + int val = (enable ? 1 : 0); + + int r = _StandardSocket_setsockopt(this, SOL_TCP, TCP_CORK, (char*) &val, sizeof val); + + return r == 0; +} + +bool _StandardSocket_connectByIP(Socket* this, struct in_addr ipaddress, unsigned short port) +{ + // note: this might look a bit strange (it's kept similar to the c++ version) + + // note: error messages here would flood the log if hosts are unreachable on primary interface + + + const int timeoutMS = STANDARDSOCKET_CONNECT_TIMEOUT_MS; + + StandardSocket* thisCast = (StandardSocket*)this; + + int connRes; + + struct sockaddr_in serveraddr = + { + .sin_family = AF_INET, + .sin_addr = ipaddress, + .sin_port = htons(port), + }; + + connRes = kernel_connect(thisCast->sock, + (struct sockaddr*) &serveraddr, + sizeof(serveraddr), + O_NONBLOCK); + + if(connRes) + { + if(connRes == -EINPROGRESS) + { // wait for "ready to send data" + PollState state; + int pollRes; + + PollState_init(&state); + PollState_addSocket(&state, this, POLLOUT); + + pollRes = SocketTk_poll(&state, timeoutMS); + + if(pollRes > 0) + { // we got something (could also be an error) + + /* note: it's important to test ERR/HUP/NVAL here instead of POLLOUT only, because + POLLOUT and POLLERR can be returned together. */ + + if(this->poll.revents & (POLLERR | POLLHUP | POLLNVAL) ) + return false; + + // connection successfully established + + if(!this->peername[0]) + { + SocketTk_endpointAddrToStrNoAlloc(this->peername, SOCKET_PEERNAME_LEN, ipaddress, port); + this->peerIP = ipaddress; + } + + return true; + } + else + if(!pollRes) + return false; // timeout + else + return false; // connection error + + } // end of "EINPROGRESS" + } + else + { // connected immediately + + // set peername if not done so already (e.g. by connect(hostname) ) + if(!this->peername[0]) + { + SocketTk_endpointAddrToStrNoAlloc(this->peername, SOCKET_PEERNAME_LEN, ipaddress, port); + this->peerIP = ipaddress; + } + + return true; + } + + return false; +} + + +bool _StandardSocket_bindToAddr(Socket* this, struct in_addr ipaddress, unsigned short port) +{ + StandardSocket* thisCast = (StandardSocket*)this; + + struct sockaddr_in bindAddr; + int bindRes; + + bindAddr.sin_family = thisCast->sockDomain; + bindAddr.sin_addr = ipaddress; + bindAddr.sin_port = htons(port); + + bindRes = kernel_bind(thisCast->sock, (struct sockaddr*)&bindAddr, sizeof(bindAddr) ); + + if(bindRes) + { + printk_fhgfs(KERN_WARNING, "Failed to bind socket. ErrCode: %d\n", bindRes); + return false; + } + + this->boundPort = port; + + return true; +} + +bool _StandardSocket_listen(Socket* this) +{ + StandardSocket* thisCast = (StandardSocket*)this; + int r; + + r = kernel_listen(thisCast->sock, SOCKET_LISTEN_BACKLOG); + if(r) + { + printk_fhgfs(KERN_WARNING, "Failed to set socket to listening mode. ErrCode: %d\n", + r); + return false; + } + + snprintf(this->peername, SOCKET_PEERNAME_LEN, "Listen(Port: %d)", this->boundPort); + + return true; +} + +bool _StandardSocket_shutdown(Socket* this) +{ + StandardSocket* thisCast = (StandardSocket*)this; + + int sendshutRes; + + sendshutRes = kernel_sock_shutdown(thisCast->sock, SEND_SHUTDOWN); + + if( (sendshutRes < 0) && (sendshutRes != -ENOTCONN) ) + { + printk_fhgfs(KERN_WARNING, "Failed to send shutdown. ErrCode: %d\n", sendshutRes); + return false; + } + + return true; +} + +bool _StandardSocket_shutdownAndRecvDisconnect(Socket* this, int timeoutMS) +{ + bool shutRes; + char buf[SOCKET_SHUTDOWN_RECV_BUF_LEN]; + int recvRes; + + shutRes = this->ops->shutdown(this); + if(!shutRes) + return false; + + // receive until shutdown arrives + do + { + recvRes = Socket_recvT_kernel(this, buf, SOCKET_SHUTDOWN_RECV_BUF_LEN, 0, timeoutMS); + } while(recvRes > 0); + + if(recvRes && + (recvRes != -ECONNRESET) ) + { // error occurred (but we're disconnecting, so we don't really care about errors) + return false; + } + + return true; +} + + + +/* Compatibility wrappers for sock_sendmsg / sock_recvmsg. At some point in the + * 4.x series, the size argument disappeared. */ +static int beegfs_recvmsg(struct socket *sock, struct msghdr *msg, size_t len, int flags) +{ +#ifdef KERNEL_HAS_RECVMSG_SIZE + return sock_recvmsg(sock, msg, len, flags); +#else + return sock_recvmsg(sock, msg, flags); +#endif +} +static int beegfs_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) +{ +#ifdef KERNEL_HAS_RECVMSG_SIZE + return sock_sendmsg(sock, msg, len); +#else + return sock_sendmsg(sock, msg); +#endif +} + + + + +/** + * @return -ETIMEDOUT on timeout + */ +ssize_t _StandardSocket_recvT(Socket* this, struct iov_iter* iter, int flags, int timeoutMS) +{ + StandardSocket* thisCast = (StandardSocket*)this; + + return StandardSocket_recvfromT(thisCast, iter, flags, NULL, timeoutMS); +} + + +ssize_t _StandardSocket_sendto(Socket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *to) +{ + StandardSocket* thisCast = (StandardSocket*)this; + struct socket *sock = thisCast->sock; + + int sendRes; + size_t len; + struct sockaddr_in toSockAddr; + + struct msghdr msg = + { + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = flags | MSG_NOSIGNAL, + .msg_name = (struct sockaddr*)(to ? &toSockAddr : NULL), + .msg_namelen = sizeof(toSockAddr), + .msg_iter = *iter, + }; + + len = iov_iter_count(iter); + + if (to) + { + toSockAddr.sin_family = thisCast->sockDomain; + toSockAddr.sin_addr = to->addr; + toSockAddr.sin_port = to->port; + } + + sendRes = beegfs_sendmsg(sock, &msg, len); + + if(sendRes >= 0) + iov_iter_advance(iter, sendRes); + + return sendRes; +} + +ssize_t StandardSocket_recvfrom(StandardSocket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *from) +{ + int recvRes; + size_t len; + struct sockaddr_in fromSockAddr; + struct socket *sock = this->sock; + + struct msghdr msg = + { + .msg_control = NULL, + .msg_controllen = 0, + .msg_flags = flags, + .msg_name = (struct sockaddr*)&fromSockAddr, + .msg_namelen = sizeof(fromSockAddr), + .msg_iter = *iter, + }; + + len = iov_iter_count(iter); + + recvRes = beegfs_recvmsg(sock, &msg, len, flags); + + if(recvRes > 0) + iov_iter_advance(iter, recvRes); + + if(from) + { + from->addr = fromSockAddr.sin_addr; + from->port = fromSockAddr.sin_port; + } + + return recvRes; +} + +/** + * @return -ETIMEDOUT on timeout + */ +ssize_t StandardSocket_recvfromT(StandardSocket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *from, int timeoutMS) +{ + Socket* thisBase = (Socket*)this; + + int pollRes; + PollState state; + + if(timeoutMS < 0) + return StandardSocket_recvfrom(this, iter, flags, from); + + PollState_init(&state); + PollState_addSocket(&state, thisBase, POLLIN); + + pollRes = SocketTk_poll(&state, timeoutMS); + + if( (pollRes > 0) && (thisBase->poll.revents & POLLIN) ) + return StandardSocket_recvfrom(this, iter, flags, from); + + if(!pollRes) + return -ETIMEDOUT; + + if(thisBase->poll.revents & POLLERR) + printk_fhgfs_debug(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Error condition\n", + thisBase->peername); + else + if(thisBase->poll.revents & POLLHUP) + printk_fhgfs_debug(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Hung up\n", + thisBase->peername); + else + if(thisBase->poll.revents & POLLNVAL) + printk_fhgfs(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: Invalid request\n", + thisBase->peername); + else + printk_fhgfs(KERN_DEBUG, "StandardSocket_recvfromT: poll(): %s: ErrCode: %d\n", + thisBase->peername, pollRes); + + return -ECOMM; +} diff --git a/client_module/source/common/net/sock/StandardSocket.h b/client_module/source/common/net/sock/StandardSocket.h new file mode 100644 index 0000000..1101a77 --- /dev/null +++ b/client_module/source/common/net/sock/StandardSocket.h @@ -0,0 +1,68 @@ +#ifndef OPEN_STANDARDSOCKET_H_ +#define OPEN_STANDARDSOCKET_H_ + +#include +#include +#include +#include + + +struct StandardSocket; +typedef struct StandardSocket StandardSocket; + + +extern __must_check bool StandardSocket_init(StandardSocket* this, int domain, int type, + int protocol); +extern StandardSocket* StandardSocket_construct(int domain, int type, int protocol); +extern StandardSocket* StandardSocket_constructUDP(void); +extern StandardSocket* StandardSocket_constructTCP(void); +extern void _StandardSocket_uninit(Socket* this); + +int StandardSocket_getSoRcvBuf(StandardSocket* this); +extern bool StandardSocket_setSoKeepAlive(StandardSocket* this, bool enable); +extern bool StandardSocket_setSoBroadcast(StandardSocket* this, bool enable); +extern bool StandardSocket_setSoRcvBuf(StandardSocket* this, int size); +extern bool StandardSocket_setTcpNoDelay(StandardSocket* this, bool enable); +extern bool StandardSocket_setTcpCork(StandardSocket* this, bool enable); + +extern bool _StandardSocket_connectByIP(Socket* this, struct in_addr ipaddress, + unsigned short port); +extern bool _StandardSocket_bindToAddr(Socket* this, struct in_addr ipaddress, + unsigned short port); +extern bool _StandardSocket_listen(Socket* this); +extern bool _StandardSocket_shutdown(Socket* this); +extern bool _StandardSocket_shutdownAndRecvDisconnect(Socket* this, int timeoutMS); + +extern ssize_t _StandardSocket_recvT(Socket* this, struct iov_iter* iter, int flags, + int timeoutMS); +extern ssize_t _StandardSocket_sendto(Socket* this, struct iov_iter* iter, int flags, + fhgfs_sockaddr_in *to); + +extern ssize_t StandardSocket_recvfrom(StandardSocket* this, struct iov_iter* iter, + int flags, fhgfs_sockaddr_in *from); +extern ssize_t StandardSocket_recvfromT(StandardSocket* this, struct iov_iter* iter, + int flags, fhgfs_sockaddr_in *from, int timeoutMS); + +extern bool _StandardSocket_initSock(StandardSocket* this, int domain, int type, + int protocol); +extern void __StandardSocket_setAllocMode(StandardSocket* this, gfp_t flags); +extern int _StandardSocket_setsockopt(StandardSocket* this, int level, int optname, char* optval, + int optlen); + +// getters & setters +static inline struct socket* StandardSocket_getRawSock(StandardSocket* this); + +struct StandardSocket +{ + PooledSocket pooledSocket; + struct socket* sock; + unsigned short sockDomain; +}; + +struct socket* StandardSocket_getRawSock(StandardSocket* this) +{ + return this->sock; +} + + +#endif /*OPEN_STANDARDSOCKET_H_*/ diff --git a/client_module/source/common/net/sock/ibv/IBVBuffer.c b/client_module/source/common/net/sock/ibv/IBVBuffer.c new file mode 100644 index 0000000..a832629 --- /dev/null +++ b/client_module/source/common/net/sock/ibv/IBVBuffer.c @@ -0,0 +1,152 @@ + + +#include "IBVBuffer.h" +#include "IBVSocket.h" +#ifdef BEEGFS_RDMA +#include + + +bool IBVBuffer_init(IBVBuffer* buffer, IBVCommContext* ctx, size_t bufLen, + size_t fragmentLen, enum dma_data_direction dma_dir) +{ + unsigned count; + unsigned i; + + if (fragmentLen == 0) + fragmentLen = bufLen; + count = (bufLen + fragmentLen - 1) / fragmentLen; + bufLen = MIN(fragmentLen, bufLen); + + buffer->dma_dir = dma_dir; + buffer->buffers = kzalloc(count * sizeof(*buffer->buffers), GFP_KERNEL); + buffer->lists = kzalloc(count * sizeof(*buffer->lists), GFP_KERNEL); + if(!buffer->buffers || !buffer->lists) + goto fail; + + for(i = 0; i < count; i++) + { + buffer->lists[i].lkey = ctx->pd->local_dma_lkey; + buffer->lists[i].length = bufLen; + buffer->buffers[i] = kmalloc(bufLen, GFP_KERNEL); + if(unlikely(!buffer->buffers[i])) + { + printk_fhgfs(KERN_ERR, "Failed to allocate buffer size=%zu\n", bufLen); + goto fail; + } + buffer->lists[i].addr = ib_dma_map_single(ctx->pd->device, buffer->buffers[i], + bufLen, dma_dir); + if (unlikely(ib_dma_mapping_error(ctx->pd->device, buffer->lists[i].addr))) + { + buffer->lists[i].addr = 0; + printk_fhgfs(KERN_ERR, "Failed to dma map buffer size=%zu\n", bufLen); + goto fail; + } + BUG_ON(buffer->lists[i].addr == 0); + } + + buffer->bufferSize = bufLen; + buffer->listLength = count; + buffer->bufferCount = count; + return true; + +fail: + IBVBuffer_free(buffer, ctx); + return false; +} + + +bool IBVBuffer_initRegistration(IBVBuffer* buffer, IBVCommContext* ctx) +{ + struct scatterlist* sg; + int res; + int i; + + buffer->mr = ib_alloc_mr(ctx->pd, IB_MR_TYPE_MEM_REG, buffer->bufferCount); + if (IS_ERR(buffer->mr)) + { + printk_fhgfs(KERN_ERR, "Failed to alloc mr, errCode=%ld\n", PTR_ERR(buffer->mr)); + buffer->mr = NULL; + goto fail; + } + + sg = kzalloc(buffer->bufferCount * sizeof(struct scatterlist), GFP_KERNEL); + if (sg == NULL) + { + printk_fhgfs(KERN_ERR, "Failed to alloc sg\n"); + goto fail; + } + + for (i = 0; i < buffer->bufferCount; ++i) + { + sg_dma_address(&sg[i]) = buffer->lists[i].addr; + sg_dma_len(&sg[i]) = buffer->lists[i].length; + } + + res = ib_map_mr_sg(buffer->mr, sg, buffer->bufferCount, NULL, PAGE_SIZE); + kfree(sg); + if (res < 0) + { + printk_fhgfs(KERN_ERR, "Failed to map mr res=%d\n", res); + goto fail; + } + + return true; + +fail: + if (buffer->mr) + { + ib_dereg_mr(buffer->mr); + buffer->mr = NULL; + } + return false; +} + + +void IBVBuffer_free(IBVBuffer* buffer, IBVCommContext* ctx) +{ + if(buffer->buffers && buffer->lists) + { + unsigned i; + for(i = 0; i < buffer->bufferCount; i++) + { + if (buffer->lists[i].addr) + ib_dma_unmap_single(ctx->pd->device, buffer->lists[i].addr, + buffer->bufferSize, buffer->dma_dir); + + if (buffer->buffers[i]) + kfree(buffer->buffers[i]); + } + } + + if (buffer->mr) + ib_dereg_mr(buffer->mr); + + if (buffer->buffers) + kfree(buffer->buffers); + + if (buffer->lists) + kfree(buffer->lists); +} + +ssize_t IBVBuffer_fill(IBVBuffer* buffer, struct iov_iter* iter) +{ + ssize_t total = 0; + unsigned i; + + for(i = 0; i < buffer->bufferCount && iov_iter_count(iter) > 0; i++) + { + size_t fragment = MIN(MIN(iov_iter_count(iter), buffer->bufferSize), 0xFFFFFFFF); + + if(copy_from_iter(buffer->buffers[i], fragment, iter) != fragment) + return -EFAULT; + + buffer->lists[i].length = fragment; + buffer->listLength = i + 1; + + total += fragment; + } + + return total; +} + +#endif diff --git a/client_module/source/common/net/sock/ibv/IBVBuffer.h b/client_module/source/common/net/sock/ibv/IBVBuffer.h new file mode 100644 index 0000000..1fcba0a --- /dev/null +++ b/client_module/source/common/net/sock/ibv/IBVBuffer.h @@ -0,0 +1,49 @@ +#ifndef IBVBuffer_h_aMQFNfzrjbEHDOcv216fi +#define IBVBuffer_h_aMQFNfzrjbEHDOcv216fi + +#include +#ifdef BEEGFS_RDMA + +#include +#include +#include + +#include + + +struct IBVBuffer; +typedef struct IBVBuffer IBVBuffer; + +struct IBVCommContext; +struct IBVSocket; + + +extern bool IBVBuffer_init(IBVBuffer* buffer, struct IBVCommContext* ctx, size_t bufLen, + size_t fragmentLen, enum dma_data_direction dma_dir); +/** + * Prepare the instance to use its internal ib_mr. This is only needed for buffers used + * with RDMA READ/WRITE and when not using a global rkey. This may be called before + * the connection is established. Once the connection has been established, + * the registration must be completed via a call to IBVSocket_registerMr(). + */ +extern bool IBVBuffer_initRegistration(IBVBuffer* buffer, struct IBVCommContext* ctx); +extern void IBVBuffer_free(IBVBuffer* buffer, struct IBVCommContext* ctx); +extern ssize_t IBVBuffer_fill(IBVBuffer* buffer, struct iov_iter* iter); + + +struct IBVBuffer +{ + char** buffers; + struct ib_sge* lists; + struct ib_mr* mr; + + size_t bufferSize; + unsigned bufferCount; + + unsigned listLength; + enum dma_data_direction dma_dir; +}; + +#endif + +#endif diff --git a/client_module/source/common/net/sock/ibv/IBVSocket.c b/client_module/source/common/net/sock/ibv/IBVSocket.c new file mode 100644 index 0000000..4016380 --- /dev/null +++ b/client_module/source/common/net/sock/ibv/IBVSocket.c @@ -0,0 +1,2092 @@ +#include "IBVSocket.h" + +#ifdef BEEGFS_RDMA + +#ifdef KERNEL_HAS_SCSI_FC_COMPAT +#include // some kernels (e.g. rhel 5.9) forgot this in their rdma headers +#endif // KERNEL_HAS_SCSI_FC_COMPAT + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define IBVSOCKET_CONN_TIMEOUT_MS 5000 + /* this also includes send completion wait times */ +#define IBVSOCKET_COMPLETION_TIMEOUT_MS 300000 +#define IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS 180000 +#define IBVSOCKET_FLOWCONTROL_ONRECV_TIMEOUT_MS 180000 +#define IBVSOCKET_SHUTDOWN_TIMEOUT_MS 250 +#define IBVSOCKET_POLL_TIMEOUT_MS 10000 +#define IBVSOCKET_FLOWCONTROL_MSG_LEN 1 +#define IBVSOCKET_STALE_RETRIES_NUM 128 +#define IBVSOCKET_MIN_SGE 1 +#define IBVSOCKET_MIN_WR 1 + +/** + * IBVSOCKET_RECVT_INFINITE_TIMEOUT_MS is used by IBVSocket_recvT when timeoutMS + * is passed as < 0, which indicates that __IBVSocket_receiveCheck should be + * called until it does not timeout. Thus, the long timeout value is + * inconsequential for that case. There doesn't appear to be any current code + * that passes timeoutMS < 0 to IBVSocket_recvT. + */ +#define IBVSOCKET_RECVT_INFINITE_TIMEOUT_MS 1000000 + +#define ibv_print_info(str, ...) printk_fhgfs(KERN_INFO, "%s:%d: " str, __func__, __LINE__, \ + ##__VA_ARGS__) +#define ibv_print_info_ir(str, ...) printk_fhgfs_ir(KERN_INFO, "%s:%d: " str, __func__, __LINE__, \ + ##__VA_ARGS__) +#define ibv_pwarn(str, ...) printk_fhgfs(KERN_WARNING, "%s:%d: " str, __func__, __LINE__, \ + ##__VA_ARGS__) +#define ibv_print_info_debug(str, ...) printk_fhgfs_debug(KERN_INFO, "%s:%d: " str, __func__, \ + __LINE__, ##__VA_ARGS__) +#define ibv_print_info_ir_debug(str, ...) printk_fhgfs_ir_debug(KERN_INFO, "%s:%d: " str, \ + __func__, __LINE__, ##__VA_ARGS__) + +/* 4.19 added const qualifiers to ib_post_send and ib_post_recv. */ +typedef __typeof__( + __builtin_choose_expr( + __builtin_types_compatible_p( + __typeof__(&ib_post_send), + int (*)(struct ib_qp*, struct ib_send_wr*, struct ib_send_wr**)), + (struct ib_send_wr*) 0, + (const struct ib_send_wr*) 0)) + _bad_send_wr; +typedef __typeof__( + __builtin_choose_expr( + __builtin_types_compatible_p( + __typeof__(&ib_post_recv), + int (*)(struct ib_qp*, struct ib_recv_wr*, struct ib_recv_wr**)), + (struct ib_recv_wr*) 0, + (const struct ib_recv_wr*) 0)) + _bad_recv_wr; + + +bool IBVSocket_init(IBVSocket* _this, struct in_addr srcIpAddr, NicAddressStats* nicStats) +{ + memset(_this, 0, sizeof(*_this) ); + + _this->connState = IBVSOCKETCONNSTATE_UNCONNECTED; + + _this->timeoutCfg.connectMS = IBVSOCKET_CONN_TIMEOUT_MS; + _this->timeoutCfg.completionMS = IBVSOCKET_COMPLETION_TIMEOUT_MS; + _this->timeoutCfg.flowSendMS = IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS; + _this->timeoutCfg.flowRecvMS = IBVSOCKET_FLOWCONTROL_ONRECV_TIMEOUT_MS; + _this->timeoutCfg.pollMS = IBVSOCKET_POLL_TIMEOUT_MS; + + _this->typeOfService = 0; + _this->srcIpAddr = srcIpAddr; + _this->nicStats = nicStats; + + init_waitqueue_head(&_this->eventWaitQ); + + Mutex_init(&_this->cmaMutex); + return __IBVSocket_createNewID(_this); +} + +void IBVSocket_uninit(IBVSocket* _this) +{ + Mutex_lock(&_this->cmaMutex); + __IBVSocket_cleanupCommContext(_this->cm_id, _this->commContext); + Mutex_unlock(&_this->cmaMutex); + if(_this->cm_id) + rdma_destroy_id(_this->cm_id); + + Mutex_uninit(&_this->cmaMutex); + SAFE_KFREE(_this->remoteDest); +} + +bool IBVSocket_rdmaDevicesExist() +{ + // Note: We use this (currently) just to inform the higher levels + // about availability of RDMA functionality + return true; +} + +unsigned IBVSocket_getRkey(IBVSocket *_this) +{ + return _this->commContext->checkConnRkey; +} + +struct ib_device* IBVSocket_getDevice(IBVSocket* _this) +{ + return _this->commContext->pd->device; +} + + +/** + * Create a new cm_id. + * This is not only intended for new sockets, but also for stale cm_ids, so this can cleanup/replace + * existing cm_ids and resets error states. + */ +bool __IBVSocket_createNewID(IBVSocket* _this) +{ + struct rdma_cm_id* new_cm_id; + + // We need to unconditionally destroy the old CM id. It is unusable at this point. + if(_this->cm_id) + { + rdma_destroy_id(_this->cm_id); + _this->cm_id = NULL; + } + + #if defined(OFED_HAS_NETNS) || defined(rdma_create_id) + new_cm_id = rdma_create_id(&init_net, __IBVSocket_cmaHandler, _this, RDMA_PS_TCP, IB_QPT_RC); + #elif defined(OFED_HAS_RDMA_CREATE_QPTYPE) + new_cm_id = rdma_create_id(__IBVSocket_cmaHandler, _this, RDMA_PS_TCP, IB_QPT_RC); + #else + new_cm_id = rdma_create_id(__IBVSocket_cmaHandler, _this, RDMA_PS_TCP); + #endif + + if(IS_ERR(new_cm_id) ) + { + ibv_print_info("rdma_create_id failed. ErrCode: %ld\n", PTR_ERR(new_cm_id) ); + return false; + } + + _this->cm_id = new_cm_id; + + _this->connState = IBVSOCKETCONNSTATE_UNCONNECTED; + _this->errState = 0; + + return true; +} + +bool IBVSocket_connectByIP(IBVSocket* _this, struct in_addr ipaddress, unsigned short port, + IBVCommConfig* commCfg) +{ + struct sockaddr_in sin; + struct sockaddr_in src; + struct sockaddr_in* srcp; + long connTimeoutJiffies = TimeTk_msToJiffiesSchedulable(IBVSOCKET_CONN_TIMEOUT_MS); + Time connElapsed; + int rc; + + /* note: rejected as stale means remote side still had an old open connection associated with + our current cm_id. what most likely happened is that the client was reset (i.e. no clean + disconnect) and our new cm_id after reboot now matches one of the old previous cm_ids. + => only possible solution seems to be retrying with another cm_id. */ + int numStaleRetriesLeft = IBVSOCKET_STALE_RETRIES_NUM; + + Time_setToNow(&connElapsed); + for( ; ; ) // stale retry loop + { + // set type of service for this connection + #ifdef OFED_HAS_SET_SERVICE_TYPE + if (_this->typeOfService) + rdma_set_service_type(_this->cm_id, _this->typeOfService); + #endif // OFED_HAS_SET_SERVICE_TYPE + + /* note: the rest of the connect procedure is invoked through the cmaHandler when the + corresponding asynchronous events arrive => we just have to wait for the connState + to change here */ + + _this->connState = IBVSOCKETCONNSTATE_CONNECTING; + + // resolve IP address ... + // (async event handler also automatically resolves route on success) + + sin.sin_addr.s_addr = ipaddress.s_addr; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + srcp = NULL; + if (_this->srcIpAddr.s_addr != 0) + { + src.sin_addr = _this->srcIpAddr; + src.sin_family = AF_INET; + src.sin_port = 0; + srcp = &src; + } + + if(rdma_resolve_addr(_this->cm_id, (struct sockaddr*)srcp, (struct sockaddr*)&sin, + _this->timeoutCfg.connectMS) ) + { + ibv_print_info_debug("rdma_resolve_addr failed\n"); + goto err_invalidateSock; + } + + // wait for async event + wait_event_interruptible(_this->eventWaitQ, + _this->connState != IBVSOCKETCONNSTATE_CONNECTING); + if(_this->connState != IBVSOCKETCONNSTATE_ADDRESSRESOLVED) + goto err_invalidateSock; + + if(rdma_resolve_route(_this->cm_id, _this->timeoutCfg.connectMS) ) + { + ibv_print_info_debug("rdma_resolve_route failed.\n"); + goto err_invalidateSock; + } + + wait_event_interruptible(_this->eventWaitQ, + _this->connState != IBVSOCKETCONNSTATE_ADDRESSRESOLVED); + if(_this->connState != IBVSOCKETCONNSTATE_ROUTERESOLVED) + goto err_invalidateSock; + + // establish connection... + + // (handler calls rdma_connect() ) + Mutex_lock(&_this->cmaMutex); + rc = __IBVSocket_routeResolvedHandler(_this, _this->cm_id, commCfg, &_this->commContext); + Mutex_unlock(&_this->cmaMutex); + if (rc) + { + ibv_print_info_debug("route resolved handler failed\n"); + goto err_invalidateSock; + } + + // wait for async event + // Note: rdma_connect() can take a very long time (>5m) if the peer's HCA has gone down. + wait_event_interruptible_timeout(_this->eventWaitQ, + _this->connState != IBVSOCKETCONNSTATE_ROUTERESOLVED, + connTimeoutJiffies); + + // test point for failed connections + if((_this->connState != IBVSOCKETCONNSTATE_ESTABLISHED) && + (_this->remapConnectionFailureStatus != 0)) + _this->connState = _this->remapConnectionFailureStatus; + + // check if cm_id was reported as stale by remote side + if(_this->connState == IBVSOCKETCONNSTATE_REJECTED_STALE) + { + bool createIDRes; + + if(!numStaleRetriesLeft) + { // no more stale retries left + if(IBVSOCKET_STALE_RETRIES_NUM) // did we have any retries at all + ibv_print_info("Giving up after %d stale connection retries\n", + IBVSOCKET_STALE_RETRIES_NUM); + + goto err_invalidateSock; + } + + printk_fhgfs_connerr(KERN_INFO, "Stale connection detected. Retrying with a new one...\n"); + // We need to clean up the commContext created in the routeResolvedHandler because + // the next time through the loop it will get recreated. If this is the final try, + // then we don't need it anymore. + Mutex_lock(&_this->cmaMutex); + __IBVSocket_cleanupCommContext(_this->cm_id, _this->commContext); + _this->commContext = NULL; + createIDRes = __IBVSocket_createNewID(_this); + Mutex_unlock(&_this->cmaMutex); + if(!createIDRes) + goto err_invalidateSock; + + numStaleRetriesLeft--; + continue; + } + + if(_this->connState != IBVSOCKETCONNSTATE_ESTABLISHED) + { + ibv_print_info_debug("Failed after %d stale connection retries, elapsed = %u\n", + IBVSOCKET_STALE_RETRIES_NUM - numStaleRetriesLeft, Time_elapsedMS(&connElapsed)); + goto err_invalidateSock; + } + + // connected + + if(numStaleRetriesLeft != IBVSOCKET_STALE_RETRIES_NUM) + { + ibv_print_info_debug("Succeeded after %d stale connection retries, elapsed = %u\n", + IBVSOCKET_STALE_RETRIES_NUM - numStaleRetriesLeft, Time_elapsedMS(&connElapsed)); + } + + return true; + } + + +err_invalidateSock: + // If we have a comm context, we need to delete it since we can't use it. We set an error state + // on the socket first, so we stop accepting callbacks that would access the commContext that is + // in the process of being destroyed. + _this->errState = -1; + Mutex_lock(&_this->cmaMutex); + __IBVSocket_cleanupCommContext(_this->cm_id, _this->commContext); + _this->commContext = NULL; + Mutex_unlock(&_this->cmaMutex); + return false; +} + + + +bool IBVSocket_bindToAddr(IBVSocket* _this, struct in_addr ipAddr, unsigned short port) +{ + struct sockaddr_in bindAddr; + + bindAddr.sin_family = AF_INET; + bindAddr.sin_addr = ipAddr; + bindAddr.sin_port = htons(port); + + if(rdma_bind_addr(_this->cm_id, (struct sockaddr*)&bindAddr) ) + { + _this->errState = -1; + return false; + } + + return true; +} + + +/** + * @return true on success + */ +bool IBVSocket_listen(IBVSocket* _this) +{ + if(rdma_listen(_this->cm_id, 0) ) + { + ibv_print_info("rdma_listen failed\n"); + _this->errState = -1; + return false; + } + + return true; +} + + + +bool IBVSocket_shutdown(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + + unsigned numWaitWrites = 0; + unsigned numWaitReads = 0; + int timeoutMS = IBVSOCKET_SHUTDOWN_TIMEOUT_MS; + + if(_this->errState) + return true; // true, because the conn is down anyways + + if(!commContext) + return true; // this socket has never been connected + + if(commContext->incompleteSend.numAvailable) + { // wait for all incomplete sends + int waitRes; + + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + &commContext->incompleteSend.numAvailable, &numWaitWrites, &numWaitReads, timeoutMS); + if(waitRes < 0) + { + ibv_print_info_debug("Waiting for incomplete send requests failed\n"); + return false; + } + } + + return true; +} + + +/** + * Continues an incomplete former recv() by returning immediately available data from the + * corresponding buffer. + */ +ssize_t __IBVSocket_recvContinueIncomplete(IBVSocket* _this, struct iov_iter* iter) +{ + IBVCommContext* commContext = _this->commContext; + struct IBVIncompleteRecv* recv = &commContext->incompleteRecv; + size_t bufIndex = recv->bufIndex; + struct IBVBuffer* buffer = &commContext->recvBufs[bufIndex]; + size_t copyRes = 0; + ssize_t total = 0; + + if(unlikely(_this->errState) ) + return -1; + + while(iov_iter_count(iter) > 0 && recv->totalSize != recv->completedOffset) + { + unsigned page = recv->completedOffset / buffer->bufferSize; + unsigned offset = recv->completedOffset % buffer->bufferSize; + unsigned fragment = MIN(MIN(iov_iter_count(iter), buffer->bufferSize - offset), + recv->totalSize - recv->completedOffset); + + copyRes = copy_to_iter(buffer->buffers[page] + offset, fragment, iter); + if(copyRes != fragment) + { + copyRes = 0; + break; + } + + total += fragment; + + recv->completedOffset += fragment; + } + + if(recv->completedOffset == recv->totalSize) + { + int postRes; + + commContext->incompleteRecv.isAvailable = 0; + + postRes = __IBVSocket_postRecv(_this, _this->commContext, bufIndex); + if(unlikely(postRes) ) + goto err_invalidateSock; + } + + if(!copyRes) + goto err_fault; + + return total; + +err_invalidateSock: + ibv_print_info_debug("invalidating connection\n"); + +err_fault: + _this->errState = -1; + return -EFAULT; +} + + +/** + * @return number of received bytes on success, -ETIMEDOUT on timeout, -ECOMM on error + */ +ssize_t IBVSocket_recvT(IBVSocket* _this, struct iov_iter* iter, int flags, int timeoutMS) +{ + int checkRes; + int wait = timeoutMS < 0 ? IBVSOCKET_RECVT_INFINITE_TIMEOUT_MS : timeoutMS; + + do { + checkRes = __IBVSocket_receiveCheck(_this, wait); + } while (checkRes == 0 && timeoutMS < 0); + + if(checkRes < 0) + return -ECOMM; + + if(checkRes == 0) + return -ETIMEDOUT; + + return __IBVSocket_recvContinueIncomplete(_this, iter); +} + + +/** + * @flags supports MSG_DONTWAIT + * @return number of bytes sent or negative error code (-EAGAIN in case of MSG_DONTWAIT if no data + * could be sent without blocking) + */ +ssize_t IBVSocket_send(IBVSocket* _this, struct iov_iter* iter, int flags) +{ + IBVCommContext* commContext = _this->commContext; + int flowControlRes; + size_t currentBufIndex; + struct iov_iter source = *iter; + int postRes; + size_t postedLen = 0; + ssize_t currentPostLen; + int waitRes; + + unsigned numWaitWrites = 0; + unsigned numWaitReads = 0; + int timeoutMS = _this->timeoutCfg.completionMS; + + if(unlikely(_this->errState) ) + return -1; + + // handle flags + if(flags & MSG_DONTWAIT) + { // send only as much as we can without blocking + + // note: we adapt the bufLen variable as necessary here for simplicity + + int checkSendRes; + size_t bufNumLeft; + size_t bufLenLeft; + + checkSendRes = __IBVSocket_nonblockingSendCheck(_this); + if(!checkSendRes) + { // we can't send non-blocking at the moment, caller shall try again later + return -EAGAIN; + } + else + if(unlikely(checkSendRes < 0) ) + goto err_invalidateSock; + + // buffers available => adapt bufLen (if necessary) + + bufNumLeft = MIN(commContext->commCfg.bufNum - commContext->incompleteSend.numAvailable, + commContext->numSendBufsLeft); + bufLenLeft = bufNumLeft * commContext->commCfg.bufSize; + + iov_iter_truncate(&source, bufLenLeft); + } + + // send data cut in buf-sized pieces... + + do + { + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, + _this->timeoutCfg.flowSendMS); + if(unlikely(flowControlRes <= 0) ) + goto err_invalidateSock; + + // note: we only poll for completed sends if forced or after we used up all (!) available bufs + + if(commContext->incompleteSend.forceWaitForAll || + (commContext->incompleteSend.numAvailable == commContext->commCfg.bufNum) ) + { // wait for all (!) incomplete sends + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + &commContext->incompleteSend.numAvailable, &numWaitWrites, &numWaitReads, timeoutMS); + if(waitRes <= 0) + goto err_invalidateSock; + + commContext->incompleteSend.forceWaitForAll = false; + } + + + currentBufIndex = commContext->incompleteSend.numAvailable; + + currentPostLen = IBVBuffer_fill(&commContext->sendBufs[currentBufIndex], &source); + if(currentPostLen < 0) + goto err_fault; + + commContext->incompleteSend.numAvailable++; /* inc'ed before postSend() for conn checks */ + + postRes = __IBVSocket_postSend(_this, currentBufIndex); + if(unlikely(postRes) ) + { + commContext->incompleteSend.numAvailable--; + goto err_invalidateSock; + } + + postedLen += currentPostLen; + } while(iov_iter_count(&source)); + + iov_iter_advance(iter, postedLen); + return (ssize_t)postedLen; + +err_invalidateSock: + _this->errState = -1; + return -ECOMM; + +err_fault: + _this->errState = -1; + return -EFAULT; +} + + +void IBVSocket_setTimeouts(IBVSocket* _this, int connectMS, + int completionMS, int flowSendMS, int flowRecvMS, int pollMS) +{ + _this->timeoutCfg.connectMS = connectMS > 0? connectMS : IBVSOCKET_CONN_TIMEOUT_MS; + _this->timeoutCfg.completionMS = completionMS > 0? completionMS : IBVSOCKET_COMPLETION_TIMEOUT_MS; + _this->timeoutCfg.flowSendMS = flowSendMS > 0? flowSendMS : IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS; + _this->timeoutCfg.flowRecvMS = flowRecvMS > 0? flowRecvMS : IBVSOCKET_FLOWCONTROL_ONRECV_TIMEOUT_MS; + _this->timeoutCfg.pollMS = pollMS > 0? pollMS : IBVSOCKET_POLL_TIMEOUT_MS; +#ifdef BEEGFS_DEBUG + ibv_print_info_debug("connectMS=%d completionMS=%d flowSendMS=%d flowRecvMS=%d pollMS=%d\n", + _this->timeoutCfg.connectMS, _this->timeoutCfg.completionMS, _this->timeoutCfg.flowSendMS, + _this->timeoutCfg.flowRecvMS, _this->timeoutCfg.pollMS); +#endif +} + + +void IBVSocket_setTypeOfService(IBVSocket* _this, int typeOfService) +{ + _this->typeOfService = typeOfService; +} + + +void IBVSocket_setConnectionFailureStatus(IBVSocket* _this, unsigned value) +{ + _this->remapConnectionFailureStatus = value; +} + + +bool __IBVSocket_createCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext) +{ + IBVCommContext* commContext; + struct ib_device* dev = cm_id->device; + struct ib_qp_init_attr qpInitAttr; + int qpRes; + unsigned i; + unsigned fragmentSize; + unsigned maxSge; + unsigned maxWr; + bool globalRkey = (commCfg->keyType == IBVSOCKETKEYTYPE_UnsafeGlobal); + + commContext = kzalloc(sizeof(*commContext), GFP_KERNEL); + if(!commContext) + goto err_cleanup; + + ibv_print_info_debug("Alloc CommContext @ %p\n", commContext); + + // prepare recv and send event notification + + init_waitqueue_head(&commContext->recvCompWaitQ); + init_waitqueue_head(&commContext->sendCompWaitQ); + + atomic_set(&commContext->recvCompEventCount, 0); + atomic_set(&commContext->sendCompEventCount, 0); + + // protection domain... + // IB_PD_UNSAFE_GLOBAL_RKEY is still present as of kernel 6.3. +#ifdef OFED_UNSAFE_GLOBAL_RKEY + commContext->pd = ib_alloc_pd(dev, globalRkey? IB_PD_UNSAFE_GLOBAL_RKEY : 0); +#else + if (globalRkey) + { + ibv_print_info("Unsafe global rkey not supported on this platform."); + goto err_cleanup; + } + commContext->pd = ib_alloc_pd(dev, 0); +#endif + if(IS_ERR(commContext->pd) ) + { + ibv_print_info("Couldn't allocate PD. ErrCode: %ld\n", PTR_ERR(commContext->pd) ); + + commContext->pd = NULL; + goto err_cleanup; + } +#ifdef OFED_UNSAFE_GLOBAL_RKEY + if (globalRkey) + commContext->checkConnRkey = commContext->pd->unsafe_global_rkey; +#endif + + if (commCfg->keyType == IBVSOCKETKEYTYPE_UnsafeDMA) + { + // DMA system mem region... + + // (Note: IB spec says: + // "The consumer is not allowed to assign remote-write (or remote-atomic) to + // a memory region that has not been assigned local-write.") + + // ib_get_dma_mr() goes away in kernel 4.9. Is is still present in MOFED 5.9. + // If not using the global rkey, then either ib_get_dmr_mr() or an allocated ib_mr + // needs to be used. +#ifdef OFED_IB_GET_DMA_MR + commContext->dmaMR = ib_get_dma_mr(commContext->pd, + IB_ACCESS_LOCAL_WRITE| IB_ACCESS_REMOTE_READ| IB_ACCESS_REMOTE_WRITE); + if(IS_ERR_OR_NULL(commContext->dmaMR) ) + { + ibv_print_info("ib_get_dma_mr failed. ErrCode: %ld\n", PTR_ERR(commContext->dmaMR) ); + commContext->dmaMR = NULL; + goto err_cleanup; + } + commContext->checkConnRkey = commContext->dmaMR->rkey; +#else + ibv_print_info("RDMA keyType is dma and ib_get_dma_mr() not supported on this platform.\n"); + goto err_cleanup; +#endif + + } + +#ifdef BEEGFS_DEBUG + ibv_print_info("%s: checkConnRkey = %u\n", __func__, commContext->checkConnRkey); +#endif + + // alloc and register buffers... + + commContext->commCfg = *commCfg; + + commContext->recvBufs = kzalloc(commCfg->bufNum * sizeof(struct IBVBuffer), GFP_KERNEL); + if(!commContext->recvBufs) + { + ibv_print_info("couldn't prepare receive buffer list\n"); + goto err_cleanup; + } + + for(i=0; i < commCfg->bufNum; i++) + { + if(!IBVBuffer_init(&commContext->recvBufs[i], commContext, commCfg->bufSize, + commCfg->fragmentSize, DMA_FROM_DEVICE) ) + { + ibv_print_info("couldn't prepare recvBuf #%d\n", i + 1); + goto err_cleanup; + } + } + + commContext->sendBufs = kzalloc(commCfg->bufNum * sizeof(struct IBVBuffer), GFP_KERNEL); + if(!commContext->sendBufs) + { + ibv_print_info("couldn't prepare send buffer list\n"); + goto err_cleanup; + } + + for(i=0; i < commCfg->bufNum; i++) + { + if(!IBVBuffer_init(&commContext->sendBufs[i], commContext, commCfg->bufSize, + commCfg->fragmentSize, DMA_TO_DEVICE) ) + { + ibv_print_info("couldn't prepare sendBuf #%d\n", i + 1); + goto err_cleanup; + } + } + + if(!IBVBuffer_init(&commContext->checkConBuffer, commContext, sizeof(u64), + 0, DMA_TO_DEVICE) ) + { + ibv_print_info("couldn't alloc dma control memory region\n"); + goto err_cleanup; + } + + // init flow control v2 (to avoid long receiver-not-ready timeouts) + + /* note: we use -1 because the last buf might not be read by the user (eg during + nonblockingRecvCheck) and so it might not be immediately available again. */ + commContext->numReceivedBufsLeft = commCfg->bufNum - 1; + commContext->numSendBufsLeft = commCfg->bufNum - 1; + + // create completion queues... + + commContext->recvCQ = __IBVSocket_createCompletionQueue(cm_id->device, + __IBVSocket_recvCompletionHandler, __IBVSocket_cqRecvEventHandler, + _this, commCfg->bufNum); + if(IS_ERR(commContext->recvCQ) ) + { + ibv_print_info("couldn't create recv CQ. ErrCode: %ld\n", PTR_ERR(commContext->recvCQ) ); + + commContext->recvCQ = NULL; + goto err_cleanup; + } + + // note: 1+commCfg->bufNum here for the checkConnection() RDMA read + commContext->sendCQ = __IBVSocket_createCompletionQueue(cm_id->device, + __IBVSocket_sendCompletionHandler, __IBVSocket_cqSendEventHandler, + _this, 1+commCfg->bufNum); + if(IS_ERR(commContext->sendCQ) ) + { + ibv_print_info("couldn't create send CQ. ErrCode: %ld\n", PTR_ERR(commContext->sendCQ) ); + + commContext->sendCQ = NULL; + goto err_cleanup; + } + + fragmentSize = commCfg->fragmentSize; + if (fragmentSize == 0) + fragmentSize = commCfg->bufSize; + maxSge = MAX(IBVSOCKET_MIN_SGE, commCfg->bufSize / fragmentSize + 1); + maxWr = MAX(IBVSOCKET_MIN_WR, commCfg->bufNum + 1); + + // note: 1+commCfg->bufNum here for the checkConnection() RDMA read + memset(&qpInitAttr, 0, sizeof(qpInitAttr) ); + + qpInitAttr.event_handler = __IBVSocket_qpEventHandler; + qpInitAttr.send_cq = commContext->sendCQ; + qpInitAttr.recv_cq = commContext->recvCQ; + qpInitAttr.qp_type = IB_QPT_RC; + qpInitAttr.sq_sig_type = IB_SIGNAL_REQ_WR; + qpInitAttr.cap.max_send_wr = maxWr; + qpInitAttr.cap.max_recv_wr = maxWr; + qpInitAttr.cap.max_send_sge = maxSge; + qpInitAttr.cap.max_recv_sge = maxSge; + qpInitAttr.cap.max_inline_data = 0; + + qpRes = rdma_create_qp(cm_id, commContext->pd, &qpInitAttr); + if(qpRes) + { + ibv_print_info("couldn't create QP. ErrCode: %d\n", qpRes); + goto err_cleanup; + } + + commContext->qp = cm_id->qp; + + // prepare event notification... + + // initial event notification requests + if(ib_req_notify_cq(commContext->recvCQ, IB_CQ_NEXT_COMP) ) + { + ibv_print_info("couldn't request CQ notification\n"); + goto err_cleanup; + } + + if(ib_req_notify_cq(commContext->sendCQ, IB_CQ_NEXT_COMP) ) + { + ibv_print_info("couldn't request CQ notification\n"); + goto err_cleanup; + } + + *outCommContext = commContext; + return true; + + + // error handling + +err_cleanup: + __IBVSocket_cleanupCommContext(cm_id, commContext); + *outCommContext = NULL; + return false; +} + +void __IBVSocket_cleanupCommContext(struct rdma_cm_id* cm_id, IBVCommContext* commContext) +{ + unsigned i; + struct ib_device* dev; + + ibv_print_info_debug("Free CommContext @ %p\n", commContext); + + if(!commContext) + return; + + dev = commContext->pd ? commContext->pd->device : NULL; + + if(!dev) + goto cleanup_no_dev; + + if(cm_id && commContext->qp && cm_id->qp) + rdma_destroy_qp(cm_id); + + + if(commContext->sendCQ) +#ifdef OFED_IB_DESTROY_CQ_IS_VOID + ib_destroy_cq(commContext->sendCQ); +#else + { + int destroyRes = ib_destroy_cq(commContext->sendCQ); + if (unlikely(destroyRes) ) + { + ibv_pwarn("sendCQ destroy failed: %d\n", destroyRes); + dump_stack(); + } + } +#endif + + if(commContext->recvCQ) +#ifdef OFED_IB_DESTROY_CQ_IS_VOID + ib_destroy_cq(commContext->recvCQ); +#else + { + int destroyRes = ib_destroy_cq(commContext->recvCQ); + if (unlikely(destroyRes) ) + { + ibv_pwarn("recvCQ destroy failed: %d\n", destroyRes); + dump_stack(); + } + } +#endif + + IBVBuffer_free(&commContext->checkConBuffer, commContext); + + for(i=0; i < commContext->commCfg.bufNum; i++) + { + if(commContext->recvBufs) + IBVBuffer_free(&commContext->recvBufs[i], commContext); + + if(commContext->sendBufs) + IBVBuffer_free(&commContext->sendBufs[i], commContext); + } + + SAFE_KFREE(commContext->recvBufs); + SAFE_KFREE(commContext->sendBufs); + + if(commContext->dmaMR) + ib_dereg_mr(commContext->dmaMR); + if(commContext->pd) + ib_dealloc_pd(commContext->pd); + +cleanup_no_dev: + kfree(commContext); +} + +/** + * Initializes a (local) IBVCommDest. + */ +bool __IBVSocket_initCommDest(IBVCommContext* commContext, IBVCommDest* outDest) +{ + memcpy(outDest->verificationStr, IBVSOCKET_PRIVATEDATA_STR, IBVSOCKET_PRIVATEDATA_STR_LEN); + outDest->protocolVersion = cpu_to_le64(IBVSOCKET_PRIVATEDATA_PROTOCOL_VER); + outDest->rkey = cpu_to_le32(commContext->checkConnRkey); + outDest->vaddr = cpu_to_le64(commContext->checkConBuffer.lists[0].addr); + outDest->recvBufNum = cpu_to_le32(commContext->commCfg.bufNum); + outDest->recvBufSize = cpu_to_le32(commContext->commCfg.bufSize); +#ifdef BEEGFS_DEBUG + ibv_print_info("%s: rkey=%u vaddr=%llu", __func__, outDest->rkey, outDest->vaddr); +#endif + return true; +} + +/** + * Parses and checks a (remote) IBVCommDest. + * + * @param buf should usually be the private_data of the connection handshake + * @param outDest will be kmalloced (if true is returned) and needs to be kfree'd by the + * caller + * @return true if data is okay, false otherwise + */ +bool __IBVSocket_parseCommDest(const void* buf, size_t bufLen, IBVCommDest** outDest) +{ + const IBVCommDest* src = buf; + IBVCommDest* dest = NULL; + + // Note: "bufLen < ..." (and not "!="), because there might be some extra padding + if(!buf || (bufLen < sizeof(*dest) ) ) + { + ibv_print_info("Bad private data size. length: %zu\n", bufLen); + return false; + } + + dest = kmalloc(sizeof(*dest), GFP_ATOMIC); + if(!dest) + return false; + + *outDest = dest; + *dest = *src; + + dest->protocolVersion = le64_to_cpu(dest->protocolVersion); + dest->vaddr = le64_to_cpu(dest->vaddr); + dest->rkey = le32_to_cpu(dest->rkey); + dest->recvBufNum = le32_to_cpu(dest->recvBufNum); + dest->recvBufSize = le32_to_cpu(dest->recvBufSize); + + if(memcmp(dest->verificationStr, IBVSOCKET_PRIVATEDATA_STR, IBVSOCKET_PRIVATEDATA_STR_LEN) ) + goto err_cleanup; + + if(dest->protocolVersion != IBVSOCKET_PRIVATEDATA_PROTOCOL_VER) + goto err_cleanup; + + return true; + +err_cleanup: + kfree(dest); + *outDest = NULL; + return false; +} + + +/** + * Append buffer to receive queue. + * + * @param commContext passed seperately because it's not the _this->commContext during + * accept() of incoming connections + * @return 0 on success, -1 on error + */ +int __IBVSocket_postRecv(IBVSocket* _this, IBVCommContext* commContext, size_t bufIndex) +{ + struct ib_recv_wr wr; + _bad_recv_wr bad_wr; + int postRes; + + commContext->sendBufs[bufIndex].lists[0].length = commContext->commCfg.bufSize; + + wr.next = NULL; + wr.wr_id = bufIndex; + wr.sg_list = commContext->recvBufs[bufIndex].lists; + wr.num_sge = commContext->recvBufs[bufIndex].listLength; + + postRes = ib_post_recv(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + ibv_print_info("ib_post_recv failed. ErrCode: %d\n", postRes); + + return -1; + } + + return 0; +} + +int IBVSocket_checkConnection(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; +#ifdef OFED_SPLIT_WR +# define rdma_of(wr) (wr) +# define wr_of(wr) (wr.wr) + struct ib_rdma_wr wr; +#else +# define rdma_of(wr) (wr.wr.rdma) +# define wr_of(wr) (wr) + struct ib_send_wr wr; +#endif + _bad_send_wr bad_wr; + int postRes; + int waitRes; + + int timeoutMS = _this->timeoutCfg.completionMS; + unsigned numWaitWrites = 0; + unsigned numWaitReads = 1; + + rdma_of(wr).remote_addr = _this->remoteDest->vaddr; + rdma_of(wr).rkey = _this->remoteDest->rkey; + + wr_of(wr).wr_id = 0; + wr_of(wr).sg_list = commContext->checkConBuffer.lists; + wr_of(wr).num_sge = commContext->checkConBuffer.listLength; + wr_of(wr).opcode = IB_WR_RDMA_READ; + wr_of(wr).send_flags = IB_SEND_SIGNALED; + wr_of(wr).next = NULL; + + postRes = ib_post_send(commContext->qp, &wr_of(wr), &bad_wr); + if(unlikely(postRes) ) + { + ibv_print_info("ib_post_send() failed. ErrCode: %d\n", postRes); + + goto error; + } + + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + &commContext->incompleteSend.numAvailable, &numWaitWrites, &numWaitReads, timeoutMS); + + if(unlikely(waitRes <= 0) ) + goto error; + + commContext->incompleteSend.forceWaitForAll = false; + + return 0; + +#undef rdma_of +#undef wr_of + +error: + _this->errState = -1; + return -1; +} + +/** + * Note: contains flow control. + * + * @return 0 on success, -1 on error + */ +int __IBVSocket_postSend(IBVSocket* _this, size_t bufIndex) +{ + IBVCommContext* commContext = _this->commContext; + struct ib_send_wr wr; + _bad_send_wr bad_wr; + int postRes; + + wr.wr_id = bufIndex; + wr.sg_list = commContext->sendBufs[bufIndex].lists; + wr.num_sge = commContext->sendBufs[bufIndex].listLength; + wr.opcode = IB_WR_SEND; + wr.send_flags = IB_SEND_SIGNALED; + wr.next = NULL; + + postRes = ib_post_send(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + ibv_print_info("ib_post_send() failed. ErrCode: %d\n", postRes); + + return -1; + } + + // flow control + __IBVSocket_flowControlOnSendUpdateCounters(_this); + + return 0; +} + + +/** + * Receive work completion. + * + * Note: contains flow control. + * + * @param timeoutMS 0 for non-blocking + * @return 1 on success, 0 on timeout, <0 on error + */ +int __IBVSocket_recvWC(IBVSocket* _this, int timeoutMS, struct ib_wc* outWC) +{ + IBVCommContext* commContext = _this->commContext; + int waitRes; + size_t bufIndex; + + waitRes = __IBVSocket_waitForRecvCompletionEvent(_this, timeoutMS, outWC); + if(waitRes <= 0) + { // (note: waitRes==0 can often happen, because we call this with timeoutMS==0) + + if(unlikely(waitRes < 0) ) + { + if(waitRes != -ERESTARTSYS) + { // only print message if user didn't press "-C" + ibv_print_info("retrieval of completion event failed. result: %d\n", waitRes); + } + } + + return waitRes; + } + + // we got something... + + if(unlikely(outWC->status != IB_WC_SUCCESS) ) + { + printk_fhgfs_connerr(KERN_INFO, "%s: Connection error (wc_status: %d; msg: %s)\n", + "IBVSocket (recv work completion)", (int)outWC->status, + __IBVSocket_wcStatusStr(outWC->status) ); + return -1; + } + + bufIndex = outWC->wr_id; + + if(unlikely(bufIndex >= commContext->commCfg.bufNum) ) + { + ibv_print_info("Completion for unknown/invalid wr_id %d\n", (int)outWC->wr_id); + return -1; + } + + // receive completed + + // flow control + + if(unlikely(__IBVSocket_flowControlOnRecv(_this, _this->timeoutCfg.flowRecvMS) ) ) + { + ibv_print_info_debug("got an error from flowControlOnRecv().\n"); + return -1; + } + + return 1; +} + +/** + * Intention: Avoid IB rnr by sending control msg when (almost) all our recv bufs are used up to + * show that we got our new recv bufs ready. + * + * @timeoutMS don't set this to 0, we really need to wait here sometimes + * @return 0 on success, -1 on error + */ +int __IBVSocket_flowControlOnRecv(IBVSocket* _this, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + + // we received a packet, so peer has received all of our currently pending data => reset counter + commContext->numSendBufsLeft = commContext->commCfg.bufNum - 1; /* (see + createCommContext() for "-1" reason) */ + + // send control packet if recv counter expires... + + #ifdef BEEGFS_DEBUG + if(!commContext->numReceivedBufsLeft) + ibv_print_info("BUG: numReceivedBufsLeft underflow!\n"); + #endif // BEEGFS_DEBUG + + commContext->numReceivedBufsLeft--; + + if(!commContext->numReceivedBufsLeft) + { + size_t currentBufIndex; + int postRes; + + if(commContext->incompleteSend.forceWaitForAll || + (commContext->incompleteSend.numAvailable == commContext->commCfg.bufNum) ) + { // wait for all (!) incomplete sends + + /* note: it's ok that all send bufs are used up, because it's possible that we do a lot of + recv without the user sending any data in between (so the bufs were actually used up by + flow control). */ + unsigned numWaitWrites = 0; + unsigned numWaitReads = 0; + + int waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + &commContext->incompleteSend.numAvailable, &numWaitWrites, &numWaitReads, timeoutMS); + if(waitRes <= 0) + { + ibv_print_info("problem encountered during waitForTotalSendCompletion(). ErrCode: %d\n", + waitRes); + return -1; + } + + commContext->incompleteSend.forceWaitForAll = false; + } + + currentBufIndex = commContext->incompleteSend.numAvailable; + + commContext->incompleteSend.numAvailable++; /* inc'ed before postSend() for conn checks */ + + commContext->sendBufs[currentBufIndex].lists[0].length = IBVSOCKET_FLOWCONTROL_MSG_LEN; + commContext->sendBufs[currentBufIndex].listLength = 1; + + postRes = __IBVSocket_postSend(_this, currentBufIndex); + if(unlikely(postRes) ) + { + commContext->incompleteSend.numAvailable--; + return -1; + } + + + // note: numReceivedBufsLeft is reset during postSend() flow control + } + + return 0; +} + +/** + * Called after sending a packet to update flow control counters. + * + * Intention: Avoid IB rnr by waiting for control msg when (almost) all peer bufs are used up. + * + * Note: This is only one part of the on-send flow control. The other one is + * _flowControlOnSendWait(). + */ +void __IBVSocket_flowControlOnSendUpdateCounters(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + + // we sent a packet, so we received all currently pending data from the peer => reset counter + commContext->numReceivedBufsLeft = commContext->commCfg.bufNum - 1; /* (see + createCommContext() for "-1" reason) */ + + #ifdef BEEGFS_DEBUG + + if(!commContext->numSendBufsLeft) + ibv_print_info("BUG: numSendBufsLeft underflow!\n"); + + #endif + + commContext->numSendBufsLeft--; +} + +/** + * Intention: Avoid IB rnr by waiting for control msg when (almost) all peer bufs are used up. + * + * @timeoutMS may be 0 for non-blocking operation, otherwise typically + * IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS + * @return >0 on success, 0 on timeout (waiting for flow control packet from peer), <0 on error + */ +int __IBVSocket_flowControlOnSendWait(IBVSocket* _this, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + + struct ib_wc wc; + int recvRes; + size_t bufIndex; + int postRecvRes; + + if(commContext->numSendBufsLeft) + return 1; // flow control not triggered yet + + recvRes = __IBVSocket_recvWC(_this, timeoutMS, &wc); + if(recvRes <= 0) + return recvRes; + + bufIndex = wc.wr_id; + + if(unlikely(wc.byte_len != IBVSOCKET_FLOWCONTROL_MSG_LEN) ) + { // error (bad length) + ibv_print_info("received flow control packet length mismatch %d\n", (int)wc.byte_len); + return -1; + } + + postRecvRes = __IBVSocket_postRecv(_this, _this->commContext, bufIndex); + if(postRecvRes) + return -1; + + // note: numSendBufsLeft is reset during recvWC() (if it actually received a packet) + + return 1; +} + + +/** + * @return 1 on available data, 0 on timeout, <0 on error + */ +int __IBVSocket_waitForRecvCompletionEvent(IBVSocket* _this, int timeoutMS, struct ib_wc* outWC) +{ + IBVCommContext* commContext = _this->commContext; + long waitRes; + int numEvents = 0; + int checkRes; + + // special quick path: other than in the userspace version of this method, we only need the + // quick path when timeoutMS==0, because then we might have been called from a special + // context, in which we don't want to sleep + if(!timeoutMS) + return ib_poll_cq(commContext->recvCQ, 1, outWC); + + + while(timeoutMS != 0) + { + /* note: we use pollTimeoutMS to check the conn every few secs (otherwise we might + wait for a very long time in case the other side disconnected silently) */ + + int pollTimeoutMS = MIN(_this->timeoutCfg.pollMS, timeoutMS); + long pollTimeoutJiffies = TimeTk_msToJiffiesSchedulable(pollTimeoutMS); + + /* note: don't think about ib_peek_cq here, because it is not implemented in the drivers. */ + + waitRes = wait_event_timeout(commContext->recvCompWaitQ, + (numEvents = ib_poll_cq(commContext->recvCQ, 1, outWC) ), pollTimeoutJiffies); + + if(unlikely(waitRes == -ERESTARTSYS || fatal_signal_pending(current))) + { // signal pending + ibv_print_info_debug("wait for recvCompEvent ended by pending signal\n"); + + return waitRes; + } + + if(likely(numEvents) ) + { // we got something + return numEvents; + } + + // timeout + + checkRes = IBVSocket_checkConnection(_this); + if(checkRes < 0) + return -ECONNRESET; + + timeoutMS -= pollTimeoutMS; + } // end of for-loop + + return 0; +} + + +/** + * @param oldSendCount old sendCompEventCount + * @return 1 on available data, 0 on timeout, -1 on error + */ +int __IBVSocket_waitForSendCompletionEvent(IBVSocket* _this, int oldSendCount, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + long waitRes; + + while(timeoutMS != 0) + { + // Note: We use pollTimeoutMS to check the conn every few secs (otherwise we might + // wait for a very long time in case the other side disconnected silently) + int pollTimeoutMS = MIN(_this->timeoutCfg.pollMS, timeoutMS); + long pollTimeoutJiffies = TimeTk_msToJiffiesSchedulable(pollTimeoutMS); + + waitRes = wait_event_timeout(commContext->sendCompWaitQ, + atomic_read(&commContext->sendCompEventCount) != oldSendCount, pollTimeoutJiffies); + + if(unlikely(waitRes == -ERESTARTSYS || fatal_signal_pending(current))) + { // signal pending + ibv_print_info_debug("wait for sendCompEvent ended by pending signal\n"); + + return -1; + } + + if(likely(atomic_read(&commContext->sendCompEventCount) != oldSendCount) ) + return 1; + + // timeout + timeoutMS -= pollTimeoutMS; + } + + return 0; +} + + +/** + * @param numSendElements also used as out-param to return the remaining number + * @param timeoutMS 0 for non-blocking; this is a soft timeout that is reset after each received + * completion + * @return 1 if all completions received, 0 if completions missing (in case you wanted non-blocking) + * or -1 in case of an error. + */ +int __IBVSocket_waitForTotalSendCompletion(IBVSocket* _this, + unsigned* numSendElements, unsigned* numWriteElements, unsigned* numReadElements, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + int numElements; + int waitRes; + int oldSendCount; + int i; + size_t bufIndex; + struct ib_wc wc[2]; + + do + { + oldSendCount = atomic_read(&commContext->sendCompEventCount); + + numElements = ib_poll_cq(commContext->sendCQ, 2, wc); + if(unlikely(numElements < 0) ) + { + ibv_print_info("bad ib_poll_cq result: %d\n", numElements); + + return -1; + } + else + if(!numElements) + { // no completions available yet => wait + if(!timeoutMS) + return 0; + + waitRes = __IBVSocket_waitForSendCompletionEvent(_this, oldSendCount, timeoutMS); + if(likely(waitRes > 0) ) + continue; + + return waitRes; + } + + // we got something... + + // for each completion element + for(i=0; i < numElements; i++) + { + if(unlikely(wc[i].status != IB_WC_SUCCESS) ) + { + printk_fhgfs_connerr(KERN_INFO, "%s: Connection error (wc_status: %d; msg: %s)\n", + "IBVSocket (wait for total send completion)", (int)(wc[i].status), + __IBVSocket_wcStatusStr(wc[i].status) ); + + return -1; + } + + switch(wc[i].opcode) + { + case IB_WC_SEND: + { + bufIndex = wc[i].wr_id; + + if(unlikely(bufIndex >= commContext->commCfg.bufNum) ) + { + ibv_print_info("bad send completion wr_id 0x%x\n", (int)wc[i].wr_id); + + return -1; + } + + if(likely(*numSendElements) ) + (*numSendElements)--; + else + { + ibv_print_info("received bad/unexpected send completion\n"); + + return -1; + } + + } break; + + case IB_WC_RDMA_READ: + { + if(unlikely(wc[i].wr_id != 0) ) + { + ibv_print_info("bad read completion wr_id 0x%x\n", (int)wc[i].wr_id); + + return -1; + } + + if(likely(*numReadElements) ) + (*numReadElements)--; + else + { + ibv_print_info("received bad/unexpected RDMA read completion\n"); + + return -1; + } + } break; + + default: + { + ibv_print_info("received bad/unexpected completion opcode %d\n", wc[i].opcode); + + return -1; + } break; + + } // end of switch + + } // end of for-loop + + } while(*numSendElements || *numWriteElements || *numReadElements); + + return 1; +} + + +/** + * @return <0 on error, 0 if recv would block, >0 if recv would not block + */ +int __IBVSocket_receiveCheck(IBVSocket* _this, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + struct ib_wc wc; + int flowControlRes; + int recvRes; + + if(unlikely(_this->errState) ) + return -1; + + if(commContext->incompleteRecv.isAvailable) + return 1; + + // check whether we have a pending on-send flow control packet that needs to be received first + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, timeoutMS); + if(unlikely(flowControlRes < 0) ) + { + ibv_print_info_debug("got an error from flowControlOnSendWait(). ErrCode: %d\n", + flowControlRes); + goto err_invalidateSock; + } + + if(!flowControlRes) + return 0; + + // recv one packet (if available) and add it as incompleteRecv + recvRes = __IBVSocket_recvWC(_this, timeoutMS, &wc); + if(unlikely(recvRes < 0) ) + { + ibv_print_info_debug("got an error from __IBVSocket_recvWC(). ErrCode: %d\n", recvRes); + goto err_invalidateSock; + } + + if(!recvRes) + return 0; + + // we got something => prepare to continue later + + commContext->incompleteRecv.totalSize = wc.byte_len; + commContext->incompleteRecv.bufIndex = wc.wr_id; + commContext->incompleteRecv.completedOffset = 0; + commContext->incompleteRecv.isAvailable = 1; + + return 1; + +err_invalidateSock: + ibv_print_info_debug("invalidating connection\n"); + _this->errState = -1; + return -1; +} + + +/** + * @return <0 on error, 0 if send would block, >0 if send would not block + */ +int __IBVSocket_nonblockingSendCheck(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + int flowControlRes; + int waitRes; + + unsigned numWaitWrites = 0; + unsigned numWaitReads = 0; + int timeoutMS = 0; + + if(unlikely(_this->errState) ) + return -1; + + // check whether we have a pending on-send flow control packet that needs to be received first + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, 0); + if(unlikely(flowControlRes < 0) ) + goto err_invalidateSock; + + if(!flowControlRes) + return flowControlRes; + + if(!commContext->incompleteSend.forceWaitForAll && + (commContext->incompleteSend.numAvailable < commContext->commCfg.bufNum) ) + return 1; + + commContext->incompleteSend.forceWaitForAll = true; // always setting saves an "if" below + + // we have to wait for completions before we can send... + + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + &commContext->incompleteSend.numAvailable, &numWaitWrites, &numWaitReads, timeoutMS); + if(unlikely(waitRes < 0) ) + goto err_invalidateSock; + + if(waitRes > 0) + commContext->incompleteSend.forceWaitForAll = false; // no more completions peding + + return waitRes; + +err_invalidateSock: + ibv_print_info_debug("invalidating connection\n"); + + _this->errState = -1; + return -1; +} + + +/** + * Note: Call this only once with finishPoll==true (=> non-blocking) or multiple times with + * finishPoll==true in the last call from the current thread (for cleanup). + * Note: It's safe to call this multiple times with finishPoll==true (in case that caller does + * not want to sleep anymore). + * + * @param events the event flags you are interested in (POLL...) + * @param finishPoll true for cleanup if you don't call poll again from this thread; (it's also ok + * to set this to true if you call poll only once and want to avoid blocking) + * @return mask all available revents (like poll(), POLL... flags), may not only be the events + * that were requested (and can also be the error events) + */ +unsigned long IBVSocket_poll(IBVSocket* _this, short events, bool finishPoll) +{ + /* note: there are two possible uses for finishPoll==true: + 1) this method is called multiple times and finishPoll is true in the last loop + (for cleanup) + 2) this method is called only once non-blocking and finishPoll is true to avoid + blocking */ + + /* note: it's good to call prepare_to_wait more than once for the same thread (e.g. if + caller woke up from schedule() and decides to sleep again) */ + + /* note: condition needs to be re-checked after prepare_to_wait to avoid the race when it became + true between the initial check and the call to prepare_to_wait */ + + /* note: we assume that after we returned a positive result, the caller will not try to sleep + (but will still call this again with finishPoll==true to cleanup) */ + + + IBVCommContext* commContext = _this->commContext; + unsigned long revents = 0; // return value + + + if(unlikely(_this->errState) ) + { + ibv_print_info_debug("called for an erroneous connection. ErrCode: %d\n", _this->errState); + + revents |= POLLERR; + return revents; + } + + if(!commContext->numSendBufsLeft) + { /* special case: on-send flow control triggered, so we actually need to wait for incoming data + even though the user set POLLOUT */ + + events |= POLLIN; + + /* note: the actual checks for POLLIN will be handled below like in the normal POLLIN case. + (we just want need to wake up when there is incoming data.) */ + + /* note: this only works efficiently, because the beegfs client just checks for any revent and + then calls _send() again. checking for POLLOUT explicitly would result in another call to + _poll() and then we would notice that we can send now => would work, but is less efficient, + obviously. */ + } + + + /* note: the "if(POLLIN || recvWaitInitialized)" is necessary because !numSendBufsLeft might have + triggered unrequested POLLIN check and we need to clean that up later. */ + + if( (events & POLLIN) || (commContext->recvWaitInitialized) ) + { + // initial check and wait preparations for incoming data + + if(__IBVSocket_receiveCheck(_this, 0) ) + { // immediate data available => no need to prepare wait + revents |= POLLIN; + } + else + if(!finishPoll && !commContext->recvWaitInitialized) + { // no incoming data and caller is planning to schedule() + #ifdef BEEGFS_DEBUG + if(waitqueue_active(&commContext->recvCompWaitQ) ) + ibv_print_info("BUG: recvCompWaitQ was not empty\n"); + #endif // BEEGFS_DEBUG + + commContext->recvWaitInitialized = true; + + init_waitqueue_entry(&commContext->recvWait, current); + add_wait_queue(&commContext->recvCompWaitQ, &commContext->recvWait); + + if(__IBVSocket_receiveCheck(_this, 0) ) + revents |= POLLIN; + } + + // cleanup + + if(finishPoll && commContext->recvWaitInitialized) + { + commContext->recvWaitInitialized = false; + + remove_wait_queue(&commContext->recvCompWaitQ, &commContext->recvWait); + } + } + + /* note: POLLOUT check must come _after_ POLLIN check, because the pollin-part + won't set the POLLOUT flag if we recv the on-send flow ctl packet during + nonblockingRecvCheck(). */ + + if(events & POLLOUT) + { + // initial check and wait preparations for outgoing data + + if(__IBVSocket_nonblockingSendCheck(_this) ) + { // immediate data available => no need to prepare wait + revents |= POLLOUT; + } + else + if(!finishPoll && !commContext->sendWaitInitialized) + { // no incoming data and caller is planning to schedule() + #ifdef BEEGFS_DEBUG + if(waitqueue_active(&commContext->sendCompWaitQ) ) + ibv_print_info("BUG: sendCompWaitQ was not empty\n"); + #endif // BEEGFS_DEBUG + + commContext->sendWaitInitialized = true; + + init_waitqueue_entry(&commContext->sendWait, current); + add_wait_queue(&commContext->sendCompWaitQ, &commContext->sendWait); + + if(__IBVSocket_nonblockingSendCheck(_this) ) + revents |= POLLOUT; + } + + // cleanup + + if(finishPoll && commContext->sendWaitInitialized) + { + commContext->sendWaitInitialized = false; + + remove_wait_queue(&commContext->sendCompWaitQ, &commContext->sendWait); + } + } + + // check errState again in case it was modified during the checks above + if(unlikely(_this->errState) ) + { + ibv_print_info_debug("got erroneous connection state. ErrCode: %d\n", _this->errState); + + revents |= POLLERR; + } + + return revents; +} + + +/** + * Handle connection manager event callbacks. + * + * Locking of mutexes and sleeping is permitted. + * + * @return negative Linux error code on error, 0 otherwise; in case of return!=0, rdma_cm will + * automatically call rdma_destroy_id(). + */ +int __IBVSocket_cmaHandler(struct rdma_cm_id* cm_id, struct rdma_cm_event* event) +{ + IBVSocket* _this = cm_id->context; + int retVal = 0; + + if(unlikely(!_this || _this->errState !=0) ) + { + ibv_print_info_debug("cm_id is being torn down. Event: %d\n", event->event); + return (event->event == RDMA_CM_EVENT_CONNECT_REQUEST) ? -EINVAL : 0; + } + + Mutex_lock(&_this->cmaMutex); + if (_this->cm_id != cm_id) + { + Mutex_unlock(&_this->cmaMutex); + return -EINVAL; + } + + ibv_print_info_debug("rdma event: %i, status: %i\n", event->event, event->status); + + switch(event->event) + { + case RDMA_CM_EVENT_ADDR_RESOLVED: + _this->connState = IBVSOCKETCONNSTATE_ADDRESSRESOLVED; + break; + + case RDMA_CM_EVENT_ADDR_ERROR: + case RDMA_CM_EVENT_UNREACHABLE: + retVal = -ENETUNREACH; + break; + + case RDMA_CM_EVENT_ROUTE_RESOLVED: + _this->connState = IBVSOCKETCONNSTATE_ROUTERESOLVED; + break; + + case RDMA_CM_EVENT_ROUTE_ERROR: + case RDMA_CM_EVENT_CONNECT_ERROR: + retVal = -ETIMEDOUT; + break; + + case RDMA_CM_EVENT_CONNECT_REQUEST: + // incoming connections not supported => reject all + #ifdef OFED_RDMA_REJECT_NEEDS_REASON + rdma_reject(cm_id, NULL, 0, 0); + #else + rdma_reject(cm_id, NULL, 0); + #endif // OFED_RDMA_REJECT_NEEDS_REASON + break; + + case RDMA_CM_EVENT_CONNECT_RESPONSE: + retVal = rdma_accept(cm_id, NULL); + break; + + case RDMA_CM_EVENT_REJECTED: + retVal = event->status == IB_CM_REJ_STALE_CONN ? -ESTALE : -ECONNREFUSED; + break; + + case RDMA_CM_EVENT_ESTABLISHED: + retVal = __IBVSocket_connectedHandler(_this, event); + _this->connState = IBVSOCKETCONNSTATE_ESTABLISHED; + break; + + case RDMA_CM_EVENT_DISCONNECTED: + rdma_disconnect(cm_id); + break; + + case RDMA_CM_EVENT_DEVICE_REMOVAL: + /** + * Sigh... what to do? There were previous attempts to perform cleanup of the + * IBVCommContext and return -ENETRESET when this event is encountered. + * Returning a nonzero value causes RDMA CM to destroy the cm_id and anything + * belonging to that cm_id must be destroyed here before returning nonzero. There + * are a lot of race conditions with the worker thread and attempting to implement + * a locking scheme would require significant redesign due to the use of + * blocking calls to ib_poll_cq. Ignoring the event appears to allow normal + * cleanup of resources after the RDMA routines return an error to the caller. + */ + ibv_print_info("Device has been removed: %s\n", cm_id->device->name); + break; + + default: + ibv_print_info_debug("Ignoring RDMA_CMA event: %d\n", event->event); + break; + } + + if(unlikely(retVal) ) + { + if(retVal == -ESTALE) + _this->connState = IBVSOCKETCONNSTATE_REJECTED_STALE; + else + _this->connState = IBVSOCKETCONNSTATE_FAILED; + + _this->errState = -1; + // free connection resources later. freeing everything here may race with send/recv + // operations in case of a connection breakage. + retVal = 0; + } + + Mutex_unlock(&_this->cmaMutex); + wake_up(&_this->eventWaitQ); + return retVal; +} + +/** + * Invoked when an asynchronous event not associated with a completion occurs on the CQ. + */ +void __IBVSocket_cqSendEventHandler(struct ib_event *event, void *data) +{ + ibv_print_info_debug("called. event type: %d (not handled)\n", event->event); +} + +/** + * Invoked when a completion event occurs on the CQ. + * + * @param cq_context IBVSocket* _this + */ +void __IBVSocket_sendCompletionHandler(struct ib_cq *cq, void *cq_context) +{ + IBVSocket* _this = cq_context; + IBVCommContext* commContext = _this->commContext; + int reqNotifySendRes; + + atomic_inc(&commContext->sendCompEventCount); + + reqNotifySendRes = ib_req_notify_cq(commContext->sendCQ, IB_CQ_NEXT_COMP); + if(unlikely(reqNotifySendRes) ) + ibv_print_info("Couldn't request CQ notification\n"); + + wake_up(&commContext->sendCompWaitQ); +} + +/** + * Invoked when an asynchronous event not associated with a completion occurs on the CQ. + */ +void __IBVSocket_cqRecvEventHandler(struct ib_event *event, void *data) +{ + ibv_print_info_debug("called. event type: %d (not handled)\n", event->event); +} + +/** + * Invoked when a completion event occurs on the CQ. + * + * @param cq_context IBVSocket* _this + */ +void __IBVSocket_recvCompletionHandler(struct ib_cq *cq, void *cq_context) +{ + IBVSocket* _this = cq_context; + IBVCommContext* commContext = _this->commContext; + int reqNotifyRecvRes; + + atomic_inc(&commContext->recvCompEventCount); + + reqNotifyRecvRes = ib_req_notify_cq(commContext->recvCQ, IB_CQ_NEXT_COMP); + if(unlikely(reqNotifyRecvRes) ) + ibv_print_info("Couldn't request CQ notification\n"); + + wake_up(&commContext->recvCompWaitQ); +} + +void __IBVSocket_qpEventHandler(struct ib_event *event, void *data) +{ + ibv_print_info_debug("called. event type: %d (not handled)\n", event->event); +} + +int __IBVSocket_routeResolvedHandler(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext) +{ + bool createContextRes; + struct rdma_conn_param conn_param; + + createContextRes = __IBVSocket_createCommContext(_this, _this->cm_id, commCfg, + &_this->commContext); + if(!createContextRes) + { + ibv_print_info("creation of CommContext failed\n"); + _this->errState = -1; + + return -EPERM; + } + + // establish connection... + if (_this->commContext->checkConnRkey == 0) + { + if (!IBVBuffer_initRegistration(&_this->commContext->checkConBuffer, _this->commContext)) + { + _this->errState = -1; + + return -EPERM; + } + _this->commContext->checkConnRkey = _this->commContext->checkConBuffer.mr->rkey; + } + + if (!__IBVSocket_initCommDest(_this->commContext, &_this->localDest)) + { + ibv_print_info_ir("creation of CommDest failed\n"); + _this->errState = -1; + + return -EPERM; + } + + memset(&conn_param, 0, sizeof(conn_param) ); +#ifdef BEEGFS_NVFS + conn_param.responder_resources = _this->commContext->pd->device->attrs.max_qp_rd_atom; + conn_param.initiator_depth = _this->commContext->pd->device->attrs.max_qp_init_rd_atom; +#else + conn_param.responder_resources = 1; + conn_param.initiator_depth = 1; +#endif + conn_param.flow_control = 0; + conn_param.retry_count = 7; // (3 bits) + conn_param.rnr_retry_count = 7; // rnr = receiver not ready (3 bits, 7 means infinity) + conn_param.private_data = &_this->localDest; + conn_param.private_data_len = sizeof(_this->localDest); + + return rdma_connect(_this->cm_id, &conn_param); +} + + +int __IBVSocket_connectedHandler(IBVSocket* _this, struct rdma_cm_event* event) +{ + IBVCommContext* commContext = _this->commContext; + IBVCommConfig* commCfg; + int retVal = 0; + bool parseCommDestRes; + const void* private_data; + u8 private_data_len; + int i; + + if (!commContext) + return -EINVAL; + + commCfg = &commContext->commCfg; + if (_this->commContext->commCfg.keyType == IBVSOCKETKEYTYPE_Register) + { + if(IBVSocket_registerMr(_this, _this->commContext->checkConBuffer.mr, IB_ACCESS_REMOTE_READ)) + { + ibv_print_info("register buffer failed\n"); + _this->errState = -1; + return -EPERM; + } + } + + // post initial recv buffers... + for(i=0; i < commCfg->bufNum; i++) + { + if(__IBVSocket_postRecv(_this, commContext, i) ) + { + ibv_print_info("couldn't post recv buffer with index %d\n", i); + goto err_invalidateSock; + } + } + +#if defined BEEGFS_OFED_1_2_API && (BEEGFS_OFED_1_2_API == 1) + private_data = event->private_data; + private_data_len = event->private_data_len; +#else // OFED 1.2.5 or higher API + private_data = event->param.conn.private_data; + private_data_len = event->param.conn.private_data_len; +#endif + + parseCommDestRes = __IBVSocket_parseCommDest( + private_data, private_data_len, &_this->remoteDest); + if(!parseCommDestRes) + { + ibv_print_info("bad private data received. len: %d\n", private_data_len); + + retVal = -EOPNOTSUPP; + goto err_invalidateSock; + } + + + return retVal; + + +err_invalidateSock: + _this->errState = -1; + + return retVal; +} + +struct ib_cq* __IBVSocket_createCompletionQueue(struct ib_device* device, + ib_comp_handler comp_handler, void (*event_handler)(struct ib_event *, void *), + void* cq_context, int cqe) +{ + #if defined (BEEGFS_OFED_1_2_API) && BEEGFS_OFED_1_2_API >= 1 + return ib_create_cq(device, comp_handler, event_handler, cq_context, cqe); + #elif defined OFED_HAS_IB_CREATE_CQATTR || defined ib_create_cq + struct ib_cq_init_attr attrs = { + .cqe = cqe, + #ifdef KERNEL_HAS_GET_RANDOM_INT + .comp_vector = get_random_int()%device->num_comp_vectors, + #else + .comp_vector = get_random_long()%device->num_comp_vectors, + #endif + + }; + + return ib_create_cq(device, comp_handler, event_handler, cq_context, &attrs); + #else // OFED 1.2.5 or higher API + return ib_create_cq(device, comp_handler, event_handler, cq_context, cqe, 0); + #endif +} + +/** + * @return pointer to static buffer with human readable string for a wc status code + */ +const char* __IBVSocket_wcStatusStr(int wcStatusCode) +{ + switch(wcStatusCode) + { + case IB_WC_WR_FLUSH_ERR: + return "work request flush error"; + case IB_WC_RETRY_EXC_ERR: + return "retries exceeded error"; + case IB_WC_RESP_TIMEOUT_ERR: + return "response timeout error"; + + default: + return ""; + } +} + +int IBVSocket_registerMr(IBVSocket* _this, struct ib_mr* mr, int access) +{ + struct ib_reg_wr wr; + int res; + + memset(&wr, 0, sizeof(wr)); + wr.wr.opcode = IB_WR_REG_MR; + wr.mr = mr; + wr.key = mr->rkey; + wr.access = IB_ACCESS_LOCAL_WRITE | access; + + res = ib_post_send(_this->commContext->qp, &wr.wr, NULL); + if (unlikely(res)) + { + printk_fhgfs(KERN_ERR, "Failed to post IB_WR_REG_MR res=%d\n", res); + return -1; + } + + return 0; +} + +struct in_addr IBVSocket_getSrcIpAddr(IBVSocket* _this) +{ + return _this->srcIpAddr; +} + + +NicAddressStats* IBVSocket_getNicStats(IBVSocket* _this) +{ + return _this->nicStats; +} + +#endif diff --git a/client_module/source/common/net/sock/ibv/IBVSocket.h b/client_module/source/common/net/sock/ibv/IBVSocket.h new file mode 100644 index 0000000..a102355 --- /dev/null +++ b/client_module/source/common/net/sock/ibv/IBVSocket.h @@ -0,0 +1,283 @@ +#ifndef OPENTK_IBVSOCKET_H_ +#define OPENTK_IBVSOCKET_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define IBVSOCKET_PRIVATEDATA_STR "fhgfs0 " // must be exactly(!!) 8 bytes long +#define IBVSOCKET_PRIVATEDATA_STR_LEN 8 +#define IBVSOCKET_PRIVATEDATA_PROTOCOL_VER 1 + +struct ib_device; +struct ib_mr; + +struct IBVIncompleteRecv; +typedef struct IBVIncompleteRecv IBVIncompleteRecv; +struct IBVIncompleteSend; +typedef struct IBVIncompleteSend IBVIncompleteSend; + +struct IBVCommContext; +typedef struct IBVCommContext IBVCommContext; + +struct IBVCommDest; +typedef struct IBVCommDest IBVCommDest; + +struct IBVTimeoutConfig; +typedef struct IBVTimeoutConfig IBVTimeoutConfig; + +struct IBVSocket; // forward declaration +typedef struct IBVSocket IBVSocket; + +struct IBVCommConfig; +typedef struct IBVCommConfig IBVCommConfig; + +struct NicAddressStats; +typedef struct NicAddressStats NicAddressStats; + +enum IBVSocketKeyType +{ + IBVSOCKETKEYTYPE_UnsafeGlobal = 0, + IBVSOCKETKEYTYPE_UnsafeDMA, + IBVSOCKETKEYTYPE_Register +}; +typedef enum IBVSocketKeyType IBVSocketKeyType; + +// construction/destruction +extern __must_check bool IBVSocket_init(IBVSocket* _this, struct in_addr srcIpAddr, NicAddressStats* nicStats); +extern void IBVSocket_uninit(IBVSocket* _this); + +// static +extern bool IBVSocket_rdmaDevicesExist(void); + +// methods +extern bool IBVSocket_connectByIP(IBVSocket* _this, struct in_addr ipaddress, + unsigned short port, IBVCommConfig* commCfg); +extern bool IBVSocket_bindToAddr(IBVSocket* _this, struct in_addr ipAddr, + unsigned short port); +extern bool IBVSocket_listen(IBVSocket* _this); +extern bool IBVSocket_shutdown(IBVSocket* _this); + +extern ssize_t IBVSocket_recvT(IBVSocket* _this, struct iov_iter* iter, int flags, + int timeoutMS); +extern ssize_t IBVSocket_send(IBVSocket* _this, struct iov_iter* iter, int flags); + +extern int IBVSocket_checkConnection(IBVSocket* _this); + +extern unsigned long IBVSocket_poll(IBVSocket* _this, short events, bool finishPoll); + +// getters & setters +extern void IBVSocket_setTimeouts(IBVSocket* _this, int connectMS, + int completionMS, int flowSendMS, int flowRecvMS, int pollMS); +extern void IBVSocket_setTypeOfService(IBVSocket* _this, int typeOfService); +extern void IBVSocket_setConnectionFailureStatus(IBVSocket* _this, unsigned value); +extern struct in_addr IBVSocket_getSrcIpAddr(IBVSocket* _this); + +// Only access members of NicAddressStats when the owner NodeConnPool mutex is held. +// OK to access "nic" without holding mutex. +extern NicAddressStats* IBVSocket_getNicStats(IBVSocket* _this); + +extern unsigned IBVSocket_getRkey(IBVSocket* _this); +extern struct ib_device* IBVSocket_getDevice(IBVSocket* _this); +extern int IBVSocket_registerMr(IBVSocket* _this, struct ib_mr* mr, int access); + +struct IBVTimeoutConfig +{ + int connectMS; + int completionMS; + int flowSendMS; + int flowRecvMS; + int pollMS; +}; + +struct IBVCommConfig +{ + unsigned bufNum; // number of available buffers + unsigned bufSize; // total size of each buffer + /** + * IBVBuffer can allocate the buffer in multiple memory regions. This + * is to allow allocation of large buffers without requiring the + * buffer to be entirely contiguous. A value of 0 means that the + * buffer should not be fragmented. + */ + unsigned fragmentSize; // size of buffer fragments + IBVSocketKeyType keyType; // Which type of rkey for RDMA +}; + +#ifdef BEEGFS_RDMA +#include +#include +#include +#include +#include "IBVBuffer.h" + + +enum IBVSocketConnState; +typedef enum IBVSocketConnState IBVSocketConnState_t; + + +extern bool __IBVSocket_createNewID(IBVSocket* _this); +extern bool __IBVSocket_createCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext); +extern void __IBVSocket_cleanupCommContext(struct rdma_cm_id* cm_id, IBVCommContext* commContext); + +extern bool __IBVSocket_initCommDest(IBVCommContext* commContext, IBVCommDest* outDest); +extern bool __IBVSocket_parseCommDest(const void* buf, size_t bufLen, IBVCommDest** outDest); + +extern int __IBVSocket_receiveCheck(IBVSocket* _this, int timeoutMS); +extern int __IBVSocket_nonblockingSendCheck(IBVSocket* _this); + +extern int __IBVSocket_postRecv(IBVSocket* _this, IBVCommContext* commContext, size_t bufIndex); +extern int __IBVSocket_postSend(IBVSocket* _this, size_t bufIndex); +extern int __IBVSocket_recvWC(IBVSocket* _this, int timeoutMS, struct ib_wc* outWC); + +extern int __IBVSocket_flowControlOnRecv(IBVSocket* _this, int timeoutMS); +extern void __IBVSocket_flowControlOnSendUpdateCounters(IBVSocket* _this); +extern int __IBVSocket_flowControlOnSendWait(IBVSocket* _this, int timeoutMS); + +extern int __IBVSocket_waitForRecvCompletionEvent(IBVSocket* _this, int timeoutMS, + struct ib_wc* outWC); +extern int __IBVSocket_waitForSendCompletionEvent(IBVSocket* _this, int oldSendCount, + int timeoutMS); +extern int __IBVSocket_waitForTotalSendCompletion(IBVSocket* _this, + unsigned* numSendElements, unsigned* numWriteElements, unsigned* numReadElements, int timeoutMS); + +extern ssize_t __IBVSocket_recvContinueIncomplete(IBVSocket* _this, struct iov_iter* iter); + +extern int __IBVSocket_cmaHandler(struct rdma_cm_id* cm_id, struct rdma_cm_event* event); +extern void __IBVSocket_cqSendEventHandler(struct ib_event* event, void* data); +extern void __IBVSocket_sendCompletionHandler(struct ib_cq* cq, void* cq_context); +extern void __IBVSocket_cqRecvEventHandler(struct ib_event* event, void* data); +extern void __IBVSocket_recvCompletionHandler(struct ib_cq* cq, void* cq_context); +extern void __IBVSocket_qpEventHandler(struct ib_event* event, void* data); +extern int __IBVSocket_routeResolvedHandler(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext); +extern int __IBVSocket_connectedHandler(IBVSocket* _this, struct rdma_cm_event *event); + +extern struct ib_cq* __IBVSocket_createCompletionQueue(struct ib_device* device, + ib_comp_handler comp_handler, void (*event_handler)(struct ib_event *, void *), + void* cq_context, int cqe); + +extern const char* __IBVSocket_wcStatusStr(int wcStatusCode); + + +enum IBVSocketConnState +{ + IBVSOCKETCONNSTATE_UNCONNECTED=0, + IBVSOCKETCONNSTATE_CONNECTING=1, + IBVSOCKETCONNSTATE_ADDRESSRESOLVED=2, + IBVSOCKETCONNSTATE_ROUTERESOLVED=3, + IBVSOCKETCONNSTATE_ESTABLISHED=4, + IBVSOCKETCONNSTATE_FAILED=5, + IBVSOCKETCONNSTATE_REJECTED_STALE=6 +}; + + +struct IBVIncompleteRecv +{ + int isAvailable; + int completedOffset; + int bufIndex; + int totalSize; +}; + +struct IBVIncompleteSend +{ + unsigned numAvailable; + bool forceWaitForAll; // true if we received only some completions and need + // to wait for the rest before we can send more data +}; + +struct IBVCommContext +{ + struct ib_pd* pd; // protection domain + struct ib_mr* dmaMR; // system DMA MR. Not supported on all platforms. + atomic_t recvCompEventCount; // incremented on incoming event notification + wait_queue_head_t recvCompWaitQ; // for recvCompEvents + wait_queue_t recvWait; + bool recvWaitInitialized; // true if init_wait was called for the thread + atomic_t sendCompEventCount; // incremented on incoming event notification + wait_queue_head_t sendCompWaitQ; // for sendCompEvents + wait_queue_t sendWait; + bool sendWaitInitialized; // true if init_wait was called for the thread + + struct ib_cq* recvCQ; // recv completion queue + struct ib_cq* sendCQ; // send completion queue + struct ib_qp* qp; // send+recv queue pair + + IBVCommConfig commCfg; + struct IBVBuffer* sendBufs; + struct IBVBuffer* recvBufs; + struct IBVBuffer checkConBuffer; + unsigned numReceivedBufsLeft; // flow control v2 to avoid IB rnr timeout + unsigned numSendBufsLeft; // flow control v2 to avoid IB rnr timeout + + IBVIncompleteRecv incompleteRecv; + IBVIncompleteSend incompleteSend; + u32 checkConnRkey; +}; + +#pragma pack(push, 1) +// Note: Make sure this struct has the same size on all architectures (because we use +// sizeof(IBVCommDest) for private_data during handshake) +struct IBVCommDest +{ + char verificationStr[IBVSOCKET_PRIVATEDATA_STR_LEN]; + uint64_t protocolVersion; + uint64_t vaddr; + unsigned rkey; + unsigned recvBufNum; + unsigned recvBufSize; +}; +#pragma pack(pop) + +struct IBVSocket +{ + wait_queue_head_t eventWaitQ; // used to wait for connState change during connect + + + struct rdma_cm_id* cm_id; + struct in_addr srcIpAddr; + + IBVCommDest localDest; + IBVCommDest* remoteDest; + + IBVCommContext* commContext; + + int errState; // 0 = ; -1 = + + volatile IBVSocketConnState_t connState; + + int typeOfService; + unsigned remapConnectionFailureStatus; + NicAddressStats* nicStats; // Owned by a NodeConnPool instance. Do not access + // members without locking the NodeConnPool mutex. + // Possibly NULL. + IBVTimeoutConfig timeoutCfg; + Mutex cmaMutex; // used to manage concurrency of cm_id and commContext + // with __IBVSocket_cmaHandler +}; + + +#else + + +struct IBVSocket +{ + /* empty structs are not allowed, so until this kludge can go, add a dummy member */ + unsigned:0; +}; + + +#endif + +#endif /*OPENTK_IBVSOCKET_H_*/ diff --git a/client_module/source/common/net/sock/ibv/No_IBVSocket.c b/client_module/source/common/net/sock/ibv/No_IBVSocket.c new file mode 100644 index 0000000..f9cb4b2 --- /dev/null +++ b/client_module/source/common/net/sock/ibv/No_IBVSocket.c @@ -0,0 +1,114 @@ +#include "IBVSocket.h" + +#ifndef BEEGFS_RDMA + +#define no_ibvsocket_err() \ + printk_fhgfs(KERN_INFO, "%s:%d: You should never see this message\n", __func__, __LINE__) + +bool IBVSocket_init(IBVSocket* _this, struct in_addr srcIpAddr, NicAddressStats* nicStats) +{ + no_ibvsocket_err(); + return false; +} + +void IBVSocket_uninit(IBVSocket* _this) +{ + // nothing to be done here +} + +bool IBVSocket_rdmaDevicesExist(void) +{ + return false; +} + +bool IBVSocket_connectByIP(IBVSocket* _this, struct in_addr ipaddress, unsigned short port, + IBVCommConfig* commCfg) +{ + no_ibvsocket_err(); + return false; +} + +bool IBVSocket_bindToAddr(IBVSocket* _this, struct in_addr ipAddr, unsigned short port) +{ + no_ibvsocket_err(); + return false; +} + +bool IBVSocket_listen(IBVSocket* _this) +{ + no_ibvsocket_err(); + return false; +} + +bool IBVSocket_shutdown(IBVSocket* _this) +{ + no_ibvsocket_err(); + return false; +} + +ssize_t IBVSocket_recvT(IBVSocket* _this, struct iov_iter* iter, int flags, int timeoutMS) +{ + no_ibvsocket_err(); + return -1; +} + +ssize_t IBVSocket_send(IBVSocket* _this, struct iov_iter* iter, int flags) +{ + no_ibvsocket_err(); + return -1; +} + +/** + * @return 0 on success, -1 on error + */ +int IBVSocket_checkConnection(IBVSocket* _this) +{ + no_ibvsocket_err(); + return -1; +} + +unsigned long IBVSocket_poll(IBVSocket* _this, short events, bool finishPoll) +{ + no_ibvsocket_err(); + return ~0; +} + +unsigned IBVSocket_getRkey(IBVSocket* _this) +{ + no_ibvsocket_err(); + return ~0; +} + +struct ib_device* IBVSocket_getDevice(IBVSocket* _this) +{ + return NULL; +} + +void IBVSocket_setTimeouts(IBVSocket* _this, int connectMS, + int completionMS, int flowSendMS, int flowRecvMS, int pollMS) +{ +} + +void IBVSocket_setTypeOfService(IBVSocket* _this, int typeOfService) +{ +} + +void IBVSocket_setConnectionFailureStatus(IBVSocket* _this, unsigned value) +{ +} + +struct in_addr IBVSocket_getSrcIpAddr(IBVSocket* _this) +{ + struct in_addr r = { + .s_addr = ~0 + }; + return r; +} + +NicAddressStats* IBVSocket_getNicStats(IBVSocket* _this) +{ + return NULL; +} + +#endif + diff --git a/client_module/source/common/nodes/ConnectionList.h b/client_module/source/common/nodes/ConnectionList.h new file mode 100644 index 0000000..80c541c --- /dev/null +++ b/client_module/source/common/nodes/ConnectionList.h @@ -0,0 +1,90 @@ +#ifndef CONNECTIONLIST_H_ +#define CONNECTIONLIST_H_ + +#include +#include +#include + +struct ConnectionList; +typedef struct ConnectionList ConnectionList; + + +static inline void ConnectionList_init(ConnectionList* this, bool owner); +static inline void ConnectionList_uninit(ConnectionList* this); +static inline void ConnectionList_append(ConnectionList* this, PooledSocket* socket); +static inline void ConnectionList_prepend(ConnectionList* this, PooledSocket* socket); +static inline int ConnectionList_moveToHead(ConnectionList* this, PooledSocket* socket); +static inline int ConnectionList_moveToTail(ConnectionList* this, PooledSocket* socket); +static inline int ConnectionList_remove(ConnectionList* this, PooledSocket* socket); +static inline size_t ConnectionList_length(ConnectionList* this); + + +struct ConnectionList +{ + PointerList pointerList; + bool owner; +}; + +/* + * @param owner this list owns any PooledSockets added as list elements. The owner list + maintains the socket's pool and poolElem members. A temporary list should set + this parameter to false. + */ +void ConnectionList_init(ConnectionList* this, bool owner) +{ + PointerList_init( (PointerList*)this); + this->owner = owner; +} + +void ConnectionList_uninit(ConnectionList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void ConnectionList_prepend(ConnectionList* this, PooledSocket* socket) +{ + PointerList_addHead( (PointerList*)this, socket); + if (this->owner) + PooledSocket_setPool(socket, this, PointerList_getHead( (PointerList*) this)); +} + +void ConnectionList_append(ConnectionList* this, PooledSocket* socket) +{ + PointerList_append( (PointerList*)this, socket); + if (this->owner) + PooledSocket_setPool(socket, this, PointerList_getTail( (PointerList*) this)); +} + +int ConnectionList_remove(ConnectionList* this, PooledSocket* socket) +{ + if (unlikely(PooledSocket_getPoolElem(socket) == NULL)) + return -EINVAL; + PointerList_removeElem( (PointerList*)this, PooledSocket_getPoolElem(socket)); + if (this->owner) + PooledSocket_setPool(socket, NULL, NULL); + return 0; +} + +int ConnectionList_moveToHead(ConnectionList* this, PooledSocket* socket) +{ + if (unlikely(PooledSocket_getPoolElem(socket) == NULL)) + return -EINVAL; + PointerList_moveToHead( (PointerList*) this, PooledSocket_getPoolElem(socket)); + return 0; +} + +int ConnectionList_moveToTail(ConnectionList* this, PooledSocket* socket) +{ + if (unlikely(PooledSocket_getPoolElem(socket) == NULL)) + return -EINVAL; + PointerList_moveToTail( (PointerList*) this, PooledSocket_getPoolElem(socket)); + return 0; +} + +static inline size_t ConnectionList_length(ConnectionList* this) +{ + return PointerList_length( (PointerList*)this); +} + + +#endif /*CONNECTIONLIST_H_*/ diff --git a/client_module/source/common/nodes/ConnectionListIter.h b/client_module/source/common/nodes/ConnectionListIter.h new file mode 100644 index 0000000..f3e8814 --- /dev/null +++ b/client_module/source/common/nodes/ConnectionListIter.h @@ -0,0 +1,61 @@ +#ifndef CONNECTIONLISTITER_H_ +#define CONNECTIONLISTITER_H_ + +#include +#include "ConnectionList.h" + +struct ConnectionListIter; +typedef struct ConnectionListIter ConnectionListIter; + +static inline void ConnectionListIter_init(ConnectionListIter* this, ConnectionList* list); +static inline void ConnectionListIter_next(ConnectionListIter* this); +static inline struct PooledSocket* ConnectionListIter_value(ConnectionListIter* this); +static inline bool ConnectionListIter_end(ConnectionListIter* this); +static inline ConnectionListIter ConnectionListIter_remove(ConnectionListIter* this); + + +struct ConnectionListIter +{ + PointerListIter pointerListIter; +}; + + +void ConnectionListIter_init(ConnectionListIter* this, ConnectionList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void ConnectionListIter_next(ConnectionListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +struct PooledSocket* ConnectionListIter_value(ConnectionListIter* this) +{ + return (struct PooledSocket*)PointerListIter_value( (PointerListIter*)this); +} + +bool ConnectionListIter_end(ConnectionListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + +/** + * note: the current iterator becomes invalid after the call (use the returned iterator) + * @return the new iterator that points to the element just behind the erased one + */ +ConnectionListIter ConnectionListIter_remove(ConnectionListIter* this) +{ + ConnectionListIter newIter = *this; + PooledSocket* sock = ConnectionListIter_value(this); + + ConnectionListIter_next(&newIter); // the new iter that will be returned + + PointerListIter_remove( (PointerListIter*)this); + PooledSocket_setPool(sock, NULL, NULL); + + return newIter; +} + + +#endif /*CONNECTIONLISTITER_H_*/ diff --git a/client_module/source/common/nodes/DevicePriorityContext.h b/client_module/source/common/nodes/DevicePriorityContext.h new file mode 100644 index 0000000..a1faa12 --- /dev/null +++ b/client_module/source/common/nodes/DevicePriorityContext.h @@ -0,0 +1,15 @@ +#ifndef DEVICEPRIORITYCONTEXT_H_ +#define DEVICEPRIORITYCONTEXT_H_ + +struct DevicePriorityContext +{ + int maxConns; +#ifdef BEEGFS_NVFS + // index of GPU related to the first page, -1 for none + int gpuIndex; +#endif +}; + +typedef struct DevicePriorityContext DevicePriorityContext; + +#endif // DEVICEPRIORITYCONTEXT_H_ diff --git a/client_module/source/common/nodes/MirrorBuddyGroup.c b/client_module/source/common/nodes/MirrorBuddyGroup.c new file mode 100644 index 0000000..6c8004a --- /dev/null +++ b/client_module/source/common/nodes/MirrorBuddyGroup.c @@ -0,0 +1,227 @@ +#include "MirrorBuddyGroup.h" + +struct MirrorBuddyGroup* MirrorBuddyGroup_constructFromTargetIDs(uint16_t groupID, + uint16_t doneBufferSize, uint16_t firstTargetID, uint16_t secondTargetID) +{ + struct MirrorBuddyGroup* this = (MirrorBuddyGroup*) os_kmalloc(sizeof(*this)); + + if (!this) + return NULL; + + this->groupID = groupID; + + this->firstTargetID = firstTargetID; + this->secondTargetID = secondTargetID; + this->sequence = 0; + + if (doneBufferSize == 0) + goto fail; + + this->inFlightSize = 0; + this->inFlightCapacity = doneBufferSize; + this->seqNoInFlight = kmalloc(doneBufferSize * sizeof(struct BuddySequenceNumber), GFP_NOFS); + if (!this->seqNoInFlight) + this->seqNoInFlight = vmalloc(doneBufferSize * sizeof(struct BuddySequenceNumber)); + + if (!this->seqNoInFlight) + goto fail; + + this->firstFinishedIndex = 0; + this->finishedCount = 0; + this->finishedSeqNums = kmalloc(doneBufferSize * sizeof(uint64_t), GFP_NOFS); + if (!this->finishedSeqNums) + this->finishedSeqNums = vmalloc(doneBufferSize * sizeof(uint64_t)); + + if (!this->finishedSeqNums) + goto fail_seqNoInFlight; + + mutex_init(&this->mtx); + sema_init(&this->slotsAvail, doneBufferSize); + kref_init(&this->refs); + + return this; + +fail_seqNoInFlight: + if (is_vmalloc_addr(this->seqNoInFlight)) + vfree(this->seqNoInFlight); + else + kfree(this->seqNoInFlight); + +fail: + kfree(this); + return NULL; +} + +static void __MirrorBuddyGroup_destruct(struct kref* ref) +{ + MirrorBuddyGroup* this = container_of(ref, MirrorBuddyGroup, refs); + + if (is_vmalloc_addr(this->seqNoInFlight)) + vfree(this->seqNoInFlight); + else + kfree(this->seqNoInFlight); + + if (is_vmalloc_addr(this->finishedSeqNums)) + vfree(this->finishedSeqNums); + else + kfree(this->finishedSeqNums); + + mutex_destroy(&this->mtx); + + kfree(this); +} + +void MirrorBuddyGroup_put(MirrorBuddyGroup* this) +{ + kref_put(&this->refs, __MirrorBuddyGroup_destruct); +} + +int MirrorBuddyGroup_acquireSequenceNumber(MirrorBuddyGroup* this, + uint64_t* acknowledgeSeq, bool* isSelective, uint64_t* seqNo, + struct BuddySequenceNumber** handle, bool allowWait) +{ + int result = 0; + + if (allowWait) + { + if (down_killable(&this->slotsAvail)) + return EINTR; + } + else + { + if (down_trylock(&this->slotsAvail)) + return EAGAIN; + } + + kref_get(&this->refs); + + mutex_lock(&this->mtx); + do { + if (this->sequence == 0) + { + *seqNo = 0; + *handle = NULL; + result = ENOENT; + up(&this->slotsAvail); + MirrorBuddyGroup_put(this); + break; + } + + *seqNo = ++this->sequence; + + // seqNoInFlight is a binary min-heap, and we add values from a strictly increasing sequence. + // thus any such append produces a correctly formed heap. + this->seqNoInFlight[this->inFlightSize].pSelf = handle; + this->seqNoInFlight[this->inFlightSize].value = *seqNo; + *handle = &this->seqNoInFlight[this->inFlightSize]; + + this->inFlightSize++; + + if (this->finishedCount > 0) + { + *acknowledgeSeq = this->finishedSeqNums[this->firstFinishedIndex]; + this->firstFinishedIndex = (this->firstFinishedIndex + 1) % this->inFlightCapacity; + this->finishedCount -= 1; + *isSelective = true; + } + else + { + *acknowledgeSeq = this->seqNoInFlight[0].value - 1; + *isSelective = false; + } + } while (0); + mutex_unlock(&this->mtx); + + return result; +} + +void MirrorBuddyGroup_releaseSequenceNumber(MirrorBuddyGroup* this, + struct BuddySequenceNumber** handle) +{ + mutex_lock(&this->mtx); + { + struct BuddySequenceNumber* slot = *handle; + unsigned nextFinishedIndex; + + BEEGFS_BUG_ON_DEBUG(slot < &this->seqNoInFlight[0], ""); + BEEGFS_BUG_ON_DEBUG(slot >= &this->seqNoInFlight[this->inFlightSize], ""); + + // before maintaining the heap, add the sequence number to the "finished seq#" ringbuffer. + if (this->finishedCount < this->inFlightCapacity) + { + this->finishedCount = this->finishedCount + 1; + nextFinishedIndex = (this->firstFinishedIndex + this->finishedCount - 1) + % this->inFlightCapacity; + } + else + { + this->firstFinishedIndex = (this->firstFinishedIndex + 1) % this->inFlightCapacity; + nextFinishedIndex = this->firstFinishedIndex; + } + + this->finishedSeqNums[nextFinishedIndex] = (*handle)->value; + + // decrease the key of the seq# to minimum + while (slot != &this->seqNoInFlight[0]) + { + const ptrdiff_t index = slot - &this->seqNoInFlight[0]; + const ptrdiff_t parentIndex = index / 2; + + swap(this->seqNoInFlight[index], this->seqNoInFlight[parentIndex]); + + *this->seqNoInFlight[index].pSelf = &this->seqNoInFlight[index]; + *this->seqNoInFlight[parentIndex].pSelf = &this->seqNoInFlight[parentIndex]; + + slot = &this->seqNoInFlight[parentIndex]; + } + + // remove the "minimal" element + this->inFlightSize--; + swap(this->seqNoInFlight[0], this->seqNoInFlight[this->inFlightSize]); + *this->seqNoInFlight[0].pSelf = &this->seqNoInFlight[0]; + *this->seqNoInFlight[this->inFlightSize].pSelf = &this->seqNoInFlight[this->inFlightSize]; + + // move the new root down to restore the heap property + { + unsigned i; + for (i = 0; ;) + { + const unsigned leftChild = 2 * i + 1; + const unsigned rightChild = 2 * i + 2; + unsigned minNode = i; + + if (leftChild < this->inFlightSize + && this->seqNoInFlight[leftChild].value < this->seqNoInFlight[minNode].value) + minNode = leftChild; + if (rightChild < this->inFlightSize + && this->seqNoInFlight[rightChild].value < this->seqNoInFlight[minNode].value) + minNode = rightChild; + + if (minNode != i) + { + swap(this->seqNoInFlight[i], this->seqNoInFlight[minNode]); + + *this->seqNoInFlight[i].pSelf = &this->seqNoInFlight[i]; + *this->seqNoInFlight[minNode].pSelf = &this->seqNoInFlight[minNode]; + i = minNode; + } + else + { + break; + } + } + } + } + mutex_unlock(&this->mtx); + + up(&this->slotsAvail); + MirrorBuddyGroup_put(this); +} + +void MirrorBuddyGroup_setSeqNoBase(MirrorBuddyGroup* this, uint64_t seqNoBase) +{ + mutex_lock(&this->mtx); + if (this->sequence < seqNoBase) + this->sequence = seqNoBase; + mutex_unlock(&this->mtx); +} diff --git a/client_module/source/common/nodes/MirrorBuddyGroup.h b/client_module/source/common/nodes/MirrorBuddyGroup.h new file mode 100644 index 0000000..bb26cfd --- /dev/null +++ b/client_module/source/common/nodes/MirrorBuddyGroup.h @@ -0,0 +1,62 @@ +#ifndef MIRRORBUDDYGROUP_H +#define MIRRORBUDDYGROUP_H + +#include +#include +#include + +struct MirrorBuddyGroup; +typedef struct MirrorBuddyGroup MirrorBuddyGroup; + +// used to build a binary min-heap with backlink handles. +// an inserter will receive a handle to the inserted value, this handle may be used to remove the +// value in logarithmic time. +struct BuddySequenceNumber +{ + struct BuddySequenceNumber** pSelf; + uint64_t value; +}; + +struct MirrorBuddyGroup +{ + uint16_t groupID; + + uint16_t firstTargetID; + uint16_t secondTargetID; + + uint64_t sequence; + + struct semaphore slotsAvail; + + struct BuddySequenceNumber* seqNoInFlight; + unsigned inFlightSize; + unsigned inFlightCapacity; + + uint64_t* finishedSeqNums; + unsigned firstFinishedIndex; + unsigned finishedCount; + + struct mutex mtx; + + struct kref refs; + +/* private */ + union { + struct rb_node _rb_node; + struct list_head _list; + }; +}; + +extern struct MirrorBuddyGroup* MirrorBuddyGroup_constructFromTargetIDs(uint16_t groupID, + uint16_t doneBufferSize, uint16_t firstTargetID, uint16_t secondTargetID); +extern void MirrorBuddyGroup_put(MirrorBuddyGroup* this); + +extern int MirrorBuddyGroup_acquireSequenceNumber(MirrorBuddyGroup* this, + uint64_t* acknowledgeSeq, bool* isSelective, uint64_t* seqNo, + struct BuddySequenceNumber** handle, bool allowWait); +extern void MirrorBuddyGroup_releaseSequenceNumber(MirrorBuddyGroup* this, + struct BuddySequenceNumber** handle); + +extern void MirrorBuddyGroup_setSeqNoBase(MirrorBuddyGroup* this, uint64_t seqNoBase); + +#endif /* MIRRORBUDDYGROUP_H */ diff --git a/client_module/source/common/nodes/MirrorBuddyGroupMapper.c b/client_module/source/common/nodes/MirrorBuddyGroupMapper.c new file mode 100644 index 0000000..223b6ac --- /dev/null +++ b/client_module/source/common/nodes/MirrorBuddyGroupMapper.c @@ -0,0 +1,293 @@ +#include + +#include "MirrorBuddyGroupMapper.h" + +BEEGFS_RBTREE_FUNCTIONS(static, _MirrorBuddyGroupMapper, struct MirrorBuddyGroupMapper, + mirrorBuddyGroups, + uint16_t, + struct MirrorBuddyGroup, groupID, _rb_node, + BEEGFS_RB_KEYCMP_LT_INTEGRAL) + +void MirrorBuddyGroupMapper_init(MirrorBuddyGroupMapper* this) +{ + RWLock_init(&this->rwlock); + this->mirrorBuddyGroups = RB_ROOT; +} + +static void _MirrorBuddyGroupMapper_clear(MirrorBuddyGroupMapper* this) +{ + MirrorBuddyGroup* pos; + MirrorBuddyGroup* n; + + rbtree_postorder_for_each_entry_safe(pos, n, &this->mirrorBuddyGroups, _rb_node) + MirrorBuddyGroup_put(pos); + + this->mirrorBuddyGroups = RB_ROOT; +} + +MirrorBuddyGroupMapper* MirrorBuddyGroupMapper_construct(void) +{ + MirrorBuddyGroupMapper* this = (MirrorBuddyGroupMapper*)os_kmalloc(sizeof(*this) ); + + if (likely(this)) + MirrorBuddyGroupMapper_init(this); + + return this; +} + +void MirrorBuddyGroupMapper_uninit(MirrorBuddyGroupMapper* this) +{ + _MirrorBuddyGroupMapper_clear(this); +} + +void MirrorBuddyGroupMapper_destruct(MirrorBuddyGroupMapper* this) +{ + MirrorBuddyGroupMapper_uninit(this); + + kfree(this); +} + +/** + * @return 0 if group ID not found + */ +uint16_t MirrorBuddyGroupMapper_getPrimaryTargetID(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID) +{ + MirrorBuddyGroup* buddyGroup; + uint16_t targetID; + + RWLock_readLock(&this->rwlock); // L O C K + + buddyGroup = _MirrorBuddyGroupMapper_find(this, mirrorBuddyGroupID); + + if(likely(buddyGroup)) + targetID = buddyGroup->firstTargetID; + else + targetID = 0; + + RWLock_readUnlock(&this->rwlock); // U N L O C K + + return targetID; +} + +/** + * @return 0 if group ID not found + */ +uint16_t MirrorBuddyGroupMapper_getSecondaryTargetID(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID) +{ + MirrorBuddyGroup* buddyGroup; + uint16_t targetID; + + RWLock_readLock(&this->rwlock); // L O C K + + buddyGroup = _MirrorBuddyGroupMapper_find(this, mirrorBuddyGroupID); + if(likely(buddyGroup)) + targetID = buddyGroup->secondTargetID; + else + targetID = 0; + + RWLock_readUnlock(&this->rwlock); // U N L O C K + + return targetID; +} + +int MirrorBuddyGroupMapper_acquireSequenceNumber(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID, uint64_t* seqNo, uint64_t* finishedSeqNo, bool* isSelective, + struct BuddySequenceNumber** handle, struct MirrorBuddyGroup** group) +{ + MirrorBuddyGroup* buddyGroup; + int result = 0; + + RWLock_readLock(&this->rwlock); + + buddyGroup = _MirrorBuddyGroupMapper_find(this, mirrorBuddyGroupID); + if (!buddyGroup) + { + RWLock_readUnlock(&this->rwlock); + return ENOENT; + } + + *group = buddyGroup; + + result = MirrorBuddyGroup_acquireSequenceNumber(buddyGroup, finishedSeqNo, isSelective, seqNo, + handle, false); + // treat ENOENT (no seqNoBase set) as success. the target node will reply with a generic response + // that sets the seqNoBase for this buddy group. + if (result == 0 || result == ENOENT) + { + RWLock_readUnlock(&this->rwlock); + return 0; + } + + // nowait acquire failed, need to wait for a free slot. get a ref, unlock this, and go on + kref_get(&buddyGroup->refs); + + RWLock_readUnlock(&this->rwlock); + + result = MirrorBuddyGroup_acquireSequenceNumber(buddyGroup, finishedSeqNo, isSelective, seqNo, + handle, true); + MirrorBuddyGroup_put(buddyGroup); + // treat ENOENT as success here, too. we will hopefully never have to wait for a sequence number + // on such a partially initialized state, but it may happen under very high load. + return result == ENOENT ? 0 : result; +} + +/** + * NOTE: no sanity checks in here + */ +void MirrorBuddyGroupMapper_syncGroups(MirrorBuddyGroupMapper* this, + Config* config, struct list_head* groups) +{ + RWLock_writeLock(&this->rwlock); // L O C K + + __MirrorBuddyGroupMapper_syncGroupsUnlocked(this, config, groups); + + RWLock_writeUnlock(&this->rwlock); // U N L O C K +} + + +/** + * note: caller must hold writelock. + */ +void __MirrorBuddyGroupMapper_syncGroupsUnlocked(MirrorBuddyGroupMapper* this, + Config* config, struct list_head* groups) +{ + struct BuddyGroupMapping* mapping; + + LIST_HEAD(newGroups); + + list_for_each_entry(mapping, groups, _list) + { + MirrorBuddyGroup* group; + + // if the group exists already, update it and move it over to the new tree + group = _MirrorBuddyGroupMapper_find(this, mapping->groupID); + if (group) + { + group->firstTargetID = mapping->primaryTargetID; + group->secondTargetID = mapping->secondaryTargetID; + + _MirrorBuddyGroupMapper_erase(this, group); + list_add_tail(&group->_list, &newGroups); + } + else + { + group = MirrorBuddyGroup_constructFromTargetIDs(mapping->groupID, + config->connMaxInternodeNum, mapping->primaryTargetID, mapping->secondaryTargetID); + + list_add_tail(&group->_list, &newGroups); + } + } + + _MirrorBuddyGroupMapper_clear(this); + + { + MirrorBuddyGroup* pos; + MirrorBuddyGroup* tmp; + + list_for_each_entry_safe(pos, tmp, &newGroups, _list) + { + MirrorBuddyGroup* replaced = _MirrorBuddyGroupMapper_insertOrReplace(this, pos); + if (replaced) + MirrorBuddyGroup_put(replaced); + } + } +} + +/** + * Adds a new buddy group to the map. + * @param targetMapper global targetmapper; must be set for storage nodes (and only for storage) + * @param buddyGroupID The ID of the new buddy group. Must be non-zero. + * @param primaryTargetID + * @param secondaryTargetID + * @param allowUpdate Allow updating an existing buddy group. + */ +FhgfsOpsErr MirrorBuddyGroupMapper_addGroup(MirrorBuddyGroupMapper* this, + Config* config, TargetMapper* targetMapper, uint16_t buddyGroupID, uint16_t primaryTargetID, + uint16_t secondaryTargetID, bool allowUpdate) +{ + FhgfsOpsErr res = FhgfsOpsErr_SUCCESS; + + uint16_t primaryInGroup; + uint16_t secondaryInGroup; + MirrorBuddyGroup* buddyGroup; + NumNodeID primaryNodeID; + NumNodeID secondaryNodeID; + + // ID = 0 is an error. + if (buddyGroupID == 0) + return FhgfsOpsErr_INVAL; + + RWLock_writeLock(&this->rwlock); // L O C K + + if (!allowUpdate) + { + // If group already exists return error. + MirrorBuddyGroup* group = _MirrorBuddyGroupMapper_find(this, buddyGroupID); + + if (group) + { + res = FhgfsOpsErr_EXISTS; + goto unlock; + } + } + + // Check if both targets exist for storage nodes + if (targetMapper) + { + primaryNodeID = TargetMapper_getNodeID(targetMapper, primaryTargetID); + secondaryNodeID = TargetMapper_getNodeID(targetMapper, secondaryTargetID); + if(NumNodeID_isZero(&primaryNodeID) || NumNodeID_isZero(&secondaryNodeID)) + { + res = FhgfsOpsErr_UNKNOWNTARGET; + goto unlock; + } + } + + // Check that both targets are not yet part of any group. + primaryInGroup = __MirrorBuddyGroupMapper_getBuddyGroupIDUnlocked(this, primaryTargetID); + secondaryInGroup = __MirrorBuddyGroupMapper_getBuddyGroupIDUnlocked(this, secondaryTargetID); + + if ( ( (primaryInGroup != 0) && (primaryInGroup != buddyGroupID) ) + || ( (secondaryInGroup != 0) && (secondaryInGroup != buddyGroupID) ) ) + { + res = FhgfsOpsErr_INUSE; + goto unlock; + } + + // Create and insert new mirror buddy group. + buddyGroup = MirrorBuddyGroup_constructFromTargetIDs(buddyGroupID, config->connMaxInternodeNum, + primaryTargetID, secondaryTargetID); + + if (unlikely(!buddyGroup) ) + { + printk_fhgfs(KERN_INFO, "%s:%d: Failed to allocate memory for MirrorBuddyGroup.", + __func__, __LINE__); + res = FhgfsOpsErr_OUTOFMEM; + goto unlock; + } + + _MirrorBuddyGroupMapper_insert(this, buddyGroup); + +unlock: + RWLock_writeUnlock(&this->rwlock); // U N L O C K + + return res; +} + + +uint16_t __MirrorBuddyGroupMapper_getBuddyGroupIDUnlocked(MirrorBuddyGroupMapper* this, + uint16_t targetID) +{ + MirrorBuddyGroup* buddyGroup; + BEEGFS_RBTREE_FOR_EACH_ENTRY(buddyGroup, &this->mirrorBuddyGroups, _rb_node) + { + if (buddyGroup->firstTargetID == targetID || buddyGroup->secondTargetID == targetID) + { + return buddyGroup->groupID; + } + } + + return 0; +} diff --git a/client_module/source/common/nodes/MirrorBuddyGroupMapper.h b/client_module/source/common/nodes/MirrorBuddyGroupMapper.h new file mode 100644 index 0000000..555b3cf --- /dev/null +++ b/client_module/source/common/nodes/MirrorBuddyGroupMapper.h @@ -0,0 +1,47 @@ +#ifndef MIRRORBUDDYGROUPMAPPER_H_ +#define MIRRORBUDDYGROUPMAPPER_H_ + +#include +#include +#include +#include +#include +#include +#include + +struct MirrorBuddyGroupMapper; +typedef struct MirrorBuddyGroupMapper MirrorBuddyGroupMapper; + +extern void MirrorBuddyGroupMapper_init(MirrorBuddyGroupMapper* this); +extern MirrorBuddyGroupMapper* MirrorBuddyGroupMapper_construct(void); +extern void MirrorBuddyGroupMapper_uninit(MirrorBuddyGroupMapper* this); +extern void MirrorBuddyGroupMapper_destruct(MirrorBuddyGroupMapper* this); + +extern uint16_t MirrorBuddyGroupMapper_getPrimaryTargetID(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID); +extern uint16_t MirrorBuddyGroupMapper_getSecondaryTargetID(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID); +extern int MirrorBuddyGroupMapper_acquireSequenceNumber(MirrorBuddyGroupMapper* this, + uint16_t mirrorBuddyGroupID, uint64_t* seqNo, uint64_t* finishedSeqNo, bool* isSelective, + struct BuddySequenceNumber** handle, struct MirrorBuddyGroup** group); + +extern void MirrorBuddyGroupMapper_syncGroups(MirrorBuddyGroupMapper* this, + Config* config, struct list_head* groups); +extern FhgfsOpsErr MirrorBuddyGroupMapper_addGroup(MirrorBuddyGroupMapper* this, + Config* config, TargetMapper* targetMapper, uint16_t buddyGroupID, uint16_t primaryTargetID, + uint16_t secondaryTargetID, bool allowUpdate); + +extern uint16_t __MirrorBuddyGroupMapper_getBuddyGroupIDUnlocked(MirrorBuddyGroupMapper* this, + uint16_t targetID); +extern void __MirrorBuddyGroupMapper_syncGroupsUnlocked(MirrorBuddyGroupMapper* this, + Config* config, struct list_head* groups); + +struct MirrorBuddyGroupMapper +{ + // friend class TargetStateStore; // for atomic update of state change plus mirror group switch + + RWLock rwlock; + struct rb_root mirrorBuddyGroups; /* struct MirrorBuddyGroup */ +}; + +#endif /* MIRRORBUDDYGROUPMAPPER_H_ */ diff --git a/client_module/source/common/nodes/Node.c b/client_module/source/common/nodes/Node.c new file mode 100644 index 0000000..ee405b4 --- /dev/null +++ b/client_module/source/common/nodes/Node.c @@ -0,0 +1,214 @@ +#include +#include "Node.h" + +/** + * @param portUDP value 0 if undefined + * @param portTCP value 0 if undefined + * @param nicList an internal copy will be created + * @param localRdmaNicList an internal copy will be created + */ +void Node_init(Node* this, struct App* app, const char* alias, NumNodeID nodeNumID, + unsigned short portUDP, unsigned short portTCP, NicAddressList* nicList, + NicAddressList* localRdmaNicList) +{ + Mutex_init(&this->mutex); + Condition_init(&this->changeCond); + + Time_init(&this->lastHeartbeatT); + this->isActive = false; + kref_init(&this->references); + + this->numID = nodeNumID; + RWLock_init(&this->aliasAndTypeMu); + this->alias = NULL; + this->nodeAliasWithTypeStr = NULL; + this->nodeType = NODETYPE_Invalid; + // We don't know the node type at this stage, it is set later. + Node_setNodeAliasAndType(this, alias, NODETYPE_Invalid); + + this->portUDP = portUDP; + + this->connPool = NodeConnPool_construct(app, this, portTCP, nicList, localRdmaNicList); +} + +/** + * @param nicList an internal copy will be created + * @param localRdmaNicList an internal copy will be created + */ +Node* Node_construct(struct App* app, const char* nodeID, NumNodeID nodeNumID, + unsigned short portUDP, unsigned short portTCP, NicAddressList* nicList, + NicAddressList* localRdmaNicList) +{ + Node* this = (Node*)os_kmalloc(sizeof(*this) ); + + Node_init(this, app, nodeID, nodeNumID, portUDP, portTCP, nicList, localRdmaNicList); + + return this; +} + +void Node_uninit(Node* this) +{ + SAFE_DESTRUCT(this->connPool, NodeConnPool_destruct); + SAFE_KFREE(this->alias); + SAFE_KFREE(this->nodeAliasWithTypeStr); + Mutex_uninit(&this->mutex); +} + +void __Node_destruct(Node* this) +{ + Node_uninit(this); + + kfree(this); +} + +void Node_updateLastHeartbeatT(Node* this) +{ + Mutex_lock(&this->mutex); + + Time_setToNow(&this->lastHeartbeatT); + + Condition_broadcast(&this->changeCond); + + Mutex_unlock(&this->mutex); +} + +/** + * @param portUDP value 0 if undefined + * @param portTCP value 0 if undefined + * @return true if a port changed + */ +bool Node_updateInterfaces(Node* this, unsigned short portUDP, unsigned short portTCP, + NicAddressList* nicList) +{ + bool portChanged = false; + + Mutex_lock(&this->mutex); + + if(portUDP && (portUDP != this->portUDP) ) + { + this->portUDP = portUDP; + portChanged = true; + } + + if(NodeConnPool_updateInterfaces(this->connPool, portTCP, nicList) ) + portChanged = true; + + Mutex_unlock(&this->mutex); + + return portChanged; +} + +/** + * Returns human-readable node type. + * + * @return static string (not alloced => don't free it) + */ +const char* Node_nodeTypeToStr(NodeType nodeType) +{ + switch(nodeType) + { + case NODETYPE_Invalid: + { + return ""; + } break; + + case NODETYPE_Meta: + { + return "beegfs-meta"; + } break; + + case NODETYPE_Storage: + { + return "beegfs-storage"; + } break; + + case NODETYPE_Client: + { + return "beegfs-client"; + } break; + + case NODETYPE_Mgmt: + { + return "beegfs-mgmtd"; + } break; + + default: + { + return ""; + } break; + } +} + +inline void putToNodeString(char *from, NodeString *outStr) { + int result; + result = snprintf(outStr->buf, sizeof outStr->buf, "%s", from); + if (unlikely(result < 0)) { + snprintf(outStr->buf, sizeof(outStr->buf), ""); + } else if (unlikely((size_t) result >= sizeof outStr->buf)) { + memcpy(outStr->buf + sizeof outStr->buf - 4, "...\0", 4); + } +} + +void Node_copyAlias(Node *this, NodeString *outStr) { + RWLock_readLock(&this->aliasAndTypeMu); + putToNodeString(this->alias, outStr); + RWLock_readUnlock(&this->aliasAndTypeMu); +} + +void Node_copyAliasWithTypeStr(Node *this, NodeString *outStr) { + RWLock_readLock(&this->aliasAndTypeMu); + putToNodeString(this->nodeAliasWithTypeStr, outStr); + RWLock_readUnlock(&this->aliasAndTypeMu); +} + +bool Node_setNodeAliasAndType(Node* this, const char *aliasInput, NodeType nodeTypeInput) { + char *alias = NULL; + char *aliasAndTypeStr = NULL; + bool err = false; + + if (!aliasInput && nodeTypeInput == NODETYPE_Invalid) { + return true; // Nothing to do, return early. + } + + if (aliasInput) { + alias = StringTk_strDup(aliasInput); + if (!alias) { + return false; + } + } + + RWLock_writeLock(&this->aliasAndTypeMu); + { + const char *nextAlias = alias ? alias : this->alias; + NodeType nextNodeType = nodeTypeInput != NODETYPE_Invalid ? nodeTypeInput : this->nodeType; + if (nextNodeType == NODETYPE_Client) { + aliasAndTypeStr = kasprintf(GFP_NOFS, "%s [%s:?]", nextAlias, Node_nodeTypeToStr(nextNodeType)); + } + else { + aliasAndTypeStr = kasprintf(GFP_NOFS, "%s [%s:%u]", nextAlias, Node_nodeTypeToStr(nextNodeType), this->numID.value); + } + if (!aliasAndTypeStr) { + err = true; + } + } + if (! err) { + if (alias) { + swap(this->alias, alias); + } + if (nodeTypeInput != NODETYPE_Invalid) { + swap(this->nodeType, nodeTypeInput); + } + if (aliasAndTypeStr) { + swap(this->nodeAliasWithTypeStr, aliasAndTypeStr); + } + } + RWLock_writeUnlock(&this->aliasAndTypeMu); + + if (alias) { + kfree(alias); + } + if (aliasAndTypeStr) { + kfree(aliasAndTypeStr); + } + return ! err; +} diff --git a/client_module/source/common/nodes/Node.h b/client_module/source/common/nodes/Node.h new file mode 100644 index 0000000..bc2520c --- /dev/null +++ b/client_module/source/common/nodes/Node.h @@ -0,0 +1,269 @@ +#ifndef NODE_H_ +#define NODE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "NodeConnPool.h" + +// forward declaration +struct App; + + +enum NodeType; +typedef enum NodeType NodeType; + +struct Node; +typedef struct Node Node; + + +extern void Node_init(Node* this, struct App* app, const char* nodeID, NumNodeID nodeNumID, + unsigned short portUDP, unsigned short portTCP, NicAddressList* nicList, + NicAddressList* localRdmaNicList); +extern Node* Node_construct(struct App* app, const char* nodeID, NumNodeID nodeNumID, + unsigned short portUDP, unsigned short portTCP, NicAddressList* nicList, + NicAddressList* localRdmaNicList); +extern void Node_uninit(Node* this); +extern void __Node_destruct(Node* this); + +extern void Node_updateLastHeartbeatT(Node* this); +extern bool Node_updateInterfaces(Node* this, unsigned short portUDP, unsigned short portTCP, + NicAddressList* nicList); + +/** + * NodeString is a structure to hold a formatted string representation related to a Node. + * + * This structure provides a fixed-size buffer that can store various strings associated with a + * Node, such as the node's alias or a combination of the alias and node type. It is designed to be + * used in functions where thread-safe operations are required to copy or format node-related + * strings into a pre-allocated buffer. + */ +typedef struct NodeString { + // The mgmtd only allows aliases up to 32 characters with characters limited to "a-zA-Z0-9.-_". + // It is also used for the AliasWithTypeStr which may be up to 66 bytes: + // + // `<32 CHARACTER ALIAS> [:4294967295]\0` + // + // Because it is unlikely we ever encounter an node with an ID that is the + // uint32 max and a 32 character alias the buffer size is set to 64 bytes which corresponds to + // the cache line on most architectures. Longer strings will be truncated and end with ellipses. + char buf[64]; +} NodeString; + +/** + * Node_copyAliasWithTypeStr copies the alias, numerical ID (for servers) and the node type in a + * human-readable string. Intended as a convenient way to get a string identifying a node/client for + * log messages. + * + * @param this is which Node to return an identifying string for. + * @param outStr is where the string should be copied to. + * +* IMPORTANT: With the shift from string IDs to aliases in b8.0, aliases may be updated dynamically. + * Callers MUST use this function to access the alias. + */ +extern void Node_copyAliasWithTypeStr(Node *this, NodeString *outStr); +/** + * Node_copyAlias gets a copy of the node's alias (formerly known as a string ID). + * + * @param this is a pointer to the Node structure with the alias to get. + * @param outStr is where the alias should be copied to. + * + * @return string remains valid until Node_putAlias is called; caller must not free the string. + * IMPORTANT: With the shift from string IDs to aliases in b8.0, aliases may be updated dynamically. + * Callers MUST use this function to access the alias. + */ +extern void Node_copyAlias(Node *this, NodeString *outStr); + +// static +extern const char* Node_nodeTypeToStr(NodeType nodeType); + +// getters & setters + +/** + * Node_setNodeAliasAndType() handles thread safe updates to the alias (formerly node string ID), + * node type, and nodeAliasWithTypeStr. It blocks until other writers (unlikely) have finished + * making updates or readers (more likely) have released these fields with the corresponding + * Node_putX functions. + * + * @param this is a pointer to the Node structure with the alias, nodeType, and + * nodeAliasWithTypeStr. + * @param alias is the alias to set. Copies the alias and does not take ownership of it. The caller + * is responsible for freeing the alias when appropriate. + * @param nodeType the nodeType to set. + * + * The alias or nodeType can be respectively NULL or set to NODETYPE_Invalid to only update one + * field and update the nodeAliasWithTypeStr. + */ +extern bool Node_setNodeAliasAndType(Node* this, const char *alias, NodeType nodeType); +static inline NumNodeID Node_getNumID(Node* this); +static inline void Node_setNumID(Node* this, const NumNodeID numID); +static inline void Node_cloneNicList(Node* this, NicAddressList* nicList); +static inline void Node_updateLocalInterfaces(Node* this, NicAddressList* localNicList, + NicListCapabilities* localNicCaps, NicAddressList* localRdmaNicList); +static inline NodeConnPool* Node_getConnPool(Node* this); +static inline void Node_setIsActive(Node* this, bool isActive); +static inline bool Node_getIsActive(Node* this); +static inline unsigned short Node_getPortUDP(Node* this); +static inline unsigned short Node_getPortTCP(Node* this); +static inline NodeType Node_getNodeType(Node* this); +static inline const char* Node_getNodeTypeStr(Node* this); + +enum NodeType + {NODETYPE_Invalid = 0, NODETYPE_Meta = 1, NODETYPE_Storage = 2, NODETYPE_Client = 3, + NODETYPE_Mgmt = 4}; + + +struct Node +{ + NumNodeID numID; // numeric ID, assigned by mgmtd server store (unused for clients) + + // Must be locked before accessing the alias, nodeType, or nodeAliasWithTypeStr. + RWLock aliasAndTypeMu; + char* alias; // alias (formerly string ID): initially generated locally on each node but thread safe and able to be updated as of b8.0 + NodeType nodeType; // set by NodeStore::addOrUpdate() + char* nodeAliasWithTypeStr; // for log messages (initially NULL, initialized when the alias is set/updated) + + NodeConnPool* connPool; + unsigned short portUDP; + + Time lastHeartbeatT; // last heartbeat receive time + + bool isActive; // for internal use by the NodeStore only + + Mutex mutex; + Condition changeCond; // for last heartbeat time only + + struct kref references; + + /* used by node tree */ + struct { + struct rb_node rbTreeElement; + } _nodeTree; +}; + + +static inline Node* Node_get(Node* node) +{ + kref_get(&node->references); + return node; +} + +static inline void __Node_put(struct kref* ref) +{ + __Node_destruct(container_of(ref, Node, references) ); +} + +static inline int Node_put(Node* node) +{ + return kref_put(&node->references, __Node_put); +} + +NumNodeID Node_getNumID(Node* this) +{ + return this->numID; +} + +void Node_setNumID(Node* this, const NumNodeID numID) +{ + this->numID = numID; +} + +/** + * Retrieve NICs for the node. + * + * @param nicList an uninitialized NicAddressList. Caller is responsible for + * memory management. + */ +void Node_cloneNicList(Node* this, NicAddressList* nicList) +{ + NodeConnPool_cloneNicList(this->connPool, nicList); +} + +/** + * @param localNicList copied + * @param localNicCaps copied + * @param localRdmaNicList copied + */ +void Node_updateLocalInterfaces(Node* this, NicAddressList* localNicList, + NicListCapabilities* localNicCaps, NicAddressList* localRdmaNicList) +{ + NodeConnPool_updateLocalInterfaces(this->connPool, localNicList, localNicCaps, localRdmaNicList); +} + +NodeConnPool* Node_getConnPool(Node* this) +{ + return this->connPool; +} + +/** + * Note: For nodes that live inside a NodeStore, this method should only be called by the NodeStore. + */ +void Node_setIsActive(Node* this, bool isActive) +{ + Mutex_lock(&this->mutex); + + this->isActive = isActive; + + Mutex_unlock(&this->mutex); +} + +bool Node_getIsActive(Node* this) +{ + bool isActive; + + Mutex_lock(&this->mutex); + + isActive = this->isActive; + + Mutex_unlock(&this->mutex); + + return isActive; +} + +unsigned short Node_getPortUDP(Node* this) +{ + unsigned short retVal; + + Mutex_lock(&this->mutex); + + retVal = this->portUDP; + + Mutex_unlock(&this->mutex); + + return retVal; +} + +unsigned short Node_getPortTCP(Node* this) +{ + return NodeConnPool_getStreamPort(this->connPool); +} + +NodeType Node_getNodeType(Node* this) +{ + NodeType nodeType; + RWLock_readLock(&this->aliasAndTypeMu); + nodeType = this->nodeType; + RWLock_readUnlock(&this->aliasAndTypeMu); + return nodeType; +} + +/** + * Returns human-readable node type string. + * + * @return static string (not alloced => don't free it) + */ +const char* Node_getNodeTypeStr(Node* this) +{ + const char* nodeType; + RWLock_readLock(&this->aliasAndTypeMu); + nodeType = Node_nodeTypeToStr(this->nodeType); + RWLock_readUnlock(&this->aliasAndTypeMu); + return nodeType; +} + +#endif /*NODE_H_*/ diff --git a/client_module/source/common/nodes/NodeConnPool.c b/client_module/source/common/nodes/NodeConnPool.c new file mode 100644 index 0000000..cd07e5c --- /dev/null +++ b/client_module/source/common/nodes/NodeConnPool.c @@ -0,0 +1,1276 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef BEEGFS_NVFS +#include +#endif +#include "Node.h" +#include "NodeConnPool.h" + +#define NODECONNPOOL_SHUTDOWN_WAITTIMEMS 100 + +/** + * Note: localNicCaps should to be initialized before acquiring a stream. + * + * @param parentNode the node object to which this conn pool belongs. + * @param nicList an internal copy will be created. + */ +void NodeConnPool_init(NodeConnPool* this, struct App* app, struct Node* parentNode, + unsigned short streamPort, NicAddressList* nicList, NicAddressList* localRdmaNicList) +{ + Config* cfg = App_getConfig(app); + + this->app = app; + + Mutex_init(&this->mutex); + Condition_init(&this->changeCond); + + ConnectionList_init(&this->connList, true); + + ListTk_cloneNicAddressList(nicList, &this->nicList, true); + if (localRdmaNicList) + ListTk_cloneNicAddressList(localRdmaNicList, &this->localRdmaNicList, true); + else + NicAddressList_init(&this->localRdmaNicList); + + this->establishedConns = 0; + this->availableConns = 0; + + this->maxConns = Config_getConnMaxInternodeNum(cfg); + this->fallbackExpirationSecs = Config_getConnFallbackExpirationSecs(cfg); + this->maxConcurrentAttempts = Config_getConnMaxConcurrentAttempts(cfg); + + sema_init(&this->connSemaphore, this->maxConcurrentAttempts); + + this->parentNode = parentNode; + this->streamPort = streamPort; + memset(&this->localNicCaps, 0, sizeof(this->localNicCaps) ); + memset(&this->stats, 0, sizeof(this->stats) ); + memset(&this->errState, 0, sizeof(this->errState) ); + + // rdmaNicStatsList should only have elements when parentNode "type" is meta or storage, but + // parentNode "type" isn't yet known. Initialize elements in the first call to + // acquireStreamSocketEx per NodeConnPool instance. + NicAddressStatsList_init(&this->rdmaNicStatsList); + this->rdmaNicCount = -1; + this->logConnErrors = true; + this->enableTCPFallback = Config_getConnTCPFallbackEnabled(cfg); +} + +static bool NodeConnPool_loadRdmaNicStatsList(NodeConnPool* this) +{ + NicAddressListIter nicIter; + NicAddressStatsList statsList; + NicAddressStatsListIter stIter; + NicAddressStatsListIter srcIter; + NicAddress* nic; + NicAddressStats* st; + NicAddressStats* cur; + int rdmaNicCount = 0; + int nodeType; + + // indicate that this function has been called at least once + this->rdmaNicCount = 0; + nodeType = Node_getNodeType(this->parentNode); + if (nodeType == NODETYPE_Meta || nodeType == NODETYPE_Storage) + { + + // Construct temporary list of stat per RDMA NIC. Search for existing + // stat by interface name and use that if it exists. Remove + // matching stats from this->rdmaNicStatsList. + + NicAddressStatsList_init(&statsList); + NicAddressListIter_init(&nicIter, &this->localRdmaNicList); + for ( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + nic = NicAddressListIter_value(&nicIter); + st = NULL; + NicAddressStatsListIter_init(&srcIter, &this->rdmaNicStatsList); + while (!NicAddressStatsListIter_end(&srcIter)) + { + cur = NicAddressStatsListIter_value(&srcIter); + if (!strncmp(cur->nic.name, nic->name, sizeof(nic->name))) + { + NicAddressStatsListIter_remove(&srcIter); + st = cur; + NicAddressStats_setValid(cur, nic); + break; + } + NicAddressStatsListIter_next(&srcIter); + } + + if (st == NULL) + { + st = (NicAddressStats*)os_kmalloc(sizeof(NicAddressStats)); + NicAddressStats_init(st, nic); + } + + NicAddressStatsList_append(&statsList, st); + rdmaNicCount++; + } + + // Add any remaining stats from the this->rdmaNicStatsList. These were not removed + // during the iteration of RDMA NICs, so they are stats for devices that have disappeared. + // They have to be kept around because there may be IBVSocket instances that still + // point to the memory. + + NicAddressStatsListIter_init(&stIter, &this->rdmaNicStatsList); + while (!NicAddressStatsListIter_end(&stIter)) + { + st = NicAddressStatsListIter_value(&stIter); + NicAddressStats_invalidate(st); + NicAddressStatsList_append(&statsList, st); + stIter = NicAddressStatsListIter_remove(&stIter); + } + + NicAddressStatsList_uninit(&this->rdmaNicStatsList); + this->rdmaNicStatsList = statsList; + this->rdmaNicCount = rdmaNicCount; + } + +#ifdef BEEGFS_DEBUG + { + const char* logContext = "NodeConn (load RDMA NIC stats)"; + Logger* log = App_getLogger(this->app); + Logger_logFormatted(log, Log_DEBUG, logContext, "%d RDMA NICs", this->rdmaNicCount); + NicAddressStatsListIter_init(&stIter, &this->rdmaNicStatsList); + for(; !NicAddressStatsListIter_end(&stIter); NicAddressStatsListIter_next(&stIter)) + { + char* addr; + st = NicAddressStatsListIter_value(&stIter); + addr = SocketTk_ipaddrToStr(st->nic.ipAddr); + Logger_logFormatted(log, Log_DEBUG, logContext, "NIC: %s Addr: %s nicValid=%d established=%d available=%d", + st->nic.name, addr, st->nicValid, st->established, st->available); + kfree(addr); + } + } +#endif + + return true; +} + +NodeConnPool* NodeConnPool_construct(struct App* app, struct Node* parentNode, + unsigned short streamPort, NicAddressList* nicList, NicAddressList* localRdmaNicList) +{ + NodeConnPool* this = (NodeConnPool*)os_kmalloc(sizeof(*this) ); + + NodeConnPool_init(this, app, parentNode, streamPort, nicList, localRdmaNicList); + + return this; +} + +void NodeConnPool_uninit(NodeConnPool* this) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "NodeConn (uninit)"; + + // close open connections + if(ConnectionList_length(&this->connList) > 0) + { + Logger_logFormatted(log, Log_DEBUG, logContext, "Closing %lu connections...", + ConnectionList_length(&this->connList) ); + } + + // Note: invalidateStreamSocket changes the original connList + // (so we must invalidate the iterator each time) + while(ConnectionList_length(&this->connList) ) + { + ConnectionListIter connIter; + PooledSocket* socket; + + ConnectionListIter_init(&connIter, &this->connList); + + socket = ConnectionListIter_value(&connIter); + + __NodeConnPool_invalidateSpecificStreamSocket(this, (Socket*)socket); + } + + // delete nicList elems + ListTk_kfreeNicAddressListElems(&this->nicList); + ListTk_kfreeNicAddressListElems(&this->localRdmaNicList); + ListTk_kfreeNicAddressStatsListElems(&this->rdmaNicStatsList); + + // normal clean-up + NicAddressList_uninit(&this->nicList); + NicAddressList_uninit(&this->localRdmaNicList); + NicAddressStatsList_uninit(&this->rdmaNicStatsList); + + Mutex_uninit(&this->mutex); +} + +void NodeConnPool_destruct(NodeConnPool* this) +{ + NodeConnPool_uninit(this); + + kfree(this); +} + +static NicAddressStats* NodeConnPool_rdmaNicPriority(NodeConnPool* this, DevicePriorityContext* ctx) +{ + NicAddressStats* cur; + NicAddressStats* min = NULL; + NicAddressStatsListIter statsIter; + bool skipped; + int numa; + Time now; + +#ifdef BEEGFS_NVFS + int minNvfsPrio = INT_MAX; + int nvfsPrio; + numa = cpu_to_node(current->cpu); +#endif + + if (this->rdmaNicCount < 1) + return NULL; + +#ifdef KERNEL_HAS_CPU_IN_THREAD_INFO + numa = cpu_to_node(task_thread_info(current)->cpu); +#else + numa = cpu_to_node(current->cpu); +#endif + + Time_init(&now); + + NicAddressStatsListIter_init(&statsIter, &this->rdmaNicStatsList); + for (; !NicAddressStatsListIter_end(&statsIter); NicAddressStatsListIter_next(&statsIter)) + { + cur = NicAddressStatsListIter_value(&statsIter); + // elements with nicValid == false are at the end of the list + if (!cur->nicValid) + break; + + skipped = false; + + if (!NicAddressStats_usable(cur, ctx->maxConns)) + skipped = true; + else if (!NicAddressStats_lastErrorExpired(cur, &now, this->fallbackExpirationSecs)) + skipped = true; + else + { +#ifndef BEEGFS_NVFS + if (min == NULL || NicAddressStats_comparePriority(min, cur, numa) > 0) + min = cur; +#else + if (ctx->gpuIndex < 0) + { + if (min == NULL || NicAddressStats_comparePriority(min, cur, numa) > 0) + min = cur; + } + else + { + nvfsPrio = RdmaInfo_nvfsDevicePriority(cur->nic.ibdev, ctx->gpuIndex); +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s:%d cur=%p minNvfsPrio=%d nvfsPrio=%d gpuIndex=%d\n", + __func__, __LINE__, cur, minNvfsPrio, nvfsPrio, ctx->gpuIndex); +#endif + if (min == NULL || minNvfsPrio > nvfsPrio) + { + minNvfsPrio = nvfsPrio; + min = cur; + } + else if (minNvfsPrio == nvfsPrio && NicAddressStats_comparePriority(min, cur, numa) > 0) + min = cur; + } +#endif + } +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s:%d: min=%p cur=%p skipped=%d cur.avail=%d cur.established=%d cur.used=%lld\n", + __func__, __LINE__, min, cur, skipped, cur->available, cur->established, Time_toNS(&cur->used)); +#endif + } + + if (min) + NicAddressStats_updateUsed(min); + +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s:%d: return %p\n", __func__, __LINE__, min); +#endif + + return min; +} + +/** + * Note: Will block if no stream socket is immediately available. + * + * @return connected socket; NULL on error or pending signal + */ +Socket* NodeConnPool_acquireStreamSocket(NodeConnPool* this) +{ + return NodeConnPool_acquireStreamSocketEx(this, true, NULL); +} + +/** + * note: this initially checks for pending signals and returns NULL immediately if signal pending. + * + * @param true to allow waiting if all avaiable conns are currently in use; if waiting is not + * allowed, there is no difference between all conns in use and connection error (so it is expected + * that the caller will sooner or later allow waiting to detect conn errors). + * @return connected socket; NULL on error or pending signal + */ +Socket* NodeConnPool_acquireStreamSocketEx(NodeConnPool* this, bool allowWaiting, + DevicePriorityContext* devPrioCtx) +{ + const char* logContext = "NodeConn (acquire stream)"; + + Logger* log = App_getLogger(this->app); + NetFilter* netFilter = App_getNetFilter(this->app); + NetFilter* tcpOnlyFilter = App_getTcpOnlyFilter(this->app); + + Socket* sock = NULL; + unsigned short port; + NicAddressList nicListCopy; + NicAddressListIter nicIter; + bool isPrimaryInterface = true; // used to set expiration for non-primary interfaces; + // "primary" means: first interface in the list that is supported by client and server + NicAddressStats* srcRdma = NULL; + NodeType nodeType; + bool rdmaConnectInterrupted = false; + bool includeTcp = true; + + if (unlikely(fatal_signal_pending(current))) + return NULL; // no need to try if the process will terminate soon anyway + + Mutex_lock(&this->mutex); // L O C K + if (unlikely(this->rdmaNicCount < 0)) + NodeConnPool_loadRdmaNicStatsList(this); + + if(!this->availableConns && (this->establishedConns == this->maxConns) ) + { // wait for a conn to become available (or disconnected) + + if(!allowWaiting) + { // all conns in use and waiting not allowed => exit + Mutex_unlock(&this->mutex); // U N L O C K + return NULL; + } + + while(!this->availableConns && (this->establishedConns == this->maxConns) ) + { // sleep interruptbile + cond_wait_res_t waitRes = Condition_waitKillable(&this->changeCond, &this->mutex); + if(unlikely(waitRes == COND_WAIT_SIGNAL) ) + { // interrupted by signal + Mutex_unlock(&this->mutex); // U N L O C K + return NULL; + } + } + } + + if (this->rdmaNicCount > 0) + { + DevicePriorityContext* dpc = devPrioCtx; + // metaDevPrioCtx is hacky. When the node is meta and the client is doing + // multi-rail for RDMA this structure is used for the call to + // NodeConnPool_rdmaNicPriority(). This allows use of multiple RDMA + // interfaces to communicate with meta. When the node is storage, devPrioCtx + // will be used if not null. In that devPrioCtx has been initialized with + // information from NVFS detection that happens before the stream is + // acquired. + DevicePriorityContext metaDevPrioCtx = + { + .maxConns = 0, +#ifdef BEEGFS_NVFS + .gpuIndex = -1, +#endif + }; + + if (!dpc && Node_getNodeType(this->parentNode) == NODETYPE_Meta) + dpc = &metaDevPrioCtx; + + if (dpc) + { + dpc->maxConns = this->maxConns / this->rdmaNicCount; + srcRdma = NodeConnPool_rdmaNicPriority(this, dpc); +#ifdef BEEGFS_DEBUG + Logger_logTopFormatted(log, LogTopic_CONN, Log_DEBUG, logContext, + "Preferred IP addr is 0x%x", srcRdma == NULL? 0 : srcRdma->nic.ipAddr.s_addr); +#endif + } + } + + if(likely(this->availableConns) ) + { // established connection available => grab it + PooledSocket* pooledSock; + ConnectionListIter connIter; + + ConnectionListIter_init(&connIter, &this->connList); + for(; !ConnectionListIter_end(&connIter); ConnectionListIter_next(&connIter)) + { + pooledSock = ConnectionListIter_value(&connIter); + if (PooledSocket_isAvailable(pooledSock) && + (srcRdma == NULL || + (Socket_getSockType((Socket*) pooledSock) == NICADDRTYPE_RDMA + && IBVSocket_getNicStats(&((RDMASocket*) pooledSock)->ibvsock) == srcRdma))) + break; + pooledSock = NULL; + } + + if (pooledSock != NULL) + { + PooledSocket_setAvailable(pooledSock, false); + PooledSocket_setHasActivity(pooledSock); + // this invalidates the iterator but this code returns before the next iteration + ConnectionList_moveToTail(&this->connList, pooledSock); + + this->availableConns--; + if (srcRdma) + srcRdma->available--; + + Mutex_unlock(&this->mutex); // U N L O C K + + return (Socket*)pooledSock; + } + } + + // no conn available, but maxConns not reached yet => establish a new conn + + port = this->streamPort; + + nodeType = Node_getNodeType(this->parentNode); + if (!this->enableTCPFallback && (nodeType == NODETYPE_Meta || nodeType == NODETYPE_Storage)) + includeTcp = false; + + ListTk_cloneNicAddressList(&this->nicList, &nicListCopy, includeTcp); + + this->establishedConns++; + if (srcRdma) + srcRdma->established++; + + Mutex_unlock(&this->mutex); // U N L O C K + + if (this->maxConcurrentAttempts > 0) + { + if (down_killable(&this->connSemaphore)) + return NULL; + } + + // walk over all available NICs, create the corresponding socket and try to connect + + NicAddressListIter_init(&nicIter, &nicListCopy); + + for( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + const char* nodeTypeStr = Node_getNodeTypeStr(this->parentNode); + char* endpointStr; + bool connectRes; + + if(!NetFilter_isAllowed(netFilter, nicAddr->ipAddr) ) + continue; + + if( (nicAddr->nicType != NICADDRTYPE_STANDARD) && + NetFilter_isContained(tcpOnlyFilter, nicAddr->ipAddr) ) + continue; + + endpointStr = SocketTk_endpointAddrToStr(nicAddr->ipAddr, port); + + switch(nicAddr->nicType) + { + case NICADDRTYPE_RDMA: + { // RDMA + char from[NICADDRESS_IP_STR_LEN]; + struct in_addr srcAddr; + + if(!this->localNicCaps.supportsRDMA) + goto continue_clean_endpointStr; + + if (srcRdma) + { + srcAddr = srcRdma->nic.ipAddr; + NicAddress_ipToStr(srcAddr, from); + } + else + { + srcAddr.s_addr = 0; + strncpy(from, "any", sizeof(from)); + } + + Logger_logTopFormatted(log, LogTopic_CONN, Log_DEBUG, logContext, + "Establishing new RDMA connection from %s to: %s@%s", from, nodeTypeStr, endpointStr); + sock = (Socket*)RDMASocket_construct(srcAddr, srcRdma); + } break; + case NICADDRTYPE_STANDARD: + { // TCP + Logger_logTopFormatted(log, LogTopic_CONN, Log_DEBUG, logContext, + "Establishing new TCP connection to: %s@%s", nodeTypeStr, endpointStr); + sock = (Socket*)StandardSocket_constructTCP(); + } break; + + default: + { // unknown + if(this->logConnErrors) + Logger_logTopFormatted(log, LogTopic_CONN, Log_WARNING, logContext, + "Skipping unknown connection type to: %s@%s", nodeTypeStr, endpointStr); + goto continue_clean_endpointStr; + } + } // end of switch + + // check whether the socket was successfully initialized + if(!sock) + { // socket initialization failed + if(this->logConnErrors) + Logger_logFormatted(log, Log_WARNING, logContext, "Socket initialization failed: %s@%s", + nodeTypeStr, endpointStr); + + goto continue_clean_endpointStr; + } + + // the actual connection attempt + __NodeConnPool_applySocketOptionsPreConnect(this, sock); + + connectRes = sock->ops->connectByIP(sock, nicAddr->ipAddr, port); + + if(connectRes) + { // connected + struct in_addr peerIP = Socket_getPeerIP(sock); + NicAddrType_t sockType = Socket_getSockType(sock); + + if(__NodeConnPool_shouldPrintConnectedLogMsg(this, peerIP, sockType) ) + { + const char* protocolStr = NIC_nicTypeToString(sockType); + const char* fallbackStr = isPrimaryInterface ? "" : "; fallback route"; + + Logger_logTopFormatted(log, LogTopic_CONN, Log_NOTICE, logContext, + "Connected: %s@%s (protocol: %s%s)", + nodeTypeStr, endpointStr, protocolStr, fallbackStr); + } + + __NodeConnPool_applySocketOptionsConnected(this, (Socket*)sock); + + if(!isPrimaryInterface) // non-primary => set expiration counter + { + PooledSocket* psock = (PooledSocket*) sock; + + PooledSocket_setExpireTimeStart(psock); + /* connection establishment can be interrupted for rdma, not for tcp. if we were + * interrupted during rdma setup and connected via tcp instead, we should close that + * tco connection as soon as possible. */ + psock->closeOnRelease = signal_pending(current); + } + + kfree(endpointStr); + + break; + } + else + { // not connected + if(this->logConnErrors) + { + struct in_addr peerIP = Socket_getPeerIP(sock); + NicAddrType_t sockType = Socket_getSockType(sock); + + if(__NodeConnPool_shouldPrintConnectFailedLogMsg(this, peerIP, sockType) ) + { + Logger_logTopFormatted(log, LogTopic_CONN, Log_NOTICE, logContext, + "Connect failed: %s@%s (protocol: %s)", + nodeTypeStr, endpointStr, NIC_nicTypeToString(sockType) ); + } + } + + // the next interface is definitely non-primary + isPrimaryInterface = false; + if (nicAddr->nicType == NICADDRTYPE_RDMA && fatal_signal_pending(current)) + rdmaConnectInterrupted = true; + + Socket_virtualDestruct(sock); + } + + + continue_clean_endpointStr: + kfree(endpointStr); + + if (fatal_signal_pending(current)) + break; + } + + if (this->maxConcurrentAttempts > 0) + { + up(&this->connSemaphore); + } + + + Mutex_lock(&this->mutex); // L O C K + + if(!NicAddressListIter_end(&nicIter) && !fatal_signal_pending(current)) + { // success => add to list (as unavailable) and update stats + PooledSocket* pooledSock = (PooledSocket*)sock; + + if (srcRdma && Socket_getSockType(sock) != NICADDRTYPE_RDMA) + { + srcRdma->established--; + if (!rdmaConnectInterrupted) + NicAddressStats_updateLastError(srcRdma); + } + + ConnectionList_append(&this->connList, pooledSock); + __NodeConnPool_statsAddNic(this, PooledSocket_getNicType(pooledSock) ); + + __NodeConnPool_setConnSuccess(this, Socket_getPeerIP(sock), Socket_getSockType(sock) ); + } + else + { // absolutely unable to connect + sock = NULL; + this->establishedConns--; + if (srcRdma) + { + srcRdma->established--; + if (!rdmaConnectInterrupted) + NicAddressStats_updateLastError(srcRdma); + } + // connect may be interrupted by signals. return no connection without setting up alternate + // routes or other error handling in that case. + if (!fatal_signal_pending(current)) + { + if(this->logConnErrors && !__NodeConnPool_getWasLastTimeCompleteFail(this) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(this->parentNode, &nodeAndType); + Logger_logTopFormatted(log, LogTopic_CONN, Log_CRITICAL, logContext, + "Connect failed on all available routes: %s", nodeAndType.buf); + } + + __NodeConnPool_setCompleteFail(this); + } + + // we are not using this connection => notify next waiter + Condition_signal(&this->changeCond); + } + + ListTk_kfreeNicAddressListElems(&nicListCopy); + NicAddressList_uninit(&nicListCopy); + + Mutex_unlock(&this->mutex); // U N L O C K + + return sock; +} + +void NodeConnPool_releaseStreamSocket(NodeConnPool* this, Socket* sock) +{ + PooledSocket* pooledSock = (PooledSocket*)sock; + + // test whether this socket has expired + + if(unlikely(PooledSocket_getHasExpired(pooledSock, this->fallbackExpirationSecs) || + pooledSock->closeOnRelease)) + { // this socket just expired => invalidate it + __NodeConnPool_invalidateSpecificStreamSocket(this, sock); + return; + } + + // mark the socket as available + + Mutex_lock(&this->mutex); // L O C K + + if (unlikely(PooledSocket_getPool(pooledSock) != &this->connList)) + { + printk_fhgfs(KERN_ERR, "%s:%d: socket %p not in pool %p\n", + __func__, __LINE__, pooledSock, this); + goto exit; + } + + this->availableConns++; + PooledSocket_setAvailable(pooledSock, true); + ConnectionList_moveToHead(&this->connList, pooledSock); + + if (Socket_getSockType((Socket*) pooledSock) == NICADDRTYPE_RDMA) + { + NicAddressStats* st = IBVSocket_getNicStats(&((RDMASocket*) pooledSock)->ibvsock); + if (st) + st->available++; + } + + Condition_signal(&this->changeCond); + +exit: + Mutex_unlock(&this->mutex); // U N L O C K +} + +void NodeConnPool_invalidateStreamSocket(NodeConnPool* this, Socket* sock) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "NodeConn (invalidate stream)"; + + unsigned numInvalidated; + + /* note: we use invalidateAvailableStreams first here, because we want to keep other threads + from trying to acquire an available socket immediately - and the parameter socket isn't + availabe anyways. (we don't want to acquire a mutex here that would block the whole pool + while we're just waiting for the sock-close response messages) */ + + + numInvalidated = __NodeConnPool_invalidateAvailableStreams(this, false, false); + + Logger_logTopFormatted(log, LogTopic_CONN, 4, logContext, + "Invalidated %u pooled connections. (1 more invalidation pending...)", numInvalidated); + + __NodeConnPool_invalidateSpecificStreamSocket(this, sock); +} + +void __NodeConnPool_invalidateSpecificStreamSocket(NodeConnPool* this, Socket* sock) +{ + const char* logContext = "NodeConn (invalidate stream)"; + Logger* log = App_getLogger(this->app); + + bool sockValid = true; + PooledSocket* pooledSock = (PooledSocket*)sock; + bool shutdownRes; + + + Mutex_lock(&this->mutex); // L O C K + + if (unlikely(PooledSocket_getPool(pooledSock) != &this->connList)) + { // socket not in the list + Logger_logErrFormatted(log, logContext, + "Tried to remove a socket that was not found in the pool: %s", Socket_getPeername(sock) ); + sockValid = false; + } + else + { + this->establishedConns--; + if (Socket_getSockType((Socket*) sock) == NICADDRTYPE_RDMA) + { + NicAddressStats* st = IBVSocket_getNicStats(&((RDMASocket*) sock)->ibvsock); + if (st) + st->established--; + } + + ConnectionList_remove(&this->connList, pooledSock); + __NodeConnPool_statsRemoveNic(this, PooledSocket_getNicType(pooledSock) ); + + Condition_signal(&this->changeCond); + } + + Mutex_unlock(&this->mutex); // U N L O C K + + if(!sockValid) + return; + + + shutdownRes = sock->ops->shutdownAndRecvDisconnect(sock, NODECONNPOOL_SHUTDOWN_WAITTIMEMS); + if(shutdownRes) + { + Logger_logTopFormatted(log, LogTopic_CONN, Log_DEBUG, logContext, "Disconnected: %s@%s", + Node_getNodeTypeStr(this->parentNode), Socket_getPeername(sock) ); + } + else + { + Logger_logTopFormatted(log, LogTopic_CONN, Log_DEBUG, logContext, "Hard disconnect: %s@%s", + Node_getNodeTypeStr(this->parentNode), Socket_getPeername(sock) ); + } + + Socket_virtualDestruct(sock); +} + +/** + * Invalidate (disconnect) connections that are currently not acquired. + * + * @param idleStreamsOnly invalidate only conns that are marked as idle (ie don't have the activity + * flag set). + * @param closeOnRelease if true set every PooledSocket's closeOnRelease flag + * @return number of invalidated streams + */ +unsigned __NodeConnPool_invalidateAvailableStreams(NodeConnPool* this, bool idleStreamsOnly, + bool closeOnRelease) +{ + /* note: we have TWO STAGES here, because we don't want to hold the mutex and block everything + while we're waiting for the conns to be dropped. */ + + unsigned numInvalidated = 0; // retVal + ConnectionListIter connIter; + ConnectionListIter availableIter; + + ConnectionList availableConnsList; + ConnectionList_init(&availableConnsList, false); + + Mutex_lock(&this->mutex); // L O C K + + // STAGE 1: grab all sockets that should be disconnected + + ConnectionListIter_init(&connIter, &this->connList); + + for( ; !ConnectionListIter_end(&connIter); ConnectionListIter_next(&connIter) ) + { + PooledSocket* sock = ConnectionListIter_value(&connIter); + + if(closeOnRelease) + sock->closeOnRelease = true; + + if(!PooledSocket_isAvailable(sock) ) + continue; // this one is currently in use + + if(idleStreamsOnly && PooledSocket_getHasActivity(sock) ) + continue; // idle-only requested and this one was not idle + + // don't worry about reordering the connList here, the socket + // will be removed in stage 2 + PooledSocket_setAvailable(sock, false); + this->availableConns--; + if (Socket_getSockType((Socket*)sock) == NICADDRTYPE_RDMA) + { + NicAddressStats* st = IBVSocket_getNicStats(&((RDMASocket*) sock)->ibvsock); + if (st) + st->available--; + } + ConnectionList_append(&availableConnsList, sock); + + numInvalidated++; + } + + Mutex_unlock(&this->mutex); // U N L O C K + + + // STAGE 2: invalidate all grabbed sockets + + ConnectionListIter_init(&availableIter, &availableConnsList); + + for( ; !ConnectionListIter_end(&availableIter); ConnectionListIter_next(&availableIter) ) + { + PooledSocket* sock = ConnectionListIter_value(&availableIter); + __NodeConnPool_invalidateSpecificStreamSocket(this, (Socket*)sock); + } + + ConnectionList_uninit(&availableConnsList); + + return numInvalidated; +} + +/** + * Disconnect all established (available) connections. + * + * @return number of disconnected streams + */ +unsigned NodeConnPool_disconnectAvailableStreams(NodeConnPool* this) +{ + unsigned numInvalidated; + + numInvalidated = __NodeConnPool_invalidateAvailableStreams(this, false, false); + + return numInvalidated; +} + +/** + * Note: There is no locking around dropping and resetting afterwards, so there should only be one + * thread which calls this function (=> InternodeSyncer). + * + * @return number of disconnected streams + */ +unsigned NodeConnPool_disconnectAndResetIdleStreams(NodeConnPool* this) +{ + unsigned numInvalidated; + + numInvalidated = __NodeConnPool_invalidateAvailableStreams(this, true, false); + + __NodeConnPool_resetStreamsIdleFlag(this); + + return numInvalidated; +} + +/** + * Resets the activity flag of all available connections to mark them as idle. + */ +void __NodeConnPool_resetStreamsIdleFlag(NodeConnPool* this) +{ + ConnectionListIter connIter; + + Mutex_lock(&this->mutex); // L O C K + + ConnectionListIter_init(&connIter, &this->connList); + + for( ; !ConnectionListIter_end(&connIter); ConnectionListIter_next(&connIter) ) + { + PooledSocket* sock = ConnectionListIter_value(&connIter); + + if(!PooledSocket_isAvailable(sock) ) + continue; // this one is currently in use + + PooledSocket_resetHasActivity(sock); + } + + Mutex_unlock(&this->mutex); // U N L O C K +} + +bool __NodeConnPool_applySocketOptionsPreConnect(NodeConnPool* this, Socket* sock) +{ + Config* cfg = App_getConfig(this->app); + + NicAddrType_t sockType = Socket_getSockType(sock); + + if(sockType == NICADDRTYPE_STANDARD) + { + StandardSocket* stdSock = (StandardSocket*)sock; + int bufSize = Config_getConnTCPRcvBufSize(cfg); + if (bufSize > 0) + StandardSocket_setSoRcvBuf(stdSock, bufSize); + } + else + if(sockType == NICADDRTYPE_RDMA) + { + RDMASocket* rdmaSock = (RDMASocket*)sock; + if (Node_getNodeType(this->parentNode) == NODETYPE_Meta) + RDMASocket_setBuffers(rdmaSock, Config_getConnRDMAMetaBufNum(cfg), + Config_getConnRDMAMetaBufSize(cfg), Config_getConnRDMAMetaFragmentSize(cfg), + Config_getConnRDMAKeyTypeNum(cfg)); + else + RDMASocket_setBuffers(rdmaSock, Config_getConnRDMABufNum(cfg), + Config_getConnRDMABufSize(cfg), Config_getConnRDMAFragmentSize(cfg), + Config_getConnRDMAKeyTypeNum(cfg)); + RDMASocket_setTimeouts(rdmaSock, Config_getConnRDMATimeoutConnect(cfg), + Config_getConnRDMATimeoutCompletion(cfg), Config_getConnRDMATimeoutFlowSend(cfg), + Config_getConnRDMATimeoutFlowRecv(cfg), Config_getConnRDMATimeoutPoll(cfg)); + RDMASocket_setTypeOfService(rdmaSock, Config_getConnRDMATypeOfService(cfg)); + RDMASocket_setConnectionFailureStatus(rdmaSock, Config_getRemapConnectionFailureStatus(cfg)); + } + + return true; +} + + +/** + * Apply socket tweaks and send channel control messages. + */ +bool __NodeConnPool_applySocketOptionsConnected(NodeConnPool* this, Socket* sock) +{ + const char* logContext = "NodeConn (apply socket options)"; + Logger* log = App_getLogger(this->app); + Config* cfg = App_getConfig(this->app); + uint64_t authHash = Config_getConnAuthHash(cfg); + + bool allSuccessful = true; + + NicAddrType_t sockType = Socket_getSockType(sock); + + // apply general socket options + if(sockType == NICADDRTYPE_STANDARD) + { + StandardSocket* standardSock = (StandardSocket*)sock; + bool corkRes; + bool noDelayRes; + bool keepAliveRes; + + corkRes = StandardSocket_setTcpCork(standardSock, false); + if(!corkRes) + { + Logger_log(log, Log_NOTICE, logContext, "Failed to disable TcpCork"); + allSuccessful = false; + } + + noDelayRes = StandardSocket_setTcpNoDelay(standardSock, true); + if(!noDelayRes) + { + Logger_log(log, Log_NOTICE, logContext, "Failed to enable TcpNoDelay"); + allSuccessful = false; + } + + keepAliveRes = StandardSocket_setSoKeepAlive(standardSock, true); + if(!keepAliveRes) + { + Logger_log(log, Log_NOTICE, logContext, "Failed to enable SoKeepAlive"); + allSuccessful = false; + } + } + + // send control messages + + if(authHash) + { // authenticate channel + char* sendBuf; + size_t sendBufLen; + AuthenticateChannelMsg authMsg; + size_t sendRes; + + AuthenticateChannelMsg_initFromValue(&authMsg, authHash); + sendBufLen = NetMessage_getMsgLength( (NetMessage*)&authMsg); + sendBuf = (char*)os_kmalloc(sendBufLen); + + NetMessage_serialize( (NetMessage*)&authMsg, sendBuf, sendBufLen); + sendRes = Socket_send_kernel(sock, sendBuf, sendBufLen, 0); + if(sendRes <= 0) + { + Logger_log(log, Log_WARNING, logContext, "Failed to send authentication"); + allSuccessful = false; + } + + kfree(sendBuf); + } + + { // make channel indirect + char* sendBuf; + size_t sendBufLen; + SetChannelDirectMsg directMsg; + size_t sendRes; + + SetChannelDirectMsg_initFromValue(&directMsg, 0); + sendBufLen = NetMessage_getMsgLength( (NetMessage*)&directMsg); + sendBuf = (char*)os_kmalloc(sendBufLen); + + NetMessage_serialize( (NetMessage*)&directMsg, sendBuf, sendBufLen); + sendRes = Socket_send_kernel(sock, sendBuf, sendBufLen, 0); + if(sendRes <= 0) + { + Logger_log(log, Log_WARNING, logContext, "Failed to set channel to indirect mode"); + allSuccessful = false; + } + + kfree(sendBuf); + } + + { + char* sendBuf; + size_t sendBufLen; + PeerInfoMsg peerInfo; + size_t sendRes = -1; + + PeerInfoMsg_init(&peerInfo, NODETYPE_Client, this->app->localNode->numID); + sendBufLen = NetMessage_getMsgLength(&peerInfo.netMessage); + sendBuf = kmalloc(sendBufLen, GFP_KERNEL); + + if (sendBuf) + { + NetMessage_serialize(&peerInfo.netMessage, sendBuf, sendBufLen); + sendRes = Socket_send_kernel(sock, sendBuf, sendBufLen, 0); + } + + if(sendRes <= 0) + { + Logger_log(log, Log_WARNING, logContext, "Failed to transmit local node info"); + allSuccessful = false; + } + + kfree(sendBuf); + } + + return allSuccessful; +} + +void NodeConnPool_getStats(NodeConnPool* this, NodeConnPoolStats* outStats) +{ + Mutex_lock(&this->mutex); // L O C K + + *outStats = this->stats; + + Mutex_unlock(&this->mutex); // U N L O C K +} + +/** + * Gets the first socket peer name for a connection to the specified nicType. + * Only works on currently available connections. + * + * @outBuf buffer for the peer name string (contains "n/a" if no such connection exists or is + * currently not available). + * @param outIsNonPrimary set to true if conn in outBuf has an expiration counter, false otherwise + * @return number of chars written to buf + */ +unsigned NodeConnPool_getFirstPeerName(NodeConnPool* this, NicAddrType_t nicType, ssize_t outBufLen, + char* outBuf, bool* outIsNonPrimary) +{ + unsigned numWritten = 0; // return value + ConnectionListIter connIter; + bool foundMatch = false; + + Mutex_lock(&this->mutex); // L O C K + + ConnectionListIter_init(&connIter, &this->connList); + + for( ; !ConnectionListIter_end(&connIter); ConnectionListIter_next(&connIter) ) + { + PooledSocket* pooledSock = ConnectionListIter_value(&connIter); + Socket* sock = (Socket*)pooledSock; + + if(PooledSocket_isAvailable(pooledSock) && + (Socket_getSockType(sock) == nicType) ) + { // found a match => print to buf and stop + numWritten += scnprintf(outBuf, outBufLen, "%s", Socket_getPeername(sock) ); + + *outIsNonPrimary = PooledSocket_getHasExpirationTimer(pooledSock); + + foundMatch = true; + break; + } + } + + if(!foundMatch) + { // print "n/a" + numWritten += scnprintf(outBuf, outBufLen, "busy"); + + *outIsNonPrimary = false; + } + + Mutex_unlock(&this->mutex); // U N L O C K + + return numWritten; +} + + +/** + * Increase stats counter for number of established conns (by NIC type). + */ +void __NodeConnPool_statsAddNic(NodeConnPool* this, NicAddrType_t nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: + { + (this->stats.numEstablishedRDMA)++; + } break; + + default: + { + (this->stats.numEstablishedStd)++; + } break; + } +} + +/** + * Decrease stats counter for number of established conns (by NIC type). + */ +void __NodeConnPool_statsRemoveNic(NodeConnPool* this, NicAddrType_t nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: + { + (this->stats.numEstablishedRDMA)--; + } break; + + default: + { + (this->stats.numEstablishedStd)--; + } break; + } +} + +/** + * @param streamPort value 0 will be ignored + * @param nicList will be copied + * @return true if port has changed + */ +bool NodeConnPool_updateInterfaces(NodeConnPool* this, unsigned short streamPort, + NicAddressList* nicList) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "NodeConn (update stream port)"; + bool hasChanged = false; // retVal + NodeString alias; + + Mutex_lock(&this->mutex); // L O C K + Node_copyAlias(this->parentNode, &alias); + + if(streamPort && (streamPort != this->streamPort) ) + { + this->streamPort = streamPort; + hasChanged = true; + Logger_logFormatted(log, Log_NOTICE, logContext, + "Node %s port has changed", alias.buf); + } + + if (!NicAddressList_equals(&this->nicList, nicList)) + { // update nicList (if allocation of new list fails, we just keep the old list) + NicAddressList newNicList; + + hasChanged = true; + Logger_logFormatted(log, Log_NOTICE, logContext, + "Node %s interfaces have changed", alias.buf); + + ListTk_cloneNicAddressList(nicList, &newNicList, true); + ListTk_kfreeNicAddressListElems(&this->nicList); + NicAddressList_uninit(&this->nicList); + + this->nicList = newNicList; + } + + Mutex_unlock(&this->mutex); // U N L O C K + + if (unlikely(hasChanged)) + { + // closeOnRelease is true, all of these sockets need to be invalidated ASAP + unsigned numInvalidated = __NodeConnPool_invalidateAvailableStreams(this, false, true); + if(numInvalidated) + { + Logger_logFormatted(log, Log_DEBUG, logContext, + "Invalidated %u pooled connections (due to port/interface change)", numInvalidated); + } + } + + return hasChanged; +} + +void __NodeConnPool_setCompleteFail(NodeConnPool* this) +{ + this->errState.lastSuccessPeerIP.s_addr = 0; + this->errState.wasLastTimeCompleteFail = true; +} + +void __NodeConnPool_setConnSuccess(NodeConnPool* this, struct in_addr lastSuccessPeerIP, + NicAddrType_t lastSuccessNicType) +{ + this->errState.lastSuccessPeerIP = lastSuccessPeerIP; + this->errState.lastSuccessNicType = lastSuccessNicType; + + this->errState.wasLastTimeCompleteFail = false; +} + +bool __NodeConnPool_equalsLastSuccess(NodeConnPool* this, struct in_addr lastSuccessPeerIP, + NicAddrType_t lastSuccessNicType) +{ + return (this->errState.lastSuccessPeerIP.s_addr == lastSuccessPeerIP.s_addr) && + (this->errState.lastSuccessNicType == lastSuccessNicType); + +} + +bool __NodeConnPool_isLastSuccessInitialized(NodeConnPool* this) +{ + return (this->errState.lastSuccessPeerIP.s_addr != 0); +} + +bool __NodeConnPool_shouldPrintConnectedLogMsg(NodeConnPool* this, struct in_addr currentPeerIP, + NicAddrType_t currentNicType) +{ + /* log only if we didn's succeed at all last time, or if we succeeded last time and it was + with a different IP/NIC pair */ + + return this->errState.wasLastTimeCompleteFail || + !__NodeConnPool_isLastSuccessInitialized(this) || + !__NodeConnPool_equalsLastSuccess(this, currentPeerIP, currentNicType); +} + +bool __NodeConnPool_shouldPrintConnectFailedLogMsg(NodeConnPool* this, struct in_addr currentPeerIP, + NicAddrType_t currentNicType) +{ + /* log only if this is the first connection attempt or if we succeeded last time this this + IP/NIC pair */ + + return (!this->errState.wasLastTimeCompleteFail && + (!__NodeConnPool_isLastSuccessInitialized(this) || + __NodeConnPool_equalsLastSuccess(this, currentPeerIP, currentNicType) ) ); +} + +void NodeConnPool_updateLocalInterfaces(NodeConnPool* this, NicAddressList* localNicList, + NicListCapabilities* localNicCaps, NicAddressList* localRdmaNicList) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "NodeConn (update local NIC list)"; + unsigned numInvalidated; + + Mutex_lock(&this->mutex); // L O C K + this->localNicCaps = *localNicCaps; + if (localRdmaNicList) + { + ListTk_kfreeNicAddressListElems(&this->localRdmaNicList); + NicAddressList_uninit(&this->localRdmaNicList); + ListTk_cloneNicAddressList(localRdmaNicList, &this->localRdmaNicList, true); + NodeConnPool_loadRdmaNicStatsList(this); + } + Mutex_unlock(&this->mutex); // U N L O C K + + numInvalidated = __NodeConnPool_invalidateAvailableStreams(this, false, true); + if(numInvalidated) + { + Logger_logFormatted(log, Log_DEBUG, logContext, + "Invalidated %u pooled connections (due to local interface change)", numInvalidated); + } +} diff --git a/client_module/source/common/nodes/NodeConnPool.h b/client_module/source/common/nodes/NodeConnPool.h new file mode 100644 index 0000000..1994b37 --- /dev/null +++ b/client_module/source/common/nodes/NodeConnPool.h @@ -0,0 +1,226 @@ +#ifndef NODECONNPOOL_H_ +#define NODECONNPOOL_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ConnectionList.h" +#include "ConnectionListIter.h" +#include "DevicePriorityContext.h" + + +// forward declaration +struct App; +struct Node; + +struct NodeConnPoolStats; +typedef struct NodeConnPoolStats NodeConnPoolStats; +struct NodeConnPoolErrorState; +typedef struct NodeConnPoolErrorState NodeConnPoolErrorState; + +struct NodeConnPool; +typedef struct NodeConnPool NodeConnPool; + + +extern void NodeConnPool_init(NodeConnPool* this, struct App* app, struct Node* parentNode, + unsigned short streamPort, NicAddressList* nicList, NicAddressList* localRdmaNicList); +extern NodeConnPool* NodeConnPool_construct(struct App* app, struct Node* parentNode, + unsigned short streamPort, NicAddressList* nicList, NicAddressList* localRdmaNicList); +extern void NodeConnPool_uninit(NodeConnPool* this); +extern void NodeConnPool_destruct(NodeConnPool* this); + +extern Socket* NodeConnPool_acquireStreamSocket(NodeConnPool* this); +extern Socket* NodeConnPool_acquireStreamSocketEx(NodeConnPool* this, bool allowWaiting, + DevicePriorityContext* devPrioCtx); +extern void NodeConnPool_releaseStreamSocket(NodeConnPool* this, Socket* sock); +extern void NodeConnPool_invalidateStreamSocket(NodeConnPool* this, Socket* sock); +extern unsigned NodeConnPool_disconnectAvailableStreams(NodeConnPool* this); +extern unsigned NodeConnPool_disconnectAndResetIdleStreams(NodeConnPool* this); +extern bool NodeConnPool_updateInterfaces(NodeConnPool* this, unsigned short streamPort, + NicAddressList* nicList); + +extern void __NodeConnPool_invalidateSpecificStreamSocket(NodeConnPool* this, Socket* sock); +extern unsigned __NodeConnPool_invalidateAvailableStreams(NodeConnPool* this, + bool idleStreamsOnly, bool closeOnRelease); +extern void __NodeConnPool_resetStreamsIdleFlag(NodeConnPool* this); +extern bool __NodeConnPool_applySocketOptionsPreConnect(NodeConnPool* this, Socket* sock); +extern bool __NodeConnPool_applySocketOptionsConnected(NodeConnPool* this, Socket* sock); + +extern void __NodeConnPool_statsAddNic(NodeConnPool* this, NicAddrType_t nicType); +extern void __NodeConnPool_statsRemoveNic(NodeConnPool* this, NicAddrType_t nicType); + +extern void __NodeConnPool_setCompleteFail(NodeConnPool* this); +extern void __NodeConnPool_setConnSuccess(NodeConnPool* this, struct in_addr lastSuccessPeerIP, + NicAddrType_t lastSuccessNicType); +extern bool __NodeConnPool_equalsLastSuccess(NodeConnPool* this, struct in_addr lastSuccessPeerIP, + NicAddrType_t lastSuccessNicType); +extern bool __NodeConnPool_isLastSuccessInitialized(NodeConnPool* this); +extern bool __NodeConnPool_shouldPrintConnectedLogMsg(NodeConnPool* this, + struct in_addr currentPeerIP, NicAddrType_t currentNicType); +extern bool __NodeConnPool_shouldPrintConnectFailedLogMsg(NodeConnPool* this, + struct in_addr currentPeerIP, NicAddrType_t currentNicType); + + +extern void NodeConnPool_getStats(NodeConnPool* this, NodeConnPoolStats* outStats); +extern unsigned NodeConnPool_getFirstPeerName(NodeConnPool* this, NicAddrType_t nicType, + ssize_t outBufLen, char* outBuf, bool* outIsNonPrimary); + +/** + * @param localNicList copied + * @param localNicCaps copied + * @param localRdmaNicList copied + */ +extern void NodeConnPool_updateLocalInterfaces(NodeConnPool* this, NicAddressList* localNicList, + NicListCapabilities* localNicCaps, NicAddressList* localRdmaNicList); + +// getters & setters +/** + * Called to lock the internal mute when accessing resources that may be + * modified by other threads. Case in point: NodeConnPool_getNicListLocked. + */ +static inline void NodeConnPool_lock(NodeConnPool* this); +/** + * Release the lock acquired by NodeConnPool_lock. + */ +static inline void NodeConnPool_unlock(NodeConnPool* this); +static inline void NodeConnPool_cloneNicList(NodeConnPool* this, NicAddressList* nicList); +static inline NicAddressList* NodeConnPool_getNicListLocked(NodeConnPool* this); +static inline void NodeConnPool_setLocalNicCaps(NodeConnPool* this, + NicListCapabilities* localNicCaps); +static inline unsigned short NodeConnPool_getStreamPort(NodeConnPool* this); +static inline void NodeConnPool_setLogConnErrors(NodeConnPool* this, bool logConnErrors); +static inline bool __NodeConnPool_getWasLastTimeCompleteFail(NodeConnPool* this); + + +/** + * Holds statistics about currently established connections. + */ +struct NodeConnPoolStats +{ + unsigned numEstablishedStd; + unsigned numEstablishedRDMA; +}; + +/** + * Holds state of failed connects to avoid log spamming with conn errors. + */ +struct NodeConnPoolErrorState +{ + struct in_addr lastSuccessPeerIP; // the last IP that we connected to successfully + NicAddrType_t lastSuccessNicType; // the last nic type that we connected to successfully + bool wasLastTimeCompleteFail; // true if last attempt failed on all routes +}; + + +/** + * This class represents a pool of stream connections to a certain node. + */ +struct NodeConnPool +{ + struct App* app; + + NicAddressList nicList; + + NicAddressList localRdmaNicList; + NicAddressStatsList rdmaNicStatsList; + + ConnectionList connList; + + struct Node* parentNode; // backlink to the node object which to which this conn pool belongs + unsigned short streamPort; + NicListCapabilities localNicCaps; + + unsigned availableConns; // available established conns + unsigned establishedConns; // not equal to connList.size!! + unsigned maxConns; + unsigned fallbackExpirationSecs; // expiration time for conns to fallback interfaces + unsigned maxConcurrentAttempts; + int rdmaNicCount; + + NodeConnPoolStats stats; + NodeConnPoolErrorState errState; + + bool logConnErrors; // false to disable logging during acquireStream() + bool enableTCPFallback; + + Mutex mutex; + Condition changeCond; + struct semaphore connSemaphore; +}; + +void NodeConnPool_lock(NodeConnPool* this) +{ + Mutex_lock(&this->mutex); // L O C K +} + +void NodeConnPool_unlock(NodeConnPool* this) +{ + Mutex_unlock(&this->mutex); // U N L O C K +} + +/** + * Mutex lock must be held while using the returned pointer. + */ +NicAddressList* NodeConnPool_getNicListLocked(NodeConnPool* this) +{ + return &this->nicList; +} + +/** + * Retrieve NICs for the connection pool. + * + * @param nicList an uninitialized NicAddressList. Caller is responsible for + * memory management. + */ +void NodeConnPool_cloneNicList(NodeConnPool* this, NicAddressList* nicList) +{ + Mutex_lock(&this->mutex); // L O C K + ListTk_cloneNicAddressList(&this->nicList, nicList, true); + Mutex_unlock(&this->mutex); // U N L O C K +} + +/** + * @param localNicCaps will be copied + */ +void NodeConnPool_setLocalNicCaps(NodeConnPool* this, NicListCapabilities* localNicCaps) +{ + Mutex_lock(&this->mutex); // L O C K + this->localNicCaps = *localNicCaps; + Mutex_unlock(&this->mutex); // U N L O C K +} + +unsigned short NodeConnPool_getStreamPort(NodeConnPool* this) +{ + unsigned short retVal; + + Mutex_lock(&this->mutex); // L O C K + + retVal = this->streamPort; + + Mutex_unlock(&this->mutex); // U N L O C K + + return retVal; +} + +void NodeConnPool_setLogConnErrors(NodeConnPool* this, bool logConnErrors) +{ + this->logConnErrors = logConnErrors; +} + +/** + * @return true if connection on all available routes failed last time we tried. + */ +bool __NodeConnPool_getWasLastTimeCompleteFail(NodeConnPool* this) +{ + return this->errState.wasLastTimeCompleteFail; +} + + +#endif /*NODECONNPOOL_H_*/ diff --git a/client_module/source/common/nodes/NodeList.h b/client_module/source/common/nodes/NodeList.h new file mode 100644 index 0000000..8044421 --- /dev/null +++ b/client_module/source/common/nodes/NodeList.h @@ -0,0 +1,49 @@ +#ifndef NODELIST_H_ +#define NODELIST_H_ + +#include +#include + +struct Node; + +struct NodeList; +typedef struct NodeList NodeList; + +static inline void NodeList_init(NodeList* this); +static inline void NodeList_uninit(NodeList* this); +static inline void NodeList_append(NodeList* this, struct Node* node); +static inline void NodeList_removeHead(NodeList* this); +static inline size_t NodeList_length(NodeList* this); + +struct NodeList +{ + struct PointerList pointerList; +}; + + +void NodeList_init(NodeList* this) +{ + PointerList_init( (PointerList*)this); +} + +void NodeList_uninit(NodeList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void NodeList_append(NodeList* this, struct Node* node) +{ + PointerList_append( (PointerList*)this, node); +} + +void NodeList_removeHead(NodeList* this) +{ + PointerList_removeHead( (PointerList*)this); +} + +static inline size_t NodeList_length(NodeList* this) +{ + return PointerList_length( (PointerList*)this); +} + +#endif /* NODELIST_H_ */ diff --git a/client_module/source/common/nodes/NodeListIter.h b/client_module/source/common/nodes/NodeListIter.h new file mode 100644 index 0000000..c773843 --- /dev/null +++ b/client_module/source/common/nodes/NodeListIter.h @@ -0,0 +1,48 @@ +#ifndef NODELISTITER_H_ +#define NODELISTITER_H_ + +#include +#include +#include "NodeList.h" +#include + +struct Node; + +struct NodeListIter; +typedef struct NodeListIter NodeListIter; + +static inline void NodeListIter_init(NodeListIter* this, NodeList* list); +static inline void NodeListIter_next(NodeListIter* this); +static inline struct Node* NodeListIter_value(NodeListIter* this); +static inline bool NodeListIter_end(NodeListIter* this); + + +struct NodeListIter +{ + PointerListIter pointerListIter; +}; + + +void NodeListIter_init(NodeListIter* this, NodeList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void NodeListIter_next(NodeListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +struct Node* NodeListIter_value(NodeListIter* this) +{ + return (struct Node*)PointerListIter_value( (PointerListIter*)this); +} + +bool NodeListIter_end(NodeListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + + +#endif /* NODELISTITER_H_ */ diff --git a/client_module/source/common/nodes/NodeTree.c b/client_module/source/common/nodes/NodeTree.c new file mode 100644 index 0000000..0411de9 --- /dev/null +++ b/client_module/source/common/nodes/NodeTree.c @@ -0,0 +1,94 @@ +#include "NodeTree.h" + +#include + +void NodeTree_init(NodeTree* this) +{ + this->nodes = RB_ROOT; + this->size = 0; +} + +void NodeTree_uninit(NodeTree* this) +{ + Node* node; + Node* tmp; + + // delete activeNodes. frees all memory, but leaves the rbtree in the shape it was - after this, + // any operation on the rbtree is undefined. + rbtree_postorder_for_each_entry_safe(node, tmp, &this->nodes, _nodeTree.rbTreeElement) + Node_put(node); +} + +Node* NodeTree_find(NodeTree* this, NumNodeID nodeNumID) +{ + struct rb_node* node = this->nodes.rb_node; + + while(node) + { + Node* elem = rb_entry(node, Node, _nodeTree.rbTreeElement); + NumNodeID elemID = Node_getNumID(elem); + + if(nodeNumID.value < elemID.value) + node = node->rb_left; + else + if(nodeNumID.value > elemID.value) + node = node->rb_right; + else + return elem; + } + + return NULL; +} + +Node* NodeTree_getNext(NodeTree* this, Node* node) +{ + struct rb_node* rbNode = rb_next(&node->_nodeTree.rbTreeElement); + + if(!rbNode) + return NULL; + + return rb_entry(rbNode, Node, _nodeTree.rbTreeElement); +} + +bool NodeTree_insert(NodeTree* this, NumNodeID nodeNumID, Node* node) +{ + struct rb_node** new = &this->nodes.rb_node; + struct rb_node* parent = NULL; + + while(*new) + { + Node* elem = rb_entry(*new, Node, _nodeTree.rbTreeElement); + NumNodeID elemID = Node_getNumID(elem); + + parent = *new; + if(nodeNumID.value < elemID.value) + new = &(*new)->rb_left; + else + if(nodeNumID.value > elemID.value) + new = &(*new)->rb_right; + else + return false; + } + + rb_link_node(&node->_nodeTree.rbTreeElement, parent, new); + rb_insert_color(&node->_nodeTree.rbTreeElement, &this->nodes); + this->size++; + + return true; +} + +bool NodeTree_erase(NodeTree* this, NumNodeID nodeNumID) +{ + Node* node; + + node = NodeTree_find(this, nodeNumID); + if(node) + { + rb_erase(&node->_nodeTree.rbTreeElement, &this->nodes); + this->size--; + Node_put(node); + return true; + } + + return false; +} diff --git a/client_module/source/common/nodes/NodeTree.h b/client_module/source/common/nodes/NodeTree.h new file mode 100644 index 0000000..be1c379 --- /dev/null +++ b/client_module/source/common/nodes/NodeTree.h @@ -0,0 +1,64 @@ +#ifndef NodeTree_h_cfEUS0RiTiwS20ayW10wHn +#define NodeTree_h_cfEUS0RiTiwS20ayW10wHn + +#include +#include + + +struct NodeTree; +typedef struct NodeTree NodeTree; + +struct NodeTreeIter; +typedef struct NodeTreeIter NodeTreeIter; + + + +extern void NodeTree_init(NodeTree* this); +extern void NodeTree_uninit(NodeTree* this); + +extern Node* NodeTree_find(NodeTree* this, NumNodeID nodeNumID); +extern Node* NodeTree_getNext(NodeTree* this, Node* node); + +extern bool NodeTree_insert(NodeTree* this, NumNodeID nodeNumID, Node* node); +extern bool NodeTree_erase(NodeTree* this, NumNodeID nodeNumID); + + +static inline void NodeTreeIter_init(NodeTreeIter* this, NodeTree* tree); +static inline void NodeTreeIter_next(NodeTreeIter* this); +static inline Node* NodeTreeIter_value(NodeTreeIter* this); +static inline bool NodeTreeIter_end(NodeTreeIter* this); + +struct NodeTree +{ + struct rb_root nodes; + unsigned size; +}; + +struct NodeTreeIter +{ + struct rb_node* value; +}; + + + +void NodeTreeIter_init(NodeTreeIter* this, NodeTree* tree) +{ + this->value = rb_first(&tree->nodes); +} + +void NodeTreeIter_next(NodeTreeIter* this) +{ + this->value = rb_next(this->value); +} + +Node* NodeTreeIter_value(NodeTreeIter* this) +{ + return rb_entry(this->value, Node, _nodeTree.rbTreeElement); +} + +bool NodeTreeIter_end(NodeTreeIter* this) +{ + return this->value == NULL; +} + +#endif diff --git a/client_module/source/common/nodes/NumNodeID.c b/client_module/source/common/nodes/NumNodeID.c new file mode 100644 index 0000000..66207ca --- /dev/null +++ b/client_module/source/common/nodes/NumNodeID.c @@ -0,0 +1,15 @@ +#include +#include "NumNodeID.h" + +void NumNodeID_serialize(SerializeCtx* ctx, const NumNodeID* this) +{ + Serialization_serializeUInt(ctx, this->value); +} + +bool NumNodeID_deserialize(DeserializeCtx* ctx, NumNodeID* outThis) +{ + if(!Serialization_deserializeUInt(ctx, &(outThis->value) ) ) + return false; + + return true; +} diff --git a/client_module/source/common/nodes/NumNodeID.h b/client_module/source/common/nodes/NumNodeID.h new file mode 100644 index 0000000..2364abd --- /dev/null +++ b/client_module/source/common/nodes/NumNodeID.h @@ -0,0 +1,41 @@ +#ifndef NUMNODEID_H +#define NUMNODEID_H + +#include +#include +#include + +// Note: this must always be in sync with server's NumNodeId! + +struct NumNodeID; +typedef struct NumNodeID NumNodeID; + +struct NumNodeID +{ + uint32_t value; +}; + +static inline void NumNodeID_set(NumNodeID* this, uint32_t value) +{ + this->value = value; +} + +static inline bool NumNodeID_compare(const NumNodeID* this, const NumNodeID* other) +{ + return (this->value == other->value); +} + +static inline bool NumNodeID_isZero(const NumNodeID* this) +{ + return (this->value == 0); +} + +static inline char* NumNodeID_str(const NumNodeID* this) +{ + return StringTk_uintToStr(this->value); +} + +extern void NumNodeID_serialize(SerializeCtx* ctx, const NumNodeID* this); +extern bool NumNodeID_deserialize(DeserializeCtx* ctx, NumNodeID* outThis); + +#endif /* NUMNODEID_H */ diff --git a/client_module/source/common/nodes/TargetMapper.c b/client_module/source/common/nodes/TargetMapper.c new file mode 100644 index 0000000..309f437 --- /dev/null +++ b/client_module/source/common/nodes/TargetMapper.c @@ -0,0 +1,122 @@ +#include "TargetMapper.h" + +BEEGFS_RBTREE_FUNCTIONS(static, _TargetMapper, struct TargetMapper, _entries, + uint16_t, + struct TargetMapping, targetID, _node, + BEEGFS_RB_KEYCMP_LT_INTEGRAL) + + +void TargetMapper_init(TargetMapper* this) +{ + RWLock_init(&this->rwlock); + this->_entries = RB_ROOT; +} + +TargetMapper* TargetMapper_construct(void) +{ + TargetMapper* this = (TargetMapper*)os_kmalloc(sizeof(*this) ); + + TargetMapper_init(this); + + return this; +} + +void TargetMapper_uninit(TargetMapper* this) +{ + BEEGFS_KFREE_RBTREE(&this->_entries, struct TargetMapping, _node); +} + +void TargetMapper_destruct(TargetMapper* this) +{ + TargetMapper_uninit(this); + + kfree(this); +} + +/** + * Note: re-maps targetID if it was mapped before. + * + * @return true if the targetID was not mapped before + */ +bool TargetMapper_mapTarget(TargetMapper* this, uint16_t targetID, + NumNodeID nodeID) +{ + struct TargetMapping* entry = kmalloc(sizeof(*entry), GFP_NOFS | __GFP_NOFAIL); + struct TargetMapping* replaced; + + entry->targetID = targetID; + entry->nodeID = nodeID; + + RWLock_writeLock(&this->rwlock); + + replaced = _TargetMapper_insertOrReplace(this, entry); + + RWLock_writeUnlock(&this->rwlock); + + kfree(replaced); + return replaced == NULL; +} + +/** + * Applies the mapping from two separate lists (keys and values). + * + * @mappings list of TargetMapping objects. the list is consumed. + * + * Note: Does not add/remove targets from attached capacity pools. + */ +void TargetMapper_syncTargets(TargetMapper* this, struct list_head* mappings) +{ + struct TargetMapping* elem; + struct TargetMapping* n; + + RWLock_writeLock(&this->rwlock); // L O C K + + BEEGFS_KFREE_RBTREE(&this->_entries, struct TargetMapping, _node); + this->_entries = RB_ROOT; + + list_for_each_entry_safe(elem, n, mappings, _list) + { + list_del(&elem->_list); + kfree(_TargetMapper_insertOrReplace(this, elem)); + } + + RWLock_writeUnlock(&this->rwlock); // U N L O C K +} + +void TargetMapper_getTargetIDs(TargetMapper* this, UInt16List* outTargetIDs) +{ + struct rb_node* pos; + + RWLock_readLock(&this->rwlock); // L O C K + + for (pos = rb_first(&this->_entries); pos; pos = rb_next(pos)) + { + UInt16List_append(outTargetIDs, + rb_entry(pos, struct TargetMapping, _node)->targetID); + } + + RWLock_readUnlock(&this->rwlock); // U N L O C K +} + +/** + * Get nodeID for a certain targetID + * + * @return 0 if targetID was not mapped + */ +NumNodeID TargetMapper_getNodeID(TargetMapper* this, uint16_t targetID) +{ + const struct TargetMapping* elem; + NumNodeID nodeID; + + RWLock_readLock(&this->rwlock); // L O C K + + elem = _TargetMapper_find(this, targetID); + if (elem) + nodeID = elem->nodeID; + else + nodeID.value = 0; + + RWLock_readUnlock(&this->rwlock); // U N L O C K + + return nodeID; +} diff --git a/client_module/source/common/nodes/TargetMapper.h b/client_module/source/common/nodes/TargetMapper.h new file mode 100644 index 0000000..12c694f --- /dev/null +++ b/client_module/source/common/nodes/TargetMapper.h @@ -0,0 +1,40 @@ +#ifndef TARGETMAPPER_H_ +#define TARGETMAPPER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include + + +struct TargetMapper; +typedef struct TargetMapper TargetMapper; + + +extern void TargetMapper_init(TargetMapper* this); +extern TargetMapper* TargetMapper_construct(void); +extern void TargetMapper_uninit(TargetMapper* this); +extern void TargetMapper_destruct(TargetMapper* this); + +extern bool TargetMapper_mapTarget(TargetMapper* this, uint16_t targetID, + NumNodeID nodeID); + +extern void TargetMapper_syncTargets(TargetMapper* this, struct list_head* mappings); +extern void TargetMapper_getTargetIDs(TargetMapper* this, UInt16List* outTargetIDs); + +extern NumNodeID TargetMapper_getNodeID(TargetMapper* this, uint16_t targetID); + +struct TargetMapper +{ + RWLock rwlock; + +/* private: */ + struct rb_root _entries; /* TargetMapping */ +}; + +#endif /* TARGETMAPPER_H_ */ diff --git a/client_module/source/common/nodes/TargetStateStore.c b/client_module/source/common/nodes/TargetStateStore.c new file mode 100644 index 0000000..0408a3c --- /dev/null +++ b/client_module/source/common/nodes/TargetStateStore.c @@ -0,0 +1,184 @@ +#include "TargetStateStore.h" + +BEEGFS_RBTREE_FUNCTIONS(static, _TargetStateStore, struct TargetStateStore, states, + uint16_t, + struct TargetStateInfo, targetID, _node, + BEEGFS_RB_KEYCMP_LT_INTEGRAL) + +void TargetStateStore_init(TargetStateStore* this) +{ + RWLock_init(&this->rwlock); + this->states = RB_ROOT; +} + +TargetStateStore* TargetStateStore_construct(void) +{ + TargetStateStore* this = (TargetStateStore*)os_kmalloc(sizeof(*this) ); + + if (likely(this) ) + TargetStateStore_init(this); + + return this; +} + +void TargetStateStore_uninit(TargetStateStore* this) +{ + BEEGFS_KFREE_RBTREE(&this->states, struct TargetStateInfo, _node); +} + +void TargetStateStore_destruct(TargetStateStore* this) +{ + TargetStateStore_uninit(this); + + kfree(this); +} + +/** + * Atomically update target states and buddy groups together. This is important to avoid races + * during a switch (e.g. where an outside viewer could see an offline primary). + * + * Of course, this implies that states and groups also have to be read atomically together via + * getStatesAndGroupsAsLists() through GetStatesAndBuddyGroupsMsg. + */ +void TargetStateStore_syncStatesAndGroupsFromLists(TargetStateStore* this, Config* config, + MirrorBuddyGroupMapper* buddyGroups, struct list_head* states, struct list_head* groups) +{ + RWLock_writeLock(&this->rwlock); // L O C K targetStates + RWLock_writeLock(&buddyGroups->rwlock); // L O C K buddyGroups + + __TargetStateStore_syncStatesUnlocked(this, states); + + __MirrorBuddyGroupMapper_syncGroupsUnlocked(buddyGroups, config, groups); + + RWLock_writeUnlock(&buddyGroups->rwlock); // U N L O C K buddyGroups + RWLock_writeUnlock(&this->rwlock); // U N L O C K targetStates +} + +/** + * Note: Caller must hold writelock. + */ +void __TargetStateStore_syncStatesUnlocked(TargetStateStore* this, struct list_head* states) +{ + struct TargetStateMapping* state; + + // clear existing map + BEEGFS_KFREE_RBTREE(&this->states, struct TargetStateInfo, _node); + + list_for_each_entry(state, states, _list) + { + CombinedTargetState currentState = { state->reachabilityState, state->consistencyState }; + + TargetStateInfo* stateInfo = kmalloc(sizeof(TargetStateInfo), GFP_NOFS | __GFP_NOFAIL); + + stateInfo->targetID = state->targetID; + stateInfo->state = currentState; + + kfree(_TargetStateStore_insertOrReplace(this, stateInfo)); + } +} + +/** + * @return pointer to static string (so don't free it) + */ +const char* TargetStateStore_reachabilityStateToStr(TargetReachabilityState state) +{ + switch(state) + { + case TargetReachabilityState_ONLINE: + return "Online"; + + case TargetReachabilityState_POFFLINE: + return "Probably-offline"; + + case TargetReachabilityState_OFFLINE: + return "Offline"; + + default: + return ""; + } +} + +/** + * @return pointer to static string (so don't free it) + */ +const char* TargetStateStore_consistencyStateToStr(TargetConsistencyState state) +{ + switch(state) + { + case TargetConsistencyState_GOOD: + return "Good"; + + case TargetConsistencyState_NEEDS_RESYNC: + return "Needs-resync"; + + case TargetConsistencyState_BAD: + return "Bad"; + + default: + return ""; + } +} + +void TargetStateStore_getStatesAsLists(TargetStateStore* this, UInt16List* outTargetIDs, + UInt8List* outReachabilityStates, UInt8List* outConsistencyStates) +{ + struct TargetStateInfo* stateInfo; + RWLock_readLock(&this->rwlock); // L O C K + + BEEGFS_RBTREE_FOR_EACH_ENTRY(stateInfo, &this->states, _node) + { + UInt16List_append(outTargetIDs, stateInfo->targetID); + UInt8List_append(outReachabilityStates, stateInfo->state.reachabilityState); + UInt8List_append(outConsistencyStates, stateInfo->state.consistencyState); + } + + RWLock_readUnlock(&this->rwlock); // U N L O C K +} + +bool TargetStateStore_setAllStates(TargetStateStore* this, TargetReachabilityState state) +{ + bool res = false; + struct TargetStateInfo* stateInfo; + + RWLock_writeLock(&this->rwlock); // L O C K + + BEEGFS_RBTREE_FOR_EACH_ENTRY(stateInfo, &this->states, _node) + { + if (stateInfo->state.reachabilityState != state) + { + res = true; + stateInfo->state.reachabilityState = state; + } + } + + RWLock_writeUnlock(&this->rwlock); // U N L O C K + + return res; +} + +bool TargetStateStore_getState(TargetStateStore* this, uint16_t targetID, + CombinedTargetState* state) +{ + TargetStateInfo* stateInfo; + bool res; + + RWLock_readLock(&this->rwlock); // L O C K + + stateInfo = _TargetStateStore_find(this, targetID); + + if(likely(stateInfo) ) + { + *state = stateInfo->state; + res = true; + } + else + { + state->reachabilityState = TargetReachabilityState_OFFLINE; + state->consistencyState = TargetConsistencyState_GOOD; + res = false; + } + + RWLock_readUnlock(&this->rwlock); // U N L O C K + + return res; +} diff --git a/client_module/source/common/nodes/TargetStateStore.h b/client_module/source/common/nodes/TargetStateStore.h new file mode 100644 index 0000000..a2271bc --- /dev/null +++ b/client_module/source/common/nodes/TargetStateStore.h @@ -0,0 +1,41 @@ +#ifndef TARGETSTATESTORE_H_ +#define TARGETSTATESTORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +struct TargetStateStore; +typedef struct TargetStateStore TargetStateStore; + +extern void TargetStateStore_init(TargetStateStore* this); +extern TargetStateStore* TargetStateStore_construct(void); +extern void TargetStateStore_uninit(TargetStateStore* this); +extern void TargetStateStore_destruct(TargetStateStore* this); + +extern void TargetStateStore_syncStatesAndGroupsFromLists(TargetStateStore* this, Config* config, + MirrorBuddyGroupMapper* buddyGroups, struct list_head* states, struct list_head* groups); +extern void TargetStateStore_getStatesAsLists(TargetStateStore* this, UInt16List* outTargetIDs, + UInt8List* outReachabilityStates, UInt8List* outConsistencyStates); +extern const char* TargetStateStore_reachabilityStateToStr(TargetReachabilityState state); +extern const char* TargetStateStore_consistencyStateToStr(TargetConsistencyState state); +extern bool TargetStateStore_setAllStates(TargetStateStore* this, + TargetReachabilityState state); + +extern void __TargetStateStore_syncStatesUnlocked(TargetStateStore* this, struct list_head* states); + +extern bool TargetStateStore_getState(TargetStateStore* this, uint16_t targetID, + CombinedTargetState* state); + +struct TargetStateStore +{ + RWLock rwlock; + struct rb_root states; /* struct TargetStateInfo */ +}; + +#endif /* TARGETSTATESTORE_H_ */ diff --git a/client_module/source/common/storage/EntryInfo.c b/client_module/source/common/storage/EntryInfo.c new file mode 100644 index 0000000..c5fc843 --- /dev/null +++ b/client_module/source/common/storage/EntryInfo.c @@ -0,0 +1,85 @@ +#include +#include +#include + +#define MIN_ENTRY_ID_LEN 1 // usually A-B-C, but also "root" and "disposal" + +/** + * Serialize into outBuf, 4-byte aligned + */ +void EntryInfo_serialize(SerializeCtx* ctx, const EntryInfo* this) +{ + // DirEntryType + Serialization_serializeUInt(ctx, this->entryType); + + // featureFlags + Serialization_serializeInt(ctx, this->featureFlags); + + // parentEntryID + Serialization_serializeStrAlign4(ctx, strlen(this->parentEntryID), this->parentEntryID); + + if (unlikely(strlen(this->entryID) < MIN_ENTRY_ID_LEN) ) + { + printk_fhgfs(KERN_WARNING, "EntryID too small!\n"); // server side deserialization will fail + dump_stack(); + } + + // entryID + Serialization_serializeStrAlign4(ctx, strlen(this->entryID), this->entryID); + + // fileName + Serialization_serializeStrAlign4(ctx, strlen(this->fileName), this->fileName); + + // ownerNodeID + // also serializes owner.group, if buddymirrored. both MUST have the same size and underlying + // type! + BUILD_BUG_ON( + sizeof(this->owner.node) != sizeof(this->owner.group) + || !__builtin_types_compatible_p( + __typeof(this->owner.node.value), __typeof(this->owner.group))); + NumNodeID_serialize(ctx, &this->owner.node); + + // padding for 4-byte alignment + Serialization_serializeUShort(ctx, 0); +} + +/** + * deserialize the given buffer + */ +bool EntryInfo_deserialize(DeserializeCtx* ctx, EntryInfo* outThis) +{ + unsigned parentEntryIDLen; + unsigned entryIDLen; + unsigned fileNameLen; + unsigned entryType; + unsigned short padding; + + if (!Serialization_deserializeUInt(ctx, &entryType)) + return false; + outThis->entryType = (DirEntryType) entryType; + + if (!Serialization_deserializeInt(ctx, &outThis->featureFlags) + || !Serialization_deserializeStrAlign4(ctx, &parentEntryIDLen, &outThis->parentEntryID) + || !Serialization_deserializeStrAlign4(ctx, &entryIDLen, &outThis->entryID) + || !Serialization_deserializeStrAlign4(ctx, &fileNameLen, &outThis->fileName)) + return false; + + outThis->owner.isGroup = outThis->featureFlags & ENTRYINFO_FEATURE_BUDDYMIRRORED; + + // also deserializes owner.group, if buddy mirrored. + if (!NumNodeID_deserialize(ctx, &outThis->owner.node)) + return false; + + // padding for 4-byte alignment + if(!Serialization_deserializeUShort(ctx, &padding)) + return false; + + // Note: the root ("/") has parentEntryID = "" + if (unlikely((parentEntryIDLen < MIN_ENTRY_ID_LEN) && (parentEntryIDLen > 0))) + return false; + + if (unlikely(entryIDLen < MIN_ENTRY_ID_LEN)) + return false; + + return true; +} diff --git a/client_module/source/common/storage/EntryInfo.h b/client_module/source/common/storage/EntryInfo.h new file mode 100644 index 0000000..423e7b4 --- /dev/null +++ b/client_module/source/common/storage/EntryInfo.h @@ -0,0 +1,208 @@ +/* + * class EntryInfo - required information to find an inode or chunk files + * + * NOTE: If you change this file, do not forget to adjust commons EntryInfo.h + */ + +#ifndef ENTRYINFO_H_ +#define ENTRYINFO_H_ + +#include +#include +#include +#include + +#define ENTRYINFO_FEATURE_INLINED 1 // indicate inlined inode, might be outdated +#define ENTRYINFO_FEATURE_BUDDYMIRRORED 2 // entry is buddy mirrored + +#define EntryInfo_PARENT_ID_UNKNOWN "unknown" + +struct EntryInfo; +typedef struct EntryInfo EntryInfo; + + +static inline void EntryInfo_uninit(EntryInfo* this); + +static inline void EntryInfo_dup(const EntryInfo *inEntryInfo, EntryInfo *outEntryInfo); + +static inline void EntryInfo_update(EntryInfo* this, const EntryInfo* newEntryInfo); +static inline void EntryInfo_updateParentEntryID(EntryInfo *this, const char *newParentID); +static inline void EntryInfo_updateSetParentEntryID(EntryInfo *this, const char *newParentID); + +static inline char const* EntryInfo_getParentEntryID(const EntryInfo* this); +static inline char const* EntryInfo_getEntryID(const EntryInfo* this); +static inline void EntryInfo_updateFileName(EntryInfo *this, const char* newFileName); +static inline bool EntryInfo_getIsBuddyMirrored(const EntryInfo *this); + +extern void EntryInfo_serialize(SerializeCtx* ctx, const EntryInfo* this); +extern bool EntryInfo_deserialize(DeserializeCtx* ctx, EntryInfo* outThis); + + +typedef struct NodeOrGroup +{ + union + { + NumNodeID node; + uint32_t group; + }; + bool isGroup; +} NodeOrGroup; + +static inline NodeOrGroup NodeOrGroup_fromNode(NumNodeID node) +{ + NodeOrGroup result = { .isGroup = false }; + result.node = node; + return result; +} + +static inline NodeOrGroup NodeOrGroup_fromGroup(uint32_t group) +{ + NodeOrGroup result = { .isGroup = true }; + result.group = group; + return result; +} + +static inline bool NodeOrGroup_valid(NodeOrGroup id) +{ + return id.group != 0; +} + +/** + * minimal information about an entry (file/directory/...) + */ +struct EntryInfo +{ + // serialization will work as long as the elements in here have the same size and + // underlying type(!) + NodeOrGroup owner; + const char* parentEntryID; + const char* entryID; + const char* fileName; + DirEntryType entryType; + int featureFlags; +}; + + + + +/** + * Main initialization function for EntryInfo, should typically be used + * + * @param parentEntryID will be free'd on uninit + * @param entryID will be free'd on uninit + * @param fileName will be free'd on uninit + */ +static inline void EntryInfo_init(EntryInfo* this, NodeOrGroup owner, + const char* parentEntryID, const char* entryID, const char* fileName, DirEntryType entryType, + int featureFlags) +{ + this->owner = owner; + this->parentEntryID = parentEntryID; + this->entryID = entryID; + this->fileName = fileName; + this->entryType = entryType; + this->featureFlags = featureFlags | (owner.isGroup ? ENTRYINFO_FEATURE_BUDDYMIRRORED : 0); +} + +/** + * unitialize the object + */ +void EntryInfo_uninit(EntryInfo* this) +{ + kfree(this->parentEntryID); + kfree(this->entryID); + kfree(this->fileName); +} + +/** + * Duplicate inEntryInfo to outEntryInfo, also allocate memory for strings + */ +void EntryInfo_dup(const EntryInfo *inEntryInfo, EntryInfo *outEntryInfo) +{ + *outEntryInfo = *inEntryInfo; + + outEntryInfo->parentEntryID = StringTk_strDup(outEntryInfo->parentEntryID); + outEntryInfo->entryID = StringTk_strDup(outEntryInfo->entryID); + outEntryInfo->fileName = StringTk_strDup(outEntryInfo->fileName); +} + + +/** + * Update our EntryInfo (this) with a new EntryInfo + * + * Note: newEntryInfo must not be used any more by the caller + * Note: This does not update entryID and entryType as these values cannot change. If we would + * update entryID we would also increase locking overhead. + */ +void EntryInfo_update(EntryInfo* this, const EntryInfo* newEntryInfo) +{ + kfree(this->parentEntryID); + kfree(this->fileName); + + this->parentEntryID = newEntryInfo->parentEntryID; + this->fileName = newEntryInfo->fileName; + this->featureFlags = newEntryInfo->featureFlags; + + if (!DirEntryType_ISDIR(this->entryType) ) + { // only update the owner if it is not a directory + this->owner = newEntryInfo->owner; + } + + kfree(newEntryInfo->entryID); +} + + +void EntryInfo_updateParentEntryID(EntryInfo *this, const char *newParentID) +{ + kfree(this->parentEntryID); + this->parentEntryID = StringTk_strDup(newParentID); +} + +/** + * Note: This sets to newParentID, the caller must not use newParentID + */ +void EntryInfo_updateSetParentEntryID(EntryInfo *this, const char *newParentID) +{ + kfree(this->parentEntryID); + this->parentEntryID = newParentID; +} + +char const* EntryInfo_getParentEntryID(const EntryInfo* this) +{ + return this->parentEntryID; +} + +char const* EntryInfo_getEntryID(const EntryInfo* this) +{ + return this->entryID; +} + +void EntryInfo_updateFileName(EntryInfo *this, const char* newFileName) +{ + kfree(this->fileName); + this->fileName = StringTk_strDup(newFileName); +} + +bool EntryInfo_getIsBuddyMirrored(const EntryInfo *this) +{ + return (this->featureFlags & ENTRYINFO_FEATURE_BUDDYMIRRORED); +} + + +/* these two are mainly for logging. */ +static inline uint32_t EntryInfo_getOwner(const EntryInfo* this) +{ + /* owner.node and owner.group are required to be the same type and size */ + return this->owner.group; +} + +static inline const char* EntryInfo_getOwnerFlag(const EntryInfo* this) +{ + if (this->featureFlags & ENTRYINFO_FEATURE_BUDDYMIRRORED) + return "g"; + else + return ""; +} + + +#endif /* ENTRYINFO_H_ */ diff --git a/client_module/source/common/storage/FileEvent.c b/client_module/source/common/storage/FileEvent.c new file mode 100644 index 0000000..db5fcdf --- /dev/null +++ b/client_module/source/common/storage/FileEvent.c @@ -0,0 +1,59 @@ +#include "FileEvent.h" + +#include + +void FileEvent_init(struct FileEvent* event, enum FileEventType eventType, struct dentry* dentry) +{ + memset(event, 0, sizeof(*event)); + + event->eventType = eventType; + + if (!dentry) + return; + + event->pathPagePFN = (unsigned long) kmalloc(4096, GFP_NOFS); + if (!event->pathPagePFN) + return; + + event->path = dentry_path_raw(dentry, (char*) event->pathPagePFN, PAGE_SIZE); + if (IS_ERR(event->path)) + event->path = NULL; +} + +void FileEvent_uninit(struct FileEvent* event) +{ + if (event->pathPagePFN) + kfree((void *)event->pathPagePFN); + + FileEvent_setTargetStr(event, NULL); +} + +void FileEvent_setTargetDentry(struct FileEvent* event, struct dentry* dentry) +{ + FileEvent_setTargetStr(event, NULL); + + if (!dentry) + return; + + event->targetPagePFN = (unsigned long) kmalloc(4096, GFP_NOFS); + if (!event->targetPagePFN) + return; + + event->target = dentry_path_raw(dentry, (char*) event->targetPagePFN, PAGE_SIZE); + if (IS_ERR(event->target)) + event->target = NULL; +} + +void FileEvent_serialize(SerializeCtx* ctx, const struct FileEvent* event) +{ + Serialization_serializeUInt(ctx, event->eventType); + + if (event->path) + Serialization_serializeStr(ctx, strlen(event->path), event->path); + else + Serialization_serializeStr(ctx, 0, ""); + + Serialization_serializeBool(ctx, event->target != NULL); + if (event->target != NULL) + Serialization_serializeStr(ctx, strlen(event->target), event->target); +} diff --git a/client_module/source/common/storage/FileEvent.h b/client_module/source/common/storage/FileEvent.h new file mode 100644 index 0000000..83a6b3a --- /dev/null +++ b/client_module/source/common/storage/FileEvent.h @@ -0,0 +1,61 @@ +#ifndef FILEEVENT_H_ +#define FILEEVENT_H_ + +#include + +struct dentry; + +enum FileEventType +{ + FileEventType_FLUSH = 1, + FileEventType_TRUNCATE, + FileEventType_SETATTR, + FileEventType_CLOSE_WRITE, + FileEventType_CREATE, + FileEventType_MKDIR, + FileEventType_MKNOD, + FileEventType_SYMLINK, + FileEventType_RMDIR, + FileEventType_UNLINK, + FileEventType_HARDLINK, + FileEventType_RENAME, + FileEventType_OPEN_READ, + FileEventType_OPEN_WRITE, + FileEventType_OPEN_READ_WRITE, + FileEventType_LAST_WRITER_CLOSED, + FileEventType_OPEN_BLOCKED, +}; + +struct FileEvent +{ + uint32_t eventType; /* enum FileEventType */ + const char* path; /* NULL if invalid/could not be determined (empty is also allowed) */ + const char* target; /* link target for link, new name for rename */ + + unsigned long pathPagePFN; + unsigned long targetPagePFN; +}; + +void FileEvent_init(struct FileEvent* event, enum FileEventType eventType, struct dentry* dentry); +void FileEvent_uninit(struct FileEvent* event); + +static inline void FileEvent_setTargetStr(struct FileEvent* event, const char* target) +{ + if (event->targetPagePFN) + kfree((void *)event->targetPagePFN); + else + kfree(event->target); + + event->targetPagePFN = 0; + event->target = kstrdup(target, GFP_NOFS); +} + +void FileEvent_setTargetDentry(struct FileEvent* event, struct dentry* dentry); + +void FileEvent_serialize(SerializeCtx* ctx, const struct FileEvent* event); + +/* the empty file event object is a valid event that may be destroyed, but that holds no + * state itself. thus, it may also be reinitialized without being destroyed first. */ +#define FILEEVENT_EMPTY {0, NULL, NULL, 0, 0} + +#endif diff --git a/client_module/source/common/storage/Metadata.h b/client_module/source/common/storage/Metadata.h new file mode 100644 index 0000000..d9488e1 --- /dev/null +++ b/client_module/source/common/storage/Metadata.h @@ -0,0 +1,9 @@ +#ifndef METADATA_H_ +#define METADATA_H_ + +#include + + +#define META_ROOTDIR_ID_STR "root" + +#endif /*METADATA_H_*/ diff --git a/client_module/source/common/storage/Nvfs.c b/client_module/source/common/storage/Nvfs.c new file mode 100644 index 0000000..56acc7d --- /dev/null +++ b/client_module/source/common/storage/Nvfs.c @@ -0,0 +1,70 @@ +/* + * Copyright (c)2018, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifdef BEEGFS_NVFS +#include +#include "Nvfs.h" + +struct nvfs_dma_rw_ops *nvfs_ops = NULL; + +atomic_t nvfs_shutdown = ATOMIC_INIT(1); + +DEFINE_PER_CPU(long, nvfs_n_ops); + +int REGISTER_FUNC (struct nvfs_dma_rw_ops *ops) +{ + printk_fhgfs_debug(KERN_INFO, "%s:%d: register nvfs ops\n", + __func__, __LINE__); + if (NVIDIA_FS_CHECK_FT_SGLIST_DMA(ops) && + NVIDIA_FS_CHECK_FT_GPU_PAGE(ops) && + NVIDIA_FS_CHECK_FT_DEVICE_PRIORITY(ops)) + { + nvfs_ops = ops; + atomic_set(&nvfs_shutdown, 0); + return 0; + } + return -ENOTSUPP; +} +EXPORT_SYMBOL(REGISTER_FUNC); + +void UNREGISTER_FUNC (void) +{ + int ops; + printk_fhgfs_debug(KERN_INFO, "%s:%d: begin unregister nvfs\n", + __func__, __LINE__); + (void) atomic_cmpxchg(&nvfs_shutdown, 0, 1); + do + { + msleep(NVFS_HOLD_TIME_MS); + ops = nvfs_count_ops(); + printk_fhgfs_debug(KERN_INFO, "%s:%d: nvfs_count_ops=%d\n", + __func__, __LINE__, ops); + } + while (ops); + nvfs_ops = NULL; + printk_fhgfs_debug(KERN_INFO, "%s:%d: unregister nvfs complete\n", + __func__, __LINE__); + +} +EXPORT_SYMBOL(UNREGISTER_FUNC); + +#endif /* BEEGFS_NVFS */ diff --git a/client_module/source/common/storage/Nvfs.h b/client_module/source/common/storage/Nvfs.h new file mode 100644 index 0000000..737b9e6 --- /dev/null +++ b/client_module/source/common/storage/Nvfs.h @@ -0,0 +1,81 @@ +#ifndef BEEGFS_NVFS_H +#define BEEGFS_NVFS_H + +/** + * BeeGFS and NVIDIA have name collisions on MIN and MAX. Briefly undefine MIN and MAX + * to allow NVIDIA headers to be included without warnings. + */ +#ifdef MIN +#pragma push_macro("MIN") +#undef MIN +#define BEEGFS_POP_MIN +#endif +#ifdef MAX +#pragma push_macro("MAX") +#undef MAX +#define BEEGFS_POP_MAX +#endif + +#ifdef BEEGFS_NVFS +#define MODULE_PREFIX beegfs_v1 + +#include +#include + +#define REGSTR2(x) x##_register_nvfs_dma_ops +#define REGSTR(x) REGSTR2(x) + +#define UNREGSTR2(x) x##_unregister_nvfs_dma_ops +#define UNREGSTR(x) UNREGSTR2(x) + +#define REGISTER_FUNC REGSTR(MODULE_PREFIX) +#define UNREGISTER_FUNC UNREGSTR(MODULE_PREFIX) + +#define NVFS_IO_ERR -1 +#define NVFS_CPU_REQ -2 + +#define NVFS_HOLD_TIME_MS 1000 + +extern struct nvfs_dma_rw_ops *nvfs_ops; + +extern atomic_t nvfs_shutdown; + +DECLARE_PER_CPU(long, nvfs_n_ops); + +static inline long nvfs_count_ops(void) +{ + int i; + long sum = 0; + for_each_possible_cpu(i) + sum += per_cpu(nvfs_n_ops, i); + return sum; +} + +static inline bool nvfs_get_ops(void) +{ + if (nvfs_ops && !atomic_read(&nvfs_shutdown)) { + this_cpu_inc(nvfs_n_ops); + return true; + } + return false; +} + +static inline void nvfs_put_ops(void) +{ + this_cpu_dec(nvfs_n_ops); +} + +/** + * Restore BeeGFS definitions of MIN and MAX. + */ +#ifdef BEEGFS_POP_MIN +#pragma pop_macro("MIN") +#undef BEEGFS_POP_MIN +#endif +#ifdef BEEGFS_POP_MAX +#pragma pop_macro("MAX") +#undef BEEGFS_POP_MAX +#endif + +#endif /* BEEGFS_NVFS */ +#endif /* BEEGFS_NVFS_H */ diff --git a/client_module/source/common/storage/Path.h b/client_module/source/common/storage/Path.h new file mode 100644 index 0000000..c50e0e4 --- /dev/null +++ b/client_module/source/common/storage/Path.h @@ -0,0 +1,142 @@ +#ifndef PATH_H_ +#define PATH_H_ + +#include +#include +#include +#include + + +struct Path; +typedef struct Path Path; + +static inline void Path_init(Path* this); +static inline void Path_initFromString(Path* this, const char* pathStr); +static inline void Path_uninit(Path* this); + +static inline void Path_parseStr(Path* this, const char* pathStr); +static inline bool Path_isPathStrAbsolute(const char* pathStr); + +// getters & setters +static inline StrCpyList* Path_getPathElems(Path* this); +static inline char* Path_getPathAsStrCopy(Path* this); +static inline bool Path_isAbsolute(Path* this); +static inline void Path_setAbsolute(Path* this, bool absolute); + + +struct Path +{ + StrCpyList pathElems; + bool isPathAbsolute; +}; + +void Path_init(Path* this) +{ + StrCpyList_init(&this->pathElems); + + this->isPathAbsolute = false; +} + +void Path_initFromString(Path* this, const char* pathStr) +{ + Path_init(this); + + this->isPathAbsolute = Path_isPathStrAbsolute(pathStr); + Path_parseStr(this, pathStr); +} + +void Path_uninit(Path* this) +{ + StrCpyList_uninit(&this->pathElems); +} + +void Path_parseStr(Path* this, const char* pathStr) +{ + StringTk_explode(pathStr, '/', &this->pathElems); +} + +bool Path_isPathStrAbsolute(const char* pathStr) +{ + return (strlen(pathStr) && (pathStr[0] == '/') ); +} + +StrCpyList* Path_getPathElems(Path* this) +{ + return &this->pathElems; +} + +/** + * @return string does not end with a slash; string is kalloced and needs to be kfreed by the caller + */ +char* Path_getPathAsStrCopy(Path* this) +{ + char* pathStr; + StrCpyListIter iter; + size_t currentPathPos; + size_t totalPathLen; + + // count total path length + totalPathLen = Path_isAbsolute(this) ? 1 : 0; + + if(!StrCpyList_length(&this->pathElems) ) + { // (very unlikely) + totalPathLen = 1; // for terminating zero + } + + StrCpyListIter_init(&iter, &this->pathElems); + + for( ; !StrCpyListIter_end(&iter); StrCpyListIter_next(&iter) ) + { + char* currentPathElem = StrCpyListIter_value(&iter); + + totalPathLen += strlen(currentPathElem) + 1; // +1 for slash or terminating zero + } + + + // alloc path buffer + pathStr = os_kmalloc(totalPathLen); + + + // copy elems to path + if(Path_isAbsolute(this) ) + { + pathStr[0] = '/'; + currentPathPos = 1; + } + else + currentPathPos = 0; + + + StrCpyListIter_init(&iter, &this->pathElems); + + for( ; !StrCpyListIter_end(&iter); StrCpyListIter_next(&iter) ) + { + char* currentPathElem = StrCpyListIter_value(&iter); + size_t currentPathElemLen = strlen(currentPathElem); + + memcpy(&pathStr[currentPathPos], currentPathElem, currentPathElemLen); + + currentPathPos += currentPathElemLen; + + pathStr[currentPathPos] = '/'; + + currentPathPos++; + } + + // zero-terminate the pathStr + pathStr[totalPathLen-1] = 0; + + return pathStr; +} + +bool Path_isAbsolute(Path* this) +{ + return this->isPathAbsolute; +} + +void Path_setAbsolute(Path* this, bool absolute) +{ + this->isPathAbsolute = absolute; +} + +#endif /*PATH_H_*/ diff --git a/client_module/source/common/storage/PathInfo.c b/client_module/source/common/storage/PathInfo.c new file mode 100644 index 0000000..65114c2 --- /dev/null +++ b/client_module/source/common/storage/PathInfo.c @@ -0,0 +1,58 @@ +#include +#include +#include + +/** + * Serialize into outBuf, 4-byte aligned + */ +void PathInfo_serialize(SerializeCtx* ctx, const PathInfo* this) +{ + // flags + Serialization_serializeUInt(ctx, this->flags); + + if (this->flags & PATHINFO_FEATURE_ORIG) + { + // origParentUID + Serialization_serializeUInt(ctx, this->origParentUID); + + // origParentEntryID + Serialization_serializeStrAlign4(ctx, strlen(this->origParentEntryID), + this->origParentEntryID); + } +} + +/** + * deserialize the given buffer + */ +bool PathInfo_deserialize(DeserializeCtx* ctx, PathInfo* outThis) +{ + unsigned flags; + + unsigned origParentUID; + unsigned origParentEntryIDLen; + const char* origParentEntryID; + + // flags + if(!Serialization_deserializeUInt(ctx, &flags) ) + return false; + + if (flags & PATHINFO_FEATURE_ORIG) + { // file has origParentUID and origParentEntryID + // origParentUID + if(!Serialization_deserializeUInt(ctx, &origParentUID) ) + return false; + + // origParentEntryID + if(!Serialization_deserializeStrAlign4(ctx, &origParentEntryIDLen, &origParentEntryID) ) + return false; + } + else + { // either a directory or a file stored in old format + origParentUID = 0; + origParentEntryID = NULL; + } + + PathInfo_init(outThis, origParentUID, origParentEntryID, flags); + + return true; +} diff --git a/client_module/source/common/storage/PathInfo.h b/client_module/source/common/storage/PathInfo.h new file mode 100644 index 0000000..ffa77be --- /dev/null +++ b/client_module/source/common/storage/PathInfo.h @@ -0,0 +1,154 @@ +/* + * class PathInfo - extra information how to find chunk files (or later on inode files) + * + * NOTE: If you change this file, do not forget to adjust commons PathInfo.h + */ + +#ifndef PATHINFO_H_ +#define PATHINFO_H_ + +#include +#include +#include +#include + +#define PATHINFO_FEATURE_ORIG 1 /* inidicate chunks are stored with origParentUID and + * and origParentEntryID */ +#define PATHINFO_FEATURE_ORIG_UNKNOWN 2 /* indicates FEATURE_ORIG is unknown and needs to be + * requested from the meta-inode */ +#define PATHINFO_FEATURE_IS_STUB 4 /* indicates entry is a stub file. + * This flag not used in the client module but is added + * here for consistency with the C++ PathInfo implementation */ + +struct PathInfo; +typedef struct PathInfo PathInfo; + + +static inline void PathInfo_init(PathInfo *this, + unsigned origParentUID, const char* origParentEntryID, unsigned flags); +static inline void PathInfo_uninit(PathInfo* this); + +static inline void PathInfo_dup(const PathInfo *inPathInfo, PathInfo *outPathInfo); +static inline void PathInfo_update(PathInfo* this, const PathInfo* newPathInfo); + +static inline void PathInfo_setOrigUID(PathInfo* this, unsigned origParentUID); +static inline void PathInfo_setOrigParentEntryID(PathInfo* this, const char* origParentEntryID); +static inline void PathInfo_setFlags(PathInfo *this, unsigned flags); + +extern void PathInfo_serialize(SerializeCtx* ctx, const PathInfo* this); +extern bool PathInfo_deserialize(DeserializeCtx* ctx, PathInfo* outThis); + + +/** + * minimal information about an entry (file/directory/...) + * note: In order to properly initialize this struct, PathInfo_init() has to be called. This + * is also the only function, which ever should write to the write-unprotected '_' functions. + * Other code is supposed to use the function without the underscore. + */ +struct PathInfo +{ + union + { + const unsigned flags; // additional flags (e.g. PATHINFO_FEATURE_INLINED) + unsigned _flags; + }; + + union + { + const unsigned origParentUID; // UID who created the file, only set for FileInodes + unsigned _origParentUID; + }; + + union + { + // ID of the dir in which the file was created in. Only set for FileInodes + char const* const origParentEntryID; + char* _origParentEntryID; + }; +}; + + + + +/** + * Main initialization function for PathInfo, should typically be used + * + * @param origParentEntryID will be free'd on uninit + */ +void PathInfo_init(PathInfo* this, unsigned origParentUID, const char* origParentEntryID, unsigned flags) +{ + PathInfo_setOrigUID(this, origParentUID); + PathInfo_setOrigParentEntryID(this, origParentEntryID); + PathInfo_setFlags(this, flags); +} + +/** + * unitialize the object + */ +void PathInfo_uninit(PathInfo* this) +{ + if (this->flags & PATHINFO_FEATURE_ORIG) + kfree(this->origParentEntryID); +} + +/** + * Duplicate inPathInfo to outPathInfo, also allocate memory for strings + */ +void PathInfo_dup(const PathInfo* inPathInfo, PathInfo* outPathInfo) +{ + int outFlags = inPathInfo->flags; + + unsigned outOrigUID = inPathInfo->origParentUID; + const char* outOrigParentEntryID; + + if (outFlags & PATHINFO_FEATURE_ORIG) + outOrigParentEntryID = StringTk_strDup(inPathInfo->origParentEntryID); + else + outOrigParentEntryID = NULL; + + PathInfo_init(outPathInfo, outOrigUID, outOrigParentEntryID, outFlags); +} + +/** + * Update an existing PathInfo + */ +void PathInfo_update(PathInfo* this, const PathInfo* newPathInfo) +{ + bool needUpdate = false; + + if (this->flags != newPathInfo->flags) + needUpdate = true; + else + if (this->origParentUID != newPathInfo->origParentUID) + needUpdate = true; + else + if ( (this->origParentEntryID && newPathInfo->origParentEntryID) && + (strcmp(this->origParentEntryID, newPathInfo->origParentEntryID) != 0 ) ) + { + // note: no need for other tests in the if-condition, as flags would be different then. + needUpdate = true; + } + + if (needUpdate) + { + PathInfo_uninit(this); + PathInfo_dup(newPathInfo, this); + } +} + +void PathInfo_setFlags(PathInfo* this, unsigned flags) +{ + this->_flags = flags; +} + +void PathInfo_setOrigUID(PathInfo* this, unsigned origParentUID) +{ + this->_origParentUID = origParentUID; +} + +void PathInfo_setOrigParentEntryID(PathInfo* this, const char* origParentEntryID) +{ + this->_origParentEntryID = (char *) origParentEntryID; +} + +#endif /* PATHINFO_H_ */ diff --git a/client_module/source/common/storage/RdmaInfo.c b/client_module/source/common/storage/RdmaInfo.c new file mode 100644 index 0000000..57703de --- /dev/null +++ b/client_module/source/common/storage/RdmaInfo.c @@ -0,0 +1,399 @@ +#ifdef BEEGFS_NVFS +#include "linux/uio.h" +#include "linux/pagemap.h" +#include "linux/kernel.h" +#include +#include +#include +#include +#include "Nvfs.h" +#include "RdmaInfo.h" + +// +// These macros convert a scatterlist entry into a base address (ba) and limit address (la) +// and vice-versa. From this information, we can combine scatterlist entries which are DMA +// contiguous. +// +#define sg_to_ba_la(SG, BA, LA) \ + do \ + { \ + BA = sg_dma_address(SG); \ + LA = BA + sg_dma_len(SG); \ + } while (0) + +#define ba_la_to_sg(SG, BA, LA) \ + do \ + { \ + sg_dma_address(SG) = BA; \ + sg_dma_len(SG) = LA - BA; \ + SG->length = LA - BA; \ + } while (0) + +bool RdmaInfo_acquireNVFS(void) +{ +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s\n", __func__); +#endif // BEEGFS_DEBUG + return nvfs_get_ops(); +} + +void RdmaInfo_releaseNVFS(void) +{ +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s\n", __func__); +#endif // BEEGFS_DEBUG + nvfs_put_ops(); +} + +int RdmaInfo_detectNVFSRequest(DevicePriorityContext* dpctx, + const struct iov_iter *iter) +{ + struct page *page = NULL; + struct iov_iter iter_copy = *iter; + size_t page_offset = 0; + int status = 0; + bool is_gpu = false; + + // Test the first page of the request to determine the memory type. + status = iov_iter_get_pages(&iter_copy, &page, PAGE_SIZE, 1, &page_offset); + if (unlikely(status <= 0)) + { + // 0 means the iter is empty, so just indicate that it's not an NVFS call. + // Otherwise, indicate an error condition.if (unlikely(status <= 0)) + if (status < 0) + printk_fhgfs(KERN_WARNING, "%s: can't retrieve page from iov_iter, status=%d\n", + __func__, status); + return status == 0? false : status; + } + + // At this point, the request did come in through nvidia_fs. + // nvfs_is_gpu_page() will return false if RDMA write support + // is disabled in user space. + // TODO: if a GPU page, keep the retrieved page for a future + // RDMA map operation instead of calling put_page() + if (nvfs_ops->nvfs_is_gpu_page(page)) + { + is_gpu = true; + dpctx->gpuIndex = nvfs_ops->nvfs_gpu_index(page); + } + put_page(page); +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s:%d: page=%p is_gpu=%d gpu_index=%d\n", + __func__, __LINE__, + page, is_gpu, dpctx? dpctx->gpuIndex : -2); +#endif + return is_gpu; +} + + +/* + * RdmaInfo_putPpages - Put the pages back into free list. + * @sglist: The array of scatter/gather entries + * @count: The count of entries to process + */ +static inline void RdmaInfo_putPages(struct scatterlist *sglist, int count) +{ + int i = 0; + struct scatterlist *sgp = NULL; + + if ((sglist != NULL) && (count > 0)) + { + for (i = 0, sgp = sglist; i < count; i++, sgp++) + { + put_page(sg_page(sgp)); + } + } +} + +/* + * RdmaInfo_iovToSglist - Map an iov_iter to scatter/gather list + * @iter: iov_iter + * @sglist: The array of scatter/gather entries (needs to be big enough for all pages) + * @returns number of sg entries set up for the iov_iter + */ +static int RdmaInfo_iovToSglist(const struct iov_iter *iter, + struct scatterlist *sglist) +{ + struct page **pages = NULL; + struct scatterlist *sg = NULL; + struct scatterlist *sg_prev = NULL; + struct iov_iter iter_copy = *iter; + int sg_count = 0; + size_t page_length = 0; + size_t page_offset = 0; + size_t bytes = 0; + ssize_t result = 0; + unsigned i = 0; + unsigned npages = 0; + + while (iov_iter_count(&iter_copy) > 0) + { + result = iov_iter_get_pages_alloc(&iter_copy, &pages, iov_iter_count(&iter_copy), &page_offset); + if (result < 0) + { + printk_fhgfs(KERN_ERR, "RdmaInfo_iovToSglist: no memory pages\n"); + RdmaInfo_putPages(sglist, sg_count); + return -ENOMEM; + } + + bytes = result; + npages = (bytes + page_offset + PAGE_SIZE - 1) / PAGE_SIZE; + sg_count += npages; + + for (i = 0, sg = sglist; i < npages; i++, sg = sg_next(sg)) + { + page_length = min(bytes, PAGE_SIZE - page_offset); + sg_set_page(sg, pages[i], page_length, page_offset); + + bytes -= page_length; + page_offset = 0; + sg_prev = sg; + } + + kvfree(pages); + iov_iter_advance(&iter_copy, result); + } + + if (sg_prev) + { + sg_mark_end(sg_prev); + } + return sg_count; +} + +/* + * RdmaInfo_coalesceSglist - Coalesce scatterlist entries for optimal RDMA operations. + * @sglist: input list (not necessarily coalesced) + * @dmalist: output list (coalesced) + * @count: Number of scatterlist entries + * @returns count of coalesed list + */ +static int RdmaInfo_coalesceSglist(struct scatterlist *sglist, + struct scatterlist *dmalist, int count) +{ + struct scatterlist *sgp = sglist; + struct scatterlist *dmap = dmalist; + dma_addr_t dma_ba = 0, dma_la = 0; + dma_addr_t sg_ba = 0, sg_la = 0; + int i = 0; +#ifdef BEEGFS_DEBUG + size_t len = sg_dma_len(sgp); +#endif + + // + // Load the first range. + // + sg_to_ba_la(sgp, dma_ba, dma_la); + + if (count > 1) + { + for_each_sg(&sglist[1], sgp, count-1, i) + { +#ifdef BEEGFS_DEBUG + len += sg_dma_len(sgp); +#endif + sg_to_ba_la(sgp, sg_ba, sg_la); + + // + // If the regions aren't contiguous, then set the current + // range and start a new range. Otherwise, add on to the + // current range. + // + if (dma_la != sg_ba) + { + ba_la_to_sg(dmap, dma_ba, dma_la); + sg_unmark_end(dmap); + dmap = sg_next(dmap); + + dma_ba = sg_ba; + dma_la = sg_la; + } + else + { + dma_la = sg_la; + } + } + } + // + // Set the last range. + // + ba_la_to_sg(dmap, dma_ba, dma_la); + sg_mark_end(dmap); + +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_INFO, "%s len=%zu count=%d return=%d\n", __func__, len, count, (int)(1 + dmap - dmalist)); +#endif + return 1 + dmap - dmalist; +} + +/* + * RdmaInfo_map - Map GPU buffers for RDMA operations. + * @iter: iov_iter + * @socket: RDMA capable socket struct. + * @dma_dir: read (DMA_FROM_DEVICE) or write (DMA_TO_DEVICE) + * @returns RdmaInfo struct + */ +static RdmaInfo * RdmaInfo_map(const struct iov_iter *iter, Socket *socket, + enum dma_data_direction dma_dir) +{ + RdmaInfo *rdmap; + RDMASocket *rs; + struct ib_device *device; + struct scatterlist *sglist; + struct scatterlist *dmalist; + int status = 0; + int sg_count; + int dma_count; + int count; + unsigned npages; + unsigned key; + + if (Socket_getSockType(socket) != NICADDRTYPE_RDMA) + return ERR_PTR(-EINVAL); + + rs = (RDMASocket*) socket; + if (!RDMASocket_isRkeyGlobal(rs)) + { + printk_fhgfs(KERN_ERR, "ERROR: rkey type is not compatible with GDS\n"); + return ERR_PTR(-EINVAL); + } + + npages = 1 + iov_iter_npages(iter, INT_MAX); + + // + // Allocate the scatterlist. + // + rdmap = kzalloc(sizeof(RdmaInfo), GFP_ATOMIC); + sglist = kzalloc(npages * sizeof(struct scatterlist), GFP_ATOMIC); + dmalist = kzalloc(npages * sizeof(struct scatterlist), GFP_ATOMIC); + if (unlikely(!rdmap || !sglist || !dmalist)) + { + printk_fhgfs(KERN_ERR, "%s: no memory for scatterlist\n", __func__); + status = -ENOMEM; + goto error_return; + } + + // + // Populate the scatterlist from the iov_iter. + // + sg_count = RdmaInfo_iovToSglist(iter, sglist); + if (unlikely(sg_count < 0)) + { + printk_fhgfs(KERN_ERR, "%s: can't convert iov_iter to scatterlist\n", __func__); + status = -EIO; + goto error_return; + } + + // + // DMA map all of the pages. + // + device = RDMASocket_getDevice(rs); + key = RDMASocket_getRkey(rs); + + count = nvfs_ops->nvfs_dma_map_sg_attrs(device->dma_device, sglist, sg_count, + dma_dir, DMA_ATTR_NO_WARN); + if (unlikely(count != sg_count)) + { + if (count == NVFS_CPU_REQ) + { + printk_fhgfs(KERN_ERR, "%s: NVFS_CPU_REQ\n", __func__); + status = 0; + } + else if (count == NVFS_IO_ERR) + { + printk_fhgfs(KERN_ERR, "%s: can't DMA map mixed CPU/GPU pages\n", __func__); + status = -EINVAL; + } + else + { + printk_fhgfs(KERN_ERR, "%s: unknown error returned from NVFS (%d)\n", __func__, count); + status = -EIO; + } + goto error_return; + } + + // + // Coalesce the scatterlist. + // + dma_count = RdmaInfo_coalesceSglist(sglist, dmalist, count); + if (unlikely(dma_count > RDMA_MAX_DMA_COUNT)) + { + printk_fhgfs(KERN_ERR, "%s: too many DMA elements count=%d max=%d\n", __func__, + dma_count, RDMA_MAX_DMA_COUNT); + status = -EIO; + goto error_return; + } + + // + // Fill in the rdma info. + // + rdmap->dma_count = dma_count; + rdmap->sg_count = sg_count; + rdmap->tag = 0x00000000; + rdmap->device = device; + rdmap->key = key; + rdmap->sglist = sglist; + rdmap->dmalist = dmalist; + +#ifdef BEEGFS_DEBUG_RDMA + RdmaInfo_dumpIovIter(iter); + RdmaInfo_dumpSgtable("MAP", rdmap->dmalist, rdmap->dma_count); + RdmaInfo_dumpRdmaInfo(rdmap); +#endif /* BEEGFS_DEBUG_RDMA */ + + return rdmap; + +error_return: + if (sglist) + { + RdmaInfo_putPages(sglist, sg_count); + kfree(sglist); + } + if (dmalist) + kfree(dmalist); + if (rdmap) + kfree(rdmap); + return (status == 0) ? NULL : ERR_PTR(status); +} + +RdmaInfo* RdmaInfo_mapRead(const struct iov_iter *iter, Socket *socket) +{ + return RdmaInfo_map(iter, socket, DMA_FROM_DEVICE); +} + +RdmaInfo* RdmaInfo_mapWrite(const struct iov_iter *iter, Socket *socket) +{ + return RdmaInfo_map(iter, socket, DMA_TO_DEVICE); +} + +/* + * RdmaInfo_unmap - Unmap GPU buffers for RDMA operations. + * @rdmap: RdmaInfo created by RdmaInfo_map (see above) + * @dma_dir: read (DMA_FROM_DEVICE) or write (DMA_TO_DEVICE) + */ +static inline void RdmaInfo_unmap(RdmaInfo *rdmap, enum dma_data_direction dma_dir) +{ + if (rdmap->sglist) + { + if (rdmap->dmalist) + { + nvfs_ops->nvfs_dma_unmap_sg(rdmap->device->dma_device, rdmap->sglist, rdmap->sg_count, dma_dir); + RdmaInfo_putPages(rdmap->sglist, rdmap->sg_count); + kfree(rdmap->dmalist); + } + kfree(rdmap->sglist); + } + kfree(rdmap); +} + +void RdmaInfo_unmapRead(RdmaInfo *rdmap) +{ + RdmaInfo_unmap(rdmap, DMA_FROM_DEVICE); +} + +void RdmaInfo_unmapWrite(RdmaInfo *rdmap) +{ + RdmaInfo_unmap(rdmap, DMA_TO_DEVICE); +} + +#endif /* BEEGFS_NVFS */ diff --git a/client_module/source/common/storage/RdmaInfo.h b/client_module/source/common/storage/RdmaInfo.h new file mode 100644 index 0000000..34bdf5b --- /dev/null +++ b/client_module/source/common/storage/RdmaInfo.h @@ -0,0 +1,117 @@ +#ifndef _RDMAINFO_H_ +#define _RDMAINFO_H_ +#ifdef BEEGFS_NVFS + +#include "linux/uio.h" +#include +#include +#include + +#define RDMA_MAX_DMA_COUNT 64 + +struct RdmaInfo +{ + size_t sg_count; + size_t dma_count; + uint32_t tag; + uint64_t key; + struct ib_device *device; + void *pages; + struct scatterlist *sglist; + struct scatterlist *dmalist; +}; +typedef struct RdmaInfo RdmaInfo; + +static inline void RdmaInfo_serialize(SerializeCtx* ctx, RdmaInfo *rdmap) +{ + int i = 0; + struct scatterlist *sgp; + + if (rdmap != NULL) + { + Serialization_serializeUInt64(ctx, rdmap->dma_count); + + if (rdmap->dma_count > 0) + { + Serialization_serializeUInt(ctx, rdmap->tag); + Serialization_serializeUInt64(ctx, rdmap->key); + for (i = 0, sgp = rdmap->dmalist; i < rdmap->dma_count; i++, sgp++) + { + Serialization_serializeUInt64(ctx, sg_dma_address(sgp)); + Serialization_serializeUInt64(ctx, (uint64_t)sgp->length); + Serialization_serializeUInt64(ctx, (uint64_t)sgp->offset); + } + } + } + else + { + Serialization_serializeUInt64(ctx, 0ull); + } +} + +bool RdmaInfo_acquireNVFS(void); +void RdmaInfo_releaseNVFS(void); +int RdmaInfo_detectNVFSRequest(DevicePriorityContext* dpctx, + const struct iov_iter *iter); +static inline int RdmaInfo_nvfsDevicePriority(struct ib_device* dev, + int gpuIndex); + +RdmaInfo* RdmaInfo_mapRead(const struct iov_iter *iter, Socket *socket); +RdmaInfo* RdmaInfo_mapWrite(const struct iov_iter *iter, Socket *socket); +void RdmaInfo_unmapRead(RdmaInfo *rdmap); +void RdmaInfo_unmapWrite(RdmaInfo *rdmap); + +#ifdef BEEGFS_DEBUG_RDMA +static inline void RdmaInfo_dumpIovIter(const struct iov_iter *iter) +{ + int i = 0; + struct iov_iter iter_copy = *iter; + + printk(KERN_ALERT "IOV_ITER : count=%ld", iov_iter_count(&iter_copy)); + while (iov_iter_count(&iter_copy) > 0) + { + printk(KERN_INFO " %3d: %px %ld", i, iter_iov_addr(&iter_copy), iter_iov_len(&iter_copy)); + iov_iter_advance(&iter_copy, iter_iov_len(&iter_copy)); + } +} + +static inline void RdmaInfo_dumpSgtable(const char *header, + struct scatterlist *sglist, size_t sg_count) +{ + int i = 0; + struct scatterlist *sgp = NULL; + + printk_fhgfs(KERN_INFO, "%-12s : (%ld entries)", header, sg_count); + for (i = 0, sgp = sglist; i < sg_count; i++, sgp++) + { + printk_fhgfs(KERN_INFO, " [%3d] = %016llx %d %d %d", + i, + sg_dma_address(sgp), + sg_dma_len(sgp), + sgp->length, + sgp->offset); + } +} + +static inline void RdmaInfo_dumpRdmaInfo(RdmaInfo *rdmap) +{ + if (rdmap != NULL) + { + printk_fhgfs(KERN_INFO, "RDMA :\n" + " NENTS = %ld\n" + " TAG = %x\n" + " KEY = %llx\n", + rdmap->dma_count, rdmap->tag, rdmap->key); + RdmaInfo_dumpSgtable("SGLIST", rdmap->sglist, rdmap->sg_count); + RdmaInfo_dumpSgtable("DMALIST", rdmap->dmalist, rdmap->dma_count); + } +} +#endif /* BEEGFS_DEBUG_RDMA */ + +int RdmaInfo_nvfsDevicePriority(struct ib_device* dev, int gpuIndex) +{ + return nvfs_ops->nvfs_device_priority(dev->dma_device, gpuIndex); +} + +#endif /* BEEGFS_NVFS */ +#endif /* _RDMAINFO_H_ */ diff --git a/client_module/source/common/storage/StatData.c b/client_module/source/common/storage/StatData.c new file mode 100644 index 0000000..2767f18 --- /dev/null +++ b/client_module/source/common/storage/StatData.c @@ -0,0 +1,62 @@ +/* + * Information provided by stat() + */ + +#include +#include "StatData.h" + +bool StatData_deserialize(DeserializeCtx* ctx, StatData* outThis) +{ + // flags + if(!Serialization_deserializeUInt(ctx, &outThis->flags) ) + return false; + + // mode + if(!Serialization_deserializeInt(ctx, &outThis->settableFileAttribs.mode) ) + return false; + + // sumChunkBlocks + if(!Serialization_deserializeUInt64(ctx, &outThis->numBlocks) ) + return false; + + // creationTime + if(!Serialization_deserializeInt64(ctx, &outThis->creationTimeSecs) ) + return false; + + // aTime + if(!Serialization_deserializeInt64(ctx, &outThis->settableFileAttribs.lastAccessTimeSecs) ) + return false; + + // mtime + if(!Serialization_deserializeInt64(ctx, &outThis->settableFileAttribs.modificationTimeSecs) ) + return false; + + // ctime + if(!Serialization_deserializeInt64(ctx, &outThis->attribChangeTimeSecs) ) + return false; + + // fileSize + if(!Serialization_deserializeInt64(ctx, &outThis->fileSize) ) + return false; + + // nlink + if(!Serialization_deserializeUInt(ctx, &outThis->nlink) ) + return false; + + // metaVersion + if(!Serialization_deserializeUInt(ctx, &outThis->metaVersion) ) + return false; + + // uid + if(!Serialization_deserializeUInt(ctx, &outThis->settableFileAttribs.userID) ) + return false; + + // gid + if(!Serialization_deserializeUInt(ctx, &outThis->settableFileAttribs.groupID) ) + return false; + + return true; +} + + + diff --git a/client_module/source/common/storage/StatData.h b/client_module/source/common/storage/StatData.h new file mode 100644 index 0000000..ddc52e8 --- /dev/null +++ b/client_module/source/common/storage/StatData.h @@ -0,0 +1,61 @@ +/* + * Information provided by stat() + */ + +#ifndef STATDATA_H_ +#define STATDATA_H_ + +#include +#include + +#define STATDATA_FEATURE_SPARSE_FILE 1 + +// stat blocks are 512 bytes, ">> 9" is then the same as "/ 512", but faster +#define STATDATA_SIZETOBLOCKSBIT_SHIFT 9 + + +struct StatData; +typedef struct StatData StatData; + +extern bool StatData_deserialize(DeserializeCtx* ctx, StatData* outThis); + +static inline void StatData_getOsStat(StatData* this, fhgfs_stat* outOsStat); + +struct StatData +{ + unsigned flags; + + int64_t fileSize; + uint64_t numBlocks; + int64_t creationTimeSecs; // real creation time + int64_t attribChangeTimeSecs; // this corresponds to unix ctime + unsigned nlink; + + SettableFileAttribs settableFileAttribs; + + unsigned metaVersion; // inc'ed when internal metadata is modified (for cache invalidation) + +}; + +void StatData_getOsStat(StatData* this, fhgfs_stat* outOsStat) +{ + outOsStat->mode = this->settableFileAttribs.mode; + outOsStat->size = this->fileSize; + outOsStat->blocks = this->numBlocks; + + outOsStat->uid = this->settableFileAttribs.userID; + outOsStat->gid = this->settableFileAttribs.groupID; + outOsStat->nlink = this->nlink; + + outOsStat->atime.tv_sec = this->settableFileAttribs.lastAccessTimeSecs; + outOsStat->atime.tv_nsec = 0; + + outOsStat->mtime.tv_sec = this->settableFileAttribs.modificationTimeSecs; + outOsStat->mtime.tv_nsec = 0; + + outOsStat->ctime.tv_sec = this->attribChangeTimeSecs; + outOsStat->ctime.tv_nsec = 0; + outOsStat->metaVersion = this->metaVersion; +} + +#endif /* STATDATA_H_ */ diff --git a/client_module/source/common/storage/StorageDefinitions.h b/client_module/source/common/storage/StorageDefinitions.h new file mode 100644 index 0000000..fcaa806 --- /dev/null +++ b/client_module/source/common/storage/StorageDefinitions.h @@ -0,0 +1,98 @@ +#ifndef STORAGEDEFINITIONS_H_ +#define STORAGEDEFINITIONS_H_ + +/* + * Remember to keep these definitions in sync with StorageDefinitions.h of fhgfs_common!!! + */ + +#include + + +// open rw flags +#define OPENFILE_ACCESS_READ 1 +#define OPENFILE_ACCESS_WRITE 2 +#define OPENFILE_ACCESS_READWRITE 4 +// open extra flags +#define OPENFILE_ACCESS_APPEND 8 +#define OPENFILE_ACCESS_TRUNC 16 +#define OPENFILE_ACCESS_DIRECT 32 /* for direct IO */ +#define OPENFILE_ACCESS_SYNC 64 /* for sync'ed IO */ +// open masks +#define OPENFILE_ACCESS_MASK_RW \ + (OPENFILE_ACCESS_READ | OPENFILE_ACCESS_WRITE | OPENFILE_ACCESS_READWRITE) +#define OPENFILE_ACCESS_MASK_EXTRA (~OPENFILE_ACCESS_MASK_RW) + + +// set attribs flags +#define SETATTR_CHANGE_MODE 1 +#define SETATTR_CHANGE_USERID 2 +#define SETATTR_CHANGE_GROUPID 4 +#define SETATTR_CHANGE_MODIFICATIONTIME 8 +#define SETATTR_CHANGE_LASTACCESSTIME 16 + + +// entry and append lock type flags +#define ENTRYLOCKTYPE_UNLOCK 1 +#define ENTRYLOCKTYPE_EXCLUSIVE 2 +#define ENTRYLOCKTYPE_SHARED 4 +#define ENTRYLOCKTYPE_NOWAIT 8 /* operation may not block if lock not available */ +#define ENTRYLOCKTYPE_CANCEL 16 /* cancel all granted and pending locks for given handle + (normally on client file close) */ +// entry lock mask +#define ENTRYLOCKTYPE_LOCKOPS_ADD (ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED) +#define ENTRYLOCKTYPE_LOCKOPS_REMOVE (ENTRYLOCKTYPE_UNLOCK | ENTRYLOCKTYPE_CANCEL) + + +struct SettableFileAttribs; +typedef struct SettableFileAttribs SettableFileAttribs; + + +enum DirEntryType +{ // don't use these directly, use the DirEntryType_IS...() macros below to check entry types + DirEntryType_INVALID = 0, DirEntryType_DIRECTORY = 1, DirEntryType_REGULARFILE = 2, + DirEntryType_SYMLINK = 3, DirEntryType_BLOCKDEV = 4, DirEntryType_CHARDEV = 5, + DirEntryType_FIFO = 6, DirEntryType_SOCKET = 7 +}; +typedef enum DirEntryType DirEntryType; + +#define DirEntryType_ISVALID(dirEntryType) (dirEntryType != DirEntryType_INVALID) +#define DirEntryType_ISDIR(dirEntryType) (dirEntryType == DirEntryType_DIRECTORY) +#define DirEntryType_ISREGULARFILE(dirEntryType) (dirEntryType == DirEntryType_REGULARFILE) +#define DirEntryType_ISSYMLINK(dirEntryType) (dirEntryType == DirEntryType_SYMLINK) +#define DirEntryType_ISBLOCKDEV(dirEntryType) (dirEntryType == DirEntryType_BLOCKDEV) +#define DirEntryType_ISCHARDEV(dirEntryType) (dirEntryType == DirEntryType_CHARDEV) +#define DirEntryType_ISFIFO(dirEntryType) (dirEntryType == DirEntryType_FIFO) +#define DirEntryType_ISSOCKET(dirEntryType) (dirEntryType == DirEntryType_SOCKET) + +/* @return true for any kind of file, including symlinks and special files */ +#define DirEntryType_ISFILE(dirEntryType) ( (dirEntryType >= 2) && (dirEntryType <= 7) ) + + +// hint (but not strict enforcement) that the inode is inlined into the dentry +#define STATFLAG_HINT_INLINE 1 + + + + +enum EntryLockRequestType + {EntryLockRequestType_ENTRYFLOCK=0, EntryLockRequestType_RANGEFLOCK=1, + EntryLockRequestType_ENTRYCOHERENCE=2, EntryLockRequestType_RANGECOHERENCE=3}; +typedef enum EntryLockRequestType EntryLockRequestType; + + + +/** + * Note: Typically used in combination with a value of SETATTR_CHANGE_...-Flags to determine which + * of the fields are actually used + */ +struct SettableFileAttribs +{ + int mode; + unsigned userID; + unsigned groupID; + int64_t modificationTimeSecs; // unix mtime + int64_t lastAccessTimeSecs; // unix atime +}; + + +#endif /*STORAGEDEFINITIONS_H_*/ diff --git a/client_module/source/common/storage/StorageErrors.c b/client_module/source/common/storage/StorageErrors.c new file mode 100644 index 0000000..56203ce --- /dev/null +++ b/client_module/source/common/storage/StorageErrors.c @@ -0,0 +1,93 @@ +#include +#include + + +#define __FHGFSOPS_ERRLIST_SIZE \ + ( (sizeof(__FHGFSOPS_ERRLIST) ) / (sizeof(struct FhgfsOpsErrListEntry) ) - 1) + /* -1 because last elem is NULL */ + + + +// Note: This is based on the FhgfsOpsErr entries +// Note: We use EREMOTEIO as a generic error here +struct FhgfsOpsErrListEntry const __FHGFSOPS_ERRLIST[] = +{ + {"Success", 0}, // FhgfsOpsErr_SUCCESS + {"Internal error", EREMOTEIO}, // FhgfsOpsErr_INTERNAL + {"Interrupted system call", EINTR}, // FhgfsOpsErr_INTERRUPTED + {"Communication error", ECOMM}, // FhgfsOpsErr_COMMUNICATION + {"Communication timeout", ETIMEDOUT}, // FhgfsOpsErr_COMMTIMEDOUT + {"Unknown node", EREMOTEIO}, // FhgfsOpsErr_UNKNOWNNODE + {"Node is not owner of entry", EREMOTEIO}, // FhgfsOpsErr_NOTOWNER + {"Entry exists already", EEXIST}, // FhgfsOpsErr_EXISTS + {"Path does not exist", ENOENT}, // FhgfsOpsErr_PATHNOTEXISTS + {"Entry is in use", EBUSY}, // FhgfsOpsErr_INUSE + {"Dynamic attributes of entry are outdated", EREMOTEIO}, // FhgfsOpsErr_INUSE + {"Removed", 999}, // former FhgfsOpsErr_PARENTTOSUBDIR, not used + {"Entry is not a directory", ENOTDIR}, // FhgfsOpsErr_NOTADIR + {"Directory is not empty", ENOTEMPTY}, // FhgfsOpsErr_NOTEMPTY + {"No space left", ENOSPC}, // FhgfsOpsErr_NOSPACE + {"Unknown storage target", EREMOTEIO}, // FhgfsOpsErr_UNKNOWNTARGET + {"Operation would block", EWOULDBLOCK}, // FhgfsOpsErr_WOULDBLOCK + {"Inode not inlined", EREMOTEIO}, // FhgfsOpsErr_INODENOTINLINED + {"Underlying file system error", EREMOTEIO}, // FhgfsOpsErr_SAVEERROR + {"Argument too large", EFBIG}, // FhgfsOpsErr_TOOBIG + {"Invalid argument", EINVAL}, // FhgfsOpsErr_INVAL + {"Bad memory address", EFAULT}, // FhgfsOpsErr_ADDRESSFAULT + {"Try again", EAGAIN}, // FhgfsOpsErr_AGAIN + {"Potential cache loss for open file handle. (Server crash detected.)" , EREMOTEIO}, /* + FhgfsOpsErr_STORAGE_SRV_CRASHED*/ + {"Permission denied", EPERM}, // FhgfsOpsErr_PERM + {"Quota exceeded", EDQUOT}, // FhgfsOpsErr_DQUOT + {"Out of memory", ENOMEM}, // FhgfsOpsErr_OUTOFMEM + {"Numerical result out of range", ERANGE}, // FhgfsOpsErr_RANGE + {"No data available", ENODATA}, // FhgfsOpsErr_NODATA + {"Operation not supported", EOPNOTSUPP}, // FhgfsOpsErr_NOTSUPP + {"Argument list too long", E2BIG}, // FhgfsOpsErr_TOOLONG + {"Metadata version mismatch", ESTALE}, // FhgfsOpsErr_METAVERSIONMISMATCH + {"Inode is locked", EBUSY}, // FhgfsOpsErr_INODELOCKED + {"File access denied by state restrictions", EWOULDBLOCK}, // FhgfsOpsErr_FILEACCESS_DENIED + {NULL, 0} +}; + + + +/** + * @return static human-readable error string + */ +const char* FhgfsOpsErr_toErrString(FhgfsOpsErr errCode) +{ + size_t unsignedErrCode = (size_t)errCode; + + if(likely(unsignedErrCode < __FHGFSOPS_ERRLIST_SIZE) ) + return __FHGFSOPS_ERRLIST[unsignedErrCode].errString; + +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_WARNING, "Unknown errCode given to FhgfsOpsErr_toErrString(): %d/%u " + "(dumping stack...)\n", (int)errCode, (unsigned)errCode); + + dump_stack(); +#endif + + return "Unknown error code"; +} + +/** + * @return negative linux error code + */ +int FhgfsOpsErr_toSysErr(FhgfsOpsErr errCode) +{ + size_t unsignedErrCode = (size_t)errCode; + + if(likely(unsignedErrCode < __FHGFSOPS_ERRLIST_SIZE) ) + return -(__FHGFSOPS_ERRLIST[errCode].sysErr); + +#ifdef BEEGFS_DEBUG + printk_fhgfs(KERN_WARNING, "Unknown errCode given to FhgfsOpsErr_toSysErr(): %d/%u " + "(dumping stack...)\n", (int)errCode, (unsigned)errCode); + + dump_stack(); +#endif + + return -EPERM; +} diff --git a/client_module/source/common/storage/StorageErrors.h b/client_module/source/common/storage/StorageErrors.h new file mode 100644 index 0000000..9cf07ef --- /dev/null +++ b/client_module/source/common/storage/StorageErrors.h @@ -0,0 +1,79 @@ +#ifndef STORAGEERRORS_H_ +#define STORAGEERRORS_H_ + +/* + * Remember to keep these definitions in sync with StorageErrors.h of fhgfs_common! + */ + + +#include + + +// Note: keep this above __FHGFSOPS_ERRLIST declaration +struct FhgfsOpsErrListEntry +{ + const char* errString; // human-readable error string + int sysErr; // positive linux system error code +}; + + +extern struct FhgfsOpsErrListEntry const __FHGFSOPS_ERRLIST[]; + + +/** + * Note: Remember to keep this in sync with FHGFSOPS_ERRLIST + * + * Note: We need the negative dummy (-1) because some return values (like CommKit) cast this enum to + * negative int64_t and this leads bad (positive) values when the enum isn't signed. So the dummy + * forces the compiler to make the enum a signed variable. + */ +enum FhgfsOpsErr +{ + FhgfsOpsErr_DUMMY_DONTUSEME = -1, /* see comment above */ + FhgfsOpsErr_SUCCESS = 0, + FhgfsOpsErr_INTERNAL = 1, + FhgfsOpsErr_INTERRUPTED = 2, + FhgfsOpsErr_COMMUNICATION = 3, + FhgfsOpsErr_COMMTIMEDOUT = 4, + FhgfsOpsErr_UNKNOWNNODE = 5, + FhgfsOpsErr_NOTOWNER = 6, + FhgfsOpsErr_EXISTS = 7, + FhgfsOpsErr_PATHNOTEXISTS = 8, + FhgfsOpsErr_INUSE = 9, + FhgfsOpsErr_DYNAMICATTRIBSOUTDATED = 10, + FhgfsOpsErr_PARENTTOSUBDIR = 11, + FhgfsOpsErr_NOTADIR = 12, + FhgfsOpsErr_NOTEMPTY = 13, + FhgfsOpsErr_NOSPACE = 14, + FhgfsOpsErr_UNKNOWNTARGET = 15, + FhgfsOpsErr_WOULDBLOCK = 16, + FhgfsOpsErr_INODENOTINLINED = 17, // inode is not inlined into the dentry + FhgfsOpsErr_SAVEERROR = 18, // saving to the underlying file system failed + FhgfsOpsErr_TOOBIG = 19, // corresponds to EFBIG + FhgfsOpsErr_INVAL = 20, // corresponds to EINVAL + FhgfsOpsErr_ADDRESSFAULT = 21, // corresponds to EFAULT + FhgfsOpsErr_AGAIN = 22, // corresponds to EAGAIN + FhgfsOpsErr_STORAGE_SRV_CRASHED = 23, /* Potential cache loss for open file handle. + (Server crash detected.)*/ + FhgfsOpsErr_PERM = 24, // corresponds to EPERM + FhgfsOpsErr_DQUOT = 25, // corresponds to EDQUOT (quota exceeded) + FhgfsOpsErr_OUTOFMEM = 26, // corresponds to ENOMEM (mem allocation failed) + FhgfsOpsErr_RANGE = 27, // corresponds to ERANGE (needed for xattrs) + FhgfsOpsErr_NODATA = 28, // corresponds to ENODATA==ENOATTR (xattr not found) + FhgfsOpsErr_NOTSUPP = 29, // corresponds to EOPNOTSUPP + FhgfsOpsErr_TOOLONG = 30, // corresponds to E2BIG (needed for xattrs) + FhgfsOpsErr_METAVERSIONMISMATCH = 31, // metadata versions do not match, needed for cache invalidation + FhgfsOpsErr_INODELOCKED = 32, // inode is locked, needed for GlobalInodeLock store + FhgfsOpsErr_FILEACCESS_DENIED = 33, // file access denied due to current file state restrictions +}; +typedef enum FhgfsOpsErr FhgfsOpsErr; + + + +extern const char* FhgfsOpsErr_toErrString(FhgfsOpsErr fhgfsOpsErr); +extern int FhgfsOpsErr_toSysErr(FhgfsOpsErr fhgfsOpsErr); + + + + +#endif /*STORAGEERRORS_H_*/ diff --git a/client_module/source/common/storage/StoragePoolId.c b/client_module/source/common/storage/StoragePoolId.c new file mode 100644 index 0000000..ceb1aec --- /dev/null +++ b/client_module/source/common/storage/StoragePoolId.c @@ -0,0 +1,16 @@ +#include "StoragePoolId.h" + +#include + +void StoragePoolId_serialize(SerializeCtx* ctx, const StoragePoolId* this) +{ + Serialization_serializeUShort(ctx, this->value); +} + +bool StoragePoolId_deserialize(DeserializeCtx* ctx, StoragePoolId* outThis) +{ + if(!Serialization_deserializeUShort(ctx, &(outThis->value) ) ) + return false; + + return true; +} diff --git a/client_module/source/common/storage/StoragePoolId.h b/client_module/source/common/storage/StoragePoolId.h new file mode 100644 index 0000000..6b43e75 --- /dev/null +++ b/client_module/source/common/storage/StoragePoolId.h @@ -0,0 +1,37 @@ +#ifndef CLIENT_STORAGEPOOLID_H +#define CLIENT_STORAGEPOOLID_H + +#include +#include + +// keep in sync with values from server's StoragePoolStore +#define STORAGEPOOLID_INVALIDPOOLID 0 + +// Note: this must always be in sync with server's StoragePoolId! +struct StoragePoolId; +typedef struct StoragePoolId StoragePoolId; + +struct StoragePoolId +{ + uint16_t value; +}; + +static inline void StoragePoolId_set(StoragePoolId* this, uint16_t value) +{ + this->value = value; +} + +static inline bool StoragePoolId_compare(const StoragePoolId* this, const StoragePoolId* other) +{ + return (this->value == other->value); +} + +static inline char* StoragePoolId_str(const StoragePoolId* this) +{ + return StringTk_uintToStr(this->value); +} + +extern void StoragePoolId_serialize(SerializeCtx* ctx, const StoragePoolId* this); +extern bool StoragePoolId_deserialize(DeserializeCtx* ctx, StoragePoolId* outThis); + +#endif /* CLIENT_STORAGEPOOLID_H */ diff --git a/client_module/source/common/storage/striping/BuddyMirrorPattern.c b/client_module/source/common/storage/striping/BuddyMirrorPattern.c new file mode 100644 index 0000000..326633d --- /dev/null +++ b/client_module/source/common/storage/striping/BuddyMirrorPattern.c @@ -0,0 +1,70 @@ +#include +#include "BuddyMirrorPattern.h" + +bool BuddyMirrorPattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + RawList mirrorBuddyGroupIDsVec; + + // defaultNumTargets + if(!Serialization_deserializeUInt(ctx, &thisCast->defaultNumTargets) ) + return false; + + // mirrorBuddyGroupIDs + if(!Serialization_deserializeUInt16VecPreprocess(ctx, &mirrorBuddyGroupIDsVec) ) + return false; + + if(!Serialization_deserializeUInt16Vec(&mirrorBuddyGroupIDsVec, &thisCast->mirrorBuddyGroupIDs) ) + return false; + + // check mirrorBuddyGroupIDs + if(!UInt16Vec_length(&thisCast->mirrorBuddyGroupIDs) ) + return false; + + return true; +} + +size_t BuddyMirrorPattern_getStripeTargetIndex(StripePattern* this, int64_t pos) +{ + struct BuddyMirrorPattern* p = container_of(this, struct BuddyMirrorPattern, stripePattern); + + return (pos / this->chunkSize) % UInt16Vec_length(&p->mirrorBuddyGroupIDs); +} + +uint16_t BuddyMirrorPattern_getStripeTargetID(StripePattern* this, int64_t pos) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + size_t targetIndex = BuddyMirrorPattern_getStripeTargetIndex(this, pos); + + return UInt16Vec_at(&thisCast->mirrorBuddyGroupIDs, targetIndex); +} + +void BuddyMirrorPattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + ListTk_copyUInt16ListToVec( (UInt16List*)&thisCast->mirrorBuddyGroupIDs, outTargetIDs); +} + +UInt16Vec* BuddyMirrorPattern_getStripeTargetIDs(StripePattern* this) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + return &thisCast->mirrorBuddyGroupIDs; +} + +unsigned BuddyMirrorPattern_getMinNumTargets(StripePattern* this) +{ + return 1; +} + +unsigned BuddyMirrorPattern_getDefaultNumTargets(StripePattern* this) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + return thisCast->defaultNumTargets; +} + + diff --git a/client_module/source/common/storage/striping/BuddyMirrorPattern.h b/client_module/source/common/storage/striping/BuddyMirrorPattern.h new file mode 100644 index 0000000..1285072 --- /dev/null +++ b/client_module/source/common/storage/striping/BuddyMirrorPattern.h @@ -0,0 +1,124 @@ +#ifndef BUDDYMIRRORPATTERN_H_ +#define BUDDYMIRRORPATTERN_H_ + +#include +#include "StripePattern.h" + +struct BuddyMirrorPattern; +typedef struct BuddyMirrorPattern BuddyMirrorPattern; + + +static inline void BuddyMirrorPattern_init(BuddyMirrorPattern* this, + unsigned chunkSize, UInt16Vec* mirrorBuddyGroupIDs, unsigned defaultNumTargets); +static inline void BuddyMirrorPattern_initFromChunkSize(BuddyMirrorPattern* this, + unsigned chunkSize); +static inline BuddyMirrorPattern* BuddyMirrorPattern_construct( + unsigned chunkSize, UInt16Vec* mirrorBuddyGroupIDs, unsigned defaultNumTargets); +static inline BuddyMirrorPattern* BuddyMirrorPattern_constructFromChunkSize(unsigned chunkSize); +static inline void BuddyMirrorPattern_uninit(StripePattern* this); + +static inline void __BuddyMirrorPattern_assignVirtualFunctions(BuddyMirrorPattern* this); + +// virtual functions +extern bool BuddyMirrorPattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx); + +extern size_t BuddyMirrorPattern_getStripeTargetIndex(StripePattern* this, int64_t pos); +extern uint16_t BuddyMirrorPattern_getStripeTargetID(StripePattern* this, int64_t pos); +extern void BuddyMirrorPattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs); +extern UInt16Vec* BuddyMirrorPattern_getStripeTargetIDs(StripePattern* this); +extern unsigned BuddyMirrorPattern_getMinNumTargets(StripePattern* this); +extern unsigned BuddyMirrorPattern_getDefaultNumTargets(StripePattern* this); + + + +struct BuddyMirrorPattern +{ + StripePattern stripePattern; + + UInt16Vec mirrorBuddyGroupIDs; + unsigned defaultNumTargets; +}; + + +/** + * @param mirrorBuddyGroupIDs will be copied + * @param defaultNumTargets default number of targets (0 for app-level default) + */ +void BuddyMirrorPattern_init(BuddyMirrorPattern* this, + unsigned chunkSize, UInt16Vec* mirrorBuddyGroupIDs, unsigned defaultNumTargets) +{ + StripePattern_initFromPatternType( (StripePattern*)this, STRIPEPATTERN_BuddyMirror, chunkSize); + + // assign virtual functions + __BuddyMirrorPattern_assignVirtualFunctions(this); + + // init attribs + UInt16Vec_init(&this->mirrorBuddyGroupIDs); + + ListTk_copyUInt16ListToVec( (UInt16List*)mirrorBuddyGroupIDs, &this->mirrorBuddyGroupIDs); + + this->defaultNumTargets = defaultNumTargets ? defaultNumTargets : 4; +} + +/** + * Note: for deserialization only + */ +void BuddyMirrorPattern_initFromChunkSize(BuddyMirrorPattern* this, unsigned chunkSize) +{ + StripePattern_initFromPatternType( (StripePattern*)this, STRIPEPATTERN_BuddyMirror, chunkSize); + + // assign virtual functions + __BuddyMirrorPattern_assignVirtualFunctions(this); + + // init attribs + UInt16Vec_init(&this->mirrorBuddyGroupIDs); +} + +/** + * @param mirrorBuddyGroupIDs will be copied + * @param defaultNumTargets default number of targets (0 for app-level default) + */ +BuddyMirrorPattern* BuddyMirrorPattern_construct( + unsigned chunkSize, UInt16Vec* mirrorBuddyGroupIDs, unsigned defaultNumTargets) +{ + struct BuddyMirrorPattern* this = os_kmalloc(sizeof(*this) ); + + BuddyMirrorPattern_init(this, chunkSize, mirrorBuddyGroupIDs, defaultNumTargets); + + return this; +} + +/** + * Note: for deserialization only + */ +BuddyMirrorPattern* BuddyMirrorPattern_constructFromChunkSize(unsigned chunkSize) +{ + struct BuddyMirrorPattern* this = os_kmalloc(sizeof(*this) ); + + BuddyMirrorPattern_initFromChunkSize(this, chunkSize); + + return this; +} + +void BuddyMirrorPattern_uninit(StripePattern* this) +{ + BuddyMirrorPattern* thisCast = (BuddyMirrorPattern*)this; + + UInt16Vec_uninit(&thisCast->mirrorBuddyGroupIDs); +} + +void __BuddyMirrorPattern_assignVirtualFunctions(BuddyMirrorPattern* this) +{ + ( (StripePattern*)this)->uninit = BuddyMirrorPattern_uninit; + + ( (StripePattern*)this)->deserializePattern = BuddyMirrorPattern_deserializePattern; + + ( (StripePattern*)this)->getStripeTargetIndex = BuddyMirrorPattern_getStripeTargetIndex; + ( (StripePattern*)this)->getStripeTargetID = BuddyMirrorPattern_getStripeTargetID; + ( (StripePattern*)this)->getStripeTargetIDsCopy = BuddyMirrorPattern_getStripeTargetIDsCopy; + ( (StripePattern*)this)->getStripeTargetIDs = BuddyMirrorPattern_getStripeTargetIDs; + ( (StripePattern*)this)->getMinNumTargets = BuddyMirrorPattern_getMinNumTargets; + ( (StripePattern*)this)->getDefaultNumTargets = BuddyMirrorPattern_getDefaultNumTargets; +} + +#endif /*BUDDYMIRRORPATTERN_H_*/ diff --git a/client_module/source/common/storage/striping/Raid0Pattern.c b/client_module/source/common/storage/striping/Raid0Pattern.c new file mode 100644 index 0000000..7f01b77 --- /dev/null +++ b/client_module/source/common/storage/striping/Raid0Pattern.c @@ -0,0 +1,70 @@ +#include +#include "Raid0Pattern.h" + +bool Raid0Pattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + RawList targetIDsList; + + // defaultNumTargets + if(!Serialization_deserializeUInt(ctx, &thisCast->defaultNumTargets) ) + return false; + + // targetIDs + if(!Serialization_deserializeUInt16VecPreprocess(ctx, &targetIDsList) ) + return false; + + if(!Serialization_deserializeUInt16Vec(&targetIDsList, &thisCast->stripeTargetIDs) ) + return false; + + // check targetIDs + if(!UInt16Vec_length(&thisCast->stripeTargetIDs) ) + return false; + + return true; +} + +size_t Raid0Pattern_getStripeTargetIndex(StripePattern* this, int64_t pos) +{ + struct Raid0Pattern* p = container_of(this, struct Raid0Pattern, stripePattern); + + return (pos / this->chunkSize) % UInt16Vec_length(&p->stripeTargetIDs); +} + +uint16_t Raid0Pattern_getStripeTargetID(StripePattern* this, int64_t pos) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + size_t targetIndex = Raid0Pattern_getStripeTargetIndex(this, pos); + + return UInt16Vec_at(&thisCast->stripeTargetIDs, targetIndex); +} + +void Raid0Pattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + ListTk_copyUInt16ListToVec( (UInt16List*)&thisCast->stripeTargetIDs, outTargetIDs); +} + +UInt16Vec* Raid0Pattern_getStripeTargetIDs(StripePattern* this) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + return &thisCast->stripeTargetIDs; +} + +unsigned Raid0Pattern_getMinNumTargets(StripePattern* this) +{ + return 1; +} + +unsigned Raid0Pattern_getDefaultNumTargets(StripePattern* this) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + return thisCast->defaultNumTargets; +} + + diff --git a/client_module/source/common/storage/striping/Raid0Pattern.h b/client_module/source/common/storage/striping/Raid0Pattern.h new file mode 100644 index 0000000..180e958 --- /dev/null +++ b/client_module/source/common/storage/striping/Raid0Pattern.h @@ -0,0 +1,124 @@ +#ifndef RAID0PATTERN_H_ +#define RAID0PATTERN_H_ + +#include +#include "StripePattern.h" + +struct Raid0Pattern; +typedef struct Raid0Pattern Raid0Pattern; + + +static inline void Raid0Pattern_init(Raid0Pattern* this, + unsigned chunkSize, UInt16Vec* stripeTargetIDs, unsigned defaultNumTargets); +static inline void Raid0Pattern_initFromChunkSize(Raid0Pattern* this, unsigned chunkSize); +static inline Raid0Pattern* Raid0Pattern_construct( + unsigned chunkSize, UInt16Vec* stripeTargetIDs, unsigned defaultNumTargets); +static inline Raid0Pattern* Raid0Pattern_constructFromChunkSize(unsigned chunkSize); +static inline void Raid0Pattern_uninit(StripePattern* this); + +static inline void __Raid0Pattern_assignVirtualFunctions(Raid0Pattern* this); + +// virtual functions +extern bool Raid0Pattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx); + +extern size_t Raid0Pattern_getStripeTargetIndex(StripePattern* this, int64_t pos); +extern uint16_t Raid0Pattern_getStripeTargetID(StripePattern* this, int64_t pos); +extern void Raid0Pattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs); +extern UInt16Vec* Raid0Pattern_getStripeTargetIDs(StripePattern* this); +extern unsigned Raid0Pattern_getMinNumTargets(StripePattern* this); +extern unsigned Raid0Pattern_getDefaultNumTargets(StripePattern* this); + + + +struct Raid0Pattern +{ + StripePattern stripePattern; + + UInt16Vec stripeTargetIDs; + unsigned defaultNumTargets; +}; + + +/** + * @param stripeTargetIDs will be copied + * @param defaultNumTargets default number of targets (0 for app-level default) + */ +void Raid0Pattern_init(Raid0Pattern* this, + unsigned chunkSize, UInt16Vec* stripeTargetIDs, unsigned defaultNumTargets) +{ + StripePattern_initFromPatternType( (StripePattern*)this, STRIPEPATTERN_Raid0, chunkSize); + + // assign virtual functions + __Raid0Pattern_assignVirtualFunctions(this); + + // init attribs + UInt16Vec_init(&this->stripeTargetIDs); + + ListTk_copyUInt16ListToVec( (UInt16List*)stripeTargetIDs, &this->stripeTargetIDs); + + this->defaultNumTargets = defaultNumTargets ? defaultNumTargets : 4; + +} + +/** + * Note: for deserialization only + */ +void Raid0Pattern_initFromChunkSize(Raid0Pattern* this, unsigned chunkSize) +{ + StripePattern_initFromPatternType( (StripePattern*)this, STRIPEPATTERN_Raid0, chunkSize); + + // assign virtual functions + __Raid0Pattern_assignVirtualFunctions(this); + + // init attribs + UInt16Vec_init(&this->stripeTargetIDs); +} + +/** + * @param stripeTargetIDs will be copied + * @param defaultNumTargets default number of targets (0 for app-level default) + */ +Raid0Pattern* Raid0Pattern_construct( + unsigned chunkSize, UInt16Vec* stripeTargetIDs, unsigned defaultNumTargets) +{ + struct Raid0Pattern* this = os_kmalloc(sizeof(*this) ); + + Raid0Pattern_init(this, chunkSize, stripeTargetIDs, defaultNumTargets); + + return this; +} + +/** + * Note: for deserialization only + */ +Raid0Pattern* Raid0Pattern_constructFromChunkSize(unsigned chunkSize) +{ + struct Raid0Pattern* this = os_kmalloc(sizeof(*this) ); + + Raid0Pattern_initFromChunkSize(this, chunkSize); + + return this; +} + +void Raid0Pattern_uninit(StripePattern* this) +{ + Raid0Pattern* thisCast = (Raid0Pattern*)this; + + UInt16Vec_uninit(&thisCast->stripeTargetIDs); +} + +void __Raid0Pattern_assignVirtualFunctions(Raid0Pattern* this) +{ + ( (StripePattern*)this)->uninit = Raid0Pattern_uninit; + + ( (StripePattern*)this)->deserializePattern = Raid0Pattern_deserializePattern; + + ( (StripePattern*)this)->getStripeTargetIndex = Raid0Pattern_getStripeTargetIndex; + ( (StripePattern*)this)->getStripeTargetID = Raid0Pattern_getStripeTargetID; + ( (StripePattern*)this)->getStripeTargetIDsCopy = Raid0Pattern_getStripeTargetIDsCopy; + ( (StripePattern*)this)->getStripeTargetIDs = Raid0Pattern_getStripeTargetIDs; + ( (StripePattern*)this)->getMinNumTargets = Raid0Pattern_getMinNumTargets; + ( (StripePattern*)this)->getDefaultNumTargets = Raid0Pattern_getDefaultNumTargets; +} + +#endif /*RAID0PATTERN_H_*/ diff --git a/client_module/source/common/storage/striping/Raid10Pattern.c b/client_module/source/common/storage/striping/Raid10Pattern.c new file mode 100644 index 0000000..b86bf61 --- /dev/null +++ b/client_module/source/common/storage/striping/Raid10Pattern.c @@ -0,0 +1,113 @@ +#include +#include "Raid10Pattern.h" + +bool Raid10Pattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + RawList targetIDsList; + RawList mirrorTargetIDsList; + + // defaultNumTargets + if(!Serialization_deserializeUInt(ctx, &thisCast->defaultNumTargets) ) + return false; + + // targetIDs + if(!Serialization_deserializeUInt16VecPreprocess(ctx, &targetIDsList) ) + return false; + + if(!Serialization_deserializeUInt16Vec(&targetIDsList, &thisCast->stripeTargetIDs) ) + return false; + + // mirrorTargetIDs + if(!Serialization_deserializeUInt16VecPreprocess(ctx, &mirrorTargetIDsList) ) + return false; + + if(!Serialization_deserializeUInt16Vec(&mirrorTargetIDsList, &thisCast->mirrorTargetIDs) ) + return false; + + // calc stripeSetSize + thisCast->stripeSetSize = UInt16Vec_length( + &thisCast->stripeTargetIDs) * StripePattern_getChunkSize(this); + + return true; +} + +size_t Raid10Pattern_getStripeTargetIndex(StripePattern* this, int64_t pos) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + /* the code below is an optimization (wrt division/modulo) of following the two lines: + int64_t stripeSetInnerOffset = pos % thisCast->stripeSetSize; + int64_t targetIndex = stripeSetInnerOffset / StripePattern_getChunkSize(this); */ + + // note: do_div(n64, base32) assigns the result to n64 and returns the remainder! + // (do_div is needed for 64bit division on 32bit archs) + + unsigned stripeSetSize = thisCast->stripeSetSize; + + int64_t stripeSetInnerOffset; + unsigned chunkSize; + size_t targetIndex; + + if(MathTk_isPowerOfTwo(stripeSetSize) ) + { // quick path => no modulo needed + stripeSetInnerOffset = pos & (stripeSetSize - 1); + } + else + { // slow path => modulo + stripeSetInnerOffset = do_div(pos, thisCast->stripeSetSize); + + // warning: do_div modifies pos! (so do not use it afterwards within this method) + } + + chunkSize = StripePattern_getChunkSize(this); + + // this is "a=b/c" written as "a=b>>log2(c)", because chunkSize is a power of two. + targetIndex = (stripeSetInnerOffset >> MathTk_log2Int32(chunkSize) ); + + return targetIndex; +} + +uint16_t Raid10Pattern_getStripeTargetID(StripePattern* this, int64_t pos) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + size_t targetIndex = Raid10Pattern_getStripeTargetIndex(this, pos); + + return UInt16Vec_at(&thisCast->stripeTargetIDs, targetIndex); +} + +void Raid10Pattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + ListTk_copyUInt16ListToVec( (UInt16List*)&thisCast->stripeTargetIDs, outTargetIDs); +} + +UInt16Vec* Raid10Pattern_getStripeTargetIDs(StripePattern* this) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + return &thisCast->stripeTargetIDs; +} + +UInt16Vec* Raid10Pattern_getMirrorTargetIDs(StripePattern* this) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + return &thisCast->mirrorTargetIDs; +} + +unsigned Raid10Pattern_getMinNumTargets(StripePattern* this) +{ + return 2; +} + +unsigned Raid10Pattern_getDefaultNumTargets(StripePattern* this) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + return thisCast->defaultNumTargets; +} + diff --git a/client_module/source/common/storage/striping/Raid10Pattern.h b/client_module/source/common/storage/striping/Raid10Pattern.h new file mode 100644 index 0000000..08f9491 --- /dev/null +++ b/client_module/source/common/storage/striping/Raid10Pattern.h @@ -0,0 +1,96 @@ +#ifndef RAID10PATTERN_H_ +#define RAID10PATTERN_H_ + +#include +#include "StripePattern.h" + +struct Raid10Pattern; +typedef struct Raid10Pattern Raid10Pattern; + + +static inline void Raid10Pattern_initFromChunkSize(Raid10Pattern* this, unsigned chunkSize); +static inline Raid10Pattern* Raid10Pattern_constructFromChunkSize(unsigned chunkSize); +static inline void Raid10Pattern_uninit(StripePattern* this); + +static inline void __Raid10Pattern_assignVirtualFunctions(Raid10Pattern* this); + +// virtual functions +extern bool Raid10Pattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx); + +extern size_t Raid10Pattern_getStripeTargetIndex(StripePattern* this, int64_t pos); +extern uint16_t Raid10Pattern_getStripeTargetID(StripePattern* this, int64_t pos); +extern void Raid10Pattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs); +extern UInt16Vec* Raid10Pattern_getStripeTargetIDs(StripePattern* this); +extern UInt16Vec* Raid10Pattern_getMirrorTargetIDs(StripePattern* this); +extern unsigned Raid10Pattern_getMinNumTargets(StripePattern* this); +extern unsigned Raid10Pattern_getDefaultNumTargets(StripePattern* this); + + +/** + * Note: We don't have the general _construct() and _init() methods implemented, because we just + * don't need them at the moment in the client. (We only have the special _constructFrom...() and + * _initFrom...() for deserialization.) + */ +struct Raid10Pattern +{ + StripePattern stripePattern; + + UInt16Vec stripeTargetIDs; + UInt16Vec mirrorTargetIDs; + unsigned stripeSetSize; // = numStripeTargets * chunkSize + unsigned defaultNumTargets; +}; + + +/** + * Note: for deserialization only + */ +void Raid10Pattern_initFromChunkSize(Raid10Pattern* this, unsigned chunkSize) +{ + StripePattern_initFromPatternType( (StripePattern*)this, STRIPEPATTERN_Raid10, chunkSize); + + // assign virtual functions + __Raid10Pattern_assignVirtualFunctions(this); + + // init attribs + UInt16Vec_init(&this->stripeTargetIDs); + UInt16Vec_init(&this->mirrorTargetIDs); +} + +/** + * Note: for deserialization only + */ +Raid10Pattern* Raid10Pattern_constructFromChunkSize(unsigned chunkSize) +{ + struct Raid10Pattern* this = os_kmalloc(sizeof(*this) ); + + Raid10Pattern_initFromChunkSize(this, chunkSize); + + return this; +} + +void Raid10Pattern_uninit(StripePattern* this) +{ + Raid10Pattern* thisCast = (Raid10Pattern*)this; + + UInt16Vec_uninit(&thisCast->stripeTargetIDs); + UInt16Vec_uninit(&thisCast->mirrorTargetIDs); +} + +void __Raid10Pattern_assignVirtualFunctions(Raid10Pattern* this) +{ + ( (StripePattern*)this)->uninit = Raid10Pattern_uninit; + + ( (StripePattern*)this)->deserializePattern = Raid10Pattern_deserializePattern; + + ( (StripePattern*)this)->getStripeTargetIndex = Raid10Pattern_getStripeTargetIndex; + ( (StripePattern*)this)->getStripeTargetID = Raid10Pattern_getStripeTargetID; + ( (StripePattern*)this)->getStripeTargetIDsCopy = Raid10Pattern_getStripeTargetIDsCopy; + ( (StripePattern*)this)->getStripeTargetIDs = Raid10Pattern_getStripeTargetIDs; + ( (StripePattern*)this)->getMirrorTargetIDs = Raid10Pattern_getMirrorTargetIDs; + ( (StripePattern*)this)->getMinNumTargets = Raid10Pattern_getMinNumTargets; + ( (StripePattern*)this)->getDefaultNumTargets = Raid10Pattern_getDefaultNumTargets; +} + + +#endif /* RAID10PATTERN_H_ */ diff --git a/client_module/source/common/storage/striping/SimplePattern.c b/client_module/source/common/storage/striping/SimplePattern.c new file mode 100644 index 0000000..85b8778 --- /dev/null +++ b/client_module/source/common/storage/striping/SimplePattern.c @@ -0,0 +1,37 @@ +#include "SimplePattern.h" + +bool SimplePattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx) +{ + return true; +} + +size_t SimplePattern_getStripeTargetIndex(StripePattern* this, int64_t pos) +{ + return 0; +} + +uint16_t SimplePattern_getStripeTargetID(StripePattern* this, int64_t pos) +{ + return 0; +} + +unsigned SimplePattern_getMinNumTargets(StripePattern* this) +{ + return 0; +} + +unsigned SimplePattern_getDefaultNumTargets(StripePattern* this) +{ + return 0; +} + +void SimplePattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs) +{ + // nothing to be done here +} + +UInt16Vec* SimplePattern_getStripeTargetIDs(StripePattern* this) +{ + return NULL; +} + diff --git a/client_module/source/common/storage/striping/SimplePattern.h b/client_module/source/common/storage/striping/SimplePattern.h new file mode 100644 index 0000000..651242d --- /dev/null +++ b/client_module/source/common/storage/striping/SimplePattern.h @@ -0,0 +1,65 @@ +#ifndef SIMPLEPATTERN_H_ +#define SIMPLEPATTERN_H_ + +#include "StripePattern.h" + +struct SimplePattern; +typedef struct SimplePattern SimplePattern; + + +static inline void SimplePattern_init(SimplePattern* this, + unsigned patternType, unsigned chunkSize); +static inline SimplePattern* SimplePattern_construct( + unsigned patternType, unsigned chunkSize); +static inline void SimplePattern_uninit(StripePattern* this); + +// virtual functions +extern bool SimplePattern_deserializePattern(StripePattern* this, DeserializeCtx* ctx); + +extern size_t SimplePattern_getStripeTargetIndex(StripePattern* this, int64_t pos); +extern uint16_t SimplePattern_getStripeTargetID(StripePattern* this, int64_t pos); +extern unsigned SimplePattern_getMinNumTargets(StripePattern* this); +extern unsigned SimplePattern_getDefaultNumTargets(StripePattern* this); +extern void SimplePattern_getStripeTargetIDsCopy(StripePattern* this, UInt16Vec* outTargetIDs); +extern UInt16Vec* SimplePattern_getStripeTargetIDs(StripePattern* this); + + + +struct SimplePattern +{ + StripePattern stripePattern; +}; + + +void SimplePattern_init(SimplePattern* this, + unsigned patternType, unsigned chunkSize) +{ + StripePattern_initFromPatternType( (StripePattern*)this, patternType, chunkSize); + + // assign virtual functions + ( (StripePattern*)this)->uninit = SimplePattern_uninit; + + ( (StripePattern*)this)->deserializePattern = SimplePattern_deserializePattern; + + ( (StripePattern*)this)->getStripeTargetIndex = SimplePattern_getStripeTargetIndex; + ( (StripePattern*)this)->getStripeTargetID = SimplePattern_getStripeTargetID; + ( (StripePattern*)this)->getStripeTargetIDsCopy = SimplePattern_getStripeTargetIDsCopy; + ( (StripePattern*)this)->getStripeTargetIDs = SimplePattern_getStripeTargetIDs; + ( (StripePattern*)this)->getMinNumTargets = SimplePattern_getMinNumTargets; + ( (StripePattern*)this)->getDefaultNumTargets = SimplePattern_getDefaultNumTargets; +} + +SimplePattern* SimplePattern_construct(unsigned patternType, unsigned chunkSize) +{ + struct SimplePattern* this = os_kmalloc(sizeof(struct SimplePattern) ); + + SimplePattern_init(this, patternType, chunkSize); + + return this; +} + +void SimplePattern_uninit(StripePattern* this) +{ +} + +#endif /*SIMPLEPATTERN_H_*/ diff --git a/client_module/source/common/storage/striping/StripePattern.c b/client_module/source/common/storage/striping/StripePattern.c new file mode 100644 index 0000000..144fdc6 --- /dev/null +++ b/client_module/source/common/storage/striping/StripePattern.c @@ -0,0 +1,139 @@ +#include +#include "BuddyMirrorPattern.h" +#include "Raid0Pattern.h" +#include "Raid10Pattern.h" +#include "SimplePattern.h" +#include "StripePattern.h" + +#define HAS_NO_POOL_FLAG (1 << 24) + +/** + * Calls the virtual uninit method and kfrees the object. + */ +void StripePattern_virtualDestruct(StripePattern* this) +{ + this->uninit(this); + kfree(this); +} + + +bool StripePattern_deserializePatternPreprocess(DeserializeCtx* ctx, + const char** outPatternStart, uint32_t* outPatternLength) +{ + DeserializeCtx temp = *ctx; + + if(!Serialization_deserializeUInt(&temp, outPatternLength)) + return false; + + *outPatternStart = ctx->data; + + if (*outPatternLength > ctx->length) + return false; + + ctx->data += *outPatternLength; + ctx->length -= *outPatternLength; + + return true; +} + +/** + * @return outPattern; outPattern->patternType is STRIPEPATTERN_Invalid on error + */ +StripePattern* StripePattern_createFromBuf(const char* patternStart, + uint32_t patternLength) +{ + struct StripePatternHeader patternHeader; + StripePattern* pattern; + DeserializeCtx ctx = { + .data = patternStart, + .length = patternLength, + }; + bool deserRes; + + if (!__StripePattern_deserializeHeader(&ctx, &patternHeader)) + return (StripePattern*)SimplePattern_construct(STRIPEPATTERN_Invalid, 0); + + switch (patternHeader.patternType) + { + case STRIPEPATTERN_Raid0: + { + pattern = (StripePattern*)Raid0Pattern_constructFromChunkSize(patternHeader.chunkSize); + } break; + + case STRIPEPATTERN_Raid10: + { + pattern = (StripePattern*)Raid10Pattern_constructFromChunkSize(patternHeader.chunkSize); + } break; + + case STRIPEPATTERN_BuddyMirror: + { + pattern = (StripePattern*)BuddyMirrorPattern_constructFromChunkSize( + patternHeader.chunkSize); + } break; + + default: + { + pattern = (StripePattern*)SimplePattern_construct(STRIPEPATTERN_Invalid, 0); + return pattern; + } break; + } + + deserRes = pattern->deserializePattern(pattern, &ctx); + if(unlikely(!deserRes) ) + { // deserialization failed => discard half-initialized pattern and create new invalid pattern + StripePattern_virtualDestruct(pattern); + + pattern = (StripePattern*)SimplePattern_construct(STRIPEPATTERN_Invalid, 0); + + return pattern; + } + + return pattern; + +} + + +bool __StripePattern_deserializeHeader(DeserializeCtx* ctx, + struct StripePatternHeader* outPatternHeader) +{ + // pattern length + if(!Serialization_deserializeUInt(ctx, &outPatternHeader->patternLength) ) + return false; + + // pattern type + if(!Serialization_deserializeUInt(ctx, &outPatternHeader->patternType) ) + return false; + + // chunkSize + if(!Serialization_deserializeUInt(ctx, &outPatternHeader->chunkSize) ) + return false; + + // storagePoolId + if (!(outPatternHeader->patternType & HAS_NO_POOL_FLAG)) { + if(!StoragePoolId_deserialize(ctx, &outPatternHeader->storagePoolId) ) + return false; + } + + outPatternHeader->patternType &= ~HAS_NO_POOL_FLAG; + + // check length field + if(outPatternHeader->patternLength < STRIPEPATTERN_HEADER_LENGTH) + return false; + + // check chunkSize + if(!outPatternHeader->chunkSize) + return false; + + return true; +} + +/** + * Predefined virtual method returning NULL. Will be overridden by StripePatterns (e.g. Raid10) + * that actually do have mirror targets. + * + * @return NULL for patterns that don't have mirror targets. + */ +UInt16Vec* StripePattern_getMirrorTargetIDs(StripePattern* this) +{ + return NULL; +} diff --git a/client_module/source/common/storage/striping/StripePattern.h b/client_module/source/common/storage/striping/StripePattern.h new file mode 100644 index 0000000..9e946df --- /dev/null +++ b/client_module/source/common/storage/striping/StripePattern.h @@ -0,0 +1,146 @@ +#ifndef STRIPEPATTERN_H_ +#define STRIPEPATTERN_H_ + +/** + * Note: Do not instantiate this "class" directly (it contains pure virtual functions) + */ + +#include +#include +#include +#include + + +// pattern types +#define STRIPEPATTERN_Invalid 0 +#define STRIPEPATTERN_Raid0 1 +#define STRIPEPATTERN_Raid10 2 +#define STRIPEPATTERN_BuddyMirror 3 + +// minimum allowed stripe pattern chunk size (in bytes) +#define STRIPEPATTERN_MIN_CHUNKSIZE (1024*64) + +// pattern serialization defs +#define STRIPEPATTERN_HEADER_LENGTH \ + (sizeof(unsigned) + sizeof(unsigned) + sizeof(unsigned)) +/* length + type + chunkSize*/ + + +struct StripePatternHeader +{ + // everything in this struct is in host byte order! + + unsigned patternLength; // in bytes + unsigned patternType; // the type of pattern, defined as STRIPEPATTERN_x + unsigned chunkSize; + // storagePoolId is unused in the client at the moment; however we deserialize it to avoid human + // errors later + StoragePoolId storagePoolId; +}; + + +struct StripePattern; +typedef struct StripePattern StripePattern; + + +static inline void StripePattern_initFromPatternType(StripePattern* this, + unsigned patternType, unsigned chunkSize); + +extern void StripePattern_virtualDestruct(struct StripePattern* this); + +extern bool StripePattern_deserializePatternPreprocess(DeserializeCtx* ctx, + const char** outPatternStart, uint32_t* outPatternLength); +extern bool __StripePattern_deserializeHeader(DeserializeCtx* ctx, + struct StripePatternHeader* outPatternHeader); + +// static functions +extern StripePattern* StripePattern_createFromBuf(const char* patternStart, + uint32_t patternLength); + +// virtual functions +extern UInt16Vec* StripePattern_getMirrorTargetIDs(StripePattern* this); + +// getters & setters +static inline int StripePattern_getPatternType(StripePattern* this); +static inline unsigned StripePattern_getChunkSize(StripePattern* this); +static inline int64_t StripePattern_getChunkStart(StripePattern* this, int64_t pos); +static inline int64_t StripePattern_getNextChunkStart(StripePattern* this, int64_t pos); +static inline int64_t StripePattern_getChunkEnd(StripePattern* this, int64_t pos); + + +struct StripePattern +{ + unsigned patternType; // STRIPEPATTERN_... + unsigned chunkSize; // must be a power of two (optimizations rely on it) + + unsigned serialPatternLength; // for (de)serialization + + // virtual functions + void (*uninit) (StripePattern* this); + + // (de)serialization + bool (*deserializePattern) (StripePattern* this, DeserializeCtx* ctx); + + size_t (*getStripeTargetIndex) (StripePattern* this, int64_t pos); + uint16_t (*getStripeTargetID) (StripePattern* this, int64_t pos); + void (*getStripeTargetIDsCopy) (StripePattern* this, UInt16Vec* outTargetIDs); + UInt16Vec* (*getStripeTargetIDs) (StripePattern* this); + UInt16Vec* (*getMirrorTargetIDs) (StripePattern* this); + unsigned (*getMinNumTargets) (StripePattern* this); + unsigned (*getDefaultNumTargets) (StripePattern* this); +}; + + +void StripePattern_initFromPatternType(StripePattern* this, unsigned patternType, + unsigned chunkSize) +{ + this->patternType = patternType; + this->chunkSize = chunkSize; + + this->serialPatternLength = 0; + + // pre-defined virtual methods + this->getMirrorTargetIDs = StripePattern_getMirrorTargetIDs; +} + + +int StripePattern_getPatternType(StripePattern* this) +{ + return this->patternType; +} + +unsigned StripePattern_getChunkSize(StripePattern* this) +{ + return this->chunkSize; +} + +int64_t StripePattern_getChunkStart(StripePattern* this, int64_t pos) +{ + // the code below is an optimization (wrt division) for the following line: + // int64_t chunkStart = pos - (pos % this->chunkSize); + + // "& chunkSize -1" instead of "%", because chunkSize is a power of two + unsigned posModChunkSize = pos & (this->chunkSize - 1); + + int64_t chunkStart = pos - posModChunkSize; + + return chunkStart; +} + +/** + * Get the exact file position where the next chunk starts + */ +int64_t StripePattern_getNextChunkStart(StripePattern* this, int64_t pos) +{ + return StripePattern_getChunkStart(this, pos) + this->chunkSize; +} + +/** + * Get the exact file position where the current chunk ends + */ +int64_t StripePattern_getChunkEnd(StripePattern* this, int64_t pos) +{ + return StripePattern_getNextChunkStart(this, pos) - 1; +} + +#endif /*STRIPEPATTERN_H_*/ diff --git a/client_module/source/common/system/System.c b/client_module/source/common/system/System.c new file mode 100644 index 0000000..56efa7a --- /dev/null +++ b/client_module/source/common/system/System.c @@ -0,0 +1,33 @@ +#include +#include +#include "System.h" + +char* System_getHostnameCopy(void) +{ + // note: this is racy since 2.6.27 unexported uts_sem. however, NFS (fs/nfs/nfsroot.c) and + // CIFS (fs/cifs/connect.c) use utsname()->nodename without the semaphore. + // we'll have to keep an eye on it... + + char* hostnameOrig; + char* hostnameCopy; + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26) + down_read(&uts_sem); +#endif + +#ifdef KERNEL_HAS_SYSTEM_UTSNAME + hostnameOrig = system_utsname.nodename; +#else + hostnameOrig = utsname()->nodename; +#endif + + hostnameCopy = kmalloc(strlen(hostnameOrig)+1, GFP_KERNEL); + if(likely(hostnameCopy) ) + strcpy(hostnameCopy, hostnameOrig); + +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26) + up_read(&uts_sem); +#endif + + return hostnameCopy; +} diff --git a/client_module/source/common/system/System.h b/client_module/source/common/system/System.h new file mode 100644 index 0000000..b69727e --- /dev/null +++ b/client_module/source/common/system/System.h @@ -0,0 +1,8 @@ +#ifndef SYSTEM_H_ +#define SYSTEM_H_ + +#include + +extern char* System_getHostnameCopy(void); + +#endif /*SYSTEM_H_*/ diff --git a/client_module/source/common/threading/AtomicInt.h b/client_module/source/common/threading/AtomicInt.h new file mode 100644 index 0000000..3c31fa4 --- /dev/null +++ b/client_module/source/common/threading/AtomicInt.h @@ -0,0 +1,129 @@ +#ifndef OPEN_ATOMICINT_H_ +#define OPEN_ATOMICINT_H_ + +#include + +#include + + +struct AtomicInt; +typedef struct AtomicInt AtomicInt; + + +static inline void AtomicInt_init(AtomicInt* this, int initialVal); +static inline void AtomicInt_uninit(AtomicInt* this); + +static inline int AtomicInt_incAndRead(AtomicInt* this); +static inline void AtomicInt_inc(AtomicInt* this); +static inline void AtomicInt_dec(AtomicInt* this); +static inline int AtomicInt_read(AtomicInt* this); +static inline void AtomicInt_set(AtomicInt* this, int newValue); +static inline int AtomicInt_compareAndSwap(AtomicInt* this, int oldValue, int newValue); +static inline void AtomicInt_max(AtomicInt* this, int minValue); +static inline int AtomicInt_decAndTest(AtomicInt* this); + + +struct AtomicInt +{ + atomic_t kernelAtomic; +}; + + +void AtomicInt_init(AtomicInt* this, int initialVal) +{ + AtomicInt initializer = + { + .kernelAtomic = ATOMIC_INIT(initialVal), + }; + + *this = initializer; +} + +void AtomicInt_uninit(AtomicInt* this) +{ + // nothing to be done here +} + +/** + * Increase and return the new value + */ +int AtomicInt_incAndRead(AtomicInt* this) +{ + return atomic_inc_return(&this->kernelAtomic); +} + +/** + * Increase by 1. + */ +void AtomicInt_inc(AtomicInt* this) +{ + atomic_inc(&this->kernelAtomic); +} + +/** + * Decrease by 1. + */ +void AtomicInt_dec(AtomicInt* this) +{ + atomic_dec(&this->kernelAtomic); +} + +int AtomicInt_read(AtomicInt* this) +{ + return atomic_read(&this->kernelAtomic); +} + +/** + * Set to "newValue". + */ +void AtomicInt_set(AtomicInt* this, int newValue) +{ + atomic_set(&this->kernelAtomic, newValue); +} + +/** + * Test whether current value matches "oldValue" and only if it matches, update it to "newValue". + * + * @return the value *before* the swap, regardless if the swap was successful or not + */ +int AtomicInt_compareAndSwap(AtomicInt* this, int oldValue, int newValue) +{ + return atomic_cmpxchg(&this->kernelAtomic, oldValue, newValue); +} + +/** + * Increase current value to "minValue" if it is currently smaller, otherwise leave it as it was. + * + * Note: Use this carefully and only in special scenarios, because it could run endless if the + * atomic value is not monotonically increasing. + */ +void AtomicInt_max(AtomicInt* this, int minValue) +{ + int currentVal = AtomicInt_read(this); + for( ; ; ) + { + int swapRes; + + if(currentVal >= minValue) + return; // no need to update it at all + + swapRes = AtomicInt_compareAndSwap(this, currentVal, minValue); + if (swapRes == currentVal) + return; + + currentVal = swapRes; // swap was not successful, update currentVal + } +} + +/** + * Decrease by one and test if the counter is 0 + * + * @return 1 if the counter is zero, 0 in all other cases + */ +int AtomicInt_decAndTest(AtomicInt* this) +{ + return atomic_dec_and_test(&this->kernelAtomic); +} + + +#endif /* OPEN_ATOMICINT_H_ */ diff --git a/client_module/source/common/threading/AtomicInt64.h b/client_module/source/common/threading/AtomicInt64.h new file mode 100644 index 0000000..fa36476 --- /dev/null +++ b/client_module/source/common/threading/AtomicInt64.h @@ -0,0 +1,137 @@ +#ifndef OPEN_AtomicInt64_H_ +#define OPEN_AtomicInt64_H_ + +#include + +#include // also adds ATOMIC64_INIT if available + + +#ifndef ATOMIC64_INIT + #include +#endif + + +struct AtomicInt64; +typedef struct AtomicInt64 AtomicInt64; + + +static inline void AtomicInt64_init(AtomicInt64* this, uint64_t initialVal); +static inline void AtomicInt64_uninit(AtomicInt64* this); + +static inline uint64_t AtomicInt64_incAndRead(AtomicInt64* this); +static inline void AtomicInt64_inc(AtomicInt64* this); +static inline void AtomicInt64_dec(AtomicInt64* this); +static inline uint64_t AtomicInt64_read(AtomicInt64* this); +static inline void AtomicInt64_set(AtomicInt64* this, uint64_t newValue); + +#if 0 // not available in old kernels + +static inline uint64_t AtomicInt64_compareAndSwap( + AtomicInt64* this, uint64_t oldValue, uint64_t newValue); +static inline void AtomicInt64_max(AtomicInt64* this, uint64_t minValue); + +#endif // if 0 // not available in old kernels + + +struct AtomicInt64 +{ + atomic64_t kernelAtomic64; +}; + + +void AtomicInt64_init(AtomicInt64* this, uint64_t initialVal) +{ +#ifdef ATOMIC64_INIT + AtomicInt64 initializer = + { + .kernelAtomic64 = ATOMIC64_INIT(initialVal), + }; + + *this = initializer; +#else + atomic_init(&this->kernelAtomic64, initialVal); +#endif +} + +void AtomicInt64_uninit(AtomicInt64* this) +{ + // nothing to be done here +} + +/** + * Increase and return the new value + */ +uint64_t AtomicInt64_incAndRead(AtomicInt64* this) +{ + return atomic64_inc_return(&this->kernelAtomic64); +} + +/** + * Increase by 1. + */ +void AtomicInt64_inc(AtomicInt64* this) +{ + atomic64_inc(&this->kernelAtomic64); +} + +/** + * Decrease by 1. + */ +void AtomicInt64_dec(AtomicInt64* this) +{ + atomic64_dec(&this->kernelAtomic64); +} + +uint64_t AtomicInt64_read(AtomicInt64* this) +{ + return atomic64_read(&this->kernelAtomic64); +} + +/** + * Set to "newValue". + */ +void AtomicInt64_set(AtomicInt64* this, uint64_t newValue) +{ + atomic64_set(&this->kernelAtomic64, newValue); +} + +#if 0 // not available in old kernel version +/** + * Test whether current value matches "oldValue" and only if it matches, update it to "newValue". + * + * @return the value *before* the swap, regardless if the swap was successful or not + */ +uint64_t AtomicInt64_compareAndSwap(AtomicInt64* this, uint64_t oldValue, uint64_t newValue) +{ + return atomic64_cmpxchg(&this->kernelAtomic64, oldValue, newValue); +} + + +/** + * Increase current value to "minValue" if it is currently smaller, otherwise leave it as it was. + * + * Note: Use this carefully and only in special scenarios, because it could run endless if the + * atomic value is not monotonically increasing. + */ +void AtomicInt64_max(AtomicInt64* this, uint64_t minValue) +{ + uint64_t currentVal = AtomicInt64_read(this); + for( ; ; ) + { + uint64_t swapRes; + + if(currentVal >= minValue) + return; // no need to update it at all + + swapRes = AtomicInt64_compareAndSwap(this, currentVal, minValue); + if (swapRes == currentVal) + return; + + currentVal = swapRes; // swap was not successful, update currentVal + } +} + +#endif // if 0 // not available in old kernel version + + +#endif /* OPEN_AtomicInt64_H_ */ diff --git a/client_module/source/common/threading/Condition.h b/client_module/source/common/threading/Condition.h new file mode 100644 index 0000000..a781434 --- /dev/null +++ b/client_module/source/common/threading/Condition.h @@ -0,0 +1,185 @@ +#ifndef OPEN_CONDITION_H_ +#define OPEN_CONDITION_H_ + +#include +#include +#include +#include "Mutex.h" + +#include + + +struct Condition; +typedef struct Condition Condition; + +enum cond_wait_res; +typedef enum cond_wait_res cond_wait_res_t; + +static inline void Condition_init(Condition* this); + +static inline void Condition_wait(Condition* this, Mutex* mutex); +static inline cond_wait_res_t Condition_timedwait(Condition* this, Mutex* mutex, int timeoutMS); +static inline cond_wait_res_t Condition_timedwaitInterruptible(Condition* this, Mutex* mutex, + int timeoutMS); +static inline void Condition_broadcast(Condition* this); +static inline void Condition_signal(Condition* this); + + +struct Condition +{ + wait_queue_head_t queue; +}; + + +enum cond_wait_res +{ + COND_WAIT_TIMEOUT = 0, // make sure that timeout is always 0 + COND_WAIT_SUCCESS = 1, + COND_WAIT_SIGNAL = -1 +}; + + +void Condition_init(Condition* this) +{ + init_waitqueue_head(&this->queue); +} + +/** + * Note: Waits uninterruptible. + * + * Uninterruptible waits are important in cases where we cannot continue until a certain external + * condition becomes true. Otherwise we might go up to 100% cpu usage on ctrl+c because schedule() + * will keep waking up immediately. + * + * The problem with uninterruptible waiting is that it will produce hung_task log msgs in the + * kernel, so it is not appropriate for long sleeps like a worker waiting for new work. + */ +void Condition_wait(Condition* this, Mutex* mutex) +{ + wait_queue_t wait; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&this->queue, &wait); + + set_current_state(TASK_UNINTERRUPTIBLE); + + Mutex_unlock(mutex); + + schedule(); + + Mutex_lock(mutex); + + remove_wait_queue(&this->queue, &wait); + + __set_current_state(TASK_RUNNING); +} + +static inline enum cond_wait_res Condition_waitKillable(Condition* this, Mutex* mutex) +{ + cond_wait_res_t retVal; + wait_queue_t wait; + + init_waitqueue_entry(&wait, current); + add_wait_queue(&this->queue, &wait); + + set_current_state(TASK_KILLABLE); + + Mutex_unlock(mutex); + + schedule(); + + retVal = fatal_signal_pending(current) ? COND_WAIT_SIGNAL : COND_WAIT_SUCCESS; + + Mutex_lock(mutex); + + remove_wait_queue(&this->queue, &wait); + + __set_current_state(TASK_RUNNING); + + return retVal; +} + +/** + * Note: Waits uninterruptible. Read the _wait() comments. + * + * @return COND_WAIT_TIMEOUT if timeout occurred + */ +cond_wait_res_t Condition_timedwait(Condition* this, Mutex* mutex, int timeoutMS) +{ + cond_wait_res_t retVal; + long __timeout; + + wait_queue_t wait; + init_waitqueue_entry(&wait, current); + add_wait_queue(&this->queue, &wait); + + set_current_state(TASK_UNINTERRUPTIBLE); + + Mutex_unlock(mutex); + + // timeval to jiffies + __timeout = TimeTk_msToJiffiesSchedulable(timeoutMS); + + // wait + if( (__timeout = schedule_timeout(__timeout) ) ) + retVal = COND_WAIT_SUCCESS; + else + retVal = COND_WAIT_TIMEOUT; + + Mutex_lock(mutex); + + remove_wait_queue(&this->queue, &wait); + + __set_current_state(TASK_RUNNING); + + return retVal; +} + +/** + * Note: Waits interruptible. Read the _waitInterruptible() comments. + * + * @return COND_WAIT_TIMEOUT if timeout occurred, COND_WAIT_SIGNAL on pending signal. + */ +cond_wait_res_t Condition_timedwaitInterruptible(Condition* this, Mutex* mutex, int timeoutMS) +{ + cond_wait_res_t retVal; + long __timeout; + + wait_queue_t wait; + init_waitqueue_entry(&wait, current); + add_wait_queue(&this->queue, &wait); + + set_current_state(TASK_INTERRUPTIBLE); + + Mutex_unlock(mutex); + + // timeval to jiffies + __timeout = TimeTk_msToJiffiesSchedulable(timeoutMS); + + // wait + if( (__timeout = schedule_timeout(__timeout) ) ) + retVal = signal_pending(current) ? COND_WAIT_SIGNAL : COND_WAIT_SUCCESS; + else + retVal = COND_WAIT_TIMEOUT; + + Mutex_lock(mutex); + + remove_wait_queue(&this->queue, &wait); + + __set_current_state(TASK_RUNNING); + + return retVal; +} + +void Condition_broadcast(Condition* this) +{ + wake_up_all(&this->queue); +} + +void Condition_signal(Condition* this) +{ + wake_up(&this->queue); +} + + +#endif /*OPEN_CONDITION_H_*/ diff --git a/client_module/source/common/threading/Mutex.h b/client_module/source/common/threading/Mutex.h new file mode 100644 index 0000000..5787b8e --- /dev/null +++ b/client_module/source/common/threading/Mutex.h @@ -0,0 +1,47 @@ +#ifndef OPEN_MUTEX_H_ +#define OPEN_MUTEX_H_ + +#include + +#include + + +struct Mutex; +typedef struct Mutex Mutex; + +static inline void Mutex_init(Mutex* this); +static inline void Mutex_uninit(Mutex* this); +static inline void Mutex_lock(Mutex* this); +static inline void Mutex_unlock(Mutex* this); + + +struct Mutex +{ + struct mutex mutex; +}; + + +void Mutex_init(Mutex* this) +{ + mutex_init(&this->mutex); +} + +void Mutex_uninit(Mutex* this) +{ + mutex_destroy(&this->mutex); /* optional call according to kernel api description, so we might + just drop it if necessary (however, it has some functionality regarding to kernel mutex + debugging) */ +} + +void Mutex_lock(Mutex* this) +{ + mutex_lock(&this->mutex); +} + +void Mutex_unlock(Mutex* this) +{ + mutex_unlock(&this->mutex); +} + + +#endif /*OPEN_MUTEX_H_*/ diff --git a/client_module/source/common/threading/RWLock.h b/client_module/source/common/threading/RWLock.h new file mode 100644 index 0000000..47f14c2 --- /dev/null +++ b/client_module/source/common/threading/RWLock.h @@ -0,0 +1,64 @@ +#ifndef OPEN_RWLOCK_H_ +#define OPEN_RWLOCK_H_ + +#include + +#include + + +struct RWLock; +typedef struct RWLock RWLock; + + +static inline void RWLock_init(RWLock* this); +static inline void RWLock_writeLock(RWLock* this); +static inline int RWLock_writeTryLock(RWLock* this); +static inline void RWLock_readLock(RWLock* this); +static inline void RWLock_writeUnlock(RWLock* this); +static inline void RWLock_readUnlock(RWLock* this); + + +struct RWLock +{ + struct rw_semaphore rwSem; +}; + + +void RWLock_init(RWLock* this) +{ + init_rwsem(&this->rwSem); +} + +void RWLock_writeLock(RWLock* this) +{ + down_write(&this->rwSem); +} + +/** + * Try locking and return immediately even if lock cannot be aqcuired immediately. + * + * @return 1 if lock acquired, 0 if contention + */ +int RWLock_writeTryLock(RWLock* this) +{ + return down_write_trylock(&this->rwSem); +} + +void RWLock_readLock(RWLock* this) +{ + down_read(&this->rwSem); +} + +void RWLock_writeUnlock(RWLock* this) +{ + up_write(&this->rwSem); +} + +void RWLock_readUnlock(RWLock* this) +{ + up_read(&this->rwSem); +} + + + +#endif /* OPEN_RWLOCK_H_ */ diff --git a/client_module/source/common/threading/Thread.c b/client_module/source/common/threading/Thread.c new file mode 100644 index 0000000..a4c724f --- /dev/null +++ b/client_module/source/common/threading/Thread.c @@ -0,0 +1,130 @@ +#include +#include +#include +#include + +#include +#include + + +void Thread_init(Thread* this, const char* threadName, ThreadRoutine threadRoutine) +{ + Mutex_init(&this->selfTerminateMutex); + Condition_init(&this->shallSelfTerminateCond); + Condition_init(&this->isSelfTerminatedChangeCond); + + this->shallSelfTerminate = false; + this->isSelfTerminated = true; // start with true so that join(..) works with a nerver-ran thread + + StringTk_strncpyTerminated(this->initialThreadName, threadName, BEEGFS_TASK_COMM_LEN); + + this->threadRoutine = threadRoutine; +} + +void Thread_uninit(Thread* this) +{ + Mutex_uninit(&this->selfTerminateMutex); +} + +bool Thread_start(Thread* this) +{ + // set thread to not be selfTerminated + Mutex_lock(&this->selfTerminateMutex); + + this->isSelfTerminated = false; + + Mutex_unlock(&this->selfTerminateMutex); + + // actually run the thread + this->threadData = kthread_run(__Thread_runStatic, this, "%s", this->initialThreadName); + + return !IS_ERR(this->threadData); +} + +void Thread_join(Thread* this) +{ + Mutex_lock(&this->selfTerminateMutex); + + while(!this->isSelfTerminated) + Condition_wait(&this->isSelfTerminatedChangeCond, &this->selfTerminateMutex); + + Mutex_unlock(&this->selfTerminateMutex); +} + +/** + * @return true if thread terminated before timeout expired + */ +bool Thread_timedjoin(Thread* this, int timeoutMS) +{ + bool isTerminated; + + Mutex_lock(&this->selfTerminateMutex); + + if(!this->isSelfTerminated) + Condition_timedwait(&this->isSelfTerminatedChangeCond, &this->selfTerminateMutex, timeoutMS); + + isTerminated = this->isSelfTerminated; + + Mutex_unlock(&this->selfTerminateMutex); + + return isTerminated; +} + +void Thread_selfTerminate(Thread* this) +{ + Mutex_lock(&this->selfTerminateMutex); + + this->shallSelfTerminate = true; + Condition_broadcast(&this->shallSelfTerminateCond); + + Mutex_unlock(&this->selfTerminateMutex); +} + +void Thread_sleep(int timeoutMS) +{ + long timeout = TimeTk_msToJiffiesSchedulable(timeoutMS); + + /* wait INTERRUPTIBLE first to keep load down. callers of this function should ... not. */ + set_current_state(TASK_INTERRUPTIBLE); + + timeout = schedule_timeout(timeout); + if (timeout && !fatal_signal_pending(current)) + { + set_current_state(TASK_KILLABLE); + schedule_timeout(timeout); + } + + __set_current_state(TASK_RUNNING); +} + +/** + * @param data a this-pointer to the Thread struct + */ +int __Thread_runStatic(void* data) +{ + Thread* this = (Thread*)data; + + this->threadRoutine(this); + + __Thread_setSelfTerminated(this); + + return 0; +} + +/** + * Get (comm-)name of the given thread argument. + */ +const char* Thread_getName(Thread* this) +{ + return this->initialThreadName; +} + +/** + * Get comm name of the thread calling this function. + * + * @param outBuf must be at least of length BEEGFS_TASK_COMM_LEN + */ +void Thread_getCurrentThreadName(char* outBuf) +{ + StringTk_strncpyTerminated(outBuf, current->comm, BEEGFS_TASK_COMM_LEN); +} diff --git a/client_module/source/common/threading/Thread.h b/client_module/source/common/threading/Thread.h new file mode 100644 index 0000000..a48634c --- /dev/null +++ b/client_module/source/common/threading/Thread.h @@ -0,0 +1,106 @@ +#ifndef OPEN_THREAD_H_ +#define OPEN_THREAD_H_ + +#include +#include +#include +#include +#include + + +#define BEEGFS_TASK_COMM_LEN 16 /* task command name length */ + + +struct task_struct; // forward declaration + + +struct Thread; +typedef struct Thread Thread; + + +typedef void (*ThreadRoutine)(Thread* this); + + +extern void Thread_init(Thread* this, const char* initialThreadName, ThreadRoutine threadRoutine); +extern void Thread_uninit(Thread* this); + +extern bool Thread_start(Thread* this); +extern void Thread_join(Thread* this); +extern bool Thread_timedjoin(Thread* this, int timeoutMS); +extern void Thread_selfTerminate(Thread* this); +extern void Thread_sleep(int timeoutMS); + +extern int __Thread_runStatic(void* data); + +// inliners +static inline bool _Thread_waitForSelfTerminateOrder(Thread* this, int timeoutMS); + +// getters & setters +extern const char* Thread_getName(Thread* this); +extern void Thread_getCurrentThreadName(char* outBuf); +static inline bool Thread_getSelfTerminate(Thread* this); +static inline void __Thread_setSelfTerminated(Thread* this); + + +struct Thread +{ + bool shallSelfTerminate; + Mutex selfTerminateMutex; + Condition shallSelfTerminateCond; + + bool isSelfTerminated; + Condition isSelfTerminatedChangeCond; + + char initialThreadName[BEEGFS_TASK_COMM_LEN]; + + struct task_struct* threadData; + + ThreadRoutine threadRoutine; +}; + + +bool _Thread_waitForSelfTerminateOrder(Thread* this, int timeoutMS) +{ + bool shallSelfTerminate; + + Mutex_lock(&this->selfTerminateMutex); + + if(!this->shallSelfTerminate) + { + Condition_timedwaitInterruptible( + &this->shallSelfTerminateCond, &this->selfTerminateMutex, timeoutMS); + } + + shallSelfTerminate = this->shallSelfTerminate; + + Mutex_unlock(&this->selfTerminateMutex); + + return shallSelfTerminate; +} + + +bool Thread_getSelfTerminate(Thread* this) +{ + bool shallSelfTerminate; + + Mutex_lock(&this->selfTerminateMutex); + + shallSelfTerminate = this->shallSelfTerminate; + + Mutex_unlock(&this->selfTerminateMutex); + + return shallSelfTerminate; +} + +void __Thread_setSelfTerminated(Thread* this) +{ + Mutex_lock(&this->selfTerminateMutex); + + this->isSelfTerminated = true; + + Condition_broadcast(&this->isSelfTerminatedChangeCond); + + Mutex_unlock(&this->selfTerminateMutex); +} + +#endif /*OPEN_THREAD_H_*/ diff --git a/client_module/source/common/toolkit/HashTk.c b/client_module/source/common/toolkit/HashTk.c new file mode 100644 index 0000000..eee0b8b --- /dev/null +++ b/client_module/source/common/toolkit/HashTk.c @@ -0,0 +1,351 @@ +#include +#include + +#define get16bits(d) (*((const uint16_t *) (d))) + +#define HashTkDefaultHash HASHTK_HALFMD4 +//#define HashTkDefaultHash HASHTK_HSIEHHASH32 + +#define Hashtk_HALFMD4_IN_BUF_SIZE 8 +#define HashTk_HALFMD4_OUT_BUF_SIZE 4 +#define HashTk_INT_BYTES 4 + +#define HashTk_HALFMD4_MAJOR_BUFPOS 1 // as in ext4 +#define HashTk_HALFMD4_MINOR_BUFPOS 2 + + +#ifdef KERNEL_HAS_HALF_MD4_TRANSFORM +#include +#include +#else +/* half_md4_transform and macros taken from lib/halfmd4.c */ +/* F, G and H are basic MD4 functions: selection, majority, parity */ +#define F(x, y, z) ((z) ^ ((x) & ((y) ^ (z)))) +#define G(x, y, z) (((x) & (y)) + (((x) ^ (y)) & (z))) +#define H(x, y, z) ((x) ^ (y) ^ (z)) + +/* + * The generic round function. The application is so specific that + * we don't bother protecting all the arguments with parens, as is generally + * good macro practice, in favor of extra legibility. + * Rotation is separate from addition to prevent recomputation + */ +#define ROUND(f, a, b, c, d, x, s) \ + (a += f(b, c, d) + x, a = rol32(a, s)) +#define K1 0 +#define K2 013240474631UL +#define K3 015666365641UL + +/* + * Basic cut-down MD4 transform. Returns only 32 bits of result. + */ +static __u32 half_md4_transform(__u32 buf[4], __u32 const in[8]) +{ + __u32 a = buf[0], b = buf[1], c = buf[2], d = buf[3]; + + /* Round 1 */ + ROUND(F, a, b, c, d, in[0] + K1, 3); + ROUND(F, d, a, b, c, in[1] + K1, 7); + ROUND(F, c, d, a, b, in[2] + K1, 11); + ROUND(F, b, c, d, a, in[3] + K1, 19); + ROUND(F, a, b, c, d, in[4] + K1, 3); + ROUND(F, d, a, b, c, in[5] + K1, 7); + ROUND(F, c, d, a, b, in[6] + K1, 11); + ROUND(F, b, c, d, a, in[7] + K1, 19); + + /* Round 2 */ + ROUND(G, a, b, c, d, in[1] + K2, 3); + ROUND(G, d, a, b, c, in[3] + K2, 5); + ROUND(G, c, d, a, b, in[5] + K2, 9); + ROUND(G, b, c, d, a, in[7] + K2, 13); + ROUND(G, a, b, c, d, in[0] + K2, 3); + ROUND(G, d, a, b, c, in[2] + K2, 5); + ROUND(G, c, d, a, b, in[4] + K2, 9); + ROUND(G, b, c, d, a, in[6] + K2, 13); + + /* Round 3 */ + ROUND(H, a, b, c, d, in[3] + K3, 3); + ROUND(H, d, a, b, c, in[7] + K3, 9); + ROUND(H, c, d, a, b, in[2] + K3, 11); + ROUND(H, b, c, d, a, in[6] + K3, 15); + ROUND(H, a, b, c, d, in[1] + K3, 3); + ROUND(H, d, a, b, c, in[5] + K3, 9); + ROUND(H, c, d, a, b, in[0] + K3, 11); + ROUND(H, b, c, d, a, in[4] + K3, 15); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; + + return buf[1]; /* "most hashed" word */ +} +#endif + + + +static uint32_t HashTk_HsiehHash32(const char* data, int len); + +static void HashTk_string2HashBufSigned(const char *msg, int len, __u32 *buf, int num); + +/** + * Copied from ext4 hash.c (str2hashbuf_signed() ) + */ +static void HashTk_string2HashBufSigned(const char *msg, int len, __u32 *buf, int num) +{ + __u32 pad, val; + int i; + const signed char *scp = (const signed char *) msg; + + pad = (__u32)len | ((__u32)len << 8); + pad |= pad << 16; + + val = pad; + if (len > num*4) + len = num * 4; + for (i = 0; i < len; i++) { + if ((i % 4) == 0) + val = pad; + val = ((int) scp[i]) + (val << 8); + if ((i % 4) == 3) { + *buf++ = val; + val = pad; + num--; + } + } + if (--num >= 0) + *buf++ = val; + while (--num >= 0) + *buf++ = pad; +} + + +/** + * Note: This is the Hsieh hash function, which is available under old BSD-style license. + * (It performs very well on x86 and PowerPC archs compared to other famous hash functions.) + * + * @data the buffer for which you want the hash value to be computed (arbitraty length) + * @len length of the data buffer + */ +uint32_t HashTk_HsiehHash32(const char* data, int len) +{ + uint32_t hash = len, tmp; + int rem; + + if(unlikely(len <= 0 || data == NULL) ) + return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for(; len > 0; len--) + { + hash += get16bits(data); + tmp = (get16bits(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch(rem) + { + case 3: + hash += get16bits(data); + hash ^= hash << 16; + hash ^= data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: + hash += get16bits(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: + hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +/** + * Do the halfMD4 hash computation. + * Note: OutBuf must be an array of size HashTk_HALFMD4_OUT_BUF_SIZE + */ +static void HashTk_halfMD4(const char* data, int len, uint32_t* outBuf) +{ + uint32_t inBuf[Hashtk_HALFMD4_IN_BUF_SIZE]; + int maxMD4StrLen = Hashtk_HALFMD4_IN_BUF_SIZE * HashTk_INT_BYTES; // 32 + const char* dataPtr = data; + int remainingLen = len; + + /* Initialize the default seed for the hash checksum functions, magic numbers taken from + * ext4fs_dirhash() */ + outBuf[0] = 0x67452301; + outBuf[1] = 0xefcdab89; + outBuf[2] = 0x98badcfe; + outBuf[3] = 0x10325476; + + while (remainingLen > 0) { + HashTk_string2HashBufSigned(dataPtr, len, inBuf, Hashtk_HALFMD4_IN_BUF_SIZE); + half_md4_transform(outBuf, inBuf); + + remainingLen -= maxMD4StrLen; + dataPtr += maxMD4StrLen; + } +} + + +uint32_t HashTk_hash32(HashTkHashTypes hashType, const char* data, int len) +{ + switch (hashType) + { + default: + { + printk_fhgfs(KERN_INFO, "Unknown hashtype: %d\n", hashType); + hashType = HashTkDefaultHash; + } + BEEGFS_FALLTHROUGH; + + case HASHTK_HSIEHHASH32: + { + return HashTk_HsiehHash32(data, len); + } break; + + case HASHTK_HALFMD4: + { + uint32_t buf[HashTk_HALFMD4_OUT_BUF_SIZE]; + uint32_t majHash; + + HashTk_halfMD4(data, len, buf); + + majHash = buf[HashTk_HALFMD4_MAJOR_BUFPOS]; + + return majHash; + + } break; + + } +} + +/** + * Note: This generates the 64bit hash by computing two 32bit hashes for the first and second half + * of the data buf. + * + * @data the buffer for which you want the hash value to be computed (arbitraty length) + * @len length of the data buffer + */ +uint64_t HashTk_hash64(HashTkHashTypes hashType, const char* data, int len) +{ + uint64_t hash64; + + switch (hashType) + { + default: + { + printk_fhgfs(KERN_INFO, "Unknown hashtype: %d\n", hashType); + hashType = HashTkDefaultHash; + } + BEEGFS_FALLTHROUGH; + + case HASHTK_HSIEHHASH32: + { + int len1stHalf = len / 2; + int len2ndHalf = len - len1stHalf; + + uint64_t high = HashTk_HsiehHash32(data, len1stHalf); + uint64_t low = HashTk_HsiehHash32(&data[len1stHalf], len2ndHalf); + + hash64 = (high << 32) | low; + } break; + + case HASHTK_HALFMD4: + { + uint32_t buf[HashTk_HALFMD4_OUT_BUF_SIZE]; + uint32_t majHash; + uint32_t minHash; + + HashTk_halfMD4(data, len, buf); + + majHash = buf[HashTk_HALFMD4_MAJOR_BUFPOS]; + minHash = buf[HashTk_HALFMD4_MINOR_BUFPOS]; + + hash64 = (uint64_t) majHash << 32 | (uint64_t) minHash; + + } break; + + } + + return hash64; +} + + +// Generates sha256 hash using the kernel crypto interface. +int HashTk_sha256(const unsigned char* data, unsigned int dataLen, unsigned char* outHash) +{ + const char* hashAlgName = "sha256"; + struct crypto_shash *alg; + struct shash_desc *sdesc; + int res = 0; + + alg = crypto_alloc_shash(hashAlgName, 0, 0); + if(IS_ERR(alg)) + { + printk_fhgfs(KERN_ERR, "Allocating shash failed: %ld\n", PTR_ERR(alg)); + return -1; + } + + sdesc = kmalloc(crypto_shash_descsize(alg), GFP_KERNEL); + if (sdesc == NULL) + { + printk_fhgfs(KERN_ERR, "Allocating hash memory failed\n"); + crypto_free_shash(alg); + return -1; + } + + sdesc->tfm = alg; + + res = crypto_shash_digest(sdesc, data, dataLen, outHash); + if (res != 0) + { + printk_fhgfs(KERN_ERR, "Calculating hash failed: %d\n", res); + } + + kfree(sdesc); + crypto_free_shash(alg); + return res; +} + +// Generates sha256 hash from the input byte slice and returns the auth secret containing the +// 8 most significant bytes of the hash in little endian order. Matches the behavior of other +// implementations. +int HashTk_authHash(const unsigned char* data, unsigned int dataLen, uint64_t* outHash) +{ + int res; + unsigned char buf[32]; + + res = HashTk_sha256(data, dataLen, buf); + if (res != 0) { + return res; + } + + *outHash = 0; + for(int i = 7; i >= 0; --i) + { + *outHash <<= 8; + *outHash += buf[i]; + } + + return 0; +} diff --git a/client_module/source/common/toolkit/HashTk.h b/client_module/source/common/toolkit/HashTk.h new file mode 100644 index 0000000..4d83461 --- /dev/null +++ b/client_module/source/common/toolkit/HashTk.h @@ -0,0 +1,36 @@ +#ifndef BUFFERTK_H_ +#define BUFFERTK_H_ + +#include + +enum HashTkHashTypes; +typedef enum HashTkHashTypes HashTkHashTypes; + + +extern uint32_t HashTk_hash32(HashTkHashTypes hashType, const char* data, int len); +extern uint64_t HashTk_hash64(HashTkHashTypes hashType, const char* data, int len); +static inline uint64_t HashTk_hash(HashTkHashTypes hashType, size_t hashSize, + const char* data, int len); + +int HashTk_sha256(const unsigned char* data, unsigned dataLen, unsigned char* outHash); +int HashTk_authHash(const unsigned char* data, unsigned dataLen, uint64_t* outHash); + +enum HashTkHashTypes +{ + HASHTK_HSIEHHASH32 = 0, + HASHTK_HALFMD4, // as used by ext4 +}; + +/** + * Caller decides if it wants a 32- or 64-bit hash + */ +uint64_t HashTk_hash(HashTkHashTypes hashType, size_t hashSize, const char* data, int len) +{ + if (hashSize == 64) + return HashTk_hash64(hashType, data, len); + else + return HashTk_hash32(hashType, data, len); +} + + +#endif /* BUFFERTK_H_ */ diff --git a/client_module/source/common/toolkit/ListTk.c b/client_module/source/common/toolkit/ListTk.c new file mode 100644 index 0000000..7a3a51a --- /dev/null +++ b/client_module/source/common/toolkit/ListTk.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include +#include "ListTk.h" + +#include + +struct nicaddr_sort_entry { + StrCpyList* preferences; + NicAddress* addr; +}; + +static int nicaddr_sort_comp(const void* l, const void* r) +{ + const struct nicaddr_sort_entry* lhs = l; + const struct nicaddr_sort_entry* rhs = r; + + StrCpyListIter it; + + StrCpyListIter_init(&it, lhs->preferences); + while (!StrCpyListIter_end(&it)) { + const char* value = StrCpyListIter_value(&it); + + if (strcmp(value, lhs->addr->name) == 0) + return -1; + if (strcmp(value, rhs->addr->name) == 0) + return 1; + + StrCpyListIter_next(&it); + } + + if (NicAddress_preferenceComp(lhs->addr, rhs->addr)) + return -1; + if (NicAddress_preferenceComp(rhs->addr, lhs->addr)) + return 1; + + return 0; +} + + + +void ListTk_cloneNicAddressList(NicAddressList* nicList, NicAddressList* nicListClone, bool includeTcp) +{ + NicAddressListIter iter; + + NicAddressList_init(nicListClone); + + NicAddressListIter_init(&iter, nicList); + + for( ; !NicAddressListIter_end(&iter); NicAddressListIter_next(&iter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&iter); + if (includeTcp || nicAddr->nicType != NICADDRTYPE_STANDARD) + { + NicAddress* nicAddrClone; + + // clone element + + nicAddrClone = (NicAddress*)os_kmalloc(sizeof(NicAddress) ); + + memcpy(nicAddrClone, nicAddr, sizeof(NicAddress) ); + + // append to the clone list + NicAddressList_append(nicListClone, nicAddrClone); + } + } +} + + +/** + * Returns a sorted clone of a nicList. + * + * Note: the comparison implemented here is a valid only if preferences contains + * all possible names ever encountered, or none at all. + */ +void ListTk_cloneSortNicAddressList(NicAddressList* nicList, NicAddressList* nicListClone, + StrCpyList* preferences) +{ + NicAddressListIter listIter; + + struct nicaddr_sort_entry* list, *p; + + /* i'm so sorry. we can't indicate failure here without a lot of work in App, and the + * lists are usually tiny anyway. */ + list = kcalloc(NicAddressList_length(nicList), sizeof(*list), GFP_NOFS | __GFP_NOFAIL); + p = list; + + NicAddressListIter_init(&listIter, nicList); + + while (!NicAddressListIter_end(&listIter)) { + p->preferences = preferences; + p->addr = NicAddressListIter_value(&listIter); + + NicAddressListIter_next(&listIter); + p++; + } + + sort(list, NicAddressList_length(nicList), sizeof(*list), nicaddr_sort_comp, NULL); + + /* *maybe* the clone already contains elements, don't rely on its size */ + p = list; + NicAddressList_init(nicListClone); + while (NicAddressList_length(nicList) != NicAddressList_length(nicListClone)) { + NicAddress* clone = os_kmalloc(sizeof(*clone)); + memcpy(clone, p->addr, sizeof(*clone)); + NicAddressList_append(nicListClone, clone); + p++; + } + + kfree(list); +} + +void ListTk_kfreeNicAddressListElems(NicAddressList* nicList) +{ + NicAddressListIter nicIter; + + NicAddressListIter_init(&nicIter, nicList); + + for( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + kfree(nicAddr); + } +} + +void ListTk_kfreeNicAddressStatsListElems(NicAddressStatsList* nicStatsList) +{ + NicAddressStatsListIter nicStatsIter; + + NicAddressStatsListIter_init(&nicStatsIter, nicStatsList); + + for( ; !NicAddressStatsListIter_end(&nicStatsIter); NicAddressStatsListIter_next(&nicStatsIter) ) + { + NicAddressStats* nicStats = NicAddressStatsListIter_value(&nicStatsIter); + NicAddressStats_uninit(nicStats); + kfree(nicStats); + } +} + +void ListTk_copyUInt16ListToVec(UInt16List* srcList, UInt16Vec* destVec) +{ + UInt16ListIter listIter; + + UInt16ListIter_init(&listIter, srcList); + + for( ; !UInt16ListIter_end(&listIter); UInt16ListIter_next(&listIter) ) + { + int currentElem = UInt16ListIter_value(&listIter); + + UInt16Vec_append(destVec, currentElem); + } +} + + +/** + * @param outPos zero-based list position of the searchStr if found, undefined otherwise + */ +bool ListTk_listContains(char* searchStr, StrCpyList* list, ssize_t* outPos) +{ + StrCpyListIter iter; + + (*outPos) = 0; + + StrCpyListIter_init(&iter, list); + + for( ; !StrCpyListIter_end(&iter); StrCpyListIter_next(&iter) ) + { + char* currentElem = StrCpyListIter_value(&iter); + + if(!strcmp(searchStr, currentElem) ) + return true; + + (*outPos)++; + } + + (*outPos) = -1; + + return false; +} + + diff --git a/client_module/source/common/toolkit/ListTk.h b/client_module/source/common/toolkit/ListTk.h new file mode 100644 index 0000000..74609d1 --- /dev/null +++ b/client_module/source/common/toolkit/ListTk.h @@ -0,0 +1,26 @@ +#ifndef LISTTK_H_ +#define LISTTK_H_ + +#include +#include +#include +#include +#include +#include +#include + + +/* includeTcp indicates whether or not to include TCP NICs in the cloned list */ +extern void ListTk_cloneNicAddressList(NicAddressList* nicList, NicAddressList* nicListClone, bool includeTcp); +extern void ListTk_cloneSortNicAddressList(NicAddressList* nicList, NicAddressList* nicListClone, + StrCpyList* preferences); +extern void ListTk_copyUInt16ListToVec(UInt16List* srcList, UInt16Vec* destVec); + +extern void ListTk_kfreeNicAddressListElems(NicAddressList* nicList); + +extern void ListTk_kfreeNicAddressStatsListElems(NicAddressStatsList* nicStatsList); + +extern bool ListTk_listContains(char* searchStr, StrCpyList* list, ssize_t* outPos); + + +#endif /*LISTTK_H_*/ diff --git a/client_module/source/common/toolkit/LockingTk.h b/client_module/source/common/toolkit/LockingTk.h new file mode 100644 index 0000000..c98ce28 --- /dev/null +++ b/client_module/source/common/toolkit/LockingTk.h @@ -0,0 +1,46 @@ +#ifndef LOCKINGTK_H_ +#define LOCKINGTK_H_ + +#include +#include + + +static inline const char* LockingTk_lockTypeToStr(int entryLockType); + + +/** + * @param entryLockType ENTRYLOCKTYPE_... + * @return pointer to static string (no freeing by caller required) + */ +const char* LockingTk_lockTypeToStr(int entryLockType) +{ + if(entryLockType & ENTRYLOCKTYPE_NOWAIT) + { + if(entryLockType & ENTRYLOCKTYPE_UNLOCK) + return "unlock|nowait"; + else + if(entryLockType & ENTRYLOCKTYPE_EXCLUSIVE) + return "exclusive|nowait"; + else + if(entryLockType & ENTRYLOCKTYPE_SHARED) + return "shared|nowait"; + else + return "unknown|nowait"; + } + else + { // waiting allowed + if(entryLockType & ENTRYLOCKTYPE_UNLOCK) + return "unlock|wait"; + else + if(entryLockType & ENTRYLOCKTYPE_EXCLUSIVE) + return "exclusive|wait"; + else + if(entryLockType & ENTRYLOCKTYPE_SHARED) + return "shared|wait"; + else + return "unknown|wait"; + } +} + + +#endif /* LOCKINGTK_H_ */ diff --git a/client_module/source/common/toolkit/LookupIntentInfoOut.h b/client_module/source/common/toolkit/LookupIntentInfoOut.h new file mode 100644 index 0000000..dd5a03d --- /dev/null +++ b/client_module/source/common/toolkit/LookupIntentInfoOut.h @@ -0,0 +1,164 @@ +#ifndef LOOKUPINTENTINFO_H_ +#define LOOKUPINTENTINFO_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct LookupIntentInfoOut; +typedef struct LookupIntentInfoOut LookupIntentInfoOut; + +static inline void LookupIntentInfoOut_initFromRespMsg(LookupIntentInfoOut* this, + LookupIntentRespMsg* respMsg); +static inline void LookupIntentInfoOut_setEntryInfoPtr(LookupIntentInfoOut* this, + EntryInfo* entryInfo); +static inline void LookupIntentInfoOut_setStripePattern(LookupIntentInfoOut* this, + StripePattern *pattern); + +static inline void LookupIntentInfoOut_uninit(LookupIntentInfoOut* this); + + + +/** + * Out-Args for _lookupCreateStat operations. Init only with LookupIntentInfoOut_prepare(). + * + * Note: This needs preparation by the same caller that does init of LookupIntentInfoIn + * (to assign some out-pointers before the actual method is called). + */ +struct LookupIntentInfoOut +{ + int responseFlags; // combination of LOOKUPINTENTRESPMSG_FLAG_... + + int lookupRes; + int statRes; + int createRes; // FhgfsOpsErr_... + int revalidateRes; // FhgfsOpsErr_SUCCESS if still valid, any other value otherwise + int openRes; + + fhgfs_stat* fhgfsStat; + + + EntryInfo* entryInfoPtr; // Note: Not owned by this object. + // Only deserialized if either lookup or create was successful. + int revalidateUpdatedFlags; + + + unsigned fileHandleIDLen; + const char* fileHandleID; + + // only set if open was successful + PathInfo pathInfo; + + StripePattern* stripePattern; +}; + + +static inline void LookupIntentInfoOut_prepare(LookupIntentInfoOut* this, + EntryInfo* outEntryInfo, fhgfs_stat* outFhgfsStat) +{ + // note: we only assign the pointers to the out-structs here + this->entryInfoPtr = outEntryInfo; + this->fhgfsStat = outFhgfsStat; + + this->responseFlags = 0; + + this->lookupRes = FhgfsOpsErr_INTERNAL; + this->statRes = FhgfsOpsErr_INTERNAL; + this->createRes = FhgfsOpsErr_INTERNAL; + this->revalidateRes = FhgfsOpsErr_INTERNAL; + this->openRes = FhgfsOpsErr_INTERNAL; + + this->stripePattern = NULL; +} + +void LookupIntentInfoOut_initFromRespMsg(LookupIntentInfoOut* this, + LookupIntentRespMsg* respMsg) +{ + this->responseFlags = respMsg->responseFlags; + + this->lookupRes = respMsg->lookupResult; + + if (this->responseFlags & LOOKUPINTENTRESPMSG_FLAG_CREATE) + this->createRes = respMsg->createResult; + + if (respMsg->responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) + { + this->statRes = respMsg->statResult; + + if (this->statRes == FhgfsOpsErr_SUCCESS) + StatData_getOsStat(&respMsg->statData, this->fhgfsStat); + } + + if (respMsg->responseFlags & LOOKUPINTENTRESPMSG_FLAG_REVALIDATE) + { + this->revalidateRes = respMsg->revalidateResult; + + /* no need to fill in EntryInfo on revalidate, we (the client) already have it. Only flags + * might need to be updated. */ + this->revalidateUpdatedFlags = respMsg->entryInfo.featureFlags; + } + else + if (respMsg->lookupResult == FhgfsOpsErr_SUCCESS || respMsg->createResult == FhgfsOpsErr_SUCCESS) + EntryInfo_dup(&respMsg->entryInfo, this->entryInfoPtr); + + + // only provided by the server on open + if (respMsg->responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) + { + this->openRes = respMsg->openResult; + + if (respMsg->openResult == FhgfsOpsErr_SUCCESS) + { + this->fileHandleIDLen = respMsg->fileHandleIDLen; + this->fileHandleID = kstrndup(respMsg->fileHandleID, respMsg->fileHandleIDLen, GFP_NOFS); + + PathInfo_dup(&respMsg->pathInfo, &this->pathInfo); + + this->stripePattern = + StripePattern_createFromBuf(respMsg->patternStart, respMsg->patternLength); + } + } +} + +void LookupIntentInfoOut_setEntryInfoPtr(LookupIntentInfoOut* this, EntryInfo* entryInfo) +{ + this->entryInfoPtr = entryInfo; +} + +void LookupIntentInfoOut_setStripePattern(LookupIntentInfoOut* this, StripePattern *pattern) +{ + this->stripePattern = pattern; +} + +/** + * Uninitialize (free) LookupIntentInfoOut values + */ +void LookupIntentInfoOut_uninit(LookupIntentInfoOut* this) +{ + // Free EntryInfo values + if ( (this->entryInfoPtr) && + (this->createRes == FhgfsOpsErr_SUCCESS || this->lookupRes == FhgfsOpsErr_SUCCESS) ) + EntryInfo_uninit(this->entryInfoPtr); + + // Destruct PathInfo + if (this->openRes == FhgfsOpsErr_SUCCESS) + { // unitialize values only set on open success and if there was an open at all + PathInfo_uninit(&this->pathInfo); + + if (this->stripePattern) + StripePattern_virtualDestruct(this->stripePattern); + } +} + + +#endif /*LOOKUPINTENTINFO_H_*/ diff --git a/client_module/source/common/toolkit/MathTk.h b/client_module/source/common/toolkit/MathTk.h new file mode 100644 index 0000000..c272548 --- /dev/null +++ b/client_module/source/common/toolkit/MathTk.h @@ -0,0 +1,40 @@ +#ifndef OPEN_MATHTK_H_ +#define OPEN_MATHTK_H_ + +#include + + +static inline unsigned MathTk_log2Int32(unsigned value); +static inline bool MathTk_isPowerOfTwo(unsigned value); + +/** + * Base 2 logarithm. + * + * See log2Int64() for details. + */ +unsigned MathTk_log2Int32(unsigned value) +{ + /* __builtin_clz: Count leading zeros - returns the number of leading 0-bits in x, starting + * at the most significant bit position. If x is 0, the result is undefined. */ + // (note: 8 is bits_per_byte) + unsigned result = (sizeof(value) * 8) - 1 - __builtin_clz(value); + + return result; +} + +/** + * Checks whether there is only a single bit set in value, in which case value is a power of + * two. + * + * @param value may not be 0 (result is undefined in that case). + * @return true if only a single bit is set (=> value is a power of two), false otherwise + */ +bool MathTk_isPowerOfTwo(unsigned value) +{ + //return ( (x != 0) && !(x & (x - 1) ) ); // this version is compatible with value==0 + + return !(value & (value - 1) ); +} + + +#endif /* OPEN_MATHTK_H_ */ diff --git a/client_module/source/common/toolkit/MessagingTk.c b/client_module/source/common/toolkit/MessagingTk.c new file mode 100644 index 0000000..9a04f6a --- /dev/null +++ b/client_module/source/common/toolkit/MessagingTk.c @@ -0,0 +1,826 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MessagingTk.h" + + +#define MSGTK_KMALLOC_RECV_BUF_LEN (4*1024) /* kmalloc-style recv is only ok for small replies */ +#define MSGTK_STATE_SLEEP_MS 5000 /* how long to sleep if target state not good/offline */ +#define MSGTK_INFINITE_RETRY_WAIT_MS 5000 // how long to wait if peer asks for retry + +/** + * Note: rrArgs->outRespBuf must be returned/freed by the caller (depending on respBufType) + * @param sock socket to use. If null, one will be pulled from Node's pool + */ +FhgfsOpsErr MessagingTk_requestResponseWithRRArgsSock(App* app, + RequestResponseArgs* rrArgs, Socket* sock) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Messaging (RPC)"; + + bool infiniteRetries = rrArgs->numRetries ? false : true; + + unsigned currentRetryNum = 0; + FhgfsOpsErr commRes; + bool wasIndirectCommErr = false; + + for( ; ; ) // retry loop + { + App_incNumRPCs(app); + + commRes = __MessagingTk_requestResponseWithRRArgsComm(app, rrArgs, NULL, &wasIndirectCommErr, sock); + if(likely(commRes == FhgfsOpsErr_SUCCESS) ) + return FhgfsOpsErr_SUCCESS; + else + if (fatal_signal_pending(current)) + { // no retry allowed in this situation + return FhgfsOpsErr_INTERRUPTED; + } + else + if(!Node_getIsActive(rrArgs->node) ) + { // no retry allowed in this situation + return FhgfsOpsErr_UNKNOWNNODE; + } + else + if(commRes == FhgfsOpsErr_WOULDBLOCK) + return FhgfsOpsErr_COMMUNICATION; // no retries in this case + else + if( (commRes == FhgfsOpsErr_AGAIN) && App_getConnRetriesEnabled(app) ) + { // retry infinitely + currentRetryNum = 0; + + Thread_sleep(MSGTK_INFINITE_RETRY_WAIT_MS); + + continue; + } + else + if(commRes != FhgfsOpsErr_COMMUNICATION) + { // no retry allowed in this situation + return commRes; + } + + if(App_getConnRetriesEnabled(app) && + (infiniteRetries || (currentRetryNum < rrArgs->numRetries) ) ) + { // we have a retry left + MessagingTk_waitBeforeRetry(currentRetryNum); + currentRetryNum++; + + if(currentRetryNum == 1 // log retry message only on first retry (to not spam the log) + && !(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_RETRY) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + Logger_logFormatted(log, Log_NOTICE, logContext, + "Retrying communication with node: %s", nodeAndType.buf); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + } + } + else + { // no more retries left + return FhgfsOpsErr_COMMUNICATION; + } + + } +} + +/** + * Note: You probably rather want to call the alternative method, which gets buffers from the + * store (unless you are the logger and want to avoid a deadlock wrt depleted msg buffers from the + * store). + * Note: Allows only a single retry. (One retry allowed because we might have gotten an already + * broken connection from the conn pool.) + * + * @param outRespBuf will be kmalloced and needs to be kfreed by the caller + */ +FhgfsOpsErr MessagingTk_requestResponseKMalloc(App* app, Node* node, NetMessage* requestMsg, + unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg) +{ + RequestResponseArgs rrArgs; + FhgfsOpsErr rrRes; + + RequestResponseArgs_prepare(&rrArgs, node, requestMsg, respMsgType); + + rrArgs.respBufType = MessagingTkBufType_kmalloc; + + rrRes = MessagingTk_requestResponseWithRRArgs(app, &rrArgs); + + *outRespBuf = rrArgs.outRespBuf; + *outRespMsg = rrArgs.outRespMsg; + + return rrRes; +} + + +/** + * Note: Allows only a single retry. (One retry allowed because we might have gotten an already + * broken connection from the conn pool.) + * + * @param outRespBuf must be returned to the store - not freed! + * @param sock socket to use. If null, one will be pulled from Node's pool + */ +FhgfsOpsErr MessagingTk_requestResponseSock(App* app, Node* node, NetMessage* requestMsg, + unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg, Socket* sock) +{ + RequestResponseArgs rrArgs; + FhgfsOpsErr rrRes; + + RequestResponseArgs_prepare(&rrArgs, node, requestMsg, respMsgType); + + rrRes = MessagingTk_requestResponseWithRRArgsSock(app, &rrArgs, sock); + + *outRespBuf = rrArgs.outRespBuf; + *outRespMsg = rrArgs.outRespMsg; + + return rrRes; +} + +/** + * Note: Uses the number of retries that has been defined in the app config. + * + * @param outRespBuf must be returned to the store - not freed! + */ +FhgfsOpsErr MessagingTk_requestResponseRetry(App* app, Node* node, NetMessage* requestMsg, + unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg) +{ + Config* cfg = App_getConfig(app); + + RequestResponseArgs rrArgs; + FhgfsOpsErr rrRes; + + RequestResponseArgs_prepare(&rrArgs, node, requestMsg, respMsgType); + + rrArgs.numRetries = Config_getConnNumCommRetries(cfg); + + rrRes = MessagingTk_requestResponseWithRRArgs(app, &rrArgs); + + *outRespBuf = rrArgs.outRespBuf; + *outRespMsg = rrArgs.outRespMsg; + + return rrRes; +} + + + +/** + * Sends a message to a node and receives a response. + * Can handle target states and mapped mirror IDs. Node does not need to be referenced by caller. + * + * If target states are provided, communication might be skipped for certain states. + * + * This version allows only a single retry. (One retry allowed because we might have gotten an + * already broken connection from the conn pool.) + * + * note: uses the number of retries that has been defined in the app config. + * + * @param rrArgs outRespBuf must be returned to the store - not freed; rrArgs->nodeID may optionally + * be provided when calling this. + * @return received message and buffer are available through rrArgs in case of success. + */ +FhgfsOpsErr MessagingTk_requestResponseNode(App* app, + RequestResponseNode* rrNode, RequestResponseArgs* rrArgs) +{ + FhgfsOpsErr rrRes; + + rrArgs->numRetries = 1; + rrArgs->rrFlags = 0; + rrArgs->respBufType = MessagingTkBufType_BufStore; + + rrRes = __MessagingTk_requestResponseNodeRetry(app, rrNode, rrArgs); + + return rrRes; +} + +/** + * Sends a message to a node and receives a response. + * Can handle target states and mapped mirror IDs. Node does not need to be referenced by caller. + * + * If target states are provided, communication might be skipped for certain states. + * + * note: uses the number of retries that has been defined in the app config. + * + * @param rrArgs outRespBuf must be returned to the store - not freed; rrArgs->nodeID may optionally + * be provided when calling this. + * @return received message and buffer are available through rrArgs in case of success. + */ +FhgfsOpsErr MessagingTk_requestResponseNodeRetryAutoIntr(App* app, RequestResponseNode* rrNode, + RequestResponseArgs* rrArgs) +{ + Config* cfg = App_getConfig(app); + + rrArgs->numRetries = Config_getConnNumCommRetries(cfg); + rrArgs->rrFlags = REQUESTRESPONSEARGS_FLAG_ALLOWSTATESLEEP; + rrArgs->respBufType = MessagingTkBufType_BufStore; + + return __MessagingTk_requestResponseNodeRetry(app, rrNode, rrArgs); +} + + +/** + * Sends a message to a node and receives a response. + * Can handle target states and mapped mirror IDs. Node does not need to be referenced by caller. + * + * If target states are provided, communication might be skipped for certain states. + * + * @param rrArgs rrArgs->nodeID may optionally be provided when calling this. + * @return received message and buffer are available through rrArgs in case of success. + */ +FhgfsOpsErr __MessagingTk_requestResponseNodeRetry(App* app, RequestResponseNode* rrNode, + RequestResponseArgs* rrArgs) +{ + const char* logContext = "Messaging (RPC node)"; + + unsigned currentRetryNum = 0; // used number of retries so far + FhgfsOpsErr commRes; + struct BuddySequenceNumber* handle = NULL; + struct MirrorBuddyGroup* group = NULL; + bool wasIndirectCommErr = false; + + BEEGFS_BUG_ON_DEBUG(rrNode->targetStates == NULL, "targetStates missing"); + BEEGFS_BUG_ON_DEBUG(rrNode->mirrorBuddies == NULL, "mirrorBuddies missing"); + + for( ; ; ) // retry loop + { + bool nodeNeedsRelease = false; + int acquireSeqRes = 0; + bool seqAckIsSelective = false; + + // select the right targetID + + NumNodeID nodeID; // don't modify caller's nodeID + + if (rrNode->peer.isMirrorGroup) + { // given targetID refers to a buddy mirror group + nodeID = (NumNodeID){MirrorBuddyGroupMapper_getPrimaryTargetID(rrNode->mirrorBuddies, + rrNode->peer.address.group)}; + + if (unlikely(NumNodeID_isZero(&nodeID))) + { + Logger* log = App_getLogger(app); + Logger_logErrFormatted(log, logContext, "Invalid mirror buddy group ID: %u", + rrNode->peer.address.group); + + commRes = FhgfsOpsErr_UNKNOWNNODE; + goto exit; + } + + if (rrArgs->requestMsg->ops->supportsSequenceNumbers) + { + rrArgs->requestMsg->msgHeader.msgFlags |= MSGHDRFLAG_HAS_SEQUENCE_NO; + + if (rrArgs->requestMsg->msgHeader.msgSequence == 0) + acquireSeqRes = MirrorBuddyGroupMapper_acquireSequenceNumber(rrNode->mirrorBuddies, + rrNode->peer.address.group, &rrArgs->requestMsg->msgHeader.msgSequence, + &rrArgs->requestMsg->msgHeader.msgSequenceDone, &seqAckIsSelective, &handle, + &group); + + if (!acquireSeqRes) + { + if (seqAckIsSelective) + rrArgs->requestMsg->msgHeader.msgFlags |= MSGHDRFLAG_IS_SELECTIVE_ACK; + } + else + { + Logger* log = App_getLogger(app); + NodeType storeType = NodeStoreEx_getStoreType(rrNode->nodeStore); + Logger_logFormatted(log, Log_WARNING, logContext, + "Could not generate seq#. Group IP: %u; type: %s", rrNode->peer.address.group, + Node_nodeTypeToStr(storeType)); + + commRes = acquireSeqRes == EINTR ? FhgfsOpsErr_INTERRUPTED : FhgfsOpsErr_UNKNOWNNODE; + goto exit; + } + } + } + else + nodeID = rrNode->peer.address.target; + + // check target state + + if (rrNode->targetStates) + { + CombinedTargetState state; + bool getStateRes = TargetStateStore_getState(rrNode->targetStates, nodeID.value, + &state); + + if (!getStateRes || + state.reachabilityState != TargetReachabilityState_ONLINE || + (rrNode->peer.isMirrorGroup && + state.consistencyState != TargetConsistencyState_GOOD)) + { + if(state.reachabilityState == TargetReachabilityState_OFFLINE) + { // no need to wait for offline servers + LOG_DEBUG_FORMATTED(App_getLogger(app), Log_SPAM, logContext, + "Skipping communication with offline nodeID: %u", nodeID.value); + + commRes = FhgfsOpsErr_COMMUNICATION; + goto exit; + } + + if(!(rrArgs->rrFlags & REQUESTRESPONSEARGS_FLAG_ALLOWSTATESLEEP) ) + { // caller did not allow sleeping if target state is not {good, offline} + LOG_DEBUG_FORMATTED(App_getLogger(app), Log_SPAM, logContext, + "Skipping communication with nodeID: %u; " + "target state: %s / %s", + nodeID.value, TargetStateStore_reachabilityStateToStr(state.reachabilityState), + TargetStateStore_consistencyStateToStr(state.consistencyState) ); + + commRes = FhgfsOpsErr_COMMUNICATION; + goto exit; + } + + // sleep on states other than "good" and "offline" with mirroring + if(rrNode->mirrorBuddies) + { + LOG_DEBUG_FORMATTED(App_getLogger(app), Log_DEBUG, logContext, + "Waiting before communication because of node state. " + "nodeID: %u; node state: %s / %s", + nodeID.value, TargetStateStore_reachabilityStateToStr(state.reachabilityState), + TargetStateStore_consistencyStateToStr(state.consistencyState) ); + + Thread_sleep(MSGTK_STATE_SLEEP_MS); + if (fatal_signal_pending(current)) + { // make sure we don't loop endless if signal pending + LOG_DEBUG_FORMATTED(App_getLogger(app), Log_DEBUG, logContext, + "Waiting before communication was interrupted by signal. " + "nodeID: %u; node state: %s / %s", + nodeID.value, TargetStateStore_reachabilityStateToStr(state.reachabilityState), + TargetStateStore_consistencyStateToStr(state.consistencyState) ); + + commRes = FhgfsOpsErr_INTERRUPTED; + goto exit; + } + + currentRetryNum = 0; // reset retries in case of unusable target state + continue; + } + } + } + + // reference node (if not provided by caller already) + + if(!rrArgs->node) + { + rrArgs->node = NodeStoreEx_referenceNode(rrNode->nodeStore, nodeID); + + if(!rrArgs->node) + { + Logger* log = App_getLogger(app); + NodeType storeType = NodeStoreEx_getStoreType(rrNode->nodeStore); + Logger_logFormatted(log, Log_WARNING, logContext, "Unknown nodeID: %u; type: %s", + nodeID.value, Node_nodeTypeToStr(storeType)); + + commRes = FhgfsOpsErr_UNKNOWNNODE; + goto exit; + } + + nodeNeedsRelease = true; + } + else + BEEGFS_BUG_ON_DEBUG(Node_getNumID(rrArgs->node).value != nodeID.value, + "Mismatch between given rrArgs->node ID and nodeID"); + + // communicate + + commRes = __MessagingTk_requestResponseWithRRArgsComm(app, rrArgs, group, + &wasIndirectCommErr, NULL); + + if(likely(commRes == FhgfsOpsErr_SUCCESS) ) + goto release_node_and_break; + else + if (fatal_signal_pending(current)) + { // no retry allowed in this situation + commRes = FhgfsOpsErr_INTERRUPTED; + goto release_node_and_break; + } + else + if(!Node_getIsActive(rrArgs->node) ) + { // no retry allowed in this situation + commRes = FhgfsOpsErr_UNKNOWNNODE; + goto release_node_and_break; + } + else + if(commRes == FhgfsOpsErr_WOULDBLOCK) + { // no retries in this case + commRes = FhgfsOpsErr_COMMUNICATION; + goto release_node_and_break; + } + else + if( (commRes == FhgfsOpsErr_AGAIN) && App_getConnRetriesEnabled(app) ) + { // retry infinitely + currentRetryNum = 0; + + Thread_sleep(MSGTK_INFINITE_RETRY_WAIT_MS); + + goto release_node_and_continue; + } + else + if(commRes != FhgfsOpsErr_COMMUNICATION) + { // no retry allowed in this situation + goto release_node_and_break; + } + + if(App_getConnRetriesEnabled(app) && + (!rrArgs->numRetries || (currentRetryNum < rrArgs->numRetries) ) ) + { // we have a retry left + MessagingTk_waitBeforeRetry(currentRetryNum); + currentRetryNum++; + + /* if the metadata server reports an indirect communication error, we must retry the + * communication with a new sequence number. if we reuse the current sequence number, the + * meta server will continue to reply "indirect communication error", sending us into a + * very long loop of pointless retries, followed by -EIO to userspace. */ + if (wasIndirectCommErr && handle) + { + MirrorBuddyGroup_releaseSequenceNumber(group, &handle); + rrArgs->requestMsg->msgHeader.msgSequence = 0; + wasIndirectCommErr = false; + handle = NULL; + } + + if(currentRetryNum == 1 // log retry message only on first retry (to not spam the log) + && !(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_RETRY) ) + { + Logger* log = App_getLogger(app); + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + Logger_logFormatted(log, Log_NOTICE, logContext, + "Retrying communication with node: %s", nodeAndType.buf); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + } + } + else + { // no more retries left + commRes = FhgfsOpsErr_COMMUNICATION; + goto release_node_and_break; + } + + release_node_and_continue: + if(nodeNeedsRelease) + { + Node_put(rrArgs->node); + rrArgs->node = NULL; + } + + continue; + + // cleanup before early loop exit + release_node_and_break: + if(nodeNeedsRelease) + { + Node_put(rrArgs->node); + rrArgs->node = NULL; + } + + break; + } + +exit: + if (handle) + MirrorBuddyGroup_releaseSequenceNumber(group, &handle); + + return commRes; +} + + + +/** + * Send a request message to a node and receive the response. + * + * @param rrArgs: + * .node receiver of msg; + * .requestMsg the message that should be sent to the receiver; + * .respMsgType expected response message type; + * .outRespBuf response buffer if successful (must be returned to store by the caller); + * .outRespMsg response message if successful (must be deleted by the caller); + * @param sock socket to use. If NULL, one will be pulled from Node's pool + * @return FhgfsOpsErr_COMMUNICATION on comm error, FhgfsOpsErr_WOULDBLOCK if remote side + * encountered an indirect comm error and suggests not to try again, FhgfsOpsErr_AGAIN if other + * side is suggesting infinite retries. + */ +FhgfsOpsErr __MessagingTk_requestResponseWithRRArgsComm(App* app, + RequestResponseArgs* rrArgs, MirrorBuddyGroup* group, bool* wasIndirectCommErr, + Socket* sock) +{ + /* note: keep in mind that there are multiple alternative response buf alloc types avilable, + e.g. "kmalloc" or "get from store". */ + + Logger* log = App_getLogger(app); + const char* logContext = "Messaging (RPC)"; + + NodeConnPool* connPool = Node_getConnPool(rrArgs->node); + + FhgfsOpsErr retVal = FhgfsOpsErr_COMMUNICATION; + + unsigned bufLen; // length of shared send/recv buffer + unsigned sendBufLen; // serialization length for sending + ssize_t respRes = 0; + ssize_t sendRes; + + // cleanup init + bool releaseSock = sock == NULL; + rrArgs->outRespBuf = NULL; + rrArgs->outRespMsg = NULL; + + // connect + // note: acquireStreamSocket() will fail immediately if a signal is pending + + if (sock == NULL) + sock = NodeConnPool_acquireStreamSocket(connPool); + if(unlikely(!sock) ) + { // not connected + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED) && + !fatal_signal_pending(current)) + { // only log once and only if user didn't manually interrupt with signal (to avoid log spam) + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + Logger_logFormatted(log, Log_WARNING, logContext, + "Unable to connect to: %s", nodeAndType.buf); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED; + } + + return FhgfsOpsErr_COMMUNICATION; + } + + // prepare send buffer + + sendBufLen = NetMessage_getMsgLength(rrArgs->requestMsg); + + if(rrArgs->respBufType == MessagingTkBufType_BufStore) + { // pre-alloc'ed buffer from store + + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + bufLen = NoAllocBufferStore_getBufSize(bufStore); + + if(unlikely(bufLen < sendBufLen) ) + { // should never happen: trying to send a msg that is larger than pre-alloc'ed buf size + Logger_logFormatted(log, Log_CRITICAL, logContext, + "BufferStore buf size (%u) too small for msg length (%u). Message type: %hu", + bufLen, sendBufLen, NetMessage_getMsgType(rrArgs->requestMsg) ); + + retVal = FhgfsOpsErr_INTERNAL; + goto socket_invalidate; + } + + rrArgs->outRespBuf = NoAllocBufferStore_waitForBuf(bufStore); + } + else + { // alloc'ed buffer + + bufLen = MAX(MSGTK_KMALLOC_RECV_BUF_LEN, sendBufLen); + + rrArgs->outRespBuf = (char*)os_kmalloc(bufLen); + if(unlikely(!rrArgs->outRespBuf) ) + { + Logger_logFormatted(log, Log_CRITICAL, logContext, + "Buffer allocation failed. Message type: %hu; Alloc size: %u", + NetMessage_getMsgType(rrArgs->requestMsg), bufLen); + + retVal = FhgfsOpsErr_OUTOFMEM; + goto socket_invalidate; + } + } + + NetMessage_serialize(rrArgs->requestMsg, rrArgs->outRespBuf, sendBufLen); + + // send request + + sendRes = Socket_send_kernel(sock, rrArgs->outRespBuf, sendBufLen, 0); + + if(unlikely(sendRes != (ssize_t)sendBufLen) ) + goto socket_exception; + + // receive response + + respRes = MessagingTk_recvMsgBuf(app, sock, rrArgs->outRespBuf, bufLen); + + if(unlikely(respRes <= 0) ) + { // error + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_COMMERR) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + if (fatal_signal_pending(current)){ + Logger_logFormatted(log, Log_NOTICE, logContext, + "Receive interrupted by signal. Node: %s @ %s", + nodeAndType.buf, Socket_getPeername(sock) ); + } + else + if(respRes == -ETIMEDOUT) { + Logger_logFormatted(log, Log_WARNING, logContext, + "Receive timed out from %s @ %s", + nodeAndType.buf, Socket_getPeername(sock) ); + } + else { + Logger_logFormatted(log, Log_WARNING, logContext, + "Receive failed from %s @ %s (recv result: %zi)", + nodeAndType.buf, Socket_getPeername(sock), respRes); + } + + Logger_logFormatted(log, Log_DEBUG, logContext, + "Expected response type: %u", rrArgs->respMsgType); + } + + goto socket_invalidate; + } + + // got response => deserialize it + rrArgs->outRespMsg = NetMessageFactory_createFromBuf(app, rrArgs->outRespBuf, respRes); + + if (unlikely(rrArgs->outRespMsg->msgHeader.msgType == NETMSGTYPE_AckNotifyResp)) + { + /* a failover happened before the primary could send a negative response to us, and the + * secondary has already received word about the failed operation. treat this case like a + * communication error and retry the message with a new sequence number. */ + *wasIndirectCommErr = true; + goto socket_invalidate; + } + + if(unlikely(NetMessage_getMsgType(rrArgs->outRespMsg) == NETMSGTYPE_GenericResponse) ) + { // special control msg received + retVal = __MessagingTk_handleGenericResponse(app, rrArgs, group, wasIndirectCommErr); + if(retVal != FhgfsOpsErr_INTERNAL) + { // we can re-use the connection + if (releaseSock) + NodeConnPool_releaseStreamSocket(connPool, sock); + goto cleanup_no_socket; + } + + goto socket_invalidate; + } + + if(unlikely(NetMessage_getMsgType(rrArgs->outRespMsg) != rrArgs->respMsgType) ) + { // response invalid (wrong msgType) + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + Logger_logErrFormatted(log, logContext, + "Received invalid response type: %hu; expected: %d. Disconnecting: %s (%s)", + NetMessage_getMsgType(rrArgs->outRespMsg), rrArgs->respMsgType, + nodeAndType.buf, Socket_getPeername(sock) ); + + retVal = FhgfsOpsErr_INTERNAL; + goto socket_invalidate; + } + + // correct response => return it (through rrArgs) + + if (releaseSock) + NodeConnPool_releaseStreamSocket(connPool, sock); + + return FhgfsOpsErr_SUCCESS; + + + // error handling (something went wrong)... + + socket_exception: + { + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_COMMERR) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + Logger_logErrFormatted(log, logContext, + "Communication error: Node: %s (comm result: %lld; message type: %hu)", + nodeAndType.buf, + (long long)( (sendRes <= 0) ? sendRes : respRes), + NetMessage_getMsgType(rrArgs->requestMsg) ); + + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_COMMERR; + } + } + + socket_invalidate: + if (releaseSock) + { + NodeConnPool_invalidateStreamSocket(connPool, sock); + } + + // clean up + cleanup_no_socket: + + if(rrArgs->outRespMsg) + { + NETMESSAGE_FREE(rrArgs->outRespMsg); + rrArgs->outRespMsg = NULL; + } + + if(rrArgs->outRespBuf) + { + if(rrArgs->respBufType == MessagingTkBufType_BufStore) + { + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + NoAllocBufferStore_addBuf(bufStore, rrArgs->outRespBuf); + } + else + kfree(rrArgs->outRespBuf); + + rrArgs->outRespBuf = NULL; + } + + return retVal; +} + +/** + * Print log message and determine appropriate return code for requestResponseComm. + * + * If FhgfsOpsErr_INTERNAL is returned, the connection should be invalidated. + * + * @return FhgfsOpsErr_COMMUNICATION on indirect comm error (retry suggested), + * FhgfsOpsErr_WOULDBLOCK if remote side encountered an indirect comm error and suggests not to + * try again, FhgfsOpsErr_AGAIN if other side is suggesting infinite retries. + */ +FhgfsOpsErr __MessagingTk_handleGenericResponse(App* app, RequestResponseArgs* rrArgs, + MirrorBuddyGroup* group, bool* wasIndirectCommErr) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Messaging (RPC)"; + + FhgfsOpsErr retVal; + + GenericResponseMsg* genericResp = (GenericResponseMsg*)rrArgs->outRespMsg; + NodeString nodeAndType; + Node_copyAliasWithTypeStr(rrArgs->node, &nodeAndType); + + *wasIndirectCommErr = false; + + switch(GenericResponseMsg_getControlCode(genericResp) ) + { + case GenericRespMsgCode_TRYAGAIN: + { + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN) ) + { + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN; + + Logger_logFormatted(log, Log_NOTICE, logContext, + "Peer is asking for a retry: %s; Reason: %s", + nodeAndType.buf, + GenericResponseMsg_getLogStr(genericResp) ); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + } + + retVal = FhgfsOpsErr_AGAIN; + } break; + + case GenericRespMsgCode_INDIRECTCOMMERR: + { + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM) ) + { + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM; + + Logger_logFormatted(log, Log_NOTICE, logContext, + "Peer reported indirect communication error: %s; Reason: %s", + nodeAndType.buf, + GenericResponseMsg_getLogStr(genericResp) ); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + } + + retVal = FhgfsOpsErr_COMMUNICATION; + *wasIndirectCommErr = true; + } break; + + case GenericRespMsgCode_NEWSEQNOBASE: + { + if (group) + { + MirrorBuddyGroup_setSeqNoBase(group, + genericResp->simpleIntStringMsg.netMessage.msgHeader.msgSequence); + retVal = FhgfsOpsErr_COMMUNICATION; + } + else + { + Logger_logFormatted(log, Log_WARNING, logContext, + "Received invalid seqNoBase update"); + retVal = FhgfsOpsErr_INTERNAL; + } + + break; + } + + default: + { + Logger_logFormatted(log, Log_NOTICE, logContext, + "Peer replied with unknown control code: %s; Code: %u; Reason: %s", + nodeAndType.buf, + (unsigned)GenericResponseMsg_getControlCode(genericResp), + GenericResponseMsg_getLogStr(genericResp) ); + Logger_logFormatted(log, Log_DEBUG, logContext, + "Message type: %hu", NetMessage_getMsgType(rrArgs->requestMsg) ); + + retVal = FhgfsOpsErr_INTERNAL; + } break; + } + + + return retVal; +} diff --git a/client_module/source/common/toolkit/MessagingTk.h b/client_module/source/common/toolkit/MessagingTk.h new file mode 100644 index 0000000..02f707b --- /dev/null +++ b/client_module/source/common/toolkit/MessagingTk.h @@ -0,0 +1,208 @@ +#ifndef MESSAGINGTK_H_ +#define MESSAGINGTK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct Socket; + +extern FhgfsOpsErr MessagingTk_requestResponseWithRRArgsSock(App* app, + RequestResponseArgs* rrArgs, Socket* sock); +extern FhgfsOpsErr MessagingTk_requestResponseKMalloc(App* app, Node* node, + NetMessage* requestMsg, unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg); +extern FhgfsOpsErr MessagingTk_requestResponseSock(App* app, Node* node, + NetMessage* requestMsg, unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg, + Socket* sock); + +extern FhgfsOpsErr MessagingTk_requestResponseRetry(App* app, Node* node, + NetMessage* requestMsg, unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg); + +extern FhgfsOpsErr MessagingTk_requestResponseNode(App* app, + RequestResponseNode* rrNode, RequestResponseArgs* rrArgs); +extern FhgfsOpsErr MessagingTk_requestResponseNodeRetryAutoIntr(App* app, + RequestResponseNode* rrNode, RequestResponseArgs* rrArgs); + +// private + +extern FhgfsOpsErr __MessagingTk_requestResponseWithRRArgsComm(App* app, + RequestResponseArgs* rrArgs, MirrorBuddyGroup* group, bool* wasIndirectCommErr, Socket* sock); +extern FhgfsOpsErr __MessagingTk_handleGenericResponse(App* app, RequestResponseArgs* rrArgs, + MirrorBuddyGroup* group, bool* wasIndirectCommErr); + +extern FhgfsOpsErr __MessagingTk_requestResponseNodeRetry(App* app, + RequestResponseNode* rrNode, RequestResponseArgs* rrArgs); + +// inliners + +static inline char* MessagingTk_createMsgBuf(NetMessage* msg); +static inline ssize_t MessagingTk_recvMsgBuf(App* app, Socket* sock, char* bufIn, size_t bufInLen); + +static inline void MessagingTk_waitBeforeRetry(unsigned currentNumRetry); +static inline int MessagingTk_getRetryWaitMS(unsigned currentNumRetry); +static inline FhgfsOpsErr MessagingTk_requestResponseWithRRArgs(App* app, + RequestResponseArgs* rrArgs); +static inline FhgfsOpsErr MessagingTk_requestResponse(App* app, Node* node, + NetMessage* requestMsg, unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg); + + + +/** + * Creates a message buffer of the required size and serializes the message to it. + * Note: Uses kmalloc, so this is only safe for messages < 128kB + * + * @return buffer must be kfreed by the caller + */ +char* MessagingTk_createMsgBuf(NetMessage* msg) +{ + size_t bufLen = NetMessage_getMsgLength(msg); + char* buf = (char*)os_kmalloc(bufLen); + + NetMessage_serialize(msg, buf, buf ? bufLen : 0); + + return buf; +} + +/** + * Receive a complete message into given buffer, extended version. + * + * Note: This is the version for pre-allocated buffers. + * + * @param bufIn incoming message buffer. + * @return positive message length on success, <=0 on error (e.g. -ETIMEDOUT on recv timeout, + * -EMSGSIZE if msg too large for buffer). + */ +ssize_t MessagingTk_recvMsgBuf(App* app, Socket* sock, char* bufIn, size_t bufInLen) +{ + const char* logContext = "MessagingTk (recv msg)"; + + size_t numReceived = 0; + ssize_t recvRes; + size_t msgLength; + Config* cfg = App_getConfig(app); + + // receive at least the message header + + recvRes = Socket_recvExactTEx_kernel(sock, bufIn, NETMSG_MIN_LENGTH, 0, cfg->connMsgLongTimeout, + &numReceived); + + if(unlikely(recvRes <= 0) ) + { // socket error + Logger* log = App_getLogger(app); + Logger_logFormatted(log, Log_DEBUG, logContext, "Failed to receive message header from: %s", + Socket_getPeername(sock) ); + + goto socket_exception; + } + + msgLength = NetMessage_extractMsgLengthFromBuf(bufIn); + + if(msgLength <= numReceived) + return msgLength; // success (msg had no additional payload) + + + // receive the message payload part + + if(unlikely(msgLength > bufInLen) ) + { // message too big to be accepted + Logger* log = App_getLogger(app); + Logger_logFormatted(log, Log_WARNING, logContext, + "Received a message that is too large from: %s (bufLen: %lld, msgLen: %lld)", + Socket_getPeername(sock), (long long)bufInLen, (long long)msgLength); + + return -EMSGSIZE; + } + + recvRes = Socket_recvExactTEx_kernel(sock, &bufIn[numReceived], msgLength-numReceived, 0, + cfg->connMsgLongTimeout, &numReceived); + + if(unlikely(recvRes <= 0) ) + goto socket_exception; + + // success + return msgLength; + + + socket_exception: + { + Logger* log = App_getLogger(app); + + if (fatal_signal_pending(current)) + Logger_logFormatted(log, Log_DEBUG, logContext, + "Receive interrupted by signal"); + else + Logger_logFormatted(log, Log_DEBUG, logContext, + "Receive failed from: %s (ErrCode: %lld)", + Socket_getPeername(sock), (long long)recvRes); + } + + return recvRes; +} + +void MessagingTk_waitBeforeRetry(unsigned currentNumRetry) +{ + int retryWaitMS = MessagingTk_getRetryWaitMS(currentNumRetry); + + if(retryWaitMS) + Thread_sleep(retryWaitMS); +} + +int MessagingTk_getRetryWaitMS(unsigned currentNumRetry) +{ + // note: keep in sync with __Config_initConnNumCommRetries() + + int retryWaitMS ; + + if(!currentNumRetry) + { + retryWaitMS = 0; + } + else + if(currentNumRetry <= 12) // 1st minute + { + retryWaitMS = 5000; + } + else + if(currentNumRetry <= 24) // 2nd to 5th minute + { + retryWaitMS = 20000; + } + else // after 5th minute + { + retryWaitMS = 60000; + } + + return retryWaitMS; +} + +/** + * Note: rrArgs->outRespBuf must be returned/freed by the caller (depending on respBufType) + */ +FhgfsOpsErr MessagingTk_requestResponseWithRRArgs(App* app, RequestResponseArgs* rrArgs) +{ + return MessagingTk_requestResponseWithRRArgsSock(app, rrArgs, NULL); +} + +/** + * Note: Allows only a single retry. (One retry allowed because we might have gotten an already + * broken connection from the conn pool.) + * + * @param outRespBuf must be returned to the store - not freed! + */ +FhgfsOpsErr MessagingTk_requestResponse(App* app, Node* node, NetMessage* requestMsg, + unsigned respMsgType, char** outRespBuf, NetMessage** outRespMsg) +{ + return MessagingTk_requestResponseSock(app, node, requestMsg, respMsgType, + outRespBuf, outRespMsg, NULL); +} + +#endif /*MESSAGINGTK_H_*/ diff --git a/client_module/source/common/toolkit/MessagingTkArgs.h b/client_module/source/common/toolkit/MessagingTkArgs.h new file mode 100644 index 0000000..efe1dc0 --- /dev/null +++ b/client_module/source/common/toolkit/MessagingTkArgs.h @@ -0,0 +1,157 @@ +#ifndef MESSAGINGTKARGS_H_ +#define MESSAGINGTKARGS_H_ + +#include +#include +#include + + +#define REQUESTRESPONSEARGS_FLAG_ALLOWSTATESLEEP 2 // sleep on non-{good,offline} state + +#define REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN 1 // peer asked for retry +#define REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM 2 /* peer encountered indirect comm + error, suggests retry */ +#define REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM_NOTAGAIN 4 /* peer encountered indirect comm + error, suggests no retry */ +#define REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED 8 // unable to establish connection +#define REQUESTRESPONSEARGS_LOGFLAG_COMMERR 16 // communication error +#define REQUESTRESPONSEARGS_LOGFLAG_RETRY 32 // communication retry + + +struct TargetMapper; // forward declaration +struct TargetStateStore; // forward declaration +struct NodeStoreEx; // forward declaration +struct MirrorBuddyGroupMapper; // forward declaration + + +struct RequestResponseNode; +typedef struct RequestResponseNode RequestResponseNode; + +enum MessagingTkBufType; +typedef enum MessagingTkBufType MessagingTkBufType; + +struct RequestResponseArgs; +typedef struct RequestResponseArgs RequestResponseArgs; + + +static inline void RequestResponseArgs_prepare(RequestResponseArgs* this, Node* node, + NetMessage* requestMsg, unsigned respMsgType); +static inline void RequestResponseArgs_freeRespBuffers(RequestResponseArgs* this, App* app); + + +struct RRPeer +{ + union { + NumNodeID target; + uint32_t group; + } address; + bool isMirrorGroup; +}; + +static inline struct RRPeer RRPeer_target(NumNodeID target) +{ + struct RRPeer result = { + .address = { + .target = target, + }, + .isMirrorGroup = false, + }; + return result; +} + +static inline struct RRPeer RRPeer_group(uint32_t group) +{ + struct RRPeer result = { + .address = { + .group = group, + }, + .isMirrorGroup = true, + }; + return result; +} + +/** + * This value container contains arguments for requestResponse() with a certain node. + */ +struct RequestResponseNode +{ + struct RRPeer peer; + struct NodeStoreEx* nodeStore; // for nodeID lookup + + struct TargetStateStore* targetStates; /* if !NULL, check for state "good" or fail immediately if + offline (other states handling depend on mirrorBuddies) */ + struct MirrorBuddyGroupMapper* mirrorBuddies; // if !NULL, the given targetID is a mirror groupID +}; + +enum MessagingTkBufType +{ + MessagingTkBufType_BufStore = 0, + MessagingTkBufType_kmalloc = 1, // only for small response messages (<4KiB) +}; + +/** + * Arguments for request-response communication. + */ +struct RequestResponseArgs +{ + Node* node; // receiver + + NetMessage* requestMsg; + unsigned respMsgType; // expected type of response message + + unsigned numRetries; // 0 means infinite retries + unsigned rrFlags; // combination of REQUESTRESPONSEARGS_FLAG_... flags + MessagingTkBufType respBufType; // defines how to get/alloc outRespBuf + + char* outRespBuf; + NetMessage* outRespMsg; + + // internal (initialized by MessagingTk_requestResponseWithRRArgs() ) + unsigned char logFlags; // REQUESTRESPONSEARGS_LOGFLAG_... combination to avoid double-logging +}; + +/** + * Default initializer. + * Some of the default values will overridden by the corresponding MessagingTk methods. + * + * @param node may be NULL, depending on which requestResponse...() method is used. + * @param respMsgType expected type of response message (NETMSGTYPE_...) + */ +void RequestResponseArgs_prepare(RequestResponseArgs* this, Node* node, NetMessage* requestMsg, + unsigned respMsgType) +{ + this->node = node; + + this->requestMsg = requestMsg; + this->respMsgType = respMsgType; + + this->numRetries = 1; + this->rrFlags = 0; + this->respBufType = MessagingTkBufType_BufStore; + + this->outRespBuf = NULL; + this->outRespMsg = NULL; + + this->logFlags = 0; +} + +/** + * Free/release outRespBuf and desctruct outRespMsg if they are not NULL. + */ +void RequestResponseArgs_freeRespBuffers(RequestResponseArgs* this, App* app) +{ + NETMESSAGE_FREE(this->outRespMsg); + + if(this->outRespBuf) + { + if(this->respBufType == MessagingTkBufType_BufStore) + { + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + NoAllocBufferStore_addBuf(bufStore, this->outRespBuf); + } + else + SAFE_KFREE(this->outRespBuf); + } +} + +#endif /* MESSAGINGTKARGS_H_ */ diff --git a/client_module/source/common/toolkit/MetadataTk.c b/client_module/source/common/toolkit/MetadataTk.c new file mode 100644 index 0000000..fd9e14c --- /dev/null +++ b/client_module/source/common/toolkit/MetadataTk.c @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "MetadataTk.h" + +/** + * Used to initialize struct CreateInfo. This function always should be used, to make sure, + * values are not forgotten. + * + * Note: preferred meta/storage targets are automatically set to app's preferred meta/storage + * targets; keep that in mind when you're cleaning up. + */ +void CreateInfo_init(App* app, struct inode* parentDirInode, const char* entryName, + int mode, int umask, bool isExclusiveCreate, const struct FileEvent* fileEvent, + CreateInfo* outCreateInfo) +{ + MountConfig *mountConfig = app->mountConfig; + + outCreateInfo->userID = FhgfsCommon_getCurrentUserID(); + + // groupID and mode logic taken from inode_init_owner() + if (parentDirInode && ((parentDirInode->i_mode & S_ISGID) || mountConfig->grpid)) + { + outCreateInfo->groupID = i_gid_read(parentDirInode); + if (S_ISDIR(mode) && (parentDirInode->i_mode & S_ISGID)) + mode |= S_ISGID; + } + else + outCreateInfo->groupID = FhgfsCommon_getCurrentGroupID(); + + outCreateInfo->entryName = entryName; + outCreateInfo->mode = mode; + outCreateInfo->umask = umask; + outCreateInfo->isExclusiveCreate = isExclusiveCreate; + + outCreateInfo->preferredStorageTargets = App_getPreferredStorageTargets(app); + outCreateInfo->preferredMetaTargets = App_getPreferredMetaNodes(app); + outCreateInfo->fileEvent = fileEvent; + + StoragePoolId_set(&(outCreateInfo->storagePoolId), STORAGEPOOLID_INVALIDPOOLID); +} + + +/** + * @param outEntryInfo contained values will be kalloced (on success) and need to be kfreed with + * FhgfsInode_freeEntryMinInfoVals() later. + */ +bool MetadataTk_getRootEntryInfoCopy(App* app, EntryInfo* outEntryInfo) +{ + NodeStoreEx* nodes = App_getMetaNodes(app); + + NodeOrGroup rootOwner = NodeStoreEx_getRootOwner(nodes); + const char* parentEntryID = StringTk_strDup(""); + const char* entryID = StringTk_strDup(META_ROOTDIR_ID_STR); + const char* dirName = StringTk_strDup(""); + DirEntryType entryType = (DirEntryType) DirEntryType_DIRECTORY; + + /* Even if rootOwner is invalid, we still init outEntryInfo and malloc as FhGFS + * policy says that kfree(NULL) is not allowed (the kernel allows it). */ + + EntryInfo_init(outEntryInfo, rootOwner, parentEntryID, entryID, dirName, entryType, 0); + + return NodeOrGroup_valid(rootOwner); +} diff --git a/client_module/source/common/toolkit/MetadataTk.h b/client_module/source/common/toolkit/MetadataTk.h new file mode 100644 index 0000000..86d2fa9 --- /dev/null +++ b/client_module/source/common/toolkit/MetadataTk.h @@ -0,0 +1,149 @@ +#ifndef METADATATK_H_ +#define METADATATK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "LookupIntentInfoOut.h" + +#include + +#define METADATATK_OWNERSEARCH_MAX_STEPS 128 /* to avoid infinite searching */ + + +struct CreateInfo; +typedef struct CreateInfo CreateInfo; + +struct OpenInfo; +typedef struct OpenInfo OpenInfo; + +struct LookupIntentInfoIn; +typedef struct LookupIntentInfoIn LookupIntentInfoIn; + +struct FileEvent; + + +extern bool MetadataTk_getRootEntryInfoCopy(App* app, EntryInfo* outEntryInfo); + +// inliners + +void CreateInfo_init(App* app, struct inode* parentDirInode, const char* entryName, + int mode, int umask, bool isExclusiveCreate, const struct FileEvent* fileEvent, + struct CreateInfo* outCreateInfo); +static inline void CreateInfo_setStoragePoolId(CreateInfo* this, StoragePoolId storagePoolId); +static inline void LookupIntentInfoIn_init(LookupIntentInfoIn* this, + const EntryInfo* parentEntryInfo, const char* entryName); +static inline void LookupIntentInfoIn_addEntryInfo(LookupIntentInfoIn* this, + const EntryInfo* entryInfo); +static inline void LookupIntentInfoIn_addMetaVersion(LookupIntentInfoIn* this, + uint32_t metaVersion); +static inline void LookupIntentInfoIn_addOpenInfo(LookupIntentInfoIn* this, + const OpenInfo* openInfo); +static inline void LookupIntentInfoIn_addCreateInfo(LookupIntentInfoIn* this, + const CreateInfo* createInfo); +static inline void OpenInfo_init(OpenInfo* this, int accessFlags, bool isPagedMode); +static inline void LookupIntentInfoOut_prepare(LookupIntentInfoOut* this, + EntryInfo* outEntryInfo, fhgfs_stat* outFhgfsStat); + + + +/** + * File/Dir create information. Should be initialized using CreateInfo_init(). + */ +struct CreateInfo +{ + const char* entryName; // file name + unsigned userID; + unsigned groupID; + int mode; + int umask; + UInt16List* preferredMetaTargets; + UInt16List* preferredStorageTargets; + bool isExclusiveCreate; // only for open() and creat(), is O_CREAT set? + StoragePoolId storagePoolId; // can be set to override storage pool information of parent + // directory + const struct FileEvent* fileEvent; +}; + +struct OpenInfo +{ + int accessFlags; // fhgfs flags, not in-kernel open/access flags + // const char* sessionID; // set at lower level on initializing LookupIntentMsg +}; + +/** + * In-Args for _lookupCreateStat operations. Init only with LookupIntentInfoIn_init(). + */ +struct LookupIntentInfoIn +{ + const EntryInfo* parentEntryInfo; + const EntryInfo* entryInfoPtr; // only set on revalidate + const char* entryName; // file name + + const CreateInfo* createInfo; // only set on file create + bool isExclusiveCreate; // true iff O_EXCL is set + + const OpenInfo* openInfo; // only set on file open + uint32_t metaVersion; //only set on revalidate +}; + + +/** + * @param accessFlags is the same as openFlags + */ +void OpenInfo_init(OpenInfo* this, int accessFlags, bool isPagedMode) +{ + this->accessFlags = OsTypeConv_openFlagsOsToFhgfs(accessFlags, isPagedMode); + + // this->sessionID = sessionID; // see OpenInfo definition +} + +static inline void LookupIntentInfoIn_init(LookupIntentInfoIn* this, + const EntryInfo* parentEntryInfo, const char* entryName) +{ + this->parentEntryInfo = parentEntryInfo; + this->entryName = entryName; + this->entryInfoPtr = NULL; + this->openInfo = NULL; + this->createInfo = NULL; +} + +void LookupIntentInfoIn_addEntryInfo(LookupIntentInfoIn* this, const EntryInfo* entryInfo) +{ + this->entryInfoPtr = entryInfo; +} + + +void LookupIntentInfoIn_addMetaVersion(LookupIntentInfoIn* this, uint32_t version) +{ + this->metaVersion = version; +} + +void LookupIntentInfoIn_addOpenInfo(LookupIntentInfoIn* this, const OpenInfo* openInfo) +{ + this->openInfo = openInfo; +} + +void LookupIntentInfoIn_addCreateInfo(LookupIntentInfoIn* this, const CreateInfo* createInfo) +{ + this->createInfo = createInfo; + + if (likely(createInfo) ) + this->isExclusiveCreate = createInfo->isExclusiveCreate; +} + +static inline void CreateInfo_setStoragePoolId(CreateInfo* this, StoragePoolId storagePoolId) +{ + this->storagePoolId = storagePoolId; +} + +#endif /*METADATATK_H_*/ diff --git a/client_module/source/common/toolkit/NetFilter.h b/client_module/source/common/toolkit/NetFilter.h new file mode 100644 index 0000000..eaf6b89 --- /dev/null +++ b/client_module/source/common/toolkit/NetFilter.h @@ -0,0 +1,198 @@ +#ifndef NETFILTER_H_ +#define NETFILTER_H_ + +#include +#include +#include +#include +#include + + +struct NetFilterEntry; +typedef struct NetFilterEntry NetFilterEntry; + +struct NetFilter; +typedef struct NetFilter NetFilter; + + +static inline __must_check bool NetFilter_init(NetFilter* this, const char* filename); +static inline NetFilter* NetFilter_construct(const char* filename); +static inline void NetFilter_uninit(NetFilter* this); +static inline void NetFilter_destruct(NetFilter* this); + +// inliners +static inline bool NetFilter_isAllowed(NetFilter* this, struct in_addr ipAddr); +static inline bool NetFilter_isContained(NetFilter* this, struct in_addr ipAddr); +static inline bool __NetFilter_prepareArray(NetFilter* this, const char* filename); + +// getters & setters +static inline size_t NetFilter_getNumFilterEntries(NetFilter* this); + + +struct NetFilterEntry +{ + uint32_t netAddressShifted; // shifted by number of significant bits + // Note: this address is in host(!) byte order to enable correct shift operator usage + int shiftBitsNum; // for right shift +}; + +struct NetFilter +{ + NetFilterEntry* filterArray; + size_t filterArrayLen; +}; + + +/** + * @param filename path to filter file. + */ +bool NetFilter_init(NetFilter* this, const char* filename) +{ + return __NetFilter_prepareArray(this, filename); +} + +NetFilter* NetFilter_construct(const char* filename) +{ + NetFilter* this = kmalloc(sizeof(*this), GFP_NOFS); + + if(!this || + !NetFilter_init(this, filename) ) + { + kfree(this); + return NULL; + } + + return this; +} + +void NetFilter_uninit(NetFilter* this) +{ + SAFE_KFREE(this->filterArray); +} + +void NetFilter_destruct(NetFilter* this) +{ + NetFilter_uninit(this); + + kfree(this); +} + +/** + * Note: Empty filter will always return allowed (i.e. "true"). + * Note: Will always allow the loopback address. + */ +bool NetFilter_isAllowed(NetFilter* this, struct in_addr ipAddr) +{ + if(!this->filterArrayLen) + return true; + + return NetFilter_isContained(this, ipAddr); +} + +/** + * Note: Always implicitly contains the loopback address. + */ +bool NetFilter_isContained(NetFilter* this, struct in_addr ipAddr) +{ + uint32_t ipHostOrder; + size_t i; + + // note: stored addresses are in host byte order to enable correct shift operator usage + ipHostOrder = ntohl(ipAddr.s_addr); + + if(ipHostOrder == INADDR_LOOPBACK) // (inaddr_loopback is in host byte order) + return true; + + for(i = 0; i < this->filterArrayLen; i++) + { + const uint32_t ipHostOrderShifted = ipHostOrder >> this->filterArray[i].shiftBitsNum; + + if(this->filterArray[i].netAddressShifted == ipHostOrderShifted) + { // address match + return true; + } + } + + // no match found + return false; +} + +/** + * @param filename path to filter file. + * @return false if a specified file could not be opened. + */ +bool __NetFilter_prepareArray(NetFilter* this, const char* filename) +{ + bool loadRes = true; + StrCpyList filterList; + + this->filterArray = NULL; + this->filterArrayLen = 0; + + if(!StringTk_hasLength(filename) ) + { // no file specified => no filter entries + return true; + } + + + StrCpyList_init(&filterList); + + loadRes = Config_loadStringListFile(filename, &filterList); + if(!loadRes) + { // file error + printk_fhgfs(KERN_WARNING, + "Unable to load configured net filter file: %s\n", filename); + return false; + } + + if(StrCpyList_length(&filterList) ) + { // file did contain some lines + StrCpyListIter iter; + size_t i; + + this->filterArrayLen = StrCpyList_length(&filterList); + this->filterArray = kmalloc(this->filterArrayLen * sizeof(NetFilterEntry), GFP_NOFS); + if(!this->filterArray) + goto err_alloc; + + StrCpyListIter_init(&iter, &filterList); + i = 0; + + for( ; !StrCpyListIter_end(&iter); StrCpyListIter_next(&iter) ) + { + char* line = StrCpyListIter_value(&iter); + uint32_t entryAddr; + + char* findRes = strchr(line, '/'); + if(!findRes) + { // slash character missing => ignore this faulty line (and reduce filter length) + this->filterArrayLen--; + continue; + } + + *findRes = 0; + + // note: we store addresses in host byte order to enable correct shift operator usage + entryAddr = ntohl(SocketTk_in_aton(line).s_addr); + this->filterArray[i].shiftBitsNum = 32 - StringTk_strToUInt(findRes + 1); + this->filterArray[i].netAddressShifted = entryAddr >> this->filterArray[i].shiftBitsNum; + + i++; // must be increased here because of the faulty line handling + } + } + + StrCpyList_uninit(&filterList); + return true; + +err_alloc: + StrCpyList_uninit(&filterList); + return false; +} + +size_t NetFilter_getNumFilterEntries(NetFilter* this) +{ + return this->filterArrayLen; +} + + +#endif /* NETFILTER_H_ */ diff --git a/client_module/source/common/toolkit/NodesTk.c b/client_module/source/common/toolkit/NodesTk.c new file mode 100644 index 0000000..85b7f73 --- /dev/null +++ b/client_module/source/common/toolkit/NodesTk.c @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NodesTk.h" + +/** + * Download node list from given source node. + * + * @param sourceNode the node from which node you want to download + * @param nodeType which type of node list you want to download + * @param outNodeList caller is responsible for the deletion of the received nodes + * @param outRootID may be NULL if caller is not interested + * @param outRootIsBuddyMirrored may be NULL if caller is not interested + * @return true if download successful + */ +bool NodesTk_downloadNodes(App* app, Node* sourceNode, NodeType nodeType, NodeList* outNodeList, + NumNodeID* outRootNodeID, bool* outRootIsBuddyMirrored) +{ + bool retVal = false; + + GetNodesMsg msg; + + FhgfsOpsErr commRes; + GetNodesRespMsg* respMsgCast; + RequestResponseArgs rrArgs; + + // prepare request + GetNodesMsg_initFromValue(&msg, nodeType); + RequestResponseArgs_prepare(&rrArgs, sourceNode, (NetMessage*)&msg, NETMSGTYPE_GetNodesResp); + +#ifndef BEEGFS_DEBUG + // Silence log message unless built in debug mode. + rrArgs.logFlags |= ( REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY ); +#endif // BEEGFS_DEBUG + + // connect & communicate + + commRes = MessagingTk_requestResponseWithRRArgs(app, &rrArgs); + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + goto cleanup_request; + + // handle result + respMsgCast = (GetNodesRespMsg*)rrArgs.outRespMsg; + + GetNodesRespMsg_parseNodeList(app, respMsgCast, outNodeList); + + if(outRootNodeID) + *outRootNodeID = GetNodesRespMsg_getRootNumID(respMsgCast); + + if (outRootIsBuddyMirrored) + *outRootIsBuddyMirrored = GetNodesRespMsg_getRootIsBuddyMirrored(respMsgCast); + + retVal = true; + + // cleanup + + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * Downloads target mappings from given source node. + * + * @return true if download successful + */ +bool NodesTk_downloadTargetMappings(App* app, Node* sourceNode, struct list_head* mappings) +{ + GetTargetMappingsMsg msg; + + FhgfsOpsErr commRes; + GetTargetMappingsRespMsg* respMsgCast; + RequestResponseArgs rrArgs; + + // prepare request + GetTargetMappingsMsg_init(&msg); + RequestResponseArgs_prepare(&rrArgs, sourceNode, (NetMessage*)&msg, + NETMSGTYPE_GetTargetMappingsResp); + +#ifndef BEEGFS_DEBUG + // Silence log message unless built in debug mode. + rrArgs.logFlags |= ( REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY ); +#endif // BEEGFS_DEBUG + + // communicate + + commRes = MessagingTk_requestResponseWithRRArgs(app, &rrArgs); + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + return false; + + // handle result + + respMsgCast = (GetTargetMappingsRespMsg*)rrArgs.outRespMsg; + + list_splice_tail_init(&respMsgCast->mappings, mappings); + + // cleanup + + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + return true; +} + +/** + * Download target states and buddy groups combined in a single message. + */ +bool NodesTk_downloadStatesAndBuddyGroups(App* app, Node* sourceNode, NodeType nodeType, + struct list_head* groups, struct list_head* states) +{ + bool retVal = false; + + GetStatesAndBuddyGroupsMsg msg; + + FhgfsOpsErr commRes; + GetStatesAndBuddyGroupsRespMsg* respMsgCast; + RequestResponseArgs rrArgs; + + Node* localNode = App_getLocalNode(app); + NumNodeID localNodeNumID = Node_getNumID(localNode); + + // prepare request + GetStatesAndBuddyGroupsMsg_init(&msg, nodeType, localNodeNumID); + RequestResponseArgs_prepare(&rrArgs, sourceNode, (NetMessage*)&msg, + NETMSGTYPE_GetStatesAndBuddyGroupsResp); + +#ifndef BEEGFS_DEBUG + // Silence log message unless built in debug mode. + rrArgs.logFlags |= ( REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY ); +#endif // BEEGFS_DEBUG + + // communicate + + commRes = MessagingTk_requestResponseWithRRArgs(app, &rrArgs); + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + goto cleanup_request; + + // handle result + + respMsgCast = (GetStatesAndBuddyGroupsRespMsg*)rrArgs.outRespMsg; + + list_splice_tail_init(&respMsgCast->groups, groups); + list_splice_tail_init(&respMsgCast->states, states); + retVal = true; + + // cleanup + + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + + +/** + * Walk over all nodes in the given store and drop established (available) connections. + * + * @return number of dropped connections + */ +unsigned NodesTk_dropAllConnsByStore(NodeStoreEx* nodes) +{ + unsigned numDroppedConns = 0; + + Node* node = NodeStoreEx_referenceFirstNode(nodes); + while(node) + { + NodeConnPool* connPool = Node_getConnPool(node); + + numDroppedConns += NodeConnPool_disconnectAvailableStreams(connPool); + + node = NodeStoreEx_referenceNextNodeAndReleaseOld(nodes, node); // iterate to next node + } + + return numDroppedConns; +} diff --git a/client_module/source/common/toolkit/NodesTk.h b/client_module/source/common/toolkit/NodesTk.h new file mode 100644 index 0000000..552720b --- /dev/null +++ b/client_module/source/common/toolkit/NodesTk.h @@ -0,0 +1,18 @@ +#ifndef NODESTK_H_ +#define NODESTK_H_ + +#include +#include +#include +#include + + +extern bool NodesTk_downloadNodes(App* app, Node* sourceNode, NodeType nodeType, + NodeList* outNodeList, NumNodeID* outRootNodeID, bool* outRootIsBuddyMirrored); +extern bool NodesTk_downloadTargetMappings(App* app, Node* sourceNode, struct list_head* mappings); +extern bool NodesTk_downloadStatesAndBuddyGroups(App* app, Node* sourceNode, + NodeType nodeType, struct list_head* groups, struct list_head* states); +extern unsigned NodesTk_dropAllConnsByStore(NodeStoreEx* nodes); + + +#endif /* NODESTK_H_ */ diff --git a/client_module/source/common/toolkit/Random.c b/client_module/source/common/toolkit/Random.c new file mode 100644 index 0000000..d00a392 --- /dev/null +++ b/client_module/source/common/toolkit/Random.c @@ -0,0 +1,31 @@ +#include +#include + +#include + + +/** + * @return positive (incl. zero) random number + */ +int Random_getNextInt(void) +{ + int randVal; + + get_random_bytes(&randVal, sizeof(randVal) ); + + // Note: -randVal (instead of ~randVal) wouldn't work for INT_MIN + + return (randVal < 0) ? (~randVal) : randVal; +} + +/** + * @param min inclusive min value + * @param max inclusive max value + */ +int Random_getNextInRange(int min, int max) +{ + int randVal = Random_getNextInt() % (max - min + 1); + + return(min + randVal); +} + diff --git a/client_module/source/common/toolkit/Random.h b/client_module/source/common/toolkit/Random.h new file mode 100644 index 0000000..d5a9aef --- /dev/null +++ b/client_module/source/common/toolkit/Random.h @@ -0,0 +1,10 @@ +#ifndef OPEN_RANDOM_H_ +#define OPEN_RANDOM_H_ + +#include + + +extern int Random_getNextInt(void); +extern int Random_getNextInRange(int min, int max); + +#endif /* OPEN_RANDOM_H_ */ diff --git a/client_module/source/common/toolkit/Serialization.c b/client_module/source/common/toolkit/Serialization.c new file mode 100644 index 0000000..1644d2d --- /dev/null +++ b/client_module/source/common/toolkit/Serialization.c @@ -0,0 +1,919 @@ +#include "Serialization.h" +#include "common/Common.h" +#include + +bool __Serialization_deserializeNestedField(DeserializeCtx* ctx, DeserializeCtx* outCtx) +{ + unsigned length; + + if(!Serialization_deserializeUInt(ctx, &length) ) + return false; + + length -= Serialization_serialLenUInt(); + + if (ctx->length < length) + return false; + + outCtx->data = ctx->data; + outCtx->length = length; + + ctx->data += length; + ctx->length -= length; + return true; +} + +/** + * @return 0 on error (e.g. strLen is greater than bufLen), used buffer size otherwise + */ +void Serialization_serializeStr(SerializeCtx* ctx, unsigned strLen, const char* strStart) +{ + // write length field + Serialization_serializeUInt(ctx, strLen); + + // write raw string + Serialization_serializeBlock(ctx, strStart, strLen); + + // write termination char + Serialization_serializeChar(ctx, 0); +} + +/** + * @return false on error (e.g. strLen is greater than bufLen) + */ +bool Serialization_deserializeStr(DeserializeCtx* ctx, + unsigned* outStrLen, const char** outStrStart) +{ + // length field + if(unlikely(!Serialization_deserializeUInt(ctx, outStrLen) ) ) + return false; + + // check length and terminating zero + if(unlikely( (ctx->length < *outStrLen + 1) || ( (ctx->data)[*outStrLen] != 0) ) ) + return false; + + // string start + *outStrStart = ctx->data; + ctx->data += *outStrLen + 1; + ctx->length -= *outStrLen + 1; + + return true; +} + +/** + * @return 0 on error (e.g. arrLen is greater than bufLen), used buffer size otherwise + */ +void Serialization_serializeCharArray(SerializeCtx* ctx, unsigned arrLen, const char* arrStart) +{ + // elem count info field + Serialization_serializeUInt(ctx, arrLen); + + // copy raw array data + Serialization_serializeBlock(ctx, arrStart, arrLen); +} + +/** + * @return false on error (e.g. arrLen is greater than bufLen) + */ +bool Serialization_deserializeCharArray(DeserializeCtx* ctx, + unsigned* outElemNum, const char** outArrStart, unsigned* outLen) +{ + if(unlikely(!Serialization_deserializeUInt(ctx, outElemNum) ) ) + return false; + + if(ctx->length < *outElemNum) + return false; + + // array start + *outArrStart = ctx->data; + + ctx->data += *outElemNum; + ctx->length -= *outElemNum; + + return true; +} + +/** + * Note: adds padding to achieve 4 byte alignment + * + * @return 0 on error (e.g. strLen is greater than bufLen), used buffer size otherwise + */ +void Serialization_serializeStrAlign4(SerializeCtx* ctx, unsigned strLen, const char* strStart) +{ + // write length field + Serialization_serializeUInt(ctx, strLen); + + // write raw string + Serialization_serializeBlock(ctx, strStart, strLen); + + // write termination char + Serialization_serializeChar(ctx, 0); + + // align to 4b length + if( (strLen + 1) % 4) + Serialization_serializeBlock(ctx, "\0\0\0", 4 - (strLen + 1) % 4); +} + +/** + * @return false on error (e.g. strLen is greater than bufLen) + */ +bool Serialization_deserializeStrAlign4(DeserializeCtx* ctx, + unsigned* outStrLen, const char** outStrStart) +{ + const char* const bufAtStart = ctx->data; + unsigned padding; + + // length field + if(unlikely(!Serialization_deserializeUInt(ctx, outStrLen) ) ) + return false; + + // check length and terminating zero + if(unlikely( (ctx->length < *outStrLen + 1) || ( (ctx->data)[*outStrLen] != 0) ) ) + return false; + + // string start + *outStrStart = ctx->data; + ctx->data += *outStrLen + 1; + ctx->length -= *outStrLen + 1; + + padding = (ctx->data - bufAtStart) % 4; + if(padding != 0) + { + if(unlikely(ctx->length < padding) ) + return false; + + ctx->data += 4 - padding; + ctx->length -= 4 - padding; + } + + return true; +} + +/** + * Serialization of a NicList. + * + * note: 4 byte aligned. + * + * @return 0 on error, used buffer size otherwise + */ +void Serialization_serializeNicList(SerializeCtx* ctx, NicAddressList* nicList) +{ + unsigned nicListSize = NicAddressList_length(nicList); + NicAddressListIter iter; + unsigned startOffset = ctx->length; + + // length field placeholder + Serialization_serializeUInt(ctx, 0xFFFFFFFF); + + // elem count info field + Serialization_serializeUInt(ctx, nicListSize); + + // serialize each element of the nicList + NicAddressListIter_init(&iter, nicList); + + for(unsigned i = 0; i < nicListSize; i++, NicAddressListIter_next(&iter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&iter); + const size_t minNameSize = MIN(sizeof(nicAddr->name), SERIALIZATION_NICLISTELEM_NAME_SIZE); + + // We currently only support and store ipv4 addresses internally, so we always set the + // protocol field to 4. + Serialization_serializeUInt8(ctx, 4); + + { // ipAddress + Serialization_serializeBlock(ctx, &nicAddr->ipAddr.s_addr, sizeof(nicAddr->ipAddr.s_addr) ); + } + + { // name + Serialization_serializeBlock(ctx, nicAddr->name, minNameSize); + } + + { // nicType + Serialization_serializeBlock(ctx, &nicAddr->nicType, sizeof(uint8_t) ); + } + + { // 2 bytes padding (for 4 byte alignment) + Serialization_serializeBlock(ctx, "\0\0", 2); + } + } + + // Overwrite placeholder with length + if (ctx->data) + put_unaligned_le32(ctx->length - startOffset, ctx->data + startOffset); +} + +/** + * Pre-processes a serialized NicList for deserialization via deserializeNicList(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeNicListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + if(!Serialization_deserializeUInt(ctx, &outList->length) ) + return false; + + if(!Serialization_deserializeUInt(ctx, &outList->elemCount) ) + return false; + + outList->data = ctx->data; + // We need the length of the actual list without the two header fields above (which are included + // in the sequence length field) + outList->length -= 8; + + if(unlikely(ctx->length < outList->length) ) + return false; + + ctx->data += outList->length; + ctx->length -= outList->length; + + return true; +} + +/** + * Deserializes a NicList. + * (requires pre-processing via nicListBufToMsg() ) + */ +void Serialization_deserializeNicList(const RawList* inList, NicAddressList* outNicList) +{ + const char* currentNicListPos = inList->data; + unsigned skipped = 0; + + for(unsigned i = 0; i < inList->elemCount; i++) + { + uint8_t protocol; + NicAddress* nicAddr = os_kmalloc(sizeof(NicAddress) ); + const size_t minNameSize = MIN(sizeof(nicAddr->name), SERIALIZATION_NICLISTELEM_NAME_SIZE); + memset(nicAddr, 0, sizeof(NicAddress) ); // clear unused fields + + { // protocol + protocol = (uint8_t) get_unaligned((char*)currentNicListPos); + currentNicListPos += 1; + } + + if (protocol == 4) + { // ipAddress + // Ipv4 address + nicAddr->ipAddr.s_addr = get_unaligned((unsigned*)currentNicListPos); + currentNicListPos += 4; + } else { + // If this is an ipv6 address, skip it as it is not supported yet. + // 16 bytes ipv6 address + name + nicType + padding + currentNicListPos += 16 + SERIALIZATION_NICLISTELEM_NAME_SIZE + 1 + 2; + skipped += 1; + continue; + } + + { // name + memcpy(&nicAddr->name, currentNicListPos, minNameSize); + (nicAddr->name)[minNameSize-1] = 0; // make sure the string is zero-terminated + + currentNicListPos += SERIALIZATION_NICLISTELEM_NAME_SIZE; + } + + { // nicType + nicAddr->nicType = (NicAddrType_t) get_unaligned((char*)currentNicListPos); + currentNicListPos += 1; + } + + { // 2 bytes padding (for 4 byte alignment) + currentNicListPos += 2; + } + + NicAddressList_append(outNicList, nicAddr); + } + + if (skipped > 0) { + printk_fhgfs(KERN_INFO, "Skipped deserializing %d unsupported IPv6 Nics", skipped); + } +} + +/** + * Pre-processes a serialized NodeList for deserialization via deserializeNodeList(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeNodeListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + unsigned i; + + if(!Serialization_deserializeUInt(ctx, &outList->elemCount) ) + return false; + + // store start pos for later deserialize() + + outList->data = ctx->data; + + for(i=0; i < outList->elemCount; i++) + { + {// nodeID + unsigned nodeIDLen = 0; + const char* nodeID = NULL; + + if(unlikely(!Serialization_deserializeStr(ctx, &nodeIDLen, &nodeID) ) ) + return false; + } + + {// nicList (4b aligned) + RawList nicList; + + if(unlikely(!Serialization_deserializeNicListPreprocess(ctx, &nicList) ) ) + return false; + } + + {// nodeNumID + NumNodeID nodeNumID = {0}; + + if(unlikely(!NumNodeID_deserialize(ctx, &nodeNumID) ) ) + return false; + } + + {// portUDP + uint16_t portUDP = 0; + + if(unlikely(!Serialization_deserializeUShort(ctx, &portUDP) ) ) + return false; + } + + {// portTCP + uint16_t portTCP = 0; + + if(unlikely(!Serialization_deserializeUShort(ctx, &portTCP) ) ) + return false; + } + + {// nodeType + char nodeType = 0; + + if(unlikely(!Serialization_deserializeChar(ctx, &nodeType) ) ) + return false; + } + } + + return true; +} + +/** + * Deserializes a NodeList. + * (requires pre-processing) + * + * Note: Nodes will be constructed and need to be deleted later + */ +void Serialization_deserializeNodeList(App* app, const RawList* inList, NodeList* outNodeList) +{ + unsigned i; + + DeserializeCtx ctx = { + .data = inList->data, + .length = -1, + }; + + for(i=0; i < inList->elemCount; i++) + { + NicAddressList nicList; + + const char* nodeID = NULL; + NumNodeID nodeNumID = (NumNodeID){0}; + uint16_t portUDP = 0; + uint16_t portTCP = 0; + char nodeType = 0; + + + NicAddressList_init(&nicList); + + + {// nodeID + unsigned nodeIDLen = 0; + + Serialization_deserializeStr(&ctx, &nodeIDLen, &nodeID); + } + + {// nicList (4b aligned) + RawList rawNicList; + + Serialization_deserializeNicListPreprocess(&ctx, &rawNicList); + Serialization_deserializeNicList(&rawNicList, &nicList); + } + + // nodeNumID + NumNodeID_deserialize(&ctx, &nodeNumID); + + // portUDP + Serialization_deserializeUShort(&ctx, &portUDP); + + // portTCP + Serialization_deserializeUShort(&ctx, &portTCP); + + // nodeType + Serialization_deserializeChar(&ctx, &nodeType); + + {// construct node + Node* node; + App_lockNicList(app); + node = Node_construct(app, nodeID, nodeNumID, portUDP, portTCP, &nicList, + nodeType == NODETYPE_Meta || nodeType == NODETYPE_Storage? App_getLocalRDMANicListLocked(app) : NULL); + App_unlockNicList(app); + + Node_setNodeAliasAndType(node, NULL, (NodeType)nodeType); + + // append node to outList + NodeList_append(outNodeList, node); + } + + // cleanup + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + } + +} + +/** + * Serialization of a StrCpyList. + * + * Note: We keep the serialiazation format compatible with string vec serialization + */ +void Serialization_serializeStrCpyList(SerializeCtx* ctx, StrCpyList* list) +{ + SerializeCtx startCtx = *ctx; + StrCpyListIter iter; + + size_t listSize = StrCpyList_length(list); + size_t i; + + // totalBufLen info field + Serialization_serializeUInt(ctx, 0); + + // elem count info field + Serialization_serializeUInt(ctx, listSize); + + // store each element of the list as a raw zero-terminated string + StrCpyListIter_init(&iter, list); + + for(i=0; i < listSize; i++, StrCpyListIter_next(&iter) ) + { + char* currentElem = StrCpyListIter_value(&iter); + size_t serialElemLen = strlen(currentElem) + 1; // +1 for the terminating zero + + Serialization_serializeBlock(ctx, currentElem, serialElemLen); + } + + // fix totalBufLen info field + Serialization_serializeUInt(&startCtx, ctx->length - startCtx.length); +} + +/** + * Pre-processes a serialized StrCpyList. + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeStrCpyListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + DeserializeCtx outCtx; + + if(!__Serialization_deserializeNestedField(ctx, &outCtx) ) + return false; + + // elem count field + if(unlikely(!Serialization_deserializeUInt(&outCtx, &outList->elemCount) ) ) + return false; + + if(unlikely(outList->elemCount && ( outCtx.data[outCtx.length - 1] != 0) ) ) + return false; + + outList->data = outCtx.data; + outList->length = outCtx.length; + + return true; +} + +/** + * Deserializes a StrCpyList. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeStrCpyList(const RawList* inList, StrCpyList* outList) +{ + const char* listStart = inList->data; + unsigned listBufLen = inList->length; + unsigned i; + + // read each list element as a raw zero-terminated string + // and make sure that we do not read beyond the specified end position + + for(i = 0; (i < inList->elemCount) && (listBufLen > 0); i++) + { + size_t elemLen = strlen(listStart); + + StrCpyList_append(outList, listStart); + + listStart += elemLen + 1; // +1 for the terminating zero + listBufLen -= elemLen + 1; // +1 for the terminating zero + } + + return i == inList->elemCount; +} + +/** + * Serialization of a StrCpyVec. + * + * Note: We keep the serialiazation format compatible with string list serialization + */ +void Serialization_serializeStrCpyVec(SerializeCtx* ctx, StrCpyVec* vec) +{ + Serialization_serializeStrCpyList(ctx, &vec->strCpyList); +} + +/** + * Pre-processes a serialized StrCpyVec. + * + * Note: We keep the serialization format compatible to the string list format. + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeStrCpyVecPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + // serialization format is 100% compatible to the list version, so we can just use the list + // version code here... + + return Serialization_deserializeStrCpyListPreprocess(ctx, outList); +} + +/** + * Deserializes a StrCpyVec. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeStrCpyVec(const RawList* inList, StrCpyVec* outVec) +{ + const char* listStart = inList->data; + unsigned listBufLen = inList->length; + unsigned i; + + // read each list element as a raw zero-terminated string + // and make sure that we do not read beyond the specified end position + + for(i = 0; (i < inList->elemCount) && (listBufLen > 0); i++) + { + size_t elemLen = strlen(listStart); + + StrCpyVec_append(outVec, listStart); + + listStart += elemLen + 1; // +1 for the terminating zero + listBufLen -= elemLen + 1; // +1 for the terminating zero + } + + return i == inList->elemCount; +} + +/** + * Pre-processes a serialized UInt8List(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt8ListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + DeserializeCtx outCtx; + + if(!__Serialization_deserializeNestedField(ctx, &outCtx) ) + return false; + + // elem count field + if(unlikely(!Serialization_deserializeUInt(&outCtx, &outList->elemCount) ) ) + return false; + + if(outCtx.length != outList->elemCount * Serialization_serialLenUInt8() ) + return false; + + outList->data = outCtx.data; + outList->length = outCtx.length; + + return true; +} + +/** + * Deserializes a UInt8List. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt8List(const RawList* inList, UInt8List* outList) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + uint8_t value; + + if(!Serialization_deserializeUInt8(&ctx, &value) ) + return false; + + UInt8List_append(outList, value); + } + + return true; +} + +/** + * Pre-processes a serialized UInt8Vec(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt8VecPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + // serialization format is 100% compatible to the list version, so we can just use the list + // version code here... + + return Serialization_deserializeUInt8ListPreprocess(ctx, outList); +} + +/** + * Deserializes a UInt8Vec. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt8Vec(const RawList* inList, UInt8Vec* outVec) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + uint8_t value; + + if(!Serialization_deserializeUInt8(&ctx, &value) ) + return false; + + UInt8Vec_append(outVec, value); + } + + return true; +} + + +/** + * Serialization of a UInt16List. + * + * Note: We keep the serialization format compatible with int vec serialization + */ +void Serialization_serializeUInt16List(SerializeCtx* ctx, UInt16List* list) +{ + SerializeCtx startCtx = *ctx; + UInt16ListIter iter; + unsigned i; + + unsigned listSize = UInt16List_length(list); + + // totalBufLen info field + Serialization_serializeUInt(ctx, 0); + + // elem count info field + Serialization_serializeUInt(ctx, listSize); + + // store each element of the list + UInt16ListIter_init(&iter, list); + + for(i=0; i < listSize; i++, UInt16ListIter_next(&iter) ) + { + uint16_t currentValue = UInt16ListIter_value(&iter); + + Serialization_serializeUShort(ctx, currentValue); + } + + // fix totalBufLen info field + Serialization_serializeUInt(&startCtx, ctx->length - startCtx.length); +} + +/** + * Pre-processes a serialized UInt16List(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt16ListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + DeserializeCtx outCtx; + + if(!__Serialization_deserializeNestedField(ctx, &outCtx) ) + return false; + + // elem count field + if(unlikely(!Serialization_deserializeUInt(&outCtx, &outList->elemCount) ) ) + return false; + + if(outCtx.length != outList->elemCount * Serialization_serialLenUShort() ) + return false; + + outList->data = outCtx.data; + outList->length = outCtx.length; + + return true; +} + +/** + * Deserializes a UInt16List. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt16List(const RawList* inList, UInt16List* outList) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + uint16_t value; + + if(!Serialization_deserializeUShort(&ctx, &value) ) + return false; + + UInt16List_append(outList, value); + } + + return true; +} + +/** + * Note: We keep the serialiazation format compatible with string list serialization + */ +void Serialization_serializeUInt16Vec(SerializeCtx* ctx, UInt16Vec* vec) +{ + // UInt16Vecs are derived from UInt16Lists, so we can just do a cast here and use the list + // version code... + + Serialization_serializeUInt16List(ctx, (UInt16List*)vec); +} + +/** + * Pre-processes a serialized UInt16Vec(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt16VecPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + // serialization format is 100% compatible to the list version, so we can just use the list + // version code here... + + return Serialization_deserializeUInt16ListPreprocess(ctx, outList); +} + +/** + * Deserializes a UInt16Vec. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeUInt16Vec(const RawList* inList, UInt16Vec* outVec) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + uint16_t value; + + if(!Serialization_deserializeUShort(&ctx, &value) ) + return false; + + UInt16Vec_append(outVec, value); + } + + return true; +} + +/** + * Serialization of a Int64CpyList. + * + * Note: We keep the serialization format compatible with int vec serialization + */ +void Serialization_serializeInt64CpyList(SerializeCtx* ctx, Int64CpyList* list) +{ + SerializeCtx startCtx = *ctx; + Int64CpyListIter iter; + unsigned i; + + unsigned listSize = Int64CpyList_length(list); + + // totalBufLen info field + Serialization_serializeUInt(ctx, 0); + + // elem count info field + Serialization_serializeUInt(ctx, listSize); + + // store each element of the list + Int64CpyListIter_init(&iter, list); + + for(i=0; i < listSize; i++, Int64CpyListIter_next(&iter) ) + { + int64_t currentValue = Int64CpyListIter_value(&iter); + + Serialization_serializeInt64(ctx, currentValue); + } + + // fix totalBufLen info field + Serialization_serializeUInt(&startCtx, ctx->length - startCtx.length); +} + +/** + * Pre-processes a serialized Int64CpyList(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeInt64CpyListPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + DeserializeCtx outCtx; + + if(!__Serialization_deserializeNestedField(ctx, &outCtx) ) + return false; + + // elem count field + if(unlikely(!Serialization_deserializeUInt(&outCtx, &outList->elemCount) ) ) + return false; + + if(outCtx.length != (outList->elemCount * Serialization_serialLenInt64() ) ) + return false; + + outList->data = outCtx.data; + outList->length = outCtx.length; + + return true; +} + +/** + * Deserializes a Int64CpyList. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeInt64CpyList(const RawList* inList, Int64CpyList* outList) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + int64_t value; + + if(!Serialization_deserializeInt64(&ctx, &value) ) + return false; + + Int64CpyList_append(outList, value); + } + + return true; +} + +/** + * Note: We keep the serialiazation format compatible with string list serialization + */ +void Serialization_serializeInt64CpyVec(SerializeCtx* ctx, Int64CpyVec* vec) +{ + // Int64CpyVecs are derived from Int64CpyLists, so we can just do a cast here and use the list + // version code... + + Serialization_serializeInt64CpyList(ctx, (Int64CpyList*)vec); +} + +/** + * Pre-processes a serialized Int64CpyVec(). + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeInt64CpyVecPreprocess(DeserializeCtx* ctx, RawList* outList) +{ + // serialization format is 100% compatible to the list version, so we can just use the list + // version code here... + + return Serialization_deserializeInt64CpyListPreprocess(ctx, outList); +} + +/** + * Deserializes a Int64CpyVec. + * (requires pre-processing) + * + * @return false on error or inconsistency + */ +bool Serialization_deserializeInt64CpyVec(const RawList* inList, Int64CpyVec* outVec) +{ + DeserializeCtx ctx = { inList->data, inList->length }; + unsigned i; + + // read each list element + for(i=0; i < inList->elemCount; i++) + { + int64_t value; + + if(!Serialization_deserializeInt64(&ctx, &value) ) + return false; + + Int64CpyVec_append(outVec, value); + } + + return true; +} diff --git a/client_module/source/common/toolkit/Serialization.h b/client_module/source/common/toolkit/Serialization.h new file mode 100644 index 0000000..fe9ad59 --- /dev/null +++ b/client_module/source/common/toolkit/Serialization.h @@ -0,0 +1,617 @@ +#ifndef SERIALIZATION_H_ +#define SERIALIZATION_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef KERNEL_HAS_LINUX_UNALIGNED_H +#include +#else +#include +#endif + +#define SERIALIZATION_NICLISTELEM_NAME_SIZE (16) + +bool __Serialization_deserializeNestedField(DeserializeCtx* ctx, DeserializeCtx* outCtx); + +// string (de)serialization +void Serialization_serializeStr(SerializeCtx* ctx, unsigned strLen, const char* strStart); +bool Serialization_deserializeStr(DeserializeCtx* ctx, + unsigned* outStrLen, const char** outStrStart); + +// string aligned (de)serialization +void Serialization_serializeStrAlign4(SerializeCtx* ctx, unsigned strLen, const char* strStart); +bool Serialization_deserializeStrAlign4(DeserializeCtx* ctx, + unsigned* outStrLen, const char** outStrStart); + +// char array (arbitrary binary data) (de)serialization +void Serialization_serializeCharArray(SerializeCtx* ctx, unsigned arrLen, const char* arrStart); +bool Serialization_deserializeCharArray(DeserializeCtx* ctx, + unsigned* outArrLen, const char** outArrStart, unsigned* outLen); + +// nicList (de)serialization +void Serialization_serializeNicList(SerializeCtx* ctx, NicAddressList* nicList); +bool Serialization_deserializeNicListPreprocess(DeserializeCtx* ctx, RawList* outList); +void Serialization_deserializeNicList(const RawList* inList, NicAddressList* outNicList); + +// nodeList (de)serialization +bool Serialization_deserializeNodeListPreprocess(DeserializeCtx* ctx, RawList* outList); +void Serialization_deserializeNodeList(App* app, const RawList* inList, NodeList* outNodeList); + +// strCpyList (de)serialization +void Serialization_serializeStrCpyList(SerializeCtx* ctx, StrCpyList* list); +bool Serialization_deserializeStrCpyListPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeStrCpyList(const RawList* inList, StrCpyList* outList); + +// strCpyVec (de)serialization +void Serialization_serializeStrCpyVec(SerializeCtx* ctx, StrCpyVec* vec); +bool Serialization_deserializeStrCpyVecPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeStrCpyVec(const RawList* inList, StrCpyVec* outVec); + +// uint8List (de)serialization +bool Serialization_deserializeUInt8ListPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeUInt8List(const RawList* inList, UInt8List* outList); + +// uint8Vec (de)serialization +bool Serialization_deserializeUInt8VecPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeUInt8Vec(const RawList* inList, UInt8Vec* outList); + +// uint16List (de)serialization +void Serialization_serializeUInt16List(SerializeCtx* ctx, UInt16List* list); +bool Serialization_deserializeUInt16ListPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeUInt16List(const RawList* inList, UInt16List* outList); + +// uint16Vec (de)serialization +void Serialization_serializeUInt16Vec(SerializeCtx* ctx, UInt16Vec* vec); +bool Serialization_deserializeUInt16VecPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeUInt16Vec(const RawList* inList, UInt16Vec* outVec); + +// int64CpyList (de)serialization +void Serialization_serializeInt64CpyList(SerializeCtx* ctx, Int64CpyList* list); +bool Serialization_deserializeInt64CpyListPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeInt64CpyList(const RawList* inList, Int64CpyList* outList); + +// uint64Vec (de)serialization +void Serialization_serializeInt64CpyVec(SerializeCtx* ctx, Int64CpyVec* vec); +bool Serialization_deserializeInt64CpyVecPreprocess(DeserializeCtx* ctx, RawList* outList); +bool Serialization_deserializeInt64CpyVec(const RawList* inList, Int64CpyVec* outVec); + +// inliners +static inline void Serialization_serializeBlock(SerializeCtx* ctx, const void* value, + unsigned length); + +static inline unsigned Serialization_serialLenChar(void); +static inline void Serialization_serializeChar(SerializeCtx* ctx, char value); +static inline bool Serialization_deserializeChar(DeserializeCtx* ctx, char* outValue); +static inline unsigned Serialization_serialLenBool(void); +static inline void Serialization_serializeBool(SerializeCtx* ctx, bool value); +static inline bool Serialization_deserializeBool(DeserializeCtx* ctx, bool* outValue); +static inline unsigned Serialization_serialLenUInt8(void); +static inline bool Serialization_deserializeUInt8(DeserializeCtx* ctx, uint8_t* outValue); +static inline unsigned Serialization_serialLenShort(void); +static inline void Serialization_serializeShort(SerializeCtx* ctx, short value); +static inline bool Serialization_deserializeShort(DeserializeCtx* ctx, short* outValue); +static inline unsigned Serialization_serialLenUShort(void); +static inline void Serialization_serializeUShort(SerializeCtx* ctx, unsigned short value); +static inline bool Serialization_deserializeUShort(DeserializeCtx* ctx, unsigned short* outValue); +static inline void Serialization_serializeInt(SerializeCtx* ctx, int value); +static inline bool Serialization_deserializeInt(DeserializeCtx* ctx, int* outValue); +static inline unsigned Serialization_serialLenInt(void); +static inline void Serialization_serializeUInt(SerializeCtx* ctx, unsigned value); +static inline bool Serialization_deserializeUInt(DeserializeCtx* ctx, unsigned* outValue); +static inline unsigned Serialization_serialLenUInt(void); +static inline void Serialization_serializeInt64(SerializeCtx* ctx, int64_t value); +static inline bool Serialization_deserializeInt64(DeserializeCtx* ctx, int64_t* outValue); +static inline unsigned Serialization_serialLenInt64(void); +static inline void Serialization_serializeUInt64(SerializeCtx* ctx, uint64_t value); +static inline bool Serialization_deserializeUInt64(DeserializeCtx* ctx, uint64_t* outValue); +static inline unsigned Serialization_serialLenUInt64(void); + + +void Serialization_serializeBlock(SerializeCtx* ctx, const void* value, unsigned length) +{ + if(ctx->data) + memcpy(ctx->data + ctx->length, value, length); + + ctx->length += length; +} + +#define __Serialization_deserializeRaw(ctx, convert, outValue) \ + ({ \ + DeserializeCtx* ctx__ = (ctx); \ + bool result = false; \ + (void) (outValue == (__typeof__(convert(ctx__->data) )*) 0); \ + if(likely(ctx__->length >= sizeof(*(outValue) ) ) ) \ + { \ + *(outValue) = convert(ctx__->data); \ + ctx__->data += sizeof(*(outValue) ); \ + ctx__->length -= sizeof(*(outValue) ); \ + result = true; \ + } \ + result; \ + }) + + +// char (de)serialization +static inline unsigned Serialization_serialLenChar(void) +{ + return sizeof(char); +} + +static inline void Serialization_serializeChar(SerializeCtx* ctx, char value) +{ + if(ctx->data) + ctx->data[ctx->length] = value; + + ctx->length += sizeof(value); +} + +static inline bool Serialization_deserializeChar(DeserializeCtx* ctx, char* outValue) +{ + return __Serialization_deserializeRaw(ctx, *, outValue); +} + + +// bool (de)serialization +static inline unsigned Serialization_serialLenBool(void) +{ + return Serialization_serialLenChar(); +} + +static inline void Serialization_serializeBool(SerializeCtx* ctx, bool value) +{ + Serialization_serializeChar(ctx, value ? 1 : 0); +} + +static inline bool Serialization_deserializeBool(DeserializeCtx* ctx, bool* outValue) +{ + char charVal = 0; + bool deserRes = Serialization_deserializeChar(ctx, &charVal); + + *outValue = (charVal != 0); + return deserRes; +} + +// uint8_t (de)serialization +static inline void Serialization_serializeUInt8(SerializeCtx* ctx, uint8_t value) +{ + Serialization_serializeChar(ctx, (char) value); +} + +static inline unsigned Serialization_serialLenUInt8(void) +{ + return Serialization_serialLenChar(); +} + +static inline bool Serialization_deserializeUInt8(DeserializeCtx* ctx, + uint8_t* outValue) +{ + return Serialization_deserializeChar(ctx, (char*)outValue); +} + +// short (de)serialization +static inline unsigned Serialization_serialLenShort(void) +{ + return Serialization_serialLenUShort(); +} + +static inline void Serialization_serializeShort(SerializeCtx* ctx, short value) +{ + Serialization_serializeUShort(ctx, value); +} + +static inline bool Serialization_deserializeShort(DeserializeCtx* ctx, + short* outValue) +{ + return Serialization_deserializeUShort(ctx, (unsigned short*)outValue); +} + +// ushort (de)serialization +static inline unsigned Serialization_serialLenUShort(void) +{ + return sizeof(unsigned short); +} + +static inline void Serialization_serializeUShort(SerializeCtx* ctx, unsigned short value) +{ + if(ctx->data) + put_unaligned_le16(value, ctx->data + ctx->length); + + ctx->length += sizeof(value); +} + +static inline bool Serialization_deserializeUShort(DeserializeCtx* ctx, unsigned short* outValue) +{ + return __Serialization_deserializeRaw(ctx, get_unaligned_le16, outValue); +} + +// int (de)serialization +static inline unsigned Serialization_serialLenInt(void) +{ + return Serialization_serialLenUInt(); +} + +static inline void Serialization_serializeInt(SerializeCtx* ctx, int value) +{ + Serialization_serializeUInt(ctx, value); +} + +static inline bool Serialization_deserializeInt(DeserializeCtx* ctx, int* outValue) +{ + return Serialization_deserializeUInt(ctx, (unsigned*)outValue); +} + + +// uint (de)serialization +static inline void Serialization_serializeUInt(SerializeCtx* ctx, unsigned value) +{ + if(ctx->data) + put_unaligned_le32(value, ctx->data + ctx->length); + + ctx->length += sizeof(value); +} + +static inline bool Serialization_deserializeUInt(DeserializeCtx* ctx, unsigned* outValue) +{ + return __Serialization_deserializeRaw(ctx, get_unaligned_le32, outValue); +} + +static inline unsigned Serialization_serialLenUInt(void) +{ + return sizeof(unsigned); +} + +// int64 (de)serialization +static inline void Serialization_serializeInt64(SerializeCtx* ctx, int64_t value) +{ + Serialization_serializeUInt64(ctx, value); +} + +static inline bool Serialization_deserializeInt64(DeserializeCtx* ctx, + int64_t* outValue) +{ + return Serialization_deserializeUInt64(ctx, (uint64_t*)outValue); +} + +static inline unsigned Serialization_serialLenInt64(void) +{ + return Serialization_serialLenUInt64(); +} + +// uint64 (de)serialization +static inline void Serialization_serializeUInt64(SerializeCtx* ctx, uint64_t value) +{ + if(ctx->data) + put_unaligned_le64(value, ctx->data + ctx->length); + + ctx->length += sizeof(value); +} + +static inline bool Serialization_deserializeUInt64(DeserializeCtx* ctx, + uint64_t* outValue) +{ + return __Serialization_deserializeRaw(ctx, get_unaligned_le64, outValue); +} + +static inline unsigned Serialization_serialLenUInt64(void) +{ + return sizeof(uint64_t); +} + +#define __SERDES_CONCAT_I(a, b) a ## b +#define __SERDES_CONCAT(a, b) __SERDES_CONCAT_I(a, b) + +#define __SERDES_COUNT_I(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, count, ...) count +#define __SERDES_COUNT(...) __SERDES_COUNT_I(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) +#define __SERDES_CALL(Base, ...) __SERDES_CONCAT(Base, __SERDES_COUNT(__VA_ARGS__))(__VA_ARGS__) + +#define __SERDES_FIELD_NAME(Name, SerMod, NS, Suffix) Name +#define __SERDES_FIELD_SER(Name, SerMod, NS, Suffix) NS##_serialize##Suffix +#define __SERDES_FIELD_SER_MOD(Name, SerMod, NS, Suffix) SerMod +#define __SERDES_FIELD_DES(Name, SerMod, NS, Suffix) NS##_deserialize##Suffix + +#define __SERDES_DEFINE_SER_1(Field) \ + __SERDES_FIELD_SER Field(ctx, __SERDES_FIELD_SER_MOD Field(value->__SERDES_FIELD_NAME Field)); +#define __SERDES_DEFINE_SER_2(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_1(__VA_ARGS__) +#define __SERDES_DEFINE_SER_3(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_2(__VA_ARGS__) +#define __SERDES_DEFINE_SER_4(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_3(__VA_ARGS__) +#define __SERDES_DEFINE_SER_5(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_4(__VA_ARGS__) +#define __SERDES_DEFINE_SER_6(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_5(__VA_ARGS__) +#define __SERDES_DEFINE_SER_7(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_6(__VA_ARGS__) +#define __SERDES_DEFINE_SER_8(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_7(__VA_ARGS__) +#define __SERDES_DEFINE_SER_9(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_8(__VA_ARGS__) +#define __SERDES_DEFINE_SER_10(Field, ...) __SERDES_DEFINE_SER_1(Field) __SERDES_DEFINE_SER_9(__VA_ARGS__) + +#define __SERDES_DEFINE_SER(NS, Type, ...) \ + __attribute__((unused)) \ + void NS##_serialize(SerializeCtx* ctx, const Type* value) \ + { \ + __SERDES_CALL(__SERDES_DEFINE_SER_, __VA_ARGS__) \ + } + + +#define __SERDES_DEFINE_DES_1(Field) \ + if (!__SERDES_FIELD_DES Field(ctx, &value->__SERDES_FIELD_NAME Field)) \ + goto err; +#define __SERDES_DEFINE_DES_2(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_1(__VA_ARGS__) +#define __SERDES_DEFINE_DES_3(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_2(__VA_ARGS__) +#define __SERDES_DEFINE_DES_4(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_3(__VA_ARGS__) +#define __SERDES_DEFINE_DES_5(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_4(__VA_ARGS__) +#define __SERDES_DEFINE_DES_6(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_5(__VA_ARGS__) +#define __SERDES_DEFINE_DES_7(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_6(__VA_ARGS__) +#define __SERDES_DEFINE_DES_8(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_7(__VA_ARGS__) +#define __SERDES_DEFINE_DES_9(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_8(__VA_ARGS__) +#define __SERDES_DEFINE_DES_10(Field, ...) __SERDES_DEFINE_DES_1(Field) __SERDES_DEFINE_DES_9(__VA_ARGS__) + +#define __SERDES_DEFINE_DES(NS, Type, Init, ErrFini, ...) \ + __attribute__((unused)) \ + bool NS##_deserialize(DeserializeCtx* ctx, Type* value) \ + { \ + Init; \ + __SERDES_CALL(__SERDES_DEFINE_DES_, __VA_ARGS__) \ + return true; \ + err: \ + ErrFini; \ + return false; \ + } + + +/** + * SERDES_DECLARE_SERIALIZERS() - declares serialize and deserialize functions for a given type. + * @NS namespace prefix for the generated functions + * @Type the type to be made serializable/deserializable + * + * creates ``extern`` declarations for serializer functions as generated by + * ``SERDES_DEFINE_SERIALIZERS`` or ``SERDES_DEFINE_SERIALIZERS_SIMPLE``. + */ +#define SERDES_DECLARE_SERIALIZERS(NS, Type) \ + extern void NS##_serialize(SerializeCtx* ctx, const Type* value); \ + extern bool NS##_deserialize(DeserializeCtx* ctx, Type* value); +/** + * SERDES_DEFINE_SERIALIZERS() - defines serialize and deserialize functions for a given type. + * @Access access modifiers for the generated functions + * @NS namespace prefix for the generated functions + * @Type the type to be made serializable/deserializable + * @DesInit initialization code to be run before an instance is deserialized + * @DesErrFini finalization code to be run when deserialization fails + * @... description of @Type + * + * Declares a function ``NS##_serialize(SerializeCtx* ctx, const Type* value)`` and a function + * ``NS##_deserialize(DeserializeCtx* ctx, Type* value)`` that follow the + * serializer and deserializer conventions. + * + * During deserialization, ``DesInit`` is evaluated as an expression in the scope of the + * deserializer function (ie, ``ctx`` and ``value`` are available). @DesInit may be called on + * objects that have been initialized previously, via @DesInit or some other method. The invoker + * must be careful to not introduce memory leaks via this expression. @DesInit and @DesErrFini + * should always call the usual initialization and release functions for the type. + * + * If deserialization fails for any reason, ``DesErrFini`` is evaluated in the context of the + * deserializer function just like ``DesInit``. All resources allocated during initialization or + * deserialization must be released by ``DesErrFini``. + * + * @Type is exposed to the serializer as a list if one to 10 comma-separated tuples of the form + * ``(FieldName, SerMod, SerNS, Suffix)``. + * + * * ``FieldName`` is the name of the serialized field. Fields are serialized and deserialized in + * the order they appear in the description list. + * * ``SerMod`` is a modifier applied to the serialized value before passing it on to the + * serializer (see example). + * * ``SerNS`` is the namespace of the serializer and deserializer functions for this field. + * * ``Suffix`` is appended to the canonical name of the serializer and deserializer functions for + * the field. + * + * For the generated serializer, each field expands into a single call to a serializer. + * ``SerNS##_serialize##Suffix(ctx, SerMod (value->FieldName);`` + * + * For the generated deserializer, each field expands into a single call to a deserializer. For + * deserializers the ``SerMod`` field of the description is ignored and a pointer is always passed. + * ``SerNS##_deserialize##Suffix(ctx, &(value->FieldName));`` + * + * Example: + * `` + * struct S + * { + * const char* str; + * }; + * SERDES_DEFINE_SERIALIZERS(static inline, _S, struct S, + * value->str = NULL, + * kfree(value->str), + * (str, &, Serdes, CStr)) + * `` + * will expand to this serializer and deserializer code: + * `` + * static inline void _S_serialize(SerializeCtx* ctx, const struct S* value) + * { + * Serdes_serializeCStr(ctx, &(value->str)); + * } + * static inline bool _S_deserialize(DeserializeCtx* ctx, struct S* value) + * { + * value->str = NULL; + * if (!Serdes_deserializeCStr(ctx, &value->str)) + * goto err; + * return true; + * err: + * kfree(value->str); + * return false; + * } + * `` + */ +#define SERDES_DEFINE_SERIALIZERS(Access, NS, Type, DesInit, DesErrFini, ...) \ + Access __SERDES_DEFINE_SER(NS, Type, __VA_ARGS__) \ + Access __SERDES_DEFINE_DES(NS, Type, DesInit, DesErrFini, __VA_ARGS__) +/** + * SERDES_DEFINE_SERIALIZERS_SIMPLE() - defines serialize and deserialize functions for a given type + * @Access access modifiers for the generated functions + * @NS namespace prefix for the generated functions + * @Type the type to be made serializable/deserializable + * @... description of @Type + * + * expands to ``SERDES_DEFINE_SERIALIZERS(Access, NS, Type, ({}), ({}), __VA_ARGS__)``, ie no + * special intialization or cleanup is done for deserialization. + */ +#define SERDES_DEFINE_SERIALIZERS_SIMPLE(Access, NS, Type, ...) \ + SERDES_DEFINE_SERIALIZERS(Access, NS, Type, ({}), ({}), __VA_ARGS__) + +/** + * SERDES_SERIALIZE_AS - defines enum serializers based on underlying type + * @Access access modifiers for the generated functions + * @NS namespace prefix for the generated functions + * @Type the type to be made serializable/deserializable + * @As underlying type to used for serialized data + * @AsSuffix suffix for de/serializer in ``Serialization`` + * + * enum types that go over the wire must be serialized with a consistent type at both end. this + * macro defines serializers for a given enum @Type which use @As for the binary representation. + * since this is about underlying types this macro can only call ``Serialization_*`` functions. + */ +#define SERDES_SERIALIZE_AS(Access, NS, Type, As, AsSuffix) \ + __attribute__((unused)) \ + Access void NS##_serialize(SerializeCtx* ctx, Type value) \ + { \ + Serialization_serialize##AsSuffix(ctx, (As) value); \ + } \ + __attribute__((unused)) \ + Access bool NS##_deserialize(DeserializeCtx* ctx, Type* value) \ + { \ + As tmp; \ + if (!Serialization_deserialize##AsSuffix(ctx, &tmp)) \ + return false; \ + *value = (Type) tmp; \ + return true; \ + } + +/** + * SERDES_DECLARE_LIST_SERIALIZERS() - declare list serializers for a given type + * @NS namespace prefix for the generated functions + * @Type type of list entry + * + * Declares ``extern`` prototypes of the functions as generated by + * ``SERDES_DEFINE_LIST_SERIALIZERS`` + */ +#define SERDES_DECLARE_LIST_SERIALIZERS(NS, Type) \ + extern void NS##_serialize(SerializeCtx* ctx, const struct list_head* list); \ + extern bool NS##_deserialize(DeserializeCtx* ctx, struct list_head* list); \ +/** + * SERDES_DEFINE_LIST_SERIALIZERS() - defines list serializers for a given type + * @Access access modifiers for the generated functions + * @NS namespace prefix for the generated functions + * @Type type of list entry + * @TypeNS namespace of serializer/deserializer functions for @Type + * @TypeFini cleanup expression for a @Type + * @ListMember name of the &struct list_head for this list in @Type + * @HasLength %true if the binary representation of the list required a length field, %false if it + * must not have a length field + * + * Defines two functions ``void NS##_serialize(SerializeCtx* ctx, const struct list_head* list)`` + * and ``bool NS##_deserialize(DeserializeCtx* ctx, struct list_head* list)`` for serialization and + * deserialization of lists. + * + * For serialization, each field is serialized in order by calling ``TypeNS##_serialize``. + * + * For deserialization, each field is deserialized into a newly kmalloc()ed element by calling + * ``TypeNS##_deserialize``. Correctly deserialized elements are appended to the provided + * &struct list_head. If deserialization fails the expression ``TypeFini(entry)`` is evaluated for + * each successfully deserialized element, ``list`` is left unchanged. + */ +#define SERDES_DEFINE_LIST_SERIALIZERS(Access, NS, Type, TypeNS, TypeFini, ListMember, HasLength) \ + __attribute__((unused)) \ + Access void NS##_serialize(SerializeCtx* ctx, const struct list_head* list) \ + { \ + unsigned items = 0; \ + Type* item; \ + SerializeCtx ctxAtStart = *ctx; \ + if (HasLength) \ + Serialization_serializeUInt(ctx, 0); /* total field size */ \ + Serialization_serializeUInt(ctx, 0); /* number of elements */ \ + list_for_each_entry(item, list, ListMember) \ + { \ + TypeNS##_serialize(ctx, item); \ + items += 1; \ + } \ + if (HasLength) \ + Serialization_serializeUInt(&ctxAtStart, ctx->length - ctxAtStart.length); \ + Serialization_serializeUInt(&ctxAtStart, items); \ + } \ + __attribute__((unused)) \ + Access bool NS##_deserialize(DeserializeCtx* ctx, struct list_head* list) \ + { \ + DeserializeCtx innerCtx = {0, 0}; \ + DeserializeCtx* usedCtx = NULL; \ + unsigned items = 0; \ + LIST_HEAD(desItems); \ + \ + if (HasLength) { \ + if (!__Serialization_deserializeNestedField(ctx, &innerCtx)) \ + return false; \ + usedCtx = &innerCtx; \ + } else { \ + usedCtx = ctx; \ + } \ + if (!Serialization_deserializeUInt(usedCtx, &items)) \ + return false; \ + while (items-- > 0) \ + { \ + Type* entry = kmalloc(sizeof(*entry), GFP_NOFS); \ + if (!entry) \ + goto err; \ + if (!TypeNS##_deserialize(usedCtx, entry)) \ + { \ + kfree(entry); \ + goto err; \ + } \ + list_add_tail(&entry->ListMember, &desItems); \ + } \ + list_splice_tail(&desItems, list); \ + return true; \ + err: \ + BEEGFS_KFREE_LIST_DTOR(&desItems, Type, ListMember, TypeFini); \ + return false; \ + } + + +/** + * SERDES_DEFINE_ENUM_SERIALIZERS() - defines static inline serializers for an enum type with a + * given underlying type. + * @NS namespace prefix for the generated functions + * @EnumTy full enum type + * @Underlying underlying integer type of the enum + * @SerdesSuffix suffix for the ``Serialization_`` functions that serialize the underlying type + */ +#define SERDES_DEFINE_ENUM_SERIALIZERS(NS, EnumTy, Underlying, SerdesSuffix) \ + static inline void NS##_serialize(SerializeCtx* ctx, EnumTy value) \ + { \ + Serialization_serialize##SerdesSuffix(ctx, (Underlying) value); \ + } \ + static inline bool NS##_deserialize(DeserializeCtx* ctx, EnumTy* value) \ + { \ + Underlying raw; \ + if (!Serialization_deserialize##SerdesSuffix(ctx, &raw)) \ + return false; \ + *value = (EnumTy) raw; \ + return true; \ + } + +#endif /*SERIALIZATION_H_*/ diff --git a/client_module/source/common/toolkit/SerializationTypes.h b/client_module/source/common/toolkit/SerializationTypes.h new file mode 100644 index 0000000..fb71760 --- /dev/null +++ b/client_module/source/common/toolkit/SerializationTypes.h @@ -0,0 +1,22 @@ +#ifndef common_toolkit_SerializationTypes_h_uxGn0NqFVyO6ma2TYHAEI +#define common_toolkit_SerializationTypes_h_uxGn0NqFVyO6ma2TYHAEI + +#include + +typedef struct SerializeCtx { + char* const data; + unsigned length; +} SerializeCtx; + +typedef struct DeserializeCtx { + const char* data; + size_t length; +} DeserializeCtx; + +typedef struct RawList { + const char* data; + unsigned length; + unsigned elemCount; +} RawList; + +#endif diff --git a/client_module/source/common/toolkit/SocketTk.c b/client_module/source/common/toolkit/SocketTk.c new file mode 100644 index 0000000..1991f97 --- /dev/null +++ b/client_module/source/common/toolkit/SocketTk.c @@ -0,0 +1,235 @@ +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + + +struct file* SocketTkDummyFilp = NULL; + + +/** + * One-time initialization of a dummy file pointer (required for polling) + * Note: remember to call the corresponding uninit routine + */ +bool SocketTk_initOnce(void) +{ + SocketTkDummyFilp = filp_open("/dev/null", O_RDONLY, 0); + if(IS_ERR(SocketTkDummyFilp) ) + { + printk_fhgfs(KERN_WARNING, "Failed to open the dummy filp for polling\n"); + return false; + } + + return true; +} + +void SocketTk_uninitOnce(void) +{ + if(SocketTkDummyFilp && !IS_ERR(SocketTkDummyFilp) ) + filp_close(SocketTkDummyFilp, NULL); +} + + +/* + * Synchronous I/O multiplexing for standard and RDMA sockets. + * Note: comparable to userspace poll() + * + * @param pollState list of sockets and event wishes + * @return number of socks for which interesting events are available or negative linux error code. + * (tvp is being set to time remaining) + */ +int SocketTk_poll(PollState* state, int timeoutMS) +{ + struct poll_wqueues stdTable; + poll_table* stdWait = NULL; // value NULL means "don't register for waiting" + int table_err = 0; + int numSocksWithREvents = 0; // the return value + Socket* socket; + + long __timeout = TimeTk_msToJiffiesSchedulable(timeoutMS); + + /* 4.19 (vanilla, not stable) had a bug in the sock_poll_wait signature. rhel 4.18 backports + * this bug. 4.19.1 fixes it again. */ + BUILD_BUG_ON(__builtin_types_compatible_p( + __typeof__(&sock_poll_wait), + void (*)(struct file*, poll_table*))); + + poll_initwait(&stdTable); + + if(__timeout) + stdWait = &stdTable.pt; + + // 1st loop: register the socks that we're waiting for and wait blocking + // if no data is available yet + // 2nd loop (after event or timeout): check all socks for available data + // note: std socks return all events, even those we haven't been waiting for + // 3rd and futher loops: in case an uninteresting event occurred + for( ; ; ) + { + numSocksWithREvents = 0; // (must be inside the loop to be consistent) + + /* wait INTERRUPTIBLE if no signal is pending, otherwise the wait contributes to load. + * users of this function should be migrated to use socket callbacks instead. */ + set_current_state(signal_pending(current) ? TASK_KILLABLE : TASK_INTERRUPTIBLE); + + // for each sock: ask for available data and register waitqueue + list_for_each_entry(socket, &state->list, poll._list) + { + if(Socket_getSockType(socket) == NICADDRTYPE_RDMA) + { // RDMA socket + struct RDMASocket* currentRDMASock = (RDMASocket*)socket; + bool finishPoll = (numSocksWithREvents || !__timeout); + + unsigned long mask = RDMASocket_poll( + currentRDMASock, socket->poll._events, finishPoll); + + if(mask) + { // interesting event occurred + socket->poll.revents = mask; // save event mask as revents + numSocksWithREvents++; + } + } + else + { // Standard socket + struct socket* currentRawSock = + StandardSocket_getRawSock( (StandardSocket*)socket); + poll_table* currentStdWait = numSocksWithREvents ? NULL : stdWait; + + unsigned long mask = (*currentRawSock->ops->poll)( + SocketTkDummyFilp, currentRawSock, currentStdWait); + + if(mask & (socket->poll._events | POLLERR | POLLHUP | POLLNVAL) ) + { // interesting event occurred + socket->poll.revents = mask; // save event mask as revents + numSocksWithREvents++; + } + + //cond_resched(); // (commented out for latency reasons) + } + } // end of for_each_socket loop + + stdWait = NULL; // don't register standard socks for waiting in following loops + + // skip the waiting if we already found something + if (numSocksWithREvents || !__timeout || fatal_signal_pending(current)) + break; + + // skip the waiting if we have an error + if(unlikely(stdTable.error) ) + { + table_err = stdTable.error; + break; + } + + // wait (and reduce remaining timeout) + __timeout = schedule_timeout(__timeout); + + } // end of sleep loop + + __set_current_state(TASK_RUNNING); + + // cleanup loop for RDMA socks + list_for_each_entry(socket, &state->list, poll._list) + { + if(Socket_getSockType(socket) == NICADDRTYPE_RDMA) + { + struct RDMASocket* currentRDMASock = (RDMASocket*)socket; + RDMASocket_poll(currentRDMASock, socket->poll._events, true); + } + } + + // cleanup for standard socks + poll_freewait(&stdTable); + + //printk_fhgfs(KERN_INFO, "%s:%d: return %d\n", __func__, __LINE__, + // table_err ? table_err : numSocksWithREvents); // debug in + + return table_err ? table_err : numSocksWithREvents; +} + + +/** + * Note: Old kernel versions do not support validation of the IP string. + * + * @param outIPAddr set to INADDR_NONE if an error was detected + * @return false for wrong input on modern kernels (>= 2.6.20), old kernels always return + * true + */ +bool SocketTk_getHostByAddrStr(const char* hostAddr, struct in_addr* outIPAddr) +{ + if(unlikely(!in4_pton(hostAddr, strlen(hostAddr), (u8 *)outIPAddr, -1, NULL) ) ) + { // not a valid address string + outIPAddr->s_addr = INADDR_NONE; + return false; + } + + return true; +} + +/** + * Note: Better use SocketTk_getHostByAddrStr(), which can also check for errors on recent kernels. + * + * @return INADDR_NONE if an error was detected (recent kernels only) + */ +struct in_addr SocketTk_in_aton(const char* hostAddr) +{ + struct in_addr retVal; + + // Note: retVal INADDR_NONE will be set by getHostByAddrStr() + + SocketTk_getHostByAddrStr(hostAddr, &retVal); + + return retVal; +} + +/** + * @param buf the buffer to which should be written. + */ +void SocketTk_ipaddrToStrNoAlloc(struct in_addr ipaddress, char* ipStr, size_t ipStrLen) +{ + int printRes = snprintf(ipStr, ipStrLen, "%pI4", &ipaddress); + if(unlikely( (size_t)printRes >= ipStrLen) ) + ipStr[ipStrLen-1] = 0; // ipStrLen exceeded => zero-terminate result +} + +/** + * @return string is kalloced and needs to be kfreed + */ +char* SocketTk_ipaddrToStr(struct in_addr ipaddress) +{ + char* ipStr = os_kmalloc(SOCKETTK_IPADDRSTR_LEN); + if (likely(ipStr != NULL)) + SocketTk_ipaddrToStrNoAlloc(ipaddress, ipStr, SOCKETTK_IPADDRSTR_LEN); + return ipStr; +} + +/** + * @param buf the buffer to which : should be written. + */ +void SocketTk_endpointAddrToStrNoAlloc(char* buf, size_t bufLen, struct in_addr ipaddress, + unsigned short port) +{ + int printRes = snprintf(buf, bufLen, "%pI4:%u", &ipaddress, port); + if(unlikely( (unsigned)printRes >= bufLen) ) + buf[bufLen-1] = 0; // bufLen exceeded => zero-terminate result +} + +/** + * @return string is kalloced and needs to be kfreed + */ +char* SocketTk_endpointAddrToStr(struct in_addr ipaddress, unsigned short port) +{ + char* endpointStr = os_kmalloc(SOCKETTK_ENDPOINTSTR_LEN); + if (likely(endpointStr != NULL)) + SocketTk_endpointAddrToStrNoAlloc(endpointStr, SOCKETTK_ENDPOINTSTR_LEN, ipaddress, port); + return endpointStr; +} + + diff --git a/client_module/source/common/toolkit/SocketTk.h b/client_module/source/common/toolkit/SocketTk.h new file mode 100644 index 0000000..97410d6 --- /dev/null +++ b/client_module/source/common/toolkit/SocketTk.h @@ -0,0 +1,49 @@ +#ifndef OPEN_SOCKETTK_H_ +#define OPEN_SOCKETTK_H_ + +#include +#include +#include +#include + +#define SOCKETTK_ENDPOINTSTR_LEN SOCKET_PEERNAME_LEN // size for _endpointAddrToStrNoAlloc() +#define SOCKETTK_IPADDRSTR_LEN (4*4) + +// forward declarations +struct PollState; +typedef struct PollState PollState; + + +extern bool SocketTk_initOnce(void); +extern void SocketTk_uninitOnce(void); + +extern int SocketTk_poll(PollState* state, int timeoutMS); + +extern bool SocketTk_getHostByAddrStr(const char* hostAddr, struct in_addr* outIPAddr); +extern struct in_addr SocketTk_in_aton(const char* hostAddr); + +extern char* SocketTk_ipaddrToStr(struct in_addr ipaddress); +extern void SocketTk_ipaddrToStrNoAlloc(struct in_addr ipaddress, char* ipStr, size_t ipStrLen); +extern char* SocketTk_endpointAddrToStr(struct in_addr ipaddress, unsigned short port); +extern void SocketTk_endpointAddrToStrNoAlloc(char* buf, size_t bufLen, + struct in_addr ipaddress, unsigned short port); + + +struct PollState +{ + struct list_head list; +}; + +static inline void PollState_init(PollState* state) +{ + INIT_LIST_HEAD(&state->list); +} + +static inline void PollState_addSocket(PollState* state, Socket* socket, short events) +{ + list_add_tail(&socket->poll._list, &state->list); + socket->poll._events = events; + socket->poll.revents = 0; +} + +#endif /*OPEN_SOCKETTK_H_*/ diff --git a/client_module/source/common/toolkit/StringTk.c b/client_module/source/common/toolkit/StringTk.c new file mode 100644 index 0000000..1ecbbfb --- /dev/null +++ b/client_module/source/common/toolkit/StringTk.c @@ -0,0 +1,125 @@ +#include +#include + + +void StringTk_explode(const char* s, char delimiter, StrCpyList* outList) +{ + ssize_t sLen = strlen(s); + ssize_t lastPos = (strlen(s) && (s[0] == delimiter) ) ? 0 : -1; + ssize_t currentPos; + char* findRes; + ssize_t newElemLen; + + // note: starts at pos 1 because an occurence at pos 0 would lead to an empty string, + // which would be ignored anyways + // note: i think the initialization of currentPos here is useless + for(currentPos = 1; ; ) + { + findRes = strchr(&s[lastPos+1], delimiter); + + if(!findRes) + { + ssize_t restLen = sLen - (lastPos+1); + + if(restLen) + { // add rest + char* newElem = StringTk_subStr(&s[lastPos+1], restLen); + + StrCpyList_append(outList, newElem); + kfree(newElem); + } + + return; + } + + currentPos = findRes - s; + + // add substring to outList (leave the delimiter positions out) + newElemLen = currentPos-lastPos-1; // -1=last delimiter + if(newElemLen) + { + char* newElem = StringTk_subStr(&s[lastPos+1], newElemLen); + + StrCpyList_append(outList, newElem); + kfree(newElem); + } + + lastPos = currentPos; + } +} + + +bool StringTk_strToBool(const char* s) +{ + if( !StringTk_hasLength(s) || + !strcmp(s, "1") || + !strcasecmp(s, "y") || + !strcasecmp(s, "on") || + !strcasecmp(s, "yes") || + !strcasecmp(s, "true") ) + return true; + + return false; +} + + +/** + * @return string is kmalloc'ed and has to be kfree'd by the caller + */ +char* StringTk_trimCopy(const char* s) +{ + int lastRight; + int firstLeft; + int sLen; + + sLen = strlen(s); + + firstLeft = 0; + while( (firstLeft < sLen) && + (s[firstLeft]==' ' || s[firstLeft]=='\n' || s[firstLeft]=='\r' || s[firstLeft]=='\t') ) + firstLeft++; + + if(firstLeft == sLen) + return StringTk_strDup(""); // the string is empty or contains only space chars + + // the string contains at least one non-space char + + lastRight = sLen - 1; + while(s[lastRight]==' ' || s[lastRight]=='\n' || s[lastRight]=='\r' || s[lastRight]=='\t') + lastRight--; + + return StringTk_subStr(&s[firstLeft], lastRight - firstLeft + 1); +} + +/** + * Note: Provided, because old kernels don't provide kasprintf(). + * + * @return will be kalloced and needs to be kfree'd by the caller + */ +char* StringTk_kasprintf(const char *fmt, ...) +{ + char* printStr; + int printLen; + char tmpChar; // (old kernels don't seem to be able to handle vsnprintf(NULL, 0, ...) ) + va_list ap; + va_list apCopy; // kasprintf uses this + + va_start(ap, fmt); + + va_copy(apCopy, ap); + + printLen = vsnprintf(&tmpChar, 1, fmt, apCopy); + + va_end(apCopy); + + printStr = os_kmalloc(printLen + 1); + if(likely(printStr) ) + { + vsnprintf(printStr, printLen + 1, fmt, ap); + } + + va_end(ap); + + return printStr; +} + diff --git a/client_module/source/common/toolkit/StringTk.h b/client_module/source/common/toolkit/StringTk.h new file mode 100644 index 0000000..9256171 --- /dev/null +++ b/client_module/source/common/toolkit/StringTk.h @@ -0,0 +1,101 @@ +#ifndef OPEN_STRINGTK_H_ +#define OPEN_STRINGTK_H_ + +#include +#include + + +// manipulation +extern void StringTk_explode(const char* s, char delimiter, StrCpyList* outList); +static inline char* StringTk_strncpyTerminated(char* dest, const char* src, size_t count); + +// numerical +static inline int StringTk_strToInt(const char* s); +static inline unsigned StringTk_strToUInt(const char* s); +static inline uint64_t StringTk_strToUInt64(const char* s); +extern bool StringTk_strToBool(const char* s); + +// construction +static inline char* StringTk_strDup(const char* s); + +static inline char* StringTk_subStr(const char* start, unsigned numChars); +extern char* StringTk_trimCopy(const char* s); +extern char* StringTk_kasprintf(const char *fmt, ...); + +static inline char* StringTk_intToStr(int a); +static inline char* StringTk_uintToStr(unsigned a); + +// inliners +static inline bool StringTk_hasLength(const char* s); + + + +bool StringTk_hasLength(const char* s) +{ + return s[0] ? true : false; +} + + +char* StringTk_strncpyTerminated(char* dest, const char* src, size_t count) +{ + // Note: The problem with strncpy() is that dest is not guaranteed to be zero-terminated. + // strlcpy() does guarantee that. + // strlcpy() was removed in commit d26270061ae6 (string: Remove strlcpy()), use strscpy() instead. + + strscpy(dest, src, count); + + return dest; +} + +int StringTk_strToInt(const char* s) +{ + return (int)simple_strtol(s, NULL, 10); +} + +unsigned StringTk_strToUInt(const char* s) +{ + return (unsigned)simple_strtoul(s, NULL, 10); +} + +uint64_t StringTk_strToUInt64(const char* s) +{ + return simple_strtoull(s, NULL, 10); +} + +char* StringTk_strDup(const char* s) +{ + return kstrdup(s, GFP_NOFS); +} + +/** + * @return string is kmalloc'ed and has to be kfree'd by the caller + */ +char* StringTk_subStr(const char* start, unsigned numChars) +{ + char* subStr = (char*)os_kmalloc(numChars+1); + + if(numChars) + memcpy(subStr, start, numChars); + + subStr[numChars] = 0; + + return subStr; +} + +char* StringTk_intToStr(int a) +{ + char aStr[24]; + sprintf(aStr, "%d", a); + + return StringTk_strDup(aStr); +} + +char* StringTk_uintToStr(unsigned a) +{ + char aStr[24]; + sprintf(aStr, "%u", a); + + return StringTk_strDup(aStr); +} + +#endif /*OPEN_STRINGTK_H_*/ diff --git a/client_module/source/common/toolkit/SynchronizedCounter.h b/client_module/source/common/toolkit/SynchronizedCounter.h new file mode 100644 index 0000000..4ae148e --- /dev/null +++ b/client_module/source/common/toolkit/SynchronizedCounter.h @@ -0,0 +1,52 @@ +#ifndef SYNCHRONIZEDCOUNTER_H_ +#define SYNCHRONIZEDCOUNTER_H_ + +#include +#include +#include +#include +#include + +struct SynchronizedCounter; +typedef struct SynchronizedCounter SynchronizedCounter; + +static inline void SynchronizedCounter_init(SynchronizedCounter* this); + +// inliners +static inline void SynchronizedCounter_waitForCount(SynchronizedCounter* this, int waitCount); +static inline void SynchronizedCounter_incCount(SynchronizedCounter* this); +static inline void SynchronizedCounter_incCountBy(SynchronizedCounter* this, int count); + + +struct SynchronizedCounter +{ + atomic_t count; + + struct completion barrier; +}; + + +void SynchronizedCounter_init(SynchronizedCounter* this) +{ + atomic_set(&this->count, 0); + init_completion(&this->barrier); +} + +void SynchronizedCounter_waitForCount(SynchronizedCounter* this, int waitCount) +{ + SynchronizedCounter_incCountBy(this, -waitCount); + wait_for_completion(&this->barrier); +} + +void SynchronizedCounter_incCount(SynchronizedCounter* this) +{ + SynchronizedCounter_incCountBy(this, 1); +} + +void SynchronizedCounter_incCountBy(SynchronizedCounter* this, int count) +{ + if (atomic_add_return(count, &this->count) == 0) + complete(&this->barrier); +} + +#endif /*SYNCHRONIZEDCOUNTER_H_*/ diff --git a/client_module/source/common/toolkit/Time.h b/client_module/source/common/toolkit/Time.h new file mode 100644 index 0000000..6cd81bc --- /dev/null +++ b/client_module/source/common/toolkit/Time.h @@ -0,0 +1,122 @@ +#ifndef OPEN_TIME_H_ +#define OPEN_TIME_H_ + +#include + +#include +#include + + + +/** + * The basis for this Time class is a monotonic clock. + */ +#ifdef KERNEL_HAS_64BIT_TIMESTAMPS +typedef struct timespec64 Time; +#else +typedef struct timespec Time; +#endif + + +static inline unsigned Time_elapsedSinceMS(Time* this, Time* earlierT); +static inline unsigned long long Time_elapsedSinceNS(Time* this, Time* earlierT); +static inline unsigned Time_elapsedMS(Time* this); +static inline int Time_compare(Time* this, Time* o); +static inline unsigned long long Time_toNS(Time* this); + +static inline void Time_setToNow(Time* this) +{ +#ifdef KERNEL_HAS_KTIME_GET_TS64 + ktime_get_ts64(this); +#else + ktime_get_ts(this); +#endif +} + +/** + * Set time to to "real" time, i.e. get the time from a non-monotonic clock. + */ +static inline void Time_setToNowReal(Time *this) +{ +#ifdef KERNEL_HAS_KTIME_GET_REAL_TS64 + ktime_get_real_ts64(this); +#else + ktime_get_real_ts(this); +#endif +} + +static inline void Time_setZero(Time *this) +{ + this->tv_sec = 0; + this->tv_nsec = 0; +} + + + +/** + * Set to current time. + */ +static inline void Time_init(Time* this) +{ + Time_setToNow(this); +} + +/** + * Set time values to zero, e.g. to mark this time as unitialized or far back in the past. + */ +static inline void Time_initZero(Time* this) +{ + Time_setZero(this); +} + +static inline bool Time_getIsZero(Time* this) +{ + return (!this->tv_sec && !this->tv_sec); +} + +static inline unsigned Time_elapsedSinceMS(Time* this, Time* earlierT) +{ + unsigned secs = (this->tv_sec - earlierT->tv_sec) * 1000; + int micros = (this->tv_nsec - earlierT->tv_nsec) / 1000000; /* can also be negative, + so this must be signed */ + + return secs + micros; +} + +unsigned long long Time_elapsedSinceNS(Time* this, Time* earlierT) +{ + unsigned long long secs = (this->tv_sec - earlierT->tv_sec) * 1000000000ull; + long nanos = (this->tv_nsec - earlierT->tv_nsec); /* can also be negative, + so this must be signed */ + + return secs + nanos; +} + +unsigned long long Time_toNS(Time* this) +{ + return this->tv_sec * 1000000000ull + this->tv_nsec; +} + +int Time_compare(Time* this, Time* o) +{ + long long diff = Time_toNS(this) - Time_toNS(o); + if (diff < 0) + return -1; + else if (diff > 0) + return 1; + return 0; +} + +unsigned Time_elapsedMS(Time* this) +{ + unsigned elapsedMS; + + Time currentT; + Time_init(¤tT); + + elapsedMS = Time_elapsedSinceMS(¤tT, this); + + return elapsedMS; +} + +#endif /* OPEN_TIME_H_ */ diff --git a/client_module/source/common/toolkit/TimeTk.h b/client_module/source/common/toolkit/TimeTk.h new file mode 100644 index 0000000..e00b369 --- /dev/null +++ b/client_module/source/common/toolkit/TimeTk.h @@ -0,0 +1,21 @@ +#ifndef TIMETK_H_ +#define TIMETK_H_ + +#include + +#include + + +static inline long TimeTk_msToJiffiesSchedulable(unsigned ms); + + + +long TimeTk_msToJiffiesSchedulable(unsigned ms) +{ + unsigned long resultJiffies = msecs_to_jiffies(ms); + + return (resultJiffies >= MAX_SCHEDULE_TIMEOUT) ? (MAX_SCHEDULE_TIMEOUT-1) : resultJiffies; +} + + +#endif /*TIMETK_H_*/ diff --git a/client_module/source/common/toolkit/ackstore/AckStoreMap.c b/client_module/source/common/toolkit/ackstore/AckStoreMap.c new file mode 100644 index 0000000..9bdb7f2 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/AckStoreMap.c @@ -0,0 +1,20 @@ +#include "AckStoreMap.h" +#include "AckStoreMapIter.h" + +AckStoreMapIter AckStoreMap_find(AckStoreMap* this, const char* searchKey) +{ + RBTreeElem* treeElem = _PointerRBTree_findElem( (RBTree*)this, searchKey); + + AckStoreMapIter iter; + AckStoreMapIter_init(&iter, this, treeElem); + + return iter; +} + +int compareAckStoreMapElems(const void* key1, const void* key2) +{ + return strcmp(key1, key2); +} + + + diff --git a/client_module/source/common/toolkit/ackstore/AckStoreMap.h b/client_module/source/common/toolkit/ackstore/AckStoreMap.h new file mode 100644 index 0000000..af77904 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/AckStoreMap.h @@ -0,0 +1,117 @@ +#ifndef ACKSTOREMAP_H_ +#define ACKSTOREMAP_H_ + +#include +#include +#include "WaitAckMap.h" + + +struct AckStoreEntry; +typedef struct AckStoreEntry AckStoreEntry; + + +struct AckStoreMapElem; +typedef struct AckStoreMapElem AckStoreMapElem; +struct AckStoreMap; +typedef struct AckStoreMap AckStoreMap; + +struct AckStoreMapIter; // forward declaration of the iterator + + +static inline void AckStoreEntry_init(AckStoreEntry* this, char* ackID, + WaitAckMap* waitMap, WaitAckMap* receivedMap, WaitAckNotification* notifier); +static inline AckStoreEntry* AckStoreEntry_construct(char* ackID, + WaitAckMap* waitMap, WaitAckMap* receivedMap, WaitAckNotification* notifier); +static inline void AckStoreEntry_destruct(AckStoreEntry* this); + + +static inline void AckStoreMap_init(AckStoreMap* this); +static inline void AckStoreMap_uninit(AckStoreMap* this); + +static inline bool AckStoreMap_insert(AckStoreMap* this, char* newKey, + AckStoreEntry* newValue); +static inline bool AckStoreMap_erase(AckStoreMap* this, const char* eraseKey); + +extern struct AckStoreMapIter AckStoreMap_find(AckStoreMap* this, const char* searchKey); + +extern int compareAckStoreMapElems(const void* key1, const void* key2); + + +struct AckStoreEntry +{ + char* ackID; + + WaitAckMap* waitMap; // ack will be removed from this map if it is received + WaitAckMap* receivedMap; // ack will be added to this map if it is received + + WaitAckNotification* notifier; +}; + + + +struct AckStoreMapElem +{ + RBTreeElem rbTreeElem; +}; + +struct AckStoreMap +{ + RBTree rbTree; +}; + + +void AckStoreEntry_init(AckStoreEntry* this, char* ackID, + WaitAckMap* waitMap, WaitAckMap* receivedMap, WaitAckNotification* notifier) +{ + this->ackID = ackID; + this->waitMap = waitMap; + this->receivedMap = receivedMap; + this->notifier = notifier; +} + +AckStoreEntry* AckStoreEntry_construct(char* ackID, + WaitAckMap* waitMap, WaitAckMap* receivedMap, WaitAckNotification* notifier) +{ + AckStoreEntry* this = (AckStoreEntry*)os_kmalloc(sizeof(*this) ); + + AckStoreEntry_init(this, ackID, waitMap, receivedMap, notifier); + + return this; +} + +void AckStoreEntry_destruct(AckStoreEntry* this) +{ + kfree(this); +} + + + +void AckStoreMap_init(AckStoreMap* this) +{ + PointerRBTree_init( (RBTree*)this, compareAckStoreMapElems); +} + +void AckStoreMap_uninit(AckStoreMap* this) +{ + PointerRBTree_uninit( (RBTree*)this); +} + +bool AckStoreMap_insert(AckStoreMap* this, char* newKey, AckStoreEntry* newValue) +{ + bool insRes; + + insRes = PointerRBTree_insert( (RBTree*)this, newKey, newValue); + if(!insRes) + { + // not inserted because the key already existed + } + + return insRes; +} + +bool AckStoreMap_erase(AckStoreMap* this, const char* eraseKey) +{ + return PointerRBTree_erase( (RBTree*)this, eraseKey); +} + +#endif /* ACKSTOREMAP_H_ */ diff --git a/client_module/source/common/toolkit/ackstore/AckStoreMapIter.h b/client_module/source/common/toolkit/ackstore/AckStoreMapIter.h new file mode 100644 index 0000000..78ac3bc --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/AckStoreMapIter.h @@ -0,0 +1,34 @@ +#ifndef ACKSTOREMAPITER_H_ +#define ACKSTOREMAPITER_H_ + +#include "AckStoreMap.h" + +struct AckStoreMapIter; +typedef struct AckStoreMapIter AckStoreMapIter; + +static inline void AckStoreMapIter_init(AckStoreMapIter* this, AckStoreMap* map, RBTreeElem* treeElem); +static inline AckStoreEntry* AckStoreMapIter_value(AckStoreMapIter* this); +static inline bool AckStoreMapIter_end(AckStoreMapIter* this); + +struct AckStoreMapIter +{ + RBTreeIter rbTreeIter; +}; + +void AckStoreMapIter_init(AckStoreMapIter* this, AckStoreMap* map, RBTreeElem* treeElem) +{ + PointerRBTreeIter_init( (RBTreeIter*)this, (RBTree*)map, (RBTreeElem*)treeElem); +} + +AckStoreEntry* AckStoreMapIter_value(AckStoreMapIter* this) +{ + return (AckStoreEntry*)PointerRBTreeIter_value( (RBTreeIter*)this); +} + +bool AckStoreMapIter_end(AckStoreMapIter* this) +{ + return PointerRBTreeIter_end( (RBTreeIter*)this); +} + + +#endif /* ACKSTOREMAPITER_H_ */ diff --git a/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.c b/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.c new file mode 100644 index 0000000..43bab18 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.c @@ -0,0 +1,117 @@ +#include "AcknowledgmentStore.h" + +/** + * Note: This method does not lock the notifier->waitAcksMutex, because this is typically + * called from a context that does not yet require syncing. + * + * @param waitAcks do not access this map until you called unregister (for thread-safety) + * @param receivedAcks do not access this map until you called unregister (for thread-safety) + * @param notifier used to wake up the thread waiting for acks + */ +void AcknowledgmentStore_registerWaitAcks(AcknowledgmentStore* this, + WaitAckMap* waitAcks, WaitAckMap* receivedAcks, WaitAckNotification* notifier) +{ + WaitAckMapIter iter; + + Mutex_lock(&this->mutex); + + for(iter = WaitAckMap_begin(waitAcks); !WaitAckMapIter_end(&iter); WaitAckMapIter_next(&iter) ) + { + WaitAck* currentWaitAck = WaitAckMapIter_value(&iter); + + AckStoreEntry* newStoreEntry = AckStoreEntry_construct( + currentWaitAck->ackID, waitAcks, receivedAcks, notifier); + + AckStoreMap_insert(&this->storeMap, currentWaitAck->ackID, newStoreEntry); + } + + Mutex_unlock(&this->mutex); +} + +void AcknowledgmentStore_unregisterWaitAcks(AcknowledgmentStore* this, WaitAckMap* waitAcks) +{ + WaitAckMapIter iter; + + Mutex_lock(&this->mutex); + + for(iter = WaitAckMap_begin(waitAcks); !WaitAckMapIter_end(&iter); WaitAckMapIter_next(&iter) ) + { + char* currentKey = WaitAckMapIter_key(&iter); + AckStoreMapIter storeIter = AckStoreMap_find(&this->storeMap, currentKey); + + // free allocated entry and remove it from store map + + AckStoreEntry_destruct(AckStoreMapIter_value(&storeIter) ); + AckStoreMap_erase(&this->storeMap, currentKey); + } + + Mutex_unlock(&this->mutex); +} + +/** + * @return true if someone was waiting for this ackID, false otherwise + */ +bool AcknowledgmentStore_receivedAck(AcknowledgmentStore* this, const char* ackID) +{ + bool retVal = false; + AckStoreMapIter storeIter; + + Mutex_lock(&this->mutex); // L O C K (store) + + storeIter = AckStoreMap_find(&this->storeMap, ackID); + if(!AckStoreMapIter_end(&storeIter) ) + { // ack entry exists in store + WaitAckMapIter waitIter; + AckStoreEntry* storeEntry = AckStoreMapIter_value(&storeIter); + + Mutex_lock(&storeEntry->notifier->waitAcksMutex); // L O C K (notifier) + + waitIter = WaitAckMap_find(storeEntry->waitMap, ackID); + if(!WaitAckMapIter_end(&waitIter) ) + { // entry exists in waitMap => move to receivedMap + char* waitAckID = WaitAckMapIter_key(&waitIter); + WaitAck* waitAck = WaitAckMapIter_value(&waitIter); + + WaitAckMap_insert(storeEntry->receivedMap, waitAckID, waitAck); + WaitAckMap_erase(storeEntry->waitMap, ackID); + + if(!WaitAckMap_length(storeEntry->waitMap) ) + { // all acks received => notify + WaitAckNotification* notifier = storeEntry->notifier; + + Condition_broadcast(¬ifier->waitAcksCompleteCond); + } + + retVal = true; + } + + Mutex_unlock(&storeEntry->notifier->waitAcksMutex); // U N L O C K (notifier) + + + AckStoreEntry_destruct(storeEntry); + AckStoreMap_erase(&this->storeMap, ackID); + } + + Mutex_unlock(&this->mutex); // U N L O C K (store) + + return retVal; +} + + +bool AcknowledgmentStore_waitForAckCompletion(AcknowledgmentStore* this, + WaitAckMap* waitAcks, WaitAckNotification* notifier, int timeoutMS) +{ + bool retVal; + + Mutex_lock(¬ifier->waitAcksMutex); + + if(WaitAckMap_length(waitAcks) ) + Condition_timedwait(¬ifier->waitAcksCompleteCond, ¬ifier->waitAcksMutex, timeoutMS); + + retVal = WaitAckMap_length(waitAcks) ? false : true; + + Mutex_unlock(¬ifier->waitAcksMutex); + + return retVal; +} + diff --git a/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.h b/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.h new file mode 100644 index 0000000..762b419 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/AcknowledgmentStore.h @@ -0,0 +1,67 @@ +#ifndef ACKNOWLEDGMENTSTORE_H_ +#define ACKNOWLEDGMENTSTORE_H_ + +#include +#include +#include +#include +#include "AckStoreMap.h" +#include "AckStoreMapIter.h" +#include "WaitAckMap.h" +#include "WaitAckMapIter.h" + + +struct AcknowledgmentStore; +typedef struct AcknowledgmentStore AcknowledgmentStore; + +static inline void AcknowledgmentStore_init(AcknowledgmentStore* this); +static inline AcknowledgmentStore* AcknowledgmentStore_construct(void); +static inline void AcknowledgmentStore_uninit(AcknowledgmentStore* this); +static inline void AcknowledgmentStore_destruct(AcknowledgmentStore* this); + +extern void AcknowledgmentStore_registerWaitAcks(AcknowledgmentStore* this, + WaitAckMap* waitAcks, WaitAckMap* receivedAcks, WaitAckNotification* notifier); +extern void AcknowledgmentStore_unregisterWaitAcks(AcknowledgmentStore* this, WaitAckMap* waitAcks); +extern bool AcknowledgmentStore_receivedAck(AcknowledgmentStore* this, const char* ackID); +extern bool AcknowledgmentStore_waitForAckCompletion(AcknowledgmentStore* this, + WaitAckMap* waitAcks, WaitAckNotification* notifier, int timeoutMS); + + + +struct AcknowledgmentStore +{ + AckStoreMap storeMap; + Mutex mutex; +}; + + +void AcknowledgmentStore_init(AcknowledgmentStore* this) +{ + AckStoreMap_init(&this->storeMap); + Mutex_init(&this->mutex); +} + +AcknowledgmentStore* AcknowledgmentStore_construct(void) +{ + AcknowledgmentStore* this = (AcknowledgmentStore*)os_kmalloc(sizeof(*this) ); + + AcknowledgmentStore_init(this); + + return this; +} + +void AcknowledgmentStore_uninit(AcknowledgmentStore* this) +{ + Mutex_uninit(&this->mutex); + AckStoreMap_uninit(&this->storeMap); +} + +void AcknowledgmentStore_destruct(AcknowledgmentStore* this) +{ + AcknowledgmentStore_uninit(this); + + kfree(this); +} + + +#endif /* ACKNOWLEDGMENTSTORE_H_ */ diff --git a/client_module/source/common/toolkit/ackstore/WaitAckMap.c b/client_module/source/common/toolkit/ackstore/WaitAckMap.c new file mode 100644 index 0000000..b27acc9 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/WaitAckMap.c @@ -0,0 +1,32 @@ +#include "WaitAckMap.h" +#include "WaitAckMapIter.h" + +WaitAckMapIter WaitAckMap_find(WaitAckMap* this, const char* searchKey) +{ + RBTreeElem* treeElem = _PointerRBTree_findElem( (RBTree*)this, searchKey); + + WaitAckMapIter iter; + WaitAckMapIter_init(&iter, this, treeElem); + + return iter; +} + +WaitAckMapIter WaitAckMap_begin(WaitAckMap* this) +{ + struct rb_node* node = rb_first(&this->rbTree.treeroot); + RBTreeElem* treeElem = node ? container_of(node, RBTreeElem, treenode) : NULL; + + WaitAckMapIter iter; + WaitAckMapIter_init(&iter, this, treeElem); + + return iter; +} + + +int compareWaitAckMapElems(const void* key1, const void* key2) +{ + return strcmp(key1, key2); +} + + + diff --git a/client_module/source/common/toolkit/ackstore/WaitAckMap.h b/client_module/source/common/toolkit/ackstore/WaitAckMap.h new file mode 100644 index 0000000..4df210f --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/WaitAckMap.h @@ -0,0 +1,136 @@ +#ifndef WAITACKMAP_H_ +#define WAITACKMAP_H_ + +#include +#include +#include +#include +#include + + +struct WaitAckNotification; +typedef struct WaitAckNotification WaitAckNotification; + +struct WaitAck; +typedef struct WaitAck WaitAck; + + +struct WaitAckMapElem; +typedef struct WaitAckMapElem WaitAckMapElem; +struct WaitAckMap; +typedef struct WaitAckMap WaitAckMap; + +struct WaitAckMapIter; // forward declaration of the iterator + + +static inline void WaitAckNotification_init(WaitAckNotification* this); +static inline void WaitAckNotification_uninit(WaitAckNotification* this); + +static inline void WaitAck_init(WaitAck* this, char* ackID, void* privateData); + + +static inline void WaitAckMap_init(WaitAckMap* this); +static inline void WaitAckMap_uninit(WaitAckMap* this); + +static inline bool WaitAckMap_insert(WaitAckMap* this, char* newKey, + WaitAck* newValue); +static inline bool WaitAckMap_erase(WaitAckMap* this, const char* eraseKey); +static inline size_t WaitAckMap_length(WaitAckMap* this); + +static inline void WaitAckMap_clear(WaitAckMap* this); + +extern struct WaitAckMapIter WaitAckMap_find(WaitAckMap* this, const char* searchKey); +extern struct WaitAckMapIter WaitAckMap_begin(WaitAckMap* this); + +extern int compareWaitAckMapElems(const void* key1, const void* key2); + + +struct WaitAckNotification +{ + Mutex waitAcksMutex; // this mutex also syncs access to the waitMap/receivedMap during the + // wait phase (which is between registration and deregistration) + Condition waitAcksCompleteCond; // in case all WaitAcks have been received +}; + +struct WaitAck +{ + char* ackID; + void* privateData; // caller's private data +}; + + + +struct WaitAckMapElem +{ + RBTreeElem rbTreeElem; +}; + +struct WaitAckMap +{ + RBTree rbTree; +}; + + +void WaitAckNotification_init(WaitAckNotification* this) +{ + Mutex_init(&this->waitAcksMutex); + Condition_init(&this->waitAcksCompleteCond); +} + +void WaitAckNotification_uninit(WaitAckNotification* this) +{ + Mutex_uninit(&this->waitAcksMutex); +} + +/** + * @param ackID is not copied, so don't free or touch it while you use this object + * @param privateData any private data that helps the caller to identify to ack later + */ +void WaitAck_init(WaitAck* this, char* ackID, void* privateData) +{ + this->ackID = ackID; + this->privateData = privateData; +} + +void WaitAckMap_init(WaitAckMap* this) +{ + PointerRBTree_init( (RBTree*)this, compareWaitAckMapElems); +} + +void WaitAckMap_uninit(WaitAckMap* this) +{ + PointerRBTree_uninit( (RBTree*)this); +} + + +bool WaitAckMap_insert(WaitAckMap* this, char* newKey, WaitAck* newValue) +{ + bool insRes; + + insRes = PointerRBTree_insert( (RBTree*)this, newKey, newValue); + if(!insRes) + { + // not inserted because the key already existed + } + + return insRes; +} + +bool WaitAckMap_erase(WaitAckMap* this, const char* eraseKey) +{ + return PointerRBTree_erase( (RBTree*)this, eraseKey); +} + +size_t WaitAckMap_length(WaitAckMap* this) +{ + return PointerRBTree_length( (RBTree*)this); +} + + +void WaitAckMap_clear(WaitAckMap* this) +{ + PointerRBTree_clear(&this->rbTree); +} + + +#endif /* WAITACKMAP_H_ */ diff --git a/client_module/source/common/toolkit/ackstore/WaitAckMapIter.h b/client_module/source/common/toolkit/ackstore/WaitAckMapIter.h new file mode 100644 index 0000000..ae19fe1 --- /dev/null +++ b/client_module/source/common/toolkit/ackstore/WaitAckMapIter.h @@ -0,0 +1,46 @@ +#ifndef WAITACKMAPITER_H_ +#define WAITACKMAPITER_H_ + +#include "WaitAckMap.h" + +struct WaitAckMapIter; +typedef struct WaitAckMapIter WaitAckMapIter; + +static inline void WaitAckMapIter_init(WaitAckMapIter* this, WaitAckMap* map, RBTreeElem* treeElem); +static inline WaitAck* WaitAckMapIter_next(WaitAckMapIter* this); +static inline char* WaitAckMapIter_key(WaitAckMapIter* this); +static inline WaitAck* WaitAckMapIter_value(WaitAckMapIter* this); +static inline bool WaitAckMapIter_end(WaitAckMapIter* this); + +struct WaitAckMapIter +{ + RBTreeIter rbTreeIter; +}; + +void WaitAckMapIter_init(WaitAckMapIter* this, WaitAckMap* map, RBTreeElem* treeElem) +{ + PointerRBTreeIter_init( (RBTreeIter*)this, (RBTree*)map, (RBTreeElem*)treeElem); +} + +WaitAck* WaitAckMapIter_next(WaitAckMapIter* this) +{ + return (WaitAck*)PointerRBTreeIter_next( (RBTreeIter*)this); +} + +char* WaitAckMapIter_key(WaitAckMapIter* this) +{ + return (char*)PointerRBTreeIter_key( (RBTreeIter*)this); +} + +WaitAck* WaitAckMapIter_value(WaitAckMapIter* this) +{ + return (WaitAck*)PointerRBTreeIter_value( (RBTreeIter*)this); +} + +bool WaitAckMapIter_end(WaitAckMapIter* this) +{ + return PointerRBTreeIter_end( (RBTreeIter*)this); +} + + +#endif /* WAITACKMAPITER_H_ */ diff --git a/client_module/source/common/toolkit/list/Int64CpyList.h b/client_module/source/common/toolkit/list/Int64CpyList.h new file mode 100644 index 0000000..ac3c012 --- /dev/null +++ b/client_module/source/common/toolkit/list/Int64CpyList.h @@ -0,0 +1,73 @@ +#ifndef INT64CPYLIST_H_ +#define INT64CPYLIST_H_ + +#include + +/** + * We need to copy int64 values (instead of just assigning them to just to the internal pointer) + * because we might be running on 32bit archs. + */ + +struct Int64CpyList; +typedef struct Int64CpyList Int64CpyList; + +static inline void Int64CpyList_init(Int64CpyList* this); +static inline void Int64CpyList_uninit(Int64CpyList* this); +static inline void Int64CpyList_append(Int64CpyList* this, int64_t value); +static inline size_t Int64CpyList_length(Int64CpyList* this); +static inline void Int64CpyList_clear(Int64CpyList* this); + +struct Int64CpyList +{ + struct PointerList pointerList; +}; + + +void Int64CpyList_init(Int64CpyList* this) +{ + PointerList_init( (PointerList*)this); +} + +void Int64CpyList_uninit(Int64CpyList* this) +{ + struct PointerListElem* elem = ( (PointerList*)this)->head; + while(elem) + { + struct PointerListElem* next = elem->next; + kfree(elem->valuePointer); + elem = next; + } + + + PointerList_uninit( (PointerList*)this); +} + +void Int64CpyList_append(Int64CpyList* this, int64_t value) +{ + int64_t* valueCopyPointer = (int64_t*)os_kmalloc(sizeof(int64_t) ); + + *valueCopyPointer = value; + + PointerList_append( (PointerList*)this, valueCopyPointer); +} + +static inline size_t Int64CpyList_length(Int64CpyList* this) +{ + return PointerList_length( (PointerList*)this); +} + +void Int64CpyList_clear(Int64CpyList* this) +{ + struct PointerListElem* elem = ( (PointerList*)this)->head; + while(elem) + { + struct PointerListElem* next = elem->next; + kfree(elem->valuePointer); + elem = next; + } + + PointerList_clear( (PointerList*)this); +} + + +#endif /*INT64CPYLIST_H_*/ diff --git a/client_module/source/common/toolkit/list/Int64CpyListIter.h b/client_module/source/common/toolkit/list/Int64CpyListIter.h new file mode 100644 index 0000000..af877d7 --- /dev/null +++ b/client_module/source/common/toolkit/list/Int64CpyListIter.h @@ -0,0 +1,43 @@ +#ifndef INT64CPYLISTITER_H_ +#define INT64CPYLISTITER_H_ + +#include "Int64CpyList.h" +#include "PointerListIter.h" + +struct Int64CpyListIter; +typedef struct Int64CpyListIter Int64CpyListIter; + +static inline void Int64CpyListIter_init(Int64CpyListIter* this, Int64CpyList* list); +static inline void Int64CpyListIter_next(Int64CpyListIter* this); +static inline int64_t Int64CpyListIter_value(Int64CpyListIter* this); +static inline bool Int64CpyListIter_end(Int64CpyListIter* this); + + +struct Int64CpyListIter +{ + struct PointerListIter pointerListIter; +}; + + +void Int64CpyListIter_init(Int64CpyListIter* this, Int64CpyList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void Int64CpyListIter_next(Int64CpyListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +int64_t Int64CpyListIter_value(Int64CpyListIter* this) +{ + return *(int64_t*)PointerListIter_value( (PointerListIter*)this); +} + +bool Int64CpyListIter_end(Int64CpyListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + +#endif /*INT64CPYLISTITER_H_*/ diff --git a/client_module/source/common/toolkit/list/NumNodeIDList.h b/client_module/source/common/toolkit/list/NumNodeIDList.h new file mode 100644 index 0000000..36b80b5 --- /dev/null +++ b/client_module/source/common/toolkit/list/NumNodeIDList.h @@ -0,0 +1,54 @@ +#ifndef NUMNODEIDLIST_H_ +#define NUMNODEIDLIST_H_ + +#include +#include + +/** + * Derived from PointerList. Internally, we cast NumNodeID values directly to pointers here (instead + * of allocating them and assigning the pointer to the allocated mem here). + */ + +struct NumNodeIDList; +typedef struct NumNodeIDList NumNodeIDList; + +static inline void NumNodeIDList_init(NumNodeIDList* this); +static inline void NumNodeIDList_uninit(NumNodeIDList* this); +static inline void NumNodeIDList_append(NumNodeIDList* this, NumNodeID value); +static inline size_t NumNodeIDList_length(NumNodeIDList* this); +static inline void NumNodeIDList_clear(NumNodeIDList* this); + +struct NumNodeIDList +{ + struct PointerList pointerList; +}; + + +void NumNodeIDList_init(NumNodeIDList* this) +{ + PointerList_init( (PointerList*)this); +} + +void NumNodeIDList_uninit(NumNodeIDList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void NumNodeIDList_append(NumNodeIDList* this, NumNodeID value) +{ + /* cast value directly to pointer type here to store value directly in the pointer variable + without allocating extra mem */ + PointerList_append( (PointerList*)this, (void*)(size_t)value.value); +} + +static inline size_t NumNodeIDList_length(NumNodeIDList* this) +{ + return PointerList_length( (PointerList*)this); +} + +void NumNodeIDList_clear(NumNodeIDList* this) +{ + PointerList_clear( (PointerList*)this); +} + +#endif /* NUMNODEIDLIST_H_ */ diff --git a/client_module/source/common/toolkit/list/NumNodeIDListIter.h b/client_module/source/common/toolkit/list/NumNodeIDListIter.h new file mode 100644 index 0000000..8a8c971 --- /dev/null +++ b/client_module/source/common/toolkit/list/NumNodeIDListIter.h @@ -0,0 +1,43 @@ +#ifndef NUMNODEIDLISTITER_H_ +#define NUMNODEIDLISTITER_H_ + +#include +#include "NumNodeIDList.h" + +struct NumNodeIDListIter; +typedef struct NumNodeIDListIter NumNodeIDListIter; + +static inline void NumNodeIDListIter_init(NumNodeIDListIter* this, NumNodeIDList* list); +static inline void NumNodeIDListIter_next(NumNodeIDListIter* this); +static inline NumNodeID NumNodeIDListIter_value(NumNodeIDListIter* this); +static inline bool NumNodeIDListIter_end(NumNodeIDListIter* this); + + +struct NumNodeIDListIter +{ + struct PointerListIter pointerListIter; +}; + + +void NumNodeIDListIter_init(NumNodeIDListIter* this, NumNodeIDList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void NumNodeIDListIter_next(NumNodeIDListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +NumNodeID NumNodeIDListIter_value(NumNodeIDListIter* this) +{ + return (NumNodeID){(size_t)PointerListIter_value( (PointerListIter*)this)}; +} + +bool NumNodeIDListIter_end(NumNodeIDListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + +#endif /* NUMNODEIDLISTITER_H_ */ diff --git a/client_module/source/common/toolkit/list/PointerList.h b/client_module/source/common/toolkit/list/PointerList.h new file mode 100644 index 0000000..2ce6420 --- /dev/null +++ b/client_module/source/common/toolkit/list/PointerList.h @@ -0,0 +1,267 @@ +#ifndef POINTERLIST_H_ +#define POINTERLIST_H_ + +#include + +struct PointerListElem; +typedef struct PointerListElem PointerListElem; +struct PointerList; +typedef struct PointerList PointerList; + +static inline void PointerList_init(PointerList* this); +static inline void PointerList_uninit(PointerList* this); + +static inline void PointerList_addHead(PointerList* this, void* valuePointer); +static inline void PointerList_addTail(PointerList* this, void* valuePointer); +static inline void PointerList_append(PointerList* this, void* valuePointer); +static inline void PointerList_removeHead(PointerList* this); +static inline void PointerList_removeTail(PointerList* this); +static inline void PointerList_removeElem(PointerList* this, PointerListElem* elem); +static inline void PointerList_moveToHead(PointerList* this, PointerListElem* elem); +static inline void PointerList_moveToTail(PointerList* this, PointerListElem* elem); +static inline size_t PointerList_length(const PointerList* this); +static inline void PointerList_clear(PointerList* this); + +static inline PointerListElem* PointerList_getTail(PointerList* this); +static inline PointerListElem* PointerList_getHead(PointerList* this); + + +struct PointerListElem +{ + void* valuePointer; + + struct PointerListElem* prev; + struct PointerListElem* next; +}; + +struct PointerList +{ + struct PointerListElem* head; + struct PointerListElem* tail; + + size_t length; +}; + +void PointerList_init(PointerList* this) +{ + this->head = NULL; + this->tail = NULL; + + this->length = 0; +} + +void PointerList_uninit(PointerList* this) +{ + PointerList_clear(this); +} + +static inline void __PointerList_addHead(PointerList* this, PointerListElem* elem) +{ + if(this->length) + { // elements exist => replace head + elem->next = this->head; + this->head->prev = elem; + this->head = elem; + } + else + { // no elements exist yet + this->head = elem; + this->tail = elem; + + elem->next = NULL; + } + + elem->prev = NULL; + this->length++; +} + +void PointerList_addHead(PointerList* this, void* valuePointer) +{ + PointerListElem* elem = (PointerListElem*)os_kmalloc( + sizeof(PointerListElem) ); + elem->valuePointer = valuePointer; + __PointerList_addHead(this, elem); +} + +static inline void __PointerList_addTail(PointerList* this, PointerListElem* elem) +{ + if(this->length) + { // elements exist => replace tail + elem->prev = this->tail; + this->tail->next = elem; + this->tail = elem; + } + else + { // no elements exist yet + this->head = elem; + this->tail = elem; + + elem->prev = NULL; + } + + elem->next = NULL; + this->length++; +} + +void PointerList_addTail(PointerList* this, void* valuePointer) +{ + PointerListElem* elem = (PointerListElem*)os_kmalloc(sizeof(PointerListElem) ); + elem->valuePointer = valuePointer; + __PointerList_addTail(this, elem); +} + +void PointerList_append(PointerList* this, void* valuePointer) +{ + PointerList_addTail(this, valuePointer); +} + +static inline void __PointerList_removeHead(PointerList* this, bool freeElem) +{ + #ifdef BEEGFS_DEBUG + if(!this->length) + { + BEEGFS_BUG_ON(true, "Attempt to remove head from empty list"); + return; + } + #endif + + if(this->length == 1) + { // removing the last element + if (freeElem) + kfree(this->head); + this->head = NULL; + this->tail = NULL; + this->length = 0; + } + else + { // there are more elements in the list + PointerListElem* newHead = this->head->next; + + if (freeElem) + kfree(this->head); + + this->head = newHead; + this->head->prev = NULL; + + this->length--; + } + +} + +void PointerList_removeHead(PointerList* this) +{ + __PointerList_removeHead(this, true); +} + +static inline void __PointerList_removeTail(PointerList* this, bool freeElem) +{ + #ifdef BEEGFS_DEBUG + if(!this->length) + { + BEEGFS_BUG_ON(true, "Attempt to remove tail from empty list"); + return; + } + #endif + + if(this->length == 1) + { // removing the last element + if (freeElem) + kfree(this->tail); + this->head = NULL; + this->tail = NULL; + this->length = 0; + } + else + { // there are more elements in the list + PointerListElem* newTail = this->tail->prev; + + if (freeElem) + kfree(this->tail); + + this->tail = newTail; + this->tail->next = NULL; + + this->length--; + } +} + +void PointerList_removeTail(PointerList* this) +{ + __PointerList_removeTail(this, true); +} + +static inline void __PointerList_removeElem(PointerList* this, PointerListElem* elem, bool freeElem) +{ + if(elem == this->head) + __PointerList_removeHead(this, freeElem); + else + if(elem == this->tail) + __PointerList_removeTail(this, freeElem); + else + { + // not head and not tail, so this elem is somewhere in the middle + + PointerListElem* prev = elem->prev; + PointerListElem* next = elem->next; + + prev->next = next; + next->prev = prev; + + if (freeElem) + kfree(elem); + + this->length--; + } +} + +void PointerList_removeElem(PointerList* this, PointerListElem* elem) +{ + __PointerList_removeElem(this, elem, true); +} + +size_t PointerList_length(const PointerList* this) +{ + return this->length; +} + +void PointerList_clear(PointerList* this) +{ + // free all elems + PointerListElem* elem = this->head; + while(elem) + { + PointerListElem* next = elem->next; + kfree(elem); + elem = next; + } + + // reset attributes + this->head = NULL; + this->tail = NULL; + + this->length = 0; +} + +PointerListElem* PointerList_getTail(PointerList* this) +{ + return this->tail; +} + +PointerListElem* PointerList_getHead(PointerList* this) +{ + return this->head; +} + +void PointerList_moveToHead(PointerList* this, PointerListElem *elem) +{ + __PointerList_removeElem(this, elem, false); + __PointerList_addHead(this, elem); +} + +void PointerList_moveToTail(PointerList* this, PointerListElem *elem) +{ + __PointerList_removeElem(this, elem, false); + __PointerList_addTail(this, elem); +} + +#endif /*POINTERLIST_H_*/ diff --git a/client_module/source/common/toolkit/list/PointerListIter.h b/client_module/source/common/toolkit/list/PointerListIter.h new file mode 100644 index 0000000..5c3fdad --- /dev/null +++ b/client_module/source/common/toolkit/list/PointerListIter.h @@ -0,0 +1,62 @@ +#ifndef POINTERLISTITER_H_ +#define POINTERLISTITER_H_ + +#include "PointerList.h" + +struct PointerListIter; +typedef struct PointerListIter PointerListIter; + +static inline void PointerListIter_init(PointerListIter* this, PointerList* list); +static inline void PointerListIter_next(PointerListIter* this); +static inline void* PointerListIter_value(PointerListIter* this); +static inline bool PointerListIter_end(PointerListIter* this); +static inline PointerListIter PointerListIter_remove(PointerListIter* this); + +struct PointerListIter +{ + PointerList* list; + PointerListElem* elem; +}; + +void PointerListIter_init(PointerListIter* this, PointerList* list) +{ + this->list = list; + this->elem = list->head; +} + +void PointerListIter_next(PointerListIter* this) +{ + // note: must not return the value because the current elem could be the end of the list + + this->elem = this->elem->next; +} + +void* PointerListIter_value(PointerListIter* this) +{ + return this->elem->valuePointer; +} + +bool PointerListIter_end(PointerListIter* this) +{ + return (this->elem == NULL); +} + +/** + * note: the current iterator becomes invalid after the call (use the returned iterator) + * @return the new iterator that points to the element just behind the erased one + */ +PointerListIter PointerListIter_remove(PointerListIter* this) +{ + PointerListIter newIter = *this; + + PointerListElem* elem = this->elem; + + PointerListIter_next(&newIter); + + PointerList_removeElem(this->list, elem); + + return newIter; +} + + +#endif /*POINTERLISTITER_H_*/ diff --git a/client_module/source/common/toolkit/list/StrCpyList.h b/client_module/source/common/toolkit/list/StrCpyList.h new file mode 100644 index 0000000..3b408f2 --- /dev/null +++ b/client_module/source/common/toolkit/list/StrCpyList.h @@ -0,0 +1,77 @@ +#ifndef STRCPYLIST_H_ +#define STRCPYLIST_H_ + +#include "StringList.h" + +struct StrCpyList; +typedef struct StrCpyList StrCpyList; + +static inline void StrCpyList_init(StrCpyList* this); +static inline void StrCpyList_uninit(StrCpyList* this); +static inline void StrCpyList_addHead(StrCpyList* this, const char* valuePointer); +static inline void StrCpyList_append(StrCpyList* this, const char* valuePointer); +static inline size_t StrCpyList_length(StrCpyList* this); +static inline void StrCpyList_clear(StrCpyList* this); + +struct StrCpyList +{ + struct StringList stringList; +}; + + +void StrCpyList_init(StrCpyList* this) +{ + StringList_init( (StringList*)this); +} + +void StrCpyList_uninit(StrCpyList* this) +{ + struct PointerListElem* elem = ( (PointerList*)this)->head; + while(elem) + { + struct PointerListElem* next = elem->next; + kfree(elem->valuePointer); + elem = next; + } + + + StringList_uninit( (StringList*)this); +} + +void StrCpyList_addHead(StrCpyList* this, const char* valuePointer) +{ + size_t valueLen = strlen(valuePointer)+1; + char* valueCopy = (char*)os_kmalloc(valueLen); + memcpy(valueCopy, valuePointer, valueLen); + + StringList_addHead( (StringList*)this, valueCopy); +} + +void StrCpyList_append(StrCpyList* this, const char* valuePointer) +{ + size_t valueLen = strlen(valuePointer)+1; + char* valueCopy = (char*)os_kmalloc(valueLen); + memcpy(valueCopy, valuePointer, valueLen); + + StringList_append( (StringList*)this, valueCopy); +} + +size_t StrCpyList_length(StrCpyList* this) +{ + return StringList_length( (StringList*)this); +} + +void StrCpyList_clear(StrCpyList* this) +{ + struct PointerListElem* elem = ( (PointerList*)this)->head; + while(elem) + { + struct PointerListElem* next = elem->next; + kfree(elem->valuePointer); + elem = next; + } + + StringList_clear( (StringList*)this); +} + +#endif /*STRCPYLIST_H_*/ diff --git a/client_module/source/common/toolkit/list/StrCpyListIter.h b/client_module/source/common/toolkit/list/StrCpyListIter.h new file mode 100644 index 0000000..c0f58f4 --- /dev/null +++ b/client_module/source/common/toolkit/list/StrCpyListIter.h @@ -0,0 +1,43 @@ +#ifndef STRCPYLISTITER_H_ +#define STRCPYLISTITER_H_ + +#include "StrCpyList.h" +#include "StringListIter.h" + +struct StrCpyListIter; +typedef struct StrCpyListIter StrCpyListIter; + +static inline void StrCpyListIter_init(StrCpyListIter* this, StrCpyList* list); +static inline void StrCpyListIter_next(StrCpyListIter* this); +static inline char* StrCpyListIter_value(StrCpyListIter* this); +static inline bool StrCpyListIter_end(StrCpyListIter* this); + + +struct StrCpyListIter +{ + struct StringListIter stringListIter; +}; + + +void StrCpyListIter_init(StrCpyListIter* this, StrCpyList* list) +{ + StringListIter_init( (StringListIter*)this, (StringList*)list); +} + +void StrCpyListIter_next(StrCpyListIter* this) +{ + StringListIter_next( (StringListIter*)this); +} + +char* StrCpyListIter_value(StrCpyListIter* this) +{ + return (char*)StringListIter_value( (StringListIter*)this); +} + +bool StrCpyListIter_end(StrCpyListIter* this) +{ + return StringListIter_end( (StringListIter*)this); +} + + +#endif /*STRCPYLISTITER_H_*/ diff --git a/client_module/source/common/toolkit/list/StringList.h b/client_module/source/common/toolkit/list/StringList.h new file mode 100644 index 0000000..cba53d6 --- /dev/null +++ b/client_module/source/common/toolkit/list/StringList.h @@ -0,0 +1,52 @@ +#ifndef STRINGLIST_H_ +#define STRINGLIST_H_ + +#include "PointerList.h" + +struct StringList; +typedef struct StringList StringList; + +static inline void StringList_init(StringList* this); +static inline void StringList_uninit(StringList* this); +static inline void StringList_addHead(StringList* this, char* valuePointer); +static inline void StringList_append(StringList* this, char* valuePointer); +static inline size_t StringList_length(StringList* this); +static inline void StringList_clear(StringList* this); + +struct StringList +{ + PointerList pointerList; +}; + + +void StringList_init(StringList* this) +{ + PointerList_init( (PointerList*)this); +} + +void StringList_uninit(StringList* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void StringList_addHead(StringList* this, char* valuePointer) +{ + PointerList_addHead( (PointerList*)this, valuePointer); +} + +void StringList_append(StringList* this, char* valuePointer) +{ + PointerList_append( (PointerList*)this, valuePointer); +} + +size_t StringList_length(StringList* this) +{ + return PointerList_length( (PointerList*)this); +} + +void StringList_clear(StringList* this) +{ + PointerList_clear( (PointerList*)this); +} + +#endif /*STRINGLIST_H_*/ diff --git a/client_module/source/common/toolkit/list/StringListIter.h b/client_module/source/common/toolkit/list/StringListIter.h new file mode 100644 index 0000000..5ea1dc7 --- /dev/null +++ b/client_module/source/common/toolkit/list/StringListIter.h @@ -0,0 +1,43 @@ +#ifndef STRINGLISTITER_H_ +#define STRINGLISTITER_H_ + +#include "StringList.h" +#include "PointerListIter.h" + +struct StringListIter; +typedef struct StringListIter StringListIter; + +static inline void StringListIter_init(StringListIter* this, StringList* list); +static inline void StringListIter_next(StringListIter* this); +static inline char* StringListIter_value(StringListIter* this); +static inline bool StringListIter_end(StringListIter* this); + + +struct StringListIter +{ + PointerListIter pointerListIter; +}; + + +void StringListIter_init(StringListIter* this, StringList* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void StringListIter_next(StringListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +char* StringListIter_value(StringListIter* this) +{ + return (char*)PointerListIter_value( (PointerListIter*)this); +} + +bool StringListIter_end(StringListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + +#endif /*STRINGLISTITER_H_*/ diff --git a/client_module/source/common/toolkit/list/UInt16List.h b/client_module/source/common/toolkit/list/UInt16List.h new file mode 100644 index 0000000..09924cb --- /dev/null +++ b/client_module/source/common/toolkit/list/UInt16List.h @@ -0,0 +1,53 @@ +#ifndef UINT16LIST_H_ +#define UINT16LIST_H_ + +#include + +/** + * Derived from PointerList. Internally, we cast uint16_t values directly to pointers here (instead + * of allocating them and assigning the pointer to the allocated mem here). + */ + +struct UInt16List; +typedef struct UInt16List UInt16List; + +static inline void UInt16List_init(UInt16List* this); +static inline void UInt16List_uninit(UInt16List* this); +static inline void UInt16List_append(UInt16List* this, uint16_t value); +static inline size_t UInt16List_length(UInt16List* this); +static inline void UInt16List_clear(UInt16List* this); + +struct UInt16List +{ + struct PointerList pointerList; +}; + + +void UInt16List_init(UInt16List* this) +{ + PointerList_init( (PointerList*)this); +} + +void UInt16List_uninit(UInt16List* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void UInt16List_append(UInt16List* this, uint16_t value) +{ + /* cast value directly to pointer type here to store value directly in the pointer variable + without allocating extra mem */ + PointerList_append( (PointerList*)this, (void*)(size_t)value); +} + +static inline size_t UInt16List_length(UInt16List* this) +{ + return PointerList_length( (PointerList*)this); +} + +void UInt16List_clear(UInt16List* this) +{ + PointerList_clear( (PointerList*)this); +} + +#endif /* UINT16LIST_H_ */ diff --git a/client_module/source/common/toolkit/list/UInt16ListIter.h b/client_module/source/common/toolkit/list/UInt16ListIter.h new file mode 100644 index 0000000..657ecd9 --- /dev/null +++ b/client_module/source/common/toolkit/list/UInt16ListIter.h @@ -0,0 +1,43 @@ +#ifndef UINT16LISTITER_H_ +#define UINT16LISTITER_H_ + +#include +#include "UInt16List.h" + +struct UInt16ListIter; +typedef struct UInt16ListIter UInt16ListIter; + +static inline void UInt16ListIter_init(UInt16ListIter* this, UInt16List* list); +static inline void UInt16ListIter_next(UInt16ListIter* this); +static inline uint16_t UInt16ListIter_value(UInt16ListIter* this); +static inline bool UInt16ListIter_end(UInt16ListIter* this); + + +struct UInt16ListIter +{ + struct PointerListIter pointerListIter; +}; + + +void UInt16ListIter_init(UInt16ListIter* this, UInt16List* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void UInt16ListIter_next(UInt16ListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +uint16_t UInt16ListIter_value(UInt16ListIter* this) +{ + return (uint16_t)(size_t)PointerListIter_value( (PointerListIter*)this); +} + +bool UInt16ListIter_end(UInt16ListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + +#endif /* UINT16LISTITER_H_ */ diff --git a/client_module/source/common/toolkit/list/UInt8List.h b/client_module/source/common/toolkit/list/UInt8List.h new file mode 100644 index 0000000..83f824e --- /dev/null +++ b/client_module/source/common/toolkit/list/UInt8List.h @@ -0,0 +1,53 @@ +#ifndef UINT8LIST_H_ +#define UINT8LIST_H_ + +#include + +/** + * Derived from PointerList. Internally, we cast uint8_t values directly to pointers here (instead + * of allocating them and assigning the pointer to the allocated mem here). + */ + +struct UInt8List; +typedef struct UInt8List UInt8List; + +static inline void UInt8List_init(UInt8List* this); +static inline void UInt8List_uninit(UInt8List* this); +static inline void UInt8List_append(UInt8List* this, uint8_t value); +static inline size_t UInt8List_length(UInt8List* this); +static inline void UInt8List_clear(UInt8List* this); + +struct UInt8List +{ + struct PointerList pointerList; +}; + + +void UInt8List_init(UInt8List* this) +{ + PointerList_init( (PointerList*)this); +} + +void UInt8List_uninit(UInt8List* this) +{ + PointerList_uninit( (PointerList*)this); +} + +void UInt8List_append(UInt8List* this, uint8_t value) +{ + /* cast value directly to pointer type here to store value directly in the pointer variable + without allocating extra mem */ + PointerList_append( (PointerList*)this, (void*)(size_t)value); +} + +static inline size_t UInt8List_length(UInt8List* this) +{ + return PointerList_length( (PointerList*)this); +} + +void UInt8List_clear(UInt8List* this) +{ + PointerList_clear( (PointerList*)this); +} + +#endif /* UINT8LIST_H_ */ diff --git a/client_module/source/common/toolkit/list/UInt8ListIter.h b/client_module/source/common/toolkit/list/UInt8ListIter.h new file mode 100644 index 0000000..2b5a6cc --- /dev/null +++ b/client_module/source/common/toolkit/list/UInt8ListIter.h @@ -0,0 +1,43 @@ +#ifndef UINT8LISTITER_H_ +#define UINT8LISTITER_H_ + +#include +#include "UInt8List.h" + +struct UInt8ListIter; +typedef struct UInt8ListIter UInt8ListIter; + +static inline void UInt8ListIter_init(UInt8ListIter* this, UInt8List* list); +static inline void UInt8ListIter_next(UInt8ListIter* this); +static inline uint8_t UInt8ListIter_value(UInt8ListIter* this); +static inline bool UInt8ListIter_end(UInt8ListIter* this); + + +struct UInt8ListIter +{ + struct PointerListIter pointerListIter; +}; + + +void UInt8ListIter_init(UInt8ListIter* this, UInt8List* list) +{ + PointerListIter_init( (PointerListIter*)this, (PointerList*)list); +} + +void UInt8ListIter_next(UInt8ListIter* this) +{ + PointerListIter_next( (PointerListIter*)this); +} + +uint8_t UInt8ListIter_value(UInt8ListIter* this) +{ + return (uint8_t)(size_t)PointerListIter_value( (PointerListIter*)this); +} + +bool UInt8ListIter_end(UInt8ListIter* this) +{ + return PointerListIter_end( (PointerListIter*)this); +} + + +#endif /* UINT8LISTITER_H_ */ diff --git a/client_module/source/common/toolkit/tree/IntMap.c b/client_module/source/common/toolkit/tree/IntMap.c new file mode 100644 index 0000000..33b4cae --- /dev/null +++ b/client_module/source/common/toolkit/tree/IntMap.c @@ -0,0 +1,24 @@ +#include +#include + +IntMapIter IntMap_find(IntMap* this, const int searchKey) +{ + RBTreeElem* treeElem = _PointerRBTree_findElem( + (RBTree*)this, (const void*)(const size_t)searchKey); + + IntMapIter iter; + IntMapIter_init(&iter, this, treeElem); + + return iter; +} + +IntMapIter IntMap_begin(IntMap* this) +{ + struct rb_node* node = rb_first(&this->rbTree.treeroot); + RBTreeElem* treeElem = node ? container_of(node, RBTreeElem, treenode) : NULL; + + IntMapIter iter; + IntMapIter_init(&iter, this, treeElem); + + return iter; +} diff --git a/client_module/source/common/toolkit/tree/IntMap.h b/client_module/source/common/toolkit/tree/IntMap.h new file mode 100644 index 0000000..d8e2245 --- /dev/null +++ b/client_module/source/common/toolkit/tree/IntMap.h @@ -0,0 +1,88 @@ +#ifndef INTMAP_H_ +#define INTMAP_H_ + +#include "PointerRBTree.h" +#include "PointerRBTreeIter.h" + + +/** + * We assign the integer keys directly to the key pointers (i.e. no extra allocation involved, + * but a lot of casting to convince gcc that we know what we're doing). + * Values are just arbitrary pointers. + */ + + +struct IntMapElem; +typedef struct IntMapElem IntMapElem; +struct IntMap; +typedef struct IntMap IntMap; + +struct IntMapIter; // forward declaration of the iterator + +static inline void IntMap_init(IntMap* this); +static inline void IntMap_uninit(IntMap* this); + +static inline bool IntMap_insert(IntMap* this, int newKey, char* newValue); +static inline bool IntMap_erase(IntMap* this, const int eraseKey); +static inline size_t IntMap_length(IntMap* this); + +static inline void IntMap_clear(IntMap* this); + +extern struct IntMapIter IntMap_find(IntMap* this, const int searchKey); +extern struct IntMapIter IntMap_begin(IntMap* this); + + +struct IntMapElem +{ + RBTreeElem rbTreeElem; +}; + +struct IntMap +{ + RBTree rbTree; +}; + + +void IntMap_init(IntMap* this) +{ + PointerRBTree_init( (RBTree*)this, PointerRBTree_keyCompare); +} + +void IntMap_uninit(IntMap* this) +{ + // (nothing alloc'ed by this sub map type => nothing special here to be free'd) + + PointerRBTree_uninit( (RBTree*)this); +} + + +/** + * @param newValue just assigned, not copied. + * @return true if element was inserted, false if key already existed (in which case + * nothing is changed) + */ +bool IntMap_insert(IntMap* this, int newKey, char* newValue) +{ + return PointerRBTree_insert( (RBTree*)this, (void*)(size_t)newKey, newValue); +} + +/** + * @return false if no element with the given key existed + */ +bool IntMap_erase(IntMap* this, const int eraseKey) +{ + return PointerRBTree_erase( (RBTree*)this, (const void*)(const size_t)eraseKey); +} + +size_t IntMap_length(IntMap* this) +{ + return PointerRBTree_length( (RBTree*)this); +} + + +void IntMap_clear(IntMap* this) +{ + PointerRBTree_clear(&this->rbTree); +} + +#endif /* INTMAP_H_ */ diff --git a/client_module/source/common/toolkit/tree/IntMapIter.h b/client_module/source/common/toolkit/tree/IntMapIter.h new file mode 100644 index 0000000..d59dd6f --- /dev/null +++ b/client_module/source/common/toolkit/tree/IntMapIter.h @@ -0,0 +1,45 @@ +#ifndef INTMAPITER_H_ +#define INTMAPITER_H_ + +#include "IntMap.h" + +struct IntMapIter; +typedef struct IntMapIter IntMapIter; + +static inline void IntMapIter_init(IntMapIter* this, IntMap* map, RBTreeElem* treeElem); +static inline char* IntMapIter_next(IntMapIter* this); +static inline int IntMapIter_key(IntMapIter* this); +static inline char* IntMapIter_value(IntMapIter* this); +static inline bool IntMapIter_end(IntMapIter* this); + +struct IntMapIter +{ + RBTreeIter rbTreeIter; +}; + +void IntMapIter_init(IntMapIter* this, IntMap* map, RBTreeElem* treeElem) +{ + PointerRBTreeIter_init( (RBTreeIter*)this, (RBTree*)map, (RBTreeElem*)treeElem); +} + +char* IntMapIter_next(IntMapIter* this) +{ + return (char*)PointerRBTreeIter_next( (RBTreeIter*)this); +} + +int IntMapIter_key(IntMapIter* this) +{ + return (int)(size_t)PointerRBTreeIter_key( (RBTreeIter*)this); +} + +char* IntMapIter_value(IntMapIter* this) +{ + return (char*)PointerRBTreeIter_value( (RBTreeIter*)this); +} + +bool IntMapIter_end(IntMapIter* this) +{ + return PointerRBTreeIter_end( (RBTreeIter*)this); +} + +#endif /* INTMAPITER_H_ */ diff --git a/client_module/source/common/toolkit/tree/PointerRBTree.c b/client_module/source/common/toolkit/tree/PointerRBTree.c new file mode 100644 index 0000000..072e93d --- /dev/null +++ b/client_module/source/common/toolkit/tree/PointerRBTree.c @@ -0,0 +1,24 @@ +#include +#include + + +RBTreeIter PointerRBTree_find(RBTree* this, const void* searchKey) +{ + RBTreeElem* treeElem = _PointerRBTree_findElem(this, searchKey); + + RBTreeIter iter; + PointerRBTreeIter_init(&iter, this, treeElem); + + return iter; +} + +RBTreeIter PointerRBTree_begin(RBTree* this) +{ + struct rb_node* node = rb_first(&this->treeroot); + RBTreeElem* treeElem = node ? container_of(node, RBTreeElem, treenode) : NULL; + + RBTreeIter iter; + PointerRBTreeIter_init(&iter, this, treeElem); + + return iter; +} diff --git a/client_module/source/common/toolkit/tree/PointerRBTree.h b/client_module/source/common/toolkit/tree/PointerRBTree.h new file mode 100644 index 0000000..6399d23 --- /dev/null +++ b/client_module/source/common/toolkit/tree/PointerRBTree.h @@ -0,0 +1,201 @@ +#ifndef POINTERRBTREE_H_ +#define POINTERRBTREE_H_ + +#include +#include + + +struct RBTreeElem; +typedef struct RBTreeElem RBTreeElem; +struct RBTree; +typedef struct RBTree RBTree; + + +/** + * @return: <0 if key10 otherwise + */ +typedef int (*TreeElemsComparator)(const void* key1, const void* key2); + + +struct RBTreeIter; // forward declaration of the iterator + +static inline void PointerRBTree_init(RBTree* this, TreeElemsComparator compareTreeElems); +static inline void PointerRBTree_uninit(RBTree* this); + +static inline RBTreeElem* _PointerRBTree_findElem(RBTree* this, const void* searchKey); + +static inline bool PointerRBTree_insert(RBTree* this, void* newKey, void* newValue); +static bool PointerRBTree_erase(RBTree* this, const void* eraseKey); +static inline size_t PointerRBTree_length(RBTree* this); + +static inline void PointerRBTree_clear(RBTree* this); + +static inline int PointerRBTree_keyCompare(const void* a, const void* b); + +extern struct RBTreeIter PointerRBTree_find(RBTree* this, const void* searchKey); +extern struct RBTreeIter PointerRBTree_begin(RBTree* this); + +struct RBTreeElem +{ + struct rb_node treenode; + + void* key; + void* value; +}; + + +struct RBTree +{ + struct rb_root treeroot; + + size_t length; + + TreeElemsComparator compareTreeElems; +}; + + +void PointerRBTree_init(RBTree* this, TreeElemsComparator compareTreeElems) +{ + this->treeroot.rb_node = NULL; + this->length = 0; + + this->compareTreeElems = compareTreeElems; +} + +void PointerRBTree_uninit(RBTree* this) +{ + PointerRBTree_clear(this); +} + + +/** + * @return NULL if the element was not found + */ +RBTreeElem* _PointerRBTree_findElem(RBTree* this, const void* searchKey) +{ + int compRes; + + struct rb_node* node = this->treeroot.rb_node; + + while(node) + { + RBTreeElem* treeElem = container_of(node, RBTreeElem, treenode); + + compRes = this->compareTreeElems(searchKey, treeElem->key); + + if(compRes < 0) + node = node->rb_left; + else + if(compRes > 0) + node = node->rb_right; + else + { // element found => return it + return treeElem; + } + } + + // element not found + + return NULL; +} + + +/** + * @return true if element was inserted, false if it already existed (in which case + * nothing is changed) or if out of mem. + */ +bool PointerRBTree_insert(RBTree* this, void* newKey, void* newValue) +{ + RBTreeElem* newElem; + + // the parent's (left or right) link to which the child will be connected + struct rb_node** link = &this->treeroot.rb_node; + // the parent of the new node + struct rb_node* parent = NULL; + + while(*link) + { + int compRes; + RBTreeElem* treeElem; + + parent = *link; + treeElem = container_of(parent, RBTreeElem, treenode); + + compRes = this->compareTreeElems(newKey, treeElem->key); + + if(compRes < 0) + link = &(*link)->rb_left; + else + if(compRes > 0) + link = &(*link)->rb_right; + else + { // target already exists => do nothing (according to the behavior of c++ map::insert) + //rbReplaceNode(parent, newElem, this); + + return false; + } + } + + // create new element + + newElem = os_kmalloc(sizeof(*newElem) ); + newElem->key = newKey; + newElem->value = newValue; + + // link the new element + rb_link_node(&newElem->treenode, parent, link); + rb_insert_color(&newElem->treenode, &this->treeroot); + + this->length++; + + return true; +} + +/** + * @return false if no element with the given key existed + */ +bool PointerRBTree_erase(RBTree* this, const void* eraseKey) +{ + RBTreeElem* treeElem; + + treeElem = _PointerRBTree_findElem(this, eraseKey); + if(!treeElem) + { // element not found + return false; + } + + // unlink the element + rb_erase(&treeElem->treenode, &this->treeroot); + + kfree(treeElem); + + this->length--; + + return true; +} + +size_t PointerRBTree_length(RBTree* this) +{ + return this->length; +} + +void PointerRBTree_clear(RBTree* this) +{ + while(this->length) + { + RBTreeElem* root = container_of(this->treeroot.rb_node, RBTreeElem, treenode); + PointerRBTree_erase(this, root->key); + } +} + + +int PointerRBTree_keyCompare(const void* a, const void* b) +{ + if (a < b) + return -1; + if (a == b) + return 0; + return 1; +} + +#endif /*POINTERRBTREE_H_*/ diff --git a/client_module/source/common/toolkit/tree/PointerRBTreeIter.h b/client_module/source/common/toolkit/tree/PointerRBTreeIter.h new file mode 100644 index 0000000..4c4a795 --- /dev/null +++ b/client_module/source/common/toolkit/tree/PointerRBTreeIter.h @@ -0,0 +1,57 @@ +#ifndef POINTERRBTREEITER_H_ +#define POINTERRBTREEITER_H_ + +#include "PointerRBTree.h" + +struct RBTreeIter; +typedef struct RBTreeIter RBTreeIter; + +static inline void PointerRBTreeIter_init( + RBTreeIter* this, RBTree* tree, RBTreeElem* treeElem); + +static inline void* PointerRBTreeIter_next(RBTreeIter* this); +static inline void* PointerRBTreeIter_key(RBTreeIter* this); +static inline void* PointerRBTreeIter_value(RBTreeIter* this); +static inline bool PointerRBTreeIter_end(RBTreeIter* this); + +struct RBTreeIter +{ + RBTree* tree; + RBTreeElem* treeElem; +}; + + +void PointerRBTreeIter_init(RBTreeIter* this, RBTree* tree, RBTreeElem* treeElem) +{ + this->tree = tree; + this->treeElem = treeElem; +} + +void* PointerRBTreeIter_next(RBTreeIter* this) +{ + struct rb_node* next = rb_next(&this->treeElem->treenode); + + this->treeElem = next ? container_of(next, RBTreeElem, treenode) : NULL; + + return this->treeElem; +} + +void* PointerRBTreeIter_key(RBTreeIter* this) +{ + return this->treeElem->key; +} + +void* PointerRBTreeIter_value(RBTreeIter* this) +{ + return this->treeElem->value; +} + +/** + * Return true if the end of the iterator was reached + */ +bool PointerRBTreeIter_end(RBTreeIter* this) +{ + return (this->treeElem == NULL); +} + +#endif /*POINTERRBTREEITER_H_*/ diff --git a/client_module/source/common/toolkit/tree/StrCpyMap.c b/client_module/source/common/toolkit/tree/StrCpyMap.c new file mode 100644 index 0000000..03a6c37 --- /dev/null +++ b/client_module/source/common/toolkit/tree/StrCpyMap.c @@ -0,0 +1,30 @@ +#include "StrCpyMap.h" +#include "StrCpyMapIter.h" + +StrCpyMapIter StrCpyMap_find(StrCpyMap* this, const char* searchKey) +{ + RBTreeElem* treeElem = _PointerRBTree_findElem( (RBTree*)this, searchKey); + + StrCpyMapIter iter; + StrCpyMapIter_init(&iter, this, treeElem); + + return iter; +} + +StrCpyMapIter StrCpyMap_begin(StrCpyMap* this) +{ + struct rb_node* node = rb_first(&this->rbTree.treeroot); + RBTreeElem* treeElem = node ? container_of(node, RBTreeElem, treenode) : NULL; + + StrCpyMapIter iter; + StrCpyMapIter_init(&iter, this, treeElem); + + return iter; +} + + +int compareStrCpyMapElems(const void* key1, const void* key2) +{ + return strcmp(key1, key2); +} + diff --git a/client_module/source/common/toolkit/tree/StrCpyMap.h b/client_module/source/common/toolkit/tree/StrCpyMap.h new file mode 100644 index 0000000..72c0a2c --- /dev/null +++ b/client_module/source/common/toolkit/tree/StrCpyMap.h @@ -0,0 +1,131 @@ +#ifndef STRCPYMAP_H_ +#define STRCPYMAP_H_ + +#include +#include + +struct StrCpyMapElem; +typedef struct StrCpyMapElem StrCpyMapElem; +struct StrCpyMap; +typedef struct StrCpyMap StrCpyMap; + +struct StrCpyMapIter; // forward declaration of the iterator + +static inline void StrCpyMap_init(StrCpyMap* this); +static inline void StrCpyMap_uninit(StrCpyMap* this); + +static inline bool StrCpyMap_insert(StrCpyMap* this, const char* newKey, + const char* newValue); +static inline bool StrCpyMap_erase(StrCpyMap* this, const char* eraseKey); +static inline size_t StrCpyMap_length(StrCpyMap* this); + +static inline void StrCpyMap_clear(StrCpyMap* this); + +extern struct StrCpyMapIter StrCpyMap_find(StrCpyMap* this, const char* searchKey); +extern struct StrCpyMapIter StrCpyMap_begin(StrCpyMap* this); + +extern int compareStrCpyMapElems(const void* key1, const void* key2); + + +struct StrCpyMapElem +{ + RBTreeElem rbTreeElem; +}; + +struct StrCpyMap +{ + RBTree rbTree; +}; + + +void StrCpyMap_init(StrCpyMap* this) +{ + PointerRBTree_init( (RBTree*)this, compareStrCpyMapElems); +} + +void StrCpyMap_uninit(StrCpyMap* this) +{ + StrCpyMap_clear(this); + + PointerRBTree_uninit( (RBTree*)this); +} + + + +/** + * @return true if element was inserted, false if key already existed (in which case + * nothing is changed) + */ +bool StrCpyMap_insert(StrCpyMap* this, const char* newKey, const char* newValue) +{ + size_t keyLen; + char* keyCopy; + size_t valueLen; + char* valueCopy; + bool insRes; + + // copy key + keyLen = strlen(newKey)+1; + keyCopy = (char*)os_kmalloc(keyLen); + memcpy(keyCopy, newKey, keyLen); + + // copy value + valueLen = strlen(newValue)+1; + valueCopy = (char*)os_kmalloc(valueLen); + memcpy(valueCopy, newValue, valueLen); + + insRes = PointerRBTree_insert( (RBTree*)this, keyCopy, valueCopy); + if(!insRes) + { + // not inserted because the key already existed => free up the copies + kfree(keyCopy); + kfree(valueCopy); + } + + return insRes; +} + +/** + * @return false if no element with the given key existed + */ +bool StrCpyMap_erase(StrCpyMap* this, const char* eraseKey) +{ + bool eraseRes; + void* elemKey; + void* elemValue; + + RBTreeElem* treeElem = _PointerRBTree_findElem( (RBTree*)this, eraseKey); + if(!treeElem) + { // element not found + return false; + } + + elemKey = treeElem->key; + elemValue = treeElem->value; + + eraseRes = PointerRBTree_erase( (RBTree*)this, eraseKey); + if(eraseRes) + { // treeElem has been erased + kfree(elemKey); + kfree(elemValue); + } + + return eraseRes; +} + +size_t StrCpyMap_length(StrCpyMap* this) +{ + return PointerRBTree_length( (RBTree*)this); +} + + +void StrCpyMap_clear(StrCpyMap* this) +{ + while(this->rbTree.length) + { + RBTreeElem* root = container_of(this->rbTree.treeroot.rb_node, RBTreeElem, treenode); + StrCpyMap_erase(this, root->key); + } +} + +#endif /*STRCPYMAP_H_*/ diff --git a/client_module/source/common/toolkit/tree/StrCpyMapIter.h b/client_module/source/common/toolkit/tree/StrCpyMapIter.h new file mode 100644 index 0000000..293e109 --- /dev/null +++ b/client_module/source/common/toolkit/tree/StrCpyMapIter.h @@ -0,0 +1,45 @@ +#ifndef STRCPYMAPITER_H_ +#define STRCPYMAPITER_H_ + +#include "StrCpyMap.h" + +struct StrCpyMapIter; +typedef struct StrCpyMapIter StrCpyMapIter; + +static inline void StrCpyMapIter_init(StrCpyMapIter* this, StrCpyMap* map, RBTreeElem* treeElem); +static inline char* StrCpyMapIter_next(StrCpyMapIter* this); +static inline char* StrCpyMapIter_key(StrCpyMapIter* this); +static inline char* StrCpyMapIter_value(StrCpyMapIter* this); +static inline bool StrCpyMapIter_end(StrCpyMapIter* this); + +struct StrCpyMapIter +{ + RBTreeIter rbTreeIter; +}; + +void StrCpyMapIter_init(StrCpyMapIter* this, StrCpyMap* map, RBTreeElem* treeElem) +{ + PointerRBTreeIter_init( (RBTreeIter*)this, (RBTree*)map, (RBTreeElem*)treeElem); +} + +char* StrCpyMapIter_next(StrCpyMapIter* this) +{ + return (char*)PointerRBTreeIter_next( (RBTreeIter*)this); +} + +char* StrCpyMapIter_key(StrCpyMapIter* this) +{ + return (char*)PointerRBTreeIter_key( (RBTreeIter*)this); +} + +char* StrCpyMapIter_value(StrCpyMapIter* this) +{ + return (char*)PointerRBTreeIter_value( (RBTreeIter*)this); +} + +bool StrCpyMapIter_end(StrCpyMapIter* this) +{ + return PointerRBTreeIter_end( (RBTreeIter*)this); +} + +#endif /*STRCPYMAPITER_H_*/ diff --git a/client_module/source/common/toolkit/vector/Int64CpyVec.h b/client_module/source/common/toolkit/vector/Int64CpyVec.h new file mode 100644 index 0000000..f5cf187 --- /dev/null +++ b/client_module/source/common/toolkit/vector/Int64CpyVec.h @@ -0,0 +1,89 @@ +#ifndef INT64CPYVEC_H_ +#define INT64CPYVEC_H_ + +#include + +/** + * Note: Derived from the corresponding list. Use the list iterator for read-only access + */ + +struct Int64CpyVec; +typedef struct Int64CpyVec Int64CpyVec; + +static inline void Int64CpyVec_init(Int64CpyVec* this); +static inline void Int64CpyVec_uninit(Int64CpyVec* this); +static inline void Int64CpyVec_append(Int64CpyVec* this, int64_t value); +static inline size_t Int64CpyVec_length(Int64CpyVec* this); +static inline void Int64CpyVec_clear(Int64CpyVec* this); + +// getters & setters +static inline int64_t Int64CpyVec_at(Int64CpyVec* this, size_t index); + + +struct Int64CpyVec +{ + Int64CpyList Int64CpyList; + + int64_t** vecArray; + size_t vecArrayLen; +}; + + +void Int64CpyVec_init(Int64CpyVec* this) +{ + Int64CpyList_init( (Int64CpyList*)this); + + this->vecArrayLen = 4; + this->vecArray = (int64_t**)os_kmalloc( + this->vecArrayLen * sizeof(int64_t*) ); +} + +void Int64CpyVec_uninit(Int64CpyVec* this) +{ + kfree(this->vecArray); + + Int64CpyList_uninit( (Int64CpyList*)this); +} + +void Int64CpyVec_append(Int64CpyVec* this, int64_t value) +{ + PointerListElem* lastElem; + int64_t* lastElemValuePointer; + + Int64CpyList_append( (Int64CpyList*)this, value); + + // check if we have enough buffer space for new elem + if(Int64CpyList_length( (Int64CpyList*)this) > this->vecArrayLen) + { // double vector array size (create new, copy, exchange, delete old) + int64_t** newVecArray = + (int64_t**)os_kmalloc(this->vecArrayLen*sizeof(int64_t*)*2); + memcpy(newVecArray, this->vecArray, this->vecArrayLen*sizeof(int64_t*) ); + kfree(this->vecArray); + this->vecArrayLen = this->vecArrayLen * 2; + this->vecArray = newVecArray; + } + + // get last elem and add the valuePointer to the array + lastElem = PointerList_getTail( (PointerList*)this); + lastElemValuePointer = (int64_t*)lastElem->valuePointer; + (this->vecArray)[Int64CpyList_length( (Int64CpyList*)this)-1] = lastElemValuePointer; +} + +size_t Int64CpyVec_length(Int64CpyVec* this) +{ + return Int64CpyList_length( (Int64CpyList*)this); +} + +int64_t Int64CpyVec_at(Int64CpyVec* this, size_t index) +{ + BEEGFS_BUG_ON_DEBUG(index >= Int64CpyVec_length(this), "Index out of bounds"); + + return *(this->vecArray[index]); +} + +void Int64CpyVec_clear(Int64CpyVec* this) +{ + Int64CpyList_clear( (Int64CpyList*)this); +} + +#endif /*INT64CPYVEC_H_*/ diff --git a/client_module/source/common/toolkit/vector/StrCpyVec.h b/client_module/source/common/toolkit/vector/StrCpyVec.h new file mode 100644 index 0000000..1c15690 --- /dev/null +++ b/client_module/source/common/toolkit/vector/StrCpyVec.h @@ -0,0 +1,89 @@ +#ifndef STRCPYVEC_H_ +#define STRCPYVEC_H_ + +#include + +/** + * Note: Derived from the corresponding list. Use the list iterator for read-only access + */ + +struct StrCpyVec; +typedef struct StrCpyVec StrCpyVec; + +static inline void StrCpyVec_init(StrCpyVec* this); +static inline void StrCpyVec_uninit(StrCpyVec* this); +static inline void StrCpyVec_append(StrCpyVec* this, const char* valuePointer); +static inline size_t StrCpyVec_length(StrCpyVec* this); +static inline void StrCpyVec_clear(StrCpyVec* this); + + +// getters & setters +static inline char* StrCpyVec_at(StrCpyVec* this, size_t index); + + +struct StrCpyVec +{ + StrCpyList strCpyList; + + char** vecArray; + size_t vecArrayLen; +}; + + +void StrCpyVec_init(StrCpyVec* this) +{ + StrCpyList_init( (StrCpyList*)this); + + this->vecArrayLen = 4; + this->vecArray = (char**)os_kmalloc( + this->vecArrayLen * sizeof(char*) ); +} + +void StrCpyVec_uninit(StrCpyVec* this) +{ + kfree(this->vecArray); + + StrCpyList_uninit( (StrCpyList*)this); +} + +void StrCpyVec_append(StrCpyVec* this, const char* valuePointer) +{ + PointerListElem* lastElem; + char* lastElemValuePointer; + + StrCpyList_append( (StrCpyList*)this, valuePointer); + + // check if we have enough buffer space for new elem + if(StrCpyList_length( (StrCpyList*)this) > this->vecArrayLen) + { // double vector array size (create new, copy, exchange, delete old) + char** newVecArray = (char**)os_kmalloc(this->vecArrayLen*sizeof(char*)*2); + memcpy(newVecArray, this->vecArray, this->vecArrayLen*sizeof(char*) ); + kfree(this->vecArray); + this->vecArrayLen = this->vecArrayLen * 2; + this->vecArray = newVecArray; + } + + // get last elem and add the valuePointer to the array + lastElem = PointerList_getTail( (PointerList*)this); + lastElemValuePointer = (char*)lastElem->valuePointer; + (this->vecArray)[StrCpyList_length( (StrCpyList*)this)-1] = lastElemValuePointer; +} + +size_t StrCpyVec_length(StrCpyVec* this) +{ + return StrCpyList_length( (StrCpyList*)this); +} + +char* StrCpyVec_at(StrCpyVec* this, size_t index) +{ + BEEGFS_BUG_ON_DEBUG(index >= StrCpyVec_length(this), "Index out of bounds"); + + return (this->vecArray)[index]; +} + +void StrCpyVec_clear(StrCpyVec* this) +{ + StrCpyList_clear( (StrCpyList*)this); +} + +#endif /*STRCPYVEC_H_*/ diff --git a/client_module/source/common/toolkit/vector/UInt16Vec.h b/client_module/source/common/toolkit/vector/UInt16Vec.h new file mode 100644 index 0000000..cd204ed --- /dev/null +++ b/client_module/source/common/toolkit/vector/UInt16Vec.h @@ -0,0 +1,90 @@ +#ifndef UINT16VEC_H_ +#define UINT16VEC_H_ + +#include + + +struct UInt16Vec; +typedef struct UInt16Vec UInt16Vec; + +static inline void UInt16Vec_init(UInt16Vec* this); +static inline void UInt16Vec_uninit(UInt16Vec* this); +static inline void UInt16Vec_append(UInt16Vec* this, uint16_t value); +static inline size_t UInt16Vec_length(UInt16Vec* this); +static inline void UInt16Vec_clear(UInt16Vec* this); + +// getters & setters +static inline uint16_t UInt16Vec_at(UInt16Vec* this, size_t index); + + +/** + * Note: Derived from the corresponding list. Use the list iterator for read-only access + */ +struct UInt16Vec +{ + UInt16List UInt16List; + + uint16_t* vecArray; + size_t vecArrayLen; +}; + + +void UInt16Vec_init(UInt16Vec* this) +{ + UInt16List_init( (UInt16List*)this); + + this->vecArrayLen = 4; + this->vecArray = (uint16_t*)os_kmalloc(this->vecArrayLen * sizeof(uint16_t) ); +} + +void UInt16Vec_uninit(UInt16Vec* this) +{ + kfree(this->vecArray); + + UInt16List_uninit( (UInt16List*)this); +} + +void UInt16Vec_append(UInt16Vec* this, uint16_t value) +{ + size_t newListLen; + + UInt16List_append( (UInt16List*)this, value); + + newListLen = UInt16List_length( (UInt16List*)this); + + // check if we have enough buffer space for new elem + + if(newListLen > this->vecArrayLen) + { // double vector array size: alloc new, copy values, delete old, switch to new + uint16_t* newVecArray = (uint16_t*)os_kmalloc(this->vecArrayLen * sizeof(uint16_t) * 2); + memcpy(newVecArray, this->vecArray, this->vecArrayLen * sizeof(uint16_t) ); + + kfree(this->vecArray); + + this->vecArrayLen = this->vecArrayLen * 2; + this->vecArray = newVecArray; + } + + // add value to last array elem (determine last used index based on list length) + + (this->vecArray)[newListLen-1] = value; +} + +size_t UInt16Vec_length(UInt16Vec* this) +{ + return UInt16List_length( (UInt16List*)this); +} + +uint16_t UInt16Vec_at(UInt16Vec* this, size_t index) +{ + BEEGFS_BUG_ON_DEBUG(index >= UInt16Vec_length(this), "Index out of bounds"); + + return this->vecArray[index]; +} + +void UInt16Vec_clear(UInt16Vec* this) +{ + UInt16List_clear( (UInt16List*)this); +} + +#endif /* UINT16VEC_H_ */ diff --git a/client_module/source/common/toolkit/vector/UInt8Vec.h b/client_module/source/common/toolkit/vector/UInt8Vec.h new file mode 100644 index 0000000..d0a1201 --- /dev/null +++ b/client_module/source/common/toolkit/vector/UInt8Vec.h @@ -0,0 +1,92 @@ +#ifndef UINT8VEC_H_ +#define UINT8VEC_H_ + +#include + + +struct UInt8Vec; +typedef struct UInt8Vec UInt8Vec; + +static inline void UInt8Vec_init(UInt8Vec* this); +static inline void UInt8Vec_uninit(UInt8Vec* this); +static inline void UInt8Vec_append(UInt8Vec* this, uint8_t value); +static inline size_t UInt8Vec_length(UInt8Vec* this); +static inline void UInt8Vec_clear(UInt8Vec* this); + +// getters & setters +static inline uint8_t UInt8Vec_at(UInt8Vec* this, size_t index); + + +/** + * Note: Derived from the corresponding list. Use the list iterator for read-only access + */ +struct UInt8Vec +{ + UInt8List UInt8List; + + uint8_t* vecArray; + size_t vecArrayLen; +}; + + +void UInt8Vec_init(UInt8Vec* this) +{ + UInt8List_init( (UInt8List*)this); + + this->vecArrayLen = 4; + this->vecArray = (uint8_t*)os_kmalloc(this->vecArrayLen * sizeof(uint8_t) ); +} + +void UInt8Vec_uninit(UInt8Vec* this) +{ + kfree(this->vecArray); + + UInt8List_uninit( (UInt8List*)this); +} + +void UInt8Vec_append(UInt8Vec* this, uint8_t value) +{ + size_t newListLen; + + UInt8List_append( (UInt8List*)this, value); + + newListLen = UInt8List_length( (UInt8List*)this); + + // check if we have enough buffer space for new elem + + if(newListLen > this->vecArrayLen) + { // double vector array size: alloc new, copy values, delete old, switch to new + uint8_t* newVecArray = (uint8_t*)os_kmalloc(this->vecArrayLen * sizeof(uint8_t) * 2); + memcpy(newVecArray, this->vecArray, this->vecArrayLen * sizeof(uint8_t) ); + + kfree(this->vecArray); + + this->vecArrayLen = this->vecArrayLen * 2; + this->vecArray = newVecArray; + } + + // add value to last array elem (determine last used index based on list length) + + (this->vecArray)[newListLen-1] = value; +} + +size_t UInt8Vec_length(UInt8Vec* this) +{ + return UInt8List_length( (UInt8List*)this); +} + +uint8_t UInt8Vec_at(UInt8Vec* this, size_t index) +{ + BEEGFS_BUG_ON_DEBUG(index >= UInt8Vec_length(this), "Index out of bounds"); + + return this->vecArray[index]; +} + +void UInt8Vec_clear(UInt8Vec* this) +{ + UInt8List_clear( (UInt8List*)this); +} + + + +#endif /* UINT8VEC_H_ */ diff --git a/client_module/source/components/AckManager.c b/client_module/source/components/AckManager.c new file mode 100644 index 0000000..f222bf1 --- /dev/null +++ b/client_module/source/components/AckManager.c @@ -0,0 +1,307 @@ +#include +#include +#include +#include "AckManager.h" + + +#define ACKMANAGER_MSGBUF_LEN 4096 + + +void AckManager_init(AckManager* this, App* app) +{ + Thread_init(&this->thread, BEEGFS_THREAD_NAME_PREFIX_STR "AckMgr", __AckManager_run); + + this->app = app; + this->cfg = App_getConfig(app); + + this->ackMsgBuf = vmalloc(ACKMANAGER_MSGBUF_LEN); + + Mutex_init(&this->ackQueueMutex); + Condition_init(&this->ackQueueAddedCond); + PointerList_init(&this->ackQueue); +} + +struct AckManager* AckManager_construct(App* app) +{ + struct AckManager* this = (AckManager*)os_kmalloc(sizeof(*this) ); + + AckManager_init(this, app); + + return this; +} + +void AckManager_uninit(AckManager* this) +{ + PointerListIter iter; + + // free ackQ elements + + PointerListIter_init(&iter, &this->ackQueue); + + while(!PointerListIter_end(&iter) ) + { + __AckManager_freeQueueEntry(this, (AckQueueEntry*)PointerListIter_value(&iter) ); + PointerListIter_next(&iter); + } + + PointerList_uninit(&this->ackQueue); + Mutex_uninit(&this->ackQueueMutex); + + vfree(this->ackMsgBuf); + + Thread_uninit( (Thread*)this); +} + +void AckManager_destruct(AckManager* this) +{ + AckManager_uninit(this); + + kfree(this); +} + +void _AckManager_requestLoop(AckManager* this) +{ + int sleepTimeMS = 2500; + + Thread* thisThread = (Thread*)this; + + while(!Thread_getSelfTerminate(thisThread) ) + { + // wait for new queue entries + + Mutex_lock(&this->ackQueueMutex); // L O C K + + if(!PointerList_length(&this->ackQueue) ) + { + Condition_timedwaitInterruptible( + &this->ackQueueAddedCond, &this->ackQueueMutex, sleepTimeMS); + } + + Mutex_unlock(&this->ackQueueMutex); // U N L O C K + + + // process new queue entries (if any) + + __AckManager_processAckQueue(this); + } + +} + + +void __AckManager_run(Thread* this) +{ + AckManager* thisCast = (AckManager*)this; + + const char* logContext = "AckManager (run)"; + Logger* log = App_getLogger(thisCast->app); + + + _AckManager_requestLoop(thisCast); + + Logger_log(log, 4, logContext, "Component stopped."); +} + +/** + * Send ack for all enqueued entries. + */ +void __AckManager_processAckQueue(AckManager* this) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "Queue processing"; + + const int maxNumCommRetries = 1; /* note: we really don't want to wait too long in this method + because time would run out for other acks then, so only a single retry allowed */ + + PointerListIter iter; + + Mutex_lock(&this->ackQueueMutex); // L O C K + + PointerListIter_init(&iter, &this->ackQueue); + + while(!PointerListIter_end(&iter) && !Thread_getSelfTerminate( (Thread*)this) ) + { + AckQueueEntry* currentAck = PointerListIter_value(&iter); + + NodeStoreEx* metaNodes = App_getMetaNodes(this->app); + Node* node; + AckMsgEx msg; + unsigned msgLen; + bool serialRes; + NodeConnPool* connPool; + int currentRetryNum; + Socket* sock; + ssize_t sendRes = 0; + bool removeAllNextByNode = false; + + node = NodeStoreEx_referenceNode(metaNodes, currentAck->metaNodeID); + if(unlikely(!node) ) + { // node not found in store + Logger_logFormatted(log, Log_DEBUG, logContext, "Metadata node no longer exists: %hu", + currentAck->metaNodeID.value); + + goto remove; + } + + connPool = Node_getConnPool(node); + + AckMsgEx_initFromValue(&msg, currentAck->ackID); + msgLen = NetMessage_getMsgLength( (NetMessage*)&msg); + + serialRes = NetMessage_serialize( (NetMessage*)&msg, this->ackMsgBuf, ACKMANAGER_MSGBUF_LEN); + if(unlikely(!serialRes) ) + { // serialization failed + Logger_logFormatted(log, Log_CRITICAL, logContext, + "BUG(?): Unable to serialize ack msg for metadata node: %hu (ack: %s)", + currentAck->metaNodeID.value, currentAck->ackID); + + goto release; + } + + for(currentRetryNum=0; currentRetryNum <= maxNumCommRetries; currentRetryNum++) + { + if(currentRetryNum == 1) + { // inform user about retry (only on first retry to not spam the log) + Logger_logFormatted(log, Log_NOTICE, logContext, + "Retrying communication with metadata node: %hu", currentAck->metaNodeID.value); + } + + // unlock, so that more entries can be added to the queue during remoting without waiting + + Mutex_unlock(&this->ackQueueMutex); // U N L O C K + + // connect & communicate + + sock = NodeConnPool_acquireStreamSocket(connPool); + + if(likely(sock) ) + { // send msg + sendRes = Socket_send_kernel(sock, this->ackMsgBuf, msgLen, 0); + + if(unlikely(sendRes != (ssize_t) msgLen) ) + { // comm error => invalidate conn + NodeConnPool_invalidateStreamSocket(connPool, sock); + } + else + NodeConnPool_releaseStreamSocket(connPool, sock); + } + + Mutex_lock(&this->ackQueueMutex); // R E L O C K + + // check comm errors + + if(unlikely(!sock || (sendRes != (ssize_t) msgLen) ) ) + { // no connection or communication error + Logger_logFormatted(log, Log_NOTICE, logContext, + "Communication with metadata node failed: %hu", currentAck->metaNodeID.value); + + removeAllNextByNode = true; // (only effective if no more retries) + + continue; + } + + removeAllNextByNode = false; + + break; // communication succeeded => we're done with this retry-loop + + } // end of comm retry for-loop + + + if(removeAllNextByNode) + { // comm with current node failed => remove all following entries with this nodeID + + // note: only following entries, because current entry will be free'd below anyways. + + PointerListIter iterNext = iter; + PointerListIter_next(&iterNext); + + __AckManager_removeQueueEntriesByNode(this, currentAck->metaNodeID, iterNext); + } + + release: + Node_put(node); + + remove: + __AckManager_freeQueueEntry(this, currentAck); + iter = PointerListIter_remove(&iter); + } // end of while(!list_end) loop + + + Mutex_unlock(&this->ackQueueMutex); // U N L O C K +} + +/** + * Frees/uninits all sub-fields and kfrees the closeEntry itself (but does not remove it from the + * queue). + */ +void __AckManager_freeQueueEntry(AckManager* this, AckQueueEntry* ackEntry) +{ + kfree(ackEntry->ackID); + + kfree(ackEntry); +} + +/** + * Free all entries in the queue for the given nodeID. + * (Typcally used when communication with a certain node failed.) + * + * @param iter starting point for removal + */ +void __AckManager_removeQueueEntriesByNode(AckManager* this, NumNodeID nodeID, + PointerListIter iter) +{ + while(!PointerListIter_end(&iter) ) + { + AckQueueEntry* currentAck = PointerListIter_value(&iter); + + if(NumNodeID_compare(&nodeID, ¤tAck->metaNodeID)) + { // nodeID matches + __AckManager_freeQueueEntry(this, currentAck); + + iter = PointerListIter_remove(&iter); + } + else + PointerListIter_next(&iter); + } + +} + +/** + * Add an ack that should reliably (i.e. not via UDP) be transmitted to the given meta server. + * + * @param metaNodeID will be copied + * @param ackID will be copied + */ +void AckManager_addAckToQueue(AckManager* this, NumNodeID metaNodeID, const char* ackID) +{ + AckQueueEntry* newEntry = (AckQueueEntry*)os_kmalloc(sizeof(*newEntry) ); + + Time_init(&newEntry->ageT); + + newEntry->metaNodeID = metaNodeID; + newEntry->ackID = StringTk_strDup(ackID); + + // add new entry to queue and wake up AckManager thread... + + Mutex_lock(&this->ackQueueMutex); // L O C K + + PointerList_append(&this->ackQueue, newEntry); + + Condition_signal(&this->ackQueueAddedCond); + + Mutex_unlock(&this->ackQueueMutex); // U N L O C K +} + + +size_t AckManager_getAckQueueSize(AckManager* this) +{ + size_t retVal; + + Mutex_lock(&this->ackQueueMutex); // L O C K + + retVal = PointerList_length(&this->ackQueue); + + Mutex_unlock(&this->ackQueueMutex); // U N L O C K + + return retVal; +} + diff --git a/client_module/source/components/AckManager.h b/client_module/source/components/AckManager.h new file mode 100644 index 0000000..b2121a5 --- /dev/null +++ b/client_module/source/components/AckManager.h @@ -0,0 +1,71 @@ +#ifndef ACKMANAGER_H_ +#define ACKMANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * note: AckEntry-queues management is integrated into AckManager, because it needs + * direct access to the queues via iterators and relies on special access patterns, e.g. + * it must be the only one removing entries from the queue (to keep iterators valid). + */ + +// forward declarations... + +struct AckQueueEntry; +typedef struct AckQueueEntry AckQueueEntry; + +struct AckManager; +typedef struct AckManager AckManager; + + +extern void AckManager_init(AckManager* this, App* app); +extern AckManager* AckManager_construct(App* app); +extern void AckManager_uninit(AckManager* this); +extern void AckManager_destruct(AckManager* this); + +extern void _AckManager_requestLoop(AckManager* this); + +extern void __AckManager_run(Thread* this); +extern void __AckManager_processAckQueue(AckManager* this); +extern void __AckManager_freeQueueEntry(AckManager* this, AckQueueEntry* ackEntry); +extern void __AckManager_removeQueueEntriesByNode(AckManager* this, NumNodeID nodeID, + PointerListIter iter); +extern void AckManager_addAckToQueue(AckManager* this, NumNodeID metaNodeID, const char* ackID); + +// getters & setters +extern size_t AckManager_getAckQueueSize(AckManager* this); + + +struct AckQueueEntry +{ + Time ageT; // time when this entry was created (to compute entry age) + + NumNodeID metaNodeID; + const char* ackID; +}; + + +struct AckManager +{ + Thread thread; // base class + + App* app; + Config* cfg; + + char* ackMsgBuf; // static buffer for message serialization + + Mutex ackQueueMutex; // for ackQueue + Condition ackQueueAddedCond; // when entries are added to queue + PointerList ackQueue; /* remove from head, add to tail (important, because we rely on + validity of an iterator even when a new entry was added) */ +}; + +#endif /* ACKMANAGER_H_ */ diff --git a/client_module/source/components/DatagramListener.c b/client_module/source/components/DatagramListener.c new file mode 100644 index 0000000..1b65be1 --- /dev/null +++ b/client_module/source/components/DatagramListener.c @@ -0,0 +1,287 @@ +#include "DatagramListener.h" +#include +#include +#include +#include +#include +#include + + +void __DatagramListener_run(Thread* this) +{ + DatagramListener* thisCast = (DatagramListener*)this; + + Logger* log = App_getLogger(thisCast->app); + const char* logContext = "DatagramListener (run)"; + + __DatagramListener_initBuffers(thisCast); + + __DatagramListener_listenLoop(thisCast); + + Logger_log(log, 4, logContext, "Component stopped."); +} + +void __DatagramListener_listenLoop(DatagramListener* this) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "DatagramListener (listen loop)"; + + Thread* thisThread = (Thread*)this; + + fhgfs_sockaddr_in fromAddr; + const int recvTimeoutMS = 2000; + + while(!Thread_getSelfTerminate(thisThread) ) + { + NetMessage* msg; + ssize_t recvRes; + + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(this->recvBuf, DGRAMMGR_RECVBUF_SIZE, READ); + recvRes = StandardSocket_recvfromT(this->udpSock, iter, 0, &fromAddr, recvTimeoutMS); + + if(recvRes == -ETIMEDOUT) + { // timeout: nothing to worry about, just idle + continue; + } + else + if(recvRes == 0) + { + char* fromIP = SocketTk_ipaddrToStr(fromAddr.addr); + Logger_logFormatted(log, Log_NOTICE, logContext, + "Received an empty datagram. IP: %s; port: %d", + fromIP, fromAddr.port); + kfree(fromIP); + + continue; + } + else + if(unlikely(recvRes < 0) ) + { // error + + Logger_logErrFormatted(log, logContext, + "Encountered an unrecoverable socket error. ErrCode: %ld", recvRes); + + break; + } + + if(__DatagramListener_isDGramFromLocalhost(this, &fromAddr) ) + { + //log.log(5, "Discarding DGram from localhost"); + continue; + } + + msg = NetMessageFactory_createFromBuf(this->app, this->recvBuf, recvRes); + + if (msg->msgHeader.msgType == NETMSGTYPE_Invalid + || msg->msgHeader.msgLength != recvRes + || msg->msgHeader.msgSequence != 0 + || msg->msgHeader.msgSequenceDone != 0) + { + char* ipStr = SocketTk_ipaddrToStr(fromAddr.addr); + + Logger_logFormatted(this->app->logger, Log_NOTICE, logContext, + "Received invalid message from peer %s", ipStr); + kfree(ipStr); + } + else + { + _DatagramListener_handleIncomingMsg(this, &fromAddr, msg); + } + + NETMESSAGE_FREE(msg); + + } // end of while loop +} + +void _DatagramListener_handleIncomingMsg(DatagramListener* this, + fhgfs_sockaddr_in* fromAddr, NetMessage* msg) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "DatagramListener (incoming msg)"; + + switch(NetMessage_getMsgType(msg) ) + { + // An ack has historically been considered a valid message in this context, but the client + // doesn't actually do anything with them. Ack messages are handled as a SimpleStringMsg which + // uses the default NetMessage_processIncoming() handler which always returns false causing a + // confusing "problem encountered" error to be logged if `processIncoming()` is called below. + // Probably historically this wasn't an issue because clients didn't usually see acks, but at + // least with the 8.0 mgmtd this can happen when the client and mgmtd are on the same node. + case NETMSGTYPE_Ack: + { + Logger_log(log, 4, logContext, "Ignoring incoming ack message"); + } break; + // valid messages within this context + case NETMSGTYPE_HeartbeatRequest: + case NETMSGTYPE_Heartbeat: + case NETMSGTYPE_MapTargets: + case NETMSGTYPE_RemoveNode: + case NETMSGTYPE_LockGranted: + case NETMSGTYPE_RefreshTargetStates: + case NETMSGTYPE_SetMirrorBuddyGroup: + { + if(!msg->ops->processIncoming(msg, this->app, fromAddr, (Socket*)this->udpSock, + this->sendBuf, DGRAMMGR_SENDBUF_SIZE) ) + { + Logger_logFormatted(log, 2, logContext, + "Problem encountered during handling of incoming message of type %d", + NetMessage_getMsgType(msg)); + } + } break; + + default: + { // valid fhgfs message, but not allowed within this context + char* ipStr = SocketTk_ipaddrToStr(fromAddr->addr); + Logger_logErrFormatted(log, logContext, "Received a message of type %d " + "that is invalid within the current context from: %s", + NetMessage_getMsgType(msg), ipStr); + kfree(ipStr); + } break; + }; +} + +bool __DatagramListener_initSock(DatagramListener* this, unsigned short udpPort) +{ + Config* cfg = App_getConfig(this->app); + Logger* log = App_getLogger(this->app); + const char* logContext = "DatagramListener (init sock)"; + + bool broadcastRes; + bool bindRes; + Socket* udpSockBase; + int bufsize; + + this->udpPortNetByteOrder = htons(udpPort); + + this->udpSock = StandardSocket_constructUDP(); + + if(!this->udpSock) + { + Logger_logErr(log, logContext, "Initialization of UDP socket failed"); + return false; + } + + udpSockBase = &this->udpSock->pooledSocket.socket; + + // set some socket options + + broadcastRes = StandardSocket_setSoBroadcast(this->udpSock, true); + if(!broadcastRes) + { + Logger_logErr(log, logContext, "Enabling broadcast for UDP socket failed."); + goto err_valid; + } + + bufsize = Config_getConnUDPRcvBufSize(cfg); + if (bufsize > 0) + StandardSocket_setSoRcvBuf(this->udpSock, bufsize); + + // bind the socket + + bindRes = Socket_bind(udpSockBase, udpPort); + if(!bindRes) + { + Logger_logErrFormatted(log, logContext, "Binding UDP socket to port %d failed.", udpPort); + goto err_valid; + } + + Logger_logFormatted(log, 3, logContext, "Listening for UDP datagrams: Port %d", udpPort); + + return true; + +err_valid: + Socket_virtualDestruct(udpSockBase); + return false; +} + +/** + * Note: Delayed init of buffers (better for NUMA). + */ +void __DatagramListener_initBuffers(DatagramListener* this) +{ + this->recvBuf = (char*)vmalloc(DGRAMMGR_RECVBUF_SIZE); + this->sendBuf = (char*)vmalloc(DGRAMMGR_SENDBUF_SIZE); +} + + +/** + * Sends the buffer to all available node interfaces. + */ +static void DatagramListener_sendBufToNode_kernel(DatagramListener* this, Node* node, + char* buf, size_t bufLen) +{ + NodeConnPool* connPool = Node_getConnPool(node); + NicAddressList* nicList; + unsigned short port = Node_getPortUDP(node); + NicAddressListIter iter; + + NodeConnPool_lock(connPool); + nicList = NodeConnPool_getNicListLocked(connPool); + NicAddressListIter_init(&iter, nicList); + + for( ; !NicAddressListIter_end(&iter); NicAddressListIter_next(&iter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&iter); + + if(nicAddr->nicType != NICADDRTYPE_STANDARD) + continue; + + if(!NetFilter_isAllowed(this->netFilter, nicAddr->ipAddr) ) + continue; + + DatagramListener_sendtoIP_kernel(this, buf, bufLen, 0, nicAddr->ipAddr, port); + } + NodeConnPool_unlock(connPool); +} + +/** + * Sends the message to all available node interfaces. + */ +void DatagramListener_sendMsgToNode(DatagramListener* this, Node* node, NetMessage* msg) +{ + char* msgBuf = MessagingTk_createMsgBuf(msg); + unsigned msgLen = NetMessage_getMsgLength(msg); + + DatagramListener_sendBufToNode_kernel(this, node, msgBuf, msgLen); + + kfree(msgBuf); +} + +bool __DatagramListener_isDGramFromLocalhost(DatagramListener* this, + fhgfs_sockaddr_in* fromAddr) +{ + NodeConnPool* connPool; + NicAddressList* nicList; + NicAddressListIter iter; + int nicListSize; + int i; + bool result = false; + + if(fromAddr->port != this->udpPortNetByteOrder) + return false; + + // (inaddr_loopback is in host byte order) + if(ntohl(INADDR_LOOPBACK) == fromAddr->addr.s_addr) + return true; + + connPool = Node_getConnPool(this->localNode); + NodeConnPool_lock(connPool); + nicList = NodeConnPool_getNicListLocked(connPool); + + NicAddressListIter_init(&iter, nicList); + nicListSize = NicAddressList_length(nicList); + + for(i = 0; i < nicListSize; i++, NicAddressListIter_next(&iter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&iter); + + if(nicAddr->ipAddr.s_addr == fromAddr->addr.s_addr) + { + result = true; + break; + } + } + + NodeConnPool_unlock(connPool); + return result; +} diff --git a/client_module/source/components/DatagramListener.h b/client_module/source/components/DatagramListener.h new file mode 100644 index 0000000..ca6fb4c --- /dev/null +++ b/client_module/source/components/DatagramListener.h @@ -0,0 +1,156 @@ +#ifndef DATAGRAMLISTENER_H_ +#define DATAGRAMLISTENER_H_ + +#include +#include +#include +#include +#include +#include +#include + + +#define DGRAMMGR_RECVBUF_SIZE 65536 +#define DGRAMMGR_SENDBUF_SIZE DGRAMMGR_RECVBUF_SIZE + + +struct DatagramListener; +typedef struct DatagramListener DatagramListener; + +static inline __must_check bool DatagramListener_init(DatagramListener* this, App* app, + Node* localNode, unsigned short udpPort); +static inline DatagramListener* DatagramListener_construct(App* app, Node* localNode, + unsigned short udpPort); +static inline void DatagramListener_uninit(DatagramListener* this); +static inline void DatagramListener_destruct(DatagramListener* this); + +//extern ssize_t DatagramListener_broadcast(DatagramListener* this, void* buf, size_t len, +// int flags, unsigned short port); // no longer needed +extern void DatagramListener_sendMsgToNode(DatagramListener* this, Node* node, NetMessage* msg); + +extern void __DatagramListener_run(Thread* this); +extern void __DatagramListener_listenLoop(DatagramListener* this); + +extern void _DatagramListener_handleIncomingMsg(DatagramListener* this, + fhgfs_sockaddr_in* fromAddr, NetMessage* msg); + +extern bool __DatagramListener_initSock(DatagramListener* this, unsigned short udpPort); +extern void __DatagramListener_initBuffers(DatagramListener* this); +extern bool __DatagramListener_isDGramFromLocalhost(DatagramListener* this, + fhgfs_sockaddr_in* fromAddr); + + +struct DatagramListener +{ + Thread thread; + + App* app; + + char* recvBuf; + + Node* localNode; + NetFilter* netFilter; + + StandardSocket* udpSock; + unsigned short udpPortNetByteOrder; + char* sendBuf; + Mutex sendMutex; +}; + + +bool DatagramListener_init(DatagramListener* this, App* app, Node* localNode, + unsigned short udpPort) +{ + Thread_init( (Thread*)this, BEEGFS_THREAD_NAME_PREFIX_STR "DGramLis", __DatagramListener_run); + + this->app = app; + this->udpSock = NULL; + + this->localNode = localNode; + this->netFilter = App_getNetFilter(app); + + this->recvBuf = NULL; + this->sendBuf = NULL; + + Mutex_init(&this->sendMutex); + + if(!__DatagramListener_initSock(this, udpPort) ) + { + Logger* log = App_getLogger(app); + const char* logContext = "DatagramListener_init"; + + Logger_logErr(log, logContext, "Unable to initialize the socket"); + goto err; + } + + return true; + +err: + Mutex_uninit(&this->sendMutex); + return false; +} + +struct DatagramListener* DatagramListener_construct(App* app, Node* localNode, + unsigned short udpPort) +{ + struct DatagramListener* this = kmalloc(sizeof(*this), GFP_NOFS); + + if(!this || + !DatagramListener_init(this, app, localNode, udpPort) ) + { + kfree(this); + return NULL; + } + + return this; +} + +void DatagramListener_uninit(DatagramListener* this) +{ + Socket* udpSockBase = (Socket*)this->udpSock; + + if(udpSockBase) + Socket_virtualDestruct(udpSockBase); + + Mutex_uninit(&this->sendMutex); + + SAFE_VFREE(this->sendBuf); + SAFE_VFREE(this->recvBuf); + + Thread_uninit( (Thread*)this); +} + +void DatagramListener_destruct(DatagramListener* this) +{ + DatagramListener_uninit(this); + + kfree(this); +} + + +static inline ssize_t DatagramListener_sendto_kernel(DatagramListener* this, void* buf, size_t len, int flags, + fhgfs_sockaddr_in* to) +{ + ssize_t sendRes; + + Mutex_lock(&this->sendMutex); + + sendRes = Socket_sendto_kernel(&this->udpSock->pooledSocket.socket, buf, len, flags, to); + + Mutex_unlock(&this->sendMutex); + + return sendRes; +} + +static inline ssize_t DatagramListener_sendtoIP_kernel(DatagramListener* this, void *buf, size_t len, int flags, + struct in_addr ipAddr, unsigned short port) +{ + fhgfs_sockaddr_in peer = { + .addr = ipAddr, + .port = htons(port), + }; + + return DatagramListener_sendto_kernel(this, buf, len, flags, &peer); +} + +#endif /*DATAGRAMLISTENER_H_*/ diff --git a/client_module/source/components/Flusher.c b/client_module/source/components/Flusher.c new file mode 100644 index 0000000..a6be9a8 --- /dev/null +++ b/client_module/source/components/Flusher.c @@ -0,0 +1,131 @@ +#include +#include +#include +//#include +#include "Flusher.h" + + +void Flusher_init(Flusher* this, App* app) +{ + // call super constructor + Thread_init( (Thread*)this, BEEGFS_THREAD_NAME_PREFIX_STR "Flusher", __Flusher_run); + + this->app = app; +} + +struct Flusher* Flusher_construct(App* app) +{ + struct Flusher* this = (Flusher*)os_kmalloc(sizeof(*this) ); + + Flusher_init(this, app); + + return this; +} + +void Flusher_uninit(Flusher* this) +{ + Thread_uninit( (Thread*)this); +} + +void Flusher_destruct(Flusher* this) +{ + Flusher_uninit(this); + + kfree(this); +} + +void _Flusher_requestLoop(Flusher* this) +{ + int sleepTimeMS = 5*1000; + + Thread* thisThread = (Thread*)this; + + while(!_Thread_waitForSelfTerminateOrder(thisThread, sleepTimeMS) ) + { + __Flusher_flushBuffers(this); + } + +} + + +void __Flusher_run(Thread* this) +{ + Flusher* thisCast = (Flusher*)this; + + const char* logContext = "Flusher (run)"; + Logger* log = App_getLogger(thisCast->app); + + + _Flusher_requestLoop(thisCast); + + Logger_log(log, 4, logContext, "Component stopped."); +} + + +void __Flusher_flushBuffers(Flusher* this) +{ + const char* logContext = "flushBuffers (async)"; + + InodeRefStore* refStore = App_getInodeRefStore(this->app); + Thread* thisThread = (Thread*)this; + + struct inode* inode = InodeRefStore_getAndRemoveFirstInode(refStore); + + while(inode) + { + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + FhgfsOpsErr flushRes = FhgfsOpsHelper_flushCacheNoWait(this->app, fhgfsInode, false); + + if(flushRes == FhgfsOpsErr_SUCCESS) + { // flush succeeded => drop inode reference + iput(inode); + } + else + if(flushRes == FhgfsOpsErr_INUSE) + { // file wasn't flushed, because the lock is busy right now => re-add and continue with next + InodeRefStore_addOrPutInode(refStore, inode); + } + else + if( (flushRes != FhgfsOpsErr_COMMUNICATION) || !FhgfsInode_getIsFileOpen(fhgfsInode) ) + { /* unrecoverable error and file is no longer open (so there is no chance that the user app + can see an error code) so we have to discard the buffer to avoid retrying infintely + on this inode */ + + Logger* log = App_getLogger(this->app); + + FhgfsOpsErr finalFlushRes = FhgfsOpsHelper_flushCache( + this->app, fhgfsInode, true); + + if(finalFlushRes != FhgfsOpsErr_SUCCESS) + { // final flush attempt failed => notify user + Logger_logFormatted(log, Log_DEBUG, logContext, + "Discarded file buffer due to unrecoverable error on closed file: %s", + FhgfsOpsErr_toErrString(finalFlushRes) ); + } + + iput(inode); + } + else + { // comm error (or unrecoverable error, but file still open); flush failed => re-add inode + + /* note: decreasing ref count if inode exists in store is important in addOrPutInode(), + * because we might race with a user app, e.g.: + * 1) flusher gets a comm error and flusher thread sleeps before calling addOrPutInode() + * 2) user app runs, flushes successfully, creates new cache buf and adds it to store + * 3) flusher wakes up and calls addOrPutInode() */ + + InodeRefStore_addOrPutInode(refStore, inode); + } + + // check if user wants to unmount + if(Thread_getSelfTerminate(thisThread) ) + break; + + // proceed to next inode + /* note: it doesn't matter that the inode may no longer be valid here (after we dropped the + reference), because we're not accessing the inode in the InodeRefStore methods. */ + inode = InodeRefStore_getAndRemoveNextInode(refStore, inode); + } + +} diff --git a/client_module/source/components/Flusher.h b/client_module/source/components/Flusher.h new file mode 100644 index 0000000..6842483 --- /dev/null +++ b/client_module/source/components/Flusher.h @@ -0,0 +1,39 @@ +#ifndef FLUSHER_H_ +#define FLUSHER_H_ + +#include +#include + + +/* + * This component performs async cache flushes in buffered file cache mode. + * + * Note: Flushing needs to happen in a dedicated thread to avoid blocking other threads during + * retries while a server is unreachable. + */ + + +struct Flusher; +typedef struct Flusher Flusher; + + +extern void Flusher_init(Flusher* this, App* app); +extern Flusher* Flusher_construct(App* app); +extern void Flusher_uninit(Flusher* this); +extern void Flusher_destruct(Flusher* this); + +extern void _Flusher_requestLoop(Flusher* this); + +extern void __Flusher_run(Thread* this); +void __Flusher_flushBuffers(Flusher* this); + + + +struct Flusher +{ + Thread thread; // base class + + App* app; +}; + +#endif /* FLUSHER_H_ */ diff --git a/client_module/source/components/InternodeSyncer.c b/client_module/source/components/InternodeSyncer.c new file mode 100644 index 0000000..5baf619 --- /dev/null +++ b/client_module/source/components/InternodeSyncer.c @@ -0,0 +1,1221 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "InternodeSyncer.h" + + +void InternodeSyncer_init(InternodeSyncer* this, App* app) +{ + // call super constructor + Thread_init( (Thread*)this, BEEGFS_THREAD_NAME_PREFIX_STR "XNodeSync", __InternodeSyncer_run); + + this->forceTargetStatesUpdate = false; + + this->app = app; + this->cfg = App_getConfig(app); + + this->dgramLis = App_getDatagramListener(app); + + this->mgmtNodes = App_getMgmtNodes(app); + this->metaNodes = App_getMetaNodes(app); + this->storageNodes = App_getStorageNodes(app); + + this->nodeRegistered = false; + this->mgmtInitDone = false; + + Mutex_init(&this->mgmtInitDoneMutex); + Condition_init(&this->mgmtInitDoneCond); + + Mutex_init(&this->delayedCloseMutex); + PointerList_init(&this->delayedCloseQueue); + + Mutex_init(&this->delayedEntryUnlockMutex); + PointerList_init(&this->delayedEntryUnlockQueue); + + Mutex_init(&this->delayedRangeUnlockMutex); + PointerList_init(&this->delayedRangeUnlockQueue); + + Mutex_init(&this->forceTargetStatesUpdateMutex); + Time_init(&this->lastSuccessfulTargetStatesUpdateT); + this->targetOfflineTimeoutMS = Config_getSysTargetOfflineTimeoutSecs(this->cfg) * 1000; +} + +struct InternodeSyncer* InternodeSyncer_construct(App* app) +{ + struct InternodeSyncer* this = (InternodeSyncer*)os_kmalloc(sizeof(*this) ); + + if(likely(this) ) + InternodeSyncer_init(this, app); + + return this; +} + +void InternodeSyncer_uninit(InternodeSyncer* this) +{ + PointerListIter iter; + + // free delayed close Q elements + + PointerListIter_init(&iter, &this->delayedCloseQueue); + + while(!PointerListIter_end(&iter) ) + { + __InternodeSyncer_delayedCloseFreeEntry(this, + (DelayedCloseEntry*)PointerListIter_value(&iter) ); + PointerListIter_next(&iter); + } + + PointerList_uninit(&this->delayedCloseQueue); + Mutex_uninit(&this->delayedCloseMutex); + + // free delayed entry unlock Q elements + + PointerListIter_init(&iter, &this->delayedEntryUnlockQueue); + + while(!PointerListIter_end(&iter) ) + { + __InternodeSyncer_delayedEntryUnlockFreeEntry(this, + (DelayedEntryUnlockEntry*)PointerListIter_value(&iter) ); + PointerListIter_next(&iter); + } + + PointerList_uninit(&this->delayedEntryUnlockQueue); + Mutex_uninit(&this->delayedEntryUnlockMutex); + + // free delayed range unlock Q elements + + PointerListIter_init(&iter, &this->delayedRangeUnlockQueue); + + while(!PointerListIter_end(&iter) ) + { + __InternodeSyncer_delayedRangeUnlockFreeEntry(this, + (DelayedRangeUnlockEntry*)PointerListIter_value(&iter) ); + PointerListIter_next(&iter); + } + + PointerList_uninit(&this->delayedRangeUnlockQueue); + Mutex_uninit(&this->delayedRangeUnlockMutex); + + Mutex_uninit(&this->forceTargetStatesUpdateMutex); + + Mutex_uninit(&this->mgmtInitDoneMutex); + + Thread_uninit( (Thread*)this); +} + +void InternodeSyncer_destruct(InternodeSyncer* this) +{ + InternodeSyncer_uninit(this); + + kfree(this); +} + +void __InternodeSyncer_run(Thread* this) +{ + InternodeSyncer* thisCast = (InternodeSyncer*)this; + + const char* logContext = "InternodeSyncer (run)"; + Logger* log = App_getLogger(thisCast->app); + + Logger_logFormatted(log, Log_DEBUG, logContext, "Searching for nodes..."); + + __InternodeSyncer_mgmtInit(thisCast); + + _InternodeSyncer_requestLoop(thisCast); + + __InternodeSyncer_signalMgmtInitDone(thisCast); + + if(thisCast->nodeRegistered) + __InternodeSyncer_unregisterNode(thisCast); + + Logger_log(log, Log_DEBUG, logContext, "Component stopped."); +} + +void _InternodeSyncer_requestLoop(InternodeSyncer* this) +{ + const unsigned sleepTimeMS = 5*1000; + + const unsigned mgmtInitIntervalMS = 5000; + const unsigned reregisterIntervalMS = 60*1000; /* send heartbeats to mgmt in this interval. must + be a lot lower than tuneClientAutoRemoveMins of mgmt. the value is capped to at least 5 + (or disabled) on mgmt. */ + const unsigned downloadNodesIntervalMS = 180000; + const unsigned updateTargetStatesMS = Config_getSysUpdateTargetStatesSecs(this->cfg) * 1000; + const unsigned delayedOpsIntervalMS = 60*1000; + const unsigned idleDisconnectIntervalMS = 70*60*1000; /* 70 minutes (must be + less than half the server-side streamlis idle disconnect interval to avoid + server disconnecting first) */ + const unsigned checkNetworkIntervalMS = 60*1000; // 1 minute + + Time lastMgmtInitT; + Time lastReregisterT; + Time lastDownloadNodesT; + + Time lastDelayedOpsT; + Time lastIdleDisconnectT; + Time lastTargetStatesUpdateT; + Time lastCheckNetworkT; + + Thread* thisThread = (Thread*)this; + + Time_init(&lastMgmtInitT); + Time_init(&lastReregisterT); + Time_init(&lastDownloadNodesT); + + Time_init(&lastDelayedOpsT); + Time_init(&lastIdleDisconnectT); + Time_init(&lastTargetStatesUpdateT); + Time_init(&lastCheckNetworkT); + + while(!_Thread_waitForSelfTerminateOrder(thisThread, sleepTimeMS) ) + { + bool targetStatesUpdateForced = + InternodeSyncer_getAndResetForceTargetStatesUpdate(this); + + // mgmt init + if(!this->mgmtInitDone) + { + if(Time_elapsedMS(&lastMgmtInitT) > mgmtInitIntervalMS) + { + __InternodeSyncer_mgmtInit(this); + + Time_setToNow(&lastMgmtInitT); + } + + continue; + } + + // everything below only happens after successful management init... + + // check for NIC changes + if(Time_elapsedMS(&lastCheckNetworkT) > checkNetworkIntervalMS) + { + if (__InternodeSyncer_checkNetwork(this)) + Time_setZero(&lastReregisterT); + Time_setToNow(&lastCheckNetworkT); + } + + // re-register + if(Time_elapsedMS(&lastReregisterT) > reregisterIntervalMS) + { + __InternodeSyncer_reregisterNode(this); + + Time_setToNow(&lastReregisterT); + } + + // download & sync nodes + if(Time_elapsedMS(&lastDownloadNodesT) > downloadNodesIntervalMS) + { + __InternodeSyncer_downloadAndSyncNodes(this); + __InternodeSyncer_downloadAndSyncTargetMappings(this); + + Time_setToNow(&lastDownloadNodesT); + } + + // download target states + if( targetStatesUpdateForced || + (Time_elapsedMS(&lastTargetStatesUpdateT) > updateTargetStatesMS) ) + { + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Meta); + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Storage); + + Time_setToNow(&lastTargetStatesUpdateT); + } + + // delayed operations (that were left due to interruption by signal or network errors) + if(Time_elapsedMS(&lastDelayedOpsT) > delayedOpsIntervalMS) + { + __InternodeSyncer_delayedEntryUnlockComm(this); + __InternodeSyncer_delayedRangeUnlockComm(this); + __InternodeSyncer_delayedCloseComm(this); + + Time_setToNow(&lastDelayedOpsT); + } + + // drop idle connections + if(Time_elapsedMS(&lastIdleDisconnectT) > idleDisconnectIntervalMS) + { + __InternodeSyncer_dropIdleConns(this); + + Time_setToNow(&lastIdleDisconnectT); + } + } +} + +void __InternodeSyncer_signalMgmtInitDone(InternodeSyncer* this) +{ + Mutex_lock(&this->mgmtInitDoneMutex); + + this->mgmtInitDone = true; + + Condition_broadcast(&this->mgmtInitDoneCond); + + Mutex_unlock(&this->mgmtInitDoneMutex); +} + +void __InternodeSyncer_mgmtInit(InternodeSyncer* this) +{ + static bool waitForMgmtLogged = false; // to avoid log spamming + + const char* logContext = "Init"; + Logger* log = App_getLogger(this->app); + + if(!waitForMgmtLogged) + { + const char* hostname = Config_getSysMgmtdHost(this->cfg); + unsigned short port = Config_getConnMgmtdPort(this->cfg); + + Logger_logFormatted(log, Log_WARNING, logContext, + "Waiting for beegfs-mgmtd@%s:%hu...", hostname, port); + waitForMgmtLogged = true; + } + + if(!__InternodeSyncer_waitForMgmtHeartbeat(this) ) + return; + + Logger_log(log, Log_NOTICE, logContext, "Management node found. Downloading node groups..."); + + __InternodeSyncer_downloadAndSyncNodes(this); + __InternodeSyncer_downloadAndSyncTargetMappings(this); + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Storage); + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Meta); + + Logger_log(log, Log_NOTICE, logContext, "Node registration..."); + + if(__InternodeSyncer_registerNode(this) ) + { // download nodes again now that we will receive notifications about add/remove (avoids race) + __InternodeSyncer_downloadAndSyncNodes(this); + __InternodeSyncer_downloadAndSyncTargetMappings(this); + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Storage); + __InternodeSyncer_updateTargetStatesAndBuddyGroups(this, NODETYPE_Meta); + } + + __InternodeSyncer_signalMgmtInitDone(this); + + Logger_log(log, Log_NOTICE, logContext, "Init complete."); +} + + +/** + * @param timeoutMS 0 to wait infinitely (which is probably never what you want) + */ +bool InternodeSyncer_waitForMgmtInit(InternodeSyncer* this, int timeoutMS) +{ + bool retVal; + + Mutex_lock(&this->mgmtInitDoneMutex); + + if(!timeoutMS) + { + while(!this->mgmtInitDone) + Condition_wait(&this->mgmtInitDoneCond, &this->mgmtInitDoneMutex); + } + else + if(!this->mgmtInitDone) + Condition_timedwait(&this->mgmtInitDoneCond, &this->mgmtInitDoneMutex, timeoutMS); + + retVal = this->mgmtInitDone; + + Mutex_unlock(&this->mgmtInitDoneMutex); + + return retVal; +} + +bool __InternodeSyncer_waitForMgmtHeartbeat(InternodeSyncer* this) +{ + const int waitTimeoutMS = 400; + const int numRetries = 1; + + char heartbeatReqBuf[NETMSG_MIN_LENGTH]; + Thread* thisThread = (Thread*)this; + + const char* hostname = Config_getSysMgmtdHost(this->cfg); + unsigned short port = Config_getConnMgmtdPort(this->cfg); + struct in_addr ipAddr; + int i; + + HeartbeatRequestMsgEx msg; + + if(NodeStoreEx_getSize(this->mgmtNodes) ) + return true; + + + // prepare request message + + HeartbeatRequestMsgEx_init(&msg); + NetMessage_serialize( (NetMessage*)&msg, heartbeatReqBuf, NETMSG_MIN_LENGTH); + + + // resolve name, send message, wait for incoming heartbeat + + if (!SocketTk_getHostByAddrStr(hostname, &ipAddr)) { + return false; + } + + for(i=0; (i <= numRetries) && !Thread_getSelfTerminate(thisThread); i++) + { + int tryTimeoutWarpMS = Random_getNextInRange(-(waitTimeoutMS/4), waitTimeoutMS/4); + + DatagramListener_sendtoIP_kernel(this->dgramLis, heartbeatReqBuf, sizeof heartbeatReqBuf, 0, + ipAddr, port); + + if(NodeStoreEx_waitForFirstNode(this->mgmtNodes, waitTimeoutMS + tryTimeoutWarpMS) ) + { + return true; // heartbeat received => we're done + } + } + + return false; +} + +/** + * @return true if an ack was received for the heartbeat, false otherwise + */ +bool __InternodeSyncer_registerNode(InternodeSyncer* this) +{ + static bool registrationFailureLogged = false; // to avoid log spamming + + const char* logContext = "Registration"; + Config* cfg = App_getConfig(this->app); + Logger* log = App_getLogger(this->app); + + Node* mgmtNode; + + NoAllocBufferStore* bufStore = App_getMsgBufStore(this->app); + Node* localNode = App_getLocalNode(this->app); + NodeString alias; + NumNodeID localNodeNumID = Node_getNumID(localNode); + NumNodeID newLocalNodeNumID; + NicAddressList nicList; + + RegisterNodeMsg msg; + char* respBuf; + NetMessage* respMsg; + FhgfsOpsErr requestRes; + RegisterNodeRespMsg* registerResp; + bool result; + + Node_cloneNicList(localNode, &nicList); + mgmtNode = NodeStoreEx_referenceFirstNode(this->mgmtNodes); + if(!mgmtNode) + { + result = false; + goto exit; + } + + Node_copyAlias(localNode, &alias); + RegisterNodeMsg_initFromNodeData(&msg, alias.buf, localNodeNumID, NODETYPE_Client, + &nicList, Config_getConnClientPort(this->cfg)); + + // connect & communicate + requestRes = MessagingTk_requestResponse(this->app, mgmtNode, + (NetMessage*)&msg, NETMSGTYPE_RegisterNodeResp, &respBuf, &respMsg); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // request/response failed + goto cleanup_request; + } + + // handle result + registerResp = (RegisterNodeRespMsg*)respMsg; + newLocalNodeNumID = RegisterNodeRespMsg_getNodeNumID(registerResp); + Config_setConnMgmtdGrpcPort(cfg, RegisterNodeRespMsg_getGrpcPort(registerResp)); + App_updateFsUUID(this->app, RegisterNodeRespMsg_getFsUUID(registerResp)); + + if (newLocalNodeNumID.value == 0) + { + Logger_log(log, Log_CRITICAL, logContext, + "Unable to register at management daemon. No valid numeric node ID retrieved."); + } + else + { + Node_setNumID(localNode, newLocalNodeNumID); + + this->nodeRegistered = true; + } + + // clean-up + NETMESSAGE_FREE(respMsg); + NoAllocBufferStore_addBuf(bufStore, respBuf); + +cleanup_request: + Node_put(mgmtNode); + + // log registration result + if(this->nodeRegistered) + Logger_log(log, Log_WARNING, logContext, "Node registration successful."); + else + if(!registrationFailureLogged) + { + Logger_log(log, Log_CRITICAL, logContext, + "Node registration failed. Management node offline? Will keep on trying..."); + registrationFailureLogged = true; + } + + result = this->nodeRegistered; + +exit: + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + + return result; +} + +bool __InternodeSyncer_checkNetwork(InternodeSyncer* this) +{ + Logger* log = App_getLogger(this->app); + NicAddressList newNicList; + NodeConnPool* connPool = Node_getConnPool(App_getLocalNode(this->app)); + bool result = false; + + if (App_findAllowedInterfaces(this->app, &newNicList)) + { + NodeConnPool_lock(connPool); + result = !NicAddressList_equals(&newNicList, NodeConnPool_getNicListLocked(connPool)); + NodeConnPool_unlock(connPool); + if (result) + { + Logger_log(log, Log_NOTICE, "checkNetwork", "Local interfaces have changed."); + App_updateLocalInterfaces(this->app, &newNicList); + } + ListTk_kfreeNicAddressListElems(&newNicList); + NicAddressList_uninit(&newNicList); + } + return result; +} + +/** + * Note: This just sends a heartbeat to the mgmt node to re-new our existence information + * (in case the mgmt node assumed our death for some reason like network errors etc.) + */ +void __InternodeSyncer_reregisterNode(InternodeSyncer* this) +{ + Node* mgmtNode; + + Node* localNode = App_getLocalNode(this->app); + NodeString alias; + NumNodeID localNodeNumID = Node_getNumID(localNode); + NicAddressList nicList; + + HeartbeatMsgEx msg; + + Node_cloneNicList(localNode, &nicList); + + mgmtNode = NodeStoreEx_referenceFirstNode(this->mgmtNodes); + if(!mgmtNode) + goto exit; + + Node_copyAlias(localNode, &alias); + HeartbeatMsgEx_initFromNodeData(&msg, alias.buf, localNodeNumID, NODETYPE_Client, &nicList); + HeartbeatMsgEx_setPorts(&msg, Config_getConnClientPort(this->cfg), 0); + + DatagramListener_sendMsgToNode(this->dgramLis, mgmtNode, (NetMessage*)&msg); + + Node_put(mgmtNode); + +exit: + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + +} + +/** + * @return true if an ack was received for the heartbeat, false otherwise + */ +bool __InternodeSyncer_unregisterNode(InternodeSyncer* this) +{ + const char* logContext = "Deregistration"; + Logger* log = App_getLogger(this->app); + + /* note: be careful not to use datagrams here, because this is called during App stop and hence + the DGramLis is probably not even listening for responses anymore */ + + Node* mgmtNode; + + NoAllocBufferStore* bufStore = App_getMsgBufStore(this->app); + Node* localNode = App_getLocalNode(this->app); + NodeString alias; + NumNodeID localNodeNumID = Node_getNumID(localNode); + + RemoveNodeMsgEx msg; + char* respBuf; + NetMessage* respMsg; + FhgfsOpsErr requestRes; + //RemoveNodeRespMsg* rmResp; // response value not needed currently + bool nodeUnregistered = false; + + mgmtNode = NodeStoreEx_referenceFirstNode(this->mgmtNodes); + if(!mgmtNode) + return false; + + RemoveNodeMsgEx_initFromNodeData(&msg, localNodeNumID, NODETYPE_Client); + + // connect & communicate + requestRes = MessagingTk_requestResponse(this->app, mgmtNode, + (NetMessage*)&msg, NETMSGTYPE_RemoveNodeResp, &respBuf, &respMsg); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // request/response failed + goto cleanup_request; + } + + // handle result + // rmResp = (RemoveNodeRespMsg*)respMsg; // response value not needed currently + + nodeUnregistered = true; + + + // cleanup + NETMESSAGE_FREE(respMsg); + NoAllocBufferStore_addBuf(bufStore, respBuf); + + +cleanup_request: + Node_put(mgmtNode); + + // log deregistration result + if(nodeUnregistered) + Logger_log(log, Log_WARNING, logContext, "Node deregistration successful."); + else + { + Node_copyAlias(localNode, &alias); + Logger_log(log, Log_CRITICAL, logContext, + "Node deregistration failed. Management node offline?"); + Logger_logFormatted(log, Log_CRITICAL, logContext, + "In case you didn't enable automatic removal, you will have to remove this ClientID " + "manually from the system: %s", alias.buf); + } + + return nodeUnregistered; +} + + +void __InternodeSyncer_downloadAndSyncNodes(InternodeSyncer* this) +{ + Node* mgmtNode; + Node* localNode; + + NodeList metaNodesList; + NumNodeIDList addedMetaNodes; + NumNodeIDList removedMetaNodes; + NumNodeID rootNodeID = (NumNodeID){0}; + bool rootIsBuddyMirrored; + + NodeList storageNodesList; + NumNodeIDList addedStorageNodes; + NumNodeIDList removedStorageNodes; + + + mgmtNode = NodeStoreEx_referenceFirstNode(this->mgmtNodes); + if(!mgmtNode) + return; + + localNode = App_getLocalNode(this->app); + + // metadata nodes + + NodeList_init(&metaNodesList); + NumNodeIDList_init(&addedMetaNodes); + NumNodeIDList_init(&removedMetaNodes); + + if(NodesTk_downloadNodes(this->app, mgmtNode, NODETYPE_Meta, &metaNodesList, &rootNodeID, + &rootIsBuddyMirrored) ) + { + const NodeOrGroup rootOwner = rootIsBuddyMirrored + ? NodeOrGroup_fromGroup(rootNodeID.value) + : NodeOrGroup_fromNode(rootNodeID); + + NodeStoreEx_syncNodes(this->metaNodes, + &metaNodesList, &addedMetaNodes, &removedMetaNodes, localNode); + NodeStoreEx_setRootOwner(this->metaNodes, rootOwner, false); + __InternodeSyncer_printSyncResults(this, NODETYPE_Meta, &addedMetaNodes, &removedMetaNodes); + } + + NodeList_uninit(&metaNodesList); + NumNodeIDList_uninit(&addedMetaNodes); + NumNodeIDList_uninit(&removedMetaNodes); + + // storage nodes + + NodeList_init(&storageNodesList); + NumNodeIDList_init(&addedStorageNodes); + NumNodeIDList_init(&removedStorageNodes); + + if(NodesTk_downloadNodes(this->app, mgmtNode, NODETYPE_Storage, &storageNodesList, NULL, NULL) ) + { + NodeStoreEx_syncNodes(this->storageNodes, + &storageNodesList, &addedStorageNodes, &removedStorageNodes, localNode); + __InternodeSyncer_printSyncResults(this, + NODETYPE_Storage, &addedStorageNodes, &removedStorageNodes); + } + + // cleanup + + NodeList_uninit(&storageNodesList); + NumNodeIDList_uninit(&addedStorageNodes); + NumNodeIDList_uninit(&removedStorageNodes); + + Node_put(mgmtNode); +} + +void __InternodeSyncer_printSyncResults(InternodeSyncer* this, NodeType nodeType, + NumNodeIDList* addedNodes, NumNodeIDList* removedNodes) +{ + const char* logContext = "Sync"; + Logger* log = App_getLogger(this->app); + + if(NumNodeIDList_length(addedNodes) ) + Logger_logFormatted(log, Log_WARNING, logContext, + "Nodes added (sync results): %d (Type: %s)", + (int)NumNodeIDList_length(addedNodes), Node_nodeTypeToStr(nodeType) ); + + if(NumNodeIDList_length(removedNodes) ) + Logger_logFormatted(log, Log_WARNING, logContext, + "Nodes removed (sync results): %d (Type: %s)", + (int)NumNodeIDList_length(removedNodes), Node_nodeTypeToStr(nodeType) ); +} + +void __InternodeSyncer_downloadAndSyncTargetMappings(InternodeSyncer* this) +{ + TargetMapper* targetMapper = App_getTargetMapper(this->app); + Node* mgmtNode; + bool downloadRes; + + LIST_HEAD(mappings); + + mgmtNode = NodeStoreEx_referenceFirstNode(this->mgmtNodes); + if(!mgmtNode) + return; + + downloadRes = NodesTk_downloadTargetMappings(this->app, mgmtNode, &mappings); + if(downloadRes) + TargetMapper_syncTargets(targetMapper, /*move*/&mappings); + + // cleanup + + Node_put(mgmtNode); +} + + +/** + * note: currently only downloads storage targets, not meta. + * + * @param nodeType the node type (NODETYPE_Storage or NODTYPE_Meta) for which the target states + * and buddy groups should be synced + */ +void __InternodeSyncer_updateTargetStatesAndBuddyGroups(InternodeSyncer* this, NodeType nodeType) +{ + const char* logContext = "Update states and mirror groups"; + Logger* log = App_getLogger(this->app); + + NodeStoreEx* mgmtdNodes = App_getMgmtNodes(this->app); + TargetStateStore* targetStateStore; + MirrorBuddyGroupMapper* buddyGroupMapper; + + Node* mgmtdNode; + bool downloadRes; + + LIST_HEAD(buddyGroups); /* struct BuddyGroupMapping */ + LIST_HEAD(states); /* struct TargetStateMapping */ + + switch (nodeType) + { + case NODETYPE_Storage: + buddyGroupMapper = App_getStorageBuddyGroupMapper(this->app); + targetStateStore = App_getTargetStateStore(this->app); + break; + + case NODETYPE_Meta: + buddyGroupMapper = App_getMetaBuddyGroupMapper(this->app); + targetStateStore = App_getMetaStateStore(this->app); + break; + + default: + return; + } + + mgmtdNode = NodeStoreEx_referenceFirstNode(mgmtdNodes); + if(!mgmtdNode) + return; + + downloadRes = NodesTk_downloadStatesAndBuddyGroups(this->app, mgmtdNode, nodeType, + &buddyGroups, &states); + + if(downloadRes) + { + TargetStateStore_syncStatesAndGroupsFromLists(targetStateStore, this->app->cfg, + buddyGroupMapper, &states, &buddyGroups); + + Time_setToNow(&this->lastSuccessfulTargetStatesUpdateT); + Logger_logFormatted(log, Log_DEBUG, logContext, "%s states synced.", + (nodeType == NODETYPE_Meta) ? "Metadata node" : "Storage target"); + } + else + if( (this->targetOfflineTimeoutMS != 0) + && (Time_elapsedMS(&this->lastSuccessfulTargetStatesUpdateT) > this->targetOfflineTimeoutMS) ) + { + bool setStateRes = + TargetStateStore_setAllStates(targetStateStore, TargetReachabilityState_OFFLINE); + + if(setStateRes) + Logger_logFormatted(log, Log_WARNING, logContext, + "%s state sync failed. All %s set to offline.", + (nodeType == NODETYPE_Meta) ? "Metadata node" : "Storage target", + (nodeType == NODETYPE_Meta) ? "nodes" : "targets"); + } + else + { + bool setStatesRes = + TargetStateStore_setAllStates(targetStateStore, TargetReachabilityState_POFFLINE); + + if(setStatesRes) + Logger_logFormatted(log, Log_WARNING, logContext, + "%s state sync failed. All %s set to probably-offline.", + (nodeType == NODETYPE_Meta) ? "Metadata node" : "Storage target", + (nodeType == NODETYPE_Meta) ? "nodes" : "targets"); + } + + // cleanup + + BEEGFS_KFREE_LIST(&buddyGroups, struct BuddyGroupMapping, _list); + BEEGFS_KFREE_LIST(&states, struct TargetStateMapping, _list); + + Node_put(mgmtdNode); +} + + +/** + * Try to close all delayedClose files. + */ +void __InternodeSyncer_delayedCloseComm(InternodeSyncer* this) +{ + PointerListIter iter; + + Mutex_lock(&this->delayedCloseMutex); // L O C K + + PointerListIter_init(&iter, &this->delayedCloseQueue); + + while(!PointerListIter_end(&iter) && !Thread_getSelfTerminate( (Thread*)this) ) + { + DelayedCloseEntry* currentClose = PointerListIter_value(&iter); + + EntryInfo* entryInfo; + RemotingIOInfo ioInfo; + FhgfsOpsErr closeRes; + + __InternodeSyncer_delayedClosePrepareRemoting(this, currentClose, &entryInfo, &ioInfo); + + // note: unlock, so that more entries can be added to the queue during remoting + Mutex_unlock(&this->delayedCloseMutex); // U N L O C K + + closeRes = FhgfsOpsRemoting_closefileEx(entryInfo, &ioInfo, false, + currentClose->hasEvent ? ¤tClose->event : NULL); + + Mutex_lock(&this->delayedCloseMutex); // R E L O C K + + if(closeRes == FhgfsOpsErr_COMMUNICATION) + { // comm error => we will try again later + PointerListIter_next(&iter); + } + else + { /* anything other than communication error means our job is done and we can delete the + entry */ + __InternodeSyncer_delayedCloseFreeEntry(this, currentClose); + + iter = PointerListIter_remove(&iter); + } + } + + + Mutex_unlock(&this->delayedCloseMutex); // U N L O C K +} + +/** + * Try to unlock all delayedEntryUnlock files. + */ +void __InternodeSyncer_delayedEntryUnlockComm(InternodeSyncer* this) +{ + PointerListIter iter; + + Mutex_lock(&this->delayedEntryUnlockMutex); // L O C K + + PointerListIter_init(&iter, &this->delayedEntryUnlockQueue); + + while(!PointerListIter_end(&iter) && !Thread_getSelfTerminate( (Thread*)this) ) + { + DelayedEntryUnlockEntry* currentUnlock = PointerListIter_value(&iter); + + EntryInfo* entryInfo; + RemotingIOInfo ioInfo; + FhgfsOpsErr unlockRes; + + __InternodeSyncer_delayedEntryUnlockPrepareRemoting(this, currentUnlock, &entryInfo, &ioInfo); + + // note: unlock, so that more entries can be added to the queue during remoting + Mutex_unlock(&this->delayedEntryUnlockMutex); // U N L O C K + + unlockRes = FhgfsOpsRemoting_flockEntryEx(entryInfo, NULL, this->app, ioInfo.fileHandleID, + currentUnlock->clientFD, 0, ENTRYLOCKTYPE_CANCEL, false); + + Mutex_lock(&this->delayedEntryUnlockMutex); // R E L O C K + + if(unlockRes == FhgfsOpsErr_COMMUNICATION) + { // comm error => we will try again later + PointerListIter_next(&iter); + } + else + { /* anything other than communication error means our job is done and we can delete the + entry */ + __InternodeSyncer_delayedEntryUnlockFreeEntry(this, currentUnlock); + + iter = PointerListIter_remove(&iter); + } + } + + + Mutex_unlock(&this->delayedEntryUnlockMutex); // U N L O C K +} + +/** + * Try to unlock all delayedRangeUnlock files. + */ +void __InternodeSyncer_delayedRangeUnlockComm(InternodeSyncer* this) +{ + PointerListIter iter; + + Mutex_lock(&this->delayedRangeUnlockMutex); // L O C K + + PointerListIter_init(&iter, &this->delayedRangeUnlockQueue); + + while(!PointerListIter_end(&iter) && !Thread_getSelfTerminate( (Thread*)this) ) + { + DelayedRangeUnlockEntry* currentUnlock = PointerListIter_value(&iter); + + EntryInfo* entryInfo; + RemotingIOInfo ioInfo; + FhgfsOpsErr unlockRes; + + __InternodeSyncer_delayedRangeUnlockPrepareRemoting(this, currentUnlock, &entryInfo, &ioInfo); + + // note: unlock, so that more entries can be added to the queue during remoting + Mutex_unlock(&this->delayedRangeUnlockMutex); // U N L O C K + + unlockRes = FhgfsOpsRemoting_flockRangeEx(entryInfo, NULL, ioInfo.app, ioInfo.fileHandleID, + currentUnlock->ownerPID, ENTRYLOCKTYPE_CANCEL, 0, ~0ULL, false); + + Mutex_lock(&this->delayedRangeUnlockMutex); // R E L O C K + + if(unlockRes == FhgfsOpsErr_COMMUNICATION) + { // comm error => we will try again later + PointerListIter_next(&iter); + } + else + { /* anything other than communication error means our job is done and we can delete the + entry */ + __InternodeSyncer_delayedRangeUnlockFreeEntry(this, currentUnlock); + + iter = PointerListIter_remove(&iter); + } + } + + + Mutex_unlock(&this->delayedRangeUnlockMutex); // U N L O C K +} + + +/** + * Prepare remoting args for delayed close. + * + * Note: This uses only references to the closeEntry, so no cleanup call required. + */ +void __InternodeSyncer_delayedClosePrepareRemoting(InternodeSyncer* this, + DelayedCloseEntry* closeEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo) +{ + *outEntryInfo = &closeEntry->entryInfo; + + outIOInfo->app = this->app; + outIOInfo->fileHandleID = closeEntry->fileHandleID; + outIOInfo->pattern = NULL; + outIOInfo->accessFlags = closeEntry->accessFlags; + outIOInfo->needsAppendLockCleanup = &closeEntry->needsAppendLockCleanup; + outIOInfo->maxUsedTargetIndex = &closeEntry->maxUsedTargetIndex; + outIOInfo->firstWriteDone = NULL; + outIOInfo->userID = 0; + outIOInfo->groupID = 0; +#ifdef BEEGFS_NVFS + outIOInfo->nvfs = false; +#endif +} + +/** + * Prepare remoting args for delayed entry unlock. + * + * Note: This uses only references to the unlockEntry, so no cleanup call required. + */ +void __InternodeSyncer_delayedEntryUnlockPrepareRemoting(InternodeSyncer* this, + DelayedEntryUnlockEntry* unlockEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo) +{ + *outEntryInfo = &unlockEntry->entryInfo; + + outIOInfo->app = this->app; + outIOInfo->fileHandleID = unlockEntry->fileHandleID; + outIOInfo->pattern = NULL; + outIOInfo->accessFlags = 0; + outIOInfo->maxUsedTargetIndex = NULL; + outIOInfo->firstWriteDone = NULL; + outIOInfo->userID = 0; + outIOInfo->groupID = 0; +#ifdef BEEGFS_NVFS + outIOInfo->nvfs = false; +#endif +} + +/** + * Prepare remoting args for delayed range unlock. + * + * Note: This uses only references to the unlockEntry, so no cleanup call required. + */ +void __InternodeSyncer_delayedRangeUnlockPrepareRemoting(InternodeSyncer* this, + DelayedRangeUnlockEntry* unlockEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo) +{ + *outEntryInfo = &unlockEntry->entryInfo; + + outIOInfo->app = this->app; + outIOInfo->fileHandleID = unlockEntry->fileHandleID; + outIOInfo->pattern = NULL; + outIOInfo->accessFlags = 0; + outIOInfo->maxUsedTargetIndex = NULL; + outIOInfo->firstWriteDone = NULL; + outIOInfo->userID = 0; + outIOInfo->groupID = 0; +#ifdef BEEGFS_NVFS + outIOInfo->nvfs = false; +#endif +} + +/** + * Frees/uninits all sub-fields and kfrees the closeEntry itself (but does not remove it from the + * queue). + */ +void __InternodeSyncer_delayedCloseFreeEntry(InternodeSyncer* this, DelayedCloseEntry* closeEntry) +{ + EntryInfo_uninit(&closeEntry->entryInfo); + if (closeEntry->hasEvent) + FileEvent_uninit(&closeEntry->event); + + kfree(closeEntry->fileHandleID); + + AtomicInt_uninit(&closeEntry->maxUsedTargetIndex); + + kfree(closeEntry); +} + +/** + * Frees/uninits all sub-fields and kfrees the unlockEntry itself (but does not remove it from the + * queue). + */ +void __InternodeSyncer_delayedEntryUnlockFreeEntry(InternodeSyncer* this, + DelayedEntryUnlockEntry* unlockEntry) +{ + EntryInfo_uninit(&unlockEntry->entryInfo); + + kfree(unlockEntry->fileHandleID); + + kfree(unlockEntry); +} + +/** + * Frees/uninits all sub-fields and kfrees the unlockEntry itself (but does not remove it from the + * queue). + */ +void __InternodeSyncer_delayedRangeUnlockFreeEntry(InternodeSyncer* this, + DelayedRangeUnlockEntry* unlockEntry) +{ + EntryInfo_uninit(&unlockEntry->entryInfo); + + kfree(unlockEntry->fileHandleID); + + kfree(unlockEntry); +} + +/** + * Add an entry that could not be closed on the server due to communication error and should be + * retried again later. + * + * @param entryInfo will be copied + * @param ioInfo will be copied + */ +void InternodeSyncer_delayedCloseAdd(InternodeSyncer* this, const EntryInfo* entryInfo, + const RemotingIOInfo* ioInfo, struct FileEvent* event) +{ + DelayedCloseEntry* newClose = (DelayedCloseEntry*)os_kmalloc(sizeof(*newClose) ); + + Time_init(&newClose->ageT); + + EntryInfo_dup(entryInfo, &newClose->entryInfo); + + newClose->fileHandleID = StringTk_strDup(ioInfo->fileHandleID); + newClose->accessFlags = ioInfo->accessFlags; + + newClose->needsAppendLockCleanup = + (ioInfo->needsAppendLockCleanup && *ioInfo->needsAppendLockCleanup); + + AtomicInt_init(&newClose->maxUsedTargetIndex, AtomicInt_read(ioInfo->maxUsedTargetIndex) ); + + newClose->hasEvent = event != NULL; + if (event) + newClose->event = *event; + + Mutex_lock(&this->delayedCloseMutex); // L O C K + + PointerList_append(&this->delayedCloseQueue, newClose); + + Mutex_unlock(&this->delayedCloseMutex); // U N L O C K +} + +/** + * Add an entry that could not be unlocked on the server due to communication error and should be + * retried again later. + * + * @param entryInfo will be copied + * @param ioInfo will be copied + */ +void InternodeSyncer_delayedEntryUnlockAdd(InternodeSyncer* this, const EntryInfo* entryInfo, + const RemotingIOInfo* ioInfo, int64_t clientFD) +{ + DelayedEntryUnlockEntry* newUnlock = (DelayedEntryUnlockEntry*)os_kmalloc(sizeof(*newUnlock) ); + + Time_init(&newUnlock->ageT); + + EntryInfo_dup(entryInfo, &newUnlock->entryInfo); + + newUnlock->fileHandleID = StringTk_strDup(ioInfo->fileHandleID); + newUnlock->clientFD = clientFD; + + Mutex_lock(&this->delayedEntryUnlockMutex); // L O C K + + PointerList_append(&this->delayedEntryUnlockQueue, newUnlock); + + Mutex_unlock(&this->delayedEntryUnlockMutex); // U N L O C K +} + +/** + * Add an entry that could not be unlocked on the server due to communication error and should be + * retried again later. + * + * @param entryInfo will be copied + * @param ioInfo will be copied + */ +void InternodeSyncer_delayedRangeUnlockAdd(InternodeSyncer* this, const EntryInfo* entryInfo, + const RemotingIOInfo* ioInfo, int ownerPID) +{ + DelayedRangeUnlockEntry* newUnlock = (DelayedRangeUnlockEntry*)os_kmalloc(sizeof(*newUnlock) ); + + Time_init(&newUnlock->ageT); + + EntryInfo_dup(entryInfo, &newUnlock->entryInfo); + + newUnlock->fileHandleID = StringTk_strDup(ioInfo->fileHandleID); + newUnlock->ownerPID = ownerPID; + + Mutex_lock(&this->delayedRangeUnlockMutex); // L O C K + + PointerList_append(&this->delayedRangeUnlockQueue, newUnlock); + + Mutex_unlock(&this->delayedRangeUnlockMutex); // U N L O C K +} + +size_t InternodeSyncer_getDelayedCloseQueueSize(InternodeSyncer* this) +{ + size_t retVal; + + Mutex_lock(&this->delayedCloseMutex); // L O C K + + retVal = PointerList_length(&this->delayedCloseQueue); + + Mutex_unlock(&this->delayedCloseMutex); // U N L O C K + + return retVal; +} + +size_t InternodeSyncer_getDelayedEntryUnlockQueueSize(InternodeSyncer* this) +{ + size_t retVal; + + Mutex_lock(&this->delayedEntryUnlockMutex); // L O C K + + retVal = PointerList_length(&this->delayedEntryUnlockQueue); + + Mutex_unlock(&this->delayedEntryUnlockMutex); // U N L O C K + + return retVal; +} + +size_t InternodeSyncer_getDelayedRangeUnlockQueueSize(InternodeSyncer* this) +{ + size_t retVal; + + Mutex_lock(&this->delayedRangeUnlockMutex); // L O C K + + retVal = PointerList_length(&this->delayedRangeUnlockQueue); + + Mutex_unlock(&this->delayedRangeUnlockMutex); // U N L O C K + + return retVal; +} + +/** + * Drop/reset idle conns from all server stores. + */ +void __InternodeSyncer_dropIdleConns(InternodeSyncer* this) +{ + Logger* log = App_getLogger(this->app); + const char* logContext = "Idle disconnect"; + + unsigned numDroppedConns = 0; + + numDroppedConns += __InternodeSyncer_dropIdleConnsByStore(this, App_getMgmtNodes(this->app) ); + numDroppedConns += __InternodeSyncer_dropIdleConnsByStore(this, App_getMetaNodes(this->app) ); + numDroppedConns += __InternodeSyncer_dropIdleConnsByStore(this, App_getStorageNodes(this->app) ); + + if(numDroppedConns) + { + Logger_logFormatted(log, Log_DEBUG, logContext, "Dropped idle connections: %u", + numDroppedConns); + } +} + +/** + * Walk over all nodes in the given store and drop/reset idle connections. + * + * @return number of dropped connections + */ +unsigned __InternodeSyncer_dropIdleConnsByStore(InternodeSyncer* this, NodeStoreEx* nodes) +{ + unsigned numDroppedConns = 0; + + Node* node = NodeStoreEx_referenceFirstNode(nodes); + while(node) + { + NodeConnPool* connPool = Node_getConnPool(node); + + numDroppedConns += NodeConnPool_disconnectAndResetIdleStreams(connPool); + + node = NodeStoreEx_referenceNextNodeAndReleaseOld(nodes, node); // iterate to next node + } + + return numDroppedConns; +} + diff --git a/client_module/source/components/InternodeSyncer.h b/client_module/source/components/InternodeSyncer.h new file mode 100644 index 0000000..b14ac0a --- /dev/null +++ b/client_module/source/components/InternodeSyncer.h @@ -0,0 +1,209 @@ +#ifndef INTERNODESYNCER_H_ +#define INTERNODESYNCER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/* + * note: Delayed...Entry-queues management is integrated into InternodeSyncer, because it needs + * direct access to the queues via iterators and relies on special access patterns, e.g. + * it must be the only one removing entries from the queue (to keep iterators valid). + */ + + +// forward declarations... + +struct InternodeSyncer; +typedef struct InternodeSyncer InternodeSyncer; + +struct DelayedCloseEntry; +typedef struct DelayedCloseEntry DelayedCloseEntry; +struct DelayedEntryUnlockEntry; +typedef struct DelayedEntryUnlockEntry DelayedEntryUnlockEntry; +struct DelayedRangeUnlockEntry; +typedef struct DelayedRangeUnlockEntry DelayedRangeUnlockEntry; + + +extern void InternodeSyncer_init(InternodeSyncer* this, App* app); +extern InternodeSyncer* InternodeSyncer_construct(App* app); +extern void InternodeSyncer_uninit(InternodeSyncer* this); +extern void InternodeSyncer_destruct(InternodeSyncer* this); + +extern bool InternodeSyncer_waitForMgmtInit(InternodeSyncer* this, int timeoutMS); + +extern void InternodeSyncer_delayedCloseAdd(InternodeSyncer* this, const EntryInfo* entryInfo, + const RemotingIOInfo* ioInfo, struct FileEvent* event); +extern void InternodeSyncer_delayedEntryUnlockAdd(InternodeSyncer* this, + const EntryInfo* entryInfo, const RemotingIOInfo* ioInfo, int64_t clientFD); +extern void InternodeSyncer_delayedRangeUnlockAdd(InternodeSyncer* this, + const EntryInfo* entryInfo, const RemotingIOInfo* ioInfo, int ownerPID); + + +extern void _InternodeSyncer_requestLoop(InternodeSyncer* this); + + +extern void __InternodeSyncer_run(Thread* this); + +extern void __InternodeSyncer_signalMgmtInitDone(InternodeSyncer* this); +extern void __InternodeSyncer_mgmtInit(InternodeSyncer* this); +extern bool __InternodeSyncer_waitForMgmtHeartbeat(InternodeSyncer* this); + +extern bool __InternodeSyncer_registerNode(InternodeSyncer* this); +extern void __InternodeSyncer_reregisterNode(InternodeSyncer* this); +extern bool __InternodeSyncer_unregisterNode(InternodeSyncer* this); + +extern void __InternodeSyncer_downloadAndSyncNodes(InternodeSyncer* this); +extern void __InternodeSyncer_printSyncResults(InternodeSyncer* this, NodeType nodeType, + NumNodeIDList* addedNodes, NumNodeIDList* removedNodes); +extern void __InternodeSyncer_downloadAndSyncTargetMappings(InternodeSyncer* this); + +extern void __InternodeSyncer_delayedCloseComm(InternodeSyncer* this); +extern void __InternodeSyncer_delayedEntryUnlockComm(InternodeSyncer* this); +extern void __InternodeSyncer_delayedRangeUnlockComm(InternodeSyncer* this); + +extern void __InternodeSyncer_delayedClosePrepareRemoting(InternodeSyncer* this, + DelayedCloseEntry* closeEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo); +extern void __InternodeSyncer_delayedEntryUnlockPrepareRemoting(InternodeSyncer* this, + DelayedEntryUnlockEntry* closeEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo); +extern void __InternodeSyncer_delayedRangeUnlockPrepareRemoting(InternodeSyncer* this, + DelayedRangeUnlockEntry* closeEntry, EntryInfo** outEntryInfo, RemotingIOInfo* outIOInfo); + +extern void __InternodeSyncer_delayedCloseFreeEntry(InternodeSyncer* this, + DelayedCloseEntry* closeEntry); +extern void __InternodeSyncer_delayedEntryUnlockFreeEntry(InternodeSyncer* this, + DelayedEntryUnlockEntry* closeEntry); +extern void __InternodeSyncer_delayedRangeUnlockFreeEntry(InternodeSyncer* this, + DelayedRangeUnlockEntry* closeEntry); + +extern void __InternodeSyncer_dropIdleConns(InternodeSyncer* this); +extern unsigned __InternodeSyncer_dropIdleConnsByStore(InternodeSyncer* this, NodeStoreEx* nodes); + +extern void __InternodeSyncer_updateTargetStatesAndBuddyGroups(InternodeSyncer* this, + NodeType nodeType); +extern bool __InternodeSyncer_checkNetwork(InternodeSyncer* this); + +// getters & setters +static inline void InternodeSyncer_setForceTargetStatesUpdate(InternodeSyncer* this); +static inline bool InternodeSyncer_getAndResetForceTargetStatesUpdate(InternodeSyncer* this); +extern size_t InternodeSyncer_getDelayedCloseQueueSize(InternodeSyncer* this); +extern size_t InternodeSyncer_getDelayedEntryUnlockQueueSize(InternodeSyncer* this); +extern size_t InternodeSyncer_getDelayedRangeUnlockQueueSize(InternodeSyncer* this); + + +struct DelayedCloseEntry +{ + Time ageT; // time when this entry was created (to compute entry age) + + EntryInfo entryInfo; + + // fields for RemotingIOInfo + const char* fileHandleID; + unsigned accessFlags; // OPENFILE_ACCESS_... flags + + bool needsAppendLockCleanup; + AtomicInt maxUsedTargetIndex; // (used as a reference in ioInfo) + + bool hasEvent; + struct FileEvent event; +}; + +struct DelayedEntryUnlockEntry +{ + Time ageT; // time when this entry was created (to compute entry age) + + EntryInfo entryInfo; + + // fields for RemotingIOInfo + const char* fileHandleID; + + int64_t clientFD; +}; + +struct DelayedRangeUnlockEntry +{ + Time ageT; // time when this entry was created (to compute entry age) + + EntryInfo entryInfo; + + // fields for RemotingIOInfo + const char* fileHandleID; + + int ownerPID; +}; + + +struct InternodeSyncer +{ + Thread thread; // base class + + App* app; + Config* cfg; + + DatagramListener* dgramLis; + + NodeStoreEx* mgmtNodes; + NodeStoreEx* metaNodes; + NodeStoreEx* storageNodes; + + bool mgmtInitDone; + Mutex mgmtInitDoneMutex; + Condition mgmtInitDoneCond; // signaled when init is done (doesn't mean it was successful) + + bool nodeRegistered; // true if the mgmt host ack'ed our heartbeat + + bool forceTargetStatesUpdate; // force an update of target states + Time lastSuccessfulTargetStatesUpdateT; + unsigned targetOfflineTimeoutMS; + + Mutex delayedCloseMutex; // for delayedCloseQueue + PointerList delayedCloseQueue; /* remove from head, add to tail (important, because we rely on + validity of an iterator even when a new entry was added) */ + Mutex delayedEntryUnlockMutex; // for delayedEntryUnlockQueue + PointerList delayedEntryUnlockQueue; /* remove from head, add to tail (important, because we + rely on validity of an iterator even when a new entry was added) */ + Mutex delayedRangeUnlockMutex; // for delayedRangeUnlockQueue + PointerList delayedRangeUnlockQueue; /* remove from head, add to tail (important, because we + rely on validity of an iterator even when a new entry was added) */ + Mutex forceTargetStatesUpdateMutex; // for forceTargetStates +}; + + +void InternodeSyncer_setForceTargetStatesUpdate(InternodeSyncer* this) +{ + Mutex_lock(&this->forceTargetStatesUpdateMutex); // L O C K + + this->forceTargetStatesUpdate = true; + + Mutex_unlock(&this->forceTargetStatesUpdateMutex); // U N L O C K +} + +bool InternodeSyncer_getAndResetForceTargetStatesUpdate(InternodeSyncer* this) +{ + bool retVal; + + Mutex_lock(&this->forceTargetStatesUpdateMutex); // L O C K + + retVal = this->forceTargetStatesUpdate; + this->forceTargetStatesUpdate = false; + + Mutex_unlock(&this->forceTargetStatesUpdateMutex); // U N L O C K + + return retVal; +} + +#endif /*INTERNODESYNCER_H_*/ diff --git a/client_module/source/components/worker/RWPagesWork.c b/client_module/source/components/worker/RWPagesWork.c new file mode 100644 index 0000000..7221815 --- /dev/null +++ b/client_module/source/components/worker/RWPagesWork.c @@ -0,0 +1,188 @@ +#include +#include +#include + +#include "RWPagesWork.h" + +#define RWPagesWorkQueue_SUB_NAME BEEGFS_MODULE_NAME_STR "-rwPgWQ" // read-pages-work-queue + +static struct workqueue_struct* rwPagesWorkQueue = NULL; + + +static void RWPagesWork_processQueue(RWPagesWork* this); +static bool RWPagesWork_queue(RWPagesWork *this); + +static FhgfsOpsErr _RWPagesWork_initReferenceFile(struct inode* inode, Fhgfs_RWType rwType, + FileHandleType* outHandleType, RemotingIOInfo* outIOInfo); + +bool RWPagesWork_initworkQueue(void) +{ + rwPagesWorkQueue = create_workqueue(RWPagesWorkQueue_SUB_NAME); + + return !!rwPagesWorkQueue; +} + +void RWPagesWork_destroyWorkQueue(void) +{ + if (rwPagesWorkQueue) + { + flush_workqueue(rwPagesWorkQueue); + destroy_workqueue(rwPagesWorkQueue); + } +} + +void RWPagesWork_flushWorkQueue(void) +{ + if (rwPagesWorkQueue) + flush_workqueue(rwPagesWorkQueue); +} + + +bool RWPagesWork_queue(RWPagesWork *this) +{ + return queue_work(rwPagesWorkQueue, &this->kernelWork); +} + +bool RWPagesWork_init(RWPagesWork* this, App* app, struct inode* inode, + FhgfsChunkPageVec *pageVec, Fhgfs_RWType rwType) +{ + FhgfsOpsErr referenceRes; + + this->app = app; + this->inode = inode; + this->pageVec = pageVec; + this->rwType = rwType; + + referenceRes = _RWPagesWork_initReferenceFile(inode, rwType, &this->handleType, &this->ioInfo); + + if (unlikely(referenceRes != FhgfsOpsErr_SUCCESS) ) + return false; + + INIT_WORK(&this->kernelWork, RWPagesWork_process); + + return true; +} + +/** + * Init helper function to reference a file. + * + * Note: The file is already supposed to be referenced by the FhgfsOpsPages_readpages or + * FhgfsOpsPages_writepages, so file referencing is not supposed to fail + */ +FhgfsOpsErr _RWPagesWork_initReferenceFile(struct inode* inode, Fhgfs_RWType rwType, + FileHandleType* outHandleType, RemotingIOInfo* outIOInfo) +{ + FhgfsOpsErr referenceRes; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + int openFlags = (rwType == BEEGFS_RWTYPE_WRITE) ? OPENFILE_ACCESS_WRITE : OPENFILE_ACCESS_READ; + + referenceRes = FhgfsInode_referenceHandle(fhgfsInode, NULL, openFlags, true, NULL, + outHandleType, NULL); + + if (unlikely(referenceRes != FhgfsOpsErr_SUCCESS) ) + { // failure + printk_fhgfs(KERN_INFO, "Bug: file not referenced"); + dump_stack(); + } + else + { // success + + //get the right openFlags (might have changed to OPENFILE_ACCESS_READWRITE) + openFlags = FhgfsInode_handleTypeToOpenFlags(*outHandleType); + + FhgfsInode_getRefIOInfo(fhgfsInode, *outHandleType, openFlags, outIOInfo); + } + + return referenceRes; +} + + +/** + * Process the work queue + */ +void RWPagesWork_process(struct work_struct* work) +{ + RWPagesWork* thisCast = (RWPagesWork*)work; + + RWPagesWork_processQueue(thisCast); +} + +/** + * Needed for old INIT_WORK() with 3 parameters (before 2.6.20) + */ +void RWPagesWork_oldProcess(void* data) +{ + struct work_struct* work = (struct work_struct*) data; + + return RWPagesWork_process(work); +} + +/** + * Build worker queues + */ +bool RWPagesWork_createQueue(App* app, FhgfsChunkPageVec* pageVec, struct inode* inode, + Fhgfs_RWType rwType) +{ + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + bool retVal = true; + + RWPagesWork* work; + + work = RWPagesWork_construct(app, inode, pageVec, rwType); + if (likely(work) ) + { + bool queueRes; + + queueRes = RWPagesWork_queue(work); + if (!queueRes) + { + Logger_logErr(log, logContext, "RWPagesWork_construct failed."); + + if (rwType == BEEGFS_RWTYPE_READ) + FhgfsChunkPageVec_iterateAllHandleReadErr(pageVec); + else + FhgfsChunkPageVec_iterateAllHandleWritePages(pageVec, -EIO); + + RWPagesWork_destruct(work); + } + } + + if (unlikely(!work)) + { // Creating the work-queue failed + + Logger_logErr(log, logContext, "Failed to create work queue."); + + retVal = false; + } + + return retVal; + +} + +/** + * Process a request from the queue + */ +void RWPagesWork_processQueue(RWPagesWork* this) +{ + App* app = this->app; + Logger* log = App_getLogger(app); + + ssize_t rwRes; + + rwRes = FhgfsOpsRemoting_rwChunkPageVec(this->pageVec, &this->ioInfo, this->rwType); + + if (unlikely(rwRes < 0) ) + LOG_DEBUG_FORMATTED(log, 1, __func__, "error: %s", FhgfsOpsErr_toErrString(-rwRes) ); + else + { + LOG_DEBUG_FORMATTED(log, 5, __func__, "rwRes: %zu", rwRes ); + IGNORE_UNUSED_VARIABLE(log); + } + + RWPagesWork_destruct(this); +} + + diff --git a/client_module/source/components/worker/RWPagesWork.h b/client_module/source/components/worker/RWPagesWork.h new file mode 100644 index 0000000..58dc96e --- /dev/null +++ b/client_module/source/components/worker/RWPagesWork.h @@ -0,0 +1,104 @@ +#ifndef RWPAGESWORK_H_ +#define RWPAGESWORK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +struct RWPagesWork; +typedef struct RWPagesWork RWPagesWork; + +extern bool RWPagesWork_createQueue(App* app, FhgfsChunkPageVec* pageVec, struct inode* inode, + Fhgfs_RWType rwType); + +bool RWPagesWork_init(RWPagesWork* this, App* app, struct inode* inode, + FhgfsChunkPageVec *pageVec, Fhgfs_RWType rwType); +static inline RWPagesWork* RWPagesWork_construct(App* app, struct inode* inode, + FhgfsChunkPageVec *pageVec, Fhgfs_RWType rwType); +static inline void RWPagesWork_uninit(RWPagesWork* this); +static inline void RWPagesWork_destruct(RWPagesWork* this); + +extern bool RWPagesWork_initworkQueue(void); +extern void RWPagesWork_destroyWorkQueue(void); +extern void RWPagesWork_flushWorkQueue(void); + + +// virtual functions +extern void RWPagesWork_process(struct work_struct* work); +extern void RWPagesWork_oldProcess(void* data); + +struct RWPagesWork +{ + struct work_struct kernelWork; + + App* app; + + FhgfsChunkPageVec* pageVec; + + RemotingIOInfo ioInfo; + + FileHandleType handleType; + Fhgfs_RWType rwType; + + struct inode* inode; +}; + + +/** + * RWPagesWork Constructor + */ +struct RWPagesWork* RWPagesWork_construct(App* app, struct inode* inode, + FhgfsChunkPageVec * pageVec, Fhgfs_RWType rwType) +{ + bool initRes; + + struct RWPagesWork* this = (RWPagesWork*)os_kmalloc(sizeof(*this) ); + if (unlikely(!this) ) + return NULL; + + initRes = RWPagesWork_init(this, app, inode, pageVec, rwType); + + if (unlikely(!initRes) ) + { // uninitilize everything that already has been initialized and free this + + if (rwType == BEEGFS_RWTYPE_READ) + FhgfsChunkPageVec_iterateAllHandleReadErr(pageVec); + else + FhgfsChunkPageVec_iterateAllHandleWritePages(pageVec, -EIO); + + RWPagesWork_destruct(this); + this = NULL; + } + + return this; +} + +/** + * Unitinialize worker data + */ +void RWPagesWork_uninit(RWPagesWork* this) +{ + FhgfsInode* fhgfsInode = BEEGFS_INODE(this->inode); + + FhgfsChunkPageVec_destroy(this->pageVec); + + FhgfsInode_releaseHandle(fhgfsInode, this->handleType, NULL); +} + +void RWPagesWork_destruct(RWPagesWork* this) +{ + RWPagesWork_uninit(this); + kfree(this); +} + +#endif /* RWPAGESWORK_H_ */ diff --git a/client_module/source/fault-inject/fault-inject.c b/client_module/source/fault-inject/fault-inject.c new file mode 100644 index 0000000..6ba039e --- /dev/null +++ b/client_module/source/fault-inject/fault-inject.c @@ -0,0 +1,89 @@ +#include "fault-inject.h" + +#include + +#if defined(BEEGFS_DEBUG) && defined(CONFIG_FAULT_INJECTION) + +static struct dentry* debug_dir; +static struct dentry* fault_dir; + +#define BEEGFS_DECLARE_FAULT_ATTR(name) DECLARE_FAULT_ATTR(beegfs_fault_ ## name) + +BEEGFS_DECLARE_FAULT_ATTR(readpage); +BEEGFS_DECLARE_FAULT_ATTR(writepage); + +BEEGFS_DECLARE_FAULT_ATTR(write_force_cache_bypass); +BEEGFS_DECLARE_FAULT_ATTR(read_force_cache_bypass); + +BEEGFS_DECLARE_FAULT_ATTR(commkit_sendheader_timeout); +BEEGFS_DECLARE_FAULT_ATTR(commkit_polltimeout); +BEEGFS_DECLARE_FAULT_ATTR(commkit_recvheader_timeout); + +BEEGFS_DECLARE_FAULT_ATTR(commkit_readfile_receive_timeout); +BEEGFS_DECLARE_FAULT_ATTR(commkit_writefile_senddata_timeout); + +#define INIT_FAULT(name) \ + if(IS_ERR(fault_create_debugfs_attr(#name, fault_dir, &beegfs_fault_ ## name))) \ + goto err_fault_dir; + +#ifndef KERNEL_HAS_FAULTATTR_DNAME +#define DESTROY_FAULT(name) do { } while (0) +#else +#define DESTROY_FAULT(name) \ + do { \ + if((beegfs_fault_ ## name).dname) \ + dput( (beegfs_fault_ ## name).dname); \ + } while (0) +#endif + +bool beegfs_fault_inject_init() +{ + debug_dir = debugfs_create_dir(BEEGFS_MODULE_NAME_STR, NULL); + if(!debug_dir) + goto err_debug_dir; + + fault_dir = debugfs_create_dir("fault", debug_dir); + if(!fault_dir) + goto err_fault_dir; + + INIT_FAULT(readpage); + INIT_FAULT(writepage); + + INIT_FAULT(write_force_cache_bypass); + INIT_FAULT(read_force_cache_bypass); + + INIT_FAULT(commkit_sendheader_timeout); + INIT_FAULT(commkit_polltimeout); + INIT_FAULT(commkit_recvheader_timeout); + + INIT_FAULT(commkit_readfile_receive_timeout); + INIT_FAULT(commkit_writefile_senddata_timeout); + + return true; + +err_fault_dir: + debugfs_remove_recursive(debug_dir); + debug_dir = NULL; +err_debug_dir: + return false; +} + +void beegfs_fault_inject_release() +{ + DESTROY_FAULT(readpage); + DESTROY_FAULT(writepage); + + DESTROY_FAULT(write_force_cache_bypass); + DESTROY_FAULT(read_force_cache_bypass); + + DESTROY_FAULT(commkit_sendheader_timeout); + DESTROY_FAULT(commkit_polltimeout); + DESTROY_FAULT(commkit_recvheader_timeout); + + DESTROY_FAULT(commkit_readfile_receive_timeout); + DESTROY_FAULT(commkit_writefile_senddata_timeout); + + debugfs_remove_recursive(debug_dir); +} + +#endif diff --git a/client_module/source/fault-inject/fault-inject.h b/client_module/source/fault-inject/fault-inject.h new file mode 100644 index 0000000..643c339 --- /dev/null +++ b/client_module/source/fault-inject/fault-inject.h @@ -0,0 +1,51 @@ +#ifndef _fault_inject_fault_inject_h_dBK5RbRnOrGDnIW5Y7zvnv +#define _fault_inject_fault_inject_h_dBK5RbRnOrGDnIW5Y7zvnv + +#include + +#if defined(BEEGFS_DEBUG) && defined(CONFIG_FAULT_INJECTION) + +#include + +#ifndef CONFIG_DEBUG_FS +#error debugfs required for beegfs fault injection +#endif +#ifndef CONFIG_FAULT_INJECTION_DEBUG_FS +#error CONFIG_FAULT_INJECTION_DEBUG_FS required for beegfs fault injection +#endif + +#define BEEGFS_SHOULD_FAIL(name, size) should_fail(&beegfs_fault_ ## name, size) +#define BEEGFS_DEFINE_FAULT(name) extern struct fault_attr beegfs_fault_ ## name + +BEEGFS_DEFINE_FAULT(readpage); +BEEGFS_DEFINE_FAULT(writepage); + +BEEGFS_DEFINE_FAULT(write_force_cache_bypass); +BEEGFS_DEFINE_FAULT(read_force_cache_bypass); + +BEEGFS_DEFINE_FAULT(commkit_sendheader_timeout); +BEEGFS_DEFINE_FAULT(commkit_polltimeout); +BEEGFS_DEFINE_FAULT(commkit_recvheader_timeout); + +BEEGFS_DEFINE_FAULT(commkit_readfile_receive_timeout); +BEEGFS_DEFINE_FAULT(commkit_writefile_senddata_timeout); + +extern bool beegfs_fault_inject_init(void); +extern void beegfs_fault_inject_release(void); + +#else // BEEGFS_DEBUG + +#define BEEGFS_SHOULD_FAIL(name, size) (false) + +static inline bool beegfs_fault_inject_init(void) +{ + return true; +} + +static inline void beegfs_fault_inject_release(void) +{ +} + +#endif // BEEGFS_DEBUG + +#endif diff --git a/client_module/source/filesystem/FhgfsInode.c b/client_module/source/filesystem/FhgfsInode.c new file mode 100644 index 0000000..105f01f --- /dev/null +++ b/client_module/source/filesystem/FhgfsInode.c @@ -0,0 +1,647 @@ +#include +#include +#include +#include +#include "FhgfsOpsSuper.h" +#include "FhgfsInode.h" + + +/** + * Called once for initialization of new inodes (and not again if they are recycled) when they + * are pre-alloced by the mem cache. + * + * Note: Initializes only the FhGFS part of the inode, not the general VFS part. + */ +void FhgfsInode_initOnce(FhgfsInode* fhgfsInode) +{ + RWLock_init(&fhgfsInode->entryInfoLock); + Mutex_init(&fhgfsInode->fileHandlesMutex); + Mutex_init(&fhgfsInode->rangeLockPIDsMutex); + AtomicInt_init(&fhgfsInode->numRangeLockPIDs, 0); + IntMap_init(&fhgfsInode->rangeLockPIDs); + Mutex_init(&fhgfsInode->appendMutex); + AtomicInt_init(&fhgfsInode->appendFDsOpen, 0); + RWLock_init(&fhgfsInode->fileCacheLock); + FhgfsInode_setNumDirtyPages(fhgfsInode, 0); +} + +/** + * Called each time an inode is alloced from the mem cache. + * + * Note: Initializes only the FhGFS part of the inode, not the general VFS part. + */ +void FhgfsInode_allocInit(FhgfsInode* fhgfsInode) +{ + /* note: keep in mind that we don't only do first-time init here, but also reset everything that + might have been left over after re-cycling */ + + int i; + + memset(&fhgfsInode->entryInfo, 0, sizeof(fhgfsInode->entryInfo) ); + + fhgfsInode->parentNodeID = (NumNodeID){0}; + + memset(&fhgfsInode->pathInfo, 0, sizeof(fhgfsInode->pathInfo) ); + + Time_init(&fhgfsInode->dataCacheTime); + + memset(&fhgfsInode->fileHandles, 0, sizeof(fhgfsInode->fileHandles) ); + + for(i=0; i < BEEGFS_INODE_FILEHANDLES_NUM; i++) + { + fhgfsInode->fileHandles[i].needsAppendLockCleanup = false; + + AtomicInt_set(&(fhgfsInode->fileHandles[i].maxUsedTargetIndex), -1); + + // not zeroed at this place, referenceHandle() will zero the BitStore + BitStore_init(&(fhgfsInode->fileHandles[i].firstWriteDone), false); + } + + fhgfsInode->pattern = NULL; + + AtomicInt_set(&fhgfsInode->numRangeLockPIDs, 0); + + fhgfsInode->fileCacheBuffer.buf = NULL; + fhgfsInode->fileCacheBuffer.bufType = FileBufferType_NONE; + + FhgfsInode_setNumDirtyPages(fhgfsInode, 0); + + AtomicInt_set(&fhgfsInode->writeBackCounter, 0); + AtomicInt_set(&fhgfsInode->noRemoteIsizeDecrease, 0); + AtomicInt64_set(&fhgfsInode->lastWriteBackEndOrIsizeWriteTime, 0); + + fhgfsInode->flags = 0; + fhgfsInode->fileVersion = 0; + fhgfsInode->metaVersion = 0; + atomic_set(&fhgfsInode->modified, 0); + atomic_set(&fhgfsInode->coRWInProg, 0); +} + +/** + * Called each time an inode is returned to the mem cache. + * + * Note: Destroys only the FhGFS part of the inode, not the general VFS part. + */ +void FhgfsInode_destroyUninit(FhgfsInode* fhgfsInode) +{ + /* note: keep in mind that we shouldn't reset everything here because we don't know whether this + inode will ever be recycled. but we definitely need to free all alloc'ed stuff here. */ + int i; + + EntryInfo_uninit(&fhgfsInode->entryInfo); + + PathInfo_uninit(&fhgfsInode->pathInfo); + + SAFE_DESTRUCT_NOSET(fhgfsInode->pattern, StripePattern_virtualDestruct); + + IntMap_clear(&fhgfsInode->rangeLockPIDs); + + for(i=0; i < BEEGFS_INODE_FILEHANDLES_NUM; i++) + { + BitStore_uninit(&fhgfsInode->fileHandles[i].firstWriteDone); + } +} + +/** + * @param openFlags OPENFILE_ACCESS_... flags + * @param allowRWHandle true if you specified read or write flags and would also accept + * a rw handle to save the overhead for open/close (only appropriate for writepage etc., use + * _handleTypeToOpenFlags() in this case when you get the RemoteIOInfo). + * @param handleType pass this to releaseHandle() + * @param lookupInfo NULL if this is 'normal' open, non-NULL if the file was already remotely opened + * by lookup-intent or atomic_open + * @param outFileHandleID caller may not free or modify (it's not alloced for the caller!) + */ +FhgfsOpsErr FhgfsInode_referenceHandle(FhgfsInode* this, struct dentry* dentry, int openFlags, + bool allowRWHandle, LookupIntentInfoOut* lookupInfo, FileHandleType* outHandleType, + uint32_t* outVersion) +{ + App* app = FhgfsOps_getApp(this->vfs_inode.i_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsInode_referenceHandle"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + FhgfsInodeFileHandle* desiredHandle; + FhgfsInodeFileHandle* rwHandle = &this->fileHandles[FileHandleType_RW]; + + *outHandleType = __FhgfsInode_openFlagsToHandleType(openFlags); + desiredHandle = &this->fileHandles[*outHandleType]; + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, NULL, &this->vfs_inode, logContext, "handle-type: %d", + *outHandleType); + + Mutex_lock(&this->fileHandlesMutex); // L O C K + + if(desiredHandle->refCount) + { // desired handle exists => return it + retVal = __FhgfsInode_referenceTrunc(this, app, openFlags, dentry); + if (retVal == FhgfsOpsErr_SUCCESS && outVersion) + retVal = FhgfsOpsRemoting_getFileVersion(app, &this->entryInfo, outVersion); + if(retVal != FhgfsOpsErr_SUCCESS) + goto err_unlock; + + + desiredHandle->refCount++; + //*outFileHandleID = desiredHandle->fileHandleID; + } + else + if(allowRWHandle && rwHandle->refCount) + { // rw handle allowed and exists => return it + retVal = __FhgfsInode_referenceTrunc(this, app, openFlags, dentry); + if (retVal == FhgfsOpsErr_SUCCESS && outVersion) + retVal = FhgfsOpsRemoting_getFileVersion(app, &this->entryInfo, outVersion); + if(retVal != FhgfsOpsErr_SUCCESS) + goto err_unlock; + + rwHandle->refCount++; + //*outFileHandleID = rwHandle->fileHandleID; + *outHandleType = FileHandleType_RW; + } + else + { // no matching handle yet => open file to create a new handle + + RemotingIOInfo ioInfo; + const EntryInfo* entryInfo; + PathInfo* pathInfo = FhgfsInode_getPathInfo(this); + + if (lookupInfo) + { // file already open by lookup/atomic_open + entryInfo = lookupInfo->entryInfoPtr; + ioInfo.fileHandleID = lookupInfo->fileHandleID; + ioInfo.pattern = lookupInfo->stripePattern; + PathInfo_update(pathInfo, &lookupInfo->pathInfo); + + retVal = FhgfsOpsErr_SUCCESS; + } + else + { // file yet opened by lookup or atomic open + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + __FhgfsInode_initOpenIOInfo(this, desiredHandle, openFlags, pathInfo, &ioInfo); + + FhgfsInode_entryInfoReadLock(this); // LOCK EntryInfo + + // Prioritize the evaluation of read-write flags to determine the appropriate event type. + // The order of assessment is critical as only one event will be dispatched for a + // file's open() operation, contingent upon the supplied read-write flags. + + // The sequence for evaluating read-write flags is as follows: + // 1. Read and Write + // 2. Write Only + // 3. Read Only + if ((openFlags & OPENFILE_ACCESS_READWRITE) && + (app->cfg->eventLogMask & EventLogMask_OPEN_READ_WRITE)) + { + FileEvent_init(&event, FileEventType_OPEN_READ_WRITE, dentry); + eventSent = &event; + } + else if ((openFlags & OPENFILE_ACCESS_WRITE) && + (app->cfg->eventLogMask & EventLogMask_OPEN_WRITE)) + { + FileEvent_init(&event, FileEventType_OPEN_WRITE, dentry); + eventSent = &event; + } + else if ((openFlags & OPENFILE_ACCESS_READ) && + (app->cfg->eventLogMask & EventLogMask_OPEN_READ)) + { + FileEvent_init(&event, FileEventType_OPEN_READ, dentry); + eventSent = &event; + } + + if ((openFlags & OPENFILE_ACCESS_TRUNC) && (app->cfg->eventLogMask & EventLogMask_TRUNC)) + { + FileEvent_init(&event, FileEventType_TRUNCATE, dentry); + eventSent = &event; + } + + entryInfo = FhgfsInode_getEntryInfo(this); + + retVal = FhgfsOpsRemoting_openfile(entryInfo, &ioInfo, outVersion, eventSent); + + FileEvent_uninit(&event); + + FhgfsInode_entryInfoReadUnlock(this); // UNLOCK EntryInfo + } + + + if(retVal == FhgfsOpsErr_SUCCESS) + { + unsigned stripeCount; + + desiredHandle->fileHandleID = ioInfo.fileHandleID; + this->pattern = ioInfo.pattern; + + stripeCount = FhgfsInode_getStripeCount(this); + BitStore_setSize(&desiredHandle->firstWriteDone, stripeCount); + BitStore_clearBits(&desiredHandle->firstWriteDone); + + desiredHandle->refCount++; + //*outFileHandleID = ioInfo.fileHandleID; + } + + } + + +err_unlock: + + // clean up + + Mutex_unlock(&this->fileHandlesMutex); // U N L O C K + + return retVal; +} + +/** + * Note: even if this results in a (remote) error, the ref count will be decreased and the caller + * may no longer access the corresponding ressources. + * + * @param handleType what you got back from _referenceHandle() + */ +FhgfsOpsErr FhgfsInode_releaseHandle(FhgfsInode* this, FileHandleType handleType, + struct dentry* dentry) +{ + App* app = FhgfsOps_getApp(this->vfs_inode.i_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsInode_releaseHandle"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + FhgfsInodeFileHandle* handle = &this->fileHandles[handleType]; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, NULL, &this->vfs_inode, logContext); + + Mutex_lock(&this->fileHandlesMutex); // L O C K + + if(unlikely(!handle->refCount) ) + BEEGFS_BUG_ON(true, "refCount already 0"); // refcount already zero => bug + else + { // positive refCount + handle->refCount--; + + if(!handle->refCount) + { // we dropped the last reference => close remote + int openFlags = FhgfsInode_handleTypeToOpenFlags(handleType); + RemotingIOInfo ioInfo; + const EntryInfo* entryInfo; + PathInfo* pathInfo = NULL; // not required here + struct FileEvent event = FILEEVENT_EMPTY; + struct FileEvent* eventSent = NULL; + + __FhgfsInode_initOpenIOInfo(this, handle, openFlags, pathInfo, &ioInfo); + + FhgfsInode_entryInfoReadLock(this); // LOCK EntryInfo + + entryInfo = FhgfsInode_getEntryInfo(this); + + if (handleType != FileHandleType_READ && (app->cfg->eventLogMask & EventLogMask_CLOSE)) + { + FileEvent_init(&event, FileEventType_CLOSE_WRITE, dentry); + eventSent = &event; + } + + // &event is destroyed by callee + retVal = FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, &ioInfo, eventSent); + + FhgfsInode_entryInfoReadUnlock(this); // UNLOCK EntryInfo + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, + "handleID: %s remoting complete. result: %s", handle->fileHandleID, + FhgfsOpsErr_toErrString(retVal) ); + + // clean up + kfree(handle->fileHandleID); + handle->needsAppendLockCleanup = false; + AtomicInt_set(&handle->maxUsedTargetIndex, -1); + BitStore_setSize(&handle->firstWriteDone, 0); // free extra mem from very high stripe count + } + } + + Mutex_unlock(&this->fileHandlesMutex); // U N L O C K + + + + return retVal; +} + + +bool FhgfsInode_hasWriteHandle(FhgfsInode* this) +{ + bool retVal = false; + FileHandleType handleType; + FhgfsInodeFileHandle* handle; + + Mutex_lock(&this->fileHandlesMutex); // L O C K + + handleType = FileHandleType_WRITE; + handle = &this->fileHandles[handleType]; + if (handle->refCount) + { + retVal = true; + goto out; + } + + handleType = FileHandleType_RW; + handle = &this->fileHandles[handleType]; + if (handle->refCount) + { + retVal = true; + goto out; + } + +out: + Mutex_unlock(&this->fileHandlesMutex); // U N L O C K + + return retVal; +} + +/** + * Remove a pid (if it existed) and decrease the numRangeLockPIDs counter. + * + * @return true if the pid was found (i.e. was previously added via _addRangeLockPID() ). + */ +bool FhgfsInode_removeRangeLockPID(FhgfsInode* this, int pid) +{ + bool pidExisted; + + Mutex_lock(&this->rangeLockPIDsMutex); // L O C K + + pidExisted = IntMap_erase(&this->rangeLockPIDs, pid); + + if(pidExisted) + AtomicInt_dec(&this->numRangeLockPIDs); + + Mutex_unlock(&this->rangeLockPIDsMutex); // U N L O C K + + return pidExisted; +} + +/** + * Add a new pid (if it didn't exist already) and increased the numRangeLockPIDs counter. + */ +void FhgfsInode_addRangeLockPID(FhgfsInode* this, int pid) +{ + bool pidInserted; + + Mutex_lock(&this->rangeLockPIDsMutex); // L O C K + + pidInserted = IntMap_insert(&this->rangeLockPIDs, pid, NULL); + + if(pidInserted) + AtomicInt_inc(&this->numRangeLockPIDs); + + Mutex_unlock(&this->rangeLockPIDsMutex); // U N L O C K +} + + +/** + * Converts OPENFILE_ACCESS_... fhgfs flags to FileHandleType + */ +FileHandleType __FhgfsInode_openFlagsToHandleType(int openFlags) +{ + switch(openFlags & OPENFILE_ACCESS_MASK_RW) + { + case OPENFILE_ACCESS_WRITE: + { + return FileHandleType_WRITE; + } break; + + case OPENFILE_ACCESS_READWRITE: + { + return FileHandleType_RW; + } break; + + default: + { + return FileHandleType_READ; + } break; + } +} + +/** + * Truncates the file if OPENFILE_ACCESS_TRUNC has been specified in openFlags. + */ +FhgfsOpsErr __FhgfsInode_referenceTrunc(FhgfsInode* this, App* app, int openFlags, + struct dentry* dentry) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + if(openFlags & OPENFILE_ACCESS_TRUNC) + { + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + const EntryInfo* entryInfo; + + FhgfsInode_entryInfoReadLock(this); // LOCK EntryInfo + + entryInfo = FhgfsInode_getEntryInfo(this); + + if (app->cfg->eventLogMask & EventLogMask_TRUNC) + { + FileEvent_init(&event, FileEventType_TRUNCATE, dentry); + eventSent = &event; + } + + retVal = FhgfsOpsRemoting_truncfile(app, entryInfo, 0, eventSent); + + FileEvent_uninit(&event); + + FhgfsInode_entryInfoReadUnlock(this); // UNLOCK EntryInfo + } + + return retVal; +} + +/** + * Init minimal ioInfo for mds open/close (referenceHandle/releaseHandle), so without the values + * that are e.g. only required for read/write etc. + */ +void __FhgfsInode_initOpenIOInfo(FhgfsInode* this, FhgfsInodeFileHandle* fileHandle, + unsigned accessFlags, PathInfo* pathInfo, RemotingIOInfo* outIOInfo) +{ + outIOInfo->app = FhgfsOps_getApp(this->vfs_inode.i_sb); + + outIOInfo->fileHandleID = fileHandle->fileHandleID; + outIOInfo->pattern = this->pattern; + outIOInfo->pathInfo = pathInfo; + outIOInfo->accessFlags = accessFlags; + + outIOInfo->needsAppendLockCleanup = &fileHandle->needsAppendLockCleanup; + outIOInfo->maxUsedTargetIndex = &fileHandle->maxUsedTargetIndex; + outIOInfo->firstWriteDone = NULL; + + outIOInfo->userID = i_uid_read(&this->vfs_inode); + outIOInfo->groupID = i_gid_read(&this->vfs_inode); +} + +/** + * Retrieve IO information for a file which was previously referenced (i.e. opened). + * + * Note: This may only be used by callers that have aqcuired a reference, either explicitly by + * calling _referenceHandle() or implicitly via open (=> struct file::FsFileInfo). + * + * @param outIOInfo the IO info will be stored in this out-arg; no buffers will be allocated, so + * there is no need to cleanup anything afterwards; but be sure to only use this while your + * reference is valid. + */ +void FhgfsInode_getRefIOInfo(FhgfsInode* this, FileHandleType handleType, + unsigned accessFlags, RemotingIOInfo* outIOInfo) +{ + FhgfsInodeFileHandle* fileHandle = &this->fileHandles[handleType]; + + __FhgfsInode_initOpenIOInfo(this, fileHandle, accessFlags, &this->pathInfo, outIOInfo); + + // remaining values, which are not assigned by initOpenIOInfo()... + + outIOInfo->firstWriteDone = &fileHandle->firstWriteDone; +} + +/** + * Acquire the internal file cache lock. + * + * Note: remember to call the corresponding unlock method!! + */ +void FhgfsInode_fileCacheSharedLock(FhgfsInode* this) +{ + RWLock_readLock(&this->fileCacheLock); +} + +void FhgfsInode_fileCacheSharedUnlock(FhgfsInode* this) +{ + RWLock_readUnlock(&this->fileCacheLock); +} + +/** + * Acquire the internal file cache lock. + * + * Note: remember to call the corresponding unlock method!! + */ +void FhgfsInode_fileCacheExclusiveLock(FhgfsInode* this) +{ + RWLock_writeLock(&this->fileCacheLock); +} + +/** + * Acquire the internal file cache lock if it is available immediately. + * + * Note: remember to call the corresponding unlock method (if the lock has been acquired)!! + * + * @return 1 if lock acquired, 0 if contention + */ +int FhgfsInode_fileCacheExclusiveTryLock(FhgfsInode* this) +{ + return RWLock_writeTryLock(&this->fileCacheLock); +} + +void FhgfsInode_fileCacheExclusiveUnlock(FhgfsInode* this) +{ + RWLock_writeUnlock(&this->fileCacheLock); +} + +/** + * Get the current attached file cache entry. + * Caller must hold the fileCache lock to work with this cache entry! + */ +struct CacheBuffer* Fhgfsinode_getFileCacheBuffer(FhgfsInode* this) +{ + return &this->fileCacheBuffer; +} + +/** + * Generate IDs suitable for i_ino. + * + * @param entryID zero-terminated string + * @param entryIDLen entryID length without terminating zero + */ +uint64_t FhgfsInode_generateInodeID(struct super_block* sb, const char* entryID, int entryIDLen) +{ + App* app = FhgfsOps_getApp(sb); + Config* cfg = App_getConfig(app); + InodeIDStyle inodeIDStyle = Config_getSysInodeIDStyleNum(cfg); + size_t hashBits; // 32 or 64 bit + + if(sizeof(ino_t) >= sizeof(uint64_t) ) + hashBits = 64; + else + hashBits = 32; + + /* note: iunique() or something else, which doesn't generate reproducible results isn't + allowed here, because we need a reproducible i_ino/hashval for + iget5_locked/__FhgfsOps_compareInodeID. */ + + + switch(inodeIDStyle) + { + case INODEIDSTYLE_Hash64HSieh: + { + uint64_t hashRes = HashTk_hash(HASHTK_HSIEHHASH32, hashBits, entryID, entryIDLen); + + // add terminating zero to hash buffer if resultig ID is too low + if(unlikely(hashRes <= BEEGFS_INODE_MAXRESERVED_INO) ) + hashRes = HashTk_hash(HASHTK_HSIEHHASH32, hashBits, entryID, entryIDLen+1); + + return hashRes; + + } break; + + case INODEIDSTYLE_Hash64MD4: + { + uint64_t hashRes = HashTk_hash(HASHTK_HALFMD4, hashBits, entryID, entryIDLen); + + // add terminating zero to hash buffer if resultig ID is too low + if(unlikely(hashRes <= BEEGFS_INODE_MAXRESERVED_INO) ) + hashRes = HashTk_hash(HASHTK_HALFMD4, hashBits, entryID, entryIDLen+1); + + return hashRes; + } break; + + case INODEIDSTYLE_Hash32MD4: + { // generate 32bit hash of fhgfs entryID string + + uint64_t hashRes = HashTk_hash32(HASHTK_HALFMD4, entryID, entryIDLen); + + // add terminating zero to hash buffer if resultig ID is too low + if(unlikely(hashRes <= BEEGFS_INODE_MAXRESERVED_INO) ) + hashRes = HashTk_hash32(HASHTK_HALFMD4, entryID, entryIDLen+1); + + return hashRes; + + } // fall through to hsieh32bit hash on 32bit systems... + + default: + { // generate 32bit hash of fhgfs entryID string + uint32_t hashRes = HashTk_hash32(HASHTK_HSIEHHASH32, entryID, entryIDLen); + + // add terminating zero to hash buffer if resultig ID is too low + if(unlikely(hashRes <= BEEGFS_INODE_MAXRESERVED_INO) ) + hashRes = HashTk_hash32(HASHTK_HSIEHHASH32, entryID, entryIDLen+1); + + return hashRes; + } + + } +} + +bool FhgfsInode_isCacheValid(FhgfsInode* this, umode_t i_mode, Config* cfg) +{ + unsigned cacheValidityMS; + unsigned cacheElapsedMS; + bool cacheValid; + + + if(S_ISDIR(i_mode) ) + cacheValidityMS = Config_getTuneDirSubentryCacheValidityMS(cfg); // defaults to 1s + else + cacheValidityMS = Config_getTuneFileSubentryCacheValidityMS(cfg); // defaults to 0 + + if(!cacheValidityMS) // caching disabled + cacheValid = false; + else + { + cacheElapsedMS = Time_elapsedMS(&this->dataCacheTime); + cacheValid = cacheValidityMS > cacheElapsedMS; + } + + return cacheValid; +} diff --git a/client_module/source/filesystem/FhgfsInode.h b/client_module/source/filesystem/FhgfsInode.h new file mode 100644 index 0000000..3170aad --- /dev/null +++ b/client_module/source/filesystem/FhgfsInode.h @@ -0,0 +1,759 @@ +#ifndef FHGFSINODE_H_ +#define FHGFSINODE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FhgfsOpsSuper.h" + + +#include +#include + + + +#define BEEGFS_INODE_ROOT_INO 2 /* traditional root inode number */ +#define BEEGFS_INODE_MAXRESERVED_INO BEEGFS_INODE_ROOT_INO + +#define BEEGFS_INODE_FILEHANDLES_NUM 3 /* r, rw, w (see "enum FileHandleType") */ + +#define BEEGFS_INODE(inodePointer) ( (FhgfsInode*)inodePointer) +#define BEEGFS_VFSINODE(fhgfsInodePointer) ( (struct inode*)fhgfsInodePointer) + + +#define BEEGFS_INODE_FLAG_WRITE_ERROR 1 // there was a write error with this inode + // if set switches to sync writes to faster notify + // applications +#define BEEGFS_INODE_FLAG_PAGED 2 // the inode was written to from page functions + +struct StripePattern; // forward declaration + +struct FhgfsInodeFileHandle; +typedef struct FhgfsInodeFileHandle FhgfsInodeFileHandle; + +enum FileHandleType; +typedef enum FileHandleType FileHandleType; + +enum FileBufferType; +typedef enum FileBufferType FileBufferType; + +struct CacheBuffer; +typedef struct CacheBuffer CacheBuffer; + +struct FhgfsInode; +typedef struct FhgfsInode FhgfsInode; + +struct FhgfsIsizeHints; +typedef struct FhgfsIsizeHints FhgfsIsizeHints; + + +enum FileHandleType // (note: used as array index for FhgfsInode::fileHandles) + {FileHandleType_READ=0, FileHandleType_RW=1, FileHandleType_WRITE=2}; + +enum FileBufferType + {FileBufferType_NONE=0, FileBufferType_READ=1, FileBufferType_WRITE=2}; + +struct CacheBuffer +{ + char* buf; // the file contents cache buffer + //size_t bufLen; // length of buf (not needed, because we have bufUsageMaxLen) + size_t bufUsageLen; // how much of the buffer is already used up + size_t bufUsageMaxLen; // how much of the buffer may be used (is min(bufLen, chunk_end) ) + + loff_t fileOffset; // the file offset where this buffer starts, -1 for append + + enum FileBufferType bufType; +}; + +/* + * Used to avoid isize invalid decreases in paged mode due to outdated server side i_size + */ +struct FhgfsIsizeHints +{ + bool ignoreIsize; // caller knows that the i_size needs to be ignored + uint64_t timeBeforeRemoteStat; // time before doing a remote stat call +}; + + +// kernel inode cache helpers + +extern void FhgfsInode_initOnce(FhgfsInode* this); +extern void FhgfsInode_allocInit(FhgfsInode* this); +extern void FhgfsInode_destroyUninit(FhgfsInode* this); + + +// public extern + +extern FhgfsOpsErr FhgfsInode_referenceHandle(FhgfsInode* this, struct dentry* dentry, + int openFlags, bool allowRWHandle, LookupIntentInfoOut* lookupInfo, + FileHandleType* outHandleType, uint32_t* outVersion); +extern FhgfsOpsErr FhgfsInode_releaseHandle(FhgfsInode* this, FileHandleType handleType, + struct dentry* dentry); +extern bool FhgfsInode_hasWriteHandle(FhgfsInode* this); + +extern void FhgfsInode_getRefIOInfo(FhgfsInode* this, FileHandleType handleType, + unsigned accessFlags, RemotingIOInfo* outIOInfo); + +extern void FhgfsInode_fileCacheExclusiveLock(FhgfsInode* this); +extern void FhgfsInode_fileCacheExclusiveUnlock(FhgfsInode* this); +extern int FhgfsInode_fileCacheExclusiveTryLock(FhgfsInode* this); +extern void FhgfsInode_fileCacheSharedLock(FhgfsInode* this); +extern void FhgfsInode_fileCacheSharedUnlock(FhgfsInode* this); + +extern struct CacheBuffer* Fhgfsinode_getFileCacheBuffer(FhgfsInode* this); + +extern bool FhgfsInode_removeRangeLockPID(FhgfsInode* this, int pid); +extern void FhgfsInode_addRangeLockPID(FhgfsInode* this, int pid); + +extern uint64_t FhgfsInode_generateInodeID(struct super_block* sb, const char* entryID, + int entryIDLen); + +extern bool FhgfsInode_isCacheValid(FhgfsInode* this, umode_t i_mode, Config* cfg); + + +// private extern + +extern FileHandleType __FhgfsInode_openFlagsToHandleType(int openFlags); +extern FhgfsOpsErr __FhgfsInode_referenceTrunc(FhgfsInode* this, App* app, int openFlag, + struct dentry* dentry); +extern void __FhgfsInode_initOpenIOInfo(FhgfsInode* this, FhgfsInodeFileHandle* fileHandle, + unsigned accessFlags, PathInfo* pathInfo, RemotingIOInfo* outIOInfo); + + +// getters & setters + +static inline const EntryInfo* FhgfsInode_getEntryInfo(FhgfsInode* this); +static inline PathInfo* FhgfsInode_getPathInfo(FhgfsInode* this); + +static inline DirEntryType FhgfsInode_getDirEntryType(FhgfsInode* this); + +static inline int FhgfsInode_getNumRangeLockPIDs(FhgfsInode* this); +static inline bool FhgfsInode_getIsFileOpen(FhgfsInode* this); +static inline bool FhgfsInode_getIsFileOpenByMultipleReaders(FhgfsInode* this); + +static inline void FhgfsInode_setLastWriteBackOrIsizeWriteTime(FhgfsInode* this); +static inline uint64_t FhgfsInode_getLastWriteBackOrIsizeWriteTime(FhgfsInode* this); +static inline int FhgfsInode_getWriteBackCounter(FhgfsInode* this); + +static inline void FhgfsInode_setWritePageError(FhgfsInode* this); +static inline int FhgfsInode_getHasWritePageError(FhgfsInode* this); + + + +// inliners + +static inline void Fhgfsinode_appendLock(FhgfsInode* this); +static inline void Fhgfsinode_appendUnlock(FhgfsInode* this); + +static inline void _FhgfsInode_initRootEntryInfo(FhgfsInode* this); + +static inline void FhgfsInode_entryInfoReadLock(FhgfsInode* this); +static inline void FhgfsInode_entryInfoReadUnlock(FhgfsInode* this); +static inline void FhgfsInode_entryInfoWriteLock(FhgfsInode* this); +static inline void FhgfsInode_entryInfoWriteUnlock(FhgfsInode* this); + +static inline bool FhgfsInode_compareEntryID(FhgfsInode* this, const char* otherEntryID); +static inline void FhgfsInode_updateEntryInfoOnRenameUnlocked(FhgfsInode* this, + const EntryInfo* newParentInfo, const char* newEntryName); +static inline void FhgfsInode_updateEntryInfoUnlocked(FhgfsInode* this, + const EntryInfo* newEntryInfo); + +static inline int FhgfsInode_getStripeCount(FhgfsInode* this); +static inline StripePattern* FhgfsInode_getStripePattern(FhgfsInode* this); +static inline void FhgfsInode_clearStripePattern(FhgfsInode* this); + + +static inline int FhgfsInode_handleTypeToOpenFlags(FileHandleType handleType); + +static inline void FhgfsInode_invalidateCache(FhgfsInode* this); + +static inline void FhgfsInode_incNumDirtyPages(FhgfsInode* this); +static inline void FhgfsInode_decNumDirtyPages(FhgfsInode* this); +static inline uint64_t FhgfsInode_getNumDirtyPages(FhgfsInode* this); +static inline void FhgfsInode_setNumDirtyPages(FhgfsInode* this, uint64_t value); +static inline bool FhgfsInode_getHasDirtyPages(FhgfsInode* this); + +static inline void FhgfsInode_setParentNodeID(FhgfsInode* this, NumNodeID parentNodeID); +static inline NumNodeID FhgfsInode_getParentNodeID(FhgfsInode* this); + +static inline void FhgfsInode_incWriteBackCounter(FhgfsInode* this); +static inline void FhgfsInode_decWriteBackCounter(FhgfsInode* this); + +static inline void FhgfsInode_initIsizeHints(FhgfsInode* this, FhgfsIsizeHints* outISizeHints); + +static inline void FhgfsInode_setNoIsizeDecrease(FhgfsInode* this); +static inline void FhgfsInode_unsetNoIsizeDecrease(FhgfsInode* this); +static inline int FhgfsInode_getNoIsizeDecrease(FhgfsInode* this); + + +static inline void FhgfsInode_setPageWriteFlag(FhgfsInode* this); +static inline void FhgfsInode_clearWritePageError(FhgfsInode* this); +static inline int FhgfsInode_getHasPageWriteFlag(FhgfsInode* this); + +/** + * Represents an open file handle on the mds. + */ +struct FhgfsInodeFileHandle +{ + const char* fileHandleID; // set when this handle is used for a remote file open + unsigned refCount; // number of references to this handle + + bool needsAppendLockCleanup; // true if append lock release failed + + AtomicInt maxUsedTargetIndex; /* zero-based target index in stripe pattern (-1 means "none"). + this is the highest target index that was read/written, so higher targets don't need + fsync() or close(). */ + + BitStore firstWriteDone; /* one bit per storage target in stripe pattern; bit is set when we send + data to this target. */ +}; + +/** + * Represents a linux file or directory inode with fhgfs extensions. + */ +struct FhgfsInode +{ + struct inode vfs_inode; // linux vfs inode + + EntryInfo entryInfo; + RWLock entryInfoLock; + + NumNodeID parentNodeID; /* nodeID of the parent directory, should be moved to EntryInfo + * in the future. So far only used for NFS exports and only set for + * DirInodes in FhgfsOpsExport to reconnect dentries. */ + + PathInfo pathInfo; + + + Time dataCacheTime; /* last time we updated file contents (monotonic clock) or dir attribs; + protected by i_lock */ + + Mutex fileHandlesMutex; + FhgfsInodeFileHandle fileHandles[BEEGFS_INODE_FILEHANDLES_NUM]; // use FileHandleType as index + struct StripePattern* pattern; // initialized when file is opened; multiple readers allowed + + Mutex rangeLockPIDsMutex; + AtomicInt numRangeLockPIDs; // modified only with mutex held, but readable without it + IntMap rangeLockPIDs; /* all PIDs (TGIDs) that used range locking and have this file open + (values are dummies) */ + + Mutex appendMutex; // for atomic writes with append-flag + AtomicInt appendFDsOpen; + + struct CacheBuffer fileCacheBuffer; // current buffer cache entry (protected by fileCacheLock) + RWLock fileCacheLock; /* for sync'ed access to cacheEntry and page cache (required to ensure + order, e.g. when a new r/w-call comes in while we're about to disable caching) */ + + AtomicInt64 numPageCacheDirtyPages; // number of written (=> dirty) bytes since last flush + + /* writeBackCounter and lastWriteBackEndOrIsizeWriteTime are hints for refreshInode to + * ingore the servers inode size */ + AtomicInt writeBackCounter; // number of writeBack threads sending pages to the server + AtomicInt64 lastWriteBackEndOrIsizeWriteTime; + AtomicInt noRemoteIsizeDecrease; // if set remote attributes won't decrease the i_size + + int flags; // protected by inode->i_lock + uint32_t fileVersion; /* protected by entryInfoLock (which would subsume a more granular lock). + used by native cache mode to track file contents change. */ + uint32_t metaVersion; /* protected by entryInfoLock (which would subsume a more granular lock). + used as a way to invalidate the cache due to internal metadata changes + (like stripe pattern change) via lookup::revalidateIntent. */ + atomic_t modified; // tracks whether the inode data has been written since its last full flush + atomic_t coRWInProg; //Coherent read/write in progress. +}; + + +int FhgfsInode_getNumRangeLockPIDs(FhgfsInode* this) +{ + return AtomicInt_read(&this->numRangeLockPIDs); +} + +/** + * Test whether a given file inode is currently open. + * + * @return true if the file is currently open. + */ +bool FhgfsInode_getIsFileOpen(FhgfsInode* this) +{ + bool retVal = false; + int i; + + Mutex_lock(&this->fileHandlesMutex); // L O C K + + for(i=0; i < BEEGFS_INODE_FILEHANDLES_NUM; i++) + { + if(this->fileHandles[i].refCount) + { // we found an open file handle + retVal = true; + break; + } + } + + Mutex_unlock(&this->fileHandlesMutex); // U N L O C K + + return retVal; +} + +/** + * Test whether a given file inode is currently open for reading by multiple readers. + * + * Note: This is specifically intended to give a hint for read-ahead disabling (because the current + * buffered mode logic could do read-ahead trashing with multiple concurrent readers on the same + * file). Since this is just a hint, we don't care about mutex locking here. + * + * @return true if the file is currently open by more than one reader (including read+write). + */ +bool FhgfsInode_getIsFileOpenByMultipleReaders(FhgfsInode* this) +{ + unsigned numReaders = this->fileHandles[FileHandleType_READ].refCount + + this->fileHandles[FileHandleType_RW].refCount; + + return (numReaders > 1); +} + +/** + * Acquire the internal append mutex + * + * Note: remember to call _appendUnlock()!! + */ +void Fhgfsinode_appendLock(FhgfsInode* this) +{ + Mutex_lock(&this->appendMutex); +} + +void Fhgfsinode_appendUnlock(FhgfsInode* this) +{ + Mutex_unlock(&this->appendMutex); +} + + +/** + * Get the EntryInfo of this inode + * + * Note: you will probably need to call _entryInfoReadLock() before using this (and don't forget to + * unlock afterwards). + */ +const EntryInfo* FhgfsInode_getEntryInfo(FhgfsInode* this) +{ + return &this->entryInfo; +} + +PathInfo* FhgfsInode_getPathInfo(FhgfsInode* this) +{ + return &this->pathInfo; +} + + +/** + * Initialize the root inode. We can not do that at mount time as we allow mounts without a + * connection to the management server. + */ +void _FhgfsInode_initRootEntryInfo(FhgfsInode* this) +{ + struct inode* vfs_inode = &this->vfs_inode; + struct super_block* sb = vfs_inode->i_sb; + bool hasRootInfo = FhgfsOps_getHasRootEntryInfo(sb); + + // unlikely as it will happen only once + if (unlikely(!hasRootInfo) && (vfs_inode->i_ino == BEEGFS_INODE_ROOT_INO) ) + { + /* root inode, EntryInfo not yet set during mount, as the meta server might be unknown. + * Set it now. */ + + App* app = FhgfsOps_getApp(sb); + + RWLock_writeLock(&this->entryInfoLock); // Write-LOCK + + hasRootInfo = FhgfsOps_getHasRootEntryInfo(sb); // verify under a lock + if (unlikely(hasRootInfo) ) + { + // another thread updated it already, nothing to do + } + else + { + bool isSuccess; + + EntryInfo_uninit(&this->entryInfo); + + isSuccess = MetadataTk_getRootEntryInfoCopy(app, &this->entryInfo); + if (isSuccess) + FhgfsOps_setHasRootEntryInfo(sb, true); + } + + RWLock_writeUnlock(&this->entryInfoLock); // Write-UNLOCK + + } + +} + +/* + * Get an EntryInfo read-lock. + * + * Note: Must be taken on reading parentEntryID. Should not be taken if only entryID is read, + * as entryID is never updated. + * See FhgfsInode_updateEntryInfoUnlocked() and FhgfsInode_updateEntryInfoOnRenameUnlocked() + * Note: If the root inode is not initialized, it will be initialized by this method under + * a write-lock. + */ +void FhgfsInode_entryInfoReadLock(FhgfsInode* this) +{ + _FhgfsInode_initRootEntryInfo(this); // NOTE: might temporarily writelock entryInfoLock + + RWLock_readLock(&this->entryInfoLock); // Read-LOCK +} + +void FhgfsInode_entryInfoReadUnlock(FhgfsInode* this) +{ + RWLock_readUnlock(&this->entryInfoLock); +} + +/** + * Note: Also might initialize the root-inode. + */ +void FhgfsInode_entryInfoWriteLock(FhgfsInode* this) +{ + _FhgfsInode_initRootEntryInfo(this); // NOTE: might temporarily writelock entryInfoLock + + RWLock_writeLock(&this->entryInfoLock); // Write-LOCK +} + +void FhgfsInode_entryInfoWriteUnlock(FhgfsInode* this) +{ + RWLock_writeUnlock(&this->entryInfoLock); +} + + + +/** + * Compares given entryID with the entryID of this inode. + * + * Note: This is intended to be called from methods like __FhgfsOps_compareInodeID, which is + * called e.g. by ilookup5 with the inode_hash_lock held, so this method may not sleep + * + * @return true if given ID matches, false otherwise + */ +bool FhgfsInode_compareEntryID(FhgfsInode* this, const char* otherEntryID) +{ + if(this->vfs_inode.i_ino == BEEGFS_INODE_ROOT_INO) + { // root node => owner info is not stored in the inode + return !strcmp(otherEntryID, META_ROOTDIR_ID_STR); + } + else + { + const char* entryID; + + entryID = this->entryInfo.entryID; + + if (unlikely(!entryID) ) + { + printk_fhgfs(KERN_WARNING, "Bug: entryID is a NULL pointer!\n"); + return false; + } + + return !strcmp(otherEntryID, entryID); + } +} + +/** + * Return the DirEntryType of the given inode + */ +DirEntryType FhgfsInode_getDirEntryType(FhgfsInode* this) +{ + return this->entryInfo.entryType; +} + +/** + * Update EntryInfo on rename + * + * Note: newParentInfo and newName are not owned by this object! + * + * Note: FhgfsInode->entryInfoLock already needs to be write-locked by the caller. + */ +void FhgfsInode_updateEntryInfoOnRenameUnlocked(FhgfsInode* this, const EntryInfo* newParentInfo, + const char* newName) +{ + EntryInfo_updateParentEntryID(&this->entryInfo, newParentInfo->entryID); + EntryInfo_updateFileName(&this->entryInfo, newName); + + if (!DirEntryType_ISDIR(this->entryInfo.entryType) ) + { // only update the ownerNodeID if it is not a directory + this->entryInfo.owner = newParentInfo->owner; + if (this->entryInfo.owner.isGroup) + this->entryInfo.featureFlags |= ENTRYINFO_FEATURE_BUDDYMIRRORED; + else + this->entryInfo.featureFlags &= ~ENTRYINFO_FEATURE_BUDDYMIRRORED; + } +} + +/** + * Update an existing EntryInfo with a new one + * + * Note: this->entryInfoLock already needs to be write-locked by the caller. + */ +void FhgfsInode_updateEntryInfoUnlocked(FhgfsInode* this, const EntryInfo* newEntryInfo) +{ + EntryInfo_update(&this->entryInfo, newEntryInfo); +} + + +int FhgfsInode_getStripeCount(FhgfsInode* this) +{ + return UInt16Vec_length(this->pattern->getStripeTargetIDs(this->pattern)); +} + +StripePattern* FhgfsInode_getStripePattern(FhgfsInode* this) +{ + return this->pattern; +} + +/** + * Clear the stripe pattern of an inode. + * Should only be called if inode->i_count is 1. + * After stripePattern is set to NULL, next operation should only be open. + * Nothing should access the pattern ptr until open loads a new pattern. + * Note: This is called with a spin_lock held. + */ +void FhgfsInode_clearStripePattern(FhgfsInode* this) +{ + this->pattern=NULL; +} + +/** + * Converts FileHandleType to OPENFILE_ACCESS_... fhgfs flags. + * + * Note: This is especially required when a handle was referenced with allowRWHandle, because then + * you don't know the OPENFILE_ACCESS_... flags. + */ +int FhgfsInode_handleTypeToOpenFlags(FileHandleType handleType) +{ + switch(handleType) + { + case FileHandleType_WRITE: + { + return OPENFILE_ACCESS_WRITE; + } break; + + case FileHandleType_RW: + { + return OPENFILE_ACCESS_READWRITE; + } break; + + default: + { + return OPENFILE_ACCESS_READ; + } break; + } +} + +/** + * Invalidate the cache of an inode + */ +void FhgfsInode_invalidateCache(FhgfsInode* this) +{ + Time_setZero(&this->dataCacheTime); +} + +void FhgfsInode_incNumDirtyPages(FhgfsInode* this) +{ + AtomicInt64_inc(&this->numPageCacheDirtyPages); +} + +void FhgfsInode_decNumDirtyPages(FhgfsInode* this) +{ + AtomicInt64_dec(&this->numPageCacheDirtyPages); +} + +uint64_t FhgfsInode_getNumDirtyPages(FhgfsInode* this) +{ + return AtomicInt64_read(&this->numPageCacheDirtyPages); +} + +void FhgfsInode_setNumDirtyPages(FhgfsInode* this, uint64_t value) +{ + return AtomicInt64_set(&this->numPageCacheDirtyPages, value); +} + +/** + * Test whether a given file inode is currently write-opened. + * + * @return true if the file is currently open. + */ +bool FhgfsInode_getHasDirtyPages(FhgfsInode* this) +{ + return !!FhgfsInode_getNumDirtyPages(this); +} + +void FhgfsInode_setParentNodeID(FhgfsInode* this, NumNodeID parentNodeID) +{ + this->parentNodeID = parentNodeID; +} + +NumNodeID FhgfsInode_getParentNodeID(FhgfsInode* this) +{ + return this->parentNodeID; +} + +void FhgfsInode_incWriteBackCounter(FhgfsInode* this) +{ + AtomicInt_inc(&this->writeBackCounter); +} + +void FhgfsInode_decWriteBackCounter(FhgfsInode* this) +{ + AtomicInt_dec(&this->writeBackCounter); +} + +/** + * @param this: Will be NULL on lookup calls + */ +int FhgfsInode_getWriteBackCounter(FhgfsInode* this) +{ + if (!this) + return 0; + + return AtomicInt_read(&this->writeBackCounter); +} + +void FhgfsInode_setNoIsizeDecrease(FhgfsInode* this) +{ + AtomicInt_set(&this->noRemoteIsizeDecrease, 1); +} + +/** + * Unset noRemoteIsizeDecrease if appropriate + */ +void FhgfsInode_unsetNoIsizeDecrease(FhgfsInode* this) +{ + struct address_space *mapping = this->vfs_inode.i_mapping; + + if (!FhgfsInode_getNoIsizeDecrease(this) ) + return; // not set, so no need to do anything + + if (FhgfsInode_getWriteBackCounter(this) ) + return; // still write-back threads left + + // check if there are dirty, writeback or mmapped-write pages + if ( (FhgfsInode_getHasDirtyPages(this) ) || + (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY) ) || + (mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) ) || + (mapping_writably_mapped(mapping) ) ) + { // server did not receive all pages yet + return; + } + + AtomicInt_set(&this->noRemoteIsizeDecrease, 0); +} + +int FhgfsInode_getNoIsizeDecrease(FhgfsInode* this) +{ + return AtomicInt_read(&this->noRemoteIsizeDecrease); +} + +void FhgfsInode_setLastWriteBackOrIsizeWriteTime(FhgfsInode* this) +{ + AtomicInt64_set(&this->lastWriteBackEndOrIsizeWriteTime, get_jiffies_64() ); +} + +uint64_t FhgfsInode_getLastWriteBackOrIsizeWriteTime(FhgfsInode* this) +{ + return AtomicInt64_read(&this->lastWriteBackEndOrIsizeWriteTime); +} + +/** + * Initialize iSizeHints + * + * @param this might be a NULL pointer + * + * Note: As optimization it will only read atomic values and jiffies if the inode refers to + * a regular file. As this is not known before doing lookup-stat, this might be a NULL + * pointer. + */ +void FhgfsInode_initIsizeHints(FhgfsInode* this, FhgfsIsizeHints* outISizeHints) +{ + struct address_space *mapping; + + if (this && !S_ISREG(this->vfs_inode.i_mode) ) + return; + + outISizeHints->timeBeforeRemoteStat = get_jiffies_64(); + outISizeHints->ignoreIsize = false; + + /* Check if there is a hint that we should ignore the remote isize */ + if (this) + { + mapping = this->vfs_inode.i_mapping; + if ((FhgfsInode_getWriteBackCounter(this) || FhgfsInode_getHasDirtyPages(this) || + (mapping_tagged(mapping, PAGECACHE_TAG_DIRTY) ) || + (mapping_tagged(mapping, PAGECACHE_TAG_WRITEBACK) ) || + (mapping_writably_mapped(mapping) ) ) ) + { + outISizeHints->ignoreIsize = true; + } + } +} + +/** + * Set the page-write flag (there was a page write to this inode + */ +void FhgfsInode_setPageWriteFlag(FhgfsInode* this) +{ + /* Note: This is called for each an every page (in paged mode, mmap, splice, ...) + * and therefore a very hot path and therefore needs to be optimized. + * Mostly the read will succeed if there are paged writes and the read will only fail + * for the first inode. And as reading values is cheaper than writing values, we first + * check if the flag is already set */ + if (!FhgfsInode_getHasPageWriteFlag(this) ) + this->flags |= BEEGFS_INODE_FLAG_PAGED; +} + +/** + * Get the page-write flag + * + * Note: In order to avoid inode locking overhead, this is usually called without hold i_lock + * It is not perfectly reliable then, but at some point CPUs get synchronized and that + * is sufficient for us. + */ +int FhgfsInode_getHasPageWriteFlag(FhgfsInode* this) +{ + return this->flags & BEEGFS_INODE_FLAG_PAGED; +} + +/** + * There was an error writing pages of this inode + */ +void FhgfsInode_setWritePageError(FhgfsInode* this) +{ + this->flags |= BEEGFS_INODE_FLAG_WRITE_ERROR; +} + +/** + * Clear the write error + */ +void FhgfsInode_clearWritePageError(FhgfsInode* this) +{ + this->flags &= ~(BEEGFS_INODE_FLAG_WRITE_ERROR); +} + +int FhgfsInode_getHasWritePageError(FhgfsInode* this) +{ + return this->flags & BEEGFS_INODE_FLAG_WRITE_ERROR; +} + +#endif /* FHGFSINODE_H_ */ diff --git a/client_module/source/filesystem/FhgfsOpsDir.c b/client_module/source/filesystem/FhgfsOpsDir.c new file mode 100644 index 0000000..66048c3 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsDir.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOps_versions.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsDir.h" +#include "FhgfsOpsSuper.h" +#include "FhgfsOpsHelper.h" + + +#include + +static int __FhgfsOps_revalidateIntent(struct dentry* parentDentry, struct dentry* dentry); + +struct dentry_operations fhgfs_dentry_ops = +{ + .d_revalidate = FhgfsOps_revalidateIntent, + .d_delete = FhgfsOps_deleteDentry, +}; + +/** + * Called when the dcache has a lookup hit (and wants to know whether the cache data + * is still valid). + * + * @return value is quasi-boolean: 0 if entry invalid, 1 if still valid (no other return values + * allowed). + */ +#ifndef KERNEL_HAS_ATOMIC_OPEN +int FhgfsOps_revalidateIntent(struct dentry* dentry, struct nameidata* nameidata) +#else +int FhgfsOps_revalidateIntent(struct dentry* dentry, unsigned flags) +#endif // LINUX_VERSION_CODE +{ + App* app; + Config* cfg; + Logger* log; + const char* logContext; + + int isValid = 0; // quasi-boolean (return value) + struct dentry* parentDentry; + struct inode* parentInode; + struct inode* inode; + Time now; + + + unsigned long nowTime; + unsigned cacheValidityMS; + + #ifndef KERNEL_HAS_ATOMIC_OPEN + unsigned flags = nameidata ? nameidata->flags : 0; + + IGNORE_UNUSED_VARIABLE(flags); + #endif // LINUX_VERSION_CODE + + + #ifdef LOOKUP_RCU + /* note: 2.6.38 introduced rcu-walk mode, which is inappropriate for us, because we need the + parentDentry and need to sleep for communication. ECHILD below tells vfs to call this again + in old ref-walk mode. (see Documentation/filesystems/vfs.txt:d_revalidate) */ + + if(flags & LOOKUP_RCU) + return -ECHILD; + #endif // LINUX_VERSION_CODE + + inode = dentry->d_inode; + + // access to parentDentry and inode needs to live below the rcu check. + + app = FhgfsOps_getApp(dentry->d_sb); + cfg = App_getConfig(app); + log = App_getLogger(app); + logContext = "FhgfsOps_revalidateIntent"; + + parentDentry = dget_parent(dentry); + parentInode = parentDentry->d_inode; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, dentry, inode, logContext); + + if(!inode || !parentInode || is_bad_inode(inode) ) + { + if(inode && S_ISDIR(inode->i_mode) ) + { + if(have_submounts(dentry) ) + goto cleanup_put_parent; + + shrink_dcache_parent(dentry); + } + + // dentry->d_time is updated with the current time during the first lookup, + // the cache is only valid if the difference of CURRENT_TIME and revalidate + // time is less than the tuneENOENTCacheValidityMS from the config + + Time_setToNowReal(&now); + cacheValidityMS = Config_getTuneENOENTCacheValidityMS(cfg); + nowTime = (now.tv_sec * 1000000000UL + now.tv_nsec); + if (!cacheValidityMS || ((nowTime - dentry->d_time)/1000000UL) > cacheValidityMS) + { + d_drop(dentry); + goto cleanup_put_parent; + } + else + { + isValid = 1; + goto cleanup_put_parent; + } + } + + // active dentry => remote-stat and local-compare + isValid = __FhgfsOps_revalidateIntent(parentDentry, dentry ); + +cleanup_put_parent: + // clean-up + dput(parentDentry); + + LOG_DEBUG_FORMATTED(log, 5, logContext, "'%s': isValid: %s", + dentry->d_name.name, isValid ? "yes" : "no"); + + return isValid; +} + +/* + * sub function of FhgfsOps_revalidateIntent(), supposed to be inlined, as we resolve several + * pointers two times, in this function and also already in the caller + * + * @return value is quasi-boolean: 0 if entry invalid, 1 if still valid (no other return values + * allowed). + */ +int __FhgfsOps_revalidateIntent(struct dentry* parentDentry, struct dentry* dentry) +{ + const char* logContext = "__FhgfsOps_revalidateIntent"; + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + + const char* entryName = dentry->d_name.name; + + fhgfs_stat fhgfsStat; + fhgfs_stat* fhgfsStatPtr; + + struct inode* parentInode = parentDentry->d_inode; + FhgfsInode* parentFhgfsInode = BEEGFS_INODE(parentInode); + + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + bool cacheValid = FhgfsInode_isCacheValid(fhgfsInode, inode->i_mode, cfg); + int isValid = 0; // quasi-boolean (return value) + bool needDrop = false; + FhgfsIsizeHints iSizeHints; + + + FhgfsOpsHelper_logOp(Log_SPAM, app, dentry, inode, logContext); + + + if (cacheValid) + { + isValid = 1; + return isValid; + } + + if(IS_ROOT(dentry) ) + fhgfsStatPtr = NULL; + else + { // any file or directory except our mount root + const EntryInfo* parentInfo; + EntryInfo* entryInfo; + uint32_t metaVersion; + + + FhgfsOpsErr remotingRes; + LookupIntentInfoIn inInfo; // input data for combo-request + LookupIntentInfoOut outInfo; // result data of combo-request + + FhgfsInode_initIsizeHints(fhgfsInode, &iSizeHints); + + FhgfsInode_entryInfoReadLock(parentFhgfsInode); // LOCK parentInfo + FhgfsInode_entryInfoWriteLock(fhgfsInode); // LOCK EntryInfo + + parentInfo = FhgfsInode_getEntryInfo(parentFhgfsInode); + entryInfo = &fhgfsInode->entryInfo; + metaVersion = fhgfsInode->metaVersion; + + LookupIntentInfoIn_init(&inInfo, parentInfo, entryName); + LookupIntentInfoIn_addEntryInfo(&inInfo, entryInfo); + LookupIntentInfoIn_addMetaVersion(&inInfo, metaVersion); + + LookupIntentInfoOut_prepare(&outInfo, NULL, &fhgfsStat); + + remotingRes = FhgfsOpsRemoting_lookupIntent(app, &inInfo, &outInfo); + + // we only need to update entryInfo flags + if (remotingRes == FhgfsOpsErr_SUCCESS && outInfo.revalidateRes == FhgfsOpsErr_SUCCESS) + entryInfo->featureFlags = outInfo.revalidateUpdatedFlags; + + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); // UNLOCK EntryInfo + FhgfsInode_entryInfoReadUnlock(parentFhgfsInode); // UNLOCK parentInfo + + if (unlikely(remotingRes != FhgfsOpsErr_SUCCESS) ) + { + needDrop = true; + goto out; + } + + if (outInfo.revalidateRes == FhgfsOpsErr_PATHNOTEXISTS) + { + if(unlikely(!(outInfo.responseFlags & LOOKUPINTENTRESPMSG_FLAG_REVALIDATE) ) ) + Logger_logErrFormatted(log, logContext, "Unexpected revalidate info missing: %s", + entryInfo->fileName); + needDrop = true; + goto out; + } + + // check the stat result here and set fhgfsStatPtr accordingly + if (outInfo.statRes == FhgfsOpsErr_SUCCESS) + fhgfsStatPtr = &fhgfsStat; // successful, so we can use existing stat values + else if (outInfo.statRes == FhgfsOpsErr_NOTOWNER) + fhgfsStatPtr = NULL; // stat values not available + else if (outInfo.statRes == FhgfsOpsErr_INTERNAL + && outInfo.revalidateRes == FhgfsOpsErr_METAVERSIONMISMATCH + && Config_getSysCacheInvalidationVersion(cfg) ) + { + // case when we want to invalidate the inode cache due to inode version change + // this case will not drop the dentry + // must goto out, since we clear the inode stripe pattern + fhgfsStatPtr = NULL; // stat values not available + __FhgfsOps_clearInodeStripePattern(app, inode); + goto out; + } + else + { + if(unlikely(!(outInfo.responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) ) ) + Logger_logErrFormatted(log, logContext, "Unexpected stat info missing: %s", + entryInfo->fileName); + + // now its getting difficult as there is an unexpected error + needDrop = true; + goto out; + } + } + + + if (!__FhgfsOps_refreshInode(app, inode, fhgfsStatPtr, &iSizeHints) ) + isValid = 1; + else + isValid = 0; + +out: + if (needDrop) + d_drop(dentry); + + return isValid; +} + +/** + * This is called from dput() when d_count is going to 0 and dput() wants to know from us whether + * not it should delete the dentry. + * + * @return !=0 to delete dentry, 0 to keep it + */ +#ifndef KERNEL_HAS_D_DELETE_CONST_ARG + int FhgfsOps_deleteDentry(struct dentry* dentry) +#else + int FhgfsOps_deleteDentry(const struct dentry* dentry) +#endif // LINUX_VERSION_CODE +{ + int shouldBeDeleted = 0; // quasi-boolean (return value) + struct inode* inode = dentry->d_inode; + + // For both positive and negative dentry cases, + // dentry cache (dcache) is mainatined + + if(inode) + { + if(is_bad_inode(inode) ) + shouldBeDeleted = 1; // inode marked as bad => no need to keep dentry + } + + return shouldBeDeleted; +} + +/* + * Constructs the path from the root dentry (of the mount-point) to an arbitrary hashed dentry. + * + * Note: Acquires a buf from the pathBufStore that must be released by the caller. + * Note: This is safe for paths larger than bufSize, but returns -ENAMETOOLONG in that case + * NOTE: Do NOT call two times in the same thread, as it might deadlock if multiple threads + * try to aquire a buffer. The thread that takes the buffer two times might never finish, + * if not sufficient buffers are available. If multiple threads take multiple buffers in + * parallel an infinity deadlock of the filesystem will happen. + * + * @outBuf the buf that must be returned to the pathBufStore of the app + * @return some offset within the outStoreBuf or the linux ERR_PTR(errorCode) (already negative) and + * *outStoreBuf will be NULL then + */ +char* __FhgfsOps_pathResolveToStoreBuf(NoAllocBufferStore* bufStore, struct dentry* dentry, + char** outStoreBuf) +{ + char * path; + const ssize_t storeBufLen = NoAllocBufferStore_getBufSize(bufStore); + *outStoreBuf = NoAllocBufferStore_waitForBuf(bufStore); + + path = dentry_path_raw(dentry, *outStoreBuf, storeBufLen); + if (unlikely (IS_ERR(path) ) ) + { + NoAllocBufferStore_addBuf(bufStore, *outStoreBuf); + *outStoreBuf = NULL; + } + + return path; +} diff --git a/client_module/source/filesystem/FhgfsOpsDir.h b/client_module/source/filesystem/FhgfsOpsDir.h new file mode 100644 index 0000000..4d96bb9 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsDir.h @@ -0,0 +1,39 @@ +#ifndef FHGFSOPSDIR_H_ +#define FHGFSOPSDIR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOps_versions.h" +#include "FhgfsOpsInode.h" + +#include + + +extern struct dentry_operations fhgfs_dentry_ops; + + +#ifndef KERNEL_HAS_ATOMIC_OPEN + extern int FhgfsOps_revalidateIntent(struct dentry* dentry, struct nameidata* nameidata); +#else + extern int FhgfsOps_revalidateIntent(struct dentry* dentry, unsigned flags); +#endif // LINUX_VERSION_CODE + +#ifndef KERNEL_HAS_D_DELETE_CONST_ARG + extern int FhgfsOps_deleteDentry(struct dentry* dentry); +#else + extern int FhgfsOps_deleteDentry(const struct dentry* dentry); +#endif // LINUX_VERSION_CODE + + +extern char* __FhgfsOps_pathResolveToStoreBuf(NoAllocBufferStore* bufStore, + struct dentry* dentry, char** outStoreBuf); + +#endif /* FHGFSOPSDIR_H_ */ diff --git a/client_module/source/filesystem/FhgfsOpsExport.c b/client_module/source/filesystem/FhgfsOpsExport.c new file mode 100644 index 0000000..1244b0d --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsExport.c @@ -0,0 +1,951 @@ +#include // (placed up here for LINUX_VERSION_CODE definition) + + +/** + * NFS export is probably not worth the backport efforts for kernels before 2.6.29 + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) + +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsExport.h" +#include "FhgfsOpsHelper.h" +#include "FhgfsOpsFile.h" +#include "FsDirInfo.h" +#include "FhgfsOpsDir.h" + + + +/** + * Operations for NFS export (and open_by_handle). + */ +const struct export_operations fhgfs_export_ops = +{ + .encode_fh = FhgfsOpsExport_encodeNfsFileHandle, + .fh_to_dentry = FhgfsOpsExport_nfsFileHandleToDentry, + .fh_to_parent = FhgfsOpsExport_nfsFileHandleToParent, + .get_parent = FhgfsOpsExport_getParentDentry, + .get_name = FhgfsOpsExport_getName, +}; + + +// placed here to make it possible to inline it (compiler decision) +static int __FhgfsOpsExport_encodeNfsFileHandleV3(struct inode* inode, __u32* file_handle_buf, + int* max_len, const EntryInfo* parentInfo); +static bool __FhgfsOpsExport_iterateDirFindName(struct dentry* dentry, const char* entryID, + char* outName); + + + +/** + * Single-byte handle type identifier that will be returned by _encodeNfsFileHandle and later be + * used by _nfsFileHandleToDentry to decode the handle. + * + * note: in-tree file systems use "enum fid_type" in linux/exportfs.h; this is our own version of + * it to let _nfsFileHandleToDentry know how to decode the FhgfsNfsFileHandleV2 structure. + * note: according to linux/exportfs.h, "the filesystem must not use the value '0' or '0xff'" as + * valid types; 0xff means given handle buffer size is too small. + */ +enum FhgfsNfsHandleType +{ + FhgfsNfsHandle_STANDARD_V1 = 0xf1, /* some arbitrary number that doesn't conflict with others + in linux/exportfs.h (though that wouldn't be a real + conflict) to identify our standard valid handle. */ + FhgfsNfsHandle_STANDARD_V2 = 0xf2, // Adds the parentOwnerNodeID + FhgfsNfsHandle_STANDARD_V3 = 0xf3, /* Adds isBuddyMirrored */ + + FhgfsNfsHandle_INVALID = 0xfe, /* special meaning: error occured, invalid handle + (this is only a hint for _nfsFileHandleToDentry, + because _encodeNfsFileHandle has no way to return an + error to the calling kernel code). */ + + FhgfsNfsHandle_BUFTOOSMALL = 0xff, /* special meaning for callers: given handle buffer + was too small */ +}; + +typedef enum FhgfsNfsHandleType FhgfsNfsHandleType; + + +/** + * Encode a file handle (typically for NFS) that can later be used to lookup an inode via + * _nfsFileHanleToDentry(). + * + * @param max_len file_handle_buf array length (=> length in 4-byte words), will be set to actually + * used or desired length. + * @param parent_inode/connectable if set, this means we should try to create a connectable file + * handle, so that we can later do a parent dir lookup from it. + * + * @return FhgfsNfsHandleType_... + */ +#ifndef KERNEL_HAS_ENCODE_FH_INODE + +int FhgfsOpsExport_encodeNfsFileHandle(struct dentry* dentry, __u32* file_handle_buf, int* max_len, + int connectable) +{ + struct inode* inode = dentry->d_inode; + struct inode* parent_inode = dentry->d_parent->d_inode; + + if (connectable) + { + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(parent_inode); + const EntryInfo* parentInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + + return __FhgfsOpsExport_encodeNfsFileHandleV3(inode, file_handle_buf, max_len, parentInfo); + } + else + return __FhgfsOpsExport_encodeNfsFileHandleV3(inode, file_handle_buf, max_len, NULL); +} + +#else // LINUX_VERSION_CODE >= KERNEL_VERSION(3,5,0) + +int FhgfsOpsExport_encodeNfsFileHandle(struct inode* inode, __u32* file_handle_buf, int* max_len, + struct inode* parent_inode) +{ + if (parent_inode) + { + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(parent_inode); + const EntryInfo* parentInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + + return __FhgfsOpsExport_encodeNfsFileHandleV3(inode, file_handle_buf, max_len, parentInfo); + } + else + return __FhgfsOpsExport_encodeNfsFileHandleV3(inode, file_handle_buf, max_len, NULL); +} + +#endif // LINUX_VERSION_CODE + +int __FhgfsOpsExport_encodeNfsFileHandleV3(struct inode* inode, __u32* file_handle_buf, + int* max_len, const EntryInfo* parentInfo) +{ + FhgfsNfsHandleType retVal = FhgfsNfsHandle_STANDARD_V3; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + struct FhgfsNfsFileHandleV3* fhgfsNfsHandle = (void*)file_handle_buf; + __u8* handleAsArray = (__u8 *)fhgfsNfsHandle; // Used for padding + + size_t fhgfsNfsHandleLen = sizeof(struct FhgfsNfsFileHandleV3); + size_t givenHandleByteLength = (*max_len) * sizeof(__u32); + + const EntryInfo* entryInfo; + bool parseParentIDRes; + bool parseEntryIDRes; + + *max_len = (fhgfsNfsHandleLen + sizeof(__u32)-1) / sizeof(__u32); /* set desired/used max_len for + caller (4-byte words; "+sizeof(u32)-1" rounds up) */ + + // check whether given buf length is enough for our handle + if(givenHandleByteLength < fhgfsNfsHandleLen) + return FhgfsNfsHandle_BUFTOOSMALL; + + // get entryInfo and serialize it in a special small format + // (normal string-based serialization would use too much buffer space) + + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + parseParentIDRes = __FhgfsOpsExport_parseEntryIDForNfsHandle(entryInfo->parentEntryID, + &fhgfsNfsHandle->parentEntryIDCounter, &fhgfsNfsHandle->parentEntryIDTimestamp, + &fhgfsNfsHandle->parentEntryIDNodeID); + + if(unlikely(!parseParentIDRes) ) + { // parsing failed (but we have no real way to return an error) + retVal = FhgfsNfsHandle_INVALID; + goto cleanup; + } + + parseEntryIDRes = __FhgfsOpsExport_parseEntryIDForNfsHandle(entryInfo->entryID, + &fhgfsNfsHandle->entryIDCounter, &fhgfsNfsHandle->entryIDTimestamp, + &fhgfsNfsHandle->entryIDNodeID); + + if(unlikely(!parseEntryIDRes) ) + { // parsing failed (but we have no real way to return an error) + retVal = FhgfsNfsHandle_INVALID; + goto cleanup; + } + + fhgfsNfsHandle->ownerNodeID = entryInfo->owner.node; + fhgfsNfsHandle->entryType = entryInfo->entryType; + fhgfsNfsHandle->isBuddyMirrored = EntryInfo_getIsBuddyMirrored(entryInfo); + + if (parentInfo) + { + // NOTE: Does not need to be locked, as it is not a char* value + fhgfsNfsHandle->parentOwnerNodeID = parentInfo->owner.node; + } + else + fhgfsNfsHandle->parentOwnerNodeID = (NumNodeID){0}; + + // Pad remaining space between real file handle length and max_len with zeroes + for (size_t i = fhgfsNfsHandleLen; i < givenHandleByteLength; i++) { + handleAsArray[i] = 0x00; + } + +cleanup: + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + return retVal; +} + +/** + * Lookup an inode based on a file handle that was previously created via _encodeNfsFileHandle(). + * + * @param fid file handle buffer + * @param fh_len fid buffer length in 4-byte words + * @param fileid_type FhgfsNfsHandleType_... (as returned by _encodeNfsFileHandle). + */ +struct dentry* FhgfsOpsExport_nfsFileHandleToDentry(struct super_block *sb, struct fid *fid, + int fh_len, int fileid_type) +{ + App* app = FhgfsOps_getApp(sb); + const char* logContext = "NFS-handle-to-dentry"; + + FhgfsNfsFileHandleV2 fhgfsNfsHandleV2; + struct FhgfsNfsFileHandleV3 fhgfsNfsHandleV3; + struct FhgfsNfsFileHandleV3* fhgfsNfsHandle = (struct FhgfsNfsFileHandleV3*) fid->raw; + + FhgfsNfsHandleType handleType = (FhgfsNfsHandleType)fileid_type; + + void* rawHandle = fid->raw; + + size_t fhgfsNfsHandleLen; + size_t givenHandleByteLength = fh_len * sizeof(__u32); // fh_len is in 4-byte words + + static bool isFirstCall = true; // true if this method is called for the first time + + FhgfsOpsHelper_logOp(Log_SPAM, app, NULL, NULL, logContext); + + if (unlikely(isFirstCall) ) + { + /* Our getattr functions assumes it is called after a lookup-intent and therefore does not + * validate the inode by default. However, this assumption is not true for NFS, and as result + * inode updates are never detected. NFS even caches readdir results and without inode updates + * even deleted entries are not detected. So we need to disable the getattr optimization for + * nfs exports. + */ + + Config* cfg = App_getConfig(app); + Logger* log = App_getLogger(app); + + Config_setTuneRefreshOnGetAttr(cfg); + + Logger_logFormatted(log, Log_DEBUG, logContext, + "nfs export detected: auto enabling refresh-on-getattr"); + + isFirstCall = false; + } + + // check if this handle is valid + if (unlikely(handleType != FhgfsNfsHandle_STANDARD_V1 && + handleType != FhgfsNfsHandle_STANDARD_V2 && + handleType != FhgfsNfsHandle_STANDARD_V3)) + { + printk_fhgfs_debug(KERN_INFO, "%s: Called with invalid handle type: 0x%x\n", + __func__, fileid_type); + + return NULL; + } + + if (likely(handleType == FhgfsNfsHandle_STANDARD_V3)) + fhgfsNfsHandleLen = sizeof(struct FhgfsNfsFileHandleV3); + else if (handleType == FhgfsNfsHandle_STANDARD_V2) + fhgfsNfsHandleLen = sizeof(FhgfsNfsFileHandleV2); + else + fhgfsNfsHandleLen = sizeof(FhgfsNfsFileHandleV1); + + // check if given handle length is valid + if(unlikely(givenHandleByteLength < fhgfsNfsHandleLen) ) + { + printk_fhgfs_debug(KERN_INFO, "%s: Called with too small handle length: " + "%d bytes; (handle type: 0x%x)\n", + __func__, (int)givenHandleByteLength, fileid_type); + + return NULL; + } + + if (unlikely(handleType == FhgfsNfsHandle_STANDARD_V1)) + { // old handle, generated before the update that creates V2 handles, convert V1 to V2 + FhgfsNfsFileHandleV1* handleV1 = (FhgfsNfsFileHandleV1*) rawHandle; + + fhgfsNfsHandleV2.entryIDCounter = handleV1->entryIDCounter; + fhgfsNfsHandleV2.entryIDNodeID = handleV1->entryIDNodeID; + fhgfsNfsHandleV2.entryIDTimestamp = handleV1->entryIDTimestamp; + fhgfsNfsHandleV2.ownerNodeID = handleV1->ownerNodeID; + fhgfsNfsHandleV2.parentEntryIDCounter = handleV1->parentEntryIDCounter; + fhgfsNfsHandleV2.parentEntryIDNodeID = handleV1->parentEntryIDNodeID; + fhgfsNfsHandleV2.parentEntryIDTimestamp = handleV1->parentEntryIDTimestamp; + fhgfsNfsHandleV2.entryType = handleV1->entryType; + + fhgfsNfsHandleV2.parentOwnerNodeID = 0; // only available in V2 handles + + rawHandle = &fhgfsNfsHandleV2; + handleType = FhgfsNfsHandle_STANDARD_V2; + } + + if (unlikely(handleType == FhgfsNfsHandle_STANDARD_V2)) + { + FhgfsNfsFileHandleV2* handleV2 = (FhgfsNfsFileHandleV2*) rawHandle; + + fhgfsNfsHandleV3.entryIDCounter = handleV2->entryIDCounter; + fhgfsNfsHandleV3.entryIDNodeID.value = handleV2->entryIDNodeID; + fhgfsNfsHandleV3.entryIDTimestamp = handleV2->entryIDTimestamp; + fhgfsNfsHandleV3.ownerNodeID.value = handleV2->ownerNodeID; + fhgfsNfsHandleV3.parentEntryIDCounter = handleV2->parentEntryIDCounter; + fhgfsNfsHandleV3.parentEntryIDNodeID.value = handleV2->parentEntryIDNodeID; + fhgfsNfsHandleV3.parentEntryIDTimestamp = handleV2->parentEntryIDTimestamp; + fhgfsNfsHandleV3.entryType = handleV2->entryType; + fhgfsNfsHandleV3.parentOwnerNodeID.value = handleV2->parentOwnerNodeID; + + fhgfsNfsHandleV3.isBuddyMirrored = 0; + + fhgfsNfsHandle = &fhgfsNfsHandleV3; + handleType = FhgfsNfsHandle_STANDARD_V3; + } + + return __FhgfsOpsExport_lookupDentryFromNfsHandle(sb, fhgfsNfsHandle, false); +} + +/** + * Lookup an inode based on a file handle that was previously created via _encodeNfsFileHandle(). + * + * @param fid file handle buffer + * @param fh_len fid buffer length in 4-byte words + * @param fileid_type FhgfsNfsHandleType_... (as returned by _encodeNfsFileHandle). + * + * Note: Always a V2 handle + */ +struct dentry* FhgfsOpsExport_nfsFileHandleToParent(struct super_block *sb, struct fid *fid, + int fh_len, int fileid_type) +{ + App* app = FhgfsOps_getApp(sb); + const char* logContext = "NFS-handle-to-parent"; + + struct FhgfsNfsFileHandleV3* fhgfsNfsHandle = (struct FhgfsNfsFileHandleV3*)fid->raw; + + FhgfsNfsHandleType handleType = (FhgfsNfsHandleType)fileid_type; + + size_t fhgfsNfsHandleLen = sizeof(struct FhgfsNfsFileHandleV3); + size_t givenHandleByteLength = fh_len * sizeof(__u32); // fh_len is in 4-byte words + + FhgfsOpsHelper_logOp(Log_SPAM, app, NULL, NULL, logContext); + + // check if this handle is valid + if (unlikely(handleType != FhgfsNfsHandle_STANDARD_V2 + && handleType != FhgfsNfsHandle_STANDARD_V3)) + { // V1 handles didn't include the parentOwnerNodeID + + #ifdef BEEGFS_DEBUG + if (handleType != FhgfsNfsHandle_STANDARD_V1) + printk_fhgfs_debug(KERN_INFO, "%s: Called with invalid handle type: 0x%x\n", + __func__, fileid_type); + #endif + + return NULL; + } + + // check if given handle length is valid + if(unlikely(givenHandleByteLength < fhgfsNfsHandleLen) ) + { + printk_fhgfs_debug(KERN_INFO, "%s: Called with too small handle length: " + "%d bytes; (handle type: 0x%x)\n", + __func__, (int)givenHandleByteLength, fileid_type); + + return NULL; + } + + return __FhgfsOpsExport_lookupDentryFromNfsHandle(sb, fhgfsNfsHandle, true); +} + + +/** + * Check whether a dentry/inode for the nfs handle exists in the local cache and try server lookup + * otherwise. + * + * @param isParent if true the parent will be looked up. + */ +struct dentry* __FhgfsOpsExport_lookupDentryFromNfsHandle(struct super_block *sb, + struct FhgfsNfsFileHandleV3* fhgfsNfsHandle, bool lookupParent) +{ + bool entryRes; + char* entryID = NULL; + size_t entryIDLen; + char* parentEntryID = NULL; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = + lookupParent ? "NFS-decode-handle-to-parent-dentry" : "NFS-decode-handle-to-dentry"; + + ino_t inodeHash; // (simply the inode number for us) + struct inode* inode; + FhgfsInodeComparisonInfo comparisonInfo; + + EntryInfo entryInfo; + + struct dentry* resDentry; + + // generate entryID string + + if (!lookupParent) + entryRes = __FhgfsOpsExport_entryIDFromNfsHandle(fhgfsNfsHandle->entryIDCounter, + fhgfsNfsHandle->entryIDTimestamp, fhgfsNfsHandle->entryIDNodeID, &entryID); + else + entryRes = __FhgfsOpsExport_entryIDFromNfsHandle(fhgfsNfsHandle->parentEntryIDCounter, + fhgfsNfsHandle->parentEntryIDTimestamp, fhgfsNfsHandle->parentEntryIDNodeID, &entryID); + + if(unlikely(!entryRes) ) + goto err_cleanup_entryids; + + entryIDLen = strlen(entryID); + + if (strncmp(entryID, META_ROOTDIR_ID_STR, entryIDLen) == 0) + inodeHash = BEEGFS_INODE_ROOT_INO; + else + inodeHash = FhgfsInode_generateInodeID(sb, entryID, entryIDLen); + + comparisonInfo.inodeHash = inodeHash; + comparisonInfo.entryID = entryID; + + inode = ilookup5(sb, inodeHash, __FhgfsOps_compareInodeID, + &comparisonInfo); // (ilookup5 calls iget() on match) + + if(!inode) + { // not found in cache => try to get it from mds + + App* app = FhgfsOps_getApp(sb); + fhgfs_stat fhgfsStat; + struct kstat kstat; + FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS; + + NumNodeID ownerNodeID; + + FhgfsIsizeHints iSizeHints; + + NumNodeID parentNodeID; + char* statParentEntryID; + + unsigned int metaVersion; + + if (!lookupParent) + { + // generate parentEntryID string + + bool parentRes = __FhgfsOpsExport_entryIDFromNfsHandle( + fhgfsNfsHandle->parentEntryIDCounter, fhgfsNfsHandle->parentEntryIDTimestamp, + fhgfsNfsHandle->parentEntryIDNodeID, &parentEntryID); + + if(unlikely(!parentRes) ) + goto err_cleanup_entryids; + + ownerNodeID = fhgfsNfsHandle->ownerNodeID; + } + else + { // parentDir + ownerNodeID = fhgfsNfsHandle->parentOwnerNodeID; + + /* Note: Needs to be special value to tell fhgfs-meta it is unknown! + * Must not be empty, as this would tell fhgfs-meta it is the root ID. */ + parentEntryID = StringTk_strDup(EntryInfo_PARENT_ID_UNKNOWN); + } + + FhgfsInode_initIsizeHints(NULL, &iSizeHints); + + // init entry info + + if (fhgfsNfsHandle->isBuddyMirrored) + EntryInfo_init(&entryInfo, NodeOrGroup_fromGroup(ownerNodeID.value), parentEntryID, + entryID, StringTk_strDup(""), fhgfsNfsHandle->entryType, 0); + else + EntryInfo_init(&entryInfo, NodeOrGroup_fromNode(ownerNodeID), parentEntryID, entryID, + StringTk_strDup(""), fhgfsNfsHandle->entryType, 0); + + // communicate + + statRes = FhgfsOpsRemoting_statAndGetParentInfo(app, &entryInfo, &fhgfsStat, + &parentNodeID, &statParentEntryID); + + // the lookup of parent information may have failed. this can happen if the entry info we + // tried to stat describes a directory inode that is mirrored, but whose parent directory is + // not mirrored. similarly, an unmirrored directory with a mirrored parent directory can fail + // here. try again with the other choice of mirroring flag. + if (lookupParent && statRes == FhgfsOpsErr_PATHNOTEXISTS) + { + entryInfo.featureFlags ^= ENTRYINFO_FEATURE_BUDDYMIRRORED; + statRes = FhgfsOpsRemoting_statAndGetParentInfo(app, &entryInfo, &fhgfsStat, + &parentNodeID, &statParentEntryID); + } + + if(statRes != FhgfsOpsErr_SUCCESS) + goto err_cleanup_entryinfo; + + // entry found => create inode + metaVersion = fhgfsStat.metaVersion; + OsTypeConv_kstatFhgfsToOs(&fhgfsStat, &kstat); + + kstat.ino = inodeHash; + + inode = __FhgfsOps_newInode(sb, &kstat, 0, &entryInfo, &iSizeHints, metaVersion); + + if (likely(inode) ) + { + if (parentNodeID.value) + { + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + FhgfsInode_setParentNodeID(fhgfsInode, parentNodeID); + } + + if (statParentEntryID) + { // update the parentEntryID + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + // make absolute sure we use the right entryInfo + FhgfsInode_entryInfoWriteLock(fhgfsInode); // L O C K + EntryInfo_updateSetParentEntryID(&fhgfsInode->entryInfo, statParentEntryID); + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); // U N L O C K + } + } + + } + else + SAFE_KFREE(entryID); // ilookup5 found an existing inode, free the comparison entryID + + if (unlikely(!inode) ) + goto err_cleanup_entryinfo; + + // (d_obtain_alias can also handle pointer error codes and NULL) + resDentry = d_obtain_alias(inode); + + if (resDentry && !IS_ERR(resDentry) ) + { + #ifndef KERNEL_HAS_S_D_OP + resDentry->d_op = &fhgfs_dentry_ops; + #endif // KERNEL_HAS_S_D_OP + + if (unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + { + FhgfsInode* fhgfsInode = BEEGFS_INODE(resDentry->d_inode); + const EntryInfo* entryInfo; + + FhgfsInode_entryInfoReadLock(fhgfsInode); // L O C K fhgfsInode + + entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + Logger_logFormatted(log, Log_SPAM, logContext, "result dentry inode id: %s", + entryInfo->entryID); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // U N L O C K fhgfsInode + } + } + + + return resDentry; + +err_cleanup_entryids: + SAFE_KFREE(parentEntryID); + SAFE_KFREE(entryID); + + return ERR_PTR(-ESTALE); + +err_cleanup_entryinfo: + EntryInfo_uninit(&entryInfo); + + return ERR_PTR(-ESTALE); +} + +/** + * Parse an entryID string to get its three components. + * + * Note: META_ROOTDIR_ID_STR is a special case (all three components are set to 0). + * + * @return false on error + */ +bool __FhgfsOpsExport_parseEntryIDForNfsHandle(const char* entryID, uint32_t* outCounter, + uint32_t* outTimestamp, NumNodeID* outNodeID) +{ + const int numEntryIDComponents = 3; // sscanf must find 3 components in a valid entryID string + + uint32_t nodeID32; //just a tmp variable, because 16-bit outNodeID cannot be used with sscanf %X + int scanRes; + + if(!strcmp(META_ROOTDIR_ID_STR, entryID) ) + { // special case: this is the root ID, which doesn't have the usual three components + *outCounter = 0; + *outTimestamp = 0; + *outNodeID = (NumNodeID){0}; + + return true; + } + + scanRes = sscanf(entryID, "%X-%X-%X", outCounter, outTimestamp, &nodeID32); + + if(unlikely(scanRes != numEntryIDComponents) ) + { // parsing failed + printk_fhgfs_debug(KERN_INFO, "%s: Parsing of entryID failed. entryID: %s\n", + __func__, entryID); + + return false; + } + + *outNodeID = (NumNodeID){nodeID32}; + + return true; +} + +/** + * Generate an entryID string from the NFS handle components. + * + * @param outEntryID will be kmalloced on success and needs to be kfree'd by the caller + * @return false on error + */ +bool __FhgfsOpsExport_entryIDFromNfsHandle(uint32_t counter, uint32_t timestamp, + NumNodeID nodeID, char** outEntryID) +{ + if(!counter && !timestamp && !nodeID.value) + { // special case: root ID + *outEntryID = StringTk_strDup(META_ROOTDIR_ID_STR); + } + else + *outEntryID = StringTk_kasprintf("%X-%X-%X", counter, timestamp, (uint32_t)nodeID.value); + + return (*outEntryID != NULL); // (just in case kmalloc failed) +} + + +/** + * getParentDentry - export_operations->get_parent function + * + * Get the directory dentry (and inode) that has childDentry + * + * Note: We do not lock childDentry's EntryInfo here, as childDentry does not have a connected + * path yet, so childDentry's EntryInfo also cannot change. + */ +struct dentry* FhgfsOpsExport_getParentDentry(struct dentry* childDentry) +{ + int retVal = -ESTALE; + + struct super_block* superBlock = childDentry->d_sb; + App* app = FhgfsOps_getApp(superBlock); + Logger* log = App_getLogger(app); + const char* logContext = "Export_getParentDentry"; + + struct inode* parentInode; + struct inode* childInode = childDentry->d_inode; + FhgfsInode* fhgfsChildInode = BEEGFS_INODE(childInode); + + EntryInfo childEntryInfoCopy; + FhgfsInodeComparisonInfo comparisonInfo; + + size_t parentIDLen; + const char* parentEntryID; + + struct dentry* parentDentry = NULL; + + FhgfsInode_entryInfoReadLock(fhgfsChildInode); // L O C K childInode + EntryInfo_dup(FhgfsInode_getEntryInfo(fhgfsChildInode), &childEntryInfoCopy); + FhgfsInode_entryInfoReadUnlock(fhgfsChildInode); // U N L O C K childInode + + parentEntryID = EntryInfo_getParentEntryID(&childEntryInfoCopy); + + + /* NOTE: IS_ROOT() would not work, as any childDentry is not connected to its parent + * and IS_ROOT() would *always* be true here. */ + if (strlen(parentEntryID) == 0) + { + /* This points to a bug, as we should never be called for the root dentry and so this means + * either root was not correctly identified or setting the parentEntryID failed */ + Logger_logErrFormatted(log, Log_ERR, logContext, + "Bug: Root does not have parentEntryID set!"); + + retVal = -EINVAL; + goto outErr; + } + + parentIDLen = strlen(parentEntryID); + comparisonInfo.entryID = parentEntryID; + + if (strncmp(parentEntryID, META_ROOTDIR_ID_STR, parentIDLen) == 0) + comparisonInfo.inodeHash = BEEGFS_INODE_ROOT_INO; // root inode + else + comparisonInfo.inodeHash = FhgfsInode_generateInodeID(superBlock, + parentEntryID, strlen(parentEntryID) ); + + Logger_logFormatted(log, Log_SPAM, logContext, "Find inode for ID: %s inodeHash: %lu", + comparisonInfo.entryID, comparisonInfo.inodeHash); + + parentInode = ilookup5(superBlock, comparisonInfo.inodeHash, __FhgfsOps_compareInodeID, + &comparisonInfo); // (ilookup5 calls iget() on match) + + if(!parentInode) + { // not found in cache => try to get it from mds + + fhgfs_stat fhgfsStat; + struct kstat kstat; + FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS; + const char* fileName = StringTk_strDup(""); + EntryInfo parentInfo; + unsigned int metaVersion; + + NumNodeID parentNodeID = (NumNodeID){0}; + char* parentEntryID = NULL; + + NumNodeID parentOwnerNodeID; + + FhgfsIsizeHints iSizeHints; + + /* Note: Needs to be (any) special value to tell fhgfs-meta it is unknown! + * Must not be empty, as this would tell fhgfs-meta it is the root ID. */ + char* grandParentID = StringTk_strDup(EntryInfo_PARENT_ID_UNKNOWN); + + parentOwnerNodeID = FhgfsInode_getParentNodeID(fhgfsChildInode); + if (parentOwnerNodeID.value == 0) + { /* Hmm, so we don't know the real ownerNodeId, we try the childs ID, but if that fails + * with FhgfsOpsErr_NOTOWNER, we would need to cycle through all meta-targets, which + * is too slow and we therefore don't do that, but hope the client can + * recover -ESTALE itself */ + parentOwnerNodeID = childEntryInfoCopy.owner.node; + } + + // generate parentEntryID string + if (EntryInfo_getIsBuddyMirrored(&childEntryInfoCopy)) + EntryInfo_init(&parentInfo, NodeOrGroup_fromGroup(parentOwnerNodeID.value), + grandParentID, StringTk_strDup(comparisonInfo.entryID), fileName, + DirEntryType_DIRECTORY, 0); + else + EntryInfo_init(&parentInfo, NodeOrGroup_fromNode(parentOwnerNodeID), grandParentID, + StringTk_strDup(comparisonInfo.entryID), fileName, DirEntryType_DIRECTORY, 0); + + // communicate + + FhgfsInode_initIsizeHints(NULL, &iSizeHints); + + statRes = FhgfsOpsRemoting_statAndGetParentInfo(app, &parentInfo, &fhgfsStat, + &parentNodeID, &parentEntryID); + + // the lookup of parent information may have failed. this can happen if the entry info we + // tried to stat describes a directory inode that is mirrored, but whose parent directory + // is not mirrored. similarly, an unmirrored directory with a mirrored parent directory + // can fail here. try again with the other choice of mirroring flag. + if (statRes == FhgfsOpsErr_PATHNOTEXISTS) + { + parentInfo.featureFlags ^= ENTRYINFO_FEATURE_BUDDYMIRRORED; + statRes = FhgfsOpsRemoting_statAndGetParentInfo(app, &parentInfo, &fhgfsStat, + &parentNodeID, &parentEntryID); + } + + if(statRes != FhgfsOpsErr_SUCCESS) + { + EntryInfo_uninit(&parentInfo); + goto outErr; + } + + // entry found => create inode + metaVersion = fhgfsStat.metaVersion; + + OsTypeConv_kstatFhgfsToOs(&fhgfsStat, &kstat); + + kstat.ino = comparisonInfo.inodeHash; + + parentInode = __FhgfsOps_newInodeWithParentID(superBlock, &kstat, 0, &parentInfo, + parentNodeID, &iSizeHints, metaVersion); + + if (likely(parentInode) && parentEntryID) + { // update the parentEntryID + FhgfsInode* parentFhgfsInode = BEEGFS_INODE(parentInode); + + // make absolute sure we use the right entryInfo + FhgfsInode_entryInfoWriteLock(parentFhgfsInode); // L O C K + EntryInfo_updateSetParentEntryID(&parentFhgfsInode->entryInfo, parentEntryID); + FhgfsInode_entryInfoWriteUnlock(parentFhgfsInode); // U N L O C K + } + + } + + // (d_obtain_alias can also handle pointer error codes and NULL) + parentDentry = d_obtain_alias(parentInode); + + if (parentDentry && !IS_ERR(parentDentry) ) + { + #ifndef KERNEL_HAS_S_D_OP + parentDentry->d_op = &fhgfs_dentry_ops; + #endif // KERNEL_HAS_S_D_OP + } + + // printk(KERN_INFO "%s: d_obtain_alias(inode) res: %ld\n", __func__, IS_ERR(parentDentry) ); + + EntryInfo_uninit(&childEntryInfoCopy); + + return parentDentry; + +outErr: + EntryInfo_uninit(&childEntryInfoCopy); + return ERR_PTR(retVal); +} + + + +/** + * getName - export_operations->get_name function + * + * calls readdir on the parent until it finds an entry with + * the same entryID as the child, and returns that. + + * @param dirDentry the directory in which to find a name + * @param outName a pointer to a %NAME_MAX+1 char buffer to store the name + * @param child the dentry for the child directory. + */ +int FhgfsOpsExport_getName(struct dentry* dirDentry, char *outName, struct dentry *child) +{ + struct inode *dirInode = dirDentry->d_inode; + + struct inode *childInode = child->d_inode; + FhgfsInode* fhgfsChildInode = BEEGFS_INODE(childInode); + EntryInfo childEntryInfoCopy; + const char* childEntryID; + + + int retVal; + bool findRes; + + retVal = -ENOTDIR; + if (!dirInode || !S_ISDIR(dirInode->i_mode)) + goto out; + + retVal = -EINVAL; + if (!dirInode->i_fop) + goto out; + + FhgfsInode_entryInfoReadLock(fhgfsChildInode); // LOCK childInode + EntryInfo_dup(FhgfsInode_getEntryInfo(fhgfsChildInode), &childEntryInfoCopy); + FhgfsInode_entryInfoReadUnlock(fhgfsChildInode); // UNLOCK childInode + + childEntryID = EntryInfo_getEntryID(&childEntryInfoCopy); + + findRes = __FhgfsOpsExport_iterateDirFindName(dirDentry, childEntryID, outName); + if (!findRes) + { + retVal = -ESTALE; + goto outUnitEntryInfo; + } + + retVal = 0; + + +outUnitEntryInfo: + EntryInfo_uninit(&childEntryInfoCopy); + +out: + return retVal; +} + + +/** + * Find the name of an entry with the given entryID in the directory dirDentry. + * + * Note: This uses a rather slow client-side readdir() to find the entry. + * Maybe we should add another NetMsg and do it directly on the server! + */ +bool __FhgfsOpsExport_iterateDirFindName(struct dentry* dirDentry, const char* entryID, + char* outName) +{ + struct super_block* superBlock = dirDentry->d_sb; + App* app = FhgfsOps_getApp(superBlock); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOpsExport_readdirFindName"; + + bool retVal = false; + struct inode* dirInode = dirDentry->d_inode; + FhgfsInode* fhgfsDirInode = BEEGFS_INODE(dirInode); + + size_t contentsPos = 0; + size_t contentsLength; + + StrCpyVec* dirContents; + StrCpyVec* dirContentIDs; + + EntryInfo dirEntryInfoCopy; + FsDirInfo dirInfo; + + FsDirInfo_init(&dirInfo, app); + + dirContents = FsDirInfo_getDirContents(&dirInfo); + dirContentIDs = FsDirInfo_getEntryIDs(&dirInfo); + + FhgfsInode_entryInfoReadLock(fhgfsDirInode); // L O C K EntryInfo + EntryInfo_dup(FhgfsInode_getEntryInfo(fhgfsDirInode), &dirEntryInfoCopy); + FhgfsInode_entryInfoReadUnlock(fhgfsDirInode); // U N L O C K EntryInfo + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + { + const EntryInfo* dirInfo = FhgfsInode_getEntryInfo(fhgfsDirInode); + + struct inode* inode = dirDentry->d_inode; + + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, dirDentry, inode, logContext, + "dir-id: %s searchID: %s", dirInfo->entryID, entryID); + } + + + for( ; ; ) // loop as long as we didn't find entryID and as long as the dir has entries + { + int refreshRes; + char* currentName; + const char* currentEntryID; + + refreshRes = FhgfsOpsHelper_refreshDirInfoIncremental(app, + &dirEntryInfoCopy, &dirInfo, false); + if(unlikely(refreshRes) ) + { // error occurred + break; + } + + contentsPos = FsDirInfo_getCurrentContentsPos(&dirInfo); + contentsLength = StrCpyVec_length(dirContents); + +#if 0 + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, + "contentsPos: %lld/%lld, endOfDir: %s", + (long long)contentsPos, (long long)contentsLength, + FsDirInfo_getEndOfDir(&dirInfo) ? "yes" : "no"); +#endif + + // refreshDirInfoInc guarantees that we either have a valid range for current offset + // or that contentsLength is empty + if(!contentsLength) + { // end of dir + LOG_DEBUG(log, Log_SPAM, logContext, "reached end of dir"); + break; + } + + currentName = StrCpyVec_at(dirContents, contentsPos); + currentEntryID = StrCpyVec_at(dirContentIDs, contentsPos); + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, + "searchID: %s current dir-entry: %s entryID: %s ", + entryID, currentName, currentEntryID); + + if(!strcmp(currentEntryID, entryID) ) + { // match found + // note: out name buf is guaranteed to be NAME_MAX+1 according to + StringTk_strncpyTerminated(outName, currentName, NAME_MAX+1); + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "Found childName: %s", outName); + + retVal = true; + break; + } + + FsDirInfo_setCurrentContentsPos(&dirInfo, contentsPos + 1); + } // end of for-loop + + // clean-up + FsDirInfo_uninit((FsObjectInfo*) &dirInfo); + EntryInfo_uninit(&dirEntryInfoCopy); + + return retVal; +} + + +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) diff --git a/client_module/source/filesystem/FhgfsOpsExport.h b/client_module/source/filesystem/FhgfsOpsExport.h new file mode 100644 index 0000000..8446119 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsExport.h @@ -0,0 +1,115 @@ +#ifndef FHGFSOPSEXPORT_H_ +#define FHGFSOPSEXPORT_H_ + +#include // (placed up here for LINUX_VERSION_CODE definition) + + +/** + * NFS export is probably not worth the backport efforts for kernels before 2.6.29 + */ +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) + + +#include + + +struct FhgfsNfsFileHandleV1; +typedef struct FhgfsNfsFileHandleV1 FhgfsNfsFileHandleV1; + +struct FhgfsNfsFileHandleV2; +typedef struct FhgfsNfsFileHandleV2 FhgfsNfsFileHandleV2; + +struct FhgfsNfsFileHandleV3; + +extern const struct export_operations fhgfs_export_ops; + + +#ifndef KERNEL_HAS_ENCODE_FH_INODE +int FhgfsOpsExport_encodeNfsFileHandle(struct dentry* dentry, __u32* file_handle_buf, int* max_len, + int connectable); +#else +int FhgfsOpsExport_encodeNfsFileHandle(struct inode* inode, __u32* file_handle_buf, int* max_len, + struct inode* parent_inode); +#endif + +struct dentry* FhgfsOpsExport_nfsFileHandleToDentry(struct super_block *sb, struct fid *fid, + int fh_len, int fileid_type); +struct dentry* FhgfsOpsExport_nfsFileHandleToParent(struct super_block *sb, struct fid *fid, + int fh_len, int fileid_type); + +int FhgfsOpsExport_getName(struct dentry* dirDentry, char *outName, struct dentry *child); + + + +struct dentry* __FhgfsOpsExport_lookupDentryFromNfsHandle(struct super_block *sb, + struct FhgfsNfsFileHandleV3* fhgfsNfsHandle, bool isParent); +struct dentry* FhgfsOpsExport_getParentDentry(struct dentry* childDentry); + +bool __FhgfsOpsExport_parseEntryIDForNfsHandle(const char* entryID, uint32_t* outCounter, + uint32_t* outTimestamp, NumNodeID* outNodeID); +bool __FhgfsOpsExport_entryIDFromNfsHandle(uint32_t counter, uint32_t timestamp, + NumNodeID nodeID, char** outEntryID); + + + +/** + * The structure of the handle that will be encoded by _encodeFileHandle(). + * + * EntryID string format (except for "root"): -- + */ +struct FhgfsNfsFileHandleV1 +{ + // note: fields not in natural order for better alignment and packed size + + uint32_t parentEntryIDCounter; // from EntryInfo::parentEntryID + uint32_t parentEntryIDTimestamp; // from EntryInfo::parentEntryID + uint32_t entryIDCounter; // from EntryInfo::entryID + uint32_t entryIDTimestamp; // from EntryInfo::entryID + uint16_t parentEntryIDNodeID; // from EntryInfo::parentEntryID + uint16_t entryIDNodeID; // from EntryInfo::entryID + uint16_t ownerNodeID; // from EntryInfo::ownerNodeID + char entryType; // from EntryInfo::entryType + +} __attribute__((packed)); + +/** + * The structure of the handle that will be encoded by _encodeFileHandle(). + * + * EntryID string format (except for "root"): -- + * + * Note: V2 adds parentOwnerNodeID + */ +struct FhgfsNfsFileHandleV2 +{ + // note: fields not in natural order for better alignment and packed size + + uint32_t parentEntryIDCounter; // from EntryInfo::parentEntryID + uint32_t parentEntryIDTimestamp; // from EntryInfo::parentEntryID + uint32_t entryIDCounter; // from EntryInfo::entryID + uint32_t entryIDTimestamp; // from EntryInfo::entryID + uint16_t parentEntryIDNodeID; // from EntryInfo::parentEntryID + uint16_t entryIDNodeID; // from EntryInfo::entryID + uint16_t parentOwnerNodeID; // from EntryInfo::ownerNodeID -> parentEntryInfo! + uint16_t ownerNodeID; // from EntryInfo::ownerNodeID + char entryType; // from EntryInfo::entryType + +} __attribute__((packed)); + +struct FhgfsNfsFileHandleV3 +{ + uint32_t parentEntryIDCounter; // from EntryInfo::parentEntryID + uint32_t parentEntryIDTimestamp; // from EntryInfo::parentEntryID + uint32_t entryIDCounter; // from EntryInfo::entryID + uint32_t entryIDTimestamp; // from EntryInfo::entryID + NumNodeID parentEntryIDNodeID; // from EntryInfo::parentEntryID + NumNodeID entryIDNodeID; // from EntryInfo::entryID + NumNodeID parentOwnerNodeID; // from EntryInfo::ownerNodeID -> parentEntryInfo! + NumNodeID ownerNodeID; // from EntryInfo::ownerNodeID + char entryType; // from EntryInfo::entryType + uint8_t isBuddyMirrored; // from EntryInfo::isBuddyMirrored +} __attribute__((packed)); + + +#endif // LINUX_VERSION_CODE + +#endif /* FHGFSOPSEXPORT_H_ */ diff --git a/client_module/source/filesystem/FhgfsOpsFile.c b/client_module/source/filesystem/FhgfsOpsFile.c new file mode 100644 index 0000000..5224219 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsFile.c @@ -0,0 +1,1793 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsHelper.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsDir.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsIoctl.h" +#include "FhgfsOpsSuper.h" +#include "FhgfsOps_versions.h" +#include "FhgfsOpsPages.h" + +#include +#include +#include +#include +#include +#include +#include + + +#ifdef CONFIG_COMPAT +#include +#endif // CONFIG_COMPAT + +static ssize_t FhgfsOps_buffered_write_iter(struct kiocb *iocb, struct iov_iter *from); +static ssize_t FhgfsOps_buffered_read_iter(struct kiocb *iocb, struct iov_iter *to); + +static int FhgfsOps_write_begin(struct file* file, struct address_space* mapping, + loff_t pos, unsigned len, +#if BEEGFS_HAS_WRITE_FLAGS + unsigned flags, +#endif + beegfs_pgfol_t *pgfolp, void** fsdata); + +static int FhgfsOps_write_end(struct file* file, struct address_space* mapping, + loff_t pos, unsigned len, unsigned copied, beegfs_pgfol_t pgfol, void* fsdata); + +#define MMAP_RETRY_LOCK_EASY 100 +#define MMAP_RETRY_LOCK_HARD 500 + +/** + * Operations for files with cache type "buffered" and "none". + */ +struct file_operations fhgfs_file_buffered_ops = +{ + .open = FhgfsOps_open, + .release = FhgfsOps_release, + .fsync = FhgfsOps_fsync, + .flush = FhgfsOps_flush, + .llseek = FhgfsOps_llseek, + .flock = FhgfsOps_flock, + .lock = FhgfsOps_lock, + .mmap = FhgfsOps_mmap, + .unlocked_ioctl = FhgfsOpsIoctl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = FhgfsOpsIoctl_compatIoctl, +#endif // CONFIG_COMPAT +#ifdef KERNEL_HAS_GENERIC_FILE_SPLICE_READ + .splice_read = generic_file_splice_read, +#else + .splice_read = filemap_splice_read, +#endif +#ifdef KERNEL_HAS_ITER_FILE_SPLICE_WRITE + .splice_write = iter_file_splice_write, +#else + .splice_write = generic_file_splice_write, +#endif + + .read_iter = FhgfsOps_buffered_read_iter, + .write_iter = FhgfsOps_buffered_write_iter, // replacement for aio_write + +#ifdef KERNEL_HAS_GENERIC_FILE_SENDFILE + .sendfile = generic_file_sendfile, // removed in 2.6.23 (now handled via splice) +#endif // LINUX_VERSION_CODE +}; + +/** + * Operations for files with cache type "paged". + */ +struct file_operations fhgfs_file_pagecache_ops = +{ + .open = FhgfsOps_open, + .release = FhgfsOps_release, + .read_iter = FhgfsOps_read_iter, + .write_iter = FhgfsOps_write_iter, + .fsync = FhgfsOps_fsync, + .flush = FhgfsOps_flush, + .llseek = FhgfsOps_llseek, + .flock = FhgfsOps_flock, + .lock = FhgfsOps_lock, + .mmap = FhgfsOps_mmap, + .unlocked_ioctl = FhgfsOpsIoctl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = FhgfsOpsIoctl_compatIoctl, +#endif // CONFIG_COMPAT +#ifdef KERNEL_HAS_GENERIC_FILE_SPLICE_READ + .splice_read = generic_file_splice_read, +#else + .splice_read = filemap_splice_read, +#endif +#ifdef KERNEL_HAS_ITER_FILE_SPLICE_WRITE + .splice_write = iter_file_splice_write, +#else + .splice_write = generic_file_splice_write, +#endif + +#ifdef KERNEL_HAS_GENERIC_FILE_SENDFILE + .sendfile = generic_file_sendfile, // removed in 2.6.23 (now handled via splice) +#endif // LINUX_VERSION_CODE +}; + +struct file_operations fhgfs_dir_ops = +{ + .open = FhgfsOps_opendirIncremental, + .release = FhgfsOps_releasedir, +#ifdef KERNEL_HAS_ITERATE_DIR +#if defined(KERNEL_HAS_FOPS_ITERATE) + .iterate = FhgfsOps_iterateIncremental, // linux 3.11 renamed readdir to iterate +#else + .iterate_shared = FhgfsOps_iterateIncremental, // linux 6.3 removed .iterate & it's a parallel variant of .iterate(). +#endif +#else + .readdir = FhgfsOps_readdirIncremental, // linux 3.11 renamed readdir to iterate +#endif // LINUX_VERSION_CODE + .read = generic_read_dir, // just returns the appropriate error code + .fsync = FhgfsOps_fsync, + .llseek = FhgfsOps_llseekdir, + .unlocked_ioctl = FhgfsOpsIoctl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = FhgfsOpsIoctl_compatIoctl, +#endif // CONFIG_COMPAT +}; + +/** + * Operations for files with cache type "buffered" and "none". + */ +struct address_space_operations fhgfs_address_ops = +{ +#ifdef KERNEL_HAS_READ_FOLIO + .read_folio = FhgfsOps_read_folio, +#else + .readpage = FhgfsOpsPages_readpage, +#endif + +#ifdef KERNEL_HAS_FOLIO + .readahead = FhgfsOpsPages_readahead, + .dirty_folio = filemap_dirty_folio, +#else + .readpages = FhgfsOpsPages_readpages, + .set_page_dirty = __set_page_dirty_nobuffers, +#endif + .writepage = FhgfsOpsPages_writepage, + .writepages = FhgfsOpsPages_writepages, + .direct_IO = FhgfsOps_directIO, + .write_begin = FhgfsOps_write_begin, + .write_end = FhgfsOps_write_end, +}; + +/** + * Operations for files with cache type "paged". + */ +struct address_space_operations fhgfs_address_pagecache_ops = +{ +#ifdef KERNEL_HAS_READ_FOLIO + .read_folio = FhgfsOps_read_folio, +#else + .readpage = FhgfsOpsPages_readpage, +#endif + +#ifdef KERNEL_HAS_FOLIO + .readahead = FhgfsOpsPages_readahead, + .dirty_folio = filemap_dirty_folio, +#else + .readpages = FhgfsOpsPages_readpages, + .set_page_dirty = __set_page_dirty_nobuffers, +#endif + .writepage = FhgfsOpsPages_writepage, + .writepages = FhgfsOpsPages_writepages, + .direct_IO = FhgfsOps_directIO, + .write_begin = FhgfsOps_write_begin, + .write_end = FhgfsOps_write_end, +}; + + +/** + * note: rewinddir is seek to offset 0. + * + * @param origin dirs allow only SEEK_SET (via seekdir/rewinddir from userspace). + */ +loff_t FhgfsOps_llseekdir(struct file *file, loff_t offset, int origin) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + const char* logContext = "FhgfsOps_llseekDir"; + struct inode* inode = file_inode(file); + + loff_t retVal = 0; + FsDirInfo* dirInfo = __FhgfsOps_getDirInfo(file); + + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, file_dentry(file), inode, logContext, + "offset: %lld directive: %d", (long long)offset, origin); + + if(origin != SEEK_SET) + { + if (origin == SEEK_CUR && offset == 0) { + // Some applications use lseek with SEEK_CUR and offset = 0 to get the current position in + // the file. To support that special case, we will translate the request into a SEEK_SET + // with the current file position as the offset. + offset = file->f_pos; + origin = SEEK_SET; + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, file_dentry(file), inode, logContext, + "offset: %lld position: %lld directive: %d", (long long)offset, (long long)file->f_pos, + origin); + } else { + return -EINVAL; + } + } + + + retVal = generic_file_llseek_unlocked(file, offset, origin); + if(likely(retVal >= 0) ) + { + // invalidate any retrieved contents to keep things in sync with server offset + StrCpyVec* contents = FsDirInfo_getDirContents(dirInfo); + + StrCpyVec_clear(contents); + FsDirInfo_setCurrentContentsPos(dirInfo, 0); + + FsDirInfo_setServerOffset(dirInfo, offset); + FsDirInfo_setEndOfDir(dirInfo, false); + } + + return retVal; +} + +loff_t FhgfsOps_llseek(struct file *file, loff_t offset, int origin) +{ + const char* logContext = "FhgfsOps_llseek"; + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + bool isGloballyLockedAppend = + FsFileInfo_getAppending(fileInfo) && Config_getTuneUseGlobalAppendLocks(cfg); + + loff_t retVal = 0; + struct inode *inode = file->f_mapping->host; + + FhgfsIsizeHints iSizeHints; + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, file_dentry(file), inode, logContext, + "offset: %lld directive: %d", (long long)offset, origin); + + /* note: globally locked append with SEEK_CUR is a special case, because we need to flush + the cache to find out the current offset (which is not required without append) */ + if( (origin == SEEK_END) || + (isGloballyLockedAppend && (origin == SEEK_CUR) ) ) + { // seek to position relative to end-of-file => flush cache and update current file size first + + // (note: refreshInode() also flushes caches for correct file size) + + retVal = __FhgfsOps_refreshInode(app, inode, NULL, &iSizeHints); + if(retVal) + goto clean_up; + + spin_lock(&inode->i_lock); // L O C K + + // SEEK_CUR reads (and modifies) f_pos, so in buffered append mode move to end first + if(origin == SEEK_CUR) + file->f_pos = inode->i_size; + + retVal = generic_file_llseek_unlocked(file, offset, origin); + + spin_unlock(&inode->i_lock); // U N L O C K + } + else + { // abolute or relative-to-current_pos seeks => generic stuff + retVal = generic_file_llseek_unlocked(file, offset, origin); + } + + +clean_up: + // clean-up + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, logContext, "retVal: %lld", + retVal); + + return retVal; +} + +/** + * Note: Currently unsused method, as we're using the kernel's generic_readlink function. + */ +int FhgfsOps_readlink(struct dentry* dentry, char __user* buf, int size) +{ + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_readlink"; + + int retVal; + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_WRITE, buf, size) ) ) + return -EFAULT; + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + retVal = FhgfsOpsHelper_readlink_kernel(app, FhgfsInode_getEntryInfo(fhgfsInode), buf, size); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + + return retVal; +} + +/** + * Opens a directory and prepares the handle for incremental readdir(). + */ +int FhgfsOps_opendirIncremental(struct inode* inode, struct file* file) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_opendirIncremental"; + + int retVal = 0; + //struct dentry* dentry = file_dentry(file); + FsDirInfo* dirInfo; + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, file_dentry(file), inode, logContext); + + //retVal = __FhgfsOps_refreshInode(app, inode); // not necessary + if(!retVal) + { // success + dirInfo = FsDirInfo_construct(app); + __FhgfsOps_setDirInfo(dirInfo, file); + } + +#ifdef FMODE_KABI_ITERATE + file->f_mode |= FMODE_KABI_ITERATE; +#endif + + return retVal; +} + +#ifdef KERNEL_HAS_ITERATE_DIR +int FhgfsOps_iterateIncremental(struct file* file, struct dir_context* ctx) +#else +int FhgfsOps_readdirIncremental(struct file* file, void* buf, filldir_t filldir) +#endif // LINUX_VERSION_CODE +{ + /* note: if the user seeks to a custom offset, llseekdir will invalidate any retrieved contents + and set the new offset in the dirinfo object */ + + struct dentry* dentry = file_dentry(file); + struct super_block* superBlock = dentry->d_sb; + App* app = FhgfsOps_getApp(superBlock); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_readdirIncremental"; + + int retVal = 0; + FsDirInfo* dirInfo = __FhgfsOps_getDirInfo(file); + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + StrCpyVec* dirContents = FsDirInfo_getDirContents(dirInfo); + UInt8Vec* dirContentsTypes = FsDirInfo_getDirContentsTypes(dirInfo); + StrCpyVec* dirContentIDs = FsDirInfo_getEntryIDs(dirInfo); + Int64CpyVec* serverOffsets = FsDirInfo_getServerOffsets(dirInfo); + + #ifdef KERNEL_HAS_ITERATE_DIR + loff_t* pos = &(ctx->pos); // used by dir_emit() + #else + loff_t* pos = &(file->f_pos); + #endif // LINUX_VERSION_CODE + + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, dentry, inode, logContext); + + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + + // loop as long as filldir (or dir_emit) swallows more entries (or end of dir contents reached) + for( ; ; ) + { + int refreshRes; + size_t contentsPos; + size_t contentsLength; + char* currentName; + DirEntryType currentEntryType; + unsigned currentOSEntryType; + uint64_t currentIno; + + refreshRes = FhgfsOpsHelper_refreshDirInfoIncremental(app, + FhgfsInode_getEntryInfo(fhgfsInode), dirInfo, false); + if(unlikely(refreshRes) ) + { // error occurred + retVal = refreshRes; + break; + } + + contentsLength = StrCpyVec_length(dirContents); + + /* refreshDirInfoIncremental() guarantees that we either have a valid range for current + dir offset or that dirContents list is empty */ + if(!contentsLength) + { // end of dir + LOG_DEBUG(log, Log_SPAM, logContext, "reached end of dir"); + break; + } + + contentsPos = FsDirInfo_getCurrentContentsPos(dirInfo); + + currentName = StrCpyVec_at(dirContents, contentsPos); + + currentEntryType = UInt8Vec_at(dirContentsTypes, contentsPos); + currentOSEntryType = OsTypeConv_dirEntryTypeToOS(currentEntryType); + + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, + "name: %s; pos: %lld; contentsPos: %lld/%lld; finalContents: %s", + currentName, (long long)*pos, (long long)contentsPos, + (long long)contentsLength, FsDirInfo_getEndOfDir(dirInfo) ? "yes" : "no"); + + + if(!strcmp(".", currentName) ) + currentIno = inode->i_ino; + else + if(!strcmp("..", currentName) ) + #if defined(KERNEL_HAS_PARENT_INO) + currentIno = parent_ino(dentry); + #else + currentIno = d_parent_ino(dentry); + #endif + else + { // generate inode number from entryID + const char* currentEntryID = StrCpyVec_at(dirContentIDs, contentsPos); + + currentIno = FhgfsInode_generateInodeID(superBlock, currentEntryID, + strlen(currentEntryID) ); + } + + + if(is_32bit_api() && (currentIno > UINT_MAX) ) + currentIno = currentIno >> 32; // (32-bit apps would fail with EOVERFLOW) + + + #ifdef KERNEL_HAS_ITERATE_DIR + if(!dir_emit( + ctx, currentName, strlen(currentName), currentIno, currentOSEntryType) ) + break; + #else + if(filldir( + buf, currentName, strlen(currentName), *pos, currentIno, currentOSEntryType) < 0) + break; + #endif // LINUX_VERSION_CODE + + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "filled: %s", currentName); + + // advance dir position (yes, it's alright to use the old contentsPos for the next round here) + (*pos) = Int64CpyVec_at(serverOffsets, contentsPos); + + // increment contents vector offset + FsDirInfo_setCurrentContentsPos(dirInfo, contentsPos+1); + + } // end of for-loop + + + // clean-up + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + return retVal; +} + + +/** + * Note: This works for _opendir() and for _opendirIncremental(). + */ +int FhgfsOps_releasedir(struct inode* inode, struct file* file) +{ + const char* logContext = "FhgfsOps_releasedir"; + + FsObjectInfo* fsObjectInfo = __FhgfsOps_getObjectInfo(file); + + App* app = FsObjectInfo_getApp(fsObjectInfo); + + FhgfsOpsHelper_logOp(Log_SPAM, app, file_dentry(file), inode, logContext); + + FsObjectInfo_virtualDestruct(fsObjectInfo); + + return 0; +} + +/** + * Open a file, may be called from vfs or lookup/atomic open. + * + * @param lookupInfo is NULL if this is a direct open call from the vfs + */ +int FhgfsOps_openReferenceHandle(App* app, struct inode* inode, struct file* file, + unsigned openFlags, LookupIntentInfoOut* lookupInfo, uint32_t* outVersion) +{ + Config* cfg = App_getConfig(app); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_openReferenceHandle"; + + struct super_block* sb = inode->i_sb; + struct dentry* dentry = file_dentry(file); + + int retVal = 0; + int fhgfsOpenFlags; + FileHandleType handleType; + FhgfsOpsErr openRes; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + if(unlikely(Logger_getLogLevel(log) >= 4) ) + FhgfsOpsHelper_logOp(Log_DEBUG, app, dentry, inode, logContext); + + fhgfsOpenFlags = OsTypeConv_openFlagsOsToFhgfs(openFlags, __FhgfsOps_isPagedMode(sb) ); + + openRes = FhgfsInode_referenceHandle(fhgfsInode, file_dentry(file), fhgfsOpenFlags, false, + lookupInfo, &handleType, outVersion); + + LOG_DEBUG_FORMATTED(log, 4, logContext, "remoting complete. result: %s", + FhgfsOpsErr_toErrString(openRes) ); + + if(openRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(openRes); + } + else + { // success => file is open (=> handle open flags) + FsFileInfo* fileInfo = FsFileInfo_construct(app, fhgfsOpenFlags, handleType); + + // handle O_APPEND + if(file->f_flags & O_APPEND) + FsFileInfo_setAppending(fileInfo, true); + + // handle O_DIRECT + disabled caching + if( (file->f_flags & O_DIRECT) || + ( (file->f_flags & O_APPEND) && !Config_getTuneUseBufferedAppend(cfg) ) || + (Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_None) ) + { // disable caching + FsFileInfo_setAllowCaching(fileInfo, false); + } + + __FhgfsOps_setFileInfo(fileInfo, file); + } + + return retVal; +} + +/** + * Open a file, vfs interface + */ +int FhgfsOps_open(struct inode* inode, struct file* file) +{ + const char* logContext = "FhgfsOps_open"; + + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + + struct dentry* dentry = file_dentry(file); + + unsigned openFlags = file->f_flags; + LookupIntentInfoOut* lookupInfo = NULL; // not available for direct open + + if(unlikely(Logger_getLogLevel(log) >= 4) ) + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + return FhgfsOps_openReferenceHandle(app, inode, file, openFlags, lookupInfo, NULL); +} + +/** + * Close a file. + * + * Note: We only got one shot, even in case of an error. + */ +int FhgfsOps_release(struct inode* inode, struct file* file) +{ + const char* logContext = "FhgfsOps_release"; + + int retVal = 0; + FhgfsOpsErr closeRes; + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + FsObjectInfo* fsObjectInfo = __FhgfsOps_getObjectInfo(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + FileHandleType handleType = FsFileInfo_getHandleType(fileInfo); + + App* app = FsObjectInfo_getApp(fsObjectInfo); + + FhgfsOpsHelper_logOp(Log_SPAM, app, file_dentry(file), inode, logContext); + + if(unlikely(!fileInfo) ) + { // invalid file handle + return -EBADF; + } + + FhgfsOps_releaseCancelLocks(inode, file); // cancel all locks that were not properly released yet + + closeRes = FhgfsInode_releaseHandle(fhgfsInode, handleType, file_dentry(file)); + + if(closeRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(closeRes); + } + + // note: we free the fileInfo no matter whether the communication succeeded or not + // (because _release() won't be called again even if it didn't succeed) + + FsObjectInfo_virtualDestruct( (FsObjectInfo*)fileInfo); + __FhgfsOps_setFileInfo( (FsFileInfo*)NULL, file); + + // warning: linux vfs won't return this result to user apps. only flush() res is passed to apps. + return retVal; +} + +/** + * Called during file close to unlock remaining entry locks and range locks that were not properly + * unlocked by the user-space application yet. + */ +int FhgfsOps_releaseCancelLocks(struct inode* inode, struct file* file) +{ + int retVal = 0; + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + RemotingIOInfo ioInfo; + FhgfsOpsErr unlockRes; + + /* (note: it is very unlikely that an application will use entry and range locking together on + the same file, so we have no special optimization regarding EntryMinInfoCopy for that case) */ + + if(FsFileInfo_getUsedEntryLocking(fileInfo) ) + { // entry locks were used with this file handle + int64_t clientFD = __FhgfsOps_getCurrentLockFD(file); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + unlockRes = FhgfsOpsHelper_unlockEntryWithAsyncRetry(&fhgfsInode->entryInfo, + &fhgfsInode->entryInfoLock, &ioInfo, clientFD); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(!retVal) + retVal = FhgfsOpsErr_toSysErr(unlockRes); + } + + /* (note: FhgfsInode_getNumRangeLockPIDs() below is a shortcut to save the time for mutex locking + if no range locks were used for this inode.) */ + + if(FhgfsInode_getNumRangeLockPIDs(fhgfsInode) && + FhgfsInode_removeRangeLockPID(fhgfsInode, __FhgfsOps_getCurrentLockPID() ) ) + { // current pid used range locking on this inode + int ownerPID = __FhgfsOps_getCurrentLockPID(); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + unlockRes = FhgfsOpsHelper_unlockRangeWithAsyncRetry(&fhgfsInode->entryInfo, + &fhgfsInode->entryInfoLock, &ioInfo, ownerPID); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(!retVal) + retVal = FhgfsOpsErr_toSysErr(unlockRes); + } + + return retVal; +} + +/** + * Called by flock syscall. + * + * @return 0 on success, negative linux error code otherwise + */ +int FhgfsOps_flock(struct file* file, int cmd, struct file_lock* fileLock) +{ + const char* logContext = __func__; + + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + bool useGlobalFileLocks = Config_getTuneUseGlobalFileLocks(cfg); + FhgfsOpsErr globalLockRes = FhgfsOpsErr_SUCCESS; + int lockTypeFlags; + + lockTypeFlags = OsTypeConv_flockTypeToFhgfs(fileLock); + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, file_dentry(file), inode, logContext, "lockType: %s", + LockingTk_lockTypeToStr(lockTypeFlags) ); + + + // flush buffers before removing a global lock + + if(useGlobalFileLocks && (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_REMOVE) ) + { + int flushRes = __FhgfsOps_flush(app, file, false, false, true, + false); + + /* note: can't return error here and must continue, because local unlock must always be done + to avoid BUG() statement being triggered in locks_remove_flock() on cleanup after kill */ + if(unlikely(flushRes < 0) ) + Logger_logFormatted(log, Log_NOTICE, logContext, + "Flushing before unlock failed. Continuing anyways. flushRes: %d", flushRes); + } + + // global locking + + if(useGlobalFileLocks) + { + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + FsFileInfo_setUsedEntryLocking(fileInfo); + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + globalLockRes = FhgfsOpsRemoting_flockEntryEx(&fhgfsInode->entryInfo, + &fhgfsInode->entryInfoLock, app, ioInfo.fileHandleID, (size_t)FhgfsCommon_getFileLock(fileLock), + FhgfsCommon_getFileLockPID(fileLock), lockTypeFlags, true); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "remoting complete. result: %s", + FhgfsOpsErr_toErrString(globalLockRes) ); + + /* note: local unlock must always be done for cleanup (otherwise e.g. killing a process + holding a lock results in the BUG() statement being triggered in locks_remove_flock() ) */ + if( (globalLockRes != FhgfsOpsErr_SUCCESS) && + !(lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_REMOVE) ) + return FhgfsOpsErr_toSysErr(globalLockRes); + } + + // local locking + + { + #if defined(KERNEL_HAS_LOCKS_FILELOCK_INODE_WAIT) || defined(KERNEL_HAS_LOCKS_LOCK_INODE_WAIT) + int localLockRes = locks_lock_inode_wait(file_inode(file), fileLock); + #else + int localLockRes = flock_lock_file_wait(file, fileLock); + #endif + + if(!useGlobalFileLocks) + return localLockRes; + + if(localLockRes && + (globalLockRes == FhgfsOpsErr_SUCCESS) && + (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_ADD) ) + { // sanity check + Logger_logFormatted(log, Log_NOTICE, logContext, + "Unexpected: Global locking succeeded, but local locking failed. SysErr: %d", + localLockRes); + } + } + + // flush buffers after we got a new global lock + + if(useGlobalFileLocks && + (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_ADD) && + (globalLockRes == FhgfsOpsErr_SUCCESS) ) + { + int flushRes = __FhgfsOps_flush(app, file, false, false, true, + false); + + if(unlikely(flushRes < 0) ) + return flushRes; // flush error occured + } + + return FhgfsOpsErr_toSysErr(globalLockRes); +} + +/** + * Called by fcntl syscall (F_GETLK, F_SETLK, F_SETLKW) for file range locking. + * + * @return 0 on success, negative linux error code otherwise + */ +int FhgfsOps_lock(struct file* file, int cmd, struct file_lock* fileLock) +{ + const char* logContext = "FhgfsOps_lock (fcntl)"; + + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + bool useGlobalFileLocks = Config_getTuneUseGlobalFileLocks(cfg); + FhgfsOpsErr globalLockRes = FhgfsOpsErr_SUCCESS; + int lockTypeFlags; + + + // handle user request for conflicting locks (always local-only currently, see notes below) + + if(cmd == F_GETLK) + { // get confliciting lock + + /* note: it's questionable if returning remote locks makes sense (because the local app could + misinterpret the pid of the lock-holder), so we do local only for now. */ + + posix_test_lock(file, fileLock); + + /* note: "fileLock->fl_type != F_UNLCK" would tell us now whether a conflicting local lock + was found */ + + return 0; + } + + + lockTypeFlags = OsTypeConv_flockTypeToFhgfs(fileLock); + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, file_dentry(file), inode, logContext, + "lockType: %s; start: %lld; end: %lld", LockingTk_lockTypeToStr(lockTypeFlags), + (long long)fileLock->fl_start, (long long)fileLock->fl_end); + + + // flush buffers before removing a global lock + + if(useGlobalFileLocks && + (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_REMOVE) ) + { + int flushRes = __FhgfsOps_flush(app, file, false, false, true, false); + + /* note: can't return error here and must continue, because local unlock must always be done + to avoid BUG() statement being triggered in locks_remove_flock() on cleanup after kill */ + if(unlikely(flushRes < 0) ) + Logger_logFormatted(log, Log_NOTICE, logContext, + "Flushing before unlock failed. Continuing anyways. flushRes: %d", flushRes); + } + + // global locking + + if(useGlobalFileLocks) + { + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + FhgfsInode_addRangeLockPID(fhgfsInode, FhgfsCommon_getFileLockPID(fileLock)); + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + globalLockRes = FhgfsOpsRemoting_flockRangeEx(&fhgfsInode->entryInfo, + &fhgfsInode->entryInfoLock, ioInfo.app, ioInfo.fileHandleID, FhgfsCommon_getFileLockPID(fileLock), + lockTypeFlags, fileLock->fl_start, fileLock->fl_end, true); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "remoting complete. result: %s", + FhgfsOpsErr_toErrString(globalLockRes) ); + + /* note: local unlock must always be done for cleanup (otherwise e.g. killing a process + holding a lock results in the BUG() statement being triggered in locks_remove_flock() ) */ + if( (globalLockRes != FhgfsOpsErr_SUCCESS) && + !(lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_REMOVE) ) + return FhgfsOpsErr_toSysErr(globalLockRes); + } + + // local locking + + { + /* note on local+global locking: + we need to call posix_lock_file_wait() even with global locks, because inode->i_flock needs + to be set, so that locks_remove_posix() gets active (via filp_close() ) and thus the + condition "Record locks are not inherited by a child created via fork(2), but are preserved + across an execve(2)." [man 2 fcntl] holds. + Otherwise we wouldn't be notified about an unlock on parent process exit, as there are + still references to the filp and thus our ->release() isn't invoked. (See trac #271) */ + + /* note on local/global locking order: + local locking needs to be done after global locking, because otherwise if global locking + failed we wouldn't know how to undo the local locking (e.g. if the process acquires a + shared lock for the second time or does a merge with existing ranges). */ + +#if defined(KERNEL_HAS_LOCKS_FILELOCK_INODE_WAIT) || defined(KERNEL_HAS_LOCKS_LOCK_INODE_WAIT) + int localLockRes = locks_lock_inode_wait(file_inode(file), fileLock); +#else + int localLockRes = posix_lock_file_wait(file, fileLock); +#endif + + //printk_fhgfs_debug(KERN_WARNING, "posix_lock_file result=%d, cmd=%d\n", localLockRes, cmd); + + if(!useGlobalFileLocks) + return localLockRes; + + if(localLockRes && + (globalLockRes == FhgfsOpsErr_SUCCESS) && + (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_ADD) ) + { // sanity check + Logger_logFormatted(log, Log_NOTICE, logContext, + "Unexpected: Global locking succeeded, but local locking failed. SysErr: %d", + localLockRes); + } + } + + // flush buffers after we got a new global lock + + if(useGlobalFileLocks && + (lockTypeFlags & ENTRYLOCKTYPE_LOCKOPS_ADD) && + (globalLockRes == FhgfsOpsErr_SUCCESS) ) + { + int flushRes = __FhgfsOps_flush(app, file, false, false, true, false); + if(unlikely(flushRes < 0) ) + return flushRes; // flush error occured + } + + return FhgfsOpsErr_toSysErr(globalLockRes); +} + + +static ssize_t read_common(struct file *file, struct iov_iter *iter, size_t size, loff_t *offsetPointer) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + + struct inode* inode = file->f_mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + ssize_t readRes; + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "(offset: %lld; size: %lld)", + (long long)*offsetPointer, (long long)size); + IGNORE_UNUSED_VARIABLE(app); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + if (app->cfg->tuneCoherentBuffers) + { + readRes = filemap_write_and_wait(file->f_mapping); + if (readRes < 0) + return readRes; + + // ignore the -EBUSY we could receive here, because there is just *no* way we can keep caches + // coherent without locking everything all the time. if this produces inconsistent data, + // something must have been racy anyway. + invalidate_inode_pages2(file->f_mapping); + // Increment coherent read/write counter + atomic_inc(&fhgfsInode->coRWInProg); + } + + readRes = FhgfsOpsHelper_readCached(iter, size, *offsetPointer, fhgfsInode, fileInfo, &ioInfo); + //readRes = FhgfsOpsRemoting_readfile(buf, size, *offsetPointer, &ioInfo); + + if(readRes < 0) + { // read error (=> transform negative fhgfs error code to system error code) + if (app->cfg->tuneCoherentBuffers) + atomic_dec(&fhgfsInode->coRWInProg); + return FhgfsOpsErr_toSysErr(-readRes); + } + + *offsetPointer += readRes; + FsFileInfo_setLastReadOffset(fileInfo, *offsetPointer); + + if( ( (size_t)readRes < size) && (i_size_read(inode) > *offsetPointer) ) + { // sparse file compatibility mode + ssize_t readSparseRes = __FhgfsOps_readSparse( + file, iter, size - readRes, *offsetPointer); + + if(unlikely(readSparseRes < 0) ) + { + if (app->cfg->tuneCoherentBuffers) + atomic_dec(&fhgfsInode->coRWInProg); + return readSparseRes; + } + + *offsetPointer += readSparseRes; + readRes += readSparseRes; + + FsFileInfo_setLastReadOffset(fileInfo, *offsetPointer); + } + + // add to /proc//io + task_io_account_read(readRes); + + // Decrement coherent read/write counter + if (app->cfg->tuneCoherentBuffers) + atomic_dec(&fhgfsInode->coRWInProg); + + return readRes; +} + + +/** + * Special reading mode that is slower (e.g. not parallel) but compatible with sparse files. + * + * Note: Intended to be just a helper for actual read methods (e.g. won't increase the offset + * pointer). + * + * @return negative Linux error code on error, read bytes otherwise + */ +ssize_t __FhgfsOps_readSparse(struct file* file, struct iov_iter *iter, size_t size, loff_t offset) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + + struct inode* inode = file->f_mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + ssize_t readRes; + loff_t i_size; + FhgfsOpsErr helperReadRes; + FhgfsIsizeHints iSizeHints; + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "(offset: %lld; size: %lld)", + (long long)offset, (long long)size); + + readRes = __FhgfsOps_refreshInode(app, inode, NULL, &iSizeHints); + if(unlikely(readRes) ) + return readRes; + + i_size = i_size_read(inode); + if(i_size <= offset) + return 0; // EOF + + // adapt read length to current file length + size = MIN(size, (unsigned long long)(i_size - offset) ); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + helperReadRes = FhgfsOpsHelper_readOrClearUser(app, iter, size, offset, fileInfo, &ioInfo); + + if(unlikely(helperReadRes != FhgfsOpsErr_SUCCESS) ) + return FhgfsOpsErr_toSysErr(helperReadRes); + + return size; +} + + +ssize_t FhgfsOps_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + size_t count = iov_iter_count(to); + loff_t pos = iocb->ki_pos; + + struct file* file = iocb->ki_filp; + struct address_space* mapping = file->f_mapping; + struct inode* inode = mapping->host; + + App* app = FhgfsOps_getApp(inode->i_sb); + + ssize_t retVal; + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "(offset: %lld; size: %lld)", + (long long)pos, (long long)count); + + IGNORE_UNUSED_VARIABLE(pos); + IGNORE_UNUSED_VARIABLE(count); + + retVal = __FhgfsOps_revalidateMapping(app, inode); + if(unlikely(retVal) ) + { // error + return retVal; + } + + return generic_file_read_iter(iocb, to); +} + +static ssize_t FhgfsOps_buffered_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + return read_common(iocb->ki_filp, to, iov_iter_count(to), &iocb->ki_pos); +} + +static ssize_t write_common(struct file *file, struct iov_iter *from, size_t size, loff_t *offsetPointer) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Config* cfg = App_getConfig(app); + + struct inode* inode = file->f_mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + loff_t writeOffset; + ssize_t writeRes; + loff_t newMinFileSize; // to update i_size after write + + bool isLocallyLockedAppend = + FsFileInfo_getAppending(fileInfo) && !Config_getTuneUseGlobalAppendLocks(cfg); + bool isGloballyLockedAppend = + FsFileInfo_getAppending(fileInfo) && Config_getTuneUseGlobalAppendLocks(cfg); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "(offset: %lld; size: %lld)", + (long long)*offsetPointer, (long long)size); + + inode_lock(inode); + { + writeRes = os_generic_write_checks(file, offsetPointer, &size, S_ISBLK(inode->i_mode) ); + if (likely(! writeRes)) // success + writeRes = file_remove_privs(file); + } + inode_unlock(inode); + + if (unlikely(writeRes)) + return writeRes; + + if (app->cfg->tuneCoherentBuffers) + { + /* this flush is necessary to ensure that delayed flushing of the page cache does not + * overwrite the data written here, even though it was written to the file first. */ + writeRes = filemap_write_and_wait(file->f_mapping); + if (writeRes < 0) + return writeRes; + + /* ignore the -EBUSY we could receive here, because there is just *no* way we can keep caches + * coherent without locking everything all the time. if this produces inconsistent data, + * something must have been racy anyway. */ + invalidate_inode_pages2(file->f_mapping); + //Increment coherent rw counter + atomic_inc(&fhgfsInode->coRWInProg); + } + + if(isLocallyLockedAppend) + { // appending without global locks => move file offset to end-of-file before writing + + /* note on flush and lock: the flush here must be inside the local lock, but cannot happen at + the place where we take the global lock (because that might be called from a flush path + itself), that's why global and local locks are taken at different places. */ + + int flushRes; + FhgfsOpsErr statRes; + fhgfs_stat fhgfsStat; + + Fhgfsinode_appendLock(fhgfsInode); // L O C K (append) + + flushRes = __FhgfsOps_flush(app, file, false, false, true, false); + if(unlikely(flushRes < 0) ) + { // flush error + writeRes = flushRes; + goto unlockappend_and_exit; + } + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + /* note on stat here: we could pass -1 to _writeCached and remove the stat here, but the + disadvantage would be that we don't have the correct file offset for i_size then, so we + leave the stat here for now. */ + + statRes = FhgfsOpsRemoting_statDirect(app, FhgfsInode_getEntryInfo(fhgfsInode), &fhgfsStat); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(unlikely(statRes != FhgfsOpsErr_SUCCESS) ) + { // remote stat error + writeRes = FhgfsOpsErr_toSysErr(statRes); + goto unlockappend_and_exit; + } + + *offsetPointer = fhgfsStat.size; + } + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + writeOffset = isGloballyLockedAppend ? -1 : *offsetPointer; + + writeRes = FhgfsOpsHelper_writeCached(from, size, writeOffset, fhgfsInode, fileInfo, &ioInfo); + //writeRes = FhgfsOpsRemoting_writefile(from, size, *offsetPointer, &ioInfo); + + if(unlikely(writeRes < 0) ) + { // write error (=> transform negative fhgfs error code to system error code) + writeRes = FhgfsOpsErr_toSysErr(-writeRes); + goto unlockappend_and_exit; + } + + if(!isGloballyLockedAppend) + { // for (buffered) global append locks, new offset/filesize would be unknown + newMinFileSize = *offsetPointer + writeRes; // update with old offset to avoid offset==0 check + *offsetPointer += writeRes; + + FsFileInfo_setLastWriteOffset(fileInfo, *offsetPointer); + + // check current file size and update if necessary (also important for sparse read heuristic) + spin_lock(&inode->i_lock); + if(inode->i_size < newMinFileSize) + i_size_write(inode, newMinFileSize); + spin_unlock(&inode->i_lock); + } + + // add to /proc//io + task_io_account_write(writeRes); + +unlockappend_and_exit: + if(isLocallyLockedAppend) + Fhgfsinode_appendUnlock(fhgfsInode); // U N L O C K (append) + + // Decrement coherent read/write counter + if (app->cfg->tuneCoherentBuffers) + atomic_dec(&fhgfsInode->coRWInProg); + return writeRes; +} + +ssize_t FhgfsOps_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + size_t count = iov_iter_count(from); + loff_t pos = iocb->ki_pos; + + struct file* file = iocb->ki_filp; + struct dentry* dentry = file_dentry(file); + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + FhgfsIsizeHints iSizeHints; + + ssize_t retVal; + int writeCheckRes; + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "(offset: %lld; size: %lld)", + (long long)pos, (long long)count); + + if (iocb->ki_pos != pos) + { /* Similiar to WARN_ON(iocb->ki_pos != pos), as fuse does */ + Logger_logErrFormatted(log, logContext, "Bug: iocb->ki_pos != pos (%lld vs %lld)", + iocb->ki_pos, pos); + + dump_stack(); + } + + if(iocb->ki_filp->f_flags & O_APPEND) + { // O_APPEND => flush (for correct size) and refresh file size + + retVal = __FhgfsOps_refreshInode(app, inode, NULL, &iSizeHints); + if(retVal) + return retVal; + } + + writeCheckRes = os_generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode) ); + if(unlikely(writeCheckRes) ) + return writeCheckRes; + + if(!count) + return 0; + + if( (file->f_flags & O_APPEND) && (pos != iocb->ki_pos) ) + { + /* pos was updated by generic_write_checks (append writes), so we also need to update + * iocb->ki_pos, otherwise generic_file_aio_write() will call BUG_ON */ + iocb->ki_pos = pos; + } + + iov_iter_truncate(from, count); + + retVal = generic_file_write_iter(iocb, from); + + if( (retVal >= 0) + && ( (IS_SYNC(inode) || (iocb->ki_filp->f_flags & O_SYNC) ) + || unlikely(FhgfsInode_getHasWritePageError(fhgfsInode)) ) ) + { // sync I/O => flush and wait + struct address_space* mapping = inode->i_mapping; + + if(mapping->nrpages) + { + int writeRes = filemap_fdatawrite(mapping); + if(writeRes >= 0) + { + int waitRes = filemap_fdatawait(mapping); + if(waitRes < 0) + retVal = waitRes; + } + else + retVal = writeRes; + } + + if (unlikely(FhgfsInode_getHasWritePageError(fhgfsInode) ) && retVal >= 0) + FhgfsInode_clearWritePageError(fhgfsInode); + } // end of if(sync) + + return retVal; +} + +static ssize_t FhgfsOps_buffered_write_iter(struct kiocb *iocb, struct iov_iter *from) +{ + return write_common(iocb->ki_filp, from, iov_iter_count(from), &iocb->ki_pos); +} + + +#ifdef KERNEL_HAS_FSYNC_RANGE /* added in vanilla 3.1 */ +int FhgfsOps_fsync(struct file* file, loff_t start, loff_t end, int datasync) +{ + struct dentry* dentry = file_dentry(file); +#elif !defined(KERNEL_HAS_FSYNC_DENTRY) +int FhgfsOps_fsync(struct file* file, int datasync) +{ + struct dentry* dentry = file_dentry(file); +#else /* LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) */ +int FhgfsOps_fsync(struct file* file, struct dentry* dentry, int datasync) +{ +#endif // LINUX_VERSION_CODE + App* app = FhgfsOps_getApp(dentry->d_sb); + Config* cfg = App_getConfig(app); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_fsync"; + + int retVal = 0; + FsObjectInfo* fsObjectInfo; + + struct inode* inode = file_inode(file); + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + fsObjectInfo = __FhgfsOps_getObjectInfo(file); + + // syncing something other than a file? + if(FsObjectInfo_getObjectType(fsObjectInfo) != FsObjectType_FILE) + goto clean_up; + + retVal = __FhgfsOps_flush(app, file, false, Config_getTuneRemoteFSync(cfg), true, + false); + + if(retVal) + goto clean_up; + + +clean_up: + + return retVal; +} + +/** + * Flush data from local cache to servers. + * Note: This method is in fact a local sync (as wait until data have arrived remotely) + * + * @param discardCacheOnError whether or not to discard the internal buffer cache in case of an + * error (typically you will only want to set true here during file close). + * @param forceRemoteFlush whether or not remote fsync will be executed depends on this value + * and the corresponding config value (fsync will use true here and flush won't) + * @param checkSession whether or not a server crash detection must be done on the server + * @param isClose whether or not the method is called by a close + * @return negative linux error code on error + */ +int __FhgfsOps_flush(App* app, struct file *file, bool discardCacheOnError, + bool forceRemoteFlush, bool checkSession, bool isClose) +{ + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + const char* logContext = __func__; + + struct inode* inode = file->f_mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + RemotingIOInfo ioInfo; + int filemapWaitRes; + int inodeWriteRes; + FhgfsOpsErr flushRes; + FhgfsOpsErr bumpRes; + int retVal = 0; + bool hasWriteHandle = FhgfsInode_hasWriteHandle(fhgfsInode); + + bool doSyncOnClose = Config_getSysSyncOnClose(App_getConfig(app)) && isClose; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, file_dentry(file), inode, logContext); + + if (hasWriteHandle || FhgfsInode_getHasDirtyPages(fhgfsInode) ) + { + // flush page cache + inodeWriteRes = write_inode_now(inode, 1); + filemapWaitRes = filemap_fdatawait(file->f_mapping); + if(unlikely(inodeWriteRes < 0 || filemapWaitRes < 0) ) + { + retVal = (inodeWriteRes < 0) ? inodeWriteRes : filemapWaitRes; + goto clean_up; + } + } + + // flush buffer cache + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + flushRes = FhgfsOpsHelper_flushCache(app, fhgfsInode, discardCacheOnError); + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + { // error + retVal = FhgfsOpsErr_toSysErr(flushRes); + goto clean_up; + } + + // remote fsync + + if(forceRemoteFlush || (checkSession && Config_getSysSessionCheckOnClose(cfg)) || doSyncOnClose) + { + FhgfsOpsErr fsyncRes = FhgfsOpsRemoting_fsyncfile(&ioInfo, forceRemoteFlush, checkSession, + doSyncOnClose); + if(unlikely(fsyncRes != FhgfsOpsErr_SUCCESS) ) + { + retVal = FhgfsOpsErr_toSysErr(fsyncRes); + goto clean_up; + } + } + + if ((cfg->eventLogMask & EventLogMask_FLUSH) && (file->f_flags & (O_ACCMODE | O_TRUNC))) + { + struct FileEvent event; + + FileEvent_init(&event, FileEventType_FLUSH, file_dentry(file)); + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + bumpRes = FhgfsOpsRemoting_bumpFileVersion(FhgfsOps_getApp(file_dentry(file)->d_sb), + &fhgfsInode->entryInfo, false, &event); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + + FileEvent_uninit(&event); + + if (bumpRes != FhgfsOpsErr_SUCCESS) + retVal = FhgfsOpsErr_toSysErr(bumpRes); + } + +clean_up: + + return retVal; +} + +int FhgfsOps_mmap(struct file* file, struct vm_area_struct* vma) +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_mmap"; + int locked; + int retry; + int max_retry; + + FhgfsIsizeHints iSizeHints; + + int retVal; + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, file_dentry(file), inode, logContext); + + locked = 0; + retry = 0; + + /* + * If there are reads/writes already in progress, retry for the inode cache + * lock till MMAP_RETRY_LOCK_EASY iterations. reads/write will anyway + * flush the cache. So even if mmap can not get the inode cache lock, it can + * proceed with the operation. If read/writes are not in progress, wait for + * more iteration before we get the lock. But mmap should not block forever + * for the cache lock to avoid the deadlock condition. + * If mmap has to proceed without getting lock, we will print warning message + * indicating cache might not be coherent. + */ + + max_retry = atomic_read(&fhgfsInode->coRWInProg) > 0 ? + MMAP_RETRY_LOCK_EASY : MMAP_RETRY_LOCK_HARD; + + if (app->cfg->tuneCoherentBuffers) + { + FhgfsOpsErr flushRes; + + do + { + locked = FhgfsInode_fileCacheExclusiveTryLock(fhgfsInode); + if (locked) + break; + + // Sleep and retry for the lock + mdelay(10); + retry++; + } while (!locked && retry < max_retry); + + if (locked) + { + flushRes = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, false); + if (flushRes != FhgfsOpsErr_SUCCESS) + { + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); + retVal = FhgfsOpsErr_toSysErr(flushRes); + goto exit; + } + } + else + printk_fhgfs_debug(KERN_WARNING, + "mmap couldn't flush the cache. Cache might not be coherent\n"); + } + + retVal = generic_file_mmap(file, vma); + + if(!retVal) + retVal = __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, true); + + if (app->cfg->tuneCoherentBuffers && locked) + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); + +exit: + LOG_DEBUG_FORMATTED(log, 5, logContext, "result: %d", retVal); + + return retVal; +} + + +/** + * @param fsdata can be used to hand any data over to write_end() (but note that the old + * prepare_write() doesn't have this) + * @return 0 on success + */ +static int FhgfsOps_write_begin(struct file* file, struct address_space* mapping, + loff_t pos, unsigned len, +#if BEEGFS_HAS_WRITE_FLAGS + unsigned flags, +#endif + beegfs_pgfol_t *pgfolp, void** fsdata) +{ + pgoff_t index = pos >> PAGE_SHIFT; + loff_t offset = pos & (PAGE_SIZE - 1); + loff_t page_start = pos & PAGE_MASK; + + loff_t i_size; + + struct page* page; + + int retVal = 0; + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + + // FhgfsOpsHelper_logOpDebug(app, file->f_dentry, __func__, "(offset: %lld; page_start: %lld; len: %u)", + // (long long)offset, (long long)page_start, len); + IGNORE_UNUSED_VARIABLE(app); + + page = beegfs_grab_cache_page(mapping, index, +#if BEEGFS_HAS_WRITE_FLAGS + flags +#else + 0 +#endif + ); + + if(!page) + { + retVal = -ENOMEM; + goto clean_up; + } + + if(PageUptodate(page) ) + goto clean_up; + + if(len == PAGE_SIZE) + { + // two possibilities: + // a) full page write => no need to read-update the page from the server + // b) short write (with offset) => will lead to sync write + goto clean_up; + } + + i_size = i_size_read(mapping->host); + if( (page_start >= i_size) || + (!offset && ( (pos + len) >= i_size) ) ) + { + // we don't need to read data beyond the end of the file + // => zero it, and set the page up-to-date + + zero_user_segments(page, 0, offset, offset + len, PAGE_SIZE); + + // note: PageChecked means rest of the page (to which is not being written) is up-to-date. + // so when our data is written, the whole page is up-to-date. + SetPageChecked(page); + + goto clean_up; + } + + // it is read-modify-write, so update the page with the server content + retVal = FhgfsOpsPages_readpageSync(file, page); + +clean_up: + // clean-up + *pgfolp = beegfs_to_pgfol(page); + return retVal; +} + + +/** + * @param copied the amount that was able to be copied ("copied==len" is always true if + * write_begin() was called with the AOP_FLAG_UNINTERRUPTIBLE flag) + * @param fsdata whatever write_begin() set here + * @return < 0 on failure, number of bytes copied into pagecache (<= 'copied') otherwise + **/ +static int FhgfsOps_write_end(struct file* file, struct address_space* mapping, + loff_t pos, unsigned len, unsigned copied, beegfs_pgfol_t pgfol, void* fsdata) +{ + struct page* page = beegfs_get_page(pgfol); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + + struct inode* inode = mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + int retVal; + + struct dentry *dentry= file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + // FhgfsOpsHelper_logOpDebug(app, dentry, logContext, "pos: %lld; len: %u; copied: %u", + // (long long)pos, len, copied); + IGNORE_UNUSED_VARIABLE(logContext); + + if(PageChecked(page) ) + { + // note: see write_begin() for meaning of PageChecked() + + if(copied == len) + SetPageUptodate(page); + + ClearPageChecked(page); + } + else + if(!PageUptodate(page) && (copied == PAGE_SIZE) ) + SetPageUptodate(page); + + + if(!PageUptodate(page) ) + { + unsigned offset = pos & (PAGE_SIZE - 1); + char* buf = kmap(page); + RemotingIOInfo ioInfo; + ssize_t writeRes; + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + FhgfsInode_incWriteBackCounter(fhgfsInode); + + writeRes = FhgfsOpsRemoting_writefile(&buf[offset], copied, pos, &ioInfo); + + spin_lock(&inode->i_lock); + FhgfsInode_setLastWriteBackOrIsizeWriteTime(fhgfsInode); + FhgfsInode_decWriteBackCounter(fhgfsInode); + spin_unlock(&inode->i_lock); + + if(likely(writeRes > 0) ) + { + retVal = writeRes; + pos += writeRes; + } + else + retVal = FhgfsOpsErr_toSysErr(-writeRes); + + kunmap(page); + } + else + { + retVal = copied; + pos += copied; + + if (!PageDirty(page) ) + { // Only add if the page is not dirty yet (don't add the same page twice...) + FhgfsInode_incNumDirtyPages(fhgfsInode); + } + set_page_dirty(page); // could be in the if-condition above, but for safety we set it here + + } + + if(likely(retVal > 0) ) + { + spin_lock(&inode->i_lock); + if(pos > inode->i_size) + { + FhgfsInode_setPageWriteFlag(fhgfsInode); + FhgfsInode_setLastWriteBackOrIsizeWriteTime(fhgfsInode); + FhgfsInode_setNoIsizeDecrease(fhgfsInode); + i_size_write(inode, pos); + } + spin_unlock(&inode->i_lock); + } + + unlock_page(page); + put_page(page); + + // clean-up + + // LOG_DEBUG_FORMATTED(log, 5, logContext, "complete. retVal: %d", retVal); + IGNORE_UNUSED_VARIABLE(log); + + return retVal; +} + +static ssize_t __FhgfsOps_directIO_common(int rw, struct kiocb *iocb, struct iov_iter *iter, loff_t pos) +{ + struct iov_iter bgfsIter = *iter; // Was a wrapper copy. Now is just a defensive copy. Still needed? + struct file* file = iocb->ki_filp; + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + struct dentry* dentry = file_dentry(file); + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + RemotingIOInfo ioInfo; + + ssize_t remotingRes; + + + const char* logContext = __func__; + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, logContext, "(%s, pos: %lld, nr_seqs: %lld)", + (rw == WRITE) ? "WRITE" : "READ", (long long)pos); + IGNORE_UNUSED_VARIABLE(logContext); // non-debug builds + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + if(rw == WRITE) + { // write + remotingRes = FhgfsOpsRemoting_writefileVec(&bgfsIter, pos, &ioInfo, false); + } + else if(rw == READ) + { // read + remotingRes = FhgfsOpsRemoting_readfileVec(&bgfsIter, iov_iter_count(&bgfsIter), pos, &ioInfo, fhgfsInode); + + if( (remotingRes >= 0 && iov_iter_count(&bgfsIter)) + && ( i_size_read(inode) > (pos + remotingRes) ) ) + { // sparse file compatibility mode + ssize_t readSparseRes = __FhgfsOps_readSparse(file, &bgfsIter, iov_iter_count(&bgfsIter), pos + remotingRes); + + if(unlikely(readSparseRes < 0) ) + remotingRes = readSparseRes; + else + remotingRes += readSparseRes; + } + } + else + { +#ifdef WARN_ONCE + WARN_ONCE(1, "unexpected: rw value !=READ and !=WRITE. (int value: %d)\n", rw); +#endif + return -EINVAL; + } + + //Write back wrapped iter. + *iter = bgfsIter; + + if(unlikely(remotingRes < 0) ) + { // error occurred + LOG_DEBUG_FORMATTED(log, 1, logContext, "error: %s", + FhgfsOpsErr_toErrString(-remotingRes) ); + IGNORE_UNUSED_VARIABLE(log); + + return FhgfsOpsErr_toSysErr(-remotingRes); + } + + if(rw == WRITE) + task_io_account_write(remotingRes); + else + task_io_account_read(remotingRes); + + return remotingRes; +} + +/** + * Note: This method must be defined because otherwise the kernel rejects open() with O_DIRECT in + * fs/open.c. However, it is only called indirectoy through the generic file read/write routines + * (and swapping code), so it should actually never be called for buffered IO. + * + * @param rw whether this is a read or a write {READ, WRITE} + * @param iocb I/O control block with open file handle + * @param iov I/O buffer vectors (array) + * @param pos file offset + * @param nr_segs length of iov array + */ +ssize_t FhgfsOps_directIO(struct kiocb *iocb, struct iov_iter *iter) +{ + int rw = iov_iter_rw(iter); + loff_t pos = iocb->ki_pos; + return __FhgfsOps_directIO_common(rw, iocb, iter, pos); +} diff --git a/client_module/source/filesystem/FhgfsOpsFile.h b/client_module/source/filesystem/FhgfsOpsFile.h new file mode 100644 index 0000000..65d3bea --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsFile.h @@ -0,0 +1,205 @@ +#ifndef FHGFSOPSFILE_H_ +#define FHGFSOPSFILE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOps_versions.h" +#include "FhgfsOpsInode.h" + + +#include +#include +#include +#include + + +#ifndef SEEK_SET +#define SEEK_SET 0 /* seek relative to beginning of file */ +#define SEEK_CUR 1 /* seek relative to current file position */ +#define SEEK_END 2 /* seek relative to end of file */ +#endif + + +// forward declaration +struct App; + + +extern struct file_operations fhgfs_file_buffered_ops; +extern struct file_operations fhgfs_file_pagecache_ops; + +extern struct file_operations fhgfs_dir_ops; + +extern struct address_space_operations fhgfs_address_ops; +extern struct address_space_operations fhgfs_address_pagecache_ops; + + +extern loff_t FhgfsOps_llseekdir(struct file *file, loff_t offset, int origin); +extern loff_t FhgfsOps_llseek(struct file *file, loff_t offset, int origin); + +extern int FhgfsOps_opendirIncremental(struct inode* inode, struct file* file); +extern int FhgfsOps_releasedir(struct inode* inode, struct file* file); + +#ifdef KERNEL_HAS_ITERATE_DIR +extern int FhgfsOps_iterateIncremental(struct file* file, struct dir_context* ctx); +#else +extern int FhgfsOps_readdirIncremental(struct file* file, void* buf, filldir_t filldir); +#endif // LINUX_VERSION_CODE + +extern int FhgfsOps_open(struct inode* inode, struct file* file); +extern int FhgfsOps_openReferenceHandle(App* app, struct inode* inode, struct file* file, + unsigned openFlags, LookupIntentInfoOut* lookupInfo, uint32_t* outVersion); +extern int FhgfsOps_release(struct inode* inode, struct file* file); + + +#ifdef KERNEL_HAS_FSYNC_RANGE /* added in vanilla 3.1 */ + int FhgfsOps_fsync(struct file* file, loff_t start, loff_t end, int datasync); +#elif !defined(KERNEL_HAS_FSYNC_DENTRY) + int FhgfsOps_fsync(struct file* file, int datasync); +#else + /* LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,34) */ + int FhgfsOps_fsync(struct file* file, struct dentry* dentry, int datasync); +#endif // LINUX_VERSION_CODE + +extern int __FhgfsOps_flush(App* app, struct file *file, bool discardCacheOnError, + bool forceRemoteFlush, bool checkSession, bool isClose); + +extern int FhgfsOps_flock(struct file* file, int cmd, struct file_lock* fileLock); +extern int FhgfsOps_lock(struct file* file, int cmd, struct file_lock* fileLock); + +extern int FhgfsOps_readlink(struct dentry* dentry, char __user* buf, int size); + +extern ssize_t FhgfsOps_read(struct file* file, char __user *buf, size_t size, + loff_t* offsetPointer); +extern ssize_t FhgfsOps_write(struct file* file, const char __user *buf, size_t size, + loff_t* offsetPointer); + +ssize_t FhgfsOps_read_iter(struct kiocb *iocb, struct iov_iter *to); +ssize_t FhgfsOps_write_iter(struct kiocb *iocb, struct iov_iter *from); + +extern int FhgfsOps_mmap(struct file *, struct vm_area_struct *); + +extern ssize_t FhgfsOps_directIO(struct kiocb *iocb, struct iov_iter *iter); + +extern int FhgfsOps_releaseCancelLocks(struct inode* inode, struct file* file); +extern ssize_t __FhgfsOps_readSparse(struct file* file, struct iov_iter *iter, size_t size, + loff_t offset); + + +// getters & setters +static inline FsObjectInfo* __FhgfsOps_getObjectInfo(struct file* file); +static inline FsDirInfo* __FhgfsOps_getDirInfo(struct file* file); +static inline void __FhgfsOps_setDirInfo(FsDirInfo* dirInfo, struct file* outFile); +static inline FsFileInfo* __FhgfsOps_getFileInfo(struct file* file); +static inline void __FhgfsOps_setFileInfo(FsFileInfo* fileInfo, struct file* outFile); +static inline int __FhgfsOps_getCurrentLockPID(void); +static inline int64_t __FhgfsOps_getCurrentLockFD(struct file* file); + + +FsObjectInfo* __FhgfsOps_getObjectInfo(struct file* file) +{ + return (FsObjectInfo*)file->private_data; +} + +FsDirInfo* __FhgfsOps_getDirInfo(struct file* file) +{ + return (FsDirInfo*)file->private_data; +} + +void __FhgfsOps_setDirInfo(FsDirInfo* dirInfo, struct file* outFile) +{ + outFile->private_data = dirInfo; +} + +FsFileInfo* __FhgfsOps_getFileInfo(struct file* file) +{ + return (FsFileInfo*)file->private_data; +} + +void __FhgfsOps_setFileInfo(FsFileInfo* fileInfo, struct file* outFile) +{ + outFile->private_data = fileInfo; +} + +/** + * @return lock pid of the current process (which is _not_ current->pid) + */ +int __FhgfsOps_getCurrentLockPID(void) +{ + /* note: tgid (not current->pid) is the actual equivalent of the number that getpid() returns to + user-space and is also the thing that is assigned to fl_pid in "/fs/locks.c" */ + + return current->tgid; +} + +/** + * @return virtual file descriptor for entry locking (which is _not_ the user-space fd) + */ +int64_t __FhgfsOps_getCurrentLockFD(struct file* file) +{ + /* note: we can't get the user-space fd here and the rest of the kernel entry locking routines + in "/fs/locks.c" also uses the struct file pointer for comparison instead, so we + return that one here. */ + + return (size_t)file; +} + +#ifdef KERNEL_WRITE_BEGIN_USES_FOLIO +typedef struct folio* beegfs_pgfol_t; +#else +typedef struct page* beegfs_pgfol_t; +#endif + +#ifdef KERNEL_WRITE_BEGIN_HAS_FLAGS +#define BEEGFS_HAS_WRITE_FLAGS 1 +#else +#define BEEGFS_HAS_WRITE_FLAGS 0 +#endif + +/** + * Converts a struct page* into a beegfs_pgfol_t, which may be a folio* or page*. + */ +static inline beegfs_pgfol_t beegfs_to_pgfol(struct page *page) +{ +#ifdef KERNEL_WRITE_BEGIN_USES_FOLIO + return page_folio(page); +#else + return page; +#endif +} + +/** + * Retrieves the struct page* from a beegfs_pgfol_t (whether folio or page). + */ +static inline struct page* beegfs_get_page(beegfs_pgfol_t pgfol) +{ +#ifdef KERNEL_WRITE_BEGIN_USES_FOLIO + return &pgfol->page; +#else + return pgfol; +#endif +} + +/** + * Wrapper for grab_cache_page_write_begin that accounts for whether the kernel + * expects a flags parameter or not. + */ +static inline struct page* beegfs_grab_cache_page(struct address_space* mapping, + pgoff_t index, + unsigned flags) +{ +#ifdef KERNEL_WRITE_BEGIN_HAS_FLAGS + return grab_cache_page_write_begin(mapping, index, flags); +#else + IGNORE_UNUSED_VARIABLE(flags); + return grab_cache_page_write_begin(mapping, index); +#endif +} + +#endif /*FHGFSOPSFILE_H_*/ diff --git a/client_module/source/filesystem/FhgfsOpsFileNative.c b/client_module/source/filesystem/FhgfsOpsFileNative.c new file mode 100644 index 0000000..8fa07f5 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsFileNative.c @@ -0,0 +1,1719 @@ +#include +#include +#include +#include "FhgfsOpsFileNative.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsHelper.h" +#include "FhgfsOpsIoctl.h" +#include +#include +#include +#include +#include +#include + +static int writepages_init(void); +static void writepages_release(void); + +static void readpages_init(void); + +static ssize_t __beegfs_direct_IO(int rw, struct kiocb* iocb, struct iov_iter* iter, loff_t offset); + +static struct workqueue_struct* remoting_io_queue; + +bool beegfs_native_init() +{ + if(writepages_init() < 0) + return false; + + readpages_init(); + +#ifndef KERNEL_HAS_ALLOC_WORKQUEUE + remoting_io_queue = create_workqueue("beegfs/flush"); +#elif defined(KERNEL_HAS_WQ_RESCUER) + // WQ_RESCUER and WQ_MEM_RECLAIM are effectively the same thing: they ensure that + // at least one thread to run work items on is always available. + remoting_io_queue = alloc_workqueue("beegfs/flush", WQ_RESCUER, num_online_cpus()); +#else + remoting_io_queue = alloc_workqueue("beegfs/flush", WQ_MEM_RECLAIM, num_online_cpus()); +#endif + + if(!remoting_io_queue) + goto fail_queue; + + return true; + +fail_queue: + writepages_release(); + return false; +} + +void beegfs_native_release() +{ + writepages_release(); + + if(remoting_io_queue) + destroy_workqueue(remoting_io_queue); +} + + + +/* + * PVRs and ARDs use the Private and Checked bits of pages to determine which is attached to a + * page. Once we support only kernels that have the Private2 bit, we should use Private2 instead + * of Checked. + * Note: this is to avoid problems with 64k pages on 32 bit machines, otherwise we could use + * low-order bits of page->private to discriminate. + */ + +enum +{ + PVR_FIRST_SHIFT = 0, + PVR_FIRST_MASK = ~PAGE_MASK << PVR_FIRST_SHIFT, + + PVR_LAST_SHIFT = PAGE_SHIFT, + PVR_LAST_MASK = ~PAGE_MASK << PVR_LAST_SHIFT, +}; + +static void pvr_init(struct page* page) +{ + // pvr values *must* fit into the unsigned long private of struct page + BUILD_BUG_ON(PVR_LAST_SHIFT + PAGE_SHIFT > 8 * sizeof(unsigned long) ); + + SetPagePrivate(page); + ClearPageChecked(page); + page->private = 0; +} + +static bool pvr_present(struct page* page) +{ + return PagePrivate(page) && !PageChecked(page); +} + +static void pvr_clear(struct page* page) +{ + ClearPagePrivate(page); +} + +static unsigned pvr_get_first(struct page* page) +{ + return (page->private & PVR_FIRST_MASK) >> PVR_FIRST_SHIFT; +} + +static unsigned pvr_get_last(struct page* page) +{ + return (page->private & PVR_LAST_MASK) >> PVR_LAST_SHIFT; +} + +static void pvr_set_first(struct page* page, unsigned first) +{ + page->private &= ~PVR_FIRST_MASK; + page->private |= (first << PVR_FIRST_SHIFT) & PVR_FIRST_MASK; +} + +static void pvr_set_last(struct page* page, unsigned last) +{ + page->private &= ~PVR_LAST_MASK; + page->private |= (last << PVR_LAST_SHIFT) & PVR_LAST_MASK; +} + +static bool pvr_can_merge(struct page* page, unsigned first, unsigned last) +{ + unsigned oldFirst = pvr_get_first(page); + unsigned oldLast = pvr_get_last(page); + + if(first == oldLast + 1 || last + 1 == oldFirst) + return true; + + if(oldFirst <= first && first <= oldLast) + return true; + + if(oldFirst <= last && last <= oldLast) + return true; + + return false; +} + +static void pvr_merge(struct page* page, unsigned first, unsigned last) +{ + if(pvr_get_first(page) > first) + pvr_set_first(page, first); + + if(pvr_get_last(page) < last) + pvr_set_last(page, last); +} + + +static void beegfs_drop_all_caches(struct inode* inode) +{ + os_inode_lock(inode); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(3,12,0) + // 2.6.32 and later have truncate_pagecache, but that was incorrect + unmap_mapping_range(inode->i_mapping, 0, 0, 1); + truncate_inode_pages(inode->i_mapping, 0); + unmap_mapping_range(inode->i_mapping, 0, 0, 1); +#else + truncate_pagecache(inode, 0); +#endif + + i_size_write(inode, 0); + + os_inode_unlock(inode); +} + + + +static int beegfs_release_range(struct file* filp, loff_t first, loff_t last) +{ + int writeRes; + + // expand range to fit full pages + first &= PAGE_MASK; + last |= ~PAGE_MASK; + + if (unlikely(last == -1)) + { + printk_fhgfs(KERN_DEBUG, "range end given was -1"); + last = LLONG_MAX; + } + + clear_bit(AS_EIO, &filp->f_mapping->flags); + + writeRes = file_write_and_wait_range(filp, first, last); + if(writeRes < 0) + { + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + FhgfsOpsHelper_logOpMsg(3, app, file_dentry(filp), filp->f_mapping->host, __func__, + "error %i during flush", writeRes); + IGNORE_UNUSED_VARIABLE(app); + return writeRes; + } + + return 0; +} + +static int beegfs_acquire_range(struct file* filp, loff_t first, loff_t last) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + FhgfsIsizeHints iSizeHints; + int err; + + // expand range to fit full pages + first &= PAGE_MASK; + last |= ~PAGE_MASK; + + if (unlikely(last == -1)) + { + printk_fhgfs(KERN_DEBUG, "range end given was -1"); + last = LLONG_MAX; // In linux-6.2, checks for the bytes offset i.e., (end_byte < start_byte) + // moved from __filemap_fdatawrite_range() to the interface + // file[map]_write_and_wait_range() in order to provide consistent + // behaviour between write and wait. + } + + + err = beegfs_release_range(filp, first, last); + if(err) + return err; + + err = __FhgfsOps_refreshInode(app, file_inode(filp), NULL, &iSizeHints); + if(err) + return err; + + err = invalidate_inode_pages2_range(filp->f_mapping, first >> PAGE_SHIFT, last >> PAGE_SHIFT); + return err; +} + + + +static int beegfs_open(struct inode* inode, struct file* filp) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + struct FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + int err; + RemotingIOInfo ioInfo; + uint32_t remoteVersion; + bool mustAcquire; + + FhgfsOpsHelper_logOp(5, app, file_dentry(filp), file_inode(filp), __func__); + IGNORE_UNUSED_VARIABLE(app); + + err = FhgfsOps_openReferenceHandle(app, inode, filp, filp->f_flags, NULL, &remoteVersion); + if(err) + return err; + + FsFileInfo_getIOInfo(__FhgfsOps_getFileInfo(filp), fhgfsInode, &ioInfo); + if(ioInfo.pattern->chunkSize % PAGE_SIZE) + { + FhgfsOpsHelper_logOpMsg(1, app, file_dentry(filp), inode, __func__, + "chunk size is not a multiple of PAGE_SIZE!"); + FhgfsOps_release(inode, filp); + return -EIO; + } + + FhgfsInode_entryInfoWriteLock(fhgfsInode); + { + mustAcquire = + (fhgfsInode->fileVersion > remoteVersion && + fhgfsInode->fileVersion - remoteVersion < 0x80000000ULL) || + (fhgfsInode->fileVersion < remoteVersion && + remoteVersion - fhgfsInode->fileVersion < 0x80000000ULL); + + fhgfsInode->fileVersion = remoteVersion; + } + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); + + if(filp->f_flags & O_APPEND) + AtomicInt_inc(&fhgfsInode->appendFDsOpen); + + if (mustAcquire) + err = beegfs_acquire_range(filp, 0, LLONG_MAX); + + return err; +} + +static int beegfs_flush(struct file* filp, fl_owner_t id) +{ + FhgfsInode* inode = BEEGFS_INODE(file_inode(filp)); + Config* cfg = FhgfsOps_getApp(inode->vfs_inode.i_sb)->cfg; + int result; + FhgfsOpsErr bumpRes; + struct FileEvent event = FILEEVENT_EMPTY; + + IGNORE_UNUSED_VARIABLE(id); + + if(filp->f_flags & O_APPEND) + AtomicInt_dec(&BEEGFS_INODE(file_inode(filp) )->appendFDsOpen); + + /* if the file was not modified, we need not flush the caches. if we do not flush the caches, + * we also need not bump the file version - which means that other clients can keep their + * caches. */ + if (atomic_read(&inode->modified) == 0) + return 0; + + /* clear the modified bit *before* any data is written out. if the inode data is modified + * further, the flag *must* be set, even if all modifications are written out by us right + * here. clearing the flag only after everything has been written out requires exclusion + * between modifiers and flushers, which is prohibitively expensive. */ + atomic_set(&inode->modified, 0); + + result = beegfs_release_range(filp, 0, LLONG_MAX); + if (result < 0) + { + atomic_set(&inode->modified, 1); + return result; + } + + if (cfg->eventLogMask & EventLogMask_FLUSH) + FileEvent_init(&event, FileEventType_FLUSH, file_dentry(filp)); + + FhgfsInode_entryInfoWriteLock(inode); + + bumpRes = FhgfsOpsRemoting_bumpFileVersion( + FhgfsOps_getApp(file_dentry(filp)->d_sb), + &inode->entryInfo, + true, cfg->eventLogMask & EventLogMask_FLUSH ? &event : NULL); + + inode->fileVersion += 1; + + FhgfsInode_entryInfoWriteUnlock(inode); + + FileEvent_uninit(&event); + + if (bumpRes != FhgfsOpsErr_SUCCESS) + atomic_set(&inode->modified, 1); + + return FhgfsOpsErr_toSysErr(bumpRes); +} + +static int beegfs_release(struct inode* inode, struct file* filp) +{ + int flushRes; + + // flush entire contents of file, if this fails, a previous release operation has likely also + // failed. the only sensible thing to do then is to drop the entire cache (because we may be + // arbitrarily inconsistent with the rest of the world and would never know). + flushRes = beegfs_release_range(filp, 0, LLONG_MAX); + + if(flushRes < 0) + beegfs_drop_all_caches(inode); + + return FhgfsOps_release(inode, filp); +} + +static ssize_t beegfs_file_write_iter(struct kiocb* iocb, struct iov_iter* from) +{ + return generic_file_write_iter(iocb, from); +} + +static ssize_t beegfs_write_iter_direct(struct kiocb* iocb, struct iov_iter* from) +{ + iocb->ki_flags |= IOCB_DIRECT; + return generic_file_write_iter(iocb, from); +} + +static ssize_t beegfs_write_iter_locked_append(struct kiocb* iocb, struct iov_iter* from) +{ + struct file* filp = iocb->ki_filp; + struct inode *inode = file_inode(filp); + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + FhgfsInode *fhgfsInode = BEEGFS_INODE(inode); + FhgfsIsizeHints iSizeHints; + RemotingIOInfo ioInfo; + FhgfsOpsErr ferr; + ssize_t ret = 0; + + FsFileInfo_getIOInfo(__FhgfsOps_getFileInfo(filp), fhgfsInode, &ioInfo); + + Mutex_lock(&fhgfsInode->appendMutex); + + ferr = FhgfsOpsHelper_getAppendLock(fhgfsInode, &ioInfo); + if (ferr) + { + ret = FhgfsOpsErr_toSysErr(ferr); + goto out; + } + + ret = __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, false); + + if (! ret) + ret = beegfs_write_iter_direct(iocb, from); + + FhgfsOpsHelper_releaseAppendLock(fhgfsInode, &ioInfo); + +out: + Mutex_unlock(&fhgfsInode->appendMutex); + + return ret; +} + +static ssize_t beegfs_write_iter(struct kiocb* iocb, struct iov_iter* from) +{ + + struct file* filp = iocb->ki_filp; + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + + atomic_set(&BEEGFS_INODE(file_inode(filp))->modified, 1); + + if ((filp->f_flags & O_APPEND) + && Config_getTuneUseGlobalAppendLocks(App_getConfig(app))) + return beegfs_write_iter_locked_append(iocb, from); + + // Switch to direct (non-buffered) writes in various circumstances. + // + if (!iov_iter_is_pipe(from) + && (from->count >= Config_getTuneFileCacheBufSize(App_getConfig(app)) + || BEEGFS_SHOULD_FAIL(write_force_cache_bypass, 1))) + return beegfs_write_iter_direct(iocb, from); + + return beegfs_file_write_iter(iocb, from); +} + + +static ssize_t beegfs_file_read_iter(struct kiocb* iocb, struct iov_iter* to) +{ + return generic_file_read_iter(iocb, to); +} + + +/* like with write_iter, this is basically the O_DIRECT generic_file_read_iter. */ +static ssize_t beegfs_direct_read_iter(struct kiocb *iocb, struct iov_iter *to) +{ + struct file* filp = iocb->ki_filp; + + struct address_space* mapping = filp->f_mapping; + struct inode* inode = mapping->host; + size_t count = to->count; + loff_t size; + ssize_t result; + + if(!count) + return 0; /* skip atime */ + + size = i_size_read(inode); + result = beegfs_release_range(filp, iocb->ki_pos, iocb->ki_pos + count - 1); + if(!result) + { + struct iov_iter data = *to; + result = __beegfs_direct_IO(READ, iocb, &data, iocb->ki_pos); + } + + if(result > 0) + iocb->ki_pos += result; + + if(result < 0 || to->count == result || iocb->ki_pos + result >= size) + file_accessed(filp); + + return result; +} + +static ssize_t beegfs_read_iter(struct kiocb* iocb, struct iov_iter* to) +{ + size_t size = to->count; + struct file* filp = iocb->ki_filp; + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(filp), file_inode(filp), __func__, + "(offset: %lld; size: %zu)", iocb->ki_pos, size); + IGNORE_UNUSED_VARIABLE(size); + + if (to->count >= Config_getTuneFileCacheBufSize(App_getConfig(app)) + || BEEGFS_SHOULD_FAIL(read_force_cache_bypass, 1)) + return beegfs_direct_read_iter(iocb, to); + else + return beegfs_file_read_iter(iocb, to); +} + + +static int __beegfs_fsync(struct file* filp, loff_t start, loff_t end, int datasync) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + Config* cfg = App_getConfig(app); + int err; + FhgfsInode* fhgfsInode = BEEGFS_INODE(file_inode(filp)); + + FhgfsOpsHelper_logOp(5, app, file_dentry(filp), file_inode(filp), __func__); + + IGNORE_UNUSED_VARIABLE(start); + IGNORE_UNUSED_VARIABLE(end); + IGNORE_UNUSED_VARIABLE(datasync); + + /* see comment in beegfs_flush for explanation */ + atomic_set(&fhgfsInode->modified, 0); + + err = beegfs_release_range(filp, start, end); + if(err) + { + atomic_set(&fhgfsInode->modified, 0); + return err; + } + + if(Config_getTuneRemoteFSync(cfg) ) + { + RemotingIOInfo ioInfo; + FhgfsOpsErr res; + + FsFileInfo_getIOInfo(__FhgfsOps_getFileInfo(filp), fhgfsInode, &ioInfo); + res = FhgfsOpsRemoting_fsyncfile(&ioInfo, true, true, false); + if(res != FhgfsOpsErr_SUCCESS) + { + atomic_set(&fhgfsInode->modified, 0); + return FhgfsOpsErr_toSysErr(res); + } + } + + FhgfsInode_entryInfoWriteLock(fhgfsInode); + + err = FhgfsOpsRemoting_bumpFileVersion( + FhgfsOps_getApp(file_dentry(filp)->d_sb), + &fhgfsInode->entryInfo, + true, NULL); + if (err != FhgfsOpsErr_SUCCESS) + { + atomic_set(&fhgfsInode->modified, 0); + err = FhgfsOpsErr_toSysErr(err); + } + + fhgfsInode->fileVersion += 1; + + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); + + return err; +} + +#ifdef KERNEL_HAS_FSYNC_RANGE +static int beegfs_fsync(struct file* file, loff_t start, loff_t end, int datasync) +{ + return __beegfs_fsync(file, start, end, datasync); +} +#elif defined(KERNEL_HAS_FSYNC_2) +static int beegfs_fsync(struct file* file, int datasync) +{ + return __beegfs_fsync(file, 0, LLONG_MAX, datasync); +} +#else +static int beegfs_fsync(struct file* file, struct dentry* dentry, int datasync) +{ + return __beegfs_fsync(file, 0, LLONG_MAX, datasync); +} +#endif + +static int beegfs_flock(struct file* filp, int cmd, struct file_lock* flock) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + int err = -EINVAL; + + FhgfsOpsHelper_logOp(5, app, file_dentry(filp), file_inode(filp), __func__); + IGNORE_UNUSED_VARIABLE(app); + + switch(FhgfsCommon_getFileLockType(flock)) + { + case F_RDLCK: + case F_WRLCK: + err = beegfs_acquire_range(filp, 0, LLONG_MAX); + break; + + case F_UNLCK: + err = beegfs_release_range(filp, 0, LLONG_MAX); + break; + } + + if(err) + return err; + + return FhgfsOps_flock(filp, cmd, flock); +} + +static int beegfs_lock(struct file* filp, int cmd, struct file_lock* flock) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + int err = -EINVAL; + + FhgfsOpsHelper_logOp(5, app, file_dentry(filp), file_inode(filp), __func__); + IGNORE_UNUSED_VARIABLE(app); + + switch(FhgfsCommon_getFileLockType(flock)) + { + case F_RDLCK: + case F_WRLCK: + err = beegfs_acquire_range(filp, flock->fl_start, flock->fl_end); + break; + + case F_UNLCK: + err = beegfs_release_range(filp, flock->fl_start, flock->fl_end); + break; + } + + if(err) + return err; + + return FhgfsOps_lock(filp, cmd, flock); +} + +static int beegfs_mmap(struct file* filp, struct vm_area_struct* vma) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + int err = -EINVAL; + + FhgfsOpsHelper_logOp(5, app, file_dentry(filp), file_inode(filp), __func__); + IGNORE_UNUSED_VARIABLE(app); + + err = beegfs_acquire_range(filp, 0, LLONG_MAX); + if(err) + return err; + + err = generic_file_mmap(filp, vma); + return err; +} + +const struct file_operations fhgfs_file_native_ops = { + .open = beegfs_open, + .flush = beegfs_flush, + .release = beegfs_release, + .fsync = beegfs_fsync, + .llseek = FhgfsOps_llseek, + .flock = beegfs_flock, + .lock = beegfs_lock, + .mmap = beegfs_mmap, + + .unlocked_ioctl = FhgfsOpsIoctl_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = FhgfsOpsIoctl_compatIoctl, +#endif + + .read_iter = beegfs_read_iter, + .write_iter = beegfs_write_iter, + +#ifdef KERNEL_HAS_GENERIC_FILE_SPLICE_READ + .splice_read = generic_file_splice_read, +#else + .splice_read = filemap_splice_read, +#endif +#if defined(KERNEL_HAS_ITER_FILE_SPLICE_WRITE) + .splice_write = iter_file_splice_write, +#else + .splice_write = generic_file_splice_write, +#endif +}; + + + +struct beegfs_writepages_context +{ + RemotingIOInfo ioInfo; + struct writeback_control* wbc; + bool unlockPages; + + // only ever written by the flusher thread + struct beegfs_writepages_state* currentState; + int submitted; + + SynchronizedCounter barrier; +}; + +static int writepages_block_size __read_mostly; + +struct beegfs_writepages_state +{ + struct page** pages; + struct kvec* kvecs; + unsigned nr_pages; + + struct beegfs_writepages_context* context; + + struct work_struct work; +}; + +static mempool_t* writepages_pool; + +static void* __writepages_pool_alloc(gfp_t mask, void* pool_data) +{ + struct beegfs_writepages_state* state; + + state = kmalloc(sizeof(*state), mask); + if(!state) + goto fail_state; + + // should use kmalloc_array, but that's not available everywhere. luckily, this will not + // overflow. + state->pages = kmalloc(writepages_block_size * sizeof(struct page*), mask); + if(!state->pages) + goto fail_pages; + + state->kvecs = kmalloc(writepages_block_size * sizeof(*state->kvecs), mask); + if(!state->kvecs) + goto fail_kvecs; + + return state; + +fail_kvecs: + kfree(state->pages); +fail_pages: + kfree(state); +fail_state: + return NULL; +} + +static void __writepages_pool_free(void* element, void* pool_data) +{ + struct beegfs_writepages_state* state = element; + + if(!state) + return; + + kfree(state->kvecs); + kfree(state->pages); + kfree(state); +} + +static int writepages_init() +{ + // a state contains a pointer to page* array and an iovec array, so fill a page with one + // and allocate the other to match + writepages_block_size = PAGE_SIZE / MAX(sizeof(struct page*), sizeof(struct kvec) ); + + writepages_pool = mempool_create(1, __writepages_pool_alloc, __writepages_pool_free, NULL); + if(!writepages_pool) + return -ENOMEM; + + return 0; +} + +static void writepages_release() +{ + if(writepages_pool) + mempool_destroy(writepages_pool); +} + +static struct beegfs_writepages_state* wps_alloc(struct beegfs_writepages_context* ctx) +{ + struct beegfs_writepages_state* result = mempool_alloc(writepages_pool, GFP_NOFS); + + result->nr_pages = 0; + result->context = ctx; + + return result; +} + +static void wps_free(struct beegfs_writepages_state* state) +{ + mempool_free(state, writepages_pool); +} + +static int beegfs_wps_prepare(struct beegfs_writepages_state* state, loff_t* offset, size_t* size) +{ + int i; + + *size = 0; + + if(pvr_present(state->pages[0]) ) + { + *offset = page_offset(state->pages[0]) + pvr_get_first(state->pages[0]); + + for(i = 0; i < state->nr_pages; i++) + { + struct page* page = state->pages[i]; + unsigned length; + + length = pvr_get_last(page) - pvr_get_first(page) + 1; + + state->kvecs[i].iov_base = page_address(page) + pvr_get_first(page); + state->kvecs[i].iov_len = length; + + *size += length; + } + + return 0; + } + + // ARDs were deleted + BUG(); +} + +static void __beegfs_writepages_work(struct beegfs_writepages_state* state) +{ + int err = 0; + loff_t offset; + ssize_t size; + ssize_t written = 0; + + err = beegfs_wps_prepare(state, &offset, &size); + + if(err < 0) + { + // Probably EIO or EDQUOT + } + else if(BEEGFS_SHOULD_FAIL(writepage, 1) ) + { + // artificial write error + err = -EIO; + } + else + { + struct iov_iter iter; + BEEGFS_IOV_ITER_KVEC(&iter, WRITE, state->kvecs, state->nr_pages, size); + + written = FhgfsOpsRemoting_writefileVec(&iter, offset, &state->context->ioInfo, false); + + if(written < 0) + err = FhgfsOpsErr_toSysErr(-written); + else + task_io_account_write(written); + } + + size = 0; + for(unsigned i = 0; i < state->nr_pages; i++) + { + struct page* page = state->pages[i]; + struct address_space *mapping = page->mapping; + BUG_ON(! mapping); //??? + + size += state->kvecs[i].iov_len; + + if (size <= written) + { + pvr_clear(page); + } + else if (err) + { + fhgfs_set_wb_error(page, err); + pvr_clear(page); + } + else + { + // NOTE: this will cause the kernel to retry writeback at a later point + redirty_page_for_writepage(state->context->wbc, page); + } + + /* + Note: As per the documentation, as of Linux 2.5.12, + we could unlock the pages as early as after marking them using + set_page_writeback(). + It could be that our own code requires it somewhere, though. + */ + if(state->context->unlockPages) + unlock_page(page); + + end_page_writeback(page); + } +} + +static void beegfs_writepages_work(struct beegfs_writepages_state* state) +{ + if(state->nr_pages > 0) + __beegfs_writepages_work(state); + + wps_free(state); +} + +static void beegfs_writepages_work_wrapper(struct work_struct* w) +{ + struct beegfs_writepages_state* state = container_of(w, struct beegfs_writepages_state, work); + SynchronizedCounter* barrier = &state->context->barrier; + + beegfs_writepages_work(state); + SynchronizedCounter_incCount(barrier); +} + +static void beegfs_writepages_submit(struct beegfs_writepages_context* context) +{ + struct beegfs_writepages_state* state = context->currentState; + + context->submitted += 1; + + INIT_WORK(&state->work, beegfs_writepages_work_wrapper); + queue_work(remoting_io_queue, &state->work); +} + +static bool beegfs_wps_must_flush_before(struct beegfs_writepages_state* state, struct page* next) +{ + if(state->nr_pages == 0) + return false; + + if(state->nr_pages == writepages_block_size) + return true; + + if(state->pages[state->nr_pages - 1]->index + 1 != next->index) + return true; + + if(pvr_present(next) ) + { + if(pvr_get_first(next) != 0) + return true; + + if(pvr_get_last(state->pages[state->nr_pages - 1]) != PAGE_SIZE - 1) + return true; + + if(!pvr_present(state->pages[state->nr_pages - 1]) ) + return true; + } + + return false; +} + +#ifdef KERNEL_WRITEPAGE_HAS_FOLIO +static int beegfs_writepages_callback(struct folio *folio, struct writeback_control* wbc, void* data) +{ + struct page *page = &folio->page; +#else +static int beegfs_writepages_callback(struct page* page, struct writeback_control* wbc, void* data) +{ +#endif + struct beegfs_writepages_context* context = data; + struct beegfs_writepages_state* state = context->currentState; + + BUG_ON(!pvr_present(page)); + + if(beegfs_wps_must_flush_before(state, page) ) + { + beegfs_writepages_submit(context); + state = wps_alloc(context); + context->currentState = state; + } + + state->pages[state->nr_pages] = page; + state->nr_pages += 1; + + //XXX can't we defer this to later? + set_page_writeback(page); + + return 0; +} + +static int beegfs_do_write_pages(struct address_space* mapping, struct writeback_control* wbc, + struct page* page, bool unlockPages) +{ + struct inode* inode = mapping->host; + App* app = FhgfsOps_getApp(inode->i_sb); + + FhgfsOpsErr referenceRes; + FileHandleType handleType; + int err; + + struct beegfs_writepages_context context = { + .unlockPages = unlockPages, + .wbc = wbc, + .submitted = 0, + }; + + FhgfsOpsHelper_logOpDebug(app, NULL, inode, __func__, "page? %i %lu", page != NULL, + page ? page->index : 0); + IGNORE_UNUSED_VARIABLE(app); + + referenceRes = FhgfsInode_referenceHandle(BEEGFS_INODE(inode), NULL, OPENFILE_ACCESS_READWRITE, + true, NULL, &handleType, NULL); + if(referenceRes != FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_toSysErr(referenceRes); + + context.currentState = wps_alloc(&context); + SynchronizedCounter_init(&context.barrier); + + FhgfsInode_getRefIOInfo(BEEGFS_INODE(inode), handleType, OPENFILE_ACCESS_READWRITE, + &context.ioInfo); + + FhgfsInode_incWriteBackCounter(BEEGFS_INODE(inode) ); + + if(page) + { + #ifdef KERNEL_WRITEPAGE_HAS_FOLIO + struct folio *folio = page_folio(page); + err = beegfs_writepages_callback(folio, wbc, &context); + #else + err = beegfs_writepages_callback(page, wbc, &context); + #endif + + //XXX not sure if it's supposed to be like that + WARN_ON(wbc->nr_to_write != 1); + if (! err) + -- wbc->nr_to_write; + } + else + err = write_cache_pages(mapping, wbc, beegfs_writepages_callback, &context); + + beegfs_writepages_submit(&context); + + SynchronizedCounter_waitForCount(&context.barrier, context.submitted); + + FhgfsInode_releaseHandle(BEEGFS_INODE(inode), handleType, NULL); + + FhgfsInode_decWriteBackCounter(BEEGFS_INODE(inode) ); + FhgfsInode_unsetNoIsizeDecrease(BEEGFS_INODE(inode) ); + + return err; +} + +static int beegfs_writepage(struct page* page, struct writeback_control* wbc) +{ + struct inode* inode = page->mapping->host; + App* app = FhgfsOps_getApp(inode->i_sb); + + FhgfsOpsHelper_logOpDebug(app, NULL, inode, __func__, ""); + IGNORE_UNUSED_VARIABLE(app); + + return beegfs_do_write_pages(page->mapping, wbc, page, true); +} + +static int beegfs_writepages(struct address_space* mapping, struct writeback_control* wbc) +{ + struct inode* inode = mapping->host; + App* app = FhgfsOps_getApp(inode->i_sb); + + FhgfsOpsHelper_logOpDebug(app, NULL, inode, __func__, ""); + IGNORE_UNUSED_VARIABLE(app); + + return beegfs_do_write_pages(mapping, wbc, NULL, true); +} + + + +static int beegfs_flush_page(struct page* page) +{ + struct writeback_control wbc = { + .nr_to_write = 1, + .sync_mode = WB_SYNC_ALL, + }; + + if(!clear_page_dirty_for_io(page) ) + { + return 0; + } + + return beegfs_do_write_pages(page->mapping, &wbc, page, false); +} + +static int beegfs_readpage(struct file* filp, struct page* page) +{ + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + + struct inode* inode = filp->f_mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(filp); + RemotingIOInfo ioInfo; + ssize_t readRes = -EIO; + + loff_t offset = page_offset(page); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(filp), inode, __func__, "offset: %lld", offset); + IGNORE_UNUSED_VARIABLE(app); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + if (pvr_present(page)) + { + readRes = beegfs_flush_page(page); + if(readRes) + goto out; + } + + if(BEEGFS_SHOULD_FAIL(readpage, 1) ) + goto out; + + readRes = FhgfsOpsRemoting_readfile_kernel(page_address(page), PAGE_SIZE, offset, &ioInfo, + fhgfsInode); + + if(readRes < 0) + { + readRes = FhgfsOpsErr_toSysErr(-readRes); + goto out; + } + + if(readRes < PAGE_SIZE) + memset(page_address(page) + readRes, 0, PAGE_SIZE - readRes); + + readRes = 0; + task_io_account_read(PAGE_SIZE); + +out: + page_endio(page, READ, readRes); + return readRes; +} + +#ifdef KERNEL_HAS_READ_FOLIO +static int beegfs_read_folio(struct file* filp, struct folio* folio) +{ + return beegfs_readpage(filp, &folio->page); +} +#endif + +struct beegfs_readpages_context +{ + RemotingIOInfo ioInfo; + FileHandleType handleType; + struct inode* inode; + + struct beegfs_readpages_state* currentState; + + struct kref refs; +}; + +static struct beegfs_readpages_context* rpc_create(struct inode* inode) +{ + struct beegfs_readpages_context* context; + FhgfsOpsErr referenceRes; + + context = kmalloc(sizeof(*context), GFP_NOFS); + if(!context) + return NULL; + + context->inode = inode; + context->currentState = NULL; + kref_init(&context->refs); + + referenceRes = FhgfsInode_referenceHandle(BEEGFS_INODE(inode), NULL, OPENFILE_ACCESS_READWRITE, + true, NULL, &context->handleType, NULL); + if(referenceRes != FhgfsOpsErr_SUCCESS) + goto fail_reference; + + FhgfsInode_getRefIOInfo(BEEGFS_INODE(inode), context->handleType, OPENFILE_ACCESS_READWRITE, + &context->ioInfo); + + return context; + +fail_reference: + kfree(context); + return ERR_PTR(FhgfsOpsErr_toSysErr(referenceRes) ); +} + +static void __beegfs_readpages_context_free(struct kref* ref) +{ + struct beegfs_readpages_context* context; + + context = container_of(ref, struct beegfs_readpages_context, refs); + + FhgfsInode_releaseHandle(BEEGFS_INODE(context->inode), context->handleType, NULL); + kfree(context); +} + +static void rpc_get(struct beegfs_readpages_context* context) +{ + kref_get(&context->refs); +} + +static void rpc_put(struct beegfs_readpages_context* context) +{ + kref_put(&context->refs, __beegfs_readpages_context_free); +} + + +struct beegfs_readpages_state +{ + struct page** pages; + struct kvec *kvecs; + unsigned nr_pages; + + struct beegfs_readpages_context* context; + + struct work_struct work; +}; + +static int readpages_block_size __read_mostly; + +static void readpages_init() +{ + // much the same as writepages_block_size + readpages_block_size = PAGE_SIZE / MAX(sizeof(struct page*), sizeof(struct kvec) ); +} + +static struct beegfs_readpages_state* rps_alloc(struct beegfs_readpages_context* context) +{ + struct beegfs_readpages_state* state; + + state = kmalloc(sizeof(*state), GFP_NOFS); + if(!state) + goto fail_state; + + // should use kmalloc_array, see __writepages_pool_alloc + state->pages = kmalloc(readpages_block_size * sizeof(struct page*), GFP_NOFS); + if(!state->pages) + goto fail_pages; + + state->kvecs = kmalloc(readpages_block_size * sizeof(*state->kvecs), GFP_NOFS); + if(!state->kvecs) + goto fail_kvecs; + + state->nr_pages = 0; + state->context = context; + rpc_get(context); + + return state; + +fail_kvecs: + kfree(state->pages); +fail_pages: + kfree(state); +fail_state: + return NULL; +} + +static void rps_free(struct beegfs_readpages_state* state) +{ + if(!state) + return; + + rpc_put(state->context); + kfree(state->kvecs); + kfree(state->pages); + kfree(state); +} + +static void beegfs_readpages_work(struct work_struct* w) +{ + struct beegfs_readpages_state* state = container_of(w, struct beegfs_readpages_state, work); + + App* app; + struct iov_iter iter; + ssize_t readRes; + unsigned validPages = 0; + int err = 0; + int i; + + if(!state->nr_pages) + goto done; + + app = FhgfsOps_getApp(state->pages[0]->mapping->host->i_sb); + + FhgfsOpsHelper_logOpDebug(app, NULL, state->pages[0]->mapping->host, __func__, + "first offset: %lld nr_pages %u", page_offset(state->pages[0]), state->nr_pages); + IGNORE_UNUSED_VARIABLE(app); + + if(BEEGFS_SHOULD_FAIL(readpage, 1) ) + { + err = -EIO; + goto endio; + } + + BEEGFS_IOV_ITER_KVEC(&iter, READ, state->kvecs, state->nr_pages, + state->nr_pages * PAGE_SIZE); + + readRes = FhgfsOpsRemoting_readfileVec(&iter, iov_iter_count(&iter), page_offset(state->pages[0]), + &state->context->ioInfo, BEEGFS_INODE(state->pages[0]->mapping->host) ); + if(readRes < 0) + err = FhgfsOpsErr_toSysErr(-readRes); + + if(err < 0) + goto endio; + + validPages = readRes / PAGE_SIZE; + + if(readRes % PAGE_SIZE != 0) + { + int start = readRes % PAGE_SIZE; + memset(page_address(state->pages[validPages]) + start, 0, PAGE_SIZE - start); + validPages += 1; + } + +endio: + for(i = 0; i < validPages; i++) + page_endio(state->pages[i], READ, err); + + for(i = validPages; i < state->nr_pages; i++) + { + ClearPageUptodate(state->pages[i]); + unlock_page(state->pages[i]); + } + +done: + rps_free(state); +} + +static void beegfs_readpages_submit(struct beegfs_readpages_context* context) +{ + struct beegfs_readpages_state* state = context->currentState; + + INIT_WORK(&state->work, beegfs_readpages_work); + queue_work(remoting_io_queue, &state->work); +} + +static int beegfs_readpages_add_page(void* data, struct page* page) +{ + struct beegfs_readpages_context* context = data; + struct beegfs_readpages_state* state = context->currentState; + bool mustFlush; + + mustFlush = (state->nr_pages == readpages_block_size) + || (state->nr_pages > 0 && state->pages[state->nr_pages - 1]->index + 1 != page->index); + + if(mustFlush) + { + beegfs_readpages_submit(context); + state = rps_alloc(context); + if(!state) + return -ENOMEM; + + context->currentState = state; + } + + state->pages[state->nr_pages] = page; + state->kvecs[state->nr_pages].iov_base = page_address(page); + state->kvecs[state->nr_pages].iov_len = PAGE_SIZE; + state->nr_pages += 1; + + return 0; +} + +#ifdef KERNEL_HAS_FOLIO +static void beegfs_readahead(struct readahead_control *ractl) +#else +static int beegfs_readpages(struct file* filp, struct address_space* mapping, + struct list_head* pages, unsigned nr_pages) +#endif +{ + +#ifdef KERNEL_HAS_FOLIO + struct inode* inode = ractl->mapping->host; + struct file* filp = ractl->file; + struct page* page_ra; +#else + struct inode* inode = mapping->host; +#endif + + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(filp); + RemotingIOInfo ioInfo; + int err; + + struct beegfs_readpages_context* context; + + context = rpc_create(inode); + if(IS_ERR(context) ) + #ifdef KERNEL_HAS_FOLIO + return; + #else + return PTR_ERR(context); + #endif + + context->currentState = rps_alloc(context); + if(!context->currentState) + { + err = -ENOMEM; + goto out; + } + + #ifdef KERNEL_HAS_FOLIO + FhgfsOpsHelper_logOpDebug(app, file_dentry(filp), inode, __func__, + "first offset: %lld \n nr_pages %u \n no. of bytes in this readahead request: %zu\n", + readahead_pos(ractl), readahead_count(ractl), readahead_length(ractl)); + #else + FhgfsOpsHelper_logOpDebug(app, file_dentry(filp), inode, __func__, + "first offset: %lld nr_pages %u", page_offset(list_entry(pages->prev, struct page, lru)), + nr_pages); + #endif + + IGNORE_UNUSED_VARIABLE(app); + + FsFileInfo_getIOInfo(fileInfo, fhgfsInode, &ioInfo); + + #ifdef KERNEL_HAS_FOLIO + if (readahead_count(ractl)) + { + while ((page_ra = readahead_page(ractl)) != NULL) + { + err = beegfs_readpages_add_page(context, page_ra); + put_page(page_ra); + if (err) + goto out; + } + } + #else + err = read_cache_pages(mapping, pages, beegfs_readpages_add_page, context); + #endif + + beegfs_readpages_submit(context); + +out: + rpc_put(context); + #ifdef KERNEL_HAS_FOLIO + return; + #else + return err; + #endif +} + +static int __beegfs_write_begin(struct file* filp, loff_t pos, unsigned len, struct page* page) +{ + int result = 0; + + if(!pvr_present(page) ) + goto success; + + if(pvr_can_merge(page, pos & ~PAGE_MASK, (pos & ~PAGE_MASK) + len - 1)) + goto success; + + result = beegfs_flush_page(page); + if(result) + goto out_err; + +success: + return result; + +out_err: + unlock_page(page); + put_page(page); + return result; +} + +static int __beegfs_write_end(struct file* filp, loff_t pos, unsigned len, unsigned copied, + struct page* page) +{ + struct inode* inode = page->mapping->host; + int result = copied; + + App* app = FhgfsOps_getApp(file_dentry(filp)->d_sb); + + if(copied != len && pvr_present(page) ) + { + FhgfsOpsHelper_logOpMsg(2, app, file_dentry(filp), inode, __func__, "short write!"); + result = 0; + goto out; + } + + if(i_size_read(inode) < pos + copied) + { + i_size_write(inode, pos + copied); + FhgfsInode_setPageWriteFlag(BEEGFS_INODE(inode) ); + FhgfsInode_setLastWriteBackOrIsizeWriteTime(BEEGFS_INODE(inode) ); + FhgfsInode_setNoIsizeDecrease(BEEGFS_INODE(inode) ); + } + + if(pvr_present(page) ) + pvr_merge(page, pos & ~PAGE_MASK, (pos & ~PAGE_MASK) + copied - 1); + else + { + pvr_init(page); + pvr_set_first(page, pos & ~PAGE_MASK); + pvr_set_last(page, (pos & ~PAGE_MASK) + copied - 1); + } + +out: + ClearPageUptodate(page); + +#ifdef KERNEL_HAS_FOLIO + filemap_dirty_folio(page->mapping, page_folio(page)); +#else + __set_page_dirty_nobuffers(page); +#endif + + unlock_page(page); + put_page(page); + + return result; +} + +static int beegfs_write_begin(struct file *filp, struct address_space *mapping, + loff_t pos, unsigned len, +#if BEEGFS_HAS_WRITE_FLAGS + unsigned flags, +#endif + beegfs_pgfol_t *pgfolp, void **fsdata) +{ + pgoff_t index = pos >> PAGE_SHIFT; + + struct page *page = beegfs_grab_cache_page(mapping, index, +#if BEEGFS_HAS_WRITE_FLAGS + flags +#else + 0 +#endif + ); + + // Common check for all + if (!page) + return -ENOMEM; + + *pgfolp = beegfs_to_pgfol(page); + return __beegfs_write_begin(filp, pos, len, page); + +} + +static int beegfs_write_end(struct file *filp, struct address_space *mapping, loff_t pos, + unsigned len, unsigned copied, beegfs_pgfol_t pgfol, void *fsdata) +{ + struct page *page = beegfs_get_page(pgfol); + return __beegfs_write_end(filp, pos, len, copied, page); +} + +static int beegfs_releasepage(struct page* page, gfp_t gfp) +{ + + IGNORE_UNUSED_VARIABLE(gfp); + + if(pvr_present(page) ) + { + pvr_clear(page); + return 1; + } + + // ARDs were deleted + BUG(); +} + +#ifdef KERNEL_HAS_READ_FOLIO +static bool beegfs_release_folio(struct folio* folio, gfp_t gfp) +{ + return beegfs_releasepage(&folio->page, gfp) != 0; +} +#endif + +#ifdef KERNEL_HAS_FOLIO +static bool beegfs_set_dirty_folio(struct address_space *mapping, struct folio *folio) +{ + struct page *page = &folio->page; + if (folio_test_dirty(folio)) + { + printk_fhgfs_debug(KERN_INFO,"%s %p dirty_folio %p idx %lu -- already dirty\n", __func__, + mapping->host, folio, folio->index); + VM_BUG_ON_FOLIO(!folio_test_private(folio), folio); + return false; + } + +#else +static int beegfs_set_page_dirty(struct page* page) +{ +#endif + + atomic_set(&BEEGFS_INODE(page->mapping->host)->modified, 1); + + pvr_init(page); + pvr_set_first(page, 0); + pvr_set_last(page, PAGE_SIZE - 1); + +#ifdef KERNEL_HAS_FOLIO + return filemap_dirty_folio(mapping,folio); +#else + return __set_page_dirty_nobuffers(page); +#endif +} + +static void __beegfs_invalidate_page(struct page* page, unsigned begin, unsigned end) +{ + if(pvr_present(page) ) + { + unsigned pvr_begin = pvr_get_first(page); + unsigned pvr_end = pvr_get_last(page); + + if(begin == 0 && end == PAGE_SIZE) + { + pvr_clear(page); + ClearPageUptodate(page); + return; + } + + if(begin < pvr_begin) + pvr_set_first(page, begin); + + if(pvr_end < end) + pvr_set_last(page, end); + + return; + } + + // ARDs were deleted + BUG(); +} + +#if defined(KERNEL_HAS_FOLIO) +static void beegfs_invalidate_folio(struct folio *folio, size_t offset, size_t length) +{ + if (offset != 0 || length < folio_size(folio)) + return; + + //FIX ME: If this folio_wait_writeback(folio) makes sense here (imp: as per doc) + __beegfs_invalidate_page(&folio->page, offset, (offset+length)); + +} + +#elif !defined(KERNEL_HAS_INVALIDATEPAGE_RANGE) +static void beegfs_invalidate_page(struct page* page, unsigned long begin) +{ + __beegfs_invalidate_page(page, begin, PAGE_CACHE_SIZE); +} +#else +static void beegfs_invalidate_page(struct page* page, unsigned begin, unsigned end) +{ + __beegfs_invalidate_page(page, begin, end); +} +#endif + +static ssize_t beegfs_dIO_read(struct kiocb* iocb, struct iov_iter* iter, loff_t offset, + RemotingIOInfo* ioInfo) +{ + struct file* filp = iocb->ki_filp; + struct inode* inode = file_inode(filp); + struct dentry* dentry = file_dentry(filp); + + App* app = FhgfsOps_getApp(dentry->d_sb); + + ssize_t result = 0; + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "pos: %lld, nr_segs: %lld", + offset, beegfs_iov_iter_nr_segs(iter)); + IGNORE_UNUSED_VARIABLE(app); + + result = FhgfsOpsRemoting_readfileVec(iter, iov_iter_count(iter), offset, ioInfo, BEEGFS_INODE(inode)); + + if(result < 0) + return FhgfsOpsErr_toSysErr(-result); + + offset += result; + + if(iov_iter_count(iter) > 0) + { + ssize_t readRes = __FhgfsOps_readSparse(filp, iter, iov_iter_count(iter), offset + result); + + result += readRes; + } + + task_io_account_read(result); + + if(offset > i_size_read(inode) ) + i_size_write(inode, offset); + + return result; +} + +static ssize_t beegfs_dIO_write(struct kiocb* iocb, struct iov_iter* iter, loff_t offset, + RemotingIOInfo* ioInfo) +{ + struct file* filp = iocb->ki_filp; + struct inode* inode = file_inode(filp); + struct dentry* dentry = file_dentry(filp); + + App* app = FhgfsOps_getApp(dentry->d_sb); + + ssize_t result = 0; + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "pos: %lld, nr_segs: %lld", + offset, beegfs_iov_iter_nr_segs(iter)); + IGNORE_UNUSED_VARIABLE(app); + IGNORE_UNUSED_VARIABLE(inode); + + result = FhgfsOpsRemoting_writefileVec(iter, offset, ioInfo, false); + + if(result < 0) + return FhgfsOpsErr_toSysErr(-result); + + offset += result; + + task_io_account_write(result); + + return result; +} + +static ssize_t __beegfs_direct_IO(int rw, struct kiocb* iocb, struct iov_iter* iter, loff_t offset) +{ + struct file* filp = iocb->ki_filp; + struct inode* inode = file_inode(filp); + RemotingIOInfo ioInfo; + + FsFileInfo_getIOInfo(__FhgfsOps_getFileInfo(filp), BEEGFS_INODE(inode), &ioInfo); + + { + ssize_t result; + + switch(rw) + { + case READ: + result = beegfs_dIO_read(iocb, iter, offset, &ioInfo); + break; + + case WRITE: + result = beegfs_dIO_write(iocb, iter, offset, &ioInfo); + break; + + default: + BUG(); + return -EINVAL; + } + + return result; + } +} + +static ssize_t beegfs_direct_IO(struct kiocb* iocb, struct iov_iter* iter) +{ + return __beegfs_direct_IO(iov_iter_rw(iter), iocb, iter, iocb->ki_pos); +} + +#ifdef KERNEL_HAS_FOLIO +static int beegfs_launder_folio(struct folio *folio) +{ + return beegfs_flush_page(&folio->page); +} +#else +static int beegfs_launderpage(struct page* page) +{ + return beegfs_flush_page(page); +} +#endif + +const struct address_space_operations fhgfs_addrspace_native_ops = { +#ifdef KERNEL_HAS_READ_FOLIO + .read_folio = beegfs_read_folio, + .release_folio = beegfs_release_folio, +#else + .readpage = beegfs_readpage, + .releasepage = beegfs_releasepage, +#endif + + .writepage = beegfs_writepage, + .direct_IO = beegfs_direct_IO, + +#ifdef KERNEL_HAS_FOLIO + .readahead = beegfs_readahead, + .dirty_folio = beegfs_set_dirty_folio, + .invalidate_folio = beegfs_invalidate_folio, + .launder_folio = beegfs_launder_folio, +#else + .readpages = beegfs_readpages, + .set_page_dirty = beegfs_set_page_dirty, + .invalidatepage = beegfs_invalidate_page, + .launder_page = beegfs_launderpage, +#endif + + .writepages = beegfs_writepages, + .write_begin = beegfs_write_begin, + .write_end = beegfs_write_end, +}; diff --git a/client_module/source/filesystem/FhgfsOpsFileNative.h b/client_module/source/filesystem/FhgfsOpsFileNative.h new file mode 100644 index 0000000..d66b8c5 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsFileNative.h @@ -0,0 +1,12 @@ +#ifndef FhgfsOpsFileNative_h_bI2n6XRVSaWCQHxNdwQA0I +#define FhgfsOpsFileNative_h_bI2n6XRVSaWCQHxNdwQA0I + +#include + +extern const struct file_operations fhgfs_file_native_ops; +extern const struct address_space_operations fhgfs_addrspace_native_ops; + +extern bool beegfs_native_init(void); +extern void beegfs_native_release(void); + +#endif diff --git a/client_module/source/filesystem/FhgfsOpsHelper.c b/client_module/source/filesystem/FhgfsOpsHelper.c new file mode 100644 index 0000000..758da5c --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsHelper.c @@ -0,0 +1,1372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsDir.h" +#include "FhgfsOpsHelper.h" + + +/** + * Log file system operations and optionally additional messages. + * Used to trace function calls and to print the path the function is to operate on. + * + * @param level The log level + * @param dentry Common vfs directory entry + * @param logContext Usually the name of the calling function + * @param msgStr Optional message string, may be NULL + * @param ... Optional arguments according to format given in msgStr + */ +void FhgfsOpsHelper_logOpMsg(int level, App* app, struct dentry* dentry, struct inode* inode, + const char *logContext, const char *msgStr, ...) +{ + Logger* log = App_getLogger(app); + NoAllocBufferStore* bufStore = App_getPathBufStore(app); + + char* pathStoreBuf = NULL; // NoAllocBufferStore_addBuf() can detect a wrong NULL... + char* path = NULL; + const char* entryID = NULL; + + const char* noPath = "n/a (no dentry)"; + const char* noEntryID = "n/a (no inode)"; + + + if(level > Logger_getLogLevel(log) ) + return; + + + if(dentry) + { + path = __FhgfsOps_pathResolveToStoreBuf(bufStore, dentry, &pathStoreBuf); + if(IS_ERR(path) ) + path = NULL; + } + + if(inode) + { // get entryInfo lock for entryID + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + const EntryInfo* entryInfo; + + FhgfsInode_entryInfoReadLock(fhgfsInode); // L O C K entryInfo + + entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + entryID = EntryInfo_getEntryID(entryInfo); + } + + if(msgStr) + { // generate new msg string for given msg formatting + va_list ap; + char* newMsg; + + newMsg = kmalloc(LOGGER_LOGBUF_SIZE, GFP_NOFS); + if(newMsg) + { + int prefixLen = snprintf(newMsg, LOGGER_LOGBUF_SIZE, "called. Path: %s; EntryID: %s; %s", + path ? path : noPath, + entryID ? entryID : noEntryID, + msgStr); + + va_start(ap, msgStr); + vsnprintf(newMsg + prefixLen, LOGGER_LOGBUF_SIZE - prefixLen, msgStr, ap); + va_end(ap); + + Logger_logFormatted(log, level, logContext, "%s", newMsg); + + kfree(newMsg); + } + else + { // alloc failed, still try to log the operation at least + Logger_logFormatted(log, level, logContext, "called. Path: %s; EntryID: %s; (msg n/a)", + path ? path : noPath, + entryID ? entryID : noEntryID); + } + + } + else + Logger_logFormatted(log, level, logContext, "called. Path: %s; EntryID: %s", + path ? path : noPath, + entryID ? entryID : noEntryID); + + + if(inode) + { // release entryInfo lock + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // U N L O C K entryInfo + } + + + if(pathStoreBuf) + NoAllocBufferStore_addBuf(bufStore, pathStoreBuf); +} + +ssize_t FhgfsOpsHelper_appendfileVecOffset(FhgfsInode* fhgfsInode, struct iov_iter *iter, + size_t count, RemotingIOInfo* ioInfo, loff_t offsetFromEnd, loff_t* outNewOffset) +{ + App* app = ioInfo->app; + + ssize_t writeRes = 0; + FhgfsOpsErr lockRes; + FhgfsOpsErr statRes; + fhgfs_stat fhgfsStat; + + // get MDS append lock... + lockRes = FhgfsOpsHelper_getAppendLock(fhgfsInode, ioInfo); + if(unlikely(lockRes != FhgfsOpsErr_SUCCESS) ) + return -lockRes; + + // get current file size from servers... + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + statRes = FhgfsOpsRemoting_statDirect(app, FhgfsInode_getEntryInfo(fhgfsInode), &fhgfsStat); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(unlikely(statRes != FhgfsOpsErr_SUCCESS) ) + { // remote stat error + writeRes = -statRes; + goto unlock_and_exit; + } + + // the actual remote write... + + fhgfsStat.size += offsetFromEnd; + + (void) count; // count currently not used, writefileVec looks at iter->count + BUG_ON(count != iov_iter_count(iter)); + writeRes = FhgfsOpsRemoting_writefileVec(iter, fhgfsStat.size, ioInfo, false); + + if(writeRes >= 0) + fhgfsStat.size += writeRes; + + *outNewOffset = fhgfsStat.size; + +unlock_and_exit: + FhgfsOpsHelper_releaseAppendLock(fhgfsInode, ioInfo); + return writeRes; +} + +/** + * Append data to a file, protected by MDS locking. + * + * Note: This method does not try to flush local file buffers after acquiring the MDS lock, because + * this method might be called during a file buffer flush (so callers must ensure that there are + * no conflicing local file buffers). + * + * @param size buffer length to be appended + * @param outNewOffset new file offset after append completes (only valid if no error returned) + * @return number of bytes written or negative fhgfs error code + */ +static ssize_t FhgfsOpsHelper_appendfile_kernel(FhgfsInode* fhgfsInode, const char *buf, size_t size, + RemotingIOInfo* ioInfo, loff_t* outNewOffset) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, size, WRITE); + return FhgfsOpsHelper_appendfileVecOffset(fhgfsInode, iter, size, ioInfo, 0, outNewOffset); +} + +/** + * Wrapper for FhgfsOpsRemoting_writefile, but this automatically calls _appendfile() if -1 offset + * is given. + * + * @param offset offset in file, -1 for append + */ +static ssize_t FhgfsOpsHelper_writefileEx(FhgfsInode* fhgfsInode, + struct iov_iter *iter, size_t size, loff_t offset, RemotingIOInfo* ioInfo) +{ + if(offset == -1) + return FhgfsOpsHelper_appendfileVecOffset(fhgfsInode, iter, size, ioInfo, 0, &offset); + else + return FhgfsOpsRemoting_writefileVec(iter, offset, ioInfo, false); +} +static ssize_t FhgfsOpsHelper_writefileEx_kernel(FhgfsInode* fhgfsInode, + const char *buf, size_t size, loff_t offset, RemotingIOInfo* ioInfo) +{ + if(offset == -1) + return FhgfsOpsHelper_appendfile_kernel(fhgfsInode, buf, size, ioInfo, &offset); + else + return FhgfsOpsRemoting_writefile_kernel(buf, size, offset, ioInfo); +} + +/** + * Refreshes the dirInfo by retrieving a new version from the nodes (but only if required or + * forced). + * + * Note: This method guarantees either valid local names range for currentServerOffset or empty + * dirContents list on completion. + * + * @param dirInfo holds the relevant serverOffset for the refresh + * @param forceUpdate use this to force an update (e.g. when the user seeked) + * @return negative linux error code on error, 0 otherwise + */ +int FhgfsOpsHelper_refreshDirInfoIncremental(App* app, const EntryInfo* entryInfo, + FsDirInfo* dirInfo, bool forceUpdate) +{ + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOpsHelper (refresh dir info incremental)"; + + const unsigned maxNames = 100; // max number of retrieved names + + int retVal = 0; + FhgfsOpsErr listRes = FhgfsOpsErr_SUCCESS; + StrCpyVec* dirContents = FsDirInfo_getDirContents(dirInfo); + size_t dirContentsLen = StrCpyVec_length(dirContents); + size_t currentContentsPos = FsDirInfo_getCurrentContentsPos(dirInfo); // pos inside contents vec + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "called. EntryID: %s, Owner: %hu", + entryInfo->entryID, entryInfo->owner.node.value); + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + + + if(forceUpdate) + { // user seeked backwards (or something else makes an update necessary) + LOG_DEBUG(log, Log_SPAM, logContext, "forced update of dir contents"); + + listRes = FhgfsOpsRemoting_listdirFromOffset(entryInfo, dirInfo, maxNames); + } + else + if(FsDirInfo_getEndOfDir(dirInfo) && (currentContentsPos >= dirContentsLen) ) + { // we reached the end of the dir contents => do nothing + LOG_DEBUG(log, Log_SPAM, logContext, "reached end of dir contents by offset"); + + StrCpyVec_clear(FsDirInfo_getDirContents(dirInfo) ); + } + else + if(currentContentsPos < dirContentsLen) + { // inside the local contents region => do nothing + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, + "offset inside local contents region: %lld <= %lld", + (long long)currentContentsPos, (long long)dirContentsLen); + } + else + { // initial retrieval or offset outside current local contents region + LOG_DEBUG(log, Log_SPAM, logContext, "retrieving by offset"); + + listRes = FhgfsOpsRemoting_listdirFromOffset(entryInfo, dirInfo, maxNames); + } + + + if(unlikely(listRes != FhgfsOpsErr_SUCCESS) ) + { // error + StrCpyVec_clear(FsDirInfo_getDirContents(dirInfo) ); + FsDirInfo_setCurrentContentsPos(dirInfo, 0); + + Logger_logFormatted(log, Log_DEBUG, logContext, "result: %s", + FhgfsOpsErr_toErrString(listRes) ); + + retVal = FhgfsOpsErr_toSysErr(listRes); + } + + return retVal; +} + +/** + * Flush cache buffer and return it to the store, but return immediately if the cache lock cannot + * be acquired immediately. + * + * @param discardCacheOnError true to discard a write cache if the remote write + * was not successful; false to just release it to the store in this case + * @return FhgfsOpsErr_INUSE if cache lock is contended and nothing has been flushed. + */ +FhgfsOpsErr FhgfsOpsHelper_flushCacheNoWait(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError) +{ + int gotLock; // 1 if we got the lock, 0 otherwise + FhgfsOpsErr retVal; + + gotLock = FhgfsInode_fileCacheExclusiveTryLock(fhgfsInode); // (T R Y) L O C K + if(gotLock != 1) + return FhgfsOpsErr_INUSE; + + retVal = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, discardCacheOnError); + + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); // U N L O C K + + return retVal; +} + +/** + * Buffered writing. + * Tries to add written data to an existing cache or flushes the cache (if any) and delegates to + * writeCacheFlushed(). + * + * Note: This method also updates FsFileInfo cache hits counter. + * + * @param offset offset in file, -1 for append + * @return number of bytes written or negative fhgfs error code + */ +ssize_t FhgfsOpsHelper_writeCached(struct iov_iter *iter, size_t size, + loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo) +{ + App* app = ioInfo->app; + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + + ssize_t retVal; + bool isAppendWrite = (offset == -1); // true if this is an append write + CacheBuffer* cacheBuffer; + enum FileBufferType cacheType; + bool isAppendCacheBuf; // true if the current cache buffer is an append write buffer + size_t bufLenLeft; // remaining buffer length + loff_t currentCacheEndOffset; + int userBufCopyRes; + + if (app->cfg->tuneCoherentBuffers) + { + i_mmap_lock_read(fhgfsInode->vfs_inode.i_mapping); + + if (beegfs_hasMappings(&fhgfsInode->vfs_inode)) + { + FsFileInfo_decCacheHits(fileInfo); + i_mmap_unlock_read(fhgfsInode->vfs_inode.i_mapping); + + return FhgfsOpsHelper_writefileEx(fhgfsInode, iter, size, offset, ioInfo); + } + + i_mmap_unlock_read(fhgfsInode->vfs_inode.i_mapping); + } + + FhgfsInode_fileCacheExclusiveLock(fhgfsInode); // L O C K + + cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + cacheType = cacheBuffer->bufType; + + if(cacheType == FileBufferType_NONE) + { // file doesn't have any cache buffer currently + + // update cache hits by guessing whether this would have been a cache hit if we had a buffer + + if(isAppendWrite) + FsFileInfo_incCacheHits(fileInfo); // could have been a cache hit + else + { + loff_t virtualCacheStart = FsFileInfo_getLastWriteOffset(fileInfo); + loff_t virtualCacheEnd = virtualCacheStart + NoAllocBufferStore_getBufSize(cacheStore) - 1; + + if( (offset >= virtualCacheStart) && (offset <= virtualCacheEnd) ) + FsFileInfo_incCacheHits(fileInfo); // could have been a cache hit + else + FsFileInfo_decCacheHits(fileInfo); // would probably not have been a cache hit + } + + retVal = __FhgfsOpsHelper_writeCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + if(cacheType == FileBufferType_READ) + { // file has a read cache => just discard the cached data + FsFileInfo_decCacheHits(fileInfo); + + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + + retVal = __FhgfsOpsHelper_writeCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + // file has a write cache => can we extend it or do we have to flush it? + + isAppendCacheBuf = cacheBuffer->fileOffset == -1; // -1 offset means append buf + currentCacheEndOffset = cacheBuffer->fileOffset + cacheBuffer->bufUsageLen; + bufLenLeft = cacheBuffer->bufUsageMaxLen - cacheBuffer->bufUsageLen; + + if( (isAppendWrite != isAppendCacheBuf) || + (!isAppendWrite && (offset != currentCacheEndOffset) ) || + (size > bufLenLeft) ) + { // offset doesn't fit or not enough cache room left => flush cache + FhgfsOpsErr flushRes; + + FsFileInfo_decCacheHits(fileInfo); + + flushRes = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, false); + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + { // flush failed + retVal = -flushRes; + goto unlock_and_exit; + } + + retVal = __FhgfsOpsHelper_writeCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + // offset fits and we have enough room left for the write => copy data to cache buf (and update) + + FsFileInfo_incCacheHits(fileInfo); + + userBufCopyRes = copy_from_iter(&(cacheBuffer->buf)[cacheBuffer->bufUsageLen], size, iter); + if(unlikely(userBufCopyRes != size)) + { // copy failed + Logger* log = App_getLogger(app); + Logger_log(log, Log_DEBUG, __func__, "Buffer copy from userspace failed (invalid buffer)"); + + retVal = -FhgfsOpsErr_ADDRESSFAULT; + goto unlock_and_exit; + } + + cacheBuffer->bufUsageLen += size; + + bufLenLeft = cacheBuffer->bufUsageMaxLen - cacheBuffer->bufUsageLen; // re-calc remaining bufLen + + // write has been completely cached => check whether the cache is full now + + if(!bufLenLeft) + { // cache buf used up => flush it + FhgfsOpsErr flushRes = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, false); + + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + { // flush failed + retVal = -flushRes; + goto unlock_and_exit; + } + } + + // write was successful if we got here + + retVal = size; + + +unlock_and_exit: + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); // U N L O C K + + return retVal; +} + +/** + * Buffered reading. + * Tries to read data from an existing cache or flushes the cache and delegates to + * readCacheFlushed(). + * + * @return number of bytes read or negative fhgfs error code + */ +ssize_t FhgfsOpsHelper_readCached(struct iov_iter *iter, size_t size, loff_t offset, + FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo) +{ + App* app = ioInfo->app; + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + + ssize_t retVal; + CacheBuffer* cacheBuffer; + enum FileBufferType cacheType; + loff_t readEndOffset = offset + size - 1; + loff_t cacheEndOffset; + ssize_t remoteReadSize; + loff_t cacheCopyOffset; + size_t cacheCopySize; + + if (app->cfg->tuneCoherentBuffers) + { + i_mmap_lock_read(fhgfsInode->vfs_inode.i_mapping); + + if (beegfs_hasMappings(&fhgfsInode->vfs_inode)) + { + FsFileInfo_decCacheHits(fileInfo); + i_mmap_unlock_read(fhgfsInode->vfs_inode.i_mapping); + + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); + } + + i_mmap_unlock_read(fhgfsInode->vfs_inode.i_mapping); + } + + + // fast path for parallel readers (other path takes exclusive lock) + if(FhgfsInode_getIsFileOpenByMultipleReaders(fhgfsInode) || + !FsFileInfo_getAllowCaching(fileInfo) ) + { // no caching needed + ssize_t readRes; + + FhgfsInode_fileCacheSharedLock(fhgfsInode); // L O C K (shared) + + cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + cacheType = cacheBuffer->bufType; + + if(cacheType != FileBufferType_NONE) + { // file already has buffer attached, so we need exclusive lock + FhgfsInode_fileCacheSharedUnlock(fhgfsInode); // U N L O C K (shared) + goto exclusive_lock_path; + } + + readRes = FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); + + FhgfsInode_fileCacheSharedUnlock(fhgfsInode); // U N L O C K (shared) + + return readRes; + } + + +exclusive_lock_path: + + FhgfsInode_fileCacheExclusiveLock(fhgfsInode); // L O C K (exclusive) + + cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + cacheType = cacheBuffer->bufType; + + if(cacheType == FileBufferType_NONE) + { // file has no buffer attached + + // update cache hits by guessing whether this would have been a cache hit if we had a buffer + loff_t virtualCacheStart = FsFileInfo_getLastReadOffset(fileInfo); + loff_t virtualCacheEnd = virtualCacheStart + NoAllocBufferStore_getBufSize(cacheStore) - 1; + + if( (virtualCacheEnd >= offset) && (virtualCacheStart <= readEndOffset) ) + FsFileInfo_incCacheHits(fileInfo); // range overlap => could have been a cache hit + else + FsFileInfo_decCacheHits(fileInfo); // would probably not have been a cache hit + + retVal = __FhgfsOpsHelper_readCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + if(cacheType == FileBufferType_WRITE) + { // file has a write cache => flush it + FhgfsOpsErr flushRes; + + FsFileInfo_decCacheHits(fileInfo); + + flushRes = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, false); + + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + { // flush failed + retVal = -flushRes; + goto unlock_and_exit; + } + + retVal = __FhgfsOpsHelper_readCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + // file has a read cache => does it overlap with the read range? + + cacheEndOffset = cacheBuffer->fileOffset + cacheBuffer->bufUsageLen - 1; + + if( (cacheEndOffset < offset) || + (cacheBuffer->fileOffset > readEndOffset) ) + { // cache range and read range do not overlap => flush + FhgfsOpsErr flushRes; + + FsFileInfo_decCacheHits(fileInfo); + + flushRes = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, false); + + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + { // flush failed + retVal = -flushRes; + goto unlock_and_exit; + } + + retVal = __FhgfsOpsHelper_readCacheFlushed(iter, size, offset, fhgfsInode, fileInfo, ioInfo); + goto unlock_and_exit; + } + + // cache range and read range are overlapping. three possible cases to be handled: + // read range before/inside/behind the cache range + + FsFileInfo_incCacheHits(fileInfo); + + remoteReadSize = 0; + + if(offset < cacheBuffer->fileOffset) + { // read begins before cache => remote-read and copy the rest + ssize_t readRes; + + remoteReadSize = cacheBuffer->fileOffset - offset; + + readRes = FhgfsOpsRemoting_readfileVec(iter, remoteReadSize, offset, ioInfo, fhgfsInode); + if(readRes < remoteReadSize) + { // error or end-of-file => invalidate cache + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + + retVal = readRes; + goto unlock_and_exit; + } + } + + // remote read (if any) succeeded => copy part from cache + cacheCopyOffset = (offset <= cacheBuffer->fileOffset) ? 0 : (offset - cacheBuffer->fileOffset); + cacheCopySize = MIN(cacheBuffer->fileOffset + cacheBuffer->bufUsageLen, offset + size) - + (cacheBuffer->fileOffset + cacheCopyOffset); + + { + size_t numCopied = copy_to_iter(&(cacheBuffer->buf)[cacheCopyOffset], cacheCopySize, iter); + if (unlikely(numCopied != cacheCopySize)) + { // copy failed + Logger* log = App_getLogger(app); + Logger_log(log, Log_DEBUG, __func__, "Buffer copy to userspace failed (invalid buffer)"); + + retVal = -FhgfsOpsErr_ADDRESSFAULT; + goto unlock_and_exit; + } + } + + + // has everything been read or is there a remainder behind the cacheBuf? + + if(readEndOffset > cacheEndOffset) + { // there is a remainder behind the cacheBuf => discard cache (to allow new caching) + //loff_t remainderBufOffset = remoteReadSize + cacheCopySize; + loff_t remainderFileOffset = cacheEndOffset + 1; + size_t remainderSize = readEndOffset - cacheEndOffset; + ssize_t remainderRes; + + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + + remainderRes = __FhgfsOpsHelper_readCacheFlushed(iter, + remainderSize, remainderFileOffset, fhgfsInode, fileInfo, ioInfo); + if(unlikely(remainderRes < 0) ) + { // reading failed + retVal = remainderRes; + goto unlock_and_exit; + } + + retVal = remoteReadSize + cacheCopySize + remainderRes; + goto unlock_and_exit; + } + + // read was successful if we got here + + retVal = size; + + +unlock_and_exit: + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); // U N L O C K (exclusive) + + return retVal; +} + + + +/** + * Try to cache the write data or delegate to the normal remoting writefile(). + * + * Note: Use this only when definitely no file cache entry exists. + * Note: Unlocked, so caller must hold inode cache lock. + * + * @param offset offset in file, -1 for append + * @return number of bytes written or negative fhgfs error code + */ +ssize_t __FhgfsOpsHelper_writeCacheFlushed(struct iov_iter *iter, + size_t size, loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo) +{ + App* app = ioInfo->app; + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + InodeRefStore* refStore = App_getInodeRefStore(app); + + bool isAppendWrite = (offset == -1); // true if this is an append write + StripePattern* pattern = ioInfo->pattern; + size_t maxCacheLen; + CacheBuffer* cacheBuffer; + int userBufCopyRes; + + // caching disabled? + if(!FsFileInfo_getAllowCaching(fileInfo) || + (!isAppendWrite && (FsFileInfo_getCacheHits(fileInfo) <= 0) ) ) + return FhgfsOpsHelper_writefileEx(fhgfsInode, iter, size, offset, ioInfo); + + + // check whether the write size is larger than cacheBuf or would span multiple chunks + + if(isAppendWrite) + maxCacheLen = NoAllocBufferStore_getBufSize(cacheStore); + else + { // normal write (not append) + size_t currentChunkSize = StripePattern_getChunkEnd(pattern, offset) - offset + 1; + + maxCacheLen = MIN(currentChunkSize, NoAllocBufferStore_getBufSize(cacheStore) ); + } + + if(size >= maxCacheLen) // (Note: '=' because we don't want a completely filled up buffer) + { // scenario not allowed => write through + return FhgfsOpsHelper_writefileEx(fhgfsInode, iter, size, offset, ioInfo); + } + + + // we got something to cache here (data fits completely into a cacheBuf) + + // create new cache (if buffer available) + + cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + + #ifdef BEEGFS_DEBUG + BEEGFS_BUG_ON(cacheBuffer->buf, "Looks like we're about to leak a cache buffer"); + #endif // BEEGFS_DEBUG + + cacheBuffer->buf = NoAllocBufferStore_instantBuf(cacheStore); + if(!cacheBuffer->buf) + { // no cache buffer left in the store => write through + return FhgfsOpsHelper_writefileEx(fhgfsInode, iter, size, offset, ioInfo); + } + + cacheBuffer->bufType = FileBufferType_WRITE; // (needed here for _discardCache() below) + + // init cache entry fields and copy data to cacheBuf + userBufCopyRes = copy_from_iter(cacheBuffer->buf, size, iter); + if(unlikely(userBufCopyRes != size)) + { // copy failed + Logger* log = App_getLogger(app); + Logger_log(log, Log_DEBUG, __func__, "Buffer copy from userspace failed (invalid buffer)"); + + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + return -FhgfsOpsErr_ADDRESSFAULT; + } + + cacheBuffer->bufUsageLen = size; + cacheBuffer->bufUsageMaxLen = maxCacheLen; + cacheBuffer->fileOffset = offset; // (note: can be -1 in case of append) + + // add to inode ref store for async flush + InodeRefStore_addAndReferenceInode(refStore, BEEGFS_VFSINODE(fhgfsInode) ); + + return size; +} + + +/** + * Try to read-ahead data or just do a plain normal remoting readfile(). + * + * Note: Use this only when definitely no file cache entry exists. + * Note: Unlocked, so caller must hold inode cache lock. + * + * @return number of bytes read or negative fhgfs error code + */ +ssize_t __FhgfsOpsHelper_readCacheFlushed(struct iov_iter *iter, size_t size, loff_t offset, + FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo) +{ + App* app = ioInfo->app; + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + InodeRefStore* refStore = App_getInodeRefStore(app); + + StripePattern* pattern = ioInfo->pattern; + size_t currentChunkSize; + size_t maxCacheLen; + CacheBuffer* cacheBuffer; + ssize_t readRes; + ssize_t numIterCopy; + + + // caching disabled? + if(!FsFileInfo_getAllowCaching(fileInfo) || (FsFileInfo_getCacheHits(fileInfo) <= 0) || + FhgfsInode_getIsFileOpenByMultipleReaders(fhgfsInode) ) + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); + + // check whether the read size is larger than a single cacheBuf or would span multiple chunks + currentChunkSize = StripePattern_getChunkEnd(pattern, offset) - offset + 1; + maxCacheLen = MIN(currentChunkSize, NoAllocBufferStore_getBufSize(cacheStore) ); + + if(size >= maxCacheLen) // (Note: '=' because we don't want a completely used up buffer) + { // scenario not allowed => read direct + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); + } + + // looks like we got something to cache here (read size is smaller than a single cacheBuf) + + // create new cache (if buffer available) + + cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + + #ifdef BEEGFS_DEBUG + BEEGFS_BUG_ON(cacheBuffer->buf, "Looks like we're about to leak a cache buffer"); + #endif // BEEGFS_DEBUG + + cacheBuffer->buf = NoAllocBufferStore_instantBuf(cacheStore); + if(!cacheBuffer->buf) + { // no cache buffer left in the store => read direct + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); + } + + cacheBuffer->bufType = FileBufferType_READ; // (needed here for _discardCache() below) + + // we got a buffer => read as much as possible into the cache buffer + + if(!offset && (size < FSFILEINFO_CACHE_SLOWSTART_READLEN) ) + maxCacheLen = FSFILEINFO_CACHE_SLOWSTART_READLEN; /* reduce read-ahead at file start for small + reads. good if e.g. a process is only looking at file starts */ + + + readRes = FhgfsOpsRemoting_readfile_kernel(cacheBuffer->buf, maxCacheLen, offset, ioInfo, fhgfsInode); + if(readRes <= 0) + { // error or immediate end of file + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + return readRes; + } + + numIterCopy = MIN((ssize_t) size, readRes); + + // init cache entry fields and copy data from cache buffer to client iter + { + size_t numCopied = copy_to_iter(cacheBuffer->buf, numIterCopy, iter); + + if (unlikely(numCopied != numIterCopy)) + { // copy failed + Logger* log = App_getLogger(app); + Logger_log(log, Log_DEBUG, __func__, "Buffer copy to userspace failed (invalid buffer)"); + + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + return -FhgfsOpsErr_ADDRESSFAULT; + } + } + + cacheBuffer->bufUsageLen = readRes; + cacheBuffer->bufUsageMaxLen = maxCacheLen; + cacheBuffer->fileOffset = offset; + + // add to inode ref store for async flush + InodeRefStore_addAndReferenceInode(refStore, BEEGFS_VFSINODE(fhgfsInode) ); + + return numIterCopy; +} + +/** + * Discard the current cache buffer and return it to the store. + * + * Note: Unlocked, so caller must hold the inode cache lock. + */ +void __FhgfsOpsHelper_discardCache(App* app, FhgfsInode* fhgfsInode) +{ + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + InodeRefStore* refStore = App_getInodeRefStore(app); + + CacheBuffer* cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + + #ifdef BEEGFS_DEBUG + if( (cacheBuffer->buf == NULL) || (cacheBuffer->bufType == FileBufferType_NONE) ) + { + BEEGFS_BUG_ON(1, "Attempting to discard an invalid cache buffer"); + return; + } + #endif // BEEGFS_DEBUG + + NoAllocBufferStore_addBuf(cacheStore, cacheBuffer->buf); + + cacheBuffer->buf = NULL; // (NULL'ing required for debug sanity checks) + cacheBuffer->bufType = FileBufferType_NONE; + + // remove inode from async flush store + InodeRefStore_removeAndReleaseInode(refStore, BEEGFS_VFSINODE(fhgfsInode) ); +} + +FhgfsOpsErr FhgfsOpsHelper_getAppendLock(FhgfsInode* inode, RemotingIOInfo* ioInfo) +{ + FhgfsOpsErr lockRes; + + FhgfsInode_entryInfoReadLock(inode); // LOCK EntryInfo + + lockRes = FhgfsOpsRemoting_flockAppendEx(&inode->entryInfo, &inode->entryInfoLock, ioInfo->app, + ioInfo->fileHandleID, 0, current->pid, ENTRYLOCKTYPE_EXCLUSIVE, true); + + FhgfsInode_entryInfoReadUnlock(inode); // UNLOCK EntryInfo + + if(unlikely(lockRes != FhgfsOpsErr_SUCCESS) ) + { + LOG_DEBUG_FORMATTED(App_getLogger(ioInfo->app), Log_DEBUG, __func__, "Append lock error: %s", + FhgfsOpsErr_toErrString(lockRes) ); + SAFE_ASSIGN(ioInfo->needsAppendLockCleanup, true); + } + + return lockRes; +} + +FhgfsOpsErr FhgfsOpsHelper_releaseAppendLock(FhgfsInode* inode, RemotingIOInfo* ioInfo) +{ + FhgfsOpsErr unlockRes; + + FhgfsInode_entryInfoReadLock(inode); // LOCK EntryInfo + + unlockRes = FhgfsOpsRemoting_flockAppendEx(&inode->entryInfo, &inode->entryInfoLock, ioInfo->app, + ioInfo->fileHandleID, 0, current->pid, ENTRYLOCKTYPE_UNLOCK, true); + + FhgfsInode_entryInfoReadUnlock(inode); // UNLOCK EntryInfo + + if(unlikely(unlockRes != FhgfsOpsErr_SUCCESS) ) + SAFE_ASSIGN(ioInfo->needsAppendLockCleanup, true); + + return unlockRes; +} + + +/** + * Reads chunk by chunk from the servers and zero-fills the buffer if a server returns EOF. + * So the caller must make sure that the file actually has at least the requested size. + * + * Note: Intended for sparse file reading. + * Note: There is also a similar version for kernel buffers. + */ +FhgfsOpsErr FhgfsOpsHelper_readOrClearUser(App* app, struct iov_iter *iter, size_t size, + loff_t offset, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo) +{ + StripePattern* pattern = ioInfo->pattern; + + while(size) + { + size_t currentChunkSize = StripePattern_getChunkEnd(pattern, offset) - offset + 1; + size_t currentReadSize = MIN(currentChunkSize, size); + ssize_t currentReadRes; + + currentReadRes = FhgfsOpsRemoting_readfileVec(iter, currentReadSize, offset, ioInfo, NULL); + + if(unlikely(currentReadRes < 0) ) + return -currentReadRes; + + if( (size_t)currentReadRes < currentReadSize) + { // zero-fill the remainder + long clearVal; + ssize_t nclear = currentReadSize - currentReadRes; + + clearVal = iov_iter_zero(nclear, iter); + if (clearVal != nclear) + return FhgfsOpsErr_ADDRESSFAULT; + } + + offset += currentReadSize; + size -= currentReadSize; + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Takes two paths that are relative to the mount point and creates a new path that is relative from + * pathFromStr to pathToStr. + * + * @param outPathRelativeToStr will be kalloc'ed and needs to be kfree'd by the caller + */ +void FhgfsOpsHelper_getRelativeLinkStr(const char* pathFromStr, const char* pathToStr, + char** outPathRelativeToStr) +{ + // the idea here is to prepend a "../" to pathTo for every parent dir of pathFrom + + int i; + int pathFromLen; // (must be signed, because it might be negative for certain paths) + Path pathFrom; + Path pathTo; // will be modified to become the relative result path + StrCpyList* pathFromElems; + StrCpyList* pathToElems; + + Path_initFromString(&pathFrom, pathFromStr); + Path_initFromString(&pathTo, pathToStr); + + pathFromElems = Path_getPathElems(&pathFrom); + pathToElems = Path_getPathElems(&pathTo); + + pathFromLen = StrCpyList_length(pathFromElems); + + // insert a ".." for every parent dir in pathFrom + + // (note: -1 to excluse the final path element) + for(i = 0; i < (pathFromLen-1); i++) + { + StrCpyList_addHead(pathToElems, ".."); + } + + Path_setAbsolute(&pathTo, false); + + *outPathRelativeToStr = Path_getPathAsStrCopy(&pathTo); + + Path_uninit(&pathFrom); + Path_uninit(&pathTo); +} + +/** + * Note: creates the symlink as a normal file and writes the target to it + * + * @param mode access mode (permission flags) + * @return 0 on success, negative linux error code otherwise + */ +int FhgfsOpsHelper_symlink(App* app, const EntryInfo* parentInfo, const char* to, + struct CreateInfo* createInfo, EntryInfo* outEntryInfo) +{ + int retVal = 0; + + size_t toStrLen = strlen(to); + FhgfsOpsErr mkRes; + AtomicInt maxUsedTargetIndex; + RemotingIOInfo ioInfo; + FhgfsOpsErr openRes; + size_t numTargets; + ssize_t writeRes; + FhgfsOpsErr closeRes; + BitStore firstWriteDone; + PathInfo pathInfo; + const struct FileEvent* event; + + AtomicInt_init(&maxUsedTargetIndex, -1); + + // create the file + + // stash createInfo->fileEvent. we want to send it only during close (when the symlink is fully + // created) - mkfile would generate its own event if createInfo->fileEvent was set. + event = createInfo->fileEvent; + createInfo->fileEvent = NULL; + mkRes = FhgfsOpsRemoting_mkfile(app, parentInfo, createInfo, outEntryInfo); + createInfo->fileEvent = event; + if(mkRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(mkRes); + goto err_exit; + } + + // open the file + + memset(&pathInfo, 0, sizeof(pathInfo) ); + + RemotingIOInfo_initOpen(app, OPENFILE_ACCESS_WRITE, &maxUsedTargetIndex, &pathInfo, &ioInfo); + + openRes = FhgfsOpsRemoting_openfile(outEntryInfo, &ioInfo, NULL, NULL); + if(openRes != FhgfsOpsErr_SUCCESS) + { // error + FhgfsOpsRemoting_unlinkfile(app, parentInfo, createInfo->entryName, NULL); + + retVal = FhgfsOpsErr_toSysErr(openRes); + goto err_cleanup_open; + } + + numTargets = UInt16Vec_length(ioInfo.pattern->getStripeTargetIDs(ioInfo.pattern)); + BitStore_initWithSizeAndReset(&firstWriteDone, numTargets); + ioInfo.firstWriteDone = &firstWriteDone; + ioInfo.userID = createInfo->userID; + ioInfo.groupID = createInfo->groupID; +#ifdef BEEGFS_NVFS + ioInfo.nvfs = false; +#endif + + // write link-destination to the file + + writeRes = FhgfsOpsRemoting_writefile_kernel(to, toStrLen, 0, &ioInfo); + if(writeRes < (ssize_t)toStrLen) + { // error + FhgfsOpsHelper_closefileWithAsyncRetry(outEntryInfo, &ioInfo, NULL); + FhgfsOpsRemoting_unlinkfile(app, parentInfo, createInfo->entryName, NULL); + + retVal = (writeRes < 0) ? FhgfsOpsErr_toSysErr(-writeRes) : FhgfsOpsErr_INTERNAL; + goto err_cleanup_open; + } + + + // close the file + + // callee frees fileEvent and wants a non-const pointer to signify this. callers of _symlink + // must be aware of this and not free the fileEvent themselves + closeRes = FhgfsOpsHelper_closefileWithAsyncRetry(outEntryInfo, &ioInfo, + (struct FileEvent*) createInfo->fileEvent); + + if(closeRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(closeRes); + // createInfo->fileEvent has been taken care of, don't free it again + createInfo->fileEvent = NULL; + goto err_cleanup_open; + } + + + // clean-up + + BitStore_uninit(&firstWriteDone); + RemotingIOInfo_freeVals(&ioInfo); + + return retVal; + +err_cleanup_open: + if (ioInfo.firstWriteDone) + BitStore_uninit(ioInfo.firstWriteDone); + + RemotingIOInfo_freeVals(&ioInfo); + EntryInfo_uninit(outEntryInfo); + + if (createInfo->fileEvent) + FileEvent_uninit((struct FileEvent*) createInfo->fileEvent); + +err_exit: + return retVal; +} + +/** + * Opens a file (the file must exist), reads data from it and closes it. + * + * Note: This is really slow currently, because of the open/close overhead. + * + * @return number of read bytes or negative linux error code + */ +ssize_t FhgfsOpsHelper_readStateless(App* app, const EntryInfo* entryInfo, + struct iov_iter *iter, size_t size, loff_t offset) +{ + int retVal = -EREMOTEIO; + + AtomicInt maxUsedTargetIndex; + RemotingIOInfo ioInfo; + FhgfsOpsErr openRes; + ssize_t readRes; + size_t numTargets; + FhgfsOpsErr closeRes; + BitStore firstWriteDone; + PathInfo pathInfo; + + AtomicInt_init(&maxUsedTargetIndex, -1); + + // open file + + memset(&pathInfo, 0, sizeof(pathInfo) ); + + RemotingIOInfo_initOpen(app, OPENFILE_ACCESS_READ, &maxUsedTargetIndex, &pathInfo, &ioInfo); + + openRes = FhgfsOpsRemoting_openfile(entryInfo, &ioInfo, NULL, NULL); + + if(openRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(openRes); + goto clean_up_open; + } + + numTargets = UInt16Vec_length(ioInfo.pattern->getStripeTargetIDs(ioInfo.pattern)); + BitStore_initWithSizeAndReset(&firstWriteDone, numTargets); + ioInfo.firstWriteDone = &firstWriteDone; + + // read file + + readRes = FhgfsOpsRemoting_readfileVec(iter, size, offset, &ioInfo, NULL); + if(readRes < 0) + { // error + FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, &ioInfo, NULL); + + retVal = FhgfsOpsErr_toSysErr(-readRes); + goto clean_up_open; + } + + retVal = readRes; + + // close the file + + closeRes = FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, &ioInfo, NULL); + + if(closeRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(closeRes); + } + + + // clean-up + +clean_up_open: + if (ioInfo.firstWriteDone) + BitStore_uninit(ioInfo.firstWriteDone); + + RemotingIOInfo_freeVals(&ioInfo); + + return retVal; +} + +/** + * Writes the given buffer by getting a reference handle from the inode and releasing that handle + * immediately after the write. + * + * Note: This can safe the overhead for remote open/close if the file is already open for writing + * (or reading+writing) by any other process, so it is way better than the _writeStateless() + * method. + * + * @param offset offset in file, -1 for append + * @return bytes written or negative fhgfs error code + */ +static ssize_t FhgfsOpsHelper_writeStatelessInode(FhgfsInode* fhgfsInode, const char *buf, + size_t size, loff_t offset) +{ + FileHandleType handleType; + RemotingIOInfo ioInfo; + + FhgfsOpsErr referenceRes; + ssize_t writeRes; + FhgfsOpsErr releaseRes; + + + // open file + + /* referenceHandle needs a dentry only for possible TRUNC operations. */ + referenceRes = FhgfsInode_referenceHandle(fhgfsInode, NULL, OPENFILE_ACCESS_WRITE, true, NULL, + &handleType, NULL); + if(unlikely(referenceRes != FhgfsOpsErr_SUCCESS) ) + { // error + return -referenceRes; + } + + // write file + + FhgfsInode_getRefIOInfo(fhgfsInode, handleType, FhgfsInode_handleTypeToOpenFlags(handleType), + &ioInfo); + + writeRes = FhgfsOpsHelper_writefileEx_kernel(fhgfsInode, buf, size, offset, &ioInfo); + if(unlikely(writeRes < 0) ) + { // error + FhgfsInode_releaseHandle(fhgfsInode, handleType, NULL); + + return writeRes; + } + + // close file + + releaseRes = FhgfsInode_releaseHandle(fhgfsInode, handleType, NULL); + if(unlikely(releaseRes != FhgfsOpsErr_SUCCESS) ) + { // error + return -releaseRes; + } + + return writeRes; +} + +/** + * Opens a file (the file must exist), writes the data to it and closes it. + * + * Note: This is really slow, because of the open/close overhead - use _writeStatelessInode() + * method instead, if possible. + * + * @return number of written bytes or negative linux error code + */ +ssize_t FhgfsOpsHelper_writeStateless(App* app, const EntryInfo* entryInfo, + struct iov_iter *iter, size_t size, loff_t offset, unsigned uid, unsigned gid) +{ + int retVal = -EREMOTEIO; + + AtomicInt maxUsedTargetIndex; + RemotingIOInfo ioInfo; + FhgfsOpsErr openRes; + ssize_t writeRes; + FhgfsOpsErr closeRes; + BitStore firstWriteDone; + size_t numTargets; + PathInfo pathInfo; + + AtomicInt_init(&maxUsedTargetIndex, -1); + + // open file + + memset(&pathInfo, 0, sizeof(pathInfo) ); + + RemotingIOInfo_initOpen(app, OPENFILE_ACCESS_WRITE, &maxUsedTargetIndex, &pathInfo, &ioInfo); + + openRes = FhgfsOpsRemoting_openfile(entryInfo, &ioInfo, NULL, NULL); + + if(openRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(openRes); + goto clean_up_open; + } + + numTargets = UInt16Vec_length(ioInfo.pattern->getStripeTargetIDs(ioInfo.pattern)); + BitStore_initWithSizeAndReset(&firstWriteDone, numTargets); + ioInfo.firstWriteDone = &firstWriteDone; + ioInfo.userID = uid; + ioInfo.groupID = gid; +#ifdef BEEGFS_NVFS + ioInfo.nvfs = false; +#endif + + // write file + + writeRes = FhgfsOpsRemoting_writefileVec(iter, offset, &ioInfo, false); + if(writeRes < 0) + { // error + FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, &ioInfo, NULL); + + retVal = FhgfsOpsErr_toSysErr(-writeRes); + goto clean_up_open; + } + + retVal = writeRes; + + // close file + + closeRes = FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, &ioInfo, NULL); + + if(closeRes != FhgfsOpsErr_SUCCESS) + { // error + retVal = FhgfsOpsErr_toSysErr(closeRes); + } + + + // clean-up + +clean_up_open: + if (ioInfo.firstWriteDone) + BitStore_uninit(ioInfo.firstWriteDone); + + RemotingIOInfo_freeVals(&ioInfo); + + return retVal; +} + +/** + * Flush cache buffer and return it to the store. + * + * Note: Unlocked, so caller must hold inode cache lock. + * + * @param discardCacheOnError true to discard a write cache if the remote write + * was not successful; false to just release it to the store in this case + */ +FhgfsOpsErr __FhgfsOpsHelper_flushCacheUnlocked(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError) +{ + /* note: we don't take a handle to an open file here, because we wouldn't know whether the + given handle is a read-only handle that just needs to flush the write cache to establish a new + read cache. that's why we use the _writeStatelessInode() method here. */ + + CacheBuffer* cacheBuffer = Fhgfsinode_getFileCacheBuffer(fhgfsInode); + enum FileBufferType cacheType = cacheBuffer->bufType; + ssize_t writeRes; + + if(cacheType == FileBufferType_NONE) + return FhgfsOpsErr_SUCCESS; + + if(cacheType == FileBufferType_READ) + { // file has a read cache => just discard the cached data + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + return FhgfsOpsErr_SUCCESS; + } + + // file has a write cache => send the cached data to the storage nodes + + writeRes = FhgfsOpsHelper_writeStatelessInode(fhgfsInode, cacheBuffer->buf, + cacheBuffer->bufUsageLen, cacheBuffer->fileOffset); + if(unlikely(writeRes < (ssize_t)cacheBuffer->bufUsageLen) ) + { // write did not succeed + if(discardCacheOnError) + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + + if(writeRes > 0) + return FhgfsOpsErr_NOSPACE; + + return -writeRes; + } + + // write succeeded + + __FhgfsOpsHelper_discardCache(app, fhgfsInode); + + return FhgfsOpsErr_SUCCESS; +} + + +/** + * Flush cache buffer and return it to the store. + * + * @param discardCacheOnError true to discard a write cache if the remote write + * was not successful; false to just release it to the store in this case + */ +FhgfsOpsErr FhgfsOpsHelper_flushCache(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError) +{ + FhgfsOpsErr retVal; + + FhgfsInode_fileCacheExclusiveLock(fhgfsInode); // L O C K + + retVal = __FhgfsOpsHelper_flushCacheUnlocked(app, fhgfsInode, discardCacheOnError); + + FhgfsInode_fileCacheExclusiveUnlock(fhgfsInode); // U N L O C K + + return retVal; +} diff --git a/client_module/source/filesystem/FhgfsOpsHelper.h b/client_module/source/filesystem/FhgfsOpsHelper.h new file mode 100644 index 0000000..0391fb9 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsHelper.h @@ -0,0 +1,169 @@ +#ifndef FHGFSOPSHELPER_H_ +#define FHGFSOPSHELPER_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifdef LOG_DEBUG_MESSAGES + /** + * Print debug messages. Used to trace functions which are frequently called and therefore + * has to be exlicitly enabled at compilation time. + * Has to be a macro due to usage of __VA_ARGS__. + */ + #define FhgfsOpsHelper_logOpDebug(app, dentry, inode, logContext, msgStr, ...) \ + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, dentry, inode, logContext, msgStr, ##__VA_ARGS__) +#else + // no debug build, so those debug messages disabled at all + #define FhgfsOpsHelper_logOpDebug(app, dentry, inode, logContext, msgStr, ...) +#endif // LOG_DEBUG_MESSAGES + + + +extern void FhgfsOpsHelper_logOpMsg(int level, App* app, struct dentry* dentry, struct inode* inode, + const char *logContext, const char *msgStr, ...); + +extern int FhgfsOpsHelper_refreshDirInfoIncremental(App* app, const EntryInfo* entryInfo, + FsDirInfo* dirInfo, bool forceUpdate); + +extern FhgfsOpsErr FhgfsOpsHelper_flushCache(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError); +extern FhgfsOpsErr FhgfsOpsHelper_flushCacheNoWait(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError); +extern ssize_t FhgfsOpsHelper_writeCached(struct iov_iter *iter, size_t size, + loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo); +extern ssize_t FhgfsOpsHelper_readCached(struct iov_iter *iter, size_t size, + loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo); + +extern FhgfsOpsErr __FhgfsOpsHelper_flushCacheUnlocked(App* app, FhgfsInode* fhgfsInode, + bool discardCacheOnError); +extern ssize_t __FhgfsOpsHelper_writeCacheFlushed(struct iov_iter *iter, size_t size, + loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo); +extern ssize_t __FhgfsOpsHelper_readCacheFlushed(struct iov_iter *iter, size_t size, + loff_t offset, FhgfsInode* fhgfsInode, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo); +extern void __FhgfsOpsHelper_discardCache(App* app, FhgfsInode* fhgfsInode); + +extern FhgfsOpsErr FhgfsOpsHelper_getAppendLock(FhgfsInode* inode, RemotingIOInfo* ioInfo); +extern FhgfsOpsErr FhgfsOpsHelper_releaseAppendLock(FhgfsInode* inode, RemotingIOInfo* ioInfo); + +ssize_t FhgfsOpsHelper_appendfileVecOffset(FhgfsInode* fhgfsInode, struct iov_iter *iter, + size_t count, RemotingIOInfo* ioInfo, loff_t offsetFromEnd, loff_t* outNewOffset); + +extern FhgfsOpsErr FhgfsOpsHelper_readOrClearUser(App* app, struct iov_iter *iter, size_t size, + loff_t offset, FsFileInfo* fileInfo, RemotingIOInfo* ioInfo); + +extern void FhgfsOpsHelper_getRelativeLinkStr(const char* pathFrom, const char* pathTo, + char** pathRelativeTo); +extern int FhgfsOpsHelper_symlink(App* app, const EntryInfo* parentInfo, const char* to, + struct CreateInfo *createInfo, EntryInfo* outEntryInfo); + + +extern ssize_t FhgfsOpsHelper_readStateless(App* app, const EntryInfo* entryInfo, + struct iov_iter *iter, size_t size, loff_t offset); +extern ssize_t FhgfsOpsHelper_writeStateless(App* app, const EntryInfo* entryInfo, + struct iov_iter *iter, size_t size, loff_t offset, unsigned uid, unsigned gid); + + +// inliners +static inline void FhgfsOpsHelper_logOp(int level, App* app, struct dentry* dentry, + struct inode* inode, const char *logContext); +static inline FhgfsOpsErr FhgfsOpsHelper_closefileWithAsyncRetry(const EntryInfo* entryInfo, + RemotingIOInfo* ioInfo, struct FileEvent* event); +static inline FhgfsOpsErr FhgfsOpsHelper_unlockEntryWithAsyncRetry(const EntryInfo* entryInfo, + RWLock* eiRLock, RemotingIOInfo* ioInfo, int64_t clientFD); +static inline FhgfsOpsErr FhgfsOpsHelper_unlockRangeWithAsyncRetry(const EntryInfo* entryInfo, + RWLock* eiRLock, RemotingIOInfo* ioInfo, int ownerPID); + +/** + * Reads a symlink. + * + * @return number of read bytes or negative linux error code + */ +static inline int FhgfsOpsHelper_readlink_kernel(App* app, const EntryInfo* entryInfo, + char *buf, int size) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, size, READ); + return FhgfsOpsHelper_readStateless(app, entryInfo, iter, size, 0); +} + + +/** + * Wrapper function for FhgfsOpsHelper_logOpMsg(), just skips msgStr and sets it to NULL + */ +void FhgfsOpsHelper_logOp(int level, App* app, struct dentry* dentry, struct inode* inode, + const char *logContext) +{ + Logger* log = App_getLogger(app); + + // check the log level here, as this function is inlined + if (likely(level > Logger_getLogLevel(log) ) ) + return; + + FhgfsOpsHelper_logOpMsg(level, app, dentry, inode, logContext, NULL); +} + +/** + * Call close remoting and add close operation to the corresponding async retry queue if a + * communucation error occurred. + * + * @param entryInfo will be copied + * @param ioInfo will be copied + * @return remoting result (independent of whether the operation was added for later retry) + */ +FhgfsOpsErr FhgfsOpsHelper_closefileWithAsyncRetry(const EntryInfo* entryInfo, + RemotingIOInfo* ioInfo, struct FileEvent* event) +{ + FhgfsOpsErr closeRes; + + closeRes = FhgfsOpsRemoting_closefile(entryInfo, ioInfo, event); + + if( (closeRes == FhgfsOpsErr_COMMUNICATION) || (closeRes == FhgfsOpsErr_INTERRUPTED) ) + { // comm error => add for later retry + InternodeSyncer* syncer = App_getInternodeSyncer(ioInfo->app); + InternodeSyncer_delayedCloseAdd(syncer, entryInfo, ioInfo, event); + } + else if (event) + FileEvent_uninit(event); + + return closeRes; +} + +FhgfsOpsErr FhgfsOpsHelper_unlockEntryWithAsyncRetry(const EntryInfo* entryInfo, RWLock* eiRLock, + RemotingIOInfo* ioInfo, int64_t clientFD) +{ + FhgfsOpsErr unlockRes = FhgfsOpsRemoting_flockEntryEx(entryInfo, eiRLock, ioInfo->app, + ioInfo->fileHandleID, clientFD, 0, ENTRYLOCKTYPE_CANCEL, true); + + if( (unlockRes == FhgfsOpsErr_COMMUNICATION) || (unlockRes == FhgfsOpsErr_INTERRUPTED) ) + { // comm error => add for later retry + + InternodeSyncer* syncer = App_getInternodeSyncer(ioInfo->app); + InternodeSyncer_delayedEntryUnlockAdd(syncer, entryInfo, ioInfo, clientFD); + } + + return unlockRes; +} + +FhgfsOpsErr FhgfsOpsHelper_unlockRangeWithAsyncRetry(const EntryInfo* entryInfo, RWLock* eiRLock, + RemotingIOInfo* ioInfo, int ownerPID) +{ + FhgfsOpsErr unlockRes = FhgfsOpsRemoting_flockRangeEx(entryInfo, eiRLock, ioInfo->app, + ioInfo->fileHandleID, ownerPID, ENTRYLOCKTYPE_CANCEL, 0, ~0ULL, true); + + if( (unlockRes == FhgfsOpsErr_COMMUNICATION) || (unlockRes == FhgfsOpsErr_INTERRUPTED) ) + { // comm error => add for later retry + InternodeSyncer* syncer = App_getInternodeSyncer(ioInfo->app); + InternodeSyncer_delayedRangeUnlockAdd(syncer, entryInfo, ioInfo, ownerPID); + } + + return unlockRes; +} + + +#endif /*FHGFSOPSHELPER_H_*/ diff --git a/client_module/source/filesystem/FhgfsOpsInode.c b/client_module/source/filesystem/FhgfsOpsInode.c new file mode 100644 index 0000000..a4d32ba --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsInode.c @@ -0,0 +1,2928 @@ +#include +#include +#include +#include // response flags +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsSuper.h" +#include "FhgfsOpsDir.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsFileNative.h" +#include "FhgfsOpsHelper.h" + +#include +#include +#include +#include +#include +#include +#include + + +static struct kmem_cache* FhgfsInodeCache = NULL; + +#define FhgfsOpsInode_CACHE_NAME BEEGFS_MODULE_NAME_STR "_inode_cache" + + +static void FhgfsOps_newAttrToInode(struct iattr* iAttr, struct inode* outInode); + +static __always_inline int maybeRefreshInode(struct inode* inode, bool whenCacheInvalid, + bool withFileSize, bool force) +{ + App* app = FhgfsOps_getApp(inode->i_sb); + Config* cfg = app->cfg; + + if(unlikely(!FhgfsOps_getIsRootInited(inode->i_sb) && inode->i_ino == BEEGFS_INODE_ROOT_INO) + || (!FhgfsInode_isCacheValid(BEEGFS_INODE(inode), inode->i_mode, cfg) && whenCacheInvalid) + || unlikely(force)) + { + FhgfsIsizeHints iSizeHints; + int refreshRes; + + refreshRes = __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, !withFileSize); + if(refreshRes) + return refreshRes; + + if (inode->i_ino == BEEGFS_INODE_ROOT_INO) + FhgfsOps_setIsRootInited(inode->i_sb, true); + } + + return 0; +} + + +/** + * Find out whether a directory entry exists. Add the dentry and create/associate the + * corresponding inode in case it does exist. + * + * Note: Newer version, superseding the old _lookup() with more efficient messaging based on + * combined intent requests. + * + * @param dentry the thing that we are looking for. + * @param flags LOOKUP_... + * @return NULL if we're using the given new dentry, or pointer to another dentry if we + * find out that it already existed (e.g. from a NFS handle), or ERR_PTR(x) with x being a negative + * linux error code. + */ +#ifndef KERNEL_HAS_ATOMIC_OPEN +struct dentry* FhgfsOps_lookupIntent(struct inode* parentDir, struct dentry* dentry, + struct nameidata* nameidata) +#else +struct dentry* FhgfsOps_lookupIntent(struct inode* parentDir, struct dentry* dentry, + unsigned flags) +#endif // KERNEL_HAS_ATOMIC_OPEN +{ + App* app = FhgfsOps_getApp(dentry->d_sb); + Config* cfg = App_getConfig(app); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_lookupIntent"; + + struct dentry* returnDentry = NULL; // can be NULL or existing dentry or ERR_PTR + FhgfsOpsErr statRes; + fhgfs_stat fhgfsStat; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(parentDir); + EntryInfo newEntryInfo; + const char* entryName = dentry->d_name.name; + bool freeNewEntryInfo = false; + struct inode* inode = dentry->d_inode; + + FhgfsIsizeHints iSizeHints; + + Time now; + Time_setToNowReal(&now); + + // For validating the cache, this field is updated + // with CURRENT_TIME on first lookup + + dentry->d_time = (now.tv_sec * 1000000000UL + now.tv_nsec); + + if(unlikely(Logger_getLogLevel(log) >= Log_SPAM) ) + FhgfsOpsHelper_logOp(Log_SPAM, app, dentry, inode, logContext); + + if(unlikely(dentry->d_name.len > NAME_MAX) ) + return ERR_PTR(-ENAMETOOLONG); + + /* check if root inode attribs have been fetched already + (because the kernel doesn't do lookup/revalidate for the root inode) */ + + { + int refreshRes = maybeRefreshInode(parentDir, true, false, false); + + // root permissions might have changed now => recheck permissions + if (!refreshRes) + refreshRes = os_generic_permission(parentDir, MAY_EXEC); + + if (refreshRes) + return ERR_PTR(refreshRes); + } + + + // retrieve remote stat info for given entry... + + if(unlikely(IS_ROOT(dentry) ) ) + { // root inode is special (though the kernel never actually does a root lookup => unlikely) + bool isGetSuccess = MetadataTk_getRootEntryInfoCopy(app, &newEntryInfo); + + freeNewEntryInfo = true; + + if (isGetSuccess) + statRes = FhgfsOpsRemoting_statDirect(app, &newEntryInfo, &fhgfsStat); + else + statRes = FhgfsOpsErr_INTERNAL; + } + else + { // just a normal subentry of some dir => lookup entryInfoPtr and stat it + FhgfsOpsErr remotingRes; + LookupIntentInfoIn inInfo; // input data for combo-request + LookupIntentInfoOut outInfo; // result data of combo-request + + FhgfsInode_initIsizeHints(NULL, &iSizeHints); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + LookupIntentInfoIn_init(&inInfo, FhgfsInode_getEntryInfo(fhgfsParentInode), entryName); + + LookupIntentInfoOut_prepare(&outInfo, &newEntryInfo, &fhgfsStat); + + remotingRes = FhgfsOpsRemoting_lookupIntent(app, &inInfo, &outInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(remotingRes != FhgfsOpsErr_SUCCESS || outInfo.lookupRes != FhgfsOpsErr_SUCCESS) + statRes = outInfo.lookupRes; // entry not found + else + { // lookup successful (entry exists) + statRes = outInfo.statRes; + if(unlikely(!(outInfo.responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) ) ) + { + Logger_logErrFormatted(log, logContext, "Unexpected stat info missing: %s", + entryName); + } + + freeNewEntryInfo = true; + + // EntryInfo uninitialize already handled + LookupIntentInfoOut_setEntryInfoPtr(&outInfo, NULL); + + if(statRes == FhgfsOpsErr_NOTOWNER) + { // metadata not owned by parent/lookup server => need separate stat remoting + statRes = FhgfsOpsRemoting_statDirect(app, &newEntryInfo, &fhgfsStat); + } + } + + LookupIntentInfoOut_uninit(&outInfo); + + } + + // handle result of stat retrieval (e.g. create new inode)... + + if(statRes != FhgfsOpsErr_SUCCESS) + { // stat error (e.g. entry doesn't exist) + if(statRes == FhgfsOpsErr_PATHNOTEXISTS) + { + /* note: "not exists" does not mean that we return a lookup error. the kernel will check + after lookup() whether we attached an inode to this dentry and handle it accordingly. */ + #ifndef KERNEL_HAS_S_D_OP + dentry->d_op = &fhgfs_dentry_ops; // (see below for KERNEL_HAS_S_D_OP comments) + #endif // KERNEL_HAS_S_D_OP + + d_add(dentry, NULL); + } + else + returnDentry = ERR_PTR(FhgfsOpsErr_toSysErr(statRes) ); + + } + else + { // entry exists => create inode + struct kstat kstat; + struct inode* newInode; + unsigned int metaVersion = fhgfsStat.metaVersion; + + OsTypeConv_kstatFhgfsToOs(&fhgfsStat, &kstat); + + kstat.ino = FhgfsInode_generateInodeID(dentry->d_sb, + newEntryInfo.entryID, strlen(newEntryInfo.entryID) ); + + newInode = __FhgfsOps_newInode(parentDir->i_sb, &kstat, 0, &newEntryInfo, &iSizeHints, metaVersion); + + freeNewEntryInfo = false; // newEntryInfo now owned or freed by _newInode() + + if(unlikely(!newInode || IS_ERR(newInode) ) ) + returnDentry = IS_ERR(newInode) ? ERR_PTR(PTR_ERR(newInode) ) : ERR_PTR(-EACCES); + else + { // new inode created + + #ifndef KERNEL_HAS_S_D_OP + /* per-dentry d_ops are deprecated as of linux 2.6.38 (commit c8aebb0c9f8c7471643d5f). + they are now set on the superblock. (individual per-dentry d_ops could still be set + with d_set_d_op(), but are not required for us.) */ + + dentry->d_op = &fhgfs_dentry_ops; + #endif // KERNEL_HAS_S_D_OP + + if (Config_getSysXAttrsCheckCapabilities(cfg) == CHECKCAPABILITIES_Never) + // The configuration is to never check for capabilities on writes, so use + // "inode_has_no_xattr" which does exactly one thing and that is to set the flag + // "S_NOSEC" on the inode, if the inode doesn't have the setuid and setgid bits set and + // the superblock flag "SB_NOSEC" is set. We set that flag on the superblock according + // to user configuration. When "S_NOSEC" is set on the inode, the kernel will skip + // checking for capabilities on every write operation. + inode_has_no_xattr(newInode); + + if (S_ISDIR(newInode->i_mode) ) + returnDentry = d_materialise_unique(dentry, newInode); + else + { + returnDentry = d_splice_alias(newInode, dentry); /* (d_splice_alias() also replaces a + disconnected dentry that was created from a nfs handle) */ + } + } + + } + + // clean-up + if(freeNewEntryInfo) + EntryInfo_uninit(&newEntryInfo); + + return returnDentry; +} + + +#ifdef KERNEL_HAS_STATX +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_getattr(struct mnt_idmap* idmap, const struct path* path, + struct kstat* kstat, u32 request_mask, unsigned int query_flags) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_getattr(struct user_namespace* mnt_userns, const struct path* path, + struct kstat* kstat, u32 request_mask, unsigned int query_flags) +#else +int FhgfsOps_getattr(const struct path* path, struct kstat* kstat, u32 request_mask, + unsigned int query_flags) +#endif +{ + struct vfsmount* mnt = path->mnt; + struct dentry* dentry = path->dentry; + + bool mustQuery = (query_flags & AT_STATX_SYNC_TYPE) == AT_STATX_FORCE_SYNC; + +#else +int FhgfsOps_getattr(struct vfsmount* mnt, struct dentry* dentry, struct kstat* kstat) +{ + const bool mustQuery = false; + +#endif + App* app = FhgfsOps_getApp(dentry->d_sb); + Config* cfg = App_getConfig(app); + const char* logContext = "FhgfsOps_getattr"; + + /* Note: Do not use IS_ROOT(dentry) as this does not work for NFS file dentries + * (disconnected dentries), i.e. after a server reboot */ + bool isRoot = (dentry == mnt->mnt_root) ? true : false; + + int retVal = 0; + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + bool refreshOnGetAttr = Config_getTuneRefreshOnGetAttr(cfg); + + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + /* note: we assumed that we could live without refreshInode() here, because either lookup() or + revalidate() was called before. But that assumption is wrong when a open file is fstat'ed + (which is relevant for e.g. "tail -f"), so we only use this optimization for closed files. + The root dir inode is also an important exception here, because it is not being revalidated + by the kernel. + + note on dirs: we could always refresh dirs here because of caching and tools like "find" + relying on an updated link count, but while a user is creating subdirs, the stat from "find" + can never be up-to-date, so that would also be quite useless. */ + + retVal = maybeRefreshInode(inode, + isRoot || FhgfsInode_getIsFileOpen(fhgfsInode) || refreshOnGetAttr, true, mustQuery); + + if(!retVal) + { +#if defined(KERNEL_HAS_GENERIC_FILLATTR_REQUEST_MASK) + os_generic_fillattr(inode, kstat, request_mask); +#else + os_generic_fillattr(inode, kstat); +#endif + + kstat->blksize = Config_getTuneInodeBlockSize(cfg); + + if(isRoot) + kstat->ino = BEEGFS_INODE_ROOT_INO; // root => assign the constant root inode number + else + if (is_32bit_api() && kstat->ino > UINT_MAX) + { // 32-bit applications cannot handle 64bit inodes and would fail with EOVERFLOW + kstat->ino = kstat->ino >> 32; + } + } + + // clean-up + + return retVal; +} + +/** + * Get a list of extended attributes of a file, copy the list into a buffer as null-separated string + * list; or compute the size of the buffer required. + * @param value Pointer to the buffer. NULL to compute the size of the buffer required. + * @param size Size of the buffer. + * @return Negative error number on failure, number of bytes used / required on success. + */ +ssize_t FhgfsOps_listxattr(struct dentry* dentry, char* value, size_t size) +{ + App* app = FhgfsOps_getApp(dentry->d_sb); + FhgfsOpsErr remotingRes; + ssize_t resSize; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(dentry->d_inode); + + int refreshRes = maybeRefreshInode(dentry->d_inode, true, false, false); + if (refreshRes) + return refreshRes; + + FhgfsOpsHelper_logOpDebug(app, dentry, NULL, __func__, "(size: %u)", size); + + // "Just get the size, don't return any value" is signaled by value=NULL. Since we only send + // the "size" parameter to the server, we make sure that size is set to 0 in that case. + if(!value) + size = 0; + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_listXAttr(app, FhgfsInode_getEntryInfo(fhgfsInode), value, size, + &resSize); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + resSize = FhgfsOpsErr_toSysErr(remotingRes); + + return resSize; +} + +/** + * Get an extended attribute of a file, copy it into a buffer; or compute the size of the buffer + * required. + * @param value Pointer to the buffer. NULL to compute the size of the buffer required. + * @param size Size of the buffer. + * @return Negative error number on failure, or the number of bytes used / required on success. + */ +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER +ssize_t FhgfsOps_getxattr(struct dentry* dentry, const char* name, void* value, size_t size) +{ + struct inode* inode = dentry->d_inode; +#else +ssize_t FhgfsOps_getxattr(struct inode* inode, const char* name, void* value, size_t size) +{ +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + + App* app = FhgfsOps_getApp(inode->i_sb); + Config* cfg = App_getConfig(app); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FhgfsOpsErr remotingRes; + ssize_t resSize; + + int refreshRes = maybeRefreshInode(inode, true, false, false); + if (refreshRes) + return refreshRes; + + // "Just get the size, don't return any value" is signaled by value=NULL. Since we only send + // the "size" parameter to the server, we make sure that size is set to 0 in that case. + if(!value) + size = 0; + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_getXAttr(app, FhgfsInode_getEntryInfo(fhgfsInode), name, value, + size, &resSize); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + resSize = FhgfsOpsErr_toSysErr(remotingRes); + else + if (Config_getSysXAttrsCheckCapabilities(cfg) == CHECKCAPABILITIES_Cache + && resSize == 0 && !strcmp(name, XATTR_NAME_CAPS)) { + // We were looking for the xattr "security.capability" (XATTR_NAME_CAPS) and got an empty + // result, meaning that it doesn't exist. We cache that result via the weirdly named function + // "inode_has_no_xattr" which does exactly one thing and that is to set the flag "S_NOSEC" + // on the inode, if the inode doesn't have the setuid and setgid bits set and the superblock + // flag "SB_NOSEC" is set. We set that flag on the superblock according to user configuration. + // When "S_NOSEC" is set on the inode, the kernel will skip checking for capabilities on + // every write operation. + inode_has_no_xattr(inode); + } + + return resSize; +} + +/** + * Remove an extended attribute from a file. + */ +int FhgfsOps_removexattr(struct dentry* dentry, const char* name) +{ + App* app = FhgfsOps_getApp(dentry->d_sb); + + FhgfsOpsHelper_logOpDebug(app, dentry, NULL, __func__, "(name: %s)", name); + (void) app; + + return FhgfsOps_removexattrInode(dentry->d_inode, name); +} + +int FhgfsOps_removexattrInode(struct inode* inode, const char* name) +{ + App* app = FhgfsOps_getApp(inode->i_sb); + FhgfsOpsErr remotingRes; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + int refreshRes = maybeRefreshInode(inode, true, false, false); + if (refreshRes) + return refreshRes; + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_removeXAttr(app, FhgfsInode_getEntryInfo(fhgfsInode), name); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_toSysErr(remotingRes); + + return 0; +} + +/** + * Set an extended attribute for a file. + */ +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER +int FhgfsOps_setxattr(struct dentry* dentry, const char* name, const void* value, size_t size, + int flags) +{ + struct inode* inode = dentry->d_inode; +#else +int FhgfsOps_setxattr(struct inode* inode, const char* name, const void* value, size_t size, + int flags) +{ +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + + App* app = FhgfsOps_getApp(inode->i_sb); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + FhgfsOpsErr remotingRes; + + int refreshRes = maybeRefreshInode(inode, true, false, false); + if (refreshRes) + return refreshRes; + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_setXAttr(app, FhgfsInode_getEntryInfo(fhgfsInode), + name, value, size, flags); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_toSysErr(remotingRes); + + return 0; +} + +static struct posix_acl* Fhgfs_get_acl(struct inode* inode, int type) +{ + App* app = FhgfsOps_getApp(inode->i_sb); + + struct posix_acl* res = NULL; + char* xAttrName; + FhgfsOpsErr remotingRes; + size_t remotingResSize; + size_t xAttrSize; + char* xAttrBuf = NULL; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + const EntryInfo* entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + int refreshRes; + + forget_all_cached_acls(inode); + + refreshRes = maybeRefreshInode(inode, true, false, false); + if (refreshRes) + return ERR_PTR(refreshRes); + + if(type == ACL_TYPE_ACCESS) + xAttrName = XATTR_NAME_POSIX_ACL_ACCESS; + else if(type == ACL_TYPE_DEFAULT) + xAttrName = XATTR_NAME_POSIX_ACL_DEFAULT; + else + return ERR_PTR(-EOPNOTSUPP); + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + // read extended attributes from the file + remotingRes = FhgfsOpsRemoting_getXAttr(app, entryInfo, xAttrName, NULL, 0, &remotingResSize); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + { + int sysErr = FhgfsOpsErr_toSysErr(remotingRes); + + if (sysErr == -ENODATA || sysErr == -ENOSYS) + res = NULL; + else + res = ERR_PTR(sysErr); + + goto cleanup; + } + + xAttrBuf = os_kmalloc(remotingResSize); + if(!xAttrBuf) + { + res = ERR_PTR(-ENOMEM); + goto cleanup; + } + + xAttrSize = remotingResSize; + + remotingRes = FhgfsOpsRemoting_getXAttr(app, entryInfo, xAttrName, xAttrBuf, xAttrSize, + &remotingResSize); + + if(remotingRes != FhgfsOpsErr_SUCCESS) + { + res = ERR_PTR(FhgfsOpsErr_toSysErr(remotingRes) ); + goto cleanup; + } + + // determine posix_acl from extended attributes + res = os_posix_acl_from_xattr(xAttrBuf, remotingResSize); + +cleanup: + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + kfree(xAttrBuf); + return res; +} + +/* Kernel API changes for get_acl() and get_inode_acl() + * 6.3 + * struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); + * struct posix_acl *(*get_acl)(struct mnt_idmap *, struct dentry *, int); + * + * 6.2 + * get_acl() was introduced as get_inode_acl() but both interfaces still provided + * struct posix_acl * (*get_inode_acl)(struct inode *, int, bool); + * struct posix_acl *(*get_acl)(struct user_namespace *, struct dentry *, int); + * + * 5.15 + * struct posix_acl * (*get_acl)(struct inode *, int type, bool rcu); + * + * 3.2 + * struct posix_acl * (*get_acl)(struct inode *, int); + */ +#if defined(KERNEL_HAS_POSIX_GET_ACL_IDMAP) +struct posix_acl * FhgfsOps_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type) +{ + struct inode* inode = dentry->d_inode; + bool rcu = 0; +#elif defined(KERNEL_HAS_POSIX_GET_ACL_NS) +struct posix_acl * FhgfsOps_get_acl(struct user_namespace *userns, struct dentry *dentry, int type) +{ + struct inode* inode = dentry->d_inode; + bool rcu = 0; +#elif defined(KERNEL_POSIX_GET_ACL_HAS_RCU) +struct posix_acl* FhgfsOps_get_acl(struct inode* inode, int type, bool rcu) +{ +#else +struct posix_acl* FhgfsOps_get_acl(struct inode* inode, int type) +{ +#endif +#if defined(KERNEL_POSIX_GET_ACL_HAS_RCU) || defined(KERNEL_HAS_GET_INODE_ACL) + if (rcu) + return ERR_PTR(-ECHILD); +#endif // KERNEL_POSIX_GET_ACL_HAS_RCU + + return Fhgfs_get_acl(inode, type); +} + + +#if defined(KERNEL_HAS_GET_INODE_ACL) +struct posix_acl* FhgfsOps_get_inode_acl(struct inode* inode, int type, bool rcu) +{ + return Fhgfs_get_acl(inode, type); +} +#endif + +#if defined(KERNEL_HAS_SET_ACL) + +#if defined(KERNEL_HAS_SET_ACL_DENTRY) && defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_set_acl(struct mnt_idmap* mnt_userns, struct dentry* dentry, + struct posix_acl* acl, int type) +{ + struct inode* inode = d_inode(dentry); +#elif defined(KERNEL_HAS_SET_ACL_DENTRY) && defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_set_acl(struct user_namespace* mnt_userns, struct dentry* dentry, + struct posix_acl* acl, int type) +{ + struct inode* inode = d_inode(dentry); +#elif defined(KERNEL_HAS_SET_ACL_NS_INODE) +extern int FhgfsOps_set_acl(struct user_namespace* mnt_userns, struct inode* inode, + struct posix_acl* acl, int type) +{ +#else +extern int FhgfsOps_set_acl(struct inode* inode, struct posix_acl* acl, int type) +{ +#endif // KERNEL_HAS_SET_ACL_DENTRY +#endif // KERNEL_HAS_SET_ACL + App* app = FhgfsOps_getApp(inode->i_sb); + int res; + FhgfsOpsErr remotingRes; + char* xAttrName; + int xAttrBufLen; + void* xAttrBuf = NULL; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + const EntryInfo* entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + int refreshRes = maybeRefreshInode(inode, true, false, false); + if (refreshRes) + return refreshRes; + + if (type == ACL_TYPE_ACCESS) + { +#ifdef KERNEL_HAS_SET_ACL_DENTRY + if (acl) + { + int ret = 0; + struct iattr iattr; + int setAttrRes; + + memset(&iattr, 0, sizeof iattr); + + /* Update the file mode when setting an ACL: compute the new file permission + * bits based on the ACL. + */ + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + ret = posix_acl_update_mode(&nop_mnt_idmap, inode, &iattr.ia_mode, &acl); + #else + ret = posix_acl_update_mode(&init_user_ns, inode, &iattr.ia_mode, &acl); + #endif + if (ret) + return ret; + + if (inode->i_mode != iattr.ia_mode) + { + iattr.ia_valid = ATTR_MODE; //update the file mode permission bit + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + setAttrRes = FhgfsOps_setattr(&nop_mnt_idmap, dentry, &iattr); + #else + setAttrRes = FhgfsOps_setattr(&init_user_ns, dentry, &iattr); + #endif + if(setAttrRes < 0) + return setAttrRes; + } + } +#endif //KERNEL_HAS_SET_ACL_DENTRY + xAttrName = XATTR_NAME_POSIX_ACL_ACCESS; + } + else if (type == ACL_TYPE_DEFAULT) + xAttrName = XATTR_NAME_POSIX_ACL_DEFAULT; + else + return -EOPNOTSUPP; + + if (acl) + { + // prepare extended attribute - determine size needed for buffer. + xAttrBufLen = os_posix_acl_to_xattr(acl, NULL, 0); + + if (xAttrBufLen < 0) + return xAttrBufLen; + + xAttrBuf = os_kmalloc(xAttrBufLen); + if (!xAttrBuf) + return -ENOMEM; + + res = os_posix_acl_to_xattr(acl, xAttrBuf, xAttrBufLen); + if (res != xAttrBufLen) + goto cleanup; + + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_setXAttr(app, entryInfo, xAttrName, xAttrBuf, xAttrBufLen, 0); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + } + else + { + FhgfsInode_entryInfoReadLock(fhgfsInode); + + remotingRes = FhgfsOpsRemoting_removeXAttr(app, entryInfo, xAttrName); + if (remotingRes == FhgfsOpsErr_NODATA) + remotingRes = FhgfsOpsErr_SUCCESS; + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); + } + + if (remotingRes != FhgfsOpsErr_SUCCESS) + res = FhgfsOpsErr_toSysErr(remotingRes); + else + res = 0; + +cleanup: + kfree(xAttrBuf); + + return res; +} + +#ifdef KERNEL_HAS_GET_ACL +/** + * Update the ACL of an inode after a chmod + */ +int FhgfsOps_aclChmod(struct iattr* iattr, struct dentry* dentry) +{ +#if defined(KERNEL_HAS_SET_ACL) || defined(KERNEL_HAS_SET_ACL_DENTRY) + if (iattr->ia_valid & ATTR_MODE) + return os_posix_acl_chmod(dentry, iattr->ia_mode); + else + return 0; +#else + struct posix_acl* acl; + int res; + + void* xAttrBuf; + int xAttrBufLen; + + if( !(iattr->ia_valid & ATTR_MODE) ) // don't have to do anything if the mode hasn't changed + return 0; + +#if defined(KERNEL_HAS_POSIX_GET_ACL_IDMAP) + acl = FhgfsOps_get_acl(&nop_mnt_idmap, dentry, ACL_TYPE_ACCESS); +#elif defined(KERNEL_HAS_POSIX_GET_ACL_NS) + acl = FhgfsOps_get_acl(&init_user_ns, dentry, ACL_TYPE_ACCESS); +#else + acl = FhgfsOps_get_acl(dentry->d_inode, ACL_TYPE_ACCESS); +#endif + + if(PTR_ERR(acl) == -ENODATA) // entry doesn't have an ACL. We don't have to do anything. + return 0; + else if(IS_ERR(acl) ) // some error occured - pass the error code on to the caller. + return PTR_ERR(acl); + + // We have an actual ACL for that entry, so we need to update it. + res = posix_acl_chmod(&acl, GFP_KERNEL, iattr->ia_mode); + + if(res != 0) + goto cleanup; + + // Set the ACL Xattr. + xAttrBufLen = os_posix_acl_to_xattr(acl, NULL, 0); // Determine size needed for buffer. + + if(xAttrBufLen < 0) + { + res = xAttrBufLen; + goto cleanup; + } + + xAttrBuf = os_kmalloc(xAttrBufLen); + if(!xAttrBuf) + { + res = -ENOMEM; + goto cleanup; + } + + res = os_posix_acl_to_xattr(acl, xAttrBuf, xAttrBufLen); + if(res != xAttrBufLen) + goto buf_cleanup; // if it's not the same as xAttrBufLen, it is -ERANGE - so no need to modify res + + // We call FhgfsOps_setxattr directly instead of using the XAttr handler + // because the handler would try to chmod again. + res = FhgfsOps_setxattr(dentry, XATTR_NAME_POSIX_ACL_ACCESS, xAttrBuf, xAttrBufLen, 0); + +buf_cleanup: + kfree(xAttrBuf); + +cleanup: + posix_acl_release(acl); + + return res; +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(3,14,0) +} +#endif // KERNEL_HAS_GET_ACL + +/** + * @return 0 on success, negative linux error code otherwise + */ +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_setattr(struct mnt_idmap* idmap, struct dentry* dentry, + struct iattr* iattr) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_setattr(struct user_namespace* mnt_userns, struct dentry* dentry, + struct iattr* iattr) +#else +int FhgfsOps_setattr(struct dentry* dentry, struct iattr* iattr) +#endif +{ + // note: called for chmod(), chown(), utime(), truncate() + // note: changed fields can be determined by the iattr->ia_valid flags (ATTR_...) + + App* app = FhgfsOps_getApp(dentry->d_sb); + Config* cfg = App_getConfig(app); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_setattr"; + + int retVal = 0; + int setAttrPrepRes; + SettableFileAttribs fhgfsAttr; + int validFhgfsAttribs; + FhgfsOpsErr setAttrRes = FhgfsOpsErr_SUCCESS; + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + struct FileEvent event = FILEEVENT_EMPTY; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + { + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, dentry, inode, logContext, + (iattr->ia_valid & ATTR_SIZE) ? "(with trunc)" : ""); + } + +#ifdef KERNEL_HAS_SETATTR_PREPARE + setAttrPrepRes = os_setattr_prepare(dentry, iattr); +#else + setAttrPrepRes = inode_change_ok(inode, iattr); +#endif + if(setAttrPrepRes < 0) + return setAttrPrepRes; + + /* we do trunc during open message on meta server, so we don't want this redundant trunc + (and ctime/mtime update) from the kernel */ + if(iattr->ia_valid & ATTR_OPEN) + { + if (iattr->ia_valid & ATTR_SIZE) + { // the file was already remotely truncate by open, now also truncate it locally + FhgfsOps_vmtruncate(inode, iattr->ia_size); + } + + return 0; + } + + if(iattr->ia_valid & ATTR_SIZE) + { // make sure we only update size of regular files + if(!S_ISREG(inode->i_mode) ) + iattr->ia_valid &= ~ATTR_SIZE; + } + + if(S_ISREG(inode->i_mode) ) + { // flush all dirty file data + filemap_write_and_wait(inode->i_mapping); + + if(Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Buffered) + FhgfsOpsHelper_flushCache(app, fhgfsInode, false); + } + + OsTypeConv_iattrOsToFhgfs(iattr, &fhgfsAttr, &validFhgfsAttribs); + + if(validFhgfsAttribs) + { + const struct FileEvent* eventSent = NULL; + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + if (app->cfg->eventLogMask & EventLogMask_SETATTR) + { + FileEvent_init(&event, FileEventType_SETATTR, dentry); + eventSent = &event; + } + + setAttrRes = FhgfsOpsRemoting_setAttr(app, FhgfsInode_getEntryInfo(fhgfsInode), &fhgfsAttr, + validFhgfsAttribs, eventSent); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + } + + if(setAttrRes == FhgfsOpsErr_SUCCESS) + { + const struct FileEvent* eventSent = NULL; + + if (validFhgfsAttribs) + { + FhgfsOps_newAttrToInode(iattr, inode); + +#ifdef KERNEL_HAS_GET_ACL + if (Config_getSysACLsEnabled(cfg) ) + { + int aclRes = FhgfsOps_aclChmod(iattr, dentry); + if(aclRes < 0) + { + Logger_logFormatted(log, Log_ERR, logContext, "ACL chmod failed for '%s' (mode: 0%o), error: %d", + dentry->d_name.name, iattr->ia_mode, aclRes); + return aclRes; + } + } +#endif // KERNEL_HAS_GET_ACL + } + + // all right so far => handle truncation + if(iattr->ia_valid & ATTR_SIZE) + { + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + if (app->cfg->eventLogMask & EventLogMask_TRUNC) + { + /* if path is not set, free the resources possibly acquired by the setattr operation + * we did earlier and try again. otherwise, we have a valid path to send to the meta + * server, only the event type needs to be changed. */ + if (!event.path) + { + FileEvent_uninit(&event); + FileEvent_init(&event, FileEventType_TRUNCATE, dentry); + } + else + { + event.eventType = FileEventType_TRUNCATE; + } + + eventSent = &event; + } + + setAttrRes = FhgfsOpsRemoting_truncfile(app, FhgfsInode_getEntryInfo(fhgfsInode), + iattr->ia_size, eventSent); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(setAttrRes == FhgfsOpsErr_SUCCESS) + { + FhgfsOps_vmtruncate(inode, iattr->ia_size); + } + } + } + + FileEvent_uninit(&event); + + if(setAttrRes != FhgfsOpsErr_SUCCESS) + retVal = FhgfsOpsErr_toSysErr(setAttrRes); + else + FhgfsInode_invalidateCache(fhgfsInode); + + return retVal; +} + +/** + * Set inode attributes after a successful setattr + */ +void FhgfsOps_newAttrToInode(struct iattr* iAttr, struct inode* outInode) +{ + Time now; + #if defined(KERNEL_HAS_CURRENT_TIME_SPEC64) + struct timespec64 ts; + #else + struct timespec ts; + #endif + Time_setToNowReal(&now); + + spin_lock(&outInode->i_lock); + + ts.tv_sec = now.tv_sec; + ts.tv_nsec = 0; + + if(iAttr->ia_valid & ATTR_MODE) + outInode->i_mode = iAttr->ia_mode; + + if(iAttr->ia_valid & ATTR_UID) + outInode->i_uid = iAttr->ia_uid; + + if(iAttr->ia_valid & ATTR_GID) + outInode->i_gid = iAttr->ia_gid; + + if(iAttr->ia_valid & ATTR_MTIME_SET) + { + inode_set_mtime_to_ts(outInode, iAttr->ia_mtime); + } + else + if(iAttr->ia_valid & ATTR_MTIME) + { // set mtime to "now" + inode_set_mtime_to_ts(outInode, ts); + } + + if(iAttr->ia_valid & ATTR_ATIME_SET) + { + inode_set_atime_to_ts(outInode, iAttr->ia_atime); + } + else + if(iAttr->ia_valid & ATTR_ATIME) + { // set atime to "now" + inode_set_atime_to_ts(outInode, ts); + } + + if(iAttr->ia_valid & ATTR_CTIME) + { + inode_set_ctime_to_ts(outInode, ts); + } + + spin_unlock(&outInode->i_lock); +} + + + +/** + * Create directory. + */ +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +int FhgfsOps_mkdir(struct mnt_idmap* idmap, struct inode* dir, struct dentry* dentry, + umode_t mode) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +int FhgfsOps_mkdir(struct user_namespace* mnt_userns, struct inode* dir, struct dentry* dentry, + umode_t mode) +#elif defined(KERNEL_HAS_UMODE_T) +int FhgfsOps_mkdir(struct inode* dir, struct dentry* dentry, umode_t mode) +#else +int FhgfsOps_mkdir(struct inode* dir, struct dentry* dentry, int mode) +#endif +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_mkdir"; + + int retVal = 0; + FhgfsOpsErr mkRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + const char* entryName = dentry->d_name.name; + const int umask = current_umask(); + struct CreateInfo createInfo; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + struct inode* inode = dentry->d_inode; + + FhgfsIsizeHints iSizeHints; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + mode |= S_IFDIR; // just make sure this is a dir + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_MKDIR, dentry); + eventSent = &event; + } + + CreateInfo_init(app, dir, entryName, mode, umask, false, eventSent, &createInfo); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + mkRes = FhgfsOpsRemoting_mkdir(app, FhgfsInode_getEntryInfo(fhgfsParentInode), &createInfo, + &newEntryInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(mkRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(mkRes); + } + else + { // remote success => create the local inode + retVal = __FhgfsOps_instantiateInode(dentry, &newEntryInfo, NULL, &iSizeHints); + + inode_set_mc_time(dir, current_fs_time(sb)); + } + + FileEvent_uninit(&event); + + return retVal; +} + +int FhgfsOps_rmdir(struct inode* dir, struct dentry* dentry) +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_rmdir"; + + int retVal = 0; + FhgfsOpsErr rmRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + const char* entryName = dentry->d_name.name; + struct inode* inode = dentry->d_inode; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_RMDIR, dentry); + eventSent = &event; + } + + rmRes = FhgfsOpsRemoting_rmdir(app, FhgfsInode_getEntryInfo(fhgfsParentInode), entryName, + eventSent); + + FileEvent_uninit(&event); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(rmRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(rmRes); + } + else + { // remote success + clear_nlink(dentry->d_inode); + + inode_set_mc_time(dir, current_fs_time(sb)); + } + + return retVal; +} + +/** + * Create file based on combined intent message. + * + * @param isExclusiveCreate true if this is an exclusive (O_EXCL) create + */ +#if defined KERNEL_HAS_ATOMIC_OPEN +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +int FhgfsOps_createIntent(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, umode_t createMode, bool isExclusiveCreate) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +int FhgfsOps_createIntent(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, umode_t createMode, bool isExclusiveCreate) +#else +int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, umode_t createMode, + bool isExclusiveCreate) +#endif +#elif defined KERNEL_HAS_UMODE_T +int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, umode_t createMode, + struct nameidata* nameidata) +#else +int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, int createMode, + struct nameidata* nameidata) +#endif // KERNEL_HAS_ATOMIC_OPEN +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_createIntent"; + + int retVal = 0; + FhgfsOpsErr remotingRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + const char* entryName = dentry->d_name.name; + const int currentUmask = current_umask(); + fhgfs_stat statData; + CreateInfo createInfo; + OpenInfo openInfo; + LookupIntentInfoIn inInfo; // input data for combo-request + LookupIntentInfoOut lookupOutInfo; // result data of combo-request + struct inode* inode = dentry->d_inode; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + FhgfsIsizeHints iSizeHints; + + #ifndef KERNEL_HAS_ATOMIC_OPEN + bool isExclusiveCreate = (nameidata && (nameidata->intent.open.flags & O_EXCL) ); + int openFlags = (nameidata && nameidata->flags & LOOKUP_OPEN) ? + nameidata->intent.open.flags : 0; + #else + int openFlags = 0; + #endif // LINUX_VERSION_CODE + + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + if(unlikely(dentry->d_name.len > NAME_MAX) ) + return -ENAMETOOLONG; + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + if(unlikely(!S_ISREG(createMode) ) ) + return -EINVAL; // just make sure we create a regular file here + + FhgfsInode_initIsizeHints(NULL, &iSizeHints); + + LookupIntentInfoIn_init(&inInfo, FhgfsInode_getEntryInfo(fhgfsParentInode), entryName); + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_CREATE, dentry); + eventSent = &event; + } + + CreateInfo_init(app, dir, entryName, createMode, currentUmask, isExclusiveCreate, eventSent, + &createInfo); + LookupIntentInfoIn_addCreateInfo(&inInfo, &createInfo); + + if (openFlags) + { + OpenInfo_init(&openInfo, openFlags, __FhgfsOps_isPagedMode(sb) ); + LookupIntentInfoIn_addOpenInfo(&inInfo, &openInfo); + } + + LookupIntentInfoOut_prepare(&lookupOutInfo, &newEntryInfo, &statData); + + remotingRes = FhgfsOpsRemoting_lookupIntent(app, &inInfo, &lookupOutInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if (unlikely(remotingRes != FhgfsOpsErr_SUCCESS) ) + { + d_drop(dentry); // avoid leaving a negative dentry behind + retVal = FhgfsOpsErr_toSysErr(remotingRes); + goto outErr; + } + + if (unlikely(lookupOutInfo.createRes != FhgfsOpsErr_SUCCESS) ) + { + d_drop(dentry); // avoid leaving a negative dentry behind + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.createRes); + goto outErr; + } + + if (unlikely(lookupOutInfo.statRes != FhgfsOpsErr_SUCCESS) ) + { // something went wrong before the server could stat the file + + if (!(lookupOutInfo.responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) ) + Logger_logErrFormatted(log, logContext, "Unexpected stat info missing: %s", + createInfo.entryName); + + + d_drop(dentry); // avoid leaving a negative dentry behind + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.statRes); + goto outErr; + } + + if(unlikely( + lookupOutInfo.stripePattern && + (StripePattern_getPatternType(lookupOutInfo.stripePattern) == STRIPEPATTERN_Invalid) ) ) + { // unknown/invalid stripe pattern + Logger_logErrFormatted(log, logContext, "Entry has invalid/unknown stripe pattern type: %s", + createInfo.entryName); + + d_drop(dentry); // avoid leaving a negative dentry behind + retVal = FhgfsOpsErr_toSysErr(FhgfsOpsErr_INTERNAL); + goto outErr; + } + + // remote success => create local inode + retVal = __FhgfsOps_instantiateInode(dentry, lookupOutInfo.entryInfoPtr, + lookupOutInfo.fhgfsStat, &iSizeHints); + LookupIntentInfoOut_setEntryInfoPtr(&lookupOutInfo, NULL); /* Make sure entryInfo will not be + * de-initialized */ + inode_set_mc_time(dir, current_fs_time(sb)); + +#ifndef KERNEL_HAS_ATOMIC_OPEN + if (lookupOutInfo.openRes == FhgfsOpsErr_SUCCESS) + { + struct file* file; + struct inode* newInode; + int openHandleRes; + + file = lookup_instantiate_filp(nameidata, dentry, generic_file_open); + if (IS_ERR(file) ) + { + retVal = PTR_ERR(file); + goto outErr; + } + + newInode = dentry->d_inode; + + openHandleRes = FhgfsOps_openReferenceHandle(app, newInode, file, openFlags, + &lookupOutInfo, NULL); + if (unlikely(openHandleRes) ) + { // failed to get an fhgfs internal handle + // printk_fhgfs_debug(KERN_INFO, "Reference handle failed\n"); + + fput(file); + + retVal = openHandleRes; + goto outErr; + } + + LookupIntentInfoOut_setStripePattern(&lookupOutInfo, NULL); + + } +#endif // KERNEL_HAS_ATOMIC_OPEN + + // clean-up + + LookupIntentInfoOut_uninit(&lookupOutInfo); + FileEvent_uninit(&event); + + retVal = 0; // success + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "complete. result: %d", retVal); + + return retVal; + + + +outErr: + + // clean-up on error + + if (unlikely(lookupOutInfo.openRes == FhgfsOpsErr_SUCCESS) ) + { + RemotingIOInfo closeInfo; + FhgfsOpsErr closeRes; + const EntryInfo* parentEntryInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + AtomicInt maxUsedTargetIndex; + + AtomicInt_init(&maxUsedTargetIndex, 0); // file was not open to user space yet, so 0 + + RemotingIOInfo_initSpecialClose(app, lookupOutInfo.fileHandleID, + &maxUsedTargetIndex, openFlags, &closeInfo); + + closeRes = FhgfsOpsHelper_closefileWithAsyncRetry(lookupOutInfo.entryInfoPtr, &closeInfo, + NULL); + if (closeRes != FhgfsOpsErr_SUCCESS) + Logger_logErrFormatted(log, logContext, "Close on error: Failed to close file " + "parentID: %s fileName: %s", EntryInfo_getEntryID(parentEntryInfo), entryName); + } + + LookupIntentInfoOut_uninit(&lookupOutInfo); + FileEvent_uninit(&event); + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "complete. result: %d", retVal); + + return retVal; +} + + +#ifdef KERNEL_HAS_ATOMIC_OPEN +/** + * Lookup/create/open file + * + * Note: O_CREAT is not required to be set, so we must also handle a lookup-open. + * Note2: Inode permissions are handled in vfs' atomic_open(), once we have successfully opened + * the file. If userspace is not allowed to open the file, vfs will immediately send a close + * request. + * Note3: As this is a general lookup, it can also be called for directories, symlinks etc. + * + * Warning: This is currently only enabled when define BEEGFS_ENABLE_ATOMIC_OPEN is set, because + * a) dentries come out as d_unhashed() here, which should not happen. + * b) users reported hangs with 3.10 lt elrepo kernel when doing fcntl locking stresstests with + * global locks enabled. + */ +int FhgfsOps_atomicOpen(struct inode* dir, struct dentry* dentry, struct file* file, + unsigned openFlags, umode_t createMode + #ifndef FMODE_CREATED + , int* outOpenedFlags + #endif + ) +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_atomicOpen"; + + int retVal = -EINVAL; // any error is fine by default + FhgfsOpsErr remotingRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + struct inode* newInode; + const char* entryName = dentry->d_name.name; + const int createUmask = current_umask(); + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode; + + + CreateInfo createInfo; + OpenInfo openInfo; + + LookupIntentInfoIn inInfo; // input data for combo-request + LookupIntentInfoOut lookupOutInfo; // result data of combo-request + + fhgfs_stat statData; // passed to lookupOutInfo + fhgfs_stat* statDataPtr; + + int openHandleRes; + int instantiateRes; + + FhgfsIsizeHints iSizeHints; + + bool isCreate = !!(openFlags & O_CREAT); + + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + if(unlikely(dentry->d_name.len > NAME_MAX) ) + return -ENAMETOOLONG; + + if (inode) + fhgfsInode = BEEGFS_INODE(inode); + else + fhgfsInode = NULL; + + FhgfsInode_initIsizeHints(fhgfsInode, &iSizeHints); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + LookupIntentInfoIn_init(&inInfo, FhgfsInode_getEntryInfo(fhgfsParentInode), entryName); + + if (isCreate) + { + bool isExclusiveCreate = !!(openFlags & O_EXCL); + + if (!(createMode & S_IFREG) ) + { + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + return -EINVAL; // we can still easily return here, no need to free something yet + } + + // XXX event + CreateInfo_init(app, dir, entryName, createMode, createUmask, isExclusiveCreate, NULL, + &createInfo); + LookupIntentInfoIn_addCreateInfo(&inInfo, &createInfo); + } + + OpenInfo_init(&openInfo, openFlags, __FhgfsOps_isPagedMode(sb) ); + LookupIntentInfoIn_addOpenInfo(&inInfo, &openInfo); + + LookupIntentInfoOut_prepare(&lookupOutInfo, &newEntryInfo, &statData); + + remotingRes = FhgfsOpsRemoting_lookupIntent(app, &inInfo, &lookupOutInfo); // remote call + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if (unlikely(remotingRes != FhgfsOpsErr_SUCCESS) ) + { + retVal = FhgfsOpsErr_toSysErr(remotingRes); + goto outErr; + } + + if (!isCreate && lookupOutInfo.lookupRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.lookupRes); + goto outErr; + } + else + if (lookupOutInfo.statRes != FhgfsOpsErr_SUCCESS) + { // something went wrong before the server could stat the file + + // d_drop(dentry); // avoid leaving a negative dentry behind. Don't for atomic_open! + + if (lookupOutInfo.statRes == FhgfsOpsErr_NOTOWNER) + { // directory or inter-dir hardlink + statDataPtr = NULL; // force a stat call to the real owner + } + else + { + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.statRes); + goto outErr; + } + } + else + { // stat success + statDataPtr = lookupOutInfo.fhgfsStat; + } + + if (isCreate) + { + if (lookupOutInfo.createRes == FhgfsOpsErr_SUCCESS) // implies isCreate == true + { +#ifdef FMODE_CREATED + file->f_mode |= FMODE_CREATED; +#else + *outOpenedFlags |= FILE_CREATED; +#endif + + if (lookupOutInfo.lookupRes != FhgfsOpsErr_SUCCESS) + { // only update directory time stamps if the file did not exist yet + inode_set_mc_time(dir, current_fs_time(sb)); + } + } + else + { + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.createRes); + goto outErr; + } + } + + + // remote success => create local inode + instantiateRes = __FhgfsOps_instantiateInode(dentry, lookupOutInfo.entryInfoPtr, statDataPtr, + &iSizeHints); + LookupIntentInfoOut_setEntryInfoPtr(&lookupOutInfo, NULL); /* Make sure entryInfo will not be + * de-initialized */ + + if (unlikely(instantiateRes) ) + { // instantiate error + retVal = instantiateRes; + goto outErr; + } + + // instantiate success + + if (lookupOutInfo.openRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(lookupOutInfo.openRes); + goto outLookupSuccessOpenFailure; + } + + // remote open success + + file->f_path.dentry = dentry; /* Assign the dentry, finish open does that, but we + * already need it in openReferenceHandle() */ + + newInode = dentry->d_inode; + + openHandleRes = FhgfsOps_openReferenceHandle(app, newInode, file, openFlags, + &lookupOutInfo, NULL); + if (unlikely(openHandleRes) ) + { // failed to get an fhgfs internal handle + retVal = openHandleRes; + goto outLookupSuccessOpenFailure; + } + + // internal open handle success + + // stripePattern is assigned to FhgfsInode now, make sure it does not get free'ed + LookupIntentInfoOut_setStripePattern(&lookupOutInfo, NULL); + + retVal = finish_open(file, dentry, generic_file_open +#ifndef FMODE_CREATED + , outOpenedFlags +#endif + ); + if (unlikely(retVal) ) + { // finish open failed + int releaseRes; + + releaseRes = FhgfsOps_release(dentry->d_inode, file); + if (unlikely(releaseRes) ) + { + const EntryInfo* parentEntryInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + + Logger_logErrFormatted(log, logContext, "Close on error: Failed to close file " + "parentID: %s fileName: %s", EntryInfo_getEntryID(parentEntryInfo), entryName); + } + } + + // clean-up + + LookupIntentInfoOut_uninit(&lookupOutInfo); + + retVal = 0; // success + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "complete. result: %d", retVal); + + return retVal; + + +outLookupSuccessOpenFailure: + + dget(dentry); /* Get another dentry reference, the caller (atomic_open) will drop it + * it again immediately */ + retVal = finish_no_open(file, dentry); // successful lookup/create, but failed open + + +outErr: + + // clean-up on error + + if (unlikely(lookupOutInfo.openRes == FhgfsOpsErr_SUCCESS) ) + { + RemotingIOInfo closeInfo; + FhgfsOpsErr closeRes; + const EntryInfo* parentEntryInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + AtomicInt maxUsedTargetIndex; + + AtomicInt_init(&maxUsedTargetIndex, 0); // file was not open to user space yet, so 0 + + RemotingIOInfo_initSpecialClose(app, lookupOutInfo.fileHandleID, + &maxUsedTargetIndex, openFlags, &closeInfo); + + closeRes = FhgfsOpsRemoting_closefile(lookupOutInfo.entryInfoPtr, &closeInfo, NULL); + if (closeRes != FhgfsOpsErr_SUCCESS) + Logger_logErrFormatted(log, logContext, "Close on error: Failed to close file " + "parentID: %s fileName: %s", EntryInfo_getEntryID(parentEntryInfo), entryName); + } + + LookupIntentInfoOut_uninit(&lookupOutInfo); + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "complete. result: %d", retVal); + + return retVal; +} +#endif // KERNEL_HAS_ATOMIC_OPEN + + +int FhgfsOps_unlink(struct inode* dir, struct dentry* dentry) +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_unlink"; + + int retVal = 0; + FhgfsOpsErr unlinkRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + const char* entryName = dentry->d_name.name; + struct inode* inode = dentry->d_inode; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + + if(Logger_getLogLevel(log) >= 4) + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_UNLINK, dentry); + eventSent = &event; + } + + unlinkRes = FhgfsOpsRemoting_unlinkfile(app, FhgfsInode_getEntryInfo(fhgfsParentInode), + entryName, eventSent); + + FileEvent_uninit(&event); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(unlinkRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(unlinkRes); + + if(retVal == -ENOENT) + d_drop(dentry); + } + else + { // remote success + inode_set_mc_time(dir, current_fs_time(sb)); + + if(dentry->d_inode) + { + struct inode* fileInode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(fileInode); + + FhgfsInode_invalidateCache(fhgfsInode); + #if defined(KERNEL_HAS_INODE_CTIME) + fileInode->i_ctime = dir->i_ctime; + #else + inode_set_ctime_to_ts(fileInode, inode_get_ctime(dir)); + #endif + + spin_lock(&fileInode->i_lock); + // only try to drop link count if it still is > 0. Check is needed, + // because there are some situations in which this is called when the + // link count is already 0. NFS for example does the same. + if(fileInode->i_nlink > 0) + { + drop_nlink(fileInode); + } + spin_unlock(&fileInode->i_lock); + } + } + + return retVal; +} + +/** + * Create special file. + */ +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +int FhgfsOps_mknod(struct mnt_idmap* idmap, struct inode* dir, struct dentry* dentry, + umode_t mode, dev_t dev) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +int FhgfsOps_mknod(struct user_namespace* mnt_userns, struct inode* dir, struct dentry* dentry, + umode_t mode, dev_t dev) +#elif defined(KERNEL_HAS_UMODE_T) +int FhgfsOps_mknod(struct inode* dir, struct dentry* dentry, umode_t mode, dev_t dev) +#else +int FhgfsOps_mknod(struct inode* dir, struct dentry* dentry, int mode, dev_t dev) +#endif +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_mknod"; + + int retVal = 0; + FhgfsOpsErr mkRes; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + const char* entryName = dentry->d_name.name; + const int umask = current_umask(); + struct CreateInfo createInfo; + struct inode* inode = dentry->d_inode; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + FhgfsIsizeHints iSizeHints; + + if(Logger_getLogLevel(log) >= 4) + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_MKNOD, dentry); + eventSent = &event; + } + + CreateInfo_init(app, dir, entryName, mode, umask, false, eventSent, &createInfo); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + mkRes = FhgfsOpsRemoting_mkfile(app, FhgfsInode_getEntryInfo(fhgfsParentInode), + &createInfo, &newEntryInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(mkRes != FhgfsOpsErr_SUCCESS) + { + d_drop(dentry); // avoid leaving a negative dentry behind + retVal = FhgfsOpsErr_toSysErr(mkRes); + goto outErr; + } + + retVal = __FhgfsOps_instantiateInode(dentry, &newEntryInfo, NULL, &iSizeHints); + + inode_set_mc_time(dir, current_fs_time(sb)); + + FileEvent_uninit(&event); + +outErr: + // clean-up + + return retVal; +} + + +/** + * @param dir directory for the new link + * @param dentry dentry of the new entry that we want to create + * @param to where the symlink points to + */ +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_symlink(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, const char* to) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_symlink(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, const char* to) +#else +extern int FhgfsOps_symlink(struct inode* dir, struct dentry* dentry, const char* to) +#endif +{ + struct super_block* sb = dentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_symlink"; + + int retVal; + int mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + const char* entryName = dentry->d_name.name; + const int umask = current_umask(); + struct CreateInfo createInfo; + struct inode* inode = dentry->d_inode; + struct FileEvent event = FILEEVENT_EMPTY; + struct FileEvent* eventSent = NULL; + + FhgfsIsizeHints iSizeHints; + + if(unlikely(Logger_getLogLevel(log) >= 4) ) + FhgfsOpsHelper_logOp(4, app, dentry, inode, logContext); + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_SYMLINK, dentry); + FileEvent_setTargetStr(&event, to); + eventSent = &event; + } + + CreateInfo_init(app, dir, entryName, mode, umask, false, eventSent, &createInfo); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + // destroys &event from createInfo.fileEvent + retVal = FhgfsOpsHelper_symlink(app, FhgfsInode_getEntryInfo(fhgfsParentInode), to, &createInfo, + &newEntryInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(retVal) + { // error occurred + d_drop(dentry); // avoid leaving a negative dentry behind + } + else + { // remote success => create local inode + retVal = __FhgfsOps_instantiateInode(dentry, &newEntryInfo, NULL, &iSizeHints); + + inode_set_mc_time(dir, current_fs_time(sb)); + } + + return retVal; +} + +/** + * @param fromFileDentry the already existing dentry + * @param toDirInode parent (directory) inode of the new dentry + * @param toFileDentry the new dentry (link) that we want to create + */ +int FhgfsOps_link(struct dentry* fromFileDentry, struct inode* toDirInode, + struct dentry* toFileDentry) +{ + struct super_block* sb = toFileDentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + const char* logContext = "FhgfsOps_link"; + + struct inode* fileInode = fromFileDentry->d_inode; + FhgfsInode* fhgfsFileInode = BEEGFS_INODE(fileInode); + + FhgfsInode* fhgfsToDirInode = BEEGFS_INODE(toDirInode); + + struct inode* fromDirInode = fromFileDentry->d_parent->d_inode; + FhgfsInode* fhgfsFromDirInode = BEEGFS_INODE(fromDirInode); + + const char* fromFileName = fromFileDentry->d_name.name; + unsigned fromFileLen = fromFileDentry->d_name.len; + + const char* toFileName = toFileDentry->d_name.name; + unsigned toFileLen = toFileDentry->d_name.len; + + const EntryInfo* fromDirInfo; + const EntryInfo* toDirInfo; + const EntryInfo* fromFileInfo; + + int retVal; + + + if(unlikely(Logger_getLogLevel(log) >= Log_DEBUG) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, fromFileDentry, fromFileDentry->d_inode, logContext, + "From: %s; To: %s", fromFileName, toFileName); + + FhgfsInode_entryInfoReadLock(fhgfsFromDirInode); // LOCK EntryInfo + if (fhgfsFromDirInode != fhgfsToDirInode) + FhgfsInode_entryInfoReadLock(fhgfsToDirInode); // LOCK EntryInfo + FhgfsInode_entryInfoReadLock(fhgfsFileInode); // LOCK EntryInfo + + fromDirInfo = FhgfsInode_getEntryInfo(fhgfsFromDirInode); + toDirInfo = FhgfsInode_getEntryInfo(fhgfsToDirInode); + fromFileInfo = FhgfsInode_getEntryInfo(fhgfsFileInode); + + if (!Config_getSysCreateHardlinksAsSymlinks(cfg)) + { // create hardlink instead of symlink + int linkRes; + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + ihold(fileInode); + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_HARDLINK, toFileDentry); + FileEvent_setTargetDentry(&event, fromFileDentry); + eventSent = &event; + } + + linkRes = FhgfsOpsRemoting_hardlink(app, fromFileName, fromFileLen, fromFileInfo, + fromDirInfo, toFileName, toFileLen, toDirInfo, eventSent); + + FileEvent_uninit(&event); + + retVal = FhgfsOpsErr_toSysErr(linkRes); + + if (retVal) + { + d_drop(toFileDentry); + iput(fileInode); + } + else + { + spin_lock(&fileInode->i_lock); + inc_nlink(fileInode); + spin_unlock(&fileInode->i_lock); + + d_instantiate(toFileDentry, fileInode); + } + } + else + { + // create symlink instead of hardlink + retVal = FhgfsOps_hardlinkAsSymlink(fromFileDentry, toDirInode, toFileDentry); + } + + FhgfsInode_entryInfoReadUnlock(fhgfsFileInode); // UNLOCK fromFileInfo + if (fhgfsFromDirInode != fhgfsToDirInode) + FhgfsInode_entryInfoReadUnlock(fhgfsToDirInode); // UNLOCK toDirInfo + FhgfsInode_entryInfoReadUnlock(fhgfsFromDirInode); // UNLOCK fromDirInfo + + FhgfsInode_invalidateCache(fhgfsFileInode); + if (fhgfsFromDirInode != fhgfsToDirInode) + FhgfsInode_invalidateCache(fhgfsToDirInode); + FhgfsInode_invalidateCache(fhgfsFromDirInode); + + return retVal; +} + +/** + * create symlink instead of hardlink + * + * @param oldDentry the already existing link + * @param dir directory of the new link + * @param newDentry the new link that we want to create + * + * Note: + */ +int FhgfsOps_hardlinkAsSymlink(struct dentry* oldDentry, struct inode* dir, + struct dentry* newDentry) +{ + struct super_block* sb = newDentry->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_link"; + + FhgfsIsizeHints iSizeHints; + + NoAllocBufferStore* bufStore = App_getPathBufStore(app); + + int retVal; + int mode = S_IFLNK | S_IRWXU | S_IRWXG | S_IRWXO; + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(dir); + EntryInfo newEntryInfo; + const char* entryName = newDentry->d_name.name; + const int umask = current_umask(); + + char *oldPathStr, *newPathStr; + char *oldPathStoreBuf, *newPathStoreBuf; + char *oldPathTmp, *newPathTmp; + char* oldRelativePathStr; + + struct CreateInfo createInfo; + struct FileEvent event = FILEEVENT_EMPTY; + struct FileEvent* eventSent = NULL; + + // resolve oldDentry to full path + oldPathTmp = __FhgfsOps_pathResolveToStoreBuf(bufStore, oldDentry, &oldPathStoreBuf); + if (unlikely(IS_ERR(oldPathTmp) ) ) + { + int error = IS_ERR(oldPathTmp); + Logger_logFormatted(log, 2, logContext, "Error in link(): %d", error); + retVal = error; + goto errorOldPath; + } + + oldPathStr = StringTk_strDup(oldPathTmp); + NoAllocBufferStore_addBuf(bufStore, oldPathStoreBuf); + + // resolve newDentry to full path + newPathTmp = __FhgfsOps_pathResolveToStoreBuf(bufStore, newDentry, &newPathStoreBuf); + if (unlikely(IS_ERR(newPathTmp) ) ) + { + int error = IS_ERR(oldPathTmp); + Logger_logFormatted(log, 2, logContext, "Error in link(): %d", error); + retVal = error; + goto errorNewPath; + } + + newPathStr = StringTk_strDup(newPathTmp); + NoAllocBufferStore_addBuf(bufStore, newPathStoreBuf); + + // resolve relative path from new to old + FhgfsOpsHelper_getRelativeLinkStr(newPathStr, oldPathStr, &oldRelativePathStr); + + if(unlikely(Logger_getLogLevel(log) >= 4) ) + { + Logger_logFormatted(log, 4, logContext, "called (as symlink). Path: %s; To: %s", + newPathStr, oldRelativePathStr); + } + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_SYMLINK, newDentry); + FileEvent_setTargetDentry(&event, oldDentry); + eventSent = &event; + } + + CreateInfo_init(app, dir, entryName, mode, umask, false, eventSent, &createInfo); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + // destroys &event from createInfo.fileEvent + retVal = FhgfsOpsHelper_symlink(app, FhgfsInode_getEntryInfo(fhgfsParentInode), + oldRelativePathStr, &createInfo, &newEntryInfo); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(retVal) + { // error occurred + d_drop(newDentry); // avoid leaving a negative dentry behind + } + else + { // remote success => create local inode + retVal = __FhgfsOps_instantiateInode(newDentry, &newEntryInfo, NULL, &iSizeHints); + + inode_set_mc_time(dir, current_fs_time(sb)); + } + + // clean-up + + kfree(oldRelativePathStr); + kfree(newPathStr); +errorNewPath: + kfree(oldPathStr); +errorOldPath: + return retVal; +} + + +static int __beegfs_follow_link(struct dentry* dentry, char** linkBody, void** cookie) +{ + struct inode* inode = dentry->d_inode; + App* app = FhgfsOps_getApp(inode->i_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_follow_link"; + + int readRes; + char* bufPage = (char*)__get_free_page(GFP_NOFS); + char* destination = bufPage; + + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(inode); + + if(unlikely(Logger_getLogLevel(log) >= 5) ) + FhgfsOpsHelper_logOp(5, app, dentry, inode, logContext); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // LOCK EntryInfo + + readRes = FhgfsOpsHelper_readlink_kernel(app, FhgfsInode_getEntryInfo(fhgfsParentInode), bufPage, PAGE_SIZE-1); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // UNLOCK EntryInfo + + if(readRes < 0) + { + destination = ERR_PTR(readRes); + } + else + bufPage[readRes] = 0; + + if(readRes == (PAGE_SIZE-1) ) + { // link destination too long + destination = ERR_PTR(-ENAMETOOLONG); + } + + // store link destination + *linkBody = destination; + *cookie = bufPage; + + if(IS_ERR(destination) ) + { + free_page( (unsigned long)bufPage); + *cookie = destination; + + return PTR_ERR(destination); + } + + // Note: free_page() is called by the put_link method in the success case + return 0; +} + +static void __beegfs_put_link(void* cookie) +{ + free_page( (unsigned long)cookie); +} + +#if defined KERNEL_HAS_GET_LINK +const char* FhgfsOps_get_link(struct dentry* dentry, struct inode* inode, + struct delayed_call* done) +{ + void* cookie; + char* destination; + + if (!dentry) + return ERR_PTR(-ECHILD); + + if(!__beegfs_follow_link(dentry, &destination, &cookie) ) + set_delayed_call(done, __beegfs_put_link, cookie); + + return destination; +} +#elif defined(KERNEL_HAS_FOLLOW_LINK_COOKIE) +const char* FhgfsOps_follow_link(struct dentry* dentry, void** cookie) +{ + char* destination; + + __beegfs_follow_link(dentry, &destination, cookie); + return destination; +} + + +void FhgfsOps_put_link(struct inode* inode, void* cookie) +{ + __beegfs_put_link(cookie); +} +#else +void* FhgfsOps_follow_link(struct dentry* dentry, struct nameidata* nd) +{ + char* destination; + void* cookie; + + __beegfs_follow_link(dentry, &destination, &cookie); + nd_set_link(nd, destination); + return cookie; +} + + +void FhgfsOps_put_link(struct dentry* dentry, struct nameidata* nd, void* p) +{ + if(!IS_ERR(p) ) + __beegfs_put_link(p); +} +#endif + + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +int FhgfsOps_rename(struct mnt_idmap* idmap, struct inode* inodeDirFrom, + struct dentry* dentryFrom, struct inode* inodeDirTo, struct dentry* dentryTo +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +int FhgfsOps_rename(struct user_namespace* mnt_userns, struct inode* inodeDirFrom, + struct dentry* dentryFrom, struct inode* inodeDirTo, struct dentry* dentryTo +#else +int FhgfsOps_rename(struct inode* inodeDirFrom, struct dentry* dentryFrom, + struct inode* inodeDirTo, struct dentry* dentryTo +#endif +#ifdef KERNEL_HAS_RENAME_FLAGS + , unsigned flags +#endif + ) +{ + struct super_block* sb = dentryFrom->d_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + FhgfsOpsErr renameRes; + + struct inode* fromEntryInode = dentryFrom->d_inode; // no need to lock it, see other in kernel fs + FhgfsInode* fhgfsFromEntryInode = BEEGFS_INODE(fromEntryInode); + DirEntryType entryType = FhgfsInode_getDirEntryType(fhgfsFromEntryInode); + + const EntryInfo* fromDirInfo; // EntryInfo about the 'from' directory + const EntryInfo* toDirInfo; // EntryInfo about the 'to' directory + + int retVal = 0; + + FhgfsInode* fhgfsFromDirInode = BEEGFS_INODE(inodeDirFrom); + FhgfsInode* fhgfsToDirInode = BEEGFS_INODE(inodeDirTo); + + const char* oldName = dentryFrom->d_name.name; + unsigned oldLen = dentryFrom->d_name.len; + + const char* newName = dentryTo->d_name.name; + unsigned newLen = dentryTo->d_name.len; + + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + +#ifdef KERNEL_HAS_RENAME_FLAGS + if (flags != 0) + return -EINVAL; +#endif + + if(unlikely(Logger_getLogLevel(log) >= Log_DEBUG) ) + FhgfsOpsHelper_logOpMsg(Log_SPAM, app, dentryFrom, dentryFrom->d_inode, logContext, + "From: %s; To: %s", oldName, newName); + + FhgfsInode_entryInfoReadLock(fhgfsFromDirInode); // LOCK EntryInfo + if (fhgfsFromDirInode != fhgfsToDirInode) + FhgfsInode_entryInfoReadLock(fhgfsToDirInode); // LOCK EntryInfo + + // note the fileInode also needs to be locked to prevent reference/release during a rename + FhgfsInode_entryInfoWriteLock(fhgfsFromEntryInode); // LOCK EntryInfo (renamed file dir) + + fromDirInfo = FhgfsInode_getEntryInfo(fhgfsFromDirInode); + toDirInfo = FhgfsInode_getEntryInfo(fhgfsToDirInode); + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + FileEvent_init(&event, FileEventType_RENAME, dentryFrom); + FileEvent_setTargetDentry(&event, dentryTo); + eventSent = &event; + } + + renameRes = FhgfsOpsRemoting_rename(app, oldName, oldLen, entryType, fromDirInfo, + newName, newLen, toDirInfo, eventSent); + if(renameRes != FhgfsOpsErr_SUCCESS) + { + int logLevel = Log_NOTICE; + const EntryInfo* fromEntryInfo = FhgfsInode_getEntryInfo(fhgfsFromEntryInode); + + if( (renameRes == FhgfsOpsErr_PATHNOTEXISTS) || (renameRes == FhgfsOpsErr_INUSE) ) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + if (renameRes == FhgfsOpsErr_EXISTS && + dentryTo->d_inode && S_ISDIR(dentryTo->d_inode->i_mode) ) + logLevel = Log_DEBUG; + + Logger_logFormatted(log, logLevel, logContext, + "Rename failed: %s fromDirID: %s oldName: %s toDirID: %s newName: %s EntryID: %s", + FhgfsOpsErr_toErrString(renameRes), + fromDirInfo->entryID, oldName, toDirInfo->entryID, newName, fromEntryInfo->entryID); + + retVal = FhgfsOpsErr_toSysErr(renameRes); + } + else + { // remote success + inode_timespec ts = current_fs_time(sb); + inode_set_mc_time(inodeDirTo, ts); + inode_set_mc_time(inodeDirFrom, ts); + inode_set_ctime_to_ts(fromEntryInode, ts); + + FhgfsInode_updateEntryInfoOnRenameUnlocked(fhgfsFromEntryInode, toDirInfo, newName); + } + + FhgfsInode_entryInfoWriteUnlock(fhgfsFromEntryInode); // UNLOCK EntryInfo (renamed file dir) + if (fhgfsFromDirInode != fhgfsToDirInode) + FhgfsInode_entryInfoReadUnlock(fhgfsToDirInode); // UNLOCK ToDirEntryInfo + FhgfsInode_entryInfoReadUnlock(fhgfsFromDirInode); // UNLOCK FromDirEntryInfo + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "remoting complete. result: %d", (int)renameRes); + + FileEvent_uninit(&event); + + if (unlikely(retVal == -EBUSY && app->cfg->sysRenameEbusyAsXdev)) + { + const EntryInfo* fromEntryInfo = FhgfsInode_getEntryInfo(fhgfsFromEntryInode); + + Logger_logFormatted(log, Log_NOTICE, logContext, "Rewriting EBUSY to EXDEV: " + "%s fromDirID: %s oldName: %s toDirID: %s newName: %s EntryID: %s", + FhgfsOpsErr_toErrString(renameRes), + fromDirInfo->entryID, oldName, toDirInfo->entryID, newName, fromEntryInfo->entryID); + retVal = -EXDEV; + } + + // clean-up + + return retVal; +} + +/** + * Note: This is almost a copy of general vmtruncate(), just with inode->i_lock around the i_size + * updates. + * Note: This is not called directly, but via setattr + * + * @param offset file offset for truncation + */ +int FhgfsOps_vmtruncate(struct inode* inode, loff_t offset) +{ + if(i_size_read(inode) < offset) + { + unsigned long limit; + + limit = current->signal->rlim[RLIMIT_FSIZE].rlim_cur; + if (limit != RLIM_INFINITY && offset > (loff_t)limit) + goto out_sig; + + if (offset > (loff_t)(inode->i_sb->s_maxbytes) ) + goto out_big; + + spin_lock(&inode->i_lock); + i_size_write(inode, offset); + spin_unlock(&inode->i_lock); + } + else + { + struct address_space *mapping = inode->i_mapping; + + /* + * truncation of in-use swapfiles is disallowed - it would + * cause subsequent swapout to scribble on the now-freed + * blocks. + */ + if(IS_SWAPFILE(inode) ) + return -ETXTBSY; + + spin_lock(&inode->i_lock); + i_size_write(inode, offset); + spin_unlock(&inode->i_lock); + + /* + * unmap_mapping_range is called twice, first simply for + * efficiency so that truncate_inode_pages does fewer + * single-page unmaps. However after this first call, and + * before truncate_inode_pages finishes, it is possible for + * private pages to be COWed, which remain after + * truncate_inode_pages finishes, hence the second + * unmap_mapping_range call must be made for correctness. + */ + unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); + truncate_inode_pages(mapping, offset); + unmap_mapping_range(mapping, offset + PAGE_SIZE - 1, 0, 1); + } + + return 0; + +out_sig: + send_sig(SIGXFSZ, current, 0); +out_big: + return -EFBIG; +} + +/** + * Note: Call this once during module init (and remember to call _destroyInodeCache() ) + */ +bool FhgfsOps_initInodeCache(void) +{ + FhgfsInodeCache = + OsCompat_initKmemCache(FhgfsOpsInode_CACHE_NAME, sizeof(FhgfsInode), FhgfsOps_initInodeOnce); + + if (!FhgfsInodeCache) + return false; + + return true; +} + +void FhgfsOps_destroyInodeCache(void) +{ + if(FhgfsInodeCache) + kmem_cache_destroy(FhgfsInodeCache); +} + + +struct inode* FhgfsOps_alloc_inode(struct super_block *sb) +{ + App* app = FhgfsOps_getApp(sb); + Config* cfg = App_getConfig(app); + + FhgfsInode* fhgfsInode; + + fhgfsInode = kmem_cache_alloc(FhgfsInodeCache, GFP_KERNEL); + if(unlikely(!fhgfsInode) ) + return NULL; + + FhgfsInode_allocInit(fhgfsInode); + + fhgfsInode->vfs_inode.i_blkbits = Config_getTuneInodeBlockBits(cfg); + + return (struct inode*)fhgfsInode; +} + +void FhgfsOps_destroy_inode(struct inode* inode) +{ + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + FhgfsInode_destroyUninit(fhgfsInode); + + kmem_cache_free(FhgfsInodeCache, inode); +} + +/** + * Creates a new inode, inits it from the kstat, inits the ops (depending on the mode) + * and hashes it. + * + * Note: Make sure everything is set in the kstat _before_ you call this, because we hash + * the inode in here (so it can be found and accessed by others when this method returns). + * Note: Consider using the _instantiateInode()-wrapper instead of calling this directly for new + * files/dirs. + * + * @param kstat must have a valid .ino (inode number) + * @param dev set to 0 if not required (only used for special files) + * @param entryInfoPtr contained strings will just be moved to the new inode or free'd in case of an + * error (or cached inode), so don't access the given entryInfoPtr anymore after calling this. + * @param parentNodeID: usually 0, except for NFS export callers, which needs it to connect dentries + * with their parents. By default dentries are connected to their parents, so usually this + * is not required (nfs is an exception). + * @param metaVersion: set to 0 for root inode, otherwise to the value from stat + * @return NULL if not successful + */ +struct inode* __FhgfsOps_newInodeWithParentID(struct super_block* sb, struct kstat* kstat, + dev_t dev, EntryInfo* entryInfo, NumNodeID parentNodeID, FhgfsIsizeHints* iSizeHints, unsigned int metaVersion) +{ + App* app = FhgfsOps_getApp(sb); + Config* cfg = App_getConfig(app); + + FhgfsInode* fhgfsInode; + + FhgfsInodeComparisonInfo comparisonInfo = + { + .inodeHash = kstat->ino, // pre-set by caller + .entryID = entryInfo->entryID, + }; + + + // check inode cache for an existing inode with this ID (and get it) or allocate a new one + + struct inode* inode = iget5_locked(sb, kstat->ino, + __FhgfsOps_compareInodeID, __FhgfsOps_initNewInodeDummy, &comparisonInfo); + + if(unlikely(!inode || IS_ERR(inode) ) ) + goto cleanup_entryInfo; // allocation of new inode failed + + fhgfsInode = BEEGFS_INODE(inode); + + if( !(inode->i_state & I_NEW) ) + { // Found an existing inode, which is possibly actively used. We still need to update it. + FhgfsInode_entryInfoWriteLock(fhgfsInode); // LOCK EntryInfo + + FhgfsInode_updateEntryInfoUnlocked(fhgfsInode, entryInfo); + fhgfsInode->metaVersion = metaVersion; //set the metaVersion of new inode to match the meta version + + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); // UNLOCK EntryInfo + + spin_lock(&inode->i_lock); + + __FhgfsOps_applyStatDataToInodeUnlocked(kstat, iSizeHints, inode); // already locked + Time_setToNow(&fhgfsInode->dataCacheTime); + spin_unlock(&inode->i_lock); + + goto outNoCleanUp; // we found a matching existing inode => no init needed + } + + fhgfsInode->parentNodeID = parentNodeID; + + /* note: new inodes are protected by the I_NEW flag from access by other threads until we + * call unlock_new_inode(). */ + + // init this fresh new inode... + + // no one can access inode yet => unlocked + __FhgfsOps_applyStatDataToInodeUnlocked(kstat, iSizeHints, inode); + + //set the version of new inode to match the meta version + FhgfsInode_entryInfoWriteLock(fhgfsInode); //LOCK EntryInfo + { + fhgfsInode->metaVersion = metaVersion; //set the metaVersion of new inode to match the meta version + } + FhgfsInode_entryInfoWriteUnlock(fhgfsInode); // UNLOCK EntryInfo + + + inode->i_ino = kstat->ino; // pre-set by caller + + inode->i_flags |= S_NOATIME | S_NOCMTIME; // timestamps updated by server + + mapping_set_gfp_mask(&inode->i_data, GFP_USER); // avoid highmem for page cache pages + + // move values (no actual string copy) + fhgfsInode->entryInfo = *entryInfo; + + switch (kstat->mode & S_IFMT) + { + case S_IFREG: // regular file + { + if(Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Native) + { + inode->i_fop = &fhgfs_file_native_ops; + inode->i_data.a_ops = &fhgfs_addrspace_native_ops; + } + else + if(Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Paged) + { // with pagecache + inode->i_fop = &fhgfs_file_pagecache_ops; + inode->i_data.a_ops = &fhgfs_address_pagecache_ops; + } + else + { // no pagecache (=> either none or buffered cache) + inode->i_fop = &fhgfs_file_buffered_ops; + inode->i_data.a_ops = &fhgfs_address_ops; + } + + #ifdef KERNEL_HAS_ADDRESS_SPACE_BDI + inode->i_data.backing_dev_info = FhgfsOps_getBdi(sb); + #endif + + inode->i_op = App_getFileInodeOps(app); + } break; + + case S_IFDIR: // directory + { + inode->i_op = App_getDirInodeOps(app); + inode->i_fop = &fhgfs_dir_ops; + } break; + + case S_IFLNK: // symlink + { + inode->i_op = App_getSymlinkInodeOps(app); + } break; + + default: // pipes and other special files + { + inode->i_op = App_getSpecialInodeOps(app); + init_special_inode(inode, kstat->mode, dev); + } break; + } + + + unlock_new_inode(inode); // remove I_NEW flag, so the inode can be accessed by others + + return inode; + + + // error occured +cleanup_entryInfo: + EntryInfo_uninit(entryInfo); + + // found an existing inode +outNoCleanUp: + return inode; +} + +/** + * Retrieves the attribs from the metadata node, generates an inode number (ID) and instantiates + * a local version of the inode for the dentry. + * + * Note: This is a wrapper for _newInode() that also retrieves the the entry attributes (so the + * entry must exist already on the server). + * + * @param dentry the new entry + * @param entryInfo contained values (not the struct itself!) will be owned by the inode + * (or kfreed on error). So those values need to be allocated by the caller, + * but MUST NOT be free'ed by the caller. + * @param fhgfsStat may be given to avoid extra remoting, or may be NULL (in which case a remote + * stat will be done). + * @return 0 on success, negative linux error code otherwise + */ +int __FhgfsOps_instantiateInode(struct dentry* dentry, EntryInfo* entryInfo, fhgfs_stat* fhgfsStat, + FhgfsIsizeHints* iSizeHints) +{ + const char* logContext = "__FhgfsOps_instantiateInode"; + App* app = FhgfsOps_getApp(dentry->d_sb); + Config* cfg = App_getConfig(app); + int retVal = 0; + fhgfs_stat fhgfsStatInternal; + fhgfs_stat* actualStatInfo; // points either external given or internal stat data + FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS; + + FhgfsOpsHelper_logOpDebug(app, dentry, NULL, logContext, "(%s)", + fhgfsStat ? "with stat info" : "without stat info"); + IGNORE_UNUSED_VARIABLE(logContext); + + if(fhgfsStat) + actualStatInfo = fhgfsStat; + else + { // no stat data given by caller => request from server + actualStatInfo = &fhgfsStatInternal; + + FhgfsInode_initIsizeHints(NULL, iSizeHints); + + statRes = FhgfsOpsRemoting_statDirect(app, entryInfo, &fhgfsStatInternal); + } + + + if(statRes != FhgfsOpsErr_SUCCESS) + { // error + EntryInfo_uninit(entryInfo); + + retVal = FhgfsOpsErr_toSysErr(statRes); + } + else + { // success (entry exists on server or was already given by caller) + struct kstat kstat; + struct inode* newInode; + unsigned int metaVersion = actualStatInfo->metaVersion; + + OsTypeConv_kstatFhgfsToOs(actualStatInfo, &kstat); + + kstat.ino = FhgfsInode_generateInodeID(dentry->d_sb, entryInfo->entryID, + strlen(entryInfo->entryID) ); + + newInode = __FhgfsOps_newInode(dentry->d_sb, &kstat, 0, entryInfo, iSizeHints, metaVersion); + if(unlikely(!newInode || IS_ERR(newInode) ) ) + retVal = IS_ERR(newInode) ? PTR_ERR(newInode) : -EACCES; + else + { // new inode created + if (Config_getSysXAttrsCheckCapabilities(cfg) == CHECKCAPABILITIES_Never) + // The configuration is to never check for capabilities on writes, so use + // "inode_has_no_xattr" which does exactly one thing and that is to set the flag + // "S_NOSEC" on the inode, if the inode doesn't have the setuid and setgid bits set and + // the superblock flag "SB_NOSEC" is set. We set that flag on the superblock according + // to user configuration. When "S_NOSEC" is set on the inode, the kernel will skip + // checking for capabilities on every write operation. + inode_has_no_xattr(newInode); + + d_instantiate(dentry, newInode); + } + } + + return retVal; +} + +/** + * Compare ID of given cachedInode with ID from comparison info arg. + * This is called by iget5_locked() to make sure that we don't have an instance of a given inode + * already (e.g. due to a hardlink or from an NFS handle) before we allocate a new one. + * + * Note: This is called with a spin_lock (inode_hash_lock) held, so we may not sleep. + * + * @param voidComparisonInfo opaque data pointer as passed to iget5_locked. + * @return 0 if IDs don't match, !=0 on match. + */ +int __FhgfsOps_compareInodeID(struct inode* cachedInode, void* voidComparisonInfo) +{ + FhgfsInodeComparisonInfo* comparisonInfo = voidComparisonInfo; + + if(cachedInode->i_ino != comparisonInfo->inodeHash) + { // entryID string hashes don't match + + return 0; + } + else + { // inode hashes match => compare entryID strings + + FhgfsInode* fhgfsInode = BEEGFS_INODE(cachedInode); + const char* searchEntryID = comparisonInfo->entryID; + + return FhgfsInode_compareEntryID(fhgfsInode, searchEntryID); + } + +} + +/** + * A dummy (to be passed to iget5_locked() ), which actually does nothing. + * + * We prefer to do initialization afterwards on the new inode (which is safe, because the inode + * is still marked as new after iget5_locked returns). This method is called with the + * inode_hash_lock spin lock held and we don't want to keep that lock longer than necessary. + * + * @return 0 on success, !=0 on error (specific error code is not checked by calling code). + */ +int __FhgfsOps_initNewInodeDummy(struct inode* newInode, void* newInodeInfo) +{ + return 0; +} + + +/** + * Flush file content caches of the given inode. + * + * @return 0 on success, negative linux error code otherwise. + */ +int __FhgfsOps_flushInodeFileCache(App* app, struct inode* inode) +{ + Config* cfg = App_getConfig(app); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + if(inode->i_mapping) + { // flush out file contents to servers for correct file size + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + bool hasWriteHandle = FhgfsInode_hasWriteHandle(fhgfsInode); + + if (hasWriteHandle || FhgfsInode_getHasDirtyPages(fhgfsInode) ) + { + int inodeWriteRes = write_inode_now(inode, 1); + int filemapWaitRes = filemap_fdatawait(inode->i_mapping); + + if(unlikely(inodeWriteRes < 0 || filemapWaitRes < 0) ) + return (inodeWriteRes < 0) ? inodeWriteRes : filemapWaitRes; + } + else + { + // no need to flush + } + } + + if(S_ISREG(inode->i_mode) && Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Buffered) + { // regular file and buffered mode => flush write cache for correct file size + FhgfsOpsErr flushRes = FhgfsOpsHelper_flushCache(app, fhgfsInode, false); + if(unlikely(flushRes != FhgfsOpsErr_SUCCESS) ) + return FhgfsOpsErr_toSysErr(flushRes); + } + + return 0; +} + +/** + * Called when we want to check whether the inode has changed on the server (or to update + * the inode attribs after we've made changes to a file/dir). + * (If it has changed, we invalidate the caches.) + * + * Note: This method will indirectly aqcuire the i_lock spinlock. + * + * @param fhgfsStat if NULL is given, this method will perform a remote stat; if not NULL, no + * remoting is needed. (note that usually _flushInodeFileCache() or similar should be called before + * retrieving stat info). + * @param iSizeHints is not initialized if fhgfsStat is NULL, but must not be a NULL pointer + * @param noFlush if (unlikely) true the inode must not be flushed + * @return 0 on success (validity), negative linux error code if no longer valid + */ +int __FhgfsOps_doRefreshInode(App* app, struct inode* inode, fhgfs_stat* fhgfsStat, + FhgfsIsizeHints* iSizeHints, bool noFlush) +{ + const char* logContext = "FhgfsOps_refreshInode"; + Config* cfg = App_getConfig(app); + + int retVal = 0; + struct kstat kstat; + int flushRes; + FhgfsOpsErr statRes; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + #if defined(KERNEL_HAS_INODE_MTIME) + typeof(inode->i_mtime.tv_sec) oldMTime; + #else + time64_t oldMTime; + #endif + loff_t oldSize; + unsigned cacheElapsedMS; + bool mtimeSizeInvalidate; + bool timeoutInvalidate; + + FhgfsOpsHelper_logOpDebug(app, NULL, inode, logContext, "(%s)", + fhgfsStat ? "with stat info" : "without stat info"); + + IGNORE_UNUSED_VARIABLE(logContext); + + if(fhgfsStat) + { // stat info given by caller => no remoting needed + OsTypeConv_kstatFhgfsToOs(fhgfsStat, &kstat); + } + else + { // no stat info given by caller => get it from server + fhgfs_stat fhgfsStatInternal; + + if (likely(!noFlush) ) + { // flush file contents for correct stat size + flushRes = __FhgfsOps_flushInodeFileCache(app, inode); + if(flushRes < 0) + return flushRes; + } + + FhgfsInode_initIsizeHints(fhgfsInode, iSizeHints); + + FhgfsInode_entryInfoReadLock(fhgfsInode); // LOCK EntryInfo + + statRes = FhgfsOpsRemoting_statDirect(app, FhgfsInode_getEntryInfo(fhgfsInode), + &fhgfsStatInternal); + + FhgfsInode_entryInfoReadUnlock(fhgfsInode); // UNLOCK EntryInfo + + if(statRes != FhgfsOpsErr_SUCCESS) + { // error or entry doesn't exist anymore + retVal = FhgfsOpsErr_toSysErr(statRes); + goto cleanup; + } + + OsTypeConv_kstatFhgfsToOs(&fhgfsStatInternal, &kstat); + } + + // stat succeeded, so the entry still exists + + if(inode->i_ino == BEEGFS_INODE_ROOT_INO) + { // root node deserves special handling (less checking) + __FhgfsOps_applyStatDataToInode(&kstat, NULL, inode); + + goto cleanup; + } + + // check whether entry is still the same object type + + if(unlikely( (inode->i_mode & S_IFMT) != (kstat.mode & S_IFMT) ) ) + { // object type changed => limit damage by marking it as bad + + /* note: this is quite impossible since we're verifying by entryID and an ID should + never ever be assigned to another object type. */ + + umode_t savedMode = inode->i_mode; // save mode + + make_bad_inode(inode); + + inode->i_mode = savedMode; // restore mode + + printk_fhgfs(KERN_WARNING, "%s: Inode object type changed unexpectedly.\n", logContext); + + // invalidate cached pages + if(!S_ISDIR(inode->i_mode) ) + { + invalidate_remote_inode(inode); + } + + retVal = -ENOENT; + goto cleanup; + } + + // apply new stat data + + spin_lock(&inode->i_lock); // I _ L O C K + + #if defined(KERNEL_HAS_INODE_MTIME) + oldMTime = inode->i_mtime.tv_sec; + #else + oldMTime = inode_get_mtime_sec(inode); + #endif + oldSize = i_size_read(inode); + + __FhgfsOps_applyStatDataToInodeUnlocked(&kstat, iSizeHints, inode); + + // compare previous size/mtime to detect modifications by other clients + + #if defined(KERNEL_HAS_INODE_MTIME) + mtimeSizeInvalidate = + (inode->i_mtime.tv_sec != oldMTime) || (i_size_read(inode) != oldSize); + #else + mtimeSizeInvalidate = + (inode_get_mtime_sec(inode) != oldMTime) || (i_size_read(inode) != oldSize); + #endif + cacheElapsedMS = Time_elapsedMS(&fhgfsInode->dataCacheTime); + timeoutInvalidate = cacheElapsedMS > Config_getTunePageCacheValidityMS(cfg); + + if( !S_ISDIR(inode->i_mode) && (mtimeSizeInvalidate || timeoutInvalidate)) + { // file contents changed => invalidate non-dirty pages + spin_unlock(&inode->i_lock); // I _ U N L O C K + invalidate_remote_inode(inode); // might sleep => unlocked + spin_lock(&inode->i_lock); // I _ R E L O C K + } + + // update the dataCacheTime because we've either invalidated the inode, or + // we've seen the mtime and size have not changed and the timeout compared to + // tunePageCacheBufferMS has not timed out either. + + Time_setToNow(&fhgfsInode->dataCacheTime); + spin_unlock(&inode->i_lock); // I _ U N L O C K + + + // clean up +cleanup: + + return retVal; +} + + +/** + * Note: Refreshes inode and mapping only if cache validity timeout expired. + * + * @return 0 on success (validity), negative linux error code if no longer valid + */ +int __FhgfsOps_revalidateMapping(App* app, struct inode* inode) +{ + const char* logContext = "FhgfsOps_revalidateMapping"; + Config* cfg = App_getConfig(app); + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + unsigned cacheElapsedMS; + bool timeoutInvalidate; + bool needRefresh = false; + + FhgfsIsizeHints iSizeHints; + + FhgfsOpsHelper_logOp(Log_SPAM, app, NULL, inode, logContext); + IGNORE_UNUSED_VARIABLE(logContext); + + spin_lock(&inode->i_lock); // I _ L O C K + + cacheElapsedMS = Time_elapsedMS(&fhgfsInode->dataCacheTime); + timeoutInvalidate = cacheElapsedMS > Config_getTunePageCacheValidityMS(cfg); + + if(!S_ISDIR(inode->i_mode) && timeoutInvalidate) + needRefresh = true; + + spin_unlock(&inode->i_lock); // I _ U N L O C K + + if(needRefresh) + return __FhgfsOps_refreshInode(app, inode, NULL, &iSizeHints); + + return 0; +} + +/** + * Used for client cache invalidation with metadata version. + * Either clears inode stripe pattern or makes the inode invalid. + * Acquires i_lock spinlock. + */ +void __FhgfsOps_clearInodeStripePattern(App* app, struct inode* inode) +{ + const char* logContext = "FhgfsOps_clearInodeStripePattern"; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + IGNORE_UNUSED_VARIABLE(logContext); + spin_lock(&inode->i_lock); + + //check the inode reference counter + if (atomic_read(&inode->i_count) == 1) + { + //we are the only ones with reference, clear stripe pattern (updates on open) + FhgfsInode_clearStripePattern(fhgfsInode); + spin_unlock(&inode->i_lock); + } + else + { + //someone else is holding a reference to the inode + //should not occur, but as a precaution we mark the inode as bad + //forces new inode creation (and therefore new stripe pattern) + umode_t savedMode = inode->i_mode; // save mode + spin_unlock(&inode->i_lock); //must release spin lock before make_bad_inode + make_bad_inode(inode); + inode->i_mode = savedMode; // restore mode + + FhgfsOpsHelper_logOpMsg(Log_WARNING, app, NULL, inode, logContext, + "Blocked access to inode due to unexpected inode access during cache invalidation"); + + + // invalidate cached pages + if(!S_ISDIR(inode->i_mode) ) + { + invalidate_remote_inode(inode); + } + } +} diff --git a/client_module/source/filesystem/FhgfsOpsInode.h b/client_module/source/filesystem/FhgfsOpsInode.h new file mode 100644 index 0000000..a870953 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsInode.h @@ -0,0 +1,513 @@ +#ifndef FHGFSOPSINODE_H_ +#define FHGFSOPSINODE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsInode.h" +#include "FhgfsOps_versions.h" + +#include +#include + +// forward declaration +struct App; + +struct FhgfsInodeComparisonInfo; +typedef struct FhgfsInodeComparisonInfo FhgfsInodeComparisonInfo; + +#if defined(KERNEL_HAS_CURRENT_TIME_SPEC64) +typedef struct timespec64 inode_timespec; +#else +typedef struct timespec inode_timespec; +#endif + +#if ! defined(KERNEL_HAS_INODE_GET_SET_CTIME) +/* These functions have been adapted from kernel code to work for older kernels. +The function signatures were introduced in version ~6.5 */ + +static inline time64_t inode_get_ctime_sec(const struct inode *inode) +{ + return inode->i_ctime.tv_sec; +} + +static inline long inode_get_ctime_nsec(const struct inode *inode) +{ + return inode->i_ctime.tv_nsec; +} + +static inline inode_timespec inode_get_ctime(const struct inode *inode) +{ + return inode->i_ctime; +} + +static inline inode_timespec inode_set_ctime_to_ts(struct inode *inode, + inode_timespec ts) +{ + inode->i_ctime = ts; + return ts; +} + +static inline inode_timespec inode_set_ctime(struct inode *inode, + time64_t sec, long nsec) +{ + inode_timespec ts = { .tv_sec = sec, + .tv_nsec = nsec }; + + return inode_set_ctime_to_ts(inode, ts); +} +#endif + +#if ! defined(KERNEL_HAS_INODE_GET_SET_CTIME_MTIME_ATIME) +/* These functions have been adapted from kernel code to work for older kernels. +The function signatures were introduced in version ~6.6 */ + +static inline time64_t inode_get_atime_sec(const struct inode *inode) +{ + return inode->i_atime.tv_sec; +} + +static inline long inode_get_atime_nsec(const struct inode *inode) +{ + return inode->i_atime.tv_nsec; +} + +static inline inode_timespec inode_get_atime(const struct inode *inode) +{ + return inode->i_atime; +} + +static inline inode_timespec inode_set_atime_to_ts(struct inode *inode, + inode_timespec ts) +{ + inode->i_atime = ts; + return ts; +} + +static inline inode_timespec inode_set_atime(struct inode *inode, + time64_t sec, long nsec) +{ + inode_timespec ts = { .tv_sec = sec, + .tv_nsec = nsec }; + return inode_set_atime_to_ts(inode, ts); +} + +static inline time64_t inode_get_mtime_sec(const struct inode *inode) +{ + return inode->i_mtime.tv_sec; +} + +static inline long inode_get_mtime_nsec(const struct inode *inode) +{ + return inode->i_mtime.tv_nsec; +} + +static inline inode_timespec inode_get_mtime(const struct inode *inode) +{ + return inode->i_mtime; +} + +static inline inode_timespec inode_set_mtime_to_ts(struct inode *inode, + inode_timespec ts) +{ + inode->i_mtime = ts; + return ts; +} + +static inline inode_timespec inode_set_mtime(struct inode *inode, + time64_t sec, long nsec) +{ + inode_timespec ts = { .tv_sec = sec, + .tv_nsec = nsec }; + return inode_set_mtime_to_ts(inode, ts); +} +#endif + +static inline void inode_set_mc_time(struct inode *inode, + inode_timespec ts) +{ + inode_set_mtime_to_ts(inode, ts); + inode_set_ctime_to_ts(inode, ts); +} + +#ifndef KERNEL_HAS_ATOMIC_OPEN + extern struct dentry* FhgfsOps_lookupIntent(struct inode* parentDir, struct dentry* dentry, + struct nameidata* nameidata); +#else + extern struct dentry* FhgfsOps_lookupIntent(struct inode* parentDir, struct dentry* dentry, + unsigned flags); +#endif // KERNEL_HAS_ATOMIC_OPEN + +#ifdef KERNEL_HAS_STATX +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_getattr(struct mnt_idmap* idmap, const struct path* path, + struct kstat* kstat, u32 request_mask, unsigned int query_flags); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_getattr(struct user_namespace* ns, const struct path* path, + struct kstat* kstat, u32 request_mask, unsigned int query_flags); +#else +extern int FhgfsOps_getattr(const struct path* path, struct kstat* kstat, u32 request_mask, + unsigned int query_flags); +#endif +#else +extern int FhgfsOps_getattr(struct vfsmount* mnt, struct dentry* dentry, struct kstat* kstat); +#endif + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_setattr(struct mnt_idmap* idmap, struct dentry* dentry, struct iattr* iattr); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_setattr(struct user_namespace* ns, struct dentry* dentry, struct iattr* iattr); +#else +extern int FhgfsOps_setattr(struct dentry* dentry, struct iattr* iattr); +#endif + +extern ssize_t FhgfsOps_listxattr(struct dentry* dentry, char* value, size_t size); +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER +extern ssize_t FhgfsOps_getxattr(struct dentry* dentry, const char* name, void* value, size_t size); +extern int FhgfsOps_setxattr(struct dentry* dentry, const char* name, const void* value, + size_t size, int flags); +#else +extern ssize_t FhgfsOps_getxattr(struct inode* inode, const char* name, void* value, size_t size); +extern int FhgfsOps_setxattr(struct inode* inode, const char* name, const void* value, + size_t size, int flags); +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER +extern int FhgfsOps_removexattr(struct dentry* dentry, const char* name); +extern int FhgfsOps_removexattrInode(struct inode* inode, const char* name); + +#if defined(KERNEL_HAS_GET_INODE_ACL) +extern struct posix_acl* FhgfsOps_get_inode_acl(struct inode* inode, int type, bool rcu); +#endif +#ifdef KERNEL_HAS_GET_ACL +#if defined(KERNEL_HAS_POSIX_GET_ACL_IDMAP) +extern struct posix_acl * FhgfsOps_get_acl(struct mnt_idmap *idmap, struct dentry *dentry, int type); +#elif defined(KERNEL_HAS_POSIX_GET_ACL_NS) +extern struct posix_acl * FhgfsOps_get_acl(struct user_namespace *userns, struct dentry *dentry, int type); +#elif defined(KERNEL_POSIX_GET_ACL_HAS_RCU) +extern struct posix_acl* FhgfsOps_get_acl(struct inode* inode, int type, bool rcu); +#else +extern struct posix_acl* FhgfsOps_get_acl(struct inode* inode, int type); +#endif + +int FhgfsOps_aclChmod(struct iattr* iattr, struct dentry* dentry); +#endif + +#if defined(KERNEL_HAS_SET_ACL) +#if defined(KERNEL_HAS_SET_ACL_DENTRY) + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_set_acl(struct mnt_idmap* mnt_userns, struct dentry* dentry, + struct posix_acl* acl, int type); +#else +extern int FhgfsOps_set_acl(struct user_namespace* mnt_userns, struct dentry* dentry, + struct posix_acl* acl, int type); +#endif + +#else + +#if defined(KERNEL_HAS_SET_ACL_NS_INODE) +extern int FhgfsOps_set_acl(struct user_namespace* mnt_userns, struct inode* inode, + struct posix_acl* acl, int type); +#else +extern int FhgfsOps_set_acl(struct inode* inode, struct posix_acl* acl, int type); +#endif + +#endif //KERNEL_HAS_SET_ACL_DENTRY +#endif // KERNEL_HAS_SET_ACL + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_mkdir(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, umode_t mode); +extern int FhgfsOps_mknod(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, umode_t mode, dev_t dev); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_mkdir(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, umode_t mode); +extern int FhgfsOps_mknod(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, umode_t mode, dev_t dev); +#elif defined(KERNEL_HAS_UMODE_T) +extern int FhgfsOps_mkdir(struct inode* dir, struct dentry* dentry, umode_t mode); +extern int FhgfsOps_mknod(struct inode* dir, struct dentry* dentry, umode_t mode, dev_t dev); +#else +extern int FhgfsOps_mkdir(struct inode* dir, struct dentry* dentry, int mode); +extern int FhgfsOps_mknod(struct inode* dir, struct dentry* dentry, int mode, dev_t dev); +#endif + +#if defined KERNEL_HAS_ATOMIC_OPEN + int FhgfsOps_atomicOpen(struct inode* dir, struct dentry* dentry, struct file* file, + unsigned openFlags, umode_t createMode + #ifndef FMODE_CREATED + , int* outOpenedFlags + #endif + ); + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + extern int FhgfsOps_createIntent(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, umode_t mode, bool isExclusiveCreate); + #elif defined(KERNEL_HAS_USER_NS_MOUNTS) + extern int FhgfsOps_createIntent(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, umode_t mode, bool isExclusiveCreate); + #else + extern int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, umode_t mode, + bool isExclusiveCreate); + #endif +#elif defined KERNEL_HAS_UMODE_T + extern int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, umode_t mode, + struct nameidata* nameidata); +#else + extern int FhgfsOps_createIntent(struct inode* dir, struct dentry* dentry, int mode, + struct nameidata* nameidata); +#endif // KERNEL_HAS_ATOMIC_OPEN + +extern int FhgfsOps_rmdir(struct inode* dir, struct dentry* dentry); +extern int FhgfsOps_unlink(struct inode* dir, struct dentry* dentry); + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_symlink(struct mnt_idmap* idmap, struct inode* dir, + struct dentry* dentry, const char* to); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_symlink(struct user_namespace* mnt_userns, struct inode* dir, + struct dentry* dentry, const char* to); +#else +extern int FhgfsOps_symlink(struct inode* dir, struct dentry* dentry, const char* to); +#endif + +extern int FhgfsOps_link(struct dentry* dentryFrom, struct inode* inode, struct dentry* dentryTo); +extern int FhgfsOps_hardlinkAsSymlink(struct dentry* oldDentry, struct inode* dir, + struct dentry* newDentry); + + +#if defined KERNEL_HAS_GET_LINK +extern const char* FhgfsOps_get_link(struct dentry* dentry, struct inode* inode, + struct delayed_call* done); +#elif defined(KERNEL_HAS_FOLLOW_LINK_COOKIE) +extern const char* FhgfsOps_follow_link(struct dentry* dentry, void** cookie); +extern void FhgfsOps_put_link(struct inode* inode, void* cookie); +#else +extern void* FhgfsOps_follow_link(struct dentry* dentry, struct nameidata* nd); +extern void FhgfsOps_put_link(struct dentry* dentry, struct nameidata* nd, void* p); +#endif + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +extern int FhgfsOps_rename(struct mnt_idmap* idmap, struct inode* inodeDirFrom, + struct dentry* dentryFrom, struct inode* inodeDirTo, struct dentry* dentryTo, unsigned flags); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +extern int FhgfsOps_rename(struct user_namespace* mnt_userns, struct inode* inodeDirFrom, + struct dentry* dentryFrom, struct inode* inodeDirTo, struct dentry* dentryTo, unsigned flags); +#elif defined(KERNEL_HAS_RENAME_FLAGS) +extern int FhgfsOps_rename(struct inode* inodeDirFrom, struct dentry* dentryFrom, + struct inode* inodeDirTo, struct dentry* dentryTo, unsigned flags); +#else +extern int FhgfsOps_rename(struct inode* inodeDirFrom, struct dentry* dentryFrom, + struct inode* inodeDirTo, struct dentry* dentryTo); +#endif + +extern int FhgfsOps_vmtruncate(struct inode* inode, loff_t offset); + + +extern bool FhgfsOps_initInodeCache(void); +extern void FhgfsOps_destroyInodeCache(void); +extern struct inode* FhgfsOps_alloc_inode(struct super_block* sb); +extern void FhgfsOps_destroy_inode(struct inode* inode); + +extern struct inode* __FhgfsOps_newInodeWithParentID(struct super_block* sb, struct kstat* kstat, + dev_t dev, EntryInfo* entryInfo, NumNodeID parentNodeID, FhgfsIsizeHints* iSizeHints, unsigned int metaVersion); + +extern int __FhgfsOps_instantiateInode(struct dentry* dentry, EntryInfo* entryInfo, + fhgfs_stat* fhgfsStat, FhgfsIsizeHints* iSizeHints); +int __FhgfsOps_compareInodeID(struct inode* cachedInode, void* newInodeInfo); +int __FhgfsOps_initNewInodeDummy(struct inode* newInode, void* newInodeInfo); + +static inline int __FhgfsOps_refreshInode(App* app, struct inode* inode, fhgfs_stat* fhgfsStat, + FhgfsIsizeHints* iSizeHints); +extern int __FhgfsOps_doRefreshInode(App* app, struct inode* inode, fhgfs_stat* fhgfsStat, + FhgfsIsizeHints* iSizeHints, bool noFlush); +extern int __FhgfsOps_revalidateMapping(App* app, struct inode* inode); +extern int __FhgfsOps_flushInodeFileCache(App* app, struct inode* inode); +extern void __FhgfsOps_clearInodeStripePattern(App* app, struct inode* inode); + +// inliners +static inline void __FhgfsOps_applyStatDataToInode(struct kstat* kstat, FhgfsIsizeHints* iSizeHints, + struct inode* outInode); +static inline void __FhgfsOps_applyStatDataToInodeUnlocked(struct kstat* kstat, + FhgfsIsizeHints* iSizeHints, struct inode* outInode); +static inline void __FhgfsOps_applyStatAttribsToInode(struct kstat* kstat, struct inode* outInode); +static inline void __FhgfsOps_applyStatSizeToInode(struct kstat* kstat, + FhgfsIsizeHints* iSizeHints, struct inode* inOutInode); +static inline struct inode* __FhgfsOps_newInode(struct super_block* sb, struct kstat* kstat, + dev_t dev, EntryInfo* entryInfo, FhgfsIsizeHints* iSizeHints, unsigned int metaVersion); +static inline bool __FhgfsOps_isPagedMode(struct super_block* sb); + + + +/** + * This structure is passed to _compareInodeID(). + */ +struct FhgfsInodeComparisonInfo +{ + ino_t inodeHash; // (=> inode::i_ino) + const char* entryID; +}; + + +/** + * Note: acquires i_lock spinlock to protect i_size and i_blocks + */ +void __FhgfsOps_applyStatDataToInode(struct kstat* kstat, FhgfsIsizeHints* iSizeHints, + struct inode* outInode) +{ + __FhgfsOps_applyStatAttribsToInode(kstat, outInode); + + spin_lock(&outInode->i_lock); // I _ L O C K + + __FhgfsOps_applyStatSizeToInode(kstat, iSizeHints, outInode); + + spin_unlock(&outInode->i_lock); // I _ U N L O C K +} + +/** + * Note: Caller must hold i_lock. + */ +void __FhgfsOps_applyStatDataToInodeUnlocked(struct kstat* kstat, FhgfsIsizeHints* iSizeHints, + struct inode* outInode) +{ + __FhgfsOps_applyStatAttribsToInode(kstat, outInode); + + __FhgfsOps_applyStatSizeToInode(kstat, iSizeHints, outInode); +} + +/** + * Note: Don't call this directly - use the _applyStatDataToInode... wrappers. + */ +void __FhgfsOps_applyStatAttribsToInode(struct kstat* kstat, struct inode* outInode) +{ + App* app = FhgfsOps_getApp(outInode->i_sb); + Config* cfg = App_getConfig(app); + + // remote attribs (received from nodes) + outInode->i_mode = kstat->mode; + outInode->i_uid = kstat->uid; + outInode->i_gid = kstat->gid; + inode_set_atime_to_ts(outInode, kstat->atime); + inode_set_mtime_to_ts(outInode, kstat->mtime); + inode_set_ctime_to_ts(outInode, kstat->ctime); + + set_nlink(outInode, kstat->nlink); + + outInode->i_blkbits = Config_getTuneInodeBlockBits(cfg); +} + +/** + * Note: Don't call this directly - use the _applyStatDataToInode... wrappers. + * Note: Caller must hold i_lock. + * + * @param iSizeHints might be NULL + */ +void __FhgfsOps_applyStatSizeToInode(struct kstat* kstat, FhgfsIsizeHints* iSizeHints, + struct inode* inOutInode) +{ + FhgfsInode* fhgfsInode = BEEGFS_INODE(inOutInode); + + if (S_ISREG(inOutInode->i_mode) && FhgfsInode_getHasPageWriteFlag(fhgfsInode) ) + { + loff_t oldSize = i_size_read(inOutInode); + uint64_t lastWriteBackOrIsizeWriteTime; + + /* We don't allow to decrease the file size in paged mode, as + * this may/will cause data corruption (zeros/holes instead of real data). + * The detection when to apply the server i_size is rather complex. */ + if (oldSize > kstat->size) + { + if (FhgfsInode_getNoIsizeDecrease(fhgfsInode) ) + return; + + // there are current write-back threads running + if (FhgfsInode_getWriteBackCounter(fhgfsInode) ) + return; + + // must be read after reading the write-back counter + lastWriteBackOrIsizeWriteTime = FhgfsInode_getLastWriteBackOrIsizeWriteTime(fhgfsInode); + + if (iSizeHints) + { + if (iSizeHints->ignoreIsize) + return; // iSizeHints tells us to ignore the value + + if (time_after_eq((unsigned long) lastWriteBackOrIsizeWriteTime, + (unsigned long) iSizeHints->timeBeforeRemoteStat) ) + return; /* i_size was updated or a writeback thread finished after we send the + * remote stat call, so concurrent stat/isize updates and we need to ignore + the remote value. */ + } + + #ifdef BEEGFS_DEBUG + { + static bool didLog = false; + + struct super_block* sb = inOutInode->i_sb; + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + + if (!didLog) + { + // unlock, as the logger gets a mutex lock -> possible lock order issue + spin_unlock(&inOutInode->i_lock); + + LOG_DEBUG_FORMATTED(log, Log_WARNING, __func__, + "Warn-once: (possibly valid) isize-shrink oldSize: %lld newSize: %lld " + "lastWBTime: %llu, timeBeforeRemote; %llu\n", + oldSize, kstat->size, lastWriteBackOrIsizeWriteTime, iSizeHints->timeBeforeRemoteStat); + dump_stack(); + + didLog = true; + + spin_lock(&inOutInode->i_lock); + } + } + #endif + } + } + + i_size_write(inOutInode, kstat->size); + inOutInode->i_blocks = kstat->blocks; +} + +/** + * See __FhgfsOps_newInodeWithParentID for details. This is just a wrapper function. + */ +struct inode* __FhgfsOps_newInode(struct super_block* sb, struct kstat* kstat, dev_t dev, + EntryInfo* entryInfo, FhgfsIsizeHints* iSizeHints, unsigned int metaVersion) +{ + return __FhgfsOps_newInodeWithParentID(sb, kstat, dev, entryInfo, (NumNodeID){0}, iSizeHints, metaVersion); +} + +bool __FhgfsOps_isPagedMode(struct super_block* sb) +{ + App* app = FhgfsOps_getApp(sb); + Config* cfg = App_getConfig(app); + + if (Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Paged) + return true; + + if (Config_getTuneFileCacheTypeNum(cfg) == FILECACHETYPE_Native) + return true; + + return false; +} + +/** + * See __FhgfsOps_doRefreshInode for details. + */ +int __FhgfsOps_refreshInode(App* app, struct inode* inode, fhgfs_stat* fhgfsStat, + FhgfsIsizeHints* iSizeHints) +{ + // do not disable inode flushing from this (default) refreshInode function + return __FhgfsOps_doRefreshInode(app, inode, fhgfsStat, iSizeHints, false); +} + + + +#endif /* FHGFSOPSINODE_H_ */ diff --git a/client_module/source/filesystem/FhgfsOpsIoctl.c b/client_module/source/filesystem/FhgfsOpsIoctl.c new file mode 100644 index 0000000..ed30a57 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsIoctl.c @@ -0,0 +1,1188 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsSuper.h" +#include "FhgfsOpsHelper.h" +#include "FhgfsOpsIoctl.h" +#include "FhgfsOpsInode.h" + +#ifdef CONFIG_COMPAT +#include +#endif + +#include + + +/* old kernels didn't have the TCGETS ioctl definition (used by isatty() test) */ +#ifndef TCGETS +#define TCGETS 0x5401 +#endif + +/* define these strings here so the IOCTL interface doesn't depend upon privately known ints */ +#define FHGFSOPSIOCTL_NODETYPE_STORAGE "storage" +#define FHGFSOPSIOCTL_NODETYPE_META "meta" +#define FHGFSOPSIOCTL_NODETYPE_METADATA "metadata" +#define FHGFSOPSIOCTL_NODETYPE_MGMT "mgmt" +#define FHGFSOPSIOCTL_NODETYPE_MANAGEMENT "management" + +static long FhgfsOpsIoctl_getCfgFile(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getRuntimeCfgFile(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_testIsFhGFS(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_createFile(struct file *file, void __user *argp, int version); +static long FhgfsOpsIoctl_getMountID(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getStripeInfo(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getStripeTarget(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getStripeTargetV2(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_mkfileWithStripeHints(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getInodeID(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_getEntryInfo(struct file *file, void __user *argp); +static long FhgfsOpsIoctl_pingNode(struct file *file, void __user *argp); + +static NodeType FhgfsOpsIoctl_strToNodeType(const char* str, size_t len) +{ + if (!strncmp(FHGFSOPSIOCTL_NODETYPE_META, str, len) || + !strncmp(FHGFSOPSIOCTL_NODETYPE_METADATA, str, len)) + return NODETYPE_Meta; + if (!strncmp(FHGFSOPSIOCTL_NODETYPE_STORAGE, str, len)) + return NODETYPE_Storage; + if (!strncmp(FHGFSOPSIOCTL_NODETYPE_MGMT, str, len) || + !strncmp(FHGFSOPSIOCTL_NODETYPE_MANAGEMENT, str, len)) + return NODETYPE_Mgmt; + return NODETYPE_Invalid; +} + +/** + * Execute FhGFS IOCTLs. + * + * @param file the file the ioctl was opened for + * @param cmd the ioctl command supposed to be done + * @param arg in and out argument + */ +long FhgfsOpsIoctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dentry *dentry = file_dentry(file); + struct inode *inode = file_inode(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = "FhgfsOps_ioctl"; + + Logger_logFormatted(log, Log_SPAM, logContext, "Called ioctl cmd: %u", cmd); + + switch(cmd) + { + case BEEGFS_IOC_GETVERSION: + case BEEGFS_IOC_GETVERSION_OLD: + { /* used by userspace filesystems or userspace nfs servers for cases where a (recycled) inode + number now refers to a different file (and thus has a different generation number) */ + return put_user(inode->i_generation, (int __user *) arg); + } break; + + case BEEGFS_IOC_GET_CFG_FILE: + { /* get the path to the client config file*/ + return FhgfsOpsIoctl_getCfgFile(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_CREATE_FILE: + { + return FhgfsOpsIoctl_createFile(file, (void __user *) arg, 1); + } break; + + case BEEGFS_IOC_CREATE_FILE_V2: + { + return FhgfsOpsIoctl_createFile(file, (void __user *) arg, 2); + } break; + + case BEEGFS_IOC_CREATE_FILE_V3: + { + return FhgfsOpsIoctl_createFile(file, (void __user *) arg, 3); + } break; + + case BEEGFS_IOC_TEST_IS_FHGFS: + { + return FhgfsOpsIoctl_testIsFhGFS(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_GET_RUNTIME_CFG_FILE: + { /* get the virtual runtime client config file (path to procfs) */ + return FhgfsOpsIoctl_getRuntimeCfgFile(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_GET_MOUNTID: + { // get the mountID (e.g. for path in procfs) + return FhgfsOpsIoctl_getMountID(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_GET_STRIPEINFO: + { // get stripe info of a file + return FhgfsOpsIoctl_getStripeInfo(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_GET_STRIPETARGET: + { // get stripe info of a file + return FhgfsOpsIoctl_getStripeTarget(file, (void __user *) arg); + } break; + + case BEEGFS_IOC_GET_STRIPETARGET_V2: + return FhgfsOpsIoctl_getStripeTargetV2(file, (void __user *) arg); + + case BEEGFS_IOC_MKFILE_STRIPEHINTS: + { // create a file with stripe hints + return FhgfsOpsIoctl_mkfileWithStripeHints(file, (void __user *) arg); + } + + case BEEGFS_IOC_GETINODEID: + { // calculate corresponding inodeID to given entryID + return FhgfsOpsIoctl_getInodeID(file, (void __user *) arg); + } + + case BEEGFS_IOC_GETENTRYINFO: + { // return BeeGFS internal path to file + return FhgfsOpsIoctl_getEntryInfo(file, (void __user *) arg); + } + + case BEEGFS_IOC_PINGNODE: + { + return FhgfsOpsIoctl_pingNode(file, (void __user *) arg); + } + + case TCGETS: + { // filter isatty() test ioctl, which is often used by various standard tools + return -ENOTTY; + } break; + + default: + { + LOG_DEBUG_FORMATTED(log, Log_WARNING, logContext, "Unknown ioctl command code: %u", cmd); + return -ENOIOCTLCMD; + } break; + } + + return 0; +} + +#ifdef CONFIG_COMPAT +/** + * Compatibility ioctl method for 64-bit kernels called from 32-bit user space + */ +long FhgfsOpsIoctl_compatIoctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct dentry *dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + Logger_logFormatted(log, Log_SPAM, logContext, "Called ioctl cmd: %u", cmd); + + switch(cmd) + { + case BEEGFS_IOC32_GETVERSION_OLD: + case BEEGFS_IOC32_GETVERSION: + { + cmd = BEEGFS_IOC_GETVERSION; + } break; + + default: + { + return -ENOIOCTLCMD; + } + } + + return FhgfsOpsIoctl_ioctl(file, cmd, (unsigned long) compat_ptr(arg)); +} +#endif // CONFIG_COMPAT + +/** + * Get the path to the client config file of this mount (e.g. /etc/fhgfs/fhgfs-client.conf). + */ +static long FhgfsOpsIoctl_getCfgFile(struct file *file, void __user *argp) +{ + struct dentry *dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + struct BeegfsIoctl_GetCfgFile_Arg __user *confFile = (struct BeegfsIoctl_GetCfgFile_Arg*)argp; + Config* cfg = App_getConfig(app); + char* fileName = Config_getCfgFile(cfg); + int cpRes; + + int cfgFileStrLen = strlen(fileName) + 1; // strlen() does not count \0 + if (unlikely(cfgFileStrLen > BEEGFS_IOCTL_CFG_MAX_PATH)) + { + Logger_logFormatted(log, Log_WARNING, logContext, + "Config file path too long (%d vs max %d)", cfgFileStrLen, BEEGFS_IOCTL_CFG_MAX_PATH); + return -EINVAL; + } + + if(!os_access_ok(VERIFY_WRITE, confFile, sizeof(*confFile) ) ) + { + Logger_logFormatted(log, Log_DEBUG, logContext, "access_ok() denied to write to conf_file"); + return -EINVAL; + } + + cpRes = copy_to_user(&confFile->path[0], fileName, cfgFileStrLen); // also calls access_ok + if(cpRes) + { + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "copy_to_user failed()"); + return -EINVAL; + } + + return 0; +} + +/** + * Get the path to the virtual runtime config file of this mount in procfs + * (e.g. /proc/fs/beegfs//config). + */ +static long FhgfsOpsIoctl_getRuntimeCfgFile(struct file *file, void __user *argp) +{ + struct dentry *dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + struct BeegfsIoctl_GetCfgFile_Arg __user *confFile = (struct BeegfsIoctl_GetCfgFile_Arg*)argp; + char* fileName = vmalloc(BEEGFS_IOCTL_CFG_MAX_PATH); + + Node* localNode = App_getLocalNode(app); + NodeString alias; + + int cpRes; + int cfgFileStrLen; + + if(!os_access_ok(VERIFY_WRITE, confFile, sizeof(*confFile) ) ) + { + Logger_logFormatted(log, Log_DEBUG, logContext, "access_ok() denied to write to conf_file"); + vfree(fileName); + return -EINVAL; + } + Node_copyAlias(localNode, &alias); + cfgFileStrLen = scnprintf(fileName, BEEGFS_IOCTL_CFG_MAX_PATH, "/proc/fs/beegfs/%s/config", alias.buf); + if (cfgFileStrLen <= 0) + { + Logger_logFormatted(log, Log_WARNING, logContext, "buffer too small"); + vfree(fileName); + return -EINVAL; + } + + cfgFileStrLen++; // scnprintf(...) does not count \0 + + cpRes = copy_to_user(&confFile->path[0], fileName, cfgFileStrLen); // also calls access_ok + if(cpRes) + { + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "copy_to_user failed()"); + vfree(fileName); + return -EINVAL; + } + + vfree(fileName); + return 0; +} + +/** + * Confirm to caller that we are a FhGFS mount. + */ +static long FhgfsOpsIoctl_testIsFhGFS(struct file *file, void __user *argp) +{ + struct dentry *dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + int cpRes = copy_to_user(argp, + BEEGFS_IOCTL_TEST_STRING, sizeof(BEEGFS_IOCTL_TEST_STRING) ); // (also calls access_ok) + if(cpRes) + { // result >0 is number of uncopied bytes + LOG_DEBUG_FORMATTED(log, Log_WARNING, logContext, "copy_to_user failed()"); + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + + return -EINVAL; + } + + return 0; +} + +/** + * Get the mountID aka clientID aka nodeID of client mount aka sessionID (aka alias :) + */ +static long FhgfsOpsIoctl_getMountID(struct file *file, void __user *argp) +{ + struct dentry *dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + int cpRes; + + Node* localNode = App_getLocalNode(app); + NodeString mountID; + size_t mountIDStrLen; + Node_copyAlias(localNode, &mountID); + mountIDStrLen = strlen(mountID.buf); + + if(unlikely(mountIDStrLen > BEEGFS_IOCTL_MOUNTID_BUFLEN) ) + { + Logger_logFormatted(log, Log_WARNING, logContext, + "unexpected: mountID too large for buffer (%d vs %d)", + (int)BEEGFS_IOCTL_MOUNTID_BUFLEN, (int)mountIDStrLen+1); + return -ENOBUFS; + } + + cpRes = copy_to_user(argp, mountID.buf, mountIDStrLen+1); // (also calls access_ok) + if(cpRes) + { // result >0 is number of uncopied bytes + LOG_DEBUG_FORMATTED(log, Log_WARNING, logContext, "copy_to_user failed()"); + + return -EINVAL; + } + + return 0; +} + +/** + * Get stripe info of a file (chunksize etc.). + */ +static long FhgfsOpsIoctl_getStripeInfo(struct file *file, void __user *argp) +{ + struct dentry* dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + struct inode* inode = file_inode(file); + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + struct BeegfsIoctl_GetStripeInfo_Arg __user *getInfoArg = + (struct BeegfsIoctl_GetStripeInfo_Arg*)argp; + StripePattern* pattern = FhgfsInode_getStripePattern(fhgfsInode); + + uint16_t numTargets; + int cpRes; + + if(S_ISDIR(inode->i_mode) ) + { // directories have no patterns attached + return -EISDIR; + } + + if(!pattern) + { // sanity check, should never happen (because file pattern is set during file open) + Logger_logFormatted(log, Log_DEBUG, logContext, + "Given file handle has no stripe pattern attached"); + return -EINVAL; + } + + + // set pattern type + + cpRes = put_user(StripePattern_getPatternType(pattern), &getInfoArg->outPatternType); + if(cpRes) + return -EFAULT; + + // set chunksize + + cpRes = put_user(StripePattern_getChunkSize(pattern), &getInfoArg->outChunkSize); + if(cpRes) + return -EFAULT; + + // set number of stripe targets + + numTargets = UInt16Vec_length(pattern->getStripeTargetIDs(pattern) ); + + cpRes = put_user(numTargets, &getInfoArg->outNumTargets); + if(cpRes) + return -EFAULT; + + return 0; +} + +static long resolveNodeToString(NodeStoreEx* nodes, uint32_t nodeID, + char __user* nodeStrID, Logger* log) +{ + NodeString nodeAlias; + NumNodeID numID = { nodeID }; + size_t nodeIDLen; + long retVal = 0; + + Node* node = NodeStoreEx_referenceNode(nodes, numID); + + if (!node) + { + // node not found in store: set empty string as result + if (copy_to_user(nodeStrID, "", 1)) + { + LOG_DEBUG_FORMATTED(log, Log_DEBUG, __func__, "copy_to_user failed()"); + return -EINVAL; + } + + return 0; + } + + Node_copyAlias(node, &nodeAlias); + nodeIDLen = strlen(nodeAlias.buf) + 1; + if (nodeIDLen > BEEGFS_IOCTL_NODEALIAS_BUFLEN) + { // nodeID too large for buffer + retVal = -ENOBUFS; + goto out; + } + + if (copy_to_user(nodeStrID, nodeAlias.buf, nodeIDLen)) + retVal = -EFAULT; + +out: + Node_put(node); + + return retVal; +} + +static long getStripePatternImpl(struct file* file, uint32_t targetIdx, + uint32_t* targetOrGroup, uint32_t* primaryTarget, uint32_t* secondaryTarget, + uint32_t* primaryNodeID, uint32_t* secondaryNodeID, + char __user* primaryNodeStrID, char __user* secondaryNodeStrID) +{ + struct dentry* dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + TargetMapper* targetMapper = App_getTargetMapper(app); + NodeStoreEx* storageNodes = App_getStorageNodes(app); + + struct inode* inode = dentry->d_inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + StripePattern* pattern = FhgfsInode_getStripePattern(fhgfsInode); + + long retVal = 0; + + size_t numTargets; + + // directories have no patterns attached + if (S_ISDIR(inode->i_mode)) + return -EISDIR; + + if (!pattern) + { // sanity check, should never happen (because file pattern is set during file open) + Logger_logFormatted(log, Log_DEBUG, logContext, + "Given file handle has no stripe pattern attached"); + return -EINVAL; + } + + // check if wanted target index is valid + numTargets = UInt16Vec_length(pattern->getStripeTargetIDs(pattern)); + if (targetIdx >= numTargets) + return -EINVAL; + + // set targetID + *targetOrGroup = UInt16Vec_at(pattern->getStripeTargetIDs(pattern), targetIdx); + + // resolve buddy group if necessary + if (pattern->patternType == STRIPEPATTERN_BuddyMirror) + { + *primaryTarget = MirrorBuddyGroupMapper_getPrimaryTargetID(app->storageBuddyGroupMapper, + *targetOrGroup); + *secondaryTarget = MirrorBuddyGroupMapper_getSecondaryTargetID(app->storageBuddyGroupMapper, + *targetOrGroup); + } + else + { + *primaryTarget = 0; + *secondaryTarget = 0; + } + + // resolve targets to nodes + *primaryNodeID = TargetMapper_getNodeID(targetMapper, + *primaryTarget ? *primaryTarget : *targetOrGroup).value; + *secondaryNodeID = *secondaryTarget + ? TargetMapper_getNodeID(targetMapper, *secondaryTarget).value + : 0; + + // resolve node ID strings + retVal = resolveNodeToString(storageNodes, *primaryNodeID, primaryNodeStrID, log); + if (retVal) + goto out; + + if (secondaryNodeStrID && *secondaryNodeID) + retVal = resolveNodeToString(storageNodes, *secondaryNodeID, secondaryNodeStrID, log); + +out: + return retVal; +} + +/** + * Get stripe target of a file (index-based). + */ +static long FhgfsOpsIoctl_getStripeTarget(struct file *file, void __user *argp) +{ + struct BeegfsIoctl_GetStripeTarget_Arg __user* arg = argp; + + uint32_t targetOrGroup; + uint32_t primaryTarget; + uint32_t secondaryTarget; + uint32_t primaryNodeID; + uint32_t secondaryNodeID; + + uint16_t wantedTargetIndex; + + long retVal = 0; + + if (get_user(wantedTargetIndex, &arg->targetIndex)) + return -EFAULT; + + retVal = getStripePatternImpl(file, wantedTargetIndex, &targetOrGroup, &primaryTarget, + &secondaryTarget, &primaryNodeID, &secondaryNodeID, arg->outNodeAlias, NULL); + if (retVal) + return retVal; + + if (put_user(targetOrGroup, &arg->outTargetNumID)) + return -EFAULT; + + if (put_user(primaryNodeID, &arg->outNodeNumID)) + return -EFAULT; + + return 0; +} + +static long FhgfsOpsIoctl_getStripeTargetV2(struct file *file, void __user *argp) +{ + struct BeegfsIoctl_GetStripeTargetV2_Arg __user* arg = argp; + + uint32_t targetOrGroup; + uint32_t primaryTarget; + uint32_t secondaryTarget; + uint32_t primaryNodeID; + uint32_t secondaryNodeID; + + uint32_t wantedTargetIndex; + + long retVal = 0; + + if (get_user(wantedTargetIndex, &arg->targetIndex)) + return -EFAULT; + + retVal = getStripePatternImpl(file, wantedTargetIndex, &targetOrGroup, &primaryTarget, + &secondaryTarget, &primaryNodeID, &secondaryNodeID, arg->primaryNodeAlias, + arg->secondaryNodeAlias); + if (retVal) + return retVal; + + if (put_user(targetOrGroup, &arg->targetOrGroup)) + return -EFAULT; + + if (put_user(primaryTarget, &arg->primaryTarget)) + return -EFAULT; + + if (put_user(secondaryTarget, &arg->secondaryTarget)) + return -EFAULT; + + if (put_user(primaryNodeID, &arg->primaryNodeID)) + return -EFAULT; + + if (put_user(secondaryNodeID, &arg->secondaryNodeID)) + return -EFAULT; + + return 0; +} + +/** + * Create a new regular file with stripe hints (chunksize, numtargets). + * + * @param file parent directory of the new file + */ +long FhgfsOpsIoctl_mkfileWithStripeHints(struct file *file, void __user *argp) +{ + struct dentry* dentry = file_dentry(file); + struct inode* parentInode = file_inode(file); + FhgfsInode* fhgfsParentInode = BEEGFS_INODE(parentInode); + + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + const int umask = current_umask(); + + struct BeegfsIoctl_MkFileWithStripeHints_Arg __user *mkfileArg = + (struct BeegfsIoctl_MkFileWithStripeHints_Arg*)argp; + + long retVal; + + const char __user* userFilename; + char* filename; + int mode; // mode (permission) of the new file + unsigned numtargets; // number of desired targets, 0 for directory default + unsigned chunksize; // must be 64K or multiple of 64K, 0 for directory default + + int cpRes; + const EntryInfo* parentEntryInfo; + FhgfsOpsErr mkRes; + + struct FileEvent event = FILEEVENT_EMPTY; + struct FileEvent* eventSent = NULL; + + struct CreateInfo createInfo = + { + .preferredStorageTargets = NULL, + .preferredMetaTargets = NULL + }; + +#ifdef KERNEL_HAS_FILE_F_VFSMNT + struct vfsmount* mnt = file->f_vfsmnt; +#else + struct vfsmount* mnt = file->f_path.mnt; +#endif + + Logger_logFormatted(log, Log_SPAM, logContext, "Create file from ioctl"); + + if(!S_ISDIR(parentInode->i_mode) ) + { // given inode does not refer to a directory + return -ENOTDIR; + } + + retVal = os_generic_permission(parentInode, MAY_WRITE | MAY_EXEC); + if (retVal) + return retVal; + + // copy mode + + cpRes = get_user(mode, &mkfileArg->mode); + if(cpRes) + return -EFAULT; + + // make sure we only use permissions bits of given mode and set regular file as format bit + mode = (mode & (S_IRWXU | S_IRWXG | S_IRWXO) ) | S_IFREG; + + // copy numtargets + + cpRes = get_user(numtargets, &mkfileArg->numtargets); + if(cpRes) + return -EFAULT; + + // copy chunksize + + cpRes = get_user(chunksize, &mkfileArg->chunksize); + if(cpRes) + return -EFAULT; + + if (get_user(userFilename, &mkfileArg->filename)) + return -EFAULT; + + if (chunksize != 0) + { // check if chunksize is valid + if(unlikely( (chunksize < STRIPEPATTERN_MIN_CHUNKSIZE) || + !MathTk_isPowerOfTwo(chunksize) ) ) + return -EINVAL; // chunksize is not a multiple of 64Ki + } + + + // check and reference mnt write counter + + retVal = mnt_want_write(mnt); + if(retVal) + return retVal; + + // copy filename + + filename = strndup_user(userFilename, BEEGFS_IOCTL_FILENAME_MAXLEN); + if(IS_ERR(filename) ) + { + retVal = PTR_ERR(filename); + goto err_cleanup_lock; + } + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + // we don't have a dentry for the target file here. we could use the dentry to the directory + // and the requested file name, but decided to not bother yet because ioctl operations are + // expected to be so rare. + FileEvent_init(&event, FileEventType_CREATE, NULL); + eventSent = &event; + } + + CreateInfo_init(app, parentInode, filename, mode, umask, true, eventSent, &createInfo); + + FhgfsInode_entryInfoReadLock(fhgfsParentInode); // L O C K EntryInfo + + parentEntryInfo = FhgfsInode_getEntryInfo(fhgfsParentInode); + + mkRes = FhgfsOpsRemoting_mkfileWithStripeHints(app, parentEntryInfo, &createInfo, + numtargets, chunksize, NULL); + + FhgfsInode_entryInfoReadUnlock(fhgfsParentInode); // U N L O C K EntryInfo + + if(mkRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(mkRes); // (note: newEntryInfo was not alloc'ed) + goto err_cleanup_filename; + } + + + retVal = 0; + +err_cleanup_filename: + // cleanup + kfree(filename); + + FileEvent_uninit(&event); + +err_cleanup_lock: + + mnt_drop_write(mnt); // release mnt write reference counter + + return retVal; +} + +/** + * create a file with special settings (such as preferred targets). + * + * @return 0 on success, negative linux error code otherwise + */ +static long FhgfsOpsIoctl_createFile(struct file *file, void __user *argp, int version) +{ + struct dentry *dentry = file_dentry(file); + struct inode *inode = file_inode(file); + + App* app = FhgfsOps_getApp(dentry->d_sb); + Logger* log = App_getLogger(app); + const char* logContext = __func__; + const int umask = current_umask(); + StoragePoolId storagePoolId; + + EntryInfo parentInfo; + struct BeegfsIoctl_MkFileV3_Arg fileInfo; + struct CreateInfo createInfo = + { + .preferredStorageTargets = NULL, + .preferredMetaTargets = NULL + }; + + int retVal = 0; + FhgfsOpsErr mkRes; + + struct FileEvent event = FILEEVENT_EMPTY; + const struct FileEvent* eventSent = NULL; + + #ifdef KERNEL_HAS_FILE_F_VFSMNT + struct vfsmount* mnt = file->f_vfsmnt; + #else + struct vfsmount* mnt = file->f_path.mnt; + #endif + + + Logger_logFormatted(log, Log_SPAM, logContext, "Create file from ioctl"); + + retVal = os_generic_permission(inode, MAY_WRITE | MAY_EXEC); + if (retVal) + return retVal; + + retVal = mnt_want_write(mnt); // check and rw-reference counter + if (retVal) + return retVal; + + + memset(&fileInfo, 0, sizeof(struct BeegfsIoctl_MkFileV3_Arg)); + + switch(version) + { + case 1: retVal = IoctlHelper_ioctlCreateFileCopyFromUser(app, argp, &fileInfo); break; + case 2: retVal = IoctlHelper_ioctlCreateFileCopyFromUserV2(app, argp, &fileInfo); break; + case 3: retVal = IoctlHelper_ioctlCreateFileCopyFromUserV3(app, argp, &fileInfo); break; + default: retVal = IoctlHelper_ioctlCreateFileCopyFromUser(app, argp, &fileInfo); break; + } + + if(retVal) + { + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, + "Copy from user of struct Fhgfs_ioctlMkFile failed"); + goto cleanup; + } + + // the actual event is initialized appropriatly later + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + eventSent = &event; + + CreateInfo_init(app, inode, fileInfo.entryName, fileInfo.mode, umask, true, eventSent, + &createInfo); + + StoragePoolId_set(&storagePoolId, fileInfo.storagePoolId); + CreateInfo_setStoragePoolId(&createInfo, storagePoolId); + + retVal = IoctlHelper_ioctlCreateFileTargetsToList(app, &fileInfo, &createInfo); // target list + if (retVal) + goto cleanup; + + // only use the provided UID and GID if we are root + /* note: this means we create the file with different uid/gid without notifying the caller. maybe + we should change that. */ + if (FhgfsCommon_getCurrentUserID() == 0 || FhgfsCommon_getCurrentGroupID() == 0) + { + createInfo.userID = fileInfo.uid; + createInfo.groupID = fileInfo.gid; + } + + if (fileInfo.parentIsBuddyMirrored) + EntryInfo_init(&parentInfo, NodeOrGroup_fromGroup(fileInfo.ownerNodeID), + fileInfo.parentParentEntryID, fileInfo.parentEntryID, fileInfo.parentName, + DirEntryType_DIRECTORY, STATFLAG_HINT_INLINE); + else + { + NumNodeID nodeID; + NumNodeID_set(&nodeID, fileInfo.ownerNodeID); + EntryInfo_init(&parentInfo, NodeOrGroup_fromNode(nodeID), + fileInfo.parentParentEntryID, fileInfo.parentEntryID, fileInfo.parentName, + DirEntryType_DIRECTORY, STATFLAG_HINT_INLINE); + } + + switch (fileInfo.fileType) + { + case DT_REG: + { + // we don't have a dentry for the target file here. we could use the dentry to the directory + // and the requested file name, but decided to not bother yet because ioctl operations are + // expected to be so rare. + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + FileEvent_init(&event, FileEventType_CREATE, NULL); + + down_read(&inode->i_sb->s_umount); + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "Going to create file %s", + createInfo.entryName); + + mkRes = FhgfsOpsRemoting_mkfile(app, &parentInfo, &createInfo, NULL); + if(mkRes != FhgfsOpsErr_SUCCESS) + { // failure (note: no need to care about newEntryInfo, it was not allocated) + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "mkfile failed"); + + retVal = FhgfsOpsErr_toSysErr(mkRes); + } + + up_read(&inode->i_sb->s_umount); + + FileEvent_uninit(&event); + } break; + + case DT_LNK: + { + EntryInfo newEntryInfo; // only needed as mandatory argument + + if (!fileInfo.symlinkTo) + { + retVal = -EINVAL; + goto cleanup; + } + + if (app->cfg->eventLogMask & EventLogMask_LINK_OP) + { + event.eventType = FileEventType_SYMLINK; + FileEvent_setTargetStr(&event, fileInfo.symlinkTo); + } + + down_read(&inode->i_sb->s_umount); + + // destroys &event + mkRes = FhgfsOpsHelper_symlink(app, &parentInfo, fileInfo.symlinkTo, + &createInfo, &newEntryInfo); + + if(mkRes != FhgfsOpsErr_SUCCESS) + { // failed (note: no need to care about newEntryInfo, it was not allocated) + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "mkfile failed"); + + retVal = FhgfsOpsErr_toSysErr(mkRes); + } + else + EntryInfo_uninit(&newEntryInfo); // unit newEntryInfo, we don't need it + + up_read(&inode->i_sb->s_umount); + } break; + + default: + { + // unsupported file type + LOG_DEBUG_FORMATTED(log, Log_NOTICE, logContext, "Unsupported file type: %d", + fileInfo.fileType); + retVal = -EINVAL; + goto cleanup; + } break; + } + +cleanup: + SAFE_KFREE(fileInfo.parentParentEntryID); + SAFE_KFREE(fileInfo.parentEntryID); + SAFE_KFREE(fileInfo.parentName); + SAFE_KFREE(fileInfo.entryName); + SAFE_KFREE(fileInfo.symlinkTo); + SAFE_KFREE(fileInfo.prefTargets); + if (createInfo.preferredStorageTargets && + createInfo.preferredStorageTargets != App_getPreferredStorageTargets(app) ) + { + UInt16List_uninit(createInfo.preferredStorageTargets); + SAFE_KFREE(createInfo.preferredStorageTargets); + } + if (createInfo.preferredMetaTargets && + createInfo.preferredMetaTargets != App_getPreferredMetaNodes(app) ) + { + UInt16List_uninit(createInfo.preferredMetaTargets); + SAFE_KFREE(createInfo.preferredMetaTargets); + } + + mnt_drop_write(mnt); // release the rw-reference counter + + return retVal; +} + +static long FhgfsOpsIoctl_getInodeID(struct file *file, void __user *argp) +{ + struct BeegfsIoctl_GetInodeID_Arg __user *getInodeIDArg = argp; + + struct super_block* superBlock = file_dentry(file)->d_sb; + Logger* log = App_getLogger(FhgfsOps_getApp(superBlock)); + uint64_t inodeID; + + char entryID[BEEGFS_IOCTL_ENTRYID_MAXLEN + 1]; + + if (copy_from_user(entryID, &getInodeIDArg->entryID, sizeof(entryID))) + { + Logger_logFormatted(log, Log_DEBUG, __func__, + "Copying entryID from userspace memory failed."); + return -EFAULT; + } + + inodeID = FhgfsInode_generateInodeID(superBlock, + entryID, strnlen(entryID, sizeof(entryID))); + + if (put_user(inodeID, &getInodeIDArg->inodeID)) + { + Logger_logFormatted(log, Log_DEBUG, __func__, + "Copying inodeID to userspace memory failed."); + return -EFAULT; + } + + return 0; +} + +static long FhgfsOpsIoctl_pingNode(struct file *file, void __user *argp) +{ + struct super_block* superBlock = file_dentry(file)->d_sb; + App* app = FhgfsOps_getApp(superBlock); + Logger* log = App_getLogger(app); + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + NodeStoreEx* nodeStore; + Node* node; + NodeString alias; + NodeConnPool* connPool; + Socket* sock = NULL; + struct BeegfsIoctl_PingNode_Arg __user *pingArg = argp; + struct BeegfsIoctl_PingNode_Arg ping; + NumNodeID nodeId; + long rc; + int i; + NodeType nodeType; + + if (copy_from_user(&ping.params, &pingArg->params, sizeof(ping.params))) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Copying ping arguments from userspace memory failed."); + return -EFAULT; + } + + ping.params.nodeType[BEEGFS_IOCTL_NODETYPE_BUFLEN - 1] = 0; + Logger_logFormatted(log, Log_DEBUG, __func__, "nodeId: %u nodeType: %s count=%u", + ping.params.nodeId, ping.params.nodeType, ping.params.count); + + memset(&ping.results, 0, sizeof(ping.results)); + + if (ping.params.count > BEEGFS_IOCTL_PING_MAX_COUNT) + { + Logger_logFormatted(log, Log_ERR, __func__, + "count too high, max is %d", BEEGFS_IOCTL_PING_MAX_COUNT); + return -EINVAL; + } + + if (ping.params.interval > BEEGFS_IOCTL_PING_MAX_INTERVAL) + { + Logger_logFormatted(log, Log_ERR, __func__, + "interval too high, max is %d", BEEGFS_IOCTL_PING_MAX_INTERVAL); + return -EINVAL; + } + + NumNodeID_set(&nodeId, ping.params.nodeId); + nodeType = FhgfsOpsIoctl_strToNodeType(ping.params.nodeType, sizeof(ping.params.nodeType)); + switch (nodeType) + { + case NODETYPE_Meta: + nodeStore = App_getMetaNodes(app); + break; + case NODETYPE_Storage: + nodeStore = App_getStorageNodes(app); + break; + case NODETYPE_Mgmt: + nodeStore = App_getMgmtNodes(app); + break; + default: + Logger_logFormatted(log, Log_ERR, __func__, + "Invalid nodetype: %s", ping.params.nodeType); + return -EINVAL; + } + + node = NodeStoreEx_referenceNode(nodeStore, nodeId); + if (node == NULL) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Node not found. nodeId: %u nodeType: %s", ping.params.nodeId, ping.params.nodeType); + return -EINVAL; + } + + Node_copyAlias(node, &alias); + StringTk_strncpyTerminated(ping.results.outNode, alias.buf, + sizeof(ping.results.outNode)); + + connPool = Node_getConnPool(node); + + rc = 0; + + for (i = 0; i <= ping.params.count; ++i) + { + HeartbeatMsgEx* rspMsgEx; + char* respBuf = NULL; + NetMessage* respMsg = NULL; + HeartbeatRequestMsgEx msg; + FhgfsOpsErr requestRes; + Time startTime; + Time endTime; + NumNodeID resNodeID; + int resNodeType; + + if (i > 0) + msleep(ping.params.interval); + + if (sock == NULL) + { + sock = NodeConnPool_acquireStreamSocket(connPool); + if (sock == NULL) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Null socket from connPool"); + rc = -EINVAL; + goto next_iter; + } + + } + + HeartbeatRequestMsgEx_init(&msg); + Time_init(&startTime); + requestRes = MessagingTk_requestResponseSock(app, node, (NetMessage*)&msg, NETMSGTYPE_Heartbeat, &respBuf, &respMsg, sock); + Time_init(&endTime); + + if (requestRes != FhgfsOpsErr_SUCCESS) + { + NodeConnPool_invalidateStreamSocket(connPool, sock); + sock = NULL; + ping.results.outErrors++; + goto next_iter; + } + + // Skip the first one because it may have connection overhead. + if (i == 0) + goto next_iter; + + + rspMsgEx = (HeartbeatMsgEx*) respMsg; + resNodeID = HeartbeatMsgEx_getNodeNumID(rspMsgEx); + resNodeType = HeartbeatMsgEx_getNodeType(rspMsgEx); + + if (!NumNodeID_compare(&resNodeID, &nodeId)) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Wrong node ID returned from heartbeat: %s", NumNodeID_str(&resNodeID)); + rc = -EPROTO; + } + else if (resNodeType != (int) nodeType) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Wrong node type returned from heartbeat: %d", resNodeType); + rc = -EPROTO; + } + else + { + unsigned elapsed = Time_elapsedSinceNS(&endTime, &startTime); + ping.results.outSuccess++; + ping.results.outPingTime[i - 1] = elapsed; + ping.results.outTotalTime += elapsed; + StringTk_strncpyTerminated(ping.results.outPingType[i - 1], + NIC_nicTypeToString(Socket_getSockType(sock)), + sizeof(ping.results.outPingType[i - 1])); + } + + next_iter: + if (respMsg) + NETMESSAGE_FREE(respMsg); + if (respBuf) + NoAllocBufferStore_addBuf(bufStore, respBuf); + + if (rc != 0) + break; + } + if (sock != NULL) + NodeConnPool_releaseStreamSocket(connPool, sock); + Node_put(node); + if (copy_to_user(&pingArg->results, &ping.results, sizeof(ping.results))) + { + Logger_logFormatted(log, Log_ERR, __func__, + "Copying ping results to userspace memory failed."); + rc = -EFAULT; + } + + return rc; +} + +/** + * Get EntryInfo data for given file. + * + * @return 0 on success, negative linux error code otherwise + */ +static long FhgfsOpsIoctl_getEntryInfo(struct file *file, void __user *argp) +{ + struct BeegfsIoctl_GetEntryInfo_Arg __user *arg = argp; + struct dentry* fileDentry = file_dentry(file); + + App* app = FhgfsOps_getApp(fileDentry->d_sb); + Logger* log = App_getLogger(app); + + FhgfsInode* beegfsInode = BEEGFS_INODE(fileDentry->d_inode); + + const EntryInfo* entryInfo; + + FhgfsInode_entryInfoReadLock(beegfsInode); + + entryInfo = FhgfsInode_getEntryInfo(beegfsInode); + + + if (put_user(EntryInfo_getOwner(entryInfo), &arg->ownerID)) + goto fail; + + if (copy_to_user(arg->parentEntryID, EntryInfo_getParentEntryID(entryInfo), + strnlen(entryInfo->parentEntryID, BEEGFS_IOCTL_ENTRYID_MAXLEN) + 1)) + goto fail; + + if (copy_to_user(arg->entryID, EntryInfo_getEntryID(entryInfo), + strnlen(entryInfo->entryID, BEEGFS_IOCTL_ENTRYID_MAXLEN) + 1)) + goto fail; + + if (put_user(entryInfo->entryType, &arg->entryType)) + goto fail; + + if (put_user(entryInfo->featureFlags, &arg->featureFlags)) + goto fail; + + FhgfsInode_entryInfoReadUnlock(beegfsInode); + return 0; + +fail: + FhgfsInode_entryInfoReadUnlock(beegfsInode); + Logger_logFormatted(log, Log_DEBUG, __func__, "Copying entryInfo to userspace memory failed."); + return -EFAULT; +} diff --git a/client_module/source/filesystem/FhgfsOpsIoctl.h b/client_module/source/filesystem/FhgfsOpsIoctl.h new file mode 100644 index 0000000..56fe8a1 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsIoctl.h @@ -0,0 +1,31 @@ +#ifndef FHGFSOPS_IOCTL_H +#define FHGFSOPS_IOCTL_H + +#include +#include + +#include +#include + +#ifdef FS_IOC_GETVERSION + #define BEEGFS_IOC_GETVERSION_OLD FS_IOC_GETVERSION // predefined in linux/fs.h +#else // old kernels didn't have FS_IOC_GETVERSION + #define BEEGFS_IOC_GETVERSION_OLD _IOR('v', BEEGFS_IOCNUM_GETVERSION_OLD, long) +#endif + +#ifdef CONFIG_COMPAT + #ifdef FS_IOC32_GETVERSION + #define BEEGFS_IOC32_GETVERSION_OLD FS_IOC32_GETVERSION // predefined in linux/fs.h + #else // old kernels didn't have FS_IOC32_GETVERSION + #define BEEGFS_IOC32_GETVERSION_OLD _IOR('v', BEEGFS_IOCNUM_GETVERSION_OLD, int) + #endif +#endif + + +extern long FhgfsOpsIoctl_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +#ifdef CONFIG_COMPAT + extern long FhgfsOpsIoctl_compatIoctl(struct file *file, unsigned int cmd, unsigned long arg); +#endif + + +#endif // FHGFSOPS_IOCTL_H diff --git a/client_module/source/filesystem/FhgfsOpsPages.c b/client_module/source/filesystem/FhgfsOpsPages.c new file mode 100644 index 0000000..05cad98 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsPages.c @@ -0,0 +1,1216 @@ +/* + * fhgfs page cache methods + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include "FhgfsOpsDir.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsHelper.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsPages.h" +#include "FhgfsOpsSuper.h" +#include "FhgfsOps_versions.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_COMPAT +#include +#endif + +#define INITIAL_FIND_PAGES (16) // search initially for this number of pages + +#define FHGFSOPSPAGES_pageVecListCacheName BEEGFS_MODULE_NAME_STR "-pageListVec" +#define BEEGFS_PAGE_VEC_LIST_POOL_SIZE 8 // number of reserve page list-vecs + + +struct FhgfsPageData; +typedef struct FhgfsPageData FhgfsPageData; + + +#if ((INITIAL_FIND_PAGES) > (BEEGFS_MAX_PAGE_LIST_SIZE)) + #error // trigger a compilation error as we would end up with memory corruption in real live +#endif + + +static struct kmem_cache* FhgfsOpsPages_pageListVecCache = NULL; +static mempool_t* FhgfsOpsPages_pageListVecPool = NULL; + +// forward declarations +struct fhgfsWritePageHelper; +typedef struct fhgfsWritePageHelper fhgfsWrPgHelper; + + + +static int _FhgfsOpsPages_sendPageVec(FhgfsPageData* pageData, + struct inode* inode, bool isFinal, Fhgfs_RWType rwType); +static FhgfsChunkPageVec* _FhgfsOpsPages_allocNewPageVec(FhgfsPageData* pageData); +static inline FhgfsOpsErr _FhgfsOpsPages_referenceReadFileHandle(FhgfsPageData* writePageData, + struct file* file); +static inline FhgfsOpsErr _FhgfsOpsPages_referenceWriteFileHandle(FhgfsPageData* writePageData); +static FhgfsOpsErr _FhgfsOpsPages_referenceFileHandle(FhgfsPageData* writePageData, + unsigned openFlags); + +static int _FhgfsOpsPages_writepages(struct address_space* mapping, struct writeback_control* wbc, + struct page* page); +#ifdef KERNEL_HAS_FOLIO +int _FhgfsOpsPages_readahead(struct readahead_control *ractl, struct page* page); +#else +int _FhgfsOpsPages_readpages(struct file* file, struct address_space* mapping, + struct list_head* pageList, struct page* page); +#endif +#ifdef KERNEL_WRITEPAGE_HAS_FOLIO +static int FhgfsOpsPages_writePageCallBack(struct folio *folio, struct writeback_control *wbc, + void *data); +#else +static int FhgfsOpsPages_writePageCallBack(struct page *page, struct writeback_control *wbc, + void *data); +#endif +static int FhgfsOpsPages_readPageCallBack(void *dataPtr, struct page *page); + + +/** + * A struct with variables to be exchanged between fhgfs writepages functions + */ +struct FhgfsPageData +{ + struct inode* inode; + + FhgfsChunkPageVec *chunkPageVec; + + bool isReferenced; + FileHandleType handleType; + RemotingIOInfo ioInfo; +}; + + +/** + * Initialize the pageListVecCache and pageListVec mempool + */ +bool FhgfsOpsPages_initPageListVecCache(void) +{ + size_t cacheSize = sizeof(FhgfsPageListVec); + + FhgfsOpsPages_pageListVecCache = + OsCompat_initKmemCache(FHGFSOPSPAGES_pageVecListCacheName, cacheSize, NULL); + + // create a kmem PageVecList allocation cache + if (!FhgfsOpsPages_pageListVecCache) + return false; + + FhgfsOpsPages_pageListVecPool = mempool_create(BEEGFS_PAGE_VEC_LIST_POOL_SIZE, + mempool_alloc_slab, mempool_free_slab, FhgfsOpsPages_pageListVecCache); + + // create a mempool as last reserve for the PageVecList allocation cache + if (!FhgfsOpsPages_pageListVecPool) + { + kmem_cache_destroy(FhgfsOpsPages_pageListVecCache); + FhgfsOpsPages_pageListVecCache = NULL; + + return false; + } + + return true; +} + +/** + * Destroy the pageListVecCache and pageListVec mempool + */ +void FhgfsOpsPages_destroyPageListVecCache(void) +{ + // first destroy the pool, then the cache, as the pool uses cached objects + if (FhgfsOpsPages_pageListVecPool) + { + mempool_destroy(FhgfsOpsPages_pageListVecPool); + FhgfsOpsPages_pageListVecPool = NULL; + } + + if (FhgfsOpsPages_pageListVecCache) + { + kmem_cache_destroy(FhgfsOpsPages_pageListVecCache); + FhgfsOpsPages_pageListVecCache = NULL; + } +} + + +/** + * If the meta server has told us for some reason a wrong file-size (i_size) the caller would + * wrongly discard data beyond i_size. So we are going to correct i_size here. + * + * This could be removed once we are sure the meta server *always* has the correct i_size + * (which might never be the case, due to concurrent writes and truncates). + * + * Note: This is the non-inlined version. Only call it from + * FhgfsOpsPages_incInodeFileSizeOnPagedRead() + */ +void __FhgfsOpsPages_incInodeFileSizeOnPagedRead(struct inode* inode, loff_t offset, size_t readRes) +{ + App* app = FhgfsOps_getApp(inode->i_sb); + Logger* log = App_getLogger(app); + const char* logContext = "Paged-read"; + loff_t i_size; + + FhgfsIsizeHints iSizeHints; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + const EntryInfo* entryInfo = FhgfsInode_getEntryInfo(fhgfsInode); + + /* Refresh the inode first, hopefully that is sufficient + * Note: The inode MUST NOT be flused from this functions, as the caller hold locked pages. + * But on flushing the inode, mm/vfs will wait for page unlocks - it would deadlock! + */ + __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, true); + + i_size = i_size_read(inode); + + if (unlikely(readRes && (offset + (loff_t)readRes > i_size) ) ) + { // _refreshInode was not sufficient, force a meta-update + FhgfsOpsErr refreshRes = FhgfsOpsRemoting_refreshEntry(app, entryInfo); + + if (refreshRes != FhgfsOpsErr_SUCCESS) + { + Logger_logErr(log, logContext, "Meta Refresh failed."); + } + + // again try to refresh the inode, again the inode must not be flushed to avoid deadlocks + __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, true); + + + /* note on i_lock/i_size: make sure we only increase i_size and do not decrease it + (e.g. in case we're racing with a concurrent writer at a higher offset) */ + spin_lock(&inode->i_lock); // L O C K + + i_size = i_size_read(inode); + + if (unlikely(offset + (loff_t)readRes > i_size) ) + { // All attempts to update the remote inode size to the position that we read failed. + + /* i_size_write() sugguests to lock i_mutex, but we might end up here from write_begin() + * (FhgfsOps_write_begin), which aready has i_mutex locked. The more common callers + * readpages() and readpage() do not have i_mutex, though. */ + + i_size_write(inode, offset + readRes); + + spin_unlock(&inode->i_lock); // U N L O C K + + /* note: this situation can also be "normal" with a concurrent trunc, so we have to be + careful regarding user warnings and error return values. */ + Logger_logFormatted(log, Log_DEBUG, logContext, "Failed to increase MDS inode size to " + "the expected value. (Application might read less data than expected or file was " + "truncated during read operation. Expected size: %lld isSize: %lld)", + offset + (loff_t)readRes, i_size); + } + else + spin_unlock(&inode->i_lock); // U N L O C K + } + + Logger_logFormatted(log, Log_DEBUG, logContext, + "EntryID: %s Correcting inode size from %lld to %lld", + entryInfo->entryID, i_size, offset + readRes); +} + +/** + * Just a small wrapper to write a pageVec + * + * Note: no-op if writePageData->chunkPageVec is NULL or chunkPageVec has a size of 0 + * + * Note: Will destroy writePageData->pageVec and create a new pageVec + * + * @param isFinalWrite If true no new pageVec will be allocated + * + * @return 0 on success, negative linux error code on error + */ +int _FhgfsOpsPages_sendPageVec(FhgfsPageData* pageData, struct inode* inode, bool isFinal, + Fhgfs_RWType rwType) +{ + const char* logContext = __func__; + App* app = FhgfsOps_getApp(inode->i_sb); + + int retVal = 0; + bool queueSuccess; + + if (!pageData->chunkPageVec) + return retVal; // nothing to do + + if (FhgfsChunkPageVec_getSize(pageData->chunkPageVec) == 0) + { // pageVec is empty + if (isFinal) + { + FhgfsChunkPageVec_destroy(pageData->chunkPageVec); + pageData->chunkPageVec = NULL; + } + + return retVal; + } + + queueSuccess = RWPagesWork_createQueue(app, pageData->chunkPageVec, inode, rwType); + pageData->chunkPageVec = NULL; + + if (unlikely(!queueSuccess) ) + { + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, "Creating the async queue failed"); + + retVal = -ENOMEM; + goto out; + } + + if (!isFinal) + { + FhgfsChunkPageVec* newPageVec; + // allocate the next chunkPageVec + newPageVec = _FhgfsOpsPages_allocNewPageVec(pageData); + + if (unlikely(!newPageVec) ) + retVal = -ENOMEM; + } + +out: + return retVal; +} + +/** + * Just allocate a pageVector + */ +FhgfsChunkPageVec* _FhgfsOpsPages_allocNewPageVec(FhgfsPageData* pageData) +{ + struct inode* inode = pageData->inode; + App* app = FhgfsOps_getApp(inode->i_sb); + unsigned chunkPages = RemotingIOInfo_getNumPagesPerChunk(&pageData->ioInfo); + FhgfsChunkPageVec* newPageVec; + + newPageVec = FhgfsChunkPageVec_create(app, inode, FhgfsOpsPages_pageListVecCache, + FhgfsOpsPages_pageListVecPool ,chunkPages); + + pageData->chunkPageVec = newPageVec; // assign new pagevec + + return newPageVec; +} + +FhgfsOpsErr _FhgfsOpsPages_referenceReadFileHandle(FhgfsPageData* writePageData, struct file* file) +{ + FsFileInfo* fileInfo = __FhgfsOps_getFileInfo(file); + unsigned openFlags = FsFileInfo_getAccessFlags(fileInfo) & OPENFILE_ACCESS_MASK_RW; + + return _FhgfsOpsPages_referenceFileHandle(writePageData, openFlags); +} + +FhgfsOpsErr _FhgfsOpsPages_referenceWriteFileHandle(FhgfsPageData* writePageData) +{ + unsigned openFlags = OPENFILE_ACCESS_WRITE; + + return _FhgfsOpsPages_referenceFileHandle(writePageData, openFlags); +} + +/** + * Reference the file if not already referenced and get the handleType + * + * @param file may be NULL (from writepages) + */ +FhgfsOpsErr _FhgfsOpsPages_referenceFileHandle(FhgfsPageData* writePageData, unsigned openFlags) +{ + const char* logContext = "OpsPages_writeReferenceFileHandle"; + FhgfsOpsErr referenceRes; + + struct inode* inode = writePageData->inode; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + if (writePageData->isReferenced) + return FhgfsOpsErr_SUCCESS; // already referenced, just return + + /* we will never truncate the file here, so referenceHandle will not send an event. with + * no event to send, we don't need to supply a dentry. */ + referenceRes = FhgfsInode_referenceHandle(fhgfsInode, NULL, openFlags, true, NULL, + &(writePageData->handleType), NULL); + + if (referenceRes != FhgfsOpsErr_SUCCESS) + { // failure + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "Referencing the file handle failed! Error: %s", FhgfsOpsErr_toErrString(referenceRes) ); + } + else + { // success + FhgfsInode_getRefIOInfo(fhgfsInode, writePageData->handleType, + FhgfsInode_handleTypeToOpenFlags(writePageData->handleType), &writePageData->ioInfo); + + writePageData->isReferenced = true; + } + + return referenceRes; +} + + +/** + * Callback for the mm/vfs write_cache_pages function. + * + * Collect the given pages into data->pageVec. If data->pageVec cannot take more pages + * (chunk is full) a write request will be send immediately + * + * @return 0 on success, negative linux error code on error + */ +#ifdef KERNEL_WRITEPAGE_HAS_FOLIO +int FhgfsOpsPages_writePageCallBack(struct folio *folio, struct writeback_control *wbc, void *dataPtr) +{ + struct page *page = &folio->page; +#else +int FhgfsOpsPages_writePageCallBack(struct page *page, struct writeback_control *wbc, void *dataPtr) +{ +#endif + const char* logContext = __func__; + int retVal = 0; + + FhgfsPageData* writePageData = (FhgfsPageData*) dataPtr; + + struct inode* inode = writePageData->inode; + + loff_t fileSize = i_size_read(inode); + pgoff_t endIndex = fileSize >> PAGE_SHIFT; + + int usedPageLen; + + const bool finalWrite = false; /* When called from this callBack method, finalWrite + * is always false. */ + + bool pageVecWasSent = false; + + int referenceRes = _FhgfsOpsPages_referenceWriteFileHandle(writePageData); + + + if (referenceRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(referenceRes); + goto outWriteErr; + } + + // note, only allocate the pageVec after referencing the file! + if (!writePageData->chunkPageVec) + { + FhgfsChunkPageVec* pageVec = _FhgfsOpsPages_allocNewPageVec(writePageData); + if (unlikely(!pageVec) ) + { + printk_fhgfs_debug(KERN_INFO, "%s:%d ENOMEM\n", __func__, __LINE__); + retVal = -ENOMEM; + goto outAgain; + } + } + + if (page->index < endIndex) + /* in this case, the page is within the limits of the file */ + usedPageLen = PAGE_SIZE; + else + { // the page does not entirely fit into the file size limit + IGNORE_UNUSED_VARIABLE(logContext); + + usedPageLen = fileSize & ~PAGE_MASK; + + if (page->index > endIndex || !usedPageLen) + { // Page is outside the file size limit, probably truncate in progess, ignore this page + int writeRes; + + #ifdef BEEGFS_DEBUG + { + Logger_logFormattedWithEntryID(inode, Log_NOTICE, logContext, + "Page outside file size limit. file-size: %llu page-offset: %llu, usedPageLen: %d " + "pg-Idx: %lu endIdx: %lu", + fileSize, page_offset(page), usedPageLen, page->index, endIndex); + } + #endif + + writeRes = _FhgfsOpsPages_sendPageVec(writePageData, inode, finalWrite, + BEEGFS_RWTYPE_WRITE); + if (unlikely(writeRes) ) + { + + retVal = writeRes; + goto outWriteErr; + } + + // set- and end page-writeback to remove the page from dirty-page-tree + set_page_writeback(page); + end_page_writeback(page); + + // invalidate the page (for reads) as there is a truncate in process + ClearPageUptodate(page); + + goto outUnlock; // don't re-dirty the page to avoid further write attempts + } + } + + + while(1) // repeats only once, until pageVecWasSent == true + { + int pushSucces; + + pushSucces = FhgfsChunkPageVec_pushPage(writePageData->chunkPageVec, page, usedPageLen); + + if (!pushSucces) + { // pageVec probably full, send it and create a new one + int writeRes; + + if (unlikely(pageVecWasSent) ) + { /* We already send the pageVec once, no need to do it again for an empty vec. + * Probably of out memory */ + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "pageVec push failed, page-index: %ld", writePageData->chunkPageVec->firstPageIdx); + + retVal = -ENOMEM; + goto outAgain; + } + else + { + #ifdef BEEGFS_DEBUG + if (writePageData->chunkPageVec->size == 0) + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "initial push failed, index: %ld", writePageData->chunkPageVec->firstPageIdx); + #endif + } + + + writeRes = _FhgfsOpsPages_sendPageVec(writePageData, inode, finalWrite, + BEEGFS_RWTYPE_WRITE); + + pageVecWasSent = true; + + if (unlikely(writeRes) ) + { + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, "ChunkPageVec writing failed."); + + retVal = writeRes; + goto outWriteErr; + } + + if (unlikely(!writePageData->chunkPageVec) ) + { + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, "Warning, chunkPageVec is NULL."); + + goto outAgain; + } + else + { + continue; // try again to push the page + } + } + else + { // push success + } + + break; // break on success + } + + BUG_ON(PageWriteback(page)); + set_page_writeback(page); + + return retVal; + + +outWriteErr: + set_bit(AS_EIO, &page->mapping->flags); + unlock_page(page); + return retVal; + +outAgain: // redirty and unlock the page, it will be handled again + set_page_dirty(page); + +outUnlock: + unlock_page(page); + return retVal; +} + + +/** + * address_space_operations.writepages method + * + * @retVal 0 on success, otherwise negative linux error code + */ +int _FhgfsOpsPages_writepages(struct address_space* mapping, struct writeback_control* wbc, + struct page* page) +{ + struct inode* inode = mapping->host; + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + int retVal; + + FhgfsPageData pageData = + { + .inode = inode, + .chunkPageVec = NULL, + .isReferenced = false, + }; + + #ifdef LOG_DEBUG_MESSAGES + { + App* app = FhgfsOps_getApp(inode->i_sb); + struct dentry* dentry = d_find_alias(inode); // calls dget_locked + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "(nr_to_write: %ld = %lluKiB)", + wbc->nr_to_write, (long long) (wbc->nr_to_write << PAGE_SHIFT) / 1024); + + if(dentry) + dput(dentry); + } + #endif // LOG_DEBUG_MESSAGES + + + if (!page) + { // writepages + retVal = write_cache_pages(mapping, wbc, FhgfsOpsPages_writePageCallBack, &pageData); + } + else + { // Called with a single page only, so we are called from ->writepage +#ifdef KERNEL_WRITEPAGE_HAS_FOLIO + struct folio *folio = page_folio(page); + retVal = FhgfsOpsPages_writePageCallBack(folio, wbc, &pageData); +#else + retVal = FhgfsOpsPages_writePageCallBack(page, wbc, &pageData); +#endif + if (unlikely(retVal < 0) ) + { // some kind of error + if (unlikely(pageData.chunkPageVec) ) + { + printk_fhgfs(KERN_ERR, + "Bug: pageData.chunkPageVec set, but error code returned\n"); + dump_stack(); + + // try to clean it up + FhgfsChunkPageVec_destroy(pageData.chunkPageVec); + pageData.chunkPageVec = NULL; + } + } + } + + if (pageData.chunkPageVec) + { + int writeRes; + + if (unlikely(!pageData.isReferenced) ) + { // This would be a big bug and should be impossible at all! + int referenceRes; + + printk_fhgfs(KERN_ERR, "%s: Bug: File is not referenced! ", __func__); + dump_stack(); + + referenceRes = _FhgfsOpsPages_referenceWriteFileHandle(&pageData); + if (unlikely(referenceRes) != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(referenceRes); + FhgfsChunkPageVec_iterateAllHandleWritePages(pageData.chunkPageVec, referenceRes); + + FhgfsChunkPageVec_destroy(pageData.chunkPageVec); + pageData.chunkPageVec = NULL; + goto outReferenceErr; + } + } + + writeRes = _FhgfsOpsPages_sendPageVec(&pageData, inode, true, BEEGFS_RWTYPE_WRITE); + + if (unlikely(writeRes)) + retVal = writeRes; + } + + if (pageData.isReferenced) + FhgfsInode_releaseHandle(fhgfsInode, pageData.handleType, NULL); + +outReferenceErr: + #ifdef LOG_DEBUG_MESSAGES + { + App* app = FhgfsOps_getApp(inode->i_sb); + struct dentry* dentry = d_find_alias(inode); // calls dget_locked + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "retVal: %d", retVal); + + if(dentry) + dput(dentry); + } + #endif // LOG_DEBUG_MESSAGES + + return retVal; +} + +/** + * @return 0 on success, negative linux error code otherwise + * + * NOTE: page is already locked here + */ +int FhgfsOpsPages_writepage(struct page *page, struct writeback_control *wbc) +{ + // Note: this is a writeback method, so we have no file handle here! (only the inode) + + // note: make sure that write-mapping is enabled in fhgfsops_mmap!!! + + #ifdef BEEGFS_DEBUG + if(!PageUptodate(page) ) + printk_fhgfs_debug(KERN_WARNING, "%s: Bug: page not up-to-date!\n", __func__); + #endif + + return _FhgfsOpsPages_writepages(page->mapping, wbc, page); +} + +int FhgfsOpsPages_writepages(struct address_space* mapping, struct writeback_control* wbc) +{ + #ifdef LOG_DEBUG_MESSAGES + { + struct inode* inode = mapping->host; + App* app = FhgfsOps_getApp(inode->i_sb); + struct dentry* dentry = d_find_alias(inode); // calls dget_locked + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, __func__, "(nr_to_write: %ld = %lluKiB)", + wbc->nr_to_write, (long long) (wbc->nr_to_write << PAGE_SHIFT) / 1024); + + if(dentry) + dput(dentry); + } + #endif // LOG_DEBUG_MESSAGES + + return _FhgfsOpsPages_writepages(mapping, wbc, NULL); +} + + +/** + * Handle a written page + * + * Note: This must be only called once the server acknowledged it received the page + * (successful write) + * + * @param writeRes positive number means the write succeeded, negative is standard error code + */ +void FhgfsOpsPages_endWritePage(struct page* page, int writeRes, struct inode* inode) +{ + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + kunmap(page); // page data access done, so unmap it + + if (unlikely(writeRes <= 0) ) + { // write error + if (writeRes == -EAGAIN || writeRes == 0) + set_page_dirty(page); + else + { + fhgfs_set_wb_error(page, writeRes); + FhgfsInode_decNumDirtyPages(fhgfsInode); + } + } + else + FhgfsInode_decNumDirtyPages(fhgfsInode); + + end_page_writeback(page); + unlock_page(page); +} + + + +/** + * We don't have data for this page, check if the page exceeds the file size limit + * + * Handle a short (sparse) read, which might have 2 two reasons + * 1) Another client truncated the file, as this client has another inode-size + * the kernel read-ahead would try forever to read missing data + * 2) Sparse files, so one or more chunks have less data than following chunks + * + * As we do not know which applies, we always update the inode size first, but calling code + * shall do this only once per chunk. + * + * */ +bool FhgfsOpsPages_isShortRead(struct inode* inode, pgoff_t pageIndex, + bool needInodeRefresh) +{ + bool retVal = false; + App* app = FhgfsOps_getApp(inode->i_sb); + + off_t iSize = i_size_read(inode); + pgoff_t fileEndIndex = iSize >> PAGE_SHIFT; + + FhgfsIsizeHints iSizeHints; + + if (needInodeRefresh && pageIndex > fileEndIndex) + { // page is outside the file size limit + + /* don't flush here, this called before FhgfsOpsPages_endReadPage(), so a page lock + * is being held and if flush would try to lock this page, it deadlocks. */ + __FhgfsOps_doRefreshInode(app, inode, NULL, &iSizeHints, true); + needInodeRefresh = false; + + iSize = i_size_read(inode); + fileEndIndex = iSize >> PAGE_SHIFT; + } + + if (pageIndex < fileEndIndex) + retVal = true; + + return retVal; +} + +/** + * Note: Needs to be called for all pages added to pageVec in FhgfsOps_readpagesVec() in order to + * set the status and to unlock it. + * + * @param readRes the number of read bytes or negative linux error code + */ +void FhgfsOpsPages_endReadPage(Logger* log, struct inode* inode, struct FhgfsPage* fhgfsPage, + int readRes) +{ + struct page* page = fhgfsPage->page; + loff_t offset = FhgfsPage_getFileOffset(fhgfsPage); + + const char* logContext = __func__; + + /* LogTopic_COMMKIT here, as the message belongs better to CommKitVec, although it is a page + * relate method */ + Logger_logTopFormatted(log, LogTopic_COMMKIT, Log_SPAM, logContext, + "Page-index: %lu readRes: %d", page->index, readRes); + + FhgfsOpsPages_incInodeFileSizeOnPagedRead(inode, offset, readRes); + + + if (readRes < 0) + { + fhgfs_set_wb_error(page, readRes); + } + else + { /* note: There is no way to mark a page outside the filesize, so even with readRes == 0 + * we need to SetPageUptodate(page) */ + + if (readRes && readRes < PAGE_SIZE) + { + // zero the remainder of the page for which we don't have data + + // zero_user_segment() would be optimal, but not available in older kernels + // zero_user_segment(page, zeroOffset, BEEGFS_PAGE_SIZE); + // BUT we can use our kmapped Fhgfs_Page directly for zeroing + + memset(fhgfsPage->data + readRes, 0, PAGE_SIZE - readRes); + } + + flush_dcache_page(page); + SetPageUptodate(page); + } + + FhgfsPage_unmapUnlockReleasePage(page); +} + + +/** + * Callback for the mm/vfs read_cache_pages function. + * + * Collect the given pages into data->pageVec. If data->pageVec cannot take more pages + * (chunk is full) a read request will be send immediately. + * + * @return 0 on success, negative linux error code on error + */ +int FhgfsOpsPages_readPageCallBack(void *dataPtr, struct page *page) +{ + const char* logContext = __func__; + int retVal = 0; + + FhgfsPageData* pageData = (FhgfsPageData*) dataPtr; + + struct inode* inode = pageData->inode; + + bool pageVecWasSent = false; + + const bool finalRead = false; // is not the final read from this function + + // note, only allocate the pageVec after referencing the file! + if (!pageData->chunkPageVec) + { + FhgfsChunkPageVec* pageVec = _FhgfsOpsPages_allocNewPageVec(pageData); + if (unlikely(!pageVec) ) + { + printk_fhgfs_debug(KERN_INFO, "%s:%d ENOMEM\n", __func__, __LINE__); + retVal = -ENOMEM; + goto outErr; + } + } + + while(1) // repeats only once, until pageVecWasSent == true + { + bool pushSucces; + + pushSucces = FhgfsChunkPageVec_pushPage(pageData->chunkPageVec, page, PAGE_SIZE); + + if (!pushSucces) + { // pageVec probably full, send it and create a new one + int readRes; + + if (unlikely(pageVecWasSent) ) + { /* We already send the pageVec once, no need to do it again for an empty vec. + * Probably of out memory */ + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "pageVec push failed, page-index: %ld", pageData->chunkPageVec->firstPageIdx); + + retVal = -ENOMEM; + goto outErr; + } + else + { + #ifdef BEEGFS_DEBUG + if (pageData->chunkPageVec->size == 0) + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "initial push failed, index: %ld", pageData->chunkPageVec->firstPageIdx); + #endif + } + + + readRes = _FhgfsOpsPages_sendPageVec(pageData, inode, finalRead, BEEGFS_RWTYPE_READ); + + pageVecWasSent = true; + + if (unlikely(readRes) ) + { + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "ChunkPageVec writing failed."); + + retVal = readRes; + goto outErr; + } + + if (unlikely(!pageData->chunkPageVec) ) + { + Logger_logFormattedWithEntryID(inode, Log_ERR, logContext, + "Warning, chunkPageVec is NULL."); + + retVal = -ENOMEM; + goto outErr; + } + else + { + continue; // try again to push the page + } + } + else + { // push success + } + + break; // break on success + } + + get_page(page); // reference page to avoid it is re-used while we read it + return retVal; + + +outErr: + return retVal; +} + +/** + * Read a page synchronously + * + * Note: Reads a locked page and returns it locked again (page will be unlocked in between) + * + */ +int FhgfsOpsPages_readpageSync(struct file* file, struct page* page) +{ + int retVal; + + ClearPageUptodate(page); + +#ifdef KERNEL_HAS_READ_FOLIO + retVal = FhgfsOps_read_folio(file, page_folio(page)); // note: async read will unlock the page +#else + retVal = FhgfsOpsPages_readpage(file, page); // note: async read will unlock the page +#endif + + if (retVal) + { + lock_page(page); // re-lock it + return retVal; // some kind of error + } + + lock_page(page); // re-lock it + + if (!PageUptodate(page) ) + { + /* The uptodate flag is not set, which means reading the page failed with an io-error. + * Note: FhgfsOpsPages_readpage *must* SetPageUptoDate even if the page does not exist on + * the server (i.e. file size too small), as other mm/vfs code requires that. + * So if it is not set there was some kind of IO error. */ + retVal = -EIO; + } + + return retVal; +} + +/* + * Read a single page + * Note: Called with the page locked and we need to unlock it when done. + * + * Note: Caller holds a reference to this page + * Note: Reading the page is done asynchronously from another thread + * + * @return 0 on success, negative linux error code otherwise + */ + +#ifdef KERNEL_HAS_READ_FOLIO +int FhgfsOps_read_folio(struct file *file, struct folio *folio) +#else +int FhgfsOpsPages_readpage(struct file* file, struct page* page) +#endif +{ + App* app = FhgfsOps_getApp(file_dentry(file)->d_sb); + struct inode* inode = file_inode(file); + +#ifdef KERNEL_HAS_READ_FOLIO + struct page* page = &folio->page; + DEFINE_READAHEAD(ractl, file, &file->f_ra, file->f_mapping, folio->index); +#elif defined(KERNEL_HAS_FOLIO) + DEFINE_READAHEAD(ractl, file, &file->f_ra, file->f_mapping, page->index); +#endif + + int writeBackRes; + + int retVal; + +#ifdef KERNEL_HAS_READ_FOLIO + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, + "folio-index: %lu", folio->index); +#else + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, + "page-index: %lu", page->index); +#endif + + IGNORE_UNUSED_VARIABLE(app); + + writeBackRes = FhgfsOpsPages_writeBackPage(inode, page); + if (writeBackRes) + { + retVal = writeBackRes; + goto outErr; + } + +#if defined(KERNEL_HAS_FOLIO) + retVal = _FhgfsOpsPages_readahead(&ractl, page); + #if defined(KERNEL_HAS_READ_FOLIO) + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "folio-index: %lu retVal: %d", + folio->index, retVal); + #else + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "page-index: %lu retVal: %d", + page->index, retVal); + #endif +#else + retVal = _FhgfsOpsPages_readpages(file, file->f_mapping, NULL, page); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "page-index: %lu retVal: %d", + page->index, retVal); +#endif + + return retVal; + +outErr: + +#ifdef KERNEL_HAS_READ_FOLIO + folio_unlock(folio); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "folio-index: %lu retVal: %d", + folio->index, retVal); +#else + unlock_page(page); + + FhgfsOpsHelper_logOpDebug(app, file_dentry(file), inode, __func__, "page-index: %lu retVal: %d", + page->index, retVal); +#endif + + return retVal; +} + +#ifdef KERNEL_HAS_FOLIO +int _FhgfsOpsPages_readahead(struct readahead_control *ractl, struct page *page) +{ + struct inode* inode = ractl->mapping->host; + struct file *file = ractl->file; +#else +int _FhgfsOpsPages_readpages(struct file* file, struct address_space* mapping, + struct list_head* pageList, struct page* page) +{ + struct inode* inode = mapping->host; +#endif + + int referenceRes; + + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + int retVal = 0; + + FhgfsPageData pageData = + { + .inode = inode, + .chunkPageVec = NULL, + .isReferenced = false, + }; + + + referenceRes = _FhgfsOpsPages_referenceReadFileHandle(&pageData, file); + if (unlikely(referenceRes) != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_toSysErr(referenceRes); + + return retVal; + } + + + ihold(inode); // make sure the inode is not released + +#ifdef KERNEL_HAS_FOLIO + if (readahead_count(ractl)) + { + while ((page = readahead_page(ractl)) != NULL) + { + retVal = FhgfsOpsPages_readPageCallBack(&pageData, page); + put_page(page); + if (retVal) + break; + } + } +#else + if (pageList) + { // classical readpages, we get a list pages, which are not in the page cache yet + retVal = read_cache_pages(mapping, pageList, FhgfsOpsPages_readPageCallBack, &pageData); + } +#endif + else + { /* Called with a single page only, that does not need to be added to the cache, + * we are called from ->readpage */ + + retVal = FhgfsOpsPages_readPageCallBack(&pageData, page); + + if (unlikely(retVal < 0) ) + { // some kind of error + if (unlikely(pageData.chunkPageVec) ) + { + printk_fhgfs(KERN_ERR, + "Bug: pageData.chunkPageVec set, but error code returned\n"); + dump_stack(); + + // try to clean it up + FhgfsChunkPageVec_destroy(pageData.chunkPageVec); + pageData.chunkPageVec = NULL; + } + + // unlock the page as it cannot be handled. + unlock_page(page); + } + } + + if (pageData.chunkPageVec) + { + int readRes; + + if (unlikely(!pageData.isReferenced) ) + { // This would be a big bug and should be impossible at all! + printk_fhgfs(KERN_ERR, "%s: Bug: File is not referenced! ", __func__); + dump_stack(); + + } + + readRes = _FhgfsOpsPages_sendPageVec(&pageData, inode, true, BEEGFS_RWTYPE_READ); + + if (unlikely(readRes)) + retVal = readRes; + } + + if (likely(pageData.isReferenced) ) + FhgfsInode_releaseHandle(fhgfsInode, pageData.handleType, NULL); + + iput(inode); + + return retVal; +} + +/** + * address_space_operations.readahead method + */ +#ifdef KERNEL_HAS_FOLIO +void FhgfsOpsPages_readahead(struct readahead_control *ractl) +{ + + struct file *file = ractl->file; + + struct dentry* dentry = file_dentry(file); + App* app = FhgfsOps_getApp(dentry->d_sb); + + FhgfsOpsHelper_logOpDebug(app, dentry, ractl->mapping->host, __func__, "(nr_pages: %u)", readahead_count(ractl)); + IGNORE_UNUSED_VARIABLE(app); + _FhgfsOpsPages_readahead(ractl, NULL); + return; +} +#else + +/** + * address_space_operations.readpages method + */ +int FhgfsOpsPages_readpages(struct file* file, struct address_space* mapping, + struct list_head* pageList, unsigned numPages) +{ + + struct dentry* dentry = file_dentry(file); + struct inode* inode = mapping->host; + App* app = FhgfsOps_getApp(dentry->d_sb); + + const char* logContext = __func__; + + FhgfsOpsHelper_logOpDebug(app, dentry, inode, logContext, "(num_pages: %u)", numPages); + IGNORE_UNUSED_VARIABLE(logContext); + IGNORE_UNUSED_VARIABLE(app); + IGNORE_UNUSED_VARIABLE(inode); + + return _FhgfsOpsPages_readpages(file, mapping, pageList, NULL); +} +#endif + +/* + * Write back all requests on one page - we do this before reading it. + * + * Note: page is locked and must stay locked + */ +int FhgfsOpsPages_writeBackPage(struct inode *inode, struct page *page) +{ + struct writeback_control wbc; + int ret; + + loff_t range_start = page_offset(page); + loff_t range_end = range_start + (loff_t)(PAGE_SIZE - 1); + + + memset(&wbc, 0, sizeof(wbc) ); + wbc.sync_mode = WB_SYNC_ALL; + wbc.nr_to_write = 0; + + wbc.range_start = range_start; + wbc.range_end = range_end; + + IGNORE_UNUSED_VARIABLE(range_start); + IGNORE_UNUSED_VARIABLE(range_end); + + for (;;) { + wait_on_page_writeback(page); + if (clear_page_dirty_for_io(page)) + { + ret = FhgfsOpsPages_writepage(page, &wbc); // note: unlocks the page + lock_page(page); // re-lock the page + if (ret < 0) + goto out_error; + continue; + } + ret = 0; + if (!PagePrivate(page)) + break; + } + +out_error: + return ret; +} + + diff --git a/client_module/source/filesystem/FhgfsOpsPages.h b/client_module/source/filesystem/FhgfsOpsPages.h new file mode 100644 index 0000000..71da184 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsPages.h @@ -0,0 +1,71 @@ +/* + * fhgfs page cache methods + * + */ + +#ifndef FHGFSOPSPAGES_H_ +#define FHGFSOPSPAGES_H_ + +#include + + +#define BEEGFS_MAX_PAGE_LIST_SIZE (65535) // Allow a maximum page list size and therefore right now + // also IO size of 65536 pages, so 262144 MiB with 4K pages + // NOTE: *MUST* be larger than INITIAL_FIND_PAGES + + +extern bool FhgfsOpsPages_initPageListVecCache(void); +extern void FhgfsOpsPages_destroyPageListVecCache(void); + +extern int FhgfsOps_readpagesVec(struct file* file, struct address_space* mapping, + struct list_head* page_list, unsigned num_pages); +extern int FhgfsOpsPages_readpageSync(struct file* file, struct page* page); +#ifdef KERNEL_HAS_READ_FOLIO +extern int FhgfsOps_read_folio(struct file *file, struct folio *folio); +#else +extern int FhgfsOpsPages_readpage(struct file *file, struct page *page); +#endif + +#ifdef KERNEL_HAS_FOLIO +extern void FhgfsOpsPages_readahead(struct readahead_control *ractl); +#else +extern int FhgfsOpsPages_readpages(struct file* file, struct address_space* mapping, + struct list_head* pageList, unsigned numPages); +#endif + +extern int FhgfsOpsPages_writepage(struct page *page, struct writeback_control *wbc); +extern int FhgfsOpsPages_writepages(struct address_space* mapping, struct writeback_control* wbc); + +static inline void FhgfsOpsPages_incInodeFileSizeOnPagedRead(struct inode* inode, loff_t offset, + ssize_t readRes); +extern void __FhgfsOpsPages_incInodeFileSizeOnPagedRead(struct inode* inode, loff_t offset, + size_t readRes); + +extern bool FhgfsOpsPages_isShortRead(struct inode* inode, pgoff_t pageIndex, + bool needInodeRefresh); +extern void FhgfsOpsPages_endReadPage(Logger* log, struct inode* inode, + struct FhgfsPage* fhgfsPage, int readRes); +extern void FhgfsOpsPages_endWritePage(struct page* page, int writeRes, struct inode* inode); + +extern int FhgfsOpsPages_writeBackPage(struct inode *inode, struct page *page); + + +/** + * If the meta server has told us for some reason a wrong file-size (i_size) the caller would + * wrongly discard data beyond i_size. So we are going to correct i_size here. + * + * Note: This is the fast inline version, which should almost always only read i_size and then + * return. So mostly no need to increase the stack size. + * */ +void FhgfsOpsPages_incInodeFileSizeOnPagedRead(struct inode* inode, loff_t offset, ssize_t readRes) +{ + loff_t i_size = i_size_read(inode); + + if (unlikely(readRes <= 0) ) + return; + + if (unlikely(readRes && (offset + (loff_t)readRes > i_size) ) ) + __FhgfsOpsPages_incInodeFileSizeOnPagedRead(inode, offset, readRes); +} + +#endif /* FHGFSOPSPAGES_H_ */ diff --git a/client_module/source/filesystem/FhgfsOpsSuper.c b/client_module/source/filesystem/FhgfsOpsSuper.c new file mode 100644 index 0000000..de7c9ba --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsSuper.c @@ -0,0 +1,425 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOps_versions.h" +#include "FhgfsOpsSuper.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsDir.h" +#include "FhgfsOpsPages.h" +#include "FhgfsOpsExport.h" +#include "FhgfsXAttrHandlers.h" + + +static int __FhgfsOps_initApp(struct super_block* sb, char* rawMountOptions); +static void __FhgfsOps_uninitApp(App* app); + +static int __FhgfsOps_constructFsInfo(struct super_block* sb, void* rawMountOptions); +static void __FhgfsOps_destructFsInfo(struct super_block* sb); + + +/* read-ahead size is limited by BEEGFS_DEFAULT_READAHEAD_PAGES, so this is the maximum already going + * to to the server. 32MiB read-head also seems to be a good number. It still may be reduced by + * setting /sys/class/bdi/fhgfs-${number}/read_ahead_kb */ +#define BEEGFS_DEFAULT_READAHEAD_PAGES BEEGFS_MAX_PAGE_LIST_SIZE + +static struct file_system_type fhgfs_fs_type = +{ + .name = BEEGFS_MODULE_NAME_STR, + .owner = THIS_MODULE, + .kill_sb = FhgfsOps_killSB, + //.fs_flags = FS_BINARY_MOUNTDATA, // not required currently + +#ifdef KERNEL_HAS_GET_SB_NODEV + .get_sb = FhgfsOps_getSB, +#else + .mount = FhgfsOps_mount, // basically the same thing as get_sb before +#endif +}; + +static struct super_operations fhgfs_super_ops = +{ + .statfs = FhgfsOps_statfs, + .alloc_inode = FhgfsOps_alloc_inode, + .destroy_inode = FhgfsOps_destroy_inode, + .drop_inode = generic_drop_inode, + .put_super = FhgfsOps_putSuper, + .show_options = FhgfsOps_showOptions, +}; + +/** + * Creates and initializes the per-mount application object. + */ +int __FhgfsOps_initApp(struct super_block* sb, char* rawMountOptions) +{ + MountConfig* mountConfig; + bool parseRes; + App* app; + int appRes; + + // create mountConfig (parse from mount options) + mountConfig = MountConfig_construct(); + + parseRes = MountConfig_parseFromRawOptions(mountConfig, rawMountOptions); + if(!parseRes) + { + MountConfig_destruct(mountConfig); + return APPCODE_INVALID_CONFIG; + } + + //printk_fhgfs(KERN_INFO, "Initializing App...\n"); // debug in + + app = FhgfsOps_getApp(sb); + App_init(app, mountConfig); + + appRes = App_run(app); + + if(appRes != APPCODE_NO_ERROR) + { // error occurred => clean up + printk_fhgfs_debug(KERN_INFO, "Stopping App...\n"); + + App_stop(app); + + printk_fhgfs_debug(KERN_INFO, "Cleaning up...\n"); + + App_uninit(app); + + printk_fhgfs_debug(KERN_INFO, "App unitialized.\n"); + + return appRes; + } + + ProcFs_createEntries(app); + + return appRes; +} + +/** + * Stops and destroys the per-mount application object. + */ +void __FhgfsOps_uninitApp(App* app) +{ + App_stop(app); + + /* note: some of the procfs entries (e.g. remove_node) won't work anymore after app components + have been stopped, but others are still useful for finding reasons why app stop is delayed + in some cases (so we remove procfs after App_stop() ). */ + + ProcFs_removeEntries(app); + + App_uninit(app); +} + +int FhgfsOps_registerFilesystem(void) +{ + return register_filesystem(&fhgfs_fs_type); +} + +int FhgfsOps_unregisterFilesystem(void) +{ + return unregister_filesystem(&fhgfs_fs_type); +} + +/** + * Initialize sb->s_fs_info + * + * @return 0 on success, negative linux error code otherwise + */ +int __FhgfsOps_constructFsInfo(struct super_block* sb, void* rawMountOptions) +{ + int res; + int appRes; + App* app; + Logger* log; + + + +#if defined(KERNEL_HAS_SB_BDI) && !defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) + struct backing_dev_info* bdi; +#endif + + // use kzalloc to also zero the bdi + FhgfsSuperBlockInfo* sbInfo = kzalloc(sizeof(FhgfsSuperBlockInfo), GFP_KERNEL); + if (!sbInfo) + { + printk_fhgfs_debug(KERN_INFO, "Failed to allocate memory for FhgfsSuperBlockInfo"); + sb->s_fs_info = NULL; + return -ENOMEM; + } + + sb->s_fs_info = sbInfo; + + appRes = __FhgfsOps_initApp(sb, rawMountOptions); + if(appRes) + { + printk_fhgfs_debug(KERN_INFO, "Failed to initialize App object"); + res = -EINVAL; + goto outFreeSB; + } + + app = FhgfsOps_getApp(sb); + log = App_getLogger(app); + IGNORE_UNUSED_VARIABLE(log); + +#if defined(KERNEL_HAS_SB_BDI) + + #if defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) && !defined(KERNEL_HAS_BDI_SETUP_AND_REGISTER) + { + static atomic_long_t bdi_seq = ATOMIC_LONG_INIT(0); + + res = super_setup_bdi_name(sb, BEEGFS_MODULE_NAME_STR "-%ld", + atomic_long_inc_return(&bdi_seq)); + } + #else + bdi = &sbInfo->bdi; + + /* NOTE: The kernel expects a fully initialized bdi structure, so at a minimum it has to be + * allocated by kzalloc() or memset(bdi, 0, sizeof(*bdi)). + * we don't set the congest_* callbacks (like every other filesystem) because those are + * intended for dm and md. + */ + bdi->ra_pages = BEEGFS_DEFAULT_READAHEAD_PAGES; + + #if defined(KERNEL_HAS_BDI_CAP_MAP_COPY) + res = bdi_setup_and_register(bdi, BEEGFS_MODULE_NAME_STR, BDI_CAP_MAP_COPY); + #else + res = bdi_setup_and_register(bdi, BEEGFS_MODULE_NAME_STR); + #endif + #endif + + if (res) + { + Logger_logFormatted(log, 2, __func__, "Failed to init super-block (bdi) information: %d", + res); + __FhgfsOps_uninitApp(app); + goto outFreeSB; + } +#endif + + // set root inode attribs to uninit'ed + + FhgfsOps_setHasRootEntryInfo(sb, false); + FhgfsOps_setIsRootInited(sb, false); + + + printk_fhgfs(KERN_INFO, "BeeGFS mount ready.\n"); + + return 0; // all ok, res should be 0 here + +outFreeSB: + kfree(sbInfo); + sb->s_fs_info = NULL; + + return res; +} + +/** + * Unitialize the entire sb->s_fs_info object + */ +void __FhgfsOps_destructFsInfo(struct super_block* sb) +{ + /* sb->s_fs_info might be NULL if __FhgfsOps_constructFsInfo() failed */ + if (sb->s_fs_info) + { + App* app = FhgfsOps_getApp(sb); + +//call destroy iff not initialised/registered by super_setup_bdi_name +#if defined(KERNEL_HAS_SB_BDI) + +#if !defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) || defined(KERNEL_HAS_BDI_SETUP_AND_REGISTER) + struct backing_dev_info* bdi = FhgfsOps_getBdi(sb); + + bdi_destroy(bdi); +#endif + +#endif + + __FhgfsOps_uninitApp(app); + + kfree(sb->s_fs_info); + sb->s_fs_info = NULL; + + printk_fhgfs(KERN_INFO, "BeeGFS unmounted.\n"); + } +} + +/** + * Fill the file system superblock (vfs object) + */ +int FhgfsOps_fillSuper(struct super_block* sb, void* rawMountOptions, int silent) +{ + App* app = NULL; + Config* cfg = NULL; + + struct inode* rootInode; + struct dentry* rootDentry; + struct kstat kstat; + EntryInfo entryInfo; + + FhgfsIsizeHints iSizeHints; + + // init per-mount app object + + if(__FhgfsOps_constructFsInfo(sb, rawMountOptions) ) + return -ECANCELED; + + app = FhgfsOps_getApp(sb); + cfg = App_getConfig(app); + + // set up super block data + + sb->s_maxbytes = MAX_LFS_FILESIZE; + sb->s_blocksize = PAGE_SIZE; + sb->s_blocksize_bits = PAGE_SHIFT; + sb->s_magic = BEEGFS_MAGIC; + sb->s_op = &fhgfs_super_ops; + sb->s_time_gran = 1000000000; // granularity of c/m/atime in ns +#ifdef KERNEL_HAS_SB_NODIRATIME + sb->s_flags |= SB_NODIRATIME; +#else + sb->s_flags |= MS_NODIRATIME; +#endif + + if (Config_getSysXAttrsEnabled(cfg ) ) + sb->s_xattr = fhgfs_xattr_handlers_noacl; // handle only user xattrs + +#ifdef KERNEL_HAS_GET_ACL + if (Config_getSysACLsEnabled(cfg) ) + { + sb->s_xattr = fhgfs_xattr_handlers; // replace with acl-capable xattr handlers +#ifdef SB_POSIXACL + sb->s_flags |= SB_POSIXACL; +#else + sb->s_flags |= MS_POSIXACL; +#endif + } +#endif // KERNEL_HAS_GET_ACL + if (Config_getSysXAttrsCheckCapabilities(cfg) != CHECKCAPABILITIES_Always) + #if defined(SB_NOSEC) + sb->s_flags |= SB_NOSEC; + #else + sb->s_flags |= MS_NOSEC; + #endif + + /* MS_ACTIVE is rather important as it marks the super block being successfully initialized and + * allows the vfs to keep important inodes in the cache. However, it seems it is already + * initialized in vfs generic mount functions. + sb->s_flags |= MS_ACTIVE; // used in iput_final() */ + + // NFS kernel export is probably not worth the backport efforts for kernels before 2.6.29 +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,29) + sb->s_export_op = &fhgfs_export_ops; +#endif + +#if defined(KERNEL_HAS_SB_BDI) + sb->s_bdi = FhgfsOps_getBdi(sb); +#endif + + // init root inode + + memset(&kstat, 0, sizeof(struct kstat) ); + + kstat.ino = BEEGFS_INODE_ROOT_INO; + kstat.mode = S_IFDIR | 0777; // allow access for everyone + kstat.atime = kstat.mtime = kstat.ctime = current_fs_time(sb); + kstat.uid = current_fsuid(); + kstat.gid = current_fsgid(); + kstat.blksize = Config_getTuneInodeBlockSize(cfg); + kstat.nlink = 1; + + // root entryInfo is always updated when someone asks for it (so we just set dummy values here) + EntryInfo_init(&entryInfo, NodeOrGroup_fromGroup(0), StringTk_strDup(""), StringTk_strDup(""), + StringTk_strDup(""), DirEntryType_DIRECTORY, 0); + + rootInode = __FhgfsOps_newInode(sb, &kstat, 0, &entryInfo, &iSizeHints, 0); + if(!rootInode || IS_ERR(rootInode)) + { + __FhgfsOps_destructFsInfo(sb); + return IS_ERR(rootInode) ? PTR_ERR(rootInode) : -ENOMEM; + } + + rootDentry = d_make_root(rootInode); + if(!rootDentry) + { + __FhgfsOps_destructFsInfo(sb); + return -ENOMEM; + } + +#ifdef KERNEL_HAS_S_D_OP + // linux 2.6.38 switched from individual per-dentry to defaul superblock d_ops. + /* note: Only set default dentry operations here, as we don't want those OPs set for the root + * dentry. In fact, setting as before would only slow down everything a bit, due to + * useless revalidation of our root dentry. */ + sb->s_d_op = &fhgfs_dentry_ops; +#endif // KERNEL_HAS_S_D_OP + + rootDentry->d_time = jiffies; + sb->s_root = rootDentry; + + return 0; +} + +/* + * Called by FhgfsOps_killSB()->kill_anon_super()->generic_shutdown_super() + */ +void FhgfsOps_putSuper(struct super_block* sb) +{ + if (sb->s_fs_info) + { + App* app = FhgfsOps_getApp(sb); + + if(app) + __FhgfsOps_destructFsInfo(sb); + } +} + +void FhgfsOps_killSB(struct super_block* sb) +{ + App* app = FhgfsOps_getApp(sb); + + if (app) // might be NULL on unsuccessful mount attempt + App_setConnRetriesEnabled(app, false); // faster umount on communication errors + + RWPagesWork_flushWorkQueue(); + +#if defined(KERNEL_HAS_SB_BDI) && LINUX_VERSION_CODE < KERNEL_VERSION(4,1,0) + /** + * s_fs_info might be NULL + */ + if (likely(sb->s_fs_info) ) + { + struct backing_dev_info* bdi = FhgfsOps_getBdi(sb); + + bdi_unregister(bdi); + } +#endif + + kill_anon_super(sb); +} + + +#ifdef KERNEL_HAS_SHOW_OPTIONS_DENTRY +extern int FhgfsOps_showOptions(struct seq_file* sf, struct dentry* dentry) +{ + struct super_block* super = dentry->d_sb; +#else +extern int FhgfsOps_showOptions(struct seq_file* sf, struct vfsmount* vfs) +{ + struct super_block* super = vfs->mnt_sb; +#endif + + App* app = FhgfsOps_getApp(super); + MountConfig* mountConfig = App_getMountConfig(app); + + MountConfig_showOptions(mountConfig, sf); + + return 0; +} + diff --git a/client_module/source/filesystem/FhgfsOpsSuper.h b/client_module/source/filesystem/FhgfsOpsSuper.h new file mode 100644 index 0000000..d5ee04f --- /dev/null +++ b/client_module/source/filesystem/FhgfsOpsSuper.h @@ -0,0 +1,124 @@ +#ifndef FHGFSOPSSUPER_H_ +#define FHGFSOPSSUPER_H_ + +#include +#include +#include "FhgfsOps_versions.h" + +#include +#include +#include +#include + + +#define BEEGFS_MAGIC 0x19830326 /* some random number to identify fhgfs */ + +#define BEEGFS_STATFS_BLOCKSIZE_SHIFT 19 /* bit shift to compute reported block size in statfs() */ +#define BEEGFS_STATFS_BLOCKSIZE (1UL << BEEGFS_STATFS_BLOCKSIZE_SHIFT) + + +struct FhgfsSuperBlockInfo; +typedef struct FhgfsSuperBlockInfo FhgfsSuperBlockInfo; + + + +extern int FhgfsOps_registerFilesystem(void); +extern int FhgfsOps_unregisterFilesystem(void); + +extern void FhgfsOps_putSuper(struct super_block* sb); +extern void FhgfsOps_killSB(struct super_block* sb); +#ifdef KERNEL_HAS_SHOW_OPTIONS_DENTRY +extern int FhgfsOps_showOptions(struct seq_file* sf, struct dentry* dentry); +#else +extern int FhgfsOps_showOptions(struct seq_file* sf, struct vfsmount* vfs); +#endif + +extern int FhgfsOps_fillSuper(struct super_block* sb, void* rawMountOptions, int silent); + +// getters & setters +static inline App* FhgfsOps_getApp(struct super_block* sb); +static inline struct backing_dev_info* FhgfsOps_getBdi(struct super_block* sb); + +static inline bool FhgfsOps_getHasRootEntryInfo(struct super_block* sb); +static inline void FhgfsOps_setHasRootEntryInfo(struct super_block* sb, bool isInited); +static inline bool FhgfsOps_getIsRootInited(struct super_block* sb); +static inline void FhgfsOps_setIsRootInited(struct super_block* sb, bool isInited); + + +struct FhgfsSuperBlockInfo +{ + App app; +#if !defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) + struct backing_dev_info bdi; +#endif + bool haveRootEntryInfo; // false until the root EntryInfo is set in root-FhgfsInode + + bool isRootInited; /* false until root inode attrs have been fetched/initialized in + _lookup() (because kernel does not automatically lookup/revalidate the root inode) */ + +}; + +/* Note: Functions below rely on sb->s_fs_info. Umount operations / super-block destroy operations + * in FhgfsOpsSuper.c might be more obvious if we would test here if sb->fs_info is NULL. + * However, the functions below are frequently called from various parts of the file system + * and additional checks might cause an overhead. Therefore those checks are not done + * here. + */ + +/** + * NOTE: Make sure sb->s_fs_info is initialized! + */ +App* FhgfsOps_getApp(struct super_block* sb) +{ + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + + return &(sbInfo->app); +} + +/** + * NOTE: Make sure sb->s_fs_info is initialized! + */ +struct backing_dev_info* FhgfsOps_getBdi(struct super_block* sb) +{ +#if defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) + return sb->s_bdi; +#else + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + + return &(sbInfo->bdi); +#endif +} + +/** + * Note: Do not get it (reliably) without also locking fhgfsInode->entryInfoLock! + */ +bool FhgfsOps_getHasRootEntryInfo(struct super_block* sb) +{ + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + return sbInfo->haveRootEntryInfo; +} + +/** + * Note: Do not set it without also locking fhgfsInode->entryInfoLock! Exception is during the + * mount process. + */ +void FhgfsOps_setHasRootEntryInfo(struct super_block* sb, bool haveRootEntryInfo) +{ + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + sbInfo->haveRootEntryInfo = haveRootEntryInfo; +} + + +bool FhgfsOps_getIsRootInited(struct super_block* sb) +{ + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + return sbInfo->isRootInited; +} + +void FhgfsOps_setIsRootInited(struct super_block* sb, bool isInited) +{ + FhgfsSuperBlockInfo* sbInfo = sb->s_fs_info; + sbInfo->isRootInited = isInited; +} + +#endif /* FHGFSOPSSUPER_H_ */ diff --git a/client_module/source/filesystem/FhgfsOps_versions.c b/client_module/source/filesystem/FhgfsOps_versions.c new file mode 100644 index 0000000..c3fa0e0 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOps_versions.c @@ -0,0 +1,319 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOps_versions.h" +#include "FhgfsOpsFile.h" +#include "FhgfsOpsDir.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsSuper.h" + + +/** + * A basic permission() method. Only required to tell the VFS we do not support RCU path walking. + * + * @return 0 on success, negative linux error code otherwise + */ +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + int FhgfsOps_permission(struct mnt_idmap* idmap, struct inode *inode, int mask) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) + int FhgfsOps_permission(struct user_namespace* mnt_userns, struct inode *inode, int mask) +#elif defined(KERNEL_HAS_PERMISSION_2) + int FhgfsOps_permission(struct inode *inode, int mask) +#elif defined(KERNEL_HAS_PERMISSION_FLAGS) + int FhgfsOps_permission(struct inode *inode, int mask, unsigned int flags) +#else + /* <= 2.6.26 */ + int FhgfsOps_permission(struct inode *inode, int mask, struct nameidata *nd) +#endif +{ + /* note: 2.6.38 introduced rcu-walk mode, which is inappropriate for us, because we need the + parent. the code below tells vfs to call this again in old ref-walk mode. + (see Documentation/filesystems/vfs.txt:d_revalidate) */ + #ifdef MAY_NOT_BLOCK + if(mask & MAY_NOT_BLOCK) + return -ECHILD; + #elif defined(IPERM_FLAG_RCU) + if(flags & IPERM_FLAG_RCU) + return -ECHILD; + #endif // LINUX_VERSION_CODE + + return os_generic_permission(inode, mask); +} + + +int FhgfsOps_statfs(struct dentry* dentry, struct kstatfs* kstatfs) +{ + struct super_block* sb = dentry->d_sb; + + const char* logContext = __func__; + + App* app = FhgfsOps_getApp(sb); + Logger* log = App_getLogger(app); + StatFsCache* statfsCache = App_getStatFsCache(app); + + int retVal = -EREMOTEIO; + FhgfsOpsErr statRes; + + int64_t sizeTotal = 0; + int64_t sizeFree = 0; + + FhgfsOpsHelper_logOp(Log_SPAM, app, NULL, NULL, logContext); + + + memset(kstatfs, 0, sizeof(struct kstatfs) ); + + kstatfs->f_type = BEEGFS_MAGIC; + + kstatfs->f_namelen = NAME_MAX; + kstatfs->f_files = 0; // total used file nodes (not supported currently) + kstatfs->f_ffree = 0; // free file nodes (not supported currently) + + kstatfs->f_bsize = BEEGFS_STATFS_BLOCKSIZE; + kstatfs->f_frsize = BEEGFS_STATFS_BLOCKSIZE; /* should be same as f_bsize, as some versions of + glibc do not correctly handle frsize!=bsize (both are used inconsistently as a unit for + f_blocks & co) */ + + statRes = StatFsCache_getFreeSpace(statfsCache, &sizeTotal, &sizeFree); + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "remoting complete. result: %d", (int)statRes); + IGNORE_UNUSED_VARIABLE(log); + + retVal = FhgfsOpsErr_toSysErr(statRes); + + if(statRes == FhgfsOpsErr_SUCCESS) + { // success => assign received values + + // note: f_blocks, f_bfree, and f_bavail are reported in units of f_bsize. + + unsigned char blockBits = BEEGFS_STATFS_BLOCKSIZE_SHIFT; // just a shorter name + unsigned long blockSizeDec = (1 << blockBits) - 1; // for rounding + + kstatfs->f_blocks = (sizeTotal + blockSizeDec) >> blockBits; + kstatfs->f_bfree = (sizeFree + blockSizeDec) >> blockBits; // free for superuser + kstatfs->f_bavail = (sizeFree + blockSizeDec) >> blockBits; // available for non-superuser + } + + return retVal; +} + + + +#ifdef KERNEL_HAS_GET_SB_NODEV + +int FhgfsOps_getSB(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, struct vfsmount *mnt) +{ + return get_sb_nodev(fs_type, flags, data, FhgfsOps_fillSuper, mnt); +} + +#else + +/* kernel 2.6.39 switched from get_sb() to mount(), which provides similar functionality from our + point of view. */ + +struct dentry* FhgfsOps_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data) +{ + return mount_nodev(fs_type, flags, data, FhgfsOps_fillSuper); +} + +#endif // LINUX_VERSION_CODE + + +/** + * Note: Called by close(2) system call before the actual FhgfsOps_close(). + */ +int FhgfsOps_flush(struct file *file, fl_owner_t id) +{ + struct dentry* dentry = file_dentry(file); + struct inode* inode = file_inode(file); + + App* app = FhgfsOps_getApp(dentry->d_sb); + const char* logContext = "FhgfsOps_flush"; + + FhgfsOpsHelper_logOp(Log_SPAM, app, dentry, inode, logContext); + + + /* note: if a buffer cannot be flushed here, we still have the inode in the InodeRefStore, so + that the flusher can write it asynchronously */ + + + return __FhgfsOps_flush(app, file, false, false, true, true); +} + +/** + * Note: Called for new cache objects (see FhgfsOps_initInodeCache() ) + */ +#ifdef SLAB_CTOR_CONSTRUCTOR + +void FhgfsOps_initInodeOnce(void* inode, struct kmem_cache* cache, unsigned long flags) +{ + // note: no, this is not a typo. since kernel version 2.6.22, we're no longer checking + // the slab_... flags (even though the argument still exists in the method signature). + + if( (flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR) ) == SLAB_CTOR_CONSTRUCTOR) + { // fresh construction => initialize object + FhgfsInode* fhgfsInode = (FhgfsInode*)inode; + + FhgfsInode_initOnce(fhgfsInode); + + inode_init_once(&fhgfsInode->vfs_inode); + } +} + +#else + + #if defined(KERNEL_HAS_KMEMCACHE_CACHE_FLAGS_CTOR) + void FhgfsOps_initInodeOnce(void* inode, struct kmem_cache* cache, unsigned long flags) + #elif defined(KERNEL_HAS_KMEMCACHE_CACHE_CTOR) + void FhgfsOps_initInodeOnce(struct kmem_cache* cache, void* inode) + #else + void FhgfsOps_initInodeOnce(void* inode) + #endif // LINUX_VERSION_CODE + + { + FhgfsInode* fhgfsInode = (FhgfsInode*)inode; + + FhgfsInode_initOnce(fhgfsInode); + + inode_init_once(&fhgfsInode->vfs_inode); + } + +#endif // LINUX_VERSION_CODE + + +#ifndef KERNEL_HAS_GENERIC_FILE_LLSEEK_UNLOCKED +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,26) + +/** + * Note: This is just an exact copy of the kernel method (from kernel 2.6.30), which was introduced + * in 2.6.27. + * + * generic_file_llseek_unlocked - lockless generic llseek implementation + * @file: file structure to seek on + * @offset: file offset to seek to + * @origin: type of seek + * + * Updates the file offset to the value specified by @offset and @origin. + * Locking must be provided by the caller. + */ +loff_t generic_file_llseek_unlocked(struct file *file, loff_t offset, int origin) +{ + struct inode *inode = file->f_mapping->host; + + switch (origin) + { + case SEEK_END: + offset += inode->i_size; + break; + case SEEK_CUR: + /* + * Here we special-case the lseek(fd, 0, SEEK_CUR) + * position-querying operation. Avoid rewriting the "same" + * f_pos value back to the file because a concurrent read(), + * write() or lseek() might have altered it + */ + if (offset == 0) + return file->f_pos; + offset += file->f_pos; + break; + } + + if (offset < 0 || offset > (loff_t) inode->i_sb->s_maxbytes) + return -EINVAL; + + /* Special lock needed here? */ + if (offset != file->f_pos) + { + file->f_pos = offset; + file->f_version = 0; + } + + return offset; +} + +#else + +/** + * Note: The lock (to which the _unlocked suffix refers) was dropped in linux 3.2, so we can now + * just call the kernel method without the _unlocked suffix. + */ +loff_t generic_file_llseek_unlocked(struct file *file, loff_t offset, int origin) +{ + return generic_file_llseek(file, offset, origin); +} + +#endif // LINUX_VERSION_CODE +#endif + +#ifndef KERNEL_HAS_DENTRY_PATH_RAW +/** + * Get the relative path of a dentry to the mount point + * + * @param dentry The dentry we want the path for + * @param buf A preallocated buffer + * @param buflen The size of buf + * + * This is a compatibility function for kernels before 2.6.38. + * Alternatively, using d_path() is not easy, as we do not have + * struct vfsmnt. Unfortunately, the kernel interface to get vfsmnt got frequently modified, + * which would require us to have even more compatibility code. + * NOTE: We use the vfs global dcache_lock here, which is rather slow and which will lock up all + * file systems if something goes wrong + * How it works: + * We build up the path backwards from the end of char * buf, put a "/" before it it and return + * the pointer to "/" + */ +char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen) +{ + char* outStoreBuf = buf; + const ssize_t storeBufLen = buflen; + ssize_t bufLenLeft = storeBufLen; // remaining length + char* currentBufStart = (outStoreBuf) + bufLenLeft; + int nameLen; + + + *--currentBufStart = '\0'; + bufLenLeft--; + + spin_lock(&dcache_lock); + + while(!IS_ROOT(dentry) ) + { + nameLen = dentry->d_name.len; + bufLenLeft -= nameLen + 1; + + if (bufLenLeft < 0) + goto err_too_long_unlock; + + currentBufStart -= nameLen; + + memcpy(currentBufStart, dentry->d_name.name, nameLen); + + *--currentBufStart = '/'; + + dentry = dentry->d_parent; + } + + spin_unlock(&dcache_lock); + + // return "/" instead of an empty string for the root directory + if(bufLenLeft == (storeBufLen-1) ) // -1 for the terminating zero + { // dentry is (mount) root directory + *--currentBufStart = '/'; + } + + return currentBufStart; + +err_too_long_unlock: + spin_unlock(&dcache_lock); + + return ERR_PTR(-ENAMETOOLONG); +} + +#endif // LINUX_VERSION_CODE (2.6.38) diff --git a/client_module/source/filesystem/FhgfsOps_versions.h b/client_module/source/filesystem/FhgfsOps_versions.h new file mode 100644 index 0000000..f25ab17 --- /dev/null +++ b/client_module/source/filesystem/FhgfsOps_versions.h @@ -0,0 +1,109 @@ +#ifndef FHGFSOPS_VERSIONS_H_ +#define FHGFSOPS_VERSIONS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + int FhgfsOps_permission(struct mnt_idmap* idmap, struct inode *inode, int mask); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) + int FhgfsOps_permission(struct user_namespace* mnt_userns, struct inode *inode, int mask); +#elif defined(KERNEL_HAS_PERMISSION_2) + int FhgfsOps_permission(struct inode *inode, int mask); +#elif defined(KERNEL_HAS_PERMISSION_FLAGS) + int FhgfsOps_permission(struct inode *inode, int mask, unsigned int flags); +#else + /* <= 2.6.26 */ + int FhgfsOps_permission(struct inode *inode, int mask, struct nameidata *nd); +#endif + +#ifdef KERNEL_HAS_GET_SB_NODEV +extern int FhgfsOps_getSB(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data, struct vfsmount *mnt); +#else +extern struct dentry* FhgfsOps_mount(struct file_system_type *fs_type, + int flags, const char *dev_name, void *data); +#endif // LINUX_VERSION_CODE + + +extern int FhgfsOps_statfs(struct dentry* dentry, struct kstatfs* kstatfs); + + +extern int FhgfsOps_flush(struct file *file, fl_owner_t id); + +#if defined(KERNEL_HAS_KMEMCACHE_CACHE_FLAGS_CTOR) +extern void FhgfsOps_initInodeOnce(void* inode, struct kmem_cache* cache, unsigned long flags); +#elif defined(KERNEL_HAS_KMEMCACHE_CACHE_CTOR) +extern void FhgfsOps_initInodeOnce(struct kmem_cache* cache, void* inode); +#else +extern void FhgfsOps_initInodeOnce(void* inode); +#endif // LINUX_VERSION_CODE + + +////////////// start of kernel method emulators ////////////// + + +#ifndef KERNEL_HAS_GENERIC_FILE_LLSEEK_UNLOCKED +extern loff_t generic_file_llseek_unlocked(struct file *file, loff_t offset, int origin); +#endif // LINUX_VERSION_CODE + +#ifndef KERNEL_HAS_SET_NLINK +static inline void set_nlink(struct inode *inode, unsigned int nlink); +#endif // LINUX_VERSION_CODE + +#ifndef KERNEL_HAS_DENTRY_PATH_RAW +char *dentry_path_raw(struct dentry *dentry, char *buf, int buflen); +#endif + +#ifndef KERNEL_HAS_FILE_INODE +static inline struct inode *file_inode(struct file *f); +#endif // KERNEL_HAS_FILE_INODE + + + +#ifndef KERNEL_HAS_SET_NLINK +/** + * Note: This is just an emulator that does the job for old kernels. + */ +void set_nlink(struct inode *inode, unsigned int nlink) +{ + inode->i_nlink = nlink; +} +#endif // LINUX_VERSION_CODE + +#ifndef KERNEL_HAS_IHOLD +/* + * get additional reference to inode; caller must already hold one. + */ +static inline void ihold(struct inode *inode) +{ + WARN_ON(atomic_inc_return(&inode->i_count) < 2); +} +#endif // KERNEL_HAS_IHOLD + +#ifndef KERNEL_HAS_FILE_DENTRY +static inline struct dentry *file_dentry(const struct file *file) +{ + return file->f_path.dentry; +} +#endif + +/* some ofeds backport this too - and put a define around it ... */ +#if !defined(KERNEL_HAS_FILE_INODE) && !defined(file_inode) + +struct inode *file_inode(struct file *f) +{ + return file_dentry(f)->d_inode; +} + +#endif // KERNEL_HAS_FILE_INODE + + + +#endif /*FHGFSOPS_VERSIONS_H_*/ diff --git a/client_module/source/filesystem/FhgfsXAttrHandlers.c b/client_module/source/filesystem/FhgfsXAttrHandlers.c new file mode 100644 index 0000000..61c564f --- /dev/null +++ b/client_module/source/filesystem/FhgfsXAttrHandlers.c @@ -0,0 +1,461 @@ +#include +#include +#include + +#include "common/Common.h" +#include "FhgfsOpsInode.h" +#include "FhgfsOpsHelper.h" + +#include "FhgfsXAttrHandlers.h" +#define FHGFS_XATTR_USER_PREFIX "user." +#define FHGFS_XATTR_SECURITY_PREFIX "security." + +#ifdef KERNEL_HAS_GET_ACL +/** + * Called when an ACL Xattr is set. Responsible for setting the mode bits corresponding to the + * ACL mask. + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +static int FhgfsXAttrSetACL(const struct xattr_handler* handler, struct mnt_idmap* id_map, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +static int FhgfsXAttrSetACL(const struct xattr_handler* handler, struct user_namespace* mnt_userns, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#else +static int FhgfsXAttrSetACL(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, const void* value, size_t size, int flags) +#endif +{ + int handler_flags = handler->flags; +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttrSetACL(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, const void* value, size_t size, int flags) +{ + int handler_flags = handler->flags; + struct inode* inode = dentry->d_inode; +#else +static int FhgfsXAttrSetACL(struct dentry *dentry, const char *name, const void *value, size_t size, + int flags, int handler_flags) +{ + struct inode* inode = dentry->d_inode; +#endif + char* attrName; + + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, "Called."); + + // Enforce an empty name here (which means the name of the Xattr has to be + // fully given by the POSIX_ACL_XATTR_... defines) + if(strcmp(name, "") ) + return -EINVAL; + + if(!os_inode_owner_or_capable(inode) ) + return -EPERM; + + if(S_ISLNK(inode->i_mode) ) + return -EOPNOTSUPP; + + if(handler_flags == ACL_TYPE_ACCESS) + { + struct posix_acl* acl; + struct iattr attr; + int aclEquivRes; + int setAttrRes; + + // if we set the access ACL, we also need to update the file mode permission bits. + attr.ia_mode = inode->i_mode; + attr.ia_valid = ATTR_MODE; + + acl = os_posix_acl_from_xattr(value, size); + + if(IS_ERR(acl) ) + return PTR_ERR(acl); + + aclEquivRes = posix_acl_equiv_mode(acl, &attr.ia_mode); + if(aclEquivRes == 0) // ACL can be exactly represented by file mode permission bits + { + value = NULL; + } + else if(aclEquivRes < 0) + { + posix_acl_release(acl); + return -EINVAL; + } + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + setAttrRes = FhgfsOps_setattr(&nop_mnt_idmap, dentry, &attr); +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) + setAttrRes = FhgfsOps_setattr(&init_user_ns, dentry, &attr); +#else + setAttrRes = FhgfsOps_setattr(dentry, &attr); +#endif + if(setAttrRes < 0) + return setAttrRes; + + posix_acl_release(acl); + + // Name of the Xattr to be set later + attrName = XATTR_NAME_POSIX_ACL_ACCESS; + } + else if(handler_flags == ACL_TYPE_DEFAULT) + { + // Note: The default acl is not reflected in any file mode permission bits. + // Just check for correctness here, and delete the xattr if the acl is empty. + struct posix_acl* acl; + acl = os_posix_acl_from_xattr(value, size); + + if (IS_ERR(acl)) + return PTR_ERR(acl); + + if (acl == NULL) + value = NULL; + else + posix_acl_release(acl); + + attrName = XATTR_NAME_POSIX_ACL_DEFAULT; + } + else + return -EOPNOTSUPP; + + if(value) + return FhgfsOps_setxattr(dentry, attrName, value, size, flags); + else // value == NULL: Remove the ACL extended attribute. + { + int removeRes = FhgfsOps_removexattr(dentry, attrName); + if (removeRes == 0 || removeRes == -ENODATA) // If XA didn't exist anyway, return 0. + return 0; + else + return removeRes; + } + +} + +/** + * The get-function of the xattr handler which handles the XATTR_NAME_POSIX_ACL_ACCESS and + * XATTR_NAME_POSIX_ACL_DEFAULT xattrs. + * @param name has to be a pointer to an empty string (""). + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) +static int FhgfsXAttrGetACL(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, void* value, size_t size) +{ + int handler_flags = handler->flags; +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttrGetACL(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, void* value, size_t size) +{ + int handler_flags = handler->flags; +#else +int FhgfsXAttrGetACL(struct dentry* dentry, const char* name, void* value, size_t size, + int handler_flags) +{ +#endif + char* attrName; + + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, "Called."); + + // For simplicity we enforce an empty name here (which means the name of the Xattr has to be + // fully given by the POSIX_ACL_XATTR_... defines) + if(strcmp(name, "") ) + return -EINVAL; + + if(handler_flags == ACL_TYPE_ACCESS) + attrName = XATTR_NAME_POSIX_ACL_ACCESS; + else if(handler_flags == ACL_TYPE_DEFAULT) + attrName = XATTR_NAME_POSIX_ACL_DEFAULT; + else + return -EOPNOTSUPP; + + return FhgfsOps_getxattr(dentry, attrName, value, size); +} +#endif // KERNEL_HAS_GET_ACL + +/** + * The get-function which is used for all the user.* xattrs. + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) +static int FhgfsXAttr_getUser(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, void* value, size_t size) +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttr_getUser(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, void* value, size_t size) +#elif defined(KERNEL_HAS_DENTRY_XATTR_HANDLER) +static int FhgfsXAttr_getUser(struct dentry* dentry, const char* name, void* value, size_t size, + int handler_flags) +#else +static int FhgfsXAttr_getUser(struct inode* inode, const char* name, void* value, size_t size) +#endif +{ + FhgfsOpsErr res; + char* prefixedName = os_kmalloc(strlen(name) + sizeof(FHGFS_XATTR_USER_PREFIX) ); + // Note: strlen does not count the terminating '\0', but sizeof does. So we have space for + // exactly one '\0' which coincidally is just what we need. + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, + "(name: %s; size: %u)", name, size); +#else + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(inode->i_sb), NULL, inode, __func__, + "(name: %s; size: %u)", name, size); +#endif + + // add name prefix which has been removed by the generic function + if(!prefixedName) + return -ENOMEM; + + strcpy(prefixedName, FHGFS_XATTR_USER_PREFIX); + strcpy(prefixedName + sizeof(FHGFS_XATTR_USER_PREFIX) - 1, name); // sizeof-1 to remove the '\0' + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + res = FhgfsOps_getxattr(dentry, prefixedName, value, size); +#else + res = FhgfsOps_getxattr(inode, prefixedName, value, size); +#endif + + kfree(prefixedName); + return res; +} + +/** + * The set-function which is used for all the user.* xattrs. + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +static int FhgfsXAttr_setUser(const struct xattr_handler* handler, struct mnt_idmap* id_map, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +static int FhgfsXAttr_setUser(const struct xattr_handler* handler, struct user_namespace* mnt_userns, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#else +static int FhgfsXAttr_setUser(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, const void* value, size_t size, int flags) +#endif + +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttr_setUser(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, const void* value, size_t size, int flags) +#elif defined(KERNEL_HAS_DENTRY_XATTR_HANDLER) +static int FhgfsXAttr_setUser(struct dentry* dentry, const char* name, const void* value, size_t size, + int flags, int handler_flags) +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER +{ + FhgfsOpsErr res; + char* prefixedName = os_kmalloc(strlen(name) + sizeof(FHGFS_XATTR_USER_PREFIX) ); + + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, + "(name: %s)", name); +#else + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(inode->i_sb), NULL, inode, __func__, + "(name: %s)", name); +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + + // add name prefix which has been removed by the generic function + if(!prefixedName) + return -ENOMEM; + strcpy(prefixedName, FHGFS_XATTR_USER_PREFIX); + strcpy(prefixedName + sizeof(FHGFS_XATTR_USER_PREFIX) - 1, name); // sizeof-1 to remove the '\0' + + if (value) + { +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + res = FhgfsOps_setxattr(dentry, prefixedName, value, size, flags); +#else + res = FhgfsOps_setxattr(inode, prefixedName, value, size, flags); +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + } + else + { + res = FhgfsOps_removexattr(dentry, prefixedName); + } + + kfree(prefixedName); + return res; +} + + +/** + * The get-function which is used for all the security.* xattrs. + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) +static int FhgfsXAttr_getSecurity(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, void* value, size_t size) +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttr_getSecurity(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, void* value, size_t size) +#elif defined(KERNEL_HAS_DENTRY_XATTR_HANDLER) +static int FhgfsXAttr_getSecurity(struct dentry* dentry, const char* name, void* value, size_t size, + int handler_flags) +#else +static int FhgfsXAttr_getSecurity(struct inode* inode, const char* name, void* value, size_t size) +#endif +{ + FhgfsOpsErr res; + char* prefixedName = os_kmalloc(strlen(name) + sizeof(FHGFS_XATTR_SECURITY_PREFIX) ); + // Note: strlen does not count the terminating '\0', but sizeof does. So we have space for + // exactly one '\0' which coincidally is just what we need. + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, + "(name: %s; size: %u)", name, size); +#else + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(inode->i_sb), NULL, inode, __func__, + "(name: %s; size: %u)", name, size); +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + + // add name prefix which has been removed by the generic function + if(!prefixedName) + return -ENOMEM; + + strcpy(prefixedName, FHGFS_XATTR_SECURITY_PREFIX); + strcpy(prefixedName + sizeof(FHGFS_XATTR_SECURITY_PREFIX) - 1, name); // sizeof-1 to remove '\0' + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + res = FhgfsOps_getxattr(dentry, prefixedName, value, size); +#else + res = FhgfsOps_getxattr(inode, prefixedName, value, size); +#endif // KERNEL_HAS_DENTRY_XATTR_HANDLER + + kfree(prefixedName); + return res; +} + +/** + * The set-function which is used for all the security.* xattrs. + */ +#if defined(KERNEL_HAS_XATTR_HANDLERS_INODE_ARG) +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) +static int FhgfsXAttr_setSecurity(const struct xattr_handler* handler, struct mnt_idmap* idmap, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) +static int FhgfsXAttr_setSecurity(const struct xattr_handler* handler, struct user_namespace* mnt_userns, + struct dentry* dentry, struct inode* inode, const char* name, const void* value, size_t size, + int flags) +#else +static int FhgfsXAttr_setSecurity(const struct xattr_handler* handler, struct dentry* dentry, + struct inode* inode, const char* name, const void* value, size_t size, int flags) +#endif +#elif defined(KERNEL_HAS_XATTR_HANDLER_PTR_ARG) +static int FhgfsXAttr_setSecurity(const struct xattr_handler* handler, struct dentry* dentry, + const char* name, const void* value, size_t size, int flags) +#elif defined(KERNEL_HAS_DENTRY_XATTR_HANDLER) +static int FhgfsXAttr_setSecurity(struct dentry* dentry, const char* name, const void* value, size_t size, + int flags, int handler_flags) +#endif +{ + FhgfsOpsErr res; + char* prefixedName = os_kmalloc(strlen(name) + sizeof(FHGFS_XATTR_SECURITY_PREFIX) ); + + +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(dentry->d_sb), dentry, NULL, __func__, + "(name: %s)", name); +#else + FhgfsOpsHelper_logOpDebug(FhgfsOps_getApp(inode->i_sb), NULL, inode, __func__, + "(name: %s)", name); +#endif + + // add name prefix which has been removed by the generic function + if(!prefixedName) + return -ENOMEM; + strcpy(prefixedName, FHGFS_XATTR_SECURITY_PREFIX); + strcpy(prefixedName + sizeof(FHGFS_XATTR_SECURITY_PREFIX) - 1, name); // sizeof-1 to remove '\0' + + if (value) + { +#ifdef KERNEL_HAS_DENTRY_XATTR_HANDLER + res = FhgfsOps_setxattr(dentry, prefixedName, value, size, flags); +#else + res = FhgfsOps_setxattr(inode, prefixedName, value, size, flags); +#endif + } + else + { + res = FhgfsOps_removexattr(dentry, prefixedName); + } + + kfree(prefixedName); + return res; +} + +#ifdef KERNEL_HAS_GET_ACL +struct xattr_handler fhgfs_xattr_acl_access_handler = +{ +#ifdef KERNEL_HAS_XATTR_HANDLER_NAME + .name = XATTR_NAME_POSIX_ACL_ACCESS, +#else + .prefix = XATTR_NAME_POSIX_ACL_ACCESS, +#endif + .flags = ACL_TYPE_ACCESS, + .list = NULL, + .get = FhgfsXAttrGetACL, + .set = FhgfsXAttrSetACL, +}; + +struct xattr_handler fhgfs_xattr_acl_default_handler = +{ +#ifdef KERNEL_HAS_XATTR_HANDLER_NAME + .name = XATTR_NAME_POSIX_ACL_DEFAULT, +#else + .prefix = XATTR_NAME_POSIX_ACL_DEFAULT, +#endif + .flags = ACL_TYPE_DEFAULT, + .list = NULL, + .get = FhgfsXAttrGetACL, + .set = FhgfsXAttrSetACL, +}; +#endif // KERNEL_HAS_GET_ACL + +struct xattr_handler fhgfs_xattr_user_handler = +{ + .prefix = FHGFS_XATTR_USER_PREFIX, + .list = NULL, + .set = FhgfsXAttr_setUser, + .get = FhgfsXAttr_getUser, +}; + +struct xattr_handler fhgfs_xattr_security_handler = +{ + .prefix = FHGFS_XATTR_SECURITY_PREFIX, + .list = NULL, + .set = FhgfsXAttr_setSecurity, + .get = FhgfsXAttr_getSecurity, +}; + +#if defined(KERNEL_HAS_CONST_XATTR_CONST_PTR_HANDLER) +const struct xattr_handler* const fhgfs_xattr_handlers[] = +#elif defined(KERNEL_HAS_CONST_XATTR_HANDLER) +const struct xattr_handler* fhgfs_xattr_handlers[] = +#else +struct xattr_handler* fhgfs_xattr_handlers[] = +#endif +{ +#ifdef KERNEL_HAS_GET_ACL + &fhgfs_xattr_acl_access_handler, + &fhgfs_xattr_acl_default_handler, +#endif + &fhgfs_xattr_user_handler, + &fhgfs_xattr_security_handler, + NULL +}; + +#if defined(KERNEL_HAS_CONST_XATTR_CONST_PTR_HANDLER) +const struct xattr_handler* const fhgfs_xattr_handlers_noacl[] = +#elif defined(KERNEL_HAS_CONST_XATTR_HANDLER) +const struct xattr_handler* fhgfs_xattr_handlers_noacl[] = +#else +struct xattr_handler* fhgfs_xattr_handlers_noacl[] = +#endif +{ + &fhgfs_xattr_user_handler, + &fhgfs_xattr_security_handler, + NULL +}; + diff --git a/client_module/source/filesystem/FhgfsXAttrHandlers.h b/client_module/source/filesystem/FhgfsXAttrHandlers.h new file mode 100644 index 0000000..9528721 --- /dev/null +++ b/client_module/source/filesystem/FhgfsXAttrHandlers.h @@ -0,0 +1,29 @@ +#ifndef FHGFSXATTRHANDLERS_H_ +#define FHGFSXATTRHANDLERS_H_ + +#include + +#if defined(KERNEL_HAS_CONST_XATTR_CONST_PTR_HANDLER) + +#ifdef KERNEL_HAS_GET_ACL +extern const struct xattr_handler* const fhgfs_xattr_handlers[]; +#endif +extern const struct xattr_handler* const fhgfs_xattr_handlers_noacl[]; + +#elif defined(KERNEL_HAS_CONST_XATTR_HANDLER) + +#ifdef KERNEL_HAS_GET_ACL +extern const struct xattr_handler* fhgfs_xattr_handlers[]; +#endif +extern const struct xattr_handler* fhgfs_xattr_handlers_noacl[]; + +#else + +#ifdef KERNEL_HAS_GET_ACL +extern struct xattr_handler* fhgfs_xattr_handlers[]; +#endif +extern struct xattr_handler* fhgfs_xattr_handlers_noacl[]; +#endif + + +#endif /* FHGFSXATTRHANDLERS_H_ */ diff --git a/client_module/source/filesystem/FsDirInfo.h b/client_module/source/filesystem/FsDirInfo.h new file mode 100644 index 0000000..bffbbd9 --- /dev/null +++ b/client_module/source/filesystem/FsDirInfo.h @@ -0,0 +1,141 @@ +#ifndef FSDIRINFO_H_ +#define FSDIRINFO_H_ + +#include +#include +#include +#include +#include +#include "FsObjectInfo.h" + + +struct FsDirInfo; +typedef struct FsDirInfo FsDirInfo; + + +static inline void FsDirInfo_init(FsDirInfo* this, App* app); +static inline FsDirInfo* FsDirInfo_construct(App* app); +static inline void FsDirInfo_uninit(FsObjectInfo* this); + +// getters & setters +static inline loff_t FsDirInfo_getServerOffset(FsDirInfo* this); +static inline void FsDirInfo_setServerOffset(FsDirInfo* this, int64_t serverOffset); +static inline size_t FsDirInfo_getCurrentContentsPos(FsDirInfo* this); +static inline void FsDirInfo_setCurrentContentsPos(FsDirInfo* this, size_t currentContentsPos); +static inline struct StrCpyVec* FsDirInfo_getDirContents(FsDirInfo* this); +static inline struct StrCpyVec* FsDirInfo_getEntryIDs(FsDirInfo* this); +static inline struct UInt8Vec* FsDirInfo_getDirContentsTypes(FsDirInfo* this); +static inline struct Int64CpyVec* FsDirInfo_getServerOffsets(FsDirInfo* this); +static inline void FsDirInfo_setEndOfDir(FsDirInfo* this, bool endOfDir); +static inline bool FsDirInfo_getEndOfDir(FsDirInfo* this); + + +struct FsDirInfo +{ + FsObjectInfo fsObjectInfo; + + StrCpyVec dirContents; // entry names + UInt8Vec dirContentsTypes; // DirEntryType elements matching dirContents vector + StrCpyVec entryIDs; // entryID elements matching dirContents vector + Int64CpyVec serverOffsets; // dir entry offsets for telldir() matching dirContents vector + int64_t serverOffset; /* offset for the next incremental list request to the server + (equals last element of serverOffsets vector) */ + size_t currentContentsPos; // current local pos in dirContents (>=0 && dirContents); + UInt8Vec_init(&this->dirContentsTypes); + StrCpyVec_init(&this->entryIDs); + Int64CpyVec_init(&this->serverOffsets); + + this->serverOffset = 0; + this->currentContentsPos = 0; + this->endOfDir = false; + + // assign virtual functions + ( (FsObjectInfo*)this)->uninit = FsDirInfo_uninit; +} + +struct FsDirInfo* FsDirInfo_construct(App* app) +{ + struct FsDirInfo* this = (FsDirInfo*)os_kmalloc(sizeof(*this) ); + + if(likely(this) ) + FsDirInfo_init(this, app); + + return this; +} + +void FsDirInfo_uninit(FsObjectInfo* this) +{ + FsDirInfo* thisCast = (FsDirInfo*)this; + + StrCpyVec_uninit(&thisCast->dirContents); + UInt8Vec_uninit(&thisCast->dirContentsTypes); + StrCpyVec_uninit(&thisCast->entryIDs); + Int64CpyVec_uninit(&thisCast->serverOffsets); +} + +loff_t FsDirInfo_getServerOffset(FsDirInfo* this) +{ + return this->serverOffset; +} + +void FsDirInfo_setServerOffset(FsDirInfo* this, int64_t serverOffset) +{ + this->serverOffset = serverOffset; +} + +size_t FsDirInfo_getCurrentContentsPos(FsDirInfo* this) +{ + return this->currentContentsPos; +} + +void FsDirInfo_setCurrentContentsPos(FsDirInfo* this, size_t currentContentsPos) +{ + this->currentContentsPos = currentContentsPos; +} + +StrCpyVec* FsDirInfo_getDirContents(FsDirInfo* this) +{ + return &this->dirContents; +} + +StrCpyVec* FsDirInfo_getEntryIDs(FsDirInfo* this) +{ + return &this->entryIDs; +} + +/** + * @return vector of DirEntryType elements, matching dirContents vector + */ +UInt8Vec* FsDirInfo_getDirContentsTypes(FsDirInfo* this) +{ + return &this->dirContentsTypes; +} + +Int64CpyVec* FsDirInfo_getServerOffsets(FsDirInfo* this) +{ + return &this->serverOffsets; +} + +void FsDirInfo_setEndOfDir(FsDirInfo* this, bool endOfDir) +{ + this->endOfDir = endOfDir; +} + +bool FsDirInfo_getEndOfDir(FsDirInfo* this) +{ + return this->endOfDir; +} + + + +#endif /*FSDIRINFO_H_*/ diff --git a/client_module/source/filesystem/FsFileInfo.c b/client_module/source/filesystem/FsFileInfo.c new file mode 100644 index 0000000..362c3b3 --- /dev/null +++ b/client_module/source/filesystem/FsFileInfo.c @@ -0,0 +1,156 @@ +#include +#include +#include + + +struct FsFileInfo +{ + FsObjectInfo fsObjectInfo; + + unsigned accessFlags; + FileHandleType handleType; + bool appending; + + ssize_t cacheHits; // hits/misses counter (min and max limited by thresholds) + bool allowCaching; // false when O_DIRECT or caching disabled in config + loff_t lastReadOffset; // offset after last read (to decide if IO would be a cache hit) + loff_t lastWriteOffset; // offset after last write (to decide if IO would be a cache hit) + + bool usedEntryLocking; // true when entry lock methods were used (needed for cleanup) +}; + + +/** + * @param accessFlags fhgfs access flags (not OS flags) + */ +void FsFileInfo_init(FsFileInfo* this, App* app, unsigned accessFlags, FileHandleType handleType) +{ + FsObjectInfo_init( (FsObjectInfo*)this, app, FsObjectType_FILE); + + this->accessFlags = accessFlags; + this->handleType = handleType; + this->appending = false; + + this->cacheHits = FSFILEINFO_CACHE_HITS_INITIAL; + this->allowCaching = true; + this->lastReadOffset = 0; + this->lastWriteOffset = 0; + + this->usedEntryLocking = false; + + // assign virtual functions + ( (FsObjectInfo*)this)->uninit = FsFileInfo_uninit; +} + +/** + * @param accessFlags fhgfs access flags (not OS flags) + */ +struct FsFileInfo* FsFileInfo_construct(App* app, unsigned accessFlags, FileHandleType handleType) +{ + struct FsFileInfo* this = (FsFileInfo*)os_kmalloc(sizeof(*this) ); + + if(likely(this) ) + FsFileInfo_init(this, app, accessFlags, handleType); + + return this; +} + +void FsFileInfo_uninit(FsObjectInfo* this) +{ +} + +/** + * Increase cache hits counter. + * + * Note: Hits counter won't get higher than a certain threshold. + */ +void FsFileInfo_incCacheHits(FsFileInfo* this) +{ + if(this->cacheHits < FSFILEINFO_CACHE_HITS_THRESHOLD) + this->cacheHits++; +} + +/** + * Decrease cache hits counter. + * + * Note: Hits counter won't get lower than a certain threshold. + */ +void FsFileInfo_decCacheHits(FsFileInfo* this) +{ + if(this->cacheHits > FSFILEINFO_CACHE_MISS_THRESHOLD) + this->cacheHits--; +} + + +unsigned FsFileInfo_getAccessFlags(FsFileInfo* this) +{ + return this->accessFlags; +} + +FileHandleType FsFileInfo_getHandleType(FsFileInfo* this) +{ + return this->handleType; +} + + +void FsFileInfo_setAppending(FsFileInfo* this, bool appending) +{ + this->appending = appending; +} + +bool FsFileInfo_getAppending(FsFileInfo* this) +{ + return this->appending; +} + +ssize_t FsFileInfo_getCacheHits(FsFileInfo* this) +{ + return this->cacheHits; +} + +void FsFileInfo_setAllowCaching(FsFileInfo* this, bool allowCaching) +{ + this->allowCaching = allowCaching; +} + +bool FsFileInfo_getAllowCaching(FsFileInfo* this) +{ + return this->allowCaching; +} + +void FsFileInfo_setLastReadOffset(FsFileInfo* this, loff_t lastReadOffset) +{ + this->lastReadOffset = lastReadOffset; +} + +loff_t FsFileInfo_getLastReadOffset(FsFileInfo* this) +{ + return this->lastReadOffset; +} + +void FsFileInfo_setLastWriteOffset(FsFileInfo* this, loff_t lastWriteOffset) +{ + this->lastWriteOffset = lastWriteOffset; +} + +loff_t FsFileInfo_getLastWriteOffset(FsFileInfo* this) +{ + return this->lastWriteOffset; +} + +void FsFileInfo_getIOInfo(FsFileInfo* this, struct FhgfsInode* fhgfsInode, + struct RemotingIOInfo* outIOInfo) +{ + FhgfsInode_getRefIOInfo(fhgfsInode, this->handleType, this->accessFlags, outIOInfo); +} + +void FsFileInfo_setUsedEntryLocking(FsFileInfo* this) +{ + this->usedEntryLocking = true; +} + +bool FsFileInfo_getUsedEntryLocking(FsFileInfo* this) +{ + return this->usedEntryLocking; +} + diff --git a/client_module/source/filesystem/FsFileInfo.h b/client_module/source/filesystem/FsFileInfo.h new file mode 100644 index 0000000..9cba09e --- /dev/null +++ b/client_module/source/filesystem/FsFileInfo.h @@ -0,0 +1,51 @@ +#ifndef FSFILEINFO_H_ +#define FSFILEINFO_H_ + +#include +#include +#include "FsObjectInfo.h" + + +#define FSFILEINFO_CACHE_HITS_INITIAL (3) /* some cache-optimistic initial hits value */ +#define FSFILEINFO_CACHE_HITS_THRESHOLD (5) /* hits won't ever get higher than this number */ +#define FSFILEINFO_CACHE_MISS_THRESHOLD (-5) /* hits won't ever get lower than this number */ +#define FSFILEINFO_CACHE_SLOWSTART_READLEN (64*1024) /* smaller read-ahead for offset==0, e.g. if + some process looks only at file starts */ + + +enum FileBufferType; // forward declaration +struct StripePattern; // forward declaration +struct FhgfsInode; // forward declaration +struct RemotingIOInfo; // forward declaration + +struct FsFileInfo; +typedef struct FsFileInfo FsFileInfo; + + +extern void FsFileInfo_init(FsFileInfo* this, App* app, unsigned accessFlags, + FileHandleType handleType); +extern FsFileInfo* FsFileInfo_construct(App* app, unsigned accessFlags, FileHandleType handleType); +extern void FsFileInfo_uninit(FsObjectInfo* this); + +extern void FsFileInfo_incCacheHits(FsFileInfo* this); +extern void FsFileInfo_decCacheHits(FsFileInfo* this); + +// getters & setters +extern unsigned FsFileInfo_getAccessFlags(FsFileInfo* this); +extern FileHandleType FsFileInfo_getHandleType(FsFileInfo* this); +extern void FsFileInfo_setAppending(FsFileInfo* this, bool appending); +extern bool FsFileInfo_getAppending(FsFileInfo* this); +extern ssize_t FsFileInfo_getCacheHits(FsFileInfo* this); +extern void FsFileInfo_setAllowCaching(FsFileInfo* this, bool allowCaching); +extern bool FsFileInfo_getAllowCaching(FsFileInfo* this); +extern void FsFileInfo_setLastReadOffset(FsFileInfo* this, loff_t lastReadOffset); +extern loff_t FsFileInfo_getLastReadOffset(FsFileInfo* this); +extern void FsFileInfo_setLastWriteOffset(FsFileInfo* this, loff_t lastWriteOffset); +extern loff_t FsFileInfo_getLastWriteOffset(FsFileInfo* this); +extern void FsFileInfo_getIOInfo(FsFileInfo* this, struct FhgfsInode* fhgfsInode, + struct RemotingIOInfo* outIOInfo); +extern void FsFileInfo_setUsedEntryLocking(FsFileInfo* this); +extern bool FsFileInfo_getUsedEntryLocking(FsFileInfo* this); + + +#endif /*FSFILEINFO_H_*/ diff --git a/client_module/source/filesystem/FsObjectInfo.h b/client_module/source/filesystem/FsObjectInfo.h new file mode 100644 index 0000000..08e5d93 --- /dev/null +++ b/client_module/source/filesystem/FsObjectInfo.h @@ -0,0 +1,68 @@ +#ifndef FSOBJECTINFO_H_ +#define FSOBJECTINFO_H_ + +#include +#include + + +enum FsObjectType + {FsObjectType_DIRECTORY=1, FsObjectType_FILE=2}; +typedef enum FsObjectType FsObjectType; + +/** + * Note: Consider this to be an abstract class. Mind the virtual function pointers. + */ + +struct FsObjectInfo; +typedef struct FsObjectInfo FsObjectInfo; + +static inline void FsObjectInfo_init(FsObjectInfo* this, App* app, FsObjectType objectType); + +static inline void FsObjectInfo_virtualDestruct(FsObjectInfo* this); + + +// getters & setters +static inline App* FsObjectInfo_getApp(FsObjectInfo* this); +static inline FsObjectType FsObjectInfo_getObjectType(FsObjectInfo* this); + + +struct FsObjectInfo +{ + App* app; + FsObjectType objectType; + + // virtual functions + void (*uninit) (FsObjectInfo* this); +}; + + +void FsObjectInfo_init(FsObjectInfo* this, App* app, FsObjectType objectType) +{ + this->app = app; + this->objectType = objectType; + + // clear virtual function pointer + this->uninit = NULL; +} + +/** + * Calls the virtual uninit method and kfrees the object. + */ +void FsObjectInfo_virtualDestruct(FsObjectInfo* this) +{ + this->uninit(this); + kfree(this); +} + +App* FsObjectInfo_getApp(FsObjectInfo* this) +{ + return this->app; +} + +FsObjectType FsObjectInfo_getObjectType(FsObjectInfo* this) +{ + return this->objectType; +} + + +#endif /*FSOBJECTINFO_H_*/ diff --git a/client_module/source/filesystem/ProcFs.c b/client_module/source/filesystem/ProcFs.c new file mode 100644 index 0000000..e714772 --- /dev/null +++ b/client_module/source/filesystem/ProcFs.c @@ -0,0 +1,742 @@ +#include +#include "ProcFs.h" + +#include +#include + + +#define BEEGFS_PROC_DIR_NAME "fs/" BEEGFS_MODULE_NAME_STR +#define BEEGFS_PROC_NAMEBUF_LEN 4096 + +#define BEEGFS_PROC_ENTRY_CONFIG "config" +#define BEEGFS_PROC_ENTRY_STATUS ".status" +#define BEEGFS_PROC_ENTRY_FSUUID "fs_uuid" +#define BEEGFS_PROC_ENTRY_MGMTNODES "mgmt_nodes" +#define BEEGFS_PROC_ENTRY_METANODES "meta_nodes" +#define BEEGFS_PROC_ENTRY_STORAGENODES "storage_nodes" +#define BEEGFS_PROC_ENTRY_CLIENTINFO "client_info" +#define BEEGFS_PROC_ENTRY_RETRIESENABLED "conn_retries_enabled" +#define BEEGFS_PROC_ENTRY_NETBENCHENABLED "netbench_mode" +#define BEEGFS_PROC_ENTRY_DROPCONNS "drop_conns" +#define BEEGFS_PROC_ENTRY_LOGLEVELS "log_levels" +#define BEEGFS_PROC_ENTRY_METATARGETSTATES "meta_target_state" +#define BEEGFS_PROC_ENTRY_STORAGETARGETSTATES "storage_target_state" +#define BEEGFS_PROC_ENTRY_REMAPCONNFAILURE "remap_connection_failure" + + +/** + * Initializer for read-only proc file ops + */ +#if defined(KERNEL_HAS_PROC_OPS) +#define BEEGFS_PROC_FOPS_INITIALIZER \ + .proc_open = __ProcFs_open, \ + .proc_read = seq_read, \ + .proc_lseek = seq_lseek, \ + .proc_release = single_release +#else +#define BEEGFS_PROC_FOPS_INITIALIZER \ + .open = __ProcFs_open, \ + .read = seq_read, \ + .llseek = seq_lseek, \ + .release = single_release +#endif + +#if defined(KERNEL_HAS_PROC_OPS) +#define PROC_OPS_WRITE_MEMBER proc_write +#else +#define PROC_OPS_WRITE_MEMBER write +#endif + + +/** + * generic file ops for procfs entries + */ +#if defined(KERNEL_HAS_PROC_OPS) +static const struct proc_ops fhgfs_proc_fops = +#else +static const struct file_operations fhgfs_proc_fops = +#endif +{ + BEEGFS_PROC_FOPS_INITIALIZER +}; + +/* + * for read-only entries: contains name and "show method" for a procfs entry + */ +struct fhgfs_proc_file +{ + char name[32]; // filename + int (*show)(struct seq_file *, void *); // the "show method" of this file +}; + +/** + * all our read-only procfs entries (terminated by empty element) + */ +static const struct fhgfs_proc_file fhgfs_proc_files[] = +{ + { BEEGFS_PROC_ENTRY_CONFIG, &__ProcFs_readV2_config }, + { BEEGFS_PROC_ENTRY_STATUS, &__ProcFs_readV2_status }, + { BEEGFS_PROC_ENTRY_FSUUID, &__ProcFs_readV2_fsUUID }, + { BEEGFS_PROC_ENTRY_MGMTNODES, &__ProcFs_readV2_mgmtNodes }, + { BEEGFS_PROC_ENTRY_METANODES, &__ProcFs_readV2_metaNodes }, + { BEEGFS_PROC_ENTRY_STORAGENODES, &__ProcFs_readV2_storageNodes }, + { BEEGFS_PROC_ENTRY_CLIENTINFO, &__ProcFs_readV2_clientInfo }, + { BEEGFS_PROC_ENTRY_METATARGETSTATES, &__ProcFs_readV2_metaTargetStates }, + { BEEGFS_PROC_ENTRY_STORAGETARGETSTATES, &__ProcFs_readV2_storageTargetStates }, + { "", NULL } // last element must be empty (for loop termination) +}; + +/* + * for read+write entries: contains name, show method and write method for a procfs entry + */ +struct fhgfs_proc_file_rw +{ + char name[32]; // filename + int (*show)(struct seq_file *, void *); // the show method of this file + //ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *); // the write method +#if defined(KERNEL_HAS_PROC_OPS) + struct proc_ops proc_fops; +#else + struct file_operations proc_fops; +#endif +}; + + +/** + * all our read+write procfs entries (terminated by empty element). + * + * other than for read-only entries, we need to assign different write methods during proc entry + * registration, so we need indidivual file_operations for each entry here. + */ +static const struct fhgfs_proc_file_rw fhgfs_proc_files_rw[] = +{ + { BEEGFS_PROC_ENTRY_RETRIESENABLED, &__ProcFs_readV2_connRetriesEnabled, + { + BEEGFS_PROC_FOPS_INITIALIZER, + .PROC_OPS_WRITE_MEMBER = &__ProcFs_writeV2_connRetriesEnabled, + }, + }, + { BEEGFS_PROC_ENTRY_NETBENCHENABLED, &__ProcFs_readV2_netBenchModeEnabled, + { + BEEGFS_PROC_FOPS_INITIALIZER, + .PROC_OPS_WRITE_MEMBER = &__ProcFs_writeV2_netBenchModeEnabled, + }, + }, + { BEEGFS_PROC_ENTRY_DROPCONNS, &__ProcFs_readV2_nothing, + { + BEEGFS_PROC_FOPS_INITIALIZER, + .PROC_OPS_WRITE_MEMBER = &__ProcFs_writeV2_dropConns, + }, + }, + { BEEGFS_PROC_ENTRY_LOGLEVELS, &__ProcFs_readV2_logLevels, + { + BEEGFS_PROC_FOPS_INITIALIZER, + .PROC_OPS_WRITE_MEMBER = &__ProcFs_writeV2_logLevels, + }, + }, + { BEEGFS_PROC_ENTRY_REMAPCONNFAILURE, &__ProcFs_read_remapConnectionFailure, + { + BEEGFS_PROC_FOPS_INITIALIZER, + .PROC_OPS_WRITE_MEMBER = &__ProcFs_write_remapConnectionFailure, + }, + }, + // last element must be empty (for loop termination) + {{ 0 }} +}; + + +/** + * Creates the general parent dir. Meant to be called only once at module load. + */ +void ProcFs_createGeneralDir(void) +{ + struct proc_dir_entry* procDir; + + procDir = proc_mkdir(BEEGFS_PROC_DIR_NAME, NULL); + if(!procDir) + printk_fhgfs(KERN_INFO, "Failed to create proc dir: " BEEGFS_PROC_DIR_NAME "\n"); +} + +/** + * Removes the general parent dir. Meant to be called only once at module unload. + */ +void ProcFs_removeGeneralDir(void) +{ + remove_proc_entry(BEEGFS_PROC_DIR_NAME, NULL); +} + +/** + * Creates the dir and entries for a specific mountpoint. + * Note: Uses sessionID to create a unique dir for the mountpoint. + */ +void ProcFs_createEntries(App* app) +{ + NodeString sessionID; + Node* localNode = App_getLocalNode(app); + + struct proc_dir_entry* procDir; + + const struct fhgfs_proc_file* procFile; + const struct fhgfs_proc_file_rw* procFileRW; + + + // create unique directory for this clientID and store app pointer as "->data" + + char* dirNameBuf = vmalloc(BEEGFS_PROC_NAMEBUF_LEN); + Node_copyAlias(localNode, &sessionID); + scnprintf(dirNameBuf, BEEGFS_PROC_NAMEBUF_LEN, BEEGFS_PROC_DIR_NAME "/%s", sessionID.buf); + + procDir = __ProcFs_mkDir(dirNameBuf, app); + if(!procDir) + { + printk_fhgfs(KERN_INFO, "Failed to create proc dir: %s\n", dirNameBuf); + goto clean_up; + } + + + // create entries + + + /* note: linux-3.10 kills create_proc_(read_)entry and uses proc_create_data instead. + proc_create_data existed for ealier linux version already, so we use it there, too. */ + + + // create read-only proc files + + for(procFile = fhgfs_proc_files; procFile->name[0]; procFile++) + { + struct proc_dir_entry* currentProcFsEntry = proc_create_data( + procFile->name, S_IFREG | S_IRUGO, procDir, &fhgfs_proc_fops, procFile->show); + + if(!currentProcFsEntry) + { + printk_fhgfs(KERN_INFO, "Failed to create read-only proc entry in %s: %s\n", + dirNameBuf, procFile->name); + goto clean_up; + } + } + + // create read+write proc files + + for(procFileRW = fhgfs_proc_files_rw; procFileRW->name[0]; procFileRW++) + { + struct proc_dir_entry* currentProcFsEntry = proc_create_data( + procFileRW->name, S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP, procDir, &procFileRW->proc_fops, + procFileRW->show); + + if(!currentProcFsEntry) + { + printk_fhgfs(KERN_INFO, "Failed to create read+write proc entry in %s: %s\n", + dirNameBuf, procFileRW->name); + goto clean_up; + } + } + +clean_up: + SAFE_VFREE(dirNameBuf); +} + +/** + * Removes the dir and entries for a specific mountpoint. + * Note: Uses sessionID for unique dir of the mountpoint. + */ +void ProcFs_removeEntries(App* app) +{ + char* dirNameBuf = vmalloc(BEEGFS_PROC_NAMEBUF_LEN); + char* entryNameBuf = vmalloc(BEEGFS_PROC_NAMEBUF_LEN); + Node* localNode = App_getLocalNode(app); + NodeString sessionID; + Node_copyAlias(localNode, &sessionID); + scnprintf(dirNameBuf, BEEGFS_PROC_NAMEBUF_LEN, BEEGFS_PROC_DIR_NAME "/%s", sessionID.buf); + + // remove entries + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_CONFIG); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_STATUS); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_FSUUID); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_MGMTNODES); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_METANODES); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_STORAGENODES); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_CLIENTINFO); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_METATARGETSTATES); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_STORAGETARGETSTATES); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_RETRIESENABLED); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_REMAPCONNFAILURE); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_NETBENCHENABLED); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_DROPCONNS); + remove_proc_entry(entryNameBuf, NULL); + + scnprintf(entryNameBuf, BEEGFS_PROC_NAMEBUF_LEN, "%s/%s", + dirNameBuf, BEEGFS_PROC_ENTRY_LOGLEVELS); + remove_proc_entry(entryNameBuf, NULL); + + + // remove unique dir + remove_proc_entry(dirNameBuf, NULL); + + + SAFE_VFREE(dirNameBuf); + SAFE_VFREE(entryNameBuf); +} + +/** + * called when a procfs file is being opened. + * + * this method handles the assignment of the corresponding readV2 method for a certain entry. + */ +int __ProcFs_open(struct inode* inode, struct file* file) +{ + int (*show)(struct seq_file *, void *) = __ProcFs_getProcDirEntryDataField(inode); + App* app = __ProcFs_getProcParentDirEntryDataField(inode); // (app is ->data in parent dir) + + return single_open(file, show, app); +} + +/** + * Does not return anything to the reading process. + * Intended for proc entries that are write-only. + */ +int __ProcFs_readV2_nothing(struct seq_file* file, void* p) +{ + return 0; +} + + +/** + * Does not return anything to the reading process. + * Intended for proc entries that are write-only. + * + * @param data specified at entry creation + */ +int ProcFs_read_nothing(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + *eof = 1; + return 0; +} + + +int __ProcFs_readV2_config(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_config(file, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_config(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + return ProcFsHelper_read_config(buf, start, offset, size, eof, (App*)data); +} + +int __ProcFs_readV2_status(struct seq_file* file, void* v) +{ + App* app = file->private; + + return ProcFsHelper_readV2_status(file, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_status(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + return ProcFsHelper_read_status(buf, start, offset, size, eof, (App*)data); +} + +int __ProcFs_readV2_mgmtNodes(struct seq_file* file, void* p) +{ + App* app = file->private; + NodeStoreEx* nodes = App_getMgmtNodes(app); + + return ProcFsHelper_readV2_nodes(file, app, nodes); +} + +int __ProcFs_readV2_fsUUID(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_fsUUID(file, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_mgmtNodes(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + return ProcFsHelper_read_nodes(buf, start, offset, size, eof, + App_getMgmtNodes( (App*)data) ); +} + +int __ProcFs_readV2_metaNodes(struct seq_file* file, void* p) +{ + App* app = file->private; + NodeStoreEx* nodes = App_getMetaNodes(app); + + return ProcFsHelper_readV2_nodes(file, app, nodes); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_metaNodes(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + return ProcFsHelper_read_nodes(buf, start, offset, size, eof, + App_getMetaNodes( (App*)data) ); +} + +int __ProcFs_readV2_storageNodes(struct seq_file* file, void* p) +{ + App* app = file->private; + NodeStoreEx* nodes = App_getStorageNodes(app); + + return ProcFsHelper_readV2_nodes(file, app, nodes); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_storageNodes(char* buf, char** start, off_t offset, int size, int* eof, void* data) +{ + return ProcFsHelper_read_nodes(buf, start, offset, size, eof, + App_getStorageNodes( (App*)data) ); +} + +int __ProcFs_readV2_clientInfo(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_clientInfo(file, app); +} + +int ProcFs_read_clientInfo(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_clientInfo(buf, start, offset, size, eof, (App*)data); +} + +int __ProcFs_readV2_metaTargetStates(struct seq_file* file, void* p) +{ + App* app = file->private; + TargetStateStore* metaStates = App_getMetaStateStore(app); + NodeStoreEx* nodes = App_getMetaNodes(app); + + return ProcFsHelper_readV2_targetStates(file, app, metaStates, nodes, true); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_metaTargetStates(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_targetStates(buf, start, offset, size, eof, (App*)data, + App_getMetaStateStore( (App*)data), App_getMetaNodes( (App*)data), true); +} + +int __ProcFs_readV2_storageTargetStates(struct seq_file* file, void* p) +{ + App* app = file->private; + TargetStateStore* targetStates = App_getTargetStateStore(app); + NodeStoreEx* nodes = App_getStorageNodes(app); + + return ProcFsHelper_readV2_targetStates(file, app, targetStates, nodes, false); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_storageTargetStates(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_targetStates(buf, start, offset, size, eof, (App*)data, + App_getTargetStateStore( (App*)data), App_getStorageNodes( (App*)data), false); +} + +int __ProcFs_readV2_connRetriesEnabled(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_connRetriesEnabled(file, app); +} + +int ProcFs_read_connRetriesEnabled(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_connRetriesEnabled(buf, start, offset, size, eof, (App*)data); +} + +/** + * @param data specified at entry creation + */ +ssize_t __ProcFs_writeV2_connRetriesEnabled(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode* procInode = file_inode(file); + App* app = __ProcFs_getProcParentDirEntryDataField(procInode); // (app is ->data in parent dir) + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_connRetriesEnabled(buf, count, app); +} + +int ProcFs_write_connRetriesEnabled(struct file* file, const char __user *buf, + unsigned long count, void* data) +{ + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_connRetriesEnabled(buf, count, (App*)data); +} + +int __ProcFs_read_remapConnectionFailure(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_read_remapConnectionFailure(file, app); +} + +ssize_t __ProcFs_write_remapConnectionFailure(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode* procInode = file_inode(file); + App* app = __ProcFs_getProcParentDirEntryDataField(procInode); // (app is ->data in parent dir) + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_remapConnectionFailure(buf, count, app); +} + +int __ProcFs_readV2_netBenchModeEnabled(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_netBenchModeEnabled(file, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_netBenchModeEnabled(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_netBenchModeEnabled(buf, start, offset, size, eof, (App*)data); +} + +ssize_t __ProcFs_writeV2_netBenchModeEnabled(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode* procInode = file_inode(file); + App* app = __ProcFs_getProcParentDirEntryDataField(procInode); // (app is ->data in parent dir) + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_netBenchModeEnabled(buf, count, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_write_netBenchModeEnabled(struct file* file, const char __user *buf, + unsigned long count, void* data) +{ + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_netBenchModeEnabled(buf, count, (App*)data); +} + +ssize_t __ProcFs_writeV2_dropConns(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode* procInode = file_inode(file); + App* app = __ProcFs_getProcParentDirEntryDataField(procInode); // (app is ->data in parent dir) + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_dropConns(buf, count, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_write_dropConns(struct file* file, const char __user *buf, + unsigned long count, void* data) +{ + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_dropConns(buf, count, (App*)data); +} + +int __ProcFs_readV2_logLevels(struct seq_file* file, void* p) +{ + App* app = file->private; + + return ProcFsHelper_readV2_logLevels(file, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_read_logLevels(char* buf, char** start, off_t offset, int size, int* eof, + void* data) +{ + return ProcFsHelper_read_logLevels(buf, start, offset, size, eof, (App*)data); +} + +ssize_t __ProcFs_writeV2_logLevels(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + struct inode* procInode = file_inode(file); + App* app = __ProcFs_getProcParentDirEntryDataField(procInode); // (app is ->data in parent dir) + + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_logLevels(buf, count, app); +} + +/** + * @param data specified at entry creation + */ +int ProcFs_write_logLevels(struct file* file, const char __user *buf, + unsigned long count, void* data) +{ + // check user buffer + if(unlikely(!os_access_ok(VERIFY_READ, buf, count) ) ) + return -EFAULT; + + return ProcFsHelper_write_logLevels(buf, count, (App*)data); +} + +/** + * Create a proc dir. + * + * Note: This is actually just a compat method for proc_mkdir_data. + * + * @param data arbitrary private value to be assigned to procDir->data + */ +struct proc_dir_entry* __ProcFs_mkDir(const char* name, void* data) +{ + /* newer kernels do no longer export struct proc_dir_entry, so the data field is only + accessible through special kernel methods. */ + + struct proc_dir_entry* procDir; + + + #if defined(KERNEL_HAS_PDE_DATA) || defined(KERNEL_HAS_NEW_PDE_DATA) + + procDir = proc_mkdir_data(name, 0, NULL, data); + + #else + + procDir = proc_mkdir(name, NULL); + + if(procDir) + procDir->data = data; + + #endif // KERNEL_HAS_PDE_DATA + + + return procDir; +} + +/** + * Return the data field of a proc entry. + * + * Note: This is actually just a compat method for PDE_DATA. + */ +void* __ProcFs_getProcDirEntryDataField(const struct inode* procInode) +{ + /* newer kernels do no longer export struct proc_dir_entry, so the data field is only + accessible through special kernel methods. */ + + #ifdef KERNEL_HAS_PDE_DATA + + return PDE_DATA(procInode); + + #elif defined(KERNEL_HAS_NEW_PDE_DATA) + + return pde_data(procInode); + + #else + + struct proc_dir_entry* procEntry = PDE(procInode); + + return procEntry->data; // (app is stored as ->data in parent dir) + + #endif // KERNEL_HAS_PDE_DATA +} + +/** + * Return the data field from the parent dir of a proc entry. + * + * Note: This is actually just a compat method for proc_get_parent_data. + */ +void* __ProcFs_getProcParentDirEntryDataField(const struct inode* procInode) +{ + /* newer kernels do no longer export struct proc_dir_entry, so the ->parent and ->data fields are + only accessible through special kernel methods. */ + + #if defined(KERNEL_HAS_PDE_DATA) || defined(KERNEL_HAS_NEW_PDE_DATA) + + return proc_get_parent_data(procInode); + + #else + + struct proc_dir_entry* procEntry = PDE(procInode); + + return procEntry->parent->data; // (app is stored as ->data in parent dir) + + #endif // KERNEL_HAS_PDE_DATA +} diff --git a/client_module/source/filesystem/ProcFs.h b/client_module/source/filesystem/ProcFs.h new file mode 100644 index 0000000..09b9271 --- /dev/null +++ b/client_module/source/filesystem/ProcFs.h @@ -0,0 +1,90 @@ +#ifndef PROCFS_H_ +#define PROCFS_H_ + +#include +#include +#include + +#include +#include + + +extern void ProcFs_createGeneralDir(void); +extern void ProcFs_removeGeneralDir(void); +extern void ProcFs_createEntries(App* app); +extern void ProcFs_removeEntries(App* app); + +extern int __ProcFs_open(struct inode* inode, struct file* file); + +extern int __ProcFs_readV2_nothing(struct seq_file* file, void* p); +extern int __ProcFs_readV2_config(struct seq_file* file, void* p); +extern int __ProcFs_readV2_status(struct seq_file* file, void* p); +extern int __ProcFs_readV2_fsUUID(struct seq_file* file, void* p); +extern int __ProcFs_readV2_mgmtNodes(struct seq_file* file, void* p); +extern int __ProcFs_readV2_metaNodes(struct seq_file* file, void* p); +extern int __ProcFs_readV2_storageNodes(struct seq_file* file, void* p); +extern int __ProcFs_readV2_clientInfo(struct seq_file* file, void* p); +extern int __ProcFs_readV2_metaTargetStates(struct seq_file* file, void* p); +extern int __ProcFs_readV2_storageTargetStates(struct seq_file* file, void* p); + +extern int __ProcFs_readV2_connRetriesEnabled(struct seq_file* file, void* p); +extern int __ProcFs_readV2_netBenchModeEnabled(struct seq_file* file, void* p); +extern int __ProcFs_readV2_logLevels(struct seq_file* file, void* p); + +extern ssize_t __ProcFs_writeV2_connRetriesEnabled(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); +extern ssize_t __ProcFs_writeV2_netBenchModeEnabled(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); +extern ssize_t __ProcFs_writeV2_dropConns(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); +extern ssize_t __ProcFs_writeV2_logLevels(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); + +extern int __ProcFs_read_remapConnectionFailure(struct seq_file* file, void* p); +extern ssize_t __ProcFs_write_remapConnectionFailure(struct file *file, const char __user *buf, + size_t count, loff_t *ppos); + +extern int ProcFs_read_nothing( + char* buf, char** start, off_t offset, int size, int* eof,void* data); +extern int ProcFs_read_config( + char* buf, char** start, off_t offset, int size, int* eof,void* data); +extern int ProcFs_read_status( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_mgmtNodes( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_metaNodes( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_storageNodes( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_clientInfo( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_metaTargetStates( + char* buf, char** start, off_t offset, int size, int* eof, void* data); +extern int ProcFs_read_storageTargetStates( + char* buf, char** start, off_t offset, int size, int* eof, void* data); + +extern int ProcFs_read_connRetriesEnabled(char* buf, char** start, off_t offset, int size, int* eof, + void* data); +extern unsigned ProcFs_read_remapConnectionFailure(char* buf, char** start, off_t offset, int size, int* eof, + void* data); +extern int ProcFs_read_netBenchModeEnabled(char* buf, char** start, off_t offset, int size, + int* eof, void* data); +extern int ProcFs_read_logLevels(char* buf, char** start, off_t offset, int size, int* eof, + void* data); + +extern int ProcFs_write_connRetriesEnabled(struct file* file, const char __user *buf, + unsigned long count, void* data); +extern int ProcFs_write_remapConnectionFailure(struct file* file, const char __user *buf, + unsigned long count, void* data); +extern int ProcFs_write_netBenchModeEnabled(struct file* file, const char __user *buf, + unsigned long count, void* data); +extern int ProcFs_write_dropConns(struct file* file, const char __user *buf, + unsigned long count, void* data); +extern int ProcFs_write_logLevels(struct file* file, const char __user *buf, unsigned long count, + void* data); + +extern struct proc_dir_entry* __ProcFs_mkDir(const char* name, void* data); +extern void* __ProcFs_getProcDirEntryDataField(const struct inode* procInode); +extern void* __ProcFs_getProcParentDirEntryDataField(const struct inode* procInode); + +#endif /* PROCFS_H_ */ diff --git a/client_module/source/filesystem/ProcFsHelper.c b/client_module/source/filesystem/ProcFsHelper.c new file mode 100644 index 0000000..873e4c1 --- /dev/null +++ b/client_module/source/filesystem/ProcFsHelper.c @@ -0,0 +1,1395 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define PROCFSHELPER_CONFIGKEYS_SIZE \ + ( sizeof(PROCFSHELPER_CONFIGKEYS) / sizeof(char*) ) + + +const char* const PROCFSHELPER_CONFIGKEYS[] = +{ + "cfgFile", + "logLevel", + "logClientID", + "connUseRDMA", + "connMgmtdPort", + "connMgmtdGrpcPort", + "connClientPort", + "connMetaPort", + "connStoragePort", + "connMaxInternodeNum", + "connInterfacesFile", + "connRDMAInterfacesFile", + "connNetFilterFile", + "connAuthFile", + "connDisableAuthentication", + "connTcpOnlyFilterFile", + "connFallbackExpirationSecs", + "connRDMABufSize", + "connRDMAFragmentSize", + "connRDMABufNum", + "connRDMAMetaBufSize", + "connRDMAMetaFragmentSize", + "connRDMAMetaBufNum", + "connRDMATypeOfService", + "connRDMAKeyType", + "connCommRetrySecs", + "connNumCommRetries", + "connUnmountRetries", + "connMessagingTimeouts", + "connRDMATimeouts", + "tunePreferredMetaFile", + "tunePreferredStorageFile", + "tuneFileCacheType", + "tuneFileCacheBufSize", + "tuneRemoteFSync", + "tuneUseGlobalFileLocks", + "tuneRefreshOnGetAttr", + "tuneInodeBlockBits", + "tuneInodeBlockSize", + "tuneEarlyCloseResponse", + "tuneUseGlobalAppendLocks", + "tuneUseBufferedAppend", + "tuneStatFsCacheSecs", + "tuneCoherentBuffers", + "sysACLsEnabled", + "sysMgmtdHost", + "sysInodeIDStyle", + "sysCacheInvalidationVersion", + "sysCreateHardlinksAsSymlinks", + "sysMountSanityCheckMS", + "sysSyncOnClose", + "sysSessionCheckOnClose", + "sysSessionChecksEnabled", + "sysXAttrsEnabled", + "sysXAttrsCheckCapabilities", + "sysBypassFileAccessCheckOnMeta", + "quotaEnabled", + "sysFileEventLogMask", + "sysRenameEbusyAsXdev", + +#ifdef LOG_DEBUG_MESSAGES + "tunePageCacheValidityMS", // currently not public + "tuneFileCacheBufNum", // currently not public +#endif // LOG_DEBUG_MESSAGES + + "" // just some final dummy element (for the syntactical comma-less end) with no effect +}; + + +#define NIC_INDENTATION_STR "+ " + + + +int ProcFsHelper_readV2_config(struct seq_file* file, App* app) +{ + Config* cfg = App_getConfig(app); + + seq_printf(file, "cfgFile = %s\n", Config_getCfgFile(cfg) ); + seq_printf(file, "logLevel = %d\n", Config_getLogLevel(cfg) ); + seq_printf(file, "logClientID = %d\n", (int)Config_getLogClientID(cfg) ); + seq_printf(file, "connUseRDMA = %d\n", (int)Config_getConnUseRDMA(cfg) ); + seq_printf(file, "connTCPFallbackEnabled = %d\n", (int)Config_getConnTCPFallbackEnabled(cfg) ); + seq_printf(file, "connMgmtdPort = %d\n", (int)Config_getConnMgmtdPort(cfg) ); + seq_printf(file, "connMgmtdGrpcPort = %d\n", (int)Config_getConnMgmtdGrpcPort(cfg) ); + seq_printf(file, "connClientPort = %d\n", (int)Config_getConnClientPort(cfg) ); + seq_printf(file, "connMaxInternodeNum = %u\n", Config_getConnMaxInternodeNum(cfg) ); + seq_printf(file, "connInterfacesFile = %s\n", Config_getConnInterfacesFile(cfg) ); + seq_printf(file, "connRDMAInterfacesFile = %s\n", Config_getConnRDMAInterfacesFile(cfg) ); + seq_printf(file, "connNetFilterFile = %s\n", Config_getConnNetFilterFile(cfg) ); + seq_printf(file, "connAuthFile = %s\n", Config_getConnAuthFile(cfg) ); + seq_printf(file, "connDisableAuthentication = %s\n", + Config_getConnDisableAuthentication(cfg) ? "true" : "false"); + seq_printf(file, "connTcpOnlyFilterFile = %s\n", Config_getConnTcpOnlyFilterFile(cfg) ); + seq_printf(file, "connFallbackExpirationSecs = %u\n", + Config_getConnFallbackExpirationSecs(cfg) ); + seq_printf(file, "connRDMABufSize = %u\n", Config_getConnRDMABufSize(cfg) ); + seq_printf(file, "connRDMAFragmentSize = %u\n", Config_getConnRDMAFragmentSize(cfg) ); + seq_printf(file, "connRDMABufNum = %u\n", Config_getConnRDMABufNum(cfg) ); + seq_printf(file, "connRDMAMetaBufSize = %u\n", Config_getConnRDMAMetaBufSize(cfg) ); + seq_printf(file, "connRDMAMetaFragmentSize = %u\n", Config_getConnRDMAMetaFragmentSize(cfg) ); + seq_printf(file, "connRDMAMetaBufNum = %u\n", Config_getConnRDMAMetaBufNum(cfg) ); + seq_printf(file, "connRDMATypeOfService = %d\n", Config_getConnRDMATypeOfService(cfg) ); + seq_printf(file, "connRDMAKeyType = %s\n", + Config_rdmaKeyTypeNumToStr(Config_getConnRDMAKeyTypeNum(cfg)) ); + seq_printf(file, "connCommRetrySecs = %u\n", Config_getConnCommRetrySecs(cfg) ); + seq_printf(file, "connNumCommRetries = %u\n", Config_getConnNumCommRetries(cfg) ); + seq_printf(file, "connUnmountRetries = %d\n", (int)Config_getConnUnmountRetries(cfg) ); + seq_printf(file, "connMessagingTimeouts = %s\n", Config_getConnMessagingTimeouts(cfg) ); + seq_printf(file, "connRDMATimeouts = %s\n", Config_getConnRDMATimeouts(cfg) ); + seq_printf(file, "tunePreferredMetaFile = %s\n", Config_getTunePreferredMetaFile(cfg) ); + seq_printf(file, "tunePreferredStorageFile = %s\n", Config_getTunePreferredStorageFile(cfg) ); + seq_printf(file, "tuneFileCacheType = %s\n", + Config_fileCacheTypeNumToStr(Config_getTuneFileCacheTypeNum(cfg) ) ); + seq_printf(file, "tuneFileCacheBufSize = %d\n", Config_getTuneFileCacheBufSize(cfg) ); + seq_printf(file, "tuneRemoteFSync = %d\n", (int)Config_getTuneRemoteFSync(cfg) ); + seq_printf(file, "tuneUseGlobalFileLocks = %d\n", (int)Config_getTuneUseGlobalFileLocks(cfg) ); + seq_printf(file, "tuneRefreshOnGetAttr = %d\n", (int)Config_getTuneRefreshOnGetAttr(cfg) ); + seq_printf(file, "tuneInodeBlockBits = %u\n", Config_getTuneInodeBlockBits(cfg) ); + seq_printf(file, "tuneInodeBlockSize = %u\n", Config_getTuneInodeBlockSize(cfg) ); + seq_printf(file, "tuneEarlyCloseResponse = %d\n", (int)Config_getTuneEarlyCloseResponse(cfg) ); + seq_printf(file, "tuneUseGlobalAppendLocks = %d\n", + (int)Config_getTuneUseGlobalAppendLocks(cfg) ); + seq_printf(file, "tuneUseBufferedAppend = %d\n", (int)Config_getTuneUseBufferedAppend(cfg) ); + seq_printf(file, "tuneStatFsCacheSecs = %u\n", Config_getTuneStatFsCacheSecs(cfg) ); + seq_printf(file, "tuneCoherentBuffers = %u\n", Config_getTuneCoherentBuffers(cfg) ); + seq_printf(file, "sysACLsEnabled = %d\n", (int)Config_getSysACLsEnabled(cfg) ); + seq_printf(file, "sysMgmtdHost = %s\n", Config_getSysMgmtdHost(cfg) ); + seq_printf(file, "sysInodeIDStyle = %s\n", + Config_inodeIDStyleNumToStr(Config_getSysInodeIDStyleNum(cfg) ) ); + seq_printf(file, "sysCacheInvalidationVersion = %d\n", + (int)Config_getSysCacheInvalidationVersion(cfg) ); + seq_printf(file, "sysCreateHardlinksAsSymlinks = %d\n", + (int)Config_getSysCreateHardlinksAsSymlinks(cfg) ); + seq_printf(file, "sysMountSanityCheckMS = %u\n", Config_getSysMountSanityCheckMS(cfg) ); + seq_printf(file, "sysSyncOnClose = %d\n", (int)Config_getSysSyncOnClose(cfg) ); + seq_printf(file, "sysXAttrsEnabled = %d\n", (int)Config_getSysXAttrsEnabled(cfg) ); + seq_printf(file, "sysXAttrsCheckCapabilities = %s\n", + Config_checkCapabilitiesTypeToStr(Config_getSysXAttrsCheckCapabilities(cfg) ) ); + seq_printf(file, "sysBypassFileAccessCheckOnMeta = %d\n", + (int)Config_getSysBypassFileAccessCheckOnMeta(cfg) ); + seq_printf(file, "sysSessionCheckOnClose = %d\n", (int)Config_getSysSessionCheckOnClose(cfg) ); + seq_printf(file, "sysSessionChecksEnabled = %d\n", (int)Config_getSysSessionChecksEnabled(cfg) ); + seq_printf(file, "quotaEnabled = %d\n", (int)Config_getQuotaEnabled(cfg) ); + seq_printf(file, "sysFileEventLogMask = %s\n", Config_eventLogMaskToStr(cfg->eventLogMask)); + seq_printf(file, "sysRenameEbusyAsXdev = %u\n", (unsigned) cfg->sysRenameEbusyAsXdev); + + +#ifdef LOG_DEBUG_MESSAGES + seq_printf(file, "tunePageCacheValidityMS = %u\n", Config_getTunePageCacheValidityMS(cfg) ); + seq_printf(file, "tuneFileCacheBufNum = %d\n", Config_getTuneFileCacheBufNum(cfg) ); +#endif // LOG_DEBUG_MESSAGES + + + return 0; +} + +int ProcFsHelper_read_config(char* buf, char** start, off_t offset, int size, int* eof, App* app) +{ + int count = 0; + Config* cfg = App_getConfig(app); + const char* currentKey = NULL; + + if(offset >= (off_t)PROCFSHELPER_CONFIGKEYS_SIZE) + { + *eof = 1; + return 0; + } + + currentKey = PROCFSHELPER_CONFIGKEYS[offset]; + + + if(!strcmp(currentKey, "cfgFile") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, Config_getCfgFile(cfg) ); + else + if(!strcmp(currentKey, "logLevel") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getLogLevel(cfg) ); + else + if(!strcmp(currentKey, "logClientID") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, (int)Config_getLogClientID(cfg) ); + else + if(!strcmp(currentKey, "connUseRDMA") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, (int)Config_getConnUseRDMA(cfg) ); + else + if(!strcmp(currentKey, "connMgmtdPort") ) + count = scnprintf(buf, size, "%s = %d\n", + currentKey, (int)Config_getConnMgmtdPort(cfg) ); + else + if(!strcmp(currentKey, "connMgmtdGrpcPort") ) + count = scnprintf(buf, size, "%s = %d\n", + currentKey, (int)Config_getConnMgmtdGrpcPort(cfg) ); + else + if(!strcmp(currentKey, "connClientPort") ) + count = scnprintf(buf, size, "%s = %d\n", + currentKey, (int)Config_getConnClientPort(cfg) ); + else + if(!strcmp(currentKey, "connMaxInternodeNum") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnMaxInternodeNum(cfg) ); + else + if(!strcmp(currentKey, "connInterfacesFile") ) + count = scnprintf(buf, size, "%s = %s\n", + currentKey, Config_getConnInterfacesFile(cfg) ); + else + if(!strcmp(currentKey, "connRDMAInterfacesFile") ) + count = scnprintf(buf, size, "%s = %s\n", + currentKey, Config_getConnRDMAInterfacesFile(cfg) ); + else + if(!strcmp(currentKey, "connNetFilterFile") ) + count = scnprintf(buf, size, "%s = %s\n", + currentKey, Config_getConnNetFilterFile(cfg) ); + else + if(!strcmp(currentKey, "connTcpOnlyFilterFile") ) + count = scnprintf(buf, size, "%s = %s\n", + currentKey, Config_getConnTcpOnlyFilterFile(cfg) ); + else + if(!strcmp(currentKey, "connFallbackExpirationSecs") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getConnFallbackExpirationSecs(cfg) ); + else + if(!strcmp(currentKey, "connRDMABufSize") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMABufSize(cfg) ); + else + if(!strcmp(currentKey, "connRDMAFragmentSize") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMAFragmentSize(cfg) ); + else + if(!strcmp(currentKey, "connRDMABufNum") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMABufNum(cfg) ); + else + if(!strcmp(currentKey, "connRDMAMetaBufSize") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMAMetaBufSize(cfg) ); + else + if(!strcmp(currentKey, "connRDMAMetaFragmentSize") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMAMetaFragmentSize(cfg) ); + else + if(!strcmp(currentKey, "connRDMAMetaBufNum") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnRDMAMetaBufNum(cfg) ); + else + if(!strcmp(currentKey, "connRDMATypeOfService") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getConnRDMATypeOfService(cfg) ); + else + if(!strcmp(currentKey, "connRDMAKeyType") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_rdmaKeyTypeNumToStr(Config_getConnRDMAKeyTypeNum(cfg) ) ); + else + if(!strcmp(currentKey, "connCommRetrySecs") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnCommRetrySecs(cfg) ); + else + if(!strcmp(currentKey, "connNumCommRetries") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getConnNumCommRetries(cfg) ); + else + if(!strcmp(currentKey, "connUnmountRetries") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + (int)Config_getConnUnmountRetries(cfg) ); + else + if(!strcmp(currentKey, "tunePreferredMetaFile") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_getTunePreferredMetaFile(cfg) ); + else + if(!strcmp(currentKey, "tunePreferredStorageFile") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_getTunePreferredStorageFile(cfg) ); + else + if(!strcmp(currentKey, "tuneFileCacheType") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_fileCacheTypeNumToStr(Config_getTuneFileCacheTypeNum(cfg) ) ); + else + if(!strcmp(currentKey, "tuneFileCacheBufSize") ) + count = scnprintf(buf, size, "%s = %d\n", + currentKey, Config_getTuneFileCacheBufSize(cfg) ); + else + if(!strcmp(currentKey, "tuneFileCacheBufNum") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getTuneFileCacheBufNum(cfg) ); + else + if(!strcmp(currentKey, "tunePageCacheValidityMS") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getTunePageCacheValidityMS(cfg) ); + else + if(!strcmp(currentKey, "tuneRemoteFSync") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getTuneRemoteFSync(cfg) ); + else + if(!strcmp(currentKey, "tuneUseGlobalFileLocks") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getTuneUseGlobalFileLocks(cfg) ); + else + if(!strcmp(currentKey, "tuneRefreshOnGetAttr") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getTuneRefreshOnGetAttr(cfg) ); + else + if(!strcmp(currentKey, "tuneInodeBlockBits") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getTuneInodeBlockBits(cfg) ); + else + if(!strcmp(currentKey, "tuneInodeBlockSize") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getTuneInodeBlockSize(cfg) ); + else + if(!strcmp(currentKey, "tuneEarlyCloseResponse") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getTuneEarlyCloseResponse(cfg) ); + else + if(!strcmp(currentKey, "tuneUseGlobalAppendLocks") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getTuneUseGlobalAppendLocks(cfg) ); + else + if(!strcmp(currentKey, "tuneUseBufferedAppend") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getTuneUseBufferedAppend(cfg) ); + else + if(!strcmp(currentKey, "tuneStatFsCacheSecs") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getTuneStatFsCacheSecs(cfg) ); + else + if(!strcmp(currentKey, "tuneCoherentBuffers") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, Config_getTuneCoherentBuffers(cfg) ); + else + if(!strcmp(currentKey, "sysACLsEnabled") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getSysACLsEnabled(cfg) ); + else + if(!strcmp(currentKey, "sysMgmtdHost") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, Config_getSysMgmtdHost(cfg) ); + else + if(!strcmp(currentKey, "sysInodeIDStyle") ) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_inodeIDStyleNumToStr(Config_getSysInodeIDStyleNum(cfg) ) ); + else + if(!strcmp(currentKey, "sysCacheInvalidationVersion") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getSysCacheInvalidationVersion(cfg) ); + else + if(!strcmp(currentKey, "sysCreateHardlinksAsSymlinks") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getSysCreateHardlinksAsSymlinks(cfg) ); + else + if(!strcmp(currentKey, "sysMountSanityCheckMS") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getSysMountSanityCheckMS(cfg) ); + else + if(!strcmp(currentKey, "sysSyncOnClose") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getSysSyncOnClose(cfg) ); + else + if(!strcmp(currentKey, "sysSessionCheckOnClose") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getSysSessionCheckOnClose(cfg) ); + else + if(!strcmp(currentKey, "sysSessionChecksEnabled") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getSysSessionChecksEnabled(cfg) ); + else + if(!strcmp(currentKey, "sysXAttrsEnabled") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getSysXAttrsEnabled(cfg) ); + else + if(!strcmp(currentKey, "sysXAttrsCheckCapabilities") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, Config_getSysXAttrsCheckCapabilities(cfg) ); + else + if(!strcmp(currentKey, "sysBypassFileAccessCheckOnMeta") ) + count = scnprintf(buf, size, "%s = %d\n", currentKey, + Config_getSysBypassFileAccessCheckOnMeta(cfg) ); + else + if (!strcmp(currentKey, "sysRenameEbusyAsXdev")) + count = scnprintf(buf, size, "%s = %u\n", currentKey, (unsigned) cfg->sysRenameEbusyAsXdev); + else + if(!strcmp(currentKey, "quotaEnabled") ) + count = scnprintf(buf, size, "%s = %u\n", currentKey, + Config_getQuotaEnabled(cfg) ); + else if (!strcmp(currentKey, "sysFileEventLogMask")) + count = scnprintf(buf, size, "%s = %s\n", currentKey, + Config_eventLogMaskToStr(cfg->eventLogMask)); + else + { // undefined (or hidden) element + // nothing to be done here + } + + + *start = (char*)1; // move to next offset (note: yes, we're casting a number to a pointer here!) + + return count; +} + +int ProcFsHelper_readV2_status(struct seq_file* file, App* app) +{ + { + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + size_t numAvailableMsgBufs = NoAllocBufferStore_getNumAvailable(bufStore); + + seq_printf(file, "numAvailableMsgBufs: %u\n", (unsigned)numAvailableMsgBufs); + } + + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedCloseQueueSize(syncer); + + seq_printf(file, "numDelayedClose: %u\n", (unsigned)delayedQSize); + } + + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedEntryUnlockQueueSize(syncer); + + seq_printf(file, "numDelayedEntryUnlock: %u\n", (unsigned)delayedQSize); + } + + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedRangeUnlockQueueSize(syncer); + + seq_printf(file, "numDelayedRangeUnlock: %u\n", (unsigned)delayedQSize); + } + + { + AckManager* ackMgr = App_getAckManager(app); + size_t queueSize = AckManager_getAckQueueSize(ackMgr); + + seq_printf(file, "numEnqueuedAcks: %u\n", (unsigned)queueSize); + } + + { + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + if(cacheStore) + { + size_t numAvailable = NoAllocBufferStore_getNumAvailable(cacheStore); + + seq_printf(file, "numAvailableIOBufs: %u\n", (unsigned)numAvailable); + } + } + + { + InodeRefStore* refStore = App_getInodeRefStore(app); + if(refStore) + { + size_t storeSize = InodeRefStore_getSize(refStore); + + seq_printf(file, "numFlushRefs: %u\n", (unsigned)storeSize); + } + } + + #ifdef BEEGFS_DEBUG + + { + unsigned long long numRPCs = App_getNumRPCs(app); + seq_printf(file, "numRPCs: %llu\n", numRPCs); + } + + { + unsigned long long numRemoteReads = App_getNumRemoteReads(app); + seq_printf(file, "numRemoteReads: %llu\n", numRemoteReads); + } + + { + unsigned long long numRemoteWrites = App_getNumRemoteWrites(app); + seq_printf(file, "numRemoteWrites: %llu\n", numRemoteWrites); + } + + #endif // BEEGFS_DEBUG + + return 0; +} + +int ProcFsHelper_read_status(char* buf, char** start, off_t offset, int size, int* eof, App* app) +{ + int count = 0; + int i=0; // just some virtual sequential index to match sequental offset + + if(offset == i++) + { + NoAllocBufferStore* bufStore = App_getMsgBufStore(app); + size_t numAvailableMsgBufs = NoAllocBufferStore_getNumAvailable(bufStore); + + count = scnprintf(buf, size, "numAvailableMsgBufs: %u\n", (unsigned)numAvailableMsgBufs); + } + else + if(offset == i++) + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedCloseQueueSize(syncer); + + count = scnprintf(buf, size, "numDelayedClose: %u\n", (unsigned)delayedQSize); + } + else + if(offset == i++) + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedEntryUnlockQueueSize(syncer); + + count = scnprintf(buf, size, "numDelayedEntryUnlock: %u\n", (unsigned)delayedQSize); + } + else + if(offset == i++) + { + InternodeSyncer* syncer = App_getInternodeSyncer(app); + size_t delayedQSize = InternodeSyncer_getDelayedRangeUnlockQueueSize(syncer); + + count = scnprintf(buf, size, "numDelayedRangeUnlock: %u\n", (unsigned)delayedQSize); + } + else + if(offset == i++) + { + AckManager* ackMgr = App_getAckManager(app); + size_t queueSize = AckManager_getAckQueueSize(ackMgr); + + count = scnprintf(buf, size, "numEnqueuedAcks: %u\n", (unsigned)queueSize); + } + else + if(offset == i++) + { + NoAllocBufferStore* cacheStore = App_getCacheBufStore(app); + if(cacheStore) + { + size_t numAvailable = NoAllocBufferStore_getNumAvailable(cacheStore); + + count = scnprintf(buf, size, "numAvailableIOBufs: %u\n", (unsigned)numAvailable); + } + } + else + if(offset == i++) + { + InodeRefStore* refStore = App_getInodeRefStore(app); + if(refStore) + { + size_t storeSize = InodeRefStore_getSize(refStore); + + count = scnprintf(buf, size, "numFlushRefs: %u\n", (unsigned)storeSize); + } + } +#ifdef BEEGFS_DEBUG + else + if(offset == i++) + { + unsigned long long numRPCs = App_getNumRPCs(app); + count = scnprintf(buf, size, "numRPCs: %llu\n", numRPCs); + } + else + if(offset == i++) + { + unsigned long long numRemoteReads = App_getNumRemoteReads(app); + count = scnprintf(buf, size, "numRemoteReads: %llu\n", numRemoteReads); + } + else + if(offset == i++) + { + unsigned long long numRemoteWrites = App_getNumRemoteWrites(app); + count = scnprintf(buf, size, "numRemoteWrites: %llu\n", numRemoteWrites); + } +#endif // BEEGFS_DEBUG + else + { + *eof = 1; + return 0; + } + + + *start = (char*)1; // move to next offset (note: yes, we're casting a number to a pointer here!) + + return count; +} + +int ProcFsHelper_readV2_fsUUID(struct seq_file* file, App* app) +{ + char* fsUUID = App_cloneFsUUID(app); + + seq_printf(file, "%s\n", fsUUID); + + kfree(fsUUID); + + return 0; +} + +/** + * @param nodes show nodes from this store + */ +int ProcFsHelper_readV2_nodes(struct seq_file* file, App* app, struct NodeStoreEx* nodes) +{ + Node* currentNode = NodeStoreEx_referenceFirstNode(nodes); + + while(currentNode) + { + NumNodeID numID = Node_getNumID(currentNode); + const char* nodeID = NumNodeID_str(&numID); + NodeString alias; + Node_copyAlias(currentNode, &alias); + + seq_printf(file, "%s [ID: %s]\n", alias.buf, nodeID); + kfree(nodeID); + + __ProcFsHelper_printGotRootV2(file, currentNode, nodes); + __ProcFsHelper_printNodeConnsV2(file, currentNode); + + // next node + currentNode = NodeStoreEx_referenceNextNodeAndReleaseOld(nodes, currentNode); + } + + return 0; +} + + +/** + * Note: This does not guarantee to return the complete list (e.g. in case the list changes during + * two calls to this method) + * + * @param nodes show nodes from this store + */ +int ProcFsHelper_read_nodes(char* buf, char** start, off_t offset, int size, int* eof, + struct NodeStoreEx* nodes) +{ + Node* currentNode = NULL; + off_t currentOffset = 0; + int count = 0; + NumNodeID nodeNumID; + const char* nodeID; + NodeString alias; + + currentNode = NodeStoreEx_referenceFirstNode(nodes); + while(currentNode && (currentOffset < offset) ) + { + currentNode = NodeStoreEx_referenceNextNodeAndReleaseOld(nodes, currentNode); + currentOffset++; + } + + if(!currentNode) + { // this offset does not exist + *eof = 1; + return 0; + } + + nodeNumID = Node_getNumID(currentNode); + nodeID = NumNodeID_str(&nodeNumID); + Node_copyAlias(currentNode, &alias); + count += scnprintf(buf+count, size-count, "%s [ID: %s]\n", alias.buf, nodeID); + kfree(nodeID); + + __ProcFsHelper_printGotRoot(currentNode, nodes, buf, &count, &size); + __ProcFsHelper_printNodeConns(currentNode, buf, &count, &size); + + *start = (char*)1; // move to next offset (note: yes, we're casting a number to a pointer here!) + + Node_put(currentNode); + + return count; +} + +static void ProcFsHelper_readV2_nics(struct seq_file* file, NicAddressList* nicList) +{ + NicAddressListIter nicIter; + + NicAddressListIter_init(&nicIter, nicList); + + for( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + char* nicAddrStr = NIC_nicAddrToString(nicAddr); + + if(unlikely(!nicAddrStr) ) + seq_printf(file, "%sNIC not available [Error: Out of memory]\n", NIC_INDENTATION_STR); + else + { + seq_printf(file, "%s%s\n", NIC_INDENTATION_STR, nicAddrStr); + + kfree(nicAddrStr); + } + } +} + +int ProcFsHelper_readV2_clientInfo(struct seq_file* file, App* app) +{ + Node* localNode = App_getLocalNode(app); + + NicAddressList nicList; + NicAddressList rdmaNicList; + + NodeString alias; + Node_copyAlias(localNode, &alias); + + Node_cloneNicList(localNode, &nicList); + App_cloneLocalRDMANicList(app, &rdmaNicList); + // print local clientID + + seq_printf(file, "ClientID: %s\n", alias.buf); + + // list usable network interfaces + + seq_printf(file, "Interfaces:\n"); + ProcFsHelper_readV2_nics(file, &nicList); + if (Config_getConnUseRDMA(App_getConfig(app))) + { + seq_printf(file, "Outbound RDMA Interfaces:\n"); + if (NicAddressList_length(&rdmaNicList) == 0) + seq_printf(file, "%sAny\n", NIC_INDENTATION_STR); + else + ProcFsHelper_readV2_nics(file, &rdmaNicList); + } + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + ListTk_kfreeNicAddressListElems(&rdmaNicList); + NicAddressList_uninit(&rdmaNicList); + + return 0; +} + + +int ProcFsHelper_read_clientInfo(char* buf, char** start, off_t offset, int size, int* eof, + App* app) +{ + int count = 0; + + Node* localNode = App_getLocalNode(app); + + size_t nicListStrLen = 1024; + char* extendedNicListStr = vmalloc(nicListStrLen); + NicAddressList nicList; + NicAddressListIter nicIter; + + NodeString alias; + Node_copyAlias(localNode, &alias); + + Node_cloneNicList(localNode, &nicList); + // print local clientID + count += scnprintf(buf+count, size-count, "ClientID: %s\n", alias.buf); + + // list usable network interfaces + NicAddressListIter_init(&nicIter, &nicList); + extendedNicListStr[0] = 0; + + for( ; !NicAddressListIter_end(&nicIter); NicAddressListIter_next(&nicIter) ) + { + char* nicAddrStr; + NicAddress* nicAddr = NicAddressListIter_value(&nicIter); + char* tmpStr = vmalloc(nicListStrLen); + + // extended NIC info + nicAddrStr = NIC_nicAddrToString(nicAddr); + snprintf(tmpStr, nicListStrLen, "%s%s%s\n", extendedNicListStr, NIC_INDENTATION_STR, + nicAddrStr); + strcpy(extendedNicListStr, tmpStr); // tmpStr to avoid writing to a buffer that we're + // reading from at the same time + kfree(nicAddrStr); + + SAFE_VFREE(tmpStr); + } + + count += scnprintf(buf+count, size-count, "Interfaces:\n%s", extendedNicListStr); + + // clean up + SAFE_VFREE(extendedNicListStr); + + *eof = 1; + + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + + return count; +} + +int ProcFsHelper_readV2_targetStates(struct seq_file* file, App* app, + struct TargetStateStore* targetsStates, struct NodeStoreEx* nodes, bool isMeta) +{ + FhgfsOpsErr inOutError = FhgfsOpsErr_SUCCESS; + + UInt8List reachabilityStates; + UInt8List consistencyStates; + UInt16List targetIDs; + + UInt8ListIter reachabilityStatesIter; + UInt8ListIter consistencyStatesIter; + UInt16ListIter targetIDsIter; + + Node* currentNode = NULL; + NodeString nodeAndType; + + TargetMapper* targetMapper = App_getTargetMapper(app); + + UInt8List_init(&reachabilityStates); + UInt8List_init(&consistencyStates); + UInt16List_init(&targetIDs); + + TargetStateStore_getStatesAsLists(targetsStates, &targetIDs, + &reachabilityStates, &consistencyStates); + + for(UInt16ListIter_init(&targetIDsIter, &targetIDs), + UInt8ListIter_init(&reachabilityStatesIter, &reachabilityStates), + UInt8ListIter_init(&consistencyStatesIter, &consistencyStates); + (!UInt16ListIter_end(&targetIDsIter) ) + && (!UInt8ListIter_end(&reachabilityStatesIter) ) + && (!UInt8ListIter_end(&consistencyStatesIter) ); + UInt16ListIter_next(&targetIDsIter), + UInt8ListIter_next(&reachabilityStatesIter), + UInt8ListIter_next(&consistencyStatesIter) ) + { + if(isMeta) + { // meta nodes + currentNode = NodeStoreEx_referenceNode(nodes, + (NumNodeID){ UInt16ListIter_value(&targetIDsIter) }); + } + else + { // storage nodes + currentNode = NodeStoreEx_referenceNodeByTargetID(nodes, + UInt16ListIter_value(&targetIDsIter), targetMapper, &inOutError); + } + + if(unlikely(!currentNode) ) + { // this offset does not exist + UInt8List_uninit(&consistencyStates); + UInt8List_uninit(&reachabilityStates); + UInt16List_uninit(&targetIDs); + + return 0; + } + + Node_copyAliasWithTypeStr(currentNode, &nodeAndType); + seq_printf(file, "[Target ID: %hu] @ %s: %s / %s \n", UInt16ListIter_value(&targetIDsIter), + nodeAndType.buf, + TargetStateStore_reachabilityStateToStr(UInt8ListIter_value(&reachabilityStatesIter) ), + TargetStateStore_consistencyStateToStr(UInt8ListIter_value(&consistencyStatesIter) ) ); + Node_put(currentNode); + } + + UInt8List_uninit(&reachabilityStates); + UInt8List_uninit(&consistencyStates); + UInt16List_uninit(&targetIDs); + + return 0; +} + +int ProcFsHelper_read_targetStates(char* buf, char** start, off_t offset, int size, int* eof, + App* app, struct TargetStateStore* targetsStates, struct NodeStoreEx* nodes, bool isMeta) +{ + off_t currentOffset = 0; + int count = 0; + + FhgfsOpsErr inOutError = FhgfsOpsErr_SUCCESS; + + Node* currentNode = NULL; + NodeString nodeAndType; + + UInt8List reachabilityStates; + UInt8List consistencyStates; + UInt16List targetIDs; + + UInt8ListIter reachabilityStatesIter; + UInt8ListIter consistencyStatesIter; + UInt16ListIter targetIDsIter; + + TargetMapper* targetMapper = App_getTargetMapper(app); + + UInt8List_init(&reachabilityStates); + UInt8List_init(&consistencyStates); + UInt16List_init(&targetIDs); + + TargetStateStore_getStatesAsLists(targetsStates, &targetIDs, + &reachabilityStates, &consistencyStates); + + UInt16ListIter_init(&targetIDsIter, &targetIDs); + UInt8ListIter_init(&reachabilityStatesIter, &reachabilityStates); + UInt8ListIter_init(&consistencyStatesIter, &consistencyStates); + + while( (!UInt16ListIter_end(&targetIDsIter) ) + && (!UInt8ListIter_end(&reachabilityStatesIter) ) + && (!UInt8ListIter_end(&consistencyStatesIter) ) + && (currentOffset < offset) ) + { + currentOffset++; + + UInt16ListIter_next(&targetIDsIter); + UInt8ListIter_next(&reachabilityStatesIter); + UInt8ListIter_next(&consistencyStatesIter); + } + + if(UInt16ListIter_end(&targetIDsIter) ) + { // this offset does not exist + UInt8List_uninit(&consistencyStates); + UInt8List_uninit(&reachabilityStates); + UInt16List_uninit(&targetIDs); + + *eof = 1; + return 0; + } + + if(isMeta) + { // meta nodes + currentNode = NodeStoreEx_referenceNode(nodes, + (NumNodeID){UInt16ListIter_value(&targetIDsIter)} ); + } + else + { // storage nodes + currentNode = NodeStoreEx_referenceNodeByTargetID(nodes, UInt16ListIter_value(&targetIDsIter), + targetMapper, &inOutError); + } + + if(unlikely(!currentNode) ) + { + UInt8List_uninit(&consistencyStates); + UInt8List_uninit(&reachabilityStates); + UInt16List_uninit(&targetIDs); + + *eof = 1; + return 0; + } + + Node_copyAliasWithTypeStr(currentNode, &nodeAndType); + count += scnprintf(buf+count, size-count, "[Target ID: %hu] @ %s: %s / %s \n", + UInt16ListIter_value(&targetIDsIter), nodeAndType.buf, + TargetStateStore_reachabilityStateToStr(UInt8ListIter_value(&reachabilityStatesIter) ), + TargetStateStore_consistencyStateToStr(UInt8ListIter_value(&consistencyStatesIter) ) ); + + Node_put(currentNode); + + *start = (char*)1; // move to next offset (note: yes, we're casting a number to a pointer here!) + + UInt8List_uninit(&consistencyStates); + UInt8List_uninit(&reachabilityStates); + UInt16List_uninit(&targetIDs); + + return count; +} + +int ProcFsHelper_readV2_connRetriesEnabled(struct seq_file* file, App* app) +{ + bool retriesEnabled = App_getConnRetriesEnabled(app); + + seq_printf(file, "%d\n", retriesEnabled ? 1 : 0); + + return 0; +} + +int ProcFsHelper_read_connRetriesEnabled(char* buf, char** start, off_t offset, int size, int* eof, + App* app) +{ + int count = 0; + + bool retriesEnabled = App_getConnRetriesEnabled(app); + + count += scnprintf(buf+count, size-count, "%d\n", retriesEnabled ? 1 : 0); + + *eof = 1; + + return count; +} + +int ProcFsHelper_write_connRetriesEnabled(const char __user *buf, unsigned long count, App* app) +{ + int retVal; + long copyVal; + + char* kernelBuf; + char* trimCopy; + bool retriesEnabled; + + kernelBuf = os_kmalloc(count+1); // +1 for zero-term + kernelBuf[count] = 0; + + copyVal = __copy_from_user(kernelBuf, buf, count); + if (copyVal != 0) + { + retVal = -EFAULT; + goto out_fault; + } + + trimCopy = StringTk_trimCopy(kernelBuf); + + retriesEnabled = StringTk_strToBool(trimCopy); + App_setConnRetriesEnabled(app, retriesEnabled); + + retVal = count; + + kfree(trimCopy); + +out_fault: + kfree(kernelBuf); + + return retVal; +} + +int ProcFsHelper_read_remapConnectionFailure(struct seq_file* file, App* app) +{ + Config* cfg = App_getConfig(app); + unsigned remapConnectionFailure = Config_getRemapConnectionFailureStatus(cfg); + + seq_printf(file, "%d\n", remapConnectionFailure); + + return 0; +} + +int ProcFsHelper_write_remapConnectionFailure(const char __user *buf, unsigned long count, App* app) +{ + int retVal; + long copyVal; + Config* cfg = App_getConfig(app); + + char* kernelBuf; + char* trimCopy; + unsigned remapConnectionFailure; + + kernelBuf = os_kmalloc(count+1); // +1 for zero-term + kernelBuf[count] = 0; + + copyVal = __copy_from_user(kernelBuf, buf, count); + if (copyVal != 0) + { + retVal = -EFAULT; + goto out_fault; + } + + trimCopy = StringTk_trimCopy(kernelBuf); + + remapConnectionFailure = StringTk_strToInt(trimCopy); + Config_setRemapConnectionFailureStatus(cfg, remapConnectionFailure); + + retVal = count; + + kfree(trimCopy); + +out_fault: + kfree(kernelBuf); + + return retVal; +} + +int ProcFsHelper_readV2_netBenchModeEnabled(struct seq_file* file, App* app) +{ + bool netBenchModeEnabled = App_getNetBenchModeEnabled(app); + + seq_printf(file, + "%d\n" + "# Enabled netbench_mode (=1) means that storage servers will not read from or\n" + "# write to the underlying file system, so that network throughput can be tested\n" + "# independent of storage device throughput from a netbench client.\n", + netBenchModeEnabled ? 1 : 0); + + return 0; +} + +int ProcFsHelper_read_netBenchModeEnabled(char* buf, char** start, off_t offset, int size, int* eof, + App* app) +{ + int count = 0; + + bool netBenchModeEnabled = App_getNetBenchModeEnabled(app); + + count += scnprintf(buf+count, size-count, + "%d\n" + "# Enabled netbench_mode (=1) means that storage servers will not read from or\n" + "# write to the underlying file system, so that network throughput can be tested\n" + "# independent of storage device throughput from a netbench client.\n", + netBenchModeEnabled ? 1 : 0); + + *eof = 1; + + return count; +} + +int ProcFsHelper_write_netBenchModeEnabled(const char __user *buf, unsigned long count, App* app) +{ + const char* logContext = "procfs (netbench mode)"; + + int retVal; + long copyVal; + Logger* log = App_getLogger(app); + + char* kernelBuf; + char* trimCopy; + bool netBenchModeOldValue; + bool netBenchModeEnabled; // new value + + kernelBuf = os_kmalloc(count+1); // +1 for zero-term + kernelBuf[count] = 0; + + copyVal = __copy_from_user(kernelBuf, buf, count); + if (copyVal != 0) + { + retVal = -EFAULT; + goto out_fault; + } + + trimCopy = StringTk_trimCopy(kernelBuf); + + netBenchModeEnabled = StringTk_strToBool(trimCopy); + + netBenchModeOldValue = App_getNetBenchModeEnabled(app); + App_setNetBenchModeEnabled(app, netBenchModeEnabled); + + if(netBenchModeOldValue != netBenchModeEnabled) + { // print log message + if(netBenchModeEnabled) + Logger_log(log, Log_CRITICAL, logContext, "Network benchmark mode enabled. " + "Storage servers will not write to or read from underlying file system."); + else + Logger_log(log, Log_CRITICAL, logContext, "Network benchmark mode disabled."); + } + + retVal = count; + + kfree(trimCopy); + +out_fault: + kfree(kernelBuf); + + return retVal; +} + +/** + * Drop all established (available) connections. + */ +int ProcFsHelper_write_dropConns(const char __user *buf, unsigned long count, App* app) +{ + NodeStoreEx* mgmtNodes = App_getMgmtNodes(app); + NodeStoreEx* metaNodes = App_getMetaNodes(app); + NodeStoreEx* storageNodes = App_getStorageNodes(app); + + NodesTk_dropAllConnsByStore(mgmtNodes); + NodesTk_dropAllConnsByStore(metaNodes); + NodesTk_dropAllConnsByStore(storageNodes); + + return count; +} + +/** + * Print all log topics and their current log level. + */ +int ProcFsHelper_readV2_logLevels(struct seq_file* file, App* app) +{ + Logger* log = App_getLogger(app); + + unsigned currentTopicNum; + + for(currentTopicNum = 0; currentTopicNum < LogTopic_LAST; currentTopicNum++) + { + const char* logTopicStr = Logger_getLogTopicStr( (LogTopic)currentTopicNum); + int logLevel = Logger_getLogTopicLevel(log, (LogTopic)currentTopicNum); + + seq_printf(file, "%s = %d\n", logTopicStr, logLevel); + } + + return 0; +} + +/** + * Print all log topics and their current log level. + */ +int ProcFsHelper_read_logLevels(char* buf, char** start, off_t offset, int size, int* eof, App* app) +{ + Logger* log = App_getLogger(app); + + int count = 0; + + if(offset < LogTopic_LAST) + { + const char* logTopicStr = Logger_getLogTopicStr( (LogTopic)offset); + int logLevel = Logger_getLogTopicLevel(log, (LogTopic)offset); + + count = scnprintf(buf, size, "%s = %d\n", logTopicStr, logLevel); + } + else + { + *eof = 1; + return 0; + } + + + *start = (char*)1; // move to next offset (note: yes, we're casting a number to a pointer here!) + + return count; +} + +/** + * Set a new log level for a certain log topic. + * + * Note: Expects a string of format "=". + */ +int ProcFsHelper_write_logLevels(const char __user *buf, unsigned long count, App* app) +{ + Logger* log = App_getLogger(app); + + int retVal = -EINVAL; + long copyVal; + + StrCpyList stringList; + StrCpyListIter iter; + char* kernelBuf = NULL; + char* trimCopy = NULL; + + const char* logTopicStr; + LogTopic logTopic; + const char* logLevelStr; + int logLevel; + + StrCpyList_init(&stringList); + + // copy user string to kernel buffer + kernelBuf = os_kmalloc(count+1); // +1 for zero-term + kernelBuf[count] = 0; + + copyVal = __copy_from_user(kernelBuf, buf, count); + if (copyVal != 0) + { + retVal = -EFAULT; + goto out_fault; + } + + trimCopy = StringTk_trimCopy(kernelBuf); + + StringTk_explode(trimCopy, '=', &stringList); + + if(StrCpyList_length(&stringList) != 2) + goto cleanup_list_and_exit; // bad input string + + // now the string list contains topic and new level + + StrCpyListIter_init(&iter, &stringList); + + logTopicStr = StrCpyListIter_value(&iter); + if(!Logger_getLogTopicFromStr(logTopicStr, &logTopic) ) + goto cleanup_list_and_exit; + + StrCpyListIter_next(&iter); + + logLevelStr = StrCpyListIter_value(&iter); + + if(sscanf(logLevelStr, "%d", &logLevel) != 1) + goto cleanup_list_and_exit; + + Logger_setLogTopicLevel(log, logTopic, logLevel); + + retVal = count; + +cleanup_list_and_exit: + kfree(trimCopy); + StrCpyList_uninit(&stringList); +out_fault: + kfree(kernelBuf); + + return retVal; +} + + +void __ProcFsHelper_printGotRootV2(struct seq_file* file, Node* node, NodeStoreEx* nodes) +{ + NodeOrGroup rootOwner = NodeStoreEx_getRootOwner(nodes); + + if (rootOwner.group == Node_getNumID(node).value) + seq_printf(file, " Root: \n"); +} + +void __ProcFsHelper_printGotRoot(Node* node, NodeStoreEx* nodes, char* buf, int* pcount, + int* psize) +{ + int count = *pcount; + int size = *psize; + + NodeOrGroup rootOwner = NodeStoreEx_getRootOwner(nodes); + + if (rootOwner.group == Node_getNumID(node).value) + count += scnprintf(buf+count, size-count, " Root: \n"); + + // set out values + *pcount = count; + *psize = size; +} + +/** + * Prints number of connections for each conn type and adds the peer name of an available conn + * to each type. + */ +void __ProcFsHelper_printNodeConnsV2(struct seq_file* file, struct Node* node) +{ + NodeConnPool* connPool = Node_getConnPool(node); + NodeConnPoolStats poolStats; + + const char* nonPrimaryTag = " [fallback route]"; // extra text to hint at non-primary conns + bool isNonPrimaryConn; + + const unsigned peerNameBufLen = 64; /* 64 is just some arbitrary size that should be big enough + to store the peer name */ + char* peerNameBuf = os_kmalloc(peerNameBufLen); + + if(unlikely(!peerNameBuf) ) + { // out of mem + seq_printf(file, " Connections not available [Error: Out of memory]\n"); + return; + } + + seq_printf(file, " Connections: "); + + + NodeConnPool_getStats(connPool, &poolStats); + + if(!(poolStats.numEstablishedStd + + poolStats.numEstablishedRDMA) ) + seq_printf(file, ""); + else + { // print ": ();" + if(poolStats.numEstablishedStd) + { + if(unlikely(!peerNameBuf) ) + seq_printf(file, "TCP: %u; ", poolStats.numEstablishedStd); + else + { + NodeConnPool_getFirstPeerName(connPool, NICADDRTYPE_STANDARD, + peerNameBufLen, peerNameBuf, &isNonPrimaryConn); + seq_printf(file, "TCP: %u (%s%s); ", poolStats.numEstablishedStd, peerNameBuf, + isNonPrimaryConn ? nonPrimaryTag : ""); + } + } + + if(poolStats.numEstablishedRDMA) + { + if(unlikely(!peerNameBuf) ) + seq_printf(file, "RDMA: %u; ", poolStats.numEstablishedRDMA); + else + { + NodeConnPool_getFirstPeerName(connPool, NICADDRTYPE_RDMA, + peerNameBufLen, peerNameBuf, &isNonPrimaryConn); + seq_printf(file, "RDMA: %u (%s%s); ", poolStats.numEstablishedRDMA, peerNameBuf, + isNonPrimaryConn ? nonPrimaryTag : ""); + } + } + } + + seq_printf(file, "\n"); + + // cleanup + SAFE_KFREE(peerNameBuf); +} + +/** + * Prints number of connections for each conn type and adds the peer name of an available conn + * to each type. + */ +void __ProcFsHelper_printNodeConns(Node* node, char* buf, int* pcount, int* psize) +{ + int count = *pcount; + int size = *psize; + + NodeConnPool* connPool; + NodeConnPoolStats poolStats; + + const char* nonPrimaryTag = " [fallback route]"; // extra text to hint at non-primary conns + bool isNonPrimaryConn; + + connPool = Node_getConnPool(node); + NodeConnPool_getStats(connPool, &poolStats); + + count += scnprintf(buf+count, size-count, " Connections: "); + + + if(!(poolStats.numEstablishedStd + + poolStats.numEstablishedRDMA) ) + count += scnprintf(buf+count, size-count, ""); + else + { // print ": ();" + if(poolStats.numEstablishedStd) + { + count += scnprintf(buf+count, size-count, "TCP: %u (", poolStats.numEstablishedStd); + count += NodeConnPool_getFirstPeerName(connPool, NICADDRTYPE_STANDARD, + size-count, buf+count, &isNonPrimaryConn); + count += scnprintf(buf+count, size-count, "%s", isNonPrimaryConn ? nonPrimaryTag : ""); + count += scnprintf(buf+count, size-count, "); "); + } + + if(poolStats.numEstablishedRDMA) + { + count += scnprintf(buf+count, size-count, "RDMA: %u (", poolStats.numEstablishedRDMA); + count += NodeConnPool_getFirstPeerName(connPool, NICADDRTYPE_RDMA, + size-count, buf+count, &isNonPrimaryConn); + count += scnprintf(buf+count, size-count, "%s", isNonPrimaryConn ? nonPrimaryTag : ""); + count += scnprintf(buf+count, size-count, "); "); + } + } + + count += scnprintf(buf+count, size-count, "\n"); + + // set out values + *pcount = count; + *psize = size; +} diff --git a/client_module/source/filesystem/ProcFsHelper.h b/client_module/source/filesystem/ProcFsHelper.h new file mode 100644 index 0000000..93a3333 --- /dev/null +++ b/client_module/source/filesystem/ProcFsHelper.h @@ -0,0 +1,55 @@ +#ifndef PROCFSHELPER_H_ +#define PROCFSHELPER_H_ + +#include +#include +#include + +#include + + +extern int ProcFsHelper_readV2_config(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_status(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_fsUUID(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_nodes(struct seq_file* file, App* app, struct NodeStoreEx* nodes); +extern int ProcFsHelper_readV2_clientInfo(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_targetStates(struct seq_file* file, App* app, + struct TargetStateStore* targetStates, struct NodeStoreEx* nodes, bool isMeta); +extern int ProcFsHelper_readV2_connRetriesEnabled(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_netBenchModeEnabled(struct seq_file* file, App* app); +extern int ProcFsHelper_readV2_logLevels(struct seq_file* file, App* app); + +extern int ProcFsHelper_read_config(char* buf, char** start, off_t offset, int size, int* eof, + App* app); +extern int ProcFsHelper_read_status(char* buf, char** start, off_t offset, int size, int* eof, + App* app); +extern int ProcFsHelper_read_nodes(char* buf, char** start, off_t offset, int size, int* eof, + struct NodeStoreEx* nodes); +extern int ProcFsHelper_read_clientInfo(char* buf, char** start, off_t offset, int size, int* eof, + App* app); +extern int ProcFsHelper_read_targetStates(char* buf, char** start, off_t offset, int size, int* eof, + App* app, struct TargetStateStore* targetStates, struct NodeStoreEx* nodes, bool isMeta); +extern int ProcFsHelper_read_connRetriesEnabled(char* buf, char** start, off_t offset, int size, + int* eof, App* app); +extern int ProcFsHelper_write_connRetriesEnabled(const char __user *buf, + unsigned long count, App* app); + +extern int ProcFsHelper_read_remapConnectionFailure(struct seq_file* file, App* app); +extern int ProcFsHelper_write_remapConnectionFailure(const char __user *buf, unsigned long count, App* app); + +extern int ProcFsHelper_read_netBenchModeEnabled(char* buf, char** start, off_t offset, int size, + int* eof, App* app); +extern int ProcFsHelper_write_netBenchModeEnabled(const char __user *buf, + unsigned long count, App* app); +extern int ProcFsHelper_write_dropConns(const char __user *buf, unsigned long count, App* app); +extern int ProcFsHelper_read_logLevels(char* buf, char** start, off_t offset, int size, int* eof, + App* app); +extern int ProcFsHelper_write_logLevels(const char __user *buf, unsigned long count, App* app); + +extern void __ProcFsHelper_printGotRootV2(struct seq_file* file, Node* node, NodeStoreEx* nodes); +extern void __ProcFsHelper_printGotRoot(struct Node* node, struct NodeStoreEx* nodes, char* buf, + int* pcount, int* psize); +extern void __ProcFsHelper_printNodeConnsV2(struct seq_file* file, struct Node* node); +extern void __ProcFsHelper_printNodeConns(struct Node* node, char* buf, int* pcount, int* psize); + +#endif /* PROCFSHELPER_H_ */ diff --git a/client_module/source/filesystem/helper/IoctlHelper.c b/client_module/source/filesystem/helper/IoctlHelper.c new file mode 100644 index 0000000..b1a460d --- /dev/null +++ b/client_module/source/filesystem/helper/IoctlHelper.c @@ -0,0 +1,326 @@ +/* + * Ioctl helper functions + */ + +#include +#include +#include "IoctlHelper.h" + +#define STRDUP_OR_RETURN(target, source, len, name) \ + do { \ + if ((len) == 0) \ + return -EINVAL; \ + (target) = strndup_user((const char __user *) (source), (len)); \ + if (IS_ERR((target))) \ + { \ + int error = PTR_ERR((target)); \ + (target) = NULL; \ + LOG_DEBUG_FORMATTED(log, Log_NOTICE, logContext, "Invalid " name " string"); \ + return error; \ + } \ + } while (0) + +/** + * Copy struct BeegfsIoctl_MkFile_Arg from user to kernel space + * + * Note: For simplicity, the calling function needs to free alloced outFileInfo members, even in + * case of errors. Therefore, *outFileInfo is supposed to be memset() with 0 by the caller. + * + * @return 0 on success, negative linux error code otherwise + */ +long IoctlHelper_ioctlCreateFileCopyFromUser(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = __func__; + struct BeegfsIoctl_MkFile_Arg userFileInfo; // fileInfo still with pointers into user space + + memset(&userFileInfo, 0, sizeof(userFileInfo) ); // avoid clang warning + + if (copy_from_user(&userFileInfo, argp, sizeof(userFileInfo) ) ) + return -EFAULT; + + /* we cannot simply do: outFileInfo = userFileInfo, as that would overwrite all NULL pointers + * and we use those NULL pointers to simplify free(), so we copy integers one by one */ + outFileInfo->ownerNodeID = userFileInfo.ownerNodeID; + outFileInfo->parentParentEntryIDLen = userFileInfo.parentParentEntryIDLen; + outFileInfo->parentEntryIDLen = userFileInfo.parentEntryIDLen; + outFileInfo->parentNameLen = userFileInfo.parentNameLen; + outFileInfo->entryNameLen = userFileInfo.entryNameLen; + outFileInfo->symlinkToLen = userFileInfo.symlinkToLen; + outFileInfo->mode = userFileInfo.mode; + outFileInfo->uid = userFileInfo.uid; + outFileInfo->gid = userFileInfo.gid; + outFileInfo->numTargets = userFileInfo.numTargets; + outFileInfo->prefTargetsLen = userFileInfo.prefTargetsLen; + outFileInfo->fileType = userFileInfo.fileType; + outFileInfo->parentIsBuddyMirrored = false; + outFileInfo->storagePoolId = STORAGEPOOLID_INVALIDPOOLID; + + /* Now copy and alloc all char* to kernel space */ + + STRDUP_OR_RETURN(outFileInfo->parentParentEntryID, userFileInfo.parentParentEntryID, + outFileInfo->parentParentEntryIDLen, "parentParentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentEntryID, userFileInfo.parentEntryID, + outFileInfo->parentEntryIDLen, "parentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentName, userFileInfo.parentName, outFileInfo->parentNameLen, + "parentName"); + STRDUP_OR_RETURN(outFileInfo->entryName, userFileInfo.entryName, outFileInfo->entryNameLen, + "entryName"); + + if (userFileInfo.fileType == DT_LNK && userFileInfo.symlinkToLen > 0) + STRDUP_OR_RETURN(outFileInfo->symlinkTo, userFileInfo.symlinkTo, outFileInfo->symlinkToLen, + "symlinkTo"); + + // copy prefTargets array and verify it... + + /* Note: try not to exceed a page, as kmalloc might fail for high order allocations. However, + * that should only happen with very high number of stripes and we will limit the number + * in fhgfs-ctl. */ + + // check if given num targets actually fits inside given targets array + // (note: +1 for terminating 0 element) + if(outFileInfo->prefTargetsLen < ( (outFileInfo->numTargets + 1) * (int) sizeof(uint16_t) ) ) + { + Logger_logFormatted(log, Log_WARNING, logContext, + "prefTargetsLen(=%d) too small for given numTargets(=%d+1)", + outFileInfo->prefTargetsLen, + outFileInfo->numTargets); + return -EINVAL; + } + + // copy prefTargets to kernel buffer based on raw prefTargetsLen + outFileInfo->prefTargets = memdup_user( (char __user *) userFileInfo.prefTargets, + outFileInfo->prefTargetsLen); + if (IS_ERR(outFileInfo->prefTargets) ) + { + int error = PTR_ERR(outFileInfo->prefTargets); + LOG_DEBUG_FORMATTED(log, Log_NOTICE, logContext, "Unable to copy prefTargets array"); + outFileInfo->prefTargets = NULL; + return error; + } + + // check if prefTargets given by user space has a terminating zero as last array element + // (note: not +1, because numTargets count does not include terminating zero element) + if(outFileInfo->prefTargets[outFileInfo->numTargets] != 0) + { + Logger_logFormatted(log, Log_WARNING, logContext, "prefTargets array is not zero-terminated"); + return -EINVAL; + } + + + return 0; +} + +long IoctlHelper_ioctlCreateFileCopyFromUserV2(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = __func__; + struct BeegfsIoctl_MkFileV2_Arg userFileInfo; // fileInfo still with pointers into user space + + memset(&userFileInfo, 0, sizeof(userFileInfo)); // avoid clang warning + + if (copy_from_user(&userFileInfo, argp, sizeof(userFileInfo))) + return -EFAULT; + + /* we cannot simply do: outFileInfo = userFileInfo, as that would overwrite all NULL pointers + * and we use those NULL pointers to simplify free(), so we copy integers one by one */ + outFileInfo->ownerNodeID = userFileInfo.ownerNodeID; + outFileInfo->parentParentEntryIDLen = userFileInfo.parentParentEntryIDLen; + outFileInfo->parentEntryIDLen = userFileInfo.parentEntryIDLen; + outFileInfo->parentNameLen = userFileInfo.parentNameLen; + outFileInfo->entryNameLen = userFileInfo.entryNameLen; + outFileInfo->symlinkToLen = userFileInfo.symlinkToLen; + outFileInfo->mode = userFileInfo.mode; + outFileInfo->uid = userFileInfo.uid; + outFileInfo->gid = userFileInfo.gid; + outFileInfo->numTargets = userFileInfo.numTargets; + outFileInfo->prefTargetsLen = userFileInfo.prefTargetsLen; + outFileInfo->fileType = userFileInfo.fileType; + outFileInfo->parentIsBuddyMirrored = userFileInfo.parentIsBuddyMirrored; + outFileInfo->storagePoolId = STORAGEPOOLID_INVALIDPOOLID; + + /* Now copy and alloc all char* to kernel space */ + + STRDUP_OR_RETURN(outFileInfo->parentParentEntryID, userFileInfo.parentParentEntryID, + outFileInfo->parentParentEntryIDLen, "parentParentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentEntryID, userFileInfo.parentEntryID, + outFileInfo->parentEntryIDLen, "parentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentName, userFileInfo.parentName, outFileInfo->parentNameLen, + "parentName"); + STRDUP_OR_RETURN(outFileInfo->entryName, userFileInfo.entryName, outFileInfo->entryNameLen, + "entryName"); + + if (userFileInfo.fileType == DT_LNK && userFileInfo.symlinkToLen > 0) + STRDUP_OR_RETURN(outFileInfo->symlinkTo, userFileInfo.symlinkTo, outFileInfo->symlinkToLen, + "symlinkTo"); + + // copy prefTargets array and verify it... + + /* Note: try not to exceed a page, as kmalloc might fail for high order allocations. However, + * that should only happen with very high number of stripes and we will limit the number + * in fhgfs-ctl. */ + + // check if given num targets actually fits inside given targets array + // (note: +1 for terminating 0 element) + if(outFileInfo->prefTargetsLen < ( (outFileInfo->numTargets + 1) * (int) sizeof(uint16_t) ) ) + { + Logger_logFormatted(log, Log_WARNING, logContext, + "prefTargetsLen(=%d) too small for given numTargets(=%d+1)", + outFileInfo->prefTargetsLen, + outFileInfo->numTargets); + return -EINVAL; + } + + // copy prefTargets to kernel buffer based on raw prefTargetsLen + outFileInfo->prefTargets = memdup_user( (char __user *) userFileInfo.prefTargets, + outFileInfo->prefTargetsLen); + if (IS_ERR(outFileInfo->prefTargets) ) + { + int error = PTR_ERR(outFileInfo->prefTargets); + LOG_DEBUG_FORMATTED(log, Log_NOTICE, logContext, "Unable to copy prefTargets array"); + outFileInfo->prefTargets = NULL; + return error; + } + + // check if prefTargets given by user space has a terminating zero as last array element + // (note: not +1, because numTargets count does not include terminating zero element) + if(outFileInfo->prefTargets[outFileInfo->numTargets] != 0) + { + Logger_logFormatted(log, Log_WARNING, logContext, "prefTargets array is not zero-terminated"); + return -EINVAL; + } + + + return 0; +} + +long IoctlHelper_ioctlCreateFileCopyFromUserV3(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = __func__; + struct BeegfsIoctl_MkFileV3_Arg userFileInfo; // fileInfo still with pointers into user space + + memset(&userFileInfo, 0, sizeof(userFileInfo)); // avoid clang warning + + if (copy_from_user(&userFileInfo, argp, sizeof(userFileInfo))) + return -EFAULT; + + /* we cannot simply do: outFileInfo = userFileInfo, as that would overwrite all NULL pointers + * and we use those NULL pointers to simplify free(), so we copy integers one by one */ + outFileInfo->ownerNodeID = userFileInfo.ownerNodeID; + outFileInfo->parentParentEntryIDLen = userFileInfo.parentParentEntryIDLen; + outFileInfo->parentEntryIDLen = userFileInfo.parentEntryIDLen; + outFileInfo->parentNameLen = userFileInfo.parentNameLen; + outFileInfo->entryNameLen = userFileInfo.entryNameLen; + outFileInfo->symlinkToLen = userFileInfo.symlinkToLen; + outFileInfo->mode = userFileInfo.mode; + outFileInfo->uid = userFileInfo.uid; + outFileInfo->gid = userFileInfo.gid; + outFileInfo->numTargets = userFileInfo.numTargets; + outFileInfo->prefTargetsLen = userFileInfo.prefTargetsLen; + outFileInfo->fileType = userFileInfo.fileType; + outFileInfo->parentIsBuddyMirrored = userFileInfo.parentIsBuddyMirrored; + outFileInfo->storagePoolId = userFileInfo.storagePoolId; + + /* Now copy and alloc all char* to kernel space */ + + STRDUP_OR_RETURN(outFileInfo->parentParentEntryID, userFileInfo.parentParentEntryID, + outFileInfo->parentParentEntryIDLen, "parentParentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentEntryID, userFileInfo.parentEntryID, + outFileInfo->parentEntryIDLen, "parentEntryID"); + STRDUP_OR_RETURN(outFileInfo->parentName, userFileInfo.parentName, outFileInfo->parentNameLen, + "parentName"); + STRDUP_OR_RETURN(outFileInfo->entryName, userFileInfo.entryName, outFileInfo->entryNameLen, + "entryName"); + + if (userFileInfo.fileType == DT_LNK && userFileInfo.symlinkToLen > 0) + STRDUP_OR_RETURN(outFileInfo->symlinkTo, userFileInfo.symlinkTo, outFileInfo->symlinkToLen, + "symlinkTo"); + + // copy prefTargets array and verify it... + + /* Note: try not to exceed a page, as kmalloc might fail for high order allocations. However, + * that should only happen with very high number of stripes and we will limit the number + * in fhgfs-ctl. */ + + // check if given num targets actually fits inside given targets array + // (note: +1 for terminating 0 element) + if(outFileInfo->prefTargetsLen < ( (outFileInfo->numTargets + 1) * (int) sizeof(uint16_t) ) ) + { + Logger_logFormatted(log, Log_WARNING, logContext, + "prefTargetsLen(=%d) too small for given numTargets(=%d+1)", + outFileInfo->prefTargetsLen, + outFileInfo->numTargets); + return -EINVAL; + } + + // copy prefTargets to kernel buffer based on raw prefTargetsLen + outFileInfo->prefTargets = memdup_user( (char __user *) userFileInfo.prefTargets, + outFileInfo->prefTargetsLen); + if (IS_ERR(outFileInfo->prefTargets) ) + { + int error = PTR_ERR(outFileInfo->prefTargets); + LOG_DEBUG_FORMATTED(log, Log_NOTICE, logContext, "Unable to copy prefTargets array"); + outFileInfo->prefTargets = NULL; + return error; + } + + // check if prefTargets given by user space has a terminating zero as last array element + // (note: not +1, because numTargets count does not include terminating zero element) + if(outFileInfo->prefTargets[outFileInfo->numTargets] != 0) + { + Logger_logFormatted(log, Log_WARNING, logContext, "prefTargets array is not zero-terminated"); + return -EINVAL; + } + + return 0; +} + +/** + * Copy targets from fileInfo->prefTargets array to outCreateInfo->preferredStorageTargets list + * + * @param outCreateInfo outCreateInfo->preferredStorageTargets will be alloced and needs to be freed + * by caller even in case an error is returned (if it is !=NULL) + * @return 0 on success, negative linux error code otherwise + */ +int IoctlHelper_ioctlCreateFileTargetsToList(App* app, struct BeegfsIoctl_MkFileV3_Arg* fileInfo, + struct CreateInfo* outCreateInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = __func__; + + int i; + + // construct output list + + outCreateInfo->preferredStorageTargets = + os_kmalloc(sizeof(*outCreateInfo->preferredStorageTargets)); + if(unlikely(!outCreateInfo->preferredStorageTargets) ) + return -ENOMEM; + + UInt16List_init(outCreateInfo->preferredStorageTargets); + + // copy all targets from array to list + + for(i=0; i < fileInfo->numTargets; i++) + { + uint16_t currentTarget = (fileInfo->prefTargets)[i]; + + if(unlikely(!currentTarget) ) + { // invalid target in the middle of the array + Logger_logFormatted(log, Log_WARNING, logContext, + "Invalid preferred target at this array index: %d (numTargets: %d)\n", + i, fileInfo->numTargets); + + return -EINVAL; + } + + UInt16List_append(outCreateInfo->preferredStorageTargets, currentTarget); + } + + return 0; +} + diff --git a/client_module/source/filesystem/helper/IoctlHelper.h b/client_module/source/filesystem/helper/IoctlHelper.h new file mode 100644 index 0000000..2a4a958 --- /dev/null +++ b/client_module/source/filesystem/helper/IoctlHelper.h @@ -0,0 +1,37 @@ +/* + * Ioctl helper functions + */ + +#ifndef IOCTLHELPER_H_ +#define IOCTLHELPER_H_ + +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_COMPAT +#include +#include +#endif + +#include + + +extern long IoctlHelper_ioctlCreateFileCopyFromUser(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo); + +extern long IoctlHelper_ioctlCreateFileCopyFromUserV2(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo); + +extern long IoctlHelper_ioctlCreateFileCopyFromUserV3(App* app, void __user *argp, + struct BeegfsIoctl_MkFileV3_Arg* outFileInfo); + +extern int IoctlHelper_ioctlCreateFileTargetsToList(App* app, + struct BeegfsIoctl_MkFileV3_Arg* fileInfo, struct CreateInfo* outCreateInfo); + + +#endif /* IOCTLHELPER_H_ */ + diff --git a/client_module/source/net/filesystem/FhgfsOpsCommKit.c b/client_module/source/net/filesystem/FhgfsOpsCommKit.c new file mode 100644 index 0000000..2651b2c --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsCommKit.c @@ -0,0 +1,1693 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef BEEGFS_NVFS +#include +#include +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsCommKit.h" + + +#define COMMKIT_RESET_SLEEP_MS 5000 /* how long to sleep if target state not good/offline */ + +static struct kmem_cache* headerBufferCache; +static mempool_t* headerBufferPool; + +bool FhgfsOpsCommKit_initEmergencyPools() +{ + const char* cacheName = BEEGFS_MODULE_NAME_STR "-msgheaders"; + + if(BEEGFS_COMMKIT_MSGBUF_SIZE > PAGE_SIZE) + { + WARN_ON(1); + return false; + } + +#ifdef KERNEL_HAS_KMEMCACHE_DTOR + #if defined(KERNEL_HAS_SLAB_MEM_SPREAD) + headerBufferCache = kmem_cache_create(cacheName, BEEGFS_COMMKIT_MSGBUF_SIZE, 0, + SLAB_MEM_SPREAD, NULL, NULL); + #else + headerBufferCache = kmem_cache_create(cacheName, BEEGFS_COMMKIT_MSGBUF_SIZE, 0, + 0, NULL, NULL); + #endif +#else + #if defined(KERNEL_HAS_SLAB_MEM_SPREAD) + headerBufferCache = kmem_cache_create(cacheName, BEEGFS_COMMKIT_MSGBUF_SIZE, 0, + SLAB_MEM_SPREAD, NULL); + #else + headerBufferCache = kmem_cache_create(cacheName, BEEGFS_COMMKIT_MSGBUF_SIZE, 0, + 0, NULL); + #endif +#endif + + if(!headerBufferCache) + return false; + + headerBufferPool = mempool_create_slab_pool(4, headerBufferCache); + if(!headerBufferPool) + { + kmem_cache_destroy(headerBufferCache); + return false; + } + + return true; +} + +void FhgfsOpsCommKit_releaseEmergencyPools() +{ + mempool_destroy(headerBufferPool); + kmem_cache_destroy(headerBufferCache); +} + +static void* allocHeaderBuffer(gfp_t gfp) +{ + return mempool_alloc(headerBufferPool, gfp); +} + +static void freeHeaderBuffer(void* header) +{ + mempool_free(header, headerBufferPool); +} + +static const struct CommKitContextOps readfileOps; +static const struct CommKitContextOps writefileOps; + +struct ReadfileIterOps +{ + void (*nextIter)(CommKitContext*, FileOpState*); + void (*prepare)(CommKitContext*, FileOpState*); +}; + +struct WritefileIterOps +{ + void (*nextIter)(CommKitContext*, FileOpState*); + void (*prepare)(CommKitContext*, FileOpState*); +}; + +static void commkit_initTargetInfo(struct CommKitTargetInfo* info, uint16_t targetID) +{ + struct CommKitTargetInfo value = { + .state = CommKitState_PREPARE, + .targetID = targetID, + .useBuddyMirrorSecond = false, + .nodeResult = -FhgfsOpsErr_INTERNAL, + }; + + *info = value; +} + +/** + * Note: Initializes the expectedNodeResult attribute from the size argument + * Note: defaults to server-side mirroring enabled. + */ +void FhgfsOpsCommKit_initFileOpState(FileOpState* state, loff_t offset, size_t size, + uint16_t targetID) +{ + *state = (FileOpState) { + .offset = offset, + .transmitted = 0, + // For read: RECVHEADER will bump this in each HEADER-DATA loop iteration + // For write: PREPARE sets this to totalSize + .toBeTransmitted = 0, + .totalSize = size, + + .firstWriteDoneForTarget = false, + .receiveFileData = false, + .expectedNodeResult = size, +#ifdef BEEGFS_NVFS + .rdmap = NULL, +#endif + }; + + commkit_initTargetInfo(&state->base, targetID); +} + +void FhgfsOpsCommKit_initFsyncState(struct FsyncContext* context, struct FsyncState* state, + uint16_t targetID) +{ + commkit_initTargetInfo(&state->base, targetID); + state->firstWriteDoneForTarget = false; + + INIT_LIST_HEAD(&state->base.targetInfoList); + list_add_tail(&state->base.targetInfoList, &context->states); +} + +void FhgfsOpsCommKit_initStatStorageState(struct list_head* states, + struct StatStorageState* state, uint16_t targetID) +{ + commkit_initTargetInfo(&state->base, targetID); + + INIT_LIST_HEAD(&state->base.targetInfoList); + list_add_tail(&state->base.targetInfoList, states); +} + + + +static void __commkit_add_socket_pollstate(CommKitContext* context, + struct CommKitTargetInfo* info, short pollEvents) +{ + PollState_addSocket(&context->pollState, info->socket, pollEvents); + context->numPollSocks++; +} + +static bool __commkit_prepare_io(CommKitContext* context, struct CommKitTargetInfo* info, + int events) +{ + if (fatal_signal_pending(current) || !Node_getIsActive(info->node)) + { + info->state = CommKitState_SOCKETINVALIDATE; + return false; + } + + // check for a poll() timeout or error (all states have to be cancelled in that case) + if(unlikely(context->pollTimedOut) || BEEGFS_SHOULD_FAIL(commkit_polltimeout, 1) ) + { + info->nodeResult = -FhgfsOpsErr_COMMUNICATION; + info->state = CommKitState_SOCKETINVALIDATE; + return false; + } + + if(!(info->socket->poll.revents & events) ) + { + __commkit_add_socket_pollstate(context, info, events); + return false; + } + + return true; +} + + +static bool __commkit_prepare_generic(CommKitContext* context, struct CommKitTargetInfo* info) +{ + TargetMapper* targetMapper = App_getTargetMapper(context->app); + NodeStoreEx* storageNodes = App_getStorageNodes(context->app); + + FhgfsOpsErr resolveErr; + NodeConnPool* connPool; +#ifdef BEEGFS_NVFS + FileOpState* currentState = container_of(info, struct FileOpState, base); + struct iov_iter* data = NULL; +#endif + DevicePriorityContext devPrioCtx = + { + .maxConns = 0, +#ifdef BEEGFS_NVFS + .gpuIndex = -1, +#endif + }; + + bool allowWaitForConn = !context->numAcquiredConns; // don't wait if we got at least + // one conn already (this is important to avoid a deadlock between racing commkit processes) + +#ifdef BEEGFS_NVFS + // only set data if this is a storage call, NVFS ops are available and the + // iov is an ITER_IOVEC. + if (context->ioInfo && context->ioInfo->nvfs) + { + struct FileOpVecState* vs = container_of(currentState, struct FileOpVecState, base); + if (beegfs_iov_iter_is_iovec(&vs->data)) + data = &vs->data; + } +#endif + info->socket = NULL; + info->nodeResult = -FhgfsOpsErr_COMMUNICATION; + info->selectedTargetID = info->targetID; + + info->headerBuffer = allocHeaderBuffer(allowWaitForConn ? GFP_NOFS : GFP_NOWAIT); + if(!info->headerBuffer) + { + context->numBufferless += 1; + return false; + } + + // select the right targetID and get target state + + if(!context->ioInfo) + info->selectedTargetID = info->targetID; + else + if(StripePattern_getPatternType(context->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = App_getStorageBuddyGroupMapper(context->app); + + info->selectedTargetID = info->useBuddyMirrorSecond ? + MirrorBuddyGroupMapper_getSecondaryTargetID(mirrorBuddies, info->targetID) : + MirrorBuddyGroupMapper_getPrimaryTargetID(mirrorBuddies, info->targetID); + + if(unlikely(!info->selectedTargetID) ) + { // invalid mirror group ID + Logger_logErrFormatted(context->log, context->ops->logContext, + "Invalid mirror buddy group ID: %hu", info->targetID); + info->nodeResult = -FhgfsOpsErr_UNKNOWNTARGET; + goto cleanup; + } + } + + // check target state + + { + TargetStateStore* stateStore = App_getTargetStateStore(context->app); + CombinedTargetState targetState; + bool getStateRes = TargetStateStore_getState(stateStore, info->selectedTargetID, + &targetState); + + if(unlikely( !getStateRes || + (targetState.reachabilityState == TargetReachabilityState_OFFLINE) || + ( context->ioInfo && + (StripePattern_getPatternType(context->ioInfo->pattern) == + STRIPEPATTERN_BuddyMirror) && + (targetState.consistencyState != TargetConsistencyState_GOOD) ) ) ) + { // unusable target state, retry details will be handled in retry handler + int targetAction = CK_SKIP_TARGET; + + info->state = CommKitState_CLEANUP; + + if(context->ops->selectedTargetBad) + targetAction = context->ops->selectedTargetBad(context, info, &targetState); + + if(targetAction == CK_SKIP_TARGET) + goto error; + } + } + + // get the target-node reference + info->node = NodeStoreEx_referenceNodeByTargetID(storageNodes, + info->selectedTargetID, targetMapper, &resolveErr); + if(unlikely(!info->node) ) + { // unable to resolve targetID + info->nodeResult = -resolveErr; + goto cleanup; + } + + connPool = Node_getConnPool(info->node); +#ifdef BEEGFS_NVFS + // perform first test for GPUD + context->gpudRc = 0; + + if (data) + context->gpudRc = RdmaInfo_detectNVFSRequest(&devPrioCtx, data); +#endif + + // connect + info->socket = NodeConnPool_acquireStreamSocketEx(connPool, allowWaitForConn, &devPrioCtx); + if(!info->socket) + { // no conn available => error or didn't want to wait + if(likely(!allowWaitForConn) ) + { // just didn't want to wait => keep stage and try again later + Node_put(info->node); + info->node = NULL; + + context->numUnconnectable++; + goto error; + } + else + { // connection error + if(!context->connFailedLogged) + { // no conn error logged yet + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + if (fatal_signal_pending(current)){ + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Connect to server canceled by pending signal: %s", + nodeAndType.buf ); + } + else { + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Unable to connect to server: %s", + nodeAndType.buf ); + } + } + + context->connFailedLogged = true; + goto cleanup; + } + } + + info->headerSize = context->ops->prepareHeader(context, info); + if(info->headerSize == 0) + goto cleanup; + + context->numAcquiredConns++; + + info->state = CommKitState_SENDHEADER; + + return true; + +cleanup: + info->state = CommKitState_CLEANUP; + return false; + +error: + freeHeaderBuffer(info->headerBuffer); + info->headerBuffer = NULL; + return false; +} + +static void __commkit_sendheader_generic(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + ssize_t sendRes; + + if(BEEGFS_SHOULD_FAIL(commkit_sendheader_timeout, 1) ) + sendRes = -ETIMEDOUT; + else + sendRes = Socket_send_kernel(info->socket, info->headerBuffer, info->headerSize, 0); + + if(unlikely(sendRes != info->headerSize) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Failed to send message to %s: %s", nodeAndType.buf, + Socket_getPeername(info->socket) ); + + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + + info->state = CommKitState_SENDDATA; + info->headerSize = 0; +} + +static void __commkit_senddata_generic(CommKitContext* context, struct CommKitTargetInfo* info) +{ + int sendRes; + + if(!__commkit_prepare_io(context, info, POLLOUT) ) + return; + + sendRes = context->ops->sendData(context, info); + + if(unlikely(sendRes < 0) ) + { + NodeString nodeAndType; + if(sendRes == -EFAULT) + { // bad buffer address given + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Bad buffer address"); + + info->nodeResult = -FhgfsOpsErr_ADDRESSFAULT; + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication error in SENDDATA stage. Node: %s", nodeAndType.buf ); + if(context->ops->printSendDataDetails) + context->ops->printSendDataDetails(context, info); + + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + + if(sendRes == 0) + { // all of the data has been sent => proceed to the next stage + info->state = CommKitState_RECVHEADER; + + __commkit_add_socket_pollstate(context, info, POLLIN); + return; + } + + // there is still data to be sent => prepare pollout for the next round + __commkit_add_socket_pollstate(context, info, POLLOUT); +} + +static void __commkit_recvheader_generic(CommKitContext* context, struct CommKitTargetInfo* info) +{ + ssize_t recvRes = -EREMOTEIO; + + // check for incoming data + if(!__commkit_prepare_io(context, info, POLLIN) ) + return; + + if(BEEGFS_SHOULD_FAIL(commkit_recvheader_timeout, 1) ) + recvRes = -ETIMEDOUT; + else + { + size_t msgLength; + + if(info->headerSize < NETMSG_MIN_LENGTH) + { + void *buffer = info->headerBuffer + info->headerSize; + ssize_t size = NETMSG_MIN_LENGTH - info->headerSize; + + recvRes = Socket_recvT_kernel(info->socket, buffer, size, MSG_DONTWAIT, 0); + + if(recvRes <= 0) + { + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Failed to receive message header from: %s", Socket_getPeername(info->socket) ); + goto recv_err; + } + + info->headerSize += recvRes; + } + + msgLength = NetMessage_extractMsgLengthFromBuf(info->headerBuffer); + + if(msgLength > BEEGFS_COMMKIT_MSGBUF_SIZE) + { // message too big to be accepted + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Received a message that is too large from: %s (bufLen: %u, msgLen: %zdd)", + Socket_getPeername(info->socket), BEEGFS_COMMKIT_MSGBUF_SIZE, msgLength); + + info->state = -CommKitState_SOCKETINVALIDATE; + info->nodeResult = -FhgfsOpsErr_COMMUNICATION; + return; + } + + if(info->headerSize < msgLength) + { + void *buffer = info->headerBuffer + info->headerSize; + size_t size = msgLength - info->headerSize; + + recvRes = Socket_recvT_kernel(info->socket, buffer, size, MSG_DONTWAIT, 0); + + if(recvRes <= 0) + { + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Failed to receive message body from: %s", Socket_getPeername(info->socket) ); + goto recv_err; + } + + info->headerSize += recvRes; + } + + if(info->headerSize < msgLength) + return; + } + +recv_err: + if(unlikely(recvRes <= 0) ) + { // receive failed + // note: signal pending log msg will be printed in stage SOCKETEXCEPTION, so no need here + if (!fatal_signal_pending(current)) { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Receive failed from: %s @ %s", nodeAndType.buf, + Socket_getPeername(info->socket) ); + } + + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + + recvRes = context->ops->recvHeader(context, info); + + if(unlikely(recvRes < 0) ) + info->state = CommKitState_SOCKETINVALIDATE; + else + info->state = CommKitState_RECVDATA; +} + +static void __commkit_recvdata_generic(CommKitContext* context, struct CommKitTargetInfo* info) +{ + int recvRes; + + if(!__commkit_prepare_io(context, info, POLLIN) ) + return; + + recvRes = context->ops->recvData(context, info); + + if(unlikely(recvRes < 0) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + if(recvRes == -EFAULT) + { // bad buffer address given + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Bad buffer address"); + + info->nodeResult = -FhgfsOpsErr_ADDRESSFAULT; + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + else if(recvRes == -ETIMEDOUT) + { // timeout + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication timeout in RECVDATA stage. Node: %s", + nodeAndType.buf ); + } + else + { // error + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication error in RECVDATA stage. Node: %s (recv result: %lld)", + nodeAndType.buf, (long long)recvRes); + } + + info->state = CommKitState_SOCKETINVALIDATE; + return; + } + + if(recvRes == 0) + info->state = CommKitState_CLEANUP; + else + __commkit_add_socket_pollstate(context, info, POLLIN); +} + +static void __commkit_socketinvalidate_generic(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + if (fatal_signal_pending(current)) + { // interrupted by signal + info->nodeResult = -FhgfsOpsErr_INTERRUPTED; + Logger_logFormatted(context->log, Log_NOTICE, context->ops->logContext, + "Communication interrupted by signal. Node: %s", nodeAndType.buf ); + } + else + if(!Node_getIsActive(info->node) ) + { + info->nodeResult = -FhgfsOpsErr_UNKNOWNNODE; + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication with inactive node. Node: %s", nodeAndType.buf ); + } + else if (info->nodeResult == -FhgfsOpsErr_ADDRESSFAULT) + { + // not a commkit error. release all resources and treat this CTI as done during cleanup. + } + else + { // "normal" connection error + info->nodeResult = -FhgfsOpsErr_COMMUNICATION; + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication error. Node: %s", nodeAndType.buf ); + + if(context->ops->printSocketDetails) + context->ops->printSocketDetails(context, info); + } + + NodeConnPool_invalidateStreamSocket(Node_getConnPool(info->node), info->socket); + context->numAcquiredConns--; + info->socket = NULL; + + info->state = CommKitState_CLEANUP; +} + +static void __commkit_cleanup_generic(CommKitContext* context, struct CommKitTargetInfo* info) +{ + +#ifdef BEEGFS_NVFS + // + // Clean up the RDMA mapping. + // + if (context->ops == &readfileOps) + { + FileOpState* currentState = container_of(info, FileOpState, base); + if (currentState->rdmap) + { + RdmaInfo_unmapRead(currentState->rdmap); + currentState->rdmap = NULL; + } + } + else if (context->ops == &writefileOps) + { + FileOpState* currentState = container_of(info, FileOpState, base); + if (currentState->rdmap) + { + RdmaInfo_unmapWrite(currentState->rdmap); + currentState->rdmap = NULL; + } + } +#endif // BEEGFS_NVFS + + if(likely(info->socket) ) + { + NodeConnPool_releaseStreamSocket(Node_getConnPool(info->node), info->socket); + context->numAcquiredConns--; + } + + freeHeaderBuffer(info->headerBuffer); + info->headerBuffer = NULL; + + if(likely(info->node) ) + { + Node_put(info->node); + info->node = NULL; + } + + // prepare next stage + + if(unlikely( + (info->nodeResult == -FhgfsOpsErr_COMMUNICATION) || + (info->nodeResult == -FhgfsOpsErr_AGAIN && + (context->ops->retryFlags & CK_RETRY_LOOP_EAGAIN) ) ) ) + { // comm error occurred => check whether we can do a retry + if (fatal_signal_pending(current)) + info->nodeResult = -FhgfsOpsErr_INTERRUPTED; + else if (App_getConnRetriesEnabled(context->app) && + (!context->maxNumRetries || context->currentRetryNum < context->maxNumRetries)) + { // we have retries left + context->numRetryWaiters++; + info->state = CommKitState_RETRYWAIT; + return; + } + } + + // success or no retries left => done + context->numDone++; + info->state = CommKitState_DONE; +} + +static void __commkit_start_retry(CommKitContext* context, int flags) +{ + struct CommKitTargetInfo* info; + TargetStateStore* stateStore = App_getTargetStateStore(context->app); + MirrorBuddyGroupMapper* mirrorBuddies = App_getStorageBuddyGroupMapper(context->app); + unsigned patternType = context->ioInfo + ? StripePattern_getPatternType(context->ioInfo->pattern) + : STRIPEPATTERN_Invalid; + + bool cancelRetries = false; // true if there are offline targets + bool resetRetries = false; /* true to not deplete retries if there are unusable target states + ("!good && !offline") */ + bool sleepOnResetRetries = true; // true to retry immediately without sleeping + + // reset context values for retry round + + context->numRetryWaiters = 0; + context->pollTimedOut = false; + + // check for offline targets + + list_for_each_entry(info, context->targetInfoList, targetInfoList) + { + if(info->state == CommKitState_RETRYWAIT) + { + CombinedTargetState targetState; + CombinedTargetState buddyTargetState; + uint16_t targetID = info->targetID; + uint16_t buddyTargetID = info->targetID; + bool getTargetStateRes; + bool getBuddyTargetStateRes = true; + + // resolve the actual targetID + + if(patternType == STRIPEPATTERN_BuddyMirror) + { + targetID = info->useBuddyMirrorSecond ? + MirrorBuddyGroupMapper_getSecondaryTargetID(mirrorBuddies, info->targetID) : + MirrorBuddyGroupMapper_getPrimaryTargetID(mirrorBuddies, info->targetID); + buddyTargetID = info->useBuddyMirrorSecond ? + MirrorBuddyGroupMapper_getPrimaryTargetID(mirrorBuddies, info->targetID) : + MirrorBuddyGroupMapper_getSecondaryTargetID(mirrorBuddies, info->targetID); + } + + // check current target state + + getTargetStateRes = TargetStateStore_getState(stateStore, targetID, + &targetState); + if (targetID == buddyTargetID) + buddyTargetState = targetState; + else + getBuddyTargetStateRes = TargetStateStore_getState(stateStore, buddyTargetID, + &buddyTargetState); + + if( (!getTargetStateRes && ( (targetID != buddyTargetID) && !getBuddyTargetStateRes) ) || + ( (targetState.reachabilityState == TargetReachabilityState_OFFLINE) && + (buddyTargetState.reachabilityState == TargetReachabilityState_OFFLINE) ) ) + { // no more retries when both buddies are offline + LOG_DEBUG_FORMATTED(context->log, Log_SPAM, context->ops->logContext, + "Skipping communication with offline targetID: %hu", + targetID); + cancelRetries = true; + break; + } + + if(flags & CK_RETRY_BUDDY_FALLBACK) + { + if( ( !getTargetStateRes || + (targetState.consistencyState != TargetConsistencyState_GOOD) || + (targetState.reachabilityState != TargetReachabilityState_ONLINE) ) + && ( getBuddyTargetStateRes && + (buddyTargetState.consistencyState == TargetConsistencyState_GOOD) && + (buddyTargetState.reachabilityState == TargetReachabilityState_ONLINE) ) ) + { // current target not good but buddy is good => switch to buddy + LOG_DEBUG_FORMATTED(context->log, Log_SPAM, context->ops->logContext, + "Switching to buddy with good target state. " + "targetID: %hu; target state: %s / %s", + targetID, TargetStateStore_reachabilityStateToStr(targetState.reachabilityState), + TargetStateStore_consistencyStateToStr(targetState.consistencyState) ); + info->useBuddyMirrorSecond = !info->useBuddyMirrorSecond; + info->state = CommKitState_PREPARE; + resetRetries = true; + sleepOnResetRetries = false; + continue; + } + } + + if( (patternType == STRIPEPATTERN_BuddyMirror) && + ( (targetState.reachabilityState != TargetReachabilityState_ONLINE) || + (targetState.consistencyState != TargetConsistencyState_GOOD) ) ) + { // both buddies not good, but at least one of them not offline => wait for clarification + LOG_DEBUG_FORMATTED(context->log, Log_DEBUG, context->ops->logContext, + "Waiting because of target state. " + "targetID: %hu; target state: %s / %s", + targetID, TargetStateStore_reachabilityStateToStr(targetState.reachabilityState), + TargetStateStore_consistencyStateToStr(targetState.consistencyState) ); + info->state = CommKitState_PREPARE; + resetRetries = true; + continue; + } + + if(info->nodeResult == -FhgfsOpsErr_AGAIN && (flags & CK_RETRY_LOOP_EAGAIN)) + { + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Waiting because target asked for infinite retries. targetID: %hu", targetID); + info->state = CommKitState_PREPARE; + resetRetries = true; + continue; + } + + // normal retry + info->state = CommKitState_PREPARE; + } + } + + // if we have offline targets, cancel all further retry waiters + if(cancelRetries) + { + list_for_each_entry(info, context->targetInfoList, targetInfoList) + { + if( (info->state != CommKitState_RETRYWAIT) && (info->state != CommKitState_PREPARE ) ) + continue; + + context->numDone++; + info->state = CommKitState_DONE; + } + + return; + } + + + // wait before we actually start the retry + if(resetRetries) + { // reset retries to not deplete them in case of non-good and non-offline targets + if(sleepOnResetRetries) + Thread_sleep(COMMKIT_RESET_SLEEP_MS); + + context->currentRetryNum = 0; + } + else + { // normal retry + MessagingTk_waitBeforeRetry(context->currentRetryNum); + + context->currentRetryNum++; + } +} + +static FhgfsOpsErr __commkit_message_genericResponse(CommKitContext* context, + struct CommKitTargetInfo* info, unsigned requestMsgType) +{ + const char* logContext = "Messaging (RPC)"; + + bool parseRes; + GenericResponseMsg msg; + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + GenericResponseMsg_init(&msg); + + + parseRes = NetMessageFactory_deserializeFromBuf(context->app, info->headerBuffer, + info->headerSize, &msg.simpleIntStringMsg.netMessage, NETMSGTYPE_GenericResponse); + if(!parseRes) + { + Logger_logFormatted(context->log, Log_ERR, "received bad message type from %s: %i", + nodeAndType.buf, + NetMessage_getMsgType(&msg.simpleIntStringMsg.netMessage) ); + return -FhgfsOpsErr_INTERNAL; + } + + switch(GenericResponseMsg_getControlCode(&msg) ) + { + case GenericRespMsgCode_TRYAGAIN: + if(!info->logged.peerTryAgain) + { + info->logged.peerTryAgain = true; + + Logger_logFormatted(context->log, Log_NOTICE, logContext, + "Peer is asking for a retry: %s; Reason: %s", + nodeAndType.buf, + GenericResponseMsg_getLogStr(&msg) ); + Logger_logFormatted(context->log, Log_DEBUG, logContext, + "Message type: %u", requestMsgType); + } + + return FhgfsOpsErr_AGAIN; + + case GenericRespMsgCode_INDIRECTCOMMERR: + if(!info->logged.indirectCommError) + { + info->logged.indirectCommError = true; + + Logger_logFormatted(context->log, Log_NOTICE, logContext, + "Peer reported indirect communication error: %s; Reason: %s", + nodeAndType.buf, + GenericResponseMsg_getLogStr(&msg) ); + Logger_logFormatted(context->log, Log_DEBUG, logContext, + "Message type: %u", requestMsgType); + } + + return FhgfsOpsErr_COMMUNICATION; + + default: + Logger_logFormatted(context->log, Log_NOTICE, logContext, + "Peer replied with unknown control code: %s; Code: %u; Reason: %s", + nodeAndType.buf, + (unsigned)GenericResponseMsg_getControlCode(&msg), + GenericResponseMsg_getLogStr(&msg) ); + Logger_logFormatted(context->log, Log_DEBUG, logContext, + "Message type: %u", requestMsgType); + + return FhgfsOpsErr_INTERNAL; + } +} + +void FhgfsOpsCommkit_communicate(App* app, RemotingIOInfo* ioInfo, struct list_head* targetInfos, + const struct CommKitContextOps* ops, void* private) +{ + Config* cfg = App_getConfig(app); + int numStates = 0; + + CommKitContext context = + { + .ops = ops, + + .app = app, + .log = App_getLogger(app), + .private = private, + + .ioInfo = ioInfo, + + .targetInfoList = targetInfos, + + .numRetryWaiters = 0, // counter for states that encountered a comm error + .numDone = 0, // counter for finished states + .numAcquiredConns = 0, // counter for currently acquired conns (to wait only for first conn) + + .pollTimedOut = false, + .pollTimeoutLogged = false, + .connFailedLogged = false, + + .currentRetryNum = 0, + .maxNumRetries = Config_getConnNumCommRetries(cfg), +#ifdef BEEGFS_NVFS + .gpudRc = -1, +#endif + }; + + do + { + struct CommKitTargetInfo* info; + + context.numUnconnectable = 0; // will be increased by states the didn't get a conn + context.numPollSocks = 0; // will be increased by the states that need to poll + context.numBufferless = 0; + numStates = 0; + + PollState_init(&context.pollState); + + // let each state do something (before we call poll) + list_for_each_entry(info, targetInfos, targetInfoList) + { + numStates++; + + switch(info->state) + { + case CommKitState_PREPARE: + if(!__commkit_prepare_generic(&context, info) ) + break; + BEEGFS_FALLTHROUGH; + case CommKitState_SENDHEADER: + __commkit_sendheader_generic(&context, info); + __commkit_add_socket_pollstate(&context, info, + context.ops->sendData ? POLLOUT : POLLIN); + break; + + case CommKitState_SENDDATA: + if(context.ops->sendData) + { + __commkit_senddata_generic(&context, info); + break; + } + BEEGFS_FALLTHROUGH; + + case CommKitState_RECVHEADER: + if(context.ops->recvHeader) + { + __commkit_recvheader_generic(&context, info); + break; + } + BEEGFS_FALLTHROUGH; + + case CommKitState_RECVDATA: + if(context.ops->recvData) + { + __commkit_recvdata_generic(&context, info); + break; + } + BEEGFS_FALLTHROUGH; + + case CommKitState_CLEANUP: + __commkit_cleanup_generic(&context, info); + break; + + case CommKitState_SOCKETINVALIDATE: + __commkit_socketinvalidate_generic(&context, info); + __commkit_cleanup_generic(&context, info); + break; + + case CommKitState_RETRYWAIT: + case CommKitState_DONE: + // nothing to be done here + break; + + default: + BUG(); + } + } + + if(context.numPollSocks) + __FhgfsOpsCommKitCommon_pollStateSocks(&context, numStates); + else + if(unlikely( + context.numRetryWaiters && + ( (context.numDone + context.numRetryWaiters) == numStates) ) ) + { // we have some retry waiters and the rest is done + // note: this can only happen if we don't have any pollsocks, hence the "else" + + __commkit_start_retry(&context, context.ops->retryFlags); + } + } while(context.numDone != numStates); +} + + + +static unsigned __commkit_readfile_prepareHeader(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + Config* cfg = App_getConfig(context->app); + Node* localNode = App_getLocalNode(context->app); + const NumNodeID localNodeNumID = Node_getNumID(localNode); + FileOpState* currentState = container_of(info, FileOpState, base); + struct ReadfileIterOps* ops = context->private; + + NetMessage *netMessage = NULL; + ReadLocalFileV2Msg readV2Msg; +#ifdef BEEGFS_NVFS + ReadLocalFileRDMAMsg readRDMAMsg; + + currentState->rdmap = NULL; + if (context->gpudRc > 0) + { + struct FileOpVecState* vecState = container_of(currentState, struct FileOpVecState, base); + currentState->rdmap = RdmaInfo_mapRead(&vecState->data, info->socket); + } + if (context->gpudRc < 0 || IS_ERR(currentState->rdmap)) + { + int st = currentState->rdmap != NULL? PTR_ERR(currentState->rdmap) : context->gpudRc; + info->state = CommKitState_CLEANUP; + info->nodeResult = (st == -ENOMEM)? -FhgfsOpsErr_OUTOFMEM : -FhgfsOpsErr_INVAL; + currentState->rdmap = NULL; + return 0; + } + + // prepare message + if (!currentState->rdmap) +#endif // BEEGFS_NVFS + { + ReadLocalFileV2Msg_initFromSession(&readV2Msg, localNodeNumID, + context->ioInfo->fileHandleID, info->targetID, context->ioInfo->pathInfo, + context->ioInfo->accessFlags, currentState->offset, currentState->totalSize); + + netMessage = &readV2Msg.netMessage; + } +#ifdef BEEGFS_NVFS + else + { + ReadLocalFileRDMAMsg_initFromSession(&readRDMAMsg, localNodeNumID, + context->ioInfo->fileHandleID, info->targetID, context->ioInfo->pathInfo, + context->ioInfo->accessFlags, currentState->offset, currentState->totalSize, + currentState->rdmap); + + netMessage = &readRDMAMsg.netMessage; + } +#endif // BEEGFS_NVFS + + NetMessage_setMsgHeaderTargetID(netMessage, info->selectedTargetID); + + if (currentState->firstWriteDoneForTarget && Config_getSysSessionChecksEnabled(cfg)) + NetMessage_addMsgHeaderFeatureFlag(netMessage, READLOCALFILEMSG_FLAG_SESSION_CHECK); + + if(StripePattern_getPatternType(context->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { + NetMessage_addMsgHeaderFeatureFlag(netMessage, READLOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(info->useBuddyMirrorSecond) + NetMessage_addMsgHeaderFeatureFlag(netMessage, + READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + if(App_getNetBenchModeEnabled(context->app) ) + NetMessage_addMsgHeaderFeatureFlag(netMessage, READLOCALFILEMSG_FLAG_DISABLE_IO); + + NetMessage_serialize(netMessage, info->headerBuffer, BEEGFS_COMMKIT_MSGBUF_SIZE); + + currentState->transmitted = 0; + currentState->toBeTransmitted = 0; + currentState->receiveFileData = false; + beegfs_iov_iter_clear(¤tState->data); + + if(ops->prepare) + ops->prepare(context, currentState); + + return NetMessage_getMsgLength(netMessage); +} + +static void __commkit_readfile_printSocketDetails(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Sent request: node: %s; fileHandleID: %s; offset: %lld; size: %zu", + nodeAndType.buf, context->ioInfo->fileHandleID, + (long long)currentState->offset, currentState->totalSize); +} + +static ssize_t __commkit_readfile_receive(CommKitContext* context, FileOpState* currentState, + struct iov_iter *iter, size_t length, bool exact) +{ + ssize_t recvRes; + Socket* socket = currentState->base.socket; + + Config* cfg = App_getConfig(context->app); + + if(BEEGFS_SHOULD_FAIL(commkit_readfile_receive_timeout, 1) ) + recvRes = -ETIMEDOUT; + else + + if(exact) + { + recvRes = Socket_recvExactT(socket, iter, length, 0, cfg->connMsgLongTimeout); + } + else + { + recvRes = Socket_recvT(socket, iter, length, 0, cfg->connMsgLongTimeout); + } + + if(unlikely(recvRes < 0) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(currentState->base.node, &nodeAndType); + Logger_logFormatted(context->log, Log_SPAM, context->ops->logContext, + "Request details: receive from %s: %lld bytes (error %zi)", + nodeAndType.buf, (long long)length, recvRes); + } + + return recvRes; +} + +static int __commkit_readfile_recvdata_prefix(CommKitContext* context, FileOpState* currentState) +{ + ssize_t recvRes; + char dataLenBuf[sizeof(int64_t)]; // length info in fhgfs network byte order + size_t size = sizeof dataLenBuf; + int64_t lengthInfo; // length info in fhgfs host byte order + DeserializeCtx ctx = { dataLenBuf, sizeof(int64_t) }; + + struct kvec kvec = { + .iov_base = dataLenBuf, + .iov_len = size, + }; + struct iov_iter iter; + BEEGFS_IOV_ITER_KVEC(&iter, READ, &kvec, 1, size); + + recvRes = __commkit_readfile_receive(context, currentState, &iter, size, true); + if(recvRes < 0) + return recvRes; + if (recvRes == 0) + return -ECOMM; + + // got the length info response + Serialization_deserializeInt64(&ctx, &lengthInfo); + + if(lengthInfo <= 0) + { // end of file data transmission + if(unlikely(lengthInfo < 0) ) + { // error occurred + currentState->base.nodeResult = lengthInfo; + } + else + { // normal end of file data transmission + currentState->base.nodeResult = currentState->transmitted; + } + + return 0; + } + + // buffer overflow check + if(unlikely(currentState->transmitted + lengthInfo > currentState->totalSize) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(currentState->base.node, &nodeAndType); + Logger_logErrFormatted(context->log, context->ops->logContext, + "Bug: Received a lengthInfo that would overflow request from %s: %lld %zu %zu", + nodeAndType.buf, (long long)lengthInfo, + currentState->transmitted, currentState->totalSize); + + return -EREMOTEIO; + } + + // positive result => node is going to send some file data + currentState->toBeTransmitted += lengthInfo; + currentState->receiveFileData = true; + return 1; +} + +static int __commkit_readfile_recvdata(CommKitContext* context, struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + struct iov_iter *iter = ¤tState->data; + size_t missingLength = currentState->toBeTransmitted - currentState->transmitted; + ssize_t recvRes; + +#ifdef BEEGFS_NVFS + // + // If we are using the RDMA message, then the protocol is simply to wait for + // a reply which is either an error code ( < 0 ), no data ( = 0 ) or length of + // data already transferred ( > 0 ). + // + if (currentState->rdmap) + { + __commkit_readfile_recvdata_prefix(context, currentState); + if (currentState->toBeTransmitted > 0) + { + currentState->transmitted = currentState->toBeTransmitted; + currentState->base.nodeResult = currentState->transmitted; + } + currentState->receiveFileData = false; + return 0; + } +#endif // BEEGFS_NVFS + + if(!currentState->receiveFileData) + return __commkit_readfile_recvdata_prefix(context, currentState); + + if(iov_iter_count(iter) == 0) + { + ((struct ReadfileIterOps*) context->private)->nextIter(context, currentState); + BUG_ON(iov_iter_count(iter) == 0); + } + + // receive available dataPart + recvRes = __commkit_readfile_receive(context, currentState, iter, missingLength, false); + if(recvRes < 0) + return recvRes; + + currentState->transmitted += recvRes; + + if(currentState->toBeTransmitted == currentState->transmitted) + { // all of the data has been received => receive the next lengthInfo in the header stage + currentState->receiveFileData = false; + } + + return 1; +} + +static const struct CommKitContextOps readfileOps = { + .prepareHeader = __commkit_readfile_prepareHeader, + .recvData = __commkit_readfile_recvdata, + + .printSocketDetails = __commkit_readfile_printSocketDetails, + + .retryFlags = CK_RETRY_BUDDY_FALLBACK, + .logContext = "readfileV2 (communication)", +}; + +void FhgfsOpsCommKit_readfileV2bCommunicate(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, void (*nextIter)(CommKitContext*, FileOpState*), + void (*prepare)(CommKitContext*, FileOpState*)) +{ + struct ReadfileIterOps iops = { + .nextIter = nextIter, + .prepare = prepare, + }; + + FhgfsOpsCommkit_communicate(app, ioInfo, states, &readfileOps, &iops); +} + + + +static unsigned __commkit_writefile_prepareHeader(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + Config* cfg = App_getConfig(context->app); + Node* localNode = App_getLocalNode(context->app); + const NumNodeID localNodeNumID = Node_getNumID(localNode); + FileOpState* currentState = container_of(info, FileOpState, base); + struct WritefileIterOps* ops = context->private; + NetMessage *netMessage = NULL; + WriteLocalFileMsg writeMsg; + +#ifdef BEEGFS_NVFS + WriteLocalFileRDMAMsg writeRDMAMsg; + // + // Determine if we can RDMA the data. + // + currentState->rdmap = NULL; + if(context->gpudRc > 0) + { + struct FileOpVecState* vecState = container_of(currentState, struct FileOpVecState, base); + currentState->rdmap = RdmaInfo_mapWrite(&vecState->data, info->socket); + } + if (context->gpudRc < 0 || IS_ERR(currentState->rdmap)) + { + int st = currentState->rdmap != NULL? PTR_ERR(currentState->rdmap) : context->gpudRc; + info->state = CommKitState_CLEANUP; + info->nodeResult = (st == -ENOMEM)? -FhgfsOpsErr_OUTOFMEM : -FhgfsOpsErr_INVAL; + currentState->rdmap = NULL; + return 0; + } + + // prepare message + if (!currentState->rdmap) +#endif // BEEGFS_NVFS + { + WriteLocalFileMsg_initFromSession(&writeMsg, localNodeNumID, + context->ioInfo->fileHandleID, info->targetID, context->ioInfo->pathInfo, + context->ioInfo->accessFlags, currentState->offset, currentState->totalSize); + + netMessage = &writeMsg.netMessage; + } +#ifdef BEEGFS_NVFS + else + { + WriteLocalFileRDMAMsg_initFromSession(&writeRDMAMsg, localNodeNumID, + context->ioInfo->fileHandleID, info->targetID, context->ioInfo->pathInfo, + context->ioInfo->accessFlags, currentState->offset, currentState->totalSize, + currentState->rdmap); + + netMessage = &writeRDMAMsg.netMessage; + } +#endif // BEEGFS_NVFS + + NetMessage_setMsgHeaderTargetID(netMessage, info->selectedTargetID); + + if (Config_getQuotaEnabled(cfg) ) + { +#ifdef BEEGFS_NVFS + if (currentState->rdmap) + WriteLocalFileRDMAMsg_setUserdataForQuota(&writeRDMAMsg, context->ioInfo->userID, + context->ioInfo->groupID); + else +#endif + WriteLocalFileMsg_setUserdataForQuota(&writeMsg, context->ioInfo->userID, + context->ioInfo->groupID); + } + + if (currentState->firstWriteDoneForTarget && Config_getSysSessionChecksEnabled(cfg)) + NetMessage_addMsgHeaderFeatureFlag(netMessage, + WRITELOCALFILEMSG_FLAG_SESSION_CHECK); + + if(StripePattern_getPatternType(context->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { + NetMessage_addMsgHeaderFeatureFlag(netMessage, WRITELOCALFILEMSG_FLAG_BUDDYMIRROR); + + NetMessage_addMsgHeaderFeatureFlag(netMessage, + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD); + + if(info->useBuddyMirrorSecond) + NetMessage_addMsgHeaderFeatureFlag(netMessage, + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + if(App_getNetBenchModeEnabled(context->app) ) + NetMessage_addMsgHeaderFeatureFlag(netMessage, WRITELOCALFILEMSG_FLAG_DISABLE_IO); + + NetMessage_serialize(netMessage, info->headerBuffer, BEEGFS_COMMKIT_MSGBUF_SIZE); + + currentState->transmitted = 0; + currentState->toBeTransmitted = currentState->totalSize; + beegfs_iov_iter_clear(¤tState->data); + + if(ops->prepare) + ops->prepare(context, currentState); + + return NetMessage_getMsgLength(netMessage); +} + +static int __commkit_writefile_sendData(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + ssize_t sendRes = 0; + struct iov_iter* data = ¤tState->data; + size_t partLen = iov_iter_count(data); + +#ifdef BEEGFS_NVFS + // + // If this is an RDMA transfer, then we are immediately done. In this case, + // the server is RDMA reading the data and hence we have nothing to do. + // + if(currentState->rdmap) + return 0; +#endif // BEEGFS_NVFS + + if(currentState->toBeTransmitted == 0) + return 0; + + if(iov_iter_count(data) == 0) + { + ((struct WritefileIterOps*) context->private)->nextIter(context, currentState); + BUG_ON(iov_iter_count(data) == 0); + } + + if(BEEGFS_SHOULD_FAIL(commkit_writefile_senddata_timeout, 1) ) + { + sendRes = -ETIMEDOUT; + goto sendsDone; + } + + while(iov_iter_count(data)) + { + sendRes = info->socket->ops->sendto(info->socket, data, MSG_DONTWAIT, NULL); + if (sendRes < 0) + break; + + currentState->toBeTransmitted -= sendRes; + currentState->transmitted += sendRes; + } + +sendsDone: + if(sendRes < 0 && sendRes != -EAGAIN) + { + if(sendRes != -EFAULT) + { + // log errors the user probably hasn't caused + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "SocketError. ErrCode: %zd", sendRes); + Logger_logFormatted(context->log, Log_SPAM, context->ops->logContext, + "partLen/missing: %zd/%zd", partLen, iov_iter_count(data)); + } + + return sendRes; + } + + if(currentState->toBeTransmitted == 0) + return 0; + + return 1; +} + +static void __commkit_writefile_printSendDataDetails(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + + Logger_logFormatted(context->log, Log_SPAM, context->ops->logContext, + "Request details: transmitted/toBeTransmitted: %lld/%lld", + (long long)currentState->transmitted, (long long)currentState->toBeTransmitted); +} + +static void __commkit_writefile_printSocketDetails(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + NodeString nodeAndType; + Node_copyAliasWithTypeStr(info->node, &nodeAndType); + Logger_logFormatted(context->log, Log_DEBUG, context->ops->logContext, + "Sent request: node: %s; fileHandleID: %s; offset: %lld; size: %lld", + nodeAndType.buf, context->ioInfo->fileHandleID, + (long long)currentState->offset, (long long)currentState->toBeTransmitted); +} + +static int __commkit_writefile_recvHeader(CommKitContext* context, struct CommKitTargetInfo* info) +{ + FileOpState* currentState = container_of(info, FileOpState, base); + bool deserRes; + int64_t writeRespValue; + WriteLocalFileRespMsg writeRespMsg; + unsigned expectedType = 0; +#ifdef BEEGFS_NVFS + WriteLocalFileRDMARespMsg writeRDMARespMsg; + + // got response => deserialize it + if(!currentState->rdmap) +#endif // BEEGFS_NVFS + { + WriteLocalFileRespMsg_init(&writeRespMsg); + + deserRes = NetMessageFactory_deserializeFromBuf(context->app, currentState->base.headerBuffer, + info->headerSize, (NetMessage*)&writeRespMsg, NETMSGTYPE_WriteLocalFileResp); + expectedType = NETMSGTYPE_WriteLocalFileResp; + } +#ifdef BEEGFS_NVFS + else + { + WriteLocalFileRDMARespMsg_init(&writeRDMARespMsg); + + deserRes = NetMessageFactory_deserializeFromBuf(context->app, currentState->base.headerBuffer, + info->headerSize, (NetMessage*)&writeRDMARespMsg, NETMSGTYPE_WriteLocalFileRDMAResp); + expectedType = NETMSGTYPE_WriteLocalFileRDMAResp; + } +#endif // BEEGFS_NVFS + + if(unlikely(!deserRes) ) + { // response invalid + NodeString nodeAndType; + Node_copyAliasWithTypeStr(currentState->base.node, &nodeAndType); + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Received invalid response from %s. Expected type: %d. Disconnecting: %s", + nodeAndType.buf, expectedType, + Socket_getPeername(currentState->base.socket) ); + + return -FhgfsOpsErr_COMMUNICATION; + } + +#ifdef BEEGFS_NVFS + if(!currentState->rdmap) +#endif + { + writeRespValue = WriteLocalFileRespMsg_getValue(&writeRespMsg); + } +#ifdef BEEGFS_NVFS + else + { + writeRespValue = WriteLocalFileRDMARespMsg_getValue(&writeRDMARespMsg); + } +#endif + + if(unlikely(writeRespValue == -FhgfsOpsErr_COMMUNICATION) && !context->connFailedLogged) + { // server was unable to communicate with another server + NodeString nodeAndType; + context->connFailedLogged = true; + Node_copyAliasWithTypeStr(currentState->base.node, &nodeAndType); + Logger_logFormatted(context->log, Log_WARNING, context->ops->logContext, + "Server reported indirect communication error: %s. targetID: %hu", + nodeAndType.buf, currentState->base.targetID); + } + + currentState->base.nodeResult = writeRespValue; + return 0; +} + +static const struct CommKitContextOps writefileOps = { + .prepareHeader = __commkit_writefile_prepareHeader, + .sendData = __commkit_writefile_sendData, + .recvHeader = __commkit_writefile_recvHeader, + + .printSendDataDetails = __commkit_writefile_printSendDataDetails, + .printSocketDetails = __commkit_writefile_printSocketDetails, + + .retryFlags = CK_RETRY_LOOP_EAGAIN, + .logContext = "writefile (communication)", +}; + +void FhgfsOpsCommKit_writefileV2bCommunicate(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, void (*nextIter)(CommKitContext*, FileOpState*), + void (*prepare)(CommKitContext*, FileOpState*)) +{ + struct WritefileIterOps iops = { + .nextIter = nextIter, + .prepare = prepare, + }; + + FhgfsOpsCommkit_communicate(app, ioInfo, states, &writefileOps, &iops); +} + + + +static enum CKTargetBadAction __commkit_fsync_selectedTargetBad(CommKitContext* context, + struct CommKitTargetInfo* info, const CombinedTargetState* targetState) +{ + // we must not try to fsync a secondary that is currently offline, but we should fsync + // secondaries that are needs-resync (because a resync might be running, but our file was already + // resynced). we will also try to fsync a secondary that is poffline, just in case it comes back + // to online. + // bad targets should be silently ignored just like offline targets because they might do + // who-knows-what with our request and produce spurious errors. + if (StripePattern_getPatternType(context->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror && + info->useBuddyMirrorSecond) + { + if (targetState->reachabilityState == TargetReachabilityState_OFFLINE || + targetState->consistencyState == TargetConsistencyState_BAD) + info->nodeResult = -FhgfsOpsErr_SUCCESS; + else if (targetState->consistencyState == TargetConsistencyState_NEEDS_RESYNC) + return CK_CONTINUE_TARGET; + } + + return CK_SKIP_TARGET; +} + +static unsigned __commkit_fsync_prepareHeader(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + struct FsyncContext* fctx = context->private; + struct FsyncState* state = container_of(info, struct FsyncState, base); + + Config* cfg = App_getConfig(context->app); + Node* localNode = App_getLocalNode(context->app); + const NumNodeID localNodeNumID = Node_getNumID(localNode); + + FSyncLocalFileMsg requestMsg; + + if(!fctx->forceRemoteFlush && !fctx->checkSession && !fctx->doSyncOnClose) + { + info->nodeResult = -FhgfsOpsErr_SUCCESS; + return 0; + } + + // prepare request message + FSyncLocalFileMsg_initFromSession(&requestMsg, localNodeNumID, context->ioInfo->fileHandleID, + info->targetID); + + NetMessage_setMsgHeaderUserID(&requestMsg.netMessage, fctx->userID); + NetMessage_setMsgHeaderTargetID(&requestMsg.netMessage, info->selectedTargetID); + + if(StripePattern_getPatternType(context->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { + NetMessage_addMsgHeaderFeatureFlag(&requestMsg.netMessage, + FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(info->useBuddyMirrorSecond) + NetMessage_addMsgHeaderFeatureFlag(&requestMsg.netMessage, + FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + // required version is checked in FSyncChunkFileWork_process and if session check is not + // supported by the server, the value of this->checkSession was set to false + if(fctx->checkSession && state->firstWriteDoneForTarget + && Config_getSysSessionChecksEnabled(cfg)) + NetMessage_addMsgHeaderFeatureFlag(&requestMsg.netMessage, + FSYNCLOCALFILEMSG_FLAG_SESSION_CHECK); + + // required version is checked in FSyncChunkFileWork_process and if syncOnClose is not + // supported by the server, the value of this->doSyncOnClose was set to false + if(!fctx->forceRemoteFlush && !fctx->doSyncOnClose) + NetMessage_addMsgHeaderFeatureFlag(&requestMsg.netMessage, + FSYNCLOCALFILEMSG_FLAG_NO_SYNC); + + NetMessage_serialize(&requestMsg.netMessage, info->headerBuffer, BEEGFS_COMMKIT_MSGBUF_SIZE); + + return NetMessage_getMsgLength(&requestMsg.netMessage); +} + +static int __commkit_fsync_recvHeader(CommKitContext* context, struct CommKitTargetInfo* info) +{ + FSyncLocalFileRespMsg respMsg; + bool parseRes; + + FSyncLocalFileRespMsg_init(&respMsg); + + parseRes = NetMessageFactory_deserializeFromBuf(context->app, info->headerBuffer, + info->headerSize, &respMsg.simpleInt64Msg.netMessage, NETMSGTYPE_FSyncLocalFileResp); + if(!parseRes) + return __commkit_message_genericResponse(context, info, NETMSGTYPE_FSyncLocalFile); + + info->nodeResult = -FSyncLocalFileRespMsg_getValue(&respMsg); + return 0; +} + +static const struct CommKitContextOps fsyncOps = { + .selectedTargetBad = __commkit_fsync_selectedTargetBad, + .prepareHeader = __commkit_fsync_prepareHeader, + .recvHeader = __commkit_fsync_recvHeader, + + .retryFlags = CK_RETRY_LOOP_EAGAIN, + .logContext = "fsync (communication)", +}; + +void FhgfsOpsCommKit_fsyncCommunicate(App* app, RemotingIOInfo* ioInfo, + struct FsyncContext* context) +{ + FhgfsOpsCommkit_communicate(app, ioInfo, &context->states, &fsyncOps, context); +} + + + +static unsigned __commkit_statstorage_prepareHeader(CommKitContext* context, + struct CommKitTargetInfo* info) +{ + StatStoragePathMsg msg; + + StatStoragePathMsg_initFromTarget(&msg, info->targetID); + NetMessage_setMsgHeaderTargetID(&msg.simpleUInt16Msg.netMessage, info->selectedTargetID); + + NetMessage_serialize(&msg.simpleUInt16Msg.netMessage, info->headerBuffer, + BEEGFS_COMMKIT_MSGBUF_SIZE); + + return NetMessage_getMsgLength(&msg.simpleUInt16Msg.netMessage); +} + +static int __commkit_statstorage_recvHeader(CommKitContext* context, struct CommKitTargetInfo* info) +{ + struct StatStorageState* state = container_of(info, struct StatStorageState, base); + + StatStoragePathRespMsg respMsg; + bool parseRes; + + StatStoragePathRespMsg_init(&respMsg); + + parseRes = NetMessageFactory_deserializeFromBuf(context->app, info->headerBuffer, + info->headerSize, &respMsg.netMessage, NETMSGTYPE_StatStoragePathResp); + if(!parseRes) + return __commkit_message_genericResponse(context, info, NETMSGTYPE_StatStoragePath); + + state->totalFree = respMsg.sizeFree; + state->totalSize = respMsg.sizeTotal; + info->nodeResult = -respMsg.result; + return 0; +} + +static const struct CommKitContextOps statstorageOps = { + .prepareHeader = __commkit_statstorage_prepareHeader, + .recvHeader = __commkit_statstorage_recvHeader, + + .retryFlags = CK_RETRY_LOOP_EAGAIN, + .logContext = "stat storage (communication)", +}; + +void FhgfsOpsCommKit_statStorageCommunicate(App* app, struct list_head* targets) +{ + FhgfsOpsCommkit_communicate(app, NULL, targets, &statstorageOps, NULL); +} diff --git a/client_module/source/net/filesystem/FhgfsOpsCommKit.h b/client_module/source/net/filesystem/FhgfsOpsCommKit.h new file mode 100644 index 0000000..d9280d4 --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsCommKit.h @@ -0,0 +1,146 @@ +#ifndef FHGFSOPSCOMMKIT_H_ +#define FHGFSOPSCOMMKIT_H_ + +#include +#ifdef BEEGFS_NVFS +#include +#endif +#include +#include "FhgfsOpsCommKitCommon.h" + +struct FileOpState; +typedef struct FileOpState FileOpState; + +struct FsyncContext; + + +bool FhgfsOpsCommKit_initEmergencyPools(void); +void FhgfsOpsCommKit_releaseEmergencyPools(void); + + +extern void FhgfsOpsCommkit_communicate(App* app, RemotingIOInfo* ioInfo, + struct list_head* targetInfos, const struct CommKitContextOps* ops, void* private); + + +extern void FhgfsOpsCommKit_readfileV2bCommunicate(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, void (*nextIter)(CommKitContext*, FileOpState*), + void (*prepare)(CommKitContext*, FileOpState*)); + +extern void FhgfsOpsCommKit_writefileV2bCommunicate(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, void (*nextIter)(CommKitContext*, FileOpState*), + void (*prepare)(CommKitContext*, FileOpState*)); + +extern void FhgfsOpsCommKit_fsyncCommunicate(App* app, RemotingIOInfo* ioInfo, + struct FsyncContext* context); + +extern void FhgfsOpsCommKit_statStorageCommunicate(App* app, struct list_head* targets); + + + +extern void FhgfsOpsCommKit_initFileOpState(FileOpState* state, loff_t offset, size_t size, + uint16_t targetID); + + +enum CommKitState +{ + CommKitState_PREPARE, + CommKitState_SENDHEADER, + CommKitState_SENDDATA, + CommKitState_RECVHEADER, + CommKitState_RECVDATA, + CommKitState_SOCKETINVALIDATE, + CommKitState_CLEANUP, + CommKitState_RETRYWAIT, + CommKitState_DONE, +}; + +struct CommKitTargetInfo +{ + struct list_head targetInfoList; + + // (set to _PREPARE in the beginning, assigned by the state-creator) + enum CommKitState state; // the current stage of the individual communication process + + // used by GenericResponse handler + struct { + unsigned peerTryAgain:1; + unsigned indirectCommError:1; + unsigned indirectCommErrorNoRetry:1; + } logged; + + // assigned by the state-creator + char* headerBuffer; // for serialization + uint16_t targetID; + uint16_t selectedTargetID; // either targetID or the buddy, if useBuddyMirrorSecond + bool useBuddyMirrorSecond; // if buddy mirroring, this msg goes to secondary + + // set by _PREPARE handler + Node* node; // target-node reference + Socket* socket; // target-node connection + unsigned headerSize; + + // error if negative, other set by specialized actions + int64_t nodeResult; +}; + + + +struct FileOpState +{ + struct CommKitTargetInfo base; + + // data for spefic modes (will be assigned by the corresponding modes) + size_t transmitted; // how much data has been transmitted already + size_t toBeTransmitted; // how much data has to be transmitted + size_t totalSize; // how much data was requested + + // data for all modes + + // (assigned by the state-creator) + struct iov_iter data; + loff_t offset; // target-node local offset + + bool firstWriteDoneForTarget; /* true if a chunk was previously written to this target in + this session; used for the session check */ + bool receiveFileData; /* if false, receive the int64 fragment length, else the fragment */ + + // result data + int64_t expectedNodeResult; // the amount of data that we wanted to read +#ifdef BEEGFS_NVFS + RdmaInfo* rdmap; +#endif +}; + + +struct FsyncContext { + unsigned userID; + bool forceRemoteFlush; + bool checkSession; + bool doSyncOnClose; + + struct list_head states; +}; + +struct FsyncState { + struct CommKitTargetInfo base; + + bool firstWriteDoneForTarget; +}; + +extern void FhgfsOpsCommKit_initFsyncState(struct FsyncContext* context, struct FsyncState* state, + uint16_t targetID); + + + +struct StatStorageState +{ + struct CommKitTargetInfo base; + + int64_t totalSize; + int64_t totalFree; +}; + +extern void FhgfsOpsCommKit_initStatStorageState(struct list_head* states, + struct StatStorageState* state, uint16_t targetID); + +#endif /*FHGFSOPSCOMMKIT_H_*/ diff --git a/client_module/source/net/filesystem/FhgfsOpsCommKitCommon.h b/client_module/source/net/filesystem/FhgfsOpsCommKitCommon.h new file mode 100644 index 0000000..239debe --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsCommKitCommon.h @@ -0,0 +1,187 @@ +#ifndef FHGFSOPSCOMMKITCOMMON_H_ +#define FHGFSOPSCOMMKITCOMMON_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + + +#define BEEGFS_COMMKIT_MSGBUF_SIZE 4096 + +#define COMMKIT_DEBUG_READ_SEND (1 << 0) +#define COMMKIT_DEBUG_READ_HEADER (1 << 1) +#define COMMKIT_DEBUG_RECV_DATA (1 << 2) +#define COMMKIT_DEBUG_WRITE_HEADER (1 << 3) +#define COMMKIT_DEBUG_WRITE_POLL (1 << 4) +#define COMMKIT_DEBUG_WRITE_SEND (1 << 5) +#define COMMKIT_DEBUG_WRITE_RECV (1 << 6) + +// #define BEEGFS_COMMKIT_DEBUG 0b010 + +#ifndef BEEGFS_COMMKIT_DEBUG +#define BEEGFS_COMMKIT_DEBUG 0 +#endif + +#ifdef BEEGFS_COMMKIT_DEBUG +#define CommKitErrorInjectRate 101 // fail a send/receive req every X jiffies +#endif + + + +struct CommKitContext; +typedef struct CommKitContext CommKitContext; + + + +static inline void __FhgfsOpsCommKitCommon_pollStateSocks(CommKitContext* context, + size_t numStates); +static inline void __FhgfsOpsCommKitCommon_handlePollError(CommKitContext* context, + int pollRes); + + +struct CommKitTargetInfo; + +enum +{ + CK_RETRY_BUDDY_FALLBACK = 1 << 0, + CK_RETRY_LOOP_EAGAIN = 1 << 1, +}; + +enum CKTargetBadAction +{ + CK_SKIP_TARGET = 0, + CK_CONTINUE_TARGET = 1, +}; + + +struct CommKitContextOps +{ + enum CKTargetBadAction (*selectedTargetBad)(CommKitContext*, struct CommKitTargetInfo*, + const CombinedTargetState*); + + unsigned (*prepareHeader)(CommKitContext*, struct CommKitTargetInfo*); + + // returns >0 for successful send, 0 to advance to next state, or negative error code + int (*sendData)(CommKitContext*, struct CommKitTargetInfo*); + + // return >= for success, 0 to advance, or negative beegfs error code + int (*recvHeader)(CommKitContext*, struct CommKitTargetInfo*); + // return >= for success, 0 to advance, or negative beegfs error code + int (*recvData)(CommKitContext*, struct CommKitTargetInfo*); + + void (*printSendDataDetails)(CommKitContext*, struct CommKitTargetInfo*); + void (*printSocketDetails)(CommKitContext*, struct CommKitTargetInfo*); + + int retryFlags; + const char* logContext; +}; + + +/** + * Additional data that is required or useful for all the states. + * (This is shared states data.) + * + * invariant: (numRetryWaiters + numDone + numUnconnectable + numPollSocks) <= numStates + */ +struct CommKitContext +{ + const struct CommKitContextOps* ops; + + App* app; + Logger* log; + void* private; + + RemotingIOInfo* ioInfo; + + struct list_head* targetInfoList; + + unsigned numRetryWaiters; // number of states with a communication error + unsigned numDone; // number of finished states + + unsigned numAcquiredConns; // number of acquired conns to decide waiting or not (avoids deadlock) + unsigned numUnconnectable; // states that didn't get a conn this round (reset to 0 in each round) + unsigned numBufferless; // states that couldn't acquire a header buffer + + bool pollTimedOut; + bool pollTimeoutLogged; + bool connFailedLogged; + + PollState pollState; + unsigned numPollSocks; + + unsigned currentRetryNum; // number of used up retries (in case of comm errors) + unsigned maxNumRetries; // 0 if infinite number of retries enabled +#ifdef BEEGFS_NVFS + int gpudRc; +#endif +}; + + + +/** + * Poll the state socks that need to be polled. + * This will call _handlePollError() if something goes wrong with the poll(), e.g. timeout. + * + * Note: Caller must have checked that there actually are socks that need polling + * (context.numPollSocks>0) before calling this. + */ +void __FhgfsOpsCommKitCommon_pollStateSocks(CommKitContext* context, size_t numStates) +{ + int pollRes; + + Config* cfg = App_getConfig(context->app); + + size_t numWaiters = context->numPollSocks + context->numRetryWaiters + + context->numDone + context->numUnconnectable + context->numBufferless; + + /* if not all the states are polling or done, some states must be ready to do something + immediately => set timeout to 0 (non-blocking) */ + /* note: be very careful with timeoutMS==0, because that means we don't release the CPU, + so we need to ensure that timeoutMS==0 is no permanent condition. */ + int timeoutMS = (numWaiters < numStates) ? 0 : cfg->connMsgLongTimeout; + BEEGFS_BUG_ON_DEBUG(numWaiters > numStates, "numWaiters > numStates should never happen"); + + pollRes = SocketTk_poll(&context->pollState, timeoutMS); + if(unlikely( (timeoutMS && pollRes <= 0) || (pollRes < 0) ) ) + __FhgfsOpsCommKitCommon_handlePollError(context, pollRes); +} + +void __FhgfsOpsCommKitCommon_handlePollError(CommKitContext* context, int pollRes) +{ + // note: notifies the waiting sockets via context.pollTimedOut (they do not care whether + // it was a timeout or an error) + + if(!context->pollTimeoutLogged) + { + if(!pollRes) + { // no incoming data => timout or interrupted + if (fatal_signal_pending(current)) + { // interrupted + Logger_logFormatted(context->log, 3, context->ops->logContext, + "Communication (poll() ) interrupted by signal."); + } + else + { // timout + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication (poll() ) timeout for %u sockets.", context->numPollSocks); + } + } + else + { // error + Logger_logErrFormatted(context->log, context->ops->logContext, + "Communication (poll() ) error for %u sockets. ErrCode: %d", + context->numPollSocks, pollRes); + } + } + + context->pollTimeoutLogged = true; + context->pollTimedOut = true; +} + + +#endif /*FHGFSOPSCOMMKITCOMMON_H_*/ diff --git a/client_module/source/net/filesystem/FhgfsOpsCommKitVec.c b/client_module/source/net/filesystem/FhgfsOpsCommKitVec.c new file mode 100644 index 0000000..b28800b --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsCommKitVec.c @@ -0,0 +1,1193 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "FhgfsOpsCommKitVec.h" + + + +static void __FhgfsOpsCommKitVec_readfileStagePREPARE(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_readfileStageRECVHEADER(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_readfileStageRECVDATA(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_handleReadError(CommKitVecHelper* commHelper, FhgfsCommKitVec* comm, + FhgfsPage* fhgfsPage, ssize_t recvRes, size_t missingPgLen); +static void __FhgfsOpsCommKitVec_readfileStageSOCKETEXCEPTION(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_readfileStageCLEANUP(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_readfileStageHandlePages(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); + + + +static void __FhgfsOpsCommKitVec_writefileStagePREPARE(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageSENDHEADER(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageSENDDATA(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageRECV(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageSOCKETEXCEPTION(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageCLEANUP(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static void __FhgfsOpsCommKitVec_writefileStageHandlePages(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); + +static int64_t __FhgfsOpsCommKitVec_writefileCommunicate(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); +static int64_t __FhgfsOpsCommKitVec_readfileCommunicate(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm); + + + + +/** + * Prepare a read request + * + * @return false on error (means 'break' for the surrounding switch statement) + */ +void __FhgfsOpsCommKitVec_readfileStagePREPARE(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + Config* cfg = App_getConfig(commHelper->app); + TargetMapper* targetMapper = App_getTargetMapper(commHelper->app); + NodeStoreEx* storageNodes = App_getStorageNodes(commHelper->app); + Node* localNode = App_getLocalNode(commHelper->app); + const NumNodeID localNodeNumID = Node_getNumID(localNode); + + FhgfsOpsErr resolveErr; + NodeConnPool* connPool; + ReadLocalFileV2Msg readMsg; + ssize_t sendRes; + bool needCleanup = false; + bool isSocketException = false; + + uint16_t nodeReferenceTargetID = comm->targetID; /* to avoid overriding original targetID + in case of buddy-mirroring, because we need the mirror group ID for the msg and for retries */ + + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); + // nodeAndType is not set until later after calling NodeStoreEx_referenceNodeByTargetID. + // Don't use it before then! + NodeString nodeAndType; + + comm->read.reqLen = FhgfsOpsCommKitVec_getRemainingDataSize(comm); + + comm->sock = NULL; + comm->nodeResult = -FhgfsOpsErr_COMMUNICATION; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + // select the right targetID + + if(StripePattern_getPatternType(commHelper->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = App_getStorageBuddyGroupMapper(commHelper->app); + + nodeReferenceTargetID = comm->useBuddyMirrorSecond ? + MirrorBuddyGroupMapper_getSecondaryTargetID(mirrorBuddies, comm->targetID) : + MirrorBuddyGroupMapper_getPrimaryTargetID(mirrorBuddies, comm->targetID); + + // note: only log message here, error handling below through invalid node reference + if(unlikely(!nodeReferenceTargetID) ) + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Invalid mirror buddy group ID: %hu", comm->targetID); + } + + // get the target-node reference + comm->node = NodeStoreEx_referenceNodeByTargetID(storageNodes, + nodeReferenceTargetID, targetMapper, &resolveErr); + if(unlikely(!comm->node) ) + { // unable to resolve targetID + comm->nodeResult = -resolveErr; + + needCleanup = true; + goto outErr; + } + + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + connPool = Node_getConnPool(comm->node); + + // connect + comm->sock = NodeConnPool_acquireStreamSocketEx(connPool, true, NULL); + if(!comm->sock) + { // connection error + if (fatal_signal_pending(current)) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, + commHelper->logContext, "Connect to server canceled by pending signal: %s", + nodeAndType.buf); + comm->nodeResult = FhgfsOpsErr_INTERRUPTED; + } + else + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, + commHelper->logContext, "Unable to connect to server: %s", + nodeAndType.buf); + } + + needCleanup = true; + goto outErr; + } + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "Acquire read node: %s offset: %lli size: %zu", + nodeAndType.buf, offset, comm->read.reqLen); + + // prepare message + ReadLocalFileV2Msg_initFromSession(&readMsg, localNodeNumID, + commHelper->ioInfo->fileHandleID, comm->targetID, commHelper->ioInfo->pathInfo, + commHelper->ioInfo->accessFlags, offset, comm->read.reqLen); + + NetMessage_setMsgHeaderTargetID( (NetMessage*)&readMsg, nodeReferenceTargetID); + + if (comm->firstWriteDoneForTarget && Config_getSysSessionChecksEnabled(cfg)) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&readMsg, + READLOCALFILEMSG_FLAG_SESSION_CHECK); + + if(StripePattern_getPatternType(commHelper->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&readMsg, READLOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(comm->useBuddyMirrorSecond) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&readMsg, + READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + if(App_getNetBenchModeEnabled(commHelper->app) ) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&readMsg, READLOCALFILEMSG_FLAG_DISABLE_IO); + + NetMessage_serialize( (NetMessage*)&readMsg, comm->msgBuf, BEEGFS_COMMKIT_MSGBUF_SIZE); + + comm->hdrLen = NetMessage_getMsgLength( (NetMessage*)&readMsg); + + comm->nodeResult = 0; // ready to read, so set this variable to 0 + + sendRes = Socket_send_kernel(comm->sock, comm->msgBuf, comm->hdrLen, 0); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_READ_SEND ) + if (sendRes > 0 && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) sending data."); + sendRes = -ETIMEDOUT; + } +#endif + + if(unlikely(sendRes <= 0) ) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, commHelper->logContext, + "Failed to send message to %s: %s", nodeAndType.buf, + Socket_getPeername(comm->sock) ); + + isSocketException = true; + goto outErr; + } + + __FhgfsOpsCommKitVec_readfileStageRECVHEADER(commHelper, comm); + return; + +outErr: + if (needCleanup) + __FhgfsOpsCommKitVec_readfileStageCLEANUP(commHelper, comm); + + if (isSocketException) + __FhgfsOpsCommKitVec_readfileStageRECVHEADER(commHelper, comm); +} + +/** + * Receive how many data the server is going to send next, this is also called after each + * *complete* read request + */ +void __FhgfsOpsCommKitVec_readfileStageRECVHEADER(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + ssize_t recvRes; + char dataLenBuf[sizeof(int64_t)]; // length info in fhgfs network byte order + int64_t lengthInfo; // length info in fhgfs host byte order + DeserializeCtx ctx = { dataLenBuf, sizeof(int64_t) }; + + bool isSocketException = false; + bool needCleanup = false; + + Config* cfg = App_getConfig(commHelper->app); + NodeString alias; + Node_copyAlias(comm->node, &alias); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + recvRes = Socket_recvExactT_kernel(comm->sock, &dataLenBuf, sizeof(int64_t), 0, cfg->connMsgLongTimeout); + + if(unlikely(recvRes <= 0) ) + { // error + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, commHelper->logContext, + "Failed to receive length info from %s: %s", + nodeAndType.buf, Socket_getPeername(comm->sock) ); + + isSocketException = true; + goto outErr; + } + + // got the length info response + Serialization_deserializeInt64(&ctx, &lengthInfo); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, commHelper->logContext, + "Received lengthInfo from %s: %lld", alias.buf, (long long)lengthInfo); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_READ_HEADER) + if (recvRes > 0 && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) receiving data."); + + isSocketException = true; + goto outErr; + } +#endif + + if(lengthInfo <= 0) + { // end of file data transmission + + if(unlikely(lengthInfo < 0) ) + { + comm->nodeResult = lengthInfo; // error occurred + + isSocketException = true; + } + else + { // EOF + needCleanup = true; + } + + goto outErr; + } + + if (unlikely(lengthInfo > comm->read.reqLen) ) + { + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Bug: Server wants to send more than we requested (%zu vs. %llu)", + comm->read.reqLen, lengthInfo); + + isSocketException = true; + goto outErr; + } + + // positive result => node is going to send some file data + comm->read.serverSize = lengthInfo; + + __FhgfsOpsCommKitVec_readfileStageRECVDATA(commHelper, comm); + return; + +outErr: + if (needCleanup) + __FhgfsOpsCommKitVec_readfileStageCLEANUP(commHelper, comm); + + if (isSocketException) + __FhgfsOpsCommKitVec_readfileStageSOCKETEXCEPTION(commHelper, comm); +} + + + +/** + * Read the data from the server + */ +void __FhgfsOpsCommKitVec_readfileStageRECVDATA(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + size_t receiveSum = 0; // sum of recvRes + FhgfsPage *fhgfsPage; + size_t pageLen; // length of a page + ssize_t recvRes; + + Config* cfg = App_getConfig(commHelper->app); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + do + { // walk over all pages of this state up to the size the server promise to give us + FhgfsChunkPageVec* pageVec = comm->pageVec; + void* pageDataPtr; + size_t requestLength; + +#ifdef BEEGFS_DEBUG + size_t vecIdx = FhgfsChunkPageVec_getCurrentListVecIterIdx(pageVec); + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); +#endif + + fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(pageVec); + + if (unlikely(!fhgfsPage) ) + { + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "BUG: Iterator does not have more pages!" ); + + comm->nodeResult = -FhgfsOpsErr_INTERNAL; + goto outSocketException; + } + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "vecSize: %zu offset: %lld; Vec-Idx: %zu receivedData: %ld", + FhgfsChunkPageVec_getDataSize(pageVec), offset, + vecIdx, receiveSum); + + pageLen = fhgfsPage->length; + pageDataPtr = fhgfsPage->data; + + requestLength = MIN(pageLen, comm->read.serverSize - receiveSum); + + // receive available dataPart + recvRes = Socket_recvExactT_kernel(comm->sock, pageDataPtr, requestLength, 0, cfg->connMsgLongTimeout); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, + "requested: %lld; received: %lld", + (long long)requestLength, (long long)recvRes); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_RECV_DATA) + if (recvRes > 0 && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) receiving data."); + recvRes = -ETIMEDOUT; + } +#endif + + if(unlikely(recvRes <= 0) ) + { // something went wrong + goto outReadErr; + } + + receiveSum += recvRes; + + if (unlikely(requestLength < pageLen && receiveSum < comm->read.serverSize) ) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, + commHelper->logContext, "Bug/Warning: requestLength < pageLength (%zu, %zu)", + requestLength, pageLen); + + goto outReadErr; + } + + comm->numSuccessPages++; + + } while (receiveSum < comm->read.serverSize); + + // all data for this state and round received, add it up + comm->nodeResult += receiveSum; + + // all of the data has been received => receive the next lengthInfo in the header stage + comm->doHeader = true; + + return; + +outSocketException: + __FhgfsOpsCommKitVec_readfileStageSOCKETEXCEPTION(commHelper, comm); + return; + +outReadErr: + __FhgfsOpsCommKitVec_handleReadError(commHelper, comm, fhgfsPage, recvRes, pageLen); + return; +} + +/** + * Take the right action based on the error type + */ +void __FhgfsOpsCommKitVec_handleReadError(CommKitVecHelper* commHelper, FhgfsCommKitVec* comm, + FhgfsPage* fhgfsPage, ssize_t recvRes, size_t missingPgLen) +{ + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + if(recvRes == -EFAULT) + { // bad buffer address given + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, commHelper->logContext, + "Bad buffer address"); + + comm->nodeResult = -FhgfsOpsErr_ADDRESSFAULT; + } + else + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + if (recvRes > 0) + { /* requestLength < pageLength && comm->read.receivedLength < comm->read.serverSize */ + comm->nodeResult = -FhgfsOpsErr_INTERNAL; + } + if(recvRes == -ETIMEDOUT) + { // timeout + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Communication timeout in RECVDATA stage. Node: %s", + nodeAndType.buf); + comm->nodeResult = -FhgfsOpsErr_COMMUNICATION; + } + else + { // error + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Communication error in RECVDATA stage. Node: %s (recv result: %lld)", + nodeAndType.buf, (long long)recvRes); + + comm->nodeResult = -FhgfsOpsErr_INTERNAL; + } + + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_SPAM, commHelper->logContext, + "Request details: missingLength of %s: %lld", nodeAndType.buf, + (long long)missingPgLen); + } + + __FhgfsOpsCommKitVec_readfileStageSOCKETEXCEPTION(commHelper, comm); +} + + +/** + * Socket exception happened + */ +void __FhgfsOpsCommKitVec_readfileStageSOCKETEXCEPTION(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + const char* logCommErrTxt = "Communication error"; + + size_t remainingDataSize = FhgfsOpsCommKitVec_getRemainingDataSize(comm); + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); + + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, commHelper->logContext, + "%s: Sent request: node: %s; fileHandleID: %s; offset: %lld; size: %zd", + logCommErrTxt, nodeAndType.buf, commHelper->ioInfo->fileHandleID, + (long long) offset, remainingDataSize ); + + NodeConnPool_invalidateStreamSocket(Node_getConnPool(comm->node), comm->sock); + comm->sock = NULL; + + if (comm->nodeResult >= 0) + { // make sure not to overwrite already assigned error codes + comm->nodeResult = -FhgfsOpsErr_COMMUNICATION; + } + + __FhgfsOpsCommKitVec_readfileStageCLEANUP(commHelper, comm); +} + + +/** + * Clean up the current state after an error or too few data. + */ +void __FhgfsOpsCommKitVec_readfileStageCLEANUP(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + // actual clean-up + + if (comm->sock) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "Release socket, read node: %s", nodeAndType.buf); + + NodeConnPool_releaseStreamSocket(Node_getConnPool(comm->node), comm->sock); + comm->sock = NULL; + } + + if(likely(comm->node) ) + Node_put(comm->node); + + // prepare next stage + +} + +/** + * Handle all remaining pages of this state. + * Note: Must be always called - on read success and read-error! + */ +void __FhgfsOpsCommKitVec_readfileStageHandlePages(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + // const char* logContext = "CommKitVec_readfileStageHandlePages"; + FhgfsChunkPageVec* fhgfsPageVec = comm->pageVec; + struct inode* inode = FhgfsChunkPageVec_getInode(fhgfsPageVec); + + bool needInodeRefresh = true; + FhgfsPage* fhgfsPage; + size_t pgCount = 0; + int64_t readRes = comm->nodeResult; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, + "nodeResult: %lli (%s)", comm->nodeResult, + (comm->nodeResult < 0) ? FhgfsOpsErr_toErrString(-comm->nodeResult): + FhgfsOpsErr_toErrString(FhgfsOpsErr_SUCCESS)); + + if (unlikely(comm->nodeResult == -FhgfsOpsErr_COMMUNICATION || + comm->nodeResult == -FhgfsOpsErr_AGAIN)) + { + // don't do anything, IO can be possibly re-tried + goto out; + } + + FhgfsChunkPageVec_resetIterator(fhgfsPageVec); + + // walk over all successfully read pages + while (pgCount < comm->numSuccessPages && readRes > 0) + { + fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(fhgfsPageVec); + if (!fhgfsPage) + break; + + FhgfsOpsPages_endReadPage(commHelper->log, inode, fhgfsPage, readRes); + + readRes -= PAGE_SIZE; + pgCount++; + } + + if (unlikely(readRes > 0)) { + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "%s: Bug: readRes too large!", __func__); + comm->nodeResult = -FhgfsOpsErr_INTERNAL; + } + + // walk over the remaining pages + while (1) + { + bool isShortRead; + int pageReadRes = (comm->nodeResult >= 0) ? comm->nodeResult : -1; + + fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(fhgfsPageVec); + if (!fhgfsPage) + break; + + if (pageReadRes >= 0) + { + isShortRead = FhgfsOpsPages_isShortRead(inode, FhgfsPage_getPageIndex(fhgfsPage), + needInodeRefresh); + + needInodeRefresh = false; // a single inode refresh is sufficient + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "nodeResult: %lld pageIdx: %ld is-short-read: %d", + comm->nodeResult, FhgfsPage_getPageIndex(fhgfsPage), isShortRead); + + if (isShortRead) + { // file size includes this page, but we don't have data, so zero it + FhgfsPage_zeroPage(fhgfsPage); + } + else + { // the file is smaller than this page, nothing to be done + } + + } + + FhgfsOpsPages_endReadPage(commHelper->log, inode, fhgfsPage, pageReadRes); + } + +out: + return; +} + + +/** + * @return false on error (means 'break' for the surrounding switch statement) + */ +void __FhgfsOpsCommKitVec_writefileStagePREPARE(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + Config* cfg = App_getConfig(commHelper->app); + TargetMapper* targetMapper = App_getTargetMapper(commHelper->app); + NodeStoreEx* storageNodes = App_getStorageNodes(commHelper->app); + Node* localNode = App_getLocalNode(commHelper->app); + const NumNodeID localNodeNumID = Node_getNumID(localNode); + + FhgfsOpsErr resolveErr; + NodeConnPool* connPool; + WriteLocalFileMsg writeMsg; + + uint16_t nodeReferenceTargetID = comm->targetID; /* to avoid overriding original targetID + in case of buddy-mirroring, because we need the mirror group ID for the msg and for retries */ + + size_t remainingDataSize = FhgfsOpsCommKitVec_getRemainingDataSize(comm); + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); + + // nodeAndType is not set until later after calling NodeStoreEx_referenceNodeByTargetID. + // Don't use it before then! + NodeString nodeAndType; + + comm->sock = NULL; + comm->nodeResult = -FhgfsOpsErr_COMMUNICATION; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + if(StripePattern_getPatternType(commHelper->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = App_getStorageBuddyGroupMapper(commHelper->app); + + nodeReferenceTargetID = comm->useBuddyMirrorSecond ? + MirrorBuddyGroupMapper_getSecondaryTargetID(mirrorBuddies, comm->targetID) : + MirrorBuddyGroupMapper_getPrimaryTargetID(mirrorBuddies, comm->targetID); + + // note: only log message here, error handling below through invalid node reference + if(unlikely(!nodeReferenceTargetID) ) + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Invalid mirror buddy group ID: %hu", comm->targetID); + } + + // get the target-node reference + comm->node = NodeStoreEx_referenceNodeByTargetID(storageNodes, + nodeReferenceTargetID, targetMapper, &resolveErr); + if(unlikely(!comm->node) ) + { // unable to resolve targetID + comm->nodeResult = -resolveErr; + + goto outErr; + } + + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + connPool = Node_getConnPool(comm->node); + + // connect + comm->sock = NodeConnPool_acquireStreamSocketEx(connPool, true, NULL); + if(!comm->sock) + { // connection error + if (fatal_signal_pending(current)) { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, + commHelper->logContext, "Connect to server canceled by pending signal: %s", + nodeAndType.buf); + } + else { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, + commHelper->logContext, "Unable to connect to server: %s", + nodeAndType.buf); + } + + goto outErr; + } + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "Acquire write state: node: %s offset: %lld size: %zd", + nodeAndType.buf, (long long) offset, remainingDataSize); + + // prepare message + WriteLocalFileMsg_initFromSession(&writeMsg, localNodeNumID, + commHelper->ioInfo->fileHandleID, comm->targetID, commHelper->ioInfo->pathInfo, + commHelper->ioInfo->accessFlags, offset, remainingDataSize); + + NetMessage_setMsgHeaderTargetID( (NetMessage*)&writeMsg, nodeReferenceTargetID); + + if (Config_getQuotaEnabled(cfg) ) + WriteLocalFileMsg_setUserdataForQuota(&writeMsg, commHelper->ioInfo->userID, + commHelper->ioInfo->groupID); + + if (comm->firstWriteDoneForTarget && Config_getSysSessionChecksEnabled(cfg)) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&writeMsg, + WRITELOCALFILEMSG_FLAG_SESSION_CHECK); + + if(StripePattern_getPatternType(commHelper->ioInfo->pattern) == STRIPEPATTERN_BuddyMirror) + { + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&writeMsg, + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(comm->useServersideMirroring) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&writeMsg, + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD); + + if(comm->useBuddyMirrorSecond) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&writeMsg, + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + if(App_getNetBenchModeEnabled(commHelper->app) ) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&writeMsg, + WRITELOCALFILEMSG_FLAG_DISABLE_IO); + + NetMessage_serialize( (NetMessage*)&writeMsg, comm->msgBuf, BEEGFS_COMMKIT_MSGBUF_SIZE); + + comm->hdrLen = NetMessage_getMsgLength( (NetMessage*)&writeMsg); + + comm->doHeader = true; + + return; + +outErr: + __FhgfsOpsCommKitVec_writefileStageCLEANUP(commHelper, comm); +} + +/** + * Tell the server how many data we are going to send + */ +void __FhgfsOpsCommKitVec_writefileStageSENDHEADER(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + ssize_t sendRes; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + sendRes = Socket_send_kernel(comm->sock, comm->msgBuf, comm->hdrLen, 0); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_WRITE_HEADER ) + if (sendRes == FhgfsOpsErr_SUCCESS && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) sending data."); + sendRes = -ETIMEDOUT; + } +#endif + + if(unlikely(sendRes <= 0) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, commHelper->logContext, + "Failed to send message to %s: %s", nodeAndType.buf, + Socket_getPeername(comm->sock) ); + + goto outErr; + } + + __FhgfsOpsCommKitVec_writefileStageSENDDATA(commHelper, comm); + + return; + +outErr: + __FhgfsOpsCommKitVec_writefileStageSOCKETEXCEPTION(commHelper, comm); +} + +/** + * Send the data (of our current state) + * + * @param state - current state to send data for + */ +void __FhgfsOpsCommKitVec_writefileStageSENDDATA(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + ssize_t sendDataPartRes; + FhgfsChunkPageVec *pageVec = comm->pageVec; + + FhgfsPage *fhgfsPage; + + size_t vecIdx = FhgfsChunkPageVec_getCurrentListVecIterIdx(pageVec); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + { + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_SPAM, __func__, + "targetID: %hu offset: %lld vecSize: %u, current-pgIdx: %zu", + comm->targetID, (long long) offset, FhgfsChunkPageVec_getSize(pageVec), vecIdx); + IGNORE_UNUSED_VARIABLE(offset); + } + + // walk over all pages + while (true) + { + void* data; + + size_t dataLength; + + vecIdx = FhgfsChunkPageVec_getCurrentListVecIterIdx(pageVec); + + fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(pageVec); + if (!fhgfsPage) + break; + + dataLength = fhgfsPage->length; + data = fhgfsPage->data; + + // send dataPart blocking + sendDataPartRes = Socket_send_kernel(comm->sock, data, dataLength, 0); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, + "VecIdx: %zu; size: %lld; PgLen: %d, sendRes: %zd", + vecIdx, (long long)dataLength, fhgfsPage->length, sendDataPartRes); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_WRITE_SEND) + if (sendDataPartRes > 0 && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) sending data."); + sendDataPartRes = -ETIMEDOUT; + } +#endif + + if (unlikely(sendDataPartRes <= 0) ) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + if(sendDataPartRes == -EFAULT) + { // bad buffer address given + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, + commHelper->logContext, "Bad buffer address"); + + comm->nodeResult = -FhgfsOpsErr_ADDRESSFAULT; + + goto outErr; + } + + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Communication error in SENDDATA stage. " + "Node: %s; Request details: %zu/%u pages", + nodeAndType.buf, vecIdx, FhgfsChunkPageVec_getSize(pageVec) ); + + goto outErr; + } + + if (sendDataPartRes < dataLength) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "Error/Bug: Node: %s; Less data sent than requested; Request details: %zu/%u pages", + nodeAndType.buf, vecIdx, FhgfsChunkPageVec_getSize(pageVec) ); + + goto outErr; + } + } + + __FhgfsOpsCommKitVec_writefileStageRECV(commHelper, comm); + return; + +outErr: + __FhgfsOpsCommKitVec_writefileStageSOCKETEXCEPTION(commHelper, comm); +} + + +/** + * Ask the server side for errors + */ +void __FhgfsOpsCommKitVec_writefileStageRECV(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + ssize_t respRes; + bool isSocketException = false; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + respRes = MessagingTk_recvMsgBuf(commHelper->app, comm->sock, + comm->msgBuf, BEEGFS_COMMKIT_MSGBUF_SIZE); + +#if (BEEGFS_COMMKIT_DEBUG & COMMKIT_DEBUG_WRITE_RECV) + if (respRes == FhgfsOpsErr_SUCCESS && jiffies % CommKitErrorInjectRate == 0) + { + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, __func__, + "Injecting timeout error after (successfully) sending data."); + respRes = -ETIMEDOUT; + } +#endif + + if(unlikely(respRes <= 0) ) + { // receive failed + // note: signal pending log msg will be printed in stage SOCKETEXCEPTION, so no need here + if (!fatal_signal_pending(current)) { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, + commHelper->logContext, + "Receive failed from: %s @ %s", nodeAndType.buf, + Socket_getPeername(comm->sock) ); + } + + isSocketException = true; + goto outErr; + } + else + { // got response => deserialize it + bool deserRes; + + WriteLocalFileRespMsg_init(&comm->write.respMsg); + + deserRes = NetMessageFactory_deserializeFromBuf(commHelper->app, comm->msgBuf, respRes, + (NetMessage*)&comm->write.respMsg, NETMSGTYPE_WriteLocalFileResp); + + if(unlikely(!deserRes) ) + { // response invalid + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_WARNING, + commHelper->logContext, + "Received invalid response from %s. Expected type: %d. Disconnecting: %s", + nodeAndType.buf, NETMSGTYPE_WriteLocalFileResp, + Socket_getPeername(comm->sock) ); + + isSocketException = true; + goto outErr; + } + else + { // correct response + int64_t writeRespValue = WriteLocalFileRespMsg_getValue(&comm->write.respMsg); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, 5, __func__, + "writeRespValue: %lld", (long long) writeRespValue); + + comm->nodeResult = writeRespValue; + } + } + + __FhgfsOpsCommKitVec_writefileStageCLEANUP(commHelper, comm); + return; + +outErr: + if (isSocketException) + __FhgfsOpsCommKitVec_writefileStageSOCKETEXCEPTION(commHelper, comm); + +} + +/** + * Socket exception happend + */ +void __FhgfsOpsCommKitVec_writefileStageSOCKETEXCEPTION(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + const char* logCommInterruptedTxt = "Communication interrupted by signal"; + const char* logCommErrTxt = "Communication error"; + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + if (fatal_signal_pending(current)) + { // interrupted by signal + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_NOTICE, commHelper->logContext, + "%s. Node: %s", logCommInterruptedTxt, nodeAndType.buf); + } + else + { // "normal" connection error + size_t remainingDataSize = FhgfsOpsCommKitVec_getRemainingDataSize(comm); + loff_t offset = FhgfsOpsCommKitVec_getOffset(comm); + + Logger_logTopErrFormatted(commHelper->log, LogTopic_COMMKIT, commHelper->logContext, + "%s. Node: %s", logCommErrTxt, nodeAndType.buf); + + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, commHelper->logContext, + "Sent request: node: %s; fileHandleID: %s; offset: %lld; size: %zu", + nodeAndType.buf, commHelper->ioInfo->fileHandleID, + (long long)offset, remainingDataSize); + } + + if (comm->nodeResult >= 0) + { // make sure not to overwrite already assigned error codes + comm->nodeResult = -FhgfsOpsErr_COMMUNICATION; + } + + NodeConnPool_invalidateStreamSocket(Node_getConnPool(comm->node), comm->sock); + comm->sock = NULL; + + __FhgfsOpsCommKitVec_writefileStageCLEANUP(commHelper, comm); +} + +/** + * Clean up a connection, if there was an error + */ +void __FhgfsOpsCommKitVec_writefileStageCLEANUP(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + // actual clean-up + + if (comm->sock) + { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(comm->node, &nodeAndType); + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, 5, __func__, + "Release write node: %s", nodeAndType.buf); + + NodeConnPool_releaseStreamSocket(Node_getConnPool(comm->node), comm->sock); + comm->sock = NULL; + } + + if(likely(comm->node) ) + Node_put(comm->node); + + // prepare next stage +} + +/** + * Handle all remaining pages of this state. + * Note: Must be always called - on read success and read-error! + */ +void __FhgfsOpsCommKitVec_writefileStageHandlePages(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + FhgfsChunkPageVec *pageVec = comm->pageVec; + + struct inode* inode = FhgfsChunkPageVec_getInode(pageVec); + + FhgfsPage* lastInProgressPage = NULL; + FhgfsPage* firstUnhandledPage; + + ssize_t remainingServerWriteRes = comm->nodeResult; + + LOG_DEBUG_TOP_FORMATTED(commHelper->log, LogTopic_COMMKIT, Log_DEBUG, __func__, "enter"); + + if (unlikely(comm->nodeResult == -FhgfsOpsErr_AGAIN || + comm->nodeResult == -FhgfsOpsErr_COMMUNICATION) ) + return; // try again + + firstUnhandledPage = FhgfsChunkPageVec_iterateGetNextPage(pageVec); // might be NULL + + FhgfsChunkPageVec_resetIterator(pageVec); + + // walk over all pages in the page list vec + while(1) + { + int writeRes; + + FhgfsPage* fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(pageVec); + if (!fhgfsPage) + break; + + if (fhgfsPage == lastInProgressPage) + continue; // we already handled it above + + if (remainingServerWriteRes > 0) + { + writeRes = fhgfsPage->length; + remainingServerWriteRes -= fhgfsPage->length; + } + else + writeRes = -EREMOTEIO; + + if (unlikely(fhgfsPage == firstUnhandledPage && remainingServerWriteRes > 0) ) + { + printk_fhgfs(KERN_INFO, + "Bug: Server claims to have written more pages than we counted internally!"); + writeRes = -EIO; + } + + if (unlikely(writeRes < 0) ) + { + FhgfsInode* fhgfsInode = BEEGFS_INODE(inode); + + spin_lock(&inode->i_lock); + FhgfsInode_setWritePageError(fhgfsInode); + spin_unlock(&inode->i_lock); + } + + FhgfsOpsPages_endWritePage(fhgfsPage->page, writeRes, inode); + } + + if (unlikely(remainingServerWriteRes < 0) ) + Logger_logTopFormatted(commHelper->log, LogTopic_COMMKIT, Log_ERR, __func__, + "Write error detected (check server logs for details)!"); +} + +/** + * Start the remote read IO + */ +static int64_t __FhgfsOpsCommKitVec_readfileCommunicate(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + comm->doHeader = false; + + __FhgfsOpsCommKitVec_readfileStagePREPARE(commHelper, comm); + while (comm->doHeader) + { + comm->doHeader = false; + + // set doHeader to true if required + __FhgfsOpsCommKitVec_readfileStageRECVHEADER(commHelper, comm); + } + + return comm->nodeResult; +} + +/** + * Start the remote write IO + */ +static int64_t __FhgfsOpsCommKitVec_writefileCommunicate(CommKitVecHelper* commHelper, + FhgfsCommKitVec* comm) +{ + FhgfsInode* inode = BEEGFS_INODE(comm->pageVec->inode); + + FhgfsInode_incWriteBackCounter(inode); + + comm->doHeader = false; + + __FhgfsOpsCommKitVec_writefileStagePREPARE(commHelper, comm); + while (comm->doHeader) + { + comm->doHeader = false; + + // set doHeader to true if required + __FhgfsOpsCommKitVec_writefileStageSENDHEADER(commHelper, comm); + } + + spin_lock(&inode->vfs_inode.i_lock); + FhgfsInode_setLastWriteBackOrIsizeWriteTime(inode); + FhgfsInode_decWriteBackCounter(inode); + FhgfsInode_unsetNoIsizeDecrease(inode); + spin_unlock(&inode->vfs_inode.i_lock); + + return comm->nodeResult; +} + +int64_t FhgfsOpsCommKitVec_rwFileCommunicate(App* app, RemotingIOInfo* ioInfo, + FhgfsCommKitVec* comm, Fhgfs_RWType rwType) +{ + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + int64_t retVal; + unsigned retries = 0; + unsigned maxRetries = Config_getConnNumCommRetries(cfg); + bool isSignalPending = false; + const char* logCommInterruptedTxt = "Communication interrupted by signal"; + + CommKitVecHelper commHelper = + { + .app = app, + .log = App_getLogger(app), + .logContext = (rwType == BEEGFS_RWTYPE_READ) ? + "readfile (vec communication)" : "writefile (vec communication)", + .ioInfo = ioInfo, + }; + + do + { + FhgfsChunkPageVec_resetIterator(comm->pageVec); + + if (rwType == BEEGFS_RWTYPE_READ) + retVal = __FhgfsOpsCommKitVec_readfileCommunicate(&commHelper, comm); + else + retVal = __FhgfsOpsCommKitVec_writefileCommunicate(&commHelper, comm); + + if (unlikely(retVal == -FhgfsOpsErr_AGAIN || retVal == -FhgfsOpsErr_COMMUNICATION) ) + { + if (fatal_signal_pending(current)) + { // interrupted by signal + Logger_logTopFormatted(log, LogTopic_COMMKIT, Log_NOTICE, __func__, + "%s.", logCommInterruptedTxt); + + isSignalPending = true; + } + else + MessagingTk_waitBeforeRetry(retries); + + retries++; + } + + if (unlikely(retVal == -FhgfsOpsErr_AGAIN ) ) + { + retVal = -FhgfsOpsErr_COMMUNICATION; + retries = 0; + } + + } while (retVal == -FhgfsOpsErr_COMMUNICATION && retries <= maxRetries && !isSignalPending); + + if (rwType == BEEGFS_RWTYPE_READ) + __FhgfsOpsCommKitVec_readfileStageHandlePages(&commHelper, comm); + else + __FhgfsOpsCommKitVec_writefileStageHandlePages(&commHelper, comm); + + return retVal; +} diff --git a/client_module/source/net/filesystem/FhgfsOpsCommKitVec.h b/client_module/source/net/filesystem/FhgfsOpsCommKitVec.h new file mode 100644 index 0000000..a0b4955 --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsCommKitVec.h @@ -0,0 +1,156 @@ +#ifndef FHGFSOPSCOMMKITVEC_H_ +#define FHGFSOPSCOMMKITVEC_H_ + +#include +#include +#include + +#include + +#include "FhgfsOpsCommKitCommon.h" +#include "RemotingIOInfo.h" + + +struct FhgfsCommKitVec; +typedef struct FhgfsCommKitVec FhgfsCommKitVec; + +struct CommKitVecHelper; +typedef struct CommKitVecHelper CommKitVecHelper; + + +extern int64_t FhgfsOpsCommKitVec_rwFileCommunicate(App* app, RemotingIOInfo* ioInfo, + FhgfsCommKitVec* comm, Fhgfs_RWType rwType); + + +static inline FhgfsCommKitVec FhgfsOpsCommKitVec_assignRWfileState(FhgfsChunkPageVec* pageVec, + unsigned pageIdx, unsigned numPages, loff_t offset, uint16_t targetID, char* msgBuf); + +static inline void FhgfsOpsCommKitVec_setRWFileStateFirstWriteDone( + bool firstWriteDoneForTarget, FhgfsCommKitVec* outComm); + +static inline size_t FhgfsOpsCommKitVec_getRemainingDataSize(FhgfsCommKitVec* comm); +static inline loff_t FhgfsOpsCommKitVec_getOffset(FhgfsCommKitVec* comm); + + + +struct FhgfsCommKitVec +{ + // Depending if we are going to read or write, the corresponding struct will be used + union + { + // NOTE: The *initial* stage for read and write MUST be RW_FILE_STAGE_PREPARE + struct readState + { + size_t reqLen; // requested size from client to server + size_t serverSize; // how many data the server is going to send (<= reqLen) + } read ; + + struct writeState + { + WriteLocalFileRespMsg respMsg; // response message + } write; + }; + + size_t hdrLen; // size (length) of the header message + + size_t numSuccessPages; // number of pages successfully handled + + // data for all stages + + // (assigned by the state-creator) + FhgfsChunkPageVec* pageVec; + loff_t initialOffset; // initial offset before sending any data + + uint16_t targetID; // target ID + char* msgBuf; // for serialization + + bool firstWriteDoneForTarget; /* true if data was previously written to this target in + this session; used for the session check */ + bool useBuddyMirrorSecond; // for buddy mirroring, this msg goes to secondary + bool useServersideMirroring; // data shall be forwarded to mirror by primary + + // (assigned by the preparation stage) + Node* node; // target-node reference + Socket* sock; // target-node connection + + // result information + int64_t nodeResult; /* the amount of data that have been read/written on the server side + * (or a negative fhgfs error code) */ + + bool doHeader; // continue with the read/write header stage +}; + +/** + * Additional data that is required or useful for all the states. + * (This is shared states data.) + * + * invariant: (numRetryWaiters + isDone + numUnconnectable + numPollSocks) <= numStates + */ +struct CommKitVecHelper +{ + App* app; + Logger* log; + char* logContext; + + RemotingIOInfo* ioInfo; +}; + + +/** + * Note: Caller still MUST initialize type.{read/write}.stage = RW_FILE_STAGE_PREPARE. + * Note: Initializes the expectedNodeResult attribute from the size argument. + * Note: Defaults to server-side mirroring. + */ +FhgfsCommKitVec FhgfsOpsCommKitVec_assignRWfileState(FhgfsChunkPageVec* pageVec, + unsigned pageIdx, unsigned numPages, loff_t offset, uint16_t targetID, char* msgBuf) +{ + FhgfsCommKitVec state = + { + .initialOffset = offset, + .targetID = targetID, + .msgBuf = msgBuf, + + .firstWriteDoneForTarget = false, // set via separate setter method + .useBuddyMirrorSecond = false, // set via separate setter method + .useServersideMirroring = true, // set via separate setter method + + .nodeResult = -FhgfsOpsErr_INTERNAL, + .numSuccessPages = 0, + }; + + state.pageVec = pageVec; + + return state; +} + +void FhgfsOpsCommKitVec_setRWFileStateFirstWriteDone(bool firstWriteDoneForTarget, + FhgfsCommKitVec* outComm) +{ + outComm->firstWriteDoneForTarget = firstWriteDoneForTarget; +} + + +/** + * Get the overall remaining data size (chunkPageVec + last incompletely processed page) + */ +size_t FhgfsOpsCommKitVec_getRemainingDataSize(FhgfsCommKitVec* comm) +{ + return FhgfsChunkPageVec_getRemainingDataSize(comm->pageVec); +} + +/** + * Get the current offset + */ +loff_t FhgfsOpsCommKitVec_getOffset(FhgfsCommKitVec* comm) +{ + FhgfsChunkPageVec* pageVec = comm->pageVec; + + // data size already send/received (written/read) + size_t processedSize = + FhgfsChunkPageVec_getDataSize(pageVec) - FhgfsOpsCommKitVec_getRemainingDataSize(comm); + + return comm->initialOffset + processedSize; +} + + +#endif /*FHGFSOPSCOMMKITVEC_H_*/ diff --git a/client_module/source/net/filesystem/FhgfsOpsRemoting.c b/client_module/source/net/filesystem/FhgfsOpsRemoting.c new file mode 100644 index 0000000..c4f60dc --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsRemoting.c @@ -0,0 +1,2617 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "FhgfsOpsCommKit.h" +#include "FhgfsOpsCommKitVec.h" + +static inline const char* __FhgfsOpsRemoting_rwTypeToString(enum Fhgfs_RWType); + +static bool __FhgfsOpsRemoting_writefileVerify(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, ssize_t* outWritten, unsigned firstTargetIndex, + unsigned numStripeNodes); +static inline int64_t __FhgfsOpsRemoting_getChunkOffset(int64_t pos, unsigned chunkSize, + size_t numNodes, size_t stripeNodeIndex); + + +struct Fhgfs_RWTypeStrEntry +{ + enum Fhgfs_RWType type; + const char* typeStr; +}; + +struct Fhgfs_RWTypeStrEntry const __Fhgfs_RWTypeList[] = +{ + {BEEGFS_RWTYPE_READ, "read vec"}, + {BEEGFS_RWTYPE_WRITE, "write vec"}, +}; + +#define __FHGFSOPS_REMOTING_RW_SIZE \ + ( (sizeof(__Fhgfs_RWTypeList) ) / (sizeof(struct Fhgfs_RWTypeStrEntry) ) ) + +#define __FHGFSOPS_REMOTING_msgBufCacheName BEEGFS_MODULE_NAME_STR "-pageVecMsgBufs" +#define __FHGFSOPS_REMOTING_msgBufPoolSize 8 // number of reserve (pre-allocated) msgBufs + +const ssize_t __FHGFSOPS_REMOTING_MAX_XATTR_VALUE_SIZE = 60*1000; +const ssize_t __FHGFSOPS_REMOTING_MAX_XATTR_NAME_SIZE = 245; + +static struct kmem_cache* FhgfsOpsRemoting_msgBufCache = NULL; +static mempool_t* FhgfsOpsRemoting_msgBufPool = NULL; + +static mempool_t* writefileStatePool; + +bool FhgfsOpsRemoting_initMsgBufCache(void) +{ + FhgfsOpsRemoting_msgBufCache = OsCompat_initKmemCache(__FHGFSOPS_REMOTING_msgBufCacheName, + BEEGFS_COMMKIT_MSGBUF_SIZE, NULL); + + if(!FhgfsOpsRemoting_msgBufCache) + goto fail_msgBufCache; + + FhgfsOpsRemoting_msgBufPool = mempool_create_slab_pool(__FHGFSOPS_REMOTING_msgBufPoolSize, + FhgfsOpsRemoting_msgBufCache); + + if(!FhgfsOpsRemoting_msgBufPool) + goto fail_msgBufPool; + + writefileStatePool = mempool_create_kmalloc_pool(4, sizeof(struct FileOpVecState) ); + if(!writefileStatePool) + goto fail_statePool; + + return true; + +fail_statePool: + mempool_destroy(FhgfsOpsRemoting_msgBufPool); +fail_msgBufPool: + kmem_cache_destroy(FhgfsOpsRemoting_msgBufCache); +fail_msgBufCache: + return false; +} + +void FhgfsOpsRemoting_destroyMsgBufCache(void) +{ + if(writefileStatePool) + mempool_destroy(writefileStatePool); + + if(FhgfsOpsRemoting_msgBufPool) + mempool_destroy(FhgfsOpsRemoting_msgBufPool); + + if(FhgfsOpsRemoting_msgBufCache) + kmem_cache_destroy(FhgfsOpsRemoting_msgBufCache); +} + + +/** + * Fhgfs_RWType enum value to human-readable string. + */ +const char* __FhgfsOpsRemoting_rwTypeToString(enum Fhgfs_RWType enumType) +{ + size_t type = (size_t)enumType; + + if (likely(type < __FHGFSOPS_REMOTING_RW_SIZE) ) + return __Fhgfs_RWTypeList[type].typeStr; + + + printk_fhgfs(KERN_WARNING, "Unknown rwType given to %s(): %d; (dumping stack...)\n", + __func__, (int) type); + + dump_stack(); + + return "Unknown error code"; + +} + +static struct RRPeer rrpeer_from_entryinfo(const EntryInfo* entryInfo) +{ + return EntryInfo_getIsBuddyMirrored(entryInfo) + ? RRPeer_group(entryInfo->owner.group) + : RRPeer_target(entryInfo->owner.node); +} + +/** + * Note: Clears FsDirInfo names/types, then sets server offset, adds names/types, sets contents + * offset and endOfDir in case of RPC success. + * Note: Also retrieves the optional entry types. + * + * @param dirInfo used as input (offsets) and output (contents etc.) parameter + */ +FhgfsOpsErr FhgfsOpsRemoting_listdirFromOffset(const EntryInfo* entryInfo, FsDirInfo* dirInfo, + unsigned maxOutNames) +{ + FsObjectInfo* fsObjectInfo = (FsObjectInfo*)dirInfo; + App* app = FsObjectInfo_getApp(fsObjectInfo); + + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (list dir from offset)"; + + int64_t serverOffset = FsDirInfo_getServerOffset(dirInfo); + ListDirFromOffsetMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + ListDirFromOffsetRespMsg* listDirResp; + FhgfsOpsErr retVal; + + // prepare request + ListDirFromOffsetMsg_initFromEntryInfo( + &requestMsg, entryInfo, serverOffset, maxOutNames, false); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, + NETMSGTYPE_ListDirFromOffsetResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + listDirResp = (ListDirFromOffsetRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)ListDirFromOffsetRespMsg_getResult(listDirResp); + + if(likely(retVal == FhgfsOpsErr_SUCCESS) ) + { + UInt8Vec* dirContentsTypes = FsDirInfo_getDirContentsTypes(dirInfo); + Int64CpyVec* serverOffsets = FsDirInfo_getServerOffsets(dirInfo); + StrCpyVec* dirContents = FsDirInfo_getDirContents(dirInfo); + StrCpyVec* dirContentIDs = FsDirInfo_getEntryIDs(dirInfo); + bool endOfDirReached; + + FsDirInfo_setCurrentContentsPos(dirInfo, 0); + + Int64CpyVec_clear(serverOffsets); + UInt8Vec_clear(dirContentsTypes); + StrCpyVec_clear(dirContents); + StrCpyVec_clear(dirContentIDs); + + ListDirFromOffsetRespMsg_parseEntryTypes(listDirResp, dirContentsTypes); + ListDirFromOffsetRespMsg_parseNames(listDirResp, dirContents); + ListDirFromOffsetRespMsg_parseEntryIDs(listDirResp, dirContentIDs); + ListDirFromOffsetRespMsg_parseServerOffsets(listDirResp, serverOffsets); + + // check for equal vector lengths + if(unlikely( + (UInt8Vec_length(dirContentsTypes) != StrCpyVec_length(dirContents) ) || + (UInt8Vec_length(dirContentsTypes) != StrCpyVec_length(dirContentIDs) ) || + (UInt8Vec_length(dirContentsTypes) != Int64CpyVec_length(serverOffsets) ) ) ) + { // appearently, at least one of the vector allocations failed + printk_fhgfs(KERN_WARNING, + "Memory allocation for directory contents retrieval failed.\n"); + + Int64CpyVec_clear(serverOffsets); + UInt8Vec_clear(dirContentsTypes); + StrCpyVec_clear(dirContents); + StrCpyVec_clear(dirContentIDs); + + retVal = FhgfsOpsErr_OUTOFMEM; + + goto cleanup_resp_buffers; + } + + FsDirInfo_setServerOffset(dirInfo, ListDirFromOffsetRespMsg_getNewServerOffset(listDirResp) ); + + endOfDirReached = (StrCpyVec_length(dirContents) < maxOutNames); + FsDirInfo_setEndOfDir(dirInfo, endOfDirReached); + } + else + { + int logLevel = (retVal == FhgfsOpsErr_PATHNOTEXISTS) ? Log_DEBUG : Log_NOTICE; + + Logger_logFormatted(log, logLevel, logContext, "ListDirResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + +cleanup_resp_buffers: + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * Resolve path to entry owner and stat the entry. + * Note: This function should *only* be called to stat the root path, as it is the only dir + * without a parent, for other entries use _statDirect() instead, as the owner info is already + * available. + */ +FhgfsOpsErr FhgfsOpsRemoting_statRoot(App* app, fhgfs_stat* outFhgfsStat) +{ + FhgfsOpsErr retVal; + + Node* node; + const char* logContext = "Stat root dir"; + Logger* log = App_getLogger(app); + NodeOrGroup rootID; + + NodeStoreEx* nodes = App_getMetaNodes(app); + + node = NodeStoreEx_referenceRootNode(nodes, &rootID); + if(likely(node) ) + { + const char* parentEntryID = ""; + const char* entryID = META_ROOTDIR_ID_STR; + const char* fileName = ""; + DirEntryType entryType = DirEntryType_DIRECTORY; + + EntryInfo entryInfo; + EntryInfo_init(&entryInfo, rootID, parentEntryID, entryID, fileName, entryType, 0); + + retVal = FhgfsOpsRemoting_statDirect(app, &entryInfo, outFhgfsStat); + + // clean-up + Node_put(node); + } + else + { + Logger_logErr(log, logContext, "Unable to proceed without a working root metadata node"); + retVal = FhgfsOpsErr_UNKNOWNNODE; + } + + return retVal; +} + +/** + * Stat directly from entryInfo. + * + * Note: typically you will rather want to call the wrapper FhgfsOpsRemoting_statDirect() instead + * of this method. + * + * @param parentNodeID may be NULL if the caller is not interested (default) + * @param parentEntryID may be NULL if the caller is not interested (default) + */ +FhgfsOpsErr FhgfsOpsRemoting_statAndGetParentInfo(App* app, const EntryInfo* entryInfo, + fhgfs_stat* outFhgfsStat, NumNodeID* outParentNodeID, char** outParentEntryID) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (stat)"; + + StatMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + StatRespMsg* statResp; + FhgfsOpsErr retVal; + + // prepare request + StatMsg_initFromEntryInfo(&requestMsg, entryInfo ); + + if (outParentNodeID) + StatMsg_addParentInfoRequest(&requestMsg); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_StatResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + statResp = (StatRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)StatRespMsg_getResult(statResp); + + if(retVal == FhgfsOpsErr_SUCCESS) + { + StatData* statData = StatRespMsg_getStatData(statResp); + StatData_getOsStat(statData, outFhgfsStat); + + if (outParentNodeID) + { + StatRespMsg_getParentInfo(statResp, outParentNodeID, outParentEntryID); + } + } + else + { + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "StatResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + } + + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * Modify file or dir attributes. + */ +FhgfsOpsErr FhgfsOpsRemoting_setAttr(App* app, const EntryInfo* entryInfo, + SettableFileAttribs* fhgfsAttr, int validAttribs, const struct FileEvent* event) +{ + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + const char* logContext = "Remoting (set attr)"; + + SetAttrMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + SetAttrRespMsg* setAttrResp; + FhgfsOpsErr retVal; + + // logging + if(validAttribs & SETATTR_CHANGE_MODE) + { LOG_DEBUG(log, Log_DEBUG, logContext, "Changing mode"); } + + if(validAttribs & SETATTR_CHANGE_USERID) + { LOG_DEBUG(log, Log_DEBUG, logContext, "Changing userID"); } + + if(validAttribs & SETATTR_CHANGE_GROUPID) + { LOG_DEBUG(log, Log_DEBUG, logContext, "Changing groupID"); } + + if(validAttribs & SETATTR_CHANGE_MODIFICATIONTIME) + { LOG_DEBUG(log, Log_DEBUG, logContext, "Changing modificationTime"); } + + if(validAttribs & SETATTR_CHANGE_LASTACCESSTIME) + { LOG_DEBUG(log, Log_DEBUG, logContext, "Changing lastAccessTime"); } + + // prepare request + SetAttrMsg_initFromEntryInfo(&requestMsg, entryInfo, validAttribs, fhgfsAttr, event); + + if (validAttribs & (SETATTR_CHANGE_USERID | SETATTR_CHANGE_GROUPID)) + { + if(Config_getQuotaEnabled(cfg) ) + NetMessage_addMsgHeaderFeatureFlag((NetMessage*)&requestMsg, SETATTRMSG_FLAG_USE_QUOTA); + } + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_SetAttrResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + setAttrResp = (SetAttrRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)SetAttrRespMsg_getValue(setAttrResp); + + if(unlikely(retVal != FhgfsOpsErr_SUCCESS) ) + { // error on server + int logLevel = (retVal == FhgfsOpsErr_PATHNOTEXISTS) ? Log_DEBUG : Log_NOTICE; + + Logger_logFormatted(log, logLevel, logContext, "SetAttrResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * @param outEntryInfo attribs set only in case of success (and must then be kfreed by the + * caller) + */ +FhgfsOpsErr FhgfsOpsRemoting_mkdir(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, EntryInfo* outEntryInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (mkdir)"; + + MkDirMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(parentInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + MkDirRespMsg* mkResp; + FhgfsOpsErr retVal; + + // prepare request + MkDirMsg_initFromEntryInfo(&requestMsg, parentInfo, createInfo); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_MkDirResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + mkResp = (MkDirRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)MkDirRespMsg_getResult(mkResp); + + if(retVal == FhgfsOpsErr_SUCCESS) + { // success + EntryInfo_dup(MkDirRespMsg_getEntryInfo(mkResp), outEntryInfo ); + } + else + { + int logLevel = Log_NOTICE; + + if(retVal == FhgfsOpsErr_EXISTS) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + Logger_logFormatted(log, logLevel, logContext, + "MkDirResp ownerID: %u%s parentID: %s name: %s error code: %s ", + EntryInfo_getOwner(parentInfo), EntryInfo_getOwnerFlag(parentInfo), parentInfo->entryID, + createInfo->entryName, FhgfsOpsErr_toErrString(retVal)); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_rmdir(App* app, const EntryInfo* parentInfo, const char* entryName, + const struct FileEvent* event) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (rmdir)"; + + RmDirMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(parentInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + RmDirRespMsg* rmResp; + FhgfsOpsErr retVal; + + // prepare request + RmDirMsg_initFromEntryInfo(&requestMsg, parentInfo, entryName, event); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_RmDirResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + rmResp = (RmDirRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)RmDirRespMsg_getValue(rmResp); + + if(retVal != FhgfsOpsErr_SUCCESS) + { + int logLevel = Log_NOTICE; + + if( (retVal == FhgfsOpsErr_NOTEMPTY) || (retVal == FhgfsOpsErr_PATHNOTEXISTS) || + (retVal == FhgfsOpsErr_INUSE) ) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + Logger_logFormatted(log, logLevel, logContext, "RmDirResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * @param outEntryInfo attribs allocated/set only in case of success (and must then be kfreed by the + * caller); may be NULL. + */ +FhgfsOpsErr FhgfsOpsRemoting_mkfile(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, EntryInfo* outEntryInfo) +{ + return FhgfsOpsRemoting_mkfileWithStripeHints(app, parentInfo, createInfo, 0, 0, outEntryInfo); +} + +/** + * @param numtargets 0 for directory default. + * @param chunksize 0 for directory default, must be 64K or multiple of 64K otherwise. + * @param outEntryInfo attribs allocated/set only in case of success (and must then be kfreed by the + * caller); may be NULL. + */ +FhgfsOpsErr FhgfsOpsRemoting_mkfileWithStripeHints(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, unsigned numtargets, unsigned chunksize, EntryInfo* outEntryInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (mkfile)"; + + MkFileMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(parentInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + MkFileRespMsg* mkResp; + FhgfsOpsErr retVal; + + // prepare request + MkFileMsg_initFromEntryInfo(&requestMsg, parentInfo, createInfo); + + if(numtargets || chunksize) + MkFileMsg_setStripeHints(&requestMsg, numtargets, chunksize); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_MkFileResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + mkResp = (MkFileRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)MkFileRespMsg_getResult(mkResp); + + if(retVal == FhgfsOpsErr_SUCCESS) + { // success + const EntryInfo* entryInfo = MkFileRespMsg_getEntryInfo(mkResp); + + if(outEntryInfo) + EntryInfo_dup(entryInfo, outEntryInfo); + } + else + { + int logLevel = Log_NOTICE; + + if(retVal == FhgfsOpsErr_EXISTS) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + Logger_logFormatted(log, logLevel, logContext, + "MkFileResp: ownerID: %u%s parentID: %s name: %s error code: %s", + EntryInfo_getOwner(parentInfo), EntryInfo_getOwnerFlag(parentInfo), parentInfo->entryID, + createInfo->entryName, FhgfsOpsErr_toErrString(retVal)); + + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_unlinkfile(App* app, const EntryInfo* parentInfo, + const char* entryName, const struct FileEvent* event) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (unlink file)"; + + UnlinkFileMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(parentInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + UnlinkFileRespMsg* unlinkResp; + FhgfsOpsErr retVal; + + // prepare request + UnlinkFileMsg_initFromEntryInfo(&requestMsg, parentInfo, entryName, event); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_UnlinkFileResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + unlinkResp = (UnlinkFileRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)UnlinkFileRespMsg_getValue(unlinkResp); + + if(retVal != FhgfsOpsErr_SUCCESS) + { + int logLevel = Log_NOTICE; + + if(retVal == FhgfsOpsErr_PATHNOTEXISTS) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + Logger_logFormatted(log, logLevel, logContext, "UnlinkFileResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * @param ioInfo in and out arg; in case of success: fileHandleID and pathInfo will be set + * pattern will be set if it is NULL; values have to be freed by the caller + */ +FhgfsOpsErr FhgfsOpsRemoting_openfile(const EntryInfo* entryInfo, RemotingIOInfo* ioInfo, + uint32_t* outVersion, const struct FileEvent* event) +{ + App* app = ioInfo->app; + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + const char* logContext = "Remoting (open file)"; + + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + OpenFileMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + OpenFileRespMsg* openResp; + FhgfsOpsErr retVal; + + // log open flags + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, + "EntryID: %s access: %s%s%s; extra flags: %s|%s", entryInfo->entryID, + ioInfo->accessFlags & OPENFILE_ACCESS_READWRITE ? "rw" : "", + ioInfo->accessFlags & OPENFILE_ACCESS_WRITE ? "w" : "", + ioInfo->accessFlags & OPENFILE_ACCESS_READ ? "r" : "", + ioInfo->accessFlags & OPENFILE_ACCESS_APPEND ? "append" : "", + ioInfo->accessFlags & OPENFILE_ACCESS_TRUNC ? "trunc" : ""); + + // prepare request msg + OpenFileMsg_initFromSession(&requestMsg, localNodeNumID, entryInfo, ioInfo->accessFlags, + event); + + if(Config_getQuotaEnabled(cfg) ) + NetMessage_addMsgHeaderFeatureFlag((NetMessage*)&requestMsg, OPENFILEMSG_FLAG_USE_QUOTA); + + if(Config_getSysBypassFileAccessCheckOnMeta(cfg)) + NetMessage_addMsgHeaderFeatureFlag((NetMessage*)&requestMsg, OPENFILEMSG_FLAG_BYPASS_ACCESS_CHECK); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_OpenFileResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + openResp = (OpenFileRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)OpenFileRespMsg_getResult(openResp); + + if(likely(retVal == FhgfsOpsErr_SUCCESS) ) + { // success => store file details + const PathInfo* msgPathInfoPtr; + + ioInfo->fileHandleID = StringTk_strDup(OpenFileRespMsg_getFileHandleID(openResp) ); + + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "FileHandleID: %s", + OpenFileRespMsg_getFileHandleID(openResp) ); + + if(!ioInfo->pattern) + { // inode doesn't have pattern yet => create it from response + StripePattern* pattern = OpenFileRespMsg_createPattern(openResp); + // check stripe pattern validity + if(unlikely(StripePattern_getPatternType(pattern) == STRIPEPATTERN_Invalid) ) + { // invalid pattern + Logger_logErrFormatted(log, logContext, + "Received invalid stripe pattern from metadata node: %u%s; FileHandleID: %s", + EntryInfo_getOwner(entryInfo), EntryInfo_getOwnerFlag(entryInfo), + OpenFileRespMsg_getFileHandleID(openResp) ); + + StripePattern_virtualDestruct(pattern); + + // nevertheless, the file is open now on mds => close it + FhgfsOpsHelper_closefileWithAsyncRetry(entryInfo, ioInfo, NULL); + + kfree(ioInfo->fileHandleID); + + retVal = FhgfsOpsErr_INTERNAL; + } + else + ioInfo->pattern = pattern; // received a valid pattern + } + + msgPathInfoPtr = OpenFileRespMsg_getPathInfo(openResp); + PathInfo_update(ioInfo->pathInfo, msgPathInfoPtr); + + if (outVersion) + *outVersion = openResp->fileVersion; + } + else + { // error on server + int logLevel = (retVal == FhgfsOpsErr_PATHNOTEXISTS) ? Log_DEBUG : Log_NOTICE; + + Logger_logFormatted(log, logLevel, logContext, "OpenFileResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_closefile(const EntryInfo* entryInfo, RemotingIOInfo* ioInfo, + const struct FileEvent* event) +{ + return FhgfsOpsRemoting_closefileEx(entryInfo, ioInfo, true, event); +} + +/** + * Note: You probably want to call FhgfsOpsRemoting_closefile() instead of this method. + * + * @param allowRetries usually true, only callers like InternodeSyncer might use false + * here to make sure they don't block on communication retries too long. + */ +FhgfsOpsErr FhgfsOpsRemoting_closefileEx(const EntryInfo* entryInfo, RemotingIOInfo* ioInfo, + bool allowRetries, const struct FileEvent* event) +{ + App* app = ioInfo->app; + Config* cfg = App_getConfig(app); + + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (close file)"; + + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + const int maxUsedTargetIndex = AtomicInt_read(ioInfo->maxUsedTargetIndex); + CloseFileMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + CloseFileRespMsg* closeResp; + FhgfsOpsErr retVal; + + // prepare request + CloseFileMsg_initFromSession(&requestMsg, localNodeNumID, ioInfo->fileHandleID, entryInfo, + maxUsedTargetIndex, event); + + if(Config_getTuneEarlyCloseResponse(cfg) ) // (note: linux doesn't return release() res to apps) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&requestMsg, + CLOSEFILEMSG_FLAG_EARLYRESPONSE); + + if(ioInfo->needsAppendLockCleanup && *ioInfo->needsAppendLockCleanup) + NetMessage_addMsgHeaderFeatureFlag( (NetMessage*)&requestMsg, + CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_CloseFileResp); + + // connect + if(allowRetries) + { // normal case + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + } + else + { // caller doesn't want retries => just use the requestResponse method without retries... + requestRes = MessagingTk_requestResponseNode(app, &rrNode, &rrArgs); + } + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + closeResp = (CloseFileRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)CloseFileRespMsg_getValue(closeResp); + + if(likely(retVal == FhgfsOpsErr_SUCCESS) ) + { // success + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "FileHandleID: %s", ioInfo->fileHandleID); + } + else + { // error + Logger_logFormatted(log, Log_NOTICE, logContext, "CloseFileResp error: %s (FileHandleID: %s)", + FhgfsOpsErr_toErrString(retVal), ioInfo->fileHandleID); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +/** + * This is the common code base of _flockEntryEx, _flockRangeEx, _flockAppendEx, which asks for a + * lock and resends the lock request every few seconds. + * + * @param requestMsg initialized lock request msg (will be sent to ownerNodeID), must be cleaned up + * by caller. + * @param respMsgType must be derived from SimpleIntMsg, contained result will be interpreted as + * FhgfsOpsErr_... + * @param allowRetries usually true, only callers like InternodeSyncer might use false + * here to make sure they don't block on communication retries too long. + */ +FhgfsOpsErr __FhgfsOpsRemoting_flockGenericEx(NetMessage* requestMsg, unsigned respMsgType, + NodeOrGroup owner, bool isBuddyMirrored, App* app, const char* fileHandleID, int lockTypeFlags, + char* lockAckID, bool allowRetries, RWLock* eiRLock) +{ + const int resendIntervalMS = 5000; + + const char* logContext = "Remoting (generic lock)"; + + Logger* log = App_getLogger(app); + AcknowledgmentStore* ackStore = App_getAckStore(app); + + WaitAck lockWaitAck; + WaitAckMap waitAcks; + WaitAckMap receivedAcks; + WaitAckNotification ackNotifier; + + FhgfsOpsErr requestRes; + SimpleIntMsg* flockResp; + FhgfsOpsErr retVal; + + IGNORE_UNUSED_DEBUG_VARIABLE(log); + IGNORE_UNUSED_DEBUG_VARIABLE(logContext); + + // register lock ack wait... + + WaitAck_init(&lockWaitAck, lockAckID, NULL); + WaitAckMap_init(&waitAcks); + WaitAckMap_init(&receivedAcks); + WaitAckNotification_init(&ackNotifier); + + WaitAckMap_insert(&waitAcks, lockAckID, &lockWaitAck); + + AcknowledgmentStore_registerWaitAcks(ackStore, &waitAcks, &receivedAcks, &ackNotifier); + + do + { + RequestResponseNode rrNode = { + .peer = isBuddyMirrored + ? RRPeer_group(owner.group) + : RRPeer_target(owner.node), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + + // prepare request + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)requestMsg, respMsgType); + + // connect & communicate + if(allowRetries) + { // normal case + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + } + else + { // caller doesn't want retries => just use the requestResponse tool without retries... + requestRes = MessagingTk_requestResponseNode(app, &rrNode, &rrArgs); + } + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + break; + } + + // store result + flockResp = (SimpleIntMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)SimpleIntMsg_getValue(flockResp); + + // cleanup response + // (note: cleanup is done here to not block the buffer while we're waiting for delayed ack) + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + + // handle result + + if( (retVal == FhgfsOpsErr_WOULDBLOCK) && !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT) ) + { + // not immediately granted and caller wants to wait for the lock + + bool waitRes; + + if (eiRLock) + RWLock_readUnlock(eiRLock); + + waitRes = AcknowledgmentStore_waitForAckCompletion( + ackStore, &waitAcks, &ackNotifier, resendIntervalMS); + + if (eiRLock) + RWLock_readLock(eiRLock); + + if(waitRes) + { // received lock grant + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "received async lock grant. " + "(handleID: %s)", fileHandleID); + + retVal = FhgfsOpsErr_SUCCESS; + } + else + if (!waitRes && !signal_pending(current)) + { // no grant received yet => resend request + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "no grant yet. bailing to caller... " + "(handleID: %s)", fileHandleID); + + break; + } + else + { // signal pending => cancel waiting + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "canceling wait due to pending signal " + "(handleID: %s)", fileHandleID); + + retVal = FhgfsOpsErr_INTERRUPTED; + } + } + + if(retVal == FhgfsOpsErr_SUCCESS) + { // success + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "received grant (handleID: %s)", + fileHandleID); + } + else + { // error + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, + "FLockResp error: %s (FileHandleID: %s)", + FhgfsOpsErr_toErrString(retVal), fileHandleID); + } + } while (0); + + /* note: if we were interrupted after sending a lock request and user allowed waiting, we have + one last chance now to get our ack after unregistering. (this check is also important to avoid + race conditions because we still send acks to lock grants as long as we're registered.) */ + + AcknowledgmentStore_unregisterWaitAcks(ackStore, &waitAcks); + + /* now that we're unregistered, we can safely check whether we received the grant after the + interrupt and before we unregistered ourselves (see corresponding note above) */ + + if( (retVal == FhgfsOpsErr_INTERRUPTED) && WaitAckMap_length(&receivedAcks) ) + { // good, we received the grant just in time + retVal = FhgfsOpsErr_SUCCESS; + } + + WaitAckNotification_uninit(&ackNotifier); + WaitAckMap_uninit(&receivedAcks); + WaitAckMap_uninit(&waitAcks); + + return retVal; +} + +/** + * Note: You probably want to call FhgfsOpsRemoting_flockAppend() instead of this method. + * + * @param clientFD will typically be 0, because we request append locks for the whole client (not + * for individual FDs). + * @param ownerPID just informative (value is stored on MDS, but not used) + * @param allowRetries usually true, only callers like InternodeSyncer might use false + * here to make sure they don't block on communication retries too long. + */ +FhgfsOpsErr FhgfsOpsRemoting_flockAppendEx(const EntryInfo* entryInfo, RWLock* eiRLock, App* app, + const char* fileHandleID, int64_t clientFD, int ownerPID, int lockTypeFlags, bool allowRetries) +{ + const char* logContext = "Remoting (flock append)"; + + Logger* log = App_getLogger(app); + AtomicInt* atomicCounter = App_getLockAckAtomicCounter(app); + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + char* lockAckID; + FLockAppendMsg requestMsg; + FhgfsOpsErr lockRes; + NodeString alias; + Node_copyAlias(App_getLocalNode(app), &alias); + // (note: lockAckID must be _globally_ unique) + lockAckID = StringTk_kasprintf("%X-%s-alck", AtomicInt_incAndRead(atomicCounter), alias.buf); + if(unlikely(!lockAckID) ) + { // out of mem + Logger_logErrFormatted(log, logContext, "Unable to proceed - out of memory"); + return FhgfsOpsErr_INTERNAL; + } + + do + { + FLockAppendMsg_initFromSession(&requestMsg, localNodeNumID, fileHandleID, entryInfo, + clientFD, ownerPID, lockTypeFlags, lockAckID); + + lockRes = __FhgfsOpsRemoting_flockGenericEx( (NetMessage*)&requestMsg, + NETMSGTYPE_FLockAppendResp, entryInfo->owner, EntryInfo_getIsBuddyMirrored(entryInfo), + app, fileHandleID, lockTypeFlags, lockAckID, allowRetries, eiRLock); + } while (lockRes == FhgfsOpsErr_WOULDBLOCK && !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT)); + + // cleanup + kfree(lockAckID); + + return lockRes; +} + +/** + * Note: You probably want to call FhgfsOpsRemoting_flockEntry() instead of this method. + * + * @param allowRetries usually true, only callers like InternodeSyncer might use false + * here to make sure they don't block on communication retries too long. + */ +FhgfsOpsErr FhgfsOpsRemoting_flockEntryEx(const EntryInfo* entryInfo, RWLock* eiRLock, App* app, + const char* fileHandleID, int64_t clientFD, int ownerPID, int lockTypeFlags, bool allowRetries) +{ + const char* logContext = "Remoting (flock entry)"; + + Logger* log = App_getLogger(app); + AtomicInt* atomicCounter = App_getLockAckAtomicCounter(app); + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + char* lockAckID; + + FLockEntryMsg requestMsg; + FhgfsOpsErr lockRes; + NodeString alias; + Node_copyAlias(App_getLocalNode(app), &alias); + + // (note: lockAckID must be _globally_ unique) + lockAckID = StringTk_kasprintf("%X-%s-elck", AtomicInt_incAndRead(atomicCounter), alias.buf); + if(unlikely(!lockAckID) ) + { // out of mem + Logger_logErrFormatted(log, logContext, "Unable to proceed - out of memory"); + return FhgfsOpsErr_INTERNAL; + } + + do + { + FLockEntryMsg_initFromSession(&requestMsg, localNodeNumID, fileHandleID, entryInfo, + clientFD, ownerPID, lockTypeFlags, lockAckID); + + lockRes = __FhgfsOpsRemoting_flockGenericEx((NetMessage*) &requestMsg, + NETMSGTYPE_FLockEntryResp, entryInfo->owner, EntryInfo_getIsBuddyMirrored(entryInfo), app, + fileHandleID, lockTypeFlags, lockAckID, allowRetries, eiRLock); + } while (lockRes == FhgfsOpsErr_WOULDBLOCK && !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT)); + + // cleanup + kfree(lockAckID); + + return lockRes; +} + +/** + * Note: You probably want to call FhgfsOpsRemoting_flockRange() instead of this method. + * + * @param allowRetries usually true, only callers like InternodeSyncer might use false + * here to make sure they don't block on communication retries too long. + */ +FhgfsOpsErr FhgfsOpsRemoting_flockRangeEx(const EntryInfo* entryInfo, RWLock* eiRLock, + App* app, const char* fileHandleID, int ownerPID, int lockTypeFlags, uint64_t start, uint64_t end, + bool allowRetries) +{ + const char* logContext = "Remoting (flock range)"; + + Logger* log = App_getLogger(app); + AtomicInt* atomicCounter = App_getLockAckAtomicCounter(app); + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + char* lockAckID; + + FLockRangeMsg requestMsg; + FhgfsOpsErr lockRes; + NodeString alias; + Node_copyAlias(App_getLocalNode(app), &alias); + + // (note: lockAckID must be _globally_ unique) + lockAckID = StringTk_kasprintf("%X-%s-rlck", AtomicInt_incAndRead(atomicCounter), alias.buf); + if(unlikely(!lockAckID) ) + { // out of mem + Logger_logErrFormatted(log, logContext, "Unable to proceed - out of memory"); + return FhgfsOpsErr_INTERNAL; + } + + do + { + FLockRangeMsg_initFromSession(&requestMsg, localNodeNumID, fileHandleID, entryInfo, + ownerPID, lockTypeFlags, start, end, lockAckID); + + lockRes = __FhgfsOpsRemoting_flockGenericEx((NetMessage*) &requestMsg, + NETMSGTYPE_FLockRangeResp, entryInfo->owner, EntryInfo_getIsBuddyMirrored(entryInfo), app, + fileHandleID, lockTypeFlags, lockAckID, allowRetries, eiRLock); + } while (lockRes == FhgfsOpsErr_WOULDBLOCK && !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT)); + + // cleanup + kfree(lockAckID); + + return lockRes; +} + +/** + * Check if there have been errors during _writefile(). + * + * @supposedWritten - number of bytes already written, including the current step; if an error + * came up, the number of unwritten bytes of this step will be subtracted. + * @outWritten - number of successfully written bytes or negative fhgfs error code. + * @firstWriteDone - bit mask of the targets; the bit of a target is true if a chunk was + * successful written to this target (for server cache loss detection). + * @firstTargetIndex - the index of the first stripe target (i.e. target index of states[0]). + * @numStripeNodes - count of stripe targets. + * @return - true if all was fine, false if there was an error. + */ +static bool __FhgfsOpsRemoting_writefileVerify(App* app, RemotingIOInfo* ioInfo, + struct list_head* states, ssize_t* outWritten, unsigned firstTargetIndex, + unsigned numStripeNodes) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (write file)"; + + unsigned i = 0; + struct FileOpVecState* vstate; + + *outWritten = 0; + + list_for_each_entry(vstate, states, base.base.targetInfoList) + { + FileOpState* state = &vstate->base; + /* note: we abort on the first error that comes up to return the number of successfully + written bytes till this error. (with a stripe count > 1 there might be data successfully + written to following targets, but this info cannot be returned to the user.) */ + + if(likely(state->expectedNodeResult >= state->base.nodeResult) ) + { + // update BitStore with first write done + unsigned currentTargetIndex = (firstTargetIndex + i) % numStripeNodes; + + BitStore_setBit(ioInfo->firstWriteDone, currentTargetIndex, true); + *outWritten += state->base.nodeResult; + i += 1; + } + + if(likely(state->expectedNodeResult == state->base.nodeResult) ) + continue; + + // result was other than we expected. + + if(state->base.nodeResult >= 0) + { // node wrote only a part of the data (probably disk full => makes no sense to go on) + Logger_logFormatted(log, Log_NOTICE, logContext, + "Problem storage targetID: %hu; " + "Msg: Node wrote only %lld bytes (expected %lld bytes); FileHandle: %s", + state->base.targetID, state->base.nodeResult, state->expectedNodeResult, + ioInfo->fileHandleID); + } + else + { // error occurred + FhgfsOpsErr nodeError = -(state->base.nodeResult); + + if(nodeError == FhgfsOpsErr_INTERRUPTED) // this is normal on ctrl+c (=> no logErr() ) + Logger_logFormatted(log, Log_DEBUG, logContext, + "Storage targetID: %hu; Msg: %s; FileHandle: %s", + state->base.targetID, FhgfsOpsErr_toErrString(nodeError), ioInfo->fileHandleID); + else + Logger_logErrFormatted(log, logContext, + "Error storage targetID: %hu; Msg: %s; FileHandle: %s", + state->base.targetID, FhgfsOpsErr_toErrString(nodeError), ioInfo->fileHandleID); + + *outWritten = state->base.nodeResult; + } + + return false; + } // end of for-loop (result verification) + + return true; +} + +static void writefile_nextIter(CommKitContext* context, FileOpState* state) +{ + struct FileOpVecState* vecState = container_of(state, struct FileOpVecState, base); + + state->data = vecState->data; +} + +/** + * Single-threaded parallel file write. + * Works for mirrored and unmirrored files. In case of a mirrored file, the mirror data will be + * forwarded by the servers. + * + * @return number of bytes written or negative fhgfs error code + */ +ssize_t FhgfsOpsRemoting_writefileVec(struct iov_iter* iter, loff_t offset, + RemotingIOInfo* ioInfo, bool serializeWrites) +{ + App* app = ioInfo->app; + + bool verifyRes; + ssize_t verifyValue; + ssize_t retVal = iov_iter_count(iter); + size_t toBeWritten = iov_iter_count(iter); + loff_t currentOffset = offset; + struct iov_iter iterCopy = *iter; + + StripePattern* pattern = ioInfo->pattern; + unsigned chunkSize = StripePattern_getChunkSize(pattern); + UInt16Vec* targetIDs = pattern->getStripeTargetIDs(pattern); + unsigned numStripeTargets = UInt16Vec_length(targetIDs); + int maxUsedTargetIndex = AtomicInt_read(ioInfo->maxUsedTargetIndex); + + __FhgfsOpsRemoting_logDebugIOCall(__func__, iov_iter_count(iter), offset, ioInfo, NULL); + +#ifdef BEEGFS_NVFS + ioInfo->nvfs = RdmaInfo_acquireNVFS(); +#endif + while(toBeWritten) + { + unsigned currentTargetIndex = pattern->getStripeTargetIndex(pattern, currentOffset); + unsigned firstTargetIndex = currentTargetIndex; + unsigned numWorks = 0; + size_t expectedWritten = 0; + LIST_HEAD(statesList); + + /* stripeset-loop: loop over one stripe set (using dynamically determined stripe target + indices). */ + while(toBeWritten && (numWorks < numStripeTargets) ) + { + size_t currentChunkSize = + StripePattern_getChunkEnd(pattern, currentOffset) - currentOffset + 1; + loff_t currentNodeLocalOffset = __FhgfsOpsRemoting_getChunkOffset( + currentOffset, chunkSize, numStripeTargets, currentTargetIndex); + struct iov_iter chunkIter; + + struct FileOpVecState* state = mempool_alloc(writefileStatePool, + list_empty(&statesList) ? GFP_NOFS : GFP_NOWAIT); + + if (!state) + break; + + maxUsedTargetIndex = MAX(maxUsedTargetIndex, (int)currentTargetIndex); + + chunkIter = iterCopy; + iov_iter_truncate(&chunkIter, currentChunkSize); + + // prepare the state information + FhgfsOpsCommKit_initFileOpState( + &state->base, + currentNodeLocalOffset, + iov_iter_count(&chunkIter), + UInt16Vec_at(targetIDs, currentTargetIndex) ); + + state->base.firstWriteDoneForTarget = + BitStore_getBit(ioInfo->firstWriteDone, currentTargetIndex); + + state->data = chunkIter; + + list_add_tail(&state->base.base.targetInfoList, &statesList); + + // (note on buddy mirroring: server-side mirroring is default, so nothing to do here) + + App_incNumRemoteWrites(app); + + // prepare for next loop + { + size_t count = iov_iter_count(&chunkIter); + currentOffset += count; + toBeWritten -= count; + expectedWritten += count; + numWorks++; + iov_iter_advance(&iterCopy, count); + currentTargetIndex = (currentTargetIndex + 1) % numStripeTargets; + } + + if(serializeWrites) + break; + } + + // communicate with the nodes + FhgfsOpsCommKit_writefileV2bCommunicate(app, ioInfo, &statesList, writefile_nextIter, NULL); + + // verify results + verifyRes = __FhgfsOpsRemoting_writefileVerify(app, ioInfo, &statesList, &verifyValue, + firstTargetIndex, numStripeTargets); + + while (!list_empty(&statesList) ) + { + struct FileOpVecState* state = list_first_entry(&statesList, struct FileOpVecState, + base.base.targetInfoList); + + list_del(&state->base.base.targetInfoList); + mempool_free(state, writefileStatePool); + } + + if(unlikely(!verifyRes) ) + { + retVal = verifyValue; + break; + } + } // end of while-loop (write out data) + + AtomicInt_max(ioInfo->maxUsedTargetIndex, maxUsedTargetIndex); + if (retVal > 0) + iov_iter_advance(iter, retVal); + +#ifdef BEEGFS_NVFS + if (ioInfo->nvfs) + { + RdmaInfo_releaseNVFS(); + ioInfo->nvfs = false; + } +#endif + return retVal; +} + +/** + * Write/read a vector (array) of pages to/from the storage servers. + * + * @return number of bytes written or negative fhgfs error code + */ +ssize_t FhgfsOpsRemoting_rwChunkPageVec(FhgfsChunkPageVec *pageVec, RemotingIOInfo* ioInfo, + Fhgfs_RWType rwType) +{ + App* app = ioInfo->app; + Logger* log = App_getLogger(app); + + bool needReadWriteHandlePages = false; // needs to be done on error in this method + + loff_t offset = FhgfsChunkPageVec_getFirstPageFileOffset(pageVec); + + const char* logContext = "Remoting read/write vec"; + const char* rwTypeStr = __FhgfsOpsRemoting_rwTypeToString(rwType); + + StripePattern* pattern = ioInfo->pattern; + unsigned chunkSize = StripePattern_getChunkSize(pattern); + size_t chunkPages = RemotingIOInfo_getNumPagesPerStripe(ioInfo); + unsigned numPages = FhgfsChunkPageVec_getSize(pageVec); + unsigned pageIdx = 0; // page index + + UInt16Vec* targetIDs = pattern->getStripeTargetIDs(pattern); + unsigned numStripeNodes = UInt16Vec_length(targetIDs); + int maxUsedTargetIndex = AtomicInt_read(ioInfo->maxUsedTargetIndex); + + unsigned targetIndex = pattern->getStripeTargetIndex(pattern, offset); + + loff_t chunkOffset = __FhgfsOpsRemoting_getChunkOffset( + offset, chunkSize, numStripeNodes, targetIndex); + + ssize_t retVal; + + char* msgBuf; + FhgfsCommKitVec state; + + // sanity check + if (numPages > chunkPages) + { + Logger_logErrFormatted(log, logContext, "Bug: %s: numPages (%u) > numChunkPages (%lu)!", + rwTypeStr, numPages, chunkPages); + + needReadWriteHandlePages = true; + retVal = -EINVAL; + goto out; + } + +#ifdef LOG_DEBUG_MESSAGES + { + ssize_t supposedSize = FhgfsChunkPageVec_getDataSize(pageVec); + __FhgfsOpsRemoting_logDebugIOCall(__func__, supposedSize, offset, ioInfo, rwTypeStr); + } +#endif + + msgBuf = mempool_alloc(FhgfsOpsRemoting_msgBufPool, GFP_NOFS); + if (unlikely(!msgBuf) ) + { // pool allocs must not fail! + printk_fhgfs(KERN_WARNING, "kernel bug (%s): mempool_alloc failed\n", __func__); + + needReadWriteHandlePages = true; + retVal = -ENOMEM; + goto outNoAlloc; + } + + /* in debug mode we use individual allocations instead of a single big buffer, as we can detect + array out of bounds for those (if the proper kernel options are set) */ + + maxUsedTargetIndex = MAX(maxUsedTargetIndex, (int)targetIndex); + + // prepare the state information + state = FhgfsOpsCommKitVec_assignRWfileState( + pageVec, pageIdx, numPages, chunkOffset, UInt16Vec_at(targetIDs, targetIndex), msgBuf); + + FhgfsOpsCommKitVec_setRWFileStateFirstWriteDone( + BitStore_getBit(ioInfo->firstWriteDone, targetIndex), &state); + + if (rwType == BEEGFS_RWTYPE_WRITE) + { + // (note on buddy mirroring: server-side mirroring is default, so nothing to do here) + + App_incNumRemoteWrites(app); + } + else + { + App_incNumRemoteReads(app); + } + + retVal = FhgfsOpsCommKitVec_rwFileCommunicate(app, ioInfo, &state, rwType); + + if(retVal > 0) + { + if (rwType == BEEGFS_RWTYPE_WRITE) + BitStore_setBit(ioInfo->firstWriteDone, targetIndex, true); + } + else if (retVal == -FhgfsOpsErr_COMMUNICATION) + { + // commkit has done no communication at all, and thus hasn't touched any pages. it is our + // responsibility to end pending io with an error now. + needReadWriteHandlePages = true; + } + + LOG_DEBUG_FORMATTED(log, Log_SPAM, logContext, "fileHandleID: %s rwType %s: sum-result %ld", + ioInfo->fileHandleID, rwTypeStr, retVal); + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + IGNORE_UNUSED_VARIABLE(rwTypeStr); + + mempool_free(msgBuf, FhgfsOpsRemoting_msgBufPool); + +outNoAlloc: + + AtomicInt_max(ioInfo->maxUsedTargetIndex, maxUsedTargetIndex); + +out: + if (unlikely(needReadWriteHandlePages) ) + { + if (rwType == BEEGFS_RWTYPE_WRITE) + FhgfsChunkPageVec_iterateAllHandleWritePages(pageVec, -EAGAIN); + else + FhgfsChunkPageVec_iterateAllHandleReadErr(pageVec); + } + + return retVal; +} + +static void readfile_nextIter(CommKitContext* context, FileOpState* state) +{ + struct FileOpVecState* vecState = container_of(state, struct FileOpVecState, base); + + state->data = vecState->data; +} + +ssize_t FhgfsOpsRemoting_readfileVec(struct iov_iter* iter, size_t toBeRead, loff_t offset, + RemotingIOInfo* ioInfo, FhgfsInode* fhgfsInode) +{ + App* app = ioInfo->app; + + ssize_t retVal = 0; + ssize_t errnum = 0; + BeeGFS_ReadSink readsink = {0}; + loff_t currentOffset = offset; + + StripePattern* pattern = ioInfo->pattern; + unsigned chunkSize = StripePattern_getChunkSize(pattern); + UInt16Vec* targetIDs = pattern->getStripeTargetIDs(pattern); + unsigned numStripeNodes = UInt16Vec_length(targetIDs); + const char* fileHandleID = ioInfo->fileHandleID; + int maxUsedTargetIndex = AtomicInt_read(ioInfo->maxUsedTargetIndex); + size_t stripeSetSize = (size_t) chunkSize * numStripeNodes; + + __FhgfsOpsRemoting_logDebugIOCall(__func__, iov_iter_count(iter), offset, ioInfo, NULL); + +#ifdef BEEGFS_NVFS + ioInfo->nvfs = RdmaInfo_acquireNVFS(); +#endif + while(toBeRead && !errnum) + { + unsigned currentTargetIndex = pattern->getStripeTargetIndex(pattern, currentOffset); + LIST_HEAD(stateList); + struct FileOpVecState* state; + ssize_t bytesReadThisRound = 0; + struct iov_iter stripeSetIter; + size_t maxReadSize = min_t(size_t, stripeSetSize, toBeRead); + + beegfs_readsink_reserve(&readsink, iter, maxReadSize); + stripeSetIter = readsink.sanitized_iter; + + // stripeset-loop: loop over one stripe set (using dynamically determined stripe node + // indices). + + for(unsigned numWorks = 0; + (numWorks < numStripeNodes + && toBeRead && iov_iter_count(&stripeSetIter)); + ++ numWorks) + { + size_t currentChunkSize = + StripePattern_getChunkEnd(pattern, currentOffset) - currentOffset + 1; + size_t currentReadSize = MIN(currentChunkSize, toBeRead); + loff_t currentNodeLocalOffset = __FhgfsOpsRemoting_getChunkOffset( + currentOffset, chunkSize, numStripeNodes, currentTargetIndex); + struct iov_iter chunkIter; + + state = kmalloc(sizeof(*state), list_empty(&stateList) ? GFP_NOFS : GFP_NOWAIT); + if (!state) + break; + + maxUsedTargetIndex = MAX(maxUsedTargetIndex, (int)currentTargetIndex); + + chunkIter = stripeSetIter; + iov_iter_truncate(&chunkIter, currentChunkSize); + + // prepare the state information + FhgfsOpsCommKit_initFileOpState( + &state->base, + currentNodeLocalOffset, + iov_iter_count(&chunkIter), + UInt16Vec_at(targetIDs, currentTargetIndex) ); + + state->base.firstWriteDoneForTarget = + BitStore_getBit(ioInfo->firstWriteDone, currentTargetIndex); + + state->data = chunkIter; + + list_add_tail(&state->base.base.targetInfoList, &stateList); + + // use secondary buddy mirror for odd inode numbers + if( (StripePattern_getPatternType(pattern) == STRIPEPATTERN_BuddyMirror) ) + state->base.base.useBuddyMirrorSecond = + fhgfsInode ? (fhgfsInode->vfs_inode.i_ino & 1) : false; + + App_incNumRemoteReads(app); + + // prepare for next loop + currentOffset += currentReadSize; + toBeRead -= currentReadSize; + + currentTargetIndex = (currentTargetIndex + 1) % numStripeNodes; + + iov_iter_advance(&stripeSetIter, iov_iter_count(&chunkIter)); + } + + if(list_empty(&stateList) ) + { + errnum = -FhgfsOpsErr_OUTOFMEM; + break; + } + + // communicate with the nodes + FhgfsOpsCommKit_readfileV2bCommunicate(app, ioInfo, &stateList, readfile_nextIter, NULL); + + // verify results + list_for_each_entry(state, &stateList, base.base.targetInfoList) + { + if(state->base.base.nodeResult == state->base.expectedNodeResult) + { + bytesReadThisRound += state->base.base.nodeResult; + continue; + } + + // result not as expected => check cause: end-of-file or error condition + + if(state->base.base.nodeResult >= 0) + { // we have an end of file here (but some data might have been read) + bytesReadThisRound += state->base.base.nodeResult; + } + else + { // error occurred + FhgfsOpsErr nodeError = -(state->base.base.nodeResult); + + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (read file)"; + + if(nodeError == FhgfsOpsErr_INTERRUPTED) // normal on ctrl+c (=> no logErr() ) + Logger_logFormatted(log, Log_DEBUG, logContext, + "Storage targetID: %hu; Msg: %s; FileHandle: %s", + state->base.base.targetID, FhgfsOpsErr_toErrString(nodeError), fileHandleID); + else + Logger_logErrFormatted(log, logContext, + "Error storage targetID: %hu; Msg: %s; FileHandle: %s", + state->base.base.targetID, FhgfsOpsErr_toErrString(nodeError), fileHandleID); + + errnum = state->base.base.nodeResult; + } + + toBeRead = 0; /* abort the read here due to incomplete result/error */ + break; + } // end of results verification for-loop + + while (!list_empty(&stateList) ) + { + struct FileOpVecState* state = list_first_entry(&stateList, struct FileOpVecState, + base.base.targetInfoList); + + list_del(&state->base.base.targetInfoList); + kfree(state); + } + + beegfs_readsink_release(&readsink); + + retVal += bytesReadThisRound; + iov_iter_advance(iter, bytesReadThisRound); + + } // end of while(toBeRead) + + AtomicInt_max(ioInfo->maxUsedTargetIndex, maxUsedTargetIndex); + + beegfs_readsink_release(&readsink); // Make sure it's released even if we broke early from the loop + +#ifdef BEEGFS_NVFS + if (ioInfo->nvfs) + { + RdmaInfo_releaseNVFS(); + ioInfo->nvfs = false; + } +#endif + + return retVal ? retVal : errnum; +} + + +FhgfsOpsErr FhgfsOpsRemoting_rename(App* app, const char* oldName, unsigned oldLen, + DirEntryType entryType, const EntryInfo* fromDirInfo, const char* newName, unsigned newLen, + const EntryInfo* toDirInfo, const struct FileEvent* event) +{ + FhgfsOpsErr retVal; + + RenameMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(fromDirInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + RenameRespMsg* renameResp; + + // prepare request + + RenameMsg_initFromEntryInfo(&requestMsg, oldName, oldLen, entryType, fromDirInfo, newName, + newLen, toDirInfo, event); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_RenameResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + renameResp = (RenameRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)RenameRespMsg_getValue(renameResp); + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_truncfile(App* app, const EntryInfo* entryInfo, loff_t size, + const struct FileEvent* event) +{ + Logger* log = App_getLogger(app); + Config* cfg = App_getConfig(app); + const char* logContext = "Remoting (trunc file)"; + + TruncFileMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + TruncFileRespMsg* truncResp; + FhgfsOpsErr retVal; + + // prepare request + TruncFileMsg_initFromEntryInfo(&requestMsg, size, entryInfo, event); + + if(Config_getQuotaEnabled(cfg) ) + NetMessage_addMsgHeaderFeatureFlag((NetMessage*)&requestMsg, TRUNCFILEMSG_FLAG_USE_QUOTA); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_TruncFileResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + truncResp = (TruncFileRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)TruncFileRespMsg_getValue(truncResp); + + if(unlikely(retVal != FhgfsOpsErr_SUCCESS && retVal != FhgfsOpsErr_TOOBIG) ) + { // error on server + int logLevel = (retVal == FhgfsOpsErr_PATHNOTEXISTS) ? Log_DEBUG : Log_NOTICE; + + Logger_logFormatted(log, logLevel, logContext, "TruncFileResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_fsyncfile(RemotingIOInfo* ioInfo, bool forceRemoteFlush, + bool checkSession, bool doSyncOnClose) +{ + const char* logContext = "Remoting (fsync file)"; + + App* app = ioInfo->app; + Logger* log = App_getLogger(app); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + StripePattern* pattern = ioInfo->pattern; + UInt16Vec* targetIDs = pattern->getStripeTargetIDs(pattern); + ssize_t numStripeTargets = UInt16Vec_length(targetIDs); + ssize_t numMirrorTargets = (StripePattern_getPatternType(pattern) == STRIPEPATTERN_BuddyMirror) + ? numStripeTargets + : 0; + + int i; + + struct FsyncContext context = { + .userID = FhgfsCommon_getCurrentUserID(), + .forceRemoteFlush = forceRemoteFlush, + .checkSession = checkSession, + .doSyncOnClose = doSyncOnClose, + }; + + INIT_LIST_HEAD(&context.states); + + for(i = 0; i < numStripeTargets + numMirrorTargets; i++) + { + struct FsyncState* state = kzalloc(sizeof(*state), GFP_NOFS); + uint16_t nodeIdx = i < numStripeTargets ? i : i - numStripeTargets; + + if(!state) + { + Logger_logErr(log, logContext, "Memory allocation failed."); + retVal = FhgfsOpsErr_OUTOFMEM; + goto state_failed; + } + + FhgfsOpsCommKit_initFsyncState(&context, state, UInt16Vec_at(targetIDs, nodeIdx) ); + state->base.useBuddyMirrorSecond = i >= numStripeTargets; + } + + FhgfsOpsCommKit_fsyncCommunicate(app, ioInfo, &context); + +state_failed: + while(!list_empty(&context.states) ) + { + struct FsyncState* state = list_first_entry(&context.states, struct FsyncState, + base.targetInfoList); + + list_del(&state->base.targetInfoList); + if(state->base.nodeResult != FhgfsOpsErr_SUCCESS && retVal == FhgfsOpsErr_SUCCESS) + { + retVal = -state->base.nodeResult; + Logger_logFormatted(log, Log_WARNING, logContext, + "Error storage target: %hu; Msg: %s", state->base.selectedTargetID, + FhgfsOpsErr_toErrString(retVal) ); + } + + kfree(state); + } + + return retVal; +} + +/** + * @param ignoreErrors true if success should be returned even if some (or all) known targets + * returned errors (in which case outSizeTotal/outSizeFree values will only show numbers from + * targets without errors); local errors (e.g. failed mem alloc) will not be ignored. + * @return FhgfsOpsErr_UNKNOWNTARGET if no targets are known. + */ +FhgfsOpsErr FhgfsOpsRemoting_statStoragePath(App* app, bool ignoreErrors, int64_t* outSizeTotal, + int64_t* outSizeFree) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (stat storage targets)"; + + TargetMapper* targetMapper = App_getTargetMapper(app); + + FhgfsOpsErr retVal = FhgfsOpsErr_OUTOFMEM; + + UInt16List targetIDs; + UInt16ListIter targetsIter; + unsigned numTargets; + + struct list_head targetStates; + struct StatStorageState* state; + + *outSizeTotal = 0; + *outSizeFree = 0; + + UInt16List_init(&targetIDs); + INIT_LIST_HEAD(&targetStates); + + TargetMapper_getTargetIDs(targetMapper, &targetIDs); + numTargets = UInt16List_length(&targetIDs); + + if(unlikely(!numTargets) ) + { // we treat no known storage servers as an error + UInt16List_uninit(&targetIDs); + Logger_logFormatted(log, Log_CRITICAL, logContext, "No storage targets known."); + return FhgfsOpsErr_UNKNOWNTARGET; + } + + UInt16ListIter_init(&targetsIter, &targetIDs); + while(!UInt16ListIter_end(&targetsIter) ) + { + struct StatStorageState* state = kmalloc(sizeof(*state), GFP_NOFS); + + if(!state) + goto fail_state; + + FhgfsOpsCommKit_initStatStorageState(&targetStates, state, + UInt16ListIter_value(&targetsIter) ); + + UInt16ListIter_next(&targetsIter); + } + + retVal = FhgfsOpsErr_SUCCESS; + FhgfsOpsCommKit_statStorageCommunicate(app, &targetStates); + + list_for_each_entry(state, &targetStates, base.targetInfoList) + { + if(state->base.nodeResult != FhgfsOpsErr_SUCCESS) + { // something went wrong with this target + LogLevel logLevel = ignoreErrors ? Log_DEBUG : Log_WARNING; + + Logger_logFormatted(log, logLevel, logContext, + "Error target (storage): %hu; Msg: %s", + state->base.selectedTargetID, FhgfsOpsErr_toErrString(-state->base.nodeResult)); + + if(!ignoreErrors) + { + retVal = -state->base.nodeResult; + break; + } + } + else + { // we got data from this target => add up + *outSizeTotal += state->totalSize; + *outSizeFree += state->totalFree; + } + } + +fail_state: + while(!list_empty(&targetStates) ) + { + struct StatStorageState* state = list_first_entry(&targetStates, struct StatStorageState, + base.targetInfoList); + + list_del(&state->base.targetInfoList); + kfree(state); + } + + UInt16List_uninit(&targetIDs); + + return retVal; +} + +/** + * @param size Size of puffer pointed to by @outValue + * @param outSize size of the extended attribute list. May be larger than buffer, in that case not + * the whole list is read. + */ +FhgfsOpsErr FhgfsOpsRemoting_listXAttr(App* app, const EntryInfo* entryInfo, char* outValue, + size_t size, ssize_t* outSize) +{ + ListXAttrMsg requestMsg; + FhgfsOpsErr requestRes; + + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + + ListXAttrRespMsg* listXAttrResp; + char* xAttrList; + int xAttrListSize; + int listXAttrReturnCode; + + // prepare request msg + ListXAttrMsg_initFromEntryInfoAndSize(&requestMsg, entryInfo, size); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, NETMSGTYPE_ListXAttrResp); + + // connect & communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { + // clean up + goto cleanup_request; + } + + // handle result + listXAttrResp = (ListXAttrRespMsg*)rrArgs.outRespMsg; + + listXAttrReturnCode = ListXAttrRespMsg_getReturnCode(listXAttrResp); + xAttrList = ListXAttrRespMsg_getValueBuf(listXAttrResp); + xAttrListSize = ListXAttrRespMsg_getSize(listXAttrResp); + + if(listXAttrReturnCode != FhgfsOpsErr_SUCCESS) + { + requestRes = listXAttrReturnCode; + } + else if(outValue) + { + // If outValue != NULL, copy the xattr list to the buffer. + if(xAttrListSize <= size) + { + memcpy(outValue, xAttrList, xAttrListSize); + *outSize = xAttrListSize; + } + else // provided buffer is too small: Error. + { + requestRes = FhgfsOpsErr_RANGE; + } + } + else + { + // If outValue == NULL, just return the size. + *outSize = xAttrListSize; + } + + // clean up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return requestRes; +} + +extern FhgfsOpsErr FhgfsOpsRemoting_removeXAttr(App* app, const EntryInfo* entryInfo, + const char* name) +{ + RemoveXAttrMsg requestMsg; + FhgfsOpsErr requestRes; + + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + + RemoveXAttrRespMsg* removeXAttrResp; + + // prepqre request msg + RemoveXAttrMsg_initFromEntryInfoAndName(&requestMsg, entryInfo, name); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, NETMSGTYPE_RemoveXAttrResp); + + // connect & communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { + // clean up + goto cleanup_request; + } + + // handle result + removeXAttrResp = (RemoveXAttrRespMsg*)rrArgs.outRespMsg; + + requestRes = RemoveXAttrRespMsg_getValue(removeXAttrResp); + + // clean up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return requestRes; +} + +/** + * @param name Null-terminated string containing name of xattr. + * @param value Buffer containing new contents of xattr. + * @param size Size of buffer. + * @param flags Flags files as documented for setxattr syscall (XATTR_CREATE, XATTR_REPLACE). + */ +extern FhgfsOpsErr FhgfsOpsRemoting_setXAttr(App* app, const EntryInfo* entryInfo, const char* name, + const char* value, const size_t size, int flags) +{ + SetXAttrMsg requestMsg; + FhgfsOpsErr requestRes; + + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + + SetXAttrRespMsg* setXAttrResp; + + if (strlen(name) > __FHGFSOPS_REMOTING_MAX_XATTR_NAME_SIZE) // The attribute name is too long + return FhgfsOpsErr_RANGE; // (if the server side prefix is + // added). + + if (size > __FHGFSOPS_REMOTING_MAX_XATTR_VALUE_SIZE) // The attribute itself is too large and + return FhgfsOpsErr_TOOLONG; // would not fit into a single NetMsg. + + // prepare request msg + SetXAttrMsg_initFromEntryInfoNameValueAndSize(&requestMsg, entryInfo, name, value, size, flags); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, NETMSGTYPE_SetXAttrResp); + + // connect & communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { + // clean up + goto cleanup_request; + } + + // handle result + setXAttrResp = (SetXAttrRespMsg*)rrArgs.outRespMsg; + + requestRes = SetXAttrRespMsg_getValue(setXAttrResp); + + // clean up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return requestRes; + +} + +/** + * @param name Zero-terminated string containing name of xattr. + * @param outValue pointer to buffer to store the value in. + * @param size size of buffer pointed to by @outValue. + * @param outSize size of the extended attribute value (may be larger than buffer, in that case not + * everything is read). + */ +extern FhgfsOpsErr FhgfsOpsRemoting_getXAttr(App* app, const EntryInfo* entryInfo, const char* name, + void* outValue, size_t size, ssize_t* outSize) +{ + GetXAttrMsg requestMsg; + FhgfsOpsErr requestRes; + + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + + GetXAttrRespMsg* getXAttrResp; + char* xAttrBuf; + int xAttrSize; + int getXAttrReturnCode; + + // prepare request msg + GetXAttrMsg_initFromEntryInfoNameAndSize(&requestMsg, entryInfo, name, size); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, NETMSGTYPE_GetXAttrResp); + + // connect & communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { + // clean up + goto cleanup_request; + } + + // handle result + getXAttrResp = (GetXAttrRespMsg*)rrArgs.outRespMsg; + + getXAttrReturnCode = GetXAttrRespMsg_getReturnCode(getXAttrResp); + xAttrBuf = GetXAttrRespMsg_getValueBuf(getXAttrResp); + xAttrSize = GetXAttrRespMsg_getSize(getXAttrResp); + + if(getXAttrReturnCode != FhgfsOpsErr_SUCCESS) + { + requestRes = getXAttrReturnCode; + } + else if(outValue) + { + // If outValue != NULL, copy the xattr buffer to the buffer. + if(xAttrSize <= size) + { + memcpy(outValue, xAttrBuf, xAttrSize); + *outSize = xAttrSize; + } + else // provided buffer is too small: Error. + { + requestRes = FhgfsOpsErr_RANGE; + } + } + else + { + // If outValue == NULL, just return the size. + *outSize = xAttrSize; + } + + // clean up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return requestRes; +} + +/** + * Lookup or create a file and stat it in a single remote request. + * + * @param outInfo outInfo::outEntryInfo attribs set only in case of success (and must then be + * kfreed by the caller). + * @return success if we got the basic outInfo::outEntryInfo; this means either file existed + * and O_EXCL wasn't specified or file didn't exist and was created. + */ +FhgfsOpsErr FhgfsOpsRemoting_lookupIntent(App* app, + const LookupIntentInfoIn* inInfo, LookupIntentInfoOut* outInfo) +{ + Config* cfg = App_getConfig(app); + + FhgfsOpsErr retVal; + + const CreateInfo* createInfo = inInfo->createInfo; + const OpenInfo* openInfo = inInfo->openInfo; + + LookupIntentMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(inInfo->parentEntryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + LookupIntentRespMsg* lookupResp; + + // prepare request + if (inInfo->entryInfoPtr) + { // EntryInfo already available, so a revalidate intent (lookup-by-id/entryInfo) + LookupIntentMsg_initFromEntryInfo(&requestMsg, inInfo->parentEntryInfo, inInfo->entryName, + inInfo->entryInfoPtr, inInfo->metaVersion); + } + else + { // no EntryInfo available, we need to lookup-by-name + LookupIntentMsg_initFromName(&requestMsg, inInfo->parentEntryInfo, inInfo->entryName); + } + + // always add a stat-intent + LookupIntentMsg_addIntentStat(&requestMsg); + + if(createInfo) + { + LookupIntentMsg_addIntentCreate(&requestMsg, createInfo->userID, createInfo->groupID, + createInfo->mode, createInfo->umask, createInfo->preferredStorageTargets, + createInfo->fileEvent); + + if(inInfo->isExclusiveCreate) + LookupIntentMsg_addIntentCreateExclusive(&requestMsg); + } + + if(openInfo) + { + const NumNodeID localNodeNumID = Node_getNumID(App_getLocalNode(app) ); + LookupIntentMsg_addIntentOpen(&requestMsg, localNodeNumID, openInfo->accessFlags); + } + + if(Config_getQuotaEnabled(cfg) ) + NetMessage_addMsgHeaderFeatureFlag((NetMessage*)&requestMsg, LOOKUPINTENTMSG_FLAG_USE_QUOTA); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, + NETMSGTYPE_LookupIntentResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + retVal = requestRes; + goto cleanup_request; + } + + // handle result + + lookupResp = (LookupIntentRespMsg*)rrArgs.outRespMsg; + + LookupIntentInfoOut_initFromRespMsg(outInfo, lookupResp); + + // if we got here, the entry lookup or creation was successful + retVal = FhgfsOpsErr_SUCCESS; + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + + +#ifdef LOG_DEBUG_MESSAGES +void __FhgfsOpsRemoting_logDebugIOCall(const char* logContext, size_t size, loff_t offset, + RemotingIOInfo* ioInfo, const char* rwTypeStr) +{ + App* app = ioInfo->app; + Logger* log = App_getLogger(app); + + if (rwTypeStr) + Logger_logFormatted(log, Log_DEBUG, logContext, + "rwType: %s; size: %lld; offset: %lld; fileHandleID: %s openFlags: %d", + rwTypeStr, (long long)size, (long long)offset, ioInfo->fileHandleID, ioInfo->accessFlags); + else + Logger_logFormatted(log, Log_DEBUG, logContext, + "size: %lld; offset: %lld; fileHandleID: %s", + (long long)size, (long long)offset, ioInfo->fileHandleID); + +} +#endif // LOG_DEBUG_MESSAGES + + +/** + * Compute the chunk-file offset on a storage server from a given user file position. + * + * Note: Make sure that the given stripeNodeIndex is really correct for the given pos. + */ +static int64_t __FhgfsOpsRemoting_getChunkOffset(int64_t pos, unsigned chunkSize, size_t numNodes, + size_t stripeNodeIndex) +{ + /* the code below is an optimization (wrt division and modulo) of the following three lines: + int64_t posModChunkSize = (pos % chunkSize); + int64_t stripeSetStart = pos - posModChunkSize - (stripeNodeIndex*chunkSize); + return ( (stripeSetStart / numNodes) + posModChunkSize); */ + + /* note: "& (chunksize-1) only works as "% chunksize" replacement, because chunksize must be + a power of two */ + + int64_t posModChunkSize = pos & (chunkSize-1); + int64_t stripeSetStart = pos - posModChunkSize - (stripeNodeIndex*chunkSize); + + int64_t stripeSetStartDivNumNodes; + + // note: if numNodes is a power of two, we can do bit shifting instead of division + + if(MathTk_isPowerOfTwo(numNodes) ) + { // quick path => bit shifting + stripeSetStartDivNumNodes = stripeSetStart >> MathTk_log2Int32(numNodes); + } + else + { // slow path => division + // note: do_div(n64, base32) assigns the result to n64 and returns the remainder! + // (we need do_div to enable this division on 32bit archs) + + stripeSetStartDivNumNodes = stripeSetStart; // will be changed by do_div() + do_div(stripeSetStartDivNumNodes, (unsigned)numNodes); + } + + return (stripeSetStartDivNumNodes + posModChunkSize); +} + +FhgfsOpsErr FhgfsOpsRemoting_hardlink(App* app, const char* fromName, unsigned fromLen, + const EntryInfo* fromInfo, const EntryInfo* fromDirInfo, const char* toName, unsigned toLen, + const EntryInfo* toDirInfo, const struct FileEvent* event) +{ + Logger* log = App_getLogger(app); + + const char* logContext = "Remoting (hardlink)"; + + HardlinkMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(toDirInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + HardlinkRespMsg* hardlinkResp; + FhgfsOpsErr retVal; + + // prepare request + HardlinkMsg_initFromEntryInfo(&requestMsg, fromDirInfo, fromName, fromLen, fromInfo, toDirInfo, + toName, toLen, event); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, NETMSGTYPE_HardlinkResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + hardlinkResp = (HardlinkRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)HardlinkRespMsg_getValue(hardlinkResp); + + if(retVal == FhgfsOpsErr_SUCCESS) + { + // success (nothing to be done here) + } + else + { + int logLevel = Log_NOTICE; + + if( (retVal == FhgfsOpsErr_PATHNOTEXISTS) || (retVal == FhgfsOpsErr_INUSE) || + (retVal == FhgfsOpsErr_EXISTS) ) + logLevel = Log_DEBUG; // don't bother user with non-error messages + + Logger_logFormatted(log, logLevel, logContext, "HardlinkResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + + +/** + * Refresh the entry (meta-data update) + */ +FhgfsOpsErr FhgfsOpsRemoting_refreshEntry(App* app, const EntryInfo* entryInfo) +{ + Logger* log = App_getLogger(app); + const char* logContext = "Remoting (refresh Entry)"; + + RefreshEntryInfoMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + RefreshEntryInfoRespMsg* refreshResp; + FhgfsOpsErr retVal; + + // prepare request + RefreshEntryInfoMsg_initFromEntryInfo(&requestMsg, entryInfo ); + + RequestResponseArgs_prepare(&rrArgs, NULL, (NetMessage*)&requestMsg, + NETMSGTYPE_RefreshEntryInfoResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + refreshResp = (RefreshEntryInfoRespMsg*)rrArgs.outRespMsg; + retVal = (FhgfsOpsErr)RefreshEntryInfoRespMsg_getResult(refreshResp); + + if(retVal != FhgfsOpsErr_SUCCESS) + { + LOG_DEBUG_FORMATTED(log, Log_DEBUG, logContext, "StatResp error code: %s", + FhgfsOpsErr_toErrString(retVal) ); + IGNORE_UNUSED_VARIABLE(log); + IGNORE_UNUSED_VARIABLE(logContext); + } + + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + + +FhgfsOpsErr FhgfsOpsRemoting_bumpFileVersion(App* app, const EntryInfo* entryInfo, + bool persistent, const struct FileEvent* event) +{ + struct BumpFileVersionMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + struct BumpFileVersionRespMsg* bumpResp; + FhgfsOpsErr retVal; + + // prepare request + BumpFileVersionMsg_init(&requestMsg, entryInfo, persistent, event); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, + NETMSGTYPE_BumpFileVersionResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if (requestRes != FhgfsOpsErr_SUCCESS) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + bumpResp = (struct BumpFileVersionRespMsg*) rrArgs.outRespMsg; + retVal = bumpResp->base.value; + + if (retVal != FhgfsOpsErr_SUCCESS) + { + LOG_DEBUG_FORMATTED(app->logger, Log_DEBUG, __func__, "BumpFile error code: %s", + FhgfsOpsErr_toErrString(retVal)); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} + +FhgfsOpsErr FhgfsOpsRemoting_getFileVersion(App* app, const EntryInfo* entryInfo, + uint32_t* outVersion) +{ + struct GetFileVersionMsg requestMsg; + RequestResponseNode rrNode = { + .peer = rrpeer_from_entryinfo(entryInfo), + .nodeStore = app->metaNodes, + .targetStates = app->metaStateStore, + .mirrorBuddies = app->metaBuddyGroupMapper + }; + RequestResponseArgs rrArgs; + FhgfsOpsErr requestRes; + struct GetFileVersionRespMsg* getResp; + FhgfsOpsErr retVal; + + // prepare request + GetFileVersionMsg_init(&requestMsg, entryInfo); + + RequestResponseArgs_prepare(&rrArgs, NULL, &requestMsg.netMessage, + NETMSGTYPE_GetFileVersionResp); + + // communicate + requestRes = MessagingTk_requestResponseNodeRetryAutoIntr(app, &rrNode, &rrArgs); + + if (requestRes != FhgfsOpsErr_SUCCESS) + { // clean-up + retVal = requestRes; + goto cleanup_request; + } + + // handle result + getResp = (struct GetFileVersionRespMsg*) rrArgs.outRespMsg; + retVal = getResp->result; + *outVersion = getResp->version; + + if (retVal != FhgfsOpsErr_SUCCESS) + { + LOG_DEBUG_FORMATTED(app->logger, Log_DEBUG, __func__, "GetFileVersion error code: %s", + FhgfsOpsErr_toErrString(retVal)); + } + + // clean-up + RequestResponseArgs_freeRespBuffers(&rrArgs, app); + +cleanup_request: + return retVal; +} diff --git a/client_module/source/net/filesystem/FhgfsOpsRemoting.h b/client_module/source/net/filesystem/FhgfsOpsRemoting.h new file mode 100644 index 0000000..21423d3 --- /dev/null +++ b/client_module/source/net/filesystem/FhgfsOpsRemoting.h @@ -0,0 +1,174 @@ +#ifndef FHGFSOPSREMOTING_H_ +#define FHGFSOPSREMOTING_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +enum Fhgfs_RWType; +typedef enum Fhgfs_RWType Fhgfs_RWType; + +struct FileOpVecState { + FileOpState base; + + struct iov_iter data; +}; + +union RWFileVecState { + struct FileOpVecState read; + struct FileOpState write; +}; + + +struct StripePattern; // forward declaration +struct NetMessage; // forward declaration + +extern bool FhgfsOpsRemoting_initMsgBufCache(void); +extern void FhgfsOpsRemoting_destroyMsgBufCache(void); + +extern FhgfsOpsErr FhgfsOpsRemoting_listdirFromOffset(const EntryInfo* entryInfo, + FsDirInfo* dirInfo, unsigned maxOutNames); +extern FhgfsOpsErr FhgfsOpsRemoting_statRoot(App* app, fhgfs_stat* outFhgfsStat); +static inline FhgfsOpsErr FhgfsOpsRemoting_statDirect(App* app, const EntryInfo* entryInfo, + fhgfs_stat* outFhgfsStat); +extern FhgfsOpsErr FhgfsOpsRemoting_statDirect(App* app, const EntryInfo* entryInfo, + fhgfs_stat* outFhgfsStat); +extern FhgfsOpsErr FhgfsOpsRemoting_statAndGetParentInfo(App* app, const EntryInfo* entryInfo, + fhgfs_stat* outFhgfsStat, NumNodeID* outParentNodeID, char** outParentEntryID); +extern FhgfsOpsErr FhgfsOpsRemoting_setAttr(App* app, const EntryInfo* entryInfo, + SettableFileAttribs* fhgfsAttr, int validAttribs, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_mkdir(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, EntryInfo* outEntryInfo); +extern FhgfsOpsErr FhgfsOpsRemoting_rmdir(App* app, const EntryInfo* parentInfo, + const char* entryName, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_mkfile(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, EntryInfo* outEntryInfo); +extern FhgfsOpsErr FhgfsOpsRemoting_mkfileWithStripeHints(App* app, const EntryInfo* parentInfo, + struct CreateInfo* createInfo, unsigned numtargets, unsigned chunksize, EntryInfo* outEntryInfo); +extern FhgfsOpsErr FhgfsOpsRemoting_unlinkfile(App* app, const EntryInfo* parentInfo, + const char* entryName, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_openfile(const EntryInfo* entryInfo, RemotingIOInfo* ioInfo, + uint32_t* outVersion, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_closefile(const EntryInfo* entryInfo, + RemotingIOInfo* ioInfo, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_closefileEx(const EntryInfo* entryInfo, + RemotingIOInfo* ioInfo, bool allowRetries, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_flockAppendEx(const EntryInfo* entryInfo, RWLock* eiRLock, + App* app, const char* fileHandleID, int64_t clientFD, int ownerPID, int lockTypeFlags, + bool allowRetries); +extern FhgfsOpsErr FhgfsOpsRemoting_flockEntryEx(const EntryInfo* entryInfo, RWLock* eiRLock, + App* app, const char* fileHandleID, int64_t clientFD, int ownerPID, int lockTypeFlags, + bool allowRetries); +extern FhgfsOpsErr FhgfsOpsRemoting_flockRangeEx(const EntryInfo* entryInfo, RWLock* eiRLock, + App* app, const char* fileHandleID, int ownerPID, int lockTypeFlags, uint64_t start, uint64_t end, + bool allowRetries); + +extern ssize_t FhgfsOpsRemoting_writefileVec(struct iov_iter* iter, loff_t offset, + RemotingIOInfo* ioInfo, bool serializeWrites); + +static inline ssize_t FhgfsOpsRemoting_writefile(const char __user *buf, size_t size, loff_t offset, + RemotingIOInfo* ioInfo) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_IOV(buf, size, WRITE); + return FhgfsOpsRemoting_writefileVec(iter, offset, ioInfo, false); +} + +static inline ssize_t FhgfsOpsRemoting_writefile_kernel(const char *buf, size_t size, loff_t offset, + RemotingIOInfo* ioInfo) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, size, WRITE); + return FhgfsOpsRemoting_writefileVec(iter, offset, ioInfo, false); +} + +extern ssize_t FhgfsOpsRemoting_rwChunkPageVec(FhgfsChunkPageVec *pageVec, RemotingIOInfo* ioInfo, + Fhgfs_RWType rwType); +extern ssize_t FhgfsOpsRemoting_readfileVec(struct iov_iter *iter, size_t size, loff_t offset, + RemotingIOInfo* ioInfo, FhgfsInode* fhgfsInode); + +static inline ssize_t FhgfsOpsRemoting_readfile_user(char __user *buf, size_t size, loff_t offset, + RemotingIOInfo* ioInfo, FhgfsInode* fhgfsInode) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_IOV(buf, size, READ); + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); +} + +static inline ssize_t FhgfsOpsRemoting_readfile_kernel(char *buf, size_t size, loff_t offset, + RemotingIOInfo* ioInfo, FhgfsInode* fhgfsInode) +{ + struct iov_iter *iter = STACK_ALLOC_BEEGFS_ITER_KVEC(buf, size, READ); + return FhgfsOpsRemoting_readfileVec(iter, size, offset, ioInfo, fhgfsInode); +} + +extern FhgfsOpsErr FhgfsOpsRemoting_rename(App* app, const char* oldName, unsigned oldLen, + DirEntryType entryType, const EntryInfo* fromDirInfo, const char* newName, unsigned newLen, + const EntryInfo* toDirInfo, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_truncfile(App* app, const EntryInfo* entryInfo, loff_t size, + const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_fsyncfile(RemotingIOInfo* ioInfo, bool forceRemoteFlush, + bool checkSession, bool doSyncOnClose); +extern FhgfsOpsErr FhgfsOpsRemoting_statStoragePath(App* app, bool ignoreErrors, + int64_t* outSizeTotal, int64_t* outSizeFree); +extern FhgfsOpsErr FhgfsOpsRemoting_listXAttr(App* app, const EntryInfo* entryInfo, char* value, + size_t size, ssize_t* outSize); +extern FhgfsOpsErr FhgfsOpsRemoting_getXAttr(App* app, const EntryInfo* entryInfo, const char* name, + void* value, size_t size, ssize_t* outSize); +extern FhgfsOpsErr FhgfsOpsRemoting_removeXAttr(App* app, const EntryInfo* entryInfo, + const char* name); +extern FhgfsOpsErr FhgfsOpsRemoting_setXAttr(App* app, const EntryInfo* entryInfo, const char* name, + const char* value, const size_t size, int flags); + + +extern FhgfsOpsErr FhgfsOpsRemoting_lookupIntent(App* app, + const LookupIntentInfoIn* inInfo, LookupIntentInfoOut* outInfo); + +extern FhgfsOpsErr FhgfsOpsRemoting_hardlink(App* app, const char* fromName, unsigned fromLen, + const EntryInfo* fromInfo, const EntryInfo* fromDirInfo, const char* toName, unsigned toLen, + const EntryInfo* toDirInfo, const struct FileEvent* event); + +extern FhgfsOpsErr FhgfsOpsRemoting_refreshEntry(App* app, const EntryInfo* entryInfo); + +extern FhgfsOpsErr FhgfsOpsRemoting_bumpFileVersion(App* app, const EntryInfo* entryInfo, + bool persistent, const struct FileEvent* event); +extern FhgfsOpsErr FhgfsOpsRemoting_getFileVersion(App* app, const EntryInfo* entryInfo, + uint32_t* outVersion); + +FhgfsOpsErr __FhgfsOpsRemoting_flockGenericEx(struct NetMessage* requestMsg, unsigned respMsgType, + NodeOrGroup owner, bool isBuddyMirrored, App* app, const char* fileHandleID, int lockTypeFlags, + char* lockAckID, bool allowRetries, RWLock* eiRLock); + +#ifdef LOG_DEBUG_MESSAGES +extern void __FhgfsOpsRemoting_logDebugIOCall(const char* logContext, size_t size, loff_t offset, + RemotingIOInfo* ioInfo, const char* rwTypeStr); +#else +#define __FhgfsOpsRemoting_logDebugIOCall(logContext, size, offset, ioInfo, rwTypeStr) +#endif // LOG_DEBUG_MESSAGES + + +enum Fhgfs_RWType +{ + BEEGFS_RWTYPE_READ = 0, // read request + BEEGFS_RWTYPE_WRITE // write request +}; + + +/** + * Stat a file or directory using EntryInfo. + */ +FhgfsOpsErr FhgfsOpsRemoting_statDirect(App* app, const EntryInfo* entryInfo, + fhgfs_stat* outFhgfsStat) +{ + return FhgfsOpsRemoting_statAndGetParentInfo(app, entryInfo, outFhgfsStat, NULL, NULL); +} + + +#endif /*FHGFSOPSREMOTING_H_*/ diff --git a/client_module/source/net/filesystem/RemotingIOInfo.h b/client_module/source/net/filesystem/RemotingIOInfo.h new file mode 100644 index 0000000..7087d1d --- /dev/null +++ b/client_module/source/net/filesystem/RemotingIOInfo.h @@ -0,0 +1,144 @@ +#ifndef REMOTINGIOINFO_H_ +#define REMOTINGIOINFO_H_ + +#include +#include +#include +#include +#include + + +struct RemotingIOInfo; +typedef struct RemotingIOInfo RemotingIOInfo; + + + +// inliners +static inline void RemotingIOInfo_initOpen(App* app, unsigned accessFlags, + AtomicInt* pMaxUsedTargetIndex, PathInfo* pathInfo, RemotingIOInfo* outIOInfo); +static inline void RemotingIOInfo_initSpecialClose(App* app, const char* fileHandleID, + AtomicInt* maxUsedTargetIndex, unsigned accessFlags, RemotingIOInfo* outIOInfo); +static inline void RemotingIOInfo_freeVals(RemotingIOInfo* outIOInfo); +static inline size_t RemotingIOInfo_getNumPagesPerStripe(RemotingIOInfo* ioInfo); +static inline size_t RemotingIOInfo_getNumPagesPerChunk(RemotingIOInfo* ioInfo); + + + +struct RemotingIOInfo +{ + App* app; + + const char* fileHandleID; + struct StripePattern* pattern; + PathInfo* pathInfo; + unsigned accessFlags; // OPENFILE_ACCESS_... flags + + // pointers to fhgfsInode->fileHandles[handleType]... + + bool* needsAppendLockCleanup; // (note: can be NULL in some cases, implies "false") + AtomicInt* maxUsedTargetIndex; + BitStore* firstWriteDone; + + unsigned userID; // only used in storage server write message + unsigned groupID; // only used in storage server write message +#ifdef BEEGFS_NVFS + bool nvfs; +#endif +}; + + +/** + * Prepares a RemotingIOInfo for Remoting_openfile(). + * + * Note: Be careful with this. This is only useful in very special cases (e.g. stateless file IO, + * when you call FhgfsOpsRemoting_openfile() directly). Some values like userID also need to be + * set manually in these cases. + * You will typically rather go through the inode and thus use FhgfsInode_referenceHandle(). + * + * @param accessFlags OPENFILE_ACCESS_... flags + * @param maxUsedTargetIndex may be NULL for open, but in case you will use the ioInfo also for + * IO, you will probably want to set it. + */ +void RemotingIOInfo_initOpen(App* app, unsigned accessFlags, AtomicInt* maxUsedTargetIndex, + PathInfo* pathInfo, RemotingIOInfo* outIOInfo) +{ + outIOInfo->app = app; + + outIOInfo->fileHandleID = NULL; + outIOInfo->pattern = NULL; + outIOInfo->pathInfo = pathInfo; + outIOInfo->accessFlags = accessFlags; + + outIOInfo->needsAppendLockCleanup = NULL; + outIOInfo->maxUsedTargetIndex = maxUsedTargetIndex; + outIOInfo->firstWriteDone = NULL; + + outIOInfo->userID = 0; + outIOInfo->groupID = 0; +#ifdef BEEGFS_NVFS + outIOInfo->nvfs = false; +#endif +} + + +/** + * Initialize RemotingIOInfo for close only. This is used for very special failures in + * atomic_open and lookup_open. + */ +void RemotingIOInfo_initSpecialClose(App* app, const char* fileHandleID, + AtomicInt* maxUsedTargetIndex, unsigned accessFlags, RemotingIOInfo* outIOInfo) +{ + outIOInfo->app = app; + + outIOInfo->fileHandleID = fileHandleID; + outIOInfo->pattern = NULL; + outIOInfo->pathInfo = NULL; + outIOInfo->accessFlags = accessFlags; + + outIOInfo->needsAppendLockCleanup = NULL; + outIOInfo->maxUsedTargetIndex = maxUsedTargetIndex; + outIOInfo->firstWriteDone = NULL; + + outIOInfo->userID = 0; + outIOInfo->groupID = 0; +#ifdef BEEGFS_NVFS + outIOInfo->nvfs = false; +#endif +} + +/** + * Note: Be careful with this. This is only useful in very special cases (e.g. stateless file IO, + * when you called Remoting_openfile() directly). You will typically rather use the + * FhgfsInode_referenceHandle() & Co routines. + */ +void RemotingIOInfo_freeVals(RemotingIOInfo* outIOInfo) +{ + SAFE_DESTRUCT(outIOInfo->pattern, StripePattern_virtualDestruct); + SAFE_KFREE(outIOInfo->fileHandleID); + + if (outIOInfo->pathInfo) + PathInfo_uninit(outIOInfo->pathInfo); +} + + +/** + * Return the number of pages per stripe + */ +size_t RemotingIOInfo_getNumPagesPerStripe(RemotingIOInfo* ioInfo) +{ + StripePattern* pattern = ioInfo->pattern; + UInt16Vec* targetIDs = pattern->getStripeTargetIDs(pattern); + size_t numStripeNodes = UInt16Vec_length(targetIDs); + + return RemotingIOInfo_getNumPagesPerChunk(ioInfo) * numStripeNodes; +} + +size_t RemotingIOInfo_getNumPagesPerChunk(RemotingIOInfo* ioInfo) +{ + StripePattern* pattern = ioInfo->pattern; + unsigned chunkSize = StripePattern_getChunkSize(pattern); + + return chunkSize / PAGE_SIZE; +} + +#endif /* REMOTINGIOINFO_H_ */ diff --git a/client_module/source/net/message/NetMessageFactory.c b/client_module/source/net/message/NetMessageFactory.c new file mode 100644 index 0000000..3d55e46 --- /dev/null +++ b/client_module/source/net/message/NetMessageFactory.c @@ -0,0 +1,223 @@ +// control messages +#include +#include +// nodes messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// storage messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// session messages +#include +#include +#include +#ifdef BEEGFS_NVFS +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include "NetMessageFactory.h" + + +static bool __NetMessageFactory_deserializeRaw(App* app, DeserializeCtx* ctx, + NetMessageHeader* header, NetMessage* outMsg) +{ + bool checkCompatRes; + bool deserPayloadRes; + + outMsg->msgHeader = *header; + + checkCompatRes = NetMessage_checkHeaderFeatureFlagsCompat(outMsg); + if(unlikely(!checkCompatRes) ) + { // incompatible feature flag was set => log error with msg type + printk_fhgfs(KERN_NOTICE, + "Received a message with incompatible feature flags. Message type: %hu; Flags (hex): %X", + header->msgType, NetMessage_getMsgHeaderFeatureFlags(outMsg) ); + + _NetMessage_setMsgType(outMsg, NETMSGTYPE_Invalid); + return false; + } + + // deserialize message payload + + deserPayloadRes = outMsg->ops->deserializePayload(outMsg, ctx); + if(unlikely(!deserPayloadRes) ) + { + printk_fhgfs_debug(KERN_NOTICE, "Failed to decode message. Message type: %hu", + header->msgType); + + _NetMessage_setMsgType(outMsg, NETMSGTYPE_Invalid); + return false; + } + + return true; +} + +/** + * The standard way to create message objects from serialized message buffers. + * + * @return NetMessage which must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +NetMessage* NetMessageFactory_createFromBuf(App* app, char* recvBuf, size_t bufLen) +{ + NetMessageHeader header; + NetMessage* msg; + DeserializeCtx ctx = { + .data = recvBuf, + .length = bufLen, + }; + + // decode the message header + __NetMessage_deserializeHeader(&ctx, &header); + + // create the message object for the given message type + msg = NetMessageFactory_createFromMsgType(header.msgType); + + if(unlikely(NetMessage_getMsgType(msg) == NETMSGTYPE_Invalid) ) + { + printk_fhgfs_debug(KERN_NOTICE, + "Received an invalid or unhandled message. " + "Message type (from raw header): %hu", header.msgType); + + return msg; + } + + __NetMessageFactory_deserializeRaw(app, &ctx, &header, msg); + return msg; +} + +/** + * Special deserializer for pre-alloc'ed message objects. + * + * @param outMsg prealloc'ed msg of the expected type; must be initialized with the corresponding + * deserialization-initializer; must later be uninited/destructed by the caller, no matter whether + * this succeeds or not + * @param expectedMsgType the type of the pre-alloc'ed message object + * @return false on error (e.g. if real msgType does not match expectedMsgType) + */ +bool NetMessageFactory_deserializeFromBuf(App* app, char* recvBuf, size_t bufLen, + NetMessage* outMsg, unsigned short expectedMsgType) +{ + NetMessageHeader header; + DeserializeCtx ctx = { + .data = recvBuf, + .length = bufLen, + }; + + // decode the message header + __NetMessage_deserializeHeader(&ctx, &header); + + // verify expected message type + if(unlikely(header.msgType != expectedMsgType) ) + return false; + + return __NetMessageFactory_deserializeRaw(app, &ctx, &header, outMsg); +} + + +/** + * @return NetMessage that must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +NetMessage* NetMessageFactory_createFromMsgType(unsigned short msgType) +{ +#define HANDLE(ID, TYPE) case NETMSGTYPE_##ID: return NETMESSAGE_CONSTRUCT(TYPE) + + switch(msgType) + { + // control messages + HANDLE(Ack, AckMsgEx); + HANDLE(GenericResponse, GenericResponseMsg); + // nodes messages + HANDLE(GetNodesResp, GetNodesRespMsg); + HANDLE(GetStatesAndBuddyGroupsResp, GetStatesAndBuddyGroupsRespMsg); + HANDLE(GetTargetMappingsResp, GetTargetMappingsRespMsg); + HANDLE(HeartbeatRequest, HeartbeatRequestMsgEx); + HANDLE(Heartbeat, HeartbeatMsgEx); + HANDLE(MapTargets, MapTargetsMsgEx); + HANDLE(RefreshTargetStates, RefreshTargetStatesMsgEx); + HANDLE(RegisterNodeResp, RegisterNodeRespMsg); + HANDLE(RemoveNode, RemoveNodeMsgEx); + HANDLE(RemoveNodeResp, RemoveNodeRespMsg); + HANDLE(SetMirrorBuddyGroup, SetMirrorBuddyGroupMsgEx); + // storage messages + HANDLE(LookupIntentResp, LookupIntentRespMsg); + HANDLE(MkDirResp, MkDirRespMsg); + HANDLE(RmDirResp, RmDirRespMsg); + HANDLE(MkFileResp, MkFileRespMsg); + HANDLE(RefreshEntryInfoResp, RefreshEntryInfoRespMsg); + HANDLE(RenameResp, RenameRespMsg); + HANDLE(HardlinkResp, HardlinkRespMsg);; + HANDLE(UnlinkFileResp, UnlinkFileRespMsg); + HANDLE(ListDirFromOffsetResp, ListDirFromOffsetRespMsg); + HANDLE(SetAttrResp, SetAttrRespMsg); + HANDLE(StatResp, StatRespMsg); + HANDLE(StatStoragePathResp, StatStoragePathRespMsg); + HANDLE(TruncFileResp, TruncFileRespMsg); + HANDLE(ListXAttrResp, ListXAttrRespMsg); + HANDLE(GetXAttrResp, GetXAttrRespMsg); + HANDLE(RemoveXAttrResp, RemoveXAttrRespMsg); + HANDLE(SetXAttrResp, SetXAttrRespMsg); + // session messages + HANDLE(OpenFileResp, OpenFileRespMsg); + HANDLE(CloseFileResp, CloseFileRespMsg); + HANDLE(WriteLocalFileResp, WriteLocalFileRespMsg); +#ifdef BEEGFS_NVFS + HANDLE(WriteLocalFileRDMAResp, WriteLocalFileRDMARespMsg); +#endif + HANDLE(FSyncLocalFileResp, FSyncLocalFileRespMsg); + HANDLE(FLockAppendResp, FLockAppendRespMsg); + HANDLE(FLockEntryResp, FLockEntryRespMsg); + HANDLE(FLockRangeResp, FLockRangeRespMsg); + HANDLE(LockGranted, LockGrantedMsgEx); + HANDLE(BumpFileVersionResp, BumpFileVersionRespMsg); + HANDLE(GetFileVersionResp, GetFileVersionRespMsg); + + case NETMSGTYPE_AckNotifyResp: { + SimpleMsg* msg = os_kmalloc(sizeof(*msg)); + SimpleMsg_init(msg, msgType); + return &msg->netMessage; + } + + default: + { + SimpleMsg* msg = os_kmalloc(sizeof(*msg)); + SimpleMsg_init(msg, NETMSGTYPE_Invalid); + return (NetMessage*)msg; + } + } +} + + diff --git a/client_module/source/net/message/NetMessageFactory.h b/client_module/source/net/message/NetMessageFactory.h new file mode 100644 index 0000000..bf3786b --- /dev/null +++ b/client_module/source/net/message/NetMessageFactory.h @@ -0,0 +1,12 @@ +#ifndef NETMESSAGEFACTORY_H_ +#define NETMESSAGEFACTORY_H_ + +#include +#include + +extern NetMessage* NetMessageFactory_createFromBuf(App* app, char* recvBuf, size_t bufLen); +extern bool NetMessageFactory_deserializeFromBuf(App* app, char* recvBuf, size_t bufLen, + NetMessage* outMsg, unsigned short expectedMsgType); +extern NetMessage* NetMessageFactory_createFromMsgType(unsigned short msgType); + +#endif /*NETMESSAGEFACTORY_H_*/ diff --git a/client_module/source/nodes/NodeStoreEx.c b/client_module/source/nodes/NodeStoreEx.c new file mode 100644 index 0000000..034e5fc --- /dev/null +++ b/client_module/source/nodes/NodeStoreEx.c @@ -0,0 +1,588 @@ +#include +#include +#include +#include +#include +#include "NodeStoreEx.h" + +#define NODESTORE_WARN_REFNUM 2000 + +/** + * @param storeType will be applied to nodes on addOrUpdate() + */ +void NodeStoreEx_init(NodeStoreEx* this, App* app, NodeType storeType) +{ + this->app = app; + + RWLock_init(&this->rwLock); + + NodeTree_init(&this->nodeTree); + + this->newNodeAppeared = NULL; + + this->_rootOwner = NodeOrGroup_fromGroup(0); // 0 means undefined/invalid + + this->storeType = storeType; +} + +NodeStoreEx* NodeStoreEx_construct(App* app, NodeType storeType) +{ + NodeStoreEx* this = (NodeStoreEx*)os_kmalloc(sizeof(*this) ); + + NodeStoreEx_init(this, app, storeType); + + return this; +} + +void NodeStoreEx_uninit(NodeStoreEx* this) +{ + NodeTree_uninit(&this->nodeTree); +} + +void NodeStoreEx_destruct(NodeStoreEx* this) +{ + NodeStoreEx_uninit(this); + + kfree(this); +} + + +/** + * @param node belongs to the store after calling this method; this method will set (*node=NULL); + * so do not free it and don't use it any more afterwards (reference it from this store if you need + * it) + * @return true if the node was not in the store yet, false otherwise + */ +bool NodeStoreEx_addOrUpdateNode(NodeStoreEx* this, Node** node) +{ + const char* logContext = __func__; + Logger* log = App_getLogger(this->app); + + NumNodeID nodeNumID = Node_getNumID(*node); + NodeString incomingAlias; + NodeString activeAlias; + bool setAliasResult; + Node* active; + NicAddressList nicList; + Node_copyAlias(*node, &incomingAlias); + + // check if numeric ID is defined + + if(unlikely(!nodeNumID.value) ) + { // undefined numeric ID should never happen + Logger_logErrFormatted(log, logContext, + "Rejecting node with undefined numeric ID: %s; Type: %s", + incomingAlias.buf, Node_nodeTypeToStr(this->storeType) ); + Node_put(*node); + *node = NULL; + return false; + } + + RWLock_writeLock(&this->rwLock); // L O C K + + // is node in any of the stores already? + + active = NodeTree_find(&this->nodeTree, nodeNumID); + + if(active) + { // node was in the store already => update it + Node_copyAlias(active, &activeAlias); + // If the string IDs differ, update the current active node ID from the incoming node ID. + // Ignore if the incomingNodeID is empty, this can happen when a node first starts up + // because it has to download its own alias from the mgmtd. + if(incomingAlias.buf[0] != '\0' && strcmp(incomingAlias.buf, activeAlias.buf)) + { + // Before 8.0 BeeGFS logged "numeric ID collision for two different node string IDs". + // Starting in 8.0 string IDs are considered aliases and can be updated as needed. + Logger_logFormatted(log, 3, logContext, + "Updating alias for node: %s -> %s; Type: %s", + activeAlias.buf, incomingAlias.buf, Node_nodeTypeToStr(this->storeType) ); + // The node type should not be updated this way so we set it to invalid (no update). + setAliasResult = Node_setNodeAliasAndType(active, incomingAlias.buf, NODETYPE_Invalid); + + if (!setAliasResult) { + NodeString nodeAndType; + Node_copyAliasWithTypeStr(active, &nodeAndType); + + Logger_logErrFormatted(log, logContext, + // Partial updates should never happen. Print what is set for both the alias and node + // alias with type string. We don't know what may be helpful for debugging. + "Error updating alias for node: %s : %s (ignoring)", + activeAlias.buf, nodeAndType.buf); + } + } + + // update heartbeat time of existing node + Node_cloneNicList(*node, &nicList); + Node_updateLastHeartbeatT(active); + Node_updateInterfaces(active, Node_getPortUDP(*node), Node_getPortTCP(*node), + &nicList); + ListTk_kfreeNicAddressListElems(&nicList); + NicAddressList_uninit(&nicList); + + Node_put(*node); + } + else + { // node is not currently active => insert it + NodeTree_insert(&this->nodeTree, nodeNumID, *node); + + #ifdef BEEGFS_DEBUG + // check whether this node type and store type differ + if( (Node_getNodeType(*node) != NODETYPE_Invalid) && + (Node_getNodeType(*node) != this->storeType) ) + { + Logger_logErrFormatted(log, logContext, + "Node type and store type differ. Node: %s %s; Store: %s", + Node_getNodeTypeStr(*node), incomingAlias.buf, Node_nodeTypeToStr(this->storeType) ); + } + #endif // BEEGFS_DEBUG + + Node_setIsActive(*node, true); + Node_setNodeAliasAndType(*node, NULL, this->storeType); + + __NodeStoreEx_handleNodeVersion(this, *node); + + if(this->newNodeAppeared) + complete(this->newNodeAppeared); + } + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + *node = NULL; + + return !active; +} + +/** + * Note: remember to call releaseNode() + * + * @return NULL if no such node exists + */ +Node* NodeStoreEx_referenceNode(NodeStoreEx* this, NumNodeID id) +{ + Logger* log = App_getLogger(this->app); + Node* node = NULL; + + // check for invalid id 0 + #ifdef BEEGFS_DEBUG + if(!id.value) + { + Logger_log(log, Log_CRITICAL, __func__, "BUG?: Attempt to reference numeric node ID '0'"); + dump_stack(); + } + #endif // BEEGFS_DEBUG + + IGNORE_UNUSED_VARIABLE(log); + + RWLock_readLock(&this->rwLock); // L O C K + + node = NodeTree_find(&this->nodeTree, id); + if (likely(node)) + { // found it + unsigned refs; + Node_get(node); + + (void) refs; + // check for unusually high reference count +#ifdef BEEGFS_DEBUG +# ifdef KERNEL_HAS_KREF_READ + refs = kref_read(&node->references); +# else + refs = atomic_read(&node->references.refcount); +#endif + + if (refs > NODESTORE_WARN_REFNUM) { + NodeString alias; + Node_copyAlias(node, &alias); + Logger_logFormatted(log, Log_CRITICAL, __func__, + "WARNING: Lots of references to node (=> leak?): %s %s; ref count: %d", + Node_getNodeTypeStr(node), alias.buf, refs); // G E T + } +#endif // BEEGFS_DEBUG + } + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return node; +} + +/** + * Note: remember to call releaseNode() + * + * @return NULL if no such node exists + */ +Node* NodeStoreEx_referenceRootNode(NodeStoreEx* this, NodeOrGroup* rootID) +{ + Node* node = NULL; + MirrorBuddyGroupMapper* metaBuddyGroupMapper = App_getMetaBuddyGroupMapper(this->app); + NumNodeID nodeID; + + RWLock_readLock(&this->rwLock); // L O C K + + *rootID = this->_rootOwner; + if (this->_rootOwner.isGroup) + nodeID.value = MirrorBuddyGroupMapper_getPrimaryTargetID(metaBuddyGroupMapper, + this->_rootOwner.node.value); + else + nodeID = this->_rootOwner.node; + + node = NodeTree_find(&this->nodeTree, nodeID); + if(likely(node) ) + Node_get(node); + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return node; +} + +/** + * This is a helper to have only one call for the typical targetMapper->getNodeID() and following + * referenceNode() calls. + * + * Note: remember to call releaseNode(). + * + * @param targetMapper where to resolve the given targetID + * @param outErr will be set to FhgfsOpsErr_UNKNOWNNODE, _UNKNOWNTARGET, _SUCCESS (may be NULL) + * @return NULL if targetID is not mapped or if the mapped node does not exist in the store. + */ +Node* NodeStoreEx_referenceNodeByTargetID(NodeStoreEx* this, uint16_t targetID, + TargetMapper* targetMapper, FhgfsOpsErr* outErr) +{ + NumNodeID nodeID; + Node* node; + + nodeID = TargetMapper_getNodeID(targetMapper, targetID); + if(!nodeID.value) + { + SAFE_ASSIGN(outErr, FhgfsOpsErr_UNKNOWNTARGET); + return NULL; + } + + node = NodeStoreEx_referenceNode(this, nodeID); + + if(!node) + { + SAFE_ASSIGN(outErr, FhgfsOpsErr_UNKNOWNNODE); + return NULL; + } + + SAFE_ASSIGN(outErr, FhgfsOpsErr_SUCCESS); + return node; +} + +/** + * @return true if node existed as active node + */ +bool NodeStoreEx_deleteNode(NodeStoreEx* this, NumNodeID nodeID) +{ + const char* logContext = __func__; + Logger* log = App_getLogger(this->app); + + bool nodeWasActive; + + #ifdef BEEGFS_DEBUG + if(unlikely(!nodeID.value) ) // should never happen + Logger_logFormatted(log, Log_CRITICAL, logContext, "Called with invalid node ID '0'"); + #endif // BEEGFS_DEBUG + + IGNORE_UNUSED_VARIABLE(logContext); + IGNORE_UNUSED_VARIABLE(log); + + RWLock_writeLock(&this->rwLock); // L O C K + + nodeWasActive = NodeTree_erase(&this->nodeTree, nodeID); + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + return nodeWasActive; +} + +unsigned NodeStoreEx_getSize(NodeStoreEx* this) +{ + unsigned nodesSize; + + RWLock_readLock(&this->rwLock); // L O C K + + nodesSize = this->nodeTree.size; + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return nodesSize; +} + +/** + * This is used to iterate over all stored nodes. + * Start with this and then use referenceNextNode() until it returns NULL. + * + * Note: remember to call releaseNode() + * + * @return can be NULL + */ +Node* NodeStoreEx_referenceFirstNode(NodeStoreEx* this) +{ + NodeTreeIter iter; + Node* resultNode = NULL; + + RWLock_readLock(&this->rwLock); // L O C K + + NodeTreeIter_init(&iter, &this->nodeTree); + if (!NodeTreeIter_end(&iter) ) + { + resultNode = NodeTreeIter_value(&iter); + Node_get(resultNode); + } + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return resultNode; +} + +/** + * Note: remember to call releaseNode() + * + * @return NULL if nodeID was the last node + */ +Node* NodeStoreEx_referenceNextNodeAndReleaseOld(NodeStoreEx* this, Node* oldNode) +{ + Node* result = NULL; + + RWLock_readLock(&this->rwLock); // L O C K + + result = NodeTree_getNext(&this->nodeTree, oldNode); + if (result) + Node_get(result); + + Node_put(oldNode); + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return result; +} + +/** + * @return 0 if no root node is known + */ +NodeOrGroup NodeStoreEx_getRootOwner(NodeStoreEx* this) +{ + NodeOrGroup owner; + + RWLock_readLock(&this->rwLock); // L O C K + + owner = this->_rootOwner; + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + return owner; +} + +/** + * Set internal root node ID. + * + * @return false if the new ID was rejected (e.g. because we already had an id set and + * ignoreExistingRoot was false). + */ +bool NodeStoreEx_setRootOwner(NodeStoreEx* this, NodeOrGroup owner, + bool ignoreExistingRoot) +{ + bool setRootRes = true; + + // don't allow invalid id 0 (if not forced to do so) + if(!owner.group && !ignoreExistingRoot) + return false; + + RWLock_writeLock(&this->rwLock); // L O C K + + if (!NodeOrGroup_valid(this->_rootOwner)) + { // rootID empty => set the new root + this->_rootOwner = owner; + } + else if (!ignoreExistingRoot) + { // root defined already, reject new root + setRootRes = false; + } + else + { // root defined already, but shall be ignored + this->_rootOwner = owner; + } + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + return setRootRes; +} + +/** + * Waits for the first node that is added to the store. + * + * @return true when a new node was added to the store before the timeout expired + */ +bool NodeStoreEx_waitForFirstNode(NodeStoreEx* this, int waitTimeoutMS) +{ + bool retVal = false; + struct completion cond; + + RWLock_readLock(&this->rwLock); // L O C K + + retVal = this->nodeTree.size > 0; + + RWLock_readUnlock(&this->rwLock); // U N L O C K + + if(retVal) + return retVal; + + RWLock_writeLock(&this->rwLock); // L O C K + + WARN_ON(this->newNodeAppeared); + init_completion(&cond); + this->newNodeAppeared = &cond; + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + /* may time out or not, we don't care. activeCount is what's important */ + wait_for_completion_timeout(&cond, TimeTk_msToJiffiesSchedulable(waitTimeoutMS) ); + + RWLock_writeLock(&this->rwLock); // L O C K + + this->newNodeAppeared = NULL; + retVal = this->nodeTree.size > 0; + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + return retVal; +} + + +/** + * @param masterList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @param appLocalNode just what you get from app->getLocalNode(), to determine NIC capabilities + */ +void NodeStoreEx_syncNodes(NodeStoreEx* this, NodeList* masterList, NumNodeIDList* outAddedIDs, + NumNodeIDList* outRemovedIDs, Node* appLocalNode) +{ + // Note: We have two phases here: + // Phase 1 (locked): Identify added/removed nodes. + // Phase 2 (unlocked): Add/remove nodes from store. + // This separation is required to not break compatibility with virtual overwritten add/remove + // methods in derived classes (e.g. fhgfs_mgmtd). + + + // P H A S E 1 (Identify added/removed nodes.) + + NodeTreeIter activeIter; + NodeListIter masterIter; + + NumNodeIDListIter removedIDsIter; + NodeList addLaterNodes; // nodes to be added in phase 2 + NodeListIter addLaterIter; + + NicListCapabilities localNicCaps; + + NodeList_init(&addLaterNodes); + + + RWLock_writeLock(&this->rwLock); // L O C K + + NodeTreeIter_init(&activeIter, &this->nodeTree); + NodeListIter_init(&masterIter, masterList); + + while(!NodeTreeIter_end(&activeIter) && !NodeListIter_end(&masterIter) ) + { + Node* active = NodeTreeIter_value(&activeIter); + NumNodeID currentActive = Node_getNumID(active); + NumNodeID currentMaster = Node_getNumID(NodeListIter_value(&masterIter) ); + + if(currentMaster.value < currentActive.value) + { // currentMaster is added + NumNodeIDList_append(outAddedIDs, currentMaster); + NodeList_append(&addLaterNodes, NodeListIter_value(&masterIter) ); + + NodeList_removeHead(masterList); + NodeListIter_init(&masterIter, masterList); + } + else + if(currentActive.value < currentMaster.value) + { // currentActive is removed + NumNodeIDList_append(outRemovedIDs, currentActive); + NodeTreeIter_next(&activeIter); + } + else + { // node unchanged + NodeList_append(&addLaterNodes, NodeListIter_value(&masterIter) ); + + NodeTreeIter_next(&activeIter); + + NodeList_removeHead(masterList); + NodeListIter_init(&masterIter, masterList); + } + } + + // remaining masterList nodes are added + while(!NodeListIter_end(&masterIter) ) + { + NumNodeID currentMaster = Node_getNumID(NodeListIter_value(&masterIter) ); + + NumNodeIDList_append(outAddedIDs, currentMaster); + NodeList_append(&addLaterNodes, NodeListIter_value(&masterIter) ); + + NodeList_removeHead(masterList); + NodeListIter_init(&masterIter, masterList); + } + + // remaining active nodes are removed + for(; !NodeTreeIter_end(&activeIter); NodeTreeIter_next(&activeIter) ) + { + Node* active = NodeTreeIter_value(&activeIter); + + NumNodeIDList_append(outRemovedIDs, Node_getNumID(active) ); + } + + RWLock_writeUnlock(&this->rwLock); // U N L O C K + + + // P H A S E 2 (Add/remove nodes from store.) + + // remove nodes + NumNodeIDListIter_init(&removedIDsIter, outRemovedIDs); + + while(!NumNodeIDListIter_end(&removedIDsIter) ) + { + NumNodeID nodeID = NumNodeIDListIter_value(&removedIDsIter); + NumNodeIDListIter_next(&removedIDsIter); // (removal invalidates iter) + + NodeStoreEx_deleteNode(this, nodeID); + } + + // set local nic capabilities + if(appLocalNode) + { + NodeConnPool* connPool = Node_getConnPool(appLocalNode); + NodeConnPool_lock(connPool); + NIC_supportedCapabilities(NodeConnPool_getNicListLocked(connPool), &localNicCaps); + NodeConnPool_unlock(connPool); + } + + // add nodes + NodeListIter_init(&addLaterIter, &addLaterNodes); + + for(; !NodeListIter_end(&addLaterIter); NodeListIter_next(&addLaterIter) ) + { + Node* node = NodeListIter_value(&addLaterIter); + + if(appLocalNode) + NodeConnPool_setLocalNicCaps(Node_getConnPool(node), &localNicCaps); + + NodeStoreEx_addOrUpdateNode(this, &node); + } + + NodeList_uninit(&addLaterNodes); +} + +/** + * Take special actions based on version of a (typically new) node, e.g. compat flags deactivation. + * + * Note: Caller must hold lock. + */ +void __NodeStoreEx_handleNodeVersion(NodeStoreEx* this, Node* node) +{ + // nothing to be done here currently +} diff --git a/client_module/source/nodes/NodeStoreEx.h b/client_module/source/nodes/NodeStoreEx.h new file mode 100644 index 0000000..f14fd51 --- /dev/null +++ b/client_module/source/nodes/NodeStoreEx.h @@ -0,0 +1,82 @@ +#ifndef NODESTOREEX_H_ +#define NODESTOREEX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +struct NodeStoreEx; +typedef struct NodeStoreEx NodeStoreEx; + + +extern void NodeStoreEx_init(NodeStoreEx* this, App* app, NodeType storeType); +extern NodeStoreEx* NodeStoreEx_construct(App* app, NodeType storeType); +extern void NodeStoreEx_uninit(NodeStoreEx* this); +extern void NodeStoreEx_destruct(NodeStoreEx* this); + +extern bool NodeStoreEx_addOrUpdateNode(NodeStoreEx* this, Node** node); +extern Node* NodeStoreEx_referenceNode(NodeStoreEx* this, NumNodeID id); +extern Node* NodeStoreEx_referenceRootNode(NodeStoreEx* this, NodeOrGroup* rootID); +extern Node* NodeStoreEx_referenceNodeByTargetID(NodeStoreEx* this, uint16_t targetID, + TargetMapper* targetMapper, FhgfsOpsErr* outErr); +extern bool NodeStoreEx_deleteNode(NodeStoreEx* this, NumNodeID nodeID); +extern unsigned NodeStoreEx_getSize(NodeStoreEx* this); + +extern Node* NodeStoreEx_referenceFirstNode(NodeStoreEx* this); +extern Node* NodeStoreEx_referenceNextNode(NodeStoreEx* this, NumNodeID nodeID); +extern Node* NodeStoreEx_referenceNextNodeAndReleaseOld(NodeStoreEx* this, Node* oldNode); + +extern NodeOrGroup NodeStoreEx_getRootOwner(NodeStoreEx* this); +extern bool NodeStoreEx_setRootOwner(NodeStoreEx* this, NodeOrGroup owner, + bool ignoreExistingRoot); + +extern bool NodeStoreEx_waitForFirstNode(NodeStoreEx* this, int waitTimeoutMS); + +extern void NodeStoreEx_syncNodes(NodeStoreEx* this, NodeList* masterList, NumNodeIDList* outAddedIDs, + NumNodeIDList* outRemovedIDs, Node* localNode); + + +extern void __NodeStoreEx_handleNodeVersion(NodeStoreEx* this, Node* node); + + +// getters & setters +static inline NodeType NodeStoreEx_getStoreType(NodeStoreEx* this); + + +/** + * This is the corresponding class for NodeStoreServers in userspace. + */ +struct NodeStoreEx +{ + App* app; + + RWLock rwLock; + struct completion* newNodeAppeared; /* signalled when nodes are added (NULL without waiters) */ + + NodeTree nodeTree; + + NodeOrGroup _rootOwner; + + NodeType storeType; // will be applied to nodes on addOrUpdate() +}; + +NodeType NodeStoreEx_getStoreType(NodeStoreEx* this) +{ + return this->storeType; +} + +#endif /*NODESTOREEX_H_*/ diff --git a/client_module/source/os/OsCompat.c b/client_module/source/os/OsCompat.c new file mode 100644 index 0000000..4c20b3b --- /dev/null +++ b/client_module/source/os/OsCompat.c @@ -0,0 +1,527 @@ +/* + * Compatibility functions for older Linux versions + */ + +#include // for old sles10 kernels, which forgot to include it in backing-dev.h +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifndef KERNEL_HAS_MEMDUP_USER + /** + * memdup_user - duplicate memory region from user space + * + * @src: source address in user space + * @len: number of bytes to copy + * + * Returns an ERR_PTR() on failure. + */ + void *memdup_user(const void __user *src, size_t len) + { + void *p; + + /* + * Always use GFP_KERNEL, since copy_from_user() can sleep and + * cause pagefault, which makes it pointless to use GFP_NOFS + * or GFP_ATOMIC. + */ + p = kmalloc(len, GFP_KERNEL); + if (!p) + return ERR_PTR(-ENOMEM); + + if (copy_from_user(p, src, len)) { + kfree(p); + return ERR_PTR(-EFAULT); + } + + return p; + } +#endif // memdup_user, LINUX_VERSION_CODE < KERNEL_VERSION(2,6,30) + + +#if defined(KERNEL_HAS_SB_BDI) && !defined(KERNEL_HAS_BDI_SETUP_AND_REGISTER) && \ + !defined(KERNEL_HAS_SUPER_SETUP_BDI_NAME) + /* + * For use from filesystems to quickly init and register a bdi associated + * with dirty writeback + */ + int bdi_setup_and_register(struct backing_dev_info *bdi, char *name, + unsigned int cap) + { + static atomic_long_t fhgfs_bdiSeq = ATOMIC_LONG_INIT(0); + char tmp[32]; + int err; + + bdi->name = name; + bdi->capabilities = cap; + err = bdi_init(bdi); + if (err) + return err; + + sprintf(tmp, "%.28s%s", name, "-%d"); + err = bdi_register(bdi, NULL, tmp, atomic_long_inc_return(&fhgfs_bdiSeq)); + if (err) { + bdi_destroy(bdi); + return err; + } + + return 0; + } +#endif + + + +/* NOTE: We can't do a feature detection for find_get_pages_tag(), as + * this function is in all headers of all supported kernel versions. + * However, it is only _exported_ since 2.6.22 and also only + * exported in RHEL >=5.10. */ + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + /** + * find_get_pages_tag - find and return pages that match @tag + * @mapping: the address_space to search + * @index: the starting page index + * @tag: the tag index + * @nr_pages: the maximum number of pages + * @pages: where the resulting pages are placed + * + * Like find_get_pages, except we only return pages which are tagged with + * @tag. We update @index to index the next page for the traversal. + */ + unsigned find_get_pages_tag(struct address_space *mapping, pgoff_t *index, + int tag, unsigned int nr_pages, struct page **pages) + { + unsigned int i; + unsigned int ret; + + read_lock_irq(&mapping->tree_lock); + ret = radix_tree_gang_lookup_tag(&mapping->page_tree, + (void **)pages, *index, nr_pages, tag); + for (i = 0; i < ret; i++) + page_cache_get(pages[i]); + if (ret) + *index = pages[ret - 1]->index + 1; + read_unlock_irq(&mapping->tree_lock); + return ret; + } +#endif // find_get_pages_tag() for <2.6.22 + + +#ifndef KERNEL_HAS_D_MAKE_ROOT + +/** + * This is the former d_alloc_root with an additional iput on error. + */ +struct dentry *d_make_root(struct inode *root_inode) +{ + struct dentry* allocRes = d_alloc_root(root_inode); + if(!allocRes) + iput(root_inode); + + return allocRes; +} +#endif + +#ifndef KERNEL_HAS_D_MATERIALISE_UNIQUE +/** + * d_materialise_unique() was merged into d_splice_alias() in linux-3.19 + */ +struct dentry* d_materialise_unique(struct dentry *dentry, struct inode *inode) +{ + return d_splice_alias(inode, dentry); +} +#endif // KERNEL_HAS_D_MATERIALISE_UNIQUE + +/** + * Note: Call this once during module init (and remember to call kmem_cache_destroy() ) + */ +#if defined(KERNEL_HAS_KMEMCACHE_CACHE_FLAGS_CTOR) +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(void* initObj, struct kmem_cache* cache, unsigned long flags) ) +#elif defined(KERNEL_HAS_KMEMCACHE_CACHE_CTOR) +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(struct kmem_cache* cache, void* initObj) ) +#else +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(void* initObj) ) +#endif // LINUX_VERSION_CODE +{ + struct kmem_cache* cache; + + #if defined(KERNEL_HAS_SLAB_MEM_SPREAD) + unsigned long cacheFlags = SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD; + #else + unsigned long cacheFlags = SLAB_RECLAIM_ACCOUNT; + #endif +#if defined(KERNEL_HAS_KMEMCACHE_DTOR) + cache = kmem_cache_create(cacheName, cacheSize, 0, cacheFlags, initFuncPtr, NULL); +#else + cache = kmem_cache_create(cacheName, cacheSize, 0, cacheFlags, initFuncPtr); +#endif // LINUX_VERSION_CODE + + + return cache; +} + +#ifndef rbtree_postorder_for_each_entry_safe +static struct rb_node* rb_left_deepest_node(const struct rb_node* node) +{ + for (;;) + { + if (node->rb_left) + node = node->rb_left; + else + if (node->rb_right) + node = node->rb_right; + else + return (struct rb_node*) node; + } +} + +struct rb_node* rb_next_postorder(const struct rb_node* node) +{ + const struct rb_node *parent; + + if (!node) + return NULL; + + parent = rb_parent(node); + + /* If we're sitting on node, we've already seen our children */ + if (parent && node == parent->rb_left && parent->rb_right) + { + /* If we are the parent's left node, go to the parent's right + * node then all the way down to the left */ + return rb_left_deepest_node(parent->rb_right); + } + else + /* Otherwise we are the parent's right node, and the parent + * should be next */ + return (struct rb_node*) parent; +} + +struct rb_node* rb_first_postorder(const struct rb_root* root) +{ + if (!root->rb_node) + return NULL; + + return rb_left_deepest_node(root->rb_node); +} +#endif + +#ifdef KERNEL_HAS_GENERIC_WRITE_CHECKS_ITER +int os_generic_write_checks(struct file* filp, loff_t* offset, size_t* size, int isblk) +{ + struct iovec iov = { 0, *size }; + struct iov_iter iter; + ssize_t checkRes; + struct kiocb iocb; + + iov_iter_init(&iter, WRITE, &iov, 1, *size); + init_sync_kiocb(&iocb, filp); + iocb.ki_pos = *offset; + + checkRes = generic_write_checks(&iocb, &iter); + if(checkRes < 0) + return checkRes; + + *offset = iocb.ki_pos; + *size = iter.count; + + return 0; +} +#endif + +#ifndef KERNEL_HAS_HAVE_SUBMOUNTS +/** + * enum d_walk_ret - action to talke during tree walk + * @D_WALK_CONTINUE: contrinue walk + * @D_WALK_QUIT: quit walk + * @D_WALK_NORETRY: quit when retry is needed + * @D_WALK_SKIP: skip this dentry and its children + */ +enum d_walk_ret { + D_WALK_CONTINUE, + D_WALK_QUIT, + D_WALK_NORETRY, + D_WALK_SKIP, +}; + +/* + * Search for at least 1 mount point in the dentry's subdirs. + * We descend to the next level whenever the d_subdirs + * list is non-empty and continue searching. + */ + +static enum d_walk_ret check_mount(void *data, struct dentry *dentry) +{ + int *ret = data; + if (d_mountpoint(dentry)) { + *ret = 1; + return D_WALK_QUIT; + } + return D_WALK_CONTINUE; +} + +#if defined(KERNEL_HAS_DENTRY_SUBDIRS) +/** + * d_walk - walk the dentry tree + * @parent: start of walk + * @data: data passed to @enter() and @finish() + * @enter: callback when first entering the dentry + * @finish: callback when successfully finished the walk + * + * The @enter() and @finish() callbacks are called with d_lock held. + */ +static void d_walk(struct dentry *parent, void *data, + enum d_walk_ret (*enter)(void *, struct dentry *), + void (*finish)(void *)) +{ + struct dentry *this_parent; + struct list_head *next; + unsigned seq = 0; + enum d_walk_ret ret; + bool retry = true; + +again: + read_seqbegin_or_lock(&rename_lock, &seq); + this_parent = parent; + spin_lock(&this_parent->d_lock); + + ret = enter(data, this_parent); + switch (ret) { + case D_WALK_CONTINUE: + break; + case D_WALK_QUIT: + case D_WALK_SKIP: + goto out_unlock; + case D_WALK_NORETRY: + retry = false; + break; + } +repeat: + next = this_parent->d_subdirs.next; +resume: + while (next != &this_parent->d_subdirs) { + struct list_head *tmp = next; + struct dentry *dentry = list_entry(tmp, struct dentry, d_child); + next = tmp->next; + + if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR)) + continue; + + spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); + + ret = enter(data, dentry); + switch (ret) { + case D_WALK_CONTINUE: + break; + case D_WALK_QUIT: + spin_unlock(&dentry->d_lock); + goto out_unlock; + case D_WALK_NORETRY: + retry = false; + break; + case D_WALK_SKIP: + spin_unlock(&dentry->d_lock); + continue; + } + + if (!list_empty(&dentry->d_subdirs)) { + spin_unlock(&this_parent->d_lock); +#if defined(KERNEL_SPIN_RELEASE_HAS_3_ARGUMENTS) + spin_release(&dentry->d_lock.dep_map, 1, _RET_IP_); +#else + spin_release(&dentry->d_lock.dep_map, _RET_IP_); +#endif + this_parent = dentry; + spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_); + goto repeat; + } + spin_unlock(&dentry->d_lock); + } + /* + * All done at this level ... ascend and resume the search. + */ + rcu_read_lock(); +ascend: + if (this_parent != parent) { + struct dentry *child = this_parent; + this_parent = child->d_parent; + + spin_unlock(&child->d_lock); + spin_lock(&this_parent->d_lock); + + /* might go back up the wrong parent if we have had a rename. */ + if (need_seqretry(&rename_lock, seq)) + goto rename_retry; + /* go into the first sibling still alive */ + do { + next = child->d_child.next; + if (next == &this_parent->d_subdirs) + goto ascend; + child = list_entry(next, struct dentry, d_child); + } while (unlikely(child->d_flags & DCACHE_DENTRY_KILLED)); + rcu_read_unlock(); + goto resume; + } + if (need_seqretry(&rename_lock, seq)) + goto rename_retry; + rcu_read_unlock(); + if (finish) + finish(data); + +out_unlock: + spin_unlock(&this_parent->d_lock); + done_seqretry(&rename_lock, seq); + return; + +rename_retry: + spin_unlock(&this_parent->d_lock); + rcu_read_unlock(); + BUG_ON(seq & 1); + if (!retry) + return; + seq = 1; + goto again; +} + +#else + +/** + * d_walk - walk the dentry tree + * @parent: start of walk + * @data: data passed to @enter() and @finish() + * @enter: callback when first entering the dentry + * + * The @enter() callbacks are called with d_lock held. + */ +static void d_walk(struct dentry *parent, void *data, + enum d_walk_ret (*enter)(void *, struct dentry *)) +{ + struct dentry *this_parent, *dentry; + unsigned seq = 0; + enum d_walk_ret ret; + bool retry = true; + +again: + read_seqbegin_or_lock(&rename_lock, &seq); + this_parent = parent; + spin_lock(&this_parent->d_lock); + + ret = enter(data, this_parent); + switch (ret) { + case D_WALK_CONTINUE: + break; + case D_WALK_QUIT: + case D_WALK_SKIP: + goto out_unlock; + case D_WALK_NORETRY: + retry = false; + break; + } +repeat: + dentry = d_first_child(this_parent); +resume: + hlist_for_each_entry_from(dentry, d_sib) { + if (unlikely(dentry->d_flags & DCACHE_DENTRY_CURSOR)) + continue; + + spin_lock_nested(&dentry->d_lock, DENTRY_D_LOCK_NESTED); + + ret = enter(data, dentry); + switch (ret) { + case D_WALK_CONTINUE: + break; + case D_WALK_QUIT: + spin_unlock(&dentry->d_lock); + goto out_unlock; + case D_WALK_NORETRY: + retry = false; + break; + case D_WALK_SKIP: + spin_unlock(&dentry->d_lock); + continue; + } + + if (!hlist_empty(&dentry->d_children)) { + spin_unlock(&this_parent->d_lock); + spin_release(&dentry->d_lock.dep_map, _RET_IP_); + this_parent = dentry; + spin_acquire(&this_parent->d_lock.dep_map, 0, 1, _RET_IP_); + goto repeat; + } + spin_unlock(&dentry->d_lock); + } + /* + * All done at this level ... ascend and resume the search. + */ + rcu_read_lock(); +ascend: + if (this_parent != parent) { + dentry = this_parent; + this_parent = dentry->d_parent; + + spin_unlock(&dentry->d_lock); + spin_lock(&this_parent->d_lock); + + /* might go back up the wrong parent if we have had a rename. */ + if (need_seqretry(&rename_lock, seq)) + goto rename_retry; + /* go into the first sibling still alive */ + hlist_for_each_entry_continue(dentry, d_sib) { + if (likely(!(dentry->d_flags & DCACHE_DENTRY_KILLED))) { + rcu_read_unlock(); + goto resume; + } + } + goto ascend; + } + if (need_seqretry(&rename_lock, seq)) + goto rename_retry; + rcu_read_unlock(); + +out_unlock: + spin_unlock(&this_parent->d_lock); + done_seqretry(&rename_lock, seq); + return; + +rename_retry: + spin_unlock(&this_parent->d_lock); + rcu_read_unlock(); + BUG_ON(seq & 1); + if (!retry) + return; + seq = 1; + goto again; +} + +#endif + +/** + * have_submounts - check for mounts over a dentry + * @parent: dentry to check. + * + * Return true if the parent or its subdirectories contain + * a mount point + */ +int have_submounts(struct dentry *parent) +{ + int ret = 0; + +#if defined(KERNEL_HAS_DENTRY_SUBDIRS) + d_walk(parent, &ret, check_mount, NULL); +#else + d_walk(parent, &ret, check_mount); +#endif + + return ret; +} +#endif diff --git a/client_module/source/os/OsCompat.h b/client_module/source/os/OsCompat.h new file mode 100644 index 0000000..37ff634 --- /dev/null +++ b/client_module/source/os/OsCompat.h @@ -0,0 +1,396 @@ +/* + * Compatibility functions for older Linux versions + */ + +#ifndef OSCOMPAT_H_ +#define OSCOMPAT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + + +#ifndef KERNEL_HAS_MEMDUP_USER + extern void *memdup_user(const void __user *src, size_t len); +#endif + +#ifndef KERNEL_HAS_D_MAKE_ROOT + extern struct dentry *d_make_root(struct inode *root_inode); +#endif + +#if defined(KERNEL_HAS_SB_BDI) && !defined(KERNEL_HAS_BDI_SETUP_AND_REGISTER) + extern int bdi_setup_and_register(struct backing_dev_info *bdi, char *name, unsigned int cap); +#endif + +#ifndef KERNEL_HAS_HAVE_SUBMOUNTS +extern int have_submounts(struct dentry *parent); +#endif + +/* + * PG_error and SetPageError() have been deprecated and removed in Linux 6.12. + * We now use mapping_set_error() to record writeback errors at the address_space level. + * + * This ensures compatibility with kernels >= 4.19 and aligns with the new writeback + * error tracking model using errseq_t (see LWN: https://lwn.net/Articles/724307/). + * + * BeeGFS compatibility: + * - Buffered mode paths already use filemap_fdatawait(), which calls filemap_check_errors(). + * - Native mode uses file_write_and_wait_range(), which calls file_check_and_advance_wb_err(). + */ + +/** + * fhgfs_set_wb_error - Record a writeback error at the mapping level + * + * Replaces SetPageError(); safe across all supported kernels. + * + * @page: the page associated with the mapping + * @err: the error code + */ +static inline void fhgfs_set_wb_error(struct page *page, int err) +{ + if (page && page->mapping && err) + mapping_set_error(page->mapping, err); +} + +/** + * generic_permission() compatibility function + * + * NOTE: Only kernels > 2.6.32 do have inode->i_op->check_acl, but as we do not + * support it anyway for now, we do not need a complete kernel version check for it. + * Also, in order to skip useless pointer references we just pass NULL here. + */ +static inline int os_generic_permission(struct inode *inode, int mask) +{ + #ifdef KERNEL_HAS_GENERIC_PERMISSION_2 + return generic_permission(inode, mask); + #elif defined(KERNEL_HAS_GENERIC_PERMISSION_4) + return generic_permission(inode, mask, 0, NULL); + #elif defined(KERNEL_HAS_IDMAPPED_MOUNTS) + return generic_permission(&nop_mnt_idmap, inode, mask); + #elif defined(KERNEL_HAS_USER_NS_MOUNTS) + return generic_permission(&init_user_ns, inode, mask); + #else + return generic_permission(inode, mask, NULL); + #endif +} + +#if defined(KERNEL_HAS_GENERIC_FILLATTR_REQUEST_MASK) +static inline void os_generic_fillattr(struct inode *inode, struct kstat *kstat, u32 request_mask) +#else +static inline void os_generic_fillattr(struct inode *inode, struct kstat *kstat) +#endif +{ + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + #if defined(KERNEL_HAS_GENERIC_FILLATTR_REQUEST_MASK) + generic_fillattr(&nop_mnt_idmap, request_mask, inode, kstat); + #else + generic_fillattr(&nop_mnt_idmap, inode, kstat); + #endif // KERNEL_HAS_GENERIC_FILLATTR_REQUEST_MASK + #elif defined(KERNEL_HAS_USER_NS_MOUNTS) + generic_fillattr(&init_user_ns, inode, kstat); + #else + generic_fillattr(inode, kstat); + #endif +} + +#ifdef KERNEL_HAS_SETATTR_PREPARE +static inline int os_setattr_prepare(struct dentry *dentry, struct iattr *attr) +{ + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + return setattr_prepare(&nop_mnt_idmap, dentry, attr); + #elif defined(KERNEL_HAS_USER_NS_MOUNTS) + return setattr_prepare(&init_user_ns, dentry, attr); + #else + return setattr_prepare(dentry, attr); + #endif +} +#endif // KERNEL_HAS_SETATTR_PREPARE + +static inline bool os_inode_owner_or_capable(const struct inode *inode) +{ + #if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + return inode_owner_or_capable(&nop_mnt_idmap, inode); + #elif defined(KERNEL_HAS_USER_NS_MOUNTS) + return inode_owner_or_capable(&init_user_ns, inode); + #else + return inode_owner_or_capable(inode); + #endif +} + +#ifndef KERNEL_HAS_D_MATERIALISE_UNIQUE +extern struct dentry* d_materialise_unique(struct dentry *dentry, struct inode *inode); +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) +/** + * Taken from ext3 dir.c. is_compat_task() does work for all kernels, although it was already there. + * So we are conservativ and only allow it for recent kernels. + */ +static inline int is_32bit_api(void) +{ +#ifdef CONFIG_COMPAT +# ifdef in_compat_syscall + return in_compat_syscall(); +# else + return is_compat_task(); +# endif +#else + return (BITS_PER_LONG == 32); +#endif +} +#else +static inline int is_32bit_api(void) +{ + return (BITS_PER_LONG == 32); +} +#endif // LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,32) + +#ifndef KERNEL_HAS_I_UID_READ +static inline uid_t i_uid_read(const struct inode *inode) +{ + return inode->i_uid; +} + +static inline gid_t i_gid_read(const struct inode *inode) +{ + return inode->i_gid; +} + +static inline void i_uid_write(struct inode *inode, uid_t uid) +{ + inode->i_uid = uid; +} + +static inline void i_gid_write(struct inode *inode, gid_t gid) +{ + inode->i_gid = gid; +} + +#endif // KERNEL_HAS_I_UID_READ + + +#if defined(KERNEL_HAS_KMEMCACHE_CACHE_FLAGS_CTOR) +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(void* initObj, struct kmem_cache* cache, unsigned long flags) ); +#elif defined(KERNEL_HAS_KMEMCACHE_CACHE_CTOR) +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(struct kmem_cache* cache, void* initObj) ); +#else +struct kmem_cache* OsCompat_initKmemCache(const char* cacheName, size_t cacheSize, + void initFuncPtr(void* initObj) ); +#endif // LINUX_VERSION_CODE + + +// added to 3.13, backported to -stable +#ifndef list_next_entry +/** + * list_next_entry - get the next element in list + * @pos: the type * to cursor + * @member: the name of the list_struct within the struct. + */ +#define list_next_entry(pos, member) \ + list_entry((pos)->member.next, typeof(*(pos)), member) +#endif + + +#ifndef list_first_entry +/** + * list_first_entry - get the first element from a list + * @ptr: the list head to take the element from. + * @type: the type of the struct this is embedded in. + * @member: the name of the list_struct within the struct. + * + * Note, that list is expected to be not empty. + */ +#define list_first_entry(ptr, type, member) \ + list_entry((ptr)->next, type, member) +#endif // list_first_entry + + +static inline struct posix_acl* os_posix_acl_from_xattr(const void* value, size_t size) +{ +#ifndef KERNEL_HAS_POSIX_ACL_XATTR_USERNS_ARG + return posix_acl_from_xattr(value, size); +#else + return posix_acl_from_xattr(&init_user_ns, value, size); +#endif +} + +static inline int os_posix_acl_to_xattr(const struct posix_acl* acl, void* buffer, size_t size) +{ +#ifndef KERNEL_HAS_POSIX_ACL_XATTR_USERNS_ARG + return posix_acl_to_xattr(acl, buffer, size); +#else + return posix_acl_to_xattr(&init_user_ns, acl, buffer, size); +#endif +} + +#if defined(KERNEL_HAS_SET_ACL) || defined(KERNEL_HAS_SET_ACL_DENTRY) +static inline int os_posix_acl_chmod(struct dentry *dentry, umode_t mode) +{ + +#if defined(KERNEL_HAS_IDMAPPED_MOUNTS) + return posix_acl_chmod(&nop_mnt_idmap, dentry, mode); + +#elif defined(KERNEL_HAS_POSIX_ACL_CHMOD_NS_DENTRY) + return posix_acl_chmod(&init_user_ns, dentry, mode); + +#elif defined(KERNEL_HAS_USER_NS_MOUNTS) + return posix_acl_chmod(&init_user_ns, dentry->d_inode, mode); + +#else + return posix_acl_chmod(dentry->d_inode, mode); +#endif +} +#endif // KERNEL_HAS_SET_ACL || KERNEL_HAS_SET_ACL_DENTRY + +#ifndef KERNEL_HAS_PAGE_ENDIO +static inline void page_endio(struct page *page, int rw, int err) +{ + if (rw == READ) + { + if (!err) + { + SetPageUptodate(page); + } + else + { + ClearPageUptodate(page); + fhgfs_set_wb_error(page, err); + } + + unlock_page(page); + } + else + { /* rw == WRITE */ + if (err) + { + fhgfs_set_wb_error(page, err); + } + + end_page_writeback(page); + } +} +#endif + +#ifndef KERNEL_HAS_GENERIC_WRITE_CHECKS_ITER +# define os_generic_write_checks generic_write_checks +#else +extern int os_generic_write_checks(struct file* filp, loff_t* offset, size_t* size, int isblk); +#endif + +#ifndef rb_entry_safe +#define rb_entry_safe(ptr, type, member) \ + ({ typeof(ptr) ____ptr = (ptr); \ + ____ptr ? rb_entry(____ptr, type, member) : NULL; \ + }) +#endif + +#ifndef rbtree_postorder_for_each_entry_safe +#define rbtree_postorder_for_each_entry_safe(pos, n, root, field) \ + for (pos = rb_entry_safe(rb_first_postorder(root), typeof(*pos), field); \ + pos && ({ n = rb_entry_safe(rb_next_postorder(&pos->field), \ + typeof(*pos), field); 1; }); \ + pos = n) + +extern struct rb_node *rb_first_postorder(const struct rb_root *); +extern struct rb_node *rb_next_postorder(const struct rb_node *); +#endif + +#ifndef KERNEL_HAS_CURRENT_UMASK +#define current_umask() (current->fs->umask) +#endif + +#ifndef XATTR_NAME_POSIX_ACL_ACCESS +# define XATTR_POSIX_ACL_ACCESS "posix_acl_access" +# define XATTR_NAME_POSIX_ACL_ACCESS XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_ACCESS +# define XATTR_POSIX_ACL_DEFAULT "posix_acl_default" +# define XATTR_NAME_POSIX_ACL_DEFAULT XATTR_SYSTEM_PREFIX XATTR_POSIX_ACL_DEFAULT +#endif + +#ifndef KERNEL_HAS_I_MMAP_LOCK +static inline void i_mmap_lock_read(struct address_space* mapping) +{ +#if defined(KERNEL_HAS_I_MMAP_RWSEM) + down_read(&mapping->i_mmap_rwsem); +#elif defined(KERNEL_HAS_I_MMAP_MUTEX) + mutex_lock(&mapping->i_mmap_mutex); +#else + spin_lock(&mapping->i_mmap_lock); +#endif +} + +static inline void i_mmap_unlock_read(struct address_space* mapping) +{ +#if defined(KERNEL_HAS_I_MMAP_RWSEM) + up_read(&mapping->i_mmap_rwsem); +#elif defined(KERNEL_HAS_I_MMAP_MUTEX) + mutex_unlock(&mapping->i_mmap_mutex); +#else + spin_unlock(&mapping->i_mmap_lock); +#endif +} +#endif + +static inline bool beegfs_hasMappings(struct inode* inode) +{ +#if defined(KERNEL_HAS_I_MMAP_RBTREE) + if (!RB_EMPTY_ROOT(&inode->i_mapping->i_mmap)) + return true; +#elif defined(KERNEL_HAS_I_MMAP_CACHED_RBTREE) + if (!RB_EMPTY_ROOT(&inode->i_mapping->i_mmap.rb_root)) + return true; +#else + if (!prio_tree_empty(&inode->i_mapping->i_mmap)) + return true; +#endif + +#ifdef KERNEL_HAS_I_MMAP_NONLINEAR + if (!list_empty(&inode->i_mapping->i_mmap_nonlinear)) + return true; +#endif + + return false; +} + +#ifndef KERNEL_HAS_INODE_LOCK +static inline void os_inode_lock(struct inode* inode) +{ + mutex_lock(&inode->i_mutex); +} + +static inline void os_inode_unlock(struct inode* inode) +{ + mutex_unlock(&inode->i_mutex); +} +#else +static inline void os_inode_lock(struct inode* inode) +{ + inode_lock(inode); +} + +static inline void os_inode_unlock(struct inode* inode) +{ + inode_unlock(inode); +} +#endif + + +#if defined(KERNEL_ACCESS_OK_WANTS_TYPE) +# define os_access_ok(type, addr, size) access_ok(type, addr, size) +#else +# define os_access_ok(type, addr, size) access_ok(addr, size) +#endif + + +#endif /* OSCOMPAT_H_ */ diff --git a/client_module/source/os/OsDeps.c b/client_module/source/os/OsDeps.c new file mode 100644 index 0000000..4c7c2bb --- /dev/null +++ b/client_module/source/os/OsDeps.c @@ -0,0 +1,117 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + + +#ifdef CONFIG_STACKTRACE + #include +#endif + +#define MAX_STACK_TRACE_CHAIN 16 // number of functions to to save in a stack trace + + +#ifdef BEEGFS_DEBUG + +// Significant parts of the kernel code around struct stack_trace are removed +// when CONFIG_ARCH_STACKWALK is set. Code below needs to be rewritten to work +// with newer kernels that have CONFIG_ARCH_STACKWALK enabled. +#if defined CONFIG_STACKTRACE && !defined CONFIG_ARCH_STACKWALK + +/** + * Save a given trace. NOTE: Allocated memory has to be freed later on! + */ +void* os_saveStackTrace(void) +{ + struct stack_trace* trace; + unsigned long *entries; + + trace = kmalloc(sizeof(struct stack_trace), GFP_NOFS); + if (!trace) + return NULL; // out of memory? + + entries = kmalloc(MAX_STACK_TRACE_CHAIN * sizeof(*entries), GFP_NOFS); + if (!entries) + { // out of memory? + kfree(trace); + return NULL; + } + + trace->nr_entries = 0; + trace->max_entries = MAX_STACK_TRACE_CHAIN; + trace->entries = entries; + trace->skip = 1; // cut off ourself, so 1 + + save_stack_trace(trace); + + return trace; +} + +void os_freeStackTrace(void *trace) +{ + struct stack_trace* os_trace = (struct stack_trace*)trace; + + if (!trace) + { // May be NULL, if kmalloc or vmalloc failed + return; + } + + kfree(os_trace->entries); + kfree(os_trace); +} + +/** + * Print a stack trace + * + * @param trace The stack trace to print + * @param spaces Insert 'spaces' white-spaces at the beginning of the line + */ +void os_printStackTrace(void* trace, int spaces) +{ + if (!trace) + { // Maybe NULL, if kmalloc or vmalloc failed + return; + } + + { + struct stack_trace *stack_trace = trace; +#if defined(KERNEL_HAS_PRINT_STACK_TRACE) + print_stack_trace(stack_trace, spaces); +#elif defined(KERNEL_HAS_STACK_TRACE_PRINT) + stack_trace_print(stack_trace->entries, stack_trace->nr_entries, spaces); +#else + (void) stack_trace; +#endif + } +} + + +#else // no CONFIG_STACKTRACE or CONFIG_ARCH_STACKWALK enabled => nothing to do at all + +void* os_saveStackTrace(void) +{ + return NULL; +} + +void os_printStackTrace(void* trace, int spaces) +{ + printk_fhgfs(KERN_INFO, "Kernel without stack trace support!\n"); + return; +} + +void os_freeStackTrace(void* trace) +{ + return; +} + +#endif // CONFIG_STACKTRACE && !CONFIG_ARCH_STACKWALK + +#endif // BEEGFS_DEBUG + + diff --git a/client_module/source/os/OsDeps.h b/client_module/source/os/OsDeps.h new file mode 100644 index 0000000..24cd62a --- /dev/null +++ b/client_module/source/os/OsDeps.h @@ -0,0 +1,71 @@ +#ifndef OPEN_OSDEPS_H_ +#define OPEN_OSDEPS_H_ + +#include + +#include +#include +#include +#include +#include +#include +#include + + +#ifdef BEEGFS_DEBUG + extern void* os_saveStackTrace(void); + extern void os_printStackTrace(void * trace, int spaces); + extern void os_freeStackTrace(void *trace); +#endif // BEEGFS_DEBUG + + +// inliners +static inline void* os_kmalloc(size_t size); +static inline void* os_kzalloc(size_t size); + +static inline int os_strnicmp(const char* s1, const char* s2, size_t n); + + +void* os_kmalloc(size_t size) +{ + void* buf = kmalloc(size, GFP_NOFS); + + if(unlikely(!buf) ) + { + printk(KERN_WARNING BEEGFS_MODULE_NAME_STR ": kmalloc of '%d' bytes failed. Retrying...\n", (int)size); + buf = kmalloc(size, GFP_NOFS | __GFP_NOFAIL); + printk(KERN_WARNING BEEGFS_MODULE_NAME_STR ": kmalloc retry of '%d' bytes succeeded\n", (int)size); + } + + return buf; +} + +void* os_kzalloc(size_t size) +{ + void* buf = kzalloc(size, GFP_NOFS); + + if(unlikely(!buf) ) + { + printk(KERN_WARNING BEEGFS_MODULE_NAME_STR ": kzalloc of '%d' bytes failed. Retrying...\n", (int)size); + buf = kzalloc(size, GFP_NOFS | __GFP_NOFAIL); + printk(KERN_WARNING BEEGFS_MODULE_NAME_STR ": kzalloc retry of '%d' bytes succeeded\n", (int)size); + } + + return buf; +} + +/** + * strncasecmp was broken in the linux kernel pre-3.18. strnicmp was + * implemented correctly in that timeframe. In kernel >= 3.18, strnicmp + * is either a wrapper for strncasecmp or is not defined. + */ +int os_strnicmp(const char *s1, const char *s2, size_t n) +{ + #ifdef KERNEL_HAS_STRNICMP + return strnicmp(s1, s2, n); + #else + return strncasecmp(s1, s2, n); + #endif +} + +#endif /* OPEN_OSDEPS_H_ */ diff --git a/client_module/source/os/OsTypeConversion.h b/client_module/source/os/OsTypeConversion.h new file mode 100644 index 0000000..9f4c104 --- /dev/null +++ b/client_module/source/os/OsTypeConversion.h @@ -0,0 +1,201 @@ +#ifndef OSTYPECONVERSION_INTERNAL_H_ +#define OSTYPECONVERSION_INTERNAL_H_ + +#include +#include +#include +#include + +#include +#if defined(KERNEL_HAS_LINUX_FILELOCK_H) +#include +#endif + +static inline int OsTypeConv_openFlagsOsToFhgfs(int osFlags, bool isPagedMode); +static inline void OsTypeConv_kstatFhgfsToOs(fhgfs_stat* fhgfsStat, struct kstat* kStat); +static inline void OsTypeConv_iattrOsToFhgfs(struct iattr* iAttr, SettableFileAttribs* fhgfsAttr, + int* outValidAttribs); +static inline unsigned OsTypeConv_dirEntryTypeToOS(DirEntryType entryType); +static inline int OsTypeConv_flockTypeToFhgfs(struct file_lock* fileLock); + + +/** + * @param osFlags file open mode flags + * @return OPENFILE_ACCESS_... flags + */ +int OsTypeConv_openFlagsOsToFhgfs(int osFlags, bool isPagedMode) +{ + int fhgfsFlags = 0; + + if(osFlags & O_RDWR) + fhgfsFlags |= OPENFILE_ACCESS_READWRITE; + else + if(osFlags & O_WRONLY) + { + if (!isPagedMode) + fhgfsFlags |= OPENFILE_ACCESS_WRITE; + else + { /* in order to update read-modify-write pages with the storage content we a + * read-write handle */ + fhgfsFlags |= OPENFILE_ACCESS_READWRITE; + } + } + else + fhgfsFlags |= OPENFILE_ACCESS_READ; + + + if(osFlags & O_APPEND) + fhgfsFlags |= OPENFILE_ACCESS_APPEND; + + if(osFlags & O_TRUNC) + fhgfsFlags |= OPENFILE_ACCESS_TRUNC; + + if(osFlags & O_DIRECT) + fhgfsFlags |= OPENFILE_ACCESS_DIRECT; + + if(osFlags & O_SYNC) + fhgfsFlags |= OPENFILE_ACCESS_SYNC; + + + return fhgfsFlags; +} + +/** + * @param kStat unused fields will be set to zero + */ +void OsTypeConv_kstatFhgfsToOs(fhgfs_stat* fhgfsStat, struct kstat* kStat) +{ + memset(kStat, 0, sizeof(*kStat) ); + + kStat->mode = fhgfsStat->mode; + kStat->nlink = fhgfsStat->nlink; + kStat->uid = make_kuid(&init_user_ns, fhgfsStat->uid); + kStat->gid = make_kgid(&init_user_ns, fhgfsStat->gid); + kStat->size = fhgfsStat->size; + kStat->blocks = fhgfsStat->blocks; + kStat->atime.tv_sec = fhgfsStat->atime.tv_sec; + kStat->atime.tv_nsec = fhgfsStat->atime.tv_nsec; + kStat->mtime.tv_sec = fhgfsStat->mtime.tv_sec; + kStat->mtime.tv_nsec = fhgfsStat->mtime.tv_nsec; + kStat->ctime.tv_sec = fhgfsStat->ctime.tv_sec; // attrib change time (not creation time) + kStat->ctime.tv_nsec = fhgfsStat->ctime.tv_nsec; // attrib change time (not creation time) +} + +/** + * Convert kernel iattr to fhgfsAttr. Also update the inode with the new attributes. + */ +void OsTypeConv_iattrOsToFhgfs(struct iattr* iAttr, SettableFileAttribs* fhgfsAttr, + int* outValidAttribs) +{ + Time now; + Time_setToNowReal(&now); + + *outValidAttribs = 0; + + if(iAttr->ia_valid & ATTR_MODE) + { + (*outValidAttribs) |= SETATTR_CHANGE_MODE; + fhgfsAttr->mode = iAttr->ia_mode; + } + + if(iAttr->ia_valid & ATTR_UID) + { + (*outValidAttribs) |= SETATTR_CHANGE_USERID; + fhgfsAttr->userID = from_kuid(&init_user_ns, iAttr->ia_uid); + } + + if(iAttr->ia_valid & ATTR_GID) + { + (*outValidAttribs) |= SETATTR_CHANGE_GROUPID; + fhgfsAttr->groupID = from_kgid(&init_user_ns, iAttr->ia_gid); + } + + if(iAttr->ia_valid & ATTR_MTIME_SET) + { + (*outValidAttribs) |= SETATTR_CHANGE_MODIFICATIONTIME; + fhgfsAttr->modificationTimeSecs = iAttr->ia_mtime.tv_sec; + } + else + if(iAttr->ia_valid & ATTR_MTIME) + { // set mtime to "now" + (*outValidAttribs) |= SETATTR_CHANGE_MODIFICATIONTIME; + fhgfsAttr->modificationTimeSecs = now.tv_sec; + } + + if(iAttr->ia_valid & ATTR_ATIME_SET) + { + (*outValidAttribs) |= SETATTR_CHANGE_LASTACCESSTIME; + fhgfsAttr->lastAccessTimeSecs = iAttr->ia_atime.tv_sec; + } + else + if(iAttr->ia_valid & ATTR_ATIME) + { // set atime to "now" + (*outValidAttribs) |= SETATTR_CHANGE_LASTACCESSTIME; + fhgfsAttr->lastAccessTimeSecs = now.tv_sec; + } +} + +/** + * Convert fhgfs DirEntryType to OS DT_... for readdir()'s filldir. + */ +unsigned OsTypeConv_dirEntryTypeToOS(DirEntryType entryType) +{ + if(DirEntryType_ISDIR(entryType) ) + return DT_DIR; + + if(DirEntryType_ISREGULARFILE(entryType) ) + return DT_REG; + + if(DirEntryType_ISSYMLINK(entryType) ) + return DT_LNK; + + if(DirEntryType_ISBLOCKDEV(entryType) ) + return DT_BLK; + + if(DirEntryType_ISCHARDEV(entryType) ) + return DT_CHR; + + if(DirEntryType_ISFIFO(entryType) ) + return DT_FIFO; + + if(DirEntryType_ISSOCKET(entryType) ) + return DT_SOCK; + + return DT_UNKNOWN; +} + +/** + * Convert the OS F_..LCK lock type flags of an flock operation to fhgfs ENTRYLOCKTYPE_... lock type + * flags. + * + * @ + */ +static inline int OsTypeConv_flockTypeToFhgfs(struct file_lock* fileLock) +{ + int fhgfsLockFlags = 0; + + switch(FhgfsCommon_getFileLockType(fileLock)) + { + case F_RDLCK: + { + fhgfsLockFlags = ENTRYLOCKTYPE_SHARED; + } break; + + case F_WRLCK: + { + fhgfsLockFlags = ENTRYLOCKTYPE_EXCLUSIVE; + } break; + + default: + { + fhgfsLockFlags = ENTRYLOCKTYPE_UNLOCK; + } break; + } + + if(!(FhgfsCommon_getFileLockFlags(fileLock) & FL_SLEEP) ) + fhgfsLockFlags |= ENTRYLOCKTYPE_NOWAIT; + + return fhgfsLockFlags; +} + +#endif /* OSTYPECONVERSION_INTERNAL_H_ */ diff --git a/client_module/source/os/atomic64.c b/client_module/source/os/atomic64.c new file mode 100644 index 0000000..fa4faa8 --- /dev/null +++ b/client_module/source/os/atomic64.c @@ -0,0 +1,215 @@ +#include + +#include // also adds ATOMIC64_INIT if available + + +#ifndef ATOMIC64_INIT // basic test if the kernel already provides atomic64_t + + +/* + * Note: Below is the atomic64.c copied and modified from linux-git, for architectures, which do + * not support native 64-bit spin-locks in hardware. As we need to have compatibility with + * older kernels we had to replace the usage of raw_spin_locks. This is probably + * slower and therefore the in-kernel implementation should be used if available. + */ + + +/* + * Generic implementation of 64-bit atomics using spinlocks, + * useful on processors that don't have 64-bit atomic instructions. + * + * Copyright © 2009 Paul Mackerras, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#include +#include +#include +#include +// #include // disabled as not available in 2.6.16 +// #include // disabled as not available in 2.6.16 + +#include "atomic64.h" // added for fhgfs + +#if 0 // disabled for the simplified fhgfs version +/* + * We use a hashed array of spinlocks to provide exclusive access + * to each atomic64_t variable. Since this is expected to used on + * systems with small numbers of CPUs (<= 4 or so), we use a + * relatively small array of 16 spinlocks to avoid wasting too much + * memory on the spinlock array. + */ +#define NR_LOCKS 16 + +/* + * Ensure each lock is in a separate cacheline. + */ +static union { + spinlock_t lock; + char pad[L1_CACHE_BYTES]; +} atomic64_lock[NR_LOCKS] __cacheline_aligned_in_smp = { + [0 ... (NR_LOCKS - 1)] = { + .lock = __RAW_SPIN_LOCK_UNLOCKED(atomic64_lock.lock), + }, +}; + + +static inline spinlock_t *lock_addr(const atomic64_t *v) +{ + unsigned long addr = (unsigned long) v; + + addr >>= L1_CACHE_SHIFT; + addr ^= (addr >> 8) ^ (addr >> 16); + return &atomic64_lock[addr & (NR_LOCKS - 1)].lock; +} + +#endif + + +/** + * Simplified version for fhgfs + */ +static inline spinlock_t *lock_addr(const atomic64_t *v) +{ + atomic64_t* value = (atomic64_t*) v; + + return &value->lock; +} + +long long atomic64_read(const atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_read); + +void atomic64_set(atomic64_t *v, long long i) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + + spin_lock_irqsave(lock, flags); + v->counter = i; + spin_unlock_irqrestore(lock, flags); +} +// EXPORT_SYMBOL(atomic64_set); + +void atomic64_add(long long a, atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + + spin_lock_irqsave(lock, flags); + v->counter += a; + spin_unlock_irqrestore(lock, flags); +} +// EXPORT_SYMBOL(atomic64_add); + +long long atomic64_add_return(long long a, atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter += a; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_add_return); + +void atomic64_sub(long long a, atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + + spin_lock_irqsave(lock, flags); + v->counter -= a; + spin_unlock_irqrestore(lock, flags); +} +// EXPORT_SYMBOL(atomic64_sub); + +long long atomic64_sub_return(long long a, atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter -= a; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_sub_return); + +long long atomic64_dec_if_positive(atomic64_t *v) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter - 1; + if (val >= 0) + v->counter = val; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_dec_if_positive); + +long long atomic64_cmpxchg(atomic64_t *v, long long o, long long n) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter; + if (val == o) + v->counter = n; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_cmpxchg); + +long long atomic64_xchg(atomic64_t *v, long long new) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + long long val; + + spin_lock_irqsave(lock, flags); + val = v->counter; + v->counter = new; + spin_unlock_irqrestore(lock, flags); + return val; +} +// EXPORT_SYMBOL(atomic64_xchg); + +int atomic64_add_unless(atomic64_t *v, long long a, long long u) +{ + unsigned long flags; + spinlock_t *lock = lock_addr(v); + int ret = 0; + + spin_lock_irqsave(lock, flags); + if (v->counter != u) { + v->counter += a; + ret = 1; + } + spin_unlock_irqrestore(lock, flags); + return ret; +} +// EXPORT_SYMBOL(atomic64_add_unless); + + +#endif // #ifndef ATOMIC64_INIT diff --git a/client_module/source/os/atomic64.h b/client_module/source/os/atomic64.h new file mode 100644 index 0000000..1eb4f8c --- /dev/null +++ b/client_module/source/os/atomic64.h @@ -0,0 +1,69 @@ +#include + +#include // also adds ATOMIC64_INIT if available + + +#ifndef ATOMIC64_INIT // basic test if the kernel already provides atomic64_t + +/* + * Note: Below is the atomic64.c copied from linux-git + */ + + +/* + * Generic implementation of 64-bit atomics using spinlocks, + * useful on processors that don't have 64-bit atomic instructions. + * + * Copyright © 2009 Paul Mackerras, IBM Corp. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ +#ifndef _ASM_GENERIC_ATOMIC64_H +#define _ASM_GENERIC_ATOMIC64_H + +typedef struct { + long long counter; + spinlock_t lock; // added for fhgfs +} atomic64_t; + +// #define ATOMIC64_INIT(i) { (i) } // disabled for fhgfs + +static inline void atomic_init(atomic64_t *atomic, uint64_t value); // added for fhgfs + +extern long long atomic64_read(const atomic64_t *v); +extern void atomic64_set(atomic64_t *v, long long i); +extern void atomic64_add(long long a, atomic64_t *v); +extern long long atomic64_add_return(long long a, atomic64_t *v); +extern void atomic64_sub(long long a, atomic64_t *v); +extern long long atomic64_sub_return(long long a, atomic64_t *v); +extern long long atomic64_dec_if_positive(atomic64_t *v); +extern long long atomic64_cmpxchg(atomic64_t *v, long long o, long long n); +extern long long atomic64_xchg(atomic64_t *v, long long new); +extern int atomic64_add_unless(atomic64_t *v, long long a, long long u); + +#define atomic64_add_negative(a, v) (atomic64_add_return((a), (v)) < 0) +#define atomic64_inc(v) atomic64_add(1LL, (v)) +#define atomic64_inc_return(v) atomic64_add_return(1LL, (v)) +#define atomic64_inc_and_test(v) (atomic64_inc_return(v) == 0) +#define atomic64_sub_and_test(a, v) (atomic64_sub_return((a), (v)) == 0) +#define atomic64_dec(v) atomic64_sub(1LL, (v)) +#define atomic64_dec_return(v) atomic64_sub_return(1LL, (v)) +#define atomic64_dec_and_test(v) (atomic64_dec_return((v)) == 0) +#define atomic64_inc_not_zero(v) atomic64_add_unless((v), 1LL, 0LL) + +/* + * Initializer for fhgfs, replacement for ATOMIC64_INIT(i) + */ +void atomic_init(atomic64_t* atomic, uint64_t value) +{ + spin_lock_init(&atomic->lock); + atomic->counter = value; +} + + +#endif /* _ASM_GENERIC_ATOMIC64_H */ + +#endif // #ifndef ATOMIC64_INIT diff --git a/client_module/source/os/iov_iter.c b/client_module/source/os/iov_iter.c new file mode 100644 index 0000000..aaad4d6 --- /dev/null +++ b/client_module/source/os/iov_iter.c @@ -0,0 +1,144 @@ +#include + +#include +#include + + +static void beegfs_readsink_reserve_no_pipe(BeeGFS_ReadSink *rs, struct iov_iter *iter, size_t size) +{ + rs->sanitized_iter = *iter; + iov_iter_truncate(&rs->sanitized_iter, size); +} + +#ifdef KERNEL_HAS_ITER_PIPE +static size_t compute_max_pagecount(size_t size) +{ + // Compute maximal number of pages (in the pipe) that need to be present at once. + // We don't know the page-relative offset from which max_size bytes will be reserved. + // Assume the worst case. + size_t max_offset = PAGE_SIZE - 1; + + size_t max_pages = (max_offset + size + PAGE_SIZE - 1) / PAGE_SIZE; + + return max_pages; +} + + +static void beegfs_readsink_reserve_pipe(BeeGFS_ReadSink *rs, struct iov_iter *iter, size_t size) +{ + size_t max_pages; + + // struct should be zeroed + BUG_ON(rs->npages != 0); + BUG_ON(rs->pages != 0); + BUG_ON(rs->bvecs != 0); + + // should we disallow size > iter count? + size = min_t(size_t, size, iov_iter_count(iter)); + max_pages = compute_max_pagecount(size); + + // Could be kmalloc() instead of kzalloc(), but the iov_iter_get_pages() API + // gives back a byte count which makes it hard to detect initialization bugs + // related to the page pointers. + rs->pages = kzalloc(max_pages * sizeof *rs->pages, GFP_NOFS); + if (! rs->pages) + return; + + rs->bvecs = kmalloc(max_pages * sizeof *rs->bvecs, GFP_NOFS); + if (! rs->bvecs) + return; + + { + struct bio_vec *const bvecs = rs->bvecs; + struct page **const pages = rs->pages; + + long unsigned start; + ssize_t gpr; + + size_t view_size = 0; + + #ifdef KERNEL_HAS_IOV_ITER_GET_PAGES2 + + struct iov_iter copyIter = *iter; //Copying the iterator because iov_iter_get_pages2() + //also performs the auto-advance of the iterator and + //we don't want auto-advancement because in the end + //of the while loop of the FhgfsOpsRemoting_readfileVec() + //doing the same thing. + gpr = iov_iter_get_pages2(©Iter, pages, size, max_pages, &start); + + #else + + gpr = iov_iter_get_pages(iter, pages, size, max_pages, &start); + + #endif + + if (gpr < 0) + { + // indicate error? + // probably not necessary. The sanitized_iter field will be initialized with count 0. + } + else if (gpr > 0) + { + size_t bvs_size = 0; + size_t np = 0; + + view_size = gpr; + + for (np = 0; bvs_size < view_size; np++) + { + long unsigned offset = start; + long unsigned len = min_t(size_t, view_size - bvs_size, PAGE_SIZE - start); + + BUG_ON(np >= max_pages); + BUG_ON(! pages[np]); + + bvs_size += len; + start = 0; + + bvecs[np] = (struct bio_vec) { + .bv_page = pages[np], + .bv_offset = offset, + .bv_len = len, + }; + } + + // make sure we're using all the pages that iov_iter_get_pages() gave us. + //BUG_ON(np < max_pages && pages[np]); + WARN_ON(np < max_pages && pages[np]); + + rs->npages = np; + } + + BEEGFS_IOV_ITER_BVEC(&rs->sanitized_iter, READ, bvecs, rs->npages, view_size); + } +} +#endif + +void beegfs_readsink_reserve(BeeGFS_ReadSink *rs, struct iov_iter *iter, size_t size) +{ +#ifdef KERNEL_HAS_ITER_PIPE + if (iov_iter_type(iter) == ITER_PIPE) + beegfs_readsink_reserve_pipe(rs, iter, size); + else + beegfs_readsink_reserve_no_pipe(rs, iter, size); +#else + beegfs_readsink_reserve_no_pipe(rs, iter, size); +#endif +} + +void beegfs_readsink_release(BeeGFS_ReadSink *rs) +{ + int npages = rs->npages; + struct page **pages = rs->pages; + + for (int i = 0; i < npages; i++) + { + put_page(pages[i]); + pages[i] = NULL; // avoid this write? + } + + kfree(rs->pages); + kfree(rs->bvecs); + + memset(rs, 0, sizeof *rs); +} diff --git a/client_module/source/os/iov_iter.h b/client_module/source/os/iov_iter.h new file mode 100644 index 0000000..4308cd9 --- /dev/null +++ b/client_module/source/os/iov_iter.h @@ -0,0 +1,210 @@ +/* + * compatibility for older kernels. this code is mostly taken from include/linux/uio.h, + * include/linuxfs/fs.h and associated .c files. + * + * the originals are licensed as: + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +#ifndef KERNEL_HAS_ITER_KVEC +#error ITER_KVEC is a required feature +#endif + +#ifndef KERNEL_HAS_ITER_IS_IOVEC +#error iter_is_iovec() is a required feature +#endif + +/* + * In kernels 3.15 to 6.3 there was iov_iter_iovec(), returning the first iovec + * in an iov_iter of type ITER_IOVEC. + * 6.4 removes and started using macro iter_iov_addr & iter_iov_len. + * Using those now and providing a shim for older kernels. + */ +#if !defined(KERNEL_HAS_ITER_IOV_ADDR) +#define iter_iov_addr(iter) (iter_iov(iter)->iov_base + (iter)->iov_offset) +#define iter_iov_len(iter) (iter_iov(iter)->iov_len - (iter)->iov_offset) +#endif + +#ifndef KERNEL_HAS_IOV_ITER_INIT_DIR +#error We require kernels that have a "direction" parameter to iov_iter_init(). +#endif + + +#ifndef KERNEL_HAS_IOV_ITER_TYPE +static inline int iov_iter_type(const struct iov_iter *i) +{ + return i->type & ~(READ | WRITE); +} +#endif + + +#ifndef KERNEL_HAS_IOV_ITER_IS_PIPE +static inline bool iov_iter_is_pipe(struct iov_iter* iter) +{ +#ifdef KERNEL_HAS_ITER_PIPE + return iov_iter_type(iter) == ITER_PIPE; +#else + return false; +#endif +} +#endif + + +static inline int beegfs_iov_iter_is_iovec(const struct iov_iter *iter) +{ + return iov_iter_type(iter) == ITER_IOVEC; +} + +// TODO: Now that ITER_KVEC is required across all kernels, is this function still needed? +static inline struct iov_iter *beegfs_get_iovec_iov_iter(struct iov_iter *iter) +{ + BUG_ON(!beegfs_iov_iter_is_iovec(iter)); + return iter; +} + +static inline unsigned long beegfs_iov_iter_nr_segs(const struct iov_iter *iter) +{ + return iter->nr_segs; +} + +static inline void beegfs_iov_iter_clear(struct iov_iter *iter) +{ + iter->count = 0; +} + +#ifdef KERNEL_HAS_ITER_PIPE +static inline bool beegfs_is_pipe_iter(struct iov_iter * iter) +{ + return iov_iter_type(iter) == ITER_PIPE; +} +#endif + +#define BEEGFS_IOV_ITER_INIT iov_iter_init + +static inline void BEEGFS_IOV_ITER_KVEC(struct iov_iter *iter, int direction, + const struct kvec* kvec, unsigned long nr_segs, size_t count) +{ +#ifndef KERNEL_HAS_IOV_ITER_KVEC_NO_TYPE_FLAG_IN_DIRECTION + direction |= ITER_KVEC; +#endif + iov_iter_kvec(iter, direction, kvec, nr_segs, count); +} + +static inline void BEEGFS_IOV_ITER_BVEC(struct iov_iter *iter, int direction, + const struct bio_vec* bvec, unsigned long nr_segs, size_t count) +{ +#ifndef KERNEL_HAS_IOV_ITER_KVEC_NO_TYPE_FLAG_IN_DIRECTION + direction |= ITER_BVEC; +#endif + iov_iter_bvec(iter, direction, bvec, nr_segs, count); +} + + + +/* + BeeGFS_ReadSink + + We can't get parallel reads to work easily with ITER_PIPE. That type of iter + doesn't allow splitting up a region easily for parallel writing. The reason + is that the iov_iter_advance() implementation for ITER_PIPE modifies shared + state (the pipe_inode structure). + + The BeeGFS_ReadSink structure allows to abstract from that concern by + converting to an ITER_BVEC iter where necessary. + + Use is as follows: + 1) Initialize the struct by zeroing out, or using a {0} initializer. + This allows the cleanup routine to work even if nothing was ever + allocated. + + 1) Call _reserve() to set up a view of the given size into a given iov_iter + struct. If the given iov_iter is not of type ITER_PIPE, it will be copied + straight to the "sanitized_iter" field. Otherwise (if it is an + ITER_PIPE), an ITER_BVEC iterator will be made by allocating pages from + the pipe and setting up a bio_vec for each page. + + Note that this can fail in low memory situations. The size of the view + that was successfully allocated can be queried by calling + iov_iter_count() on the sanitized_iter field. + + 2) The sanitized_iter field should be used to read data. The field can be + used destructively. In particular it is safe to call iov_iter_advance() + on it in order to partition the view for multiple parallel reads. + + 3) When reads are done, probably, iov_iter_advance() should be called on + the iter that was given to _reserve(). + + 4) Call _release() to give back the pages that were reserved in step 2). + If the struct was properly initialized in step 1), is safe to call + _release() even if _reserve() was never called. This is useful when cleaning + up state after an early exit. + + 5) Go back to 2) if necessary, to copy more data. +*/ + +typedef struct _BeeGFS_ReadSink BeeGFS_ReadSink; +struct _BeeGFS_ReadSink { + size_t npages; // Number of pages currently in use (get_page()) + struct page **pages; // 0..npages + struct bio_vec *bvecs; // 0..npages + + // output value + struct iov_iter sanitized_iter; +}; + +void beegfs_readsink_reserve(BeeGFS_ReadSink *rs, struct iov_iter *iter, size_t size); +void beegfs_readsink_release(BeeGFS_ReadSink *rs); + + + + +/* + We have lots of code locations where we need to read or write memory using a + pointer + length pair, but need to use an iov_iter based API. This always + leads to boilerplate where struct iovec and struct iov_iter values have to be + declared on the stack. The following hack is meant to reduce that boilerplate. + */ +#define STACK_ALLOC_BEEGFS_ITER_IOV(ptr, size, direction) \ + ___BEEGFS_IOV_ITER_INIT(&(struct iov_iter){0}, &(struct iovec){0}, (ptr), (size), (direction)) + +#define STACK_ALLOC_BEEGFS_ITER_KVEC(ptr, size, direction) \ + ___BEEGFS_IOV_ITER_KVEC(&(struct iov_iter){0}, &(struct kvec){0}, (ptr), (size), (direction)) + +static inline struct iov_iter *___BEEGFS_IOV_ITER_INIT( + struct iov_iter *iter, struct iovec *iovec, + const char __user *ptr, size_t size, int direction) +{ + unsigned nr_segs = 1; + *iovec = (struct iovec) { + .iov_base = (char __user *) ptr, + .iov_len = size, + }; + BEEGFS_IOV_ITER_INIT(iter, direction, iovec, nr_segs, size); + return iter; +} + +static inline struct iov_iter *___BEEGFS_IOV_ITER_KVEC( + struct iov_iter *iter, struct kvec* kvec, + const char *ptr, size_t size, int direction) +{ + unsigned nr_segs = 1; + *kvec = (struct kvec) { + .iov_base = (char *) ptr, + .iov_len = size, + }; + BEEGFS_IOV_ITER_KVEC(iter, direction, kvec, nr_segs, size); + return iter; +} diff --git a/client_module/source/program/Main.c b/client_module/source/program/Main.c new file mode 100644 index 0000000..4e07b73 --- /dev/null +++ b/client_module/source/program/Main.c @@ -0,0 +1,101 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BEEGFS_LICENSE "GPL v2" + +static int __init init_fhgfs_client(void) +{ +#define fail_to(target, msg) \ + do { \ + printk_fhgfs(KERN_WARNING, msg "\n"); \ + goto target; \ + } while (0) + + if (!beegfs_fault_inject_init() ) + fail_to(fail_fault, "could not register fault-injection debugfs dentry"); + + if (!beegfs_native_init() ) + fail_to(fail_native, "could not allocate emergency pools"); + + if (!FhgfsOpsCommKit_initEmergencyPools() ) + fail_to(fail_commkitpools, "could not allocate emergency pools"); + + if (!SocketTk_initOnce() ) + fail_to(fail_socket, "SocketTk initialization failed"); + + if (!FhgfsOps_initInodeCache() ) + fail_to(fail_inode, "Inode cache initialization failed"); + + if (!RWPagesWork_initworkQueue() ) + fail_to(fail_rwpages, "Page work queue registration failed"); + + if (!FhgfsOpsRemoting_initMsgBufCache() ) + fail_to(fail_msgbuf, "Message cache initialization failed"); + + if (!FhgfsOpsPages_initPageListVecCache() ) + fail_to(fail_pagelists, "PageVec cache initialization failed"); + + if (FhgfsOps_registerFilesystem() ) + fail_to(fail_register, "File system registration failed"); + + ProcFs_createGeneralDir(); + + printk_fhgfs(KERN_INFO, "File system registered. Type: %s. Version: %s\n", + BEEGFS_MODULE_NAME_STR, App_getVersionStr() ); + + return 0; + +fail_register: + FhgfsOpsPages_destroyPageListVecCache(); +fail_pagelists: + FhgfsOpsRemoting_destroyMsgBufCache(); +fail_msgbuf: + RWPagesWork_destroyWorkQueue(); +fail_rwpages: + FhgfsOps_destroyInodeCache(); +fail_inode: + SocketTk_uninitOnce(); +fail_socket: + FhgfsOpsCommKit_releaseEmergencyPools(); +fail_commkitpools: + beegfs_native_release(); +fail_native: + beegfs_fault_inject_release(); +fail_fault: + return -EPERM; +} + +static void __exit exit_fhgfs_client(void) +{ + ProcFs_removeGeneralDir(); + BUG_ON(FhgfsOps_unregisterFilesystem() ); + FhgfsOpsPages_destroyPageListVecCache(); + FhgfsOpsRemoting_destroyMsgBufCache(); + RWPagesWork_destroyWorkQueue(); + FhgfsOps_destroyInodeCache(); + SocketTk_uninitOnce(); + FhgfsOpsCommKit_releaseEmergencyPools(); + beegfs_native_release(); + beegfs_fault_inject_release(); + + printk_fhgfs(KERN_INFO, "BeeGFS client unloaded.\n"); +} + +module_init(init_fhgfs_client) +module_exit(exit_fhgfs_client) + +MODULE_LICENSE(BEEGFS_LICENSE); +MODULE_DESCRIPTION("BeeGFS parallel file system client (https://www.beegfs.io)"); +MODULE_AUTHOR("ThinkParQ GmbH"); +MODULE_ALIAS("fs-" BEEGFS_MODULE_NAME_STR); +MODULE_VERSION(BEEGFS_VERSION); diff --git a/client_module/source/toolkit/BitStore.c b/client_module/source/toolkit/BitStore.c new file mode 100644 index 0000000..be1e033 --- /dev/null +++ b/client_module/source/toolkit/BitStore.c @@ -0,0 +1,278 @@ +#include +#include "BitStore.h" + +/** + * Set or unset a bit at the given index. + * + * Note: The bit operations in here are atomic. + * + * @param isSet true to set or false to unset the corresponding bit. + */ +void BitStore_setBit(BitStore* this, unsigned bitIndex, bool isSet) +{ + unsigned index = BitStore_getBitBlockIndex(bitIndex); + unsigned indexInBitBlock = BitStore_getBitIndexInBitBlock(bitIndex); + + if(unlikely(bitIndex >= this->numBits) ) + { + BEEGFS_BUG_ON(true, "index out of bounds: bitIndex >= this->numBits"); + return; + } + + if (index == 0) + { + if (isSet) + set_bit(indexInBitBlock, &this->lowerBits); + else + clear_bit(indexInBitBlock, &this->lowerBits); + } + else + { + if (isSet) + set_bit(indexInBitBlock, &this->higherBits[index - 1]); + else + clear_bit(indexInBitBlock, &this->higherBits[index - 1]); + } +} + +/** + * grow/shrink internal buffers when needed for new size. + * + * note: this method does not reset or copy bits, so consider the bits to be uninitialized after + * calling this method. thus, you might want to call clearBits() afterwards to clear all bits. + * + * @param size number of bits + */ +void BitStore_setSize(BitStore* this, unsigned newSize) +{ + unsigned oldBitBlockCount = BitStore_calculateBitBlockCount(this->numBits); + unsigned newBitBlockCount = BitStore_calculateBitBlockCount(newSize); + + if(newBitBlockCount != oldBitBlockCount) + { + SAFE_KFREE(this->higherBits); + + if(newBitBlockCount > 1) + { + this->higherBits = (bitstore_store_type*)os_kmalloc( + BITSTORE_BLOCK_SIZE * (newBitBlockCount - 1) ); + } + + this->numBits = newBitBlockCount * BITSTORE_BLOCK_BIT_COUNT; + } +} + +/** + * reset all bits to 0 + */ +void BitStore_clearBits(BitStore* this) +{ + this->lowerBits = BITSTORE_BLOCK_INIT_MASK; + + if(this->higherBits) + { + unsigned bitBlockCount = BitStore_calculateBitBlockCount(this->numBits); + + memset(this->higherBits, 0, (bitBlockCount-1) * BITSTORE_BLOCK_SIZE); + } + +} + + +/** + * note: serialized format is always one or more full 64bit blocks to keep the format independent + * of 32bit/64bit archs. + * note: serialized format is 8 byte aligned. + */ +void BitStore_serialize(SerializeCtx* ctx, const BitStore* this) +{ + unsigned index; + unsigned blockCount = BitStore_calculateBitBlockCount(this->numBits); + + // size + Serialization_serializeUInt(ctx, this->numBits); + + // padding for 8byte alignment + Serialization_serializeUInt(ctx, 0); + + { // lowerBits + if (BITSTORE_BLOCK_SIZE == sizeof(uint64_t) ) + Serialization_serializeUInt64(ctx, this->lowerBits); + else + if (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) + Serialization_serializeUInt(ctx, this->lowerBits); + } + + { // higherBits + for(index = 0; index < (blockCount - 1); index++) + { + if (BITSTORE_BLOCK_SIZE == sizeof(uint64_t) ) + Serialization_serializeUInt64(ctx, this->higherBits[index]); + else + if (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) + Serialization_serializeUInt(ctx, this->higherBits[index]); + } + } + + // add padding to allow 64bit deserialize from 32bit serialize + if( (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) && (blockCount & 1) ) + { // odd number of 32bit blocks + Serialization_serializeUInt(ctx, 0); + } +} + +/** + * @param outBitStoreStart deserialization buffer pointer for deserialize() + */ +bool BitStore_deserializePreprocess(DeserializeCtx* ctx, const char** outBitStoreStart) +{ + unsigned padding; + unsigned tmpSize = 0; // =0, because it is used later and otherwise gcc complains + // with "may be uninitialized" + unsigned blockCount; + size_t blocksLen; + + *outBitStoreStart = ctx->data; + + // size + if(unlikely(!Serialization_deserializeUInt(ctx, &tmpSize) ) ) + return false; + + // padding for 8byte alignment + if(unlikely(!Serialization_deserializeUInt(ctx, &padding) ) ) + return false; + + // bit blocks + + blockCount = BitStore_calculateBitBlockCount(tmpSize); + + if(BITSTORE_BLOCK_SIZE == sizeof(uint64_t) ) + blocksLen = Serialization_serialLenUInt64() * blockCount; + else + if(BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) + { + // add padding that allows 64bit deserialize from 32bit serialize + if(blockCount & 1) + blockCount++; + + blocksLen = Serialization_serialLenUInt() * blockCount; + } + else + return false; + + // check buffer length + if(unlikely(blocksLen > ctx->length) ) + return false; + + ctx->data += blocksLen; + ctx->length -= blocksLen; + + return true; +} + +void BitStore_deserialize(BitStore* this, DeserializeCtx* ctx) +{ + unsigned padding; + unsigned blockCount; + unsigned bitCount = 0; + + // size + // store the bit count in a temp variable because setSizeAndReset() below needs old count + Serialization_deserializeUInt(ctx, &bitCount); + + // padding for 8byte alignment + Serialization_deserializeUInt(ctx, &padding); + + // clear and alloc memory for the higher bits if needed + + BitStore_setSize(this, bitCount); + + { // lowerBits + if (BITSTORE_BLOCK_SIZE == sizeof(uint64_t) ) + Serialization_deserializeUInt64(ctx, (uint64_t*)&this->lowerBits); + else + if (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) + Serialization_deserializeUInt(ctx, (uint32_t*)&this->lowerBits); + } + + blockCount = BitStore_calculateBitBlockCount(this->numBits); + + { // higherBits + unsigned index; + + BEEGFS_BUG_ON_DEBUG( (blockCount > 1) && !this->higherBits, "Bug: higherBits==NULL"); + + for(index = 0; index < (blockCount - 1); index++) + { + bitstore_store_type* higherBits = &this->higherBits[index]; + + if (BITSTORE_BLOCK_SIZE == sizeof(uint64_t) ) + Serialization_deserializeUInt64(ctx, (uint64_t*)higherBits); + else + if (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) + Serialization_deserializeUInt(ctx, (uint32_t*)higherBits); + } + } + + // add padding that allows 64bit deserialize from 32bit serialize + if( (BITSTORE_BLOCK_SIZE == sizeof(uint32_t) ) && (blockCount & 1) ) + Serialization_deserializeUInt(ctx, &padding); +} + +/** + * Update this store with values from given other store. + */ +void BitStore_copy(BitStore* this, BitStore* other) +{ + BitStore_setSize(this, other->numBits); + + this->lowerBits = other->lowerBits; + + if(!this->higherBits) + return; + + // we have higherBits to copy + { + unsigned blockCount = BitStore_calculateBitBlockCount(this->numBits); + + memcpy(this->higherBits, other->higherBits, (blockCount-1) * BITSTORE_BLOCK_SIZE); + } +} + +/** + * Update this store with values from given other store. + * + * To make this copy thread-safe, this method does not attempt any re-alloc of internal buffers, + * so the copy will not be complete if the second store contains more blocks. + */ +void BitStore_copyThreadSafe(BitStore* this, const BitStore* other) +{ + unsigned thisBlockCount; + unsigned otherBlockCount; + + this->lowerBits = other->lowerBits; + + if(!this->higherBits) + return; + + // copy (or clear) our higherBits + + thisBlockCount = BitStore_calculateBitBlockCount(this->numBits); + otherBlockCount = BitStore_calculateBitBlockCount(other->numBits); + + if(otherBlockCount > 1) + { // copy as much as we can from other's higherBits + unsigned minCommonBlockCount = MIN(thisBlockCount, otherBlockCount); + + memcpy(this->higherBits, other->higherBits, (minCommonBlockCount-1) * BITSTORE_BLOCK_SIZE); + } + + // (this store must at least have one higherBits block, because higherBits!=NULL) + if(thisBlockCount > otherBlockCount) + { // zero remaining higherBits of this store + unsigned numBlocksDiff = thisBlockCount - otherBlockCount; + + // ("otherBlockCount-1": -1 for lowerBits block) + memset(&(this->higherBits[otherBlockCount-1]), 0, numBlocksDiff * BITSTORE_BLOCK_SIZE); + } +} diff --git a/client_module/source/toolkit/BitStore.h b/client_module/source/toolkit/BitStore.h new file mode 100644 index 0000000..8b67c9a --- /dev/null +++ b/client_module/source/toolkit/BitStore.h @@ -0,0 +1,137 @@ +#ifndef BITSTORE_H_ +#define BITSTORE_H_ + +#include +#include + + +typedef long unsigned int bitstore_store_type; + + +#define BITSTORE_BLOCK_SIZE sizeof(bitstore_store_type) // size of a block in bytes +#define BITSTORE_BLOCK_BIT_COUNT (BITSTORE_BLOCK_SIZE * 8) // size of a block in bits +#define BITSTORE_BLOCK_INIT_MASK 0UL // mask to zero all bits of block + + +struct BitStore; +typedef struct BitStore BitStore; + + +static inline void BitStore_init(BitStore* this, bool setZero); +static inline void BitStore_initWithSizeAndReset(BitStore* this, unsigned size); +static inline void BitStore_uninit(BitStore* this); + +extern void BitStore_setBit(BitStore* this, unsigned bitIndex, bool isSet); + +extern void BitStore_setSize(BitStore* this, unsigned newSize); +extern void BitStore_clearBits(BitStore* this); + +extern void BitStore_serialize(SerializeCtx* ctx, const BitStore* this); +extern bool BitStore_deserializePreprocess(DeserializeCtx* ctx, const char** outBitStoreStart); +extern void BitStore_deserialize(BitStore* this, DeserializeCtx* ctx); + +extern void BitStore_copy(BitStore* this, BitStore* other); +extern void BitStore_copyThreadSafe(BitStore* this, const BitStore* other); + +// public inliners + +static inline bool BitStore_getBit(const BitStore* this, unsigned bitIndex); + +static inline unsigned BitStore_getBitBlockIndex(unsigned bitIndex); +static inline unsigned BitStore_getBitIndexInBitBlock(unsigned bitIndex); +static inline unsigned BitStore_calculateBitBlockCount(unsigned size); + + +/** + * A vector of bits. + */ +struct BitStore +{ + unsigned numBits; // max number of bits that this bitstore can hold + bitstore_store_type lowerBits; // used to avoid overhead of extra alloc for higherBits + bitstore_store_type* higherBits; // array, which is alloc'ed only when needed +}; + + +void BitStore_init(BitStore* this, bool setZero) +{ + this->numBits = BITSTORE_BLOCK_BIT_COUNT; + this->higherBits = NULL; + + if (setZero) + this->lowerBits = BITSTORE_BLOCK_INIT_MASK; +} + +void BitStore_initWithSizeAndReset(BitStore* this, unsigned size) +{ + BitStore_init(this, false); + BitStore_setSize(this, size); + BitStore_clearBits(this); +} + +void BitStore_uninit(BitStore* this) +{ + SAFE_KFREE_NOSET(this->higherBits); +} + +/** + * Test whether the bit at the given index is set or not. + * + * Note: The bit operations in here are atomic. + */ +bool BitStore_getBit(const BitStore* this, unsigned bitIndex) +{ + unsigned index; + unsigned indexInBitBlock; + + if(unlikely(bitIndex >= this->numBits) ) + return false; + + index = BitStore_getBitBlockIndex(bitIndex); + indexInBitBlock = BitStore_getBitIndexInBitBlock(bitIndex); + + if (index == 0) + return test_bit(indexInBitBlock, &this->lowerBits); + else + return test_bit(indexInBitBlock, &this->higherBits[index - 1]); +} + + +/** + * returns the index of the bit block (array value) which contains the searched bit + */ +unsigned BitStore_getBitBlockIndex(unsigned bitIndex) +{ + return (bitIndex / BITSTORE_BLOCK_BIT_COUNT); +} + +/** + * returns the index in a bit block for the searched bit + */ +unsigned BitStore_getBitIndexInBitBlock(unsigned bitIndex) +{ + return (bitIndex % BITSTORE_BLOCK_BIT_COUNT); +} + +/** + * calculates the needed bit block count for the given BitStore size + */ +unsigned BitStore_calculateBitBlockCount(unsigned size) +{ + unsigned retVal; + + if(size <= BITSTORE_BLOCK_BIT_COUNT) + return 1; // only first block needed + + + retVal = size / BITSTORE_BLOCK_BIT_COUNT; + + // find out whether we use a partial block + + if (size % BITSTORE_BLOCK_BIT_COUNT) + retVal++; // we need another (partial) block + + return retVal; +} + +#endif /* BITSTORE_H_ */ diff --git a/client_module/source/toolkit/FhgfsChunkPageVec.c b/client_module/source/toolkit/FhgfsChunkPageVec.c new file mode 100644 index 0000000..9af415f --- /dev/null +++ b/client_module/source/toolkit/FhgfsChunkPageVec.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include + +#include "FhgfsChunkPageVec.h" + +/** + * Iterate over (remaining) pages on write and handle writeRes (might be an error) + * + * @param writeRes negative linux error code on error + */ +void FhgfsChunkPageVec_iterateAllHandleWritePages(FhgfsChunkPageVec* this, int writeRes) +{ + while(1) + { + FhgfsPage* fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(this); + if (!fhgfsPage) + break; + + FhgfsOpsPages_endWritePage(fhgfsPage->page, writeRes, this->inode); + } +} + + +/** + * Unmap, unlock and release all pages in the page list vector + */ +void FhgfsChunkPageVec_iterateAllHandleReadErr(FhgfsChunkPageVec* this) +{ + FhgfsPage* fhgfsPage; + + while (1) + { + fhgfsPage = FhgfsChunkPageVec_iterateGetNextPage(this); + if (!fhgfsPage) + break; + + FhgfsPage_unmapUnlockReleaseFhgfsPage(fhgfsPage); + } +} + + diff --git a/client_module/source/toolkit/FhgfsChunkPageVec.h b/client_module/source/toolkit/FhgfsChunkPageVec.h new file mode 100644 index 0000000..77aae79 --- /dev/null +++ b/client_module/source/toolkit/FhgfsChunkPageVec.h @@ -0,0 +1,429 @@ +#ifndef FHGFSCHUNKPAGEVEC_H_ +#define FHGFSCHUNKPAGEVEC_H_ + +#include +#include + +#include +#include "FhgfsPage.h" +#include "FhgfsPageListVec.h" + +#include // struct write_back control + + + +struct FhgfsChunkPageVec; +typedef struct FhgfsChunkPageVec FhgfsChunkPageVec; + + +static inline FhgfsChunkPageVec* FhgfsChunkPageVec_create(App *app, struct inode* inode, + struct kmem_cache* pageVecCache, mempool_t* pageVecPool, unsigned numChunkPages); +static inline void FhgfsChunkPageVec_destroy(FhgfsChunkPageVec* this); + + +static inline int FhgfsChunkPageVec_init(FhgfsChunkPageVec* this, App *app, struct inode* inode, + struct kmem_cache* pageVecCache, mempool_t* pageVecPool, unsigned numChunkPages); +static inline void FhgfsChunkPageVec_uninit(FhgfsChunkPageVec* this); + +static inline int FhgfsChunkPageVec_addPageListVec(FhgfsChunkPageVec* this, mempool_t* pageVecPool); +static inline FhgfsPageListVec* FhgfsChunkPageVec_getFirstPageListVec(FhgfsChunkPageVec* this); + + +static inline bool FhgfsChunkPageVec_pushPage(FhgfsChunkPageVec* this, struct page* page, + int usedPageLength); + +static inline unsigned FhgfsChunkPageVec_getSize(FhgfsChunkPageVec* this); + +static inline struct inode* FhgfsChunkPageVec_getInode(FhgfsChunkPageVec* this); + +static inline loff_t FhgfsChunkPageVec_getFirstPageFileOffset(FhgfsChunkPageVec* this); + +static inline FhgfsPage* FhgfsChunkPageVec_iterateGetNextPage(FhgfsChunkPageVec* this); +static inline void FhgfsChunkPageVec_resetIterator(FhgfsChunkPageVec* this); + +static inline size_t FhgfsChunkPageVec_getCurrentListVecIterIdx(FhgfsChunkPageVec* this); + +static inline size_t FhgfsChunkPageVec_getCurrentListVecIterIdx(FhgfsChunkPageVec* this); + +static inline size_t FhgfsChunkPageVec_getDataSize(FhgfsChunkPageVec* this); +static inline size_t FhgfsChunkPageVec_getRemainingDataSize(FhgfsChunkPageVec* this); + +static inline unsigned _FhgfsChunkPageVec_getChunkPageOffset(FhgfsChunkPageVec* this, + pgoff_t index); + +extern void FhgfsChunkPageVec_iterateAllHandleWritePages(FhgfsChunkPageVec* this, int writeRes); +extern void FhgfsChunkPageVec_iterateAllHandleReadErr(FhgfsChunkPageVec* this); + + + +struct FhgfsChunkPageVec +{ + App* app; + struct inode* inode; + struct kmem_cache* pageListVecCache; + mempool_t* pageVecPool; // only set to non-NULL if there was a pageVec pool allocation + + unsigned numChunkPages; + + unsigned size; // number of used elements of the vector */ + + struct list_head pageListVecHead; // list of FhgfsPageListVec entries + FhgfsPageListVec* lastAllocListVec; /* last list entry that was allocated, new pages will be + * added to this vector */ + FhgfsPageListVec* currentIterListVec; /* On iterating over the list vector this is the + * current FhgfsPageListVec element */ + size_t currentVecIdx; // current vector index within currentIterListVec + size_t maxPagesPerVec; // number of vector pages per FhgfsPageListVec + size_t currentListVecIterIdx; /* index as if the current page would be in a large single + * array. Note: For log and debugging only */ + + size_t lastPageUsedDataSize; // used bytes of the last page + + +#ifdef BEEGFS_DEBUG + int numListVecs; +#endif + + loff_t firstPageFileOffset; + unsigned firstPageChunkOffset; + pgoff_t firstPageIdx; + pgoff_t lastPageIdx; +}; + + +/** + * Constructor + */ +FhgfsChunkPageVec* FhgfsChunkPageVec_create(App* app, struct inode* inode, + struct kmem_cache* pageVecCache, mempool_t* pageVecPool, unsigned numChunkPages) +{ + int initErr; + + FhgfsChunkPageVec* this = os_kmalloc(sizeof(*this) ); + if (unlikely(!this) ) + return NULL; + + initErr = FhgfsChunkPageVec_init(this, app, inode, pageVecCache, pageVecPool, numChunkPages); + if (unlikely(initErr) ) + { + kfree(this); + return NULL; + } + + return this; +} + + +/** + * Destructor + */ +void FhgfsChunkPageVec_destroy(FhgfsChunkPageVec* this) +{ + FhgfsChunkPageVec_uninit(this); + + kfree(this); +} + + + +/** + * Initialize FhgfsChunkPageVec + * + * @return 0 on success, negative linux error code on error + */ +int FhgfsChunkPageVec_init(FhgfsChunkPageVec* this, App* app, struct inode* inode, + struct kmem_cache* pageVecCache, mempool_t* pageVecPool, unsigned numChunkPages) +{ + int listVecAddRes; + + this->app = app; + this->inode = inode; + this->pageListVecCache = pageVecCache; + this->pageVecPool = NULL; // first initialize to NULL, it will be set later if required + + this->numChunkPages = numChunkPages; + this->size = 0; + this->firstPageChunkOffset = 0; + + INIT_LIST_HEAD(&this->pageListVecHead); + +#ifdef BEEGFS_DEBUG + this->numListVecs = 0; +#endif + + // add the first pageListVec + listVecAddRes = FhgfsChunkPageVec_addPageListVec(this, pageVecPool); + if (unlikely(listVecAddRes) ) + return listVecAddRes; + + return 0; +} + + +/** + * Uninitialize the vector + */ +void FhgfsChunkPageVec_uninit(FhgfsChunkPageVec* this) +{ + while (!list_empty(&this->pageListVecHead) ) + { + FhgfsPageListVec* listVec = list_entry(this->pageListVecHead.prev, FhgfsPageListVec, list); + + list_del(&listVec->list); + + FhgfsPageListVec_destroy(listVec, this->pageListVecCache, this->pageVecPool); + +#ifdef BEEGFS_DEBUG + this->numListVecs--; +#endif + + } + +#ifdef BEEGFS_DEBUG + if (unlikely(this->numListVecs) ) + { + Logger* log = App_getLogger(this->app); + + Logger_logErrFormatted(log, __func__, "Bug: Failed to distroy all listVecs, remaining: %d", + this->numListVecs); + } +#endif + +} + + + +/** + * Create a new pageListVec entry and add it to the list + * + * @param pageVecPool must be set from FhgfsChunkPageVec_init, but is NULL on adding + * additional pageListVecs (secondary allocation) + */ +int FhgfsChunkPageVec_addPageListVec(FhgfsChunkPageVec* this, mempool_t* pageVecPool) +{ + bool isPoolAlloc = false; + + FhgfsPageListVec* pageListVec = FhgfsPageListVec_create(this->pageListVecCache, pageVecPool, + &isPoolAlloc); + if (unlikely(!pageListVec) ) + return -ENOMEM; + + if (isPoolAlloc) + this->pageVecPool = pageVecPool; + + if (list_empty(&this->pageListVecHead) ) + { // first entry + this->currentIterListVec = pageListVec; + this->currentVecIdx = 0; + this->currentListVecIterIdx = 0; + this->maxPagesPerVec = FhgfsPageListVec_getMaxPages(pageListVec); + } + + // add this listVec head to the list + list_add_tail(&pageListVec->list, &this->pageListVecHead); + + this->lastAllocListVec = pageListVec; + +#ifdef BEEGFS_DEBUG + this->numListVecs++; +#endif + + return 0; +} + + +/* + * Get the first pageListVec + */ +FhgfsPageListVec* FhgfsChunkPageVec_getFirstPageListVec(FhgfsChunkPageVec* this) +{ + if (list_empty(&this->pageListVecHead) ) + return NULL; + + return list_first_entry(&this->pageListVecHead, FhgfsPageListVec, list); +} + + +/** + * Append another page to the page list-vector + * + * Note: Returns false if the page-vec is full or a non-consecutive page is to be added + */ +bool FhgfsChunkPageVec_pushPage(FhgfsChunkPageVec* this, struct page* page, + int usedPageLength) +{ + const char* logContext = __func__; + bool pushSuccess; + + if ((this->size + this->firstPageChunkOffset) == this->numChunkPages) + return false; // pageVec full + + if ((this->size) && (this->lastPageIdx + 1 != page->index) ) + return false; // non-consecutive page + + pushSuccess = FhgfsPageListVec_pushPage(this->lastAllocListVec, page, + usedPageLength); + + if (!pushSuccess) + { + /* NOTE: pageVecPool must be set to NULL in _addPageListVec here, to ensure the pool is not + * used multiple times (2nd alloc might wait for the 1st one to complete and so might + * never succeed). */ + int listVecAddRes = FhgfsChunkPageVec_addPageListVec(this, NULL); + if (listVecAddRes) + return false; + + // now try again, should work now, if not we give up + pushSuccess = FhgfsPageListVec_pushPage(this->lastAllocListVec, page, usedPageLength); + if (!pushSuccess) + { + Logger* log = App_getLogger(this->app); + + Logger_logErr(log, logContext, "Page adding failed."); + + return false; + } + } + + if (!this->size) + { + this->firstPageFileOffset = page_offset(page); + this->firstPageIdx = page->index; + + this->firstPageChunkOffset = _FhgfsChunkPageVec_getChunkPageOffset(this, page->index); + } + + this->lastPageIdx = page->index; + + this->size++; + + this->lastPageUsedDataSize = usedPageLength; + + return true; +} + + +/** + * Get the current vector Size + */ +unsigned FhgfsChunkPageVec_getSize(FhgfsChunkPageVec* this) +{ + return this->size; +} + +struct inode* FhgfsChunkPageVec_getInode(FhgfsChunkPageVec* this) +{ + return this->inode; +} + + +/** + * Get the offset + */ +loff_t FhgfsChunkPageVec_getFirstPageFileOffset(FhgfsChunkPageVec* this) +{ + return this->firstPageFileOffset; +} + + +/** + * Return the next page in the pageListVec. + * On first call of this function it returns the first page. + * If there are no more pages left it returns NULL and resets the counters. + */ +FhgfsPage* FhgfsChunkPageVec_iterateGetNextPage(FhgfsChunkPageVec* this) +{ + FhgfsPage* fhgfsPage; + + // printk(KERN_INFO "%s curVecIdx: %zu maxPagesPerVec: %zu size: %u\n", + // __func__, this->currentVecIdx, this->maxPagesPerVec, this->size); + + if (this->currentListVecIterIdx == this->size) + return NULL; // end reached + + if (this->currentVecIdx == this->maxPagesPerVec) + { // we reached the last page of this vector, take the next list element + FhgfsPageListVec* nextListVec; + + if (unlikely(list_is_last(&this->currentIterListVec->list, &this->pageListVecHead) ) ) + { // last list element reached, reset and return NULL + Logger* log = App_getLogger(this->app); + + // actually must not happen, as we check above for this->currentVecIdx == this->size + Logger_logErrFormatted(log, __func__, + "Bug: Is last list element, but should have more pages: %zu/%u", + this->currentVecIdx, this->size); + + return NULL; + } + + nextListVec = list_next_entry(this->currentIterListVec, list); + + this->currentIterListVec = nextListVec; + this->currentVecIdx = 0; + } + + fhgfsPage = FhgfsPageListVec_getFhgfsPage(this->currentIterListVec, this->currentVecIdx); + + this->currentVecIdx++; // increase for the next round + this->currentListVecIterIdx++; + + return fhgfsPage; +} + +/** + * Reset the iterator, so that one may walk over all pages from the beginning again + */ +void FhgfsChunkPageVec_resetIterator(FhgfsChunkPageVec* this) +{ + this->currentIterListVec = FhgfsChunkPageVec_getFirstPageListVec(this); + this->currentVecIdx = 0; + this->currentListVecIterIdx = 0; +} + + + +/** + * Get the curent overall vector index (as if we would have a single vector and not a list of + * vectors. + */ +size_t FhgfsChunkPageVec_getCurrentListVecIterIdx(FhgfsChunkPageVec* this) +{ + return this->currentListVecIterIdx; +} + + +/** + * Get the overall size of the chunk-vec-list + */ +size_t FhgfsChunkPageVec_getDataSize(FhgfsChunkPageVec* this) +{ + return (this->size - 1) * PAGE_SIZE + this->lastPageUsedDataSize; +} + +/** + * Get the remaining size of the chunk-vec-list + */ +size_t FhgfsChunkPageVec_getRemainingDataSize(FhgfsChunkPageVec* this) +{ + size_t remainingPages = this->size - this->currentListVecIterIdx; + + return (remainingPages - 1) * PAGE_SIZE + this->lastPageUsedDataSize; +} + + +/** + * Compute the offset within a single chunk + */ +unsigned _FhgfsChunkPageVec_getChunkPageOffset(FhgfsChunkPageVec* this, pgoff_t index) +{ + unsigned numChunkPages = this->numChunkPages; + + /* note: "& (numChunkPages - 1) only works as "% numChunkPages" replacement, + * because chunksize (and therefore also chunkPages) must be a power of two */ + + return index & (numChunkPages - 1); +} + + +#endif // FHGFSCHUNKPAGEVEC_H_ diff --git a/client_module/source/toolkit/FhgfsPage.h b/client_module/source/toolkit/FhgfsPage.h new file mode 100644 index 0000000..d9fbd79 --- /dev/null +++ b/client_module/source/toolkit/FhgfsPage.h @@ -0,0 +1,77 @@ +#ifndef FHGFSPAGE_H_ +#define FHGFSPAGE_H_ + +#include + + +struct FhgfsPage; +typedef struct FhgfsPage FhgfsPage; + + +static inline void FhgfsPage_unmapUnlockReleasePage(struct page* page); +static inline void FhgfsPage_unmapUnlockReleaseFhgfsPage(FhgfsPage* this); + +static inline loff_t FhgfsPage_getFileOffset(FhgfsPage* this); +static inline void FhgfsPage_zeroPage(FhgfsPage* this); +static inline pgoff_t FhgfsPage_getPageIndex(FhgfsPage* this); + + +struct FhgfsPage +{ + struct page* page; + void *data; // real page data, obtained by kmap(page) + int length; // size of *used* data of this page +}; + + +/** + * Unmap, unlock and release a (kernel) page + */ +void FhgfsPage_unmapUnlockReleasePage(struct page* page) +{ + kunmap(page); + + unlock_page(page); + + put_page(page); +} + + +/** + * Unmap, unlock and release an fhgfs page + */ +void FhgfsPage_unmapUnlockReleaseFhgfsPage(FhgfsPage* this) +{ + FhgfsPage_unmapUnlockReleasePage(this->page); +} + + +/** + * Get the offset (within a file) of the page + */ +loff_t FhgfsPage_getFileOffset(FhgfsPage* this) +{ + return page_offset(this->page); // offset within the file +} + +/** + * Zero the data of a page + */ +void FhgfsPage_zeroPage(FhgfsPage* this) +{ + // zero_user_segment() would be optimal, but not available in older kernels + // zero_user_segment(page, zeroOffset, BEEGFS_PAGE_SIZE); + // BUT we can use our kmapped Fhgfs_Page directly for zeroing + + memset(this->data, 0, PAGE_SIZE); +} + +/** + * Return the page (in file) index of a page + */ +pgoff_t FhgfsPage_getPageIndex(FhgfsPage* this) +{ + return this->page->index; +} + +#endif // FHGFSPAGE_H_ diff --git a/client_module/source/toolkit/FhgfsPageListVec.h b/client_module/source/toolkit/FhgfsPageListVec.h new file mode 100644 index 0000000..8379b39 --- /dev/null +++ b/client_module/source/toolkit/FhgfsPageListVec.h @@ -0,0 +1,159 @@ +#ifndef FHGFSPAGELISTVEC_H_ +#define FHGFSPAGELISTVEC_H_ + +#include + +#include "FhgfsPage.h" + +// max pages in a single pageVec, but by using pageVecList's many of these may be used +#ifdef BEEGFS_DEBUG +#define BEEGFS_LIST_VEC_MAX_PAGES 16 // if debugging is enable we make sure lists are tested +#else +#define BEEGFS_LIST_VEC_MAX_PAGES 32 +#endif + +struct FhgfsPageListVec; +typedef struct FhgfsPageListVec FhgfsPageListVec; + + +static inline FhgfsPageListVec* FhgfsPageListVec_create(struct kmem_cache* pageVecCache, + mempool_t* pageListPool, bool* outIsPoolAlloc); +static inline void FhgfsPageListVec_destroy(FhgfsPageListVec* this, + struct kmem_cache* pageVecCache, mempool_t* pageVecPool); +static inline void FhgfsPageListVec_init(FhgfsPageListVec* this); +static inline void FhgfsPageListVec_uninit(FhgfsPageListVec* this); + +static inline bool FhgfsPageListVec_pushPage(FhgfsPageListVec* this, struct page* page, + int usedPageLength); + +static inline size_t FhgfsPageListVec_getMaxPages(FhgfsPageListVec* this); + +static inline FhgfsPage* FhgfsPageListVec_getFhgfsPage(FhgfsPageListVec* this, size_t index); + + + +struct FhgfsPageListVec +{ + FhgfsPage fhgfsPages[BEEGFS_LIST_VEC_MAX_PAGES]; + size_t usedPages; // number of used pages of this vector + + struct list_head list; +}; + + +/** + * Constructor + * + * @param pageVecPool is only set if this is the first allocation from FhgfsChunkPageVec, as + * this allocation must not fail. + * @param outIsPoolAlloc is supposed to be initialized to false + */ +FhgfsPageListVec* FhgfsPageListVec_create(struct kmem_cache* pageVecCache, + mempool_t* pageVecPool, bool* outIsPoolAlloc) +{ + FhgfsPageListVec* this; + +#ifdef BEEGFS_DEBUG + // test cache alloc failures in debug mode + if (jiffies & 1) // odd jiffies simulate an allocation error + this = NULL; + else + this = (FhgfsPageListVec*) kmem_cache_alloc(pageVecCache, GFP_NOFS); +#else + this = (FhgfsPageListVec*) kmem_cache_alloc(pageVecCache, GFP_NOFS); +#endif + + if (unlikely(!this) ) + { + if (pageVecPool) + { + this = mempool_alloc(pageVecPool, GFP_NOFS); + if (unlikely(!this) ) + { + printk_fhgfs(KERN_WARNING, "%s: mempool_alloc unexpectedly failed\n", __func__); + return NULL; + } + + *outIsPoolAlloc = true; + } + else + return NULL; + } + + FhgfsPageListVec_init(this); + + return this; +} + +/** + * Destructor + */ +void FhgfsPageListVec_destroy(FhgfsPageListVec* this, struct kmem_cache* pageVecCache, + mempool_t* pageVecPool) +{ + FhgfsPageListVec_uninit(this); + + if (pageVecPool) + mempool_free(this, pageVecPool); + else + kmem_cache_free(pageVecCache, this); +} + + +/** + * Initialize FhgfsPageListVec + */ +void FhgfsPageListVec_init(FhgfsPageListVec* this) +{ + this->usedPages = 0; + INIT_LIST_HEAD(&this->list); +} + +/** + * Uninitialize FhgfsPageListVec + */ +void FhgfsPageListVec_uninit(FhgfsPageListVec* this) +{ + // no-op +} + + +/** + * Add (append) a kernel page to this pageListVec + * + * @return false if the pageListVec is full, true if adding succeeded + */ +bool FhgfsPageListVec_pushPage(FhgfsPageListVec* this, struct page* page, int usedPageLength) +{ + FhgfsPage* fhgfsPage; + + if (this->usedPages == BEEGFS_LIST_VEC_MAX_PAGES) + return false; + + fhgfsPage = &this->fhgfsPages[this->usedPages]; + this->usedPages++; + + fhgfsPage->page = page; + fhgfsPage->data = kmap(page); + fhgfsPage->length = usedPageLength; + + return true; +} + +/** + * Return the number of max pages of this vector + */ +size_t FhgfsPageListVec_getMaxPages(FhgfsPageListVec* this) +{ + return BEEGFS_LIST_VEC_MAX_PAGES; +} + +/** + * Return the page at the given index + */ +FhgfsPage* FhgfsPageListVec_getFhgfsPage(FhgfsPageListVec* this, size_t index) +{ + return &this->fhgfsPages[index]; +} + +#endif // FHGFSPAGELISTVEC_H_ diff --git a/client_module/source/toolkit/InodeRefStore.c b/client_module/source/toolkit/InodeRefStore.c new file mode 100644 index 0000000..ce34a44 --- /dev/null +++ b/client_module/source/toolkit/InodeRefStore.c @@ -0,0 +1,13 @@ +#include "InodeRefStore.h" + + +int __InodeRefStore_keyComparator(const void* key1, const void* key2) +{ + if(key1 < key2) + return -1; + else + if(key1 > key2) + return 1; + else + return 0; +} diff --git a/client_module/source/toolkit/InodeRefStore.h b/client_module/source/toolkit/InodeRefStore.h new file mode 100644 index 0000000..d7c7b44 --- /dev/null +++ b/client_module/source/toolkit/InodeRefStore.h @@ -0,0 +1,246 @@ +#ifndef INODEREFSTORE_H_ +#define INODEREFSTORE_H_ + +#include +#include +#include +#include +#include + + +/* + * This class saves references to inodes in a tree. It has special methods to make it suitable + * for async cache flushes. + */ + +struct InodeRefStore; +typedef struct InodeRefStore InodeRefStore; + +static inline void InodeRefStore_init(InodeRefStore* this); +static inline InodeRefStore* InodeRefStore_construct(void); +static inline void InodeRefStore_uninit(InodeRefStore* this); +static inline void InodeRefStore_destruct(InodeRefStore* this); + +static inline void InodeRefStore_addAndReferenceInode(InodeRefStore* this, struct inode* inode); +static inline void InodeRefStore_addOrPutInode(InodeRefStore* this, struct inode* inode); +static inline struct inode* InodeRefStore_getAndRemoveFirstInode(InodeRefStore* this); +static inline struct inode* InodeRefStore_getAndRemoveNextInode(InodeRefStore* this, + struct inode* oldInode); +static inline bool InodeRefStore_removeAndReleaseInode(InodeRefStore* this, + struct inode* inode); + +// getters & setters +static inline size_t InodeRefStore_getSize(InodeRefStore* this); + + +// static +extern int __InodeRefStore_keyComparator(const void* key1, const void* key2); + + +struct InodeRefStore +{ + RBTree tree; // keys are inode pointers, values are unused (NULL) + + Mutex mutex; +}; + + +void InodeRefStore_init(InodeRefStore* this) +{ + Mutex_init(&this->mutex); + + PointerRBTree_init(&this->tree, __InodeRefStore_keyComparator); +} + +struct InodeRefStore* InodeRefStore_construct(void) +{ + struct InodeRefStore* this = (InodeRefStore*)os_kmalloc(sizeof(*this) ); + + InodeRefStore_init(this); + + return this; +} + +void InodeRefStore_uninit(InodeRefStore* this) +{ + RBTreeIter iter = PointerRBTree_begin(&this->tree); + for( ; !PointerRBTreeIter_end(&iter); PointerRBTreeIter_next(&iter) ) + { + struct inode* inode = PointerRBTreeIter_key(&iter); + iput(inode); + } + + PointerRBTree_uninit(&this->tree); + + Mutex_uninit(&this->mutex); +} + +void InodeRefStore_destruct(InodeRefStore* this) +{ + InodeRefStore_uninit(this); + + kfree(this); +} + +/** + * Adds the given inode to the tree if not there yet. + * + * Note: The inode is referenced by calling ihold(). It must later be released by calling + * iput(). + */ +void InodeRefStore_addAndReferenceInode(InodeRefStore* this, struct inode* inode) +{ + Mutex_lock(&this->mutex); + + // only insert it, if not already within the three (mutex lock required) + if(PointerRBTree_insert(&this->tree, inode, NULL) ) + { + /* Only increase the counter once we know we had to insert it. + * Also increase the counter within the lock to avoid racing with _removeAndRelease() */ + ihold(inode); + } + + Mutex_unlock(&this->mutex); +} + +/** + * Adds the given inode to the tree or drops the reference if the inode already exists in the tree. + * + * Note: Usually, _addAndReferenceInode() should be used; this is only called if caller ensured + * that there is an extra inode reference (igrab() ) for this inode. + */ +void InodeRefStore_addOrPutInode(InodeRefStore* this, struct inode* inode) +{ + Mutex_lock(&this->mutex); + + // only insert it, if not already within the three (mutex lock required) + if(!PointerRBTree_insert(&this->tree, inode, NULL) ) + { + /* Decrease ref counter if the inode already exists, because that means the caller who + * added the inode already increased the ref count - and we only want to have one single + * reference per inode in this store. + * Also decrease the counter within the lock to avoid racing with _removeAndRelease() */ + iput(inode); + } + + Mutex_unlock(&this->mutex); +} + +/** + * Get first element in the store (for iteration). + * + * Note: If the inode was added by _addAndReferenceInode(), the caller will probably also want to + * call iput() after work with the returned inode is complete. + * + * @return NULL if the store is empty + */ +struct inode* InodeRefStore_getAndRemoveFirstInode(InodeRefStore* this) +{ + struct inode* firstInode = NULL; + RBTreeIter iter; + + Mutex_lock(&this->mutex); + + iter = PointerRBTree_begin(&this->tree); + if(!PointerRBTreeIter_end(&iter) ) + { + firstInode = PointerRBTreeIter_key(&iter); + + PointerRBTree_erase(&this->tree, firstInode); + } + + Mutex_unlock(&this->mutex); + + return firstInode; +} + +/** + * Get next inode and remove it from the store. Typically used for iteration based on + * _getAndRemoveFirstInode(). + * + * Note: If the inode was added by _addAndReferenceInode(), the caller will probably also want to + * call iput() after work with the returned inode is complete. + * + * @return NULL if no next inode exists in the store + */ +struct inode* InodeRefStore_getAndRemoveNextInode(InodeRefStore* this, struct inode* oldInode) +{ + struct inode* nextInode = NULL; + RBTreeIter iter; + + Mutex_lock(&this->mutex); + + iter = PointerRBTree_find(&this->tree, oldInode); + if(!PointerRBTreeIter_end(&iter) ) + { // fast path: oldInode still (or again) exists in the tree, so we can easily find next + + // retrieve (and remove) next element + PointerRBTreeIter_next(&iter); + if(!PointerRBTreeIter_end(&iter) ) + { + nextInode = PointerRBTreeIter_key(&iter); + + PointerRBTree_erase(&this->tree, nextInode); + } + } + else + { // slow path: oldInode doesn't exist, so we need to insert it to find the next element + + // temporarily insert oldInode to find next element + PointerRBTree_insert(&this->tree, oldInode, NULL); + iter = PointerRBTree_find(&this->tree, oldInode); + + // retrieve (and remove) next element + PointerRBTreeIter_next(&iter); + if(!PointerRBTreeIter_end(&iter) ) + { + nextInode = PointerRBTreeIter_key(&iter); + + PointerRBTree_erase(&this->tree, nextInode); + } + + // remove temporarily inserted oldInode + PointerRBTree_erase(&this->tree, oldInode); + } + + Mutex_unlock(&this->mutex); + + return nextInode; +} + +/** + * Remove inode from store. Only if it existed in the store, we also drop an inode reference. + * + * @return false if no such element existed, true otherwise + */ +bool InodeRefStore_removeAndReleaseInode(InodeRefStore* this, struct inode* inode) +{ + bool eraseRes; + + Mutex_lock(&this->mutex); + + eraseRes = PointerRBTree_erase(&this->tree, inode); + if(eraseRes) + iput(inode); // iput must be inside the mutex to avoid races with _addAndReference() + + Mutex_unlock(&this->mutex); + + return eraseRes; +} + + +size_t InodeRefStore_getSize(InodeRefStore* this) +{ + size_t retVal; + + Mutex_lock(&this->mutex); + + retVal = PointerRBTree_length(&this->tree); + + Mutex_unlock(&this->mutex); + + return retVal; +} + + +#endif /* INODEREFSTORE_H_ */ diff --git a/client_module/source/toolkit/NoAllocBufferStore.c b/client_module/source/toolkit/NoAllocBufferStore.c new file mode 100644 index 0000000..01b41fc --- /dev/null +++ b/client_module/source/toolkit/NoAllocBufferStore.c @@ -0,0 +1,309 @@ +#include +#include +#include + +#ifdef BEEGFS_DEBUG + static int __NoAllocBufferStore_PointerComparator(const void* ptr1, const void* ptr2); + static void __NoAllocBufferStore_debugAddTask(NoAllocBufferStore* this); + static void __NoAllocBufferStore_debugRemoveTask(NoAllocBufferStore* this); + static void __NoAllocBufferStore_debugCheckTask(NoAllocBufferStore* this); +#endif + +/** + * This BufferStore does not use any kind of memory allocation during its normal + * operation. However, it does (de)allocate the requested number of buffers + * during (de)initialization. + */ + +static bool __NoAllocBufferStore_initBuffers(NoAllocBufferStore* this); + + +struct NoAllocBufferStore +{ + char** bufArray; + + size_t numBufs; + size_t bufSize; + + size_t numAvailable; // number of currently available buffers in the store + + Mutex mutex; + Condition newBufCond; + +#ifdef BEEGFS_DEBUG + RBTree pidDebugTree; // store tasks that have taken a buffer +#endif +}; + + +#ifdef BEEGFS_DEBUG +/** + * Return suitable values for a tree + */ +static int __NoAllocBufferStore_PointerComparator(const void* ptr1, const void* ptr2) +{ + if (ptr1 < ptr2) + return -1; + else if (ptr1 == ptr2) + return 0; + + return 1; +} +#endif // BEEGFS_DEBUG + +/** + * @param numBufs number of buffers to be allocated, may be 0 + * @param bufSize size of each allocated buffer + */ +bool NoAllocBufferStore_init(NoAllocBufferStore* this, size_t numBufs, size_t bufSize) +{ + this->numAvailable = 0; + this->numBufs = numBufs; + this->bufSize = bufSize; + + Mutex_init(&this->mutex); + Condition_init(&this->newBufCond); + +#ifdef BEEGFS_DEBUG + PointerRBTree_init(&this->pidDebugTree, __NoAllocBufferStore_PointerComparator); +#endif + + if(!__NoAllocBufferStore_initBuffers(this) ) + { + Mutex_uninit(&this->mutex); + return false; + } + + return true; +} + +#ifdef BEEGFS_DEBUG +/** + * Add the buffer allocator into the debug tree, including the call trace. That way we can easily + * figure out two (forbidden) calls from the same thread. For debug builds only! + */ +static void __NoAllocBufferStore_debugAddTask(NoAllocBufferStore* this) +{ + void * trace = os_saveStackTrace(); + PointerRBTree_insert(&this->pidDebugTree, (void *)(long)current->pid, trace); +} +#else +#define __NoAllocBufferStore_debugAddTask(this) +#endif // BEEGFS_DEBUG + + +#ifdef BEEGFS_DEBUG +/** + * Tasks frees the buffer, so also remove it from the debug tree. For debug builds only! + */ +static void __NoAllocBufferStore_debugRemoveTask(NoAllocBufferStore* this) +{ + void * currentPID = (void *)(long)current->pid; + RBTreeIter iter = PointerRBTree_find(&this->pidDebugTree, currentPID); + void *trace; + + if (PointerRBTreeIter_end(&iter) == true) + return; // Buffer was likely/hopefully added with NoAllocBufferStore_instantBuf() + + trace = PointerRBTreeIter_value(&iter); + os_freeStackTrace(trace); + + PointerRBTree_erase(&this->pidDebugTree, currentPID); +} +#else +#define __NoAllocBufferStore_debugRemoveTask(this) +#endif + +#ifdef BEEGFS_DEBUG +/** + * Check if the debug tree already knows about the current pid and if so, print a debug message + * to syslog to warn about the possible deadlock. NOTE: For debug builds only! + */ +static void __NoAllocBufferStore_debugCheckTask(NoAllocBufferStore* this) +{ + RBTreeIter iter = PointerRBTree_find(&this->pidDebugTree, (void *)(long)current->pid); + if (unlikely(PointerRBTreeIter_end(&iter) == false) ) + { + void *trace = PointerRBTreeIter_value(&iter); + printk_fhgfs(KERN_WARNING, "Thread called BufferStore two times - might deadlock!\n"); + dump_stack(); + printk_fhgfs(KERN_WARNING, "Call trace of the previous allocation:\n"); + os_printStackTrace(trace, 4); + } +} +#else +#define __NoAllocBufferStore_debugCheckTask(this) +#endif // BEEGFS_DEBUG + + +/** + * @param numBufs number of buffers to be allocated + * @param bufSize size of each allocated buffer + */ +struct NoAllocBufferStore* NoAllocBufferStore_construct(size_t numBufs, size_t bufSize) +{ + struct NoAllocBufferStore* this = kmalloc(sizeof(*this), GFP_NOFS); + + if(!this || + !NoAllocBufferStore_init(this, numBufs, bufSize) ) + { + kfree(this); + return NULL; + } + + return this; +} + +void NoAllocBufferStore_uninit(NoAllocBufferStore* this) +{ + // delete buffers + size_t i; + + for(i=0; i < this->numAvailable; i++) + vfree(this->bufArray[i]); + + // normal clean-up + Mutex_uninit(&this->mutex); + + SAFE_KFREE(this->bufArray); + +#ifdef BEEGFS_DEBUG + PointerRBTree_uninit(&this->pidDebugTree); +#endif + +} + +void NoAllocBufferStore_destruct(NoAllocBufferStore* this) +{ + NoAllocBufferStore_uninit(this); + + kfree(this); +} + +/** + * Gets a buffer from the store. + * Waits if no buffer is immediately available. + * + * @return a valid buffer pointer + */ +char* NoAllocBufferStore_waitForBuf(NoAllocBufferStore* this) +{ + char* buf; + + Mutex_lock(&this->mutex); + + __NoAllocBufferStore_debugCheckTask(this); + + while(!this->numAvailable) + Condition_wait(&this->newBufCond, &this->mutex); + + buf = (this->bufArray)[this->numAvailable-1]; + (this->numAvailable)--; + + __NoAllocBufferStore_debugAddTask(this); + + Mutex_unlock(&this->mutex); + + return buf; +} + +/** + * Gets a buffer from the store. + * Fails if no buffer is immediately available. + * + * @return buffer pointer if a buffer was immediately available, NULL otherwise + */ +char* NoAllocBufferStore_instantBuf(NoAllocBufferStore* this) +{ + char* buf; + + Mutex_lock(&this->mutex); + + if(!this->numAvailable) + buf = NULL; + else + { + buf = (this->bufArray)[this->numAvailable-1]; + (this->numAvailable)--; + + /* note: no _debugAddTask() here, because _instantBuf() is specifically intended to avoid + deadlocks (so we would generate only a lot of false alarms with this). */ + } + + Mutex_unlock(&this->mutex); + + return buf; +} + +/** + * Re-add a buffer to the pool + */ +void NoAllocBufferStore_addBuf(NoAllocBufferStore* this, char* buf) +{ + if (unlikely(buf == NULL) ) + { + BEEGFS_BUG_ON(buf == NULL, "NULL buffer detected!"); + return; // If the caller had a real buffer, it will leak now! + } + + Mutex_lock(&this->mutex); + + Condition_signal(&this->newBufCond); + + (this->bufArray)[this->numAvailable] = buf; + (this->numAvailable)++; + + __NoAllocBufferStore_debugRemoveTask(this); + + Mutex_unlock(&this->mutex); +} + +bool __NoAllocBufferStore_initBuffers(NoAllocBufferStore* this) +{ + size_t i; + + this->bufArray = this->numBufs ? (char**)os_kzalloc(this->numBufs * sizeof(char*) ) : NULL; + if (!this->bufArray && (this->numBufs != 0) ) // numBufs is 0 for pageIO buffers in buffered mode + return false; + + for(i = 0; i < this->numBufs; i++) + { + char* buf = vmalloc(this->bufSize); + if(!buf) + { + printk_fhgfs(KERN_WARNING, "NoAllocBufferStore_initBuffers: vmalloc failed to alloc " + "%lld bytes for buffer number %lld\n", (long long)this->bufSize, (long long)i); + + goto error; + } + + (this->bufArray)[i] = buf; + (this->numAvailable)++; + } + + return true; + +error: + for(i = 0; i < this->numBufs; i++) + vfree(this->bufArray[i]); + + return false; +} + +size_t NoAllocBufferStore_getNumAvailable(NoAllocBufferStore* this) +{ + size_t numAvailable; + + Mutex_lock(&this->mutex); + + numAvailable = this->numAvailable; + + Mutex_unlock(&this->mutex); + + return numAvailable; +} + +size_t NoAllocBufferStore_getBufSize(NoAllocBufferStore* this) +{ + return this->bufSize; +} diff --git a/client_module/source/toolkit/NoAllocBufferStore.h b/client_module/source/toolkit/NoAllocBufferStore.h new file mode 100644 index 0000000..a13cdd3 --- /dev/null +++ b/client_module/source/toolkit/NoAllocBufferStore.h @@ -0,0 +1,32 @@ +#ifndef NOALLOCBUFFERSTORE_H_ +#define NOALLOCBUFFERSTORE_H_ + +#include +#include +#include +#include + +/** + * This BufferStore does not use any kind of memory allocation during its normal + * operation. However, it does (de)allocate the requested number of buffers + * during (de)initialization. + */ + +struct NoAllocBufferStore; +typedef struct NoAllocBufferStore NoAllocBufferStore; + +extern __must_check bool NoAllocBufferStore_init(NoAllocBufferStore* this, size_t numBufs, + size_t bufSize); +extern NoAllocBufferStore* NoAllocBufferStore_construct(size_t numBufs, size_t bufSize); +extern void NoAllocBufferStore_uninit(NoAllocBufferStore* this); +extern void NoAllocBufferStore_destruct(NoAllocBufferStore* this); + +extern char* NoAllocBufferStore_waitForBuf(NoAllocBufferStore* this); +extern char* NoAllocBufferStore_instantBuf(NoAllocBufferStore* this); +extern void NoAllocBufferStore_addBuf(NoAllocBufferStore* this, char* buf); + +// getters & setters +extern size_t NoAllocBufferStore_getNumAvailable(NoAllocBufferStore* this); +extern size_t NoAllocBufferStore_getBufSize(NoAllocBufferStore* this); + +#endif /*NOALLOCBUFFERSTORE_H_*/ diff --git a/client_module/source/toolkit/StatFsCache.c b/client_module/source/toolkit/StatFsCache.c new file mode 100644 index 0000000..8778659 --- /dev/null +++ b/client_module/source/toolkit/StatFsCache.c @@ -0,0 +1,76 @@ +#include +#include +#include "StatFsCache.h" + + +/** + * If caching is enabled and cache is not expired, this will return the cached values. Otherwise + * FhgfsOpsRemoting_statStoragePath() will be called for fresh values. + * + * @param outSizeTotal in bytes. + * @param outSizeFree in bytes. + * @return if remoting fails, the error code from FhgfsOpsRemoting_statStoragePath() will be + * returned. + */ +FhgfsOpsErr StatFsCache_getFreeSpace(StatFsCache* this, int64_t* outSizeTotal, + int64_t* outSizeFree) +{ + Config* cfg = App_getConfig(this->app); + unsigned tuneStatFsCacheSecs = Config_getTuneStatFsCacheSecs(cfg); // zero means disable caching + + if(tuneStatFsCacheSecs) + { // caching enabled, test if cache values expired + + RWLock_readLock(&this->rwlock); // L O C K (read) + + if(!Time_getIsZero(&this->lastUpdateTime) && // zero time means cache values are uninitialized + (Time_elapsedMS(&this->lastUpdateTime) <= (tuneStatFsCacheSecs*1000) ) ) // *1000 for MS + { // cache values are still valid + *outSizeTotal = this->cachedSizeTotal; + *outSizeFree = this->cachedSizeFree; + + RWLock_readUnlock(&this->rwlock); // U N L O C K (read) + + return FhgfsOpsErr_SUCCESS; + } + + // cache values not valid + + RWLock_readUnlock(&this->rwlock); // U N L O C K (read) + } + + // get fresh values from servers + + { + FhgfsOpsErr retVal = FhgfsOpsRemoting_statStoragePath(this->app, true, + outSizeTotal, outSizeFree); + + if(retVal != FhgfsOpsErr_SUCCESS) + { + *outSizeTotal = 0; + *outSizeFree = 0; + + return retVal; + } + } + + // update cached values + /* note: as we don't hold a write-lock during remoting (to allow parallelism), it's possible that + multiple threads are racing with different values on the update below, but that's uncritical + for free space info */ + + if(tuneStatFsCacheSecs) + { + RWLock_writeLock(&this->rwlock); // L O C K (write) + + this->cachedSizeTotal = *outSizeTotal; + this->cachedSizeFree = *outSizeFree; + + Time_setToNow(&this->lastUpdateTime); + + RWLock_writeUnlock(&this->rwlock); // U N L O C K (write) + } + + + return FhgfsOpsErr_SUCCESS; +} diff --git a/client_module/source/toolkit/StatFsCache.h b/client_module/source/toolkit/StatFsCache.h new file mode 100644 index 0000000..e5f12e8 --- /dev/null +++ b/client_module/source/toolkit/StatFsCache.h @@ -0,0 +1,67 @@ +#ifndef STATFSCACHE_H_ +#define STATFSCACHE_H_ + +#include +#include +#include +#include + + +struct App; // forward declaration + +struct StatFsCache; +typedef struct StatFsCache StatFsCache; + + +static inline void StatFsCache_init(StatFsCache* this, App* app); +static inline StatFsCache* StatFsCache_construct(App* app); +static inline void StatFsCache_destruct(StatFsCache* this); + +FhgfsOpsErr StatFsCache_getFreeSpace(StatFsCache* this, int64_t* outSizeTotal, + int64_t* outSizeFree); + + +/** + * This class provides a cache for free space information to avoid querying all storage targets + * for each statfs() syscall. + */ +struct StatFsCache +{ + App* app; + + RWLock rwlock; + + Time lastUpdateTime; // time when cache values were last updated + + int64_t cachedSizeTotal; // in bytes + int64_t cachedSizeFree; // in bytes +}; + + +void StatFsCache_init(StatFsCache* this, App* app) +{ + this->app = app; + + RWLock_init(&this->rwlock); + + Time_initZero(&this->lastUpdateTime); +} + +struct StatFsCache* StatFsCache_construct(App* app) +{ + struct StatFsCache* this = (StatFsCache*)os_kmalloc(sizeof(*this) ); + + if(likely(this) ) + StatFsCache_init(this, app); + + return this; +} + +void StatFsCache_destruct(StatFsCache* this) +{ + kfree(this); +} + + + +#endif /* STATFSCACHE_H_ */ diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt new file mode 100644 index 0000000..d1c4e17 --- /dev/null +++ b/common/CMakeLists.txt @@ -0,0 +1,601 @@ + +include(FindPkgConfig) +if(NOT PKG_CONFIG_FOUND) + message(FATAL_ERROR "pkg-config not found!" ) +endif() + +pkg_check_modules(NL3ROUTE REQUIRED IMPORTED_TARGET libnl-route-3.0) + +include_directories( + source +) + +link_directories( + ${NL3ROUTE_LIBRARY_DIRS} +) + +add_library( + beegfs-common STATIC + ./source/common/Assert.cpp + ./source/common/toolkit/StorageTk.h + ./source/common/toolkit/RandomReentrant.h + ./source/common/toolkit/FsckTk.cpp + ./source/common/toolkit/HashTk.h + ./source/common/toolkit/UnitTk.h + ./source/common/toolkit/PreallocatedFile.h + ./source/common/toolkit/StringTk.h + ./source/common/toolkit/Time.h + ./source/common/toolkit/DisposalCleaner.h + ./source/common/toolkit/MessagingTk.cpp + ./source/common/toolkit/StringTk.cpp + ./source/common/toolkit/FileDescriptor.h + ./source/common/toolkit/EntryIdTk.h + ./source/common/toolkit/ObjectReferencer.h + ./source/common/toolkit/NamedException.h + ./source/common/toolkit/MapTk.cpp + ./source/common/toolkit/MapTk.h + ./source/common/toolkit/TimeAbs.h + ./source/common/toolkit/BitStore.h + ./source/common/toolkit/MetaStorageTk.h + ./source/common/toolkit/MetadataTk.cpp + ./source/common/toolkit/SessionTk.h + ./source/common/toolkit/BuildTypeTk.h + ./source/common/toolkit/ZipIterator.h + ./source/common/toolkit/HighResolutionStats.h + ./source/common/toolkit/NodesTk.h + ./source/common/toolkit/serialization/Serialization.h + ./source/common/toolkit/serialization/Byteswap.h + ./source/common/toolkit/serialization/SerializeStr.cpp + ./source/common/toolkit/OfflineWaitTimeoutTk.h + ./source/common/toolkit/AcknowledgmentStore.cpp + ./source/common/toolkit/SocketTk.h + ./source/common/toolkit/HashTk.cpp + ./source/common/toolkit/AtomicObjectReferencer.h + ./source/common/toolkit/poll/PollList.cpp + ./source/common/toolkit/poll/PollList.h + ./source/common/toolkit/poll/Pollable.h + ./source/common/toolkit/AcknowledgmentStore.h + ./source/common/toolkit/DisposalCleaner.cpp + ./source/common/toolkit/Time.cpp + ./source/common/toolkit/MinMaxStore.h + ./source/common/toolkit/NodesTk.cpp + ./source/common/toolkit/FsckTk.h + ./source/common/toolkit/UiTk.cpp + ./source/common/toolkit/MessagingTkArgs.h + ./source/common/toolkit/StorageTk.cpp + ./source/common/toolkit/MathTk.h + ./source/common/toolkit/MessagingTk.h + ./source/common/toolkit/TempFileTk.h + ./source/common/toolkit/FDHandle.h + ./source/common/toolkit/ListTk.h + ./source/common/toolkit/LockFD.cpp + ./source/common/toolkit/SocketTk.cpp + ./source/common/toolkit/SynchronizedCounter.h + ./source/common/toolkit/TimeTk.h + ./source/common/toolkit/NetFilter.h + ./source/common/toolkit/Pipe.h + ./source/common/toolkit/UnitTk.cpp + ./source/common/toolkit/BitStore.cpp + ./source/common/toolkit/TimeFine.h + ./source/common/toolkit/EntryIdTk.cpp + ./source/common/toolkit/DebugVariable.h + ./source/common/toolkit/Random.h + ./source/common/toolkit/UiTk.h + ./source/common/toolkit/LockFD.h + ./source/common/toolkit/TimeException.h + ./source/common/toolkit/TempFileTk.cpp + ./source/common/toolkit/MetadataTk.h + ./source/common/toolkit/BuildTypeTk.cpp + ./source/common/toolkit/hash_library/sha256.h + ./source/common/toolkit/hash_library/sha256.cpp + ./source/common/Common.h + ./source/common/benchmark/StorageBench.h + ./source/common/NumericID.h + ./source/common/net/sock/SocketDisconnectException.h + ./source/common/net/sock/Socket.cpp + ./source/common/net/sock/SocketTimeoutException.h + ./source/common/net/sock/RoutingTable.h + ./source/common/net/sock/RoutingTable.cpp + ./source/common/net/sock/RDMASocket.cpp + ./source/common/net/sock/StandardSocket.h + ./source/common/net/sock/SocketException.h + ./source/common/net/sock/Socket.h + ./source/common/net/sock/NetworkInterfaceCard.cpp + ./source/common/net/sock/NetworkInterfaceCard.h + ./source/common/net/sock/StandardSocket.cpp + ./source/common/net/sock/SocketInterruptedPollException.h + ./source/common/net/sock/SocketConnectException.h + ./source/common/net/sock/Channel.h + ./source/common/net/sock/PooledSocket.h + ./source/common/net/sock/RDMASocket.h + ./source/common/net/message/AbstractNetMessageFactory.cpp + ./source/common/net/message/SimpleStringMsg.h + ./source/common/net/message/mon/RequestStorageDataRespMsg.h + ./source/common/net/message/mon/RequestMetaDataMsg.h + ./source/common/net/message/mon/RequestStorageDataMsg.h + ./source/common/net/message/mon/RequestMetaDataRespMsg.h + ./source/common/net/message/SimpleIntStringMsg.h + ./source/common/net/message/AcknowledgeableMsg.h + ./source/common/net/message/NetMessage.h + ./source/common/net/message/control/AuthenticateChannelMsg.h + ./source/common/net/message/control/AckMsg.h + ./source/common/net/message/control/AuthenticateChannelMsgEx.h + ./source/common/net/message/control/AuthenticateChannelMsgEx.cpp + ./source/common/net/message/control/PeerInfoMsgEx.cpp + ./source/common/net/message/control/PeerInfoMsgEx.h + ./source/common/net/message/control/GenericResponseMsg.h + ./source/common/net/message/control/DummyMsg.h + ./source/common/net/message/control/SetChannelDirectMsg.h + ./source/common/net/message/control/PeerInfoMsg.h + ./source/common/net/message/NetMessageLogHelper.h + ./source/common/net/message/SimpleUInt16Msg.h + ./source/common/net/message/session/BumpFileVersionRespMsg.h + ./source/common/net/message/session/RefreshSessionMsg.h + ./source/common/net/message/session/opening/CloseChunkFileMsg.h + ./source/common/net/message/session/opening/OpenFileMsg.h + ./source/common/net/message/session/opening/CloseChunkFileRespMsg.h + ./source/common/net/message/session/opening/CloseFileRespMsg.h + ./source/common/net/message/session/opening/CloseFileMsg.h + ./source/common/net/message/session/opening/OpenFileRespMsg.h + ./source/common/net/message/session/AckNotifyRespMsg.h + ./source/common/net/message/session/AckNotifyMsg.h + ./source/common/net/message/session/rw/WriteLocalFileMsg.h + ./source/common/net/message/session/rw/WriteLocalFileRespMsg.h + ./source/common/net/message/session/rw/ReadLocalFileV2Msg.h + ./source/common/net/message/session/GetFileVersionMsg.h + ./source/common/net/message/session/RefreshSessionRespMsg.h + ./source/common/net/message/session/locking/FLockEntryRespMsg.h + ./source/common/net/message/session/locking/LockGrantedMsg.h + ./source/common/net/message/session/locking/FLockRangeRespMsg.h + ./source/common/net/message/session/locking/FLockRangeMsg.h + ./source/common/net/message/session/locking/FLockEntryMsg.h + ./source/common/net/message/session/locking/FLockAppendRespMsg.h + ./source/common/net/message/session/locking/FLockAppendMsg.h + ./source/common/net/message/session/FSyncLocalFileMsg.h + ./source/common/net/message/session/BumpFileVersionMsg.h + ./source/common/net/message/session/FSyncLocalFileRespMsg.h + ./source/common/net/message/session/GetFileVersionRespMsg.h + ./source/common/net/message/NetMessageTypes.h + ./source/common/net/message/SimpleMsg.h + ./source/common/net/message/SimpleIntMsg.h + ./source/common/net/message/nodes/GetMirrorBuddyGroupsRespMsg.h + ./source/common/net/message/nodes/RegisterTargetRespMsg.h + ./source/common/net/message/nodes/RemoveBuddyGroupRespMsg.h + ./source/common/net/message/nodes/GetTargetStatesMsg.h + ./source/common/net/message/nodes/GetTargetMappingsMsg.h + ./source/common/net/message/nodes/storagepools/RefreshStoragePoolsMsg.h + ./source/common/net/message/nodes/storagepools/RemoveStoragePoolMsg.h + ./source/common/net/message/nodes/storagepools/GetStoragePoolsRespMsg.h + ./source/common/net/message/nodes/storagepools/ModifyStoragePoolRespMsg.h + ./source/common/net/message/nodes/storagepools/GetStoragePoolsMsg.h + ./source/common/net/message/nodes/storagepools/ModifyStoragePoolMsg.h + ./source/common/net/message/nodes/storagepools/AddStoragePoolMsg.h + ./source/common/net/message/nodes/storagepools/RemoveStoragePoolRespMsg.h + ./source/common/net/message/nodes/storagepools/AddStoragePoolRespMsg.h + ./source/common/net/message/nodes/UnmapTargetRespMsg.h + ./source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h + ./source/common/net/message/nodes/GetTargetMappingsRespMsg.h + ./source/common/net/message/nodes/RemoveBuddyGroupMsg.h + ./source/common/net/message/nodes/GetTargetConsistencyStatesMsg.h + ./source/common/net/message/nodes/RegisterTargetMsg.h + ./source/common/net/message/nodes/PublishCapacitiesMsg.h + ./source/common/net/message/nodes/RefreshCapacityPoolsMsg.h + ./source/common/net/message/nodes/HeartbeatMsg.h + ./source/common/net/message/nodes/GetNodeCapacityPoolsMsg.h + ./source/common/net/message/nodes/UnmapTargetMsg.h + ./source/common/net/message/nodes/StorageBenchControlMsgResp.h + ./source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h + ./source/common/net/message/nodes/HeartbeatRequestMsg.h + ./source/common/net/message/nodes/GetTargetConsistencyStatesRespMsg.h + ./source/common/net/message/nodes/SetTargetConsistencyStatesRespMsg.h + ./source/common/net/message/nodes/GenericDebugMsg.h + ./source/common/net/message/nodes/RegisterNodeMsg.h + ./source/common/net/message/nodes/RemoveNodeMsg.h + ./source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h + ./source/common/net/message/nodes/SetTargetConsistencyStatesMsg.h + ./source/common/net/message/nodes/GenericDebugRespMsg.h + ./source/common/net/message/nodes/SetMirrorBuddyGroupRespMsg.h + ./source/common/net/message/nodes/GetClientStatsMsg.h + ./source/common/net/message/nodes/GetClientStatsRespMsg.h + ./source/common/net/message/nodes/GetNodeCapacityPoolsRespMsg.h + ./source/common/net/message/nodes/MapTargetsRespMsg.h + ./source/common/net/message/nodes/RefreshTargetStatesMsg.h + ./source/common/net/message/nodes/ChangeTargetConsistencyStatesRespMsg.h + ./source/common/net/message/nodes/MapTargetsMsg.h + ./source/common/net/message/nodes/StorageBenchControlMsg.h + ./source/common/net/message/nodes/RemoveNodeRespMsg.h + ./source/common/net/message/nodes/SetMirrorBuddyGroupMsg.h + ./source/common/net/message/nodes/GetNodesMsg.h + ./source/common/net/message/nodes/RegisterNodeRespMsg.h + ./source/common/net/message/nodes/GetTargetStatesRespMsg.h + ./source/common/net/message/nodes/GetNodesRespMsg.h + ./source/common/net/message/nodes/ChangeTargetConsistencyStatesMsg.h + ./source/common/net/message/AbstractNetMessageFactory.h + ./source/common/net/message/storage/TruncLocalFileMsg.h + ./source/common/net/message/storage/moving/MovingFileInsertRespMsg.h + ./source/common/net/message/storage/moving/RenameMsg.h + ./source/common/net/message/storage/moving/MovingDirInsertMsg.h + ./source/common/net/message/storage/moving/MovingFileInsertMsg.h + ./source/common/net/message/storage/moving/MovingDirInsertRespMsg.h + ./source/common/net/message/storage/moving/RenameRespMsg.h + ./source/common/net/message/storage/creating/RmDirMsg.h + ./source/common/net/message/storage/creating/MkFileWithPatternMsg.h + ./source/common/net/message/storage/creating/UnlinkFileRespMsg.h + ./source/common/net/message/storage/creating/MkFileWithPatternRespMsg.h + ./source/common/net/message/storage/creating/HardlinkRespMsg.h + ./source/common/net/message/storage/creating/UnlinkFileMsg.h + ./source/common/net/message/storage/creating/RmChunkPathsRespMsg.h + ./source/common/net/message/storage/creating/MkLocalDirRespMsg.h + ./source/common/net/message/storage/creating/MkFileMsg.h + ./source/common/net/message/storage/creating/MkFileRespMsg.h + ./source/common/net/message/storage/creating/HardlinkMsg.h + ./source/common/net/message/storage/creating/RmChunkPathsMsg.h + ./source/common/net/message/storage/creating/MkDirRespMsg.h + ./source/common/net/message/storage/creating/RmDirEntryMsg.h + ./source/common/net/message/storage/creating/MkLocalDirMsg.h + ./source/common/net/message/storage/creating/RmDirEntryRespMsg.h + ./source/common/net/message/storage/creating/RmDirRespMsg.h + ./source/common/net/message/storage/creating/UnlinkLocalFileMsg.h + ./source/common/net/message/storage/creating/RmLocalDirMsg.h + ./source/common/net/message/storage/creating/MkDirMsg.h + ./source/common/net/message/storage/creating/RmLocalDirRespMsg.h + ./source/common/net/message/storage/creating/UnlinkLocalFileRespMsg.h + ./source/common/net/message/storage/mirroring/ResyncLocalFileRespMsg.h + ./source/common/net/message/storage/mirroring/MirrorMetadataRespMsg.h + ./source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideMsg.h + ./source/common/net/message/storage/mirroring/ResyncSessionStoreRespMsg.h + ./source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideRespMsg.h + ./source/common/net/message/storage/mirroring/GetStorageResyncStatsRespMsg.h + ./source/common/net/message/storage/mirroring/StorageResyncStartedMsg.h + ./source/common/net/message/storage/mirroring/ResyncRawInodesRespMsg.h + ./source/common/net/message/storage/mirroring/ResyncLocalFileMsg.h + ./source/common/net/message/storage/mirroring/StorageResyncStartedRespMsg.h + ./source/common/net/message/storage/mirroring/GetMetaResyncStatsMsg.h + ./source/common/net/message/storage/mirroring/ResyncSessionStoreMsg.h + ./source/common/net/message/storage/mirroring/SetMetadataMirroringRespMsg.h + ./source/common/net/message/storage/mirroring/GetStorageResyncStatsMsg.h + ./source/common/net/message/storage/mirroring/SetMetadataMirroringMsg.h + ./source/common/net/message/storage/mirroring/GetMetaResyncStatsRespMsg.h + ./source/common/net/message/storage/mirroring/MirrorMetadataMsg.h + ./source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h + ./source/common/net/message/storage/attribs/GetXAttrRespMsg.h + ./source/common/net/message/storage/attribs/ListXAttrRespMsg.h + ./source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h + ./source/common/net/message/storage/attribs/StatRespMsg.h + ./source/common/net/message/storage/attribs/UpdateDirParentMsg.h + ./source/common/net/message/storage/attribs/SetDirPatternMsg.h + ./source/common/net/message/storage/attribs/ListXAttrMsg.h + ./source/common/net/message/storage/attribs/GetXAttrMsg.h + ./source/common/net/message/storage/attribs/StatMsg.h + ./source/common/net/message/storage/attribs/SetXAttrRespMsg.h + ./source/common/net/message/storage/attribs/SetAttrMsg.h + ./source/common/net/message/storage/attribs/RemoveXAttrMsg.h + ./source/common/net/message/storage/attribs/GetEntryInfoRespMsg.h + ./source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h + ./source/common/net/message/storage/attribs/SetXAttrMsg.h + ./source/common/net/message/storage/attribs/GetChunkFileAttribsRespMsg.h + ./source/common/net/message/storage/attribs/UpdateDirParentRespMsg.h + ./source/common/net/message/storage/attribs/GetChunkFileAttribsMsg.h + ./source/common/net/message/storage/attribs/SetAttrRespMsg.h + ./source/common/net/message/storage/attribs/SetLocalAttrMsg.h + ./source/common/net/message/storage/attribs/GetEntryInfoMsg.h + ./source/common/net/message/storage/attribs/SetDirPatternRespMsg.h + ./source/common/net/message/storage/attribs/SetLocalAttrRespMsg.h + ./source/common/net/message/storage/TruncFileMsg.h + ./source/common/net/message/storage/StatStoragePathRespMsg.h + ./source/common/net/message/storage/SetStorageTargetInfoMsg.h + ./source/common/net/message/storage/SetStorageTargetInfoRespMsg.h + ./source/common/net/message/storage/lookup/LookupIntentRespMsg.h + ./source/common/net/message/storage/lookup/FindOwnerRespMsg.h + ./source/common/net/message/storage/lookup/FindLinkOwnerRespMsg.h + ./source/common/net/message/storage/lookup/LookupIntentMsg.h + ./source/common/net/message/storage/lookup/FindLinkOwnerMsg.h + ./source/common/net/message/storage/lookup/FindOwnerMsg.h + ./source/common/net/message/storage/listing/ListChunkDirIncrementalMsg.h + ./source/common/net/message/storage/listing/ListDirFromOffsetMsg.h + ./source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h + ./source/common/net/message/storage/listing/ListChunkDirIncrementalRespMsg.h + ./source/common/net/message/storage/GetHighResStatsRespMsg.h + ./source/common/net/message/storage/TruncFileRespMsg.h + ./source/common/net/message/storage/GetHighResStatsMsg.h + ./source/common/net/message/storage/quota/SetExceededQuotaMsg.h + ./source/common/net/message/storage/quota/GetDefaultQuotaRespMsg.h + ./source/common/net/message/storage/quota/GetDefaultQuotaMsg.h + ./source/common/net/message/storage/quota/SetQuotaRespMsg.h + ./source/common/net/message/storage/quota/SetDefaultQuotaMsg.h + ./source/common/net/message/storage/quota/GetQuotaInfoMsg.h + ./source/common/net/message/storage/quota/SetQuotaMsg.h + ./source/common/net/message/storage/quota/SetDefaultQuotaRespMsg.h + ./source/common/net/message/storage/quota/RequestExceededQuotaMsg.h + ./source/common/net/message/storage/quota/GetQuotaInfoRespMsg.h + ./source/common/net/message/storage/quota/SetExceededQuotaRespMsg.h + ./source/common/net/message/storage/quota/RequestExceededQuotaRespMsg.h + ./source/common/net/message/storage/TruncLocalFileRespMsg.h + ./source/common/net/message/storage/StatStoragePathMsg.h + ./source/common/net/message/fsck/RetrieveFsIDsRespMsg.h + ./source/common/net/message/fsck/FsckSetEventLoggingMsg.h + ./source/common/net/message/fsck/RetrieveDirEntriesMsg.h + ./source/common/net/message/fsck/RemoveInodesRespMsg.h + ./source/common/net/message/fsck/FetchFsckChunkListRespMsg.h + ./source/common/net/message/fsck/MoveChunkFileMsg.h + ./source/common/net/message/fsck/CreateDefDirInodesRespMsg.h + ./source/common/net/message/fsck/UpdateDirAttribsMsg.h + ./source/common/net/message/fsck/UpdateFileAttribsMsg.h + ./source/common/net/message/fsck/FetchFsckChunkListMsg.h + ./source/common/net/message/fsck/AdjustChunkPermissionsMsg.h + ./source/common/net/message/fsck/FsckSetEventLoggingRespMsg.h + ./source/common/net/message/fsck/FixInodeOwnersInDentryMsg.h + ./source/common/net/message/fsck/RecreateDentriesRespMsg.h + ./source/common/net/message/fsck/FixInodeOwnersRespMsg.h + ./source/common/net/message/fsck/RetrieveInodesRespMsg.h + ./source/common/net/message/fsck/LinkToLostAndFoundMsg.h + ./source/common/net/message/fsck/RecreateFsIDsMsg.h + ./source/common/net/message/fsck/RetrieveFsIDsMsg.h + ./source/common/net/message/fsck/MoveChunkFileRespMsg.h + ./source/common/net/message/fsck/DeleteDirEntriesMsg.h + ./source/common/net/message/fsck/FsckModificationEventMsg.h + ./source/common/net/message/fsck/DeleteDirEntriesRespMsg.h + ./source/common/net/message/fsck/FixInodeOwnersInDentryRespMsg.h + ./source/common/net/message/fsck/RemoveInodesMsg.h + ./source/common/net/message/fsck/FixInodeOwnersMsg.h + ./source/common/net/message/fsck/DeleteChunksRespMsg.h + ./source/common/net/message/fsck/UpdateDirAttribsRespMsg.h + ./source/common/net/message/fsck/CreateEmptyContDirsRespMsg.h + ./source/common/net/message/fsck/RetrieveDirEntriesRespMsg.h + ./source/common/net/message/fsck/RetrieveInodesMsg.h + ./source/common/net/message/fsck/AdjustChunkPermissionsRespMsg.h + ./source/common/net/message/fsck/DeleteChunksMsg.h + ./source/common/net/message/fsck/RecreateDentriesMsg.h + ./source/common/net/message/fsck/CreateEmptyContDirsMsg.h + ./source/common/net/message/fsck/CreateDefDirInodesMsg.h + ./source/common/net/message/fsck/UpdateFileAttribsRespMsg.h + ./source/common/net/message/fsck/LinkToLostAndFoundRespMsg.h + ./source/common/net/message/fsck/RecreateFsIDsRespMsg.h + ./source/common/net/message/SimpleInt64Msg.h + ./source/common/net/msghelpers/MsgHelperGenericDebug.cpp + ./source/common/net/msghelpers/MsgHelperGenericDebug.h + ./source/common/logging/Backtrace.h + ./source/common/components/AbstractDatagramListener.h + ./source/common/components/ComponentInitException.h + ./source/common/components/StreamListener.h + ./source/common/components/RegistrationDatagramListener.h + ./source/common/components/RegistrationDatagramListener.cpp + ./source/common/components/StatsCollector.h + ./source/common/components/StatsCollector.cpp + ./source/common/components/TimerQueue.h + ./source/common/components/worker/WriteLocalFileWork.h + ./source/common/components/worker/queue/MultiWorkQueue.cpp + ./source/common/components/worker/queue/ListWorkContainer.h + ./source/common/components/worker/queue/UserWorkContainer.h + ./source/common/components/worker/queue/StreamListenerWorkQueue.h + ./source/common/components/worker/queue/MultiWorkQueue.h + ./source/common/components/worker/queue/AbstractWorkContainer.h + ./source/common/components/worker/queue/PersonalWorkQueue.h + ./source/common/components/worker/queue/WorkQueue.h + ./source/common/components/worker/Work.h + ./source/common/components/worker/UnixConnWorker.h + ./source/common/components/worker/ReadLocalFileV2Work.h + ./source/common/components/worker/GetQuotaInfoWork.cpp + ./source/common/components/worker/GetQuotaInfoWork.h + ./source/common/components/worker/IncSyncedCounterWork.h + ./source/common/components/worker/LocalConnWorker.cpp + ./source/common/components/worker/DecAtomicWork.cpp + ./source/common/components/worker/IncAtomicWork.h + ./source/common/components/worker/IncomingDataWork.h + ./source/common/components/worker/Worker.cpp + ./source/common/components/worker/WriteLocalFileWork.cpp + ./source/common/components/worker/Worker.h + ./source/common/components/worker/ReadLocalFileV2Work.cpp + ./source/common/components/worker/DummyWork.h + ./source/common/components/worker/IncAtomicWork.cpp + ./source/common/components/worker/IncomingDataWork.cpp + ./source/common/components/worker/DecAtomicWork.h + ./source/common/components/worker/LocalConnWorker.h + ./source/common/components/TimerQueue.cpp + ./source/common/components/streamlistenerv2/StreamListenerV2.cpp + ./source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.cpp + ./source/common/components/streamlistenerv2/ConnAcceptor.cpp + ./source/common/components/streamlistenerv2/StreamListenerV2.h + ./source/common/components/streamlistenerv2/ConnAcceptor.h + ./source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h + ./source/common/components/StreamListener.cpp + ./source/common/components/AbstractDatagramListener.cpp + ./source/common/system/System.cpp + ./source/common/system/System.h + ./source/common/app/log/LogContext.h + ./source/common/app/log/Logger.cpp + ./source/common/app/log/Logger.h + ./source/common/app/AbstractApp.cpp + ./source/common/app/AbstractApp.h + ./source/common/app/config/ICommonConfig.h + ./source/common/app/config/InvalidConfigException.h + ./source/common/app/config/ICommonConfig.cpp + ./source/common/app/config/AbstractConfig.cpp + ./source/common/app/config/AbstractConfig.h + ./source/common/threading/UniqueRWLock.h + ./source/common/threading/SafeRWLock.cpp + ./source/common/threading/RWLock.h + ./source/common/threading/RWLockException.h + ./source/common/threading/Atomics.h + ./source/common/threading/PThreadCreateException.h + ./source/common/threading/Condition.h + ./source/common/threading/PThread.cpp + ./source/common/threading/SynchronizationException.h + ./source/common/threading/PThreadException.h + ./source/common/threading/Condition.cpp + ./source/common/threading/Mutex.h + ./source/common/threading/Barrier.h + ./source/common/threading/PThread.h + ./source/common/threading/ConditionException.h + ./source/common/threading/SafeRWLock.h + ./source/common/threading/RWLockGuard.h + ./source/common/threading/MutexException.h + ./source/common/nodes/ClientOps.cpp + ./source/common/nodes/NumNodeID.h + ./source/common/nodes/NodeStoreServers.cpp + ./source/common/nodes/MirrorBuddyGroupMapper.h + ./source/common/nodes/StoragePoolStore.cpp + ./source/common/nodes/MirrorBuddyGroupCreator.h + ./source/common/nodes/NodeStore.h + ./source/common/nodes/Node.h + ./source/common/nodes/LocalNode.h + ./source/common/nodes/NodeOpStats.h + ./source/common/nodes/NodeOpStats.cpp + ./source/common/nodes/NodeConnPool.cpp + ./source/common/nodes/TargetMapper.h + ./source/common/nodes/OpCounter.h + ./source/common/nodes/StoragePoolStore.h + ./source/common/nodes/NodeConnPool.h + ./source/common/nodes/Node.cpp + ./source/common/nodes/MirrorBuddyGroupCreator.cpp + ./source/common/nodes/MirrorBuddyGroupMapper.cpp + ./source/common/nodes/DynamicPoolLimits.h + ./source/common/nodes/LocalNodeConnPool.cpp + ./source/common/nodes/DynamicPoolLimits.cpp + ./source/common/nodes/TargetStateStore.h + ./source/common/nodes/RootInfo.h + ./source/common/nodes/TargetCapacityPools.cpp + ./source/common/nodes/NodeCapacityPools.h + ./source/common/nodes/ClientOps.h + ./source/common/nodes/LocalNodeConnPool.h + ./source/common/nodes/NodeStoreServers.h + ./source/common/nodes/NodeType.h + ./source/common/nodes/NodeStoreClients.cpp + ./source/common/nodes/TargetStateInfo.h + ./source/common/nodes/NodeStoreClients.h + ./source/common/nodes/OpCounterTypes.h + ./source/common/nodes/TargetStateStore.cpp + ./source/common/nodes/MirrorBuddyGroup.h + ./source/common/nodes/TargetMapper.cpp + ./source/common/nodes/TargetCapacityPools.h + ./source/common/nodes/CapacityPoolType.h + ./source/common/nodes/NodeCapacityPools.cpp + ./source/common/nodes/AbstractNodeStore.h + ./source/common/storage/PathInfo.h + ./source/common/storage/EntryInfoWithDepth.h + ./source/common/storage/striping/Raid0Pattern.h + ./source/common/storage/striping/ChunkFileInfo.h + ./source/common/storage/striping/StripePattern.cpp + ./source/common/storage/striping/DynamicFileAttribs.h + ./source/common/storage/striping/ChunkFileInfo.cpp + ./source/common/storage/striping/BuddyMirrorPattern.h + ./source/common/storage/striping/BuddyMirrorPattern.cpp + ./source/common/storage/striping/Raid10Pattern.cpp + ./source/common/storage/striping/Raid10Pattern.h + ./source/common/storage/striping/Raid0Pattern.cpp + ./source/common/storage/striping/StripePattern.h + ./source/common/storage/Storagedata.h + ./source/common/storage/StorageTargetInfo.h + ./source/common/storage/FileEvent.h + ./source/common/storage/StoragePool.h + ./source/common/storage/EntryInfo.h + ./source/common/storage/ChunksBlocksVec.h + ./source/common/storage/EntryInfo.cpp + ./source/common/storage/StatData.h + ./source/common/storage/StorageErrors.cpp + ./source/common/storage/StatData.cpp + ./source/common/storage/mirroring/SyncCandidateStore.h + ./source/common/storage/mirroring/BuddyResyncJobStatistics.h + ./source/common/storage/StoragePoolId.h + ./source/common/storage/StorageErrors.h + ./source/common/storage/StorageDefinitions.h + ./source/common/storage/StorageTargetInfo.cpp + ./source/common/storage/StoragePool.cpp + ./source/common/storage/Path.h + ./source/common/storage/quota/ExceededQuotaPerTarget.h + ./source/common/storage/quota/QuotaDefaultLimits.h + ./source/common/storage/quota/ExceededQuotaPerTarget.cpp + ./source/common/storage/quota/GetQuotaConfig.h + ./source/common/storage/quota/QuotaDefaultLimits.cpp + ./source/common/storage/quota/ExceededQuotaStore.h + ./source/common/storage/quota/QuotaData.h + ./source/common/storage/quota/QuotaData.cpp + ./source/common/storage/quota/GetQuotaInfo.h + ./source/common/storage/quota/Quota.cpp + ./source/common/storage/quota/ExceededQuotaStore.cpp + ./source/common/storage/quota/Quota.h + ./source/common/storage/quota/QuotaConfig.h + ./source/common/storage/quota/GetQuotaInfo.cpp + ./source/common/storage/Metadata.h + ./source/common/fsck/FsckChunk.h + ./source/common/fsck/FsckFsID.h + ./source/common/fsck/FsckModificationEvent.h + ./source/common/fsck/FsckContDir.h + ./source/common/fsck/FsckFileInode.h + ./source/common/fsck/FsckTargetID.h + ./source/common/fsck/FsckDirEntry.h + ./source/common/fsck/FsckDirInode.h +) + +if(NOT BEEGFS_SKIP_TESTS) + add_executable( + test-common + ./tests/TestIPv4Network.cpp + ./tests/TestUiTk.cpp + ./tests/TestPreallocatedFile.cpp + ./tests/TestRWLock.cpp + ./tests/TestLockFD.cpp + ./tests/TestPath.cpp + ./tests/TestRWLock.h + ./tests/TestUnitTk.cpp + ./tests/TestStringTk.cpp + ./tests/TestEntryIdTk.cpp + ./tests/TestNIC.cpp + ./tests/TestNetFilter.cpp + ./tests/TestSerialization.cpp + ./tests/TestBitStore.cpp + ./tests/TestTargetCapacityPools.cpp + ./tests/TestStorageTk.cpp + ./tests/TestStripePattern.cpp + ./tests/TestListTk.cpp + ./tests/TestTimerQueue.cpp + ) + + target_link_libraries( + test-common + beegfs-common + pthread + gtest_main + dl + ${NL3ROUTE_LIBRARIES} + ) + + add_test( + NAME test-common + COMMAND test-common --compiler + ) +endif() + +add_library( + beegfs_ib SHARED + ./ib_lib/RDMASocketImpl.h + ./ib_lib/net/sock/ibvsocket/OpenTk_IBVSocket.h + ./ib_lib/net/sock/ibvsocket/IBVSocket.h + ./ib_lib/net/sock/ibvsocket/IBVSocket.cpp + ./ib_lib/RDMASocketImpl.cpp +) + +target_include_directories( + beegfs-common PRIVATE ${NL3ROUTE_INCLUDE_DIRS} +) + +target_link_libraries( + beegfs-common + ${NL3ROUTE_LIBRARIES} + ssl + crypto +) + +target_include_directories(beegfs_ib PRIVATE ./ib_lib) + +target_link_libraries( + beegfs_ib + rdmacm + ibverbs +) + +install( + TARGETS beegfs_ib + DESTINATION "usr/lib" + COMPONENT "libbeegfs-ib" +) diff --git a/common/build/Makefile b/common/build/Makefile new file mode 100644 index 0000000..de7b903 --- /dev/null +++ b/common/build/Makefile @@ -0,0 +1,21 @@ +include ../../build/Makefile + + +$(call build-static-library,\ + beegfs-common,\ + $(shell find ../source -iname '*.cpp'),\ + dl nl3-route,\ + ../source) + +$(call build-shared-library,\ + beegfs_ib,\ + $(shell find ../ib_lib -iname '*.cpp'),\ + rdma,\ + ../source ../ib_lib) + + +$(call build-test,\ + test-runner,\ + $(shell find ../tests -name '*.cpp'),\ + common,\ + ../tests) diff --git a/common/ib_lib/RDMASocketImpl.cpp b/common/ib_lib/RDMASocketImpl.cpp new file mode 100644 index 0000000..1cb192e --- /dev/null +++ b/common/ib_lib/RDMASocketImpl.cpp @@ -0,0 +1,342 @@ +#include +#include +#include +#include +#include "RDMASocketImpl.h" + +#include + + +static RDMASocket* new_rdma_socket() +{ + return new RDMASocketImpl(); +} + +RDMASocket::ImplCallbacks beegfs_socket_impl = { + IBVSocket_rdmaDevicesExist, + IBVSocket_fork_init_once, + new_rdma_socket, +}; + + +// Note: Good tradeoff between throughput and mem usage (for SDR IB): +// buf_num=64; buf_size=4*1024 (=> 512kB per socket for send and recv) + +#define RDMASOCKET_DEFAULT_BUF_NUM (128) // moved to config +#define RDMASOCKET_DEFAULT_BUF_SIZE (4*1024) // moved to config +#define RDMASOCKET_DEFAULT_SL 0; + + +/** + * Note: Did you notice the rdmaForkInitOnce() method? + * + * @throw SocketException + */ +RDMASocketImpl::RDMASocketImpl() +{ + this->sockType = NICADDRTYPE_RDMA; + + commCfg.bufNum = RDMASOCKET_DEFAULT_BUF_NUM; + commCfg.bufSize = RDMASOCKET_DEFAULT_BUF_SIZE; + commCfg.serviceLevel = RDMASOCKET_DEFAULT_SL; + + this->ibvsock = IBVSocket_construct(); + + if(!ibvsock) + throw SocketException("RDMASocket allocation failed. SysErr: " + System::getErrString() ); + + if(!IBVSocket_getSockValid(this->ibvsock) ) + { + IBVSocket_destruct(this->ibvsock); + throw SocketException("RDMASocket initialization failed. SysErr: " + System::getErrString() ); + } +} + +/** + * Note: To be used by accept() only. + * + * @param sock will be closed/destructed by the destructor of this object + */ +RDMASocketImpl::RDMASocketImpl(IBVSocket* ibvsock, struct in_addr peerIP, std::string peername) +{ + this->ibvsock = ibvsock; + this->fd = IBVSocket_getRecvCompletionFD(ibvsock); + + this->peerIP = peerIP; + this->peername = std::move(peername); + + this->sockType = NICADDRTYPE_RDMA; +} + + +RDMASocketImpl::~RDMASocketImpl() +{ + if(ibvsock) + IBVSocket_destruct(ibvsock); +} + +/** + * @throw SocketException + */ +void RDMASocketImpl::connect(const char* hostname, unsigned short port) +{ + Socket::connect(hostname, port, AF_UNSPEC, SOCK_STREAM); +} + +/** + * @throw SocketException + */ +void RDMASocketImpl::connect(const struct sockaddr* serv_addr, socklen_t addrlen) +{ + unsigned short peerPort = ntohs( ( (struct sockaddr_in*)serv_addr)->sin_port ); + + this->peerIP = ( (struct sockaddr_in*)serv_addr)->sin_addr; + + // set peername if not done so already (e.g. by connect(hostname) ) + + if(peername.empty() ) + peername = Socket::endpointAddrToStr(peerIP, peerPort); + + bool connRes = IBVSocket_connectByIP(ibvsock, peerIP, peerPort, &commCfg); + if(!connRes) + throw SocketConnectException( + std::string("RDMASocket unable to connect to: ") + std::string(peername) ); + + + this->fd = IBVSocket_getRecvCompletionFD(ibvsock); +} + +/** + * @throw SocketException + */ +void RDMASocketImpl::bindToAddr(in_addr_t ipAddr, unsigned short port) +{ + bool bindRes = IBVSocket_bindToAddr(ibvsock, ipAddr, port); + if(!bindRes) + throw SocketException("RDMASocket unable to bind to port: " + + StringTk::uintToStr(port) ); + this->bindIP.s_addr = ipAddr; + this->bindPort = port; +} + +/** + * @throw SocketException + */ +void RDMASocketImpl::listen() +{ + bool listenRes = IBVSocket_listen(ibvsock); + if(!listenRes) + throw SocketException(std::string("RDMASocket unable to listen.") ); + + this->fd = IBVSocket_getConnManagerFD(ibvsock); + peername = std::string("Listen(Port: ") + StringTk::uintToStr(bindPort) + std::string(")"); +} + +/** + * @return might return NULL in case an ignored event occurred; consider it to be a kind of false + * alert (=> this is not an error) + * @throw SocketException + */ +Socket* RDMASocketImpl::accept(struct sockaddr *addr, socklen_t *addrlen) +{ + IBVSocket* acceptedIBVSocket = NULL; + + IBVSocket_AcceptRes acceptRes = IBVSocket_accept(ibvsock, &acceptedIBVSocket, addr, addrlen); + if(acceptRes == ACCEPTRES_IGNORE) + return NULL; + else + if(acceptRes == ACCEPTRES_ERR) + throw SocketException(std::string("RDMASocket unable to accept.") ); + + // prepare new socket object + struct in_addr acceptIP = ( (struct sockaddr_in*)addr)->sin_addr; + unsigned short acceptPort = ntohs( ( (struct sockaddr_in*)addr)->sin_port); + + std::string acceptPeername = endpointAddrToStr(acceptIP, acceptPort); + + Socket* acceptedSock = new RDMASocketImpl(acceptedIBVSocket, acceptIP, acceptPeername); + + return acceptedSock; +} + +/** + * @throw SocketException + */ +void RDMASocketImpl::shutdown() +{ + bool shutRes = IBVSocket_shutdown(ibvsock); + if(!shutRes) + throw SocketException(std::string("RDMASocket shutdown failed.") ); +} + +/** + * Note: The RecvDisconnect-part is currently not implemented, so this is equal to the + * normal shutdown() method. + * + * @throw SocketException + */ +void RDMASocketImpl::shutdownAndRecvDisconnect(int timeoutMS) +{ + this->shutdown(); +} + +#ifdef BEEGFS_NVFS +/** + * Note: This is a synchronous (blocking) version + * + * @throw SocketException + */ +ssize_t RDMASocketImpl::read(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + size_t status = IBVSocket_read(this->ibvsock, (char *)buf, len, lkey, rbuf, rkey); + return (status == 0) ? len : -1; +} + +/** + * Note: This is a synchronous (blocking) version + * + * @throw SocketException + */ +ssize_t RDMASocketImpl::write(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + size_t status = IBVSocket_write(this->ibvsock, (char *)buf, len, lkey, rbuf, rkey); + return (status == 0) ? len : -1; +} +#endif /* BEEGFS_NVFS */ + +/** + * Note: This is a synchronous (blocking) version + * + * @param flags ignored + * @throw SocketException + */ +ssize_t RDMASocketImpl::send(const void *buf, size_t len, int flags) +{ + ssize_t sendRes = IBVSocket_send(ibvsock, (const char*)buf, len, flags | MSG_NOSIGNAL); + if(sendRes == (ssize_t)len) + { + stats->incVals.netSendBytes += len; + return sendRes; + } + else + if(sendRes > 0) + { + throw SocketException( + std::string("send(): Sent only ") + StringTk::int64ToStr(sendRes) + + std::string(" bytes of the requested ") + StringTk::int64ToStr(len) + + std::string(" bytes of data") ); + } + + throw SocketDisconnectException( + "Disconnect during send() to: " + peername); +} + + +/** + * Note: This is a connection-based socket type, so to and tolen are ignored. + * + * @param flags ignored + * @throw SocketException + */ +ssize_t RDMASocketImpl::sendto(const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) +{ + ssize_t sendRes = IBVSocket_send(ibvsock, (const char*)buf, len, flags | MSG_NOSIGNAL); + if(sendRes == (ssize_t)len) + { + stats->incVals.netSendBytes += len; + return sendRes; + } + else + if(sendRes > 0) + { + throw SocketException( + std::string("send(): Sent only ") + StringTk::int64ToStr(sendRes) + + std::string(" bytes of the requested ") + StringTk::int64ToStr(len) + + std::string(" bytes of data") ); + } + + throw SocketDisconnectException( + std::string("Disconnect during send() to: ") + peername); +} + +/** + * @param flags ignored + * @throw SocketException + */ +ssize_t RDMASocketImpl::recv(void *buf, size_t len, int flags) +{ + ssize_t recvRes = IBVSocket_recv(ibvsock, (char*)buf, len, flags); + if(recvRes > 0) + { + stats->incVals.netRecvBytes += recvRes; + return recvRes; + } + + if(recvRes == 0) + throw SocketDisconnectException(std::string("Soft disconnect from ") + peername); + else + throw SocketDisconnectException(std::string("Recv(): Hard disconnect from ") + peername); +} + + +/** + * Note: This is the default version, using poll only => see man pages of select(2) bugs section + * + * @param flags ignored + * @throw SocketException + */ +ssize_t RDMASocketImpl::recvT(void *buf, size_t len, int flags, int timeoutMS) +{ + ssize_t recvRes = IBVSocket_recvT(ibvsock, (char*)buf, len, flags, timeoutMS); + if(recvRes > 0) + { + stats->incVals.netRecvBytes += recvRes; + return recvRes; + } + + if(recvRes == -ETIMEDOUT) + throw SocketTimeoutException("Receive timed out from: " + peername); + else + throw SocketDisconnectException("Received disconnect from: " + peername); +} + + +/** + * Note: Don't call this for sockets that have never been connected! + * + * @throw SocketException + */ +void RDMASocketImpl::checkConnection() +{ + if(IBVSocket_checkConnection(ibvsock) ) + throw SocketDisconnectException("Disconnect from: " + peername); +} + +/** + * Find out whether it is possible to call recv without blocking. + * Useful if the fd says there is incoming data (because that might be a false alarm + * in case of an RDMASocket). + * + * @return 0 if no data immediately available, >0 if incoming data is available + * @throw SocketException + */ +ssize_t RDMASocketImpl::nonblockingRecvCheck() +{ + ssize_t checkRes = IBVSocket_nonblockingRecvCheck(ibvsock); + if(checkRes < 0) + throw SocketDisconnectException("Disconnect from: " + peername); + + return checkRes; +} + +/** + * Call this after accept() to find out whether more events are waiting (for which + * no notification would not be delivered through the file descriptor). + * + * @return true if more events are waiting and accept() should be called again + */ +bool RDMASocketImpl::checkDelayedEvents() +{ + return IBVSocket_checkDelayedEvents(ibvsock); +} diff --git a/common/ib_lib/RDMASocketImpl.h b/common/ib_lib/RDMASocketImpl.h new file mode 100644 index 0000000..3a01b9c --- /dev/null +++ b/common/ib_lib/RDMASocketImpl.h @@ -0,0 +1,83 @@ +#include +#include + +class RDMASocketImpl : public RDMASocket +{ + public: + RDMASocketImpl(); + virtual ~RDMASocketImpl() override; + + virtual void connect(const char* hostname, unsigned short port) override; + virtual void connect(const struct sockaddr* serv_addr, socklen_t addrlen) override; + virtual void bindToAddr(in_addr_t ipAddr, unsigned short port) override; + virtual void listen() override; + virtual Socket* accept(struct sockaddr* addr, socklen_t* addrlen) override; + virtual void shutdown() override; + virtual void shutdownAndRecvDisconnect(int timeoutMS) override; + +#ifdef BEEGFS_NVFS + virtual ssize_t write(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) override; + virtual ssize_t read(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) override; +#endif /* BEEGFS_NVFS */ + + virtual ssize_t send(const void *buf, size_t len, int flags) override; + virtual ssize_t sendto(const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) override; + + virtual ssize_t recv(void *buf, size_t len, int flags) override; + virtual ssize_t recvT(void *buf, size_t len, int flags, int timeoutMS) override; + + virtual void checkConnection() override; + virtual ssize_t nonblockingRecvCheck() override; + virtual bool checkDelayedEvents() override; + + private: + RDMASocketImpl(IBVSocket* ibvsock, struct in_addr peerIP, std::string peername); + + IBVSocket* ibvsock; + int fd; // for pollable interface (will be cm-fd for listening sockets and recv-channel-fd + // for connected/accepted sockets) + + IBVCommConfig commCfg; + + public: + // getters & setters + virtual int getFD() const override + { + return fd; + } + + /** + * Note: Only has an effect for unconnected sockets. + */ + virtual void setBuffers(unsigned bufNum, unsigned bufSize) override + { + commCfg.bufNum = bufNum; + commCfg.bufSize = bufSize; + } + + virtual void setTimeouts(int connectMS, int flowSendMS, int pollMS) + { + IBVSocket_setTimeouts(ibvsock, connectMS, flowSendMS, pollMS); + } + + /** + * Note: Only has an effect for unconnected sockets. + */ + virtual void setTypeOfService(uint8_t typeOfService) override + { + IBVSocket_setTypeOfService(ibvsock, typeOfService); + } + + /** + * Note: Only has an effect for testing. + */ + virtual void setConnectionRejectionRate(unsigned rate) override + { + IBVSocket_setConnectionRejectionRate(ibvsock, rate); + } +}; + +extern "C" { + extern RDMASocket::ImplCallbacks beegfs_socket_impl; +} diff --git a/common/ib_lib/net/sock/ibvsocket/IBVSocket.cpp b/common/ib_lib/net/sock/ibvsocket/IBVSocket.cpp new file mode 100644 index 0000000..373db33 --- /dev/null +++ b/common/ib_lib/net/sock/ibvsocket/IBVSocket.cpp @@ -0,0 +1,2503 @@ +#include "IBVSocket.h" + +#include + +#include +#include +#include + +#ifdef BEEGFS_NVFS +// only for WORKER_BUFOUT_SIZE +#include +#endif /* BEEGFS_NVFS */ + +#define IBVSOCKET_CONN_TIMEOUT_MS 3000 +// IBVSOCKET_CONN_TIMEOUT_INITIAL_POLL_MS is the initial timeout to wait between checks for +// RDMA response events while establishing outgoing connections. Will be scaled up exponentially +// after every poll (up to IBVSOCKET_CONN_TIMEOUT_MAX_POLL_MS) to reduce load but allow for quick +// turnaround in the majority of cases where an event will come shortly after initiating the +// connection. +// +// There used to be a fixed timeout of 500ms here, which lead to long initial connection initiation +// times when an event was not received immediately +// (https://github.com/ThinkParQ/beegfs-core/issues/4054). +#define IBVSOCKET_CONN_TIMEOUT_INITIAL_POLL_MS 1 +#define IBVSOCKET_CONN_TIMEOUT_MAX_POLL_MS 512 + +#define IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS 180000 +#define IBVSOCKET_POLL_TIMEOUT_MS 7500 +#define IBVSOCKET_LISTEN_BACKLOG 128 +#define IBVSOCKET_FLOWCONTROL_MSG_LEN 1 +#define IBVSOCKET_DEFAULT_TOS 0 +/** + * IBVSOCKET_RECV_TIMEOUT_MS is used by IBVSocket_recv, which does not take a + * timeout value. It is very long because IBVSocket_recv continues to call + * IBVSocket_recvT until it does not timeout. + */ +#define IBVSOCKET_RECV_TIMEOUT_MS (1024*1024) + +#define IBVSOCKET_MIN_BUF_NUM 1 +#define IBVSOCKET_MIN_BUF_SIZE 4096 // 4kiB +#define IBVSOCKET_MAX_BUF_SIZE_NUM 134217728 // num * size <= 128MiB +#ifdef BEEGFS_NVFS +#define IBVSOCKET_WC_ENTRIES 1 +#endif /* BEEGFS_NVFS */ + +void IBVSocket_init(IBVSocket* _this) +{ + memset(_this, 0, sizeof(*_this) ); + + _this->sockValid = false; + _this->epollFD = -1; + _this->typeOfService = IBVSOCKET_DEFAULT_TOS; + _this->timeoutCfg.connectMS = IBVSOCKET_CONN_TIMEOUT_MS; + _this->timeoutCfg.flowSendMS = IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS; + + _this->cm_channel = rdma_create_event_channel(); + if(!_this->cm_channel) + { + LOG(SOCKLIB, WARNING, "rdma_create_event_channel failed."); + return; + } + + if(rdma_create_id(_this->cm_channel, &_this->cm_id, NULL, RDMA_PS_TCP) ) + { + LOG(SOCKLIB, WARNING, "rdma_create_id failed."); + return; + } + + _this->sockValid = true; + + return; +} + +/** + * Note: Intended for incoming accepted connections. + * + * @param commContext belongs to this object (so do not use or free it after calling this!) + */ +void __IBVSocket_initFromCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommContext* commContext) +{ + memset(_this, 0, sizeof(*_this) ); + + _this->sockValid = false; + _this->epollFD = -1; + + _this->typeOfService = IBVSOCKET_DEFAULT_TOS; + + _this->cm_id = cm_id; + _this->commContext = commContext; + + #ifdef SYSTEM_HAS_RDMA_MIGRATE_ID__disabled + + // note: see _accept() for the reasons why this is currently disabled + + _this->cm_channel = rdma_create_event_channel(); + if(!_this->cm_channel) + { + LOG(SOCKLIB, WARNING, "rdma_create_event_channel failed."); + return; + } + + #endif // SYSTEM_HAS_RDMA_MIGRATE_ID + + _this->sockValid = true; + LOG(SOCKLIB, DEBUG, __func__, + ("_this", StringTk::uint64ToHexStr((uint64_t) _this)), + ("device", cm_id->verbs->device->name)); + + return; +} + +IBVSocket* IBVSocket_construct() +{ + IBVSocket* _this = (IBVSocket*)malloc(sizeof(*_this) ); + + IBVSocket_init(_this); + + return _this; +} + +IBVSocket* __IBVSocket_constructFromCommContext(struct rdma_cm_id* cm_id, + IBVCommContext* commContext) +{ + IBVSocket* _this = (IBVSocket*)malloc(sizeof(*_this) ); + + __IBVSocket_initFromCommContext(_this, cm_id, commContext); + + return _this; +} + +void IBVSocket_uninit(IBVSocket* _this) +{ + if(_this->epollFD != -1) + close(_this->epollFD); + + __IBVSocket_close(_this); +} + +void IBVSocket_destruct(IBVSocket* _this) +{ + IBVSocket_uninit(_this); + + free(_this); +} + +bool IBVSocket_rdmaDevicesExist() +{ + bool devicesExist; + + int numDevices = 1; + struct ibv_context** devicesRes; + + devicesRes = rdma_get_devices(&numDevices); + + devicesExist = (devicesRes != NULL) && (numDevices > 0); + + if(devicesRes) + rdma_free_devices(devicesRes); + + return devicesExist; +} + +/** + * Prepare ibverbs for forking a child process. This is only required if the parent process + * has mapped memory for RDMA. + * Call this only once in your program. + * + * Note: There is no corresponding uninit-method that needs to be called. + */ +void IBVSocket_fork_init_once() +{ + ibv_fork_init(); +} + +bool IBVSocket_connectByName(IBVSocket* _this, const char* hostname, unsigned short port, + IBVCommConfig* commCfg) +{ + struct addrinfo *res; + struct addrinfo hints; + + int getInfoRes; + struct in_addr ipaddress; + + memset(&hints, 0, sizeof(hints) ); + hints.ai_family = PF_INET; + hints.ai_socktype = SOCK_STREAM; + + getInfoRes = getaddrinfo(hostname, NULL, &hints, &res); + + if(getInfoRes < 0) + { + LOG(SOCKLIB, WARNING, "Name resolution error.", hostname, port, + ("error", gai_strerror(getInfoRes))); + + return false; + } + + ipaddress.s_addr = ( (struct sockaddr_in*)res->ai_addr)->sin_addr.s_addr; + + + // clean-up + freeaddrinfo(res); + + + return IBVSocket_connectByIP(_this, ipaddress, port, commCfg); +} + +bool IBVSocket_connectByIP(IBVSocket* _this, struct in_addr ipaddress, unsigned short port, + IBVCommConfig* commCfg) +{ + struct rdma_cm_event* event; + struct sockaddr_in sin; + bool createContextRes; + struct rdma_conn_param conn_param; + bool parseCommDestRes; + bool epollInitRes; + int rc; + int connTimeoutMS = IBVSOCKET_CONN_TIMEOUT_INITIAL_POLL_MS; + int connTimeoutRemaining = IBVSOCKET_CONN_TIMEOUT_MS; + int oldChannelFlags; + int setOldFlagsRes; + + LOG(SOCKLIB, DEBUG, "Connect RDMASocket", ("socket", _this), ("addr", Socket::endpointAddrToStr(ipaddress, port)), + ("bindIP", Socket::ipaddrToStr(_this->bindIP))); + + // resolve IP address... + + sin.sin_addr.s_addr = ipaddress.s_addr; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if(rdma_resolve_addr(_this->cm_id, NULL, (struct sockaddr*)&sin, _this->timeoutCfg.connectMS) ) + { + LOG(SOCKLIB, WARNING, "rdma_resolve_addr failed."); + goto err_invalidateSock; + } + + if(rdma_get_cm_event(_this->cm_channel, &event)) + goto err_invalidateSock; + + if(event->event != RDMA_CM_EVENT_ADDR_RESOLVED) + { + LOG(SOCKLIB, DEBUG, "Unexpected CM event.", ("event", rdma_event_str(event->event))); + goto err_ack_and_invalidateSock; + } + + rdma_ack_cm_event(event); + + // set type of service for connection + if (_this->typeOfService) + { + if (rdma_set_option(_this->cm_id, RDMA_OPTION_ID, RDMA_OPTION_ID_TOS, &(_this->typeOfService), + sizeof(_this->typeOfService))) + { + LOG(SOCKLIB, WARNING, "Failed to set Type Of Service.", + ("tos", _this->typeOfService)); + goto err_invalidateSock; + } + } + + // resolve route... + + if(rdma_resolve_route(_this->cm_id, _this->timeoutCfg.connectMS) ) + { + LOG(SOCKLIB, WARNING, "rdma_resolve_route failed."); + goto err_invalidateSock; + } + + if(rdma_get_cm_event(_this->cm_channel, &event)) + goto err_invalidateSock; + + if(event->event != RDMA_CM_EVENT_ROUTE_RESOLVED) + { + LOG(SOCKLIB, WARNING, "Unexpected CM event.", + ("event", rdma_event_str(event->event))); + goto err_ack_and_invalidateSock; + } + + rdma_ack_cm_event(event); + + // create comm context... + + createContextRes = __IBVSocket_createCommContext(_this, _this->cm_id, commCfg, + &_this->commContext); + if(!createContextRes) + { + LOG(SOCKLIB, WARNING, "creation of CommContext failed."); + goto err_invalidateSock; + } + + // establish connection... + + __IBVSocket_initCommDest(_this->commContext, &_this->localDest); + + memset(&conn_param, 0, sizeof(conn_param) ); +#ifdef BEEGFS_NVFS + conn_param.responder_resources = RDMA_MAX_RESP_RES; + conn_param.initiator_depth = RDMA_MAX_INIT_DEPTH; +#else + conn_param.responder_resources = 1; + conn_param.initiator_depth = 1; +#endif /* BEEGFS_NVFS */ + conn_param.flow_control = 0; + conn_param.retry_count = 7; // (3 bits) + conn_param.rnr_retry_count = 7; // rnr = receiver not ready (3 bits, 7 means infinity) + conn_param.private_data = &_this->localDest; + conn_param.private_data_len = sizeof(_this->localDest); + + if(rdma_connect(_this->cm_id, &conn_param)) + { + LOG(SOCKLIB, DEBUG, "rdma_connect failed."); + goto err_invalidateSock; + } + + oldChannelFlags = fcntl(IBVSocket_getConnManagerFD(_this), F_GETFL); + + rc = fcntl(IBVSocket_getConnManagerFD(_this), F_SETFL, oldChannelFlags | O_NONBLOCK); + if(rc < 0) + { + LOG(SOCKLIB, WARNING, "Set conn manager channel non-blocking failed.", sysErr); + goto err_invalidateSock; + } + + // rdma_connect() can take a very long time (>5m) to timeout if the peer's HCA is down. + // Change the channel to non-blocking and use a custom timeout mechanism. + rc = -1; + while (connTimeoutRemaining > 0) + { + // (non-blocking) check for new events + rc = rdma_get_cm_event(_this->cm_channel, &event); + + if (rc) + { + if (errno != ETIMEDOUT && errno != EAGAIN) + { + LOG(SOCKLIB, WARNING, "rdma_get_cm_event failed", ("errno", errno)); + break; + } + } + else + { + // we got an event + LOG(SOCKLIB, DEBUG, "Received RDMA connect response event.", ("Milliseconds spent polling", IBVSOCKET_CONN_TIMEOUT_MS - connTimeoutRemaining)); + break; + } + + connTimeoutRemaining -= connTimeoutMS; + if (connTimeoutRemaining > 0) + { + struct timespec ts = { + .tv_sec = 0, + .tv_nsec = (connTimeoutMS * 1000 * 1000) + }; + + // progressively scale the timeout by squaring it until it is larger than + // IBVSOCKET_CONN_TIMEOUT_MAX_POLL_MS + if (connTimeoutMS == 1) + { + connTimeoutMS = 2; + } + else if (connTimeoutMS * 2 <= IBVSOCKET_CONN_TIMEOUT_MAX_POLL_MS) + { + connTimeoutMS *= 2; + } + + if (::nanosleep(&ts, NULL) != 0) + { + LOG(SOCKLIB, DEBUG, "rdma_connect: sleep interrupted"); + break; + } + } + else + LOG(SOCKLIB, DEBUG, "rdma_connect: timed out"); + } + + // change channel mode back to blocking + setOldFlagsRes = fcntl(IBVSocket_getConnManagerFD(_this), F_SETFL, oldChannelFlags); + if(setOldFlagsRes < 0) + { + LOG(SOCKLIB, WARNING, "Set conn manager channel blocking failed.", sysErr); + if (rc == 0) + goto err_ack_and_invalidateSock; + else + goto err_invalidateSock; + } + + if (rc != 0) + goto err_invalidateSock; + + if(event->event != RDMA_CM_EVENT_ESTABLISHED) + { + if(event->event == RDMA_CM_EVENT_REJECTED) + LOG(SOCKLIB, DEBUG, "Connection rejected."); + else + LOG(SOCKLIB, WARNING, "Unexpected conn manager event.", + ("event", rdma_event_str(event->event))); + goto err_ack_and_invalidateSock; + } + + parseCommDestRes = __IBVSocket_parseCommDest( + event->param.conn.private_data, event->param.conn.private_data_len, &_this->remoteDest); + if(!parseCommDestRes) + { + LOG(SOCKLIB, WARNING, "Bad private data received.", + ("len", event->param.conn.private_data_len)); + goto err_ack_and_invalidateSock; + } + + rdma_ack_cm_event(event); + + epollInitRes = __IBVSocket_initEpollFD(_this); + if(!epollInitRes) + goto err_invalidateSock; + + return true; + + +err_ack_and_invalidateSock: + rdma_ack_cm_event(event); +err_invalidateSock: + _this->errState = -1; + + return false; +} + +/** + * @return true on success + */ +bool IBVSocket_bind(IBVSocket* _this, unsigned short port) +{ + in_addr_t ipAddr = INADDR_ANY; + + return IBVSocket_bindToAddr(_this, ipAddr, port); +} + +bool IBVSocket_bindToAddr(IBVSocket* _this, in_addr_t ipAddr, unsigned short port) +{ + struct sockaddr_in bindAddr; + + bindAddr.sin_family = AF_INET; + bindAddr.sin_addr.s_addr = ipAddr; + bindAddr.sin_port = htons(port); + + LOG(SOCKLIB, DEBUG, "Bind RDMASocket", ("socket", _this), ("addr", Socket::endpointAddrToStr(ipAddr, port))); + + if(rdma_bind_addr(_this->cm_id, (struct sockaddr*)&bindAddr) ) + { + //SyslogLogger::log(LOG_WARNING, "%s:%d rdma_bind_addr failed (port: %d)\n", + //__func__, __LINE__, (int)port); // debug in + goto err_invalidateSock; + } + + _this->bindIP.s_addr = ipAddr; + + return true; + + +err_invalidateSock: + _this->errState = -1; + + return false; +} + +/** + * Note: This also inits the delayedCmEventsQueue. + * + * @return true on success + */ +bool IBVSocket_listen(IBVSocket* _this) +{ + if(rdma_listen(_this->cm_id, IBVSOCKET_LISTEN_BACKLOG) ) + { + LOG(SOCKLIB, WARNING, "rdma_listen failed."); + goto err_invalidateSock; + } + + // init delayed events queue + _this->delayedCmEventsQ = new CmEventQueue(); + + + return true; + + +err_invalidateSock: + _this->errState = -1; + + return false; +} + +/** + * Note: Call IBVSocket_checkDelayedEvents() after this to find out whether more events + * are waiting. + * Note: Because of the special way ibverbs accept connections, it is possible that we receive + * some other events here as well (e.g. a child socket disconnect). In these cases, + * ACCEPTRES_IGNORE will be returned. + * + * @param outAcceptedSock only valid when ACCEPTRES_SUCCESS is returned + * @param peerAddr (out) peer address + * @param peerAddrLen (out) length of peer address + * @return ACCEPTRES_IGNORE in case an irrelevant event occurred + */ +IBVSocket_AcceptRes IBVSocket_accept(IBVSocket* _this, IBVSocket** outAcceptedSock, + struct sockaddr* peerAddr, socklen_t* peerAddrLen) +{ + struct rdma_cm_event* event = NULL; + IBVCommContext* childCommContext = NULL; + IBVSocket* acceptedSock = NULL; // auto-destructed on error/ignore (internal, not for caller) + IBVCommDest* childRemoteDest = NULL; // auto-freed on error/ignore + + *outAcceptedSock = NULL; + + + // get next waiting event from delay-queue or from event channel + + if (!_this->delayedCmEventsQ->empty()) + { + event = _this->delayedCmEventsQ->front(); + _this->delayedCmEventsQ->pop(); + } + else + if(rdma_get_cm_event(_this->cm_channel, &event) ) + { + _this->errState = -1; + return ACCEPTRES_ERR; + } + + + // handle event type + + switch(event->event) + { + case RDMA_CM_EVENT_CONNECT_REQUEST: + { + // got an incoming 'connect request' => check validity of private data and accept/reject + + bool createContextRes; + struct rdma_conn_param conn_param; + bool parseCommDestRes; + IBVCommConfig commCfg; + + struct rdma_cm_id* child_cm_id = event->id; + + //*peerAddrLen = sizeof(struct sockaddr_in); + //memcpy(peerAddr, &child_cm_id->route.addr.dst_addr, *peerAddrLen); + + + // parse private data to get remote dest + + parseCommDestRes = __IBVSocket_parseCommDest( + event->param.conn.private_data, event->param.conn.private_data_len, &childRemoteDest); + if(!parseCommDestRes) + { // bad private data => reject connection + LOG(SOCKLIB, WARNING, "Bad private data received.", + ("len", event->param.conn.private_data_len)); + + if(rdma_reject(child_cm_id, NULL, 0) ) + LOG(SOCKLIB, WARNING, "rdma_reject failed."); + + goto ignore; + } + + + // private data (remote dest) okay => create local comm context and socket instance + + // (we use the buffer config as suggested by the connecting peer) + commCfg.bufNum = childRemoteDest->recvBufNum; + commCfg.bufSize = childRemoteDest->recvBufSize; + + createContextRes = __IBVSocket_createCommContext(_this, child_cm_id, &commCfg, + &childCommContext); + if(!createContextRes) + { + LOG(SOCKLIB, WARNING, "Creation of CommContext failed."); + + if(rdma_reject(child_cm_id, NULL, 0) ) + LOG(SOCKLIB, WARNING, "rdma_reject failed."); + + goto ignore; + } + + acceptedSock = __IBVSocket_constructFromCommContext(child_cm_id, childCommContext); + if(!acceptedSock->sockValid) + goto ignore; + + + acceptedSock->remoteDest = childRemoteDest; + childRemoteDest = NULL; // would otherwise be destroyed at 'ignore' + + + // send accept message (with local destination info) + + __IBVSocket_initCommDest(childCommContext, &acceptedSock->localDest); + + memset(&conn_param, 0, sizeof(conn_param) ); +#ifdef BEEGFS_NVFS + conn_param.responder_resources = RDMA_MAX_RESP_RES; + conn_param.initiator_depth = RDMA_MAX_INIT_DEPTH; +#else + conn_param.responder_resources = 1; + conn_param.initiator_depth = 1; +#endif /* BEEGFS_NVFS */ + conn_param.flow_control = 0; + conn_param.retry_count = 7; // (3 bits) + conn_param.rnr_retry_count = 7; // rnr = receiver not ready (3 bits, 7 means infinity) + conn_param.private_data = &acceptedSock->localDest; + conn_param.private_data_len = sizeof(acceptedSock->localDest); + + // test point for dropping the connect request + if(IBVSocket_connectionRejection(_this)) + goto ignore; + + if(rdma_accept(child_cm_id, &conn_param) ) + { + LOG(SOCKLIB, WARNING, "rdma_accept failed."); + + goto ignore; + } + + if(!__IBVSocket_initEpollFD(acceptedSock) ) + goto ignore; + + + // Note that this code returns ACCEPTRES_IGNORE + LOG(SOCKLIB, DEBUG, "Connection request on RDMASocket"); + child_cm_id->context = acceptedSock; + acceptedSock = NULL; // would otherwise be destroyed at 'ignore' + + } break; + + case RDMA_CM_EVENT_ESTABLISHED: + { + // received 'established' (this is what we've actually been waiting for!) + + *peerAddrLen = sizeof(struct sockaddr_in); + memcpy(peerAddr, &event->id->route.addr.dst_addr, *peerAddrLen); + + *outAcceptedSock = (IBVSocket*)event->id->context; + + rdma_ack_cm_event(event); + + #ifdef SYSTEM_HAS_RDMA_MIGRATE_ID__disabled + + // note: this is currently disabled, because: + // a) rdma_migrate_id always returns "invalid argument" + // b) we need disconnect events for incoming conns to be handled and the handler must call + // rdma_disconnect to enable disconnect detection for the streamlistener + + // note: migration might deadlock if there are any retrieved but not yet ack'ed events + // for the current channel, so we cannot migrate if this is the case + + // note: the only purpose of migration to a separate channel is that we can do better + // disconnect detection in waitForCompletion(). so living without the migration is + // generally not a problem (but disconnect detection might take longer). + + if(_this->delayedCmEventsQ->size() ) + { // events waiting => don't migrate + LOG(SOCKLIB, WARNING, + "Skipping rdma_migrate_id due to waiting events (but we can live without it)."); + } + else + { // migrate cm_id from general accept-channel to its own channel + int migrateRes = rdma_migrate_id( + (*outAcceptedSock)->cm_id, (*outAcceptedSock)->cm_channel); + + if(migrateRes) + { + LOG(SOCKLIB, WARNING, "rdma_migrate_id failed (but we can live without it).", + migrateRes, sysErr); + } + } + + #endif // SYSTEM_HAS_RDMA_MIGRATE_ID + + return ACCEPTRES_SUCCESS; + } break; + + case RDMA_CM_EVENT_DISCONNECTED: + { + // note: be careful about what we do with the event-socket here, because the socket might + // already be under destruction in another thread. + + LOG(SOCKLIB, DEBUG, "Disconnect event."); + + // note: the additional disconnect call is required to get the streamlistener event + // channel (the one of the listen sock) to report the disconnect + rdma_disconnect(event->id); + + } break; + + case RDMA_CM_EVENT_UNREACHABLE: + { + LOG(SOCKLIB, WARNING, "Remote unreachable event while waiting for 'established'."); + acceptedSock = (IBVSocket*)event->id->context; // will be destroyed at 'ignore' + } break; + + case RDMA_CM_EVENT_CONNECT_ERROR: + { + LOG(SOCKLIB, WARNING, "Connect error event while waiting for 'established'."); + acceptedSock = (IBVSocket*)event->id->context; // will be destroyed at 'ignore' + } break; + + case RDMA_CM_EVENT_TIMEWAIT_EXIT: + { // log only with enabled debug code + LOG(SOCKLIB, DEBUG, "Ignoring conn manager event RDMA_CM_EVENT_TIMEWAIT_EXIT."); + } break; + + case RDMA_CM_EVENT_DEVICE_REMOVAL: + { + AbstractApp* app = PThread::getCurrentThreadApp(); + const char* devname = "unknown"; + if (event->id && event->id->verbs) + devname = ibv_get_device_name(event->id->verbs->device); + LOG(SOCKLIB, ERR, "Device removed", ("device", devname)); + app->handleNetworkInterfaceFailure(std::string(devname)); + } break; + + default: + { // ignore other events + // always log + LOG(SOCKLIB, WARNING, "Ignoring conn manager event.", + ("event", rdma_event_str(event->event))); + } break; + } + + + // irrelevant event (irrelevant for the caller) +ignore: + rdma_ack_cm_event(event); + + SAFE_FREE(childRemoteDest); + if(acceptedSock) + IBVSocket_destruct(acceptedSock); + + *outAcceptedSock = NULL; + + return ACCEPTRES_IGNORE; +} + +bool IBVSocket_shutdown(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + + + if(!commContext) + return true; // this socket has never been connected + + // if object is in errState, then the socket might be in an inconsistent state, + // therefore further commands (except for disconnect) should not be executed + if(!_this->errState && commContext->incompleteSend.numAvailable) + { // wait for all incomplete sends + int waitRes; + + waitRes = __IBVSocket_waitForTotalSendCompletion( + _this, commContext->incompleteSend.numAvailable, 0, 0); + if(waitRes < 0) + { + LOG(SOCKLIB, WARNING, "Waiting for incomplete send requests failed."); + return false; + } + } + + __IBVSocket_disconnect(_this); + + return true; +} + +/** + * Continues an incomplete former recv() by returning immediately available data from the + * corresponding buffer. + */ +ssize_t __IBVSocket_recvContinueIncomplete(IBVSocket* _this, char* buf, size_t bufLen) +{ + IBVCommContext* commContext = _this->commContext; + int completedOffset = commContext->incompleteRecv.completedOffset; + size_t availableLen = commContext->incompleteRecv.wc.byte_len - completedOffset; + size_t bufIndex = commContext->incompleteRecv.wc.wr_id - IBVSOCKET_RECV_WORK_ID_OFFSET; + + + if(availableLen <= bufLen) + { // old data fits completely into buf + memcpy(buf, &(commContext->recvBufs)[bufIndex][completedOffset], availableLen); + + commContext->incompleteRecv.isAvailable = 0; + + int postRes = __IBVSocket_postRecv(_this, _this->commContext, bufIndex); + if(unlikely(postRes) ) + goto err_invalidateSock; + + return availableLen; + } + else + { // still too much data for the buf => copy partially + memcpy(buf, &(commContext->recvBufs)[bufIndex][completedOffset], bufLen); + + commContext->incompleteRecv.completedOffset += bufLen; + + return bufLen; + } + + +err_invalidateSock: + _this->errState = -1; + + return -1; +} + + +ssize_t IBVSocket_recv(IBVSocket* _this, char* buf, size_t bufLen, int flags) +{ + const int timeoutMS = IBVSOCKET_RECV_TIMEOUT_MS; + ssize_t recvTRes; + + do + { + recvTRes = IBVSocket_recvT(_this, buf, bufLen, flags, timeoutMS); + } while(recvTRes == -ETIMEDOUT); + + return recvTRes; +} + + +/** + * @return number of received bytes on success, 0 on timeout, -1 on error + */ +ssize_t IBVSocket_recvT(IBVSocket* _this, char* buf, size_t bufLen, int flags, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + struct ibv_wc* wc = &commContext->incompleteRecv.wc; + int flowControlRes; + int recvWCRes; + + if(unlikely(_this->errState) ) + return -1; + + // check whether an old buffer has not been fully read yet + if(!commContext->incompleteRecv.isAvailable) + { // no partially read data available => recv new buffer + + // check whether we have a pending on-send flow control packet that needs to be received first + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, timeoutMS); + if(flowControlRes <= 0) + { + if(likely(!flowControlRes) ) + return -ETIMEDOUT; // timeout + + goto err_invalidateSock; + } + + // recv a new buffer (into the incompleteRecv structure) + recvWCRes = __IBVSocket_recvWC(_this, timeoutMS, wc); + if(recvWCRes <= 0) + { + if(likely(!recvWCRes) ) + return -ETIMEDOUT; // timeout + + goto err_invalidateSock; // error occurred + } + + // recvWC was positive => we're guaranteed to have an incompleteRecv buf availabe + + commContext->incompleteRecv.completedOffset = 0; + commContext->incompleteRecv.isAvailable = 1; + } + + return __IBVSocket_recvContinueIncomplete(_this, buf, bufLen); + + +err_invalidateSock: + _this->errState = -1; + + return -ECOMM; +} + +ssize_t IBVSocket_send(IBVSocket* _this, const char* buf, size_t bufLen, int flags) +{ + IBVCommContext* commContext = _this->commContext; + int flowControlRes; + size_t currentBufIndex; + int postRes; + size_t postedLen = 0; + int currentPostLen; + int waitRes; + + if(unlikely(_this->errState) ) + return -1; + + do + { + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, + _this->timeoutCfg.flowSendMS); + if(unlikely(flowControlRes <= 0) ) + goto err_invalidateSock; + + // note: we only poll for completed sends after we used up all (!) available bufs + + if(commContext->incompleteSend.numAvailable == commContext->commCfg.bufNum) + { // wait for all (!) incomplete sends + waitRes = __IBVSocket_waitForTotalSendCompletion( + _this, commContext->incompleteSend.numAvailable, 0, 0); + if(waitRes < 0) + goto err_invalidateSock; + + commContext->incompleteSend.numAvailable = 0; + } + + currentPostLen = BEEGFS_MIN(bufLen-postedLen, commContext->commCfg.bufSize); + currentBufIndex = commContext->incompleteSend.numAvailable; + + memcpy( (commContext->sendBufs)[currentBufIndex], &buf[postedLen], currentPostLen); + + commContext->incompleteSend.numAvailable++; /* inc'ed before postSend() for conn checks */ + + postRes = __IBVSocket_postSend(_this, currentBufIndex, currentPostLen); + if(unlikely(postRes) ) + { + commContext->incompleteSend.numAvailable--; + goto err_invalidateSock; + } + + + postedLen += currentPostLen; + + } while(postedLen < bufLen); + + return (ssize_t)bufLen; + + +err_invalidateSock: + _this->errState = -1; + + return -ECOMM; +} + + +int __IBVSocket_registerBuf(IBVCommContext* commContext, void* buf, size_t bufLen, + struct ibv_mr** outMR) +{ + /* note: IB spec says: + "The consumer is not allowed to assign remote-write or remote-atomic to + a memory region that has not been assigned local-write." */ + enum ibv_access_flags accessFlags = (enum ibv_access_flags) + (IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_LOCAL_WRITE); + + *outMR = ibv_reg_mr(commContext->pd, buf, bufLen, accessFlags); + if(!*outMR) + { + LOG(SOCKLIB, WARNING, "Couldn't allocate MR."); + return -1; + } + + return 0; +} + + +char* __IBVSocket_allocAndRegisterBuf(IBVCommContext* commContext, size_t bufLen, + struct ibv_mr** outMR) +{ + void* buf; + int registerRes; + + int allocRes = posix_memalign(&buf, sysconf(_SC_PAGESIZE), bufLen); + if(allocRes) + { + LOG(SOCKLIB, WARNING, "Couldn't allocate work buf."); + return NULL; + } + + memset(buf, 0, bufLen); + + registerRes = __IBVSocket_registerBuf(commContext, buf, bufLen, outMR); + if(registerRes < 0) + { + free(buf); + return NULL; + } + + return (char*)buf; +} + +bool __IBVSocket_createCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext) +{ + IBVCommContext* commContext = NULL; + int registerControlRes; + int registerControlResReset; + struct ibv_qp_init_attr qpInitAttr; + int createQPRes; + unsigned i; + + + // sanity checks + + if (unlikely(commCfg->bufNum < IBVSOCKET_MIN_BUF_NUM) ) + { + LOG(SOCKLIB, WARNING, "bufNum too small!", + ("got", commCfg->bufNum), ("minimum", IBVSOCKET_MIN_BUF_NUM)); + goto err_cleanup; + } + + if (unlikely(commCfg->bufSize < IBVSOCKET_MIN_BUF_SIZE) ) // sanity check + { + LOG(SOCKLIB, WARNING, "bufSize too small!", + ("got", commCfg->bufSize), ("minimum", IBVSOCKET_MIN_BUF_SIZE)); + goto err_cleanup; + } + + if (commCfg->bufSize * commCfg->bufNum > IBVSOCKET_MAX_BUF_SIZE_NUM) + { + LOG(SOCKLIB, WARNING, "bufSize*bufNum too large!", + ("got", commCfg->bufSize * commCfg->bufNum), + ("maximum", IBVSOCKET_MAX_BUF_SIZE_NUM)); + goto err_cleanup; + } + + + commContext = (IBVCommContext*)calloc(1, sizeof(*commContext) ); + if(!commContext) + goto err_cleanup; + + commContext->context = cm_id->verbs; + if(!commContext->context) + { + LOG(SOCKLIB, WARNING, "Unbound cm_id!!"); + goto err_cleanup; + } + + commContext->pd = ibv_alloc_pd(commContext->context); + if(!commContext->pd) + { + LOG(SOCKLIB, WARNING, "Couldn't allocate PD."); + goto err_cleanup; + } + + // alloc and register buffers... + + commContext->commCfg = *commCfg; + + commContext->recvBuf = __IBVSocket_allocAndRegisterBuf( + commContext, commCfg->bufSize * commCfg->bufNum, &commContext->recvMR); + if(!commContext->recvBuf) + { + LOG(SOCKLIB, WARNING, "Couldn't prepare recvBuf."); + goto err_cleanup; + } + + commContext->recvBufs = (char**)calloc(1, commCfg->bufNum * sizeof(char*) ); + + for(i=0; i < commCfg->bufNum; i++) + commContext->recvBufs[i] = &commContext->recvBuf[i * commCfg->bufSize]; + + + commContext->sendBuf = __IBVSocket_allocAndRegisterBuf( + commContext, commCfg->bufSize * commCfg->bufNum, &commContext->sendMR); + if(!commContext->sendBuf) + { + LOG(SOCKLIB, WARNING, "Couldn't prepare sendBuf."); + goto err_cleanup; + } + + commContext->sendBufs = (char**)calloc(1, commCfg->bufNum * sizeof(char*) ); + + for(i=0; i < commCfg->bufNum; i++) + commContext->sendBufs[i] = &commContext->sendBuf[i * commCfg->bufSize]; + + + registerControlRes = __IBVSocket_registerBuf( + commContext, (char*)&commContext->numUsedSendBufs, + sizeof(commContext->numUsedSendBufs), &commContext->controlMR); + if(registerControlRes < 0) + { + LOG(SOCKLIB, WARNING, "Couldn't register control memory region."); + goto err_cleanup; + } + + registerControlResReset = __IBVSocket_registerBuf( + commContext, (char*)&commContext->numUsedSendBufsReset, + sizeof(commContext->numUsedSendBufsReset), &commContext->controlResetMR); + if(registerControlResReset < 0) + { + LOG(SOCKLIB, WARNING, "Couldn't register control memory reset region."); + goto err_cleanup; + } + + // init flow control v2 (to avoid long receiver-not-ready timeouts) + + /* note: we use -1 because the last buf might not be read by the user (eg during + nonblockingRecvCheck) and so it might not be immediately available again. */ + commContext->numReceivedBufsLeft = commCfg->bufNum - 1; + commContext->numSendBufsLeft = commCfg->bufNum - 1; + + // create completion channel and queues... + + commContext->recvCompChannel = ibv_create_comp_channel(commContext->context); + if(!commContext->recvCompChannel) + { + LOG(SOCKLIB, WARNING, "Couldn't create comp channel."); + goto err_cleanup; + } + + commContext->recvCQ = ibv_create_cq( + commContext->context, commCfg->bufNum, commContext, commContext->recvCompChannel, + rand()%commContext->context->num_comp_vectors); + if(!commContext->recvCQ) + { + LOG(SOCKLIB, WARNING, "Couldn't create recv CQ."); + goto err_cleanup; + } + + // note: 1+commCfg->bufNum here for the RDMA write usedBufs reset work (=> flow/flood control) + commContext->sendCQ = ibv_create_cq( + commContext->context, 1+commCfg->bufNum, NULL, NULL, + rand()%commContext->context->num_comp_vectors); + if(!commContext->sendCQ) + { + LOG(SOCKLIB, WARNING, "Couldn't create send CQ."); + goto err_cleanup; + } + + // note: 1+commCfg->bufNum here for the RDMA write usedBufs reset work + memset(&qpInitAttr, 0, sizeof(qpInitAttr) ); + + qpInitAttr.send_cq = commContext->sendCQ; + qpInitAttr.recv_cq = commContext->recvCQ; + qpInitAttr.qp_type = IBV_QPT_RC; + qpInitAttr.sq_sig_all = 1; + qpInitAttr.cap.max_send_wr = 1+commCfg->bufNum; + qpInitAttr.cap.max_recv_wr = commCfg->bufNum; + qpInitAttr.cap.max_send_sge = 1; + qpInitAttr.cap.max_recv_sge = 1; + qpInitAttr.cap.max_inline_data = 0; + + createQPRes = rdma_create_qp(cm_id, commContext->pd, &qpInitAttr); + if(createQPRes) + { + LOG(SOCKLIB, WARNING, "Couldn't create QP.", sysErr); + goto err_cleanup; + } + + commContext->qp = cm_id->qp; + + // post initial recv buffers... + + for(i=0; i < commCfg->bufNum; i++) + { + if(__IBVSocket_postRecv(_this, commContext, i) ) + { + LOG(SOCKLIB, WARNING, "Couldn't post recv buffer.", ("index", i)); + goto err_cleanup; + } + } + + // prepare event notification... + + // initial event notification request + if(ibv_req_notify_cq(commContext->recvCQ, 0) ) + { + LOG(SOCKLIB, WARNING, "Couldn't request CQ notification."); + goto err_cleanup; + } + +#ifdef BEEGFS_NVFS + commContext->workerMRs = new MRMap(); + commContext->cqMutex = new Mutex(); + commContext->cqCompletions = new CQMap(); + // RDMA id. (This variable will increment for each RDMA operation.) + commContext->wr_id = 1; +#endif /* BEEGFS_NVFS */ + + LOG(SOCKLIB, DEBUG, __func__, + ("_this", StringTk::uint64ToHexStr((uint64_t) _this)), + ("device", cm_id->verbs->device->name)); + + *outCommContext = commContext; + return true; + + + // error handling + +err_cleanup: + __IBVSocket_cleanupCommContext(cm_id, commContext); + + *outCommContext = NULL; + return false; +} + +void __IBVSocket_cleanupCommContext(struct rdma_cm_id* cm_id, IBVCommContext* commContext) +{ + if(!commContext) + return; + + if(commContext->qp) + { + // see recommendation here: https://www.rdmamojo.com/2012/12/28/ibv_destroy_qp/ + // the qp should be set to error state, so that no more events can be pushed to that queue. + + struct ibv_qp_attr qpAttr; + qpAttr.qp_state = IBV_QPS_ERR; + if (ibv_modify_qp(commContext->qp, &qpAttr, IBV_QP_STATE)) + { + LOG(SOCKLIB, WARNING, "Failed to modify qp IBV_QP_STATE."); + } + } + + // ack remaining delayed acks + if(commContext->recvCQ && commContext->numUnackedRecvCompChannelEvents) + ibv_ack_cq_events(commContext->recvCQ, commContext->numUnackedRecvCompChannelEvents); + + if(commContext->qp) + { + rdma_destroy_qp(cm_id); + } + + if(commContext->sendCQ) + { + if(ibv_destroy_cq(commContext->sendCQ) ) + LOG(SOCKLIB, WARNING, "Failed to destroy sendCQ."); + } + + if(commContext->recvCQ) + { + if(ibv_destroy_cq(commContext->recvCQ) ) + LOG(SOCKLIB, WARNING, "Failed to destroy recvCQ."); + } + + if(commContext->recvCompChannel) + { + if(ibv_destroy_comp_channel(commContext->recvCompChannel) ) + LOG(SOCKLIB, WARNING, "Failed to destroy recvCompChannel."); + } + + if(commContext->controlMR) + { + if(ibv_dereg_mr(commContext->controlMR) ) + LOG(SOCKLIB, WARNING, "Failed to deregister controlMR."); + } + + if(commContext->controlResetMR) + { + if(ibv_dereg_mr(commContext->controlResetMR) ) + LOG(SOCKLIB, WARNING, "Failed to deregister controlResetMR."); + } + + if(commContext->recvMR) + { + if(ibv_dereg_mr(commContext->recvMR) ) + LOG(SOCKLIB, WARNING, "Failed to deregister recvMR."); + } + + if(commContext->sendMR) + { + if(ibv_dereg_mr(commContext->sendMR) ) + LOG(SOCKLIB, WARNING, "Failed to deregister sendMR."); + } + +#ifdef BEEGFS_NVFS + if (commContext->workerMRs) + { + for (auto& iter: *(commContext->workerMRs)) + { + if(ibv_dereg_mr(iter.second) ) + LOG(SOCKLIB, WARNING, "Failed to deregister workerMR."); + } + commContext->workerMRs->clear(); + delete(commContext->workerMRs); + } + + if (commContext->cqCompletions) + { + commContext->cqCompletions->clear(); + delete(commContext->cqCompletions); + } + + delete(commContext->cqMutex); +#endif /* BEEGFS_NVFS */ + + SAFE_FREE(commContext->recvBuf); + SAFE_FREE(commContext->sendBuf); + SAFE_FREE(commContext->recvBufs); + SAFE_FREE(commContext->sendBufs); + + if(commContext->pd) + { + if(ibv_dealloc_pd(commContext->pd) ) + LOG(SOCKLIB, WARNING, "Failed to dealloc pd."); + } + + free(commContext); +} + +/** + * Initializes a (local) IBVCommDest. + */ +void __IBVSocket_initCommDest(IBVCommContext* commContext, IBVCommDest* outDest) +{ + memcpy(outDest->verificationStr, IBVSOCKET_PRIVATEDATA_STR, IBVSOCKET_PRIVATEDATA_STR_LEN); + + outDest->protocolVersion = HOST_TO_LE_64(IBVSOCKET_PRIVATEDATA_PROTOCOL_VER); + outDest->rkey = HOST_TO_LE_32(commContext->controlMR->rkey); + outDest->vaddr = HOST_TO_LE_64((uintptr_t)&commContext->numUsedSendBufs); + outDest->recvBufNum = HOST_TO_LE_32(commContext->commCfg.bufNum); + outDest->recvBufSize = HOST_TO_LE_32(commContext->commCfg.bufSize); +} + +/** + * Checks and parses a (remote) IBVCommDest. + * + * @param buf should usually be the private_data of the connection handshake + * @param outDest will be alloced (if true is returned) and needs to be free'd by the caller + * @return true if data is okay, false otherwise + */ +bool __IBVSocket_parseCommDest(const void* buf, size_t bufLen, IBVCommDest** outDest) +{ + IBVCommDest* dest = NULL; + + *outDest = NULL; + + + // Note: "bufLen < ..." (and not "!="), because there might be some extra padding + if(!buf || (bufLen < sizeof(*dest) ) ) + { + LOG(SOCKLIB, WARNING, "Bad private data size.", bufLen); + + return false; + } + + dest = (IBVCommDest*)malloc(sizeof(*dest) ); + if(!dest) + return false; + + memcpy(dest, buf, sizeof(*dest) ); + + if(memcmp(dest->verificationStr, IBVSOCKET_PRIVATEDATA_STR, IBVSOCKET_PRIVATEDATA_STR_LEN) != 0 ) + goto err_cleanup; + + dest->protocolVersion = LE_TO_HOST_64(dest->protocolVersion); + + if (dest->protocolVersion != IBVSOCKET_PRIVATEDATA_PROTOCOL_VER) + goto err_cleanup; + + dest->rkey = LE_TO_HOST_32(dest->rkey); + dest->vaddr = LE_TO_HOST_64(dest->vaddr); + dest->recvBufNum = LE_TO_HOST_32(dest->recvBufNum); + dest->recvBufSize = LE_TO_HOST_32(dest->recvBufSize); + + *outDest = dest; + + return true; + + +err_cleanup: + SAFE_FREE(dest); + + return false; +} + + +/** + * Append buffer to receive queue. + * + * @param commContext passed seperately because it's not the _this->commContext during + * accept() of incoming connections + * @return 0 on success, -1 on error + */ +int __IBVSocket_postRecv(IBVSocket* _this, IBVCommContext* commContext, size_t bufIndex) +{ + struct ibv_sge list; + struct ibv_recv_wr wr; + struct ibv_recv_wr* bad_wr; + int postRes; + + list.addr = (uint64_t)commContext->recvBufs[bufIndex]; + list.length = commContext->commCfg.bufSize; + list.lkey = commContext->recvMR->lkey; + + wr.next = NULL; + wr.wr_id = bufIndex + IBVSOCKET_RECV_WORK_ID_OFFSET; + wr.sg_list = &list; + wr.num_sge = 1; + + postRes = ibv_post_recv(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + LOG(SOCKLIB, WARNING, "ibv_post_recv failed.", postRes, sysErr(postRes)); + return -1; + } + + return 0; +} + +/** + * Synchronous RDMA write (waits for completion) + * + * @return 0 on success, -1 on error + */ +int __IBVSocket_postWrite(IBVSocket* _this, IBVCommDest* remoteDest, + struct ibv_mr* localMR, char* localBuf, int bufLen) +{ + IBVCommContext* commContext = _this->commContext; + struct ibv_sge list; + struct ibv_send_wr wr; + struct ibv_send_wr *bad_wr; + int postRes; + int waitRes; + + list.addr = (uint64_t)localBuf; + list.length = bufLen; + list.lkey = localMR->lkey; + + wr.wr.rdma.remote_addr = remoteDest->vaddr; + wr.wr.rdma.rkey = remoteDest->rkey; + + wr.wr_id = IBVSOCKET_WRITE_WORK_ID; + wr.sg_list = &list; + wr.num_sge = 1; + wr.opcode = IBV_WR_RDMA_WRITE; + wr.send_flags = IBV_SEND_SIGNALED; + wr.next = NULL; + + postRes = ibv_post_send(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + LOG(SOCKLIB, WARNING, "ibv_post_send() failed.", sysErr(postRes)); + return -1; + } + + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + commContext->incompleteSend.numAvailable, 1, 0); + if(unlikely(waitRes) ) + return -1; + + commContext->incompleteSend.numAvailable = 0; + + return 0; +} + +/** + * Synchronous RDMA read (waits for completion). + * + * @return 0 on success, -1 on error + */ +int __IBVSocket_postRead(IBVSocket* _this, IBVCommDest* remoteDest, + struct ibv_mr* localMR, char* localBuf, int bufLen) +{ + IBVCommContext* commContext = _this->commContext; + struct ibv_sge list; + struct ibv_send_wr wr; + struct ibv_send_wr *bad_wr; + int postRes; + int waitRes; + + list.addr = (uint64_t) localBuf; + list.length = bufLen; + list.lkey = localMR->lkey; + + wr.wr.rdma.remote_addr = remoteDest->vaddr; + wr.wr.rdma.rkey = remoteDest->rkey; + + wr.wr_id = IBVSOCKET_READ_WORK_ID; + wr.sg_list = &list; + wr.num_sge = 1; + wr.opcode = IBV_WR_RDMA_READ; + wr.send_flags = IBV_SEND_SIGNALED; + wr.next = NULL; + + postRes = ibv_post_send(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + LOG(SOCKLIB, WARNING, "ibv_post_send() failed.", sysErr(postRes)); + return -1; + } + + waitRes = __IBVSocket_waitForTotalSendCompletion(_this, + commContext->incompleteSend.numAvailable, 0, 1); + if(unlikely(waitRes) ) + return -1; + + commContext->incompleteSend.numAvailable = 0; + + return 0; +} + +#ifdef BEEGFS_NVFS +static bool __IBVSocket_getBufferKey(IBVCommContext *commContext, char *buffer, unsigned *key) +{ + struct ibv_mr *mr = NULL; + + MRMap::const_iterator iter = commContext->workerMRs->find(buffer); + + if (iter == commContext->workerMRs->end()) + { + // It is assumed that buffer came from a Worker and is WORKER_BUFOUT_SIZE. + // TODO: pass around a Buffer with a length instead of unqualified char*. + // This cache of ibv_mr will potentially grow to Workers * Targets + // and the ibv_mr instances hang around until the IBVSocket is destroyed. + // That is probably something to look into... + if (unlikely(__IBVSocket_registerBuf(commContext, buffer, WORKER_BUFOUT_SIZE, &mr))) + { + LOG(SOCKLIB, WARNING, "ibv_postWrite(): failed to register buffer."); + return false; + } + + commContext->workerMRs->insert({buffer, mr}); + } + else + { + mr = iter->second; + } + + *key = mr->lkey; + return true; +} + +/** + * Wait for the completion of a specific RDMA operation. + * @return number of completed elements or -1 in case of an error + */ +static int __IBVSocket_waitForRDMACompletion(IBVCommContext* commContext, uint64_t id) +{ + struct ibv_wc wc[IBVSOCKET_WC_ENTRIES]; + int i = 0; + int found = 0; + int status = 0; + int num_wc = 0; + + /* + * This function is locked so that we don't get a race condition between two workers + * looking for completions. + */ + commContext->cqMutex->lock(); + CQMap::const_iterator iter = commContext->cqCompletions->find(id); + + /* + * Check to see if we have already found the completion we are looking for. + */ + if (iter != commContext->cqCompletions->end()) + { + commContext->cqCompletions->erase(id); + commContext->cqMutex->unlock(); + return 0; + } + + /* + * Continue to poll the CQ until we find the entry in question or we encounter a + * bad status. + */ + while (!found && !status) + { + num_wc = ibv_poll_cq(commContext->sendCQ, IBVSOCKET_WC_ENTRIES, wc); + if (num_wc > 0) + { + for (i = 0; i < num_wc; i++) + { + if (unlikely(wc[i].status != IBV_WC_SUCCESS)) + { + LOG(SOCKLIB, DEBUG, "Connection error.", wc[i].status); + status = -1; + break; + } + + if ((wc[i].opcode == IBV_WC_RDMA_WRITE) || (wc[i].opcode == IBV_WC_RDMA_READ)) + { + if (wc[i].wr_id == id) + { + found = 1; + } + else + { + commContext->cqCompletions->insert({wc[i].wr_id, wc[i].opcode}); + } + } + else if (wc[i].opcode == IBV_WC_SEND) + { + if (likely(commContext->incompleteSend.numAvailable)) + { + commContext->incompleteSend.numAvailable--; + } + else + { + LOG(SOCKLIB, WARNING, "Received bad/unexpected send completion."); + status = -1; + break; + } + } + else + { + LOG(SOCKLIB, WARNING, "Received unexpected CQ opcode.", wc[i].opcode); + status = -1; + break; + } + } + } + } + + commContext->cqMutex->unlock(); + return status; +} + +/** + * Process RDMA requests. + * + * @return 0 on success, -1 on error + */ +static int __IBVSocket_postRDMA(IBVSocket* _this, ibv_wr_opcode opcode, + char* localBuf, int bufLen, unsigned lkey, + uint64_t remoteBuf, unsigned rkey) +{ + IBVCommContext* commContext = _this->commContext; + struct ibv_sge list; + struct ibv_send_wr wr; + struct ibv_send_wr *bad_wr; + int postRes; + int waitRes; + + if (unlikely(lkey == 0)) + { + if (unlikely(!__IBVSocket_getBufferKey(commContext, localBuf, &lkey))) + { + LOG(SOCKLIB, WARNING, "ibv_postRDMA(): no local key."); + return -1; + } + } + + list.addr = (uint64_t) localBuf; + list.length = bufLen; + list.lkey = lkey; + + wr.wr_id = __atomic_fetch_add(&commContext->wr_id, 1, __ATOMIC_SEQ_CST); + wr.next = NULL; + wr.sg_list = &list; + wr.num_sge = 1; + wr.opcode = opcode; + wr.send_flags = IBV_SEND_SIGNALED; + wr.wr.rdma.remote_addr = remoteBuf; + wr.wr.rdma.rkey = rkey; + + postRes = ibv_post_send(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + LOG(SOCKLIB, WARNING, "ibv_post_send() failed.", sysErr(postRes)); + return -1; + } + + waitRes = __IBVSocket_waitForRDMACompletion(commContext, wr.wr_id); + return waitRes; +} + +int __IBVSocket_postWrite(IBVSocket* _this, char* localBuf, int bufLen, + unsigned lkey, uint64_t remoteBuf, unsigned rkey) +{ + return __IBVSocket_postRDMA(_this, IBV_WR_RDMA_WRITE, localBuf, bufLen, + lkey, remoteBuf, rkey); +} + +int __IBVSocket_postRead(IBVSocket* _this, char* localBuf, int bufLen, + unsigned lkey, uint64_t remoteBuf, unsigned rkey) +{ + return __IBVSocket_postRDMA(_this, IBV_WR_RDMA_READ, localBuf, bufLen, + lkey, remoteBuf, rkey); +} + +ssize_t IBVSocket_read(IBVSocket* _this, const char* buf, size_t bufLen, + unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + return __IBVSocket_postRead(_this, (char *)buf, bufLen, lkey, rbuf, rkey); +} + +ssize_t IBVSocket_write(IBVSocket* _this, const char* buf, size_t bufLen, + unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + return __IBVSocket_postWrite(_this, (char *)buf, bufLen, lkey, rbuf, rkey); +} + +#endif /* BEEGFS_NVFS */ + +/** + * Note: Contains flow control. + * + * @return 0 on success, -1 on error + */ +int __IBVSocket_postSend(IBVSocket* _this, size_t bufIndex, int bufLen) +{ + IBVCommContext* commContext = _this->commContext; + struct ibv_sge list; + struct ibv_send_wr wr; + struct ibv_send_wr *bad_wr; + int postRes; + + list.addr = (uint64_t)commContext->sendBufs[bufIndex]; + list.length = bufLen; + list.lkey = commContext->sendMR->lkey; + + wr.wr_id = bufIndex + IBVSOCKET_SEND_WORK_ID_OFFSET; + wr.next = NULL; + wr.sg_list = &list; + wr.num_sge = 1; + wr.opcode = IBV_WR_SEND; + wr.send_flags = IBV_SEND_SIGNALED; + + postRes = ibv_post_send(commContext->qp, &wr, &bad_wr); + if(unlikely(postRes) ) + { + LOG(SOCKLIB, WARNING, "ibv_post_send() failed.", sysErr(postRes)); + return -1; + } + + // flow control + __IBVSocket_flowControlOnSendUpdateCounters(_this); + + return 0; +} + + +/** + * Note: Contains flow control. + * + * @return 1 on success, 0 on timeout, -1 on error + */ +int __IBVSocket_recvWC(IBVSocket* _this, int timeoutMS, struct ibv_wc* outWC) +{ + IBVCommContext* commContext = _this->commContext; + size_t bufIndex; + + int waitRes = __IBVSocket_waitForRecvCompletionEvent(_this, timeoutMS, outWC); + if(waitRes <= 0) + { // (note: waitRes==0 can often happen, because we call this with timeoutMS==0) + + if(unlikely(waitRes < 0) ) + LOG(SOCKLIB, DEBUG, "Retrieval of completion event failed.", waitRes); + else + if(unlikely(timeoutMS) ) + LOG(SOCKLIB, DEBUG, "Waiting for recv completion timed out."); + + return waitRes; + } + + // we got something... + + if(unlikely(outWC->status != IBV_WC_SUCCESS) ) + { + LOG(SOCKLIB, DEBUG, "Connection error.", outWC->status); + return -1; + } + + bufIndex = outWC->wr_id - IBVSOCKET_RECV_WORK_ID_OFFSET; + + if(unlikely(bufIndex >= commContext->commCfg.bufNum) ) + { + LOG(SOCKLIB, WARNING, "Completion for unknown/invalid wr_id.", outWC->wr_id); + return -1; + } + + // receive completed + + //printf("%s: Recveived %u bytes.\n", __func__, outWC->byte_len); // debug in + + // flow control + + if(unlikely(__IBVSocket_flowControlOnRecv(_this, timeoutMS) ) ) + return -1; + + return 1; +} + +/** + * Intention: Avoid IB rnr by sending control msg when (almost) all our recv bufs are used up to + * show that we got our new recv bufs ready. + * + * @return 0 on success, -1 on error + */ +int __IBVSocket_flowControlOnRecv(IBVSocket* _this, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + + // we received a packet, so peer has received all of our currently pending data => reset counter + commContext->numSendBufsLeft = commContext->commCfg.bufNum - 1; /* (see + createCommContext() for "-1" reason) */ + + // send control packet if recv counter expires... + + #ifdef BEEGFS_DEBUG + if(!commContext->numReceivedBufsLeft) + LOG(SOCKLIB, WARNING, "BUG: numReceivedBufsLeft underflow!"); + #endif // BEEGFS_DEBUG + + commContext->numReceivedBufsLeft--; + + if(!commContext->numReceivedBufsLeft) + { + size_t currentBufIndex; + int postRes; + + if(commContext->incompleteSend.numAvailable == commContext->commCfg.bufNum) + { // wait for all (!) incomplete sends + + /* note: it's ok that all send bufs are used up, because it's possible that we do a lot of + recv without the user sending any data in between (so the bufs were actually used up by + flow control). */ + + int waitRes = __IBVSocket_waitForTotalSendCompletion( + _this, commContext->incompleteSend.numAvailable, 0, 0); + if(waitRes < 0) + return -1; + + commContext->incompleteSend.numAvailable = 0; + } + + currentBufIndex = commContext->incompleteSend.numAvailable; + + commContext->incompleteSend.numAvailable++; /* inc'ed before postSend() for conn checks */ + + postRes = __IBVSocket_postSend(_this, currentBufIndex, IBVSOCKET_FLOWCONTROL_MSG_LEN); + if(unlikely(postRes) ) + { + commContext->incompleteSend.numAvailable--; + return -1; + } + + + // note: numReceivedBufsLeft is reset during postSend() flow control + } + + return 0; +} + +/** + * Called after sending a packet to update flow control counters. + * + * Intention: Avoid IB rnr by waiting for control msg when (almost) all peer bufs are used up. + * + * Note: This is only one part of the on-send flow control. The other one is + * _flowControlOnSendWait(). + */ +void __IBVSocket_flowControlOnSendUpdateCounters(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + + // we sent a packet, so we received all currently pending data from the peer => reset counter + commContext->numReceivedBufsLeft = commContext->commCfg.bufNum - 1; /* (see + createCommContext() for "-1" reason) */ + + #ifdef BEEGFS_DEBUG + + if(!commContext->numSendBufsLeft) + LOG(SOCKLIB, WARNING, "BUG: numSendBufsLeft underflow!"); + + #endif + + commContext->numSendBufsLeft--; +} + +/** + * Intention: Avoid IB rnr by waiting for control msg when (almost) all peer bufs are used up. + * + * @timeoutMS may be 0 for non-blocking operation, otherwise typically + * IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS + * @return >0 on success, 0 on timeout (waiting for flow control packet from peer), <0 on error + */ +int __IBVSocket_flowControlOnSendWait(IBVSocket* _this, int timeoutMS) +{ + IBVCommContext* commContext = _this->commContext; + + struct ibv_wc wc; + int recvRes; + size_t bufIndex; + int postRecvRes; + + if(commContext->numSendBufsLeft) + return 1; // flow control not triggered yet + + recvRes = __IBVSocket_recvWC(_this, timeoutMS, &wc); + if(recvRes <= 0) + return recvRes; + + bufIndex = wc.wr_id - IBVSOCKET_RECV_WORK_ID_OFFSET; + + if(unlikely(wc.byte_len != IBVSOCKET_FLOWCONTROL_MSG_LEN) ) + { // error (bad length) + LOG(SOCKLIB, WARNING, "Received flow control packet length mismatch.", wc.byte_len); + return -1; + } + + postRecvRes = __IBVSocket_postRecv(_this, commContext, bufIndex); + if(postRecvRes) + return -1; + + // note: numSendBufsLeft is reset during recvWC() (if it actually received a packet) + + return 1; +} + + +/** + * @return 1 on available data, 0 on timeout, -1 on error + */ +int __IBVSocket_waitForRecvCompletionEvent(IBVSocket* _this, int timeoutMS, struct ibv_wc* outWC) +{ + /* Note: This will also be called with timeoutMS==0 from nonblockingRecvCheck to remove + * a potentially outdated event notification. for this reason, we have to check the event + * channel even if "ibv_poll_cq returns 0" and "timeoutMS==0". */ + + IBVCommContext* commContext = _this->commContext; + struct ibv_cq* ev_cq; // event completion queue + void* ev_ctx; // event context + struct epoll_event epollEvent; + + // check quick path (is an event available without waiting?) + + int numImmediateEvents = ibv_poll_cq(commContext->recvCQ, 1, outWC); + if(unlikely(numImmediateEvents < 0) ) + { + LOG(SOCKLIB, WARNING, "Poll CQ failed.", numImmediateEvents); + return -1; + } + else + if(numImmediateEvents > 0) + return 1; + + + // no immediate event available => wait for them... + + for( ; ; ) /* (loop until "wc retrieved" or "timeout" or "error") */ + { + /* note: we use pollTimeoutMS to check the conn every few secs (otherwise we might + wait for a very long time in case the other side disconnected silently) */ + int pollTimeoutMS = BEEGFS_MIN(_this->timeoutCfg.pollMS, timeoutMS); + + int epollRes = epoll_wait(_this->epollFD, &epollEvent, 1, pollTimeoutMS); + if(unlikely(epollRes < 0) ) + { + if(errno == EINTR) + continue; // ignore EINTR, because debugger causes it + + LOG(SOCKLIB, WARNING, "Epoll error.", sysErr); + return -1; + } + + if(epollRes == 0) + { // poll timed out + + // Note: we check "timeoutMS != 0" here because we don't want to run the + // connCheck each time this method is called from nonblockingRecvCheck + if(timeoutMS) + { + int checkRes = IBVSocket_checkConnection(_this); + if(checkRes < 0) + return -1; + } + + timeoutMS -= pollTimeoutMS; + if(!timeoutMS) + return 0; + + continue; + } + + if(unlikely(_this->cm_channel && + (epollEvent.data.fd == _this->cm_channel->fd) ) ) + { // cm event incoming + struct rdma_cm_event* event = 0; + + if (rdma_get_cm_event(_this->cm_channel, &event) < 0) + { + LOG(SOCKLIB, DEBUG, "Disconnected by rdma_get_cm_event error."); + + _this->errState = -1; + return -1; + } + + // Note: this code doesn't encounter RDMA_CM_EVENT_DEVICE_REMOVAL + if(event->event == RDMA_CM_EVENT_DISCONNECTED) + { + LOG(SOCKLIB, DEBUG, "Disconnect event received."); + + rdma_ack_cm_event(event); + + _this->errState = -1; + return -1; + } + else + { + LOG(SOCKLIB, DEBUG, "Ingoring received event", + ("event", rdma_event_str(event->event))); // debug in + + rdma_ack_cm_event(event); + + continue; + } + } + + // we received a completion event notification => retrieve the event... + + int getEventRes = ibv_get_cq_event(commContext->recvCompChannel, &ev_cq, &ev_ctx); + if(unlikely(getEventRes) ) + { + LOG(SOCKLIB, WARNING, "Failed to get cq_event."); + return -1; + } + + if(unlikely(ev_cq != commContext->recvCQ) ) + { + LOG(SOCKLIB, WARNING, "CQ event for unknown CQ.", ev_cq); + return -1; + } + + // request notification for next event + + int reqNotifyRes = ibv_req_notify_cq(commContext->recvCQ, 0); + if(unlikely(reqNotifyRes) ) + { + LOG(SOCKLIB, WARNING, "Couldn't request CQ notification."); + return -1; + } + + + // ack is expensive, so we gather and ack multiple events + // note: spec says we need this, but current send_bw.c & co don't use ibv_ack_cq_events. + + commContext->numUnackedRecvCompChannelEvents++; + if(commContext->numUnackedRecvCompChannelEvents == IBVSOCKET_EVENTS_GATHER_NUM) + { // ack events and reset counter + ibv_ack_cq_events(commContext->recvCQ, commContext->numUnackedRecvCompChannelEvents); + commContext->numUnackedRecvCompChannelEvents = 0; + } + + + // query event... + + /* note: ibv_poll_cq() does not necessarily return "!=0" after a received event, because the + event might be outdated */ + + int numEvents = ibv_poll_cq(commContext->recvCQ, 1, outWC); + if(unlikely(numEvents < 0) ) + { + LOG(SOCKLIB, WARNING, "Poll CQ failed.", numEvents); + return -1; + } + else + if(numEvents > 0) + return 1; + + // we received a notification for an outdated event => wait again in the next round + + } // end of for-loop + +} + + +/** + * @return number of completed elements or -1 in case of an error + */ +int __IBVSocket_waitForTotalSendCompletion(IBVSocket* _this, + int numSendElements, int numWriteElements, int numReadElements) +{ + IBVCommContext* commContext = _this->commContext; + int numElements; + int i; + size_t bufIndex; + struct ibv_wc wc[2]; + + + do + { + numElements = ibv_poll_cq(commContext->sendCQ, 2, wc); + if(unlikely(numElements < 0) ) + { + LOG(SOCKLIB, WARNING, "Bad ibv_poll_cq result.", numElements); + + return -1; + } + + // for each completion element + for(i=0; i < numElements; i++) + { + if(unlikely(wc[i].status != IBV_WC_SUCCESS) ) + { + LOG(SOCKLIB, DEBUG, "Connection error.", wc[i].status); + return -1; + } + + switch(wc[i].opcode) + { + case IBV_WC_SEND: + { + bufIndex = wc[i].wr_id - IBVSOCKET_SEND_WORK_ID_OFFSET; + + if(unlikely(bufIndex >= commContext->commCfg.bufNum) ) + { + LOG(SOCKLIB, WARNING, "Bad send completion wr_id.", wc[i].wr_id); + return -1; + } + + if(likely(numSendElements) ) + numSendElements--; + else + { + LOG(SOCKLIB, WARNING, "Received bad/unexpected send completion."); + + return -1; + } + + } break; + + case IBV_WC_RDMA_WRITE: + { + if(unlikely(wc[i].wr_id != IBVSOCKET_WRITE_WORK_ID) ) + { + LOG(SOCKLIB, WARNING, "bad write completion wr_id.", wc[i].wr_id); + + return -1; + } + + if(likely(numWriteElements) ) + numWriteElements--; + else + { + LOG(SOCKLIB, WARNING, "Received bad/unexpected RDMA write completion."); + + return -1; + } + } break; + + case IBV_WC_RDMA_READ: + { + if(unlikely(wc[i].wr_id != IBVSOCKET_READ_WORK_ID) ) + { + LOG(SOCKLIB, WARNING, "Bad read completion wr_id.", wc[i].wr_id); + + return -1; + } + + if(likely(numReadElements) ) + numReadElements--; + else + { + LOG(SOCKLIB, WARNING, "Received bad/unexpected RDMA read completion."); + + return -1; + } + } break; + + default: + { + LOG(SOCKLIB, WARNING, "Bad/unexpected completion opcode.", wc[i].opcode); + + return -1; + } break; + + } // end of switch + + } // end of for-loop + + } while(numSendElements || numWriteElements || numReadElements); + + return 0; +} + +/** + * @return 0 on success, -1 on error + */ +int IBVSocket_checkConnection(IBVSocket* _this) +{ + struct ibv_qp_attr qpAttr; + struct ibv_qp_init_attr qpInitAttr; + int qpRes; + int postRes; + IBVCommContext* commContext = _this->commContext; + + //printf("%s: querying qp...\n", __func__); // debug in + + // check qp status + qpRes = ibv_query_qp(commContext->qp, &qpAttr, IBV_QP_STATE, &qpInitAttr); + if(qpRes || (qpAttr.qp_state == IBV_QPS_ERR) ) + { + LOG(SOCKLIB, WARNING, "Detected QP error state."); + + _this->errState = -1; + return -1; + } + + // note: we read a remote value into the numUsedSendBufsReset field, which is actually + // meant for something else, so we need to reset the value afterwards + + //printf("%d:%s: post rdma_read to check connection...\n", __LINE__, __func__); // debug in + + postRes = __IBVSocket_postRead(_this, _this->remoteDest, commContext->controlResetMR, + (char*)&commContext->numUsedSendBufsReset, sizeof(commContext->numUsedSendBufsReset) ); + if(postRes) + { + _this->errState = -1; + return -1; + } + + commContext->numUsedSendBufsReset = 0; + + //printf("%d:%s: rdma_read succeeded\n", __LINE__, __func__); // debug in + + return 0; +} + +/** + * @return <0 on error, 0 if recv would block, >0 if recv would not block + */ +ssize_t IBVSocket_nonblockingRecvCheck(IBVSocket* _this) +{ + /* note: this will also be called from the stream listener for false alarm checks, so make + * sure that we remove an (outdated) event from the channel to mute the false alarm. */ + + IBVCommContext* commContext = _this->commContext; + struct ibv_wc* wc = &commContext->incompleteRecv.wc; + int flowControlRes; + int recvRes; + + if(unlikely(_this->errState) ) + return -1; + + if(commContext->incompleteRecv.isAvailable) + return 1; + + // check whether we have a pending on-send flow control packet that needs to be received first + flowControlRes = __IBVSocket_flowControlOnSendWait(_this, 0); + if(unlikely(flowControlRes < 0) ) + goto err_invalidateSock; + + if(!flowControlRes) + return 0; + + // recv one packet (if available) and add it as incompleteRecv + // or remove event channel notification otherwise (to avoid endless false alerts) + recvRes = __IBVSocket_recvWC(_this, 0, wc); + if(unlikely(recvRes < 0) ) + goto err_invalidateSock; + + if(!recvRes) + return 0; + + // we got something => prepare to continue later + + commContext->incompleteRecv.completedOffset = 0; + commContext->incompleteRecv.isAvailable = 1; + + return 1; + + +err_invalidateSock: + _this->errState = -1; + return -1; +} + +/** + * Call this after accept() to find out whether more events are waiting (for which + * no notification would not be delivered through the file descriptor). + * + * @return true if more events are waiting and accept() should be called again + */ +bool IBVSocket_checkDelayedEvents(IBVSocket* _this) +{ + bool retVal = false; + struct rdma_cm_event* event; + + // check for events in the delay queue + if (!_this->delayedCmEventsQ->empty()) + return true; + + + // Switch channel fd to non-blocking, check for waiting events and switch back to blocking. + // (Quite inefficient, but we really don't have to care about efficiency in this case.) + + // Note: We do this to avoid race conditions (lost events) before we're waiting for + // new notifications with poll() + + // change mode of the connection manager channel to non-blocking + int oldChannelFlags = fcntl(IBVSocket_getConnManagerFD(_this), F_GETFL); + + int setNewFlagsRes = fcntl( + IBVSocket_getConnManagerFD(_this), F_SETFL, oldChannelFlags | O_NONBLOCK); + if(setNewFlagsRes < 0) + { + LOG(SOCKLIB, WARNING, "Set conn manager channel non-blocking failed.", sysErr); + return false; + } + + // (non-blocking) check for new events + if(rdma_get_cm_event(_this->cm_channel, &event) ) + { + // non-blocking mode, so we ignore "pseudo-errors" here + } + else + { // incoming event available + //printf("%d:%s: enqueueing an event (during non-blocking check): %d (%s)\n", + // __LINE__, __func__, event->event, rdma_event_str(event->event) ); // debug in + + _this->delayedCmEventsQ->push(event); + + retVal = true; + } + + + // change channel mode back to blocking + int setOldFlagsRes = fcntl(IBVSocket_getConnManagerFD(_this), F_SETFL, oldChannelFlags); + if(setOldFlagsRes < 0) + { + LOG(SOCKLIB, WARNING, "Set conn manager channel blocking failed.", sysErr); + return false; + } + + + return retVal; +} + +void __IBVSocket_disconnect(IBVSocket* _this) +{ + /* note: we only call rdma_disconnect() here if the socket is not connected to the common + listen sock event channel to avoid a race condition (because the sock accept method also + calls rdma_disconnect() in the streamlistener thread. ...but that's ok. we really don't need + that additional event if we're actively disconnecting the sock. */ + + if(_this->cm_channel) + { + int disconnectRes = rdma_disconnect(_this->cm_id); + if(disconnectRes) + { + LOG(SOCKLIB, WARNING, "rdma disconnect error.", sysErr); + return; + } + + // note: we can't wait for events here, because the disconnect event might + // be received by the listen socket channel (for accepted sockets with older + // ofed versions). + + /* + if(!_this->cm_channel || !waitForEvent) + return; + + rdma_get_cm_event(_this->cm_channel, &event); + if(event->event != RDMA_CM_EVENT_DISCONNECTED) + { + SyslogLogger::log(LOG_WARNING, "%s: unexpected event during disconnect %d: %s\n", + __func__, event->event, rdma_event_str(event->event) ); + } + + rdma_ack_cm_event(event); + */ + } + +} + +void __IBVSocket_close(IBVSocket* _this) +{ + SAFE_FREE(_this->remoteDest); + + if(_this->delayedCmEventsQ) + { // ack all queued events + while (!_this->delayedCmEventsQ->empty()) + { + struct rdma_cm_event* nextEvent = _this->delayedCmEventsQ->front(); + + rdma_ack_cm_event(nextEvent); + + _this->delayedCmEventsQ->pop(); + } + + delete(_this->delayedCmEventsQ); + } + + if(_this->commContext) + __IBVSocket_cleanupCommContext(_this->cm_id, _this->commContext); + + if(_this->cm_id) + rdma_destroy_id(_this->cm_id); + if(_this->cm_channel) + rdma_destroy_event_channel(_this->cm_channel); +} + +/** + * Note: Call this for connected sockets only. + */ +bool __IBVSocket_initEpollFD(IBVSocket* _this) +{ + _this->epollFD = epoll_create(1); // "1" is just a hint (and is actually ignored) + if(_this->epollFD == -1) + { + LOG(SOCKLIB, WARNING, "epoll initialization error.", sysErr); + return false; + } + + struct epoll_event epollEvent; + + epollEvent.events = EPOLLIN; + epollEvent.data.fd = IBVSocket_getRecvCompletionFD(_this); + + // note: we only add the recvCompletionFD here and not commContext->context->async_fd, because + // accepted sockets don't have their own async event channel (they receive events through + // their parent's fd) + + int epollAddRes = epoll_ctl(_this->epollFD, EPOLL_CTL_ADD, + IBVSocket_getRecvCompletionFD(_this), &epollEvent); + + if(epollAddRes == -1) + { + LOG(SOCKLIB, WARNING, "Unable to add sock to epoll set.", sysErr); + + close(_this->epollFD); + _this->epollFD = -1; + + return false; + } + + if(_this->cm_channel) + { + epollEvent.events = EPOLLIN; + epollEvent.data.fd = _this->cm_channel->fd; + + int epollAddRes = epoll_ctl(_this->epollFD, EPOLL_CTL_ADD, + _this->cm_channel->fd, &epollEvent); + + if(epollAddRes == -1) + { + LOG(SOCKLIB, WARNING, "Unable to add sock to epoll set.", sysErr); + + close(_this->epollFD); + _this->epollFD = -1; + + return false; + } + } + + return true; +} + +/** + * @return pointer to static buffer with human readable string for a wc status code + */ +const char* __IBVSocket_wcStatusStr(int wcStatusCode) +{ + switch(wcStatusCode) + { + case IBV_WC_WR_FLUSH_ERR: + return "work request flush error"; + case IBV_WC_RETRY_EXC_ERR: + return "retries exceeded error"; + case IBV_WC_RESP_TIMEOUT_ERR: + return "response timeout error"; + + default: + return ""; + } + +} + +bool IBVSocket_getSockValid(IBVSocket* _this) +{ + return _this->sockValid; +} + +int IBVSocket_getRecvCompletionFD(IBVSocket* _this) +{ + IBVCommContext* commContext = _this->commContext; + + return commContext ? commContext->recvCompChannel->fd : (-1); +} + +int IBVSocket_getConnManagerFD(IBVSocket* _this) +{ + return _this->cm_channel ? _this->cm_channel->fd : (-1); +} + +void IBVSocket_setTypeOfService(IBVSocket* _this, uint8_t typeOfService) +{ + _this->typeOfService = typeOfService; +} + +void IBVSocket_setTimeouts(IBVSocket* _this, int connectMS, int flowSendMS, int pollMS) +{ + _this->timeoutCfg.connectMS = connectMS > 0 ? connectMS : IBVSOCKET_CONN_TIMEOUT_MS; + _this->timeoutCfg.flowSendMS = flowSendMS > 0? flowSendMS : IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS; + _this->timeoutCfg.pollMS = pollMS > 0? pollMS : IBVSOCKET_POLL_TIMEOUT_MS; + LOG(SOCKLIB, DEBUG, "timeouts", ("connectMS", _this->timeoutCfg.connectMS), + ("flowSendMS", _this->timeoutCfg.flowSendMS), ("pollMS", _this->timeoutCfg.pollMS)); +} + +void IBVSocket_setConnectionRejectionRate(IBVSocket* _this, unsigned rate) +{ + _this->connectionRejectionRate = rate; +} + +bool IBVSocket_connectionRejection(IBVSocket* _this) +{ + if(_this->connectionRejectionRate) + { + ++_this->connectionRejectionCount; + if((_this->connectionRejectionCount % _this->connectionRejectionRate) != 0) + { + LOG(SOCKLIB, WARNING, "dropping connection for testing.", + _this->connectionRejectionCount, + _this->connectionRejectionRate); + return true; + } + } + + return false; +} + diff --git a/common/ib_lib/net/sock/ibvsocket/IBVSocket.h b/common/ib_lib/net/sock/ibvsocket/IBVSocket.h new file mode 100644 index 0000000..06c2ccc --- /dev/null +++ b/common/ib_lib/net/sock/ibvsocket/IBVSocket.h @@ -0,0 +1,200 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + + + +#include +#include + +#ifdef BEEGFS_NVFS +#include +#include +#endif /* BEEGFS_NVFS */ + +#define IBVSOCKET_RECV_WORK_ID_OFFSET (1) +#define IBVSOCKET_SEND_WORK_ID_OFFSET (1 + IBVSOCKET_RECV_WORK_ID_OFFSET) +#define IBVSOCKET_WRITE_WORK_ID (1 + IBVSOCKET_SEND_WORK_ID_OFFSET) +#define IBVSOCKET_READ_WORK_ID (1 + IBVSOCKET_WRITE_WORK_ID) + +#define IBVSOCKET_EVENTS_GATHER_NUM (64) + +#define IBVSOCKET_PRIVATEDATA_STR "fhgfs0 " // must be exactly(!!) 8 bytes long +#define IBVSOCKET_PRIVATEDATA_STR_LEN 8 +#define IBVSOCKET_PRIVATEDATA_PROTOCOL_VER 1 + + +struct IBVIncompleteRecv; +typedef struct IBVIncompleteRecv IBVIncompleteRecv; +struct IBVIncompleteSend; +typedef struct IBVIncompleteSend IBVIncompleteSend; + +struct IBVCommContext; +typedef struct IBVCommContext IBVCommContext; + +struct IBVCommDest; +typedef struct IBVCommDest IBVCommDest; + +typedef std::queue CmEventQueue; +#ifdef BEEGFS_NVFS +typedef std::unordered_map MRMap; +typedef std::unordered_map CQMap; +#endif /* BEEGFS_NVFS */ + + +extern void __IBVSocket_initFromCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommContext* commContext); +extern IBVSocket* __IBVSocket_constructFromCommContext(struct rdma_cm_id* cm_id, + IBVCommContext* commContext); + + +extern int __IBVSocket_registerBuf(IBVCommContext* commContext, void* buf, size_t bufLen, + struct ibv_mr **outMR); +extern char* __IBVSocket_allocAndRegisterBuf(IBVCommContext* commContext, size_t bufLen, + struct ibv_mr **outMR); + +extern bool __IBVSocket_createCommContext(IBVSocket* _this, struct rdma_cm_id* cm_id, + IBVCommConfig* commCfg, IBVCommContext** outCommContext); +extern void __IBVSocket_cleanupCommContext(struct rdma_cm_id* cm_id, IBVCommContext* commContext); + +extern void __IBVSocket_initCommDest(IBVCommContext* commContext, IBVCommDest* outDest); +extern bool __IBVSocket_parseCommDest(const void* buf, size_t bufLen, IBVCommDest** outDest); + +extern int __IBVSocket_postRecv(IBVSocket* _this, IBVCommContext* commContext, size_t bufIndex); +extern int __IBVSocket_postWrite(IBVSocket* _this, IBVCommDest* remoteDest, + struct ibv_mr* localMR, char* localBuf, int bufLen); +extern int __IBVSocket_postRead(IBVSocket* _this, IBVCommDest* remoteDest, + struct ibv_mr* localMR, char* localBuf, int bufLen); +#ifdef BEEGFS_NVFS +extern int __IBVSocket_postWrite(IBVSocket* _this, char* localBuf, int bufLen, unsigned lkey, + uint64_t remoteBuf, unsigned rkey); +extern int __IBVSocket_postRead(IBVSocket* _this, char* localBuf, int bufLen, unsigned lkey, + uint64_t remoteBuf, unsigned rkey); +#endif /* BEEGFS_NVFS */ +extern int __IBVSocket_postSend(IBVSocket* _this, size_t bufIndex, int bufLen); +extern int __IBVSocket_recvWC(IBVSocket* _this, int timeoutMS, struct ibv_wc* outWC); + +extern int __IBVSocket_flowControlOnRecv(IBVSocket* _this, int timeoutMS); +extern void __IBVSocket_flowControlOnSendUpdateCounters(IBVSocket* _this); +extern int __IBVSocket_flowControlOnSendWait(IBVSocket* _this, int timeoutMS); + +extern int __IBVSocket_waitForRecvCompletionEvent(IBVSocket* _this, int timeoutMS, + struct ibv_wc* outWC); +extern int __IBVSocket_waitForTotalSendCompletion(IBVSocket* _this, + int numSendElements, int numWriteElements, int numReadElements); +extern int __IBVSocket_waitForUsedSendBufsReset(IBVSocket* _this); + +extern ssize_t __IBVSocket_recvContinueIncomplete(IBVSocket* _this, + char* buf, size_t bufLen); + +extern void __IBVSocket_disconnect(IBVSocket* _this); +extern void __IBVSocket_close(IBVSocket* _this); + +extern bool __IBVSocket_initEpollFD(IBVSocket* _this); + +extern const char* __IBVSocket_wcStatusStr(int wcStatusCode); + +struct IBVIncompleteRecv +{ + int isAvailable; + int completedOffset; + struct ibv_wc wc; +}; + +struct IBVIncompleteSend +{ + unsigned numAvailable; +}; + +struct IBVTimeoutConfig +{ + int connectMS; + int flowSendMS; + int pollMS; +}; + +struct IBVCommContext +{ + struct ibv_context* context; + struct ibv_pd* pd; // protection domain + struct ibv_mr* recvMR; // recvBuf mem region + struct ibv_mr* sendMR; // sendBuf mem region + struct ibv_mr* controlMR; // flow/flood control mem region + struct ibv_mr* controlResetMR; // flow/flood control reset mem region + + struct ibv_comp_channel* recvCompChannel; // recv completion event channel + unsigned numUnackedRecvCompChannelEvents; // number of gathered events + + struct ibv_cq* recvCQ; // recv completion queue + struct ibv_cq* sendCQ; // send completion queue + struct ibv_qp* qp; // send+recv queue pair + + IBVCommConfig commCfg; + char* recvBuf; // large alloc'ed and reg'ed buffer for recvBufs + char** recvBufs; // points to chunks inside recvBuf + char* sendBuf; // large alloc'ed and reg'ed buffer for sendBufs + char** sendBufs; // points to chunks inside sendBuf + volatile uint64_t numUsedSendBufs; // sender's flow/flood control counter (volatile!!) + volatile uint64_t numUsedSendBufsReset; // flow/flood control reset value + uint64_t numUsedRecvBufs; // receiver's flow/flood control (reset) counter + unsigned numReceivedBufsLeft; // flow control v2 to avoid IB rnr timeout + unsigned numSendBufsLeft; // flow control v2 to avoid IB rnr timeout + + IBVIncompleteRecv incompleteRecv; + IBVIncompleteSend incompleteSend; +#ifdef BEEGFS_NVFS + uint64_t wr_id; + Mutex *cqMutex; + CQMap *cqCompletions; + MRMap *workerMRs; +#endif /* BEEGFS_NVFS */ +}; + +#pragma pack(push, 1) +// Note: Make sure this struct has the same size on all architectures (because we use +// sizeof(IBVCommDest) for private_data during handshake) +struct IBVCommDest +{ + char verificationStr[IBVSOCKET_PRIVATEDATA_STR_LEN]; + uint64_t protocolVersion; + uint64_t vaddr; + unsigned rkey; + unsigned recvBufNum; + unsigned recvBufSize; +}; +#pragma pack(pop) + +struct IBVSocket +{ + struct rdma_event_channel* cm_channel; + struct rdma_cm_id* cm_id; + + IBVCommDest localDest; + IBVCommDest* remoteDest; + + IBVCommContext* commContext; + int epollFD; // only for connected sockets, invalid (-1) for listeners + + bool sockValid; + int errState; + + CmEventQueue* delayedCmEventsQ; + + uint8_t typeOfService; + + unsigned connectionRejectionRate; + unsigned connectionRejectionCount; + + IBVTimeoutConfig timeoutCfg; + struct in_addr bindIP; +}; + + diff --git a/common/ib_lib/net/sock/ibvsocket/OpenTk_IBVSocket.h b/common/ib_lib/net/sock/ibvsocket/OpenTk_IBVSocket.h new file mode 100644 index 0000000..829a926 --- /dev/null +++ b/common/ib_lib/net/sock/ibvsocket/OpenTk_IBVSocket.h @@ -0,0 +1,82 @@ +#pragma once + +#include + + +/* + * This is the interface of the ibverbs socket abstraction. + */ + + +struct IBVSocket; +typedef struct IBVSocket IBVSocket; + +struct IBVCommConfig; +typedef struct IBVCommConfig IBVCommConfig; + + +enum IBVSocket_AcceptRes + {ACCEPTRES_ERR=0, ACCEPTRES_IGNORE=1, ACCEPTRES_SUCCESS=2}; +typedef enum IBVSocket_AcceptRes IBVSocket_AcceptRes; + + +// construction/destruction +extern void IBVSocket_init(IBVSocket* _this); +extern IBVSocket* IBVSocket_construct(); +extern void IBVSocket_uninit(IBVSocket* _this); +extern void IBVSocket_destruct(IBVSocket* _this); + +// static +extern bool IBVSocket_rdmaDevicesExist(void); +extern void IBVSocket_fork_init_once(void); + +// methods +extern bool IBVSocket_connectByName(IBVSocket* _this, const char* hostname, unsigned short port, + IBVCommConfig* commCfg); +extern bool IBVSocket_connectByIP(IBVSocket* _this, struct in_addr ipaddress, unsigned short port, + IBVCommConfig* commCfg); +extern bool IBVSocket_bind(IBVSocket* _this, unsigned short port); +extern bool IBVSocket_bindToAddr(IBVSocket* _this, in_addr_t ipAddr, unsigned short port); +extern bool IBVSocket_listen(IBVSocket* _this); +extern IBVSocket_AcceptRes IBVSocket_accept(IBVSocket* _this, IBVSocket** outAcceptedSock, + struct sockaddr* peerAddr, socklen_t* peerAddrLen); +extern bool IBVSocket_shutdown(IBVSocket* _this); + +#ifdef BEEGFS_NVFS +extern ssize_t IBVSocket_write(IBVSocket* _this, const char* buf, size_t bufLen, unsigned lkey, + const uint64_t rbuf, unsigned rkey); +extern ssize_t IBVSocket_read(IBVSocket* _this, const char* buf, size_t bufLen, unsigned lkey, + const uint64_t rbuf, unsigned rkey); +#endif /* BEEGFS_NVFS */ + +extern ssize_t IBVSocket_recv(IBVSocket* _this, char* buf, size_t bufLen, int flags); +extern ssize_t IBVSocket_recvT(IBVSocket* _this, char* buf, size_t bufLen, int flags, + int timeoutMS); +extern ssize_t IBVSocket_send(IBVSocket* _this, const char* buf, size_t bufLen, int flags); + +extern int IBVSocket_checkConnection(IBVSocket* _this); +extern ssize_t IBVSocket_nonblockingRecvCheck(IBVSocket* _this); +extern bool IBVSocket_checkDelayedEvents(IBVSocket* _this); + + +// getters & setters +extern bool IBVSocket_getSockValid(IBVSocket* _this); +extern int IBVSocket_getRecvCompletionFD(IBVSocket* _this); +extern int IBVSocket_getConnManagerFD(IBVSocket* _this); +extern void IBVSocket_setTypeOfService(IBVSocket* _this, uint8_t typeOfService); +extern void IBVSocket_setTimeouts(IBVSocket* _this, int connectMS, int flowSendMS, + int pollMS); + +// testing methods +extern void IBVSocket_setConnectionRejectionRate(IBVSocket* _this, unsigned rate); +extern bool IBVSocket_connectionRejection(IBVSocket* _this); + + +struct IBVCommConfig +{ + unsigned bufNum; // number of available buffers + unsigned bufSize; // size of each buffer + uint8_t serviceLevel; +}; + + diff --git a/common/source/common/Assert.cpp b/common/source/common/Assert.cpp new file mode 100644 index 0000000..57a191b --- /dev/null +++ b/common/source/common/Assert.cpp @@ -0,0 +1,56 @@ +#include "Common.h" + +#ifdef BEEGFS_DEBUG + +#include +#include + +#include +#include + +namespace beegfs_debug +{ + +void assertMsg(const char* file, unsigned line, const char* condition) +{ + std::stringstream message; + + const char* basename = strrchr(file, '/'); + if (!basename) + basename = file; + else + basename++; + + message << "ASSERTION TRIGGERED AT " << basename << ":" << line << '\n'; + + message << '\n'; + message << condition; + message << '\n'; + + message << '\n'; + message << "Backtrace of asserting thread "; + message << PThread::getCurrentThreadName(); + message << ":"; + + std::vector backtrace(32); + int backtraceSize; + + backtraceSize = ::backtrace(&backtrace[0], (int) backtrace.size()); + while (backtraceSize == (int) backtrace.size()) + { + backtrace.resize(backtraceSize * 2); + backtraceSize = ::backtrace(&backtrace[0], (int) backtrace.size()); + } + + char** symbols = backtrace_symbols(&backtrace[0], backtraceSize); + for (int i = 0; i < backtraceSize; i++) + message << '\n' << i << ":\t" << symbols[i]; + + LOG(GENERAL,ERR, message.str()); + + exit(1); +} + +} + +#endif diff --git a/common/source/common/Common.h b/common/source/common/Common.h new file mode 100644 index 0000000..0ff778e --- /dev/null +++ b/common/source/common/Common.h @@ -0,0 +1,345 @@ +#pragma once + +// define certain macros for (u)int64_t in inttypes.h; this is useful for printf on 32-/64-bit +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif // __STDC_FORMAT_MACROS + +#include +#include +#include +#include +#include +#include +#include // output formating +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * NOTE: These timeouts can now be overridden by the connMessagingTimeouts + * option in the configuration file. If that option is unset or set to <=0, we + * still default to these constants. + */ +#define CONN_LONG_TIMEOUT 600000 +#define CONN_MEDIUM_TIMEOUT 90000 +#define CONN_SHORT_TIMEOUT 30000 + +typedef std::map StringMap; +typedef StringMap::iterator StringMapIter; +typedef StringMap::const_iterator StringMapCIter; +typedef StringMap::value_type StringMapVal; + +typedef std::map StringInt64Map; +typedef StringInt64Map::iterator StringInt64MapIter; +typedef StringInt64Map::const_iterator StringInt64MapCIter; +typedef StringInt64Map::value_type StringInt64MapVal; + +typedef std::set StringSet; +typedef StringSet::iterator StringSetIter; +typedef StringSet::const_iterator StringSetCIter; + +typedef std::list StringList; +typedef StringList::iterator StringListIter; +typedef StringList::const_iterator StringListConstIter; + +typedef std::vector StringVector; +typedef StringVector::iterator StringVectorIter; +typedef StringVector::const_iterator StringVectorConstIter; + +typedef std::list UInt8List; +typedef UInt8List::iterator UInt8ListIter; +typedef UInt8List::const_iterator UInt8ListConstIter; + +typedef std::set UInt16Set; +typedef UInt16Set::iterator UInt16SetIter; +typedef UInt16Set::const_iterator UInt16SetCIter; + +typedef std::list UInt16List; +typedef UInt16List::iterator UInt16ListIter; +typedef UInt16List::const_iterator UInt16ListConstIter; + +typedef std::vector UInt16Vector; +typedef UInt16Vector::iterator UInt16VectorIter; +typedef UInt16Vector::const_iterator UInt16VectorConstIter; + +typedef std::vector UInt16SetVector; +typedef UInt16SetVector::iterator UInt16SetVectorIter; +typedef UInt16SetVector::const_iterator UInt16SetVectorConstIter; + +typedef std::vector UInt16ListVector; + +typedef std::set IntSet; +typedef IntSet::iterator IntSetIter; +typedef IntSet::const_iterator IntSetCIter; + +typedef std::set UIntSet; +typedef UIntSet::iterator UIntSetIter; +typedef UIntSet::const_iterator UIntSetCIter; + +typedef std::list IntList; +typedef IntList::iterator IntListIter; +typedef IntList::const_iterator IntListConstIter; + +typedef std::list UIntList; +typedef UIntList::iterator UIntListIter; +typedef UIntList::const_iterator UIntListConstIter; + +typedef std::vector UIntVector; +typedef UIntVector::iterator UIntVectorIter; +typedef UIntVector::const_iterator UIntVectorConstIter; + +typedef std::vector IntVector; +typedef IntVector::iterator IntVectorIter; +typedef IntVector::const_iterator IntVectorConstIter; + +typedef std::list ShortList; +typedef ShortList::iterator ShortListIter; +typedef ShortList::const_iterator ShortListConstIter; + +typedef std::list UShortList; +typedef UShortList::iterator UShortListIter; +typedef UShortList::const_iterator UShortListConstIter; + +typedef std::vector ShortVector; +typedef ShortVector::iterator ShortVectorIter; +typedef ShortVector::const_iterator ShortVectorConstIter; + +typedef std::vector UShortVector; +typedef UShortVector::iterator UShortVectorIter; +typedef UShortVector::const_iterator UShortVectorConstIter; + +typedef std::vector CharVector; +typedef CharVector::iterator CharVectorIter; +typedef CharVector::const_iterator CharVectorConstIter; + +typedef std::list Int64List; +typedef Int64List::iterator Int64ListIter; +typedef Int64List::const_iterator Int64ListConstIter; + +typedef std::list UInt64List; +typedef UInt64List::iterator UInt64ListIter; +typedef UInt64List::const_iterator UInt64ListConstIter; + +typedef std::vector Int64Vector; +typedef Int64Vector::iterator Int64VectorIter; +typedef Int64Vector::const_iterator Int64VectorConstIter; + +typedef std::vector UInt64Vector; +typedef UInt64Vector::iterator UInt64VectorIter; +typedef UInt64Vector::const_iterator UInt64VectorConstIter; + +typedef std::list BoolList; +typedef BoolList::iterator BoolListIter; +typedef BoolList::const_iterator BoolListConstIter; + +#define BEEGFS_MIN(a, b) \ + ( ( (a) < (b) ) ? (a) : (b) ) +#define BEEGFS_MAX(a, b) \ + ( ( (a) < (b) ) ? (b) : (a) ) + + +#define SAFE_DELETE(p) \ + do{ if(p) {delete (p); (p)=NULL;} } while(0) +#define SAFE_FREE(p) \ + do{ if(p) {free(p); (p)=NULL;} } while(0) + +#define SAFE_DELETE_NOSET(p) \ + do{ if(p) delete (p); } while(0) +#define SAFE_FREE_NOSET(p) \ + do{ if(p) free(p); } while(0) + +// typically used for optional out-args +#define SAFE_ASSIGN(destPointer, sourceValue) \ + do{ if(destPointer) {*(destPointer) = (sourceValue);} } while(0) + + +// gcc branch optimization hints +#define likely(x) __builtin_expect(!!(x), 1) +#define unlikely(x) __builtin_expect(!!(x), 0) + + +// this macro mutes warnings about unused variables +#define IGNORE_UNUSED_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0) + +// this macro mutes warnings about unsused variables that are only used in debug build +#ifdef BEEGFS_DEBUG +#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ /* do nothing */ } while(0) +#else +#define IGNORE_UNUSED_DEBUG_VARIABLE(a) do{ if( ((long)a)==1) {} } while(0) +#endif + + +// dumps stack in case of a buggy condition +#define BEEGFS_BUG_ON(condition, msgStr) \ + do { \ + if(unlikely(condition) ) { \ + fprintf(stderr, "%s:%d: %s\n", __func__, __LINE__, msgStr); \ + LogContext(std::string(__func__) + StringTk::intToStr(__LINE__) ).logErr( \ + std::string("BUG: ") + msgStr + std::string(" (dumping stack...)") ); \ + LogContext(std::string(__func__) + StringTk::intToStr(__LINE__) ).logBacktrace(); \ + } \ + } while(0) + + +#ifdef LOG_DEBUG_MESSAGES + #define BEEGFS_BUG_ON_DEBUG(condition, msgStr) BEEGFS_BUG_ON(condition, msgStr) +#else // !LOG_DEBUG_MESSAGES + #define BEEGFS_BUG_ON_DEBUG(condition, msgStr) \ + do { /* nothing */ } while(0) +#endif // LOG_DEBUG_MESSAGES + +//logtype to enable syslog +enum LogType +{ + LogType_SYSLOG=0, + LogType_LOGFILE +}; + +#ifdef BEEGFS_DEBUG + #define BEEGFS_DEBUG_PROFILING +#endif + +// defined `override` (from c++11) if the compiler does not provide it. gcc >= 4.7 has it, +// clang since 3.0. +// we don't support gcc <= 4.4, so don't check those. +#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 7) || (__clang__ && __clang_major__ < 3) + #define override +#endif + +// gcc 4.4 does not seem to support eq-operators for enum classes. other old gcc versions may have +// this problem. +#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ == 4 && __GNUC_PATCHLEVEL__ == 0) +# define GCC_COMPAT_ENUM_CLASS_OPEQNEQ(TYPE) \ + inline bool operator==(TYPE a, TYPE b) { return memcmp(&a, &b, sizeof(a)) == 0; } \ + inline bool operator!=(TYPE a, TYPE b) { return !(a == b); } +#else +# define GCC_COMPAT_ENUM_CLASS_OPEQNEQ(TYPE) +#endif + +// define a nullptr type and value (also from c++11) if the compiler does not provide it. +// gcc >= 4.6 has it, as does clang >= 3.0. +// this merely approximates the actual nullptr behaviour from c++11, but it approximates it close +// enough to be useful. +// the implementation is taken from N2431, which proposes nullptr as a language feature. +#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 6) || (__clang__ && __clang_major__ < 3) +struct nullptr_t +{ + template + operator T*() const { return 0; } + + template + operator T C::*() const { return 0; } + + template + operator std::unique_ptr() const { return {}; } + + template + operator std::unique_ptr() const { return {}; } + + template + operator std::shared_ptr() const { return {}; } + + template + operator std::weak_ptr() const { return {}; } + + void operator&() = delete; + + // add a bool-ish conversion operator to allow `if (nullptr)` (e.g. in templates) + typedef void (nullptr_t::*bool_type)(); + operator bool_type() const { return 0; } +}; + +#define nullptr ((const nullptr_t)nullptr_t{}) +#endif + +#if (!__clang__ && __GNUC__ == 4 && __GNUC_MINOR__ < 6) || (__clang__ && __clang_major__ < 3) +# define noexcept(...) +#endif + +// libstdc++ <= 4.6 calls std::chrono::steady_clock monotonic_clock, set alias +#if __GLIBCXX__ && __GLIBCXX__ < 20120322 +namespace std +{ +namespace chrono +{ +typedef monotonic_clock steady_clock; +} +} +#endif + +/* + * Optional definitions: + * - BEEGFS_NO_LICENSE_CHECK: Disables all license checking at startup and runtime + */ + +/* + * Debug definitions: + * - BEEGFS_DEBUG: Generally defined for debug builds. Might turn on some other debug definitions + * below automatically. + * + * - LOG_DEBUG_MESSAGES: Enables logging of some extra debug messages that will not be + * available otherwise + * - DEBUG_REFCOUNT: Enables debugging of ObjectReferencer::refCount. Error messages will + * be logged if refCount is less than zero + * - DEBUG_MUTEX_LOCKING: Enables the debug functionality of the SafeRWLock class + * - BEEGFS_DEBUG_PROFILING: Additional timestamps in log messages. + */ + +#ifdef BEEGFS_DEBUG +namespace beegfs_debug +{ +__attribute__((noreturn)) +extern void assertMsg(const char* file, unsigned line, const char* condition); +} + +# define ASSERT(condition) \ + do { \ + if (!(condition)) \ + ::beegfs_debug::assertMsg(__FILE__, __LINE__, #condition); \ + } while (0) +#else +# define ASSERT(cond) do {} while (0) +#endif + +#if defined (__GLIBC__) && (__GLIBC__ >= 2) && (__GLIBC_MINOR__ >= 24) +#define USE_READDIR_R 0 +#else +#define USE_READDIR_R 1 +#endif + +#if __GNUC__ > 6 +# define BEEGFS_FALLTHROUGH [[fallthrough]] +#else +# define BEEGFS_FALLTHROUGH +#endif + +// XXX not sure about this. C++17 is the requirement for [[nodiscard]] +#define BEEGFS_NODISCARD [[nodiscard]] + +// version number of both the network protocol and the on-disk data structures that are versioned. +// must be kept in sync with client. +#define BEEGFS_DATA_VERSION (uint32_t(0)) + diff --git a/common/source/common/NumericID.h b/common/source/common/NumericID.h new file mode 100644 index 0000000..f9eca59 --- /dev/null +++ b/common/source/common/NumericID.h @@ -0,0 +1,197 @@ +#pragma once + +#include +#include + +#include +#include + + +/* + * template class to represent a Numeric ID; which can be used as Numeric Node ID or Group ID, etc. + * + * we use classes instead of a simple typedef to achieve "type" safety with nodeIDs, groupIDs, + * targetIDs, etc. + * + * implemented as template because different kinds of IDs might have different base data types + */ +template +class NumericID final +{ + public: + typedef T ValueT; + + NumericID(): + value(0) { } + + explicit NumericID(T value): + value(value) { } + + T val() const + { + return value; + } + + std::string str() const + { + std::stringstream out; + out << value; + return out.str(); + } + + std::string strHex() const + { + std::stringstream out; + out << std::hex << std::uppercase << value; + return out.str(); + } + + void fromHexStr(const std::string& valueStr) + { + std::stringstream in(valueStr); + in >> std::hex >> value; + } + + void fromStr(const std::string& valueStr) + { + std::stringstream in(valueStr); + in >> value; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } + + bool operator==(const NumericID& other) const + { + return (value == other.value); + } + + bool operator!=(const NumericID& other) const + { + return (value != other.value); + } + + bool operator<(const NumericID& other) const + { + return (value < other.value); + } + + bool operator>(const NumericID& other) const + { + return (value > other.value); + } + + bool operator<=(const NumericID& other) const + { + return (value <= other.value); + } + + bool operator>=(const NumericID& other) const + { + return (value >= other.value); + } + + NumericID& operator++() + { + ++value; + return *this; + } + + NumericID operator++(int) + { + NumericID result = *this; + ++value; + return result; + } + + NumericID& operator--() + { + --value; + return *this; + } + + NumericID operator--(int) + { + NumericID result = *this; + --value; + return result; + } + + bool operator!() const + { + return (value == 0); + } + + explicit operator bool() const + { + return value != 0; + } + + friend std::ostream& operator<< (std::ostream& out, const NumericID& obj) + { + return out << obj.str(); + } + + friend std::istream& operator>> (std::istream& in, NumericID& obj) + { + in >> obj.value; + return in; + } + + protected: + T value; +}; + +namespace std +{ + template + struct hash> + { + size_t operator()(const NumericID& id) const + { + return std::hash()(id.val()); + } + }; + + template + class numeric_limits> + { + public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = std::numeric_limits::is_signed; + static constexpr bool is_integer = std::numeric_limits::is_integer; + static constexpr bool is_exact = std::numeric_limits::is_exact; + static constexpr bool has_infinity = std::numeric_limits::has_infinity; + static constexpr bool has_quiet_NaN = std::numeric_limits::has_quiet_NaN; + static constexpr bool has_signaling_NaN = std::numeric_limits::has_signaling_NaN; + static constexpr bool has_denorm = std::numeric_limits::has_denorm; + static constexpr bool has_denorm_loss = std::numeric_limits::has_denorm_loss; + static constexpr std::float_round_style round_style = std::numeric_limits::round_style; + static constexpr bool is_iec559 = std::numeric_limits::is_iec559; + static constexpr bool is_bounded = std::numeric_limits::is_bounded; + static constexpr bool is_modulo = std::numeric_limits::is_modulo; + static constexpr int digits = std::numeric_limits::digits; + static constexpr int digits10 = std::numeric_limits::digits10; + static constexpr int max_digits10 = std::numeric_limits::max_digits10; + static constexpr int radix = std::numeric_limits::radix; + static constexpr int min_exponent = std::numeric_limits::min_exponent; + static constexpr int min_exponent10 = std::numeric_limits::min_exponent10; + static constexpr int max_exponent = std::numeric_limits::max_exponent; + static constexpr int max_exponent10 = std::numeric_limits::max_exponent10; + static constexpr bool traps = std::numeric_limits::traps; + static constexpr bool tinyness_before = std::numeric_limits::tinyness_before; + + static NumericID min() noexcept { return NumericID(numeric_limits::min()); } + static NumericID lowest() noexcept { return NumericID(numeric_limits::lowest()); } + static NumericID max() noexcept { return NumericID(numeric_limits::max()); } + static NumericID epsilon() noexcept { return NumericID(numeric_limits::epsilon()); } + static NumericID round_error() noexcept { return NumericID(numeric_limits::round_error()); } + static NumericID infinity() noexcept { return NumericID(numeric_limits::infinity()); } + static NumericID quiet_NaN() noexcept { return NumericID(numeric_limits::quiet_NaN()); } + static NumericID signaling_NaN() noexcept { return NumericID(numeric_limits::signaling_NaN()); } + static NumericID denorm_min() noexcept { return NumericID(numeric_limits::denorm_min()); } + }; +} diff --git a/common/source/common/app/AbstractApp.cpp b/common/source/common/app/AbstractApp.cpp new file mode 100644 index 0000000..6db2287 --- /dev/null +++ b/common/source/common/app/AbstractApp.cpp @@ -0,0 +1,201 @@ +#include +#include +#include "AbstractApp.h" + +bool AbstractApp::didRunTimeInit = false; + +/** + * Note: Will do nothing if pidFile string is empty. + * + * @pidFile will not be accepted if path is not absolute (=> throws exception). + * @return LockFD for locked pid file or invalid if pidFile string was empty. + */ +LockFD AbstractApp::createAndLockPIDFile(const std::string& pidFile) +{ + if (pidFile.empty()) + return {}; + + // PID file defined + + Path pidPath(pidFile); + if(!pidPath.absolute() ) /* (check to avoid problems after chdir) */ + throw InvalidConfigException("Path to PID file must be absolute: " + pidFile); + + auto pidFileLockFD = StorageTk::createAndLockPIDFile(pidFile, true); + if (!pidFileLockFD.valid()) + throw InvalidConfigException("Unable to create/lock PID file: " + pidFile); + + return pidFileLockFD; +} + +/** + * Updates a locked pid file, which is typically required after calling daemon(). + * + * Note: Will do nothing if pidfileFD is invalid. + */ +void AbstractApp::updateLockedPIDFile(LockFD& pidFileFD) +{ + if (!pidFileFD.valid()) + return; + + if (auto err = pidFileFD.updateWithPID()) + throw InvalidConfigException("Unable to update PID file: " + err.message()); +} + + +/** + * @param component the thread that we're waiting for via join(); may be NULL (in which case this + * method returns immediately) + */ +void AbstractApp::waitForComponentTermination(PThread* component) +{ + const char* logContext = "App (wait for component termination)"; + Logger* logger = Logger::getLogger(); + + const int timeoutMS = 2000; + + if(!component) + return; + + bool isTerminated = component->timedjoin(timeoutMS); + if(!isTerminated) + { // print message to show user which thread is blocking + if(logger) + logger->log(Log_WARNING, logContext, + "Still waiting for this component to stop: " + component->getName() ); + + component->join(); + + if(logger) + logger->log(Log_WARNING, logContext, "Component stopped: " + component->getName() ); + } +} + +/** + * Handle errors from new operator. + * + * This method is intended by the C++ standard to try to free up some memory after allocation + * failed, but our options here are very limited, because we don't know which locks are being held + * when this is called and how much memory the caller actually requested. + * + * @return this function should only return if it freed up memory (otherwise it might be called + * infinitely); as we don't free up any memory here, we exit by throwing a std::bad_alloc + * exeception. + * @throw std::bad_alloc always. + */ +void AbstractApp::handleOutOfMemFromNew() +{ + int errCode = errno; + + std::cerr << "Failed to allocate memory via \"new\" operator. " + "Will try to log a backtrace and then throw a bad_alloc exception..." << std::endl; + std::cerr << "Last errno value: " << errno << std::endl; + + LogContext log(__func__); + log.logErr("Failed to allocate memory via \"new\" operator. " + "Will try to log a backtrace and then throw a bad_alloc exception..."); + + log.logErr("Last errno value: " + System::getErrString(errCode) ); + + log.logBacktrace(); + + throw std::bad_alloc(); +} + +bool AbstractApp::performBasicInitialRunTimeChecks() +{ + bool timeTest = Time::testClock(); + if (!timeTest) + return false; + + bool conditionTimeTest = Condition::testClockID(); + return conditionTimeTest; +} + +bool AbstractApp::basicInitializations() +{ + bool condAttrRes = Condition::initStaticCondAttr(); + return condAttrRes; +} + +bool AbstractApp::basicDestructions() +{ + bool condAttrRes = Condition::destroyStaticCondAttr(); + return condAttrRes; +} + +void AbstractApp::logUsableNICs(LogContext* log, NicAddressList& nicList) +{ + // list usable network interfaces + std::string nicListStr; + std::string extendedNicListStr; + + for(NicAddressListIter nicIter = nicList.begin(); nicIter != nicList.end(); nicIter++) + { + std::string nicTypeStr; + + if (nicIter->nicType == NICADDRTYPE_RDMA) + nicTypeStr = "RDMA"; + else if (nicIter->nicType == NICADDRTYPE_STANDARD) + nicTypeStr = "TCP"; + else + nicTypeStr = "Unknown"; + + nicListStr += std::string(nicIter->name) + "(" + nicTypeStr + ")" + " "; + + extendedNicListStr += "\n+ "; + extendedNicListStr += NetworkInterfaceCard::nicAddrToString(&(*nicIter) ) + " "; + } + + nicListStr = std::string("Usable NICs: ") + nicListStr; + extendedNicListStr = std::string("Extended list of usable NICs: ") + extendedNicListStr; + + if (log) + { + log->log(Log_WARNING, nicListStr); + log->log(Log_DEBUG, extendedNicListStr); + } + else + { + LOG(GENERAL, WARNING, nicListStr); + LOG(GENERAL, DEBUG, extendedNicListStr); + } +} + +void AbstractApp::updateLocalNicListAndRoutes(LogContext* log, NicAddressList& localNicList, + std::vector& nodeStores) +{ + bool needRoutes = getCommonConfig()->getConnRestrictOutboundInterfaces(); + + if (needRoutes) + updateRoutingTable(); + + std::unique_lock lock(localNicListMutex); + this->localNicList = localNicList; + lock.unlock(); + + logUsableNICs(log, localNicList); + NicListCapabilities localNicCaps; + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + + for (auto& l : nodeStores) + { + NodesTk::applyLocalNicListToList(localNicList, localNicCaps, + l->referenceAllNodes()); + } +} + +bool AbstractApp::initNoDefaultRouteList(NetVector* outNets) +{ + std::string connNoDefaultRoute = getCommonConfig()->getConnNoDefaultRoute(); + StringVector cidrs; + StringTk::explodeEx(connNoDefaultRoute, ',', true, &cidrs); + for (auto& c : cidrs) + { + IPv4Network net; + if (!IPv4Network::parse(c, net)) + return false; + outNets->push_back(net); + } + return true; +} diff --git a/common/source/common/app/AbstractApp.h b/common/source/common/app/AbstractApp.h new file mode 100644 index 0000000..f5fdcc6 --- /dev/null +++ b/common/source/common/app/AbstractApp.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +class StreamListenerV2; // forward declaration +class LogContext; +class AbstractNodeStore; + +class AbstractApp : public PThread +{ + public: + virtual void stopComponents() = 0; + virtual void handleComponentException(std::exception& e) = 0; + virtual void handleNetworkInterfaceFailure(const std::string& devname) = 0; + + LockFD createAndLockPIDFile(const std::string& pidFile); + void updateLockedPIDFile(LockFD& pidFileFD); + + void waitForComponentTermination(PThread* component); + + void logUsableNICs(LogContext* log, NicAddressList& nicList); + + static void handleOutOfMemFromNew(); + + virtual const ICommonConfig* getCommonConfig() const = 0; + virtual const NetFilter* getNetFilter() const = 0; + virtual const NetFilter* getTcpOnlyFilter() const = 0; + virtual const AbstractNetMessageFactory* getNetMessageFactory() const = 0; + + + static bool didRunTimeInit; + + + /** + * To be overridden by Apps that support multiple stream listeners. + */ + virtual StreamListenerV2* getStreamListenerByFD(int fd) + { + return NULL; + } + + NicAddressList getLocalNicList() + { + const std::lock_guard lock(localNicListMutex); + return localNicList; + } + + protected: + NicAddressList localNicList; // intersection set of dicsovered NICs and allowedInterfaces + Mutex localNicListMutex; + std::shared_ptr noDefaultRouteNets; + + AbstractApp() : PThread("Main", this), + noDefaultRouteNets(nullptr) + { + if (!didRunTimeInit) + { + std::cerr << "Bug: Runtime variables have not been initialized" << std::endl; + throw InvalidConfigException("Bug: Runtime variables have not been initialized"); + } + + std::set_new_handler(handleOutOfMemFromNew); + } + + virtual ~AbstractApp() + { + basicDestructions(); + } + + void updateLocalNicListAndRoutes(LogContext* log, NicAddressList& nicList, + std::vector& nodeStores); + + bool initNoDefaultRouteList(NetVector* outNets); + + private: + static bool basicInitializations(); + bool basicDestructions(); + static bool performBasicInitialRunTimeChecks(); + RoutingTableFactory routingTableFactory; + + // inliners + + public: + + /** + * Note: This MUST be called before creating a new App + */ + static bool runTimeInitsAndChecks() + { + bool runTimeChecks = performBasicInitialRunTimeChecks(); + if (!runTimeChecks) + return false; + + bool initRes = basicInitializations(); + if (!initRes) + return false; + + didRunTimeInit = true; + + return true; + } + + void initRoutingTable() + { + routingTableFactory.init(); + } + + bool updateRoutingTable() + { + return routingTableFactory.load(); + } + + RoutingTable getRoutingTable() + { + return routingTableFactory.create(noDefaultRouteNets); + } + +}; + diff --git a/common/source/common/app/config/AbstractConfig.cpp b/common/source/common/app/config/AbstractConfig.cpp new file mode 100644 index 0000000..8de8209 --- /dev/null +++ b/common/source/common/app/config/AbstractConfig.cpp @@ -0,0 +1,514 @@ +#include +#include +#include +#include +#include "AbstractConfig.h" +#include "common/toolkit/HashTk.h" + + +#define ABSTRACTCONF_AUTHFILE_READSIZE 1024 // max amount of data that we read from auth file +#define ABSTRACTCONF_AUTHFILE_MINSIZE 4 // at least 2, because we compute two 32bit hashes + + +AbstractConfig::AbstractConfig(int argc, char** argv) : + argc(argc), argv(argv) +{ + // initConfig(..) must be called by derived classes + // because of the virtual init method loadDefaults() +} + +/** + * Note: This must not be called from the AbstractConfig (or base class) constructor, because it + * calls virtual methods of the sub classes. + * + * @param enableException: true to throw an exception when an unknown config element is found, + * false to ignore it. + * @param addDashes true to prepend dashes to defaults and config file keys (used e.g. by fhgfs-ctl) + */ +void AbstractConfig::initConfig(int argc, char** argv, bool enableException, bool addDashes) +{ + // load and apply args to see whether we have a cfgFile + loadDefaults(addDashes); + loadFromArgs(argc, argv); + + applyConfigMap(enableException, addDashes); + + if(this->cfgFile.length() ) + { // there is a config file specified + // start over again and include the config file this time + this->configMap.clear(); + + const std::string tmpCfgFile = this->cfgFile; /* need tmp variable here to make sure we don't + override the value when we use call loadDefaults() again below, because subclasses have + direct access to this->cfgFile */ + + loadDefaults(addDashes); + loadFromFile(tmpCfgFile.c_str(), addDashes); + loadFromArgs(argc, argv); + + applyConfigMap(enableException, addDashes); + } + + initImplicitVals(); +} + +/** + * Sets the default values for each configurable in the configMap. + * + * @param addDashes true to prepend "--" to all config keys. + */ +void AbstractConfig::loadDefaults(bool addDashes) +{ + configMapRedefine("cfgFile", "", addDashes); + + configMapRedefine("logType", "logfile", addDashes); + configMapRedefine("logLevel", "3", addDashes); + configMapRedefine("logNoDate", "true", addDashes); + configMapRedefine("logStdFile", "", addDashes); + configMapRedefine("logNumLines", "50000", addDashes); + configMapRedefine("logNumRotatedFiles", "2", addDashes); + + configMapRedefine("connPortShift", "0", addDashes); + + // To be able to merge these with the legacy settings later, we set them to -1 here. Otherwise it + // is impossible to detect if they have actually been set or just loaded the default. + // The actual default values are applied during the post processing in applyConfigMap. + configMapRedefine("connClientPort", "-1", addDashes); // 8004 + configMapRedefine("connStoragePort", "-1", addDashes); // 8003 + configMapRedefine("connMetaPort", "-1", addDashes); // 8005 + configMapRedefine("connMonPort", "-1", addDashes); // 8007 + configMapRedefine("connMgmtdPort", "-1", addDashes); // 8008 + + configMapRedefine("connUseRDMA", "true", addDashes); + configMapRedefine("connBacklogTCP", "64", addDashes); + configMapRedefine("connMaxInternodeNum", "6", addDashes); + configMapRedefine("connFallbackExpirationSecs", "900", addDashes); + configMapRedefine("connTCPRcvBufSize", "0", addDashes); + configMapRedefine("connUDPRcvBufSize", "0", addDashes); + configMapRedefine("connRDMABufSize", "8192", addDashes); + configMapRedefine("connRDMABufNum", "70", addDashes); + configMapRedefine("connRDMATypeOfService", "0", addDashes); + configMapRedefine("connNetFilterFile", "", addDashes); + configMapRedefine("connAuthFile", "", addDashes); + configMapRedefine("connDisableAuthentication", "false", addDashes); + configMapRedefine("connTcpOnlyFilterFile", "", addDashes); + configMapRedefine("connRestrictOutboundInterfaces", "false", addDashes); + configMapRedefine("connNoDefaultRoute", "0.0.0.0/0", addDashes); + + /* connMessagingTimeouts: default to zero, indicating that constants + * specified in Common.h are used. + */ + configMapRedefine("connMessagingTimeouts", "0,0,0", addDashes); + // connRDMATimeouts: zero values are interpreted as the defaults specified in IBVSocket.cpp + configMapRedefine("connRDMATimeouts", "0,0,0", addDashes); + + configMapRedefine("sysMgmtdHost", "", addDashes); + configMapRedefine("sysUpdateTargetStatesSecs", "0", addDashes); +} + +/** + * @param enableException: true to throw an exception when an unknown config element is found, + * false to ignore it. + * @param addDashes true to prepend "--" to tested config keys for matching. + */ +void AbstractConfig::applyConfigMap(bool enableException, bool addDashes) +{ + // Deprecated separate port settings. These are post processed below and merged into the new + // combined settings. + int connClientPortUDP = -1; + int connMgmtdPortUDP = -1; + int connMetaPortUDP = -1; + int connStoragePortUDP = -1; + int connMonPortUDP = -1; + int connClientPortTCP = -1; + int connMgmtdPortTCP = -1; + int connMetaPortTCP = -1; + int connStoragePortTCP = -1; + int connMonPortTCP = -1; + + for (StringMapIter iter = configMap.begin(); iter != configMap.end();) + { + bool unknownElement = false; + + if (testConfigMapKeyMatch(iter, "cfgFile", addDashes)) + cfgFile = iter->second; + else if (testConfigMapKeyMatch(iter, "logLevel", addDashes)) + logLevel = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "logNoDate", addDashes)) + logNoDate = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "logStdFile", addDashes)) + logStdFile = iter->second; + else if (testConfigMapKeyMatch(iter, "logNumLines", addDashes)) + logNumLines = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "logNumRotatedFiles", addDashes)) + logNumRotatedFiles = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connPortShift", addDashes)) + connPortShift = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connClientPort", addDashes)) + assignKeyIfNotZero(iter, connClientPort, enableException); + else if (testConfigMapKeyMatch(iter, "connStoragePort", addDashes)) + assignKeyIfNotZero(iter, connStoragePort, enableException); + else if (testConfigMapKeyMatch(iter, "connMetaPort", addDashes)) + assignKeyIfNotZero(iter, connMetaPort, enableException); + else if (testConfigMapKeyMatch(iter, "connMonPort", addDashes)) + assignKeyIfNotZero(iter, connMonPort, enableException); + else if (testConfigMapKeyMatch(iter, "connMgmtdPort", addDashes)) + assignKeyIfNotZero(iter, connMgmtdPort, enableException); + else if (testConfigMapKeyMatch(iter, "connClientPortUDP", addDashes)) + assignKeyIfNotZero(iter, connClientPortUDP, enableException); + else if (testConfigMapKeyMatch(iter, "connStoragePortUDP", addDashes)) + assignKeyIfNotZero(iter, connStoragePortUDP, enableException); + else if (testConfigMapKeyMatch(iter, "connMetaPortUDP", addDashes)) + assignKeyIfNotZero(iter, connMetaPortUDP, enableException); + else if (testConfigMapKeyMatch(iter, "connMonPortUDP", addDashes)) + assignKeyIfNotZero(iter, connMonPortUDP, enableException); + else if (testConfigMapKeyMatch(iter, "connMgmtdPortUDP", addDashes)) + assignKeyIfNotZero(iter, connMgmtdPortUDP, enableException); + else if (testConfigMapKeyMatch(iter, "connStoragePortTCP", addDashes)) + assignKeyIfNotZero(iter, connStoragePortTCP, enableException); + else if (testConfigMapKeyMatch(iter, "connMetaPortTCP", addDashes)) + assignKeyIfNotZero(iter, connMetaPortTCP, enableException); + else if (testConfigMapKeyMatch(iter, "connMgmtdPortTCP", addDashes)) + assignKeyIfNotZero(iter, connMgmtdPortTCP, enableException); + else if (testConfigMapKeyMatch(iter, "connUseRDMA", addDashes)) + connUseRDMA = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "connBacklogTCP", addDashes)) + connBacklogTCP = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connMaxInternodeNum", addDashes)) + connMaxInternodeNum = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connNonPrimaryExpiration", addDashes)) + { + // superseded by connFallbackExpirationSecs, ignored here for config file compatibility + } + else if (testConfigMapKeyMatch(iter, "connFallbackExpirationSecs", addDashes)) + connFallbackExpirationSecs = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connTCPRcvBufSize", addDashes)) + connTCPRcvBufSize = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connUDPRcvBufSize", addDashes)) + connUDPRcvBufSize = StringTk::strToInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connRDMABufSize", addDashes)) + connRDMABufSize = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connRDMABufNum", addDashes)) + connRDMABufNum = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connRDMATypeOfService", addDashes)) + connRDMATypeOfService = (uint8_t)StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "connNetFilterFile", addDashes)) + connNetFilterFile = iter->second; + else if (testConfigMapKeyMatch(iter, "connAuthFile", addDashes)) + connAuthFile = iter->second; + else if (testConfigMapKeyMatch(iter, "connDisableAuthentication", addDashes)) + connDisableAuthentication = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "connTcpOnlyFilterFile", addDashes)) + connTcpOnlyFilterFile = iter->second; + else if (testConfigMapKeyMatch(iter, "connRestrictOutboundInterfaces", addDashes)) + connRestrictOutboundInterfaces = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "connNoDefaultRoute", addDashes)) + connNoDefaultRoute = iter->second; + else if (testConfigMapKeyMatch(iter, "connMessagingTimeouts", addDashes)) + { + const size_t cfgValCount = 3; // count value in config file in order of long, medium and short + std::list split; + StringList::iterator timeoutIter; + + StringTk::explode(iter->second, ',', &split); + + if (split.size() == cfgValCount) + { + timeoutIter = split.begin(); + connMsgLongTimeout = StringTk::strToInt(*timeoutIter) > 0 ? + StringTk::strToInt(*timeoutIter) : CONN_LONG_TIMEOUT; + timeoutIter++; + + connMsgMediumTimeout = StringTk::strToInt(*timeoutIter) > 0 ? + StringTk::strToInt(*timeoutIter) : CONN_MEDIUM_TIMEOUT; + timeoutIter++; + + connMsgShortTimeout = StringTk::strToInt(*timeoutIter) > 0 ? + StringTk::strToInt(*timeoutIter) : CONN_SHORT_TIMEOUT; + } + else + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + else if (testConfigMapKeyMatch(iter, "connRDMATimeouts", addDashes)) + { + const size_t cfgValCount = 3; + const size_t cfgUtilValCount = 5; + std::list split; + + StringTk::explode(iter->second, ',', &split); + // YUCK. beegfs-ctl and beegfs-fsck use beegfs-client.conf. That file defines + // connRDMATimeouts as 5 comma-separated values, but user space only has 3 + // values. The utils are supposed to ignore the configuration. cfgUtilValCount + // is a hack to prevent the configuration from causing a runtime error. + if (split.size() != cfgUtilValCount) + { + if (split.size() == cfgValCount) + { + StringList::iterator timeoutIter = split.begin(); + connRDMATimeoutConnect = StringTk::strToInt(*timeoutIter++); + connRDMATimeoutFlowSend = StringTk::strToInt(*timeoutIter++); + connRDMATimeoutPoll = StringTk::strToInt(*timeoutIter); + } + else + { + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + } + } + else if (testConfigMapKeyMatch(iter, "sysMgmtdHost", addDashes)) + sysMgmtdHost = iter->second; + else if (testConfigMapKeyMatch(iter, "sysUpdateTargetStatesSecs", addDashes)) + sysUpdateTargetStatesSecs = StringTk::strToUInt(iter->second); + else + { + // unknown element occurred + unknownElement = true; + + if (enableException) + { + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + } + + if (unknownElement) + { + // just skip the unknown element + iter++; + } + else + { + // remove this element from the map + iter = eraseFromConfigMap(iter); + } + } + + auto processPortSettings = [&](const std::string& name, int& setting, const int& tcp, const int& udp, const int def) { + if(setting == -1) { + if(tcp != -1 && udp != -1 && tcp != udp) { + throw InvalidConfigException("Deprecated config arguments '" + name + "UDP' and \ +'" + name + "TCP' set to different values, which is no longer allowed. Please use the new '" + name + "' \ +setting instead."); + } + + // Set the new setting using the old values + if(tcp != -1) { + setting = tcp; + // TODO Doesn't work as the logger isn't initialized yet: https://github.com/ThinkParQ/beegfs-core/issues/4033 + LOG(GENERAL, WARNING, "Using deprecated config argument '" + name + "TCP'"); + } else if(udp != -1) { + setting = udp; + // TODO Doesn't work as the logger isn't initialized yet: https://github.com/ThinkParQ/beegfs-core/issues/4033 + LOG(GENERAL, WARNING, "Using deprecated config argument '" + name + "UDP'"); + } else { + setting = def; + } + } else { + if(tcp != -1 || udp != -1) { + throw InvalidConfigException("Deprecated config arguments '" + name + "UDP/TCP' set along with the new \ +'" + name + "' setting. Please use only the new setting."); + } + } + }; + + processPortSettings("connClientPort", this->connClientPort, connClientPortTCP, connClientPortUDP, 8004); + processPortSettings("connStoragePort", this->connStoragePort, connStoragePortTCP, connStoragePortUDP, 8003); + processPortSettings("connMetaPort", this->connMetaPort, connMetaPortTCP, connMetaPortUDP, 8005); + processPortSettings("connMonPort", this->connMonPort, connMonPortTCP, connMonPortUDP, 8007); + processPortSettings("connMgmtdPort", this->connMgmtdPort, connMgmtdPortTCP, connMgmtdPortUDP, 8008); +} + +void AbstractConfig::initImplicitVals() +{ + // nothing to be done here (just a dummy so that derived classes don't have to impl this) +} + +/** + * Initialize interfaces list from interfaces file if the list is currently empty and the filename + * is not empty. + * + * @param inoutConnInterfacesList will be initialized from file (comma-separated) if it was empty + * + * @throw InvalidConfigException if interfaces filename and interface list are both not empty + */ +void AbstractConfig::initInterfacesList(const std::string& connInterfacesFile, + std::string& inoutConnInterfacesList) +{ + // make sure not both (list and file) were specified + if(!inoutConnInterfacesList.empty() && !connInterfacesFile.empty() ) + { + throw InvalidConfigException( + "connInterfacesFile and connInterfacesList cannot be used together"); + } + + if(!inoutConnInterfacesList.empty() ) + return; // interfaces already given as list => nothing to do + + if(connInterfacesFile.empty() ) + return; // no interfaces file given => nothing to do + + + // load interfaces from file... + + StringList loadedInterfacesList; + + loadStringListFile(connInterfacesFile.c_str(), loadedInterfacesList); + + inoutConnInterfacesList = StringTk::implode(',', loadedInterfacesList, true); +} + +/** + * Generate connection authentication hash based on contents of given authentication file. + * + * @param outConnAuthHash will be set to 0 if file is not defined + * + * @throw InvalidConfigException if connAuthFile is defined, but cannot be read. + */ +void AbstractConfig::initConnAuthHash(const std::string& connAuthFile, uint64_t* outConnAuthHash) +{ + if (connDisableAuthentication) { + *outConnAuthHash = 0; + return; // connAuthFile explicitly disabled => no hash to be generated + } + + // Connection authentication not explicitly disabled, so bail if connAuthFile is not configured + if(connAuthFile.empty()) + throw ConnAuthFileException("No connAuthFile configured. Using BeeGFS without connection authentication is considered insecure and is not recommended. If you really want or need to run BeeGFS without connection authentication, please set connDisableAuthentication to true."); + + // open file... + + /* note: we don't reuse something like loadStringListFile() here, because: + 1) the auth file might not contain a string, but can be any binary data (including zeros). + 2) we want to react on EACCES. */ + + int fd = open(connAuthFile.c_str(), O_RDONLY); + + int errCode = errno; + + if( (fd == -1) && (errCode == EACCES) ) + { // current effective user/group ID not allowed to read file => try it with saved IDs + unsigned previousUID; + unsigned previousGID; + + // switch to saved IDs + System::elevateUserAndGroupFsID(&previousUID, &previousGID); + + fd = open(connAuthFile.c_str(), O_RDONLY); + + errCode = errno; // because ID dropping might change errno + + // restore previous IDs + System::setFsIDs(previousUID, previousGID, &previousUID, &previousGID); + } + + if(fd == -1) + { + throw ConnAuthFileException("Unable to open auth file: " + connAuthFile + " " + "(SysErr: " + System::getErrString(errCode) + ")"); + } + + // load file contents... + + unsigned char buf[ABSTRACTCONF_AUTHFILE_READSIZE]; + + const ssize_t readRes = read(fd, buf, ABSTRACTCONF_AUTHFILE_READSIZE); + + errCode = errno; // because close() might change errno + + close(fd); + + if(readRes < 0) + { + throw InvalidConfigException("Unable to read auth file: " + connAuthFile + " " + "(SysErr: " + System::getErrString(errCode) + ")"); + } + + // empty authFile is probably unintended, so treat it as error + if(!readRes || (readRes < ABSTRACTCONF_AUTHFILE_MINSIZE) ) + throw InvalidConfigException("Auth file is empty: " + connAuthFile); + + + // hash file contents + *outConnAuthHash = HashTk::authHash(buf, readRes); +} + +/** + * Sets the value of connTCPRcvBufSize and connUDPRcvBufSize according to the configuration. + * 0 indicates legacy behavior that uses RDMA bufsizes. Otherwise leave the values as + * configured. + */ +void AbstractConfig::initSocketBufferSizes() +{ + int legacy = connRDMABufSize * connRDMABufNum; + + if (connTCPRcvBufSize == 0) + connTCPRcvBufSize = legacy; + + if (connUDPRcvBufSize == 0) + connUDPRcvBufSize = legacy; +} + +/** + * Removes an element from the config map and returns an iterator that is positioned right + * after the removed element + */ +StringMapIter AbstractConfig::eraseFromConfigMap(StringMapIter iter) +{ + StringMapIter nextIter(iter); + + nextIter++; // save position after the erase element + + configMap.erase(iter); + + return nextIter; +} + +/** + * @param addDashes true to prepend "--" to every config key. + */ +void AbstractConfig::loadFromFile(const char* filename, bool addDashes) +{ + if(!addDashes) + { // no dashes needed => load directly into configMap + MapTk::loadStringMapFromFile(filename, &configMap); + return; + } + + /* we need to add dashes to keys => use temporary map with real keys and then copy to actual + config map with prepended dashes. */ + + StringMap tmpMap; + + MapTk::loadStringMapFromFile(filename, &tmpMap); + + for(StringMapCIter iter = tmpMap.begin(); iter != tmpMap.end(); iter++) + configMapRedefine(iter->first, iter->second, addDashes); +} + +/** + * Note: No addDashes param here, because the user should specify dashes himself on the command line + * if they are needed. + */ +void AbstractConfig::loadFromArgs(int argc, char** argv) +{ + for(int i=1; i < argc; i++) + MapTk::addLineToStringMap(argv[i], &configMap); +} + + +/** + * @warning It might exit the proccess if it finds an incorrect value + */ +void AbstractConfig::assignKeyIfNotZero(const StringMapIter& it, int& intVal, bool enableException) +{ + const int tempVal = StringTk::strToInt(it->second); + + if (tempVal == 0) { + if (enableException) { + throw InvalidConfigException("Invalid or unset configuration variable: " + (it->first)); + } + return; + } + + intVal = tempVal; +} + diff --git a/common/source/common/app/config/AbstractConfig.h b/common/source/common/app/config/AbstractConfig.h new file mode 100644 index 0000000..d42252b --- /dev/null +++ b/common/source/common/app/config/AbstractConfig.h @@ -0,0 +1,107 @@ +#pragma once + + +#include +#include +#include "InvalidConfigException.h" +#include "ConnAuthFileException.h" +#include "ICommonConfig.h" + + + +class AbstractConfig : public ICommonConfig +{ + public: + virtual ~AbstractConfig() {} + + protected: + // internals + + StringMap configMap; + int argc; + char** argv; + + void addLineToConfigMap(std::string line); + void loadFromArgs(int argc, char** argv); + + // configurables + + std::string cfgFile; + + AbstractConfig(int argc, char** argv); + + void initConfig(int argc, char** argv, bool enableException, bool addDashes=false); + StringMapIter eraseFromConfigMap(StringMapIter iter); + + virtual void loadDefaults(bool addDashes=false); + void loadFromFile(const char* filename, bool addDashes=false); + virtual void applyConfigMap(bool enableException, bool addDashes=false); + virtual void initImplicitVals(); + + void initInterfacesList(const std::string& connInterfacesFile, + std::string& inoutConnInterfacesList); + void initConnAuthHash(const std::string& connAuthFile, uint64_t* outConnAuthHash); + void initSocketBufferSizes(); + + // inliners + + /** + * @param addDashes true to prepend "--" to the keyStr. + */ + void configMapRedefine(std::string keyStr, std::string valueStr, bool addDashes=false) + { + std::string keyStrInternal; + + if(addDashes) + keyStrInternal = "--" + keyStr; + else + keyStrInternal = keyStr; + + MapTk::stringMapRedefine(keyStrInternal, valueStr, &configMap); + } + + /** + * Note: Read the addDashesToTestKey comment on case-sensitivity. + * + * @param addDashesToTestKey true to prepend "--" to testKey before testing for match; if this + * is specified, the check will also be case-insensitive (otherwise it is case-sensitive). + * @return true if iter->first equals testKey. + */ + bool testConfigMapKeyMatch(const StringMapCIter& iter, const std::string& testKey, + bool addDashesToTestKey) const + { + if(addDashesToTestKey) + { + std::string testKeyDashed = "--" + testKey; + return (!strcasecmp(iter->first.c_str(), testKeyDashed.c_str() ) ); + } + else + return (iter->first == testKey); + } + + // getters & setters + const StringMap* getConfigMap() const + { + return &configMap; + } + + int getArgc() const + { + return argc; + } + + char** getArgv() const + { + return argv; + } + + static void assignKeyIfNotZero(const StringMapIter&, int& intVal, bool enableException = true); + + public: + std::string getCfgFile() const + { + return cfgFile; + } + +}; + diff --git a/common/source/common/app/config/ConnAuthFileException.h b/common/source/common/app/config/ConnAuthFileException.h new file mode 100644 index 0000000..f1969c7 --- /dev/null +++ b/common/source/common/app/config/ConnAuthFileException.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +DECLARE_NAMEDEXCEPTION(ConnAuthFileException, "ConnAuthFileException") + + diff --git a/common/source/common/app/config/ICommonConfig.cpp b/common/source/common/app/config/ICommonConfig.cpp new file mode 100644 index 0000000..fa60317 --- /dev/null +++ b/common/source/common/app/config/ICommonConfig.cpp @@ -0,0 +1,36 @@ +#include +#include +#include "ICommonConfig.h" + +/** + * Loads a file into a string list (line by line). + * + * Loaded strings are trimmed before they are added to the list. Empty lines and lines starting + * with STORAGETK_FILE_COMMENT_CHAR are not added to the list. + */ +void ICommonConfig::loadStringListFile(const char* filename, StringList& outList) +{ + std::ifstream fis(filename); + if(!fis.is_open() || fis.fail() ) + { + throw InvalidConfigException( + std::string("Failed to open file: ") + filename); + } + + while(!fis.eof() && !fis.fail() ) + { + std::string line; + + std::getline(fis, line); + std::string trimmedLine = StringTk::trim(line); + if(trimmedLine.length() && (trimmedLine[0] != STORAGETK_FILE_COMMENT_CHAR) ) + outList.push_back(trimmedLine); + } + + fis.close(); +} + + + + + diff --git a/common/source/common/app/config/ICommonConfig.h b/common/source/common/app/config/ICommonConfig.h new file mode 100644 index 0000000..eddad80 --- /dev/null +++ b/common/source/common/app/config/ICommonConfig.h @@ -0,0 +1,247 @@ +#pragma once + +#include +#include "InvalidConfigException.h" + +class ICommonConfig +{ + public: + virtual ~ICommonConfig() {} + + static void loadStringListFile(const char* filename, StringList& outList); + + protected: + ICommonConfig() {} + + LogType logType; + int logLevel; + bool logNoDate; + std::string logStdFile; + unsigned logNumLines; + unsigned logNumRotatedFiles; + + int connPortShift; // shifts all UDP and TCP ports + int connClientPort; + int connStoragePort; + int connMetaPort; + int connMonPort; + int connMgmtdPort; + bool connUseRDMA; + unsigned connBacklogTCP; + unsigned connMaxInternodeNum; + unsigned connFallbackExpirationSecs; + int connTCPRcvBufSize; + int connUDPRcvBufSize; + unsigned connRDMABufSize; + unsigned connRDMABufNum; + uint8_t connRDMATypeOfService; + std::string connNetFilterFile; // for allowed IPs (empty means "allow all") + std::string connAuthFile; + bool connDisableAuthentication; + uint64_t connAuthHash; // implicitly set based on hash of connAuthFile contents + std::string connTcpOnlyFilterFile; // for IPs that only allow plain TCP (no RDMA etc) + bool connRestrictOutboundInterfaces; + std::string connNoDefaultRoute; + + int connMsgLongTimeout; + int connMsgMediumTimeout; + int connMsgShortTimeout; // connection (response) timeouts in ms + // note: be careful here, because servers not + // responding for >30secs under high load is nothing + // unusual, so never use connMsgShortTimeout for + // IO-related operations. + int connRDMATimeoutConnect; + int connRDMATimeoutFlowSend; + int connRDMATimeoutPoll; + + std::string sysMgmtdHost; + unsigned sysUpdateTargetStatesSecs; + + int connectionRejectionRate; + int connectionRejectionCount; + + + public: + // getters & setters + LogType getLogType() const + { + return logType; + } + + int getLogLevel() const + { + return logLevel; + } + + bool getLogNoDate() const + { + return logNoDate; + } + + const std::string& getLogStdFile() const + { + return logStdFile; + } + + unsigned getLogNumLines() const + { + return logNumLines; + } + + unsigned getLogNumRotatedFiles() const + { + return logNumRotatedFiles; + } + + int getConnClientPort() const + { + return connClientPort ? (connClientPort + connPortShift) : 0; + } + + int getConnStoragePort() const + { + return connStoragePort ? (connStoragePort + connPortShift) : 0; + } + + int getConnMetaPort() const + { + return connMetaPort ? (connMetaPort + connPortShift) : 0; + } + + int getConnMonPort() const + { + return connMonPort ? (connMonPort + connPortShift) : 0; + } + + int getConnMgmtdPort() const + { + return connMgmtdPort ? (connMgmtdPort + connPortShift) : 0; + } + + bool getConnUseRDMA() const + { + return connUseRDMA; + } + + unsigned getConnBacklogTCP() const + { + return connBacklogTCP; + } + + unsigned getConnMaxInternodeNum() const + { + return connMaxInternodeNum; + } + + int getConnFallbackExpirationSecs() const + { + return connFallbackExpirationSecs; + } + + int getConnTCPRcvBufSize() const + { + return connTCPRcvBufSize; + } + + int getConnUDPRcvBufSize() const + { + return connUDPRcvBufSize; + } + + unsigned getConnRDMABufSize() const + { + return connRDMABufSize; + } + + unsigned getConnRDMABufNum() const + { + return connRDMABufNum; + } + + uint8_t getConnRDMATypeOfService() const + { + return connRDMATypeOfService; + } + + const std::string& getConnNetFilterFile() const + { + return connNetFilterFile; + } + + const std::string& getConnAuthFile() const + { + return connAuthFile; + } + + uint64_t getConnAuthHash() const + { + return connAuthHash; + } + + const std::string& getConnTcpOnlyFilterFile() const + { + return connTcpOnlyFilterFile; + } + + bool getConnRestrictOutboundInterfaces() const + { + return connRestrictOutboundInterfaces; + } + + std::string getConnNoDefaultRoute() const + { + return connNoDefaultRoute; + } + + int getConnMsgLongTimeout() const + { + return connMsgLongTimeout; + } + + int getConnMsgMediumTimeout() const + { + return connMsgMediumTimeout; + } + + int getConnMsgShortTimeout() const + { + return connMsgShortTimeout; + } + + int getConnRDMATimeoutConnect() const + { + return connRDMATimeoutConnect; + } + + int getConnRDMATimeoutFlowSend() const + { + return connRDMATimeoutFlowSend; + } + + int getConnRDMATimeoutPoll() const + { + return connRDMATimeoutPoll; + } + + const std::string& getSysMgmtdHost() const + { + return sysMgmtdHost; + } + + unsigned getSysUpdateTargetStatesSecs() const + { + return sysUpdateTargetStatesSecs; + } + + unsigned getConnectionRejectionRate() const + { + return connectionRejectionRate; + } + + void setConnectionRejectionRate(unsigned rate) + { + connectionRejectionRate = rate; + connectionRejectionCount = 0; + } +}; + diff --git a/common/source/common/app/config/InvalidConfigException.h b/common/source/common/app/config/InvalidConfigException.h new file mode 100644 index 0000000..d496fe9 --- /dev/null +++ b/common/source/common/app/config/InvalidConfigException.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +DECLARE_NAMEDEXCEPTION(InvalidConfigException, "InvalidConfigException") + + diff --git a/common/source/common/app/log/LogContext.h b/common/source/common/app/log/LogContext.h new file mode 100644 index 0000000..28c0ab2 --- /dev/null +++ b/common/source/common/app/log/LogContext.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include + +#include + + +#define LOGCONTEXT_BACKTRACE_ARRAY_SIZE 32 + +#ifdef LOG_DEBUG_MESSAGES + + #define LOG_DEBUG_CONTEXT(logContext, level, msgStr) \ + do { (logContext).log(level, msgStr); } while(0) + + #define LOG_DEBUG_BACKTRACE() \ + do { LogContext(__func__).logBacktrace(); } while(0) + +#else + + #define LOG_DEBUG_CONTEXT(logContext, level, msgStr) \ + do { /* nothing */ } while(0) + + #define LOG_DEBUG_BACKTRACE() \ + do { /* nothing */ } while(0) + +#endif // LOG_DEBUG_MESSAGES + + +class LogContext +{ + public: + LogContext(const std::string& contextStr="") : contextStr(contextStr) + { + this->logger = Logger::getLogger(); + } + + void log(LogTopic logTopic, int level, const char* msg) + { + if(unlikely(!logger) ) + return; + + logger->log(logTopic, level, contextStr.c_str(), msg); + } + + void log(LogTopic logTopic, int level, const std::string msg) + { + log(logTopic, level, msg.c_str() ); + } + + void log(int level, const char* msg) + { + log(LogTopic_GENERAL, level, msg); + } + + void log(int level, const std::string& msg) + { + log(level, msg.c_str()); + } + + void logErr(const char* msg) + { + if(unlikely(!logger) ) + return; + + logger->log(Log_ERR, contextStr.c_str(), msg); + } + + void logErr(const std::string& msg) + { + logErr(msg.c_str() ); + } + + void logBacktrace() + { + int backtraceLength = 0; + char** backtraceSymbols = NULL; + + void* backtraceArray[LOGCONTEXT_BACKTRACE_ARRAY_SIZE]; + backtraceLength = backtrace(backtraceArray, LOGCONTEXT_BACKTRACE_ARRAY_SIZE); + + // note: symbols are malloc'ed and need to be freed later + backtraceSymbols = backtrace_symbols(backtraceArray, backtraceLength); + + if(unlikely(!logger) ) + return; + + logger->logBacktrace(contextStr.c_str(), backtraceLength, backtraceSymbols); + + SAFE_FREE(backtraceSymbols); + } + + + protected: + LogContext(Logger* logger, const std::string& context) : contextStr(context) + { + // for derived classes that have a different way of finding the logger + // (other than via the thread-local storage) + // ...and remember to init the context also ;) + + this->logger = logger; + } + + + // getters & setters + void setLogger(Logger* logger) + { + this->logger = logger; + } + + + private: + const std::string contextStr; + Logger* logger; + +}; + diff --git a/common/source/common/app/log/Logger.cpp b/common/source/common/app/log/Logger.cpp new file mode 100644 index 0000000..e5648b7 --- /dev/null +++ b/common/source/common/app/log/Logger.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include "Logger.h" + +#include + +#undef LOG_DEBUG +#include + +#define LOGGER_ROTATED_FILE_SUFFIX ".old-" +#define LOGGER_TIMESTR_SIZE 32 + +std::unique_ptr Logger::logger; + +// Note: Keep in sync with enum LogTopic +const char* const Logger::LogTopics[LogTopic_INVALID] = +{ + "general", + "states", + "mirroring", + "workqueues", + "storage-pools", + "capacity", + "communication", + "quota", + "sessions", + "event-logger", + "database", + "socklib", +}; + +static const int syslogLevelMapping[Log_LEVELMAX] = { LOG_ERR, LOG_CRIT, LOG_WARNING, + LOG_NOTICE, LOG_DEBUG, LOG_DEBUG }; + +Logger::Logger(int defaultLevel, LogType cfgLogType, bool noDate, const std::string& stdFile, + unsigned linesPerFile, unsigned rotatedFiles): + logType(cfgLogType), logLevels(LogTopic_INVALID, defaultLevel), + logNoDate(noDate),logStdFile(stdFile), logNumLines(linesPerFile),logNumRotatedFiles(rotatedFiles) +{ + this->stdFile = stdout; + this->errFile = stderr; + + this->timeFormat = logNoDate ? "%X" : "%b%d %X"; // set time format + + this->rotatedFileSuffix = LOGGER_ROTATED_FILE_SUFFIX; + + pthread_rwlockattr_t attr; + pthread_rwlockattr_init(&attr); + pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_READER_NP); + + pthread_rwlock_init(&this->rwLock, &attr); + + // prepare file handles and rotate + prepareLogFiles(); +} + +Logger::~Logger() +{ + // close files + if(this->stdFile != stdout) + fclose(this->stdFile); + + pthread_rwlock_destroy(&this->rwLock); +} + +/** + * Prints a message to the standard log. + * Note: Doesn't lock the outputLock + * + * @param level the level of relevance (should be greater than 0) + * @param msg the actual log message + */ +void Logger::logGrantedUnlocked(int level, const char* threadName, const char* context, + int line, const char* msg) +{ + char timeStr[LOGGER_TIMESTR_SIZE]; + + TimeAbs nowTime; + + getTimeStr(nowTime.getTimeS(), timeStr, LOGGER_TIMESTR_SIZE); + + if (line >= 0) + { + const char* contextEnd = context + ::strlen(context) - 1; + while (contextEnd > context && *contextEnd != '/') + contextEnd--; + if (*contextEnd == '/') + context = contextEnd + 1; + else + context = contextEnd; + } + + if ( logType != LogType_SYSLOG ) + { +#ifdef BEEGFS_DEBUG_PROFILING + uint64_t timeMicroS = nowTime.getTimeMicroSecPart(); // additional micro-s info for timestamp + + if (line > 0) + fprintf(stdFile, "(%d) %s.%06ld %s [%s:%i] >> %s\n", level, timeStr, (long) timeMicroS, + threadName, context, line, msg); + else + fprintf(stdFile, "(%d) %s.%06ld %s [%s] >> %s\n", level, timeStr, (long) timeMicroS, + threadName, context, msg); +#else + if (line > 0) + fprintf(stdFile, "(%d) %s %s [%s:%i] >> %s\n", level, timeStr, threadName, context, line, + msg); + else + fprintf(stdFile, "(%d) %s %s [%s] >> %s\n", level, timeStr, threadName, context, msg); +#endif // BEEGFS_DEBUG_PROFILING + + //fflush(stdFile); // no longer needed => line buf + + currentNumStdLines.increase(); + } + else + { + if (line > 0) + syslog(syslogLevelMapping[level], "%s [%s:%i] >> %s\n", threadName, context, line, msg); + else + syslog(syslogLevelMapping[level], "%s [%s] >> %s\n", threadName, context, msg); + } +} + +/** + * Wrapper for logGrantedUnlocked that locks/unlocks the outputMutex. + */ +void Logger::logGranted(int level, const char* threadName, const char* context, int line, + const char* msg) +{ + pthread_rwlock_rdlock(&this->rwLock); + + logGrantedUnlocked(level, threadName, context, line, msg); + + pthread_rwlock_unlock(&this->rwLock); + + rotateStdLogChecked(); +} + +/** + * Prints a backtrage to the standard log. + */ +void Logger::logBacktraceGranted(const char* context, int backtraceLength, char** backtraceSymbols) +{ + std::string threadName = PThread::getCurrentThreadName(); + + pthread_rwlock_rdlock(&this->rwLock); + + logGrantedUnlocked(1, threadName.c_str(), context, -1, "Backtrace:"); + + for(int i=0; i < backtraceLength; i++) + { + fprintf(stdFile, "%d: %s\n", i+1, backtraceSymbols[i] ); + } + + pthread_rwlock_unlock(&this->rwLock); +} + +/* + * Print time and date to buf. + * + * @param buf output buffer + * @param bufLen length of buf (depends on current locale, at least 32 recommended) + * @return 0 on error, strLen of buf otherwise + */ +size_t Logger::getTimeStr(uint64_t seconds, char* buf, size_t bufLen) +{ + struct tm nowStruct; + localtime_r((time_t*) &seconds, &nowStruct); + + size_t strRes = strftime(buf, bufLen, timeFormat, &nowStruct); + buf[strRes] = 0; // just to make sure - in case the given buf is too small + return strRes; +} + +void Logger::prepareLogFiles() +{ + if ( logType == LogType_SYSLOG ) + { + //if its sylog skip the file operation + return; + } + else if(!logStdFile.length() ) + { + stdFile = stdout; + } + else + { + rotateLogFile(logStdFile); + + stdFile = fopen(logStdFile.c_str(), "w"); + if(!stdFile) + { + perror("Logger::openStdLogFile"); + stdFile = stdout; + throw InvalidConfigException( + std::string("Unable to create standard log file: ") + logStdFile); + } + + } + + setlinebuf(stdFile); + + return; +} + +void Logger::rotateLogFile(std::string filename) +{ + for(int i=logNumRotatedFiles; i > 0; i--) + { + std::string oldname = filename + + ( (i==1) ? "" : (rotatedFileSuffix + StringTk::intToStr(i-1) ) ); + + std::string newname = filename + + rotatedFileSuffix + StringTk::intToStr(i); + + rename(oldname.c_str(), newname.c_str() ); + } + +} + +void Logger::rotateStdLogChecked() +{ + + if( (stdFile == stdout) || logType == LogType_SYSLOG || + !logNumLines || (currentNumStdLines.read() < logNumLines) ) + return; // nothing to do yet + + pthread_rwlock_wrlock(&this->rwLock); + + if (currentNumStdLines.read() < logNumLines) + { // we raced with another thread before aquiring the lock + pthread_rwlock_unlock(&this->rwLock); + return; + } + + currentNumStdLines.setZero(); + + fclose(stdFile); + + rotateLogFile(logStdFile); // the actual rotation + + stdFile = fopen(logStdFile.c_str(), "w"); + if(!stdFile) + { + perror("Logger::rotateStdLogChecked"); + stdFile = stdout; + } + + setlinebuf(stdFile); + + pthread_rwlock_unlock(&this->rwLock); +} diff --git a/common/source/common/app/log/Logger.h b/common/source/common/app/log/Logger.h new file mode 100644 index 0000000..95e2a11 --- /dev/null +++ b/common/source/common/app/log/Logger.h @@ -0,0 +1,380 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum LogLevel +{ + Log_ERR=0, /* system error */ + Log_CRITICAL=1, /* something the users should definitely know about */ + Log_WARNING=2, /* things that indicate or are related to a problem */ + Log_NOTICE=3, /* things that could help finding problems */ + Log_DEBUG=4, /* things that are only useful during debugging, often logged with LOG_DEBUG() */ + Log_SPAM=5, /* things that are typically too detailed even during normal debugging, + very often with LOG_DEBUG() */ + Log_LEVELMAX /* this needs to be the last entry; it's used as array length */ +}; + +enum LogTopic +{ + LogTopic_GENERAL=0, // default log topic + LogTopic_STATES=1, // everything related target/node states + LogTopic_MIRRORING=2, // everything related to mirroring + LogTopic_WORKQUEUES=3, // En/dequeueing of work items + LogTopic_STORAGEPOOLS=4, // related to storage pools + LogTopic_CAPACITY=5, // capacity (pool) information + LogTopic_COMMUNICATION=6, + LogTopic_QUOTA=7, + LogTopic_SESSIONS=8, // related to session(store) handling + LogTopic_EVENTLOGGER=9, // related to file event logger + LogTopic_DATABASE=10, // related to database operations + LogTopic_SOCKLIB=11, // socket library message (eg ib_lib) + LogTopic_INVALID +}; + +namespace beegfs { namespace logging { + +struct SystemError +{ + int value; + + SystemError() : value(errno) {} + explicit SystemError(int value) : value(value) {} + + SystemError operator()(int e) const { return SystemError(e); } + + friend std::ostream& operator<<(std::ostream& os, SystemError e) + { + char errStrBuffer[256]; + char* errStr = strerror_r(e.value, errStrBuffer, sizeof(errStrBuffer)); + + boost::io::ios_all_saver flags(os); + + os.flags(std::ios_base::dec); + os.width(0); + + return os << errStr << " (" << e.value << ")"; + } +}; + +template +struct InBase +{ + constexpr InBase() {} + + template + std::string operator()(const T& value) const + { + std::ostringstream out; + + out << Manip << value; + return out.str(); + } +}; + +struct LogInfos +{ + std::stringstream infos; + + template + LogInfos& operator<<(T data) + { + infos << data; + return *this; + } + + LogInfos& operator<<(bool data) + { + infos << (data ? "yes" : "no"); + return *this; + } + + /* + * std::error_code does have an operator<<, but this prints the numeric error code, not the + * error message, so we define our own one that pretty-prints message and category. + */ + LogInfos& operator<<(std::error_code err) + { + infos << err.message() << " (" << err.category().name() << ": " << err.value() << ")"; + return *this; + } + + std::string str() const + { + return infos.str(); + } +}; + +}} // beegfs::logging + + +class Logger +{ + private: + static const char* const LogTopics[LogTopic_INVALID]; + + private: + Logger(int defaultLevel, LogType cfgLogType, bool noDate, const std::string& stdFile, + unsigned linesPerFile, unsigned rotatedFiles); + + public: + ~Logger(); + + private: + static std::unique_ptr logger; + + // configurables + LogType logType; + IntVector logLevels; + bool logNoDate; + std::string logStdFile; + unsigned logNumLines; + unsigned logNumRotatedFiles; + + // internals + pthread_rwlock_t rwLock; // cannot use RWLock class here, because that uses logging! + + const char* timeFormat; + FILE* stdFile; + FILE* errFile; + AtomicUInt32 currentNumStdLines; + AtomicUInt64 currentNumErrLines; + std::string rotatedFileSuffix; + + void logGrantedUnlocked(int level, const char* threadName, const char* context, + int line, const char* msg); + void logGranted(int level, const char* threadName, const char* context, int line, + const char* msg); + void logBacktraceGranted(const char* context, int backtraceLength, char** backtraceSymbols); + + void prepareLogFiles(); + size_t getTimeStr(uint64_t seconds, char* buf, size_t bufLen); + void rotateLogFile(std::string filename); + void rotateStdLogChecked(); + + public: + // inliners + + void log(LogTopic logTopic, int level, const char* context, int line, const char* msg) + { + if(level > logLevels[logTopic]) + return; + + std::string threadName = PThread::getCurrentThreadName(); + + logGranted(level, threadName.c_str(), context, line, msg); + } + + void log(LogTopic logTopic, int level, const char* context, const char* msg) + { + log(logTopic, level, context, -1, msg); + } + + /** + * Just a wrapper for the normal log() method which takes "const char*" arguments. + */ + void log(LogTopic logTopic, int level, const std::string& context, const std::string& msg) + { + if(level > logLevels[logTopic]) + return; + + log(logTopic, level, context.c_str(), msg.c_str() ); + } + + void log(int level, const char* context, const char* msg) + { + log(LogTopic_GENERAL, level, context, msg); + } + + void log(int level, const std::string& context, const std::string& msg) + { + log(level, context.c_str(), msg.c_str()); + } + + void logBacktrace(const char* context, int backtraceLength, char** backtraceSymbols) + { + if(!backtraceSymbols) + { + log(1, context, "Note: No symbols for backtrace available"); + return; + } + + logBacktraceGranted(context, backtraceLength, backtraceSymbols); + } + + // getters & setters + + /** + * Note: This method is not thread-safe. + */ + void setLogLevel(int logLevel, LogTopic logTopic = LogTopic_GENERAL) + { + if ((size_t)logTopic < logLevels.size()) + logLevels.at(logTopic) = logLevel; + } + + /** + * Note: This method is not thread-safe. + */ + int getLogLevel(LogTopic logTopic) + { + try + { + return logLevels.at(logTopic); + } + catch (const std::out_of_range& e) + { + return -1; + } + } + + /** + * Note: This method is not thread-safe. + */ + const IntVector& getLogLevels() const + { + return logLevels; + } + + static LogTopic logTopicFromName(const std::string& name) + { + const auto idx = std::find_if( + std::begin(LogTopics), std::end(LogTopics), + [&] (const char* c) { return c == name; }); + + if (idx == std::end(LogTopics)) + return LogTopic_INVALID; + + return LogTopic(idx - std::begin(LogTopics)); + } + + static std::string logTopicToName(LogTopic logTopic) + { + return LogTopics[logTopic]; + } + + static Logger* createLogger(int defaultLevel, LogType logType, bool noDate, + const std::string& stdFile, unsigned linesPerFile, unsigned rotatedFiles) + { + if (logger) + throw std::runtime_error("attempted to create a second system-wide logger"); + + logger.reset(new Logger(defaultLevel, logType, noDate, stdFile, linesPerFile, + rotatedFiles)); + + return logger.get(); + } + + static void destroyLogger() + { + logger.reset(); + } + + static Logger* getLogger() + { + return logger.get(); + } + + static bool isInitialized() + { + return !!logger; + } +}; + +#define LOG_CTX_TOP_L_ITEM__sep \ + (_log_line << (_log_items++ ? "; " : " ")) +#define LOG_CTX_TOP_L_ITEM__1(item) \ + LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_STRINGIZE(item) << ": " << item +#define LOG_CTX_TOP_L_ITEM__2(item) \ + LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_TUPLE_ELEM(0, item) << ": " \ + << BOOST_PP_TUPLE_ELEM(1, item) +#define LOG_CTX_TOP_L_ITEM__3(item) \ + do { \ + if (_log_level >= BOOST_PP_CAT(Log_, BOOST_PP_TUPLE_ELEM(0, item))) { \ + LOG_CTX_TOP_L_ITEM__sep << BOOST_PP_TUPLE_ELEM(1, item) << ": " \ + << BOOST_PP_TUPLE_ELEM(2, item); \ + } \ + } while (0) +#define LOG_CTX_TOP_L_ITEM__bad(item) \ + static_assert(false, "wrong number of arguments in log info, is " BOOST_PP_STRINGIZE(item)) + +#define LOG_CTX_TOP_L_ITEM(r, data, item) \ + BOOST_PP_IF( \ + BOOST_PP_IS_BEGIN_PARENS(item), \ + BOOST_PP_IF( \ + BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(item), 2), \ + LOG_CTX_TOP_L_ITEM__2, \ + BOOST_PP_IF( \ + BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(item), 3), \ + LOG_CTX_TOP_L_ITEM__3, \ + LOG_CTX_TOP_L_ITEM__bad)), \ + LOG_CTX_TOP_L_ITEM__1)(item); + +#define LOG_CTX_TOP_L_ITEMS(items) \ + do { \ + BOOST_PP_SEQ_FOR_EACH(LOG_CTX_TOP_L_ITEM, , items) \ + } while (0) + +#define LOG_CTX_TOP_L(Topic, Level, Context, Line, Message, ...) \ + do { \ + Logger* const _log_logger = Logger::getLogger(); \ + if (!_log_logger || _log_logger->getLogLevel(Topic) < Level) \ + break; \ + auto _log_level = _log_logger->getLogLevel(Topic); \ + (void) _log_level; \ + unsigned _log_items = 0; \ + (void) _log_items; \ + beegfs::logging::LogInfos _log_line; \ + const beegfs::logging::SystemError sysErr; (void) sysErr; \ + const beegfs::logging::InBase hex; (void) hex; \ + const beegfs::logging::InBase oct; (void) oct; \ + _log_line << Message; \ + LOG_CTX_TOP_L_ITEMS( \ + BOOST_PP_SEQ_POP_FRONT(BOOST_PP_TUPLE_TO_SEQ((, ##__VA_ARGS__)))); \ + _log_logger->log(Topic, Level, Context, Line, _log_line.str().c_str()); \ + } while (0) + +#define LOG(Topic, Level, Message, ...) \ + LOG_CTX_TOP_L(LogTopic_##Topic, Log_##Level, __FILE__, __LINE__, Message, ## __VA_ARGS__) + +#define LOG_CTX(Topic, Level, Context, Message, ...) \ + LOG_CTX_TOP_L(LogTopic_##Topic, Log_##Level, Context, -1, Message, ## __VA_ARGS__) + +#ifdef BEEGFS_DEBUG + #define LOG_DEBUG_CTX LOG_CTX + // Context may be a std::string, a C string, or a string literal. + // &X[0] turns both into a const char* + #define LOG_DEBUG(Context, Level, Message) LOG_CTX_TOP_L(LogTopic_GENERAL, LogLevel(Level), \ + &Context[0], -1, Message) + #define LOG_DBG LOG + #define LOG_TOP_DBG LOG_TOP + #define LOG_CTX_TOP_DBG LOG_CTX_TOP +#else + #define LOG_DEBUG_CTX(...) do {} while (0) + #define LOG_DEBUG(...) do {} while (0) + #define LOG_DBG(...) do {} while (0) + #define LOG_TOP_DBG(...) do {} while (0) + #define LOG_CTX_TOP_DBG(...) do {} while (0) +#endif + diff --git a/common/source/common/benchmark/StorageBench.h b/common/source/common/benchmark/StorageBench.h new file mode 100644 index 0000000..808fde0 --- /dev/null +++ b/common/source/common/benchmark/StorageBench.h @@ -0,0 +1,73 @@ +#pragma once + +#include + + +// defines storage benchmark errors... +#define STORAGEBENCH_ERROR_COM_TIMEOUT -3 +#define STORAGEBENCH_ERROR_ABORT_BENCHMARK -2 +#define STORAGEBENCH_ERROR_WORKER_ERROR -1 +#define STORAGEBENCH_ERROR_NO_ERROR 0 +#define STORAGEBENCH_ERROR_UNINITIALIZED 1 + +#define STORAGEBENCH_ERROR_INITIALIZATION_ERROR 10 +#define STORAGEBENCH_ERROR_INIT_READ_DATA 11 +#define STORAGEBENCH_ERROR_INIT_CREATE_BENCH_FOLDER 12 +#define STORAGEBENCH_ERROR_INIT_TRANSFER_DATA 13 + +#define STORAGEBENCH_ERROR_RUNTIME_ERROR 20 +#define STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER 21 +#define STORAGEBENCH_ERROR_RUNTIME_OPEN_FILES 22 +#define STORAGEBENCH_ERROR_RUNTIME_UNKNOWN_TARGET 23 +#define STORAGEBENCH_ERROR_RUNTIME_IS_RUNNING 24 +#define STORAGEBENCH_ERROR_RUNTIME_CLEANUP_JOB_ACTIVE 25 + + +// map for throughput results; key: targetID, value: throughput in kb/s +typedef std::map StorageBenchResultsMap; +typedef StorageBenchResultsMap::iterator StorageBenchResultsMapIter; +typedef StorageBenchResultsMap::const_iterator StorageBenchResultsMapCIter; +typedef StorageBenchResultsMap::value_type StorageBenchResultsMapVal; + + +/* + * enum for the action parameter of the storage benchmark + */ +enum StorageBenchAction +{ + StorageBenchAction_START = 0, + StorageBenchAction_STOP = 1, + StorageBenchAction_STATUS = 2, + StorageBenchAction_CLEANUP = 3, + StorageBenchAction_NONE = 4 +}; + +/* + * enum for the different benchmark types + */ +enum StorageBenchType +{ + StorageBenchType_READ = 0, + StorageBenchType_WRITE = 1, + StorageBenchType_NONE = 2 +}; + +/* + * enum for the states of the state machine of the storage benchmark operator + * note: see STORAGEBENCHSTATUS_IS_ACTIVE + */ +enum StorageBenchStatus +{ + StorageBenchStatus_UNINITIALIZED = 0, + StorageBenchStatus_INITIALISED = 1, + StorageBenchStatus_ERROR = 2, + StorageBenchStatus_RUNNING = 3, + StorageBenchStatus_STOPPING = 4, + StorageBenchStatus_STOPPED = 5, + StorageBenchStatus_FINISHING = 6, + StorageBenchStatus_FINISHED = 7 +}; + +#define STORAGEBENCHSTATUS_IS_ACTIVE(status) ( (status == StorageBenchStatus_RUNNING) || \ + (status == StorageBenchStatus_FINISHING) || (status == StorageBenchStatus_STOPPING) ) + diff --git a/common/source/common/components/AbstractDatagramListener.cpp b/common/source/common/components/AbstractDatagramListener.cpp new file mode 100644 index 0000000..0a4436b --- /dev/null +++ b/common/source/common/components/AbstractDatagramListener.cpp @@ -0,0 +1,506 @@ +#include +#include +#include +#include +#include +#include +#include +#include "AbstractDatagramListener.h" + +#include + +AbstractDatagramListener::AbstractDatagramListener(const std::string& threadName, + NetFilter* netFilter, NicAddressList& localNicList, AcknowledgmentStore* ackStore, + unsigned short udpPort, bool restrictOutboundInterfaces) + : PThread(threadName), + log(threadName), + netFilter(netFilter), + ackStore(ackStore), + restrictOutboundInterfaces(restrictOutboundInterfaces), + udpPort(udpPort), + sendBuf(NULL), + recvBuf(NULL), + recvTimeoutMS(4000), + localNicList(localNicList) +{ + if(!initSocks() ) + throw ComponentInitException("Unable to initialize the socket"); +} + +AbstractDatagramListener::~AbstractDatagramListener() +{ + SAFE_FREE(sendBuf); + SAFE_FREE(recvBuf); +} + +void AbstractDatagramListener::configSocket(StandardSocket* s, NicAddress* nicAddr, int bufsize) +{ + s->setSoBroadcast(true); + s->setSoReuseAddr(true); + if (bufsize > 0) + s->setSoRcvBuf(bufsize); + + if (nicAddr) + s->bindToAddr(nicAddr->ipAddr.s_addr, udpPort); + else + s->bind(udpPort); + + /* + Note that if udpPort was intialized as zero, it will be set according to the + value used by the first created socket and any future sockets created by this method + will use that value. That may or may not work, depending upon the current socket + bindings. + */ + if(udpPort == 0) + { // find out which port we are actually using + sockaddr_in bindAddr; + socklen_t bindAddrLen = sizeof(bindAddr); + + int getSockNameRes = getsockname(s->getFD(), (sockaddr*)&bindAddr, &bindAddrLen); + if(getSockNameRes == -1) + throw SocketException("getsockname() failed: " + System::getErrString() ); + + udpPortNetByteOrder = bindAddr.sin_port; + udpPort = htons(bindAddr.sin_port); + } + + std::string ifname; + if (nicAddr) + ifname = Socket::ipaddrToStr(nicAddr->ipAddr); + else + ifname = "any"; + + log.log(Log_NOTICE, std::string("Listening for UDP datagrams: ") + ifname + " Port " + + StringTk::intToStr(udpPort) ); + +} + +/** + * mutex must be held in a multi-threaded scenario. + */ +bool AbstractDatagramListener::initSocks() +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + int bufsize = cfg->getConnUDPRcvBufSize(); + + try + { + udpPortNetByteOrder = htons(udpPort); + loopbackAddrNetByteOrder = htonl(INADDR_LOOPBACK); + + if (restrictOutboundInterfaces) + { + routingTable = PThread::getCurrentThreadApp()->getRoutingTable(); + + interfaceSocks.clear(); + udpSock = nullptr; + ipSrcMap.clear(); + + for (auto& i : localNicList) + { + if (i.nicType == NICADDRTYPE_STANDARD) + { + std::shared_ptr s; + if (udpSock == nullptr) + { + udpSock = std::make_shared(PF_INET, SOCK_DGRAM); + s = udpSock; + } + else + { + s = udpSock->createSubordinate(PF_INET, SOCK_DGRAM); + } + configSocket(s.get(), &i, bufsize); + interfaceSocks[i.ipAddr] = s; + } + } + } + else + { + // no need to close down any existing unbound UDP socket, it listens to all interfaces + if (udpSock == nullptr) + { + udpSock = std::make_shared(PF_INET, SOCK_DGRAM); + configSocket(udpSock.get(), NULL, bufsize); + } + } + } + catch(SocketException& e) + { + log.logErr(std::string("UDP socket: ") + e.what() ); + return false; + } + + return true; +} + +/** + * Note: This is for delayed buffer allocation for better NUMA performance. + */ +void AbstractDatagramListener::initBuffers() +{ + void* bufInVoid = NULL; + void* bufOutVoid = NULL; + + int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), DGRAMMGR_RECVBUF_SIZE); + int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), DGRAMMGR_SENDBUF_SIZE); + + IGNORE_UNUSED_VARIABLE(inAllocRes); + IGNORE_UNUSED_VARIABLE(outAllocRes); + + this->recvBuf = (char*)bufInVoid; + this->sendBuf = (char*)bufOutVoid; +} + +std::shared_ptr AbstractDatagramListener::findSenderSock(struct in_addr addr) +{ + std::lock_guard lock(mutex); + return findSenderSockUnlocked(addr); +} + +/** + * mutex must be held. + */ +std::shared_ptr AbstractDatagramListener::findSenderSockUnlocked(struct in_addr addr) +{ + std::shared_ptr sock = udpSock; + if (restrictOutboundInterfaces) + { + if (addr.s_addr != loopbackAddrNetByteOrder) + { + struct in_addr k; + if (auto it { ipSrcMap.find(addr) }; it != std::end(ipSrcMap)) + { + k = it->second; + } + else + { + if (!routingTable.match(addr, localNicList, k)) + { + k.s_addr = 0; + // addr may have come from whoever sent a message, so this is a warning + LOG(COMMUNICATION, WARNING, "No routes found.", ("addr", Socket::ipaddrToStr(addr))); + } + ipSrcMap[addr] = k; + } + + sock = interfaceSocks[k]; + //LOG(COMMUNICATION, DEBUG, "findInterfaceSock", ("addr", Socket::ipaddrToStr(addr.s_addr).c_str()), + // ("k", Socket::ipaddrToStr(k).c_str()), ("sock", sock)); + + } + } + + return sock; +} + +void AbstractDatagramListener::setLocalNicList(NicAddressList& nicList) +{ + const std::lock_guard lock(mutex); + localNicList = nicList; + LOG(COMMUNICATION, DEBUG, "setLocalNicList"); + initSocks(); +} + +void AbstractDatagramListener::run() +{ + try + { + registerSignalHandler(); + + initBuffers(); + + listenLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void AbstractDatagramListener::listenLoop() +{ + struct sockaddr_in fromAddr; + + + while(!getSelfTerminate() ) + { + socklen_t fromAddrLen = sizeof(fromAddr); + + try + { + ssize_t recvRes = udpSock->recvfromT( + recvBuf, DGRAMMGR_RECVBUF_SIZE, 0, (struct sockaddr*)&fromAddr, &fromAddrLen, + recvTimeoutMS); + if(isDGramFromSelf(&fromAddr) ) + { // note: important if we're sending msgs to all hosts and don't want to include ourselves + //log.log(Log_SPAM, "Discarding DGram from localhost"); + continue; + } + + auto netMessageFactory = PThread::getCurrentThreadApp()->getNetMessageFactory(); + auto msg = netMessageFactory->createFromRaw(recvBuf, recvRes); + + if (msg->getMsgType() == NETMSGTYPE_Invalid + || msg->getLength() != unsigned(recvRes) + || msg->getSequenceNumber() != 0 + || msg->getSequenceNumberDone() != 0) + { + LOG(COMMUNICATION, NOTICE, "Received invalid message from peer", + ("peer", Socket::ipaddrToStr(fromAddr.sin_addr))); + } + else + { + handleIncomingMsg(&fromAddr, msg.get()); + } + } + catch(SocketTimeoutException& ste) + { + // nothing to worry about, just idle + } + catch(SocketInterruptedPollException& sipe) + { + // ignore interruption, because the debugger causes this + } + catch(SocketException& se) + { + log.logErr(std::string("Encountered an unrecoverable error: ") + + se.what() ); + + throw se; // to let the component exception handler be called + } + } + +} + +/** + * Send to all nodes (with a static number of retries). + * + * @param numRetries 0 to send only once + * @timeoutMS between retries (0 for default) + */ +void AbstractDatagramListener::sendToNodesUDP(const std::vector& nodes, NetMessage* msg, + int numRetries, int timeoutMS) +{ + int retrySleepMS = timeoutMS ? timeoutMS : 750; + int numRetriesLeft = numRetries + 1; + + if (unlikely(nodes.empty())) + return; + + while (numRetriesLeft && !getSelfTerminate()) + { + for (auto iter = nodes.begin(); iter != nodes.end(); iter++) + sendMsgToNode(**iter, msg); + + numRetriesLeft--; + + if(numRetriesLeft) + waitForSelfTerminateOrder(retrySleepMS); + } +} + + +/** + * Send to all active nodes. + * + * @param numRetries 0 to send only once + * @timeoutMS between retries (0 for default) + */ +void AbstractDatagramListener::sendToNodesUDP(const AbstractNodeStore* nodes, NetMessage* msg, + int numRetries, int timeoutMS) +{ + sendToNodesUDP(nodes->referenceAllNodes(), msg, numRetries, timeoutMS); +} + +/** + * Send to all nodes and wait for ack. + * + * @return true if all acks received within a timeout of ackWaitSleepMS. + */ +bool AbstractDatagramListener::sendToNodesUDPwithAck(const std::vector& nodes, + AcknowledgeableMsg* msg, int ackWaitSleepMS, int numRetries) +{ + int numRetriesLeft = numRetries; + + WaitAckMap waitAcks; + WaitAckMap receivedAcks; + WaitAckNotification notifier; + + bool allAcksReceived = false; + + // note: we use uint for tv_sec (not uint64) because 32 bits are enough here + std::string ackIDPrefix = + StringTk::uintToHexStr(TimeAbs().getTimeval()->tv_sec) + "-" + + StringTk::uintToHexStr(incAckCounter() ) + "-" + "dgramLis" "-"; + + + if (unlikely(nodes.empty())) + return true; + + + // create and register waitAcks + + for (auto iter = nodes.begin(); iter != nodes.end(); iter++) + { + std::string ackID(ackIDPrefix + (*iter)->getAlias() ); + WaitAck waitAck(ackID, iter->get()); + + waitAcks.insert(WaitAckMapVal(ackID, waitAck) ); + } + + ackStore->registerWaitAcks(&waitAcks, &receivedAcks, ¬ifier); + + + // loop: send requests -> waitforcompletion -> resend + + while(numRetriesLeft && !getSelfTerminate() ) + { + // create waitAcks copy + + WaitAckMap currentWaitAcks; + { + const std::lock_guard lock(notifier.waitAcksMutex); + + currentWaitAcks = waitAcks; + } + + // send messages + + for(WaitAckMapIter iter = currentWaitAcks.begin(); iter != currentWaitAcks.end(); iter++) + { + Node* node = (Node*)iter->second.privateData; + + msg->setAckID(iter->first.c_str() ); + + sendMsgToNode(*node, msg); + } + + // wait for acks + + allAcksReceived = ackStore->waitForAckCompletion(¤tWaitAcks, ¬ifier, ackWaitSleepMS); + if(allAcksReceived) + break; // all acks received + + // some waitAcks left => prepare next loop + + numRetriesLeft--; + } + + // clean up + + ackStore->unregisterWaitAcks(&waitAcks); + + LOG_DBG(COMMUNICATION, DEBUG, "UDP msg ack stats", receivedAcks.size(), nodes.size()); + + return allAcksReceived; +} + +/** + * Send to all active nodes and wait for ack. + * The message should not have an ackID set. + * + * @return true if all acks received within a reasonable timeout. + */ +bool AbstractDatagramListener::sendToNodesUDPwithAck(const AbstractNodeStore* nodes, + AcknowledgeableMsg* msg, int ackWaitSleepMS, int numRetries) +{ + return sendToNodesUDPwithAck(nodes->referenceAllNodes(), msg, ackWaitSleepMS, numRetries); +} + + + +/** + * Send to node and wait for ack. + * The message should not have an ackID set. + * + * @return true if ack received within a reasonable timeout. + */ +bool AbstractDatagramListener::sendToNodeUDPwithAck(const NodeHandle& node, AcknowledgeableMsg* msg, + int ackWaitSleepMS, int numRetries) +{ + std::vector nodes(1, node); + return sendToNodesUDPwithAck(nodes, msg, ackWaitSleepMS, numRetries); +} + + +/** + * Sends the buffer to all available node interfaces. + * @return true if at least one buffer was sent + */ +bool AbstractDatagramListener::sendBufToNode(Node& node, const char* buf, size_t bufLen) +{ + NicAddressList nicList(node.getNicList() ); + unsigned short portUDP = node.getPortUDP(); + + bool sent = false; + for(NicAddressListIter iter = nicList.begin(); iter != nicList.end(); iter++) + { + if(iter->nicType != NICADDRTYPE_STANDARD) + continue; + + if(!netFilter->isAllowed(iter->ipAddr.s_addr) ) + continue; + + if (sendto(buf, bufLen, 0, iter->ipAddr, portUDP) >0) + sent = true; + } + if (!sent) + LOG(COMMUNICATION, ERR, std::string("Failed to send buf"), ("node", node.getNodeIDWithTypeStr())); + return sent; +} + +/** + * Sends the message to all available node interfaces. + */ +bool AbstractDatagramListener::sendMsgToNode(Node& node, NetMessage* msg) +{ + const auto msgBuf = MessagingTk::createMsgVec(*msg); + return sendBufToNode(node, &msgBuf[0], msgBuf.size()); +} + +/** + * Note: This is intended to wake up the datagram listener thread and thus allow faster termination + * (so you will want to call this after setting self-terminate). + */ +void AbstractDatagramListener::sendDummyToSelfUDP() +{ + in_addr hostAddr; + hostAddr.s_addr = loopbackAddrNetByteOrder; + + DummyMsg msg; + const auto msgBuf = MessagingTk::createMsgVec(msg); + + this->sendto(&msgBuf[0], msgBuf.size(), 0, hostAddr, udpPort); +} + +/** + * Increase by one and return previous value. + */ +unsigned AbstractDatagramListener::incAckCounter() +{ + return ackCounter.increase(); +} + +bool AbstractDatagramListener::isDGramFromSelf(struct sockaddr_in* fromAddr) +{ + if(fromAddr->sin_port != udpPortNetByteOrder) + return false; + + if (loopbackAddrNetByteOrder == fromAddr->sin_addr.s_addr) + return true; + + const std::lock_guard lock(mutex); + for(NicAddressListIter iter = localNicList.begin(); iter != localNicList.end(); iter++) + { + //LogContext("isDGramFromSelf").log(Log_DEBUG, std::string("fromAddr=") + Socket::ipaddrToStr(&fromAddr->sin_addr) + //+ " nic=" + Socket::ipaddrToStr(&iter->ipAddr)); + if(iter->ipAddr.s_addr == fromAddr->sin_addr.s_addr) + return true; + } + + return false; +} diff --git a/common/source/common/components/AbstractDatagramListener.h b/common/source/common/components/AbstractDatagramListener.h new file mode 100644 index 0000000..06d9adc --- /dev/null +++ b/common/source/common/components/AbstractDatagramListener.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ComponentInitException.h" + +#include + +#define DGRAMMGR_RECVBUF_SIZE 65536 +#define DGRAMMGR_SENDBUF_SIZE DGRAMMGR_RECVBUF_SIZE + +// forward declaration +class NetFilter; + +typedef std::unordered_map, InAddrHash> StandardSocketMap; + +class AbstractDatagramListener : public PThread +{ + // for efficient individual multi-notifications (needs access to mutex) + friend class LockEntryNotificationWork; + friend class LockRangeNotificationWork; + + + public: + virtual ~AbstractDatagramListener(); + + void sendToNodesUDP(const AbstractNodeStore* nodes, NetMessage* msg, int numRetries, + int timeoutMS=0); + void sendToNodesUDP(const std::vector& nodes, NetMessage* msg, int numRetries, + int timeoutMS=0); + bool sendToNodesUDPwithAck(const AbstractNodeStore* nodes, AcknowledgeableMsg* msg, + int ackWaitSleepMS = 1000, int numRetries=2); + bool sendToNodesUDPwithAck(const std::vector& nodes, AcknowledgeableMsg* msg, + int ackWaitSleepMS = 1000, int numRetries=2); + bool sendToNodeUDPwithAck(const NodeHandle& node, AcknowledgeableMsg* msg, + int ackWaitSleepMS = 1000, int numRetries=2); + bool sendBufToNode(Node& node, const char* buf, size_t bufLen); + bool sendMsgToNode(Node& node, NetMessage* msg); + + void sendDummyToSelfUDP(); + + private: + std::shared_ptr findSenderSockUnlocked(struct in_addr addr); + + protected: + AbstractDatagramListener(const std::string& threadName, NetFilter* netFilter, + NicAddressList& localNicList, AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces); + + LogContext log; + + NetFilter* netFilter; + AcknowledgmentStore* ackStore; + bool restrictOutboundInterfaces; + + unsigned short udpPort; + unsigned short udpPortNetByteOrder; + in_addr_t loopbackAddrNetByteOrder; // (because INADDR_... constants are in host byte order) + + char* sendBuf; + + virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) = 0; + + std::shared_ptr findSenderSock(struct in_addr addr); + + /** + * Returns the mutex related to seralization of sends. + */ + Mutex* getSendMutex() + { + return &mutex; + } + + private: + std::shared_ptr udpSock; + StandardSocketMap interfaceSocks; + IpSourceMap ipSrcMap; + char* recvBuf; + int recvTimeoutMS; + RoutingTable routingTable; + /** + * For now, use a single mutex for all of the members that are subject to + * thread contention. Using multiple mutexes makes the locking more difficult + * and can lead to deadlocks. The state dependencies are all intertwined, + * anyway. + */ + Mutex mutex; + + AtomicUInt32 ackCounter; // used to generate ackIDs + + NicAddressList localNicList; + + bool initSocks(); + void initBuffers(); + void configSocket(StandardSocket* sock, NicAddress* nicAddr, int bufsize); + + void run(); + void listenLoop(); + + bool isDGramFromSelf(struct sockaddr_in* fromAddr); + unsigned incAckCounter(); + + public: + // inliners + + /** + * Returns ENETUNREACH if no local NIC found to reach @to. + */ + ssize_t sendto(const void* buf, size_t len, int flags, + const struct sockaddr* to, socklen_t tolen) + { + const std::lock_guard lock(mutex); + struct in_addr a = reinterpret_cast(to)->sin_addr; + std::shared_ptr s = findSenderSockUnlocked(a); + if (s == nullptr) + return ENETUNREACH; + return s->sendto(buf, len, flags, to, tolen); + } + + /** + * Returns ENETUNREACH if no local NIC found to reach @ipAddr. + */ + ssize_t sendto(const void *buf, size_t len, int flags, + struct in_addr ipAddr, unsigned short port) + { + const std::lock_guard lock(mutex); + std::shared_ptr s = findSenderSockUnlocked(ipAddr); + if (s == nullptr) + return ENETUNREACH; + return s->sendto(buf, len, flags, ipAddr, port); + } + + // getters & setters + void setRecvTimeoutMS(int recvTimeoutMS) + { + this->recvTimeoutMS = recvTimeoutMS; + } + + unsigned short getUDPPort() + { + return udpPort; + } + + void setLocalNicList(NicAddressList& nicList); + +}; + diff --git a/common/source/common/components/ComponentInitException.h b/common/source/common/components/ComponentInitException.h new file mode 100644 index 0000000..8cc27e3 --- /dev/null +++ b/common/source/common/components/ComponentInitException.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +DECLARE_NAMEDEXCEPTION(ComponentInitException, "ComponentInitException") + + diff --git a/common/source/common/components/RegistrationDatagramListener.cpp b/common/source/common/components/RegistrationDatagramListener.cpp new file mode 100644 index 0000000..a20753f --- /dev/null +++ b/common/source/common/components/RegistrationDatagramListener.cpp @@ -0,0 +1,44 @@ +#include "RegistrationDatagramListener.h" + +RegistrationDatagramListener::RegistrationDatagramListener(NetFilter* netFilter, + NicAddressList& localNicList, AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces) : + AbstractDatagramListener("RegDGramLis", netFilter, localNicList, ackStore, udpPort, + restrictOutboundInterfaces) +{ +} + +void RegistrationDatagramListener::handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) +{ + HighResolutionStats stats; // currently ignored + std::shared_ptr sock = findSenderSock(fromAddr->sin_addr); + if (sock == nullptr) + { + log.log(Log_WARNING, "Could not handle incoming message: no socket"); + return; + } + + NetMessage::ResponseContext rctx(fromAddr, sock.get(), sendBuf, DGRAMMGR_SENDBUF_SIZE, &stats); + + switch(msg->getMsgType() ) + { + // valid messages within this context + case NETMSGTYPE_Ack: + case NETMSGTYPE_Heartbeat: + case NETMSGTYPE_Dummy: + { + if(!msg->processIncoming(rctx) ) + log.log(Log_WARNING, "Problem encountered during handling of incoming message"); + } break; + + default: + { // valid, but not within this context + log.log(Log_SPAM, + "Received a message that is invalid within the current context " + "from: " + Socket::ipaddrToStr(fromAddr->sin_addr) + "; " + "type: " + netMessageTypeToStr(msg->getMsgType() ) ); + } break; + }; +} + + diff --git a/common/source/common/components/RegistrationDatagramListener.h b/common/source/common/components/RegistrationDatagramListener.h new file mode 100644 index 0000000..18b7ce1 --- /dev/null +++ b/common/source/common/components/RegistrationDatagramListener.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +/** + * This is a minimalistic version of a datagram listener, which only handles incoming heartbeat + * messages. + * This component is required during registration to wait for a management server heartbeat, but to + * also make sure that we don't receive/handle an yincoming messages that could access data + * structures (e.g. app->localNode), which are not fully initialized prior to registration + * completion. + */ +class RegistrationDatagramListener : public AbstractDatagramListener +{ + public: + RegistrationDatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces); + + protected: + virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg); + + private: + +}; + diff --git a/common/source/common/components/StatsCollector.cpp b/common/source/common/components/StatsCollector.cpp new file mode 100644 index 0000000..da6f93c --- /dev/null +++ b/common/source/common/components/StatsCollector.cpp @@ -0,0 +1,86 @@ +#include +#include "StatsCollector.h" + +#include + +StatsCollector::StatsCollector(MultiWorkQueue* workQ, unsigned collectIntervalMS, + unsigned historyLength) + : PThread("Stats"), + log("Stats"), + workQ(workQ), + collectIntervalMS(collectIntervalMS), + historyLength(historyLength) +{ } + +StatsCollector::~StatsCollector() +{ + // nothing to be done here +} + + +void StatsCollector::run() +{ + try + { + registerSignalHandler(); + + collectLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void StatsCollector::collectLoop() +{ + while(!waitForSelfTerminateOrder(collectIntervalMS) ) + { + collectStats(); + } +} + + +void StatsCollector::collectStats() +{ + HighResolutionStats currentStats; + + std::lock_guard mutexLock(mutex); + + // Note: Newer stats in the internal list are pushed at the front side + + // get old stats and reset them + workQ->getAndResetStats(¤tStats); + + // set current stats time + currentStats.rawVals.statsTimeMS = TimeAbs().getTimeMS(); + + // take care of max history length + if(statsList.size() == historyLength) + statsList.pop_back(); + + // push new stats to front + statsList.push_front(currentStats); +} + +/** + * Returns the part of the stats history after lastStatsMS. + */ +void StatsCollector::getStatsSince(uint64_t lastStatsMS, HighResStatsList& outStatsList) +{ + std::lock_guard mutexLock(mutex); + + // Note: Newer stats in the internal list are pushed at the front side, but + // newer stats on the outStatsList are pushed to the back side. + + for(HighResStatsListIter iter = statsList.begin(); iter != statsList.end(); iter++) + { + // are the current stats older than requested? + if(iter->rawVals.statsTimeMS <= lastStatsMS) + break; + + outStatsList.push_back(*iter); + } +} diff --git a/common/source/common/components/StatsCollector.h b/common/source/common/components/StatsCollector.h new file mode 100644 index 0000000..9a32424 --- /dev/null +++ b/common/source/common/components/StatsCollector.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +#define STATSCOLLECTOR_COLLECT_INTERVAL_MS 1000 +#define STATSCOLLECTOR_HISTORY_LENGTH 60 + + +class StatsCollector : public PThread +{ + public: + StatsCollector(MultiWorkQueue* workQ, unsigned collectIntervalMS, unsigned historyLength); + virtual ~StatsCollector(); + + + void getStatsSince(uint64_t lastStatsMS, HighResStatsList& outStatsList); + + + protected: + LogContext log; + + Mutex mutex; + + HighResStatsList statsList; + + MultiWorkQueue* workQ; // might be NULL in derived classes with own collectStats() + unsigned collectIntervalMS; + unsigned historyLength; + + virtual void collectStats(); + + + private: + virtual void run(); + void collectLoop(); + + + + public: + // getters & setters +}; + + + diff --git a/common/source/common/components/StreamListener.cpp b/common/source/common/components/StreamListener.cpp new file mode 100644 index 0000000..ae69827 --- /dev/null +++ b/common/source/common/components/StreamListener.cpp @@ -0,0 +1,619 @@ +#include +#include +#include "worker/IncomingDataWork.h" +#include "StreamListener.h" + +#include + + +#define EPOLL_EVENTS_NUM (512) /* make it big to avoid starvation of higher FDs */ +#define RDMA_CHECK_FORCE_POLLLOOPS (7200) /* to avoid calling the check routine in each loop */ +#define RDMA_CHECK_INTERVAL_MS (150*60*1000) /* 150mins (must be more than double of the + client-side idle disconnect interval to avoid cases where + server disconnects first) */ +#define SOCKRETURN_SOCKS_NUM (32) + + +StreamListener::StreamListener(NicAddressList& localNicList, MultiWorkQueue* workQueue, + unsigned short listenPort) + : PThread("StreamLis"), + log("StreamLis"), + workQueue(workQueue), + rdmaCheckForceCounter(0) +{ + NicListCapabilities localNicCaps; + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + + this->epollFD = epoll_create(10); // "10" is just a hint (and is actually ignored) + if(epollFD == -1) + { + throw ComponentInitException(std::string("Error during epoll_create(): ") + + System::getErrString() ); + } + + if(!initSockReturnPipe() ) + throw ComponentInitException("Unable to initialize sock return pipe"); + + if(!initSocks(listenPort, &localNicCaps) ) + throw ComponentInitException("Unable to initialize socket"); +} + +StreamListener::~StreamListener() +{ + pollList.remove(sockReturnPipe->getReadFD() ); // may not be deleted by deleteAllConns() + delete(sockReturnPipe); + + deleteAllConns(); + + if(epollFD != -1) + close(epollFD); +} + +bool StreamListener::initSockReturnPipe() +{ + this->sockReturnPipe = new Pipe(false, true); + + pollList.add(sockReturnPipe->getReadFD() ); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = sockReturnPipe->getReadFD(); + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, sockReturnPipe->getReadFD()->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add sock return pipe (read side) to epoll set: ") + + System::getErrString() ); + return false; + } + + return true; +} + +bool StreamListener::initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps) +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + + rdmaListenSock = NULL; + tcpListenSock = NULL; + + + // RDMA + + if(localNicCaps->supportsRDMA) + { // RDMA usage is enabled + try + { + rdmaListenSock = RDMASocket::create().release(); + rdmaListenSock->bind(listenPort); + rdmaListenSock->listen(); + + pollList.add(rdmaListenSock); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = rdmaListenSock; + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, rdmaListenSock->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add RDMA listen sock to epoll set: ") + + System::getErrString() ); + return false; + } + + log.log(3, std::string("Listening for RDMA connections: Port ") + + StringTk::intToStr(listenPort) ); + } + catch(SocketException& e) + { + log.logErr(std::string("RDMA socket: ") + e.what() ); + return false; + } + } + + // TCP + + try + { + tcpListenSock = new StandardSocket(PF_INET, SOCK_STREAM); + tcpListenSock->setSoReuseAddr(true); + int bufsize = cfg->getConnTCPRcvBufSize(); + if (bufsize > 0) + tcpListenSock->setSoRcvBuf(bufsize); + tcpListenSock->bind(listenPort); + tcpListenSock->listen(); + + pollList.add(tcpListenSock); + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = tcpListenSock; + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, tcpListenSock->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add TCP listen sock to epoll set: ") + + System::getErrString() ); + return false; + } + + log.log(Log_NOTICE, std::string("Listening for TCP connections: Port ") + + StringTk::intToStr(listenPort) ); + } + catch(SocketException& e) + { + log.logErr(std::string("TCP socket: ") + e.what() ); + return false; + } + + + return true; +} + + +void StreamListener::run() +{ + try + { + registerSignalHandler(); + + listenLoop(); + + log.log(4, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void StreamListener::listenLoop() +{ + const int epollTimeoutMS = 3000; + + struct epoll_event epollEvents[EPOLL_EVENTS_NUM]; + + // (just to have 'em on the stack) + const int epollFD = this->epollFD; + RDMASocket* rdmaListenSock = this->rdmaListenSock; + StandardSocket* tcpListenSock = this->tcpListenSock; + FileDescriptor* sockReturnPipeReadEnd = this->sockReturnPipe->getReadFD(); + + bool runRDMAConnIdleCheck = false; // true just means we call the method (not enforce the check) + + // wait for incoming events and handle them... + + while(!getSelfTerminate() ) + { + //log.log(4, std::string("Before poll(). pollArrayLen: ") + + // StringTk::uintToStr(pollArrayLen) ); + + int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS); + + if(unlikely(epollRes < 0) ) + { // error occurred + if(errno == EINTR) // ignore interruption, because the debugger causes this + continue; + + log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() ); + break; + } + else + if(unlikely(!epollRes || (rdmaCheckForceCounter++ > RDMA_CHECK_FORCE_POLLLOOPS) ) ) + { // epollRes==0 is nothing to worry about, just idle + + // note: we can't run idle check here directly because the check might modify the + // poll set, which will be accessed in the loop below + runRDMAConnIdleCheck = true; + } + + // handle incoming data & connection attempts + for(size_t i=0; i < (size_t)epollRes; i++) + { + struct epoll_event* currentEvent = &epollEvents[i]; + Pollable* currentPollable = (Pollable*)currentEvent->data.ptr; + + //log.log(4, std::string("Incoming data on FD: ") + + // StringTk::intToStr(pollArray[i].fd) ); // debug in + + if(unlikely(currentPollable == rdmaListenSock) ) + onIncomingRDMAConnection(rdmaListenSock); + else + if(unlikely(currentPollable == tcpListenSock) ) + onIncomingStandardConnection(tcpListenSock); + else + if(currentPollable == sockReturnPipeReadEnd) + onSockReturn(); + else + onIncomingData( (Socket*)currentPollable); + } + + if(unlikely(runRDMAConnIdleCheck) ) + { // note: whether check actually happens depends on elapsed time since last check + runRDMAConnIdleCheck = false; + rdmaConnIdleCheck(); + } + + } +} + +/** + * Accept the incoming connection and add the new socket to the pollList. + */ +void StreamListener::onIncomingStandardConnection(StandardSocket* sock) +{ + try + { + struct sockaddr_in peerAddr; + socklen_t peerAddrLen = sizeof(peerAddr); + + std::unique_ptr acceptedSock( + (StandardSocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen)); + + // (note: level Log_DEBUG to avoid spamming the log until we have log topics) + log.log(Log_DEBUG, std::string("Accepted new connection from ") + + Socket::endpointAddrToStr(&peerAddr) + + std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) + + std::string("]") ); + + applySocketOptions(acceptedSock.get()); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = acceptedSock.get(); + + if(epoll_ctl(this->epollFD, EPOLL_CTL_ADD, acceptedSock->getFD(), &epollEvent) == -1) + { + throw SocketException(std::string("Unable to add sock to epoll set: ") + + System::getErrString() ); + } + + pollList.add(acceptedSock.release()); + } + catch(SocketException& se) + { + log.logErr(std::string("Trying to continue after connection accept error: ") + se.what() ); + } +} + +/** + * Accept the incoming connection and add the new socket to the pollList. + */ +void StreamListener::onIncomingRDMAConnection(RDMASocket* sock) +{ + // accept the incoming connection (and loop until no more delayed events are waiting on + // this socket) + // (Note: RDMASockets use this internally also to handle other kindes of events) + + // loop: check whether this is a false alarm, try to accept a new connection (if + // available) and also check for further events after accept + + while(sock->checkDelayedEvents() ) + { + try + { + struct sockaddr_in peerAddr; + socklen_t peerAddrLen = sizeof(peerAddr); + + RDMASocket* acceptedSock = + (RDMASocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen); + + // note: RDMASocket::accept() might return NULL (which is not an error) + if(!acceptedSock) + { + log.log(4, std::string("Ignoring an internal event on the listening RDMA socket") ); + continue; + } + + // (note: level Log_DEBUG to avoid spamming the log until we have log topics) + log.log(Log_DEBUG, std::string("Accepted new RDMA connection from ") + + Socket::endpointAddrToStr(&peerAddr) + + std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) + + std::string("]") ); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = acceptedSock; + + if(epoll_ctl(this->epollFD, EPOLL_CTL_ADD, acceptedSock->getFD(), &epollEvent) == -1) + { + throw SocketException(std::string("Unable to add RDMA sock to epoll set: ") + + System::getErrString() ); + } + + pollList.add(acceptedSock); + } + catch(SocketException& se) + { + log.logErr(std::string("Trying to continue after RDMA connection accept error: ") + + se.what() ); + } + + } +} + +/** + * Add the socket to the workQueue. + * + * Note: Data can also be added to the queue by the + * IncomingDataWork::checkRDMASocketImmediateData() method. + */ +void StreamListener::onIncomingData(Socket* sock) +{ + // check whether this is just a false alarm from a RDMASocket + if( (sock->getSockType() == NICADDRTYPE_RDMA) && + isFalseAlarm( (RDMASocket*)sock) ) + { + return; + } + + // we really have incoming data => add work to queue + + LOG_DEBUG("StreamListener::onIncomingData", 4, + std::string("Incoming stream data from: ") + sock->getPeername() ); + + int sockFD = sock->getFD(); // note: we store this here for delayed pollList removal, because + // this might be disconnect work, so the sock gets deleted by the worker and thus "sock->" + // becomes invalid + + //log.log(4, "Creating new work for to the queue"); + + IncomingDataWork* work = new IncomingDataWork(this, sock); + + //log.log(4, "Adding new work to the queue"); + + sock->setHasActivity(); // mark as active (for idle disconnect check) + + if(sock->getIsDirect() ) + workQueue->addDirectWork(work); + else + workQueue->addIndirectWork(work); + + //log.log(4, "Added new work to the queue"); + + // note: no need to remove sock from epoll set, because we use edge-triggered mode with oneshot + // flag (which disables further events after first one has been reported). a sock that is + // closed by a worker is not a problem, because it will automatically be removed from the + // epoll set by the kernel. + // we just need to re-arm the epoll entry upon sock return. + + pollList.removeByFD(sockFD); +} + +/** + * Receive pointer to returned socket through the sockReturnPipe and + * re-add it to the pollList. + */ +void StreamListener::onSockReturn() +{ + Socket* socks[SOCKRETURN_SOCKS_NUM]; + + ssize_t readRes = sockReturnPipe->getReadFD()->read(&socks, sizeof(socks) ); + + for(size_t i=0; ; i++) + { + Socket* currentSock = socks[i]; + + if(unlikely(readRes < (ssize_t)sizeof(Socket*) ) ) + { // recv the rest of the socket pointer + char* currentSockChar = (char*)currentSock; + + sockReturnPipe->getReadFD()->readExact( + ¤tSockChar[readRes], sizeof(Socket*)-readRes); + + readRes = sizeof(Socket*); + } + + //LOG_DEBUG("StreamListener::onSockReturn", 5, + // std::string("Socket returned (through pipe). SockFD: ") + + // StringTk::intToStr(currentSock->getFD() ) ); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = currentSock; + + int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, currentSock->getFD(), &epollEvent); + if(unlikely(epollRes == -1) ) + { // error + log.logErr("Unable to re-arm sock in epoll set: " + System::getErrString() ); + log.log(3, "Disconnecting: " + currentSock->getPeername() ); + + delete(currentSock); + } + else + { + pollList.add(currentSock); + } + + readRes -= sizeof(Socket*); + if(!readRes) + break; + } + +} + +/** + * Does not really check but instead just drops connections that have been idle for some time. + * Note: Actual checking is not performed to avoid timeout delays in the StreamListener. + */ +void StreamListener::rdmaConnIdleCheck() +{ + const unsigned checkIntervalMS = RDMA_CHECK_INTERVAL_MS; + + if(!this->rdmaListenSock) + return; + + if(rdmaCheckT.elapsedMS() < checkIntervalMS) + { + this->rdmaCheckForceCounter = 0; + return; + } + + //LOG_DEBUG("StreamListener::rdmaConnIdleCheck", 5, std::string("Performing check...") ); + + int rdmaListenFD = rdmaListenSock->getFD(); + int numCheckedConns = 0; + IntList disposalFDs; + PollMap* pollMap = pollList.getPollMap(); + + + // walk over all rdma socks, check conn status, delete idle socks and add them to + // the disposal list (note: we do not modify the pollList/pollMap yet) + + for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++) + { + int currentFD = iter->first; + + if(currentFD == sockReturnPipe->getReadFD()->getFD() ) + continue; + + Socket* currentSock = (Socket*)iter->second; + + if(currentSock->getSockType() != NICADDRTYPE_RDMA) + continue; + + if(iter->first == rdmaListenFD) + continue; + + numCheckedConns++; + + // rdma socket => check activity + if(currentSock->getHasActivity() ) + { // conn had activity since last check => reset activity flag + currentSock->resetHasActivity(); + } + else + { // conn was inactive the whole time => disconnect and add to disposal list + log.log(Log_DEBUG, + "Disconnecting idle RDMA connection: " + currentSock->getPeername() ); + + delete(currentSock); + disposalFDs.push_back(iter->first); + } + + } + + + // remove idle socks from pollMap + + for(IntListIter iter = disposalFDs.begin(); iter != disposalFDs.end(); iter++) + { + pollList.removeByFD(*iter); + } + + if (!disposalFDs.empty()) + { // logging + LOG_DEBUG("StreamListener::rdmaConnIdleCheck", 5, std::string("Check results: ") + + std::string("disposed ") + StringTk::int64ToStr(disposalFDs.size() ) + "/" + + StringTk::int64ToStr(numCheckedConns) ); + IGNORE_UNUSED_VARIABLE(numCheckedConns); + } + + + // reset time counter + rdmaCheckT.setToNow(); +} + +void StreamListener::applySocketOptions(StandardSocket* sock) +{ + LogContext log("StreamListener (apply socket options)"); + + try + { + sock->setTcpCork(false); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to disable TcpCork"); + } + + try + { + sock->setTcpNoDelay(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable TcpNoDelay"); + } + + try + { + sock->setSoKeepAlive(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable SoKeepAlive"); + } +} + +/** + * RDMA sockets might signal incoming events that are not actually incoming data, but instead + * handled internally in the IBVSocket class. + * In the latter case, we should not call recv() on the socket, because that would hang. So we need + * this method to decide wheter we put this socket into the incoming data queue or not. + * + * Note: A special case is a connection error that is detected during the false alarm + * check. We return true here, because the disconnect is handled internally so that + * no further steps are required by the caller. + * + * @return true in case of a false alarm (which means that no incoming data for recv is + * immediately available) or in case of an error; false otherwise (i.e. the caller can call recv() + * on this socket without blocking) + */ +bool StreamListener::isFalseAlarm(RDMASocket* sock) +{ + const char* logContext = "StreamListener (incoming RDMA check)"; + + try + { + if(!sock->nonblockingRecvCheck() ) + { // false alarm + LOG_DEBUG(logContext, Log_SPAM, + std::string("Ignoring false alarm from: ") + sock->getPeername() ); + + // we use one-shot mechanism, so we need to re-arm the socket after an event notification + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = sock; + + int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, sock->getFD(), &epollEvent); + if(unlikely(epollRes == -1) ) + { // error + log.logErr("Unable to re-arm socket in epoll set: " + System::getErrString() ); + log.log(Log_NOTICE, "Disconnecting: " + sock->getPeername() ); + + delete(sock); + } + + return true; + } + + // we really have incoming data + + return false; + } + catch(SocketDisconnectException& e) + { // a disconnect here is nothing to worry about + LogContext(logContext).log(Log_DEBUG, + std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContext).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + // conn error occurred + + pollList.remove(sock); + + delete(sock); + + return true; +} + +void StreamListener::deleteAllConns() +{ + PollMap* pollMap = pollList.getPollMap(); + + for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++) + { + delete(iter->second); + } +} diff --git a/common/source/common/components/StreamListener.h b/common/source/common/components/StreamListener.h new file mode 100644 index 0000000..b408907 --- /dev/null +++ b/common/source/common/components/StreamListener.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class StreamListener : public PThread +{ + public: + StreamListener(NicAddressList& localNicList, MultiWorkQueue* workQueue, + unsigned short listenPort); + virtual ~StreamListener(); + + + private: + LogContext log; + StandardSocket* tcpListenSock; + RDMASocket* rdmaListenSock; + + MultiWorkQueue* workQueue; + + int epollFD; + PollList pollList; + Pipe* sockReturnPipe; + + Time rdmaCheckT; + int rdmaCheckForceCounter; + + bool initSockReturnPipe(); + bool initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps); + + virtual void run(); + void listenLoop(); + + void onIncomingStandardConnection(StandardSocket* sock); + void onIncomingRDMAConnection(RDMASocket* sock); + void onIncomingData(Socket* sock); + void onSockReturn(); + void rdmaConnIdleCheck(); + + void applySocketOptions(StandardSocket* sock); + bool isFalseAlarm(RDMASocket* sock); + + void deleteAllConns(); + + + public: + // getters & setters + FileDescriptor* getSockReturnFD() + { + return sockReturnPipe->getWriteFD(); + } + + MultiWorkQueue* getWorkQueue() const + { + return this->workQueue; + } + +}; + diff --git a/common/source/common/components/TimerQueue.cpp b/common/source/common/components/TimerQueue.cpp new file mode 100644 index 0000000..b4a609a --- /dev/null +++ b/common/source/common/components/TimerQueue.cpp @@ -0,0 +1,282 @@ +#include "TimerQueue.h" + +#include + +#include + + +void TimerWorkList::enqueue(std::function fn) +{ + std::unique_lock lock(mutex); + + available.push_back(std::move(fn)); + condition.signal(); +} + +std::pair> TimerWorkList::deque(int timeoutMS) +{ + std::unique_lock lock(mutex); + + if (available.empty()) + { + if (!condition.timedwait(&mutex, timeoutMS)) + return {false, {}}; + } + + if (available.empty()) + return {false, {}}; + + std::function fn = std::move(available.front()); + available.pop_front(); + return {true, std::move(fn)}; +} + +size_t TimerWorkList::waitingItems() const +{ + std::unique_lock lock(mutex); + + return available.size(); +} + + + +TimerWorker::TimerWorker(TimerQueue& queue, TimerWorkList& workList, unsigned id, bool keepAlive): + PThread("TimerWork/" + StringTk::uintToStr(id)), queue(&queue), workList(&workList), + keepAlive(keepAlive), id(id), state(State_STOPPED) +{ +} + +TimerWorker::~TimerWorker() +{ + stop(); +} + +void TimerWorker::start() +{ + if (state.read() == State_RUNNING) + return; + + if (state.read() == State_STOPPING) + join(); + + state.set(State_RUNNING); + + try + { + PThread::start(); + } + catch (...) + { + state.set(State_STOPPED); + throw; + } +} + +void TimerWorker::stop() +{ + if (state.read() == State_STOPPED) + return; + + selfTerminate(); + join(); + state.set(State_STOPPED); +} + +void TimerWorker::run() +{ + static const unsigned suicideTimeoutMs = 10000; + + while (!getSelfTerminate()) + { + // try twice to dequeue an item - once not waiting at all, once waiting the full idle timeout. + // otherwise, an empty queue will cause worker threads to die immediatly, which is not what + // we want. + auto item = workList->deque(0); + if (!item.first) + item = workList->deque(suicideTimeoutMs); + + if (!item.first && !keepAlive) + break; + + if (item.first && item.second) + item.second(); + } + + state.set(State_STOPPING); + queue->deactivateWorker(this); +} + + + +TimerQueue::TimerQueue(unsigned minPoolSize, unsigned maxPoolSize) + : PThread("TimerQ"), entrySequence(0) +{ + for (unsigned i = 0; i < maxPoolSize; i++) + { + workerPool[i].reset(new TimerWorker(*this, workList, i, i < minPoolSize)); + inactiveWorkers[i] = workerPool[i].get(); + } +} + +TimerQueue::~TimerQueue() +{ + if (getID()) + { + selfTerminate(); + condition.signal(); + join(); + } + + for (auto it = workerPool.begin(); it != workerPool.end(); ++it) + { + if (!it->second->isRunning()) + continue; + + it->second->selfTerminate(); + } + + for (auto it = workerPool.begin(); it != workerPool.end(); ++it) + workList.enqueue({}); + + for (auto it = workerPool.begin(); it != workerPool.end(); ++it) + it->second->stop(); +} + +/** + * Enqueues a function for execution at some point in the future. All enqueued items are + * executed by the same thread; to avoid one item blocking other items, each item should + * execute for as little time as possible. + * + * @timeout timeout (in milliseconds) after which to execute the function + * @action function to execute + * @returns handle that can be passed to cancel + */ +TimerQueue::EntryHandle TimerQueue::enqueue(std::chrono::milliseconds timeout, + std::function action) +{ + std::lock_guard lock(mutex); + + std::pair key{Clock::now() + timeout, entrySequence}; + + entrySequence += 1; + queue.insert({key, std::move(action)}); + condition.signal(); + return {*this, key}; +} + +/** + * Cancels a deferred function. Has no effect if the function has already started executing. + * + * @handle handle to deferred function, must be acquired by enqueue + */ +void TimerQueue::cancel(const EntryHandle& handle) +{ + std::lock_guard lock(mutex); + + // assert(handle.queue == &queue) + // assert(queue.count(handle.key)) + queue.erase(handle.key); +} + +void TimerQueue::run() +{ + using namespace std::chrono; + + std::unique_lock lock(mutex); + + static const int idleTimeout = 60 * 1000; + static const int workerRetryTimeout = 10; + static const int activeTimeout = 0; + + int waitTimeout = idleTimeout; + + while (!getSelfTerminate()) + { + // wait one minute if nothing is queued, otherwise wait for at most one minute + if (queue.empty()) + { + condition.timedwait(&mutex, waitTimeout); + } + else + { + auto timeToFirstAction = queue.begin()->first.first - Clock::now(); + + // round up to next millisecond, based on the finest specified duration type + if (duration_cast(timeToFirstAction).count() % 1000000 > 0) + timeToFirstAction += milliseconds(1); + + if (timeToFirstAction.count() > 0) + condition.timedwait( + &mutex, + std::min( + duration_cast(timeToFirstAction).count(), + waitTimeout)); + } + + waitTimeout = idleTimeout; + + // if we have queued items, we can be in one of two situations: + // * we have scheduled the item a short time ago, but no worker thread has dequeued it yet + // * we have scheduled the item a waitTimeout time ago, but no worker has dequeued it yet + // + // in both cases, we will want to wake up (hopefully) enough workers to process the items + // that are currently waiting, since we will only add more to the queue. + if (workList.waitingItems() > 0) + { + if (!requestWorkers(workList.waitingItems())) + waitTimeout = workerRetryTimeout; + } + + if (queue.empty()) + continue; + + bool processedAny = false; + + // do not process more items than we had in the queue to begin with. if we do not limit this, + // an item that requeues itself with delay 0 would never allow the thread to terminate. + for (auto size = queue.size(); size > 0; size--) + { + auto earliestQueued = queue.begin(); + if (earliestQueued->first.first > Clock::now()) + break; + + auto action = std::move(earliestQueued->second); + queue.erase(earliestQueued); + + workList.enqueue(std::move(action)); + processedAny = true; + } + + if (processedAny) + waitTimeout = activeTimeout; + } +} + +bool TimerQueue::requestWorkers(unsigned count) +{ + while (count > 0 && !inactiveWorkers.empty()) + { + TimerWorker* worker = inactiveWorkers.begin()->second; + + try + { + worker->start(); + } + catch (...) + { + return false; + } + + count -= 1; + inactiveWorkers.erase(inactiveWorkers.begin()); + } + + return true; +} + +void TimerQueue::deactivateWorker(TimerWorker* worker) +{ + std::unique_lock lock(mutex); + + inactiveWorkers[worker->getID()] = worker; +} diff --git a/common/source/common/components/TimerQueue.h b/common/source/common/components/TimerQueue.h new file mode 100644 index 0000000..e86d2c7 --- /dev/null +++ b/common/source/common/components/TimerQueue.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include + +#include +#include +#include +#include + +class TimerQueue; + +class TimerWorkList +{ + public: + void enqueue(std::function fn); + + std::pair> deque(int timeoutMS); + + size_t waitingItems() const; + + private: + mutable Mutex mutex; + Condition condition; + + std::deque> available; +}; + +class TimerWorker : public PThread +{ + public: + TimerWorker(TimerQueue& queue, TimerWorkList& workList, unsigned id, bool keepAlive); + ~TimerWorker(); + + void run() override; + + void start(); + void stop(); + + bool isRunning() const + { + return state.read() == State_RUNNING; + } + + unsigned getID() const { return id; } + + private: + enum State { + State_STOPPED, + State_RUNNING, + State_STOPPING, + }; + + TimerQueue* queue; + TimerWorkList* workList; + bool keepAlive; + unsigned id; + + AtomicSizeT state; // holds values from the enum State +}; + +class TimerQueue : public PThread +{ + friend class TimerWorker; + + private: + typedef std::chrono::steady_clock Clock; + + typedef std::chrono::time_point TimePoint; + typedef std::map, std::function> QueueType; + + // if the system's steady_clock is not at least 1ms accurate, bail now. + static_assert( + std::ratio_less_equal::value, + "System clock too coarse"); + + public: + class EntryHandle + { + friend class TimerQueue; + + public: + void cancel() + { + queue->cancel(*this); + } + + private: + EntryHandle(TimerQueue& queue, std::pair key) + : queue(&queue), key(key) + {} + + TimerQueue* queue; + std::pair key; + }; + + TimerQueue(unsigned minPoolSize = 1, unsigned maxPoolSize = 100); + ~TimerQueue(); + + // class is neither copyable nor movable for now, since we do not need that (yet) + TimerQueue(TimerQueue&&) = delete; + TimerQueue& operator=(TimerQueue&&) = delete; + + EntryHandle enqueue(std::chrono::milliseconds timeout, std::function action); + + void cancel(const EntryHandle& handle); + + void run() override; + + private: + Mutex mutex; + Condition condition; + + uint64_t entrySequence; + + QueueType queue; + + TimerWorkList workList; + + // these have to be pointers instead of values because older libstdc++ versions do not have + // map::emplace. + std::map> workerPool; + std::map inactiveWorkers; + + bool requestWorkers(unsigned count); + + void deactivateWorker(TimerWorker* worker); +}; + diff --git a/common/source/common/components/streamlistenerv2/ConnAcceptor.cpp b/common/source/common/components/streamlistenerv2/ConnAcceptor.cpp new file mode 100644 index 0000000..ad32125 --- /dev/null +++ b/common/source/common/components/streamlistenerv2/ConnAcceptor.cpp @@ -0,0 +1,385 @@ +#include +#include +#include +#include +#include "ConnAcceptor.h" + +#include + + +#define EPOLL_EVENTS_NUM (8) /* how many events we can take at once from epoll_wait */ + + +ConnAcceptor::ConnAcceptor(AbstractApp* app, NicAddressList& localNicList, + unsigned short listenPort) + : PThread("ConnAccept"), + app(app), + log("ConnAccept"), + listenPort(listenPort), + localNicCapsUpdated(false) +{ + + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + + int epollCreateSize = 10; // size "10" is just a hint (and is actually ignored since Linux 2.6.8) + + this->epollFD = epoll_create(epollCreateSize); + if(epollFD == -1) + { + throw ComponentInitException(std::string("Error during epoll_create(): ") + + System::getErrString() ); + } + + if(!initSocks() ) + throw ComponentInitException("Unable to initialize socket"); +} + +ConnAcceptor::~ConnAcceptor() +{ + if(epollFD != -1) + close(epollFD); + + SAFE_DELETE(tcpListenSock); + SAFE_DELETE(rdmaListenSock); +} + +bool ConnAcceptor::startRDMASocket(NicListCapabilities* localNicCaps) +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + if(localNicCaps->supportsRDMA) + { // RDMA usage is enabled + try + { + rdmaListenSock = RDMASocket::create().release(); + rdmaListenSock->setTimeouts(cfg->getConnRDMATimeoutConnect(), + cfg->getConnRDMATimeoutFlowSend(), cfg->getConnRDMATimeoutPoll()); + rdmaListenSock->bind(listenPort); + rdmaListenSock->listen(); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = rdmaListenSock; + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, rdmaListenSock->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add RDMA listen sock to epoll set: ") + + System::getErrString() ); + return false; + } + + log.log(3, std::string("Listening for RDMA connections: Port ") + + StringTk::intToStr(listenPort) ); + } + catch(SocketException& e) + { + log.logErr(std::string("RDMA socket: ") + e.what() ); + SAFE_DELETE(rdmaListenSock); + return false; + } + } + return true; +} + +bool ConnAcceptor::initSocks() +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + + rdmaListenSock = NULL; + tcpListenSock = NULL; + + // RDMA + if (!startRDMASocket(&localNicCaps)) + return false; + + // TCP + try + { + tcpListenSock = new StandardSocket(PF_INET, SOCK_STREAM); + tcpListenSock->setSoReuseAddr(true); + int bufsize = cfg->getConnTCPRcvBufSize(); + if (bufsize > 0) + tcpListenSock->setSoRcvBuf(bufsize); + tcpListenSock->bind(listenPort); + tcpListenSock->listen(); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = tcpListenSock; + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, tcpListenSock->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add TCP listen sock to epoll set: ") + + System::getErrString() ); + return false; + } + + log.log(Log_NOTICE, std::string("Listening for TCP connections: Port ") + + StringTk::intToStr(listenPort) ); + } + catch(SocketException& e) + { + log.logErr(std::string("TCP socket: ") + e.what() ); + SAFE_DELETE(tcpListenSock); + return false; + } + + return true; +} + +void ConnAcceptor::updateLocalNicList(NicAddressList& localNicList) +{ + // we don't store the localNicList, just use it to update localNicCaps + NicListCapabilities localNicCaps; + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + const std::lock_guard lock(localNicCapsMutex); + this->localNicCaps = localNicCaps; + localNicCapsUpdated = true; +} + +void ConnAcceptor::run() +{ + try + { + registerSignalHandler(); + + listenLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void ConnAcceptor::handleNewLocalNicCaps() +{ + NicListCapabilities localNicCaps; + bool localNicCapsUpdated; + { + const std::lock_guard lock(localNicCapsMutex); + localNicCapsUpdated = this->localNicCapsUpdated; + if (localNicCapsUpdated) + { + localNicCaps = this->localNicCaps; + this->localNicCapsUpdated = false; + } + } + + if (localNicCapsUpdated) + { + if (localNicCaps.supportsRDMA) + { + if (!rdmaListenSock) + startRDMASocket(&localNicCaps); + } + else if (rdmaListenSock) + { + log.log(Log_NOTICE, std::string("Shutdown RDMA listen sock...")); + + if(epoll_ctl(epollFD, EPOLL_CTL_DEL, rdmaListenSock->getFD(), NULL) == -1) + log.logErr(std::string("Unable to remove RDMA listen sock from epoll set: ") + + System::getErrString() ); + SAFE_DELETE(rdmaListenSock); + + log.log(Log_NOTICE, std::string("Shutdown RDMA listen sock complete")); + } + } +} + +void ConnAcceptor::listenLoop() +{ + const int epollTimeoutMS = 3000; + + struct epoll_event epollEvents[EPOLL_EVENTS_NUM]; + + // (just to have these values on the stack...) + const int epollFD = this->epollFD; + RDMASocket* rdmaListenSock; + StandardSocket* tcpListenSock; + + // wait for incoming events and handle them... + + while(!getSelfTerminate() ) + { + + handleNewLocalNicCaps(); + rdmaListenSock = this->rdmaListenSock; + tcpListenSock = this->tcpListenSock; + + //log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") + + // StringTk::uintToStr(pollArrayLen) ); + + int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS); + + if(unlikely(epollRes < 0) ) + { // error occurred + if(errno == EINTR) // ignore interruption, because the debugger causes this + continue; + + log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() ); + break; + } + + // handle incoming connection attempts + for(size_t i=0; i < (size_t)epollRes; i++) + { + struct epoll_event* currentEvent = &epollEvents[i]; + Pollable* currentPollable = (Pollable*)currentEvent->data.ptr; + + //log.log(Log_DEBUG, std::string("Incoming data on FD: ") + + // StringTk::intToStr(pollArray[i].fd) ); // debug in + + if(currentPollable == rdmaListenSock) + onIncomingRDMAConnection(rdmaListenSock); + else + if(currentPollable == tcpListenSock) + onIncomingStandardConnection(tcpListenSock); + else + { // unknown connection => should never happen + log.log(Log_WARNING, "Should never happen: Ignoring event for unknown connection. " + "FD: " + StringTk::uintToStr(currentPollable->getFD() ) ); + } + } + + } +} + +/** + * Accept the incoming connection and add new socket to StreamListenerV2 queue. + * + * Note: This is for standard sockets like TCP. + */ +void ConnAcceptor::onIncomingStandardConnection(StandardSocket* sock) +{ + try + { + struct sockaddr_in peerAddr; + socklen_t peerAddrLen = sizeof(peerAddr); + + StandardSocket* acceptedSock = + (StandardSocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen); + + // (note: level Log_DEBUG to avoid spamming the log until we have log topics) + log.log(Log_DEBUG, std::string("Accepted new connection from ") + + Socket::endpointAddrToStr(&peerAddr) + + std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) + + std::string("]") ); + + applySocketOptions(acceptedSock); + + // hand the socket over to a stream listener + + StreamListenerV2* listener = app->getStreamListenerByFD(acceptedSock->getFD() ); + StreamListenerV2::SockReturnPipeInfo returnInfo( + StreamListenerV2::SockPipeReturn_NEWCONN, acceptedSock); + + listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) ); + + } + catch(SocketException& se) + { + log.logErr(std::string("Trying to continue after connection accept error: ") + + se.what() ); + } +} + +/** + * Accept the incoming RDMA connection and add new socket to StreamListenerV2 queue. + */ +void ConnAcceptor::onIncomingRDMAConnection(RDMASocket* sock) +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + /* accept the incoming connection (and loop until no more delayed events are waiting on + this socket) */ + // (Note: RDMASockets use this internally also to handle other kindes of events) */ + + + /* loop: check whether this is a false alarm (i.e. an event that is handled internally by the + socket when we call accept() ), try to accept a new connection (if + available) and also check for further events after accept */ + + while(sock->checkDelayedEvents() ) + { + try + { + struct sockaddr_in peerAddr; + socklen_t peerAddrLen = sizeof(peerAddr); + + sock->setConnectionRejectionRate(cfg->getConnectionRejectionRate()); + RDMASocket* acceptedSock = + (RDMASocket*)sock->accept( (struct sockaddr*)&peerAddr, &peerAddrLen); + + // note: RDMASocket::accept() might return NULL (which is not an error) + if(!acceptedSock) + { + log.log(Log_DEBUG, "Ignoring an internal event on the listening RDMA socket"); + continue; + } + + acceptedSock->setTimeouts(cfg->getConnRDMATimeoutConnect(), + cfg->getConnRDMATimeoutFlowSend(), cfg->getConnRDMATimeoutPoll()); + + // (note: level Log_DEBUG to avoid spamming the log until we have log topics) + log.log(Log_DEBUG, std::string("Accepted new RDMA connection from ") + + Socket::endpointAddrToStr(&peerAddr) + + std::string(" [SockFD: ") + StringTk::intToStr(acceptedSock->getFD() ) + + std::string("]") ); + + // hand the socket over to a stream listener + + StreamListenerV2* listener = app->getStreamListenerByFD(acceptedSock->getFD() ); + StreamListenerV2::SockReturnPipeInfo returnInfo( + StreamListenerV2::SockPipeReturn_NEWCONN, acceptedSock); + + listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) ); + + } + catch(SocketException& se) + { + log.logErr(std::string("Trying to continue after RDMA connection accept error: ") + + se.what() ); + } + + } +} + +/** + * Apply special socket options to a new incoming standard socket connection. + */ +void ConnAcceptor::applySocketOptions(StandardSocket* sock) +{ + LogContext log("ConnAcceptor (apply socket options)"); + + try + { + sock->setTcpCork(false); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to disable TcpCork. " + "FD: " + StringTk::uintToStr(sock->getFD() ) + ". " + "Msg: " + std::string(e.what() ) ); + } + + try + { + sock->setTcpNoDelay(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable TcpNoDelay. " + "FD: " + StringTk::uintToStr(sock->getFD() ) + ". " + "Msg: " + std::string(e.what() ) ); + } + + try + { + sock->setSoKeepAlive(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable SoKeepAlive. " + "FD: " + StringTk::uintToStr(sock->getFD() ) + ". " + "Msg: " + std::string(e.what() ) ); + } +} + diff --git a/common/source/common/components/streamlistenerv2/ConnAcceptor.h b/common/source/common/components/streamlistenerv2/ConnAcceptor.h new file mode 100644 index 0000000..124d13a --- /dev/null +++ b/common/source/common/components/streamlistenerv2/ConnAcceptor.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class AbstractApp; // forward declaration + + +class ConnAcceptor : public PThread +{ + public: + ConnAcceptor(AbstractApp* app, NicAddressList& localNicList, unsigned short listenPort); + virtual ~ConnAcceptor(); + + + private: + AbstractApp* app; + LogContext log; + unsigned short listenPort; + + StandardSocket* tcpListenSock; + RDMASocket* rdmaListenSock; + + int epollFD; + + NicListCapabilities localNicCaps; + bool localNicCapsUpdated; + Mutex localNicCapsMutex; + + bool initSocks(); + bool startRDMASocket(NicListCapabilities* localNicCaps); + void handleNewLocalNicCaps(); + + virtual void run(); + void listenLoop(); + + void onIncomingStandardConnection(StandardSocket* sock); + void onIncomingRDMAConnection(RDMASocket* sock); + + void applySocketOptions(StandardSocket* sock); + + public: + void updateLocalNicList(NicAddressList& nicList); + // getters & setters + +}; + diff --git a/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.cpp b/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.cpp new file mode 100644 index 0000000..25e8573 --- /dev/null +++ b/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.cpp @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include "IncomingPreprocessedMsgWork.h" + + +void IncomingPreprocessedMsgWork::process(char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen) +{ + const char* logContextStr = "Work (process incoming msg)"; + + const int recvTimeoutMS = 5000; + + unsigned numReceived = NETMSG_HEADER_LENGTH; // (header actually received by stream listener) + std::unique_ptr msg; + + + try + { + // attach stats to sock (stream listener already received the msg header) + + stats.incVals.netRecvBytes += NETMSG_HEADER_LENGTH; + sock->setStats(&stats); + + + // make sure msg length fits into our receive buffer + + unsigned msgLength = msgHeader.msgLength; + unsigned msgPayloadLength = msgLength - numReceived; + + if(unlikely(msgPayloadLength > bufInLen) ) + { // message too big + LogContext(logContextStr).log(Log_NOTICE, + std::string("Received a message that is too large. Disconnecting: ") + + sock->getPeername() ); + + sock->unsetStats(); + invalidateConnection(sock); + + return; + } + + // receive the message payload + + if(msgPayloadLength) + sock->recvExactT(bufIn, msgPayloadLength, 0, recvTimeoutMS); + + // we got the complete message buffer => create msg object + + AbstractApp* app = PThread::getCurrentThreadApp(); + auto cfg = app->getCommonConfig(); + auto netMessageFactory = app->getNetMessageFactory(); + + msg = netMessageFactory->createFromPreprocessedBuf(&msgHeader, bufIn, msgPayloadLength); + + if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) ) + { // message invalid + LogContext(logContextStr).log(Log_NOTICE, + std::string("Received an invalid message. Disconnecting: ") + sock->getPeername() ); + + sock->unsetStats(); + invalidateConnection(sock); + return; + } + + // process the received msg + + bool processRes = false; + + if(likely(!cfg->getConnAuthHash() || + sock->getIsAuthenticated() || + (msg->getMsgType() == NETMSGTYPE_AuthenticateChannel) ) ) + { // auth disabled or channel is auth'ed or this is an auth msg => process + NetMessage::ResponseContext rctx(NULL, sock, bufOut, bufOutLen, &stats); + LOG_DBG(COMMUNICATION, DEBUG, "Beginning message processing.", sock->getPeername(), + msg->getMsgTypeStr()); + processRes = msg->processIncoming(rctx); + } + else + LogContext(logContextStr).log(Log_NOTICE, + std::string("Rejecting message from unauthenticated peer: ") + sock->getPeername() ); + + // processing completed => cleanup + + bool needSockRelease = msg->getReleaseSockAfterProcessing(); + + msg.reset(); + + if(!needSockRelease) + return; // sock release was already done within msg->processIncoming() method + + if(unlikely(!processRes) ) + { // processIncoming encountered messaging error => invalidate connection + LogContext(logContextStr).log(Log_NOTICE, + std::string("Problem encountered during processing of a message. Disconnecting: ") + + sock->getPeername() ); + + invalidateConnection(sock); + + return; + } + + releaseSocket(app, &sock, NULL); + + return; + + } + catch(SocketTimeoutException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection timed out: ") + sock->getPeername() ); + } + catch(SocketDisconnectException& e) + { + // (note: level Log_DEBUG here to avoid spamming the log until we have log topics) + LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + // socket exception occurred => cleanup + + if(msg && msg->getReleaseSockAfterProcessing() ) + { + sock->unsetStats(); + invalidateConnection(sock); + } +} + +/** + * Release a valid incoming socket by returning it to the StreamListenerV2. + * + * Intended to be called + * a) either directly from within a msg->processIncoming() method (e.g. after sending an early + * response to a client) + * b) or by IncomingPreprocessedMsgWork::process() for cleanup after msg->processIncoming() + * returned true. + * + * @param sock pointer will be set to NULL to avoid accidental usage by caller after calling this + * method. + * @param msg may be NULL; if provided, msg->setReleaseSockAfterProcessing(false) will be called. + */ +void IncomingPreprocessedMsgWork::releaseSocket(AbstractApp* app, Socket** sock, NetMessage* msg) +{ + Socket* sockCopy = *sock; // copy of sock pointer, so that we can set caller's sock to NULL + + *sock = NULL; + + if(msg) + msg->setReleaseSockAfterProcessing(false); + + sockCopy->unsetStats(); + + // check for immediate data on rdma sockets + + bool gotImmediateDataAvailable = checkRDMASocketImmediateData(app, sockCopy); + if(gotImmediateDataAvailable) + { // immediate data available + // (socket was returned to stream listener by checkRDMASocketImmediateData() ) + return; + } + + // no immediate data available => return the socket to a stream listener + + StreamListenerV2* listener = app->getStreamListenerByFD(sockCopy->getFD() ); + StreamListenerV2::SockReturnPipeInfo returnInfo( + StreamListenerV2::SockPipeReturn_MSGDONE_NOIMMEDIATE, sockCopy); + + listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) ); +} + +void IncomingPreprocessedMsgWork::invalidateConnection(Socket* sock) +{ + const int recvTimeoutMS = 1; + + try + { + sock->shutdownAndRecvDisconnect(recvTimeoutMS); + } + catch(SocketException& e) + { + // don't care, because the conn is invalid anyway + } + + delete(sock); +} + +/** + * Checks whether there is more immediate data available on an RDMASocket. + * + * If immediate data is available, we need to notify the stream listener about it. + * The reason for this is the fact that the filedescriptor will only send a notification + * in case of new incoming data, so we need to check for old incoming data (that has already + * been buffered internally by our RDMASocket impl) here. + * + * @return true if immediate data is available or if a conn error occurred (meaning the socket + * should not be returned to the streamListener by the caller) + */ +bool IncomingPreprocessedMsgWork::checkRDMASocketImmediateData(AbstractApp* app, Socket* sock) +{ + const char* logContextStr = "Work (incoming data => check RDMA immediate data)"; + + + // the immediate data check only applies to RDMA sockets + + if(sock->getSockType() != NICADDRTYPE_RDMA) + return false; + + + // we got a RDMASocket + + try + { + RDMASocket* rdmaSock = (RDMASocket*)sock; + + if(!rdmaSock->nonblockingRecvCheck() ) + return false; // no more data available at the moment + + + // we have immediate data available => inform the stream listener + + LOG_DEBUG(logContextStr, Log_SPAM, + std::string("Got immediate data: ") + sock->getPeername() ); + + StreamListenerV2* listener = app->getStreamListenerByFD(sock->getFD() ); + StreamListenerV2::SockReturnPipeInfo returnInfo( + StreamListenerV2::SockPipeReturn_MSGDONE_WITHIMMEDIATE, sock); + + listener->getSockReturnFD()->write(&returnInfo, sizeof(returnInfo) ); + + return true; + } + catch(SocketDisconnectException& e) + { + LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + // conn error occurred + + delete(sock); + + return true; +} + + + diff --git a/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h b/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h new file mode 100644 index 0000000..13c6111 --- /dev/null +++ b/common/source/common/components/streamlistenerv2/IncomingPreprocessedMsgWork.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class IncomingPreprocessedMsgWork : public Work +{ + public: + /** + * Note: Be aware that this class is only for stream connections that need to be returned + * to a StreamListenerV2 after processing. + * + * @param msgHeader contents will be copied + */ + IncomingPreprocessedMsgWork(AbstractApp* app, Socket* sock, NetMessageHeader* msgHeader) + { + this->app = app; + this->sock = sock; + this->msgHeader = *msgHeader; + } + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + static void releaseSocket(AbstractApp* app, Socket** sock, NetMessage* msg); + static void invalidateConnection(Socket* sock); + static bool checkRDMASocketImmediateData(AbstractApp* app, Socket* sock); + + + private: + AbstractApp* app; + Socket* sock; + NetMessageHeader msgHeader; +}; + diff --git a/common/source/common/components/streamlistenerv2/StreamListenerV2.cpp b/common/source/common/components/streamlistenerv2/StreamListenerV2.cpp new file mode 100644 index 0000000..53082c9 --- /dev/null +++ b/common/source/common/components/streamlistenerv2/StreamListenerV2.cpp @@ -0,0 +1,496 @@ +#include +#include +#include +#include "StreamListenerV2.h" + +#include + + +#define EPOLL_EVENTS_NUM (512) /* make it big to avoid starvation of higher FDs */ +#define RDMA_CHECK_FORCE_POLLLOOPS (7200) /* to avoid calling the check routine in each loop */ +#define RDMA_CHECK_INTERVAL_MS (150*60*1000) /* 150mins (must be more than double of the + client-side idle disconnect interval to avoid cases where + server disconnects first) */ +#define SOCKRETURN_SOCKS_NUM (32) + + +StreamListenerV2::StreamListenerV2(const std::string& listenerID, AbstractApp* app, + StreamListenerWorkQueue* workQueue) + : PThread(listenerID), + app(app), + log("StreamLisV2"), + workQueue(workQueue), + rdmaCheckForceCounter(0), + useAggressivePoll(false) +{ + int epollCreateSize = 10; // size "10" is just a hint (and is actually ignored since Linux 2.6.8) + this->epollFD = epoll_create(epollCreateSize); + if(epollFD == -1) + { + throw ComponentInitException(std::string("Error during epoll_create(): ") + + System::getErrString() ); + } + + if(!initSockReturnPipe() ) + throw ComponentInitException("Unable to initialize sock return pipe"); +} + +StreamListenerV2::~StreamListenerV2() +{ + delete(sockReturnPipe); + + deleteAllConns(); + + if(epollFD != -1) + close(epollFD); +} + +bool StreamListenerV2::initSockReturnPipe() +{ + this->sockReturnPipe = new Pipe(false, true); + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = sockReturnPipe->getReadFD(); + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, sockReturnPipe->getReadFD()->getFD(), &epollEvent) == -1) + { + log.logErr(std::string("Unable to add sock return pipe (read side) to epoll set: ") + + System::getErrString() ); + return false; + } + + return true; +} + +void StreamListenerV2::run() +{ + try + { + registerSignalHandler(); + + listenLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void StreamListenerV2::listenLoop() +{ + const int epollTimeoutMS = useAggressivePoll ? 0 : 3000; + + struct epoll_event epollEvents[EPOLL_EVENTS_NUM]; + + // (just to have these values on the stack...) + const int epollFD = this->epollFD; + FileDescriptor* sockReturnPipeReadEnd = this->sockReturnPipe->getReadFD(); + + bool runRDMAConnIdleCheck = false; // true just means we call the method (not enforce the check) + + // wait for incoming events and handle them... + + while(!getSelfTerminate() ) + { + //log.log(Log_DEBUG, std::string("Before poll(). pollArrayLen: ") + + // StringTk::uintToStr(pollArrayLen) ); + + int epollRes = epoll_wait(epollFD, epollEvents, EPOLL_EVENTS_NUM, epollTimeoutMS); + + if(unlikely(epollRes < 0) ) + { // error occurred + if(errno == EINTR) // ignore interruption, because the debugger causes this + continue; + + log.logErr(std::string("Unrecoverable epoll_wait error: ") + System::getErrString() ); + break; + } + else + if(unlikely(!epollRes || (rdmaCheckForceCounter++ > RDMA_CHECK_FORCE_POLLLOOPS) ) ) + { // epollRes==0 is nothing to worry about, just idle + + // note: we can't run idle check here directly because the check might modify the + // poll set, which will be accessed in the loop below + runRDMAConnIdleCheck = true; + } + + // handle incoming data & connection attempts + for(size_t i=0; i < (size_t)epollRes; i++) + { + struct epoll_event* currentEvent = &epollEvents[i]; + Pollable* currentPollable = (Pollable*)currentEvent->data.ptr; + + if(currentPollable == sockReturnPipeReadEnd) + onSockReturn(); + else + onIncomingData( (Socket*)currentPollable); + } + + if(unlikely(runRDMAConnIdleCheck) ) + { // note: whether check actually happens depends on elapsed time since last check + runRDMAConnIdleCheck = false; + rdmaConnIdleCheck(); + } + + } +} + + +/** + * Receive msg header and add the socket to the work queue. + */ +void StreamListenerV2::onIncomingData(Socket* sock) +{ + // check whether this is just a false alarm from a RDMASocket + if( (sock->getSockType() == NICADDRTYPE_RDMA) && + isFalseAlarm( (RDMASocket*)sock) ) + { + return; + } + + try + { + const int recvTimeoutMS = 5000; + + char msgHeaderBuf[NETMSG_HEADER_LENGTH]; + NetMessageHeader msgHeader; + + // receive & deserialize message header + + sock->recvExactT(msgHeaderBuf, NETMSG_HEADER_LENGTH, 0, recvTimeoutMS); + + NetMessage::deserializeHeader(msgHeaderBuf, NETMSG_HEADER_LENGTH, &msgHeader); + + /* (note on header verification: we leave header verification work to the worker threads to + save CPU cycles in the stream listener and instead just take what we need to know here, no + matter whether the header is valid or not.) */ + + // create work and add it to queue + + //log.log(Log_DEBUG, "Creating new work for to the queue"); + + IncomingPreprocessedMsgWork* work = new IncomingPreprocessedMsgWork(app, sock, &msgHeader); + + int sockFD = sock->getFD(); /* note: we store this here for delayed pollList removal, because + worker thread might disconnect, so the sock gets deleted by the worker and thus "sock->" + pointer becomes invalid */ + + sock->setHasActivity(); // mark sock as active (for idle disconnect check) + + // (note: userID intToStr (not uint) because default userID (~0) looks better this way) + LOG_DEBUG("StreamListenerV2::onIncomingData", Log_DEBUG, + "Incoming message: " + netMessageTypeToStr(msgHeader.msgType) + "; " + "from: " + sock->getPeername() + "; " + "userID: " + StringTk::intToStr(msgHeader.msgUserID) + + (msgHeader.msgTargetID + ? "; targetID: " + StringTk::uintToStr(msgHeader.msgTargetID) + : "") ); + + if (sock->getIsDirect()) + getWorkQueue(msgHeader.msgTargetID)->addDirectWork(work, msgHeader.msgUserID); + else + getWorkQueue(msgHeader.msgTargetID)->addIndirectWork(work, msgHeader.msgUserID); + + /* notes on sock handling: + *) no need to remove sock from epoll set, because we use edge-triggered mode with + oneshot flag (which disables further events after first one has been reported). + *) a sock that is closed by a worker is not a problem, because it will automatically be + removed from the epoll set by the kernel. + *) we just need to re-arm the epoll entry upon sock return. */ + + pollList.removeByFD(sockFD); + + return; + + } + catch(SocketTimeoutException& e) + { + log.log(Log_NOTICE, "Connection timed out: " + sock->getPeername() ); + } + catch(SocketDisconnectException& e) + { + // (note: level Log_DEBUG here to avoid spamming the log until we have log topics) + log.log(Log_DEBUG, std::string(e.what() ) ); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, + "Connection error: " + sock->getPeername() + ": " + std::string(e.what() ) ); + } + + // socket exception occurred => cleanup + + pollList.removeByFD(sock->getFD() ); + + IncomingPreprocessedMsgWork::invalidateConnection(sock); // also includes delete(sock) +} + +/** + * Receive pointer to returned socket through the sockReturnPipe and re-add it to the pollList. + */ +void StreamListenerV2::onSockReturn() +{ + SockReturnPipeInfo returnInfos[SOCKRETURN_SOCKS_NUM]; + + // try to get multiple returnInfos at once (if available) + + ssize_t readRes = sockReturnPipe->getReadFD()->read(&returnInfos, sizeof(SockReturnPipeInfo) ); + + // loop: walk over each info and handle the contained socket + + for(size_t i=0; ; i++) + { + SockReturnPipeInfo& currentReturnInfo = returnInfos[i]; + + // make sure we have a complete SockReturnPipeInfo + + if(unlikely(readRes < (ssize_t)sizeof(SockReturnPipeInfo) ) ) + { // only got a partial SockReturnPipeInfo => recv the rest + char* currentReturnInfoChar = (char*)¤tReturnInfo; + + sockReturnPipe->getReadFD()->readExact( + ¤tReturnInfoChar[readRes], sizeof(SockReturnPipeInfo)-readRes); + + readRes = sizeof(SockReturnPipeInfo); + } + + // handle returnInfo depending on contained returnType... + + Socket* currentSock = currentReturnInfo.sock; + SockPipeReturnType returnType = currentReturnInfo.returnType; + + switch(returnType) + { + case SockPipeReturn_MSGDONE_NOIMMEDIATE: + { // most likely case: worker is done with a msg and now returns the sock to the epoll set + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = currentSock; + + int epollRes = epoll_ctl(epollFD, EPOLL_CTL_MOD, currentSock->getFD(), &epollEvent); + + if(likely(!epollRes) ) + { // sock was successfully re-armed in epoll set + pollList.add(currentSock); + + break; // break out of switch + } + else + if(errno != ENOENT) + { // error + log.logErr("Unable to re-arm sock in epoll set. " + "FD: " + StringTk::uintToStr(currentSock->getFD() ) + "; " + "SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + "; " + "SysErr: " + System::getErrString() ); + log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() ); + + delete(currentSock); + + break; // break out of switch + } + + /* for ENOENT, we fall through to NEWCONN, because this socket appearently wasn't + used with this stream listener yet, so we need to add it (instead of modify it) */ + + } // might fall through here on ENOENT + BEEGFS_FALLTHROUGH; + + case SockPipeReturn_NEWCONN: + { // new conn from ConnAcceptor (or wasn't used with this stream listener yet) + + // add new socket file descriptor to epoll set + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = currentSock; + + int epollRes = epoll_ctl(epollFD, EPOLL_CTL_ADD, currentSock->getFD(), &epollEvent); + if(likely(!epollRes) ) + { // socket was successfully added to epoll set + pollList.add(currentSock); + } + else + { // adding to epoll set failed => unrecoverable error + log.logErr("Unable to add sock to epoll set. " + "FD: " + StringTk::uintToStr(currentSock->getFD() ) + " " + "SockTypeNum: " + StringTk::uintToStr(currentSock->getSockType() ) + " " + "SysErr: " + System::getErrString() ); + log.log(Log_NOTICE, "Disconnecting: " + currentSock->getPeername() ); + + delete(currentSock); + } + + } break; + + case SockPipeReturn_MSGDONE_WITHIMMEDIATE: + { // special case: worker detected that immediate data is available after msg processing + // data immediately available => recv header and so on + onIncomingData(currentSock); + } break; + + default: + { // should never happen: unknown/unhandled returnType + log.logErr("Should never happen: " + "Unknown socket return type: " + StringTk::uintToStr(returnType) ); + } break; + + } // end of switch(returnType) + + + + readRes -= sizeof(SockReturnPipeInfo); + if(!readRes) + break; // all received returnInfos have been processed + + } // end of "for each received SockReturnPipeInfo" loop + +} + +/** + * Does not really check but instead just drops connections that have been idle for some time. + * Note: Actual checking is not performed to avoid timeout delays in the StreamListenerV2. + */ +void StreamListenerV2::rdmaConnIdleCheck() +{ + const unsigned checkIntervalMS = RDMA_CHECK_INTERVAL_MS; + + if(rdmaCheckT.elapsedMS() < checkIntervalMS) + { + this->rdmaCheckForceCounter = 0; + return; + } + + int numCheckedConns = 0; + IntList disposalFDs; + PollMap* pollMap = pollList.getPollMap(); + + + // walk over all rdma socks, check conn status, delete idle socks and add them to + // the disposal list (note: we do not modify the pollList/pollMap yet) + + for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++) + { + Socket* currentSock = (Socket*)iter->second; + + if(currentSock->getSockType() != NICADDRTYPE_RDMA) + continue; + + numCheckedConns++; + + // rdma socket => check activity + if(currentSock->getHasActivity() ) + { // conn had activity since last check => reset activity flag + currentSock->resetHasActivity(); + } + else + { // conn was inactive the whole time => disconnect and add to disposal list + log.log(Log_DEBUG, + "Disconnecting idle RDMA connection: " + currentSock->getPeername() ); + + delete(currentSock); + disposalFDs.push_back(iter->first); + } + + } + + + // remove idle socks from pollMap + + for(IntListIter iter = disposalFDs.begin(); iter != disposalFDs.end(); iter++) + { + pollList.removeByFD(*iter); + } + + if (!disposalFDs.empty()) + { // logging + LOG_DEBUG("StreamListenerV2::rdmaConnIdleCheck", Log_SPAM, std::string("Check results: ") + + std::string("disposed ") + StringTk::int64ToStr(disposalFDs.size() ) + "/" + + StringTk::int64ToStr(numCheckedConns) ); + IGNORE_UNUSED_VARIABLE(numCheckedConns); + } + + + // reset time counter + rdmaCheckT.setToNow(); +} + +/** + * RDMA sockets might signal incoming events that are not actually incoming data, but instead + * handled internally in the IBVSocket class. + * In the latter case, we should not call recv() on the socket, because that would hang. So we need + * this method to decide wheter we put this socket into the incoming data queue or not. + * + * Note: A special case is a connection error that is detected during the false alarm + * check. We return true here, because the disconnect is handled internally so that + * no further steps are required by the caller. + * + * @return true in case of a false alarm (which means that no incoming data for recv is + * immediately available) or in case of an error; false otherwise (i.e. the caller can call recv() + * on this socket without blocking) + */ +bool StreamListenerV2::isFalseAlarm(RDMASocket* sock) +{ + const char* logContext = "StreamListenerV2 (incoming RDMA check)"; + + try + { + if(!sock->nonblockingRecvCheck() ) + { // false alarm + LOG_DEBUG(logContext, Log_SPAM, + std::string("Ignoring false alarm from: ") + sock->getPeername() ); + + // we use one-shot mechanism, so we need to re-arm the socket after an event notification + + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN | EPOLLONESHOT | EPOLLET; + epollEvent.data.ptr = sock; + + int epollRes = epoll_ctl(this->epollFD, EPOLL_CTL_MOD, sock->getFD(), &epollEvent); + if(unlikely(epollRes == -1) ) + { // error + log.logErr("Unable to re-arm socket in epoll set: " + System::getErrString() ); + log.log(Log_NOTICE, "Disconnecting: " + sock->getPeername() ); + + delete(sock); + } + + return true; + } + + // we really have incoming data + + return false; + } + catch(SocketDisconnectException& e) + { // a disconnect here is nothing to worry about + LogContext(logContext).log(Log_DEBUG, + std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContext).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + // conn error occurred + + pollList.remove(sock); + + delete(sock); + + return true; +} + +void StreamListenerV2::deleteAllConns() +{ + PollMap* pollMap = pollList.getPollMap(); + + for(PollMapIter iter = pollMap->begin(); iter != pollMap->end(); iter++) + { + delete(iter->second); + } +} diff --git a/common/source/common/components/streamlistenerv2/StreamListenerV2.h b/common/source/common/components/streamlistenerv2/StreamListenerV2.h new file mode 100644 index 0000000..b08a570 --- /dev/null +++ b/common/source/common/components/streamlistenerv2/StreamListenerV2.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class AbstractApp; // forward declaration + + +class StreamListenerV2 : public PThread +{ + public: + // type definitions... + + enum SockPipeReturnType + { + SockPipeReturn_NEWCONN = 0, /* a new connection from ConnAcceptor */ + SockPipeReturn_MSGDONE_NOIMMEDIATE = 1, /* returned from msg worker, no immediate data */ + SockPipeReturn_MSGDONE_WITHIMMEDIATE = 2, /* from worker with immediate data available */ + }; + + /** + * This is what we will send over the socket return pipe + */ + struct SockReturnPipeInfo + { + /** + * Standard constructor for creating/sending a returnInfo. + */ + SockReturnPipeInfo(SockPipeReturnType returnType, Socket* sock) : + returnType(returnType), sock(sock) {} + + /** + * For receiving only (no initialization of members). + */ + SockReturnPipeInfo() {} + + SockPipeReturnType returnType; + Socket* sock; + }; + + + public: + StreamListenerV2(const std::string& listenerID, AbstractApp* app, + StreamListenerWorkQueue* workQueue); + virtual ~StreamListenerV2(); + + + private: + AbstractApp* app; + LogContext log; + + StreamListenerWorkQueue* workQueue; // may be NULL in derived classes with own getWorkQueue() + + int epollFD; + PollList pollList; + Pipe* sockReturnPipe; + + Time rdmaCheckT; + int rdmaCheckForceCounter; + + bool useAggressivePoll; // true to not sleep on epoll and burn CPU + + bool initSockReturnPipe(); + bool initSocks(unsigned short listenPort, NicListCapabilities* localNicCaps); + + virtual void run(); + void listenLoop(); + + void onIncomingData(Socket* sock); + void onSockReturn(); + void rdmaConnIdleCheck(); + + bool isFalseAlarm(RDMASocket* sock); + + void deleteAllConns(); + + + public: + // getters & setters + FileDescriptor* getSockReturnFD() const + { + return sockReturnPipe->getWriteFD(); + } + + /** + * Only effective when set before running this component. + */ + void setUseAggressivePoll() + { + this->useAggressivePoll = true; + } + + + protected: + // getters & setters + + /** + * Note: This default implementation just ignores the given targetID. Derived classes will + * make use of it. + */ + virtual StreamListenerWorkQueue* getWorkQueue(uint16_t targetID) const + { + return this->workQueue; + } + +}; + diff --git a/common/source/common/components/worker/DecAtomicWork.cpp b/common/source/common/components/worker/DecAtomicWork.cpp new file mode 100644 index 0000000..8958d97 --- /dev/null +++ b/common/source/common/components/worker/DecAtomicWork.cpp @@ -0,0 +1 @@ +#include "IncAtomicWork.h" diff --git a/common/source/common/components/worker/DecAtomicWork.h b/common/source/common/components/worker/DecAtomicWork.h new file mode 100644 index 0000000..c5af0fb --- /dev/null +++ b/common/source/common/components/worker/DecAtomicWork.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +template +class DecAtomicWork : public Work +{ + public: + DecAtomicWork(Atomic* atomicValue) + { + this->atomicValue = atomicValue; + } + + virtual ~DecAtomicWork() { }; + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) + { + LOG_DEBUG("DecAtomicWork", Log_DEBUG, + "Processing DecAtomicWork"); + + // increment counter + this->atomicValue->decrease(); + + LOG_DEBUG("DecAtomicWork", Log_DEBUG, + "Processed DecAtomicWork"); + } + + private: + Atomic* atomicValue; +}; + diff --git a/common/source/common/components/worker/DummyWork.h b/common/source/common/components/worker/DummyWork.h new file mode 100644 index 0000000..d04325b --- /dev/null +++ b/common/source/common/components/worker/DummyWork.h @@ -0,0 +1,14 @@ +#pragma once + +#include "Work.h" + +class DummyWork : public Work +{ + public: + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) + { + // nothing to be done here + } +}; + diff --git a/common/source/common/components/worker/GetQuotaInfoWork.cpp b/common/source/common/components/worker/GetQuotaInfoWork.cpp new file mode 100644 index 0000000..510c847 --- /dev/null +++ b/common/source/common/components/worker/GetQuotaInfoWork.cpp @@ -0,0 +1,208 @@ +#include "GetQuotaInfoWork.h" +#include +#include + +#include + +/** + * merges a given list into the map of this worker + * + * @param inList the list with the QuotaData to merge with the QuotaDataMap of this worker + */ +void GetQuotaInfoWork::mergeOrInsertNewQuotaData(QuotaDataList* inList, + QuotaInodeSupport inQuotaInodeSupport) +{ + { + const std::lock_guard lock(*quotaResultsMutex); + + mergeQuotaInodeSupportUnlocked(inQuotaInodeSupport); + + for(QuotaDataListIter inListIter = inList->begin(); inListIter != inList->end(); inListIter++) + { + QuotaDataMapIter outIter = this->quotaResults->find(inListIter->getID() ); + if(outIter != this->quotaResults->end() ) + { + if(outIter->second.mergeQuotaDataCounter(&(*inListIter) ) ) + { // success + *this->result = 0; + } + else + { // insert failed, error value 1 or TargetNumID depends on target selection mode + if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + *this->result = this->cfg.cfgTargetNumID; + else + *this->result = 1; + } + } + else + { + if(this->quotaResults->insert(QuotaDataMapVal(inListIter->getID(), *inListIter) ).second) + { // success + *this->result = 0; + } + else + { // insert failed, error value 1 or TargetNumID depends on target selection mode + if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + *this->result = this->cfg.cfgTargetNumID; + else + *this->result = 1; + } + } + } + } + + if(*this->result != 0) + { + LogContext("GetQuotaInfo").log(Log_WARNING, "Invalid QuotaData from target: " + + StringTk::uintToStr(this->cfg.cfgTargetNumID)); + } +} + +void GetQuotaInfoWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + GetQuotaInfoMsg msg(this->cfg.cfgType, this->storagePoolId); + prepareMessage(this->messageNumber, &msg); + + GetQuotaInfoRespMsg* respMsgCast; + + const auto respMsg = MessagingTk::requestResponse(*storageNode, msg, + NETMSGTYPE_GetQuotaInfoResp); + if (!respMsg) + { + // error value 1 or TargetNumID depends on target selection mode + if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + *this->result = this->cfg.cfgTargetNumID; + else + *this->result = 1; + + this->counter->incCount(); + return; + } + + respMsgCast = (GetQuotaInfoRespMsg*)respMsg.get(); + + //merge the QuotaData from the response with the existing quotaData + mergeOrInsertNewQuotaData(&respMsgCast->getQuotaDataList(), respMsgCast->getQuotaInodeSupport() ); + + this->counter->incCount(); +} + +/* + * type dependent message preparations + * + * @param messageNumber the sequence number of the message + * @param msg the message to send + */ +void GetQuotaInfoWork::prepareMessage(int messageNumber, GetQuotaInfoMsg* msg) +{ + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + msg->setTargetSelectionAllTargetsOneRequestForAllTargets(); + else + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET) + msg->setTargetSelectionAllTargetsOneRequestPerTarget(this->cfg.cfgTargetNumID); + else + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_SINGLE_TARGET) + msg->setTargetSelectionSingleTarget(this->cfg.cfgTargetNumID); + + if(this->cfg.cfgUseList || this->cfg.cfgUseAll) + { + msg->setQueryType(GetQuotaInfoMsg::QUERY_TYPE_ID_LIST); + getIDsFromListForMessage(messageNumber, msg->getIDList()); + } + else + if(this->cfg.cfgUseRange) + { + unsigned startRange, endRange; + getIDRangeForMessage(messageNumber, startRange, endRange); + msg->setIDRange(startRange, endRange); + } + else + { + msg->setID(this->cfg.cfgID); + } +} + +/* + * calculates the first ID and the last ID from the ID range, which fits into the payload of the msg + * + * @param messageNumber the sequence number of the message + * @param outFirstID the first ID of the range for the message with the given sequence number + * @param outLastID the last ID of the range for the message with the given sequence number + */ +void GetQuotaInfoWork::getIDRangeForMessage(int messageNumber, unsigned &outFirstID, + unsigned &outLastID) +{ + if( (this->cfg.cfgIDRangeEnd - this->cfg.cfgIDRangeStart) <= GETQUOTAINFORESPMSG_MAX_ID_COUNT) + { + outFirstID = this->cfg.cfgIDRangeStart; + outLastID = this->cfg.cfgIDRangeEnd; + } + else + { + unsigned startThisMessage = this->cfg.cfgIDRangeStart + + (messageNumber * GETQUOTAINFORESPMSG_MAX_ID_COUNT); + unsigned startNextMessage = this->cfg.cfgIDRangeStart + + ( (messageNumber + 1) * GETQUOTAINFORESPMSG_MAX_ID_COUNT); + + outFirstID = startThisMessage; + outLastID = startNextMessage - 1; + + if(outLastID > this->cfg.cfgIDRangeEnd) + outLastID = this->cfg.cfgIDRangeEnd; + } +} + +/* + * calculates the sublist of ID list, which fits into the payload of the msg + * + * @param messageNumber the sequence number of the message + * @param outList the ID list for the message with the given sequence number + */ +void GetQuotaInfoWork::getIDsFromListForMessage(int messageNumber, UIntList* outList) +{ + int counter = 0; + int startThisMessage = (messageNumber * GETQUOTAINFORESPMSG_MAX_ID_COUNT); + int startNextMessage = ( (messageNumber + 1) * GETQUOTAINFORESPMSG_MAX_ID_COUNT); + + for(UIntListIter iter = this->cfg.cfgIDList.begin(); iter != this->cfg.cfgIDList.end(); iter++) + { + if(counter < startThisMessage) + { + counter++; + continue; + } + else + if(counter >= startNextMessage) + return; + + counter++; + outList->push_back(*iter); + } + +} + +void GetQuotaInfoWork::mergeQuotaInodeSupportUnlocked(QuotaInodeSupport inQuotaInodeSupport) +{ + if(*quotaInodeSupport == QuotaInodeSupport_UNKNOWN) + { // if the current state is QuotaInodeSupport_UNKNOWN, the input value is the new state + *quotaInodeSupport = inQuotaInodeSupport; + return; + } + + if(inQuotaInodeSupport == QuotaInodeSupport_ALL_BLOCKDEVICES) + { + if(*quotaInodeSupport == QuotaInodeSupport_NO_BLOCKDEVICES) + { + *quotaInodeSupport = QuotaInodeSupport_SOME_BLOCKDEVICES; + } + } + else if(inQuotaInodeSupport == QuotaInodeSupport_NO_BLOCKDEVICES) + { + if(*quotaInodeSupport == QuotaInodeSupport_ALL_BLOCKDEVICES) + { + *quotaInodeSupport = QuotaInodeSupport_SOME_BLOCKDEVICES; + } + } + // we ignore QuotaInodeSupport_SOME_BLOCKDEVICES because this state couldn't be changed + // we also ignore QuotaInodeSupport_UNKNOWN because it can not change the state +} diff --git a/common/source/common/components/worker/GetQuotaInfoWork.h b/common/source/common/components/worker/GetQuotaInfoWork.h new file mode 100644 index 0000000..e9f0a0a --- /dev/null +++ b/common/source/common/components/worker/GetQuotaInfoWork.h @@ -0,0 +1,67 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include +#include + + +#include "Work.h" + + +class GetQuotaInfoWork: public Work +{ + public: + /* + * Constructor for thread-safe use of the result map + * + */ + GetQuotaInfoWork(GetQuotaInfoConfig cfg, NodeHandle storageNode, int messageNumber, + QuotaDataMap* outQuotaResults, Mutex* quotaResultsMutex, + SynchronizedCounter *counter, uint16_t* result, QuotaInodeSupport* quotaInodeSupport, + StoragePoolId storagePoolId) + { + this->cfg = cfg; + this->storageNode = storageNode; + this->messageNumber = messageNumber; + this->quotaResults = outQuotaResults; + this->quotaResultsMutex = quotaResultsMutex; + this->quotaInodeSupport = quotaInodeSupport; + + this->counter = counter; + this->result = result; + + this->storagePoolId = storagePoolId; + } + + virtual ~GetQuotaInfoWork() + { + } + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + GetQuotaInfoConfig cfg; // configuration qith all information to query the quota data + NodeHandle storageNode; // the node query + int messageNumber; // the message number which is processed by this work + + QuotaDataMap* quotaResults; // the quota data from the server after requesting the server + QuotaInodeSupport* quotaInodeSupport; // the support level for inode quota of the blockdevice + Mutex* quotaResultsMutex; // synchronize quotaResults and quotaInodeSupport + + SynchronizedCounter* counter; // counter for finished worker + uint16_t* result; // result of the worker, 0 if success, if error the TargetNumID + + StoragePoolId storagePoolId; // only relevant for sending a GetQuotaMsg to the mgmtd + + void prepareMessage(int messageNumber, GetQuotaInfoMsg* msg); + void getIDRangeForMessage(int messageNumber, unsigned &outFirstID, unsigned &outLastID); + void getIDsFromListForMessage(int messageNumber, UIntList* outList); + void mergeOrInsertNewQuotaData(QuotaDataList* inList, QuotaInodeSupport inQuotaInodeSupport); + void mergeQuotaInodeSupportUnlocked(QuotaInodeSupport inQuotaInodeSupport); +}; + diff --git a/common/source/common/components/worker/IncAtomicWork.cpp b/common/source/common/components/worker/IncAtomicWork.cpp new file mode 100644 index 0000000..8958d97 --- /dev/null +++ b/common/source/common/components/worker/IncAtomicWork.cpp @@ -0,0 +1 @@ +#include "IncAtomicWork.h" diff --git a/common/source/common/components/worker/IncAtomicWork.h b/common/source/common/components/worker/IncAtomicWork.h new file mode 100644 index 0000000..748d34f --- /dev/null +++ b/common/source/common/components/worker/IncAtomicWork.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +template +class IncAtomicWork : public Work +{ + public: + IncAtomicWork(Atomic* atomicValue) + { + this->atomicValue = atomicValue; + } + + virtual ~IncAtomicWork() { }; + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) + { + LOG_DEBUG("IncAtomicWork", Log_DEBUG, + "Processing IncAtomicWork"); + + // increment counter + this->atomicValue->increase(); + + LOG_DEBUG("IncAtomicWork", Log_DEBUG, + "Processed IncAtomicWork"); + } + + private: + Atomic* atomicValue; +}; + diff --git a/common/source/common/components/worker/IncSyncedCounterWork.h b/common/source/common/components/worker/IncSyncedCounterWork.h new file mode 100644 index 0000000..f8e8cdb --- /dev/null +++ b/common/source/common/components/worker/IncSyncedCounterWork.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class IncSyncedCounterWork : public Work +{ + public: + IncSyncedCounterWork(SynchronizedCounter* counter) + { + this->counter = counter; + } + + virtual ~IncSyncedCounterWork() { }; + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) + { + LOG_DEBUG("IncSyncedCounterWork", Log_DEBUG, + "Processing IncSyncedCounterWork"); + + // increment counter + counter->incCount(); + + LOG_DEBUG("IncSyncedCounterWork", Log_DEBUG, + "Processed IncSyncedCounterWork"); + } + + private: + SynchronizedCounter* counter; +}; + diff --git a/common/source/common/components/worker/IncomingDataWork.cpp b/common/source/common/components/worker/IncomingDataWork.cpp new file mode 100644 index 0000000..35e0666 --- /dev/null +++ b/common/source/common/components/worker/IncomingDataWork.cpp @@ -0,0 +1,204 @@ +#include +#include +#include +#include +#include "IncomingDataWork.h" + + +void IncomingDataWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + const char* logContextStr = "Work (process incoming data)"; + + const int recvTimeoutMS = 5000; + + unsigned numReceived = 0; + + sock->setStats(&stats); + + try + { + // receive at least the message header + + numReceived += sock->recvExactT(bufIn, NETMSG_MIN_LENGTH, 0, recvTimeoutMS); + + unsigned msgLength = NetMessageHeader::extractMsgLengthFromBuf(bufIn, numReceived); + + if(unlikely(msgLength > bufInLen) ) + { // message too big + LogContext(logContextStr).log(Log_NOTICE, + std::string("Received a message that is too large. Disconnecting: ") + + sock->getPeername() ); + + sock->unsetStats(); + invalidateConnection(sock); + + return; + } + + // receive the rest of the message + if(msgLength > numReceived) + sock->recvExactT(&bufIn[numReceived], msgLength-numReceived, 0, recvTimeoutMS); + + // we got the complete message buffer => create msg object + + AbstractApp* app = PThread::getCurrentThreadApp(); + auto cfg = app->getCommonConfig(); + auto netMessageFactory = app->getNetMessageFactory(); + + auto msg = netMessageFactory->createFromRaw(bufIn, msgLength); + + if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) ) + { // message invalid + LogContext(logContextStr).log(Log_NOTICE, + std::string("Received an invalid message. Disconnecting: ") + sock->getPeername() ); + + sock->unsetStats(); + invalidateConnection(sock); + return; + } + + // process the received msg + + bool processRes = false; + + if(likely(!cfg->getConnAuthHash() || + sock->getIsAuthenticated() || + (msg->getMsgType() == NETMSGTYPE_AuthenticateChannel) ) ) + { // auth disabled or channel is auth'ed or this is an auth msg => process + NetMessage::ResponseContext rctx(NULL, sock, bufOut, bufOutLen, &stats); + processRes = msg->processIncoming(rctx); + } + else + LogContext(logContextStr).log(Log_NOTICE, + std::string("Rejecting message from unauthenticated peer: ") + sock->getPeername() ); + + // processing completed => cleanup + + sock->unsetStats(); + + if(unlikely(!processRes) ) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Problem encountered during processing of a message. Disconnecting: ") + + sock->getPeername() ); + + invalidateConnection(sock); + return; + } + + // processing completed successfully + + if(checkRDMASocketImmediateData(streamListener, sock) ) + { // immediate data available => do not return the socket to the streamListener + // (new work has been added directly) + return; + } + + // stream socket => return the socket to the StreamListener + streamListener->getSockReturnFD()->write(&sock, sizeof(Socket*) ); + + return; + + } + catch(SocketTimeoutException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection timed out: ") + sock->getPeername() ); + } + catch(SocketDisconnectException& e) + { + // (note: level Log_DEBUG to avoid spamming the log until we have log topics) + LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + sock->unsetStats(); + invalidateConnection(sock); +} + +void IncomingDataWork::invalidateConnection(Socket* sock) +{ + const int recvTimeoutMS = 500; + + try + { + sock->shutdownAndRecvDisconnect(recvTimeoutMS); + } + catch(SocketException& e) + { + // don't care, because the conn is invalid anyway + } + + delete(sock); +} + +/** + * Checks whether there is more immediate data available on an RDMASocket. + * + * If immediate data is available, a new Work package will be added to the Queue. + * The reason for this is the fact that the filedescriptor will only send a notification + * in case of new incoming data, so we need to check for old incoming data (that has already + * been buffered internally by our impl) here. + * + * @return true if immediate data is available or if a conn error occurred (meaning the socket + * should not be returned to the streamListener) + */ +bool IncomingDataWork::checkRDMASocketImmediateData(StreamListener* streamListener, Socket* sock) +{ + const char* logContextStr = "Work (incoming data => check RDMA immediate data)"; + + if(sock->getSockType() != NICADDRTYPE_RDMA) + return false; + + // we have an RDMASocket + + try + { + RDMASocket* rdmaSock = (RDMASocket*)sock; + + if(!rdmaSock->nonblockingRecvCheck() ) + { // no more data available at the moment + LOG_DEBUG(logContextStr, Log_SPAM, + std::string("No immediate data: ") + sock->getPeername() ); + return false; + } + + // we have immediate data available => add to queue + + LOG_DEBUG(logContextStr, Log_SPAM, + std::string("Incoming RDMA data from: ") + sock->getPeername() ); + + IncomingDataWork* work = new IncomingDataWork(streamListener, sock); + + if(sock->getIsDirect() ) + streamListener->getWorkQueue()->addDirectWork(work); + else + streamListener->getWorkQueue()->addIndirectWork(work); + + return true; + } + catch(SocketDisconnectException& e) + { + LogContext(logContextStr).log(Log_DEBUG, std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext(logContextStr).log(Log_NOTICE, + std::string("Connection error: ") + sock->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + // conn error occurred + + delete(sock); + + return true; +} + + + diff --git a/common/source/common/components/worker/IncomingDataWork.h b/common/source/common/components/worker/IncomingDataWork.h new file mode 100644 index 0000000..a3cfe0d --- /dev/null +++ b/common/source/common/components/worker/IncomingDataWork.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + + +class IncomingDataWork : public Work +{ + public: + /** + * Note: Be aware that this is class is only for stream connections that need to be returned + * to the streamListener after processing. + */ + IncomingDataWork(StreamListener* streamListener, Socket* sock) + { + this->streamListener = streamListener; + this->sock = sock; + } + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + static void invalidateConnection(Socket* sock); + static bool checkRDMASocketImmediateData(StreamListener* streamListener, Socket* sock); + + + private: + StreamListener* streamListener; + Socket* sock; +}; + diff --git a/common/source/common/components/worker/LocalConnWorker.cpp b/common/source/common/components/worker/LocalConnWorker.cpp new file mode 100644 index 0000000..be86a06 --- /dev/null +++ b/common/source/common/components/worker/LocalConnWorker.cpp @@ -0,0 +1,224 @@ +#include +#include +#include +#include +#include +#include "LocalConnWorker.h" + + + +LocalConnWorker::LocalConnWorker(const std::string& workerID) + : UnixConnWorker(workerID, "LocalConnWorker"), + bufIn(NULL), + bufOut(NULL) +{ + try + { + StandardSocket::createSocketPair(PF_UNIX, SOCK_STREAM, 0, &workerEndpoint, &clientEndpoint); + + applySocketOptions(workerEndpoint); + applySocketOptions(clientEndpoint); + } + catch(SocketException& e) + { + throw ComponentInitException(e.what() ); + } +} + +LocalConnWorker::~LocalConnWorker() +{ + SAFE_DELETE(workerEndpoint); + SAFE_DELETE(clientEndpoint); + + SAFE_FREE(bufIn); + SAFE_FREE(bufOut); +} + +void LocalConnWorker::run() +{ + try + { + registerSignalHandler(); + + initBuffers(); + + workLoop(); + + log.log(4, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + +} + +void LocalConnWorker::workLoop() +{ + log.log(4, std::string("Ready (TID: ") + StringTk::uint64ToStr(System::getTID() ) + ")"); + + try + { + while(!getSelfTerminate() ) + { + //log.log(4, "Waiting for work..."); + try + { + + if(workerEndpoint->waitForIncomingData(5000) ) + { + //log.log(4, "Got work"); + + bool processRes = processIncomingData( + bufIn, WORKER_BUFIN_SIZE, bufOut, WORKER_BUFOUT_SIZE); + if(!processRes) + { + break; + } + + LOG_DEBUG("LocalConnWorker::workLoop", 4, "Work processed"); + + } + } + catch(SocketInterruptedPollException& e) + { + // ignore interruption, because the debugger causes this + } + } // end of while loop + } + catch(SocketException& e) + { + log.log(2, "Error occurred on the workerEndpoint socket: " + std::string(e.what() ) ); + } +} + +bool LocalConnWorker::processIncomingData( + char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + const int recvTimeoutMS = 5000; + + unsigned numReceived = 0; + HighResolutionStats stats; // ignored currently + + try + { + // receive at least the message header + + numReceived += workerEndpoint->recvExactT(bufIn, NETMSG_MIN_LENGTH, 0, recvTimeoutMS); + + unsigned msgLength = NetMessageHeader::extractMsgLengthFromBuf(bufIn, numReceived); + + if(msgLength > bufInLen) + { // message too big + LogContext("LocalConnWorker::processIncomingData").log(3, + std::string("Received a message that is too large. Disconnecting: ") + + workerEndpoint->getPeername() ); + + invalidateConnection(); + + return false; + } + + // receive the rest of the message + + if(msgLength > numReceived) + workerEndpoint->recvExactT(&bufIn[numReceived], msgLength-numReceived, 0, recvTimeoutMS); + + // we got the complete message => deserialize and process it + + auto msg = netMessageFactory->createFromRaw(bufIn, msgLength); + + if(msg->getMsgType() == NETMSGTYPE_Invalid) + { // message invalid + LogContext("LocalConnWorker::processIncomingData").log(3, + std::string("Received an invalid message. Disconnecting: ") + + workerEndpoint->getPeername() ); + + invalidateConnection(); + return false; + } + + NetMessage::ResponseContext rctx(NULL, workerEndpoint, bufOut, bufOutLen, &stats, true); + bool processRes = msg->processIncoming(rctx); + if(!processRes) + { + LogContext("LocalConnWorker::processIncomingData").log(3, + std::string("Problem encountered during processing of a message. Disconnecting: ") + + workerEndpoint->getPeername() ); + + invalidateConnection(); + return false; + } + + // completed successfully. + return true; + + } + catch(SocketTimeoutException& e) + { + LogContext("LocalConnWorker::processIncomingData").log(3, + std::string("Connection timed out: ") + workerEndpoint->getPeername() ); + + } + catch(SocketDisconnectException& e) + { + LogContext("LocalConnWorker::processIncomingData").log(3, std::string(e.what() ) ); + } + catch(SocketException& e) + { + LogContext("LocalConnWorker::processIncomingData").log(3, + std::string("Connection error: ") + workerEndpoint->getPeername() + std::string(": ") + + std::string(e.what() ) ); + } + + invalidateConnection(); + + return false; +} + +void LocalConnWorker::invalidateConnection() +{ + const int recvTimeoutMS = 2500; + + try + { + workerEndpoint->shutdownAndRecvDisconnect(recvTimeoutMS); + } + catch(SocketException& e) + { + // don't care, because the conn is invalid anyway + } +} + +void LocalConnWorker::applySocketOptions(StandardSocket* sock) +{ + LogContext log("LocalConnWorker::applySocketOptions"); + + try + { + sock->setSoKeepAlive(true); + } + catch(SocketException& e) + { + log.log(3, "Failed to enable SoKeepAlive"); + } +} + +/** + * Note: For delayed buffer allocation during run(), because of NUMA. + */ +void LocalConnWorker::initBuffers() +{ + void* bufInVoid = NULL; + void* bufOutVoid = NULL; + + int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), WORKER_BUFIN_SIZE); + int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), WORKER_BUFOUT_SIZE); + + IGNORE_UNUSED_VARIABLE(inAllocRes); + IGNORE_UNUSED_VARIABLE(outAllocRes); + + this->bufIn = (char*)bufInVoid; + this->bufOut = (char*)bufOutVoid; +} + diff --git a/common/source/common/components/worker/LocalConnWorker.h b/common/source/common/components/worker/LocalConnWorker.h new file mode 100644 index 0000000..095d0fc --- /dev/null +++ b/common/source/common/components/worker/LocalConnWorker.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + + +/** + * Handles connections from the local machine to itself (to avoid deadlocks by using + * the standard work-queue mechanism). + */ +class LocalConnWorker : public UnixConnWorker +{ + public: + /** + * Constructor for socket pair version. + */ + LocalConnWorker(const std::string& workerID); + + virtual ~LocalConnWorker(); + + + private: + char* bufIn; + char* bufOut; + + StandardSocket* workerEndpoint; + StandardSocket* clientEndpoint; + + virtual void run(); + void workLoop(); + + bool processIncomingData(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + void invalidateConnection(); + + void applySocketOptions(StandardSocket* sock); + void initBuffers(); + + + public: + // getters & setters + Socket* getClientEndpoint() + { + return clientEndpoint; + } +}; + diff --git a/common/source/common/components/worker/ReadLocalFileV2Work.cpp b/common/source/common/components/worker/ReadLocalFileV2Work.cpp new file mode 100644 index 0000000..e4b80ef --- /dev/null +++ b/common/source/common/components/worker/ReadLocalFileV2Work.cpp @@ -0,0 +1,131 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "ReadLocalFileV2Work.h" + + +void ReadLocalFileV2Work::process( + char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + TargetMapper* targetMapper = readInfo->targetMapper; + NodeStoreServers* nodes = readInfo->storageNodes; + FhgfsOpsErr resolveErr; + + auto node = nodes->referenceNodeByTargetID(targetID, targetMapper, &resolveErr); + if (unlikely(!node)) + { // unable to resolve targetID + *nodeResult = -resolveErr; + } + else + { // got node reference => begin communication + *nodeResult = communicate(*node, bufIn, bufInLen, bufOut, bufOutLen); + } + + readInfo->counter->incCount(); +} + +int64_t ReadLocalFileV2Work::communicate(Node& node, char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen) +{ + const char* logContext = "ReadFileV2 Work (communication)"; + NodeConnPool* connPool = node.getConnPool(); + NumNodeID localNodeNumID = readInfo->localNodeNumID; + + AbstractApp* app = PThread::getCurrentThreadApp(); + int connMsgLongTimeout = app->getCommonConfig()->getConnMsgLongTimeout(); + + int64_t retVal = -FhgfsOpsErr_COMMUNICATION; + int64_t numReceivedFileBytes = 0; + Socket* sock = NULL; + + try + { + // connect + sock = connPool->acquireStreamSocket(); + + // prepare and send message + ReadLocalFileV2Msg readMsg(localNodeNumID, fileHandleID, targetID, pathInfoPtr, + accessFlags, offset, size); + + if (this->firstWriteDoneForTarget) + readMsg.addMsgHeaderFeatureFlag(READLOCALFILEMSG_FLAG_SESSION_CHECK); + + unsigned msgLength = readMsg.serializeMessage(bufOut, bufOutLen).second; + sock->send(bufOut, msgLength, 0); + + // recv length info and file data loop + // note: we return directly from within this loop if the received header indicates an end of + // transmission + for( ; ; ) + { + int64_t lengthInfo; // length info in fhgfs host byte order + + sock->recvExactT(&lengthInfo, sizeof(lengthInfo), 0, connMsgLongTimeout); + lengthInfo = LE_TO_HOST_64(lengthInfo); + + if(lengthInfo <= 0) + { // end of file data transmission + if(unlikely(lengthInfo < 0) ) + { // error occurred + retVal = lengthInfo; + } + else + { // normal end of file data transmission + retVal = numReceivedFileBytes; + } + + connPool->releaseStreamSocket(sock); + return retVal; + } + + // buffer overflow check + if(unlikely( (uint64_t)(numReceivedFileBytes + lengthInfo) > this->size) ) + { + LogContext(logContext).logErr( + std::string("Received a lengthInfo that would lead to a buffer overflow from ") + + node.getAlias() + ": " + StringTk::int64ToStr(lengthInfo) ); + + retVal = -FhgfsOpsErr_INTERNAL; + + break; + } + + // positive result => node is going to send some file data + + // receive announced dataPart + sock->recvExactT(&(this->buf)[numReceivedFileBytes], lengthInfo, 0, connMsgLongTimeout); + + numReceivedFileBytes += lengthInfo; + + } // end of recv file data + header loop + + } + catch(SocketConnectException& e) + { + LogContext(logContext).log(2, std::string("Unable to connect to storage server: ") + + node.getAlias() ); + + retVal = -FhgfsOpsErr_COMMUNICATION; + } + catch(SocketException& e) + { + LogContext(logContext).logErr( + std::string("Communication error. SocketException: ") + e.what() ); + LogContext(logContext).log(2, std::string("Sent request: handleID/offset/size: ") + + fileHandleID + "/" + StringTk::int64ToStr(offset) + "/" + StringTk::int64ToStr(size) ); + + retVal = -FhgfsOpsErr_COMMUNICATION; + } + + // error clean up + + if(sock) + connPool->invalidateStreamSocket(sock); + + return retVal; +} diff --git a/common/source/common/components/worker/ReadLocalFileV2Work.h b/common/source/common/components/worker/ReadLocalFileV2Work.h new file mode 100644 index 0000000..d61f73c --- /dev/null +++ b/common/source/common/components/worker/ReadLocalFileV2Work.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +struct ReadLocalFileWorkInfo +{ + ReadLocalFileWorkInfo(NumNodeID localNodeNumID, TargetMapper* targetMapper, + NodeStoreServers* storageNodes, SynchronizedCounter* counter) : + localNodeNumID(localNodeNumID), targetMapper(targetMapper), storageNodes(storageNodes), + counter(counter) + { + // all init done in initializer list + } + + NumNodeID localNodeNumID; + TargetMapper* targetMapper; + NodeStoreServers* storageNodes; + SynchronizedCounter* counter; +}; + + +class ReadLocalFileV2Work : public Work +{ + public: + ReadLocalFileV2Work(const char* fileHandleID, char* buf, unsigned accessFlags, off_t offset, + size_t size, uint16_t targetID, PathInfo* pathInfoPtr, int64_t* nodeResult, + ReadLocalFileWorkInfo* readInfo, bool firstWriteDoneForTarget) + { + this->fileHandleID = fileHandleID; + this->buf = buf; + this->accessFlags = accessFlags; + this->offset = offset; + this->size = size; + this->targetID = targetID; + this->pathInfoPtr = pathInfoPtr; + this->nodeResult = nodeResult; + this->readInfo = readInfo; + this->firstWriteDoneForTarget = firstWriteDoneForTarget; + } + + virtual ~ReadLocalFileV2Work() + { + } + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + const char* fileHandleID; + char* buf; + unsigned accessFlags; + off_t offset; + size_t size; + uint16_t targetID; + PathInfo* pathInfoPtr; + int64_t* nodeResult; + ReadLocalFileWorkInfo* readInfo; + + bool firstWriteDoneForTarget; /* true if the first chunk was written to the storage target, + it's needed for the session check*/ + + int64_t communicate(Node& node, char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen); + +}; + diff --git a/common/source/common/components/worker/UnixConnWorker.h b/common/source/common/components/worker/UnixConnWorker.h new file mode 100644 index 0000000..36c597e --- /dev/null +++ b/common/source/common/components/worker/UnixConnWorker.h @@ -0,0 +1,52 @@ +#pragma once + + +#include +#include +#include + + + +class UnixConnWorker : public PThread +{ + public: + UnixConnWorker(const std::string& workerID, std::string threadName) + : PThread(std::string(threadName) + workerID), + log(std::string(threadName) + workerID), + netMessageFactory(PThread::getCurrentThreadApp()->getNetMessageFactory() ), + available(false) {}; + ~UnixConnWorker() {}; + + virtual void run() = 0; + virtual void workLoop() = 0; + + virtual bool processIncomingData(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) = 0; + virtual void invalidateConnection() = 0; + + virtual void applySocketOptions(StandardSocket* sock) = 0; + virtual void initBuffers() = 0; + + virtual Socket* getClientEndpoint() = 0; + + + protected: + LogContext log; + const AbstractNetMessageFactory* netMessageFactory; + bool available; // == !acquired + + + public: + // getters & setters + + bool isAvailable() + { + return available; + } + + void setAvailable(bool available) + { + this->available = available; + } +}; + diff --git a/common/source/common/components/worker/Work.h b/common/source/common/components/worker/Work.h new file mode 100644 index 0000000..f09a8f6 --- /dev/null +++ b/common/source/common/components/worker/Work.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include + +class Work; + +typedef std::list WorkList; +typedef WorkList::iterator WorkListIter; + +class Work +{ + public: + Work() + { + HighResolutionStatsTk::resetStats(&stats); + } + + virtual ~Work() {} + + Work(const Work&) = delete; + Work(Work&&) = delete; + Work& operator=(const Work&) = delete; + Work& operator=(Work&&) = delete; + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) = 0; + + protected: + HighResolutionStats stats; + + public: + HighResolutionStats* getHighResolutionStats() + { + return &stats; + } + +#ifdef BEEGFS_DEBUG_PROFILING + TimeFine* getAgeTime() + { + return &age; + } + + private: + TimeFine age; +#endif +}; + diff --git a/common/source/common/components/worker/Worker.cpp b/common/source/common/components/worker/Worker.cpp new file mode 100644 index 0000000..5503cf9 --- /dev/null +++ b/common/source/common/components/worker/Worker.cpp @@ -0,0 +1,147 @@ +#include +#include "Worker.h" + +Worker::Worker(const std::string& workerID, MultiWorkQueue* workQueue, QueueWorkType workType) + : PThread(workerID), + log(workerID), + terminateWithFullQueue(true), + bufInLen(WORKER_BUFIN_SIZE), + bufIn(NULL), + bufOutLen(WORKER_BUFOUT_SIZE), + bufOut(NULL), + workQueue(workQueue), + workType(workType), + personalWorkQueue(new PersonalWorkQueue() ) +{ + HighResolutionStatsTk::resetStats(&this->stats); +} + +void Worker::run() +{ + try + { + registerSignalHandler(); + + initBuffers(); + + /* note: we're not directly calling workLoop(workType) below, because: + 1) we want to check that the given workType value is really valid. + 2) we hope that the compiler can use this explicit check and value passing to optimize away + the if-condition inside waitForWorkByType (so that a worker does not need to check its + worktype for every incoming work). */ + + if(workType == QueueWorkType_DIRECT) + workLoop(QueueWorkType_DIRECT); + else + if(workType == QueueWorkType_INDIRECT) + workLoop(QueueWorkType_INDIRECT); + else + throw ComponentInitException( + "Unknown/invalid work type given: " + StringTk::intToStr(workType) ); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + +} + +void Worker::workLoop(QueueWorkType workType) +{ + LOG(WORKQUEUES, DEBUG, "Ready", ("TID", System::getTID()), workType); + + workQueue->incNumWorkers(); // add this worker to queue stats + + while(!getSelfTerminate() || !maySelfTerminateNow() ) + { + Work* work = waitForWorkByType(stats, personalWorkQueue, workType); + +#ifdef BEEGFS_DEBUG_PROFILING + TimeFine workStartTime; +#endif + + HighResolutionStatsTk::resetStats(&stats); // prepare stats + + // process the work packet + work->process(bufIn, bufInLen, bufOut, bufOutLen); + + // update stats + stats.incVals.workRequests = 1; + HighResolutionStatsTk::addHighResIncStats(*work->getHighResolutionStats(), stats); + +#ifdef BEEGFS_DEBUG_PROFILING + TimeFine workEndTime; + const auto workElapsedMS = workEndTime.elapsedSinceMS(&workStartTime); + const auto workLatencyMS = workEndTime.elapsedSinceMS(work->getAgeTime()); + + if (workElapsedMS >= 10) + { + if (workLatencyMS >= 10) + LOG(WORKQUEUES, DEBUG, "Work processed.", + ("Elapsed ms", workElapsedMS), ("Total latency (ms)", workLatencyMS)); + else + LOG(WORKQUEUES, DEBUG, "Work processed.", ("Elapsed ms", workElapsedMS), + ("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime()))); + } + else + { + if (workLatencyMS >= 10) + { + LOG(WORKQUEUES, DEBUG, "Work processed.", + ("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)), + ("Total latency (ms)", workEndTime.elapsedSinceMS(work->getAgeTime()))); + + } + else + { + LOG(WORKQUEUES, DEBUG, "Work processed.", + ("Elapsed us", workEndTime.elapsedSinceMicro(&workStartTime)), + ("Total latency (us)", workEndTime.elapsedSinceMicro(work->getAgeTime()))); + } + } +#endif + + // cleanup + delete(work); + } +} + +Work* Worker::waitForWorkByType(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue, + QueueWorkType workType) +{ + /* note: we hope the if-conditions below are optimized away when this is called from + Worker::workLoop(), that's why we have the explicit work type arg in Worker::run() */ + + if(workType == QueueWorkType_DIRECT) + return workQueue->waitForDirectWork(newStats, personalWorkQueue); + else + if(workType == QueueWorkType_INDIRECT) + return workQueue->waitForAnyWork(newStats, personalWorkQueue); + else // should never happen + throw WorkerException("Unknown/invalid work type given: " + StringTk::intToStr(workType)); +} + + +/** + * Note: For delayed buffer allocation during run(), because of NUMA-archs. + */ +void Worker::initBuffers() +{ + if(this->bufInLen) + { + void* bufInVoid = NULL; + int inAllocRes = posix_memalign(&bufInVoid, sysconf(_SC_PAGESIZE), bufInLen); + IGNORE_UNUSED_VARIABLE(inAllocRes); + this->bufIn = (char*)bufInVoid; + } + + if(this->bufOutLen) + { + void* bufOutVoid = NULL; + int outAllocRes = posix_memalign(&bufOutVoid, sysconf(_SC_PAGESIZE), bufOutLen); + IGNORE_UNUSED_VARIABLE(outAllocRes); + this->bufOut = (char*)bufOutVoid; + } +} diff --git a/common/source/common/components/worker/Worker.h b/common/source/common/components/worker/Worker.h new file mode 100644 index 0000000..8ed3e99 --- /dev/null +++ b/common/source/common/components/worker/Worker.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +#define WORKER_BUFIN_SIZE (1024*1024*4) +#define WORKER_BUFOUT_SIZE WORKER_BUFIN_SIZE + + +DECLARE_NAMEDEXCEPTION(WorkerException, "WorkerException") + + + +class Worker : public PThread +{ + public: + Worker(const std::string& workerID, MultiWorkQueue* workQueue, QueueWorkType workType); + + virtual ~Worker() + { + SAFE_FREE(bufIn); + SAFE_FREE(bufOut); + SAFE_DELETE(personalWorkQueue); + } + + + private: + LogContext log; + bool terminateWithFullQueue; // allow self-termination when queue not empty (see setter nodes) + + size_t bufInLen; + char* bufIn; + size_t bufOutLen; + char* bufOut; + + MultiWorkQueue* workQueue; + QueueWorkType workType; + + PersonalWorkQueue* personalWorkQueue; + + HighResolutionStats stats; + + + virtual void run(); + + void workLoop(QueueWorkType workType); + Work* waitForWorkByType(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue, + QueueWorkType workType); + + void initBuffers(); + + // inliners + bool maySelfTerminateNow() + { + if(terminateWithFullQueue || + (!workQueue->getNumPendingWorks() && + workQueue->getIsPersonalQueueEmpty(personalWorkQueue) ) ) + return true; + + return false; + } + + + public: + // setters & getters + + /** + * Note: Do not use this after the run method of this component has been called! + */ + void setBufLens(size_t bufInLen, size_t bufOutLen) + { + this->bufInLen = bufInLen; + this->bufOutLen = bufOutLen; + } + + MultiWorkQueue* getWorkQueue() const + { + return this->workQueue; + } + + /** + * Note: Don't add anything to this queue directly, do it only via + * MultiWorkQueue->addPersonalWork(). + */ + PersonalWorkQueue* getPersonalWorkQueue() + { + return this->personalWorkQueue; + } + + /** + * WARNING: This will only work if there is only a single worker attached to a queue. + * Otherwise the queue would need a getWorkAndDontWait() method that is used during the + * termination phase of the worker, because the queue might become empty before the worker + * calls waitForWork() after the maySelfTerminateNow check. + */ + void disableTerminationWithFullQueue() + { + this->terminateWithFullQueue = false; + } +}; + diff --git a/common/source/common/components/worker/WriteLocalFileWork.cpp b/common/source/common/components/worker/WriteLocalFileWork.cpp new file mode 100644 index 0000000..77efe93 --- /dev/null +++ b/common/source/common/components/worker/WriteLocalFileWork.cpp @@ -0,0 +1,110 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WriteLocalFileWork.h" + +void WriteLocalFileWork::process( + char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + TargetMapper* targetMapper = writeInfo->targetMapper; + NodeStoreServers* nodes = writeInfo->storageNodes; + FhgfsOpsErr resolveErr; + + auto node = nodes->referenceNodeByTargetID(targetID, targetMapper, &resolveErr); + if(unlikely(!node) ) + { // unable to resolve targetID + *nodeResult = -resolveErr; + } + else + { // got node reference => begin communication + *nodeResult = communicate(*node, bufOut, bufOutLen); + } + + writeInfo->counter->incCount(); +} + +int64_t WriteLocalFileWork::communicate(Node& node, char* bufOut, unsigned bufOutLen) +{ + const char* logContext = "WriteFile Work (communication)"; + AbstractApp* app = PThread::getCurrentThreadApp(); + NodeConnPool* connPool = node.getConnPool(); + NumNodeID localNodeNumID = writeInfo->localNodeNumID; + + int64_t retVal = -FhgfsOpsErr_COMMUNICATION; + Socket* sock = NULL; + + try + { + // connect + sock = connPool->acquireStreamSocket(); + + // prepare and send message + WriteLocalFileMsg writeMsg(localNodeNumID, fileHandleID, targetID, pathInfo, + accessFlags, offset, size); + + if (this->firstWriteDoneForTarget) + writeMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_SESSION_CHECK); + + unsigned msgLength = writeMsg.serializeMessage(bufOut, bufOutLen).second; + sock->send(bufOut, msgLength, 0); + + writeMsg.sendData(buf, sock); + + // receive response + auto resp = MessagingTk::recvMsgBuf(*sock); + if (resp.empty()) + { // error + LogContext log(logContext); + log.log(Log_WARNING, "Did not receive a response from: " + sock->getPeername() ); + } + else + { // got response => deserialize it + auto respMsg = app->getNetMessageFactory()->createFromBuf(std::move(resp)); + + if(respMsg->getMsgType() != NETMSGTYPE_WriteLocalFileResp) + { // response invalid (wrong msgType) + LogContext log(logContext); + log.logErr(std::string("Received invalid response type: ") + + StringTk::intToStr(respMsg->getMsgType() ) + std::string(". ") + + std::string("Expected: ") + StringTk::intToStr(NETMSGTYPE_WriteLocalFileResp) + + std::string(". ") + + std::string("Disconnecting: ") + sock->getPeername() ); + } + else + { // correct response => return it + connPool->releaseStreamSocket(sock); + + WriteLocalFileRespMsg* writeRespMsg = (WriteLocalFileRespMsg*)respMsg.get(); + return writeRespMsg->getValue(); + } + } + } + catch(SocketConnectException& e) + { + LogContext(logContext).log(Log_WARNING, std::string("Unable to connect to storage node: ") + + node.getAlias() ); + + retVal = -FhgfsOpsErr_COMMUNICATION; + } + catch(SocketException& e) + { + LogContext(logContext).logErr(std::string("SocketException: ") + e.what() ); + LogContext(logContext).log(Log_WARNING, "Values for sessionID/handleID/offset/size: " + + localNodeNumID.str() + "/" + fileHandleID + + "/" + StringTk::int64ToStr(offset) + "/" + StringTk::int64ToStr(size) ); + + retVal = -FhgfsOpsErr_COMMUNICATION; + } + + // clean up + if(sock) + connPool->invalidateStreamSocket(sock); + + return retVal; +} diff --git a/common/source/common/components/worker/WriteLocalFileWork.h b/common/source/common/components/worker/WriteLocalFileWork.h new file mode 100644 index 0000000..cefc7d0 --- /dev/null +++ b/common/source/common/components/worker/WriteLocalFileWork.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +struct WriteLocalFileWorkInfo +{ + WriteLocalFileWorkInfo(NumNodeID localNodeNumID, TargetMapper* targetMapper, + NodeStoreServers* storageNodes, SynchronizedCounter* counter) + : localNodeNumID(localNodeNumID), targetMapper(targetMapper), storageNodes(storageNodes), + counter(counter) + { + // all init done in initializer list + } + + NumNodeID localNodeNumID; + TargetMapper* targetMapper; + NodeStoreServers* storageNodes; + SynchronizedCounter* counter; +}; + + +class WriteLocalFileWork : public Work +{ + public: + + /** + * Note: pathInfo is not owned by this object. + */ + WriteLocalFileWork(const char* fileHandleID, const char* buf, unsigned accessFlags, + off_t offset, size_t size, uint16_t targetID, PathInfo* pathInfo, int64_t* nodeResult, + WriteLocalFileWorkInfo* writeInfo, bool firstWriteDoneForTarget) + { + this->fileHandleID = fileHandleID; + this->buf = buf; + this->accessFlags = accessFlags; + this->offset = offset; + this->size = size; + this->targetID = targetID; + this->pathInfo = pathInfo; + this->nodeResult = nodeResult; + this->writeInfo = writeInfo; + this->firstWriteDoneForTarget = firstWriteDoneForTarget; + } + + virtual ~WriteLocalFileWork() + { + } + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + const char* fileHandleID; + const char* buf; + unsigned accessFlags; + off_t offset; + size_t size; + uint16_t targetID; + PathInfo* pathInfo; + int64_t* nodeResult; + WriteLocalFileWorkInfo* writeInfo; + bool firstWriteDoneForTarget; /* true if the first chunk was written to the storage target, + it's needed for the session check*/ + + int64_t communicate(Node& node, char* bufOut, unsigned bufOutLen); + +}; + diff --git a/common/source/common/components/worker/queue/AbstractWorkContainer.h b/common/source/common/components/worker/queue/AbstractWorkContainer.h new file mode 100644 index 0000000..e17684f --- /dev/null +++ b/common/source/common/components/worker/queue/AbstractWorkContainer.h @@ -0,0 +1,29 @@ +#pragma once + +#include +#include + + +typedef std::list WorkList; +typedef WorkList::iterator WorkListIter; + + +/** + * Interface for ordered work containers. This is to unify access to trivial std::list based + * internal work list and the special user-based fair work list. + */ +class AbstractWorkContainer +{ + public: + virtual ~AbstractWorkContainer() {}; + + virtual Work* getAndPopNextWork() = 0; + virtual void addWork(Work* work, unsigned userID) = 0; + + virtual size_t getSize() = 0; + virtual bool getIsEmpty() = 0; + + virtual void getStatsAsStr(std::string& outStats) = 0; +}; + + diff --git a/common/source/common/components/worker/queue/ListWorkContainer.h b/common/source/common/components/worker/queue/ListWorkContainer.h new file mode 100644 index 0000000..6bcc235 --- /dev/null +++ b/common/source/common/components/worker/queue/ListWorkContainer.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include "AbstractWorkContainer.h" + +/** + * Simple implementation of AbstractWorkContainer interface based on a single std::list. + * + * This container ignores the given userIDs. + */ +class ListWorkContainer : public AbstractWorkContainer +{ + public: + virtual ~ListWorkContainer() + { + for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++) + delete(*iter); + } + + private: + WorkList workList; + + + public: + // inliners + + Work* getAndPopNextWork() + { + Work* work = *workList.begin(); + workList.pop_front(); + + return work; + } + + void addWork(Work* work, unsigned userID) + { + workList.push_back(work); + } + + size_t getSize() + { + return workList.size(); + } + + bool getIsEmpty() + { + return workList.empty(); + } + + void getStatsAsStr(std::string& outStats) + { + std::ostringstream statsStream; + + statsStream << "* Queue type: ListWorkContainer" << std::endl; + statsStream << "* Queue len: " << workList.size() << std::endl; + + outStats = statsStream.str(); + } +}; + + diff --git a/common/source/common/components/worker/queue/MultiWorkQueue.cpp b/common/source/common/components/worker/queue/MultiWorkQueue.cpp new file mode 100644 index 0000000..82af6e6 --- /dev/null +++ b/common/source/common/components/worker/queue/MultiWorkQueue.cpp @@ -0,0 +1,227 @@ +#include "MultiWorkQueue.h" +#include "PersonalWorkQueue.h" + +MultiWorkQueue::MultiWorkQueue() +{ + numPendingWorks = 0; + lastWorkListVecIdx = 0; + + directWorkList = new ListWorkContainer(); + indirectWorkList = new ListWorkContainer(); + + // we use QueueWorkType_... as vec index, so order must be same as in QueueWorkType + workListVec.push_back(directWorkList); + workListVec.push_back(indirectWorkList); + + HighResolutionStatsTk::resetStats(&stats); +} + +MultiWorkQueue::~MultiWorkQueue() +{ + delete(directWorkList); + delete(indirectWorkList); +} + +/** + * Code base of waitForDirectWork. + */ +Work* MultiWorkQueue::waitForDirectWork(HighResolutionStats& newStats, + PersonalWorkQueue* personalWorkQueue) +{ + std::lock_guard mutexLock(mutex); + + HighResolutionStatsTk::addHighResIncStats(newStats, stats); + stats.rawVals.busyWorkers--; + + while(directWorkList->getIsEmpty() && likely(personalWorkQueue->getIsWorkListEmpty() ) ) + newDirectWorkCond.wait(&mutex); + + stats.rawVals.busyWorkers++; + + // personal is always first + if(unlikely(!personalWorkQueue->getIsWorkListEmpty() ) ) + { // we got something in our personal queue + Work* work = personalWorkQueue->getAndPopFirstWork(); +#ifdef BEEGFS_DEBUG_PROFILING + const auto workAgeMS = work->getAgeTime()->elapsedMS(); + if (workAgeMS > 10) + LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", work, + ("age (ms)", workAgeMS)); + else + LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", work, + ("age (us)", work->getAgeTime()->elapsedMicro())); +#endif + return work; + } + else + { + Work* work = directWorkList->getAndPopNextWork(); + numPendingWorks--; +#ifdef BEEGFS_DEBUG_PROFILING + const auto workAgeMS = work->getAgeTime()->elapsedMS(); + if (workAgeMS > 10) + LOG(WORKQUEUES, DEBUG, "Fetching direct work item.", + work, ("age (ms)", workAgeMS)); + else + LOG(WORKQUEUES, DEBUG, "Fetching direct work item.", + work, ("age (us)", work->getAgeTime()->elapsedMicro())); +#endif + return work; + } +} + +/** + * Wait for work on any of the avialable queues. This is for indirect (non-specialized) workers. + * + * @param newStats the updated stats from processing of the last work package. + * @param personalWorkQueue the personal queue of the worker thread which called this method. + */ +Work* MultiWorkQueue::waitForAnyWork(HighResolutionStats& newStats, + PersonalWorkQueue* personalWorkQueue) +{ + std::lock_guard mutexLock(mutex); + + HighResolutionStatsTk::addHighResIncStats(newStats, stats); + stats.rawVals.busyWorkers--; + + while(!numPendingWorks && likely(personalWorkQueue->getIsWorkListEmpty() ) ) + { // no work available right now + newWorkCond.wait(&mutex); + } + + stats.rawVals.busyWorkers++; + + // sanity check: ensure numPendingWorks and actual number of works are equal + #ifdef BEEGFS_DEBUG + size_t numQueuedWorks = 0; + + for(unsigned i=0; i < QueueWorkType_FINAL_DONTUSE; i++) + numQueuedWorks += workListVec[i]->getSize(); + + if(unlikely(numQueuedWorks != numPendingWorks) ) + throw MultiWorkQueueException("numQueuedWorks != numPendingWorks: " + + StringTk::uint64ToStr(numQueuedWorks) + "!=" + StringTk::uint64ToStr(numPendingWorks) ); + #endif // BEEGFS_DEBUG + + + // personal is always first + if(unlikely(!personalWorkQueue->getIsWorkListEmpty() ) ) + { // we got something in our personal queue + Work* work = personalWorkQueue->getAndPopFirstWork(); +#ifdef BEEGFS_DEBUG_PROFILING + const auto workAgeMS = work->getAgeTime()->elapsedMS(); + if (workAgeMS > 10) + LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", + work, ("age (ms)", workAgeMS)); + else + LOG(WORKQUEUES, DEBUG, "Fetching personal work item.", + work, ("age (us)", work->getAgeTime()->elapsedMicro())); +#endif + return work; + } + else + { + // walk over all available queues + // (note: lastWorkListVecIdx ensures that all queue are checked in a fair way) + + for(unsigned i=0; i < QueueWorkType_FINAL_DONTUSE; i++) + { + // switch to next work queue + lastWorkListVecIdx++; + lastWorkListVecIdx = lastWorkListVecIdx % QueueWorkType_FINAL_DONTUSE; + + AbstractWorkContainer* currentWorkList = workListVec[lastWorkListVecIdx]; + + if(!currentWorkList->getIsEmpty() ) + { // this queue contains work for us + Work* work = currentWorkList->getAndPopNextWork(); + numPendingWorks--; + +#ifdef BEEGFS_DEBUG_PROFILING + const std::string direct = (i == QueueWorkType_DIRECT) ? "direct" : "indirect"; + const auto workAgeMS = work->getAgeTime()->elapsedMS(); + if (workAgeMS > 10) + LOG(WORKQUEUES, DEBUG, "Fetching direct work item.", + work, ("age (ms)", workAgeMS)); + else + LOG(WORKQUEUES, DEBUG, "Fetching direct work item.", + work, ("age (us)", work->getAgeTime()->elapsedMicro())); +#endif + + return work; + } + } + + // we should never get here: all queues are empty + + throw MultiWorkQueueException("Unexpected in " + std::string(__func__) + ": " + "All queues are empty. " + "numPendingWorks: " + StringTk::uint64ToStr(numPendingWorks) ); + } +} + +/** + * Adds a new worker thread to internal stats counters to provide correct number of idle/busy + * workers. + * + * This must be called exactly once by each worker before the worker calls waitFor...Work(). + */ +void MultiWorkQueue::incNumWorkers() +{ + std::lock_guard mutesLock(mutex); + + /* note: we increase number of busy workers here, because this value will be decreased + by 1 when the worker calls waitFor...Work(). */ + stats.rawVals.busyWorkers++; +} + +/** + * Deletes the old list and replaces it with the new given one. + * + * Note: Unlocked, because this is intended to be called during queue preparation. + * + * @param newWorkList will be owned by this class, so do no longer access it directly and don't + * delete it. + */ +void MultiWorkQueue::setIndirectWorkList(AbstractWorkContainer* newWorkList) +{ + #ifdef BEEGFS_DEBUG + // sanity check + if(!indirectWorkList->getIsEmpty() ) + throw MultiWorkQueueException("Unexpected in " + std::string(__func__) + ": " + "Queue to be replaced is not empty."); + #endif // BEEGFS_DEBUG + + + delete(indirectWorkList); + + indirectWorkList = newWorkList; + + workListVec[QueueWorkType_INDIRECT] = indirectWorkList; +} + +/** + * Note: Holds lock while generating stats strings => slow => use carefully + */ +void MultiWorkQueue::getStatsAsStr(std::string& outIndirectQueueStats, + std::string& outDirectQueueStats, std::string& outBusyStats) +{ + std::lock_guard mutexLock(mutex); + + // get queue stats + indirectWorkList->getStatsAsStr(outIndirectQueueStats); + directWorkList->getStatsAsStr(outDirectQueueStats); + + // number of busy workers + std::ostringstream busyStream; + + busyStream << "* Busy workers: " << StringTk::uintToStr(stats.rawVals.busyWorkers) << std::endl; + busyStream << "* Work Requests: " << StringTk::uintToStr(stats.incVals.workRequests) << " " + "(reset every second)" << std::endl; + busyStream << "* Bytes read: " << StringTk::uintToStr(stats.incVals.diskReadBytes) << " " + "(reset every second)" << std::endl; + busyStream << "* Bytes written: " << StringTk::uintToStr(stats.incVals.diskWriteBytes) << " " + "(reset every second)" << std::endl; + + outBusyStats = busyStream.str(); +} diff --git a/common/source/common/components/worker/queue/MultiWorkQueue.h b/common/source/common/components/worker/queue/MultiWorkQueue.h new file mode 100644 index 0000000..dd46ee9 --- /dev/null +++ b/common/source/common/components/worker/queue/MultiWorkQueue.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "ListWorkContainer.h" +#include "PersonalWorkQueue.h" + +#include + + +#define MULTIWORKQUEUE_DEFAULT_USERID (~0) // (usually similar to NETMESSAGE_DEFAULT_USERID) + + +DECLARE_NAMEDEXCEPTION(MultiWorkQueueException, "MultiWorkQueueException") + + +class MultiWorkQueue; // forward declaration + + +typedef std::map MultiWorkQueueMap; // maps targetIDs to WorkQueues +typedef MultiWorkQueueMap::iterator MultiWorkQueueMapIter; +typedef MultiWorkQueueMap::const_iterator MultiWorkQueueMapCIter; +typedef MultiWorkQueueMap::value_type MultiWorkQueueMapVal; + + + +/** + * Note: We also use these numbers as indices in the MultiWorkQueue::workListVec, so carefully + * check all related cases when you add/change something here. + */ +enum QueueWorkType +{ + QueueWorkType_DIRECT=0, + QueueWorkType_INDIRECT, + + QueueWorkType_FINAL_DONTUSE // the final element as terminating vector index +}; + + +class MultiWorkQueue : public StreamListenerWorkQueue +{ + private: + // type definitions + typedef std::vector WorkListVec; + typedef WorkListVec::iterator WorkListVecIter; + typedef WorkListVec::const_iterator WorkListVecCIter; + + + public: + MultiWorkQueue(); + ~MultiWorkQueue(); + + Work* waitForDirectWork(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue); + Work* waitForAnyWork(HighResolutionStats& newStats, PersonalWorkQueue* personalWorkQueue); + + void incNumWorkers(); + + void setIndirectWorkList(AbstractWorkContainer* newWorkList); + + void getStatsAsStr(std::string& outIndirectQueueStats, std::string& outDirectQueueStats, + std::string& outBusyStats); + + + private: + AbstractWorkContainer* directWorkList; + AbstractWorkContainer* indirectWorkList; + + Mutex mutex; + Condition newDirectWorkCond; // direct workers wait only on this condition + Condition newWorkCond; // for any type of work (indirect workers wait on this condition) + + size_t numPendingWorks; // length of direct+indirect list (not incl personal list) + unsigned lastWorkListVecIdx; // toggles indirect workers types of work (% queue types) + WorkListVec workListVec; // used to toggle next work type with nextWorkType as index + + HighResolutionStats stats; + + public: + void addDirectWork(Work* work, unsigned userID = MULTIWORKQUEUE_DEFAULT_USERID) + { +#ifdef BEEGFS_DEBUG_PROFILING + LOG(WORKQUEUES, DEBUG, "Adding direct work item.", work); +#endif + + std::lock_guard mutexLock(mutex); + + directWorkList->addWork(work, userID); + + numPendingWorks++; + + newWorkCond.signal(); + newDirectWorkCond.signal(); + } + + void addIndirectWork(Work* work, unsigned userID = MULTIWORKQUEUE_DEFAULT_USERID) + { +#ifdef BEEGFS_DEBUG_PROFILING + LOG(WORKQUEUES, DEBUG, "Adding indirect work item.", work); +#endif + + std::lock_guard mutexLock(mutex); + + indirectWorkList->addWork(work, userID); + + numPendingWorks++; + + newWorkCond.signal(); + } + + void addPersonalWork(Work* work, PersonalWorkQueue* personalQ) + { + /* note: this is in the here (instead of the PersonalWorkQueue) because the MultiWorkQueue + mutex also syncs the personal queue. */ + + std::lock_guard mutexLock(mutex); + + personalQ->addWork(work); + + // note: we do not increase numPendingWorks here (it is only for the other queues) + + // we assume this method is rarely used, so we just wake up all wokers (inefficient) + newDirectWorkCond.broadcast(); + newWorkCond.broadcast(); + } + + size_t getDirectWorkListSize() + { + std::lock_guard mutexLock(mutex); + return directWorkList->getSize(); + } + + size_t getIndirectWorkListSize() + { + std::lock_guard mutexLock(mutex); + return indirectWorkList->getSize(); + } + + bool getIsPersonalQueueEmpty(PersonalWorkQueue* personalQ) + { + std::lock_guard mutexLock(mutex); + return personalQ->getIsWorkListEmpty(); + } + + size_t getNumPendingWorks() + { + std::lock_guard mutexLock(mutex); + return numPendingWorks; + } + + /** + * Returns current stats and _resets_ them. + */ + void getAndResetStats(HighResolutionStats* outStats) + { + std::lock_guard mutexLock(mutex); + + *outStats = stats; + outStats->rawVals.queuedRequests = numPendingWorks; + + /* note: we only reset incremental stats vals, because otherwise we would lose info + like number of busyWorkers */ + HighResolutionStatsTk::resetIncStats(&stats); + } + + +}; + diff --git a/common/source/common/components/worker/queue/PersonalWorkQueue.h b/common/source/common/components/worker/queue/PersonalWorkQueue.h new file mode 100644 index 0000000..fb5913a --- /dev/null +++ b/common/source/common/components/worker/queue/PersonalWorkQueue.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + + +DECLARE_NAMEDEXCEPTION(PersonalWorkQueueException, "PersonalWorkQueueException") + + +/* + * Personal queues are intended to assign work directly to a specific worker thread. + * + * This is useful when we need to make sure that each worker gets a certain work request at least + * once, e.g. to synchronize workers for fsck modification logging. + * + * This class has no own mutex for thread-safety, it is sync'ed via the MultiWorkQueue mutex. + * So adding work to it is done via MultiWorkQueue methods. + * + * Note: Workers always prefer requests in the personal queue over requests in the other queues, + * so keep possible starvation of requests in other queues in mind when you use personal queues. + */ +class PersonalWorkQueue +{ + friend class MultiWorkQueue; /* to make sure that our methods are not called without the + MultiWorkQueue mutex being held. */ + + public: + PersonalWorkQueue() {} + + ~PersonalWorkQueue() + { + // delete remaining work packets + for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++) + delete(*iter); + } + + + private: + WorkList workList; + + + private: + // inliners + + void addWork(Work* work) + { + workList.push_back(work); + } + + /* + * get the next work package (and remove it from the queue). + * caller must ensure that the queue is not empty before calling this. + */ + Work* getAndPopFirstWork() + { + if(unlikely(workList.empty() ) ) // should never happen + throw PersonalWorkQueueException("Work requested from empty queue"); + + Work* work = *workList.begin(); + workList.pop_front(); + + return work; + } + + bool getIsWorkListEmpty() + { + return workList.empty(); + } + + size_t getWorkListSize() + { + return workList.size(); + } + +}; + diff --git a/common/source/common/components/worker/queue/StreamListenerWorkQueue.h b/common/source/common/components/worker/queue/StreamListenerWorkQueue.h new file mode 100644 index 0000000..3cd49bc --- /dev/null +++ b/common/source/common/components/worker/queue/StreamListenerWorkQueue.h @@ -0,0 +1,21 @@ +#pragma once + + +#include + + + +#define STREAMLISTENERWORKQUEUE_DEFAULT_USERID (~0) // usually similar to NETMESSAGE_DEFAULT_USERID + + +class StreamListenerWorkQueue +{ + public: + virtual ~StreamListenerWorkQueue() {}; + + virtual void addDirectWork(Work* work, + unsigned userID = STREAMLISTENERWORKQUEUE_DEFAULT_USERID) = 0; + virtual void addIndirectWork(Work* work, + unsigned userID = STREAMLISTENERWORKQUEUE_DEFAULT_USERID) = 0; +}; + diff --git a/common/source/common/components/worker/queue/UserWorkContainer.h b/common/source/common/components/worker/queue/UserWorkContainer.h new file mode 100644 index 0000000..92b7ec0 --- /dev/null +++ b/common/source/common/components/worker/queue/UserWorkContainer.h @@ -0,0 +1,176 @@ +#pragma once + +#include +#include +#include "AbstractWorkContainer.h" + + +DECLARE_NAMEDEXCEPTION(UserWorkContainerException, "UserWorkContainerException") + + +/** + * Implementation of AbstractWorkContainer interface based on per-user queues for improved fairness. + * + * Work packets will be taken from the different user queues in a round-robin fashion, so that in + * theory a user with a short queue has the same chance of being assigned to the next free worker as + * another user with a long queue. + * Of course, the global fairness is influenced by other scheduling as well, e.g. at the network + * and disk level. + * + * Note on nextWorkIter: A possible unwanted unfairness happens if we have e.g. two active users, + * where userA writes with n threads, userB writes with only a single thread; then we would see a + * 2:1 msg processing ratio with a single worker like B, A1, A2, B, A1, A2, B, ... + * This is because when A1 msg is popped, next iter points to end(), because next B request isn't in + * queue yet, so on next pop we use begin(), which is A2 even though the next B request came in + * while we processed A1. + * We accept this, because avoiding unwanted anomalies completely would get very complex, even with + * a lastWorkIter (e.g. would require starvation handling when new work is inserted between "last" + * and "next") or keeping empty queues for another round. + */ +class UserWorkContainer : public AbstractWorkContainer +{ + // typedefs... + typedef std::map UserWorkMap; // key: userID; value: per-user work queue + typedef UserWorkMap::iterator UserWorkMapIter; + typedef UserWorkMap::const_iterator UserWorkMapCIter; + typedef UserWorkMap::value_type UserWorkMapVal; + + + public: + UserWorkContainer() + { + numWorks = 0; + nextWorkIter = workMap.begin(); // initialize by pointing to end() (=> map empty) + } + + virtual ~UserWorkContainer() + { + // walk over all userIDs and delete all elems of their queues... + + for(UserWorkMapIter mapIter = workMap.begin(); mapIter != workMap.end(); mapIter++) + { + WorkList& workList = mapIter->second; + + for(WorkListIter listIter = workList.begin(); listIter != workList.end(); listIter++) + delete(*listIter); + } + } + + private: + UserWorkMap workMap; // entries added on demand and removed when queue empty + size_t numWorks; // number of works in all queues + UserWorkMapIter nextWorkIter; // points to next work (or end() if currently not set) + + + public: + // inliners + + Work* getAndPopNextWork() + { + #ifdef BEEGFS_DEBUG + // sanity check + if(workMap.empty() ) + throw UserWorkContainerException( + "Sanity check failed in " + std::string(__func__) + ": " + "caller tried to get work from empty queue"); + #endif // BEEGFS_DEBUG + + if(nextWorkIter == workMap.end() ) + nextWorkIter = workMap.begin(); + + /* note on iters after map modification: The C++ standard mandates... + "The insert members shall not affect the validity of iterators and references to the + container, and the erase members shall invalidate only iterators and references to the + erased elements." */ + + UserWorkMapIter currentWorkIter = nextWorkIter; + WorkList& workList = currentWorkIter->second; + + nextWorkIter++; // move to next elem + + + #ifdef BEEGFS_DEBUG + // sanity check + if(workList.empty() ) + throw UserWorkContainerException( + "Sanity check failed in " + std::string(__func__) + ": " + "user queue is empty"); + #endif // BEEGFS_DEBUG + + Work* work = *workList.begin(); + workList.pop_front(); + + numWorks--; + + if(workList.empty() ) + workMap.erase(currentWorkIter); // remove userID with empty work list + + #ifdef BEEGFS_DEBUG + // sanity checks... + + if(numWorks < workMap.size() ) + throw UserWorkContainerException( + "Sanity check failed in " + std::string(__func__) + ": " + "numWorks < workMap.size(): " + + StringTk::uintToStr(numWorks) + "<" + StringTk::uintToStr(workMap.size() ) ); + + if(workMap.empty() && numWorks) + throw UserWorkContainerException( + "Sanity check failed in " + std::string(__func__) + ": " + "workMap is empty, but numWorks==" + StringTk::uintToStr(numWorks) ); + #endif // BEEGFS_DEBUG + + return work; + } + + void addWork(Work* work, unsigned userID) + { + // (note: the [] operator implicitly created the key/value pair if it didn't exist yet) + workMap[userID].push_back(work); + + numWorks++; + } + + size_t getSize() + { + return numWorks; + } + + bool getIsEmpty() + { + return !numWorks; + } + + void getStatsAsStr(std::string& outStats) + { + std::ostringstream statsStream; + + statsStream << "* Queue type: UserWorkContainer" << std::endl; + statsStream << "* Num works total: " << numWorks << std::endl; + statsStream << "* Num user queues: " << workMap.size() << std::endl; + + statsStream << std::endl; + + if(workMap.empty() ) + { // no individual stats to be printed + outStats = statsStream.str(); + return; + } + + statsStream << "Individual queue stats (user/qlen)..." << std::endl; + + for(UserWorkMapCIter mapIter = workMap.begin(); mapIter != workMap.end(); mapIter++) + { + // we use int for userID because NETMSG_DEFAULT_USERID looks better signed + int userID = mapIter->first; + const WorkList& workList = mapIter->second; + + statsStream << "* " << "UserID " << userID << ": " << workList.size() << std::endl; + } + + outStats = statsStream.str(); + } + +}; + + diff --git a/common/source/common/components/worker/queue/WorkQueue.h b/common/source/common/components/worker/queue/WorkQueue.h new file mode 100644 index 0000000..827478d --- /dev/null +++ b/common/source/common/components/worker/queue/WorkQueue.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class WorkQueue +{ + public: + WorkQueue() {} + ~WorkQueue() + { + for(WorkListIter iter = workList.begin(); iter != workList.end(); iter++) + delete(*iter); + } + + Work* waitForNewWork() + { + std::lock_guard mutexLock(mutex); + + while(workList.empty() ) + newWorkCond.wait(&mutex); + + Work* work = *workList.begin(); + workList.pop_front(); + + return work; + } + + void addWork(Work* work) + { + std::lock_guard mutexLock(mutex); + + newWorkCond.signal(); + + workList.push_back(work); + } + + private: + WorkList workList; + + Mutex mutex; + Condition newWorkCond; +}; + diff --git a/common/source/common/fsck/FsckChunk.h b/common/source/common/fsck/FsckChunk.h new file mode 100644 index 0000000..2053ba5 --- /dev/null +++ b/common/source/common/fsck/FsckChunk.h @@ -0,0 +1,239 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class FsckChunk; + +typedef std::list FsckChunkList; +typedef FsckChunkList::iterator FsckChunkListIter; + +class FsckChunk +{ + friend class TestDatabase; + + public: + /* + * @param id the chunk id + * @param targetID the id of the target, on which the chunk is saved + * @param savedPath the path, where the chunk was found; relative to storeStorageDirectory + * @param filesize the size of the chunk in byte + * @param usedBlocks the actual used blocks on the FS + * @param creationTime creation time in secs since the epoch + * @param lastAccessTime last access time in secs since the epoch + * @param modificationTime modification time in secs since the epoch + * @param userID + * @param groupID + * @param buddyGroupID the buddyGroupID this chunk belongs to + */ + FsckChunk(const std::string& id, uint16_t targetID, const Path& savedPath, int64_t fileSize, + uint64_t usedBlocks, int64_t creationTime, int64_t modificationTime, int64_t lastAccessTime, + unsigned userID, unsigned groupID, uint16_t buddyGroupID) : + id(id), targetID(targetID), savedPath(savedPath), fileSize(fileSize), + usedBlocks(usedBlocks), creationTime(creationTime), modificationTime(modificationTime), + lastAccessTime(lastAccessTime), userID(userID), groupID(groupID), + buddyGroupID(buddyGroupID) + { + } + + /* + * @param id the chunk id + * @param targetID the id of the target, on which the chunk is saved + * @param savedPath the path, where the chunk was found; relative to storeStorageDirectory + * @param filesize the size of the chunk in byte + * @param usedBlocks the actual used blocks on the FS + * @param creationTime creation time in secs since the epoch + * @param lastAccessTime last access time in secs since the epoch + * @param modificationTime modification time in secs since the epoch + * @param userID + * @param groupID + */ + FsckChunk(const std::string& id, uint16_t targetID, const Path& savedPath, + int64_t fileSize, int64_t usedBlocks, int64_t creationTime, int64_t modificationTime, + int64_t lastAccessTime, unsigned userID, unsigned groupID) : + id(id), targetID(targetID), savedPath(savedPath), fileSize(fileSize), + usedBlocks(usedBlocks), creationTime(creationTime), modificationTime(modificationTime), + lastAccessTime(lastAccessTime), userID(userID), groupID(groupID), buddyGroupID(0) + { + } + + // only for deserialization! + FsckChunk() + { + } + + private: + std::string id; + uint16_t targetID; + Path savedPath; // the path, where the chunk was found; relative to storeStorageDirectory + int64_t fileSize; // in byte + uint64_t usedBlocks; + int64_t creationTime; // secs since the epoch + int64_t modificationTime; // secs since the epoch + int64_t lastAccessTime; // secs since the epoch + uint32_t userID; + uint32_t groupID; + uint16_t buddyGroupID; // if this is a buddy mirrored chunk, this field saves the + // buddyGroupID + + public: + const std::string& getID() const + { + return this->id; + } + + uint16_t getTargetID() const + { + return this->targetID; + } + + Path* getSavedPath() + { + return &(this->savedPath); + } + + void setSavedPath(const Path& path) + { + this->savedPath = path; + } + + int64_t getFileSize() const + { + return this->fileSize; + } + + int64_t getUsedBlocks() const + { + return this->usedBlocks; + } + + int64_t getCreationTime() const + { + return this->creationTime; + } + + int64_t getModificationTime() const + { + return this->modificationTime; + } + + int64_t getLastAccessTime() const + { + return this->lastAccessTime; + } + + unsigned getUserID() const + { + return this->userID; + } + + void setUserID(const unsigned userID) + { + this->userID = userID; + } + + unsigned getGroupID() const + { + return this->groupID; + } + + void setGroupID(const unsigned groupID) + { + this->groupID = groupID; + } + + uint16_t getBuddyGroupID() const + { + return this->buddyGroupID; + } + + bool operator<(const FsckChunk& other) const + { + if ( id < other.id ) + return true; + else + return false; + } + + bool operator==(const FsckChunk& other) const + { + if ( id != other.id ) + return false; + else + if ( targetID != other.targetID ) + return false; + else + if ( savedPath != other.savedPath ) + return false; + else + if ( fileSize != other.fileSize ) + return false; + else + if ( usedBlocks != other.usedBlocks ) + return false; + else + if ( creationTime != other.creationTime ) + return false; + else + if ( lastAccessTime != other.lastAccessTime ) + return false; + else + if ( modificationTime != other.modificationTime ) + return false; + else + if ( userID != other.userID ) + return false; + else + if ( groupID != other.groupID ) + return false; + else + if ( buddyGroupID != other.buddyGroupID ) + return false; + else + return true; + } + + bool operator!= (const FsckChunk& other) const + { + return !(operator==( other ) ); + } + + void print() + { + std::cout << "id: " << id << '\n' + << "targetID: " << targetID << '\n' + << "savedPath: " << savedPath.str() << '\n' + << "fileSize: " << fileSize << '\n' + << "usedBlocks: " << usedBlocks << '\n' + << "creationTime: " << creationTime << '\n' + << "modificationTime: " << modificationTime << '\n' + << "lastAccessTime: " << lastAccessTime << '\n' + << "userID: " << userID << '\n' + << "buddyGroupID: " << buddyGroupID << std::endl; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->id) + % obj->targetID + % obj->savedPath + % obj->fileSize + % obj->usedBlocks + % obj->creationTime + % obj->modificationTime + % obj->lastAccessTime + % obj->userID + % obj->groupID + % obj->buddyGroupID; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckContDir.h b/common/source/common/fsck/FsckContDir.h new file mode 100644 index 0000000..02be5f3 --- /dev/null +++ b/common/source/common/fsck/FsckContDir.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +class FsckContDir; + +typedef std::list FsckContDirList; +typedef FsckContDirList::iterator FsckContDirListIter; + +class FsckContDir +{ + private: + std::string id; + NumNodeID saveNodeID; + bool isBuddyMirrored; + + public: + FsckContDir(const std::string& id, NumNodeID saveNodeID, bool isBuddyMirrored) : + id(id), saveNodeID(saveNodeID), isBuddyMirrored(isBuddyMirrored) + { + } + + //only for deserialization + FsckContDir() {} + + const std::string& getID() const + { + return this->id; + } + + NumNodeID getSaveNodeID() const + { + return saveNodeID; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + bool operator< (const FsckContDir& other) const + { + if (id < other.id) + return true; + else + return false; + } + + bool operator== (const FsckContDir& other) const + { + return id == other.id && + saveNodeID == other.saveNodeID && + isBuddyMirrored == other.isBuddyMirrored; + } + + bool operator!= (const FsckContDir& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->id + % obj->saveNodeID + % obj->isBuddyMirrored; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckDirEntry.h b/common/source/common/fsck/FsckDirEntry.h new file mode 100644 index 0000000..d80b8c5 --- /dev/null +++ b/common/source/common/fsck/FsckDirEntry.h @@ -0,0 +1,263 @@ +#pragma once + +#include +#include +#include + +#define FsckDirEntryType_ISDIR(dirEntryType) (dirEntryType == FsckDirEntryType_DIRECTORY) +#define FsckDirEntryType_ISREGULARFILE(dirEntryType) (dirEntryType == FsckDirEntryType_REGULARFILE) +#define FsckDirEntryType_ISSYMLINK(dirEntryType) (dirEntryType == FsckDirEntryType_SYMLINK) +#define FsckDirEntryType_ISSPECIAL(dirEntryType) (dirEntryType == FsckDirEntryType_SPECIAL) +#define FsckDirEntryType_ISVALID(dirEntryType) (dirEntryType != FsckDirEntryType_INVALID) + +enum FsckDirEntryType +{ + FsckDirEntryType_INVALID = 0, + FsckDirEntryType_DIRECTORY = 1, + FsckDirEntryType_REGULARFILE = 2, + FsckDirEntryType_SYMLINK = 3, + FsckDirEntryType_SPECIAL = 4 // BLOCKDEV,CHARDEV,FIFO,etc. are not important for fsck +}; + +class FsckDirEntry; + +typedef std::list FsckDirEntryList; +typedef FsckDirEntryList::iterator FsckDirEntryListIter; + +class FsckDirEntry +{ + friend class TestDatabase; + + private: + std::string id; // a filesystem-wide identifier for this entry + std::string name; // the user-friendly name + std::string parentDirID; + NumNodeID entryOwnerNodeID; + NumNodeID inodeOwnerNodeID; // 0 for unknown owner + /* Note : The DirEntryTypes are stored as integers in the DB; + always keep them in sync with enum FsckDirEntryType! */ + FsckDirEntryType entryType; + + bool hasInlinedInode; + bool isBuddyMirrored; + + NumNodeID saveNodeID; + int32_t saveDevice; // the device on which this dentry file is saved (according to stat) + uint64_t saveInode; // the inode of this dentry file (according to stat) + + // used in fsck database to identify dentries with a compact value instead of the full + // name + uint64_t internalID; + + public: + FsckDirEntry(const std::string& id, const std::string& name, const std::string& parentDirID, + NumNodeID entryOwnerNodeID, NumNodeID inodeOwnerNodeID, FsckDirEntryType entryType, + bool hasInlinedInode, NumNodeID saveNodeID, int saveDevice, uint64_t saveInode, + bool isBuddyMirrored, uint64_t internalID = 0) + : id(id), name(name), parentDirID(parentDirID), entryOwnerNodeID(entryOwnerNodeID), + inodeOwnerNodeID(inodeOwnerNodeID), entryType(entryType), + hasInlinedInode(hasInlinedInode), isBuddyMirrored(isBuddyMirrored), + saveNodeID(saveNodeID), saveDevice(saveDevice), saveInode(saveInode), + internalID(internalID) + {} + + //only for deserialization + FsckDirEntry() {} + + const std::string& getID() const + { + return this->id; + } + + void setID(const std::string& id) + { + this->id = id; + } + + const std::string& getName() const + { + return this->name; + } + + void setName(const std::string& name) + { + this->name = name; + } + + const std::string& getParentDirID() const + { + return this->parentDirID; + } + + void setParentDirID(const std::string& parentDirID) + { + this->parentDirID = parentDirID; + } + + NumNodeID getEntryOwnerNodeID() const + { + return this->entryOwnerNodeID; + } + + void setEntryOwnerNodeID(const NumNodeID entryOwnerNodeID) + { + this->entryOwnerNodeID = entryOwnerNodeID; + } + + NumNodeID getInodeOwnerNodeID() const + { + return this->inodeOwnerNodeID; + } + + void setInodeOwnerNodeID(const NumNodeID inodeOwnerNodeID) + { + this->inodeOwnerNodeID = inodeOwnerNodeID; + } + + FsckDirEntryType getEntryType() const + { + return this->entryType; + } + + void setEntryType(const FsckDirEntryType entryType) + { + this->entryType = entryType; + } + + bool getHasInlinedInode() const + { + return hasInlinedInode; + } + + void setHasInlinedInode(const bool hasInlinedInode) + { + this->hasInlinedInode = hasInlinedInode; + } + + NumNodeID getSaveNodeID() const + { + return saveNodeID; + } + + void setSaveNodeID(const NumNodeID saveNodeID) + { + this->saveNodeID = saveNodeID; + } + + int getSaveDevice() const + { + return this->saveDevice; + } + + void setSaveDevice(const int device) + { + this->saveDevice = device; + } + + uint64_t getSaveInode() const + { + return this->saveInode; + } + + void setSaveInode(const uint64_t inode) + { + this->saveInode = inode; + } + + uint64_t getInternalID() const + { + return this->internalID; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + + bool operator< (const FsckDirEntry& other) const + { + if (id < other.id) + return true; + else + return false; + } + + bool operator== (const FsckDirEntry& other) const + { + if (id.compare(other.id) != 0) + return false; + else + if (name.compare(other.name) != 0) + return false; + else + if (parentDirID.compare(other.parentDirID) != 0) + return false; + else + if (entryOwnerNodeID != other.entryOwnerNodeID) + return false; + else + if (inodeOwnerNodeID != other.inodeOwnerNodeID) + return false; + else + if (entryType != other.entryType) + return false; + else + if (hasInlinedInode != other.hasInlinedInode) + return false; + else + if (saveNodeID != other.saveNodeID) + return false; + else + if (saveDevice != other.saveDevice) + return false; + else + if (saveInode != other.saveInode) + return false; + else + if (isBuddyMirrored != other.isBuddyMirrored) + return false; + else + return true; + } + + bool operator!= (const FsckDirEntry& other) const + { + return !(operator==( other ) ); + } + + void print() + { + std::cout + << "id: " << id << std::endl + << "name: " << name << std::endl + << "parentDirID: " << parentDirID << std::endl + << "entryOwnerNodeID: " << entryOwnerNodeID << std::endl + << "inodeOwnerNodeID: " << inodeOwnerNodeID << std::endl + << "entryType: " << (int)entryType << std::endl + << "hasInlinedInode: " << (int)hasInlinedInode << std::endl + << "saveNodeID: " << saveNodeID << std::endl + << "saveDevice: " << (int)saveDevice << std::endl + << "saveInode: " << saveInode << std::endl + << "isBuddyMirrored: " << isBuddyMirrored << std::endl + << "internalID: " << internalID << std::endl; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->id) + % serdes::stringAlign4(obj->name) + % serdes::stringAlign4(obj->parentDirID) + % obj->entryOwnerNodeID + % obj->inodeOwnerNodeID + % serdes::as(obj->entryType) + % obj->hasInlinedInode + % obj->isBuddyMirrored + % obj->saveNodeID + % obj->saveDevice + % obj->saveInode; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckDirInode.h b/common/source/common/fsck/FsckDirInode.h new file mode 100644 index 0000000..5c506fd --- /dev/null +++ b/common/source/common/fsck/FsckDirInode.h @@ -0,0 +1,227 @@ +#pragma once + +#include +#include +#include + +class FsckDirInode; + +typedef std::list FsckDirInodeList; +typedef FsckDirInodeList::iterator FsckDirInodeListIter; + +class FsckDirInode +{ + friend class TestDatabase; + + public: + FsckDirInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID, + NumNodeID ownerNodeID, int64_t size, unsigned numHardLinks, + const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType, + NumNodeID saveNodeID, bool isBuddyMirrored, bool readable, bool isMismirrored) : + id(id), parentDirID(parentDirID), parentNodeID(parentNodeID), ownerNodeID(ownerNodeID), + size(size), numHardLinks(numHardLinks), stripeTargets(stripeTargets), + stripePatternType(stripePatternType), saveNodeID(saveNodeID), + isBuddyMirrored(isBuddyMirrored), readable(readable), isMismirrored(isMismirrored) + { + } + + // only for deserialization + FsckDirInode() + { + } + + private: + std::string id; // filesystem-wide unique string + std::string parentDirID; + NumNodeID parentNodeID; + NumNodeID ownerNodeID; + int64_t size; // # of subentries + uint32_t numHardLinks; + UInt16Vector stripeTargets; + FsckStripePatternType stripePatternType; + NumNodeID saveNodeID; + bool isBuddyMirrored; + bool readable; + bool isMismirrored; + + public: + const std::string& getID() const + { + return id; + } + + const std::string& getParentDirID() const + { + return parentDirID; + } + + void setParentDirID(const std::string& parentDirID) + { + this->parentDirID = parentDirID; + } + + NumNodeID getParentNodeID() const + { + return parentNodeID; + } + + void setParentNodeID(NumNodeID parentNodeID) + { + this->parentNodeID = parentNodeID; + } + + NumNodeID getOwnerNodeID() const + { + return ownerNodeID; + } + + void setOwnerNodeID(NumNodeID ownerNodeID) + { + this->ownerNodeID = ownerNodeID; + } + + void setSize(int64_t size) + { + this->size = size; + } + + int64_t getSize() const + { + return size; + } + + void setNumHardLinks(unsigned numHardLinks) + { + this->numHardLinks = numHardLinks; + } + + unsigned getNumHardLinks() const + { + return numHardLinks; + } + + UInt16Vector getStripeTargets() const + { + return stripeTargets; + } + + UInt16Vector* getStripeTargets() + { + return &stripeTargets; + } + + void setStripeTargets(UInt16Vector& stripeTargets) + { + this->stripeTargets = stripeTargets; + } + + FsckStripePatternType getStripePatternType() const + { + return stripePatternType; + } + + void setStripePatternType(FsckStripePatternType stripePatternType) + { + this->stripePatternType = stripePatternType; + } + + bool getReadable() const + { + return readable; + } + + void setReadable(bool readable) + { + this->readable = readable; + } + + NumNodeID getSaveNodeID() const + { + return saveNodeID; + } + + void setSaveNodeID(NumNodeID saveNodeID) + { + this->saveNodeID = saveNodeID; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + bool getIsMismirrored() const { return isMismirrored; } + + bool operator<(const FsckDirInode& other) const + { + if ( id < other.getID() ) + return true; + else + return false; + } + + bool operator==(const FsckDirInode& other) const + { + if ( id.compare(other.getID()) != 0 ) + return false; + else + if ( parentDirID.compare(other.getParentDirID()) != 0 ) + return false; + else + if ( parentNodeID != other.getParentNodeID() ) + return false; + else + if ( ownerNodeID != other.getOwnerNodeID() ) + return false; + else + if ( size != other.size ) + return false; + else + if ( numHardLinks != other.numHardLinks ) + return false; + else + if ( stripeTargets != other.stripeTargets ) + return false; + else + if ( stripePatternType != other.stripePatternType ) + return false; + else + if ( saveNodeID != other.getSaveNodeID() ) + return false; + else + if ( readable != other.getReadable() ) + return false; + else + if (isBuddyMirrored != other.isBuddyMirrored) + return false; + else + if (isMismirrored != other.isMismirrored) + return false; + else + return true; + } + + bool operator!= (const FsckDirInode& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->id) + % serdes::stringAlign4(obj->parentDirID) + % obj->parentNodeID + % obj->ownerNodeID + % obj->size + % obj->numHardLinks + % obj->stripeTargets + % serdes::as(obj->stripePatternType) + % obj->saveNodeID + % obj->isBuddyMirrored + % obj->readable + % obj->isMismirrored; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckDuplicateInodeInfo.h b/common/source/common/fsck/FsckDuplicateInodeInfo.h new file mode 100644 index 0000000..97647b9 --- /dev/null +++ b/common/source/common/fsck/FsckDuplicateInodeInfo.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +class FsckDuplicateInodeInfo; + +typedef std::vector FsckDuplicateInodeInfoVector; +typedef FsckDuplicateInodeInfoVector::iterator FsckDuplicateInodeInfoListIter; + +/** + * + * class to store inode specific information for duplicate file and directory inode(s) + */ +class FsckDuplicateInodeInfo +{ + public: + FsckDuplicateInodeInfo() = default; + + FsckDuplicateInodeInfo( + const std::string& id, + const std::string& parentDirId, + uint32_t nodeId, + bool inlined, + bool mirrored, + DirEntryType entryType) : + entryID(id), parentDirID(parentDirId), saveNodeID(nodeId), isInlined(inlined), + isBuddyMirrored(mirrored), dirEntryType(entryType) + { + } + + private: + std::string entryID; + std::string parentDirID; + uint32_t saveNodeID; + bool isInlined; + bool isBuddyMirrored; + DirEntryType dirEntryType; + + public: + const std::string& getID() const { return entryID; } + + const std::string& getParentDirID() const { return parentDirID; } + + uint32_t getSaveNodeID() const { return saveNodeID; } + void setSaveNodeID(uint32_t val) { saveNodeID = val; } + + bool getIsInlined() const { return isInlined; } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + DirEntryType getDirEntryType() const { return dirEntryType; } + + bool operator<(const FsckDuplicateInodeInfo& other) const + { + return parentDirID < other.parentDirID || + saveNodeID < other.saveNodeID || + isInlined < other.isInlined || + isBuddyMirrored < other.isBuddyMirrored || + dirEntryType < other.dirEntryType; + } + + bool operator==(const FsckDuplicateInodeInfo& other) const + { + return parentDirID == other.parentDirID && + saveNodeID == other.saveNodeID && + isInlined == other.isInlined && + isBuddyMirrored == other.isBuddyMirrored && + dirEntryType == other.dirEntryType; + } + + bool operator!=(const FsckDuplicateInodeInfo& other) const + { + return !(operator==( other )); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->entryID) + % serdes::stringAlign4(obj->parentDirID) + % obj->saveNodeID + % obj->isBuddyMirrored + % obj->dirEntryType; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckFileInode.h b/common/source/common/fsck/FsckFileInode.h new file mode 100644 index 0000000..e3d3c7e --- /dev/null +++ b/common/source/common/fsck/FsckFileInode.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FsckFileInode; + +typedef std::list FsckFileInodeList; +typedef FsckFileInodeList::iterator FsckFileInodeListIter; + +class FsckFileInode +{ + friend class TestDatabase; + + public: + FsckFileInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID, + const PathInfo& pathInfo, unsigned userID, unsigned groupID, int64_t fileSize, + unsigned numHardLinks, uint64_t usedBlocks, const UInt16Vector& stripeTargets, + FsckStripePatternType stripePatternType, unsigned chunkSize, NumNodeID saveNodeID, + uint64_t saveInode, int32_t saveDevice, bool isInlined, bool isBuddyMirrored, + bool readable, bool isMismirrored) + { + SettableFileAttribs settableFileAttribs; + + settableFileAttribs.userID = userID; + settableFileAttribs.groupID = groupID; + + StatData statData(fileSize, &settableFileAttribs, 0, 0, numHardLinks, 0); + + initialize(id, parentDirID, parentNodeID, pathInfo, &statData, usedBlocks, stripeTargets, + stripePatternType, chunkSize, saveNodeID, saveInode, saveDevice, isInlined, + isBuddyMirrored, readable, isMismirrored); + } + + FsckFileInode(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID, + const PathInfo& pathInfo, StatData* statData, uint64_t usedBlocks, + const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType, + unsigned chunkSize, NumNodeID saveNodeID, uint64_t saveInode, int32_t saveDevice, + bool isInlined, bool isBuddyMirrored, bool readable, bool isMismirrored) + { + initialize(id, parentDirID, parentNodeID, pathInfo, statData, usedBlocks, stripeTargets, + stripePatternType, chunkSize, saveNodeID, saveInode, saveDevice, isInlined, + isBuddyMirrored, readable, isMismirrored); + } + + FsckFileInode() = default; + + private: + std::string id; // filesystem-wide unique string + std::string parentDirID; + NumNodeID parentNodeID; + + PathInfo pathInfo; + + uint32_t userID; + uint32_t groupID; + + uint64_t usedBlocks; // 512byte-blocks + int64_t fileSize; // in byte + uint32_t numHardLinks; + + UInt16Vector stripeTargets; + FsckStripePatternType stripePatternType; + uint32_t chunkSize; + + NumNodeID saveNodeID; // id of the node, where this inode is saved on + + bool isInlined; + bool isBuddyMirrored; + bool readable; + bool isMismirrored; + + uint64_t saveInode; + int32_t saveDevice; + + void initialize(const std::string& id, const std::string& parentDirID, NumNodeID parentNodeID, + const PathInfo& pathInfo, StatData* statData, uint64_t usedBlocks, + const UInt16Vector& stripeTargets, FsckStripePatternType stripePatternType, + unsigned chunkSize, NumNodeID saveNodeID, uint64_t saveInode, int32_t saveDevice, + bool isInlined, bool isBuddyMirrored, bool readable, bool isMismirrored) + { + this->id = id; + this->parentDirID = parentDirID; + this->parentNodeID = parentNodeID; + this->pathInfo = pathInfo; + + /* check pathInfo; deserialization from disk does not set origParentEntryID, if file was + * never moved. On fsck side, we need this set, so we set it to the current parent dir + * here */ + if (this->pathInfo.getOrigParentEntryID().empty()) + this->pathInfo.setOrigParentEntryID(parentDirID); + + this->userID = statData->getUserID(); + this->groupID = statData->getGroupID(); + this->fileSize = statData->getFileSize(); + this->numHardLinks = statData->getNumHardlinks(); + this->usedBlocks = usedBlocks; + this->stripeTargets = stripeTargets; + this->stripePatternType = stripePatternType; + this->chunkSize = chunkSize; + this->saveNodeID = saveNodeID; + this->saveInode = saveInode; + this->saveDevice = saveDevice; + this->isInlined = isInlined; + this->isBuddyMirrored = isBuddyMirrored; + this->readable = readable; + this->isMismirrored = isMismirrored; + } + + public: + const std::string& getID() const { return id; } + + const std::string& getParentDirID() const { return parentDirID; } + + NumNodeID getParentNodeID() const { return parentNodeID; } + + PathInfo* getPathInfo() { return &(this->pathInfo); } + + unsigned getUserID() const { return this->userID; } + + unsigned getGroupID() const { return this->groupID; } + + int64_t getFileSize() const { return this->fileSize; } + void setFileSize(int64_t fileSize) { this->fileSize = fileSize; } + + unsigned getNumHardLinks() const { return this->numHardLinks; } + void setNumHardLinks(unsigned numHardLinks) { this->numHardLinks = numHardLinks; } + + uint64_t getUsedBlocks() const { return this->usedBlocks; } + + const UInt16Vector& getStripeTargets() const { return stripeTargets; } + void setStripeTargets(const UInt16Vector& targets) { stripeTargets = targets; } + + FsckStripePatternType getStripePatternType() const { return stripePatternType; } + + unsigned getChunkSize() const { return chunkSize; } + + NumNodeID getSaveNodeID() const { return saveNodeID; } + + bool getIsInlined() const { return isInlined; } + void setIsInlined(bool value) { isInlined = value; } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + bool getReadable() const { return readable; } + + uint64_t getSaveInode() const { return this->saveInode; } + + int32_t getSaveDevice() const { return this->saveDevice; } + + bool getIsMismirrored() const { return isMismirrored; } + + bool operator<(const FsckFileInode& other) const + { + return id < other.id; + } + + bool operator==(const FsckFileInode& other) const + { + return id == other.id && + parentDirID == other.parentDirID && + parentNodeID == other.parentNodeID && + userID == other.userID && + groupID == other.groupID && + fileSize == other.fileSize && + numHardLinks == other.numHardLinks && + usedBlocks == other.usedBlocks && + stripeTargets == other.stripeTargets && + stripePatternType == other.stripePatternType && + chunkSize == other.chunkSize && + saveNodeID == other.saveNodeID && + saveInode == other.saveInode && + saveDevice == other.saveDevice && + isInlined == other.isInlined && + isBuddyMirrored == other.isBuddyMirrored && + readable == other.readable && + isMismirrored == other.isMismirrored; + } + + bool operator!= (const FsckFileInode& other) const + { + return !(*this == other); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->id) + % serdes::stringAlign4(obj->parentDirID) + % obj->parentNodeID + % obj->pathInfo + % obj->userID + % obj->groupID + % obj->fileSize + % obj->numHardLinks + % obj->usedBlocks + % obj->stripeTargets + % serdes::as(obj->stripePatternType) + % obj->chunkSize + % obj->saveNodeID + % obj->saveInode + % obj->saveDevice + % obj->isInlined + % obj->isBuddyMirrored + % obj->readable + % obj->isMismirrored; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckFsID.h b/common/source/common/fsck/FsckFsID.h new file mode 100644 index 0000000..0cc3ef9 --- /dev/null +++ b/common/source/common/fsck/FsckFsID.h @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +class FsckFsID; + +typedef std::list FsckFsIDList; +typedef FsckFsIDList::iterator FsckFsIDListIter; + +class FsckFsID +{ + friend class TestDatabase; + + private: + std::string id; + std::string parentDirID; + NumNodeID saveNodeID; + int32_t saveDevice; + uint64_t saveInode; + bool isBuddyMirrored; + + public: + /* + * @param id the entryID + * @param parentDirID id of the parent dir + * @param saveNodeID the id of the node, on which the fsid file is saved + * @param saveDevice the underlying device, on which the file is saved + * @param saveInode the underlying inode, which holds the file + * */ + FsckFsID(const std::string& id, const std::string& parentDirID, NumNodeID saveNodeID, + int saveDevice, uint64_t saveInode, bool isBuddyMirrored) : + id(id), parentDirID(parentDirID), saveNodeID(saveNodeID), saveDevice(saveDevice), + saveInode(saveInode), isBuddyMirrored(isBuddyMirrored) + { + } + + // only for deserialization! + FsckFsID() + { + } + + const std::string& getID() const + { + return this->id; + } + + const std::string& getParentDirID() const + { + return this->parentDirID; + } + + NumNodeID getSaveNodeID() const + { + return this->saveNodeID; + } + + int getSaveDevice() const + { + return this->saveDevice; + } + + uint64_t getSaveInode() const + { + return this->saveInode; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + bool operator<(const FsckFsID& other) const + { + if ( id < other.id ) + return true; + else + return false; + } + + bool operator==(const FsckFsID& other) const + { + if ( id.compare(other.id) ) + return false; + else + if ( parentDirID.compare(other.parentDirID) ) + return false; + else + if ( saveNodeID != other.saveNodeID ) + return false; + else + if ( saveDevice != other.saveDevice ) + return false; + else + if ( saveInode != other.saveInode ) + return false; + else + if (isBuddyMirrored != other.isBuddyMirrored) + return false; + else + return true; + } + + bool operator!= (const FsckFsID& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->id) + % serdes::stringAlign4(obj->parentDirID) + % obj->saveNodeID + % obj->saveDevice + % obj->saveInode + % obj->isBuddyMirrored; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckModificationEvent.h b/common/source/common/fsck/FsckModificationEvent.h new file mode 100644 index 0000000..79f485e --- /dev/null +++ b/common/source/common/fsck/FsckModificationEvent.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +class FsckModificationEvent; + +typedef std::list FsckModificationEventList; +typedef FsckModificationEventList::iterator FsckModificationEventListIter; + +class FsckModificationEvent +{ + friend class TestDatabase; + + private: + ModificationEventType eventType; + std::string entryID; + + public: + /* + * @param eventType + * @param entryID + */ + FsckModificationEvent(ModificationEventType eventType, const std::string& entryID): + eventType(eventType), entryID(entryID) + { + } + + // only for deserialization! + FsckModificationEvent() + { + } + + // getter/setter + ModificationEventType getEventType() const + { + return this->eventType; + } + + const std::string& getEntryID() const + { + return this->entryID; + } + + bool operator<(const FsckModificationEvent& other) const + { + if ( eventType < other.eventType ) + return true; + else + if ( ( eventType == other.eventType ) && ( entryID < other.entryID )) + return true; + else + return false; + } + + bool operator==(const FsckModificationEvent& other) const + { + if ( eventType != other.eventType ) + return false; + else + if ( entryID.compare(other.entryID) ) + return false; + else + return true; + } + + bool operator!= (const FsckModificationEvent& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->eventType) + % serdes::stringAlign4(obj->entryID); + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/fsck/FsckTargetID.h b/common/source/common/fsck/FsckTargetID.h new file mode 100644 index 0000000..35bfca0 --- /dev/null +++ b/common/source/common/fsck/FsckTargetID.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +enum FsckTargetIDType +{ + FsckTargetIDType_TARGET = 0, + FsckTargetIDType_BUDDYGROUP = 1 +}; + +class FsckTargetID +{ + private: + uint16_t id; + FsckTargetIDType targetIDType; + + public: + FsckTargetID(uint16_t id, FsckTargetIDType targetIDType) : id(id), targetIDType(targetIDType) + { + // all initialization done in initialization list + }; + + //only for deserialization + FsckTargetID() {} + + uint16_t getID() const + { + return id; + } + + FsckTargetIDType getTargetIDType() const + { + return targetIDType; + } + + bool operator< (const FsckTargetID& other) const + { + if (id < other.id) + return true; + + if (id == other.id && targetIDType < other.targetIDType) + return true; + + return false; + } + + bool operator== (const FsckTargetID& other) const + { + if (id != other.id) + return false; + else + if (targetIDType != other.targetIDType) + return false; + else + return true; + } + + bool operator!= (const FsckTargetID& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->id + % serdes::as(obj->targetIDType); + } +}; + +typedef std::list FsckTargetIDList; +typedef FsckTargetIDList::iterator FsckTargetIDListIter; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + diff --git a/common/source/common/logging/Backtrace.h b/common/source/common/logging/Backtrace.h new file mode 100644 index 0000000..cd973d3 --- /dev/null +++ b/common/source/common/logging/Backtrace.h @@ -0,0 +1,53 @@ +#pragma once + +/** + * Helper class to easily log a fragment of the current backtrace inline. + */ + +#include +#include +#include + +template +class Backtrace +{ + public: + Backtrace() __attribute__((noinline)) + { + void* btbuf[LEN + 2]; + auto btlen = backtrace(&btbuf[0], LEN + 2); + std::unique_ptr> symbols(backtrace_symbols(&btbuf[0], btlen)); + + if (!symbols || btlen < 2) + { + bt = "(error)"; + } + else + { + std::ostringstream oss; + for (int i = 2; i < btlen - 1; i++) + oss << symbols.get()[i] << "; "; + oss << symbols.get()[btlen - 1] << "."; + + bt = oss.str(); + } + } + + const std::string& str() const { return bt; } + + private: + std::string bt; + + template struct free_delete { void operator()(T* p) { free(p); } }; +}; + +template<> +class Backtrace<0> { public: Backtrace() = delete; }; + +template +std::ostream& operator<<(std::ostream& os, const Backtrace& bt) +{ + os << bt.str(); + return os; +} + diff --git a/common/source/common/memory/MallocBuffer.h b/common/source/common/memory/MallocBuffer.h new file mode 100644 index 0000000..543780f --- /dev/null +++ b/common/source/common/memory/MallocBuffer.h @@ -0,0 +1,80 @@ +#pragma once + +#include "Slice.h" + +#include +#include + +namespace +{ + +// A simple (maybe overly simple) class that owns a heap-allocated buffer. +// There is no size (only capacity), no contained type, no pluggable allocator. +// Also no exceptions. +// NOTE: allocation happens in the reset() function. You must check the return value. +class MallocBuffer +{ + void *mData = 0; + size_t mCapacity = 0; + +public: + + void *data() const + { + return mData; + } + + size_t capacity() const + { + return mCapacity; + } + + void drop() + { + std::free(mData); + mData = 0; + mCapacity = 0; + } + + [[nodiscard]] bool reset(size_t capacity) + { + drop(); + mData = std::malloc(capacity); + if (! mData) + return false; + mCapacity = capacity; + return true; + } + + Slice as_slice() const + { + return Slice(mData, mCapacity); + } + + // Avoid accidental copies + MallocBuffer(MallocBuffer const& other) = delete; + MallocBuffer& operator=(MallocBuffer const& other) = delete; + + MallocBuffer& operator=(MallocBuffer&& other) + { + drop(); + std::swap(mData, other.mData); + std::swap(mCapacity, other.mCapacity); + return *this; + } + + MallocBuffer(MallocBuffer&& other) + { + std::swap(mData, other.mData); + std::swap(mCapacity, other.mCapacity); + } + + MallocBuffer() = default; + + ~MallocBuffer() + { + drop(); + } +}; + +} diff --git a/common/source/common/memory/MallocString.h b/common/source/common/memory/MallocString.h new file mode 100644 index 0000000..09288e1 --- /dev/null +++ b/common/source/common/memory/MallocString.h @@ -0,0 +1,100 @@ +#pragma once + +#include "String.h" + +namespace +{ + +// Similar to MallocBuffer, but for strings. +// We tolerate a bit of boilerplate and duplication in exchange for API simplicity. +// The implementation currently returns a zero-sized string as a pointer to a +// statically allocated 0 byte (a "" literal). We still store an NULL pointer +// internally and check the case on .get(). This may not seem ideal but it +// simplifies the code. There might be a better solution. +class MallocString +{ + char *mData = 0; + size_t mSizeBytes = 0; + +public: + const char *data() const + { + return mData ? mData : ""; + } + + size_t sizeBytes() const + { + return mSizeBytes; + } + + size_t sizeBytes0() const + { + return mSizeBytes + 1; + } + + operator String() const + { + return String(mData, mSizeBytes); + } + + operator StringZ() const + { + return StringZ::fromZeroTerminated(data(), mSizeBytes); + } + + void drop() + { + std::free(mData); + mData = 0; + mSizeBytes = 0; + } + + [[nodiscard]] bool reset(const char *data, size_t sizeBytes) + { + drop(); + mData = (char *) std::malloc(sizeBytes + 1); + if (! mData) + return false; + mSizeBytes = sizeBytes; + memcpy(mData, data, sizeBytes); + mData[sizeBytes] = 0; + return true; + } + + [[nodiscard]] bool reset(String string) + { + return reset(string.data(), string.sizeBytes()); + } + + [[nodiscard]] bool reset(StringZ string) + { + return reset(string.data(), string.sizeBytes()); + } + + // Avoid accidental copies + MallocString(MallocString const& other) = delete; + MallocString& operator=(MallocString const& other) = delete; + + MallocString& operator=(MallocString&& other) + { + drop(); + std::swap(mData, other.mData); + std::swap(mSizeBytes, other.mSizeBytes); + return *this; + } + + MallocString(MallocString&& other) + { + std::swap(mData, other.mData); + std::swap(mSizeBytes, other.mSizeBytes); + } + + MallocString() = default; + + ~MallocString() + { + drop(); + } +}; + +} diff --git a/common/source/common/memory/Slice.h b/common/source/common/memory/Slice.h new file mode 100644 index 0000000..0cb63b2 --- /dev/null +++ b/common/source/common/memory/Slice.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include + +// Basic generic memory region handling. +// There are also read-only and write-only variants. +// Added type safety because of automatic bounds checks and convenient copy functions. + +namespace +{ + +class Slice; + +class RO_Slice +{ + const void *mData = nullptr; + size_t mSize = 0; +public: + const void *data() const + { + return mData; + } + size_t sizeBytes() const + { + return mSize; + } + RO_Slice offsetBytes(size_t offBytes) const + { + assert(offBytes <= mSize); + return RO_Slice((char *) mData + offBytes, mSize - offBytes); + } + RO_Slice limitBytes(size_t sizeBytes) const + { + assert(sizeBytes <= mSize); + return RO_Slice(mData, mSize - sizeBytes); + } + RO_Slice() = default; + RO_Slice(const void *data, size_t sizeBytes) + { + mData = data; + mSize = sizeBytes; + } +}; + +class WO_Slice +{ + void *mData = nullptr; + size_t mSize = 0; +public: + // even though this is WO, we still return the data -- it would be unnecessarily unergonomic to not offer it back. + // Nevertheless the WO_Slice is less capable than a full Slice -- we can't easily make an RO_Slice out of it for example. + void *data() const + { + return mData; + } + size_t sizeBytes() const + { + return mSize; + } + WO_Slice offsetBytes(size_t offBytes) const + { + assert(offBytes <= mSize); + return WO_Slice((char *) mData + offBytes, mSize - offBytes); + } + WO_Slice limitBytes(size_t sizeBytes) const + { + assert(sizeBytes <= mSize); + return WO_Slice(mData, mSize - sizeBytes); + } + WO_Slice() = default; + WO_Slice(void *data, size_t sizeBytes) + { + mData = data; + mSize = sizeBytes; + } +}; + +class Slice +{ + void *mData = nullptr; + size_t mSize = 0; +public: + void *data() const + { + return mData; + } + size_t sizeBytes() const + { + return mSize; + } + Slice offsetBytes(size_t offBytes) const + { + assert(offBytes <= mSize); + return Slice((char *) mData + offBytes, mSize - offBytes); + } + Slice limitBytes(size_t sizeBytes) const + { + assert(sizeBytes <= mSize); + return Slice(mData, mSize - sizeBytes); + } + operator WO_Slice() const + { + return WO_Slice(mData, mSize); + } + operator RO_Slice() const + { + return RO_Slice(mData, mSize); + } + Slice() = default; + Slice(void *data, size_t sizeBytes) + { + mData = data; + mSize = sizeBytes; + } +}; + + +static inline void sliceFill0(WO_Slice dest) +{ + if (dest.sizeBytes() > 0) // passing NULL to memset() leads to UB according to standard. + memset(dest.data(), 0, dest.sizeBytes()); +} + +// A copy function that copies all the source slice. Source slice must be <= destination slice. +// This will probably the most commonly used function. +static inline void sliceCopy(WO_Slice dest, RO_Slice source) +{ + assert(source.sizeBytes() <= dest.sizeBytes()); + memcpy(dest.data(), source.data(), source.sizeBytes()); +} + +// A variant that copies as much as possible from input to output. +static inline void sliceCopyFill0(WO_Slice dest, RO_Slice source) +{ + sliceCopy(dest, source); + sliceFill0(dest.offsetBytes(source.sizeBytes())); +} + +} diff --git a/common/source/common/memory/String.h b/common/source/common/memory/String.h new file mode 100644 index 0000000..f131b08 --- /dev/null +++ b/common/source/common/memory/String.h @@ -0,0 +1,169 @@ +#pragma once + +#include + +#include "Slice.h" + +namespace +{ + +// String slice type, not necessarily zero-terminated. It's basically like +// Slice but it comes with the expectation to contain ASCII/UTF-8 test. It is +// char-typed and has just a tiny bit of helper functionality attached +// (equals/startswith/endswith). It converts easily to Slice using slice() +// method. + +class String +{ + const char *mData = nullptr; + size_t mSizeBytes = 0; +public: + const char *data() const + { + return mData; + } + size_t sizeBytes() const + { + return mSizeBytes; + } + String offsetBytes(size_t offsetBytes) const + { + if (offsetBytes > mSizeBytes) + offsetBytes = mSizeBytes; + return String(mData + offsetBytes, mSizeBytes - offsetBytes); + } + String limitBytes(size_t sizeBytes) const + { + if (sizeBytes >= mSizeBytes) + sizeBytes = mSizeBytes; + return String(mData, sizeBytes); + } + bool equals(String other) const + { + if (mSizeBytes != other.mSizeBytes) + return false; + return !memcmp(mData, other.mData, mSizeBytes); + } + bool startswith(String other) const + { + if (mSizeBytes < other.mSizeBytes) + return false; + return limitBytes(other.mSizeBytes).equals(other); + } + bool endswith(String other) const + { + if (mSizeBytes < other.mSizeBytes) + return false; + return offsetBytes(mSizeBytes - other.mSizeBytes).equals(other); + } + RO_Slice slice() const + { + return RO_Slice(mData, mSizeBytes); + }; + operator RO_Slice() const + { + return RO_Slice(mData, mSizeBytes); + } + String() = default; + String(const char *s, size_t sizeBytes) + { + mData = s; + mSizeBytes = sizeBytes; + }; +}; + +// A zero-terminated string slice type. +// It is non-allocating and non-owning, storing pointer + length pair internally. +// The contract is that it must be a C-string of the given size i.e. it's +// sizeBytes()th byte must be zero. + +class StringZ +{ + const char *mData = ""; + size_t mSizeBytes = 0; // number of bytes, excluding the terminating NUL. +public: + const char *data() const + { + return mData; + } + const char *c_str() const + { + return mData; + } + size_t sizeBytes() const + { + return mSizeBytes; + } + size_t sizeBytesZ() const // size including terminating zero + { + return mSizeBytes + 1; + } + StringZ offsetBytes(size_t offsetBytes) const + { + if (offsetBytes > mSizeBytes) + offsetBytes = mSizeBytes; + return StringZ::fromZeroTerminated(mData + offsetBytes, mSizeBytes - offsetBytes); + } + // NOTE: the result is not zero-terminated, so we can only return String! + String limitBytes(size_t sizeBytes) const + { + if (sizeBytes >= mSizeBytes) + sizeBytes = mSizeBytes; + return String(mData, sizeBytes); + } + RO_Slice slice() const + { + return RO_Slice(mData, mSizeBytes); + } + RO_Slice sliceZ() const + { + return RO_Slice(mData, mSizeBytes + 1); + } + String string() const + { + return String(mData, mSizeBytes); + } + operator String() const + { + return String(mData, mSizeBytes); + } + bool equals(StringZ other) const + { + return string().equals(other.string()); + } + bool startswith(StringZ other) const + { + return string().startswith(other.string()); + } + bool endswith(StringZ other) const + { + return string().endswith(other.string()); + } + StringZ() = default; + explicit StringZ(const char *data) + { + mData = data; + mSizeBytes = strlen(data); + } + static StringZ fromZeroTerminated(const char *data, size_t sizeBytes) + { + StringZ out; + out.mData = data; + out.mSizeBytes = sizeBytes; + return out; + } +}; + +// We can produce String and StringZ literals using "Hello"_s and "Hello"_sz syntax. + +inline String operator ""_s(const char *lit, size_t size) +{ + return String(lit, size); +} + +inline StringZ operator ""_sz(const char *lit, size_t size) +{ + return StringZ::fromZeroTerminated(lit, size); +} + +} diff --git a/common/source/common/net/message/AbstractNetMessageFactory.cpp b/common/source/common/net/message/AbstractNetMessageFactory.cpp new file mode 100644 index 0000000..0273af2 --- /dev/null +++ b/common/source/common/net/message/AbstractNetMessageFactory.cpp @@ -0,0 +1,95 @@ +#include +#include +#include "AbstractNetMessageFactory.h" +#include "SimpleMsg.h" + +/** + * Create NetMessage object (specific type determined by msg header) from a raw msg buffer. + * + * @return (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr AbstractNetMessageFactory::createFromRaw(char* recvBuf, + size_t bufLen) const +{ + if(unlikely(bufLen < NETMSG_MIN_LENGTH)) + return boost::make_unique(NETMSGTYPE_Invalid); + + NetMessageHeader header; + + // decode the message header + NetMessage::deserializeHeader(recvBuf, bufLen, &header); + + // delegate the rest of the work to another method... + + char* msgPayloadBuf = recvBuf + NETMSG_HEADER_LENGTH; + size_t msgPayloadBufLen = bufLen - NETMSG_HEADER_LENGTH; + + return createFromPreprocessedBuf(&header, msgPayloadBuf, msgPayloadBufLen); +} + +/** + * Create NetMessage object (specific type determined by msg header) from a raw msg payload buffer, + * for which the msg header has already been deserialized. + * + * @return (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr AbstractNetMessageFactory::createFromPreprocessedBuf( + NetMessageHeader* header, char* msgPayloadBuf, size_t msgPayloadBufLen) const +{ + const char* logContext = "NetMsgFactory (create msg from buf)"; + + // create the message object for the given message type + + std::unique_ptr msg(createFromMsgType(header->msgType)); + if(unlikely(msg->getMsgType() == NETMSGTYPE_Invalid) ) + { + LogContext(logContext).log(Log_NOTICE, + "Received an invalid or unhandled message. " + "Message type (from raw header): " + netMessageTypeToStr(header->msgType)); + + return msg; + } + + // apply message feature flags and check compatibility + + msg->setMsgHeaderFeatureFlags(header->msgFeatureFlags); + + bool checkCompatRes = msg->checkHeaderFeatureFlagsCompat(); + if(unlikely(!checkCompatRes) ) + { // incompatible feature flag was set => log error with msg type + LogContext(logContext).log(Log_WARNING, + "Received a message with incompatible feature flags. " + "Message type: " + netMessageTypeToStr(header->msgType) + "; " + "Flags (hex): " + StringTk::uintToHexStr(msg->getMsgHeaderFeatureFlags() ) ); + + return boost::make_unique(NETMSGTYPE_Invalid); + } + + // check whether the header flags are as we expect them: + // * if the message does not support mirroring, header flags must be 0 + // * otherwise, they must not contain flags that are not defined + if (header->msgFlags & ~(msg->supportsMirroring() ? NetMessageHeader::FlagsMask : 0)) + { + LOG(GENERAL, WARNING, "Received a message with invalid header flags", header->msgType, + header->msgFlags); + + return boost::make_unique(NETMSGTYPE_Invalid); + } + + msg->msgHeader = *header; + + // deserialize message payload + + bool deserRes = msg->deserializePayload(msgPayloadBuf, msgPayloadBufLen); + if(unlikely(!deserRes) ) + { // deserialization failed => log error with msg type + LogContext(logContext).log(Log_NOTICE, + "Failed to decode message. " + "Message type: " + netMessageTypeToStr(header->msgType)); + + return boost::make_unique(NETMSGTYPE_Invalid); + } + + return msg; +} + diff --git a/common/source/common/net/message/AbstractNetMessageFactory.h b/common/source/common/net/message/AbstractNetMessageFactory.h new file mode 100644 index 0000000..f8c1b53 --- /dev/null +++ b/common/source/common/net/message/AbstractNetMessageFactory.h @@ -0,0 +1,28 @@ +#pragma once + +#include + + +class AbstractNetMessageFactory +{ + public: + virtual ~AbstractNetMessageFactory() {} + + std::unique_ptr createFromRaw(char* recvBuf, size_t bufLen) const; + std::unique_ptr createFromPreprocessedBuf(NetMessageHeader* header, + char* msgPayloadBuf, size_t msgPayloadBufLen) const; + + std::unique_ptr createFromBuf(std::vector buf) const + { + auto result = createFromRaw(&buf[0], buf.size()); + if (result) + result->backingBuffer = std::move(buf); + return result; + } + + protected: + AbstractNetMessageFactory() {}; + + virtual std::unique_ptr createFromMsgType(unsigned short msgType) const = 0; +}; + diff --git a/common/source/common/net/message/AcknowledgeableMsg.h b/common/source/common/net/message/AcknowledgeableMsg.h new file mode 100644 index 0000000..64ddf2c --- /dev/null +++ b/common/source/common/net/message/AcknowledgeableMsg.h @@ -0,0 +1,87 @@ +#pragma once + +#include "NetMessage.h" +#include + +// Ack messages are used for request that might not be answered immediately. For example +// UDP tranfers or file locks. + +class AcknowledgeableMsg : public NetMessage +{ + protected: + AcknowledgeableMsg(unsigned short msgType, const char* ackID) + : NetMessage(msgType), ackID(ackID), ackIDLen(strlen(ackID) ) + { + } + + void serializeAckID(Serializer& ser, unsigned align = 1) const + { + ser % serdes::rawString(ackID, ackIDLen, align); + } + + void serializeAckID(Deserializer& des, unsigned align = 1) + { + des % serdes::rawString(ackID, ackIDLen, align); + } + + private: + const char* ackID; + unsigned ackIDLen; + + public: + // setters & getters + + const char* getAckID() const { return ackID; } + bool wantsAck() const { return ackIDLen != 0; } + + /** + * @param ackID just a reference + */ + void setAckID(const char* ackID) + { + this->ackID = ackID; + this->ackIDLen = strlen(ackID); + } + + template + static void serialize(This obj, Ctx& ctx) + { + obj->serializeAckID(ctx); + } + + bool acknowledge(ResponseContext& ctx) + { + if(ackIDLen == 0) + return false; + + ctx.sendResponse(AckMsg(ackID) ); + + return true; + } +}; + +template +class AcknowledgeableMsgSerdes : public AcknowledgeableMsg +{ + protected: + typedef AcknowledgeableMsgSerdes BaseType; + + AcknowledgeableMsgSerdes(unsigned short msgType, const char* ackID = "") + : AcknowledgeableMsg(msgType, ackID) + { + } + + protected: + void serializePayload(Serializer& ser) const + { + ser % *(Derived*) this; + } + + bool deserializePayload(const char* buf, size_t bufLen) + { + Deserializer des(buf, bufLen); + des % *(Derived*) this; + return des.good(); + } +}; + diff --git a/common/source/common/net/message/NetMessage.h b/common/source/common/net/message/NetMessage.h new file mode 100644 index 0000000..e547aa2 --- /dev/null +++ b/common/source/common/net/message/NetMessage.h @@ -0,0 +1,447 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "NetMessageLogHelper.h" +#include "NetMessageTypes.h" + +#include + + +// common message constants +// ======================== +#define NETMSG_MIN_LENGTH NETMSG_HEADER_LENGTH +#define NETMSG_HEADER_LENGTH 40 /* length of the header (see struct NetMessageHeader) */ +#define NETMSG_MAX_MSG_SIZE 65536 // 64kB +#define NETMSG_MAX_PAYLOAD_SIZE ((unsigned)(NETMSG_MAX_MSG_SIZE - NETMSG_HEADER_LENGTH)) + +#define NETMSG_DEFAULT_USERID (~0) // non-zero to avoid mixing up with root userID + +struct NetMessageHeader +{ + static constexpr const uint64_t MSG_PREFIX = (0x42474653ULL << 32) + BEEGFS_DATA_VERSION; + + static const uint8_t Flag_BuddyMirrorSecond = 0x01; + static const uint8_t Flag_IsSelectiveAck = 0x02; + static const uint8_t Flag_HasSequenceNumber = 0x04; + + static const uint8_t FlagsMask = 0x07; + + uint32_t msgLength; // in bytes + uint16_t msgFeatureFlags; // feature flags for derived messages (depend on msgType) + uint8_t msgCompatFeatureFlags; + uint8_t msgFlags; + uint16_t msgType; // the type of payload, defined as NETMSGTYPE_x + uint16_t msgTargetID; // targetID (not groupID) for per-target workers on storage server + uint32_t msgUserID; // system user ID for per-user msg queues, stats etc. + uint64_t msgSequence; // for retries, 0 if not present + uint64_t msgSequenceDone; // a sequence number that has been fully processed, or 0 + + static void fixLengthField(Serializer& ser, uint32_t actualLength) + { + ser % actualLength; + } + + template + static void serialize(This obj, Ctx& ctx) + { + uint64_t prefix = MSG_PREFIX; + + ctx + % obj->msgLength + % obj->msgFeatureFlags + % obj->msgCompatFeatureFlags + % obj->msgFlags + % prefix + % obj->msgType + % obj->msgTargetID + % obj->msgUserID + % obj->msgSequence + % obj->msgSequenceDone; + + checkPrefix(ctx, prefix); + } + + static void checkPrefix(Deserializer& des, uint64_t prefix) + { + if (unlikely(!des.good() ) || prefix != MSG_PREFIX) + des.setBad(); + } + + static void checkPrefix(Serializer& ser, uint64_t prefix) + { + } + + static unsigned extractMsgLengthFromBuf(const char* recvBuf, unsigned bufLen) + { + Deserializer des(recvBuf, bufLen); + uint32_t length; + des % length; + // return -1 if the buffer was short, which will also be larger than any possible message + // we can possibly receive (due to 16bit message length) + return des.good() ? length : -1; + } +}; + +class NetMessage +{ + friend class AbstractNetMessageFactory; + friend class TestMsgSerializationBase; + + public: + virtual ~NetMessage() {} + + static void deserializeHeader(char* buf, size_t bufLen, NetMessageHeader* outHeader) + { + Deserializer des(buf, bufLen); + des % *outHeader; + if(unlikely(!des.good())) + outHeader->msgType = NETMSGTYPE_Invalid; + } + + class ResponseContext + { + friend class NetMessage; + + public: + ResponseContext(struct sockaddr_in* fromAddr, Socket* sock, char* respBuf, + unsigned bufLen, HighResolutionStats* stats, bool locallyGenerated = false) + : fromAddr(fromAddr), socket(sock), responseBuffer(respBuf), + responseBufferLength(bufLen), stats(stats), locallyGenerated(locallyGenerated) + {} + + void sendResponse(const NetMessage& response) const + { + unsigned msgLength = + response.serializeMessage(responseBuffer, responseBufferLength).second; + socket->sendto(responseBuffer, msgLength, 0, + reinterpret_cast(fromAddr), sizeof(*fromAddr) ); + } + + HighResolutionStats* getStats() const { return stats; } + Socket* getSocket() const { return socket; } + char* getBuffer() const { return responseBuffer; } + unsigned getBufferLength() const { return responseBufferLength; } + + std::string peerName() const + { + return fromAddr ? Socket::ipaddrToStr(fromAddr->sin_addr) : socket->getPeername(); + } + + bool isLocallyGenerated() const { return locallyGenerated; } + + private: + struct sockaddr_in* fromAddr; + Socket* socket; + char* responseBuffer; + unsigned responseBufferLength; + HighResolutionStats* stats; + bool locallyGenerated; + }; + + /** + * Processes this incoming message. + * + * Note: Some messages might be received over a datagram socket, so the response + * must be atomic (=> only a single sendto()-call) + * + * @param fromAddr must be NULL for stream sockets + * @return false if the client should be disconnected (e.g. because it sent invalid data) + * @throw SocketException on communication error + */ + virtual bool processIncoming(ResponseContext& ctx) + { + // Note: Has to be implemented appropriately by derived classes. + // Empty implementation provided here for invalid messages and other messages + // that don't require this way of processing (e.g. some response messages). + + return false; + } + + /** + * Returns all feature flags that are supported by this message. Defaults to "none", so this + * method needs to be overridden by derived messages that actually support header feature + * flags. + * + * @return combination of all supported feature flags + */ + virtual unsigned getSupportedHeaderFeatureFlagsMask() const + { + return 0; + } + + virtual bool supportsMirroring() const { return false; } + + protected: + NetMessage(unsigned short msgType) + { + this->msgHeader.msgLength = 0; + this->msgHeader.msgFeatureFlags = 0; + this->msgHeader.msgCompatFeatureFlags = 0; + this->msgHeader.msgFlags = 0; + this->msgHeader.msgType = msgType; + this->msgHeader.msgUserID = NETMSG_DEFAULT_USERID; + this->msgHeader.msgTargetID = 0; + this->msgHeader.msgSequence = 0; + this->msgHeader.msgSequenceDone = 0; + + this->releaseSockAfterProcessing = true; + } + + + virtual void serializePayload(Serializer& ser) const = 0; + virtual bool deserializePayload(const char* buf, size_t bufLen) = 0; + + private: + NetMessageHeader msgHeader; + + bool releaseSockAfterProcessing; /* false if sock was already released during + processIncoming(), e.g. due to early response. */ + + std::vector backingBuffer; + + public: + std::pair serializeMessage(char* buf, size_t bufLen) const + { + Serializer ser(buf, bufLen); + Serializer atStart = ser.mark(); + + ser % msgHeader; + serializePayload(ser); + + // fix message length in header and serialize header again to fix the message length + NetMessageHeader::fixLengthField(atStart, ser.size() ); + + return std::make_pair(ser.good(), ser.size() ); + } + + /** + * Check if the msg sender has set an incompatible feature flag. + * + * @return false if an incompatible feature flag was set + */ + bool checkHeaderFeatureFlagsCompat() const + { + unsigned unsupportedFlags = ~getSupportedHeaderFeatureFlagsMask(); + if(unlikely(msgHeader.msgFeatureFlags & unsupportedFlags) ) + return false; // an unsupported flag was set + + return true; + } + + // getters & setters + + unsigned short getMsgType() const + { + return msgHeader.msgType; + } + + /** + * Note: calling this method is expensive, so use it only in error/debug code paths. + * + * @return human-readable message type (intended for log messages). + */ + std::string getMsgTypeStr() const + { + return netMessageTypeToStr(msgHeader.msgType); + } + + unsigned getMsgHeaderFeatureFlags() const + { + return msgHeader.msgFeatureFlags; + } + + /** + * Note: You probably rather want to call addMsgHeaderFeatureFlag() instead of this. + */ + void setMsgHeaderFeatureFlags(unsigned msgFeatureFlags) + { + this->msgHeader.msgFeatureFlags = msgFeatureFlags; + } + + /** + * Test flag. (For convenience and readability.) + * + * @return true if given flag is set. + */ + bool isMsgHeaderFeatureFlagSet(unsigned flag) const + { + return (this->msgHeader.msgFeatureFlags & flag) != 0; + } + + /** + * Add another flag without clearing the previously set flags. + * + * Note: The receiver will reject this message if it doesn't know the given feature flag. + */ + void addMsgHeaderFeatureFlag(unsigned flag) + { + this->msgHeader.msgFeatureFlags |= flag; + } + + /** + * Remove a certain flag without clearing the other previously set flags. + */ + void unsetMsgHeaderFeatureFlag(unsigned flag) + { + this->msgHeader.msgFeatureFlags &= ~flag; + } + + uint8_t getMsgHeaderCompatFeatureFlags() const + { + return msgHeader.msgCompatFeatureFlags; + } + + void setMsgHeaderCompatFeatureFlags(uint8_t msgCompatFeatureFlags) + { + this->msgHeader.msgCompatFeatureFlags = msgCompatFeatureFlags; + } + + /** + * Test flag. (For convenience and readability.) + * + * @return true if given flag is set. + */ + bool isMsgHeaderCompatFeatureFlagSet(uint8_t flag) const + { + return (this->msgHeader.msgCompatFeatureFlags & flag) != 0; + } + + /** + * Add another flag without clearing the previously set flags. + * + * Note: "compat" means these flags might not be understood and will then just be ignored by + * the receiver (e.g. if the receiver is an older fhgfs version). + */ + void addMsgHeaderCompatFeatureFlag(uint8_t flag) + { + this->msgHeader.msgCompatFeatureFlags |= flag; + } + + unsigned getMsgHeaderUserID() const + { + return msgHeader.msgUserID; + } + + void setMsgHeaderUserID(unsigned userID) + { + this->msgHeader.msgUserID = userID; + } + + unsigned getMsgHeaderTargetID() const + { + return msgHeader.msgTargetID; + } + + /** + * @param targetID this has to be an actual targetID (not a groupID); (default is 0 if + * targetID is not applicable). + */ + void setMsgHeaderTargetID(uint16_t targetID) + { + this->msgHeader.msgTargetID = targetID; + } + + bool getReleaseSockAfterProcessing() const + { + return releaseSockAfterProcessing; + } + + void setReleaseSockAfterProcessing(bool releaseSockAfterProcessing) + { + this->releaseSockAfterProcessing = releaseSockAfterProcessing; + } + + uint64_t getSequenceNumber() const { return msgHeader.msgSequence; } + void setSequenceNumber(uint64_t value) { msgHeader.msgSequence = value; } + + uint64_t getSequenceNumberDone() const { return msgHeader.msgSequenceDone; } + void setSequenceNumberDone(uint64_t value) { msgHeader.msgSequenceDone = value; } + + uint32_t getLength() const { return msgHeader.msgLength; } + + uint8_t getFlags() const { return msgHeader.msgFlags; } + bool hasFlag(uint8_t flag) const { return msgHeader.msgFlags & flag; } + void addFlag(uint8_t flag) { msgHeader.msgFlags |= flag; } + void removeFlag(uint8_t flag) { msgHeader.msgFlags &= ~flag; } +}; + +template +class NetMessageSerdes : public NetMessage +{ + protected: + typedef NetMessageSerdes BaseType; + + NetMessageSerdes(unsigned short msgType) + : NetMessage(msgType) + {} + + void serializePayload(Serializer& ser) const + { + ser % *(Derived*) this; + } + + bool deserializePayload(const char* buf, size_t bufLen) + { + Deserializer des(buf, bufLen); + des % *(Derived*) this; + return des.good(); + } +}; + + +template +class MirroredMessageBase : public NetMessage +{ + public: + std::pair getRequestorID(NetMessage::ResponseContext& ctx) const + { + if (this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + return {requestorNodeType, requestorNodeID}; + else + return {ctx.getSocket()->getNodeType(), ctx.getSocket()->getNodeID()}; + } + + void setRequestorID(std::pair id) + { + std::tie(requestorNodeType, requestorNodeID) = id; + } + + protected: + typedef MirroredMessageBase BaseType; + + MirroredMessageBase(unsigned short msgType) + : NetMessage(msgType) + {} + + void serializePayload(Serializer& ser) const + { + ser % *(Derived*) this; + + if (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ser + % serdes::as(requestorNodeType) + % requestorNodeID; + } + + bool deserializePayload(const char* buf, size_t bufLen) + { + Deserializer des(buf, bufLen); + des % *(Derived*) this; + + if (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + des + % serdes::as(requestorNodeType) + % requestorNodeID; + + return des.good(); + } + + private: + NumNodeID requestorNodeID; + NodeType requestorNodeType; +}; + diff --git a/common/source/common/net/message/NetMessageLogHelper.h b/common/source/common/net/message/NetMessageLogHelper.h new file mode 100644 index 0000000..1aef65b --- /dev/null +++ b/common/source/common/net/message/NetMessageLogHelper.h @@ -0,0 +1,285 @@ +/* + * Main purpose is to convert NetMessage defines into a string + */ + +#pragma once + +#include + +#include + +inline std::string netMessageTypeToStr(int type) +{ + /* + Cases generated with shell command: + + awk '/NETMSGTYPE/ { printf("case %s: return \"%s (%i)\";\n", $2, substr($2, 12), $3); }' \ + common/net/message/NetMessageTypes.h + */ + + switch (type) { + case NETMSGTYPE_Invalid: return "Invalid (0)"; + case NETMSGTYPE_RemoveNode: return "RemoveNode (1013)"; + case NETMSGTYPE_RemoveNodeResp: return "RemoveNodeResp (1014)"; + case NETMSGTYPE_GetNodes: return "GetNodes (1017)"; + case NETMSGTYPE_GetNodesResp: return "GetNodesResp (1018)"; + case NETMSGTYPE_HeartbeatRequest: return "HeartbeatRequest (1019)"; + case NETMSGTYPE_Heartbeat: return "Heartbeat (1020)"; + case NETMSGTYPE_GetNodeCapacityPools: return "GetNodeCapacityPools (1021)"; + case NETMSGTYPE_GetNodeCapacityPoolsResp: return "GetNodeCapacityPoolsResp (1022)"; + case NETMSGTYPE_MapTargets: return "MapTargets (1023)"; + case NETMSGTYPE_MapTargetsResp: return "MapTargetsResp (1024)"; + case NETMSGTYPE_GetTargetMappings: return "GetTargetMappings (1025)"; + case NETMSGTYPE_GetTargetMappingsResp: return "GetTargetMappingsResp (1026)"; + case NETMSGTYPE_UnmapTarget: return "UnmapTarget (1027)"; + case NETMSGTYPE_UnmapTargetResp: return "UnmapTargetResp (1028)"; + case NETMSGTYPE_GenericDebug: return "GenericDebug (1029)"; + case NETMSGTYPE_GenericDebugResp: return "GenericDebugResp (1030)"; + case NETMSGTYPE_GetClientStats: return "GetClientStats (1031)"; + case NETMSGTYPE_GetClientStatsResp: return "GetClientStatsResp (1032)"; + case NETMSGTYPE_RefreshCapacityPools: return "RefreshCapacityPools (1035)"; + case NETMSGTYPE_StorageBenchControlMsg: return "StorageBenchControlMsg (1037)"; + case NETMSGTYPE_StorageBenchControlMsgResp: return "StorageBenchControlMsgResp (1038)"; + case NETMSGTYPE_RegisterNode: return "RegisterNode (1039)"; + case NETMSGTYPE_RegisterNodeResp: return "RegisterNodeResp (1040)"; + case NETMSGTYPE_RegisterTarget: return "RegisterTarget (1041)"; + case NETMSGTYPE_RegisterTargetResp: return "RegisterTargetResp (1042)"; + case NETMSGTYPE_SetMirrorBuddyGroup: return "SetMirrorBuddyGroup (1045)"; + case NETMSGTYPE_SetMirrorBuddyGroupResp: return "SetMirrorBuddyGroupResp (1046)"; + case NETMSGTYPE_GetMirrorBuddyGroups: return "GetMirrorBuddyGroups (1047)"; + case NETMSGTYPE_GetMirrorBuddyGroupsResp: return "GetMirrorBuddyGroupsResp (1048)"; + case NETMSGTYPE_GetTargetStates: return "GetTargetStates (1049)"; + case NETMSGTYPE_GetTargetStatesResp: return "GetTargetStatesResp (1050)"; + case NETMSGTYPE_RefreshTargetStates: return "RefreshTargetStates (1051)"; + case NETMSGTYPE_GetStatesAndBuddyGroups: return "GetStatesAndBuddyGroups (1053)"; + case NETMSGTYPE_GetStatesAndBuddyGroupsResp: return "GetStatesAndBuddyGroupsResp (1054)"; + case NETMSGTYPE_SetTargetConsistencyStates: return "SetTargetConsistencyStates (1055)"; + case NETMSGTYPE_SetTargetConsistencyStatesResp: return "SetTargetConsistencyStatesResp (1056)"; + case NETMSGTYPE_ChangeTargetConsistencyStates: return "ChangeTargetConsistencyStates (1057)"; + case NETMSGTYPE_ChangeTargetConsistencyStatesResp: return "ChangeTargetConsistencyStatesResp (1058)"; + case NETMSGTYPE_PublishCapacities: return "PublishCapacities (1059)"; + case NETMSGTYPE_RemoveBuddyGroup: return "RemoveBuddyGroup (1060)"; + case NETMSGTYPE_RemoveBuddyGroupResp: return "RemoveBuddyGroupResp (1061)"; + case NETMSGTYPE_GetTargetConsistencyStates: return "GetTargetConsistencyStates (1062)"; + case NETMSGTYPE_GetTargetConsistencyStatesResp: return "GetTargetConsistencyStatesResp (1063)"; + case NETMSGTYPE_AddStoragePool: return "AddStoragePool (1064)"; + case NETMSGTYPE_AddStoragePoolResp: return "AddStoragePoolResp (1065)"; + case NETMSGTYPE_GetStoragePools: return "GetStoragePools (1066)"; + case NETMSGTYPE_GetStoragePoolsResp: return "GetStoragePoolsResp (1067)"; + case NETMSGTYPE_ModifyStoragePool: return "ModifyStoragePool (1068)"; + case NETMSGTYPE_ModifyStoragePoolResp: return "ModifyStoragePoolResp (1069)"; + case NETMSGTYPE_RefreshStoragePools: return "RefreshStoragePools (1070)"; + case NETMSGTYPE_RemoveStoragePool: return "RemoveStoragePool (1071)"; + case NETMSGTYPE_RemoveStoragePoolResp: return "RemoveStoragePoolResp (1072)"; + case NETMSGTYPE_MkDir: return "MkDir (2001)"; + case NETMSGTYPE_MkDirResp: return "MkDirResp (2002)"; + case NETMSGTYPE_RmDir: return "RmDir (2003)"; + case NETMSGTYPE_RmDirResp: return "RmDirResp (2004)"; + case NETMSGTYPE_MkFile: return "MkFile (2005)"; + case NETMSGTYPE_MkFileResp: return "MkFileResp (2006)"; + case NETMSGTYPE_UnlinkFile: return "UnlinkFile (2007)"; + case NETMSGTYPE_UnlinkFileResp: return "UnlinkFileResp (2008)"; + case NETMSGTYPE_UnlinkLocalFile: return "UnlinkLocalFile (2011)"; + case NETMSGTYPE_UnlinkLocalFileResp: return "UnlinkLocalFileResp (2012)"; + case NETMSGTYPE_Stat: return "Stat (2015)"; + case NETMSGTYPE_StatResp: return "StatResp (2016)"; + case NETMSGTYPE_GetChunkFileAttribs: return "GetChunkFileAttribs (2017)"; + case NETMSGTYPE_GetChunkFileAttribsResp: return "GetChunkFileAttribsResp (2018)"; + case NETMSGTYPE_TruncFile: return "TruncFile (2019)"; + case NETMSGTYPE_TruncFileResp: return "TruncFileResp (2020)"; + case NETMSGTYPE_TruncLocalFile: return "TruncLocalFile (2021)"; + case NETMSGTYPE_TruncLocalFileResp: return "TruncLocalFileResp (2022)"; + case NETMSGTYPE_Rename: return "Rename (2023)"; + case NETMSGTYPE_RenameResp: return "RenameResp (2024)"; + case NETMSGTYPE_SetAttr: return "SetAttr (2025)"; + case NETMSGTYPE_SetAttrResp: return "SetAttrResp (2026)"; + case NETMSGTYPE_ListDirFromOffset: return "ListDirFromOffset (2029)"; + case NETMSGTYPE_ListDirFromOffsetResp: return "ListDirFromOffsetResp (2030)"; + case NETMSGTYPE_StatStoragePath: return "StatStoragePath (2031)"; + case NETMSGTYPE_StatStoragePathResp: return "StatStoragePathResp (2032)"; + case NETMSGTYPE_SetLocalAttr: return "SetLocalAttr (2033)"; + case NETMSGTYPE_SetLocalAttrResp: return "SetLocalAttrResp (2034)"; + case NETMSGTYPE_FindOwner: return "FindOwner (2035)"; + case NETMSGTYPE_FindOwnerResp: return "FindOwnerResp (2036)"; + case NETMSGTYPE_MkLocalDir: return "MkLocalDir (2037)"; + case NETMSGTYPE_MkLocalDirResp: return "MkLocalDirResp (2038)"; + case NETMSGTYPE_RmLocalDir: return "RmLocalDir (2039)"; + case NETMSGTYPE_RmLocalDirResp: return "RmLocalDirResp (2040)"; + case NETMSGTYPE_MovingFileInsert: return "MovingFileInsert (2041)"; + case NETMSGTYPE_MovingFileInsertResp: return "MovingFileInsertResp (2042)"; + case NETMSGTYPE_MovingDirInsert: return "MovingDirInsert (2043)"; + case NETMSGTYPE_MovingDirInsertResp: return "MovingDirInsertResp (2044)"; + case NETMSGTYPE_GetEntryInfo: return "GetEntryInfo (2045)"; + case NETMSGTYPE_GetEntryInfoResp: return "GetEntryInfoResp (2046)"; + case NETMSGTYPE_SetDirPattern: return "SetDirPattern (2047)"; + case NETMSGTYPE_SetDirPatternResp: return "SetDirPatternResp (2048)"; + case NETMSGTYPE_GetHighResStats: return "GetHighResStats (2051)"; + case NETMSGTYPE_GetHighResStatsResp: return "GetHighResStatsResp (2052)"; + case NETMSGTYPE_MkFileWithPattern: return "MkFileWithPattern (2053)"; + case NETMSGTYPE_MkFileWithPatternResp: return "MkFileWithPatternResp (2054)"; + case NETMSGTYPE_RefreshEntryInfo: return "RefreshEntryInfo (2055)"; + case NETMSGTYPE_RefreshEntryInfoResp: return "RefreshEntryInfoResp (2056)"; + case NETMSGTYPE_RmDirEntry: return "RmDirEntry (2057)"; + case NETMSGTYPE_RmDirEntryResp: return "RmDirEntryResp (2058)"; + case NETMSGTYPE_LookupIntent: return "LookupIntent (2059)"; + case NETMSGTYPE_LookupIntentResp: return "LookupIntentResp (2060)"; + case NETMSGTYPE_FindLinkOwner: return "FindLinkOwner (2063)"; + case NETMSGTYPE_FindLinkOwnerResp: return "FindLinkOwnerResp (2064)"; + case NETMSGTYPE_MirrorMetadata: return "MirrorMetadata (2067)"; + case NETMSGTYPE_MirrorMetadataResp: return "MirrorMetadataResp (2068)"; + case NETMSGTYPE_SetMetadataMirroring: return "SetMetadataMirroring (2069)"; + case NETMSGTYPE_SetMetadataMirroringResp: return "SetMetadataMirroringResp (2070)"; + case NETMSGTYPE_Hardlink: return "Hardlink (2071)"; + case NETMSGTYPE_HardlinkResp: return "HardlinkResp (2072)"; + case NETMSGTYPE_SetQuota: return "SetQuota (2075)"; + case NETMSGTYPE_SetQuotaResp: return "SetQuotaResp (2076)"; + case NETMSGTYPE_SetExceededQuota: return "SetExceededQuota (2077)"; + case NETMSGTYPE_SetExceededQuotaResp: return "SetExceededQuotaResp (2078)"; + case NETMSGTYPE_RequestExceededQuota: return "RequestExceededQuota (2079)"; + case NETMSGTYPE_RequestExceededQuotaResp: return "RequestExceededQuotaResp (2080)"; + case NETMSGTYPE_UpdateDirParent: return "UpdateDirParent (2081)"; + case NETMSGTYPE_UpdateDirParentResp: return "UpdateDirParentResp (2082)"; + case NETMSGTYPE_ResyncLocalFile: return "ResyncLocalFile (2083)"; + case NETMSGTYPE_ResyncLocalFileResp: return "ResyncLocalFileResp (2084)"; + case NETMSGTYPE_StartStorageTargetResync: return "StartStorageTargetResync (2085)"; + case NETMSGTYPE_StartStorageTargetResyncResp: return "StartStorageTargetResyncResp (2086)"; + case NETMSGTYPE_StorageResyncStarted: return "StorageResyncStarted (2087)"; + case NETMSGTYPE_StorageResyncStartedResp: return "StorageResyncStartedResp (2088)"; + case NETMSGTYPE_ListChunkDirIncremental: return "ListChunkDirIncremental (2089)"; + case NETMSGTYPE_ListChunkDirIncrementalResp: return "ListChunkDirIncrementalResp (2090)"; + case NETMSGTYPE_RmChunkPaths: return "RmChunkPaths (2091)"; + case NETMSGTYPE_RmChunkPathsResp: return "RmChunkPathsResp (2092)"; + case NETMSGTYPE_GetStorageResyncStats: return "GetStorageResyncStats (2093)"; + case NETMSGTYPE_GetStorageResyncStatsResp: return "GetStorageResyncStatsResp (2094)"; + case NETMSGTYPE_SetLastBuddyCommOverride: return "SetLastBuddyCommOverride (2095)"; + case NETMSGTYPE_SetLastBuddyCommOverrideResp: return "SetLastBuddyCommOverrideResp (2096)"; + case NETMSGTYPE_GetQuotaInfo: return "GetQuotaInfo (2097)"; + case NETMSGTYPE_GetQuotaInfoResp: return "GetQuotaInfoResp (2098)"; + case NETMSGTYPE_SetStorageTargetInfo: return "SetStorageTargetInfo (2099)"; + case NETMSGTYPE_SetStorageTargetInfoResp: return "SetStorageTargetInfoResp (2100)"; + case NETMSGTYPE_ListXAttr: return "ListXAttr (2101)"; + case NETMSGTYPE_ListXAttrResp: return "ListXAttrResp (2102)"; + case NETMSGTYPE_GetXAttr: return "GetXAttr (2103)"; + case NETMSGTYPE_GetXAttrResp: return "GetXAttrResp (2104)"; + case NETMSGTYPE_RemoveXAttr: return "RemoveXAttr (2105)"; + case NETMSGTYPE_RemoveXAttrResp: return "RemoveXAttrResp (2106)"; + case NETMSGTYPE_SetXAttr: return "SetXAttr (2107)"; + case NETMSGTYPE_SetXAttrResp: return "SetXAttrResp (2108)"; + case NETMSGTYPE_GetDefaultQuota: return "GetDefaultQuota (2109)"; + case NETMSGTYPE_GetDefaultQuotaResp: return "GetDefaultQuotaResp (2110)"; + case NETMSGTYPE_SetDefaultQuota: return "SetDefaultQuota (2111)"; + case NETMSGTYPE_SetDefaultQuotaResp: return "SetDefaultQuotaResp (2112)"; + case NETMSGTYPE_ResyncSessionStore: return "ResyncSessionStore (2113)"; + case NETMSGTYPE_ResyncSessionStoreResp: return "ResyncSessionStoreResp (2114)"; + case NETMSGTYPE_ResyncRawInodes: return "ResyncRawInodes (2115)"; + case NETMSGTYPE_ResyncRawInodesResp: return "ResyncRawInodesResp (2116)"; + case NETMSGTYPE_GetMetaResyncStats: return "GetMetaResyncStats (2117)"; + case NETMSGTYPE_GetMetaResyncStatsResp: return "GetMetaResyncStatsResp (2118)"; + case NETMSGTYPE_MoveFileInode: return "MoveFileInode (2119)"; + case NETMSGTYPE_MoveFileInodeResp: return "MoveFileInodeResp(2120)"; + case NETMSGTYPE_UnlinkLocalFileInode: return "UnlinkLocalFileInode (2121)"; + case NETMSGTYPE_UnlinkLocalFileInodeResp: return "UnlinkLocalFileInodeResp (2122)"; + case NETMSGTYPE_SetFilePattern: return "SetFilePattern (2123)"; + case NETMSGTYPE_SetFilePatternResp: return "SetFilePatternResp (2124)"; + case NETMSGTYPE_CpChunkPaths: return "CpChunkPaths (2125)"; + case NETMSGTYPE_CpChunkPathsResp: return "CpChunkPathsResp (2126)"; + case NETMSGTYPE_ChunkBalance: return "StartChunkBalance (2127)"; + case NETMSGTYPE_ChunkBalanceResp: return "StartChunkBalanceResp (2128)"; + case NETMSGTYPE_StripePatternUpdate: return "StripePatternUpdate (2129)"; + case NETMSGTYPE_StripePatternUpdateResp: return "StripePatternUpdateResp (2130)"; + case NETMSGTYPE_SetFileState: return "SetFileState (2131)"; + case NETMSGTYPE_SetFileStateResp: return "SetFileStateResp (2132)"; + case NETMSGTYPE_OpenFile: return "OpenFile (3001)"; + case NETMSGTYPE_OpenFileResp: return "OpenFileResp (3002)"; + case NETMSGTYPE_CloseFile: return "CloseFile (3003)"; + case NETMSGTYPE_CloseFileResp: return "CloseFileResp (3004)"; + case NETMSGTYPE_OpenLocalFile: return "OpenLocalFile (3005)"; + case NETMSGTYPE_OpenLocalFileResp: return "OpenLocalFileResp (3006)"; + case NETMSGTYPE_CloseChunkFile: return "CloseChunkFile (3007)"; + case NETMSGTYPE_CloseChunkFileResp: return "CloseChunkFileResp (3008)"; + case NETMSGTYPE_WriteLocalFile: return "WriteLocalFile (3009)"; + case NETMSGTYPE_WriteLocalFileResp: return "WriteLocalFileResp (3010)"; + case NETMSGTYPE_FSyncLocalFile: return "FSyncLocalFile (3013)"; + case NETMSGTYPE_FSyncLocalFileResp: return "FSyncLocalFileResp (3014)"; + case NETMSGTYPE_ReadLocalFileV2: return "ReadLocalFileV2 (3019)"; + case NETMSGTYPE_RefreshSession: return "RefreshSession (3021)"; + case NETMSGTYPE_RefreshSessionResp: return "RefreshSessionResp (3022)"; + case NETMSGTYPE_LockGranted: return "LockGranted (3023)"; + case NETMSGTYPE_FLockEntry: return "FLockEntry (3025)"; + case NETMSGTYPE_FLockEntryResp: return "FLockEntryResp (3026)"; + case NETMSGTYPE_FLockRange: return "FLockRange (3027)"; + case NETMSGTYPE_FLockRangeResp: return "FLockRangeResp (3028)"; + case NETMSGTYPE_FLockAppend: return "FLockAppend (3029)"; + case NETMSGTYPE_FLockAppendResp: return "FLockAppendResp (3030)"; + case NETMSGTYPE_AckNotify: return "AckNotify (3031)"; + case NETMSGTYPE_AckNotifyResp: return "AckNotifyResp (3032)"; + case NETMSGTYPE_BumpFileVersion: return "BumpFileVersion (3033)"; + case NETMSGTYPE_BumpFileVersionResp: return "BumpFileVersionResp (3034)"; + case NETMSGTYPE_GetFileVersion: return "GetFileVersion (3035)"; + case NETMSGTYPE_GetFileVersionResp: return "GetFileVersionResp (3036)"; +#ifdef BEEGFS_NVFS + case NETMSGTYPE_WriteLocalFileRDMA: return "WriteLocalFileRDMA (3037)"; + case NETMSGTYPE_WriteLocalFileRDMAResp: return "WriteLocalFileRDMAResp (3038)"; + case NETMSGTYPE_ReadLocalFileRDMA: return "ReadLocalFileRDMA (3039)"; + case NETMSGTYPE_ReadLocalFileRDMAResp: return "ReadLocalFileRDMAResp (3040)"; +#endif /* BEEGFS_NVFS */ + case NETMSGTYPE_SetChannelDirect: return "SetChannelDirect (4001)"; + case NETMSGTYPE_Ack: return "Ack (4003)"; + case NETMSGTYPE_Dummy: return "Dummy (4005)"; + case NETMSGTYPE_AuthenticateChannel: return "AuthenticateChannel (4007)"; + case NETMSGTYPE_GenericResponse: return "GenericResponse (4009)"; + case NETMSGTYPE_PeerInfo: return "PeerInfo (4011)"; + case NETMSGTYPE_GetNodesFromRootMetaNode: return "GetNodesFromRootMetaNode (6001)"; + case NETMSGTYPE_SendNodesList: return "SendNodesList (6002)"; + case NETMSGTYPE_RequestMetaData: return "RequestMetaData (6003)"; + case NETMSGTYPE_RequestStorageData: return "RequestStorageData (6004)"; + case NETMSGTYPE_RequestMetaDataResp: return "RequestMetaDataResp (6005)"; + case NETMSGTYPE_RequestStorageDataResp: return "RequestStorageDataResp (6006)"; + case NETMSGTYPE_RetrieveDirEntries: return "RetrieveDirEntries (7001)"; + case NETMSGTYPE_RetrieveDirEntriesResp: return "RetrieveDirEntriesResp (7002)"; + case NETMSGTYPE_RetrieveInodes: return "RetrieveInodes (7003)"; + case NETMSGTYPE_RetrieveInodesResp: return "RetrieveInodesResp (7004)"; + case NETMSGTYPE_RetrieveChunks: return "RetrieveChunks (7005)"; + case NETMSGTYPE_RetrieveChunksResp: return "RetrieveChunksResp (7006)"; + case NETMSGTYPE_RetrieveFsIDs: return "RetrieveFsIDs (7007)"; + case NETMSGTYPE_RetrieveFsIDsResp: return "RetrieveFsIDsResp (7008)"; + case NETMSGTYPE_DeleteDirEntries: return "DeleteDirEntries (7009)"; + case NETMSGTYPE_DeleteDirEntriesResp: return "DeleteDirEntriesResp (7010)"; + case NETMSGTYPE_CreateDefDirInodes: return "CreateDefDirInodes (7011)"; + case NETMSGTYPE_CreateDefDirInodesResp: return "CreateDefDirInodesResp (7012)"; + case NETMSGTYPE_FixInodeOwnersInDentry: return "FixInodeOwnersInDentry (7013)"; + case NETMSGTYPE_FixInodeOwnersInDentryResp: return "FixInodeOwnersInDentryResp (7014)"; + case NETMSGTYPE_FixInodeOwners: return "FixInodeOwners (7015)"; + case NETMSGTYPE_FixInodeOwnersResp: return "FixInodeOwnersResp (7016)"; + case NETMSGTYPE_LinkToLostAndFound: return "LinkToLostAndFound (7017)"; + case NETMSGTYPE_LinkToLostAndFoundResp: return "LinkToLostAndFoundResp (7018)"; + case NETMSGTYPE_DeleteChunks: return "DeleteChunks (7019)"; + case NETMSGTYPE_DeleteChunksResp: return "DeleteChunksResp (7020)"; + case NETMSGTYPE_CreateEmptyContDirs: return "CreateEmptyContDirs (7021)"; + case NETMSGTYPE_CreateEmptyContDirsResp: return "CreateEmptyContDirsResp (7022)"; + case NETMSGTYPE_UpdateFileAttribs: return "UpdateFileAttribs (7023)"; + case NETMSGTYPE_UpdateFileAttribsResp: return "UpdateFileAttribsResp (7024)"; + case NETMSGTYPE_UpdateDirAttribs: return "UpdateDirAttribs (7025)"; + case NETMSGTYPE_UpdateDirAttribsResp: return "UpdateDirAttribsResp (7026)"; + case NETMSGTYPE_RemoveInodes: return "RemoveInodes (7027)"; + case NETMSGTYPE_RemoveInodesResp: return "RemoveInodesResp (7028)"; + case NETMSGTYPE_RecreateFsIDs: return "RecreateFsIDs (7031)"; + case NETMSGTYPE_RecreateFsIDsResp: return "RecreateFsIDsResp (7032)"; + case NETMSGTYPE_RecreateDentries: return "RecreateDentries (7033)"; + case NETMSGTYPE_RecreateDentriesResp: return "RecreateDentriesResp (7034)"; + case NETMSGTYPE_FsckModificationEvent: return "FsckModificationEvent (7035)"; + case NETMSGTYPE_FsckSetEventLogging: return "FsckSetEventLogging (7036)"; + case NETMSGTYPE_FsckSetEventLoggingResp: return "FsckSetEventLoggingResp (7037)"; + case NETMSGTYPE_FetchFsckChunkList: return "FetchFsckChunkList (7038)"; + case NETMSGTYPE_FetchFsckChunkListResp: return "FetchFsckChunkListResp (7039)"; + case NETMSGTYPE_AdjustChunkPermissions: return "AdjustChunkPermissions (7040)"; + case NETMSGTYPE_AdjustChunkPermissionsResp: return "AdjustChunkPermissionsResp (7041)"; + case NETMSGTYPE_MoveChunkFile: return "MoveChunkFile (7042)"; + case NETMSGTYPE_MoveChunkFileResp: return "MoveChunkFileResp (7043)"; + case NETMSGTYPE_CheckAndRepairDupInode: return "CheckAndRepairDupInode (7044)"; + case NETMSGTYPE_CheckAndRepairDupInodeResp: return "CheckAndRepairDupInodeResp (7045)"; + } + + return "unknown (" + std::to_string(type) + ")"; +} + diff --git a/common/source/common/net/message/NetMessageTypes.h b/common/source/common/net/message/NetMessageTypes.h new file mode 100644 index 0000000..5ec4bea --- /dev/null +++ b/common/source/common/net/message/NetMessageTypes.h @@ -0,0 +1,282 @@ +#pragma once + +/* This file MUST be kept in sync with the corresponding client file! + * See fhgfs_client/source/closed/common/net/message/NetMessageTypes.h + * + * Also do not forget to add net NetMessages to 'class NetMsgStrMapping' + */ + +// invalid messages +#define NETMSGTYPE_Invalid 0 + +// nodes messages +#define NETMSGTYPE_RemoveNode 1013 +#define NETMSGTYPE_RemoveNodeResp 1014 +#define NETMSGTYPE_GetNodes 1017 +#define NETMSGTYPE_GetNodesResp 1018 +#define NETMSGTYPE_HeartbeatRequest 1019 +#define NETMSGTYPE_Heartbeat 1020 +#define NETMSGTYPE_GetNodeCapacityPools 1021 +#define NETMSGTYPE_GetNodeCapacityPoolsResp 1022 +#define NETMSGTYPE_MapTargets 1023 +#define NETMSGTYPE_MapTargetsResp 1024 +#define NETMSGTYPE_GetTargetMappings 1025 +#define NETMSGTYPE_GetTargetMappingsResp 1026 +#define NETMSGTYPE_UnmapTarget 1027 +#define NETMSGTYPE_UnmapTargetResp 1028 +#define NETMSGTYPE_GenericDebug 1029 +#define NETMSGTYPE_GenericDebugResp 1030 +#define NETMSGTYPE_GetClientStats 1031 +#define NETMSGTYPE_GetClientStatsResp 1032 +#define NETMSGTYPE_RefreshCapacityPools 1035 +#define NETMSGTYPE_StorageBenchControlMsg 1037 +#define NETMSGTYPE_StorageBenchControlMsgResp 1038 +#define NETMSGTYPE_RegisterNode 1039 +#define NETMSGTYPE_RegisterNodeResp 1040 +#define NETMSGTYPE_RegisterTarget 1041 +#define NETMSGTYPE_RegisterTargetResp 1042 +#define NETMSGTYPE_SetMirrorBuddyGroup 1045 +#define NETMSGTYPE_SetMirrorBuddyGroupResp 1046 +#define NETMSGTYPE_GetMirrorBuddyGroups 1047 +#define NETMSGTYPE_GetMirrorBuddyGroupsResp 1048 +#define NETMSGTYPE_GetTargetStates 1049 +#define NETMSGTYPE_GetTargetStatesResp 1050 +#define NETMSGTYPE_RefreshTargetStates 1051 +#define NETMSGTYPE_GetStatesAndBuddyGroups 1053 +#define NETMSGTYPE_GetStatesAndBuddyGroupsResp 1054 +#define NETMSGTYPE_SetTargetConsistencyStates 1055 +#define NETMSGTYPE_SetTargetConsistencyStatesResp 1056 +#define NETMSGTYPE_ChangeTargetConsistencyStates 1057 +#define NETMSGTYPE_ChangeTargetConsistencyStatesResp 1058 +#define NETMSGTYPE_PublishCapacities 1059 +#define NETMSGTYPE_RemoveBuddyGroup 1060 +#define NETMSGTYPE_RemoveBuddyGroupResp 1061 +#define NETMSGTYPE_GetTargetConsistencyStates 1062 +#define NETMSGTYPE_GetTargetConsistencyStatesResp 1063 +#define NETMSGTYPE_AddStoragePool 1064 +#define NETMSGTYPE_AddStoragePoolResp 1065 +#define NETMSGTYPE_GetStoragePools 1066 +#define NETMSGTYPE_GetStoragePoolsResp 1067 +#define NETMSGTYPE_ModifyStoragePool 1068 +#define NETMSGTYPE_ModifyStoragePoolResp 1069 +#define NETMSGTYPE_RefreshStoragePools 1070 +#define NETMSGTYPE_RemoveStoragePool 1071 +#define NETMSGTYPE_RemoveStoragePoolResp 1072 + +// storage messages +#define NETMSGTYPE_MkDir 2001 +#define NETMSGTYPE_MkDirResp 2002 +#define NETMSGTYPE_RmDir 2003 +#define NETMSGTYPE_RmDirResp 2004 +#define NETMSGTYPE_MkFile 2005 +#define NETMSGTYPE_MkFileResp 2006 +#define NETMSGTYPE_UnlinkFile 2007 +#define NETMSGTYPE_UnlinkFileResp 2008 +#define NETMSGTYPE_UnlinkLocalFile 2011 +#define NETMSGTYPE_UnlinkLocalFileResp 2012 +#define NETMSGTYPE_Stat 2015 +#define NETMSGTYPE_StatResp 2016 +#define NETMSGTYPE_GetChunkFileAttribs 2017 +#define NETMSGTYPE_GetChunkFileAttribsResp 2018 +#define NETMSGTYPE_TruncFile 2019 +#define NETMSGTYPE_TruncFileResp 2020 +#define NETMSGTYPE_TruncLocalFile 2021 +#define NETMSGTYPE_TruncLocalFileResp 2022 +#define NETMSGTYPE_Rename 2023 +#define NETMSGTYPE_RenameResp 2024 +#define NETMSGTYPE_SetAttr 2025 +#define NETMSGTYPE_SetAttrResp 2026 +#define NETMSGTYPE_ListDirFromOffset 2029 +#define NETMSGTYPE_ListDirFromOffsetResp 2030 +#define NETMSGTYPE_StatStoragePath 2031 +#define NETMSGTYPE_StatStoragePathResp 2032 +#define NETMSGTYPE_SetLocalAttr 2033 +#define NETMSGTYPE_SetLocalAttrResp 2034 +#define NETMSGTYPE_FindOwner 2035 +#define NETMSGTYPE_FindOwnerResp 2036 +#define NETMSGTYPE_MkLocalDir 2037 +#define NETMSGTYPE_MkLocalDirResp 2038 +#define NETMSGTYPE_RmLocalDir 2039 +#define NETMSGTYPE_RmLocalDirResp 2040 +#define NETMSGTYPE_MovingFileInsert 2041 +#define NETMSGTYPE_MovingFileInsertResp 2042 +#define NETMSGTYPE_MovingDirInsert 2043 +#define NETMSGTYPE_MovingDirInsertResp 2044 +#define NETMSGTYPE_GetEntryInfo 2045 +#define NETMSGTYPE_GetEntryInfoResp 2046 +#define NETMSGTYPE_SetDirPattern 2047 +#define NETMSGTYPE_SetDirPatternResp 2048 +#define NETMSGTYPE_GetHighResStats 2051 +#define NETMSGTYPE_GetHighResStatsResp 2052 +#define NETMSGTYPE_MkFileWithPattern 2053 +#define NETMSGTYPE_MkFileWithPatternResp 2054 +#define NETMSGTYPE_RefreshEntryInfo 2055 +#define NETMSGTYPE_RefreshEntryInfoResp 2056 +#define NETMSGTYPE_RmDirEntry 2057 +#define NETMSGTYPE_RmDirEntryResp 2058 +#define NETMSGTYPE_LookupIntent 2059 +#define NETMSGTYPE_LookupIntentResp 2060 +#define NETMSGTYPE_FindLinkOwner 2063 +#define NETMSGTYPE_FindLinkOwnerResp 2064 +#define NETMSGTYPE_MirrorMetadata 2067 +#define NETMSGTYPE_MirrorMetadataResp 2068 +#define NETMSGTYPE_SetMetadataMirroring 2069 +#define NETMSGTYPE_SetMetadataMirroringResp 2070 +#define NETMSGTYPE_Hardlink 2071 +#define NETMSGTYPE_HardlinkResp 2072 +#define NETMSGTYPE_SetQuota 2075 +#define NETMSGTYPE_SetQuotaResp 2076 +#define NETMSGTYPE_SetExceededQuota 2077 +#define NETMSGTYPE_SetExceededQuotaResp 2078 +#define NETMSGTYPE_RequestExceededQuota 2079 +#define NETMSGTYPE_RequestExceededQuotaResp 2080 +#define NETMSGTYPE_UpdateDirParent 2081 +#define NETMSGTYPE_UpdateDirParentResp 2082 +#define NETMSGTYPE_ResyncLocalFile 2083 +#define NETMSGTYPE_ResyncLocalFileResp 2084 +#define NETMSGTYPE_StartStorageTargetResync 2085 +#define NETMSGTYPE_StartStorageTargetResyncResp 2086 +#define NETMSGTYPE_StorageResyncStarted 2087 +#define NETMSGTYPE_StorageResyncStartedResp 2088 +#define NETMSGTYPE_ListChunkDirIncremental 2089 +#define NETMSGTYPE_ListChunkDirIncrementalResp 2090 +#define NETMSGTYPE_RmChunkPaths 2091 +#define NETMSGTYPE_RmChunkPathsResp 2092 +#define NETMSGTYPE_GetStorageResyncStats 2093 +#define NETMSGTYPE_GetStorageResyncStatsResp 2094 +#define NETMSGTYPE_SetLastBuddyCommOverride 2095 +#define NETMSGTYPE_SetLastBuddyCommOverrideResp 2096 +#define NETMSGTYPE_GetQuotaInfo 2097 +#define NETMSGTYPE_GetQuotaInfoResp 2098 +#define NETMSGTYPE_SetStorageTargetInfo 2099 +#define NETMSGTYPE_SetStorageTargetInfoResp 2100 +#define NETMSGTYPE_ListXAttr 2101 +#define NETMSGTYPE_ListXAttrResp 2102 +#define NETMSGTYPE_GetXAttr 2103 +#define NETMSGTYPE_GetXAttrResp 2104 +#define NETMSGTYPE_RemoveXAttr 2105 +#define NETMSGTYPE_RemoveXAttrResp 2106 +#define NETMSGTYPE_SetXAttr 2107 +#define NETMSGTYPE_SetXAttrResp 2108 +#define NETMSGTYPE_GetDefaultQuota 2109 +#define NETMSGTYPE_GetDefaultQuotaResp 2110 +#define NETMSGTYPE_SetDefaultQuota 2111 +#define NETMSGTYPE_SetDefaultQuotaResp 2112 +#define NETMSGTYPE_ResyncSessionStore 2113 +#define NETMSGTYPE_ResyncSessionStoreResp 2114 +#define NETMSGTYPE_ResyncRawInodes 2115 +#define NETMSGTYPE_ResyncRawInodesResp 2116 +#define NETMSGTYPE_GetMetaResyncStats 2117 +#define NETMSGTYPE_GetMetaResyncStatsResp 2118 +#define NETMSGTYPE_MoveFileInode 2119 +#define NETMSGTYPE_MoveFileInodeResp 2120 +#define NETMSGTYPE_UnlinkLocalFileInode 2121 +#define NETMSGTYPE_UnlinkLocalFileInodeResp 2122 +#define NETMSGTYPE_SetFilePattern 2123 +#define NETMSGTYPE_SetFilePatternResp 2124 +#define NETMSGTYPE_CpChunkPaths 2125 +#define NETMSGTYPE_CpChunkPathsResp 2126 +#define NETMSGTYPE_ChunkBalance 2127 +#define NETMSGTYPE_ChunkBalanceResp 2128 +#define NETMSGTYPE_StripePatternUpdate 2129 +#define NETMSGTYPE_StripePatternUpdateResp 2130 +#define NETMSGTYPE_SetFileState 2131 +#define NETMSGTYPE_SetFileStateResp 2132 + +// session messages +#define NETMSGTYPE_OpenFile 3001 +#define NETMSGTYPE_OpenFileResp 3002 +#define NETMSGTYPE_CloseFile 3003 +#define NETMSGTYPE_CloseFileResp 3004 +#define NETMSGTYPE_OpenLocalFile 3005 +#define NETMSGTYPE_OpenLocalFileResp 3006 +#define NETMSGTYPE_CloseChunkFile 3007 +#define NETMSGTYPE_CloseChunkFileResp 3008 +#define NETMSGTYPE_WriteLocalFile 3009 +#define NETMSGTYPE_WriteLocalFileResp 3010 +#define NETMSGTYPE_FSyncLocalFile 3013 +#define NETMSGTYPE_FSyncLocalFileResp 3014 +#define NETMSGTYPE_ReadLocalFileV2 3019 +#define NETMSGTYPE_RefreshSession 3021 +#define NETMSGTYPE_RefreshSessionResp 3022 +#define NETMSGTYPE_LockGranted 3023 +#define NETMSGTYPE_FLockEntry 3025 +#define NETMSGTYPE_FLockEntryResp 3026 +#define NETMSGTYPE_FLockRange 3027 +#define NETMSGTYPE_FLockRangeResp 3028 +#define NETMSGTYPE_FLockAppend 3029 +#define NETMSGTYPE_FLockAppendResp 3030 +#define NETMSGTYPE_AckNotify 3031 +#define NETMSGTYPE_AckNotifyResp 3032 +#define NETMSGTYPE_BumpFileVersion 3033 +#define NETMSGTYPE_BumpFileVersionResp 3034 +#define NETMSGTYPE_GetFileVersion 3035 +#define NETMSGTYPE_GetFileVersionResp 3036 +#ifdef BEEGFS_NVFS +#define NETMSGTYPE_WriteLocalFileRDMA 3037 +#define NETMSGTYPE_WriteLocalFileRDMAResp 3038 +#define NETMSGTYPE_ReadLocalFileRDMA 3039 +#define NETMSGTYPE_ReadLocalFileRDMAResp 3040 +#endif /* BEEGFS_NVFS */ + +// control messages +#define NETMSGTYPE_SetChannelDirect 4001 +#define NETMSGTYPE_Ack 4003 +#define NETMSGTYPE_Dummy 4005 +#define NETMSGTYPE_AuthenticateChannel 4007 +#define NETMSGTYPE_GenericResponse 4009 +#define NETMSGTYPE_PeerInfo 4011 + +// mon messages +#define NETMSGTYPE_GetNodesFromRootMetaNode 6001 +#define NETMSGTYPE_SendNodesList 6002 +#define NETMSGTYPE_RequestMetaData 6003 +#define NETMSGTYPE_RequestStorageData 6004 +#define NETMSGTYPE_RequestMetaDataResp 6005 +#define NETMSGTYPE_RequestStorageDataResp 6006 + +// fsck messages +#define NETMSGTYPE_RetrieveDirEntries 7001 +#define NETMSGTYPE_RetrieveDirEntriesResp 7002 +#define NETMSGTYPE_RetrieveInodes 7003 +#define NETMSGTYPE_RetrieveInodesResp 7004 +#define NETMSGTYPE_RetrieveChunks 7005 +#define NETMSGTYPE_RetrieveChunksResp 7006 +#define NETMSGTYPE_RetrieveFsIDs 7007 +#define NETMSGTYPE_RetrieveFsIDsResp 7008 +#define NETMSGTYPE_DeleteDirEntries 7009 +#define NETMSGTYPE_DeleteDirEntriesResp 7010 +#define NETMSGTYPE_CreateDefDirInodes 7011 +#define NETMSGTYPE_CreateDefDirInodesResp 7012 +#define NETMSGTYPE_FixInodeOwnersInDentry 7013 +#define NETMSGTYPE_FixInodeOwnersInDentryResp 7014 +#define NETMSGTYPE_FixInodeOwners 7015 +#define NETMSGTYPE_FixInodeOwnersResp 7016 +#define NETMSGTYPE_LinkToLostAndFound 7017 +#define NETMSGTYPE_LinkToLostAndFoundResp 7018 +#define NETMSGTYPE_DeleteChunks 7019 +#define NETMSGTYPE_DeleteChunksResp 7020 +#define NETMSGTYPE_CreateEmptyContDirs 7021 +#define NETMSGTYPE_CreateEmptyContDirsResp 7022 +#define NETMSGTYPE_UpdateFileAttribs 7023 +#define NETMSGTYPE_UpdateFileAttribsResp 7024 +#define NETMSGTYPE_UpdateDirAttribs 7025 +#define NETMSGTYPE_UpdateDirAttribsResp 7026 +#define NETMSGTYPE_RemoveInodes 7027 +#define NETMSGTYPE_RemoveInodesResp 7028 +#define NETMSGTYPE_RecreateFsIDs 7031 +#define NETMSGTYPE_RecreateFsIDsResp 7032 +#define NETMSGTYPE_RecreateDentries 7033 +#define NETMSGTYPE_RecreateDentriesResp 7034 +#define NETMSGTYPE_FsckModificationEvent 7035 +#define NETMSGTYPE_FsckSetEventLogging 7036 +#define NETMSGTYPE_FsckSetEventLoggingResp 7037 +#define NETMSGTYPE_FetchFsckChunkList 7038 +#define NETMSGTYPE_FetchFsckChunkListResp 7039 +#define NETMSGTYPE_AdjustChunkPermissions 7040 +#define NETMSGTYPE_AdjustChunkPermissionsResp 7041 +#define NETMSGTYPE_MoveChunkFile 7042 +#define NETMSGTYPE_MoveChunkFileResp 7043 +#define NETMSGTYPE_CheckAndRepairDupInode 7044 +#define NETMSGTYPE_CheckAndRepairDupInodeResp 7045 + diff --git a/common/source/common/net/message/SimpleInt64Msg.h b/common/source/common/net/message/SimpleInt64Msg.h new file mode 100644 index 0000000..1254d5a --- /dev/null +++ b/common/source/common/net/message/SimpleInt64Msg.h @@ -0,0 +1,30 @@ +#pragma once + +#include "NetMessage.h" + +class SimpleInt64Msg : public NetMessageSerdes +{ + protected: + SimpleInt64Msg(unsigned short msgType, int64_t value) : BaseType(msgType) + { + this->value = value; + } + + SimpleInt64Msg(unsigned short msgType) : BaseType(msgType) + { + } + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } + + private: + int64_t value; + + public: + int64_t getValue() const { return value; } +}; + diff --git a/common/source/common/net/message/SimpleIntMsg.h b/common/source/common/net/message/SimpleIntMsg.h new file mode 100644 index 0000000..a131e3b --- /dev/null +++ b/common/source/common/net/message/SimpleIntMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include "NetMessage.h" + +class SimpleIntMsg : public NetMessageSerdes +{ + protected: + SimpleIntMsg(unsigned short msgType, int value) : BaseType(msgType) + { + this->value = value; + } + + SimpleIntMsg(unsigned short msgType) : BaseType(msgType) + { + } + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } + + private: + int32_t value; + + public: + int getValue() const { return value; } +}; + diff --git a/common/source/common/net/message/SimpleIntStringMsg.h b/common/source/common/net/message/SimpleIntStringMsg.h new file mode 100644 index 0000000..c776c62 --- /dev/null +++ b/common/source/common/net/message/SimpleIntStringMsg.h @@ -0,0 +1,47 @@ +#pragma once + +#include "NetMessage.h" + +/** + * Simple message containing an integer value and a string (e.g. int error code and human-readable + * explantion with more details as string). + */ +class SimpleIntStringMsg : public NetMessageSerdes +{ + protected: + /** + * @param strValue just a reference + */ + SimpleIntStringMsg(unsigned short msgType, int intValue, std::string strValue) : + BaseType(msgType), + intValue(intValue), strValue(std::move(strValue)) + { } + + /** + * For deserialization only! + */ + SimpleIntStringMsg(unsigned short msgType) : BaseType(msgType) + { + } + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->intValue + % obj->strValue; + } + + private: + int32_t intValue; + + std::string strValue; + + public: + int getIntValue() const { return intValue; } + + const std::string& getStrValue() const { return strValue; } +}; + + diff --git a/common/source/common/net/message/SimpleMsg.h b/common/source/common/net/message/SimpleMsg.h new file mode 100644 index 0000000..2db006d --- /dev/null +++ b/common/source/common/net/message/SimpleMsg.h @@ -0,0 +1,29 @@ +#pragma once + +#include "NetMessage.h" + +/** + * Simple messages are defined by the header (resp. the msgType) only and + * require no additional data + */ +class SimpleMsg : public NetMessage +{ + public: + SimpleMsg(unsigned short msgType) : NetMessage(msgType) + { + } + + protected: + virtual void serializePayload(Serializer& ser) const + { + // nothing to be done here for simple messages + } + + virtual bool deserializePayload(const char* buf, size_t bufLen) + { + // nothing to be done here for simple messages + return true; + } +}; + + diff --git a/common/source/common/net/message/SimpleStringMsg.h b/common/source/common/net/message/SimpleStringMsg.h new file mode 100644 index 0000000..208c6a2 --- /dev/null +++ b/common/source/common/net/message/SimpleStringMsg.h @@ -0,0 +1,47 @@ +#pragma once + +#include "NetMessage.h" + +class SimpleStringMsg : public NetMessageSerdes +{ + protected: + /** + * @param value just a reference + */ + SimpleStringMsg(unsigned short msgType, const char* value) : BaseType(msgType) + { + this->value = value; + this->valueLen = strlen(value); + } + + /** + * @param value just a reference + */ + SimpleStringMsg(unsigned short msgType, const std::string& value) : BaseType(msgType) + { + this->value = value.c_str(); + this->valueLen = value.length(); + } + + /** + * For deserialization only! + */ + SimpleStringMsg(unsigned short msgType) : BaseType(msgType) + { + } + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::rawString(obj->value, obj->valueLen); + } + + private: + const char* value; + unsigned valueLen; + + public: + const char* getValue() const { return value; } +}; + diff --git a/common/source/common/net/message/SimpleUInt16Msg.h b/common/source/common/net/message/SimpleUInt16Msg.h new file mode 100644 index 0000000..2d3415b --- /dev/null +++ b/common/source/common/net/message/SimpleUInt16Msg.h @@ -0,0 +1,33 @@ +#pragma once + +#include "NetMessage.h" + +class SimpleUInt16Msg : public NetMessageSerdes +{ + protected: + SimpleUInt16Msg(unsigned short msgType, uint16_t value) : BaseType(msgType) + { + this->value = value; + } + + /** + * Constructor for deserialization only! + */ + SimpleUInt16Msg(unsigned short msgType) : BaseType(msgType) + { + } + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } + + private: + uint16_t value; + + public: + uint16_t getValue() const { return value; } +}; + diff --git a/common/source/common/net/message/control/AckMsg.h b/common/source/common/net/message/control/AckMsg.h new file mode 100644 index 0000000..103ec14 --- /dev/null +++ b/common/source/common/net/message/control/AckMsg.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +// see class AcknowledgeableMsg (fhgfs_common) for a short description + +class AckMsg : public SimpleStringMsg +{ + public: + AckMsg(const char* ackID) : SimpleStringMsg(NETMSGTYPE_Ack, ackID) + { + } + + AckMsg() : SimpleStringMsg(NETMSGTYPE_Ack) + { + } +}; + diff --git a/common/source/common/net/message/control/AuthenticateChannelMsg.h b/common/source/common/net/message/control/AuthenticateChannelMsg.h new file mode 100644 index 0000000..cf640e1 --- /dev/null +++ b/common/source/common/net/message/control/AuthenticateChannelMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include + + +class AuthenticateChannelMsg : public SimpleInt64Msg +{ + public: + AuthenticateChannelMsg(uint64_t authHash) : + SimpleInt64Msg(NETMSGTYPE_AuthenticateChannel, authHash) + { + } + + /** + * For deserialization only + */ + AuthenticateChannelMsg() : SimpleInt64Msg(NETMSGTYPE_AuthenticateChannel) + { + } + + + private: + + + public: + // getters & setters + uint64_t getAuthHash() + { + return getValue(); + } + +}; + diff --git a/common/source/common/net/message/control/AuthenticateChannelMsgEx.cpp b/common/source/common/net/message/control/AuthenticateChannelMsgEx.cpp new file mode 100644 index 0000000..c09e284 --- /dev/null +++ b/common/source/common/net/message/control/AuthenticateChannelMsgEx.cpp @@ -0,0 +1,29 @@ +#include +#include +#include +#include "AuthenticateChannelMsgEx.h" + +bool AuthenticateChannelMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "AuthenticateChannel incoming"; + + AbstractApp* app = PThread::getCurrentThreadApp(); + auto cfg = app->getCommonConfig(); + uint64_t authHash = cfg->getConnAuthHash(); + + if(authHash && (authHash != getAuthHash() ) ) + { // authentication failed + LogContext(logContext).log(Log_WARNING, + "Peer sent wrong authentication: " + ctx.peerName() ); + } + else + { + ctx.getSocket()->setIsAuthenticated(); + } + + // note: this message does not require a response + + return true; +} + + diff --git a/common/source/common/net/message/control/AuthenticateChannelMsgEx.h b/common/source/common/net/message/control/AuthenticateChannelMsgEx.h new file mode 100644 index 0000000..c281db9 --- /dev/null +++ b/common/source/common/net/message/control/AuthenticateChannelMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include + + +class AuthenticateChannelMsgEx : public AuthenticateChannelMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/common/source/common/net/message/control/DummyMsg.h b/common/source/common/net/message/control/DummyMsg.h new file mode 100644 index 0000000..e077d83 --- /dev/null +++ b/common/source/common/net/message/control/DummyMsg.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class DummyMsg : public SimpleMsg +{ + public: + DummyMsg() : SimpleMsg(NETMSGTYPE_Dummy) + { + } +}; + + diff --git a/common/source/common/net/message/control/GenericResponseMsg.h b/common/source/common/net/message/control/GenericResponseMsg.h new file mode 100644 index 0000000..f0842ed --- /dev/null +++ b/common/source/common/net/message/control/GenericResponseMsg.h @@ -0,0 +1,67 @@ +#pragma once + +#include + + +/** + * Note: Remember to keep this in sync with client. + */ +enum GenericRespMsgCode +{ + GenericRespMsgCode_TRYAGAIN = 0, /* requestor shall try again in a few seconds */ + GenericRespMsgCode_INDIRECTCOMMERR = 1, /* indirect communication error and requestor should + try again (e.g. msg forwarding to other server failed) */ + GenericRespMsgCode_NEWSEQNOBASE = 2, /* client has restarted its seq# sequence. server provides + the new starting seq#. */ +}; + + +/** + * A generic response that can be sent as a reply to any message. This special control message will + * be handled internally by the requestors MessageTk::requestResponse...() method. + * + * This is intended to submit internal information (like asking for a communication retry) to the + * requestors MessageTk through the control code. So the MessageTk caller on the requestor side + * will never actually see this GenericResponseMsg. + * + * The message string is intended to provide additional human-readable information like why this + * control code was submitted instead of the actually expected response msg. + */ +class GenericResponseMsg : public SimpleIntStringMsg +{ + public: + /** + * @param controlCode GenericRespMsgCode_... + * @param logStr human-readable explanation or background information. + */ + GenericResponseMsg(GenericRespMsgCode controlCode, std::string logStr) : + SimpleIntStringMsg(NETMSGTYPE_GenericResponse, controlCode, std::move(logStr)) + { + } + + /** + * For deserialization only. + */ + GenericResponseMsg() : SimpleIntStringMsg(NETMSGTYPE_GenericResponse) + { + } + + + private: + + + public: + // getters & setters + GenericRespMsgCode getControlCode() const + { + return (GenericRespMsgCode)getIntValue(); + } + + const std::string& getLogStr() const + { + return getStrValue(); + } + +}; + + diff --git a/common/source/common/net/message/control/PeerInfoMsg.h b/common/source/common/net/message/control/PeerInfoMsg.h new file mode 100644 index 0000000..5d537f0 --- /dev/null +++ b/common/source/common/net/message/control/PeerInfoMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +class PeerInfoMsg : public NetMessageSerdes +{ + public: + PeerInfoMsg(NodeType type, NumNodeID id) + : BaseType(NETMSGTYPE_PeerInfo), type(type), id(id) + { + } + + /** + * For deserialization only + */ + PeerInfoMsg() : BaseType(NETMSGTYPE_PeerInfo) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->type) + % obj->id; + } + + protected: + NodeType type; + NumNodeID id; +}; + diff --git a/common/source/common/net/message/control/PeerInfoMsgEx.cpp b/common/source/common/net/message/control/PeerInfoMsgEx.cpp new file mode 100644 index 0000000..bba1996 --- /dev/null +++ b/common/source/common/net/message/control/PeerInfoMsgEx.cpp @@ -0,0 +1,16 @@ +#include +#include +#include +#include "PeerInfoMsgEx.h" + +bool PeerInfoMsgEx::processIncoming(ResponseContext& ctx) +{ + ctx.getSocket()->setNodeType(type); + ctx.getSocket()->setNodeID(id); + + // note: this message does not require a response + + return true; +} + + diff --git a/common/source/common/net/message/control/PeerInfoMsgEx.h b/common/source/common/net/message/control/PeerInfoMsgEx.h new file mode 100644 index 0000000..b19e8a2 --- /dev/null +++ b/common/source/common/net/message/control/PeerInfoMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class PeerInfoMsgEx : public PeerInfoMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/common/source/common/net/message/control/SetChannelDirectMsg.h b/common/source/common/net/message/control/SetChannelDirectMsg.h new file mode 100644 index 0000000..7976bd6 --- /dev/null +++ b/common/source/common/net/message/control/SetChannelDirectMsg.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class SetChannelDirectMsg : public SimpleIntMsg +{ + public: + SetChannelDirectMsg(int isDirect) : SimpleIntMsg(NETMSGTYPE_SetChannelDirect, isDirect) + { + } + + /** + * For deserialization only + */ + SetChannelDirectMsg() : SimpleIntMsg(NETMSGTYPE_SetChannelDirect) + { + } +}; + diff --git a/common/source/common/net/message/fsck/AdjustChunkPermissionsMsg.h b/common/source/common/net/message/fsck/AdjustChunkPermissionsMsg.h new file mode 100644 index 0000000..ebb7a21 --- /dev/null +++ b/common/source/common/net/message/fsck/AdjustChunkPermissionsMsg.h @@ -0,0 +1,75 @@ +#pragma once + +#include + +class AdjustChunkPermissionsMsg : public NetMessageSerdes +{ + public: + AdjustChunkPermissionsMsg(unsigned hashDirNum, std::string& currentContDirID, + unsigned maxEntries, int64_t lastHashDirOffset, int64_t lastContDirOffset, + bool isBuddyMirrored) : + BaseType(NETMSGTYPE_AdjustChunkPermissions) + { + this->hashDirNum = hashDirNum; + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->maxEntries = maxEntries; + this->lastHashDirOffset = lastHashDirOffset; + this->lastContDirOffset = lastContDirOffset; + this->isBuddyMirrored = isBuddyMirrored; + } + + AdjustChunkPermissionsMsg() : BaseType(NETMSGTYPE_AdjustChunkPermissions) + { + } + + private: + uint32_t hashDirNum; + const char *currentContDirID; + uint32_t currentContDirIDLen; + uint32_t maxEntries; + int64_t lastHashDirOffset; + int64_t lastContDirOffset; + bool isBuddyMirrored; + + public: + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + unsigned getHashDirNum() const + { + return hashDirNum; + } + + int64_t getLastContDirOffset() const + { + return lastContDirOffset; + } + + int64_t getLastHashDirOffset() const + { + return lastHashDirOffset; + } + + unsigned getMaxEntries() const + { + return maxEntries; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4) + % obj->hashDirNum + % obj->maxEntries + % obj->lastHashDirOffset + % obj->lastContDirOffset + % obj->isBuddyMirrored; + } +}; + diff --git a/common/source/common/net/message/fsck/AdjustChunkPermissionsRespMsg.h b/common/source/common/net/message/fsck/AdjustChunkPermissionsRespMsg.h new file mode 100644 index 0000000..20a35eb --- /dev/null +++ b/common/source/common/net/message/fsck/AdjustChunkPermissionsRespMsg.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +class AdjustChunkPermissionsRespMsg : public NetMessageSerdes +{ + public: + AdjustChunkPermissionsRespMsg(unsigned count, std::string& currentContDirID, + int64_t newHashDirOffset, int64_t newContDirOffset, unsigned errorCount) : + BaseType(NETMSGTYPE_AdjustChunkPermissionsResp) + { + this->count = count; + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->newHashDirOffset = newHashDirOffset; + this->newContDirOffset = newContDirOffset; + this->errorCount = errorCount; + } + + AdjustChunkPermissionsRespMsg() : BaseType(NETMSGTYPE_AdjustChunkPermissionsResp) + { + } + + private: + const char* currentContDirID; + unsigned currentContDirIDLen; + uint32_t count; + int64_t newHashDirOffset; + int64_t newContDirOffset; + uint32_t errorCount; + + public: + // getters & setters + unsigned getCount() const + { + return count; + } + + int64_t getNewHashDirOffset() const + { + return newHashDirOffset; + } + + int64_t getNewContDirOffset() const + { + return newContDirOffset; + } + + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + bool getErrorCount() const + { + return errorCount; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4) + % obj->count + % obj->newHashDirOffset + % obj->newContDirOffset + % obj->errorCount; + } +}; + + diff --git a/common/source/common/net/message/fsck/CheckAndRepairDupInodeMsg.h b/common/source/common/net/message/fsck/CheckAndRepairDupInodeMsg.h new file mode 100644 index 0000000..4348212 --- /dev/null +++ b/common/source/common/net/message/fsck/CheckAndRepairDupInodeMsg.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +class CheckAndRepairDupInodeMsg : public NetMessageSerdes +{ + public: + CheckAndRepairDupInodeMsg(FsckDuplicateInodeInfoVector* items): + BaseType(NETMSGTYPE_CheckAndRepairDupInode), dupInodes(items) + { + } + + CheckAndRepairDupInodeMsg() : BaseType(NETMSGTYPE_CheckAndRepairDupInode) + { + } + + protected: + + // not owned by this object + FsckDuplicateInodeInfoVector* dupInodes; + + // for deserialization + struct + { + FsckDuplicateInodeInfoVector dupInodes; + } parsed; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->dupInodes, obj->parsed.dupInodes); + } + + FsckDuplicateInodeInfoVector& getDuplicateInodes() + { + return *dupInodes; + } +}; + diff --git a/common/source/common/net/message/fsck/CheckAndRepairDupInodeRespMsg.h b/common/source/common/net/message/fsck/CheckAndRepairDupInodeRespMsg.h new file mode 100644 index 0000000..c0244dc --- /dev/null +++ b/common/source/common/net/message/fsck/CheckAndRepairDupInodeRespMsg.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class CheckAndRepairDupInodeRespMsg : public NetMessageSerdes +{ + public: + CheckAndRepairDupInodeRespMsg(StringList failedEntryIDList) : + BaseType(NETMSGTYPE_CheckAndRepairDupInodeResp), failedEntryIDList(std::move(failedEntryIDList)) + { + } + + CheckAndRepairDupInodeRespMsg() : BaseType(NETMSGTYPE_CheckAndRepairDupInodeResp) + { + } + + private: + StringList failedEntryIDList; + + public: + StringList releaseFailedEntryIDList() { return failedEntryIDList; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->failedEntryIDList; + } +}; + diff --git a/common/source/common/net/message/fsck/CreateDefDirInodesMsg.h b/common/source/common/net/message/fsck/CreateDefDirInodesMsg.h new file mode 100644 index 0000000..5e8611c --- /dev/null +++ b/common/source/common/net/message/fsck/CreateDefDirInodesMsg.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class CreateDefDirInodesMsg : public NetMessageSerdes +{ + public: + typedef std::tuple Item; + + CreateDefDirInodesMsg(std::vector items): + BaseType(NETMSGTYPE_CreateDefDirInodes), items(std::move(items)) + { + } + + CreateDefDirInodesMsg() : BaseType(NETMSGTYPE_CreateDefDirInodes) + { + } + + protected: + std::vector items; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->items; + } +}; + diff --git a/common/source/common/net/message/fsck/CreateDefDirInodesRespMsg.h b/common/source/common/net/message/fsck/CreateDefDirInodesRespMsg.h new file mode 100644 index 0000000..089c9c1 --- /dev/null +++ b/common/source/common/net/message/fsck/CreateDefDirInodesRespMsg.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +class CreateDefDirInodesRespMsg : public NetMessageSerdes +{ + public: + CreateDefDirInodesRespMsg(StringList* failedInodeIDs, FsckDirInodeList* createdInodes) : + BaseType(NETMSGTYPE_CreateDefDirInodesResp) + { + this->failedInodeIDs = failedInodeIDs; + this->createdInodes = createdInodes; + } + + virtual ~CreateDefDirInodesRespMsg() { }; + + CreateDefDirInodesRespMsg() : BaseType(NETMSGTYPE_CreateDefDirInodesResp) + { + } + + private: + StringList* failedInodeIDs; + FsckDirInodeList* createdInodes; + + struct { + StringList failedInodeIDs; + FsckDirInodeList createdInodes; + } parsed; + + public: + StringList& getFailedInodeIDs() + { + return *failedInodeIDs; + } + + FsckDirInodeList& getCreatedInodes() + { + return *createdInodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->failedInodeIDs, obj->parsed.failedInodeIDs) + % serdes::backedPtr(obj->createdInodes, obj->parsed.createdInodes); + } +}; + diff --git a/common/source/common/net/message/fsck/CreateEmptyContDirsMsg.h b/common/source/common/net/message/fsck/CreateEmptyContDirsMsg.h new file mode 100644 index 0000000..ea746a0 --- /dev/null +++ b/common/source/common/net/message/fsck/CreateEmptyContDirsMsg.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class CreateEmptyContDirsMsg : public NetMessageSerdes +{ + public: + typedef std::tuple Item; + + CreateEmptyContDirsMsg(std::vector items): + BaseType(NETMSGTYPE_CreateEmptyContDirs), items(std::move(items)) + { + } + + CreateEmptyContDirsMsg() : BaseType(NETMSGTYPE_CreateEmptyContDirs) + { + } + + protected: + std::vector items; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->items; + } +}; + diff --git a/common/source/common/net/message/fsck/CreateEmptyContDirsRespMsg.h b/common/source/common/net/message/fsck/CreateEmptyContDirsRespMsg.h new file mode 100644 index 0000000..ca88f2f --- /dev/null +++ b/common/source/common/net/message/fsck/CreateEmptyContDirsRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +class CreateEmptyContDirsRespMsg : public NetMessageSerdes +{ + public: + CreateEmptyContDirsRespMsg(StringList* failedDirIDs) + : BaseType(NETMSGTYPE_CreateEmptyContDirsResp) + { + this->failedDirIDs = failedDirIDs; + } + + CreateEmptyContDirsRespMsg() : BaseType(NETMSGTYPE_CreateEmptyContDirsResp) + { + } + + + private: + StringList* failedDirIDs; + + // for deserialization + struct { + StringList failedDirIDs; + } parsed; + + public: + StringList& getFailedIDs() + { + return *failedDirIDs; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedDirIDs, obj->parsed.failedDirIDs); + } +}; + diff --git a/common/source/common/net/message/fsck/DeleteChunksMsg.h b/common/source/common/net/message/fsck/DeleteChunksMsg.h new file mode 100644 index 0000000..082922f --- /dev/null +++ b/common/source/common/net/message/fsck/DeleteChunksMsg.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class DeleteChunksMsg : public NetMessageSerdes +{ + public: + DeleteChunksMsg(FsckChunkList* chunks) : BaseType(NETMSGTYPE_DeleteChunks) + { + this->chunks = chunks; + } + + DeleteChunksMsg() : BaseType(NETMSGTYPE_DeleteChunks) + { + } + + private: + FsckChunkList* chunks; + + struct { + FsckChunkList chunks; + } parsed; + + public: + FsckChunkList& getChunks() + { + return *chunks; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->chunks, obj->parsed.chunks); + } +}; + + diff --git a/common/source/common/net/message/fsck/DeleteChunksRespMsg.h b/common/source/common/net/message/fsck/DeleteChunksRespMsg.h new file mode 100644 index 0000000..0c53303 --- /dev/null +++ b/common/source/common/net/message/fsck/DeleteChunksRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class DeleteChunksRespMsg : public NetMessageSerdes +{ + public: + DeleteChunksRespMsg(FsckChunkList* failedChunks) + : BaseType(NETMSGTYPE_DeleteChunksResp) + { + this->failedChunks = failedChunks; + } + + DeleteChunksRespMsg() : BaseType(NETMSGTYPE_DeleteChunksResp) + { + } + + private: + FsckChunkList* failedChunks; + + struct { + FsckChunkList failedChunks; + } parsed; + + public: + FsckChunkList& getFailedChunks() + { + return *failedChunks; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedChunks, obj->parsed.failedChunks); + } +}; + + diff --git a/common/source/common/net/message/fsck/DeleteDirEntriesMsg.h b/common/source/common/net/message/fsck/DeleteDirEntriesMsg.h new file mode 100644 index 0000000..c56e66c --- /dev/null +++ b/common/source/common/net/message/fsck/DeleteDirEntriesMsg.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class DeleteDirEntriesMsg : public NetMessageSerdes +{ + public: + DeleteDirEntriesMsg(FsckDirEntryList* entries) : BaseType(NETMSGTYPE_DeleteDirEntries) + { + this->entries = entries; + } + + DeleteDirEntriesMsg() : BaseType(NETMSGTYPE_DeleteDirEntries) + { + } + + private: + FsckDirEntryList* entries; + + struct { + FsckDirEntryList entries; + } parsed; + + public: + FsckDirEntryList& getEntries() + { + return *entries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->entries, obj->parsed.entries); + } +}; + + diff --git a/common/source/common/net/message/fsck/DeleteDirEntriesRespMsg.h b/common/source/common/net/message/fsck/DeleteDirEntriesRespMsg.h new file mode 100644 index 0000000..c9891ab --- /dev/null +++ b/common/source/common/net/message/fsck/DeleteDirEntriesRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class DeleteDirEntriesRespMsg : public NetMessageSerdes +{ + public: + DeleteDirEntriesRespMsg(FsckDirEntryList* failedEntries) : + BaseType(NETMSGTYPE_DeleteDirEntriesResp) + { + this->failedEntries = failedEntries; + } + + DeleteDirEntriesRespMsg() : BaseType(NETMSGTYPE_DeleteDirEntriesResp) + { + } + + private: + FsckDirEntryList* failedEntries; + + struct { + FsckDirEntryList failedEntries; + } parsed; + + public: + FsckDirEntryList& getFailedEntries() + { + return *failedEntries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedEntries, obj->parsed.failedEntries); + } +}; + + diff --git a/common/source/common/net/message/fsck/FetchFsckChunkListMsg.h b/common/source/common/net/message/fsck/FetchFsckChunkListMsg.h new file mode 100644 index 0000000..9abdb96 --- /dev/null +++ b/common/source/common/net/message/fsck/FetchFsckChunkListMsg.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +class FetchFsckChunkListMsg: public NetMessageSerdes +{ + public: + /* + * @param maxNumChunks max number of chunks to fetch at once + */ + FetchFsckChunkListMsg(unsigned maxNumChunks, FetchFsckChunkListStatus lastStatus, + bool forceRestart) : + BaseType(NETMSGTYPE_FetchFsckChunkList), + maxNumChunks(maxNumChunks), + lastStatus(lastStatus), + forceRestart(forceRestart) + { } + + // only for deserialization + FetchFsckChunkListMsg() : BaseType(NETMSGTYPE_FetchFsckChunkList) + { } + + private: + uint32_t maxNumChunks; + FetchFsckChunkListStatus lastStatus; + bool forceRestart; + + public: + unsigned getMaxNumChunks() const { return maxNumChunks; } + FetchFsckChunkListStatus getLastStatus() const { return lastStatus; } + bool getForceRestart() const { return forceRestart; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->maxNumChunks + % serdes::as(obj->lastStatus) + % obj->forceRestart; + } +}; + diff --git a/common/source/common/net/message/fsck/FetchFsckChunkListRespMsg.h b/common/source/common/net/message/fsck/FetchFsckChunkListRespMsg.h new file mode 100644 index 0000000..4cf947a --- /dev/null +++ b/common/source/common/net/message/fsck/FetchFsckChunkListRespMsg.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FetchFsckChunkListRespMsg: public NetMessageSerdes +{ + public: + FetchFsckChunkListRespMsg(FsckChunkList* chunkList, FetchFsckChunkListStatus status) : + BaseType(NETMSGTYPE_FetchFsckChunkListResp) + { + this->chunkList = chunkList; + this->status = status; + } + + // only for deserialization + FetchFsckChunkListRespMsg() : BaseType(NETMSGTYPE_FetchFsckChunkListResp) + { + } + + private: + FsckChunkList* chunkList; + FetchFsckChunkListStatus status; + + // for deserialization + struct { + FsckChunkList chunkList; + } parsed; + + public: + // getter + FetchFsckChunkListStatus getStatus() + { + return this->status; + } + + FsckChunkList& getChunkList() + { + return *chunkList; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->chunkList, obj->parsed.chunkList) + % serdes::as(obj->status); + } +}; + diff --git a/common/source/common/net/message/fsck/FixInodeOwnersInDentryMsg.h b/common/source/common/net/message/fsck/FixInodeOwnersInDentryMsg.h new file mode 100644 index 0000000..592014f --- /dev/null +++ b/common/source/common/net/message/fsck/FixInodeOwnersInDentryMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +class FixInodeOwnersInDentryMsg : public NetMessageSerdes +{ + public: + FixInodeOwnersInDentryMsg(FsckDirEntryList& dentries, NumNodeIDList& owners) + : BaseType(NETMSGTYPE_FixInodeOwnersInDentry), + dentries(&dentries), + owners(&owners) + { + } + + FixInodeOwnersInDentryMsg(): BaseType(NETMSGTYPE_FixInodeOwnersInDentry) + { + } + + private: + FsckDirEntryList* dentries; + NumNodeIDList* owners; + + struct { + FsckDirEntryList dentries; + NumNodeIDList owners; + } parsed; + + public: + FsckDirEntryList& getDentries() + { + return *dentries; + } + + NumNodeIDList& getOwners() + { + return *owners; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->dentries, obj->parsed.dentries) + % serdes::backedPtr(obj->owners, obj->parsed.owners); + } +}; + + diff --git a/common/source/common/net/message/fsck/FixInodeOwnersInDentryRespMsg.h b/common/source/common/net/message/fsck/FixInodeOwnersInDentryRespMsg.h new file mode 100644 index 0000000..9f64830 --- /dev/null +++ b/common/source/common/net/message/fsck/FixInodeOwnersInDentryRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class FixInodeOwnersInDentryRespMsg : public NetMessageSerdes +{ + public: + FixInodeOwnersInDentryRespMsg(FsckDirEntryList* failedEntries) : + BaseType(NETMSGTYPE_FixInodeOwnersInDentryResp) + { + this->failedEntries = failedEntries; + } + + FixInodeOwnersInDentryRespMsg() : BaseType(NETMSGTYPE_FixInodeOwnersInDentryResp) + { + } + + private: + FsckDirEntryList* failedEntries; + + struct { + FsckDirEntryList failedEntries; + } parsed; + + public: + FsckDirEntryList& getFailedEntries() + { + return *failedEntries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedEntries, obj->parsed.failedEntries); + } +}; + + diff --git a/common/source/common/net/message/fsck/FixInodeOwnersMsg.h b/common/source/common/net/message/fsck/FixInodeOwnersMsg.h new file mode 100644 index 0000000..23bf80c --- /dev/null +++ b/common/source/common/net/message/fsck/FixInodeOwnersMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class FixInodeOwnersMsg : public NetMessageSerdes +{ + public: + FixInodeOwnersMsg(FsckDirInodeList* inodes) : BaseType(NETMSGTYPE_FixInodeOwners) + { + this->inodes = inodes; + } + + FixInodeOwnersMsg(): BaseType(NETMSGTYPE_FixInodeOwners) + { + } + + private: + FsckDirInodeList* inodes; + + // for deserialization + struct { + FsckDirInodeList inodes; + } parsed; + + public: + FsckDirInodeList& getInodes() + { + return *inodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->inodes, obj->parsed.inodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/FixInodeOwnersRespMsg.h b/common/source/common/net/message/fsck/FixInodeOwnersRespMsg.h new file mode 100644 index 0000000..0045b0f --- /dev/null +++ b/common/source/common/net/message/fsck/FixInodeOwnersRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class FixInodeOwnersRespMsg : public NetMessageSerdes +{ + public: + FixInodeOwnersRespMsg(FsckDirInodeList* failedInodes) : + BaseType(NETMSGTYPE_FixInodeOwnersResp) + { + this->failedInodes = failedInodes; + } + + FixInodeOwnersRespMsg() : BaseType(NETMSGTYPE_FixInodeOwnersResp) + { + } + + private: + FsckDirInodeList* failedInodes; + + struct { + FsckDirInodeList failedInodes; + } parsed; + + public: + FsckDirInodeList& getFailedInodes() + { + return *failedInodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedInodes, obj->parsed.failedInodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/FsckModificationEventMsg.h b/common/source/common/net/message/fsck/FsckModificationEventMsg.h new file mode 100644 index 0000000..fce2b13 --- /dev/null +++ b/common/source/common/net/message/fsck/FsckModificationEventMsg.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +class FsckModificationEventMsg : public AcknowledgeableMsgSerdes +{ + public: + FsckModificationEventMsg(UInt8List* modificationEventTypeList, StringList* entryIDList, + bool eventsMissed = false) : + BaseType(NETMSGTYPE_FsckModificationEvent) + { + this->modificationEventTypeList = modificationEventTypeList; + this->entryIDList = entryIDList; + this->eventsMissed = eventsMissed; + } + + // only for deserialization + FsckModificationEventMsg() : BaseType(NETMSGTYPE_FsckModificationEvent) + { + } + + private: + UInt8List* modificationEventTypeList; + StringList* entryIDList; + bool eventsMissed; + + // for deserialization + struct { + UInt8List eventTypes; + StringList entryIDList; + } parsed; + + public: + UInt8List& getModificationEventTypeList() + { + return *modificationEventTypeList; + } + + StringList& getEntryIDList() + { + return *entryIDList; + } + + bool getEventsMissed() + { + return this->eventsMissed; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->modificationEventTypeList, obj->parsed.eventTypes) + % serdes::backedPtr(obj->entryIDList, obj->parsed.entryIDList) + % obj->eventsMissed; + + obj->serializeAckID(ctx); + } +}; + diff --git a/common/source/common/net/message/fsck/FsckSetEventLoggingMsg.h b/common/source/common/net/message/fsck/FsckSetEventLoggingMsg.h new file mode 100644 index 0000000..7de056b --- /dev/null +++ b/common/source/common/net/message/fsck/FsckSetEventLoggingMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +class FsckSetEventLoggingMsg: public NetMessageSerdes +{ + public: + FsckSetEventLoggingMsg(bool enableLogging, unsigned portUDP, NicAddressList* nicList, + bool forceRestart) : + BaseType(NETMSGTYPE_FsckSetEventLogging), + enableLogging(enableLogging), + portUDP(portUDP), + nicList(nicList), + forceRestart(forceRestart) + { } + + FsckSetEventLoggingMsg() : BaseType(NETMSGTYPE_FsckSetEventLogging) + { } + + private: + bool enableLogging; + uint32_t portUDP; + NicAddressList* nicList; + bool forceRestart; + + // for (de)-serialization + struct { + NicAddressList nicList; + } parsed; + + public: + bool getEnableLogging() const { return this->enableLogging; } + unsigned getPortUDP() const { return this->portUDP; } + NicAddressList& getNicList() { return *nicList; } + bool getForceRestart() const { return forceRestart; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->enableLogging + % obj->portUDP + % serdesNicAddressList(obj->nicList, obj->parsed.nicList) + % obj->forceRestart; + } +}; + diff --git a/common/source/common/net/message/fsck/FsckSetEventLoggingRespMsg.h b/common/source/common/net/message/fsck/FsckSetEventLoggingRespMsg.h new file mode 100644 index 0000000..3472a3b --- /dev/null +++ b/common/source/common/net/message/fsck/FsckSetEventLoggingRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include + +class FsckSetEventLoggingRespMsg: public NetMessageSerdes +{ + public: + FsckSetEventLoggingRespMsg(bool result, bool loggingEnabled, bool missedEvents) : + BaseType(NETMSGTYPE_FsckSetEventLoggingResp), + result(result), + loggingEnabled(loggingEnabled), + missedEvents(missedEvents) + { } + + FsckSetEventLoggingRespMsg() : BaseType(NETMSGTYPE_FsckSetEventLoggingResp) + { } + + private: + bool result; + bool loggingEnabled; + bool missedEvents; + + public: + bool getResult() const { return result; } + bool getLoggingEnabled() const { return loggingEnabled; } + unsigned getMissedEvents() const { return this->missedEvents; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->loggingEnabled + % obj->missedEvents; + } +}; + diff --git a/common/source/common/net/message/fsck/LinkToLostAndFoundMsg.h b/common/source/common/net/message/fsck/LinkToLostAndFoundMsg.h new file mode 100644 index 0000000..4fc9e28 --- /dev/null +++ b/common/source/common/net/message/fsck/LinkToLostAndFoundMsg.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include + +class LinkToLostAndFoundMsg : public NetMessageSerdes +{ + public: + LinkToLostAndFoundMsg(EntryInfo* lostAndFoundInfo, FsckDirInodeList* dirInodes) : + BaseType(NETMSGTYPE_LinkToLostAndFound) + { + this->lostAndFoundInfoPtr = lostAndFoundInfo; + this->dirInodes = dirInodes; + this->fileInodes = NULL; + this->entryType = FsckDirEntryType_DIRECTORY; + } + + LinkToLostAndFoundMsg(EntryInfo* lostAndFoundInfo, FsckFileInodeList* fileInodes) : + BaseType(NETMSGTYPE_LinkToLostAndFound) + { + this->lostAndFoundInfoPtr = lostAndFoundInfo; + this->fileInodes = fileInodes; + this->dirInodes = NULL; + this->entryType = FsckDirEntryType_REGULARFILE; + } + + LinkToLostAndFoundMsg() : BaseType(NETMSGTYPE_LinkToLostAndFound) + { + } + + private: + FsckDirEntryType entryType; // to indicate, whether dir inodes or file inodes should be + // linked; important for serialization + + // for serialization + FsckDirInodeList* dirInodes; // not owned by this object + FsckFileInodeList* fileInodes; // not owned by this object + + EntryInfo* lostAndFoundInfoPtr; // not owned by this object + + // for deserialization + EntryInfo lostAndFoundInfo; + + // for deserialization + struct { + FsckDirInodeList dirInodes; + FsckFileInodeList fileInodes; + } parsed; + + public: + FsckDirInodeList& getDirInodes() + { + return *dirInodes; + } + + FsckFileInodeList& getFileInodes() + { + return *fileInodes; + } + + EntryInfo* getLostAndFoundInfo() + { + return &(this->lostAndFoundInfo); + } + + FsckDirEntryType getEntryType() + { + return this->entryType; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->entryType) + % serdes::backedPtr(obj->lostAndFoundInfoPtr, obj->lostAndFoundInfo); + + if (FsckDirEntryType_ISDIR(obj->entryType)) + ctx % serdes::backedPtr(obj->dirInodes, obj->parsed.dirInodes); + else + ctx % serdes::backedPtr(obj->fileInodes, obj->parsed.fileInodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/LinkToLostAndFoundRespMsg.h b/common/source/common/net/message/fsck/LinkToLostAndFoundRespMsg.h new file mode 100644 index 0000000..95b13c2 --- /dev/null +++ b/common/source/common/net/message/fsck/LinkToLostAndFoundRespMsg.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +class LinkToLostAndFoundRespMsg : public NetMessageSerdes +{ + public: + LinkToLostAndFoundRespMsg(FsckDirInodeList* failedDirInodes, + FsckDirEntryList* createdDentries) : BaseType(NETMSGTYPE_LinkToLostAndFoundResp) + { + this->failedDirInodes = failedDirInodes; + this->failedFileInodes = NULL; + this->createdDentries = createdDentries; + this->entryType = FsckDirEntryType_DIRECTORY; + } + + LinkToLostAndFoundRespMsg(FsckFileInodeList* failedFileInodes, + FsckDirEntryList* createdDentries): BaseType(NETMSGTYPE_LinkToLostAndFoundResp) + { + this->failedFileInodes = failedFileInodes; + this->failedDirInodes = NULL; + this->createdDentries = createdDentries; + this->entryType = FsckDirEntryType_REGULARFILE; + } + + LinkToLostAndFoundRespMsg() : BaseType(NETMSGTYPE_LinkToLostAndFoundResp) + { + } + + private: + FsckDirEntryType entryType; // to indicate, whether dir inodes or file inodes should be + // linked; important for serialization + + FsckDirInodeList* failedDirInodes; + FsckFileInodeList* failedFileInodes; + + FsckDirEntryList* createdDentries; + + // for deserialization + struct { + FsckDirInodeList failedDirInodes; + FsckFileInodeList failedFileInodes; + FsckDirEntryList createdDentries; + } parsed; + + public: + FsckDirInodeList& getFailedDirInodes() + { + return *failedDirInodes; + } + + FsckFileInodeList& getFailedFileInodes() + { + return *failedFileInodes; + } + + FsckDirEntryList& getCreatedDirEntries() + { + return *createdDentries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::as(obj->entryType); + + if (FsckDirEntryType_ISDIR(obj->entryType)) + ctx % serdes::backedPtr(obj->failedDirInodes, obj->parsed.failedDirInodes); + else + ctx % serdes::backedPtr(obj->failedFileInodes, obj->parsed.failedFileInodes); + + ctx % serdes::backedPtr(obj->createdDentries, obj->parsed.createdDentries); + } +}; + + diff --git a/common/source/common/net/message/fsck/MoveChunkFileMsg.h b/common/source/common/net/message/fsck/MoveChunkFileMsg.h new file mode 100644 index 0000000..25d5a2b --- /dev/null +++ b/common/source/common/net/message/fsck/MoveChunkFileMsg.h @@ -0,0 +1,82 @@ +#pragma once + +#include + +class MoveChunkFileMsg: public NetMessageSerdes +{ + public: + /* + * @param chunkName the name of the chunk (i.e. the "ID") + * @param targetID + * @param isMirrored targetID describes a buddy group + * @param oldPath relative to storeStorageDirectory and mirror chunk Path + * @param newPath relative to storeStorageDirectory and mirror chunk Path + * @param overwriteExisting if true, the destination file will be overwritten if it exists + */ + MoveChunkFileMsg(const std::string& chunkName, uint16_t targetID, + bool isMirrored, const std::string& oldPath, const std::string& newPath, + bool overwriteExisting = false): + BaseType(NETMSGTYPE_MoveChunkFile), + chunkName(chunkName), oldPath(oldPath), newPath(newPath), targetID(targetID), + isMirrored(isMirrored), overwriteExisting(overwriteExisting) + { + } + + MoveChunkFileMsg() : BaseType(NETMSGTYPE_MoveChunkFile) + { + } + + private: + std::string chunkName; + std::string oldPath; + std::string newPath; + + uint16_t targetID; + bool isMirrored; + + bool overwriteExisting; + + public: + std::string getChunkName() const + { + return chunkName; + } + + uint16_t getTargetID() const + { + return targetID; + } + + bool getIsMirrored() const + { + return isMirrored; + } + + std::string getOldPath() const + { + return oldPath; + } + + std::string getNewPath() const + { + return newPath; + } + + bool getOverwriteExisting() const + { + return overwriteExisting; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->chunkName + % obj->oldPath + % obj->newPath + % obj->targetID + % obj->isMirrored + % obj->overwriteExisting; + } +}; + diff --git a/common/source/common/net/message/fsck/MoveChunkFileRespMsg.h b/common/source/common/net/message/fsck/MoveChunkFileRespMsg.h new file mode 100644 index 0000000..b2d33b4 --- /dev/null +++ b/common/source/common/net/message/fsck/MoveChunkFileRespMsg.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class MoveChunkFileRespMsg : public SimpleIntMsg +{ + public: + MoveChunkFileRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_MoveChunkFileResp, result) + { + } + + /** + * For deserialization only + */ + MoveChunkFileRespMsg() : SimpleIntMsg(NETMSGTYPE_MoveChunkFileResp) + { + } +}; + + diff --git a/common/source/common/net/message/fsck/RecreateDentriesMsg.h b/common/source/common/net/message/fsck/RecreateDentriesMsg.h new file mode 100644 index 0000000..24756f6 --- /dev/null +++ b/common/source/common/net/message/fsck/RecreateDentriesMsg.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class RecreateDentriesMsg : public NetMessageSerdes +{ + public: + RecreateDentriesMsg(FsckFsIDList* fsIDs) : BaseType(NETMSGTYPE_RecreateDentries) + { + this->fsIDs = fsIDs; + } + + RecreateDentriesMsg() : BaseType(NETMSGTYPE_RecreateDentries) + { + } + + private: + FsckFsIDList* fsIDs; + + struct { + FsckFsIDList fsIDs; + } parsed; + + public: + FsckFsIDList& getFsIDs() + { + return *fsIDs; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->fsIDs, obj->parsed.fsIDs); + } +}; + + diff --git a/common/source/common/net/message/fsck/RecreateDentriesRespMsg.h b/common/source/common/net/message/fsck/RecreateDentriesRespMsg.h new file mode 100644 index 0000000..af4e339 --- /dev/null +++ b/common/source/common/net/message/fsck/RecreateDentriesRespMsg.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +class RecreateDentriesRespMsg : public NetMessageSerdes +{ + public: + RecreateDentriesRespMsg(FsckFsIDList* failedCreates, FsckDirEntryList* createdDentries, + FsckFileInodeList* createdInodes) : + BaseType(NETMSGTYPE_RecreateDentriesResp) + { + this->failedCreates = failedCreates; + this->createdDentries = createdDentries; + this->createdInodes = createdInodes; + } + + RecreateDentriesRespMsg() : BaseType(NETMSGTYPE_RecreateDentriesResp) + { + } + + private: + FsckFsIDList* failedCreates; + FsckDirEntryList* createdDentries; + FsckFileInodeList* createdInodes; + + struct { + FsckFsIDList failedCreates; + FsckDirEntryList createdDentries; + FsckFileInodeList createdInodes; + } parsed; + + public: + FsckFsIDList& getFailedCreates() + { + return *failedCreates; + } + + FsckDirEntryList& getCreatedDentries() + { + return *createdDentries; + } + + FsckFileInodeList& getCreatedInodes() + { + return *createdInodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->failedCreates, obj->parsed.failedCreates) + % serdes::backedPtr(obj->createdDentries, obj->parsed.createdDentries) + % serdes::backedPtr(obj->createdInodes, obj->parsed.createdInodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/RecreateFsIDsMsg.h b/common/source/common/net/message/fsck/RecreateFsIDsMsg.h new file mode 100644 index 0000000..a81cada --- /dev/null +++ b/common/source/common/net/message/fsck/RecreateFsIDsMsg.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include + +class RecreateFsIDsMsg : public NetMessageSerdes +{ + public: + RecreateFsIDsMsg(FsckDirEntryList* entries) : BaseType(NETMSGTYPE_RecreateFsIDs) + { + this->entries = entries; + } + + RecreateFsIDsMsg() : BaseType(NETMSGTYPE_RecreateFsIDs) + { + } + + private: + FsckDirEntryList* entries; + + struct { + FsckDirEntryList entries; + } parsed; + + public: + FsckDirEntryList& getEntries() + { + return *entries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->entries, obj->parsed.entries); + } +}; + + diff --git a/common/source/common/net/message/fsck/RecreateFsIDsRespMsg.h b/common/source/common/net/message/fsck/RecreateFsIDsRespMsg.h new file mode 100644 index 0000000..d54e808 --- /dev/null +++ b/common/source/common/net/message/fsck/RecreateFsIDsRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +class RecreateFsIDsRespMsg : public NetMessageSerdes +{ + public: + RecreateFsIDsRespMsg(FsckDirEntryList* failedEntries) : + BaseType(NETMSGTYPE_RecreateFsIDsResp) + { + this->failedEntries = failedEntries; + } + + RecreateFsIDsRespMsg() : BaseType(NETMSGTYPE_RecreateFsIDsResp) + { + } + + private: + FsckDirEntryList* failedEntries; + + struct { + FsckDirEntryList failedEntries; + } parsed; + + public: + FsckDirEntryList& getFailedEntries() + { + return *failedEntries; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedEntries, obj->parsed.failedEntries); + } +}; + + diff --git a/common/source/common/net/message/fsck/RemoveInodesMsg.h b/common/source/common/net/message/fsck/RemoveInodesMsg.h new file mode 100644 index 0000000..b3d25e5 --- /dev/null +++ b/common/source/common/net/message/fsck/RemoveInodesMsg.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class RemoveInodesMsg : public NetMessageSerdes +{ + public: + typedef std::tuple Item; + + RemoveInodesMsg(std::vector items): + BaseType(NETMSGTYPE_RemoveInodes), items(std::move(items)) + { + } + + RemoveInodesMsg() : BaseType(NETMSGTYPE_RemoveInodes) + { + } + + protected: + std::vector items; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->items; + } +}; + + diff --git a/common/source/common/net/message/fsck/RemoveInodesRespMsg.h b/common/source/common/net/message/fsck/RemoveInodesRespMsg.h new file mode 100644 index 0000000..045a69d --- /dev/null +++ b/common/source/common/net/message/fsck/RemoveInodesRespMsg.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class RemoveInodesRespMsg : public NetMessageSerdes +{ + public: + RemoveInodesRespMsg(StringList failedEntryIDList) : + BaseType(NETMSGTYPE_RemoveInodesResp), failedEntryIDList(std::move(failedEntryIDList)) + { + } + + RemoveInodesRespMsg() : BaseType(NETMSGTYPE_RemoveInodesResp) + { + } + + private: + StringList failedEntryIDList; + + public: + StringList releaseFailedEntryIDList() { return failedEntryIDList; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->failedEntryIDList; + } +}; + + diff --git a/common/source/common/net/message/fsck/RetrieveDirEntriesMsg.h b/common/source/common/net/message/fsck/RetrieveDirEntriesMsg.h new file mode 100644 index 0000000..128c823 --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveDirEntriesMsg.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include +#include + +class RetrieveDirEntriesMsg : public NetMessageSerdes +{ + public: + RetrieveDirEntriesMsg(unsigned hashDirNum, std::string& currentContDirID, + unsigned maxOutEntries, int64_t lastHashDirOffset, int64_t lastContDirOffset, + bool isBuddyMirrored) : + BaseType(NETMSGTYPE_RetrieveDirEntries) + { + this->hashDirNum = hashDirNum; + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->maxOutEntries = maxOutEntries; + this->lastHashDirOffset = lastHashDirOffset; + this->lastContDirOffset = lastContDirOffset; + this->isBuddyMirrored = isBuddyMirrored; + } + + RetrieveDirEntriesMsg() : BaseType(NETMSGTYPE_RetrieveDirEntries) + { + } + +private: + uint32_t hashDirNum; + const char *currentContDirID; + unsigned currentContDirIDLen; + uint32_t maxOutEntries; + int64_t lastHashDirOffset; + int64_t lastContDirOffset; + bool isBuddyMirrored; + +public: + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + unsigned getHashDirNum() const + { + return hashDirNum; + } + + int64_t getLastContDirOffset() const + { + return lastContDirOffset; + } + + int64_t getLastHashDirOffset() const + { + return lastHashDirOffset; + } + + unsigned getMaxOutEntries() const + { + return maxOutEntries; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->hashDirNum + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen) + % obj->maxOutEntries + % obj->lastHashDirOffset + % obj->lastContDirOffset + % obj->isBuddyMirrored; + } +}; + diff --git a/common/source/common/net/message/fsck/RetrieveDirEntriesRespMsg.h b/common/source/common/net/message/fsck/RetrieveDirEntriesRespMsg.h new file mode 100644 index 0000000..ee37784 --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveDirEntriesRespMsg.h @@ -0,0 +1,91 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class RetrieveDirEntriesRespMsg : public NetMessageSerdes +{ + public: + RetrieveDirEntriesRespMsg(FsckContDirList* fsckContDirs, FsckDirEntryList* fsckDirEntries, + FsckFileInodeList* inlinedFileInodes, std::string& currentContDirID, + int64_t newHashDirOffset, int64_t newContDirOffset) : + BaseType(NETMSGTYPE_RetrieveDirEntriesResp) + { + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->newHashDirOffset = newHashDirOffset; + this->newContDirOffset = newContDirOffset; + this->fsckContDirs = fsckContDirs; + this->fsckDirEntries = fsckDirEntries; + this->inlinedFileInodes = inlinedFileInodes; + } + + RetrieveDirEntriesRespMsg() : BaseType(NETMSGTYPE_RetrieveDirEntriesResp) + { + } + + private: + const char* currentContDirID; + unsigned currentContDirIDLen; + int64_t newHashDirOffset; + int64_t newContDirOffset; + + FsckContDirList* fsckContDirs; + FsckDirEntryList* fsckDirEntries; + FsckFileInodeList* inlinedFileInodes; + + // for deserialization + struct { + FsckContDirList fsckContDirs; + FsckDirEntryList fsckDirEntries; + FsckFileInodeList inlinedFileInodes; + } parsed; + + public: + FsckContDirList& getContDirs() + { + return *fsckContDirs; + } + + FsckDirEntryList& getDirEntries() + { + return *fsckDirEntries; + } + + FsckFileInodeList& getInlinedFileInodes() + { + return *inlinedFileInodes; + } + + int64_t getNewHashDirOffset() const + { + return newHashDirOffset; + } + + int64_t getNewContDirOffset() const + { + return newContDirOffset; + } + + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4) + % obj->newHashDirOffset + % obj->newContDirOffset + % serdes::backedPtr(obj->fsckContDirs, obj->parsed.fsckContDirs) + % serdes::backedPtr(obj->fsckDirEntries, obj->parsed.fsckDirEntries) + % serdes::backedPtr(obj->inlinedFileInodes, obj->parsed.inlinedFileInodes); + } +}; + diff --git a/common/source/common/net/message/fsck/RetrieveFsIDsMsg.h b/common/source/common/net/message/fsck/RetrieveFsIDsMsg.h new file mode 100644 index 0000000..69b34cf --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveFsIDsMsg.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include + +class RetrieveFsIDsMsg : public NetMessageSerdes +{ + public: + RetrieveFsIDsMsg(unsigned hashDirNum, bool buddyMirrored, std::string& currentContDirID, + unsigned maxOutIDs, int64_t lastHashDirOffset, int64_t lastContDirOffset) : + BaseType(NETMSGTYPE_RetrieveFsIDs) + { + this->hashDirNum = hashDirNum; + this->buddyMirrored = buddyMirrored; + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->maxOutIDs = maxOutIDs; + this->lastHashDirOffset = lastHashDirOffset; + this->lastContDirOffset = lastContDirOffset; + } + + RetrieveFsIDsMsg() : BaseType(NETMSGTYPE_RetrieveFsIDs) + { + } + +private: + uint32_t hashDirNum; + bool buddyMirrored; + const char *currentContDirID; + unsigned currentContDirIDLen; + uint32_t maxOutIDs; + int64_t lastHashDirOffset; + int64_t lastContDirOffset; + +public: + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + unsigned getHashDirNum() const + { + return hashDirNum; + } + + bool getBuddyMirrored() const + { + return buddyMirrored; + } + + int64_t getLastContDirOffset() const + { + return lastContDirOffset; + } + + int64_t getLastHashDirOffset() const + { + return lastHashDirOffset; + } + + unsigned getMaxOutIDs() const + { + return maxOutIDs; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->hashDirNum + % obj->buddyMirrored + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen) + % obj->maxOutIDs + % obj->lastHashDirOffset + % obj->lastContDirOffset; + } +}; + diff --git a/common/source/common/net/message/fsck/RetrieveFsIDsRespMsg.h b/common/source/common/net/message/fsck/RetrieveFsIDsRespMsg.h new file mode 100644 index 0000000..6a2037f --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveFsIDsRespMsg.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include + +class RetrieveFsIDsRespMsg : public NetMessageSerdes +{ + public: + RetrieveFsIDsRespMsg(FsckFsIDList* fsckFsIDs, std::string& currentContDirID, + int64_t newHashDirOffset, int64_t newContDirOffset) : + BaseType(NETMSGTYPE_RetrieveFsIDsResp) + { + this->currentContDirID = currentContDirID.c_str(); + this->currentContDirIDLen = currentContDirID.length(); + this->newHashDirOffset = newHashDirOffset; + this->newContDirOffset = newContDirOffset; + this->fsckFsIDs = fsckFsIDs; + } + + RetrieveFsIDsRespMsg() : BaseType(NETMSGTYPE_RetrieveFsIDsResp) + { + } + + private: + const char* currentContDirID; + unsigned currentContDirIDLen; + int64_t newHashDirOffset; + int64_t newContDirOffset; + + FsckFsIDList* fsckFsIDs; + + // for deserialization + struct { + FsckFsIDList fsckFsIDs; + } parsed; + + public: + FsckFsIDList& getFsIDs() + { + return *fsckFsIDs; + } + + int64_t getNewHashDirOffset() const + { + return newHashDirOffset; + } + + int64_t getNewContDirOffset() const + { + return newContDirOffset; + } + + std::string getCurrentContDirID() const + { + return currentContDirID; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->currentContDirID, obj->currentContDirIDLen, 4) + % obj->newHashDirOffset + % obj->newContDirOffset + % serdes::backedPtr(obj->fsckFsIDs, obj->parsed.fsckFsIDs); + } +}; + diff --git a/common/source/common/net/message/fsck/RetrieveInodesMsg.h b/common/source/common/net/message/fsck/RetrieveInodesMsg.h new file mode 100644 index 0000000..f229875 --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveInodesMsg.h @@ -0,0 +1,55 @@ +#pragma once + +#include + +class RetrieveInodesMsg : public NetMessageSerdes +{ + public: + RetrieveInodesMsg(unsigned hashDirNum, int64_t lastOffset, unsigned maxOutInodes, + bool isBuddyMirrored): + BaseType(NETMSGTYPE_RetrieveInodes), + hashDirNum(hashDirNum), lastOffset(lastOffset), maxOutInodes(maxOutInodes), + isBuddyMirrored(isBuddyMirrored) + { + } + + RetrieveInodesMsg() : BaseType(NETMSGTYPE_RetrieveInodes) + { + } + + private: + uint32_t hashDirNum; + int64_t lastOffset; + uint32_t maxOutInodes; + bool isBuddyMirrored; + + public: + uint getHashDirNum() + { + return hashDirNum; + } + + int64_t getLastOffset() + { + return lastOffset; + } + + uint getMaxOutInodes() + { + return maxOutInodes; + } + + bool getIsBuddyMirrored() const { return isBuddyMirrored; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->hashDirNum + % obj->lastOffset + % obj->maxOutInodes + % obj->isBuddyMirrored; + } +}; + + diff --git a/common/source/common/net/message/fsck/RetrieveInodesRespMsg.h b/common/source/common/net/message/fsck/RetrieveInodesRespMsg.h new file mode 100644 index 0000000..b2a62f5 --- /dev/null +++ b/common/source/common/net/message/fsck/RetrieveInodesRespMsg.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include +#include + +class RetrieveInodesRespMsg : public NetMessageSerdes +{ + public: + RetrieveInodesRespMsg(FsckFileInodeList *fileInodes, FsckDirInodeList *dirInodes, + int64_t lastOffset) : BaseType(NETMSGTYPE_RetrieveInodesResp) + { + this->fileInodes = fileInodes; + this->dirInodes = dirInodes; + this->lastOffset = lastOffset; + + } + + RetrieveInodesRespMsg() : BaseType(NETMSGTYPE_RetrieveInodesResp) + { + } + + private: + FsckFileInodeList* fileInodes; + FsckDirInodeList* dirInodes; + int64_t lastOffset; + + // for deserialization + struct { + FsckFileInodeList fileInodes; + FsckDirInodeList dirInodes; + } parsed; + + public: + FsckFileInodeList& getFileInodes() + { + return *fileInodes; + } + + FsckDirInodeList& getDirInodes() + { + return *dirInodes; + } + + int64_t getLastOffset() + { + return lastOffset; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->fileInodes, obj->parsed.fileInodes) + % serdes::backedPtr(obj->dirInodes, obj->parsed.dirInodes) + % obj->lastOffset; + } +}; + + diff --git a/common/source/common/net/message/fsck/UpdateDirAttribsMsg.h b/common/source/common/net/message/fsck/UpdateDirAttribsMsg.h new file mode 100644 index 0000000..61afb88 --- /dev/null +++ b/common/source/common/net/message/fsck/UpdateDirAttribsMsg.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class UpdateDirAttribsMsg : public NetMessageSerdes +{ + public: + UpdateDirAttribsMsg(FsckDirInodeList* inodes) : + BaseType(NETMSGTYPE_UpdateDirAttribs) + { + this->inodes = inodes; + } + + UpdateDirAttribsMsg() : BaseType(NETMSGTYPE_UpdateDirAttribs) + { + } + + private: + FsckDirInodeList* inodes; + + struct { + FsckDirInodeList inodes; + } parsed; + + public: + FsckDirInodeList& getInodes() + { + return *inodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->inodes, obj->parsed.inodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/UpdateDirAttribsRespMsg.h b/common/source/common/net/message/fsck/UpdateDirAttribsRespMsg.h new file mode 100644 index 0000000..034b83a --- /dev/null +++ b/common/source/common/net/message/fsck/UpdateDirAttribsRespMsg.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class UpdateDirAttribsRespMsg : public NetMessageSerdes +{ + public: + UpdateDirAttribsRespMsg(FsckDirInodeList* failedInodes) : + BaseType(NETMSGTYPE_UpdateDirAttribsResp) + { + this->failedInodes = failedInodes; + } + + UpdateDirAttribsRespMsg() : BaseType(NETMSGTYPE_UpdateDirAttribsResp) + { + } + + private: + FsckDirInodeList* failedInodes; + + struct { + FsckDirInodeList failedInodes; + } parsed; + + public: + FsckDirInodeList& getFailedInodes() + { + return *failedInodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->failedInodes, obj->parsed.failedInodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/UpdateFileAttribsMsg.h b/common/source/common/net/message/fsck/UpdateFileAttribsMsg.h new file mode 100644 index 0000000..c8dcbc2 --- /dev/null +++ b/common/source/common/net/message/fsck/UpdateFileAttribsMsg.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class UpdateFileAttribsMsg : public NetMessageSerdes +{ + public: + UpdateFileAttribsMsg(FsckFileInodeList* inodes) : + BaseType(NETMSGTYPE_UpdateFileAttribs) + { + this->inodes = inodes; + } + + UpdateFileAttribsMsg() : BaseType(NETMSGTYPE_UpdateFileAttribs) + { + } + + private: + FsckFileInodeList* inodes; + + struct { + FsckFileInodeList inodes; + } parsed; + + public: + FsckFileInodeList& getInodes() + { + return *inodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->inodes, obj->parsed.inodes); + } +}; + + diff --git a/common/source/common/net/message/fsck/UpdateFileAttribsRespMsg.h b/common/source/common/net/message/fsck/UpdateFileAttribsRespMsg.h new file mode 100644 index 0000000..b90f246 --- /dev/null +++ b/common/source/common/net/message/fsck/UpdateFileAttribsRespMsg.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class UpdateFileAttribsRespMsg : public NetMessageSerdes +{ + public: + UpdateFileAttribsRespMsg(FsckFileInodeList* failedInodes) : + BaseType(NETMSGTYPE_UpdateFileAttribsResp) + { + this->failedInodes = failedInodes; + } + + UpdateFileAttribsRespMsg() : BaseType(NETMSGTYPE_UpdateFileAttribsResp) + { + } + + private: + FsckFileInodeList* failedInodes; + + struct { + FsckFileInodeList failedInodes; + } parsed; + + public: + FsckFileInodeList& getFailedInodes() + { + return *failedInodes; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->failedInodes, obj->parsed.failedInodes); + } +}; + + diff --git a/common/source/common/net/message/helperd/GetHostByNameMsg.h b/common/source/common/net/message/helperd/GetHostByNameMsg.h new file mode 100644 index 0000000..2038834 --- /dev/null +++ b/common/source/common/net/message/helperd/GetHostByNameMsg.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +class GetHostByNameMsg : public NetMessageSerdes +{ + public: + + /** + * @param hostname just a reference, so do not free it as long as you use this object! + */ + GetHostByNameMsg(const char* hostname) : + BaseType(NETMSGTYPE_GetHostByName) + { + this->hostname = hostname; + this->hostnameLen = strlen(hostname); + } + + GetHostByNameMsg() : BaseType(NETMSGTYPE_GetHostByName) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::rawString(obj->hostname, obj->hostnameLen); + } + + private: + unsigned hostnameLen; + const char* hostname; + + + public: + + // getters & setters + const char* getHostname() + { + return hostname; + } +}; + diff --git a/common/source/common/net/message/helperd/GetHostByNameRespMsg.h b/common/source/common/net/message/helperd/GetHostByNameRespMsg.h new file mode 100644 index 0000000..cf8f627 --- /dev/null +++ b/common/source/common/net/message/helperd/GetHostByNameRespMsg.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include + +class GetHostByNameRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param hostAddr just a reference, so do not free it as long as you use this object! + */ + GetHostByNameRespMsg(const char* hostAddr) : BaseType(NETMSGTYPE_GetHostByNameResp) + { + this->hostAddr = hostAddr; + this->hostAddrLen = strlen(hostAddr); + } + + GetHostByNameRespMsg() : BaseType(NETMSGTYPE_GetHostByNameResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::rawString(obj->hostAddr, obj->hostAddrLen); + } + + private: + unsigned hostAddrLen; + const char* hostAddr; + + + public: + + // getters & setters + const char* getHostAddr() + { + return hostAddr; + } +}; + diff --git a/common/source/common/net/message/helperd/LogMsg.h b/common/source/common/net/message/helperd/LogMsg.h new file mode 100644 index 0000000..29c6bce --- /dev/null +++ b/common/source/common/net/message/helperd/LogMsg.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include + +class LogMsg : public NetMessageSerdes +{ + public: + + /** + * @param context just a reference, so do not free it as long as you use this object! + * @param logMsg just a reference, so do not free it as long as you use this object! + */ + LogMsg(int level, int threadID, const char* threadName, const char* context, + const char* logMsg) : + BaseType(NETMSGTYPE_Log) + { + this->level = level; + this->threadID = threadID; + + this->threadName = threadName; + this->threadNameLen = strlen(threadName); + + this->context = context; + this->contextLen = strlen(context); + + this->logMsg = logMsg; + this->logMsgLen = strlen(logMsg); + } + + LogMsg() : BaseType(NETMSGTYPE_Log) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->level + % obj->threadID + % serdes::rawString(obj->threadName, obj->threadNameLen) + % serdes::rawString(obj->context, obj->contextLen) + % serdes::rawString(obj->logMsg, obj->logMsgLen); + } + + + + private: + int32_t level; + int32_t threadID; + unsigned threadNameLen; + const char* threadName; + unsigned contextLen; + const char* context; + unsigned logMsgLen; + const char* logMsg; + + + public: + + // getters & setters + int getLevel() + { + return level; + } + + int getThreadID() + { + return threadID; + } + + const char* getThreadName() + { + return threadName; + } + + const char* getContext() + { + return context; + } + + const char* getLogMsg() + { + return logMsg; + } + +}; + diff --git a/common/source/common/net/message/helperd/LogRespMsg.h b/common/source/common/net/message/helperd/LogRespMsg.h new file mode 100644 index 0000000..ac7130c --- /dev/null +++ b/common/source/common/net/message/helperd/LogRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class LogRespMsg : public SimpleIntMsg +{ + public: + LogRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_LogResp, result) + { + } + + LogRespMsg() : SimpleIntMsg(NETMSGTYPE_LogResp) + { + } +}; + diff --git a/common/source/common/net/message/mon/RequestMetaDataMsg.h b/common/source/common/net/message/mon/RequestMetaDataMsg.h new file mode 100644 index 0000000..3557bc0 --- /dev/null +++ b/common/source/common/net/message/mon/RequestMetaDataMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class RequestMetaDataMsg : public SimpleInt64Msg +{ +public: + /** + * @param lastStatsTimeMS only for high res stats + * only the elements with a time value greater than this will be + * returned in the response + */ + RequestMetaDataMsg(int64_t lastStatsTimeMS) : SimpleInt64Msg + (NETMSGTYPE_RequestMetaData, lastStatsTimeMS) + { + } + + RequestMetaDataMsg() : SimpleInt64Msg(NETMSGTYPE_RequestMetaData,0) + { + } +}; + diff --git a/common/source/common/net/message/mon/RequestMetaDataRespMsg.h b/common/source/common/net/message/mon/RequestMetaDataRespMsg.h new file mode 100644 index 0000000..ad5b1f3 --- /dev/null +++ b/common/source/common/net/message/mon/RequestMetaDataRespMsg.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include + + +class RequestMetaDataRespMsg : public NetMessageSerdes +{ + public: + /** + * @param hostnameid it will get the hostname of server + * @param nicList just a reference, so do not free it as long as you use this object! + * @param statsList just a reference, so do not free it as long as you use this object! + */ + RequestMetaDataRespMsg(const std::string& nodeID, const std::string& hostnameid, NumNodeID nodeNumID, NicAddressList *nicList, + bool isRoot, unsigned IndirectWorkListSize, unsigned DirectWorkListSize, + unsigned sessionCount, HighResStatsList* statsList) + : BaseType(NETMSGTYPE_RequestMetaDataResp) + { + this->nodeID = nodeID; + this->hostnameid = hostnameid; + this->nodeNumID = nodeNumID; + this->nicList = nicList; + this->isRoot = isRoot; + this->indirectWorkListSize = IndirectWorkListSize; + this->directWorkListSize = DirectWorkListSize; + this->sessionCount = sessionCount; + this->statsList = statsList; + } + + RequestMetaDataRespMsg() : BaseType(NETMSGTYPE_RequestMetaDataResp) + { + this->nicList = NULL; + this->isRoot = 0; + this->indirectWorkListSize = 0; + this->directWorkListSize = 0; + this->sessionCount = 0; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeID + % obj->hostnameid + % obj->nodeNumID + % serdesNicAddressList(obj->nicList, obj->parsed.nicList) + % obj->isRoot + % obj->indirectWorkListSize + % obj->directWorkListSize + % obj->sessionCount + % serdes::backedPtr(obj->statsList, obj->parsed.statsList); + } + + private: + std::string nodeID; + std::string hostnameid; + NumNodeID nodeNumID; + bool isRoot; + uint32_t indirectWorkListSize; + uint32_t directWorkListSize; + uint32_t sessionCount; + NicAddressList* nicList; + + // for serialization + HighResStatsList* statsList; // not owned by this object! + + // for deserialization + struct { + HighResStatsList statsList; + NicAddressList nicList; + } parsed; + + public: + NicAddressList& getNicList() + { + return *nicList; + } + + HighResStatsList& getStatsList() + { + return *statsList; + } + + const std::string& getNodeID() const + { + return nodeID; + } + + const std::string& gethostnameid() const + { + return hostnameid; + } + + NumNodeID getNodeNumID() const + { + return nodeNumID; + } + + NicAddressList* getNicList() const + { + return this->nicList; + } + + bool getIsRoot() const + { + return isRoot; + } + + unsigned getIndirectWorkListSize() const + { + return indirectWorkListSize; + } + + unsigned getDirectWorkListSize() const + { + return directWorkListSize; + } + + unsigned getSessionCount() const + { + return sessionCount; + } +}; + diff --git a/common/source/common/net/message/mon/RequestStorageDataMsg.h b/common/source/common/net/message/mon/RequestStorageDataMsg.h new file mode 100644 index 0000000..735f837 --- /dev/null +++ b/common/source/common/net/message/mon/RequestStorageDataMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class RequestStorageDataMsg : public SimpleInt64Msg +{ +public: + /** + * @param lastStatsTimeMS only for high res stats + * only the elements with a time value greater than this will be + * returned in the response + */ + RequestStorageDataMsg(int64_t lastStatsTimeMS) : SimpleInt64Msg(NETMSGTYPE_RequestStorageData, + lastStatsTimeMS) + { + } + + RequestStorageDataMsg() : SimpleInt64Msg(NETMSGTYPE_RequestStorageData,0) + { + }; +}; + diff --git a/common/source/common/net/message/mon/RequestStorageDataRespMsg.h b/common/source/common/net/message/mon/RequestStorageDataRespMsg.h new file mode 100644 index 0000000..c469f2f --- /dev/null +++ b/common/source/common/net/message/mon/RequestStorageDataRespMsg.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class RequestStorageDataRespMsg: public NetMessageSerdes +{ + private: + std::string nodeID; + std::string hostnameid; + NumNodeID nodeNumID; + NicAddressList* nicList; + uint32_t indirectWorkListSize; + uint32_t directWorkListSize; + int64_t diskSpaceTotalMiB; + int64_t diskSpaceFreeMiB; + uint32_t sessionCount; + // for deserialization + struct { + StorageTargetInfoList storageTargets; + HighResStatsList statsList; + NicAddressList nicList; + } parsed; + // for serialization + HighResStatsList* statsList; // not owned by this object! + StorageTargetInfoList *storageTargets; // not owned by this object! + + public: + /** + * @param hostnameid it will get the hostname of server + * @param nicList just a reference, so do not free it as long as you use this object! + * @param statsList just a reference, so do not free it as long as you use this object! + */ + RequestStorageDataRespMsg(const std::string& nodeID, const std::string& hostnameid, NumNodeID nodeNumID, NicAddressList *nicList, + unsigned indirectWorkListSize, unsigned directWorkListSize, int64_t diskSpaceTotal, + int64_t diskSpaceFree, unsigned sessionCount, HighResStatsList* statsList, + StorageTargetInfoList *storageTargets) : + BaseType(NETMSGTYPE_RequestStorageDataResp) + { + this->nodeID = nodeID; + this->hostnameid = hostnameid; + this->nodeNumID = nodeNumID; + this->nicList = nicList; + this->indirectWorkListSize = indirectWorkListSize; + this->directWorkListSize = directWorkListSize; + this->diskSpaceTotalMiB = diskSpaceTotal / 1024 / 1024; + this->diskSpaceFreeMiB = diskSpaceFree / 1024 / 1024; + this->sessionCount = sessionCount; + this->statsList = statsList; + this->storageTargets = storageTargets; + } + + RequestStorageDataRespMsg() : + BaseType(NETMSGTYPE_RequestStorageDataResp) + { + this->indirectWorkListSize = 0; + this->directWorkListSize = 0; + this->diskSpaceTotalMiB = 0; + this->diskSpaceFreeMiB = 0; + this->sessionCount = 0; + this->statsList = NULL; + this->storageTargets = NULL; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeID + % obj->hostnameid + % obj->nodeNumID + % serdesNicAddressList(obj->nicList, obj->parsed.nicList) + % obj->indirectWorkListSize + % obj->directWorkListSize + % obj->diskSpaceTotalMiB + % obj->diskSpaceFreeMiB + % obj->sessionCount + % serdes::backedPtr(obj->statsList, obj->parsed.statsList) + % serdes::backedPtr(obj->storageTargets, obj->parsed.storageTargets); + } + + NicAddressList& getNicList() + { + return *nicList; + } + + HighResStatsList& getStatsList() + { + return *statsList; + } + + StorageTargetInfoList& getStorageTargets() + { + return *storageTargets; + } + + const std::string& getNodeID() const + { + return nodeID; + } + + const std::string& gethostnameid() const + { + return hostnameid; + } + + NumNodeID getNodeNumID() const + { + return nodeNumID; + } + + unsigned getIndirectWorkListSize() const + { + return indirectWorkListSize; + } + + unsigned getDirectWorkListSize() const + { + return directWorkListSize; + } + + int64_t getDiskSpaceTotalMiB() const + { + return diskSpaceTotalMiB; + } + + int64_t getDiskSpaceFreeMiB() const + { + return diskSpaceFreeMiB; + } + + unsigned getSessionCount() const + { + return sessionCount; + } +}; + diff --git a/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesMsg.h b/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesMsg.h new file mode 100644 index 0000000..3d741a9 --- /dev/null +++ b/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesMsg.h @@ -0,0 +1,77 @@ +#pragma once + +#include +#include + +/* + * NOTE: this message will be used in mgmtd to update target state store, but also in storage + * server to set local target states + */ + +class ChangeTargetConsistencyStatesMsg + : public AcknowledgeableMsgSerdes +{ + public: + ChangeTargetConsistencyStatesMsg(NodeType nodeType, UInt16List* targetIDs, + UInt8List* oldStates, UInt8List* newStates) + : BaseType(NETMSGTYPE_ChangeTargetConsistencyStates), + nodeType(nodeType), + targetIDs(targetIDs), + oldStates(oldStates), + newStates(newStates) + { + } + + ChangeTargetConsistencyStatesMsg() + : BaseType(NETMSGTYPE_ChangeTargetConsistencyStates) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % serdes::backedPtr(obj->targetIDs, obj->parsed.targetIDs) + % serdes::backedPtr(obj->oldStates, obj->parsed.oldStates) + % serdes::backedPtr(obj->newStates, obj->parsed.newStates); + + obj->serializeAckID(ctx, 4); + } + + private: + int32_t nodeType; + + UInt16List* targetIDs; // not owned by this object! + UInt8List* oldStates; // not owned by this object! + UInt8List* newStates; // not owned by this object! + + // for deserialization + struct { + UInt16List targetIDs; + UInt8List oldStates; + UInt8List newStates; + } parsed; + + public: + NodeType getNodeType() + { + return (NodeType)nodeType; + } + + UInt16List& getTargetIDs() + { + return *targetIDs; + } + + UInt8List& getOldStates() + { + return *oldStates; + } + + UInt8List& getNewStates() + { + return *newStates; + } +}; + diff --git a/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesRespMsg.h b/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesRespMsg.h new file mode 100644 index 0000000..cfcab42 --- /dev/null +++ b/common/source/common/net/message/nodes/ChangeTargetConsistencyStatesRespMsg.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +class ChangeTargetConsistencyStatesRespMsg : public SimpleIntMsg +{ + public: + ChangeTargetConsistencyStatesRespMsg(int result) + : SimpleIntMsg(NETMSGTYPE_ChangeTargetConsistencyStatesResp, result) + { + } + + ChangeTargetConsistencyStatesRespMsg() + : SimpleIntMsg(NETMSGTYPE_ChangeTargetConsistencyStatesResp) + { + } + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/nodes/GenericDebugMsg.h b/common/source/common/net/message/nodes/GenericDebugMsg.h new file mode 100644 index 0000000..3cd86e9 --- /dev/null +++ b/common/source/common/net/message/nodes/GenericDebugMsg.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + + +/** + * This message is generic in the way that it simply takes a space-separated command string as input + * and the response is also just a string (which will just be printed to the console by fhgfs-ctl). + * That makes this msg very useful to quickly add new fuctionality for testing or query information + * for debugging purposes without the overhead of adding a completely new message. + */ +class GenericDebugMsg : public SimpleStringMsg +{ + public: + GenericDebugMsg(const char* commandStr) : SimpleStringMsg(NETMSGTYPE_GenericDebug, commandStr) + { + } + + GenericDebugMsg() : SimpleStringMsg(NETMSGTYPE_GenericDebug) + { + } + + private: + + + public: + // getters & setters + const char* getCommandStr() + { + return getValue(); + } +}; + + diff --git a/common/source/common/net/message/nodes/GenericDebugRespMsg.h b/common/source/common/net/message/nodes/GenericDebugRespMsg.h new file mode 100644 index 0000000..f822833 --- /dev/null +++ b/common/source/common/net/message/nodes/GenericDebugRespMsg.h @@ -0,0 +1,31 @@ +#pragma once + +#include + + +class GenericDebugRespMsg : public SimpleStringMsg +{ + public: + GenericDebugRespMsg(const char* cmdRespStr) : + SimpleStringMsg(NETMSGTYPE_GenericDebugResp, cmdRespStr) + { + } + + GenericDebugRespMsg() : SimpleStringMsg(NETMSGTYPE_GenericDebugResp) + { + } + + + private: + + + public: + // getters & setters + const char* getCmdRespStr() + { + return getValue(); + } + +}; + + diff --git a/common/source/common/net/message/nodes/GetClientStatsMsg.h b/common/source/common/net/message/nodes/GetClientStatsMsg.h new file mode 100644 index 0000000..2c5bc78 --- /dev/null +++ b/common/source/common/net/message/nodes/GetClientStatsMsg.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#define GETCLIENTSTATSMSG_FLAG_PERUSERSTATS 1 /* query per-user (instead per-client) stats */ + + +/** + * Request per-client or per-user operation statictics. + */ +class GetClientStatsMsg : public SimpleInt64Msg +{ + public: + /** + * @param cookieIP - Not all clients fit into the last stats message. So we use this IP + * as a cookie to know where to continue (IP + 1) + * + * This constructor is called on the side sending the mesage. + */ + GetClientStatsMsg(int64_t cookieIP) : SimpleInt64Msg(NETMSGTYPE_GetClientStats, cookieIP) + { + } + + /** + * For dersialization only. + */ + GetClientStatsMsg() : SimpleInt64Msg(NETMSGTYPE_GetClientStats) + { + } + + + protected: + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return GETCLIENTSTATSMSG_FLAG_PERUSERSTATS; + } + + + public: + uint64_t getCookieIP() + { + return getValue(); + } +}; + + diff --git a/common/source/common/net/message/nodes/GetClientStatsRespMsg.h b/common/source/common/net/message/nodes/GetClientStatsRespMsg.h new file mode 100644 index 0000000..8e4f2c0 --- /dev/null +++ b/common/source/common/net/message/nodes/GetClientStatsRespMsg.h @@ -0,0 +1,57 @@ +#pragma once + + +#include +#include +#include + + +#define GETCLIENTSTATSRESP_MAX_PAYLOAD_LEN (56*1024) // actual max is 64KiB minus header and + // encoding overhead, so we have a safe + // amount (8KiB) of room here left for that. + + +/** + * This message sends the stats for multiple client IPs encoded in a single vector. + */ +class GetClientStatsRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param statsVec - The list has: IP, data1, data2, ..., dataN, IP, data1, ..., dataN + * NOTE: Just a reference, DO NOT free it as long as you use this + * object! + */ + GetClientStatsRespMsg(UInt64Vector* statsVec) : + BaseType(NETMSGTYPE_GetClientStatsResp) + { + this->statsVec = statsVec; + } + + GetClientStatsRespMsg() : BaseType(NETMSGTYPE_GetClientStatsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->statsVec, obj->parsed.statsVec); + } + + private: + // for serialization + UInt64Vector* statsVec; // not owned by this object! + + // for deserialization + struct { + UInt64Vector statsVec; + } parsed; + + public: + UInt64Vector& getStatsVector() + { + return *statsVec; + } +}; + diff --git a/common/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h b/common/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h new file mode 100644 index 0000000..b621f81 --- /dev/null +++ b/common/source/common/net/message/nodes/GetMirrorBuddyGroupsMsg.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include + + +class GetMirrorBuddyGroupsMsg : public SimpleIntMsg +{ + public: + /** + * @param nodeType type of states to download (meta, storage). + */ + GetMirrorBuddyGroupsMsg(NodeType nodeType) : + SimpleIntMsg(NETMSGTYPE_GetMirrorBuddyGroups, nodeType) + { + } + + /** + * For deserialization only + */ + GetMirrorBuddyGroupsMsg() : SimpleIntMsg(NETMSGTYPE_GetMirrorBuddyGroups) + { + } + + + private: + + + public: + // getters & setters + NodeType getNodeType() + { + return (NodeType)getValue(); + } +}; + + diff --git a/common/source/common/net/message/nodes/GetMirrorBuddyGroupsRespMsg.h b/common/source/common/net/message/nodes/GetMirrorBuddyGroupsRespMsg.h new file mode 100644 index 0000000..46aab7c --- /dev/null +++ b/common/source/common/net/message/nodes/GetMirrorBuddyGroupsRespMsg.h @@ -0,0 +1,61 @@ +#pragma once + +#include +#include + + +class GetMirrorBuddyGroupsRespMsg : public NetMessageSerdes +{ + public: + GetMirrorBuddyGroupsRespMsg(UInt16List* buddyGroupIDs, UInt16List* primaryTargetIDs, + UInt16List* secondaryTargetIDs) : BaseType(NETMSGTYPE_GetMirrorBuddyGroupsResp) + { + this->buddyGroupIDs = buddyGroupIDs; + this->primaryTargetIDs = primaryTargetIDs; + this->secondaryTargetIDs = secondaryTargetIDs; + } + + GetMirrorBuddyGroupsRespMsg() : + BaseType(NETMSGTYPE_GetMirrorBuddyGroupsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->buddyGroupIDs, obj->parsed.buddyGroupIDs) + % serdes::backedPtr(obj->primaryTargetIDs, obj->parsed.primaryTargetIDs) + % serdes::backedPtr(obj->secondaryTargetIDs, obj->parsed.secondaryTargetIDs); + } + + private: + UInt16List* buddyGroupIDs; // not owned by this object! + UInt16List* primaryTargetIDs; // not owned by this object! + UInt16List* secondaryTargetIDs; // not owned by this object! + + // for deserialization + struct { + UInt16List buddyGroupIDs; + UInt16List primaryTargetIDs; + UInt16List secondaryTargetIDs; + } parsed; + + public: + UInt16List& getBuddyGroupIDs() + { + return *buddyGroupIDs; + } + + UInt16List& getPrimaryTargetIDs() + { + return *primaryTargetIDs; + } + + UInt16List& getSecondaryTargetIDs() + { + return *secondaryTargetIDs; + } +}; + + diff --git a/common/source/common/net/message/nodes/GetNodeCapacityPoolsMsg.h b/common/source/common/net/message/nodes/GetNodeCapacityPoolsMsg.h new file mode 100644 index 0000000..60a1446 --- /dev/null +++ b/common/source/common/net/message/nodes/GetNodeCapacityPoolsMsg.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include "../SimpleIntMsg.h" + + +enum CapacityPoolQueryType +{ + CapacityPoolQuery_META = 0, + CapacityPoolQuery_STORAGE = 1, + CapacityPoolQuery_METABUDDIES = 2, + CapacityPoolQuery_STORAGEBUDDIES = 3 +}; + + +class GetNodeCapacityPoolsMsg : public SimpleIntMsg +{ + public: + GetNodeCapacityPoolsMsg(CapacityPoolQueryType poolType) : + SimpleIntMsg(NETMSGTYPE_GetNodeCapacityPools, poolType) + { + } + + /** + * For deserialization only. + */ + GetNodeCapacityPoolsMsg() : SimpleIntMsg(NETMSGTYPE_GetNodeCapacityPools) + { + } + + static std::string queryTypeToStr(CapacityPoolQueryType t) + { + switch (t) + { + case CapacityPoolQuery_META: + return "Meta"; + case CapacityPoolQuery_STORAGE: + return "Storage"; + case CapacityPoolQuery_METABUDDIES: + return "Meta buddies"; + case CapacityPoolQuery_STORAGEBUDDIES: + return "Storage buddies"; + default: + return ""; + } + } + + // getters & setters + + CapacityPoolQueryType getCapacityPoolQueryType() + { + return (CapacityPoolQueryType)getValue(); + } +}; + diff --git a/common/source/common/net/message/nodes/GetNodeCapacityPoolsRespMsg.h b/common/source/common/net/message/nodes/GetNodeCapacityPoolsRespMsg.h new file mode 100644 index 0000000..e476c50 --- /dev/null +++ b/common/source/common/net/message/nodes/GetNodeCapacityPoolsRespMsg.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include + +class GetNodeCapacityPoolsRespMsg : public NetMessageSerdes +{ + public: + // a map with storagePoolId as key and a vector of capacity pool lists as value. The + // vector has CapacityPoolType as index, so vec[CapacityPool_NORMAL] represents th normal + // pool, etc. + typedef std::map PoolsMap; + + /** + * @param capacityLists a map with storagePoolId as key and a vector of capacity pool lists + * as value. For metadata pools, the map key is irrelevant and there will be only one + * entry in the map with key 0. + */ + GetNodeCapacityPoolsRespMsg(PoolsMap* poolsMap) : + BaseType(NETMSGTYPE_GetNodeCapacityPoolsResp), poolsMap(poolsMap) + { } + + GetNodeCapacityPoolsRespMsg() : BaseType(NETMSGTYPE_GetNodeCapacityPoolsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->poolsMap, obj->parsed.poolsMap); + } + + private: + // for serialization + PoolsMap* poolsMap; // not owned by this object! + + // for deserialization + struct { + PoolsMap poolsMap; + } parsed; + + public: + PoolsMap& getPoolsMap() + { + return *poolsMap; + } +}; + diff --git a/common/source/common/net/message/nodes/GetNodesMsg.h b/common/source/common/net/message/nodes/GetNodesMsg.h new file mode 100644 index 0000000..4de8ed0 --- /dev/null +++ b/common/source/common/net/message/nodes/GetNodesMsg.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +class GetNodesMsg : public SimpleIntMsg +{ + public: + /** + * @param nodeType type of nodes to download (meta, storage, ...) + */ + GetNodesMsg(NodeType nodeType) : SimpleIntMsg(NETMSGTYPE_GetNodes, nodeType) + { + } + + /** + * For deserialization only + */ + GetNodesMsg() : SimpleIntMsg(NETMSGTYPE_GetNodes) + { + } + + + private: + + + public: + // getters & setters + NodeType getNodeType() + { + return (NodeType)getValue(); + } +}; + + diff --git a/common/source/common/net/message/nodes/GetNodesRespMsg.h b/common/source/common/net/message/nodes/GetNodesRespMsg.h new file mode 100644 index 0000000..bcd68bf --- /dev/null +++ b/common/source/common/net/message/nodes/GetNodesRespMsg.h @@ -0,0 +1,70 @@ +#pragma once + +#include +#include +#include + + +class GetNodesRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param rootID just a reference, so do not free it as long as you use this object! + * @param rootIsBuddyMirrored + * @param nodeList just a reference, so do not free it as long as you use this object! + */ + GetNodesRespMsg(NumNodeID rootNumID, bool rootIsBuddyMirrored, + std::vector& nodeList) : + BaseType(NETMSGTYPE_GetNodesResp) + { + this->rootNumID = rootNumID; + this->rootIsBuddyMirrored = rootIsBuddyMirrored; + this->nodeList = &nodeList; + } + + /** + * For deserialization only + */ + GetNodesRespMsg() : BaseType(NETMSGTYPE_GetNodesResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->nodeList, obj->parsed.nodeList) + % obj->rootNumID + % obj->rootIsBuddyMirrored; + } + + private: + NumNodeID rootNumID; + bool rootIsBuddyMirrored; + + // for serialization + std::vector* nodeList; // not owned by this object! + + // for deserialization + struct { + std::vector nodeList; + } parsed; + + public: + std::vector& getNodeList() + { + return *nodeList; + } + + NumNodeID getRootNumID() const + { + return rootNumID; + } + + bool getRootIsBuddyMirrored() + { + return rootIsBuddyMirrored; + } +}; + diff --git a/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h b/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h new file mode 100644 index 0000000..414c471 --- /dev/null +++ b/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include "common/nodes/NumNodeID.h" +#include +#include + +class GetStatesAndBuddyGroupsMsg : public NetMessageSerdes +{ + public: + /** + * @param nodeType type of states to download (meta, storage). + */ + GetStatesAndBuddyGroupsMsg(NodeType nodeType) : + BaseType(NETMSGTYPE_GetStatesAndBuddyGroups), nodeType(nodeType), requestedByClientID(NumNodeID(0)) + { + } + + /** + * For deserialization only + */ + GetStatesAndBuddyGroupsMsg() : BaseType(NETMSGTYPE_GetStatesAndBuddyGroups) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % obj->requestedByClientID; + } + + private: + int32_t nodeType; + NumNodeID requestedByClientID; + + public: + // getters & setters + NodeType getNodeType() + { + return static_cast(nodeType); + } + + NumNodeID getRequestedByClientID() + { + return requestedByClientID; + } +}; + diff --git a/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h b/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h new file mode 100644 index 0000000..3578b60 --- /dev/null +++ b/common/source/common/net/message/nodes/GetStatesAndBuddyGroupsRespMsg.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + + +/** + * This message carries two maps: + * 1) buddyGroupID -> primaryTarget, secondaryTarget + * 2) targetID -> targetReachabilityState, targetConsistencyState + */ +class GetStatesAndBuddyGroupsRespMsg : public NetMessageSerdes +{ + public: + GetStatesAndBuddyGroupsRespMsg(const MirrorBuddyGroupMap& groups, + const TargetStateMap& states) : + BaseType(NETMSGTYPE_GetStatesAndBuddyGroupsResp), + groups(&groups), states(&states) + { + } + + /** + * For deserialization only. + */ + GetStatesAndBuddyGroupsRespMsg() : + BaseType(NETMSGTYPE_GetStatesAndBuddyGroupsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->groups, obj->parsed.groups) + % serdes::backedPtr(obj->states, obj->parsed.states); + } + + private: + const MirrorBuddyGroupMap* groups; + const TargetStateMap* states; + + // for deserialization + struct { + MirrorBuddyGroupMap groups; + TargetStateMap states; + } parsed; + + public: + const MirrorBuddyGroupMap& getGroups() const { return *groups; } + + MirrorBuddyGroupMap releaseGroups() + { + return groups == &parsed.groups ? std::move(parsed.groups) : *groups; + } + + const TargetStateMap& getStates() const { return *states; } + + TargetStateMap releaseStates() + { + return states == &parsed.states ? std::move(parsed.states) : *states; + } +}; + diff --git a/common/source/common/net/message/nodes/GetTargetConsistencyStatesMsg.h b/common/source/common/net/message/nodes/GetTargetConsistencyStatesMsg.h new file mode 100644 index 0000000..cfa68f8 --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetConsistencyStatesMsg.h @@ -0,0 +1,24 @@ +#pragma once + +class GetTargetConsistencyStatesMsg : public NetMessageSerdes +{ + public: + GetTargetConsistencyStatesMsg(const UInt16Vector& targetIDs) + : BaseType(NETMSGTYPE_GetTargetConsistencyStates), + targetIDs(targetIDs) + { } + + GetTargetConsistencyStatesMsg() : BaseType(NETMSGTYPE_GetTargetConsistencyStates) + { } + + protected: + UInt16Vector targetIDs; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->targetIDs; + } +}; + diff --git a/common/source/common/net/message/nodes/GetTargetConsistencyStatesRespMsg.h b/common/source/common/net/message/nodes/GetTargetConsistencyStatesRespMsg.h new file mode 100644 index 0000000..b1c5e95 --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetConsistencyStatesRespMsg.h @@ -0,0 +1,26 @@ +#pragma once + +class GetTargetConsistencyStatesRespMsg : public NetMessageSerdes +{ + public: + GetTargetConsistencyStatesRespMsg(const TargetConsistencyStateVec& states) + : BaseType(NETMSGTYPE_GetTargetConsistencyStatesResp), + states(states) + { } + + GetTargetConsistencyStatesRespMsg() : BaseType(NETMSGTYPE_GetTargetConsistencyStatesResp) + { } + + protected: + TargetConsistencyStateVec states; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->states; + } + + const TargetConsistencyStateVec& getStates() const { return states; } +}; + diff --git a/common/source/common/net/message/nodes/GetTargetMappingsMsg.h b/common/source/common/net/message/nodes/GetTargetMappingsMsg.h new file mode 100644 index 0000000..41331e1 --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetMappingsMsg.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + + +class GetTargetMappingsMsg : public SimpleMsg +{ + public: + GetTargetMappingsMsg() : + SimpleMsg(NETMSGTYPE_GetTargetMappings) + { + } + +}; + + diff --git a/common/source/common/net/message/nodes/GetTargetMappingsRespMsg.h b/common/source/common/net/message/nodes/GetTargetMappingsRespMsg.h new file mode 100644 index 0000000..e7b8a31 --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetMappingsRespMsg.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +class GetTargetMappingsRespMsg : public NetMessageSerdes +{ + public: + /** + * @param targetIDs just a reference => do not free while you're using this object + * @param nodeIDs just a reference => do not free while you're using this object + */ + GetTargetMappingsRespMsg(const std::map& mappings) : + BaseType(NETMSGTYPE_GetTargetMappingsResp), + mappings(&mappings) + { + } + + GetTargetMappingsRespMsg() : BaseType(NETMSGTYPE_GetTargetMappingsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->mappings, obj->parsed.mappings); + } + + private: + // for serialization + const std::map* mappings; + + // for deserialization + struct { + std::map mappings; + } parsed; + + + public: + const std::map& getMappings() const { return *mappings; } + + std::map releaseMappings() + { + return mappings == &parsed.mappings + ? std::move(parsed.mappings) + : *mappings; + } +}; + diff --git a/common/source/common/net/message/nodes/GetTargetStatesMsg.h b/common/source/common/net/message/nodes/GetTargetStatesMsg.h new file mode 100644 index 0000000..ec6032b --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetStatesMsg.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + + +class GetTargetStatesMsg : public SimpleIntMsg +{ + public: + /** + * @param nodeType type of states to download (meta, storage). + */ + GetTargetStatesMsg(NodeType nodeType) : SimpleIntMsg(NETMSGTYPE_GetTargetStates, nodeType) + { + } + + /** + * For deserialization only + */ + GetTargetStatesMsg() : SimpleIntMsg(NETMSGTYPE_GetTargetStates) + { + } + + + private: + + + public: + // getters & setters + NodeType getNodeType() + { + return (NodeType)getValue(); + } + +}; + + diff --git a/common/source/common/net/message/nodes/GetTargetStatesRespMsg.h b/common/source/common/net/message/nodes/GetTargetStatesRespMsg.h new file mode 100644 index 0000000..8f48870 --- /dev/null +++ b/common/source/common/net/message/nodes/GetTargetStatesRespMsg.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +class GetTargetStatesRespMsg : public NetMessageSerdes +{ + public: + GetTargetStatesRespMsg(UInt16List* targetIDs, UInt8List* reachabilityStates, + UInt8List* consistencyStates) : + BaseType(NETMSGTYPE_GetTargetStatesResp) + { + this->targetIDs = targetIDs; + this->reachabilityStates = reachabilityStates; + this->consistencyStates = consistencyStates; + } + + GetTargetStatesRespMsg() : + BaseType(NETMSGTYPE_GetTargetStatesResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->targetIDs, obj->parsed.targetIDs) + % serdes::backedPtr(obj->reachabilityStates, obj->parsed.reachabilityStates) + % serdes::backedPtr(obj->consistencyStates, obj->parsed.consistencyStates); + } + + private: + UInt16List* targetIDs; // not owned by this object! + UInt8List* reachabilityStates; // not owned by this object! + UInt8List* consistencyStates; // not owned by this object! + + // for deserialization + struct { + UInt16List targetIDs; + UInt8List reachabilityStates; + UInt8List consistencyStates; + } parsed; + + public: + UInt16List& getTargetIDs() + { + return *targetIDs; + } + + UInt8List& getReachabilityStates() + { + return *reachabilityStates; + } + + UInt8List& getConsistencyStates() + { + return *consistencyStates; + } +}; + + diff --git a/common/source/common/net/message/nodes/HeartbeatMsg.h b/common/source/common/net/message/nodes/HeartbeatMsg.h new file mode 100644 index 0000000..4962a36 --- /dev/null +++ b/common/source/common/net/message/nodes/HeartbeatMsg.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include +#include +#include +#include + +class HeartbeatMsg : public AcknowledgeableMsgSerdes +{ + public: + + /** + * @param nicList just a reference, so do not free it as long as you use this object + */ + HeartbeatMsg(const std::string& nodeID, NumNodeID nodeNumID, NodeType nodeType, + NicAddressList* nicList) + : BaseType(NETMSGTYPE_Heartbeat) + { + this->nodeID = nodeID; + this->nodeNumID = nodeNumID; + + this->nodeType = nodeType; + + this->rootIsBuddyMirrored = false; + + this->instanceVersion = 0; // reserved for future use + + this->nicListVersion = 0; // reserved for future use + this->nicList = nicList; + + this->portUDP = 0; // 0 means "undefined" + this->portTCP = 0; // 0 means "undefined" + + this->machineUUID = ""; + } + + /** + * For deserialization only + */ + HeartbeatMsg() : BaseType(NETMSGTYPE_Heartbeat) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->instanceVersion + % obj->nicListVersion + % obj->nodeType + % obj->nodeID; + + obj->serializeAckID(ctx, 4); + + ctx + % obj->nodeNumID + % obj->rootNumID + % obj->rootIsBuddyMirrored + % obj->portUDP + % obj->portTCP + % serdesNicAddressList(obj->nicList, obj->parsed.nicList) + % obj->machineUUID; + } + + private: + std::string nodeID; + std::string machineUUID; + int32_t nodeType; + NumNodeID nodeNumID; + NumNodeID rootNumID; // 0 means unknown/undefined + bool rootIsBuddyMirrored; + uint64_t instanceVersion; // not used currently + uint64_t nicListVersion; // not used currently + uint16_t portUDP; // 0 means "undefined" + uint16_t portTCP; // 0 means "undefined" + + // for serialization + NicAddressList* nicList; // not owned by this object + + // for deserialization + struct { + NicAddressList nicList; + } parsed; + + + public: + NicAddressList& getNicList() + { + return *nicList; + } + + const std::string& getNodeID() const + { + return nodeID; + } + + NumNodeID getNodeNumID() const + { + return nodeNumID; + } + + NodeType getNodeType() const + { + return (NodeType)nodeType; + } + + NumNodeID getRootNumID() const + { + return rootNumID; + } + + void setRootNumID(const NumNodeID rootNumID) + { + this->rootNumID = rootNumID; + } + + bool getRootIsBuddyMirrored() + { + return rootIsBuddyMirrored; + } + + void setRootIsBuddyMirrored(bool rootIsBuddyMirrored) + { + this->rootIsBuddyMirrored = rootIsBuddyMirrored; + } + + void setPorts(const uint16_t portUDP, const uint16_t portTCP) + { + this->portUDP = portUDP; + this->portTCP = portTCP; + } + + void setMachineUUID(std::string uuid) + { + this->machineUUID = uuid; + } + + uint16_t getPortUDP() const + { + return portUDP; + } + + uint16_t getPortTCP() const + { + return portTCP; + } + +}; + diff --git a/common/source/common/net/message/nodes/HeartbeatRequestMsg.h b/common/source/common/net/message/nodes/HeartbeatRequestMsg.h new file mode 100644 index 0000000..bcaff1d --- /dev/null +++ b/common/source/common/net/message/nodes/HeartbeatRequestMsg.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include "../SimpleMsg.h" + +class HeartbeatRequestMsg : public SimpleMsg +{ + public: + HeartbeatRequestMsg() : SimpleMsg(NETMSGTYPE_HeartbeatRequest) + { + } +}; + + diff --git a/common/source/common/net/message/nodes/MapTargetsMsg.h b/common/source/common/net/message/nodes/MapTargetsMsg.h new file mode 100644 index 0000000..36c75a6 --- /dev/null +++ b/common/source/common/net/message/nodes/MapTargetsMsg.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +class MapTargetsMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * Maps all targetIDs to the same given nodeID. + * + * @param targets each target has a corresponding storage pool id to map it to (pool id might + * be ignored on the receiving side, if the target is not newly mapped). + * note: just a reference => do not free while you're using this object + * @param nodeID numeric node ID + */ + MapTargetsMsg(const std::map& targets, NumNodeID nodeID): + BaseType(NETMSGTYPE_MapTargets), + nodeID(nodeID), targets(&targets) + { + } + + MapTargetsMsg() : BaseType(NETMSGTYPE_MapTargets) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->targets, obj->parsed.targets) + % obj->nodeID; + + obj->serializeAckID(ctx); + } + + NumNodeID getNodeID() const + { + return nodeID; + } + + const std::map& getTargets() const + { + return *targets; + } + + private: + NumNodeID nodeID; + + // for serialization + const std::map* targets; // not owned by this object! + + // for deserialization + struct { + std::map targets; + } parsed; +}; + diff --git a/common/source/common/net/message/nodes/MapTargetsRespMsg.h b/common/source/common/net/message/nodes/MapTargetsRespMsg.h new file mode 100644 index 0000000..d0ab6aa --- /dev/null +++ b/common/source/common/net/message/nodes/MapTargetsRespMsg.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + + +class MapTargetsRespMsg : public NetMessageSerdes +{ + public: + /** + * @param results indicates the map result of each target sent + */ + MapTargetsRespMsg(const std::map& results): + BaseType(NETMSGTYPE_MapTargetsResp), results(&results) + { + } + + /** + * For deserialization only! + */ + MapTargetsRespMsg(): + BaseType(NETMSGTYPE_MapTargetsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->results, obj->parsed.results); + } + + const std::map& getResults() const { return *results; } + + private: + // for serialization + const std::map* results; + + // for deserialization + struct + { + std::map results; + } parsed; +}; + + diff --git a/common/source/common/net/message/nodes/PublishCapacitiesMsg.h b/common/source/common/net/message/nodes/PublishCapacitiesMsg.h new file mode 100644 index 0000000..85c252e --- /dev/null +++ b/common/source/common/net/message/nodes/PublishCapacitiesMsg.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +/** + * This message notifies the server that the mgmtd needs its free capacity information. It is + * typically sent to the storage and metadata servers when the mgmtd starts up and does not have any + * capacity information yet. The storage/metadata servers should then publish their free capacity + * info on the next InternodeSyncer run. + */ +class PublishCapacitiesMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * Default constructor for serialization and deserialization. + */ + PublishCapacitiesMsg() : BaseType(NETMSGTYPE_PublishCapacities) + { } +}; + diff --git a/common/source/common/net/message/nodes/RefreshCapacityPoolsMsg.h b/common/source/common/net/message/nodes/RefreshCapacityPoolsMsg.h new file mode 100644 index 0000000..ae44d3b --- /dev/null +++ b/common/source/common/net/message/nodes/RefreshCapacityPoolsMsg.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include + + +/** + * This msg notifies the receiver about a change in the capacity pool lists. It is typically sent by + * the mgmtd to a mds. The receiver should update its capacity pools. + */ +class RefreshCapacityPoolsMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * Default constructor for serialization and deserialization. + */ + RefreshCapacityPoolsMsg() : BaseType(NETMSGTYPE_RefreshCapacityPools) + { + } +}; + + diff --git a/common/source/common/net/message/nodes/RefreshTargetStatesMsg.h b/common/source/common/net/message/nodes/RefreshTargetStatesMsg.h new file mode 100644 index 0000000..5efbea9 --- /dev/null +++ b/common/source/common/net/message/nodes/RefreshTargetStatesMsg.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + + +/** + * This msg notifies the receiver about a change in the targets states store. It is typically sent + * by the mgmtd. The receiver should update its target state store + */ +class RefreshTargetStatesMsg : public AcknowledgeableMsgSerdes +{ + public: + RefreshTargetStatesMsg() : BaseType(NETMSGTYPE_RefreshTargetStates) + { + } +}; + + diff --git a/common/source/common/net/message/nodes/RegisterNodeMsg.h b/common/source/common/net/message/nodes/RegisterNodeMsg.h new file mode 100644 index 0000000..5c2754e --- /dev/null +++ b/common/source/common/net/message/nodes/RegisterNodeMsg.h @@ -0,0 +1,149 @@ +#pragma once + +#include +#include +#include + +/** + * Registers a new node with the management daemon. + * + * This also provides the mechanism to retrieve a numeric node ID from the management node. + */ +class RegisterNodeMsg : public NetMessageSerdes +{ + public: + + /** + * @param nodeNumID set to 0 if no numeric ID was assigned yet and the numeric ID will be set + * in the reponse + * @param nicList just a reference, so do not free it as long as you use this object! + * @param portUDP 0 means undefined + * @param portTCP 0 means undefined + */ + RegisterNodeMsg(const std::string& nodeID, NumNodeID nodeNumID, NodeType nodeType, + NicAddressList* nicList, + uint16_t portUDP, uint16_t portTCP) : BaseType(NETMSGTYPE_RegisterNode) + { + this->nodeID = nodeID; + + this->nodeNumID = nodeNumID; + + this->nodeType = nodeType; + + this->rootIsBuddyMirrored = false; + + this->instanceVersion = 0; // reserved for future use + + this->nicListVersion = 0; // reserved for future use + this->nicList = nicList; + + this->portUDP = portUDP; + this->portTCP = portTCP; + + this->machineUUID = ""; + } + + /** + * For deserialization only + */ + RegisterNodeMsg() : BaseType(NETMSGTYPE_RegisterNode) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->instanceVersion + % obj->nicListVersion + % obj->nodeID + % serdesNicAddressList(obj->nicList, obj->parsed.nicList) + % obj->nodeType + % obj->nodeNumID + % obj->rootNumID + % obj->rootIsBuddyMirrored + % obj->portUDP + % obj->portTCP + % obj->machineUUID; + } + + private: + std::string nodeID; + std::string machineUUID; + int32_t nodeType; + NumNodeID nodeNumID; // 0 means "undefined" + NumNodeID rootNumID; // 0 means "undefined" + bool rootIsBuddyMirrored; + uint64_t instanceVersion; // not used currently + uint64_t nicListVersion; // not used currently + uint16_t portUDP; // 0 means "undefined" + uint16_t portTCP; // 0 means "undefined" + + // for serialization + NicAddressList* nicList; // not owned by this object! + + // for deserialization + struct { + NicAddressList nicList; + } parsed; + + public: + NicAddressList& getNicList() + { + return *nicList; + } + + const std::string& getNodeID() const + { + return nodeID; + } + + NumNodeID getNodeNumID() const + { + return nodeNumID; + } + + NodeType getNodeType() const + { + return (NodeType)nodeType; + } + + NumNodeID getRootNumID() const + { + return rootNumID; + } + + void setRootNumID(NumNodeID rootNumID) + { + this->rootNumID = rootNumID; + } + + bool getRootIsBuddyMirrored() + { + return rootIsBuddyMirrored; + } + + void setRootIsBuddyMirrored(bool rootIsBuddyMirrored) + { + this->rootIsBuddyMirrored = rootIsBuddyMirrored; + } + + void setMachineUUID(std::string uuid) + { + this->machineUUID = uuid; + } + + + uint16_t getPortUDP() const + { + return portUDP; + } + + uint16_t getPortTCP() const + { + return portTCP; + } + +}; + + diff --git a/common/source/common/net/message/nodes/RegisterNodeRespMsg.h b/common/source/common/net/message/nodes/RegisterNodeRespMsg.h new file mode 100644 index 0000000..a7f41df --- /dev/null +++ b/common/source/common/net/message/nodes/RegisterNodeRespMsg.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +class RegisterNodeRespMsg : public NetMessageSerdes +{ + public: + /** + * @param nodeNumID 0 on error (e.g. if given nodeNumID from RegisterNodeMsg was rejected), + * newly assigned numeric ID otherwise (or the old numeric ID value if it was given in + * RegisterNodeMsg and was accepted). + */ + RegisterNodeRespMsg(NumNodeID nodeNumID) : + BaseType(NETMSGTYPE_RegisterNodeResp), nodeNumID(nodeNumID) + { + } + + /** + * For deserialization only + */ + RegisterNodeRespMsg() : BaseType(NETMSGTYPE_RegisterNodeResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->nodeNumID; + ctx % obj->grpcPort; + ctx % obj->fsUUID; + } + + private: + NumNodeID nodeNumID; + // grpcPort and fsUUID are currently only used on the client. We deserialize them here for + // completeness, but don't implement any getters for now. + uint16_t grpcPort; + std::string fsUUID; + + public: + // getters & setters + NumNodeID getNodeNumID() const + { + return nodeNumID; + } +}; + + diff --git a/common/source/common/net/message/nodes/RegisterTargetMsg.h b/common/source/common/net/message/nodes/RegisterTargetMsg.h new file mode 100644 index 0000000..e9ca721 --- /dev/null +++ b/common/source/common/net/message/nodes/RegisterTargetMsg.h @@ -0,0 +1,68 @@ +#pragma once + + +#include +#include +#include + +/** + * Registers a new target with the management daemon. + * + * This also provides the mechanism to retrieve a numeric target ID from the management node. + */ +class RegisterTargetMsg : public NetMessageSerdes +{ + public: + + /** + * @param targetID just a reference, so do not free it as long as you use this object + * @param targetNumID set to 0 if no numeric ID was assigned yet and the numeric ID will be + * set in the reponse + */ + RegisterTargetMsg(const char* targetID, uint16_t targetNumID) : + BaseType(NETMSGTYPE_RegisterTarget) + { + this->targetID = targetID; + this->targetIDLen = strlen(targetID); + + this->targetNumID = targetNumID; + } + + /** + * For deserialization only + */ + RegisterTargetMsg() : BaseType(NETMSGTYPE_RegisterTarget) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->targetID, obj->targetIDLen) + % obj->targetNumID; + } + + private: + unsigned targetIDLen; + const char* targetID; + uint16_t targetNumID; // 0 means "undefined" + + + public: + + // getters & setters + + const char* getTargetID() + { + return targetID; + } + + uint16_t getTargetNumID() + { + return targetNumID; + } + +}; + + diff --git a/common/source/common/net/message/nodes/RegisterTargetRespMsg.h b/common/source/common/net/message/nodes/RegisterTargetRespMsg.h new file mode 100644 index 0000000..b39c099 --- /dev/null +++ b/common/source/common/net/message/nodes/RegisterTargetRespMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include "../SimpleUInt16Msg.h" + + +class RegisterTargetRespMsg : public SimpleUInt16Msg +{ + public: + /** + * @param targetNumID 0 on error (e.g. if given targetNumID from RegisterTargetMsg was + * rejected), newly assigned numeric ID otherwise (or the old numeric ID value if it was given + * in RegisterTargetMsg and was accepted). + */ + RegisterTargetRespMsg(uint16_t targetNumID) : + SimpleUInt16Msg(NETMSGTYPE_RegisterTargetResp, targetNumID) + { + } + + /** + * For deserialization only + */ + RegisterTargetRespMsg() : SimpleUInt16Msg(NETMSGTYPE_RegisterTargetResp) + { + } + + + private: + + + public: + // getters & setters + uint16_t getTargetNumID() + { + return getValue(); + } +}; + + diff --git a/common/source/common/net/message/nodes/RemoveBuddyGroupMsg.h b/common/source/common/net/message/nodes/RemoveBuddyGroupMsg.h new file mode 100644 index 0000000..bcd1b4c --- /dev/null +++ b/common/source/common/net/message/nodes/RemoveBuddyGroupMsg.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class RemoveBuddyGroupMsg : public NetMessageSerdes +{ + public: + RemoveBuddyGroupMsg(NodeType type, uint16_t groupID, bool checkOnly, bool force): + BaseType(NETMSGTYPE_RemoveBuddyGroup), type(type), groupID(groupID), checkOnly(checkOnly), + force(force) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->type) + % obj->groupID + % obj->checkOnly + % obj->force; + } + + protected: + RemoveBuddyGroupMsg() : BaseType(NETMSGTYPE_RemoveNode) + { + } + + protected: + NodeType type; + uint16_t groupID; + bool checkOnly; + bool force; +}; + diff --git a/common/source/common/net/message/nodes/RemoveBuddyGroupRespMsg.h b/common/source/common/net/message/nodes/RemoveBuddyGroupRespMsg.h new file mode 100644 index 0000000..1dbb21a --- /dev/null +++ b/common/source/common/net/message/nodes/RemoveBuddyGroupRespMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class RemoveBuddyGroupRespMsg : public NetMessageSerdes +{ + public: + RemoveBuddyGroupRespMsg(FhgfsOpsErr result): + BaseType(NETMSGTYPE_RemoveBuddyGroupResp), result(result) + { + } + + RemoveBuddyGroupRespMsg() : BaseType(NETMSGTYPE_RemoveBuddyGroupResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->result; + } + + private: + FhgfsOpsErr result; + + public: + FhgfsOpsErr getResult() const { return result; } +}; + diff --git a/common/source/common/net/message/nodes/RemoveNodeMsg.h b/common/source/common/net/message/nodes/RemoveNodeMsg.h new file mode 100644 index 0000000..c0e59a6 --- /dev/null +++ b/common/source/common/net/message/nodes/RemoveNodeMsg.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include + +class RemoveNodeMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * @param nodeType NODETYPE_... + */ + RemoveNodeMsg(NumNodeID nodeNumID, NodeType nodeType) : + BaseType(NETMSGTYPE_RemoveNode) + { + this->nodeNumID = nodeNumID; + + this->nodeType = (int16_t)nodeType; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % obj->nodeNumID; + + obj->serializeAckID(ctx); + } + + + protected: + RemoveNodeMsg() : BaseType(NETMSGTYPE_RemoveNode) + { + } + + private: + NumNodeID nodeNumID; + int16_t nodeType; // NODETYPE_... + + public: + NumNodeID getNodeNumID() const { return nodeNumID; } + + NodeType getNodeType() const { return (NodeType)nodeType; } +}; + + diff --git a/common/source/common/net/message/nodes/RemoveNodeRespMsg.h b/common/source/common/net/message/nodes/RemoveNodeRespMsg.h new file mode 100644 index 0000000..82b7d3f --- /dev/null +++ b/common/source/common/net/message/nodes/RemoveNodeRespMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class RemoveNodeRespMsg : public SimpleIntMsg +{ + public: + /** + * @param result FhgfsOpsErr_... code + */ + RemoveNodeRespMsg(int result) : + SimpleIntMsg(NETMSGTYPE_RemoveNodeResp, result) + { + } + + RemoveNodeRespMsg() : + SimpleIntMsg(NETMSGTYPE_RemoveNodeResp) + { + } +}; + + diff --git a/common/source/common/net/message/nodes/SetMirrorBuddyGroupMsg.h b/common/source/common/net/message/nodes/SetMirrorBuddyGroupMsg.h new file mode 100644 index 0000000..9643ab6 --- /dev/null +++ b/common/source/common/net/message/nodes/SetMirrorBuddyGroupMsg.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include + +class SetMirrorBuddyGroupMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * Create or update a mirror buddy group + * + * @param primaryTargetID + * @param secondaryTargetID + * @param buddyGroupID may be 0 => create a buddy group with random ID + * @param allowUpdate if buddyGroupID is set and that group exists, allow to update it + */ + SetMirrorBuddyGroupMsg(NodeType nodeType, uint16_t primaryTargetID, + uint16_t secondaryTargetID, uint16_t buddyGroupID = 0, bool allowUpdate = false) + : BaseType(NETMSGTYPE_SetMirrorBuddyGroup), + nodeType(nodeType), + primaryTargetID(primaryTargetID), + secondaryTargetID(secondaryTargetID), + buddyGroupID(buddyGroupID), + allowUpdate(allowUpdate) + { } + + SetMirrorBuddyGroupMsg() : BaseType(NETMSGTYPE_SetMirrorBuddyGroup) + { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % obj->primaryTargetID + % obj->secondaryTargetID + % obj->buddyGroupID + % obj->allowUpdate; + + obj->serializeAckID(ctx); + } + + private: + int32_t nodeType; + uint16_t primaryTargetID; + uint16_t secondaryTargetID; + uint16_t buddyGroupID; + bool allowUpdate; + + public: + // getters & setters + NodeType getNodeType() const + { + return (NodeType)nodeType; + } + + uint16_t getPrimaryTargetID() const + { + return primaryTargetID; + } + + uint16_t getSecondaryTargetID() const + { + return secondaryTargetID; + } + + uint16_t getBuddyGroupID() const + { + return buddyGroupID; + } + + bool getAllowUpdate() const + { + return allowUpdate; + } +}; + diff --git a/common/source/common/net/message/nodes/SetMirrorBuddyGroupRespMsg.h b/common/source/common/net/message/nodes/SetMirrorBuddyGroupRespMsg.h new file mode 100644 index 0000000..817a1ea --- /dev/null +++ b/common/source/common/net/message/nodes/SetMirrorBuddyGroupRespMsg.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + + +class SetMirrorBuddyGroupRespMsg : public NetMessageSerdes +{ + public: + /** + * @param result FhgfsOpsErr + * + * NOTE: result may be one of the following: + * - FhgfsOpsErr_SUCCESS => new group added + * - FhgfsOpsErr_EXISTS => group already exists and was updated + * - FhgfsOpsErr_INUSE => group could not be updated because target is already in use + * + * @param groupID the created ID of the group, if the group was new; 0 otherwise + */ + SetMirrorBuddyGroupRespMsg(FhgfsOpsErr result, uint16_t groupID = 0) : + BaseType(NETMSGTYPE_SetMirrorBuddyGroupResp), result(result), groupID(groupID) + { + } + + SetMirrorBuddyGroupRespMsg() : + BaseType(NETMSGTYPE_SetMirrorBuddyGroupResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + uint16_t padding = 0; // PADDING + + ctx + % serdes::as(obj->result) + % obj->groupID + % padding; + } + + private: + FhgfsOpsErr result; + uint16_t groupID; + + public: + FhgfsOpsErr getResult() const { return result; } + + uint16_t getBuddyGroupID() const { return groupID; } +}; + + diff --git a/common/source/common/net/message/nodes/SetTargetConsistencyStatesMsg.h b/common/source/common/net/message/nodes/SetTargetConsistencyStatesMsg.h new file mode 100644 index 0000000..3b4abe4 --- /dev/null +++ b/common/source/common/net/message/nodes/SetTargetConsistencyStatesMsg.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include +#include + +/* + * NOTE: this message will be used in mgmtd to update target state store, but also in storage + * server to set local target states + */ + +class SetTargetConsistencyStatesMsg : public AcknowledgeableMsgSerdes +{ + public: + SetTargetConsistencyStatesMsg(NodeType nodeType, UInt16List* targetIDs, UInt8List* states, + bool setOnline) : + BaseType(NETMSGTYPE_SetTargetConsistencyStates) + { + this->targetIDs = targetIDs; + this->states = states; + this->setOnline = setOnline; + this->nodeType = nodeType; + } + + SetTargetConsistencyStatesMsg() : BaseType(NETMSGTYPE_SetTargetConsistencyStates) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % serdes::backedPtr(obj->targetIDs, obj->parsed.targetIDs) + % serdes::backedPtr(obj->states, obj->parsed.states); + + obj->serializeAckID(ctx, 4); + + ctx % obj->setOnline; + } + + private: + int nodeType; + + UInt16List* targetIDs; // not owned by this object! + UInt8List* states; // not owned by this object! + + /** + * setOnline shall be set to "true" when reporting states for local targets (i.e. targets + * which are on the reporting server). When receiving state reports for buddy + * targets (the primary telling the mgmtd that the secondary needs a resync), the + * targets should not be ONLINEd again. + */ + bool setOnline; + + + // for deserialization + struct { + UInt16List targetIDs; + UInt8List states; + } parsed; + + public: + NodeType getNodeType() + { + return static_cast(nodeType); + } + + UInt16List& getTargetIDs() + { + return *targetIDs; + } + + UInt8List& getStates() + { + return *states; + } + + bool getSetOnline() + { + return this->setOnline; + } +}; + diff --git a/common/source/common/net/message/nodes/SetTargetConsistencyStatesRespMsg.h b/common/source/common/net/message/nodes/SetTargetConsistencyStatesRespMsg.h new file mode 100644 index 0000000..b4e7ba6 --- /dev/null +++ b/common/source/common/net/message/nodes/SetTargetConsistencyStatesRespMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class SetTargetConsistencyStatesRespMsg : public SimpleIntMsg +{ + public: + SetTargetConsistencyStatesRespMsg(int result) + : SimpleIntMsg(NETMSGTYPE_SetTargetConsistencyStatesResp, result) + { + } + + SetTargetConsistencyStatesRespMsg() : SimpleIntMsg(NETMSGTYPE_SetTargetConsistencyStatesResp) + { + } + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/nodes/StorageBenchControlMsg.h b/common/source/common/net/message/nodes/StorageBenchControlMsg.h new file mode 100644 index 0000000..0a401e0 --- /dev/null +++ b/common/source/common/net/message/nodes/StorageBenchControlMsg.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include + + +class StorageBenchControlMsg: public NetMessageSerdes +{ + public: + StorageBenchControlMsg(StorageBenchAction action, StorageBenchType type, int64_t blocksize, + int64_t size, int threads, bool odirect, UInt16List* targetIDs) + : BaseType(NETMSGTYPE_StorageBenchControlMsg) + { + this->action = action; + this->type = type; + this->blocksize = blocksize; + this->size = size; + this->threads = threads; + this->odirect = odirect; + this->targetIDs = targetIDs; + } + + /** + * Constructor for deserialization only! + */ + StorageBenchControlMsg() : BaseType(NETMSGTYPE_StorageBenchControlMsg) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->action + % obj->type + % obj->blocksize + % obj->size + % obj->threads + % obj->odirect + % serdes::backedPtr(obj->targetIDs, obj->parsed.targetIDs); + } + + private: + int32_t action; // StorageBenchAction + int32_t type; // StorageBenchType + int64_t blocksize; + int64_t size; + int32_t threads; + bool odirect; + UInt16List* targetIDs; + + // deserialization info + struct { + UInt16List targetIDs; + } parsed; + + public: + //inliners + + StorageBenchAction getAction() + { + return (StorageBenchAction)this->action; + } + + StorageBenchType getType() + { + return (StorageBenchType)this->type; + } + + int64_t getBlocksize() + { + return this->blocksize; + } + + int64_t getSize() + { + return this->size; + } + + int getThreads() + { + return this->threads; + } + + bool getODirect() + { + return this->odirect; + } + + UInt16List& getTargetIDs() + { + return *targetIDs; + } +}; + diff --git a/common/source/common/net/message/nodes/StorageBenchControlMsgResp.h b/common/source/common/net/message/nodes/StorageBenchControlMsgResp.h new file mode 100644 index 0000000..3058248 --- /dev/null +++ b/common/source/common/net/message/nodes/StorageBenchControlMsgResp.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + + +class StorageBenchControlMsgResp: public NetMessageSerdes +{ + public: + /* + * @param errorCode STORAGEBENCH_ERROR_... + */ + StorageBenchControlMsgResp(StorageBenchStatus status, StorageBenchAction action, + StorageBenchType type, int errorCode, StorageBenchResultsMap& results) : + BaseType(NETMSGTYPE_StorageBenchControlMsgResp) + { + this->status = status; + this->action = action; + this->type = type; + this->errorCode = errorCode; + + for (StorageBenchResultsMapIter iter = results.begin(); iter != results.end(); iter++) + { + this->resultTargetIDs.push_back(iter->first); + this->resultValues.push_back(iter->second); + } + } + + /** + * Constructor for deserialization only + */ + StorageBenchControlMsgResp() : BaseType(NETMSGTYPE_StorageBenchControlMsgResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->status + % obj->action + % obj->type + % obj->errorCode + % obj->resultTargetIDs + % obj->resultValues; + } + + private: + int32_t status; // StorageBenchStatus + int32_t action; // StorageBenchAction + int32_t type; // StorageBenchType + int32_t errorCode; // STORAGEBENCH_ERROR... + UInt16List resultTargetIDs; + Int64List resultValues; + + public: + //inliners + + StorageBenchStatus getStatus() + { + return (StorageBenchStatus)this->status; + } + + StorageBenchAction getAction() + { + return (StorageBenchAction)this->action; + } + + StorageBenchType getType() + { + return (StorageBenchType)this->type; + } + + int getErrorCode() + { + return this->errorCode; + } + + void parseResults(StorageBenchResultsMap* outResults) + { + for (ZipIterRange valuesTargetIDIter(resultValues, resultTargetIDs); + !valuesTargetIDIter.empty(); ++valuesTargetIDIter) + { + (*outResults)[*(valuesTargetIDIter()->second)] = *(valuesTargetIDIter()->first); + } + } +}; + diff --git a/common/source/common/net/message/nodes/UnmapTargetMsg.h b/common/source/common/net/message/nodes/UnmapTargetMsg.h new file mode 100644 index 0000000..d8f4491 --- /dev/null +++ b/common/source/common/net/message/nodes/UnmapTargetMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +class UnmapTargetMsg : public SimpleUInt16Msg +{ + public: + UnmapTargetMsg(uint16_t targetID) : SimpleUInt16Msg(NETMSGTYPE_UnmapTarget, targetID) + { + } + + /** + * Constructor for deserialization only + */ + UnmapTargetMsg() : SimpleUInt16Msg(NETMSGTYPE_UnmapTarget) + { + } + + + private: + + + public: + // getters & setters + uint16_t getTargetID() + { + return getValue(); + } +}; + diff --git a/common/source/common/net/message/nodes/UnmapTargetRespMsg.h b/common/source/common/net/message/nodes/UnmapTargetRespMsg.h new file mode 100644 index 0000000..fa27798 --- /dev/null +++ b/common/source/common/net/message/nodes/UnmapTargetRespMsg.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + + +class UnmapTargetRespMsg : public SimpleIntMsg +{ + public: + /** + * @param result FhgfsOpsErr + */ + UnmapTargetRespMsg(int result) : + SimpleIntMsg(NETMSGTYPE_UnmapTargetResp, result) + { + } + + UnmapTargetRespMsg() : + SimpleIntMsg(NETMSGTYPE_UnmapTargetResp) + { + } +}; + + diff --git a/common/source/common/net/message/nodes/storagepools/AddStoragePoolMsg.h b/common/source/common/net/message/nodes/storagepools/AddStoragePoolMsg.h new file mode 100644 index 0000000..ee2c9e2 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/AddStoragePoolMsg.h @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +class AddStoragePoolMsg : public NetMessageSerdes +{ + public: + /** + * @param poolId ID for the new pool; may be INVALID, in which case it will be auto-generated + * @param name a describing name for the pool + * @param targets a set of target IDs belonging to the new pool; may not be NULL! + * just a reference => do not free while you're using this object + * @param targets a set of buddyGroup IDs belonging to the new pool; may not be NULL! + * just a reference => do not free while you're using this object + */ + + AddStoragePoolMsg(StoragePoolId poolId, const std::string& description, + const UInt16Set* targets, const UInt16Set* buddyGroups): + BaseType(NETMSGTYPE_AddStoragePool), poolId(poolId), description(description), + targetsPtr(targets), buddyGroupsPtr(buddyGroups) { } + + /** + * For deserialization only + */ + AddStoragePoolMsg(): + BaseType(NETMSGTYPE_AddStoragePool) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->poolId + % obj->description + % serdes::backedPtr(obj->targetsPtr, obj->targets) + % serdes::backedPtr(obj->buddyGroupsPtr, obj->buddyGroups); + } + + protected: + StoragePoolId poolId; + std::string description; + + // for serialization + const UInt16Set* targetsPtr; // not owned by this object + // for deserialization + UInt16Set targets; + + // for serialization + const UInt16Set* buddyGroupsPtr; // not owned by this object + // for deserialization + UInt16Set buddyGroups; +}; + + diff --git a/common/source/common/net/message/nodes/storagepools/AddStoragePoolRespMsg.h b/common/source/common/net/message/nodes/storagepools/AddStoragePoolRespMsg.h new file mode 100644 index 0000000..e286ec1 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/AddStoragePoolRespMsg.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class AddStoragePoolRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param result return code of the add operation + * @param poolId ID of the newly inserted pool (if result is SUCCESS) + */ + AddStoragePoolRespMsg(FhgfsOpsErr result, StoragePoolId poolId) : + BaseType(NETMSGTYPE_AddStoragePoolResp), result(result), poolId(poolId) { } + + /** + * For deserialization only + */ + AddStoragePoolRespMsg() : BaseType(NETMSGTYPE_AddStoragePoolResp){ } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->result + % obj->poolId; + } + + FhgfsOpsErr getResult() const { return result; }; + StoragePoolId getPoolId() const { return poolId; }; + + private: + FhgfsOpsErr result; + StoragePoolId poolId; +}; + diff --git a/common/source/common/net/message/nodes/storagepools/GetStoragePoolsMsg.h b/common/source/common/net/message/nodes/storagepools/GetStoragePoolsMsg.h new file mode 100644 index 0000000..e6834cb --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/GetStoragePoolsMsg.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class GetStoragePoolsMsg : public SimpleMsg +{ + public: + GetStoragePoolsMsg(): + SimpleMsg(NETMSGTYPE_GetStoragePools) { } +}; + + diff --git a/common/source/common/net/message/nodes/storagepools/GetStoragePoolsRespMsg.h b/common/source/common/net/message/nodes/storagepools/GetStoragePoolsRespMsg.h new file mode 100644 index 0000000..d781d13 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/GetStoragePoolsRespMsg.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +class GetStoragePoolsRespMsg : public NetMessageSerdes +{ + public: + /** + * @param pools a vector of storage pools; just a reference + */ + GetStoragePoolsRespMsg(StoragePoolPtrVec* pools): + BaseType(NETMSGTYPE_GetStoragePoolsResp), pools(pools) { } + + /** + * For deserialization only + */ + GetStoragePoolsRespMsg(): + BaseType(NETMSGTYPE_GetStoragePoolsResp) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->pools, obj->parsed.pools); + } + + StoragePoolPtrVec& getStoragePools() const + { + return *pools; + } + + private: + StoragePoolPtrVec* pools; // not owned by this object! + + // for deserialization + struct + { + StoragePoolPtrVec pools; + } parsed; + +}; + diff --git a/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolMsg.h b/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolMsg.h new file mode 100644 index 0000000..b8793d0 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolMsg.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +class ModifyStoragePoolMsg : public NetMessageSerdes +{ + public: + // message flags + struct MsgFlags + { + static const unsigned HAS_NEWDESCRIPTION = 1; /*if the message contains a new name*/ + static const unsigned HAS_ADDTARGETS = 2; /*message contains targets to add*/ + static const unsigned HAS_RMTARGETS = 4; /*message contains targets to remove*/ + static const unsigned HAS_ADDBUDDYGROUPS = 8; /*message contains buddy groups to add*/ + static const unsigned HAS_RMBUDDYGROUPS = 16; /*message contains buddy groups to remove*/ + }; + + ModifyStoragePoolMsg(StoragePoolId poolId, const UInt16Vector* addTargets, + const UInt16Vector* rmTargets, const UInt16Vector* addBuddyGroups, + const UInt16Vector* rmBuddyGroups, const std::string* newDescription) : + BaseType(NETMSGTYPE_ModifyStoragePool), poolId(poolId), addTargets(addTargets), + rmTargets(rmTargets), addBuddyGroups(addBuddyGroups), + rmBuddyGroups(rmBuddyGroups), newDescription(newDescription) + { + if (addTargets && !addTargets->empty()) + addMsgHeaderFeatureFlag(MsgFlags::HAS_ADDTARGETS); + + if (rmTargets && !rmTargets->empty()) + addMsgHeaderFeatureFlag(MsgFlags::HAS_RMTARGETS); + + if (addBuddyGroups && !addBuddyGroups->empty()) + addMsgHeaderFeatureFlag(MsgFlags::HAS_ADDBUDDYGROUPS); + + if (rmBuddyGroups && !rmBuddyGroups->empty()) + addMsgHeaderFeatureFlag(MsgFlags::HAS_RMBUDDYGROUPS); + + if (newDescription && !newDescription->empty()) + addMsgHeaderFeatureFlag(MsgFlags::HAS_NEWDESCRIPTION); + } + + /** + * For deserialization only + */ + ModifyStoragePoolMsg(): + BaseType(NETMSGTYPE_ModifyStoragePool), addTargets(nullptr), rmTargets(nullptr), + addBuddyGroups(nullptr), rmBuddyGroups(nullptr), newDescription(nullptr) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->poolId; + + if (obj->isMsgHeaderFeatureFlagSet(MsgFlags::HAS_NEWDESCRIPTION)) + ctx % serdes::backedPtr(obj->newDescription, obj->parsed.newDescription); + + if (obj->isMsgHeaderFeatureFlagSet(MsgFlags::HAS_ADDTARGETS)) + ctx % serdes::backedPtr(obj->addTargets, obj->parsed.addTargets); + + if (obj->isMsgHeaderFeatureFlagSet(MsgFlags::HAS_RMTARGETS)) + ctx % serdes::backedPtr(obj->rmTargets, obj->parsed.rmTargets); + + if (obj->isMsgHeaderFeatureFlagSet(MsgFlags::HAS_ADDBUDDYGROUPS)) + ctx % serdes::backedPtr(obj->addBuddyGroups, obj->parsed.addBuddyGroups); + + if (obj->isMsgHeaderFeatureFlagSet(MsgFlags::HAS_RMBUDDYGROUPS)) + ctx % serdes::backedPtr(obj->rmBuddyGroups, obj->parsed.rmBuddyGroups); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return MsgFlags::HAS_NEWDESCRIPTION | MsgFlags::HAS_ADDTARGETS | MsgFlags::HAS_RMTARGETS | + MsgFlags::HAS_ADDBUDDYGROUPS | MsgFlags::HAS_RMBUDDYGROUPS; + } + + protected: + StoragePoolId poolId; + const UInt16Vector* addTargets; // not owned by this object + const UInt16Vector* rmTargets; // not owned by this object + const UInt16Vector* addBuddyGroups; // not owned by this object + const UInt16Vector* rmBuddyGroups; // not owned by this object + const std::string* newDescription; // not owned by this object + + // for deserialization + struct + { + UInt16Vector addTargets; + UInt16Vector rmTargets; + UInt16Vector addBuddyGroups; + UInt16Vector rmBuddyGroups; + std::string newDescription; + } parsed; +}; + + diff --git a/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolRespMsg.h b/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolRespMsg.h new file mode 100644 index 0000000..e461d81 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/ModifyStoragePoolRespMsg.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class ModifyStoragePoolRespMsg : public SimpleIntMsg +{ + public: + /** + * @param result result of the modify operation + */ + ModifyStoragePoolRespMsg(FhgfsOpsErr result): + SimpleIntMsg(NETMSGTYPE_ModifyStoragePoolResp, result) { } + + /** + * For deserialization only + */ + ModifyStoragePoolRespMsg() : SimpleIntMsg(NETMSGTYPE_ModifyStoragePoolResp) { } + + FhgfsOpsErr getResult() const + { + return static_cast(getValue()); + } +}; + diff --git a/common/source/common/net/message/nodes/storagepools/RefreshStoragePoolsMsg.h b/common/source/common/net/message/nodes/storagepools/RefreshStoragePoolsMsg.h new file mode 100644 index 0000000..8143831 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/RefreshStoragePoolsMsg.h @@ -0,0 +1,22 @@ +#pragma once + +#include + +class RefreshStoragePoolsMsg : public AcknowledgeableMsgSerdes +{ + public: + /** + * @param name a describing name for the pool + * @param targets a vector of target IDs belonging to the new pool; may not be NULL! + * just a reference => do not free while you're using this object + */ + RefreshStoragePoolsMsg(): + BaseType(NETMSGTYPE_RefreshStoragePools) { } + + template + static void serialize(This obj, Ctx& ctx) + { + obj->serializeAckID(ctx); + } +}; + diff --git a/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolMsg.h b/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolMsg.h new file mode 100644 index 0000000..d15cf67 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +class RemoveStoragePoolMsg : public NetMessageSerdes +{ + public: + + /** + * @param poolId ID of the pool which shall be removed (if result is SUCCESS) + */ + RemoveStoragePoolMsg(StoragePoolId poolId) : + BaseType(NETMSGTYPE_RemoveStoragePool), poolId(poolId) { } + + /** + * For deserialization only + */ + RemoveStoragePoolMsg() : BaseType(NETMSGTYPE_RemoveStoragePool){ } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->poolId; + } + + protected: + StoragePoolId poolId; +}; + diff --git a/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolRespMsg.h b/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolRespMsg.h new file mode 100644 index 0000000..e0efd39 --- /dev/null +++ b/common/source/common/net/message/nodes/storagepools/RemoveStoragePoolRespMsg.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +class RemoveStoragePoolRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param result return code of the removal + */ + RemoveStoragePoolRespMsg(FhgfsOpsErr result): + BaseType(NETMSGTYPE_RemoveStoragePoolResp), result(result) { } + + /** + * For deserialization only + */ + RemoveStoragePoolRespMsg() : BaseType(NETMSGTYPE_RemoveStoragePoolResp){ } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->result; + } + + FhgfsOpsErr getResult() const { return result; }; + + private: + FhgfsOpsErr result; +}; + diff --git a/common/source/common/net/message/session/AckNotifyMsg.h b/common/source/common/net/message/session/AckNotifyMsg.h new file mode 100644 index 0000000..3298fb2 --- /dev/null +++ b/common/source/common/net/message/session/AckNotifyMsg.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class AckNotifiyMsg : public MirroredMessageBase +{ + public: + AckNotifiyMsg(): BaseType(NETMSGTYPE_AckNotify) {} + + template + static void serialize(This obj, Ctx& ctx) + { + } + + bool supportsMirroring() const override { return true; } +}; + diff --git a/common/source/common/net/message/session/AckNotifyRespMsg.h b/common/source/common/net/message/session/AckNotifyRespMsg.h new file mode 100644 index 0000000..9f11782 --- /dev/null +++ b/common/source/common/net/message/session/AckNotifyRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class AckNotifiyRespMsg : public SimpleIntMsg +{ + public: + AckNotifiyRespMsg(FhgfsOpsErr result): + SimpleIntMsg(NETMSGTYPE_AckNotifyResp, result) + {} + + AckNotifiyRespMsg(): SimpleIntMsg(NETMSGTYPE_AckNotifyResp) {} + + FhgfsOpsErr getResult() { return static_cast(getValue()); } +}; + diff --git a/common/source/common/net/message/session/BumpFileVersionMsg.h b/common/source/common/net/message/session/BumpFileVersionMsg.h new file mode 100644 index 0000000..fe2f4e0 --- /dev/null +++ b/common/source/common/net/message/session/BumpFileVersionMsg.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +#define BUMPFILEVERSIONMSG_FLAG_PERSISTENT 1 /* change persistent file version number too */ +#define BUMPFILEVERSIONMSG_FLAG_HASEVENT 2 /* has a loggable event attached */ + +class BumpFileVersionMsg : public MirroredMessageBase +{ + public: + BumpFileVersionMsg(EntryInfo& entryInfo) : + BaseType(NETMSGTYPE_BumpFileVersion), + entryInfo(&entryInfo) + { + } + + BumpFileVersionMsg() : BaseType(NETMSGTYPE_BumpFileVersion) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->entryInfo, obj->parsed.entryInfo); + + if (obj->isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_HASEVENT)) + ctx % obj->fileEvent; + } + + virtual bool supportsMirroring() const override { return true; } + + virtual unsigned getSupportedHeaderFeatureFlagsMask() const override + { + return + BUMPFILEVERSIONMSG_FLAG_PERSISTENT | + BUMPFILEVERSIONMSG_FLAG_HASEVENT; + } + + private: + EntryInfo* entryInfo; + FileEvent fileEvent; + + struct { + EntryInfo entryInfo; + } parsed; + + public: + EntryInfo& getEntryInfo() const { return *entryInfo; } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_HASEVENT)) + return &fileEvent; + else + return nullptr; + } +}; + diff --git a/common/source/common/net/message/session/BumpFileVersionRespMsg.h b/common/source/common/net/message/session/BumpFileVersionRespMsg.h new file mode 100644 index 0000000..b507f3e --- /dev/null +++ b/common/source/common/net/message/session/BumpFileVersionRespMsg.h @@ -0,0 +1,18 @@ +#pragma once + +#include "../SimpleIntMsg.h" + +class BumpFileVersionRespMsg : public SimpleIntMsg +{ + public: + BumpFileVersionRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_BumpFileVersionResp, result) + { + } + + BumpFileVersionRespMsg() : SimpleIntMsg(NETMSGTYPE_BumpFileVersionResp) + { + } +}; + + diff --git a/common/source/common/net/message/session/FSyncLocalFileMsg.h b/common/source/common/net/message/session/FSyncLocalFileMsg.h new file mode 100644 index 0000000..a33a234 --- /dev/null +++ b/common/source/common/net/message/session/FSyncLocalFileMsg.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + + +#define FSYNCLOCALFILEMSG_FLAG_NO_SYNC 1 /* if a sync is not needed */ +#define FSYNCLOCALFILEMSG_FLAG_SESSION_CHECK 2 /* if session check should be done */ +#define FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + + +class FSyncLocalFileMsg : public NetMessageSerdes +{ + public: + + /** + * @param sessionID + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + FSyncLocalFileMsg(const NumNodeID sessionID, const char* fileHandleID, + const uint16_t targetID) + : BaseType(NETMSGTYPE_FSyncLocalFile) + { + this->sessionID = sessionID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + } + + /** + * For deserialization only! + */ + FSyncLocalFileMsg() : BaseType(NETMSGTYPE_FSyncLocalFile){} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->sessionID + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % obj->targetID; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR | FSYNCLOCALFILEMSG_FLAG_NO_SYNC | + FSYNCLOCALFILEMSG_FLAG_SESSION_CHECK | FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND; + } + + + private: + NumNodeID sessionID; + const char* fileHandleID; + unsigned fileHandleIDLen; + uint16_t targetID; + + + public: + // getters & setters + NumNodeID getSessionID() const + { + return sessionID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + uint16_t getTargetID() const + { + return targetID; + } +}; + + diff --git a/common/source/common/net/message/session/FSyncLocalFileRespMsg.h b/common/source/common/net/message/session/FSyncLocalFileRespMsg.h new file mode 100644 index 0000000..51744a2 --- /dev/null +++ b/common/source/common/net/message/session/FSyncLocalFileRespMsg.h @@ -0,0 +1,19 @@ +#pragma once + +#include "../SimpleInt64Msg.h" + +class FSyncLocalFileRespMsg : public SimpleInt64Msg +{ + public: + FSyncLocalFileRespMsg(int64_t result) : + SimpleInt64Msg(NETMSGTYPE_FSyncLocalFileResp, result) + { + } + + FSyncLocalFileRespMsg() : + SimpleInt64Msg(NETMSGTYPE_FSyncLocalFileResp) + { + } +}; + + diff --git a/common/source/common/net/message/session/GetFileVersionMsg.h b/common/source/common/net/message/session/GetFileVersionMsg.h new file mode 100644 index 0000000..27617e9 --- /dev/null +++ b/common/source/common/net/message/session/GetFileVersionMsg.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +class GetFileVersionMsg : public MirroredMessageBase +{ + public: + GetFileVersionMsg(EntryInfo& entryInfo) : + BaseType(NETMSGTYPE_GetFileVersion), + entryInfo(&entryInfo) + { + } + + GetFileVersionMsg() : BaseType(NETMSGTYPE_GetFileVersion) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->entryInfo, obj->parsed.entryInfo); + } + + bool supportsMirroring() const { return true; } + + private: + EntryInfo* entryInfo; + + struct { + EntryInfo entryInfo; + } parsed; + + public: + EntryInfo& getEntryInfo() const { return *entryInfo; } +}; + diff --git a/common/source/common/net/message/session/GetFileVersionRespMsg.h b/common/source/common/net/message/session/GetFileVersionRespMsg.h new file mode 100644 index 0000000..3746eb8 --- /dev/null +++ b/common/source/common/net/message/session/GetFileVersionRespMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class GetFileVersionRespMsg : public NetMessageSerdes +{ + public: + GetFileVersionRespMsg(FhgfsOpsErr result, uint32_t version) : + BaseType(NETMSGTYPE_GetFileVersionResp), + result(result), version(version) + { + } + + GetFileVersionRespMsg() : BaseType(NETMSGTYPE_GetFileVersionResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->version; + } + + uint64_t getVersion() const { return version; } + + private: + FhgfsOpsErr result; + uint32_t version; +}; + + diff --git a/common/source/common/net/message/session/RefreshSessionMsg.h b/common/source/common/net/message/session/RefreshSessionMsg.h new file mode 100644 index 0000000..55de2ef --- /dev/null +++ b/common/source/common/net/message/session/RefreshSessionMsg.h @@ -0,0 +1,43 @@ +#pragma once + +#include + + +class RefreshSessionMsg : public NetMessageSerdes +{ + public: + + /** + * @param sessionID just a reference, so do not free it as long as you use this object! + */ + RefreshSessionMsg(const char* sessionID) : + BaseType(NETMSGTYPE_RefreshSession) + { + this->sessionID = sessionID; + this->sessionIDLen = strlen(sessionID); + } + + RefreshSessionMsg() : BaseType(NETMSGTYPE_RefreshSession) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::rawString(obj->sessionID, obj->sessionIDLen); + } + + private: + unsigned sessionIDLen; + const char* sessionID; + + public: + + // getters & setters + const char* getSessionID() const + { + return sessionID; + } + +}; + diff --git a/common/source/common/net/message/session/RefreshSessionRespMsg.h b/common/source/common/net/message/session/RefreshSessionRespMsg.h new file mode 100644 index 0000000..1281336 --- /dev/null +++ b/common/source/common/net/message/session/RefreshSessionRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class RefreshSessionRespMsg : public SimpleIntMsg +{ + public: + RefreshSessionRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RefreshSessionResp, result) + { + } + + RefreshSessionRespMsg() : SimpleIntMsg(NETMSGTYPE_RefreshSessionResp) + { + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockAppendMsg.h b/common/source/common/net/message/session/locking/FLockAppendMsg.h new file mode 100644 index 0000000..e733a97 --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockAppendMsg.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +class FLockAppendMsg : public MirroredMessageBase +{ + public: + + /** + * @param clientID + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param lockTypeFlags ENTRYLOCKTYPE_... flags + * @param lockAckID just a reference, so do not free it as long as you use this object! + */ + FLockAppendMsg(const NumNodeID clientNumID, const char* fileHandleID, const int64_t clientFD, + const int ownerPID, const int lockTypeFlags, const char* lockAckID) : + BaseType(NETMSGTYPE_FLockAppend) + { + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->clientFD = clientFD; + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); + } + + /** + * For deserialization only! + */ + FLockAppendMsg() : BaseType(NETMSGTYPE_FLockAppend) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->clientFD + % obj->ownerPID + % obj->lockTypeFlags + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % serdes::rawString(obj->lockAckID, obj->lockAckIDLen, 4); + } + + bool supportsMirroring() const { return true; } + + private: + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + int64_t clientFD; /* some client-wide unique file handle + * corresponds to 'struct file_lock* fileLock->fl_file' on the kernel + * client side. + */ + int32_t ownerPID; // pid on client (just informative, because shared on fork() ) + int32_t lockTypeFlags; // ENTRYLOCKTYPE_... + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object + + // for deserialization + EntryInfo entryInfo; + + + public: + // getters & setters + NumNodeID getClientNumID() const + { + return clientNumID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + int64_t getClientFD() const + { + return clientFD; + } + + int getOwnerPID() const + { + return ownerPID; + } + + int getLockTypeFlags() const + { + return lockTypeFlags; + } + + const char* getLockAckID() const + { + return lockAckID; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockAppendRespMsg.h b/common/source/common/net/message/session/locking/FLockAppendRespMsg.h new file mode 100644 index 0000000..0bf3e0e --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockAppendRespMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + + +class FLockAppendRespMsg : public SimpleIntMsg +{ + public: + /** + * @result FhgfsOpsErr_WOULDBLOCK if the lock could not be immediately granted + */ + FLockAppendRespMsg(FhgfsOpsErr result) : SimpleIntMsg(NETMSGTYPE_FLockAppendResp, result) + { + } + + FLockAppendRespMsg() : SimpleIntMsg(NETMSGTYPE_FLockAppendResp) + { + } + + private: + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockEntryMsg.h b/common/source/common/net/message/session/locking/FLockEntryMsg.h new file mode 100644 index 0000000..d327f7f --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockEntryMsg.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include + + +class FLockEntryMsg : public MirroredMessageBase +{ + public: + + /** + * @param clientNumID + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param lockTypeFlags ENTRYLOCKTYPE_... flags + * @param lockAckID just a reference, so do not free it as long as you use this object! + */ + FLockEntryMsg(const NumNodeID clientNumID, const char* fileHandleID, const int64_t clientFD, + const int ownerPID, const int lockTypeFlags, const char* lockAckID) : + BaseType(NETMSGTYPE_FLockEntry) + { + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->clientFD = clientFD; + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); + } + + /** + * For deserialization only! + */ + FLockEntryMsg() : BaseType(NETMSGTYPE_FLockEntry) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->clientFD + % obj->ownerPID + % obj->lockTypeFlags + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % serdes::rawString(obj->lockAckID, obj->lockAckIDLen, 4); + } + + bool supportsMirroring() const { return true; } + + private: + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + int64_t clientFD; /* some client-wide unique file handle + * corresponds to 'struct file_lock* fileLock->fl_file' on the kernel + * client side. + */ + int32_t ownerPID; // pid on client (just informative, because shared on fork() ) + int32_t lockTypeFlags; // ENTRYLOCKTYPE_... + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object + + // for deserialization + EntryInfo entryInfo; + + + public: + // getters & setters + NumNodeID getClientNumID() const + { + return clientNumID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + int64_t getClientFD() const + { + return clientFD; + } + + int getOwnerPID() const + { + return ownerPID; + } + + int getLockTypeFlags() const + { + return lockTypeFlags; + } + + const char* getLockAckID() const + { + return lockAckID; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockEntryRespMsg.h b/common/source/common/net/message/session/locking/FLockEntryRespMsg.h new file mode 100644 index 0000000..01a90a4 --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockEntryRespMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + + +class FLockEntryRespMsg : public SimpleIntMsg +{ + public: + /** + * @result FhgfsOpsErr_WOULDBLOCK if the lock could not be immediately granted + */ + FLockEntryRespMsg(FhgfsOpsErr result) : SimpleIntMsg(NETMSGTYPE_FLockEntryResp, result) + { + } + + FLockEntryRespMsg() : SimpleIntMsg(NETMSGTYPE_FLockEntryResp) + { + } + + private: + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockRangeMsg.h b/common/source/common/net/message/session/locking/FLockRangeMsg.h new file mode 100644 index 0000000..ec75245 --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockRangeMsg.h @@ -0,0 +1,117 @@ +#pragma once + +#include +#include +#include + + +class FLockRangeMsg : public MirroredMessageBase +{ + public: + + /** + * @param clientNumID + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param lockTypeFlags ENTRYLOCKTYPE_... flags + * @param lockAckID just a reference, so do not free it as long as you use this object! + */ + FLockRangeMsg(const NumNodeID clientNumID, const char* fileHandleID, const int ownerPID, + const int lockTypeFlags, const int64_t start, const int64_t end, const char* lockAckID) : + BaseType(NETMSGTYPE_FLockRange) + { + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->ownerPID = ownerPID; + this->lockTypeFlags = lockTypeFlags; + + this->start = start; + this->end = end; + + this->lockAckID = lockAckID; + this->lockAckIDLen = strlen(lockAckID); + } + + /** + * For deserialization only! + */ + FLockRangeMsg() : BaseType(NETMSGTYPE_FLockRange) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->start + % obj->end + % obj->ownerPID + % obj->lockTypeFlags + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % serdes::rawString(obj->lockAckID, obj->lockAckIDLen, 4); + } + + bool supportsMirroring() const { return true; } + + private: + NumNodeID clientNumID; + const char* fileHandleID; + unsigned fileHandleIDLen; + int32_t ownerPID; // pid on client (just informative, because shared on fork() ) + int32_t lockTypeFlags; // ENTRYLOCKTYPE_... + uint64_t start; + uint64_t end; + const char* lockAckID; // ID for ack message when log is granted + unsigned lockAckIDLen; + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + public: + // getters & setters + NumNodeID getClientNumID() const + { + return clientNumID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + int getOwnerPID() const + { + return ownerPID; + } + + int getLockTypeFlags() const + { + return lockTypeFlags; + } + + uint64_t getStart() const + { + return start; + } + + uint64_t getEnd() const + { + return end; + } + + const char* getLockAckID() const + { + return lockAckID; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/session/locking/FLockRangeRespMsg.h b/common/source/common/net/message/session/locking/FLockRangeRespMsg.h new file mode 100644 index 0000000..4115785 --- /dev/null +++ b/common/source/common/net/message/session/locking/FLockRangeRespMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + + +class FLockRangeRespMsg : public SimpleIntMsg +{ + public: + /** + * @result FhgfsOpsErr_WOULDBLOCK if the lock could not be immediately granted + */ + FLockRangeRespMsg(FhgfsOpsErr result) : SimpleIntMsg(NETMSGTYPE_FLockRangeResp, result) + { + } + + FLockRangeRespMsg() : SimpleIntMsg(NETMSGTYPE_FLockRangeResp) + { + } + + private: + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/session/locking/LockGrantedMsg.h b/common/source/common/net/message/session/locking/LockGrantedMsg.h new file mode 100644 index 0000000..d857abd --- /dev/null +++ b/common/source/common/net/message/session/locking/LockGrantedMsg.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + + +class LockGrantedMsg : public AcknowledgeableMsgSerdes +{ + public: + + /** + * @param lockAckID just a reference, so do not free it as long as you use this object! + * @param ackID reply ack; just a reference, so do not free it as long as you use this object! + * @param granterNodeID nodeID of the sender of this msg (=> receiver of the ack); just a + * reference, so do not free it as long as you use this object! + */ + LockGrantedMsg(const std::string& lockAckID, const std::string& ackID, + NumNodeID granterNodeID) + : BaseType(NETMSGTYPE_LockGranted, ackID.c_str() ) + { + this->lockAckID = lockAckID.c_str(); + this->lockAckIDLen = lockAckID.length(); + + this->granterNodeID = granterNodeID; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::rawString(obj->lockAckID, obj->lockAckIDLen, 4); + obj->serializeAckID(ctx, 4); + ctx % obj->granterNodeID; + } + + + protected: + /** + * Constructor for deserialization only + */ + LockGrantedMsg() : BaseType(NETMSGTYPE_LockGranted) + { + } + + private: + unsigned lockAckIDLen; + const char* lockAckID; + NumNodeID granterNodeID; + + public: + + // getters & setters + const char* getLockAckID() const + { + return lockAckID; + } + + NumNodeID getGranterNodeID() + { + return granterNodeID; + } + +}; + + diff --git a/common/source/common/net/message/session/opening/CloseChunkFileMsg.h b/common/source/common/net/message/session/opening/CloseChunkFileMsg.h new file mode 100644 index 0000000..a28073c --- /dev/null +++ b/common/source/common/net/message/session/opening/CloseChunkFileMsg.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include + + +#define CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS 1 /* skip retrieval of current dyn attribs */ +#define CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + + +class CloseChunkFileMsg : public NetMessageSerdes +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + CloseChunkFileMsg(const NumNodeID sessionID, const std::string& fileHandleID, + const uint16_t targetID, const PathInfo* pathInfo) : + BaseType(NETMSGTYPE_CloseChunkFile) + { + this->sessionID = sessionID; + + this->fileHandleID = fileHandleID.c_str(); + this->fileHandleIDLen = fileHandleID.length(); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfo; + } + + /** + * For deserialization only! + */ + CloseChunkFileMsg() : BaseType(NETMSGTYPE_CloseChunkFile) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->sessionID + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo); + + ctx % obj->targetID; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS | + CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR | CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND; + } + + + private: + NumNodeID sessionID; + unsigned fileHandleIDLen; + const char* fileHandleID; + uint16_t targetID; + + // for serialization + const PathInfo *pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + public: + // getters & setters + + NumNodeID getSessionID() const + { + return sessionID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + const PathInfo* getPathInfo() const + { + return &this->pathInfo; + } +}; + + diff --git a/common/source/common/net/message/session/opening/CloseChunkFileRespMsg.h b/common/source/common/net/message/session/opening/CloseChunkFileRespMsg.h new file mode 100644 index 0000000..90a1257 --- /dev/null +++ b/common/source/common/net/message/session/opening/CloseChunkFileRespMsg.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + + +class CloseChunkFileRespMsg : public NetMessageSerdes +{ + public: + CloseChunkFileRespMsg(FhgfsOpsErr result, int64_t filesize, int64_t allocedBlocks, + int64_t modificationTimeSecs, int64_t lastAccessTimeSecs, uint64_t storageVersion) : + BaseType(NETMSGTYPE_CloseChunkFileResp) + { + this->result = (int)result; + this->filesize = filesize; + this->allocedBlocks = allocedBlocks; + this->modificationTimeSecs = modificationTimeSecs; + this->lastAccessTimeSecs = lastAccessTimeSecs; + this->storageVersion = storageVersion; + } + + /** + * For deserialization only! + */ + CloseChunkFileRespMsg() : BaseType(NETMSGTYPE_CloseChunkFileResp) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->filesize + % obj->allocedBlocks + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs + % obj->storageVersion + % obj->result; + } + + private: + int32_t result; // FhgfsOpsErr_... + int64_t filesize; + int64_t allocedBlocks; // number of allocated 512byte blocks (relevant for sparse files) + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; + + + public: + + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)result; + } + + int64_t getFileSize() + { + return filesize; + } + + int64_t getAllocedBlocks() + { + return allocedBlocks; + } + + int64_t getModificationTimeSecs() + { + return modificationTimeSecs; + } + + int64_t getLastAccessTimeSecs() + { + return lastAccessTimeSecs; + } + + uint64_t getStorageVersion() + { + return storageVersion; + } + +}; + diff --git a/common/source/common/net/message/session/opening/CloseFileMsg.h b/common/source/common/net/message/session/opening/CloseFileMsg.h new file mode 100644 index 0000000..3c0da7c --- /dev/null +++ b/common/source/common/net/message/session/opening/CloseFileMsg.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include +#include + + +#define CLOSEFILEMSG_FLAG_EARLYRESPONSE 1 /* send response before chunk files close */ +#define CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS 2 /* cancel append locks of this file handle */ +#define CLOSEFILEMSG_FLAG_DYNATTRIBS 4 // has dynattribs for secondary +#define CLOSEFILEMSG_FLAG_HAS_EVENT 8 /* contains file event logging information */ + +class CloseFileMsg : public MirroredMessageBase +{ + public: + /** + * @param clientNumID + * @param fileHandleID + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + CloseFileMsg(const NumNodeID clientNumID, const std::string& fileHandleID, + EntryInfo* entryInfo, const int maxUsedNodeIndex) : + BaseType(NETMSGTYPE_CloseFile) + { + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + + this->entryInfoPtr = entryInfo; + + this->maxUsedNodeIndex = maxUsedNodeIndex; + } + + CloseFileMsg() : BaseType(NETMSGTYPE_CloseFile) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % serdes::stringAlign4(obj->fileHandleID) + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->maxUsedNodeIndex; + + if (obj->isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_DYNATTRIBS)) + ctx % obj->dynAttribs; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->inodeTimestamps; + + if (obj->isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return CLOSEFILEMSG_FLAG_EARLYRESPONSE | CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS | + CLOSEFILEMSG_FLAG_DYNATTRIBS | CLOSEFILEMSG_FLAG_HAS_EVENT; + } + + bool supportsMirroring() const { return true; } + + private: + NumNodeID clientNumID; + std::string fileHandleID; + int32_t maxUsedNodeIndex; + FileEvent fileEvent; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object + + // for deserialization + EntryInfo entryInfo; + + protected: + DynamicFileAttribsVec dynAttribs; + MirroredTimestamps inodeTimestamps; + + public: + NumNodeID getClientNumID() const + { + return clientNumID; + } + + std::string getFileHandleID() const + { + return fileHandleID; + } + + int getMaxUsedNodeIndex() const + { + return maxUsedNodeIndex; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } +}; + diff --git a/common/source/common/net/message/session/opening/CloseFileRespMsg.h b/common/source/common/net/message/session/opening/CloseFileRespMsg.h new file mode 100644 index 0000000..9e949e7 --- /dev/null +++ b/common/source/common/net/message/session/opening/CloseFileRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class CloseFileRespMsg : public SimpleIntMsg +{ + public: + CloseFileRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_CloseFileResp, result) + { + } + + CloseFileRespMsg() : SimpleIntMsg(NETMSGTYPE_CloseFileResp) + { + } +}; + diff --git a/common/source/common/net/message/session/opening/OpenFileMsg.h b/common/source/common/net/message/session/opening/OpenFileMsg.h new file mode 100644 index 0000000..2bf4af6 --- /dev/null +++ b/common/source/common/net/message/session/opening/OpenFileMsg.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define OPENFILEMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define OPENFILEMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ +#define OPENFILEMSG_FLAG_BYPASS_ACCESS_CHECK 4 /* bypass file access checks on metadata server */ + +class OpenFileMsg : public MirroredMessageBase +{ + public: + + /** + * @param enrtyInfo just a reference, so do not free it as long as you use this object! + * @param accessFlags OPENFILE_ACCESS_... flags + */ + OpenFileMsg(const NumNodeID clientNumID, const EntryInfo* entryInfo, + const unsigned accessFlags) + : BaseType(NETMSGTYPE_OpenFile), + clientNumID(clientNumID), + accessFlags(accessFlags), + entryInfoPtr(entryInfo) + { } + + /** + * For deserialization only! + */ + OpenFileMsg() : BaseType(NETMSGTYPE_OpenFile) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->accessFlags; + + if(obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ) + ctx + % obj->sessionFileID + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % obj->fileTimestamps; + + ctx % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + + if (obj->isMsgHeaderFeatureFlagSet(OPENFILEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + private: + NumNodeID clientNumID; + + uint32_t accessFlags; + + uint32_t sessionFileID; // only needed for secondary buddy; will be set by primary + const char* fileHandleID; // only needed for secondary buddy; will be set by primary + unsigned fileHandleIDLen; + + FileEvent fileEvent; + + // serialization + const EntryInfo* entryInfoPtr; + + // deserialization + EntryInfo entryInfo; + + protected: + MirroredTimestamps fileTimestamps; + + public: + + // getters & setters + NumNodeID getClientNumID() const + { + return this->clientNumID; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + unsigned getAccessFlags() const + { + return this->accessFlags; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const override + { + return OPENFILEMSG_FLAG_USE_QUOTA + | OPENFILEMSG_FLAG_HAS_EVENT | OPENFILEMSG_FLAG_BYPASS_ACCESS_CHECK; + } + + bool supportsMirroring() const override { return true; } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + void setFileHandleID(const char* fileHandleID) + { + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + } + + unsigned getSessionFileID() const + { + return sessionFileID; + } + + void setSessionFileID(const unsigned sessionFileID) + { + this->sessionFileID = sessionFileID; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(OPENFILEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } +}; + diff --git a/common/source/common/net/message/session/opening/OpenFileRespMsg.h b/common/source/common/net/message/session/opening/OpenFileRespMsg.h new file mode 100644 index 0000000..cdbe0eb --- /dev/null +++ b/common/source/common/net/message/session/opening/OpenFileRespMsg.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include + + +class OpenFileRespMsg : public NetMessageSerdes +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + OpenFileRespMsg(int result, const char* fileHandleID, StripePattern* pattern, + PathInfo* pathInfo, uint32_t fileVersion = 0) : + BaseType(NETMSGTYPE_OpenFileResp), + fileVersion(fileVersion) + { + this->result = result; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->pattern = pattern; + + this->pathInfo.set(pathInfo); + } + + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + OpenFileRespMsg(int result, std::string& fileHandleID, StripePattern* pattern, + PathInfo* pathInfo, uint32_t fileVersion) : + BaseType(NETMSGTYPE_OpenFileResp), + fileVersion(fileVersion) + { + this->result = result; + + this->fileHandleID = fileHandleID.c_str(); + this->fileHandleIDLen = fileHandleID.length(); + + this->pattern = pattern; + + this->pathInfo.set(pathInfo); + } + + /** + * For deserialization only! + */ + OpenFileRespMsg() : BaseType(NETMSGTYPE_OpenFileResp) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % obj->pathInfo + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % obj->fileVersion; + } + + private: + int32_t result; + unsigned fileHandleIDLen; + const char* fileHandleID; + PathInfo pathInfo; + uint32_t fileVersion; + + // for serialization + StripePattern* pattern; // not owned by this object! + + // for deserialization + struct { + std::unique_ptr pattern; + } parsed; + + public: + StripePattern& getPattern() + { + return *pattern; + } + + int getResult() const + { + return result; + } + + const char* getFileHandleID() + { + return fileHandleID; + } + + PathInfo* getPathInfo() + { + return &this->pathInfo; + } +}; + diff --git a/common/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h b/common/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h new file mode 100644 index 0000000..97ad733 --- /dev/null +++ b/common/source/common/net/message/session/rw/ReadLocalFileRDMAMsg.h @@ -0,0 +1,62 @@ +#pragma once + +#ifdef BEEGFS_NVFS +#include +#include +#include +#include +#include +#include + + +class ReadLocalFileRDMAMsg : public ReadLocalFileV2MsgBase, public NetMessageSerdes +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + ReadLocalFileRDMAMsg(NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, + PathInfo* pathInfoPtr, unsigned accessFlags, int64_t offset, int64_t count) : + ReadLocalFileV2MsgBase(clientNumID, fileHandleID, targetID, pathInfoPtr, accessFlags, + offset, count), + BaseType(NETMSGTYPE_ReadLocalFileRDMA) {} + + /** + * For deserialization only! + */ + ReadLocalFileRDMAMsg() : + ReadLocalFileV2MsgBase(), + BaseType(NETMSGTYPE_ReadLocalFileRDMA) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ReadLocalFileV2MsgBase::serialize(obj, ctx); + + ctx + % obj->rdmaInfo; + } + + + private: + // for info + RdmaInfo rdmaInfo; + + public: + inline RdmaInfo* getRdmaInfo() + { + return &rdmaInfo; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return ReadLocalFileV2MsgBase::getSupportedHeaderFeatureFlagsMask(); + } + + bool isMsgValid() const + { + return rdmaInfo.isValid(); + } +}; +#endif /* BEEGFS_NVFS */ + diff --git a/common/source/common/net/message/session/rw/ReadLocalFileV2Msg.h b/common/source/common/net/message/session/rw/ReadLocalFileV2Msg.h new file mode 100644 index 0000000..05a8d65 --- /dev/null +++ b/common/source/common/net/message/session/rw/ReadLocalFileV2Msg.h @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include + + +#define READLOCALFILEMSG_FLAG_SESSION_CHECK 1 /* if session check infos should be done */ +#define READLOCALFILEMSG_FLAG_DISABLE_IO 2 /* disable read syscall for net bench */ +#define READLOCALFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + + +class ReadLocalFileV2MsgBase +{ + public: + + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + ReadLocalFileV2MsgBase(NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, + PathInfo* pathInfoPtr, unsigned accessFlags, int64_t offset, int64_t count) + { + this->clientNumID = clientNumID; + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfoPtr; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; + } + + /** + * For deserialization only! + */ + ReadLocalFileV2MsgBase() {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->offset + % obj->count + % obj->accessFlags + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % obj->clientNumID + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID; + } + + protected: + int64_t offset; + int64_t count; + uint32_t accessFlags; + const char* fileHandleID; + unsigned fileHandleIDLen; + NumNodeID clientNumID; + uint16_t targetID; + + // for serialization + PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + public: + // getters & setters + NumNodeID getClientNumID() const + { + return clientNumID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + unsigned getAccessFlags() const + { + return accessFlags; + } + + int64_t getOffset() const + { + return offset; + } + + int64_t getCount() const + { + return count; + } + + PathInfo* getPathInfo() + { + return &this->pathInfo; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return READLOCALFILEMSG_FLAG_SESSION_CHECK | READLOCALFILEMSG_FLAG_DISABLE_IO | + READLOCALFILEMSG_FLAG_BUDDYMIRROR | READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND; + } + + bool isMsgValid() const + { + return true; + } +}; + +class ReadLocalFileV2Msg : public ReadLocalFileV2MsgBase, public NetMessageSerdes +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + */ + ReadLocalFileV2Msg(NumNodeID clientNumID, const char* fileHandleID, uint16_t targetID, + PathInfo* pathInfoPtr, unsigned accessFlags, int64_t offset, int64_t count) : + ReadLocalFileV2MsgBase(clientNumID, fileHandleID, targetID, pathInfoPtr, accessFlags, + offset, count), + BaseType(NETMSGTYPE_ReadLocalFileV2) {} + + /** + * For deserialization only! + */ + ReadLocalFileV2Msg() : + ReadLocalFileV2MsgBase(), + BaseType(NETMSGTYPE_ReadLocalFileV2) {} + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return ReadLocalFileV2MsgBase::getSupportedHeaderFeatureFlagsMask(); + } +}; + diff --git a/common/source/common/net/message/session/rw/WriteLocalFileMsg.h b/common/source/common/net/message/session/rw/WriteLocalFileMsg.h new file mode 100644 index 0000000..9104dd9 --- /dev/null +++ b/common/source/common/net/message/session/rw/WriteLocalFileMsg.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include + + +#define WRITELOCALFILEMSG_FLAG_SESSION_CHECK 1 /* if session check should be done */ +#define WRITELOCALFILEMSG_FLAG_USE_QUOTA 2 /* if msg contains quota info */ +#define WRITELOCALFILEMSG_FLAG_DISABLE_IO 4 /* disable write syscall for net bench mode */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR 8 /* given targetID is a buddymirrorgroup ID */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 16 /* secondary of group, otherwise primary */ +#define WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD 32 /* forward msg to secondary */ + +class WriteLocalFileMsgBase +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + WriteLocalFileMsgBase(const NumNodeID clientNumID, const char* fileHandleID, + const uint16_t targetID, const PathInfo* pathInfo, const unsigned accessFlags, + const int64_t offset, const int64_t count) + { + this->clientNumID = clientNumID; + + this->fileHandleID = fileHandleID; + this->fileHandleIDLen = strlen(fileHandleID); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfo; + + this->accessFlags = accessFlags; + + this->offset = offset; + this->count = count; + } + + WriteLocalFileMsgBase() {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->offset + % obj->count + % obj->accessFlags; + + if(obj->isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_USE_QUOTA)) + ctx + % obj->userID + % obj->groupID; + + ctx + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % obj->clientNumID + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID; + } + + protected: + int64_t offset; + int64_t count; + uint32_t accessFlags; + const char* fileHandleID; + unsigned fileHandleIDLen; + NumNodeID clientNumID; + uint16_t targetID; + + uint32_t userID; + uint32_t groupID; + + // for serialization + const PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + public: + // getters & setters + + NumNodeID getClientNumID() const + { + return clientNumID; + } + + const char* getFileHandleID() const + { + return fileHandleID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + unsigned getAccessFlags() const + { + return accessFlags; + } + + int64_t getOffset() const + { + return offset; + } + + int64_t getCount() const + { + return count; + } + + PathInfo* getPathInfo () + { + return &this->pathInfo; + } + + unsigned getUserID() const + { + return userID; + } + + unsigned getGroupID() const + { + return groupID; + } + + void setUserdataForQuota(unsigned userID, unsigned groupID) + { + this->userID = userID; + this->groupID = groupID; + } + + // inliners + + void sendData(const char* data, Socket* sock) + { + sock->send(data, count, 0); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return WRITELOCALFILEMSG_FLAG_SESSION_CHECK | WRITELOCALFILEMSG_FLAG_USE_QUOTA | + WRITELOCALFILEMSG_FLAG_DISABLE_IO | WRITELOCALFILEMSG_FLAG_BUDDYMIRROR | + WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND | WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD; + } + + bool isMsgValid() const + { + return true; + } +}; + +class WriteLocalFileMsg : public WriteLocalFileMsgBase, public NetMessageSerdes +{ + public: + + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + WriteLocalFileMsg(const NumNodeID clientNumID, const char* fileHandleID, + const uint16_t targetID, const PathInfo* pathInfo, const unsigned accessFlags, + const int64_t offset, const int64_t count) : + WriteLocalFileMsgBase(clientNumID, fileHandleID, targetID, pathInfo, accessFlags, + offset, count), + BaseType(NETMSGTYPE_WriteLocalFile) {} + + /** + * For deserialization only! + */ + WriteLocalFileMsg() : + WriteLocalFileMsgBase(), + BaseType(NETMSGTYPE_WriteLocalFile) {} + + void setUserdataForQuota(unsigned userID, unsigned groupID) + { + addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_USE_QUOTA); + WriteLocalFileMsgBase::setUserdataForQuota(userID, groupID); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return WriteLocalFileMsgBase::getSupportedHeaderFeatureFlagsMask(); + } + +}; + diff --git a/common/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h b/common/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h new file mode 100644 index 0000000..8a3f612 --- /dev/null +++ b/common/source/common/net/message/session/rw/WriteLocalFileRDMAMsg.h @@ -0,0 +1,66 @@ +#pragma once + +#ifdef BEEGFS_NVFS +#include +#include +#include +#include +#include +#include + +class WriteLocalFileRDMAMsg : public WriteLocalFileMsgBase, public NetMessageSerdes +{ + public: + /** + * @param fileHandleID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + WriteLocalFileRDMAMsg(const NumNodeID clientNumID, const char* fileHandleID, + const uint16_t targetID, const PathInfo* pathInfo, const unsigned accessFlags, + const int64_t offset, const int64_t count) : + WriteLocalFileMsgBase(clientNumID, fileHandleID, targetID, pathInfo, accessFlags, offset, count), + BaseType(NETMSGTYPE_WriteLocalFileRDMA) {} + + /** + * For deserialization only! + */ + WriteLocalFileRDMAMsg() : + WriteLocalFileMsgBase(), + BaseType(NETMSGTYPE_WriteLocalFileRDMA) {} + + template + static void serialize(This obj, Ctx& ctx) + { + WriteLocalFileMsgBase::serialize(obj, ctx); + + ctx + % obj->rdmaInfo; + } + + private: + RdmaInfo rdmaInfo; + + public: + inline RdmaInfo* getRdmaInfo() + { + return &rdmaInfo; + } + + void setUserdataForQuota(unsigned userID, unsigned groupID) + { + addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_USE_QUOTA); + WriteLocalFileMsgBase::setUserdataForQuota(userID, groupID); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return WriteLocalFileMsgBase::getSupportedHeaderFeatureFlagsMask(); + } + + bool isMsgValid() const + { + return rdmaInfo.isValid(); + } +}; +#endif /* BEEGFS_NVFS */ + diff --git a/common/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h b/common/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h new file mode 100644 index 0000000..7dd558e --- /dev/null +++ b/common/source/common/net/message/session/rw/WriteLocalFileRDMARespMsg.h @@ -0,0 +1,20 @@ +#pragma once + +#ifdef BEEGFS_NVFS +#include + +class WriteLocalFileRDMARespMsg : public SimpleInt64Msg +{ + public: + WriteLocalFileRDMARespMsg(int64_t result) : + SimpleInt64Msg(NETMSGTYPE_WriteLocalFileRDMAResp, result) + { + } + + WriteLocalFileRDMARespMsg() : + SimpleInt64Msg(NETMSGTYPE_WriteLocalFileRDMAResp) + { + } +}; +#endif /* BEEGFS_NVFS */ + diff --git a/common/source/common/net/message/session/rw/WriteLocalFileRespMsg.h b/common/source/common/net/message/session/rw/WriteLocalFileRespMsg.h new file mode 100644 index 0000000..da424a5 --- /dev/null +++ b/common/source/common/net/message/session/rw/WriteLocalFileRespMsg.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class WriteLocalFileRespMsg : public SimpleInt64Msg +{ + public: + WriteLocalFileRespMsg(int64_t result) : + SimpleInt64Msg(NETMSGTYPE_WriteLocalFileResp, result) + { + } + + WriteLocalFileRespMsg() : + SimpleInt64Msg(NETMSGTYPE_WriteLocalFileResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/GetHighResStatsMsg.h b/common/source/common/net/message/storage/GetHighResStatsMsg.h new file mode 100644 index 0000000..1733f26 --- /dev/null +++ b/common/source/common/net/message/storage/GetHighResStatsMsg.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + + +class GetHighResStatsMsg : public SimpleInt64Msg +{ + public: + /** + * @param lastStatsTimeMS only the elements with a time value greater than this will be + * returned in the response + */ + GetHighResStatsMsg(int64_t lastStatsTimeMS) : SimpleInt64Msg(NETMSGTYPE_GetHighResStats, + lastStatsTimeMS) + { + } + + GetHighResStatsMsg() : SimpleInt64Msg(NETMSGTYPE_GetHighResStats) + { + } + +}; + + diff --git a/common/source/common/net/message/storage/GetHighResStatsRespMsg.h b/common/source/common/net/message/storage/GetHighResStatsRespMsg.h new file mode 100644 index 0000000..fdc147e --- /dev/null +++ b/common/source/common/net/message/storage/GetHighResStatsRespMsg.h @@ -0,0 +1,49 @@ +#pragma once + + +#include +#include +#include + + +class GetHighResStatsRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param statsList just a reference, so do not free it as long as you use this object! + */ + GetHighResStatsRespMsg(HighResStatsList* statsList) : + BaseType(NETMSGTYPE_GetHighResStatsResp) + { + this->statsList = statsList; + } + + GetHighResStatsRespMsg() : BaseType(NETMSGTYPE_GetHighResStatsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->statsList, obj->parsed.statsList); + } + + private: + // for serialization + HighResStatsList* statsList; // not owned by this object! + + // for deserialization + struct { + HighResStatsList statsList; + } parsed; + + + public: + HighResStatsList& getStatsList() + { + return *statsList; + } +}; + diff --git a/common/source/common/net/message/storage/SetStorageTargetInfoMsg.h b/common/source/common/net/message/storage/SetStorageTargetInfoMsg.h new file mode 100644 index 0000000..431c9c2 --- /dev/null +++ b/common/source/common/net/message/storage/SetStorageTargetInfoMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +class SetStorageTargetInfoMsg: public NetMessageSerdes +{ + public: + SetStorageTargetInfoMsg(NodeType nodeType, StorageTargetInfoList *targetInfoList) : + BaseType(NETMSGTYPE_SetStorageTargetInfo) + { + this->nodeType = nodeType; + this->targetInfoList = targetInfoList; + } + + SetStorageTargetInfoMsg() : BaseType(NETMSGTYPE_SetStorageTargetInfo) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->nodeType + % serdes::backedPtr(obj->targetInfoList, obj->parsed.targetInfoList); + } + + private: + StorageTargetInfoList* targetInfoList; // not owned by this object! + int32_t nodeType; + + // for deserializaztion + struct { + StorageTargetInfoList targetInfoList; + } parsed; + + public: + NodeType getNodeType() + { + return (NodeType)nodeType; + } + + const StorageTargetInfoList& getStorageTargetInfos() const + { + return *targetInfoList; + } +}; + diff --git a/common/source/common/net/message/storage/SetStorageTargetInfoRespMsg.h b/common/source/common/net/message/storage/SetStorageTargetInfoRespMsg.h new file mode 100644 index 0000000..8b34097 --- /dev/null +++ b/common/source/common/net/message/storage/SetStorageTargetInfoRespMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include + +class SetStorageTargetInfoRespMsg : public SimpleIntMsg +{ + public: + SetStorageTargetInfoRespMsg(int result) + : SimpleIntMsg(NETMSGTYPE_SetStorageTargetInfoResp, result) + { + } + + SetStorageTargetInfoRespMsg() : SimpleIntMsg(NETMSGTYPE_SetStorageTargetInfoResp) + { + } + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/storage/StatStoragePathMsg.h b/common/source/common/net/message/storage/StatStoragePathMsg.h new file mode 100644 index 0000000..e2e0847 --- /dev/null +++ b/common/source/common/net/message/storage/StatStoragePathMsg.h @@ -0,0 +1,39 @@ +#pragma once + +#include + + +class StatStoragePathMsg : public SimpleUInt16Msg +{ + public: + /** + * @param targetID only used for storage servers, value ignored for other nodes (but may not + * be NULL!) + */ + StatStoragePathMsg(uint16_t targetID) : + SimpleUInt16Msg(NETMSGTYPE_StatStoragePath, targetID) + { + } + + + protected: + /** + * For deserialization only! + */ + StatStoragePathMsg() : SimpleUInt16Msg(NETMSGTYPE_StatStoragePath) + { + } + + + private: + + + public: + // getters & setters + uint16_t getTargetID() const + { + return getValue(); + } + +}; + diff --git a/common/source/common/net/message/storage/StatStoragePathRespMsg.h b/common/source/common/net/message/storage/StatStoragePathRespMsg.h new file mode 100644 index 0000000..701ca4a --- /dev/null +++ b/common/source/common/net/message/storage/StatStoragePathRespMsg.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include + +class StatStoragePathRespMsg : public NetMessageSerdes +{ + public: + /* + * @param result FhgfsOpsErr + */ + StatStoragePathRespMsg(int result, int64_t sizeTotal, int64_t sizeFree, + int64_t inodesTotal, int64_t inodesFree) : + BaseType(NETMSGTYPE_StatStoragePathResp) + { + this->result = result; + this->sizeTotal = sizeTotal; + this->sizeFree = sizeFree; + this->inodesTotal = inodesTotal; + this->inodesFree = inodesFree; + } + + StatStoragePathRespMsg() : BaseType(NETMSGTYPE_StatStoragePathResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->sizeTotal + % obj->sizeFree + % obj->inodesTotal + % obj->inodesFree; + } + + private: + int32_t result; + int64_t sizeTotal; + int64_t sizeFree; + int64_t inodesTotal; + int64_t inodesFree; + + public: + // getters & setters + int getResult() + { + return result; + } + + int64_t getSizeTotal() + { + return sizeTotal; + } + + int64_t getSizeFree() + { + return sizeFree; + } + + int64_t getInodesTotal() + { + return inodesTotal; + } + + int64_t getInodesFree() + { + return inodesFree; + } +}; + diff --git a/common/source/common/net/message/storage/TruncFileMsg.h b/common/source/common/net/message/storage/TruncFileMsg.h new file mode 100644 index 0000000..dfdcf32 --- /dev/null +++ b/common/source/common/net/message/storage/TruncFileMsg.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include +#include + + +#define TRUNCFILEMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define TRUNCFILEMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ + + +class TruncFileMsg : public MirroredMessageBase +{ + public: + + /** + * @param entryID just a reference, so do not free it as long as you use this object! + */ + TruncFileMsg(int64_t filesize, EntryInfo* entryInfo) : + BaseType(NETMSGTYPE_TruncFile) + { + this->filesize = filesize; + + this->entryInfoPtr = entryInfo; + } + + TruncFileMsg() : BaseType(NETMSGTYPE_TruncFile) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->filesize + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->dynAttribs + % obj->mirroredTimestamps; + + if (obj->isMsgHeaderFeatureFlagSet(TRUNCFILEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + bool supportsMirroring() const { return true; } + + private: + int64_t filesize; + FileEvent fileEvent; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object + + // for deserialization + EntryInfo entryInfo; + + protected: + DynamicFileAttribsVec dynAttribs; + MirroredTimestamps mirroredTimestamps; + + public: + + // getters & setters + int64_t getFilesize() + { + return filesize; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(TRUNCFILEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return TRUNCFILEMSG_FLAG_USE_QUOTA | + TRUNCFILEMSG_FLAG_HAS_EVENT; + } +}; + diff --git a/common/source/common/net/message/storage/TruncFileRespMsg.h b/common/source/common/net/message/storage/TruncFileRespMsg.h new file mode 100644 index 0000000..a4e24e9 --- /dev/null +++ b/common/source/common/net/message/storage/TruncFileRespMsg.h @@ -0,0 +1,17 @@ +#pragma once + +#include + + +class TruncFileRespMsg : public SimpleIntMsg +{ + public: + TruncFileRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_TruncFileResp, result) + { + } + + TruncFileRespMsg() : SimpleIntMsg(NETMSGTYPE_TruncFileResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/TruncLocalFileMsg.h b/common/source/common/net/message/storage/TruncLocalFileMsg.h new file mode 100644 index 0000000..df367c8 --- /dev/null +++ b/common/source/common/net/message/storage/TruncLocalFileMsg.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include +#include + + +#define TRUNCLOCALFILEMSG_FLAG_NODYNAMICATTRIBS 1 /* skip retrieval of current dyn attribs */ +#define TRUNCLOCALFILEMSG_FLAG_USE_QUOTA 2 /* if the message contains quota informations */ +#define TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR 4 /* given targetID is a buddymirrorgroup ID */ +#define TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 8 /* secondary of group, otherwise primary */ + +class TruncLocalFileMsg : public NetMessageSerdes +{ + public: + + /** + * @param entryID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + TruncLocalFileMsg(int64_t filesize, std::string& entryID, uint16_t targetID, + PathInfo* pathInfo) : + BaseType(NETMSGTYPE_TruncLocalFile) + { + this->filesize = filesize; + + this->entryID = entryID.c_str(); + this->entryIDLen = entryID.length(); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfo; + } + + TruncLocalFileMsg() : BaseType(NETMSGTYPE_TruncLocalFile) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->filesize; + + if(obj->isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_USE_QUOTA) ) + { + ctx + % obj->userID + % obj->groupID; + } + + ctx + % serdes::rawString(obj->entryID, obj->entryIDLen, 4) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return TRUNCLOCALFILEMSG_FLAG_NODYNAMICATTRIBS | TRUNCLOCALFILEMSG_FLAG_USE_QUOTA | + TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR | TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND; + } + + + private: + int64_t filesize; + + unsigned entryIDLen; + const char* entryID; + + uint16_t targetID; + + uint32_t userID; + uint32_t groupID; + + // for serialization + PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + + public: + + // getters & setters + + int64_t getFilesize() const + { + return filesize; + } + + const char* getEntryID() const + { + return entryID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + const PathInfo* getPathInfo() const + { + return &this->pathInfo; + } + + unsigned getUserID() const + { + return userID; + } + + unsigned getGroupID() const + { + return groupID; + } + + void setUserdataForQuota(unsigned userID, unsigned groupID) + { + this->addMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_USE_QUOTA); + + this->userID = userID; + this->groupID = groupID; + } +}; + diff --git a/common/source/common/net/message/storage/TruncLocalFileRespMsg.h b/common/source/common/net/message/storage/TruncLocalFileRespMsg.h new file mode 100644 index 0000000..7f055e0 --- /dev/null +++ b/common/source/common/net/message/storage/TruncLocalFileRespMsg.h @@ -0,0 +1,82 @@ +#pragma once + +#include + + +class TruncLocalFileRespMsg : public NetMessageSerdes +{ + public: + TruncLocalFileRespMsg(FhgfsOpsErr result, int64_t filesize, int64_t allocedBlocks, + int64_t modificationTimeSecs, int64_t lastAccessTimeSecs, uint64_t storageVersion) : + BaseType(NETMSGTYPE_TruncLocalFileResp) + { + this->result = result; + this->allocedBlocks = allocedBlocks; + this->filesize = filesize; + this->modificationTimeSecs = modificationTimeSecs; + this->lastAccessTimeSecs = lastAccessTimeSecs; + this->storageVersion = storageVersion; + } + + /** + * For deserialization only! + */ + TruncLocalFileRespMsg() : BaseType(NETMSGTYPE_TruncLocalFileResp) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->filesize + % obj->allocedBlocks + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs + % obj->storageVersion + % obj->result; + } + + private: + int32_t result; + int64_t filesize; + int64_t allocedBlocks; // number of allocated 512byte blocks (relevant for sparse files) + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; + + + public: + + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)result; + } + + int64_t getFileSize() + { + return filesize; + } + + int64_t getAllocedBlocks() + { + return allocedBlocks; + } + + int64_t getModificationTimeSecs() + { + return modificationTimeSecs; + } + + int64_t getLastAccessTimeSecs() + { + return lastAccessTimeSecs; + } + + uint64_t getStorageVersion() + { + return storageVersion; + } + +}; + + diff --git a/common/source/common/net/message/storage/attribs/GetChunkFileAttribsMsg.h b/common/source/common/net/message/storage/attribs/GetChunkFileAttribsMsg.h new file mode 100644 index 0000000..b29981e --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetChunkFileAttribsMsg.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + + +#define GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR 1 /* given targetID is a buddymirrorgroup ID */ +#define GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR_SECOND 2 /* secondary of group, otherwise primary */ + + +class GetChunkFileAttribsMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param pathInfo: Only as pointer, not owned by this object! + */ + GetChunkFileAttribsMsg(const std::string& entryID, uint16_t targetID, PathInfo* pathInfo) : + BaseType(NETMSGTYPE_GetChunkFileAttribs) + { + this->entryID = entryID.c_str(); + this->entryIDLen = entryID.length(); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfo; + } + + /** + * For deserialization only + */ + GetChunkFileAttribsMsg() : BaseType(NETMSGTYPE_GetChunkFileAttribs) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->entryID, obj->entryIDLen, 4) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR | + GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR_SECOND; + } + + private: + unsigned entryIDLen; + const char* entryID; + uint16_t targetID; + + // for serialization + PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + + public: + + // inliners + const char* getEntryID() const + { + return entryID; + } + + // getters & setters + uint16_t getTargetID() const + { + return targetID; + } + + PathInfo* getPathInfo() + { + return &this->pathInfo; + } + + +}; + diff --git a/common/source/common/net/message/storage/attribs/GetChunkFileAttribsRespMsg.h b/common/source/common/net/message/storage/attribs/GetChunkFileAttribsRespMsg.h new file mode 100644 index 0000000..379ad76 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetChunkFileAttribsRespMsg.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +class GetChunkFileAttribsRespMsg : public NetMessageSerdes +{ + public: + GetChunkFileAttribsRespMsg(FhgfsOpsErr result, int64_t size, int64_t allocedBlocks, + int64_t modificationTimeSecs, int64_t lastAccessTimeSecs, uint64_t storageVersion) : + BaseType(NETMSGTYPE_GetChunkFileAttribsResp) + { + this->result = result; + this->size = size; + this->allocedBlocks = allocedBlocks; + this->modificationTimeSecs = modificationTimeSecs; + this->lastAccessTimeSecs = lastAccessTimeSecs; + this->storageVersion = storageVersion; + } + + /** + * For deserialization only! + */ + GetChunkFileAttribsRespMsg() : BaseType(NETMSGTYPE_GetChunkFileAttribsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->size + % obj->allocedBlocks + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs + % obj->storageVersion + % obj->result; + } + + private: + int32_t result; + int64_t size; + int64_t allocedBlocks; // allocated 512byte blocks ("!= size/512" for sparse files) + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; + + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)result; + } + + int64_t getSize() + { + return size; + } + + int64_t getAllocedBlocks() + { + return allocedBlocks; + } + + int64_t getModificationTimeSecs() + { + return modificationTimeSecs; + } + + int64_t getLastAccessTimeSecs() + { + return lastAccessTimeSecs; + } + + uint64_t getStorageVersion() + { + return storageVersion; + } + +}; + diff --git a/common/source/common/net/message/storage/attribs/GetEntryInfoMsg.h b/common/source/common/net/message/storage/attribs/GetEntryInfoMsg.h new file mode 100644 index 0000000..de3fe25 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetEntryInfoMsg.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include + +class GetEntryInfoMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param path just a reference, so do not free it as long as you use this object! + */ + GetEntryInfoMsg(EntryInfo* entryInfo) : BaseType(NETMSGTYPE_GetEntryInfo) + { + this->entryInfoPtr = entryInfo; + } + + GetEntryInfoMsg() : BaseType(NETMSGTYPE_GetEntryInfo) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + + public: + // getters & setters + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/GetEntryInfoRespMsg.h b/common/source/common/net/message/storage/attribs/GetEntryInfoRespMsg.h new file mode 100644 index 0000000..469dbc5 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetEntryInfoRespMsg.h @@ -0,0 +1,118 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +/** + * Retrieve information about an entry, such as the stripe pattern or mirroring settings. + * This is typically used by "fhgfs-ctl --getentryinfo". + */ +class GetEntryInfoRespMsg : public NetMessageSerdes +{ + public: + /** + * @param mirrorNodeID ID of metadata mirror node + */ + GetEntryInfoRespMsg(FhgfsOpsErr result, StripePattern* pattern, uint16_t mirrorNodeID, + PathInfo* pathInfoPtr, RemoteStorageTarget* rst, uint32_t numSessionsRead = 0, + uint32_t numSessionsWrite = 0, uint8_t dataState = 0) : BaseType(NETMSGTYPE_GetEntryInfoResp) + { + this->result = result; + this->pattern = pattern; + this->mirrorNodeID = mirrorNodeID; + this->numSessionsRead = numSessionsRead; + this->numSessionsWrite = numSessionsWrite; + this->fileDataState = dataState; + this->pathInfoPtr = pathInfoPtr; + this->rstPtr = rst; + } + + /** + * For deserialization only + */ + GetEntryInfoRespMsg() : BaseType(NETMSGTYPE_GetEntryInfoResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % serdes::backedPtr(obj->rstPtr, obj->rst) + % obj->mirrorNodeID + % obj->numSessionsRead + % obj->numSessionsWrite + % obj->fileDataState; + } + + private: + int32_t result; + + uint16_t mirrorNodeID; // metadata mirror node (0 means "none") + + uint32_t numSessionsRead; // only applicable for regular files + uint32_t numSessionsWrite; // only applicable for regular files + uint8_t fileDataState; // only applicable for regular files + + // for serialization + StripePattern* pattern; // not owned by this object! + PathInfo* pathInfoPtr; // not owned by this object! + RemoteStorageTarget* rstPtr; // not owned by this object! + + // for deserialization + RemoteStorageTarget rst; + struct { + std::unique_ptr pattern; + } parsed; + PathInfo pathInfo; + + public: + StripePattern& getPattern() + { + return *pattern; + } + + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)result; + } + + uint16_t getMirrorNodeID() const + { + return mirrorNodeID; + } + + PathInfo* getPathInfo() + { + return &this->pathInfo; + } + + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rst; + } + + uint32_t getNumSessionsRead() const + { + return numSessionsRead; + } + + uint32_t getNumSessionsWrite() const + { + return numSessionsWrite; + } + + uint8_t getFileDataState() const + { + return fileDataState; + } +}; + + diff --git a/common/source/common/net/message/storage/attribs/GetXAttrMsg.h b/common/source/common/net/message/storage/attribs/GetXAttrMsg.h new file mode 100644 index 0000000..14baf90 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetXAttrMsg.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +class GetXAttrMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + GetXAttrMsg(EntryInfo* entryInfo, const std::string& name, int size) + : BaseType(NETMSGTYPE_GetXAttr), + entryInfoPtr(entryInfo), size(size), name(name) + { + } + + /** + * For deserialization only! + */ + GetXAttrMsg() : BaseType(NETMSGTYPE_GetXAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->name + % obj->size; + } + + bool supportsMirroring() const { return true; } + + private: + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + int32_t size; + std::string name; + + public: + // getters and setters + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + const std::string& getName(void) const + { + return this->name; + } + + int getSize(void) const + { + return this->size; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/GetXAttrRespMsg.h b/common/source/common/net/message/storage/attribs/GetXAttrRespMsg.h new file mode 100644 index 0000000..b762ddf --- /dev/null +++ b/common/source/common/net/message/storage/attribs/GetXAttrRespMsg.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +class GetXAttrRespMsg : public NetMessageSerdes +{ + public: + GetXAttrRespMsg(const CharVector& value, int size, int returnCode) + : BaseType(NETMSGTYPE_GetXAttrResp), + value(value), size(size), returnCode(returnCode) + { + } + + /** + * For deserialization only. + */ + GetXAttrRespMsg() : BaseType(NETMSGTYPE_GetXAttrResp) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->value + % obj->size + % obj->returnCode; + } + + private: + CharVector value; + int32_t size; + int32_t returnCode; + + public: + // getters & setters + const CharVector& getValue() const + { + return this->value; + } + + int getReturnCode() const + { + return this->returnCode; + } + + int getSize() const + { + return this->size; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/ListXAttrMsg.h b/common/source/common/net/message/storage/attribs/ListXAttrMsg.h new file mode 100644 index 0000000..5b7d0e4 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/ListXAttrMsg.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +class ListXAttrMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + ListXAttrMsg(EntryInfo* entryInfo, int size) : BaseType(NETMSGTYPE_ListXAttr), + entryInfoPtr(entryInfo), size(size) + { + } + + /** + * For deserialization only! + */ + ListXAttrMsg() : BaseType(NETMSGTYPE_ListXAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->size; + } + + bool supportsMirroring() const { return true; } + + private: + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + int32_t size; + + public: + // getters and setters + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + int getSize(void) const + { + return this->size; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/ListXAttrRespMsg.h b/common/source/common/net/message/storage/attribs/ListXAttrRespMsg.h new file mode 100644 index 0000000..de60605 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/ListXAttrRespMsg.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +class ListXAttrRespMsg : public NetMessageSerdes +{ + public: + ListXAttrRespMsg(const StringVector& value, int size, int returnCode) + : BaseType(NETMSGTYPE_ListXAttrResp), + value(value), size(size), returnCode(returnCode) + { + } + + /** + * For deserialization only. + */ + ListXAttrRespMsg() : BaseType(NETMSGTYPE_ListXAttrResp) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->value + % obj->size + % obj->returnCode; + } + + private: + StringVector value; + int32_t size; + int32_t returnCode; + + + public: + // getters & setters + const StringVector& getValue() const + { + return this->value; + } + + int getReturnCode() const + { + return this->returnCode; + } + + int getSize() const + { + return this->size; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h b/common/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h new file mode 100644 index 0000000..71c1eb8 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/RefreshEntryInfoMsg.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +class RefreshEntryInfoMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + RefreshEntryInfoMsg(EntryInfo* entryInfo) : BaseType(NETMSGTYPE_RefreshEntryInfo) + { + this->entryInfoPtr = entryInfo; + } + + RefreshEntryInfoMsg() : BaseType(NETMSGTYPE_RefreshEntryInfo) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->fileTimestamps; + } + + private: + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + + protected: + MirroredTimestamps fileTimestamps; + + public: + + // getters & setters + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + bool supportsMirroring() const { return true; } +}; + + diff --git a/common/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h b/common/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h new file mode 100644 index 0000000..0926b5a --- /dev/null +++ b/common/source/common/net/message/storage/attribs/RefreshEntryInfoRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class RefreshEntryInfoRespMsg : public SimpleIntMsg +{ + public: + RefreshEntryInfoRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RefreshEntryInfoResp, result) + { + } + + RefreshEntryInfoRespMsg() : SimpleIntMsg(NETMSGTYPE_RefreshEntryInfoResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/attribs/RemoveXAttrMsg.h b/common/source/common/net/message/storage/attribs/RemoveXAttrMsg.h new file mode 100644 index 0000000..ba82147 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/RemoveXAttrMsg.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +class RemoveXAttrMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + RemoveXAttrMsg(EntryInfo* entryInfo, const std::string& name) + : BaseType(NETMSGTYPE_RemoveXAttr) + { + this->entryInfoPtr = entryInfo; + this->name = name; + } + + /** + * For deserialization only! + */ + RemoveXAttrMsg() : BaseType(NETMSGTYPE_RemoveXAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->name; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->inodeTimestamps; + } + + private: + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + std::string name; + + protected: + MirroredTimestamps inodeTimestamps; + + public: + // getters and setters + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + const std::string& getName(void) + { + return this->name; + } + + bool supportsMirroring() const { return true; } +}; + diff --git a/common/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h b/common/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h new file mode 100644 index 0000000..328c19f --- /dev/null +++ b/common/source/common/net/message/storage/attribs/RemoveXAttrRespMsg.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class RemoveXAttrRespMsg : public SimpleIntMsg +{ + public: + RemoveXAttrRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RemoveXAttrResp, result) {} + + RemoveXAttrRespMsg() : SimpleIntMsg(NETMSGTYPE_RemoveXAttrResp) {} +}; + diff --git a/common/source/common/net/message/storage/attribs/SetAttrMsg.h b/common/source/common/net/message/storage/attribs/SetAttrMsg.h new file mode 100644 index 0000000..87fc351 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetAttrMsg.h @@ -0,0 +1,108 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include + + +#define SETATTRMSG_FLAG_USE_QUOTA 1 /* if the message contains quota informations */ +#define SETATTRMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ +#define SETATTRMSG_FLAG_INCR_NLINKCNT 8 /* increment nlink count */ +#define SETATTRMSG_FLAG_DECR_NLINKCNT 16 /* decrement nlink count */ + + +class SetAttrMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param validAttribs a combination of SETATTR_CHANGE_...-Flags + */ + SetAttrMsg(EntryInfo *entryInfo, int validAttribs, SettableFileAttribs* attribs) + : BaseType(NETMSGTYPE_SetAttr), + validAttribs(validAttribs), + attribs(*attribs), + entryInfoPtr(entryInfo) + { } + + /** + * For deserialization only! + */ + SetAttrMsg() : BaseType(NETMSGTYPE_SetAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->validAttribs + % obj->attribs.mode + % obj->attribs.modificationTimeSecs + % obj->attribs.lastAccessTimeSecs + % obj->attribs.userID + % obj->attribs.groupID + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->inodeTimestamps; + + if (obj->isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + private: + int32_t validAttribs; + SettableFileAttribs attribs; + FileEvent fileEvent; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + + protected: + MirroredTimestamps inodeTimestamps; + + public: + // getters & setters + int getValidAttribs() + { + return validAttribs; + } + + SettableFileAttribs* getAttribs() + { + return &attribs; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return SETATTRMSG_FLAG_USE_QUOTA | + SETATTRMSG_FLAG_HAS_EVENT | + SETATTRMSG_FLAG_INCR_NLINKCNT | + SETATTRMSG_FLAG_DECR_NLINKCNT; + } + + bool supportsMirroring() const { return true; } +}; + diff --git a/common/source/common/net/message/storage/attribs/SetAttrRespMsg.h b/common/source/common/net/message/storage/attribs/SetAttrRespMsg.h new file mode 100644 index 0000000..4a931ed --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetAttrRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class SetAttrRespMsg : public SimpleIntMsg +{ + public: + SetAttrRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetAttrResp, result) + { + } + + SetAttrRespMsg() : SimpleIntMsg(NETMSGTYPE_SetAttrResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/attribs/SetDirPatternMsg.h b/common/source/common/net/message/storage/attribs/SetDirPatternMsg.h new file mode 100644 index 0000000..9e2216d --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetDirPatternMsg.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include +#include +#include + +class SetDirPatternMsg : public MirroredMessageBase +{ + public: + struct Flags + { + static const uint32_t HAS_UID = 1; + }; + + /** + * @param path just a reference, so do not free it as long as you use this object! + * @param pattern just a reference, so do not free it as long as you use this object! + */ + SetDirPatternMsg(EntryInfo* entryInfo, StripePattern* pattern, RemoteStorageTarget* rst) : + BaseType(NETMSGTYPE_SetDirPattern) + { + this->entryInfoPtr = entryInfo; + this->pattern = pattern; + this->rstPtr = rst; + } + + /** + * For deserialization only + */ + SetDirPatternMsg() : BaseType(NETMSGTYPE_SetDirPattern) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->rstPtr, obj->rst); + + if (obj->isMsgHeaderFeatureFlagSet(Flags::HAS_UID)) + ctx % obj->uid; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return Flags::HAS_UID; + } + + private: + uint32_t uid; + + // for serialization + EntryInfo* entryInfoPtr; + + StripePattern* pattern; // not owned by this object! + RemoteStorageTarget* rstPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + RemoteStorageTarget rst; + struct { + std::unique_ptr pattern; + } parsed; + + public: + StripePattern& getPattern() + { + return *pattern; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rst; + } + + uint32_t getUID() const { return uid; } + + void setUID(uint32_t uid) + { + setMsgHeaderFeatureFlags(Flags::HAS_UID); + this->uid = uid; + } + + bool supportsMirroring() const { return true; } +}; + + diff --git a/common/source/common/net/message/storage/attribs/SetDirPatternRespMsg.h b/common/source/common/net/message/storage/attribs/SetDirPatternRespMsg.h new file mode 100644 index 0000000..403cee8 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetDirPatternRespMsg.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + + +class SetDirPatternRespMsg : public SimpleIntMsg +{ + public: + SetDirPatternRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetDirPatternResp, result) + { + } + + /** + * For deserialization only + */ + SetDirPatternRespMsg() : SimpleIntMsg(NETMSGTYPE_SetDirPatternResp) + { + } + + + private: + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/attribs/SetFilePatternMsg.h b/common/source/common/net/message/storage/attribs/SetFilePatternMsg.h new file mode 100644 index 0000000..2c994b9 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetFilePatternMsg.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include + +class SetFilePatternMsg : public MirroredMessageBase +{ + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param rst Remote storage targets to be set on file's inode + */ + SetFilePatternMsg(EntryInfo* entryInfo, RemoteStorageTarget* rst) : + BaseType(NETMSGTYPE_SetFilePattern) + { + this->entryInfoPtr = entryInfo; + this->rstPtr = rst; + } + + /** + * For deserialization only + */ + SetFilePatternMsg() : BaseType(NETMSGTYPE_SetFilePattern) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->rstPtr, obj->rst); + } + + private: + // for serialization + EntryInfo* entryInfoPtr; + RemoteStorageTarget* rstPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + RemoteStorageTarget rst; + + public: + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rst; + } + + bool supportsMirroring() const { return true; } +}; diff --git a/common/source/common/net/message/storage/attribs/SetFilePatternRespMsg.h b/common/source/common/net/message/storage/attribs/SetFilePatternRespMsg.h new file mode 100644 index 0000000..e69d7ae --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetFilePatternRespMsg.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class SetFilePatternRespMsg : public SimpleIntMsg +{ + public: + SetFilePatternRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetFilePatternResp, result) + { + } + + /** + * For deserialization only + */ + SetFilePatternRespMsg() : SimpleIntMsg(NETMSGTYPE_SetFilePatternResp) + { + } + + FhgfsOpsErr getResult() { return static_cast(getValue()); } +}; diff --git a/common/source/common/net/message/storage/attribs/SetFileStateMsg.h b/common/source/common/net/message/storage/attribs/SetFileStateMsg.h new file mode 100644 index 0000000..0d9014a --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetFileStateMsg.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + +class SetFileStateMsg : public MirroredMessageBase +{ + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param state the new state (accessFlags+dataState) of the file + */ + SetFileStateMsg(EntryInfo* entryInfo, uint8_t state) : + BaseType(NETMSGTYPE_SetFileState) + { + this->entryInfoPtr = entryInfo; + this->state = state; + } + + SetFileStateMsg() : BaseType(NETMSGTYPE_SetFileState) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->state; + } + + private: + // for serialization + EntryInfo* entryInfoPtr; + uint8_t state; + + // for deserialization + EntryInfo entryInfo; + + public: + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + uint8_t getFileState() + { + return this->state; + } + + bool supportsMirroring() const { return true; } +}; \ No newline at end of file diff --git a/common/source/common/net/message/storage/attribs/SetFileStateRespMsg.h b/common/source/common/net/message/storage/attribs/SetFileStateRespMsg.h new file mode 100644 index 0000000..6f24be0 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetFileStateRespMsg.h @@ -0,0 +1,21 @@ +#pragma once + +#include +#include + +class SetFileStateRespMsg : public SimpleIntMsg +{ + public: + SetFileStateRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetFileStateResp, result) + { + } + + /** + * For deserialization only + */ + SetFileStateRespMsg() : SimpleIntMsg(NETMSGTYPE_SetFileStateResp) + { + } + + FhgfsOpsErr getResult() { return static_cast(getValue()); } +}; \ No newline at end of file diff --git a/common/source/common/net/message/storage/attribs/SetLocalAttrMsg.h b/common/source/common/net/message/storage/attribs/SetLocalAttrMsg.h new file mode 100644 index 0000000..3761dc9 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetLocalAttrMsg.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + + +#define SETLOCALATTRMSG_FLAG_USE_QUOTA 1 // if the message contains quota informations +#define SETLOCALATTRMSG_FLAG_BUDDYMIRROR 2 // given targetID is a buddymirrorgroup ID +#define SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND 4 // secondary of group, otherwise primary + +class SetLocalAttrMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + /** + * @param entryID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + * @param validAttribs a combination of SETATTR_CHANGE_...-Flags + */ + SetLocalAttrMsg(std::string& entryID, uint16_t targetID, PathInfo* pathInfo, int validAttribs, + SettableFileAttribs* attribs, bool enableCreation) : BaseType(NETMSGTYPE_SetLocalAttr) + { + this->entryID = entryID.c_str(); + this->entryIDLen = entryID.length(); + this->targetID = targetID; + this->pathInfoPtr = pathInfo; + + this->validAttribs = validAttribs; + this->attribs = *attribs; + this->enableCreation = enableCreation; + } + + /** + * For deserialization only! + */ + SetLocalAttrMsg() : BaseType(NETMSGTYPE_SetLocalAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->attribs.modificationTimeSecs + % obj->attribs.lastAccessTimeSecs + % obj->validAttribs + % obj->attribs.mode + % obj->attribs.userID + % obj->attribs.groupID + % serdes::rawString(obj->entryID, obj->entryIDLen, 4) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID + % obj->enableCreation; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return SETLOCALATTRMSG_FLAG_USE_QUOTA | SETLOCALATTRMSG_FLAG_BUDDYMIRROR | + SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND; + } + + + + private: + unsigned entryIDLen; + const char* entryID; + uint16_t targetID; + int32_t validAttribs; + SettableFileAttribs attribs; + bool enableCreation; + + // for serialization + PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + + public: + + // getters & setters + const char* getEntryID() const + { + return entryID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + int getValidAttribs() const + { + return validAttribs; + } + + const SettableFileAttribs* getAttribs() const + { + return &attribs; + } + + bool getEnableCreation() const + { + return enableCreation; + } + + const PathInfo* getPathInfo() const + { + return &this->pathInfo; + } + +}; + diff --git a/common/source/common/net/message/storage/attribs/SetLocalAttrRespMsg.h b/common/source/common/net/message/storage/attribs/SetLocalAttrRespMsg.h new file mode 100644 index 0000000..295e04a --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetLocalAttrRespMsg.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include + +#define SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS 1 // message contains DynAttrs of chunk + +class SetLocalAttrRespMsg : public NetMessageSerdes +{ + public: + /* + * @param result the result of the set attr operation + * @param dynamicAttribs The current dynamic attributes of the chunk, which are needed by + * the metadata server in case of a xtime change; just a reference! + */ + SetLocalAttrRespMsg(FhgfsOpsErr result, DynamicFileAttribs& dynamicAttribs) : + BaseType(NETMSGTYPE_SetLocalAttrResp), + result(result), + filesize(dynamicAttribs.fileSize), + numBlocks(dynamicAttribs.numBlocks), + modificationTimeSecs(dynamicAttribs.modificationTimeSecs), + lastAccessTimeSecs(dynamicAttribs.lastAccessTimeSecs), + storageVersion(dynamicAttribs.storageVersion) + { + addMsgHeaderFeatureFlag(SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS); + } + + SetLocalAttrRespMsg(FhgfsOpsErr result) : BaseType(NETMSGTYPE_SetLocalAttrResp), + result(result), filesize(0), numBlocks(0), modificationTimeSecs(0), lastAccessTimeSecs(0), + storageVersion(0) + { + // all initialization done in list + } + + SetLocalAttrRespMsg() : BaseType(NETMSGTYPE_SetLocalAttrResp) + { + } + + // getters & setters + FhgfsOpsErr getResult() const + { + return result; + } + + void getDynamicAttribs(DynamicFileAttribs* outDynamicAttribs) const + { + outDynamicAttribs->fileSize = filesize; + outDynamicAttribs->lastAccessTimeSecs = lastAccessTimeSecs; + outDynamicAttribs->modificationTimeSecs = modificationTimeSecs; + outDynamicAttribs->numBlocks = numBlocks; + outDynamicAttribs->storageVersion = storageVersion; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->filesize + % obj->numBlocks + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs + % obj->storageVersion + % serdes::as(obj->result); + } + + private: + FhgfsOpsErr result; + int64_t filesize; + int64_t numBlocks; + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; +}; + diff --git a/common/source/common/net/message/storage/attribs/SetXAttrMsg.h b/common/source/common/net/message/storage/attribs/SetXAttrMsg.h new file mode 100644 index 0000000..5cf0261 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetXAttrMsg.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include +#include + +class SetXAttrMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + friend class TestSerialization; + + public: + /** + * @param entryInfo just a reference, do not free it as long as you use this object! + */ + SetXAttrMsg(EntryInfo* entryInfo, const std::string& name, const CharVector& value, int flags) + : BaseType(NETMSGTYPE_SetXAttr) + { + this->entryInfoPtr = entryInfo; + this->name = name; + this->value = value; + this->flags = flags; + } + + /** + * For deserialization only! + */ + SetXAttrMsg() : BaseType(NETMSGTYPE_SetXAttr) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->name + % obj->value + % obj->flags; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->inodeTimestamps; + } + + private: + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + std::string name; + CharVector value; + int32_t flags; + + protected: + MirroredTimestamps inodeTimestamps; + + public: + // getters and setters + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + const std::string& getName(void) + { + return this->name; + } + + const CharVector& getValue(void) + { + return this->value; + } + + int getFlags(void) + { + return this->flags; + } + + bool supportsMirroring() const { return true; } +}; + diff --git a/common/source/common/net/message/storage/attribs/SetXAttrRespMsg.h b/common/source/common/net/message/storage/attribs/SetXAttrRespMsg.h new file mode 100644 index 0000000..95fb6f1 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/SetXAttrRespMsg.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class SetXAttrRespMsg : public SimpleIntMsg +{ + public: + SetXAttrRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetXAttrResp, result) {} + + SetXAttrRespMsg() : SimpleIntMsg(NETMSGTYPE_SetXAttrResp) {} +}; + diff --git a/common/source/common/net/message/storage/attribs/StatMsg.h b/common/source/common/net/message/storage/attribs/StatMsg.h new file mode 100644 index 0000000..80204ff --- /dev/null +++ b/common/source/common/net/message/storage/attribs/StatMsg.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include +#include + +#define STATMSG_FLAG_GET_PARENTINFO 1 // caller wants to have parentOwnerNodeID and parentEntryID + +class StatMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + StatMsg(EntryInfo* entryInfo) : BaseType(NETMSGTYPE_Stat) + { + this->entryInfoPtr = entryInfo; + } + + /** + * For deserialization only! + */ + StatMsg() : BaseType(NETMSGTYPE_Stat) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return STATMSG_FLAG_GET_PARENTINFO; + } + + bool supportsMirroring() const { return true; } + + private: + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + public: + + // getters & setters + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/StatRespMsg.h b/common/source/common/net/message/storage/attribs/StatRespMsg.h new file mode 100644 index 0000000..a083b75 --- /dev/null +++ b/common/source/common/net/message/storage/attribs/StatRespMsg.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include +#include + + +#define STATRESPMSG_FLAG_HAS_PARENTINFO 1 /* msg includes parentOwnerNodeID and + parentEntryID */ + + +class StatRespMsg : public NetMessageSerdes +{ + public: + StatRespMsg(int result, StatData statData) : + BaseType(NETMSGTYPE_StatResp) + { + this->result = result; + this->statData = statData; + } + + /** + * For deserialization only. + */ + StatRespMsg() : BaseType(NETMSGTYPE_StatResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->statData.serializeAs(StatDataFormat_NET); + + if(obj->isMsgHeaderFeatureFlagSet(STATRESPMSG_FLAG_HAS_PARENTINFO) ) + { + ctx + % serdes::stringAlign4(obj->parentEntryID) + % obj->parentNodeID; + } + } + + virtual unsigned getSupportedHeaderFeatureFlagsMask() const + { + return STATRESPMSG_FLAG_HAS_PARENTINFO; + } + + private: + int32_t result; + StatData statData; + + NumNodeID parentNodeID; + std::string parentEntryID; + + public: + // getters & setters + int getResult() + { + return result; + } + + StatData* getStatData() + { + return &this->statData; + } + + void addParentInfo(NumNodeID parentNodeID, std::string parentEntryID) + { + this->parentNodeID = parentNodeID; + this->parentEntryID = parentEntryID; + + addMsgHeaderFeatureFlag(STATRESPMSG_FLAG_HAS_PARENTINFO); + } + + NumNodeID getParentNodeID() + { + return this->parentNodeID; + } +}; + diff --git a/common/source/common/net/message/storage/attribs/UpdateDirParentMsg.h b/common/source/common/net/message/storage/attribs/UpdateDirParentMsg.h new file mode 100644 index 0000000..5f0537a --- /dev/null +++ b/common/source/common/net/message/storage/attribs/UpdateDirParentMsg.h @@ -0,0 +1,63 @@ +#pragma once + +#include +#include +#include +#include + +class UpdateDirParentMsg : public MirroredMessageBase +{ + public: + + /** + * @param entryInfoPtr just a reference, so do not free it as long as you use this object! + */ + UpdateDirParentMsg(EntryInfo* entryInfoPtr, NumNodeID parentOwnerNodeID) : + BaseType(NETMSGTYPE_UpdateDirParent) + { + this->entryInfoPtr = entryInfoPtr; + this->parentOwnerNodeID = parentOwnerNodeID; + } + + UpdateDirParentMsg() : BaseType(NETMSGTYPE_UpdateDirParent) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->parentOwnerNodeID; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->dirTimestamps; + } + + private: + NumNodeID parentOwnerNodeID; + + // for serialization + EntryInfo *entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + protected: + MirroredTimestamps dirTimestamps; + + public: + // getters & setters + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + NumNodeID getParentNodeID() + { + return parentOwnerNodeID; + } + + bool supportsMirroring() const { return true; } +}; + + + diff --git a/common/source/common/net/message/storage/attribs/UpdateDirParentRespMsg.h b/common/source/common/net/message/storage/attribs/UpdateDirParentRespMsg.h new file mode 100644 index 0000000..92be49e --- /dev/null +++ b/common/source/common/net/message/storage/attribs/UpdateDirParentRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class UpdateDirParentRespMsg : public SimpleIntMsg +{ + public: + UpdateDirParentRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_UpdateDirParentResp, result) + { + } + + UpdateDirParentRespMsg() : SimpleIntMsg(NETMSGTYPE_UpdateDirParentResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceMsg.h b/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceMsg.h new file mode 100644 index 0000000..fd4e049 --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceMsg.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + + +class ChunkBalanceMsg : public MirroredMessageBase +{ + public: + ChunkBalanceMsg(uint16_t targetID, uint16_t destinationID, EntryInfo* entryInfo, StringList* relativePaths) : + BaseType(NETMSGTYPE_ChunkBalance) + { + this->targetID = targetID; + this->destinationID = destinationID; + this->relativePaths = relativePaths; + this->entryInfoPtr = entryInfo; + } + + ChunkBalanceMsg() : BaseType(NETMSGTYPE_ChunkBalance) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % obj->destinationID + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->relativePaths, obj->parsed.relativePaths); + } + + bool supportsMirroring() const {return true; } + private: + uint16_t targetID; + uint16_t destinationID; + + // for serialization + StringList* relativePaths; + EntryInfo* entryInfoPtr; + + // for deserialization + struct { + StringList relativePaths; + } parsed; + EntryInfo entryInfo; + + public: + uint16_t getTargetID() const + { + return targetID; + } + + uint16_t getDestinationID() const + { + return destinationID; + } + + StringList& getRelativePaths() + { + return *relativePaths; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + +}; + + diff --git a/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceRespMsg.h b/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceRespMsg.h new file mode 100644 index 0000000..225e885 --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/ChunkBalanceRespMsg.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class ChunkBalanceRespMsg : public SimpleIntMsg +{ + public: + ChunkBalanceRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_ChunkBalanceResp, result) {} + + ChunkBalanceRespMsg() : SimpleIntMsg(NETMSGTYPE_ChunkBalanceResp) {} +}; + + diff --git a/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsMsg.h b/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsMsg.h new file mode 100644 index 0000000..09ab998 --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsMsg.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include + +#define CPCHUNKPATHSMSG_FLAG_BUDDYMIRROR 1 /* given targetID is a buddymirrorgroup ID */ + + +class CpChunkPathsMsg : public NetMessageSerdes +{ + public: + CpChunkPathsMsg(uint16_t targetID, uint16_t destinationID, EntryInfo* entryInfo, std::string* relativePath) : + BaseType(NETMSGTYPE_CpChunkPaths) + { + this->targetID = targetID; + this->destinationID = destinationID; + this->relativePath = relativePath; + this->entryInfoPtr = entryInfo; + } + + CpChunkPathsMsg() : BaseType(NETMSGTYPE_CpChunkPaths) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % obj->destinationID + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->relativePath, obj->parsed.relativePath); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return CPCHUNKPATHSMSG_FLAG_BUDDYMIRROR; + } + + private: + uint16_t targetID; + uint16_t destinationID; + + // for serialization + std::string* relativePath; + EntryInfo* entryInfoPtr; + + // for deserialization + struct { + std::string relativePath; + } parsed; + EntryInfo entryInfo; + + public: + uint16_t getTargetID() const + { + return targetID; + } + + uint16_t getDestinationID() const + { + return destinationID; + } + + std::string& getRelativePath() + { + return *relativePath; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } +}; + diff --git a/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsRespMsg.h b/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsRespMsg.h new file mode 100644 index 0000000..3ba7f75 --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/CpChunkPathsRespMsg.h @@ -0,0 +1,41 @@ +#pragma once + +#include +#include + +class CpChunkPathsRespMsg : public NetMessageSerdes +{ + public: + CpChunkPathsRespMsg(FhgfsOpsErr result ) : BaseType(NETMSGTYPE_CpChunkPathsResp) + { + this->result = static_cast(result); + } + + CpChunkPathsRespMsg() : BaseType(NETMSGTYPE_CpChunkPathsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->result; + } + + private: + // for serialization + int32_t result; // FhgfsOpsErr + + // for deserialization + struct { + int32_t result; + } parsed; + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return static_cast(result); + } +}; + + diff --git a/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateMsg.h b/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateMsg.h new file mode 100644 index 0000000..baad9ca --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateMsg.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + + +class StripePatternUpdateMsg : public MirroredMessageBase +{ + public: + StripePatternUpdateMsg(uint16_t targetID, uint16_t destinationID, EntryInfo* entryInfo,std::string* relativePath) : + BaseType(NETMSGTYPE_StripePatternUpdate) + { + this->targetID = targetID; + this->destinationID = destinationID; + this->relativePath = relativePath; + this->entryInfoPtr = entryInfo; + } + + StripePatternUpdateMsg() : BaseType(NETMSGTYPE_StripePatternUpdate) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % obj->destinationID + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->relativePath, obj->parsed.relativePath); + } + + + bool supportsMirroring() const { return true; } + + private: + uint16_t targetID; + uint16_t destinationID; + + // for serialization + std::string* relativePath; + EntryInfo* entryInfoPtr; + + // for deserialization + struct { + std::string relativePath; + } parsed; + EntryInfo entryInfo; + + public: + uint16_t getTargetID() const + { + return targetID; + } + + uint16_t getDestinationID() const + { + return destinationID; + } + + std::string& getRelativePath() + { + return *relativePath; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } +}; + + diff --git a/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateRespMsg.h b/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateRespMsg.h new file mode 100644 index 0000000..b0b90af --- /dev/null +++ b/common/source/common/net/message/storage/chunkbalancing/StripePatternUpdateRespMsg.h @@ -0,0 +1,24 @@ +#pragma once + +#include + +class StripePatternUpdateRespMsg : public SimpleIntMsg +{ + public: + StripePatternUpdateRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_StripePatternUpdateResp, result) {} + + StripePatternUpdateRespMsg() : SimpleIntMsg(NETMSGTYPE_StripePatternUpdateResp) {} + + private: + + + public: + // inliners + FhgfsOpsErr getResult() + { + return static_cast(getValue()); + } +}; + + + diff --git a/common/source/common/net/message/storage/creating/HardlinkMsg.h b/common/source/common/net/message/storage/creating/HardlinkMsg.h new file mode 100644 index 0000000..ff37c5b --- /dev/null +++ b/common/source/common/net/message/storage/creating/HardlinkMsg.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include +#include + +#define HARDLINKMSG_FLAG_IS_TO_DENTRY_CREATE 1 /* NOTE: this flag isn't used anymore, but we keep + * it, to prevent us from re-using the number */ +#define HARDLINKMSG_FLAG_HAS_EVENT 2 /* contains file event logging information */ + +class HardlinkMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * Use this NetMsg from a client to create a hard link. NetMsg send to fromDir meta node. + * @param fromDirInfo the directory, where the initial file/link exists; just a reference, so + * do not free it as long as you use this object! + * @param fromName the initial inode data; just a reference, so do not free it as long as you + * use this object! + * @param fromInfo just a reference, so do not free it as long as you use this object! + * @param toDirInfo the directory, where the new link will be creted; just a reference, so do + * not free it as long as you use this object! + * @param toName the name of the new link + */ + HardlinkMsg(EntryInfo* fromDirInfo, std::string& fromName, EntryInfo* fromInfo, + EntryInfo* toDirInfo, std::string& toName) : BaseType(NETMSGTYPE_Hardlink) + { + this->fromName = fromName; + this->fromInfoPtr = fromInfo; + + this->fromDirInfoPtr = fromDirInfo; + + this->toName = toName; + + this->toDirInfoPtr = toDirInfo; + } + + /* + * For deserialization + */ + HardlinkMsg() : BaseType(NETMSGTYPE_Hardlink) + { + } + + static void serialize(const HardlinkMsg* obj, Serializer& ctx) + { + ctx + % serdes::backedPtr(obj->fromInfoPtr, obj->fromInfo) + % serdes::backedPtr(obj->toDirInfoPtr, obj->toDirInfo) + % serdes::stringAlign4(obj->toName); + + if (obj->fromDirInfoPtr) + { + ctx + % serdes::backedPtr(obj->fromDirInfoPtr, obj->fromDirInfo) + % serdes::stringAlign4(obj->fromName); + } + + if (obj->isMsgHeaderFeatureFlagSet(HARDLINKMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->dirTimestamps + % obj->fileTimestamps; + } + + static void serialize(HardlinkMsg* obj, Deserializer& ctx) + { + ctx + % serdes::backedPtr(obj->fromInfoPtr, obj->fromInfo) + % serdes::backedPtr(obj->toDirInfoPtr, obj->toDirInfo) + % serdes::stringAlign4(obj->toName) + % serdes::backedPtr(obj->fromDirInfoPtr, obj->fromDirInfo) + % serdes::stringAlign4(obj->fromName); + + if (obj->isMsgHeaderFeatureFlagSet(HARDLINKMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->dirTimestamps + % obj->fileTimestamps; + } + + private: + + std::string fromName; + std::string toName; + FileEvent fileEvent; + + // for serialization + EntryInfo* fromInfoPtr; // not owned by this object + EntryInfo* fromDirInfoPtr; // not owned by this object + EntryInfo* toDirInfoPtr; // not owned by this object + + // for deserialization + EntryInfo fromInfo; + EntryInfo fromDirInfo; + EntryInfo toDirInfo; + + protected: + MirroredTimestamps dirTimestamps; + MirroredTimestamps fileTimestamps; + + public: + // getters & setters + + EntryInfo* getFromInfo() + { + return &this->fromInfo; + } + + EntryInfo* getFromDirInfo() + { + return &this->fromDirInfo; + } + + EntryInfo* getToDirInfo() + { + return &this->toDirInfo; + } + + std::string getFromName() const + { + return this->fromName; + } + + std::string getToName() const + { + return this->toName; + } + + bool supportsMirroring() const { return true; } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(HARDLINKMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return HARDLINKMSG_FLAG_HAS_EVENT; + } +}; + + diff --git a/common/source/common/net/message/storage/creating/HardlinkRespMsg.h b/common/source/common/net/message/storage/creating/HardlinkRespMsg.h new file mode 100644 index 0000000..a4d5d87 --- /dev/null +++ b/common/source/common/net/message/storage/creating/HardlinkRespMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class HardlinkRespMsg : public SimpleIntMsg +{ + public: + HardlinkRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_HardlinkResp, result) + { + } + + HardlinkRespMsg() : SimpleIntMsg(NETMSGTYPE_HardlinkResp) + { + } + + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/creating/MkDirMsg.h b/common/source/common/net/message/storage/creating/MkDirMsg.h new file mode 100644 index 0000000..e19c10b --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkDirMsg.h @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include + + +#define MKDIRMSG_FLAG_NOMIRROR 1 /* do not use mirror setting from parent + * (i.e. do not mirror) */ +#define MKDIRMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ + +class MkDirMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param parentEntryInfo just a reference, so do not free it as long as you use this object! + * @param preferredNodes just a reference, so do not free it as long as you use this object! + */ + MkDirMsg(const EntryInfo* parentEntryInfo, const std::string& newDirName, + const unsigned userID, const unsigned groupID, const int mode, const int umask, + const UInt16List* preferredNodes) + : BaseType(NETMSGTYPE_MkDir), + userID(userID), + groupID(groupID), + mode(mode), + umask(umask), + newDirName(newDirName.c_str() ), + newDirNameLen(newDirName.length() ), + parentEntryInfoPtr(parentEntryInfo), + preferredNodes(preferredNodes) + { } + + /** + * For deserialization only! + */ + MkDirMsg() : BaseType(NETMSGTYPE_MkDir) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->userID + % obj->groupID + % obj->mode + % obj->umask + % serdes::backedPtr(obj->parentEntryInfoPtr, obj->parentEntryInfo); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ) + ctx + % serdes::backedPtr(obj->createdEntryInfoPtr, obj->createdEntryInfo) + % obj->parentTimestamps; + + ctx + % serdes::rawString(obj->newDirName, obj->newDirNameLen, 4) + % serdes::backedPtr(obj->preferredNodes, obj->parsed.preferredNodes); + + if (obj->isMsgHeaderFeatureFlagSet(MKDIRMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + protected: + bool supportsMirroring() const { return true; } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return MKDIRMSG_FLAG_NOMIRROR | + MKDIRMSG_FLAG_HAS_EVENT; + } + + void setMode(const int mode, const int umask) + { + this->mode = mode; + this->umask = umask; + } + + void clearPreferredNodes() + { + parsed.preferredNodes.clear(); + preferredNodes = &parsed.preferredNodes; + } + + private: + uint32_t userID; + uint32_t groupID; + int32_t mode; + int32_t umask; + FileEvent fileEvent; + + // serialization / deserialization + const char* newDirName; + unsigned newDirNameLen; + + // for serialization + const EntryInfo* parentEntryInfoPtr; // not owned by this object + const UInt16List* preferredNodes; // not owned by this object! + + EntryInfo* createdEntryInfoPtr; // not owned by this object; only needed for secondary buddy + + // for deserialization + EntryInfo parentEntryInfo; + struct { + UInt16List preferredNodes; + } parsed; + + EntryInfo createdEntryInfo; // only needed for secondary buddy + + protected: + MirroredTimestamps parentTimestamps; + + public: + const UInt16List& getPreferredNodes() const + { + return *preferredNodes; + } + + // getters & setters + unsigned getUserID() const + { + return userID; + } + + unsigned getGroupID() const + { + return groupID; + } + + int getMode() const + { + return mode; + } + + int getUmask() const + { + return umask; + } + + const EntryInfo* getParentInfo() const + { + return &parentEntryInfo; + } + + const char* getNewDirName() const + { + return newDirName; + } + + EntryInfo* getCreatedEntryInfo() + { + return &createdEntryInfo; + } + + void setCreatedEntryInfo(EntryInfo* createdEntryInfo) + { + this->createdEntryInfoPtr = createdEntryInfo; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(MKDIRMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } +}; + diff --git a/common/source/common/net/message/storage/creating/MkDirRespMsg.h b/common/source/common/net/message/storage/creating/MkDirRespMsg.h new file mode 100644 index 0000000..6ffee1f --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkDirRespMsg.h @@ -0,0 +1,59 @@ +#pragma once + +#include +#include + +class MkDirRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param ownerNodeID just a reference, so do not free it as long as you use this object! + * @param entryID just a reference, so do not free it as long as you use this object! + */ + MkDirRespMsg(int result, EntryInfo* entryInfo) : + BaseType(NETMSGTYPE_MkDirResp) + { + this->result = result; + this->entryInfoPtr = entryInfo; + } + + MkDirRespMsg() : BaseType(NETMSGTYPE_MkDirResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + int32_t result; + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + + public: + + // inliners + + // getters & setters + int getResult() + { + return result; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + +}; + diff --git a/common/source/common/net/message/storage/creating/MkFileMsg.h b/common/source/common/net/message/storage/creating/MkFileMsg.h new file mode 100644 index 0000000..3e07a77 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkFileMsg.h @@ -0,0 +1,235 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#define MKFILEMSG_FLAG_STRIPEHINTS 1 /* msg contains extra stripe hints */ +#define MKFILEMSG_FLAG_STORAGEPOOLID 2 /* msg contains a storage pool ID to override parent */ +#define MKFILEMSG_FLAG_HAS_EVENT 4 /* contains file event logging information */ + +class MkFileMsg : public MirroredMessageBase +{ + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + MkFileMsg(const EntryInfo* parentInfo, const std::string& newName, const unsigned userID, + const unsigned groupID, const int mode, const int umask, + const UInt16List* preferredTargets) + : BaseType(NETMSGTYPE_MkFile), + newName(newName.c_str() ), + newNameLen(newName.length() ), + userID(userID), + groupID(groupID), + mode(mode), + umask(umask), + parentInfoPtr(parentInfo), + preferredTargets(preferredTargets) + { } + + /** + * For deserialization only! + */ + MkFileMsg() : BaseType(NETMSGTYPE_MkFile) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->userID + % obj->groupID + % obj->mode + % obj->umask; + + if(obj->isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_STRIPEHINTS) ) + { + ctx + % obj->numtargets + % obj->chunksize; + } + + if(obj->isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_STORAGEPOOLID) ) + { + ctx + % obj->storagePoolId; + } + + ctx + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::rawString(obj->newName, obj->newNameLen, 4); + + if(obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % serdes::rawString(obj->newEntryID, obj->newEntryIDLen, 4) + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->rstPtr, obj->rstInfo) + % obj->dirTimestamps + % obj->createTime; + + ctx % serdes::backedPtr(obj->preferredTargets, obj->parsed.preferredTargets); + + if (obj->isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + } + + protected: + MirroredTimestamps dirTimestamps; + int64_t createTime; + + const char* newName; + unsigned newNameLen; + uint32_t userID; + uint32_t groupID; + int32_t mode; + int32_t umask; + + uint32_t numtargets; + uint32_t chunksize; + FileEvent fileEvent; + + // if this is set, the storage pool of the parent directory is ignored and this pool is used + // instead + StoragePoolId storagePoolId; + + // secondary needs to know the created entryID, because it needs to use the same ID; it also + // needs to use exactly the same stripe pattern + // will only be set and used if NetMessageHeader::Flag_BuddyMirrorSecond is set + const char* newEntryID; + unsigned newEntryIDLen; + StripePattern* pattern; // not owned by this object! + RemoteStorageTarget* rstPtr; // not owned by this object! + + bool supportsMirroring() const { return true; } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return MKFILEMSG_FLAG_STRIPEHINTS | + MKFILEMSG_FLAG_STORAGEPOOLID | + MKFILEMSG_FLAG_HAS_EVENT; + } + + private: + // for serialization + const EntryInfo* parentInfoPtr; + const UInt16List* preferredTargets; // not owned by this object! + + // for deserialization + EntryInfo parentInfo; + RemoteStorageTarget rstInfo; + struct { + UInt16List preferredTargets; + std::unique_ptr pattern; + } parsed; + + public: + const UInt16List& getPreferredNodes() + { + return *preferredTargets; + } + + // getters & setters + unsigned getUserID() const + { + return this->userID; + } + + unsigned getGroupID() const + { + return this->groupID; + } + + int getMode() const + { + return this->mode; + } + + int getUmask() const + { + return this->umask; + } + + const char* getNewName() const + { + return this->newName; + } + + const EntryInfo* getParentInfo() const + { + return &this->parentInfo; + } + + /** + * Note: Adds MKFILEMSG_FLAG_STRIPEHINTS. + */ + void setStripeHints(unsigned numtargets, unsigned chunksize) + { + addMsgHeaderFeatureFlag(MKFILEMSG_FLAG_STRIPEHINTS); + + this->numtargets = numtargets; + this->chunksize = chunksize; + } + + /** + * @return 0 if MKFILEMSG_FLAG_STRIPEHINTS feature flag is not set. + */ + unsigned getNumTargets() const + { + return isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_STRIPEHINTS) ? numtargets : 0; + } + + /** + * @return 0 if MKFILEMSG_FLAG_STRIPEHINTS feature flag is not set. + */ + unsigned getChunkSize() const + { + return isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_STRIPEHINTS) ? chunksize : 0; + } + + void setNewEntryID(const char* newEntryID) + { + this->newEntryID = newEntryID; + this->newEntryIDLen = strlen(newEntryID); + } + + const char* getNewEntryID() + { + return this->newEntryID; + } + + void setPattern(StripePattern* pattern) + { + this->pattern = pattern; + } + + StripePattern& getPattern() + { + return *pattern; + } + + void setRemoteStorageTarget(RemoteStorageTarget* rst) + { + this->rstPtr = rst; + } + + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rstInfo; + } + + void setDirTimestamps(MirroredTimestamps ts) { dirTimestamps = ts; } + void setCreateTime(int64_t ts) { createTime = ts; } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } +}; + diff --git a/common/source/common/net/message/storage/creating/MkFileRespMsg.h b/common/source/common/net/message/storage/creating/MkFileRespMsg.h new file mode 100644 index 0000000..4b79b28 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkFileRespMsg.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +class MkFileRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + MkFileRespMsg(int result, EntryInfo* entryInfo) : + BaseType(NETMSGTYPE_MkFileResp) + { + this->result = result; + this->entryInfoPtr = entryInfo; + } + + MkFileRespMsg() : BaseType(NETMSGTYPE_MkFileResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + int32_t result; + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + public: + + // inliners + + // getters & setters + int getResult() + { + return result; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + +}; + diff --git a/common/source/common/net/message/storage/creating/MkFileWithPatternMsg.h b/common/source/common/net/message/storage/creating/MkFileWithPatternMsg.h new file mode 100644 index 0000000..56456c5 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkFileWithPatternMsg.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include + +class MkFileWithPatternMsg : public MirroredMessageBase +{ + public: + + /** + * @param path just a reference, so do not free it as long as you use this object! + * @param pattern just a reference, so do not free it as long as you use this object! + */ + MkFileWithPatternMsg(const EntryInfo* parentInfo, const std::string& newFileName, + const unsigned userID, const unsigned groupID, const int mode, const int umask, + StripePattern* pattern, RemoteStorageTarget* rst) + : BaseType(NETMSGTYPE_MkFileWithPattern), + newFileName(newFileName.c_str()), + newFileNameLen(newFileName.length()), + userID(userID), + groupID(groupID), + mode(mode), + umask(umask), + parentInfoPtr(parentInfo), + pattern(pattern), + rstPtr(rst) + { + } + + /** + * Constructor for deserialization only + */ + MkFileWithPatternMsg() : BaseType(NETMSGTYPE_MkFileWithPattern) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->userID + % obj->groupID + % obj->mode + % obj->umask + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::rawString(obj->newFileName, obj->newFileNameLen, 4) + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->rstPtr, obj->rst); + } + + private: + + const char* newFileName; + unsigned newFileNameLen; + + uint32_t userID; + uint32_t groupID; + int32_t mode; + int32_t umask; + + // for serialization + const EntryInfo* parentInfoPtr; // not owned by this object! + StripePattern* pattern; // not owned by this object! + RemoteStorageTarget* rstPtr; // not owned by this object! + + // for deserialization + EntryInfo parentInfo; + RemoteStorageTarget rst; + struct { + std::unique_ptr pattern; + } parsed; + + public: + StripePattern& getPattern() + { + return *pattern; + } + + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rst; + } + + // getters & setters + unsigned getUserID() const + { + return userID; + } + + unsigned getGroupID() const + { + return groupID; + } + + int getMode() const + { + return mode; + } + + int getUmask() const + { + return umask; + } + + const EntryInfo* getParentInfo() const + { + return &this->parentInfo; + } + + const char* getNewFileName() const + { + return this->newFileName; + } + +}; + diff --git a/common/source/common/net/message/storage/creating/MkFileWithPatternRespMsg.h b/common/source/common/net/message/storage/creating/MkFileWithPatternRespMsg.h new file mode 100644 index 0000000..1be4572 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkFileWithPatternRespMsg.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +class MkFileWithPatternRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + MkFileWithPatternRespMsg(int result, EntryInfo* entryInfo) : + BaseType(NETMSGTYPE_MkFileWithPatternResp) + { + this->result = result; + this->entryInfoPtr = entryInfo; + } + + MkFileWithPatternRespMsg() : BaseType(NETMSGTYPE_MkFileWithPatternResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + int32_t result; + + // for serialization + EntryInfo* entryInfoPtr; + + // for deserialization + EntryInfo entryInfo; + + public: + + // inliners + + // getters & setters + int getResult() + { + return result; + } + + EntryInfo* getEntryInfo(void) + { + return this->entryInfoPtr; + } + +}; + diff --git a/common/source/common/net/message/storage/creating/MkLocalDirMsg.h b/common/source/common/net/message/storage/creating/MkLocalDirMsg.h new file mode 100644 index 0000000..1f44b6a --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkLocalDirMsg.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +class MkLocalDirMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + friend class TestSerialization; + + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + MkLocalDirMsg(EntryInfo* entryInfo, unsigned userID, unsigned groupID, int mode, + StripePattern* pattern, RemoteStorageTarget* rst, NumNodeID parentNodeID, + const CharVector& defaultACLXAttr, const CharVector& accessACLXAttr) : + BaseType(NETMSGTYPE_MkLocalDir), + defaultACLXAttr(defaultACLXAttr), accessACLXAttr(accessACLXAttr) + { + this->entryInfoPtr = entryInfo; + this->userID = userID; + this->groupID = groupID; + this->mode = mode; + this->pattern = pattern; + this->rstPtr = rst; + this->parentNodeID = parentNodeID; + } + + /** + * For deserialization only! + */ + MkLocalDirMsg() : BaseType(NETMSGTYPE_MkLocalDir) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->userID + % obj->groupID + % obj->mode + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->rstPtr, obj->rst) + % obj->parentNodeID + % obj->defaultACLXAttr + % obj->accessACLXAttr; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->dirTimestamps; + } + + bool supportsMirroring() const { return true; } + + private: + uint32_t userID; + uint32_t groupID; + int32_t mode; + NumNodeID parentNodeID; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object! + StripePattern* pattern; // not owned by this object! + RemoteStorageTarget* rstPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + RemoteStorageTarget rst; + struct { + std::unique_ptr pattern; + } parsed; + + // ACLs + CharVector defaultACLXAttr; + CharVector accessACLXAttr; + + protected: + MirroredTimestamps dirTimestamps; + + public: + StripePattern& getPattern() + { + return *pattern; + } + + void setPattern(StripePattern* pattern) + { + this->pattern = pattern; + } + + // getters & setters + RemoteStorageTarget* getRemoteStorageTarget() + { + return &this->rst; + } + + unsigned getUserID() const + { + return userID; + } + + unsigned getGroupID() const + { + return groupID; + } + + int getMode() const + { + return mode; + } + + NumNodeID getParentNodeID() const + { + return this->parentNodeID; + } + + EntryInfo* getEntryInfo() + { + return this->entryInfoPtr; + } + + const CharVector& getDefaultACLXAttr() const + { + return this->defaultACLXAttr; + } + + const CharVector& getAccessACLXAttr() const + { + return this->accessACLXAttr; + } + + void setDirTimestamps(const MirroredTimestamps& ts) { dirTimestamps = ts; } +}; + diff --git a/common/source/common/net/message/storage/creating/MkLocalDirRespMsg.h b/common/source/common/net/message/storage/creating/MkLocalDirRespMsg.h new file mode 100644 index 0000000..7848a7f --- /dev/null +++ b/common/source/common/net/message/storage/creating/MkLocalDirRespMsg.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class MkLocalDirRespMsg : public SimpleIntMsg +{ + public: + MkLocalDirRespMsg(FhgfsOpsErr result) : SimpleIntMsg(NETMSGTYPE_MkLocalDirResp, result) + { + } + + /** + * Constructor for deserialization only. + */ + MkLocalDirRespMsg() : SimpleIntMsg(NETMSGTYPE_MkLocalDirResp) + { + } + + + private: + + + public: + // getters & setters + + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/storage/creating/MoveFileInodeMsg.h b/common/source/common/net/message/storage/creating/MoveFileInodeMsg.h new file mode 100644 index 0000000..ec93f15 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MoveFileInodeMsg.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +class MoveFileInodeMsg : public MirroredMessageBase +{ + public: + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param createLink If True then also increment link count after deinlining inode + */ + MoveFileInodeMsg(EntryInfo* fromFileInfo, FileInodeMode mode, bool createLink = false) : + BaseType(NETMSGTYPE_MoveFileInode), moveMode(mode), createHardlink(createLink), + fromFileInfoPtr(fromFileInfo) + { + } + + MoveFileInodeMsg(EntryInfo* fromFileInfo) : + BaseType(NETMSGTYPE_MoveFileInode), moveMode(MODE_INVALID), fromFileInfoPtr(fromFileInfo) + { + } + + /** + * For deserialization only! + */ + MoveFileInodeMsg() : BaseType(NETMSGTYPE_MoveFileInode) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->moveMode + % obj->createHardlink + % serdes::backedPtr(obj->fromFileInfoPtr, obj->fromFileInfo); + } + + bool supportsMirroring() const { return true; } + + private: + int32_t moveMode; // specify what kind of move operation is needed + bool createHardlink; // specify whether new hardlink should be created or not + + // for serialization + EntryInfo* fromFileInfoPtr; + + // for deserialization + EntryInfo fromFileInfo; + + public: + FileInodeMode getMode() const + { + return static_cast(this->moveMode); + } + + bool getCreateHardlink() const + { + return this->createHardlink; + } + + EntryInfo* getFromFileEntryInfo() + { + return this->fromFileInfoPtr; + } +}; diff --git a/common/source/common/net/message/storage/creating/MoveFileInodeRespMsg.h b/common/source/common/net/message/storage/creating/MoveFileInodeRespMsg.h new file mode 100644 index 0000000..b5c9332 --- /dev/null +++ b/common/source/common/net/message/storage/creating/MoveFileInodeRespMsg.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +class MoveFileInodeRespMsg : public NetMessageSerdes +{ + public: + MoveFileInodeRespMsg(FhgfsOpsErr result, unsigned linkCount) : + BaseType(NETMSGTYPE_MoveFileInodeResp) + { + this->result = result; + this->linkCount = linkCount; + } + + /** + * Constructor for deserialization only + */ + MoveFileInodeRespMsg() : BaseType(NETMSGTYPE_MoveFileInodeResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->linkCount; + } + + FhgfsOpsErr getResult() const + { + return this->result; + } + + unsigned getHardlinkCount() const + { + return this->linkCount; + } + + private: + FhgfsOpsErr result; + unsigned linkCount; +}; diff --git a/common/source/common/net/message/storage/creating/RmChunkPathsMsg.h b/common/source/common/net/message/storage/creating/RmChunkPathsMsg.h new file mode 100644 index 0000000..eb8d001 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmChunkPathsMsg.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include + +#define RMCHUNKPATHSMSG_FLAG_BUDDYMIRROR 1 /* given targetID is a buddymirrorgroup ID */ + +class RmChunkPathsMsg : public NetMessageSerdes +{ + public: + RmChunkPathsMsg(uint16_t targetID, StringList* relativePaths) : + BaseType(NETMSGTYPE_RmChunkPaths) + { + this->targetID = targetID; + this->relativePaths = relativePaths; + } + + RmChunkPathsMsg() : BaseType(NETMSGTYPE_RmChunkPaths) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % serdes::backedPtr(obj->relativePaths, obj->parsed.relativePaths); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return RMCHUNKPATHSMSG_FLAG_BUDDYMIRROR; + } + + private: + uint16_t targetID; + + // for serialization + StringList* relativePaths; + + // for deserialization + struct { + StringList relativePaths; + } parsed; + + public: + uint16_t getTargetID() const + { + return targetID; + } + + StringList& getRelativePaths() + { + return *relativePaths; + } +}; + + diff --git a/common/source/common/net/message/storage/creating/RmChunkPathsRespMsg.h b/common/source/common/net/message/storage/creating/RmChunkPathsRespMsg.h new file mode 100644 index 0000000..cabaab5 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmChunkPathsRespMsg.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +class RmChunkPathsRespMsg : public NetMessageSerdes +{ + public: + RmChunkPathsRespMsg(StringList* failedPaths) : BaseType(NETMSGTYPE_RmChunkPathsResp) + { + this->failedPaths = failedPaths; + } + + RmChunkPathsRespMsg() : BaseType(NETMSGTYPE_RmChunkPathsResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->failedPaths, obj->parsed.failedPaths); + } + + private: + // for serialization + StringList* failedPaths; + + // for deserialization + struct { + StringList failedPaths; + } parsed; + + public: + StringList& getFailedPaths() + { + return *failedPaths; + } +}; + + diff --git a/common/source/common/net/message/storage/creating/RmDirEntryMsg.h b/common/source/common/net/message/storage/creating/RmDirEntryMsg.h new file mode 100644 index 0000000..f1bc6b7 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmDirEntryMsg.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include + + +class RmDirEntryMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param path just a reference, so do not free it as long as you use this object! + */ + RmDirEntryMsg(EntryInfo* parentInfo, std::string& entryName) : + BaseType(NETMSGTYPE_RmDirEntry), entryName(entryName) + { + this->parentInfoPtr = parentInfo; + + } + + RmDirEntryMsg() : BaseType(NETMSGTYPE_RmDirEntry) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::stringAlign4(obj->entryName); + } + + private: + + std::string entryName; + + // for serialization + EntryInfo* parentInfoPtr; + + // for deserialization + EntryInfo parentInfo; + + + + public: + + std::string getEntryName() const + { + return this->entryName; + } + + EntryInfo* getParentInfo() + { + return &this->parentInfo; + } + + // getters & setters + +}; + + diff --git a/common/source/common/net/message/storage/creating/RmDirEntryRespMsg.h b/common/source/common/net/message/storage/creating/RmDirEntryRespMsg.h new file mode 100644 index 0000000..a890e66 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmDirEntryRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class RmDirEntryRespMsg : public SimpleIntMsg +{ + public: + RmDirEntryRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RmDirEntryResp, result) + { + } + + RmDirEntryRespMsg() : SimpleIntMsg(NETMSGTYPE_RmDirEntryResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/creating/RmDirMsg.h b/common/source/common/net/message/storage/creating/RmDirMsg.h new file mode 100644 index 0000000..304c699 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmDirMsg.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include + +#define RMDIRMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +class RmDirMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param parentInfo just a reference, so do not free it as long as you use this object! + */ + RmDirMsg(EntryInfo* parentInfo, std::string& delDirName) : BaseType(NETMSGTYPE_RmDir) + { + this->parentInfoPtr = parentInfo; + this->delDirName = delDirName; + } + + RmDirMsg() : BaseType(NETMSGTYPE_RmDir) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::stringAlign4(obj->delDirName); + + if (obj->isMsgHeaderFeatureFlagSet(RMDIRMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->dirTimestamps; + } + + bool supportsMirroring() const { return true; } + + private: + std::string delDirName; + FileEvent fileEvent; + + // for serialization + EntryInfo* parentInfoPtr; + + + // for deserialization + EntryInfo parentInfo; + + protected: + MirroredTimestamps dirTimestamps; + + public: + + // inliners + + // getters & setters + EntryInfo* getParentInfo(void) + { + return &this->parentInfo; + } + + std::string getDelDirName(void) + { + return this->delDirName; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(RMDIRMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return RMDIRMSG_FLAG_HAS_EVENT; + } +}; + + diff --git a/common/source/common/net/message/storage/creating/RmDirRespMsg.h b/common/source/common/net/message/storage/creating/RmDirRespMsg.h new file mode 100644 index 0000000..ee73a8e --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmDirRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class RmDirRespMsg : public SimpleIntMsg +{ + public: + RmDirRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RmDirResp, result) + { + } + + RmDirRespMsg() : SimpleIntMsg(NETMSGTYPE_RmDirResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/creating/RmLocalDirMsg.h b/common/source/common/net/message/storage/creating/RmLocalDirMsg.h new file mode 100644 index 0000000..ed87ed1 --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmLocalDirMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +class RmLocalDirMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param delEntryInfo just a reference, so do not free it as long as you use this object! + */ + RmLocalDirMsg(EntryInfo* delEntryInfo) : + BaseType(NETMSGTYPE_RmLocalDir) + { + this->delEntryInfoPtr = delEntryInfo; + + } + + RmLocalDirMsg() : BaseType(NETMSGTYPE_RmLocalDir) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->delEntryInfoPtr, obj->delEntryInfo); + } + + private: + + // for serialization + EntryInfo* delEntryInfoPtr; + + // for deserialization + EntryInfo delEntryInfo; + + public: + // getters & setters + EntryInfo* getDelEntryInfo(void) + { + return &this->delEntryInfo; + } + + bool supportsMirroring() const { return true; } +}; + diff --git a/common/source/common/net/message/storage/creating/RmLocalDirRespMsg.h b/common/source/common/net/message/storage/creating/RmLocalDirRespMsg.h new file mode 100644 index 0000000..7aeeb3a --- /dev/null +++ b/common/source/common/net/message/storage/creating/RmLocalDirRespMsg.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class RmLocalDirRespMsg : public SimpleIntMsg +{ + public: + RmLocalDirRespMsg(FhgfsOpsErr result) : SimpleIntMsg(NETMSGTYPE_RmLocalDirResp, result) + { + } + + /** + * Constructor for deserialization only. + */ + RmLocalDirRespMsg() : SimpleIntMsg(NETMSGTYPE_RmLocalDirResp) + { + } + + + private: + + + public: + // getters & setters + + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/storage/creating/UnlinkFileMsg.h b/common/source/common/net/message/storage/creating/UnlinkFileMsg.h new file mode 100644 index 0000000..6b82091 --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkFileMsg.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define UNLINKFILEMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +class UnlinkFileMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param parentInfo just a reference, so do not free it as long as you use this object! + */ + UnlinkFileMsg(EntryInfo* parentInfo, std::string& delFileName) : + BaseType(NETMSGTYPE_UnlinkFile) + { + this->parentInfoPtr = parentInfo; + this->delFileName = delFileName; + } + + UnlinkFileMsg() : BaseType(NETMSGTYPE_UnlinkFile) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::stringAlign4(obj->delFileName); + + if (obj->isMsgHeaderFeatureFlagSet(UNLINKFILEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->dirTimestamps + % obj->fileInfo + % obj->fileTimestamps; + } + + bool supportsMirroring() const { return true; } + + private: + std::string delFileName; + FileEvent fileEvent; + + // for serialization + EntryInfo* parentInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo parentInfo; + + protected: + MirroredTimestamps dirTimestamps; + EntryInfo fileInfo; + MirroredTimestamps fileTimestamps; + + public: + // inliners + + // getters & setters + EntryInfo* getParentInfo() + { + return &this->parentInfo; + } + + std::string getDelFileName() const + { + return this->delFileName; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(UNLINKFILEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return UNLINKFILEMSG_FLAG_HAS_EVENT; + } +}; + + diff --git a/common/source/common/net/message/storage/creating/UnlinkFileRespMsg.h b/common/source/common/net/message/storage/creating/UnlinkFileRespMsg.h new file mode 100644 index 0000000..dbab5bf --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkFileRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class UnlinkFileRespMsg : public SimpleIntMsg +{ + public: + UnlinkFileRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_UnlinkFileResp, result) + { + } + + UnlinkFileRespMsg() : SimpleIntMsg(NETMSGTYPE_UnlinkFileResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeMsg.h b/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeMsg.h new file mode 100644 index 0000000..61731de --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeMsg.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +class UnlinkLocalFileInodeMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param delEntryInfo just a reference, so do not free it as long as you use this object! + */ + UnlinkLocalFileInodeMsg(EntryInfo* delEntryInfo) : + BaseType(NETMSGTYPE_UnlinkLocalFileInode), delEntryInfoPtr(delEntryInfo) + { + } + + UnlinkLocalFileInodeMsg() : BaseType(NETMSGTYPE_UnlinkLocalFileInode) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->delEntryInfoPtr, obj->delEntryInfo); + } + + private: + + // for serialization + EntryInfo* delEntryInfoPtr; + + // for deserialization + EntryInfo delEntryInfo; + + public: + // getters & setters + EntryInfo* getDelEntryInfo(void) + { + return &this->delEntryInfo; + } + + bool supportsMirroring() const { return true; } +}; + diff --git a/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeRespMsg.h b/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeRespMsg.h new file mode 100644 index 0000000..193443a --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkLocalFileInodeRespMsg.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +class UnlinkLocalFileInodeRespMsg : public NetMessageSerdes +{ + public: + UnlinkLocalFileInodeRespMsg(FhgfsOpsErr result, unsigned linkCount) : + BaseType(NETMSGTYPE_UnlinkLocalFileInodeResp) + { + this->result = result; + this->preUnlinkHardlinkCount = linkCount; + } + + /** + * Constructor for deserialization only. + */ + UnlinkLocalFileInodeRespMsg() : BaseType(NETMSGTYPE_UnlinkLocalFileInodeResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->preUnlinkHardlinkCount; + } + + // getters & setters + FhgfsOpsErr getResult() const + { + return this->result; + } + + unsigned getPreUnlinkHardlinkCount() const + { + return this->preUnlinkHardlinkCount; + } + + private: + FhgfsOpsErr result; + unsigned preUnlinkHardlinkCount; +}; diff --git a/common/source/common/net/message/storage/creating/UnlinkLocalFileMsg.h b/common/source/common/net/message/storage/creating/UnlinkLocalFileMsg.h new file mode 100644 index 0000000..03183d2 --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkLocalFileMsg.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include + + +#define UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR 1 /* given targetID is a buddymirrorgroup ID */ +#define UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 2 /* secondary of group, otherwise primary */ + + +class UnlinkLocalFileMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + + + /** + * @param entryID just a reference, so do not free it as long as you use this object! + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + UnlinkLocalFileMsg(std::string& entryID, uint16_t targetID, PathInfo* pathInfo) : + BaseType(NETMSGTYPE_UnlinkLocalFile) + { + this->entryID = entryID.c_str(); + this->entryIDLen = entryID.length(); + + this->targetID = targetID; + + this->pathInfoPtr = pathInfo; + } + + /** + * For deserialization only! + */ + UnlinkLocalFileMsg() : BaseType(NETMSGTYPE_UnlinkLocalFile) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::rawString(obj->entryID, obj->entryIDLen, 4) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo) + % obj->targetID; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR | UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND; + } + + + private: + unsigned entryIDLen; + const char* entryID; + uint16_t targetID; + + // for serialization + PathInfo* pathInfoPtr; + + // for deserialization + PathInfo pathInfo; + + + public: + + // getters & setters + + const char* getEntryID() const + { + return entryID; + } + + uint16_t getTargetID() const + { + return targetID; + } + + const PathInfo* getPathInfo() const + { + return &this->pathInfo; + } + +}; + + diff --git a/common/source/common/net/message/storage/creating/UnlinkLocalFileRespMsg.h b/common/source/common/net/message/storage/creating/UnlinkLocalFileRespMsg.h new file mode 100644 index 0000000..b8f6f06 --- /dev/null +++ b/common/source/common/net/message/storage/creating/UnlinkLocalFileRespMsg.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +class UnlinkLocalFileRespMsg : public SimpleIntMsg +{ + public: + UnlinkLocalFileRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_UnlinkLocalFileResp, result) + { + } + + UnlinkLocalFileRespMsg() : SimpleIntMsg(NETMSGTYPE_UnlinkLocalFileResp) + { + } + + + private: + + + public: + // inliners + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/storage/listing/ListChunkDirIncrementalMsg.h b/common/source/common/net/message/storage/listing/ListChunkDirIncrementalMsg.h new file mode 100644 index 0000000..4b76f33 --- /dev/null +++ b/common/source/common/net/message/storage/listing/ListChunkDirIncrementalMsg.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include + +class ListChunkDirIncrementalMsg : public NetMessageSerdes +{ + public: + ListChunkDirIncrementalMsg(uint16_t targetID, bool isMirror, const std::string& relativeDir, + int64_t offset, unsigned maxOutEntries, bool onlyFiles, bool ignoreNotExists) : + BaseType(NETMSGTYPE_ListChunkDirIncremental), targetID(targetID), isMirror(isMirror), + relativeDir(relativeDir), offset(offset), maxOutEntries(maxOutEntries), + onlyFiles(onlyFiles), ignoreNotExists(ignoreNotExists) { } + + /** + * For deserialization only + */ + ListChunkDirIncrementalMsg() : BaseType(NETMSGTYPE_ListChunkDirIncremental) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->relativeDir) + % obj->targetID + % obj->isMirror + % obj->offset + % obj->maxOutEntries + % obj->onlyFiles + % obj->ignoreNotExists; + } + + private: + uint16_t targetID; + bool isMirror; + std::string relativeDir; + int64_t offset; + uint32_t maxOutEntries; + bool onlyFiles; + bool ignoreNotExists; + + public: + uint16_t getTargetID() const { return targetID; } + bool getIsMirror() const { return isMirror; } + int64_t getOffset() const { return offset; } + unsigned getMaxOutEntries() const { return maxOutEntries; } + std::string getRelativeDir() const { return relativeDir; } + bool getOnlyFiles() const { return onlyFiles; } + bool getIgnoreNotExists() const { return ignoreNotExists; } +}; + + diff --git a/common/source/common/net/message/storage/listing/ListChunkDirIncrementalRespMsg.h b/common/source/common/net/message/storage/listing/ListChunkDirIncrementalRespMsg.h new file mode 100644 index 0000000..a8967b2 --- /dev/null +++ b/common/source/common/net/message/storage/listing/ListChunkDirIncrementalRespMsg.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include + +class ListChunkDirIncrementalRespMsg : public NetMessageSerdes +{ + public: + ListChunkDirIncrementalRespMsg(FhgfsOpsErr result, StringList* names, IntList* entryTypes, + int64_t newOffset) : BaseType(NETMSGTYPE_ListChunkDirIncrementalResp) + { + this->result = (int)result; + this->names = names; + this->entryTypes = entryTypes; + this->newOffset = newOffset; + } + + ListChunkDirIncrementalRespMsg() : BaseType(NETMSGTYPE_ListChunkDirIncrementalResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->newOffset + % serdes::backedPtr(obj->names, obj->parsed.names) + % serdes::backedPtr(obj->entryTypes, obj->parsed.entryTypes); + } + + private: + int32_t result; + int64_t newOffset; + + // for serialization + StringList* names; // not owned by this object! + IntList* entryTypes; // not owned by this object! + + // for deserialization + struct { + StringList names; + IntList entryTypes; + } parsed; + + public: + StringList& getNames() + { + return *names; + } + + IntList& getEntryTypes() + { + return *entryTypes; + } + + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)this->result; + } + + int64_t getNewOffset() + { + return this->newOffset; + } + +}; + diff --git a/common/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h b/common/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h new file mode 100644 index 0000000..6340437 --- /dev/null +++ b/common/source/common/net/message/storage/listing/ListDirFromOffsetMsg.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include + + +/** + * Incremental directory listing, returning a limited number of entries each time. + */ +class ListDirFromOffsetMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param serverOffset zero-based, in incremental calls use only values returned via + * ListDirFromOffsetResp here (because offset is not guaranteed to be 0, 1, 2, 3, ...). + * @param filterDots true if you don't want "." and ".." as names in the result list. + */ + ListDirFromOffsetMsg(EntryInfo* entryInfo, int64_t serverOffset, + unsigned maxOutNames, bool filterDots) : BaseType(NETMSGTYPE_ListDirFromOffset) + { + this->entryInfoPtr = entryInfo; + + this->serverOffset = serverOffset; + + this->maxOutNames = maxOutNames; + + this->filterDots = filterDots; + } + + /** + * For deserialization only + */ + ListDirFromOffsetMsg() : BaseType(NETMSGTYPE_ListDirFromOffset) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->serverOffset + % obj->maxOutNames + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % obj->filterDots; + } + + private: + int64_t serverOffset; + uint32_t maxOutNames; + bool filterDots; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + + + public: + // getters & setters + + int64_t getServerOffset() const + { + return serverOffset; + } + + unsigned getMaxOutNames() const + { + return maxOutNames; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + bool getFilterDots() const + { + return filterDots; + } + + +}; + + diff --git a/common/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h b/common/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h new file mode 100644 index 0000000..b8f0e3e --- /dev/null +++ b/common/source/common/net/message/storage/listing/ListDirFromOffsetRespMsg.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include + +class ListDirFromOffsetRespMsg : public NetMessageSerdes +{ + public: + ListDirFromOffsetRespMsg(FhgfsOpsErr result, StringList* names, UInt8List* entryTypes, + StringList* entryIDs, Int64List* serverOffsets, int64_t newServerOffset) : + BaseType(NETMSGTYPE_ListDirFromOffsetResp) + { + this->result = result; + this->names = names; + this->entryIDs = entryIDs; + this->entryTypes = entryTypes; + this->serverOffsets = serverOffsets; + this->newServerOffset = newServerOffset; + } + + ListDirFromOffsetRespMsg() : BaseType(NETMSGTYPE_ListDirFromOffsetResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->newServerOffset + % serdes::backedPtr(obj->serverOffsets, obj->parsed.serverOffsets) + % obj->result + % serdes::backedPtr(obj->entryTypes, obj->parsed.entryTypes) + % serdes::backedPtr(obj->entryIDs, obj->parsed.entryIDs) + % serdes::backedPtr(obj->names, obj->parsed.names); + + serdesCheck(obj, ctx); + } + + private: + int32_t result; + int64_t newServerOffset; + + // for serialization + StringList* names; // not owned by this object! + UInt8List* entryTypes; // not owned by this object! + StringList* entryIDs; // not owned by this object! + Int64List* serverOffsets; // not owned by this object! + + // for deserialization + struct { + StringList names; + UInt8List entryTypes; + StringList entryIDs; + Int64List serverOffsets; + } parsed; + + static void serdesCheck(const ListDirFromOffsetRespMsg*, Serializer&) {} + + static void serdesCheck(ListDirFromOffsetRespMsg* obj, Deserializer& des) + { + if(unlikely( + obj->entryTypes->size() != obj->names->size() + || obj->entryTypes->size() != obj->entryIDs->size() + || obj->entryTypes->size() != obj->serverOffsets->size() ) ) + { + LogContext(__func__).log(Log_WARNING, "Sanity check failed"); + LogContext(__func__).logBacktrace(); + des.setBad(); + } + } + + public: + StringList& getNames() + { + return *this->names; + } + + UInt8List& getEntryTypes() + { + return *this->entryTypes; + } + + StringList& getEntryIDs() + { + return *this->entryIDs; + } + + Int64List& getServerOffsets() + { + return *this->serverOffsets; + } + + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)this->result; + } + + int64_t getNewServerOffset() + { + return this->newServerOffset; + } + +}; + diff --git a/common/source/common/net/message/storage/lookup/FindLinkOwnerMsg.h b/common/source/common/net/message/storage/lookup/FindLinkOwnerMsg.h new file mode 100644 index 0000000..1a60ed5 --- /dev/null +++ b/common/source/common/net/message/storage/lookup/FindLinkOwnerMsg.h @@ -0,0 +1,34 @@ +#pragma once + +#include + +/** + * intended to be used to find the link owner of a given entry ID + * + * at the moment this is only a hint, because the link owner saved in entries + * is not guaranteed to be correct. + */ +class FindLinkOwnerMsg : public SimpleStringMsg +{ + public: + FindLinkOwnerMsg(std::string& entryID) : + SimpleStringMsg(NETMSGTYPE_FindLinkOwner, entryID.c_str()) + { + } + + FindLinkOwnerMsg() : SimpleStringMsg(NETMSGTYPE_FindLinkOwner) + { + } + + + private: + + + public: + // getters & setters + std::string getEntryID() + { + return std::string(getValue()); + } +}; + diff --git a/common/source/common/net/message/storage/lookup/FindLinkOwnerRespMsg.h b/common/source/common/net/message/storage/lookup/FindLinkOwnerRespMsg.h new file mode 100644 index 0000000..f2db7c9 --- /dev/null +++ b/common/source/common/net/message/storage/lookup/FindLinkOwnerRespMsg.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include +#include + +class FindLinkOwnerRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param result 1 if an error occurs, otherwise 0 + * @param linkownerNodeID the saved owner of the link + */ + FindLinkOwnerRespMsg(int result, NumNodeID linkOwnerNodeID, std::string &parentDirID) : + BaseType(NETMSGTYPE_FindLinkOwnerResp) + { + this->result = result; + this->linkOwnerNodeID = linkOwnerNodeID; + this->parentDirID = parentDirID.c_str(); + this->parentDirIDLen = strlen(this->parentDirID); + } + + /** + * For deserialization only + */ + FindLinkOwnerRespMsg() : BaseType(NETMSGTYPE_FindLinkOwnerResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::rawString(obj->parentDirID, obj->parentDirIDLen) + % obj->linkOwnerNodeID; + } + + private: + int32_t result; + NumNodeID linkOwnerNodeID; + unsigned parentDirIDLen; + const char* parentDirID; + + public: + + // inliners + + // getters & setters + int getResult() + { + return result; + } + + NumNodeID getLinkOwnerNodeID() + { + return linkOwnerNodeID; + } + + std::string getParentDirID() + { + return parentDirID; + } +}; + diff --git a/common/source/common/net/message/storage/lookup/FindOwnerMsg.h b/common/source/common/net/message/storage/lookup/FindOwnerMsg.h new file mode 100644 index 0000000..52fca47 --- /dev/null +++ b/common/source/common/net/message/storage/lookup/FindOwnerMsg.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +class FindOwnerMsg : public NetMessageSerdes +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param path just a reference, so do not free it as long as you use this object! + * @param entryID just a reference, so do not free it as long as you use this object! + */ + FindOwnerMsg(Path* path, unsigned searchDepth, EntryInfo* entryInfo, int currentDepth) + : BaseType(NETMSGTYPE_FindOwner) + { + this->path = path; + this->searchDepth = searchDepth; + + this->entryInfoPtr = entryInfo; + + this->currentDepth = currentDepth; + } + + FindOwnerMsg() : BaseType(NETMSGTYPE_FindOwner) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->searchDepth + % obj->currentDepth + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo) + % serdes::backedPtr(obj->path, obj->parsed.path); + } + + private: + uint32_t searchDepth; + uint32_t currentDepth; + + // for serialization + Path* path; // not owned by this object! + EntryInfo* entryInfoPtr; // not owned by this object! + + // for deserialization + struct { + Path path; + } parsed; + EntryInfo entryInfo; + + + public: + Path& getPath() + { + return *path; + } + + unsigned getSearchDepth() + { + return searchDepth; + } + + unsigned getCurrentDepth() + { + return currentDepth; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + +}; + + diff --git a/common/source/common/net/message/storage/lookup/FindOwnerRespMsg.h b/common/source/common/net/message/storage/lookup/FindOwnerRespMsg.h new file mode 100644 index 0000000..3f588b8 --- /dev/null +++ b/common/source/common/net/message/storage/lookup/FindOwnerRespMsg.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include + +class FindOwnerRespMsg : public NetMessageSerdes +{ + public: + + /** + * @param entryInfo just a reference, so do not free it as long as you use this object! + */ + FindOwnerRespMsg(int result, EntryInfoWithDepth* entryInfo) : + BaseType(NETMSGTYPE_FindOwnerResp), result(result), entryInfoPtr(entryInfo) + { + } + + FindOwnerRespMsg() : BaseType(NETMSGTYPE_FindOwnerResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + int32_t result; + + EntryInfoWithDepth* entryInfoPtr; // for serialization + EntryInfoWithDepth entryInfo; // for deserialization + + public: + + // inliners + + // getters & setters + int getResult() const + { + return result; + } + + EntryInfoWithDepth& getEntryInfo() + { + return entryInfo; + } +}; + diff --git a/common/source/common/net/message/storage/lookup/LookupIntentMsg.h b/common/source/common/net/message/storage/lookup/LookupIntentMsg.h new file mode 100644 index 0000000..5bcc2d5 --- /dev/null +++ b/common/source/common/net/message/storage/lookup/LookupIntentMsg.h @@ -0,0 +1,272 @@ +#pragma once + +#include +#include +#include +#include +#include + + +/* intentFlags as payload + Keep these flags in sync with the client msg flags: + fhgfs_client_mode/source/closed/net/message/storage/LookupIntentMsg.h */ + +#define LOOKUPINTENTMSG_FLAG_REVALIDATE 1 /* revalidate entry, cancel if invalid */ +#define LOOKUPINTENTMSG_FLAG_CREATE 2 /* create file */ +#define LOOKUPINTENTMSG_FLAG_CREATEEXCLUSIVE 4 /* exclusive file creation */ +#define LOOKUPINTENTMSG_FLAG_OPEN 8 /* open file */ +#define LOOKUPINTENTMSG_FLAG_STAT 16 /* stat file */ + + +// feature flags as header flags +#define LOOKUPINTENTMSG_FLAG_USE_QUOTA 1 /* if the message contains quota information */ +#define LOOKUPINTENTMSG_FLAG_HAS_EVENT 8 /* contains file event logging information */ + +class LookupIntentMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * This just prepares the basic lookup. Use the additional addIntent...() methods to do + * more than just the lookup. + * + * @param parentInfo just a reference, so do not free it as long as you use this object! + * @param entryName + */ + LookupIntentMsg(const EntryInfo* parentInfo, const std::string& entryName) : + BaseType(NETMSGTYPE_LookupIntent) + { + this->intentFlags = 0; + + this->parentInfoPtr = parentInfo; + this->entryName = entryName; + } + + /** + * Used for revalidate intent + * @param parentInfo just a reference, so do not free it as long as you use this object! + * @param entryInfo just a reference, so do not free it as long as you use this object! + * @param metaVersion + * + */ + LookupIntentMsg(const EntryInfo* parentInfo, const EntryInfo* entryInfo, uint32_t metaVersion) : + BaseType(NETMSGTYPE_LookupIntent) + { + this->intentFlags = LOOKUPINTENTMSG_FLAG_REVALIDATE; + + this->parentInfoPtr = parentInfo; + this->entryInfoPtr = entryInfo; + this->metaVersion = metaVersion; + } + + LookupIntentMsg() : BaseType(NETMSGTYPE_LookupIntent) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->intentFlags + % serdes::backedPtr(obj->parentInfoPtr, obj->parentInfo) + % serdes::stringAlign4(obj->entryName); + + if(obj->intentFlags & LOOKUPINTENTMSG_FLAG_REVALIDATE) + ctx + % obj->metaVersion + % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + + + if(obj->intentFlags & LOOKUPINTENTMSG_FLAG_OPEN) + ctx + % obj->accessFlags + % obj->sessionID; + + if(obj->intentFlags & LOOKUPINTENTMSG_FLAG_CREATE) + { + ctx + % obj->userID + % obj->groupID + % obj->mode + % obj->umask + % serdes::backedPtr(obj->preferredTargets, obj->parsed.preferredTargets); + + if (obj->isMsgHeaderFeatureFlagSet(LOOKUPINTENTMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->newEntryID + % serdes::backedPtr(obj->newStripePattern, obj->parsed.newStripePattern) + % obj->newOwnerFD + % obj->dirTimestamps; + } + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->fileTimestamps; + } + + bool supportsMirroring() const override { return true; } + + private: + int32_t intentFlags; // combination of LOOKUPINTENTMSG_FLAG_... + + // on revalidate retrieved from entryInfo in deserialization method + std::string entryName; // (lookup data) + + uint32_t userID; // (file creation data) + uint32_t groupID; // (file creation data) + int32_t mode; // file creation mode permission bits (file creation data) + int32_t umask; // umask of context (file creation data) + FileEvent fileEvent; + + NumNodeID sessionID; // (file open data) + uint32_t accessFlags; // OPENFILE_ACCESS_... flags (file open data) + + // for serialization + const UInt16List* preferredTargets; // not owned by this object! (file creation data) + const EntryInfo* parentInfoPtr; + const EntryInfo* entryInfoPtr; // only set on revalidate + + std::string newEntryID; + StripePattern* newStripePattern; + + // for deserialization + EntryInfo parentInfo; + EntryInfo entryInfo; // (revalidate data) + uint32_t metaVersion; // (revalidate data) + struct { + UInt16List preferredTargets; + std::unique_ptr newStripePattern; + } parsed; + + protected: + unsigned newOwnerFD; + MirroredTimestamps dirTimestamps; + MirroredTimestamps fileTimestamps; + + public: + void addIntentCreate(const unsigned userID, const unsigned groupID, const int mode, + const int umask, const UInt16List* preferredTargets) + { + this->intentFlags |= LOOKUPINTENTMSG_FLAG_CREATE; + + this->userID = userID; + this->groupID = groupID; + this->mode = mode; + this->umask = umask; + this->preferredTargets = preferredTargets; + } + + void addBuddyInfo(std::string newEntryID, StripePattern* newStripePattern) + { + this->newEntryID = std::move(newEntryID); + this->newStripePattern = newStripePattern; + } + + void addIntentCreateExclusive() + { + this->intentFlags |= LOOKUPINTENTMSG_FLAG_CREATEEXCLUSIVE; + } + + /** + * @param accessFlags OPENFILE_ACCESS_... flags + */ + void addIntentOpen(const NumNodeID sessionID, const unsigned accessFlags) + { + this->intentFlags |= LOOKUPINTENTMSG_FLAG_OPEN; + + this->sessionID = sessionID; + + this->accessFlags = accessFlags; + } + + void addIntentStat() + { + this->intentFlags |= LOOKUPINTENTMSG_FLAG_STAT; + } + + const UInt16List& getPreferredTargets() + { + return *preferredTargets; + } + + // getters & setters + + int getIntentFlags() const + { + return this->intentFlags; + } + + std::string getEntryName() + { + return this->entryName; + } + + + EntryInfo* getParentInfo() + { + return &this->parentInfo; + } + + EntryInfo* getEntryInfo() + { + return &this->entryInfo; + } + + uint32_t getMetaVersion() + { + return this->metaVersion; + } + + unsigned getUserID() const + { + return this->userID; + } + + unsigned getGroupID() const + { + return this->groupID; + } + + int getMode() const + { + return mode; + } + + int getUmask() const + { + return umask; + } + + NumNodeID getSessionID() const + { + return sessionID; + } + + unsigned getAccessFlags() const + { + return accessFlags; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(LOOKUPINTENTMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const override + { + return LOOKUPINTENTMSG_FLAG_USE_QUOTA | + LOOKUPINTENTMSG_FLAG_HAS_EVENT; + } + + const std::string& getNewEntryID() const { return newEntryID; } + StripePattern* getNewStripePattern() const { return newStripePattern; } +}; + diff --git a/common/source/common/net/message/storage/lookup/LookupIntentRespMsg.h b/common/source/common/net/message/storage/lookup/LookupIntentRespMsg.h new file mode 100644 index 0000000..d2d32cc --- /dev/null +++ b/common/source/common/net/message/storage/lookup/LookupIntentRespMsg.h @@ -0,0 +1,188 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +/* Keep these flags in sync with the client msg flags: + fhgfs_client_mode/source/closed/net/message/storage/LookupIntentRespMsg.h */ + +#define LOOKUPINTENTRESPMSG_FLAG_REVALIDATE 1 /* revalidate entry, cancel if invalid */ +#define LOOKUPINTENTRESPMSG_FLAG_CREATE 2 /* create file response */ +#define LOOKUPINTENTRESPMSG_FLAG_OPEN 4 /* open file response */ +#define LOOKUPINTENTRESPMSG_FLAG_STAT 8 /* stat file response */ + + +class LookupIntentRespMsg : public NetMessageSerdes +{ + public: + /** + * This just prepares the basic lookup response. Use the addResponse...() methods to + * add responses for intents. + * + * Serialization constructor. + * + * @param ownerNodeID just a reference, so do not free it as long as you use this object! + * @param entryID just a reference, so do not free it as long as you use this object! + */ + LookupIntentRespMsg(FhgfsOpsErr lookupResult) + : BaseType(NETMSGTYPE_LookupIntentResp) + { + this->responseFlags = 0; + + this->lookupResult = (int)lookupResult; + + this->createResult = FhgfsOpsErr_INTERNAL; + + this->entryInfoPtr = NULL; + } + + /** + * Deserialization constructor. + */ + LookupIntentRespMsg() : BaseType(NETMSGTYPE_LookupIntentResp) + { + this->lookupResult = FhgfsOpsErr_INTERNAL; + this->createResult = FhgfsOpsErr_INTERNAL; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->responseFlags + % obj->lookupResult; + + if(obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) + ctx + % obj->statResult + % obj->statData.serializeAs(StatDataFormat_NET); + + if(obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_REVALIDATE) + ctx % obj->revalidateResult; + + if(obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_CREATE) + ctx % obj->createResult; + + if(obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) + ctx + % obj->openResult + % serdes::rawString(obj->fileHandleID, obj->fileHandleIDLen, 4) + % serdes::backedPtr(obj->pattern, obj->parsed.pattern) + % serdes::backedPtr(obj->pathInfoPtr, obj->pathInfo); + + if( (obj->lookupResult == FhgfsOpsErr_SUCCESS || obj->createResult == FhgfsOpsErr_SUCCESS) + && (ctx.isReading() || likely(obj->entryInfoPtr) ) ) + ctx % serdes::backedPtr(obj->entryInfoPtr, obj->entryInfo); + } + + private: + int32_t responseFlags; // combination of LOOKUPINTENTRESPMSG_FLAG_... + + int32_t lookupResult; + + int32_t statResult; + StatData statData; + + int32_t revalidateResult; // FhgfsOpsErr_SUCCESS if still valid, any other value otherwise + + int32_t createResult; // FhgfsOpsErr_... + + int32_t openResult; + unsigned fileHandleIDLen; + const char* fileHandleID; + + // for serialization + EntryInfo* entryInfoPtr; // not owned by this object + StripePattern* pattern; // not owned by this object! (open file data) + PathInfo* pathInfoPtr; // not owned by this object! + + // for deserialization + EntryInfo entryInfo; + struct { + std::unique_ptr pattern; + } parsed; + PathInfo pathInfo; // (open file data) + + + public: + + // inliners + + void setLookupResult(FhgfsOpsErr lookupRes) + { + this->lookupResult = (int)lookupRes; + } + + void addResponseRevalidate(FhgfsOpsErr revalidateResult) + { + this->responseFlags |= LOOKUPINTENTRESPMSG_FLAG_REVALIDATE; + + this->revalidateResult = (int)revalidateResult; + } + + void addResponseCreate(FhgfsOpsErr createResult) + { + this->responseFlags |= LOOKUPINTENTRESPMSG_FLAG_CREATE; + + this->createResult = (int)createResult; + } + + void addResponseOpen(FhgfsOpsErr openResult, std::string& fileHandleID, + StripePattern* pattern, PathInfo* pathInfo) + { + this->responseFlags |= LOOKUPINTENTRESPMSG_FLAG_OPEN; + + this->openResult = (int)openResult; + + this->fileHandleID = fileHandleID.c_str(); + this->fileHandleIDLen = fileHandleID.length(); + + this->pattern = pattern; + this->pathInfoPtr = pathInfo; + } + + void addResponseStat(FhgfsOpsErr statResult, StatData* statData) + { + this->responseFlags |= LOOKUPINTENTRESPMSG_FLAG_STAT; + + this->statResult = (int)statResult; + this->statData = *statData; + } + + // getters & setters + + int getResponseFlags() const + { + return responseFlags; + } + + FhgfsOpsErr getLookupResult() const { return (FhgfsOpsErr)lookupResult; } + FhgfsOpsErr getCreateResult() const { return (FhgfsOpsErr)createResult; } + FhgfsOpsErr getOpenResult() const { return (FhgfsOpsErr)openResult; } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + StatData* getStatData(void) + { + return &this->statData; + } + + /* + * @param entryInfo not owned by this object + */ + void setEntryInfo(EntryInfo *entryInfo) + { + this->entryInfoPtr = entryInfo; + } + +}; + diff --git a/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsMsg.h b/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsMsg.h new file mode 100644 index 0000000..2171c56 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsMsg.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class GetMetaResyncStatsMsg : public SimpleMsg +{ + public: + GetMetaResyncStatsMsg() : SimpleMsg(NETMSGTYPE_GetMetaResyncStats) { } +}; + diff --git a/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsRespMsg.h b/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsRespMsg.h new file mode 100644 index 0000000..879b7e0 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/GetMetaResyncStatsRespMsg.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include + +class GetMetaResyncStatsRespMsg : public NetMessageSerdes +{ + public: + GetMetaResyncStatsRespMsg(MetaBuddyResyncJobStatistics* jobStats) : + BaseType(NETMSGTYPE_GetMetaResyncStatsResp), + jobStatsPtr(jobStats) + {} + + GetMetaResyncStatsRespMsg() : BaseType(NETMSGTYPE_FsckSetEventLoggingResp) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->jobStatsPtr, obj->jobStats); + } + + private: + MetaBuddyResyncJobStatistics* jobStatsPtr; + MetaBuddyResyncJobStatistics jobStats; + + public: + MetaBuddyResyncJobStatistics* getJobStats() + { + return &jobStats; + } + + void getJobStats(MetaBuddyResyncJobStatistics& outJobStats) + { + outJobStats = jobStats; + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsMsg.h b/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsMsg.h new file mode 100644 index 0000000..a2ec222 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class GetStorageResyncStatsMsg : public SimpleUInt16Msg +{ + public: + GetStorageResyncStatsMsg(uint16_t targetID): + SimpleUInt16Msg(NETMSGTYPE_GetStorageResyncStats, targetID) + { + } + + uint16_t getTargetID() const + { + return getValue(); + } + + protected: + GetStorageResyncStatsMsg(): SimpleUInt16Msg(NETMSGTYPE_GetStorageResyncStats) + { + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsRespMsg.h b/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsRespMsg.h new file mode 100644 index 0000000..91d18b4 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/GetStorageResyncStatsRespMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + +class GetStorageResyncStatsRespMsg : public NetMessageSerdes +{ + public: + /* + * @param jobStats not owned by this object! + */ + GetStorageResyncStatsRespMsg(StorageBuddyResyncJobStatistics* jobStats) : + BaseType(NETMSGTYPE_GetStorageResyncStatsResp) + { + this->jobStatsPtr = jobStats; + } + + /** + * For deserialization only! + */ + GetStorageResyncStatsRespMsg() : BaseType(NETMSGTYPE_GetStorageResyncStatsResp) + {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % serdes::backedPtr(obj->jobStatsPtr, obj->jobStats); + } + + private: + // for serialization + StorageBuddyResyncJobStatistics* jobStatsPtr; + + // for deserialization + StorageBuddyResyncJobStatistics jobStats; + + public: + // getters & setters + StorageBuddyResyncJobStatistics* getJobStats() + { + return &jobStats; + } + + void getJobStats(StorageBuddyResyncJobStatistics& outStats) + { + outStats = jobStats; + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/MirrorMetadataMsg.h b/common/source/common/net/message/storage/mirroring/MirrorMetadataMsg.h new file mode 100644 index 0000000..532b78d --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/MirrorMetadataMsg.h @@ -0,0 +1,49 @@ +#pragma once + +#include + + +class MirrorerTask; // forward declaration +typedef std::list MirrorerTaskList; // forward declaration + + +/** + * Note: This msg is abstract. It does not implement (de)serialization, because we would need to + * move class MirrorerTask to fhgfs_common for that, which is difficult, because MirrorerTask.cpp + * needs data from fhgfs_meta (e.g. config values). So fhgfs_meta implements (de)serialization + * in its derived class MirrorMetadataMsgEx. That is ok as long as no other services need to + * send/recv this msg. + */ +class MirrorMetadataMsg : public NetMessage +{ + friend class AbstractNetMessageFactory; + + public: + + protected: + /** + * @param taskList just a reference, so do not free it as long as you use this object + * @param taskListNumElems number of all elements in list + * @param taskListSerialLen serial length of all elements in list + */ + MirrorMetadataMsg(MirrorerTaskList* taskList, unsigned taskListNumElems, + unsigned taskListSerialLen) : NetMessage(NETMSGTYPE_MirrorMetadata) + { + this->taskList = taskList; + this->taskListNumElems = taskListNumElems; + this->taskListSerialLen = taskListSerialLen; + } + + + /** + * For deserialization only + */ + MirrorMetadataMsg() : NetMessage(NETMSGTYPE_MirrorMetadata) {} + + // for serialization + MirrorerTaskList* taskList; // not owned by this object + unsigned taskListNumElems; // number of elements in list + unsigned taskListSerialLen; // serial length of all elements in list +}; + + diff --git a/common/source/common/net/message/storage/mirroring/MirrorMetadataRespMsg.h b/common/source/common/net/message/storage/mirroring/MirrorMetadataRespMsg.h new file mode 100644 index 0000000..633d885 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/MirrorMetadataRespMsg.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include + + +class MirrorMetadataRespMsg : public SimpleIntMsg +{ + public: + MirrorMetadataRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_MirrorMetadataResp, result) + { + } + + /** + * For deserialization only + */ + MirrorMetadataRespMsg() : SimpleIntMsg(NETMSGTYPE_MirrorMetadataResp) + { + } + + private: + + public: + // getters & setters + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/mirroring/ResyncLocalFileMsg.h b/common/source/common/net/message/storage/mirroring/ResyncLocalFileMsg.h new file mode 100644 index 0000000..bbc27ac --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/ResyncLocalFileMsg.h @@ -0,0 +1,136 @@ +#pragma once + +#include +#include +#include + +#define RESYNCLOCALFILEMSG_FLAG_SETATTRIBS 1 /* message contains SettableFileAttrib object, which + should be applied to the chunk */ +#define RESYNCLOCALFILEMSG_FLAG_NODATA 2 /* do not sync data, i.e. dataBuf, offset, count are + ignored */ +#define RESYNCLOCALFILEMSG_FLAG_TRUNC 4 /* truncate after write; cannot be used together with + RESYNCLOCALFILEMSG_FLAG_NODATA */ +#define RESYNCLOCALFILEMSG_CHECK_SPARSE 8 /* check if incoming data has sparse areas */ + +#define RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR 16 /*check if incoming data should be synced to buddy mirror directory*/ + +#define RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND 32 /*check if data should be written to secondary*/ + +#define RESYNCLOCALFILEMSG_FLAG_CHUNKBALANCE_BUDDYMIRROR 64 /*check if data should be written to both primary and secondary*/ + + +#define RESYNCER_SPARSE_BLOCK_SIZE 4096 //4K + +class ResyncLocalFileMsg : public NetMessageSerdes +{ + public: + /* + * @param dataBuf the actual data to be written;may be NULL if RESYNCLOCALFILEMSG_FLAG_NODATA + * @param relativePathStr path to chunk, relative to buddy mirror directory + * @param resyncToTargetID + * @param offset + * @param count amount of data to write from dataBuf + * @param chunkAttribs may be NULL, but only if FLAG_SETATTRIBS is not set + */ + ResyncLocalFileMsg(const char* dataBuf, std::string& relativePathStr, + uint16_t resyncToTargetID, int64_t offset, int count, SettableFileAttribs* chunkAttribs = + NULL) : + BaseType(NETMSGTYPE_ResyncLocalFile) + { + this->dataBuf = dataBuf; + + this->relativePathStr = relativePathStr; + + this->resyncToTargetID = resyncToTargetID; + this->offset = offset; + this->count = count; + + if (chunkAttribs) + this->chunkAttribs = *chunkAttribs; + } + + /** + * For deserialization only! + */ + ResyncLocalFileMsg() : BaseType(NETMSGTYPE_ResyncLocalFile) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::stringAlign4(obj->relativePathStr) + % obj->resyncToTargetID + % obj->offset + % obj->count; + + if(obj->isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_SETATTRIBS) ) + ctx + % obj->chunkAttribs.mode + % obj->chunkAttribs.userID + % obj->chunkAttribs.groupID; + + ctx + % serdes::rawBlock(obj->dataBuf, obj->count); + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return RESYNCLOCALFILEMSG_FLAG_SETATTRIBS | RESYNCLOCALFILEMSG_FLAG_NODATA | + RESYNCLOCALFILEMSG_FLAG_TRUNC | RESYNCLOCALFILEMSG_CHECK_SPARSE | RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR | RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND | + RESYNCLOCALFILEMSG_FLAG_CHUNKBALANCE_BUDDYMIRROR; + } + + private: + const char* dataBuf; + + std::string relativePathStr; + uint16_t resyncToTargetID; + int64_t offset; + uint32_t count; + + SettableFileAttribs chunkAttribs; + + public: + // getters & setters + + void setChunkAttribs(SettableFileAttribs& chunkAttribs) + { + this->chunkAttribs = chunkAttribs; + } + + const char* getDataBuf() const + { + return dataBuf; + } + + uint16_t getResyncToTargetID() const + { + return resyncToTargetID; + } + + int64_t getOffset() const + { + return offset; + } + + void setOffset(int64_t offset) + { + this->offset = offset; + } + + int getCount() const + { + return count; + } + + std::string getRelativePathStr() const + { + return relativePathStr; + } + + SettableFileAttribs* getChunkAttribs() + { + return &chunkAttribs; + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/ResyncLocalFileRespMsg.h b/common/source/common/net/message/storage/mirroring/ResyncLocalFileRespMsg.h new file mode 100644 index 0000000..ac9a614 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/ResyncLocalFileRespMsg.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class ResyncLocalFileRespMsg : public SimpleIntMsg +{ + public: + ResyncLocalFileRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_ResyncLocalFileResp, result) + { + } + + ResyncLocalFileRespMsg() : + SimpleIntMsg(NETMSGTYPE_ResyncLocalFileResp) + { + } + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/ResyncRawInodesRespMsg.h b/common/source/common/net/message/storage/mirroring/ResyncRawInodesRespMsg.h new file mode 100644 index 0000000..b330b8b --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/ResyncRawInodesRespMsg.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class ResyncRawInodesRespMsg : public SimpleIntMsg +{ + public: + ResyncRawInodesRespMsg(FhgfsOpsErr result) + : SimpleIntMsg(NETMSGTYPE_ResyncRawInodesResp, result) { } + + ResyncRawInodesRespMsg() + : SimpleIntMsg(NETMSGTYPE_ResyncRawInodesResp) { } + + FhgfsOpsErr getResult() { return static_cast(getValue()); } +}; + diff --git a/common/source/common/net/message/storage/mirroring/ResyncSessionStoreMsg.h b/common/source/common/net/message/storage/mirroring/ResyncSessionStoreMsg.h new file mode 100644 index 0000000..3f30caf --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/ResyncSessionStoreMsg.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include + +#include + +class ResyncSessionStoreMsg : public NetMessageSerdes +{ + public: + typedef FhgfsOpsErr (*GetStoreBufFn)(void* context); + + ResyncSessionStoreMsg(char* sessionStoreBuf, size_t sessionStoreBufSize) : + BaseType(NETMSGTYPE_ResyncSessionStore), + sessionStoreBuf(sessionStoreBuf), + sessionStoreBufSize(sessionStoreBufSize) + { } + + ResyncSessionStoreMsg() : BaseType(NETMSGTYPE_ResyncSessionStore) { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->sessionStoreBufSize; + } + + private: + char* sessionStoreBuf; + size_t sessionStoreBufSize; + + struct { + std::unique_ptr sessionStoreBuf; + } parsed; + + static FhgfsOpsErr streamToSocketFn(Socket* socket, void* context) + { + ResyncSessionStoreMsg* msg = static_cast(context); + socket->send(msg->sessionStoreBuf, msg->sessionStoreBufSize, 0); + return FhgfsOpsErr_SUCCESS; + } + + public: + std::pair getSessionStoreBuf() + { + return std::pair(sessionStoreBuf, sessionStoreBufSize); + } + + FhgfsOpsErr receiveStoreBuf(Socket* socket, int connMsgShortTimeout) + { + parsed.sessionStoreBuf.reset(new (std::nothrow)char[sessionStoreBufSize]); + if (!parsed.sessionStoreBuf) + return FhgfsOpsErr_OUTOFMEM; + + sessionStoreBuf = parsed.sessionStoreBuf.get(); + + ssize_t received = socket->recvExactT(sessionStoreBuf, sessionStoreBufSize, + 0, connMsgShortTimeout); + + if (received < 0 || received < (ssize_t)sessionStoreBufSize) + return FhgfsOpsErr_COMMUNICATION; + + return FhgfsOpsErr_SUCCESS; + } + + void registerStreamoutHook(RequestResponseArgs& rrArgs) + { + rrArgs.sendExtraData = &streamToSocketFn; + rrArgs.extraDataContext = this; + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/ResyncSessionStoreRespMsg.h b/common/source/common/net/message/storage/mirroring/ResyncSessionStoreRespMsg.h new file mode 100644 index 0000000..d8670eb --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/ResyncSessionStoreRespMsg.h @@ -0,0 +1,19 @@ +#pragma once + +#include + +class ResyncSessionStoreRespMsg : public SimpleIntMsg +{ + public: + ResyncSessionStoreRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_ResyncSessionStoreResp, result) { } + + ResyncSessionStoreRespMsg() : SimpleIntMsg(NETMSGTYPE_ResyncSessionStoreResp) { } + + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideMsg.h b/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideMsg.h new file mode 100644 index 0000000..b5f92f0 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideMsg.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +class SetLastBuddyCommOverrideMsg : public NetMessageSerdes +{ + public: + + /** + * @param targetID + * @param timestamp + * @param abortResync if a resync is already running for that target abort it, so that it + * restarts with the new timestamp file + */ + SetLastBuddyCommOverrideMsg(uint16_t targetID, int64_t timestamp, bool abortResync) : + BaseType(NETMSGTYPE_SetLastBuddyCommOverride), targetID(targetID), timestamp(timestamp), + abortResync(abortResync) + { + } + + /** + * For deserialization only! + */ + SetLastBuddyCommOverrideMsg() : BaseType(NETMSGTYPE_SetLastBuddyCommOverride) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % obj->timestamp + % obj->abortResync; + } + + private: + uint16_t targetID; + int64_t timestamp; + bool abortResync; + + public: + // getters & setters + uint16_t getTargetID() const + { + return targetID; + } + + int64_t getTimestamp() const + { + return timestamp; + } + + bool getAbortResync() const + { + return abortResync; + } + +}; + diff --git a/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideRespMsg.h b/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideRespMsg.h new file mode 100644 index 0000000..7bdb063 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/SetLastBuddyCommOverrideRespMsg.h @@ -0,0 +1,30 @@ +#pragma once + +#include + +class SetLastBuddyCommOverrideRespMsg : public SimpleIntMsg +{ + public: + + /** + * @param result + */ + SetLastBuddyCommOverrideRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_SetLastBuddyCommOverrideResp, result) + { + } + + /** + * For deserialization only! + */ + SetLastBuddyCommOverrideRespMsg() : SimpleIntMsg(NETMSGTYPE_SetLastBuddyCommOverrideResp) {} + + public: + // getters & setters + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)getValue(); + } + +}; + diff --git a/common/source/common/net/message/storage/mirroring/SetMetadataMirroringMsg.h b/common/source/common/net/message/storage/mirroring/SetMetadataMirroringMsg.h new file mode 100644 index 0000000..2d8ec19 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/SetMetadataMirroringMsg.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include + + +/** + * Enable metadata mirroring on the root directory. + */ +class SetMetadataMirroringMsg : public SimpleMsg +{ + public: + SetMetadataMirroringMsg() : SimpleMsg(NETMSGTYPE_SetMetadataMirroring) + { + } +}; + + diff --git a/common/source/common/net/message/storage/mirroring/SetMetadataMirroringRespMsg.h b/common/source/common/net/message/storage/mirroring/SetMetadataMirroringRespMsg.h new file mode 100644 index 0000000..dc73097 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/SetMetadataMirroringRespMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + + +class SetMetadataMirroringRespMsg : public SimpleIntMsg +{ + public: + SetMetadataMirroringRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_SetMetadataMirroringResp, result) + { + } + + /** + * For deserialization only + */ + SetMetadataMirroringRespMsg() : SimpleIntMsg(NETMSGTYPE_SetMetadataMirroringResp) + { + } + + + private: + + public: + // getters & setters + FhgfsOpsErr getResult() + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/mirroring/StorageResyncStartedMsg.h b/common/source/common/net/message/storage/mirroring/StorageResyncStartedMsg.h new file mode 100644 index 0000000..4f01322 --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/StorageResyncStartedMsg.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class StorageResyncStartedMsg : public SimpleUInt16Msg +{ + public: + StorageResyncStartedMsg(uint16_t buddyTargetID): + SimpleUInt16Msg(NETMSGTYPE_StorageResyncStarted, buddyTargetID) + { + } + + StorageResyncStartedMsg(): SimpleUInt16Msg(NETMSGTYPE_StorageResyncStarted) + { + } +}; + diff --git a/common/source/common/net/message/storage/mirroring/StorageResyncStartedRespMsg.h b/common/source/common/net/message/storage/mirroring/StorageResyncStartedRespMsg.h new file mode 100644 index 0000000..243d34d --- /dev/null +++ b/common/source/common/net/message/storage/mirroring/StorageResyncStartedRespMsg.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +class StorageResyncStartedRespMsg : public SimpleMsg +{ + public: + StorageResyncStartedRespMsg() : SimpleMsg(NETMSGTYPE_StorageResyncStartedResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/moving/MovingDirInsertMsg.h b/common/source/common/net/message/storage/moving/MovingDirInsertMsg.h new file mode 100644 index 0000000..a70c8ea --- /dev/null +++ b/common/source/common/net/message/storage/moving/MovingDirInsertMsg.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +class MovingDirInsertMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param path just a reference, so do not free it as long as you use this object! + * @param serialBuf serialized dir-link; + * just a reference, so do not free it as long as you use this object! + */ + MovingDirInsertMsg(EntryInfo* toDirInfo, const std::string& newName, + const char* serialBuf, unsigned serialBufLen) + : BaseType(NETMSGTYPE_MovingDirInsert), + serialBuf(serialBuf), + serialBufLen(serialBufLen), + newName(newName), + toDirInfoPtr(toDirInfo) + { } + + MovingDirInsertMsg() : BaseType(NETMSGTYPE_MovingDirInsert) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->toDirInfoPtr, obj->toDirInfo) + % serdes::stringAlign4(obj->newName) + % obj->serialBufLen + % serdes::rawBlock(obj->serialBuf, obj->serialBufLen); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx % obj->dirTimestamps; + } + + bool supportsMirroring() const { return true; } + + private: + const char* serialBuf; + uint32_t serialBufLen; + + std::string newName; + + // for serialization + EntryInfo* toDirInfoPtr; + + // for deserialization + EntryInfo toDirInfo; + + protected: + MirroredTimestamps dirTimestamps; + + public: + + EntryInfo* getToDirInfo(void) + { + return &this->toDirInfo; + } + + const std::string& getNewName(void) + { + return this->newName; + } + + const char* getSerialBuf(void) + { + return this->serialBuf; + } + + uint32_t getSerialBufLen() const { return serialBufLen; } +}; + diff --git a/common/source/common/net/message/storage/moving/MovingDirInsertRespMsg.h b/common/source/common/net/message/storage/moving/MovingDirInsertRespMsg.h new file mode 100644 index 0000000..d20afce --- /dev/null +++ b/common/source/common/net/message/storage/moving/MovingDirInsertRespMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +class MovingDirInsertRespMsg : public SimpleIntMsg +{ + public: + MovingDirInsertRespMsg(FhgfsOpsErr result) : + SimpleIntMsg(NETMSGTYPE_MovingDirInsertResp, result) + { + } + + /** + * Constructor for deserialization only. + */ + MovingDirInsertRespMsg() : SimpleIntMsg(NETMSGTYPE_MovingDirInsertResp) + { + } + + + private: + + + public: + // getters & setters + + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)getValue(); + } +}; + + diff --git a/common/source/common/net/message/storage/moving/MovingFileInsertMsg.h b/common/source/common/net/message/storage/moving/MovingFileInsertMsg.h new file mode 100644 index 0000000..0f6c508 --- /dev/null +++ b/common/source/common/net/message/storage/moving/MovingFileInsertMsg.h @@ -0,0 +1,109 @@ +#pragma once + +#include +#include +#include +#include + +#define MOVINGFILEINSERTMSG_FLAG_HAS_XATTRS 1 + +/** + * Used to tell a remote metadata server a file is to be moved between directories + */ +class MovingFileInsertMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + /** + * @param path just a reference, so do not free it as long as you use this object! + * @param serialBuf serialized file; + * just a reference, so do not free it as long as you use this object! + */ + MovingFileInsertMsg(EntryInfo* fromFileInfo, EntryInfo* toDirInfo, std::string& newName, + const char* serialBuf, unsigned serialBufLen) : BaseType(NETMSGTYPE_MovingFileInsert) + { + this->fromFileInfoPtr = fromFileInfo; + this->newName = newName; + this->toDirInfoPtr = toDirInfo; + + this->serialBuf = serialBuf; + this->serialBufLen = serialBufLen; + } + + MovingFileInsertMsg() : BaseType(NETMSGTYPE_MovingFileInsert) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::backedPtr(obj->fromFileInfoPtr, obj->fromFileInfo) + % serdes::backedPtr(obj->toDirInfoPtr, obj->toDirInfo) + % serdes::stringAlign4(obj->newName) + % obj->serialBufLen + % serdes::rawBlock(obj->serialBuf, obj->serialBufLen); + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->dirTimestamps + % obj->fileTimestamps; + } + + bool supportsMirroring() const { return true; } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return MOVINGFILEINSERTMSG_FLAG_HAS_XATTRS; + } + + private: + EntryInfo fromFileInfo; // for deserialization + EntryInfo* fromFileInfoPtr; // for serialization + + std::string newName; + + const char* serialBuf; + uint32_t serialBufLen; + + EntryInfo* toDirInfoPtr; // for serialization + EntryInfo toDirInfo; // for deserialization + + protected: + MirroredTimestamps dirTimestamps; + MirroredTimestamps fileTimestamps; + + public: + + // getters & setters + const char* getSerialBuf() + { + return serialBuf; + } + + uint32_t getSerialBufLen() const { return serialBufLen; } + + EntryInfo* getToDirInfo() + { + return &this->toDirInfo; + } + + std::string getNewName() + { + return this->newName; + } + + EntryInfo* getFromFileInfo() + { + return &this->fromFileInfo; + } + + void registerStreamoutHook(RequestResponseArgs& rrArgs, MsgHelperXAttr::StreamXAttrState& ctx) + { + addMsgHeaderFeatureFlag(MOVINGFILEINSERTMSG_FLAG_HAS_XATTRS); + rrArgs.sendExtraData = &MsgHelperXAttr::StreamXAttrState::streamXattrFn; + rrArgs.extraDataContext = &ctx; + } +}; + diff --git a/common/source/common/net/message/storage/moving/MovingFileInsertRespMsg.h b/common/source/common/net/message/storage/moving/MovingFileInsertRespMsg.h new file mode 100644 index 0000000..bce47c4 --- /dev/null +++ b/common/source/common/net/message/storage/moving/MovingFileInsertRespMsg.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + + +class MovingFileInsertRespMsg : public NetMessageSerdes +{ + public: + /** + * @param inodeBuf inode of overwritten file, might be NULL if none (and inodeBufLen must also + * be 0 in that case). + */ + MovingFileInsertRespMsg(FhgfsOpsErr result, unsigned inodeBufLen, char* inodeBuf, EntryInfo entryInfo) : + BaseType(NETMSGTYPE_MovingFileInsertResp) + { + this->result = result; + this->inodeBufLen = inodeBufLen; + this->inodeBuf = inodeBuf; + this->overWrittenEntryInfo = entryInfo; + } + + /** + * Constructor for deserialization only. + */ + MovingFileInsertRespMsg() : BaseType(NETMSGTYPE_MovingFileInsertResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->inodeBufLen + % serdes::rawBlock(obj->inodeBuf, obj->inodeBufLen) + % obj->overWrittenEntryInfo; + } + + private: + int32_t result; + uint32_t inodeBufLen; // might be 0 if there was no file overwritten + const char* inodeBuf; // might be NULL if there was no file overwritten + EntryInfo overWrittenEntryInfo; // might contain default values (not to be used) if + // no file was overwritten + + public: + // getters & setters + + FhgfsOpsErr getResult() const + { + return (FhgfsOpsErr)this->result; + } + + unsigned getInodeBufLen() const + { + return this->inodeBufLen; + } + + const char* getInodeBuf() const + { + return this->inodeBuf; + } + + EntryInfo* getOverWrittenEntryInfo() + { + return &overWrittenEntryInfo; + } +}; + diff --git a/common/source/common/net/message/storage/moving/RenameMsg.h b/common/source/common/net/message/storage/moving/RenameMsg.h new file mode 100644 index 0000000..8f2cd09 --- /dev/null +++ b/common/source/common/net/message/storage/moving/RenameMsg.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include + +#define RENAMEMSG_FLAG_HAS_EVENT 1 /* contains file event logging information */ + +class RenameMsg : public MirroredMessageBase +{ + friend class AbstractNetMessageFactory; + + public: + + /** + * @param fromDirInfo just a reference, so do not free it as long as you use this object! + * @param toDirInfo just a reference, so do not free it as long as you use this object! + */ + RenameMsg(std::string& oldName, EntryInfo* fromDirInfo, std::string& newName, + DirEntryType entryType, EntryInfo* toDirInfo) : BaseType(NETMSGTYPE_Rename) + { + this->oldName = oldName; + this->entryType = entryType; + this->fromDirInfoPtr = fromDirInfo; + + this->newName = newName; + this->toDirInfoPtr = toDirInfo; + } + + RenameMsg() : BaseType(NETMSGTYPE_Rename) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->entryType) + % serdes::backedPtr(obj->fromDirInfoPtr, obj->fromDirInfo) + % serdes::backedPtr(obj->toDirInfoPtr, obj->toDirInfo) + % serdes::stringAlign4(obj->oldName) + % serdes::stringAlign4(obj->newName); + + if (obj->isMsgHeaderFeatureFlagSet(RENAMEMSG_FLAG_HAS_EVENT)) + ctx % obj->fileEvent; + + if (obj->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + ctx + % obj->fromDirTimestamps + % obj->renamedInodeTimestamps; + } + + bool supportsMirroring() const { return true; } + + private: + std::string oldName; + std::string newName; + + DirEntryType entryType; + FileEvent fileEvent; + + // for serialization + EntryInfo* fromDirInfoPtr; + EntryInfo* toDirInfoPtr; + + // for deserialization + EntryInfo fromDirInfo; + EntryInfo toDirInfo; + + protected: + MirroredTimestamps fromDirTimestamps; + MirroredTimestamps renamedInodeTimestamps; + + public: + + // inliners + + // getters & setters + + EntryInfo* getFromDirInfo(void) + { + return &this->fromDirInfo; + } + + EntryInfo* getToDirInfo(void) + { + return &this->toDirInfo; + } + + std::string getOldName(void) + { + return this->oldName; + } + + std::string getNewName(void) + { + return this->newName; + } + + DirEntryType getEntryType(void) + { + return this->entryType; + } + + const FileEvent* getFileEvent() const + { + if (isMsgHeaderFeatureFlagSet(RENAMEMSG_FLAG_HAS_EVENT)) + return &fileEvent; + else + return nullptr; + } + + unsigned getSupportedHeaderFeatureFlagsMask() const + { + return RENAMEMSG_FLAG_HAS_EVENT; } +}; + + diff --git a/common/source/common/net/message/storage/moving/RenameRespMsg.h b/common/source/common/net/message/storage/moving/RenameRespMsg.h new file mode 100644 index 0000000..e7b6544 --- /dev/null +++ b/common/source/common/net/message/storage/moving/RenameRespMsg.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class RenameRespMsg : public SimpleIntMsg +{ + public: + RenameRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_RenameResp, result) + { + } + + RenameRespMsg() : SimpleIntMsg(NETMSGTYPE_RenameResp) + { + } +}; + + diff --git a/common/source/common/net/message/storage/quota/GetDefaultQuotaMsg.h b/common/source/common/net/message/storage/quota/GetDefaultQuotaMsg.h new file mode 100644 index 0000000..aaed756 --- /dev/null +++ b/common/source/common/net/message/storage/quota/GetDefaultQuotaMsg.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +class GetDefaultQuotaMsg : public NetMessageSerdes +{ + public: + /** + * @param storagePoolId request information for this storage pool + */ + GetDefaultQuotaMsg(StoragePoolId storagePoolId): + BaseType(NETMSGTYPE_GetDefaultQuota), storagePoolId(storagePoolId) + { } + + /** + * For deserialization only + */ + GetDefaultQuotaMsg(): + BaseType(NETMSGTYPE_GetDefaultQuota) + { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->storagePoolId; + } + + protected: + StoragePoolId storagePoolId; +}; + diff --git a/common/source/common/net/message/storage/quota/GetDefaultQuotaRespMsg.h b/common/source/common/net/message/storage/quota/GetDefaultQuotaRespMsg.h new file mode 100644 index 0000000..6759306 --- /dev/null +++ b/common/source/common/net/message/storage/quota/GetDefaultQuotaRespMsg.h @@ -0,0 +1,37 @@ +#pragma once + + +#include +#include + + + +class GetDefaultQuotaRespMsg: public NetMessageSerdes +{ + public: + /** + * For deserialization only + */ + GetDefaultQuotaRespMsg() : BaseType(NETMSGTYPE_GetDefaultQuotaResp) {}; + GetDefaultQuotaRespMsg(QuotaDefaultLimits defaultLimits) : + BaseType(NETMSGTYPE_GetDefaultQuotaResp), limits(defaultLimits) {}; + virtual ~GetDefaultQuotaRespMsg() {}; + + + private: + QuotaDefaultLimits limits; + + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->limits; + } + + QuotaDefaultLimits& getDefaultLimits() + { + return limits; + } +}; + diff --git a/common/source/common/net/message/storage/quota/GetQuotaInfoMsg.h b/common/source/common/net/message/storage/quota/GetQuotaInfoMsg.h new file mode 100644 index 0000000..bab08d7 --- /dev/null +++ b/common/source/common/net/message/storage/quota/GetQuotaInfoMsg.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define GETQUOTAINFOMSG_FEATURE_QUOTA_PER_TARGET 1 + + +class GetQuotaInfoMsg: public NetMessageSerdes +{ + friend class TestSerialization; + + public: + enum QuotaQueryType + { + QUERY_TYPE_NONE = 0, + QUERY_TYPE_SINGLE_ID = 1, + QUERY_TYPE_ID_RANGE = 2, + QUERY_TYPE_ID_LIST = 3, + QUERY_TYPE_ALL_ID = 4 + }; + + public: + /* + * @param type user or group quota + * @param storagePoolId the storage pool to fetch data for; only relevant when sent to mgmtd + */ + GetQuotaInfoMsg(QuotaDataType type, StoragePoolId storagePoolId) : + BaseType(NETMSGTYPE_GetQuotaInfo), + type(type), + targetSelection(GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST), + targetNumID(0), + storagePoolId(storagePoolId) { } + + /* + * deserialization only + */ + GetQuotaInfoMsg() : BaseType(NETMSGTYPE_GetQuotaInfo) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->queryType + % obj->type; + + if(obj->queryType == QUERY_TYPE_ID_RANGE) + ctx + % obj->idRangeStart + % obj->idRangeEnd; + + if(obj->queryType == QUERY_TYPE_ID_LIST) + ctx % obj->idList; + + if(obj->queryType == QUERY_TYPE_SINGLE_ID) + ctx % obj->idRangeStart; + + ctx + % obj->targetSelection + % obj->targetNumID + % obj->storagePoolId; + } + + private: + int32_t queryType; // QuotaQueryType + int32_t type; // QuotaDataType + uint32_t idRangeStart; + uint32_t idRangeEnd; + UIntList idList; + uint32_t targetSelection; + uint16_t targetNumID; + StoragePoolId storagePoolId; + + public: + //getter and setter + unsigned getIDRangeStart() const + { + return this->idRangeStart; + } + + unsigned getIDRangeEnd() const + { + return this->idRangeEnd; + } + + QuotaDataType getType() const + { + return (QuotaDataType)type; + } + + QuotaQueryType getQueryType() const + { + return (QuotaQueryType)queryType; + } + + unsigned getID() const + { + return this->idRangeStart; + } + + UIntList* getIDList() + { + return &this->idList; + } + + void setQueryType(QuotaQueryType queryType) + { + this->queryType = queryType; + } + + void setID(unsigned id) + { + this->queryType = QUERY_TYPE_SINGLE_ID; + this->idRangeStart = id; + } + + void setIDRange(unsigned idRangeStart, unsigned idRangeEnd) + { + this->queryType = QUERY_TYPE_ID_RANGE; + this->idRangeStart = idRangeStart; + this->idRangeEnd = idRangeEnd; + } + + uint16_t getTargetNumID() const + { + return this->targetNumID; + } + + unsigned getTargetSelection() const + { + return this->targetSelection; + } + + StoragePoolId getStoragePoolId() const + { + return storagePoolId; + } + + void setTargetSelectionSingleTarget(uint16_t newTargetNumID) + { + this->targetNumID = newTargetNumID; + this->targetSelection = GETQUOTACONFIG_SINGLE_TARGET; + } + + void setTargetSelectionAllTargetsOneRequestPerTarget(uint16_t newTargetNumID) + { + this->targetNumID = newTargetNumID; + this->targetSelection = GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET; + } + + void setTargetSelectionAllTargetsOneRequestForAllTargets() + { + this->targetNumID = 0; + this->targetSelection = GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST; + } +}; + diff --git a/common/source/common/net/message/storage/quota/GetQuotaInfoRespMsg.h b/common/source/common/net/message/storage/quota/GetQuotaInfoRespMsg.h new file mode 100644 index 0000000..57839d7 --- /dev/null +++ b/common/source/common/net/message/storage/quota/GetQuotaInfoRespMsg.h @@ -0,0 +1,67 @@ +#pragma once + + +#include +#include +#include +#include + +#define GETQUOTAINFORESPMSG_MAX_SIZE_FOR_QUOTA_DATA ( (unsigned)(NETMSG_MAX_PAYLOAD_SIZE - 1024) ) +#define GETQUOTAINFORESPMSG_MAX_ID_COUNT ( (unsigned) \ + ( (GETQUOTAINFORESPMSG_MAX_SIZE_FOR_QUOTA_DATA - sizeof(unsigned) ) / sizeof(QuotaData) ) ) + + +class GetQuotaInfoRespMsg: public NetMessageSerdes +{ + public: + /** + * Constructor for quota limits (management server). + */ + GetQuotaInfoRespMsg(QuotaDataList* quotaData) + : BaseType(NETMSGTYPE_GetQuotaInfoResp), quotaData(quotaData), + quotaInodeSupport(QuotaInodeSupport_UNKNOWN) {}; + + /** + * Constructor for quota data (storage server). + */ + GetQuotaInfoRespMsg(QuotaDataList* quotaData, QuotaInodeSupport inodeSupport) + : BaseType(NETMSGTYPE_GetQuotaInfoResp), quotaData(quotaData), + quotaInodeSupport(inodeSupport) {}; + + /** + * For deserialization only + */ + GetQuotaInfoRespMsg() : BaseType(NETMSGTYPE_GetQuotaInfoResp) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->quotaInodeSupport + % serdes::backedPtr(obj->quotaData, obj->parsed.quotaData); + } + + private: + QuotaDataList* quotaData; + uint32_t quotaInodeSupport; + + // for deserialization + struct { + QuotaDataList quotaData; + } parsed; + + + public: + QuotaDataList& getQuotaDataList() + { + return *quotaData; + } + + QuotaInodeSupport getQuotaInodeSupport() + { + return (QuotaInodeSupport)quotaInodeSupport; + } +}; + diff --git a/common/source/common/net/message/storage/quota/RequestExceededQuotaMsg.h b/common/source/common/net/message/storage/quota/RequestExceededQuotaMsg.h new file mode 100644 index 0000000..2c9c97e --- /dev/null +++ b/common/source/common/net/message/storage/quota/RequestExceededQuotaMsg.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include +#include +#include + +class RequestExceededQuotaMsg: public NetMessageSerdes +{ + public: + RequestExceededQuotaMsg(QuotaDataType idType, QuotaLimitType exType, + StoragePoolId storagePoolId) : + BaseType(NETMSGTYPE_RequestExceededQuota), + quotaDataType(idType), + exceededType(exType), + storagePoolId(storagePoolId), + targetId(0) { } + + RequestExceededQuotaMsg(QuotaDataType idType, QuotaLimitType exType, uint16_t targetId) : + BaseType(NETMSGTYPE_RequestExceededQuota), + quotaDataType(idType), + exceededType(exType), + storagePoolId(StoragePoolStore::INVALID_POOL_ID), + targetId(targetId) { } + + /** + * For deserialization only + */ + RequestExceededQuotaMsg() : BaseType(NETMSGTYPE_RequestExceededQuota) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->quotaDataType + % obj->exceededType + % obj->storagePoolId + % obj->targetId; + } + + private: + int32_t quotaDataType; + int32_t exceededType; + StoragePoolId storagePoolId; + uint16_t targetId; + + public: + // getters and setters + QuotaDataType getQuotaDataType() const + { + return (QuotaDataType) quotaDataType; + } + + QuotaLimitType getExceededType() const + { + return (QuotaLimitType) exceededType; + } + + StoragePoolId getStoragePoolId() const + { + return storagePoolId; + } + + uint16_t getTargetId() const + { + return targetId; + } +}; + diff --git a/common/source/common/net/message/storage/quota/RequestExceededQuotaRespMsg.h b/common/source/common/net/message/storage/quota/RequestExceededQuotaRespMsg.h new file mode 100644 index 0000000..c67064d --- /dev/null +++ b/common/source/common/net/message/storage/quota/RequestExceededQuotaRespMsg.h @@ -0,0 +1,66 @@ +#pragma once + + +#include +#include +#include + + +#define REQUESTEXCEEDEDQUOTARESPMSG_MAX_SIZE_FOR_QUOTA_DATA (\ + SETEXCEEDEDQUOTAMSG_MAX_SIZE_FOR_QUOTA_DATA) +#define REQUESTEXCEEDEDQUOTARESPMSG_MAX_ID_COUNT (SETEXCEEDEDQUOTAMSG_MAX_ID_COUNT) + + +class RequestExceededQuotaRespMsg: public SetExceededQuotaMsg +{ + public: + RequestExceededQuotaRespMsg(QuotaDataType idType, QuotaLimitType exType, int error) + : SetExceededQuotaMsg(idType, exType, NETMSGTYPE_RequestExceededQuotaResp), error(error) + { + } + + /** + * For deserialization only + */ + RequestExceededQuotaRespMsg() : SetExceededQuotaMsg(NETMSGTYPE_RequestExceededQuotaResp), + error(FhgfsOpsErr_INVAL) + { + } + + // overloads to use the serialize() definition of this class + void serializePayload(Serializer& ser) const + { + ser % *this; + } + + // overloads to use the serialize() definition of this class + bool deserializePayload(const char* buf, size_t bufLen) + { + Deserializer des(buf, bufLen); + des % *this; + return des.good(); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->error; + } + + FhgfsOpsErr getError() + { + return (FhgfsOpsErr)error; + } + + void setError(FhgfsOpsErr newError) + { + error = newError; + } + + private: + int error; + +}; + diff --git a/common/source/common/net/message/storage/quota/SetDefaultQuotaMsg.h b/common/source/common/net/message/storage/quota/SetDefaultQuotaMsg.h new file mode 100644 index 0000000..e3a4ae6 --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetDefaultQuotaMsg.h @@ -0,0 +1,54 @@ +#pragma once + + +#include +#include + +class SetDefaultQuotaMsg : public NetMessageSerdes +{ + public: + SetDefaultQuotaMsg() : BaseType(NETMSGTYPE_SetDefaultQuota) {}; + + SetDefaultQuotaMsg(StoragePoolId storagePoolId, QuotaDataType newType, uint64_t newSize, + uint64_t newInodes): + BaseType(NETMSGTYPE_SetDefaultQuota), storagePoolId(storagePoolId), type(newType), + size(newSize), inodes(newInodes) + {}; + + virtual ~SetDefaultQuotaMsg() {}; + + protected: + StoragePoolId storagePoolId; + int type; // enum QuotaDataType + uint64_t size; // defined size limit or used size (bytes) + uint64_t inodes; // defined inodes limit or used inodes (counter) + + public: + //getter and setter + + uint64_t getSize() const + { + return size; + } + + uint64_t getInodes() const + { + return inodes; + } + + QuotaDataType getType() const + { + return (QuotaDataType)type; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->storagePoolId + % obj->size + % obj->inodes + % obj->type; + } +}; + diff --git a/common/source/common/net/message/storage/quota/SetDefaultQuotaRespMsg.h b/common/source/common/net/message/storage/quota/SetDefaultQuotaRespMsg.h new file mode 100644 index 0000000..eba73cb --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetDefaultQuotaRespMsg.h @@ -0,0 +1,18 @@ +#pragma once + + +#include + + + +class SetDefaultQuotaRespMsg : public SimpleIntMsg +{ + public: + SetDefaultQuotaRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetDefaultQuotaResp, result) {}; + + /** + * For deserialization only + */ + SetDefaultQuotaRespMsg() : SimpleIntMsg(NETMSGTYPE_SetDefaultQuotaResp) {}; +}; + diff --git a/common/source/common/net/message/storage/quota/SetExceededQuotaMsg.h b/common/source/common/net/message/storage/quota/SetExceededQuotaMsg.h new file mode 100644 index 0000000..e32eb30 --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetExceededQuotaMsg.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include + + +#define SETEXCEEDEDQUOTAMSG_MAX_SIZE_FOR_QUOTA_DATA ( (unsigned)(NETMSG_MAX_PAYLOAD_SIZE - 1024) ) +#define SETEXCEEDEDQUOTAMSG_MAX_ID_COUNT ( (unsigned) \ + ( (SETEXCEEDEDQUOTAMSG_MAX_SIZE_FOR_QUOTA_DATA - (2 * sizeof(int) ) ) / sizeof(unsigned) ) ) + + +class SetExceededQuotaMsg: public NetMessageSerdes +{ + friend class TestSerialization; + + public: + SetExceededQuotaMsg(StoragePoolId storagePoolId, QuotaDataType idType, QuotaLimitType exType): + BaseType(NETMSGTYPE_SetExceededQuota) + { + this->storagePoolId = storagePoolId; + this->quotaDataType = idType; + this->exceededType = exType; + } + + SetExceededQuotaMsg(QuotaDataType idType, QuotaLimitType exType, unsigned short msgType): + BaseType(msgType) + { + this->storagePoolId = StoragePoolStore::INVALID_POOL_ID; + this->quotaDataType = idType; + this->exceededType = exType; + } + + /** + * For deserialization only + */ + SetExceededQuotaMsg() : BaseType(NETMSGTYPE_SetExceededQuota) + { + } + + /** + * For deserialization only + */ + SetExceededQuotaMsg(unsigned short msgType) : BaseType(msgType) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->storagePoolId + % obj->quotaDataType + % obj->exceededType + % obj->exceededQuotaIDs; + } + + private: + StoragePoolId storagePoolId; + int32_t quotaDataType; // QuotaDataType + int32_t exceededType; // ExceededType + UIntList exceededQuotaIDs; + + public: + //getters and setters + StoragePoolId getStoragePoolId() const + { + return storagePoolId; + } + + QuotaDataType getQuotaDataType() const + { + return (QuotaDataType) this->quotaDataType; + } + + QuotaLimitType getExceededType() const + { + return (QuotaLimitType) this->exceededType; + } + + UIntList* getExceededQuotaIDs() + { + return &this->exceededQuotaIDs; + } +}; + diff --git a/common/source/common/net/message/storage/quota/SetExceededQuotaRespMsg.h b/common/source/common/net/message/storage/quota/SetExceededQuotaRespMsg.h new file mode 100644 index 0000000..5d4c19d --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetExceededQuotaRespMsg.h @@ -0,0 +1,22 @@ +#pragma once + + +#include +#include + + +class SetExceededQuotaRespMsg: public SimpleIntMsg +{ + public: + SetExceededQuotaRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetExceededQuotaResp, result) + { + } + + /** + * For deserialization only + */ + SetExceededQuotaRespMsg() : SimpleIntMsg(NETMSGTYPE_SetExceededQuotaResp) + { + } +}; + diff --git a/common/source/common/net/message/storage/quota/SetQuotaMsg.h b/common/source/common/net/message/storage/quota/SetQuotaMsg.h new file mode 100644 index 0000000..bbcf1e2 --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetQuotaMsg.h @@ -0,0 +1,53 @@ +#pragma once + + +#include +#include +#include + +#define SETQUOTAMSG_MAX_SIZE_FOR_QUOTA_DATA ( (unsigned)(NETMSG_MAX_PAYLOAD_SIZE - 1024) ) +#define SETQUOTAMSG_MAX_ID_COUNT ( (unsigned) \ + (SETQUOTAMSG_MAX_SIZE_FOR_QUOTA_DATA / sizeof(QuotaData) ) ) + + +class SetQuotaMsg: public NetMessageSerdes +{ + public: + SetQuotaMsg(StoragePoolId storagePoolId) : + BaseType(NETMSGTYPE_SetQuota), storagePoolId(storagePoolId) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->storagePoolId + % obj->quotaData; + } + + protected: + // only for deserialization + SetQuotaMsg() : BaseType(NETMSGTYPE_SetQuota) {} + + private: + StoragePoolId storagePoolId; + QuotaDataList quotaData; + + public: + StoragePoolId getStoragePoolId() const + { + return storagePoolId; + } + + const QuotaDataList& getQuotaDataList() const + { + return quotaData; + } + + void insertQuotaLimit(QuotaData limit) + { + quotaData.push_back(limit); + } +}; + diff --git a/common/source/common/net/message/storage/quota/SetQuotaRespMsg.h b/common/source/common/net/message/storage/quota/SetQuotaRespMsg.h new file mode 100644 index 0000000..5d0a925 --- /dev/null +++ b/common/source/common/net/message/storage/quota/SetQuotaRespMsg.h @@ -0,0 +1,21 @@ +#pragma once + + +#include + + +class SetQuotaRespMsg: public SimpleIntMsg +{ + public: + SetQuotaRespMsg(int result) : SimpleIntMsg(NETMSGTYPE_SetQuotaResp, result) + { + } + + /** + * For deserialization only + */ + SetQuotaRespMsg() : SimpleIntMsg(NETMSGTYPE_SetQuotaResp) + { + } +}; + diff --git a/common/source/common/net/msghelpers/MsgHelperGenericDebug.cpp b/common/source/common/net/msghelpers/MsgHelperGenericDebug.cpp new file mode 100644 index 0000000..21edea6 --- /dev/null +++ b/common/source/common/net/msghelpers/MsgHelperGenericDebug.cpp @@ -0,0 +1,427 @@ +#include +#include +#include +#include +#include "MsgHelperGenericDebug.h" + + +#define GENDBGMSG_TXTFILE_MAX_READ_LEN (100*1024) + + +std::string MsgHelperGenericDebug::processOpVarLogMessages(std::istringstream& commandStream) +{ + return loadTextFile("/var/log/messages"); +} + +std::string MsgHelperGenericDebug::processOpVarLogKernLog(std::istringstream& commandStream) +{ + return loadTextFile("/var/log/kern.log"); +} + +std::string MsgHelperGenericDebug::processOpFhgfsLog(std::istringstream& commandStream) +{ + auto cfg = PThread::getCurrentThreadApp()->getCommonConfig(); + + if(cfg->getLogStdFile().empty() ) + return "No log file defined."; + + return loadTextFile(cfg->getLogStdFile() ); +} + +std::string MsgHelperGenericDebug::processOpLoadAvg(std::istringstream& commandStream) +{ + return loadTextFile("/proc/loadavg"); +} + +std::string MsgHelperGenericDebug::processOpDropCaches(std::istringstream& commandStream) +{ + return writeTextFile("/proc/sys/vm/drop_caches", "3"); +} + +std::string MsgHelperGenericDebug::processOpGetLogLevel(std::istringstream& commandStream) +{ + // procotol: no arguments + + Logger* log = Logger::getLogger(); + std::ostringstream responseStream; + std::string logTopicStr; + + const IntVector logLevels = log->getLogLevels(); + + for (size_t i = 0; i < logLevels.size(); i++) + responseStream << Logger::logTopicToName((LogTopic)i) << ": " << logLevels[i] << std::endl; + + return responseStream.str(); +} + +std::string MsgHelperGenericDebug::processOpSetLogLevel(std::istringstream& commandStream) +{ + // procotol: "newLogLevel logTopicName" + + Logger* log = Logger::getLogger(); + std::ostringstream responseStream; + + std::string logLevelStr; + std::string logTopicStr; + + // get logLevel from command string + std::getline(commandStream, logLevelStr, ' '); + + if(logLevelStr.empty() ) + return "Invalid or missing logLevel"; + + int logLevel = StringTk::strToInt(logLevelStr); + + // get logTopic from command string + std::getline(commandStream, logTopicStr, ' '); + + LogTopic logTopic; + + if (!logTopicStr.empty()) + { + logTopic = Logger::logTopicFromName(logTopicStr); + + if (logTopic == LogTopic_INVALID) + return "Log topic " + logTopicStr + " doesn't exist."; + + responseStream << "Log topic: " << logTopicStr << ";" + << " Old level: " << log->getLogLevel(logTopic) << ";"; + + log->setLogLevel(logLevel, logTopic); + + responseStream << " New level: " << log->getLogLevel(logTopic) << std::endl; + } + else + { // no log topic given => set log level for all topics + const IntVector logLevels = log->getLogLevels(); + + for (size_t i = 0; i < logLevels.size(); i++) + { + responseStream << "Log topic: " << Logger::logTopicToName((LogTopic)i) << ";" + << " Old level: " << log->getLogLevel((LogTopic)i) << ";"; + + log->setLogLevel(logLevel, (LogTopic)i); + + responseStream << " New level: " << log->getLogLevel((LogTopic)i) << std::endl; + } + } + return responseStream.str(); +} + +/** + * @param cfgFile may be an empty string, in which case an error message will be returned. + */ +std::string MsgHelperGenericDebug::processOpCfgFile(std::istringstream& commandStream, + std::string cfgFile) +{ + if(cfgFile.empty() ) + return "No config file defined."; + + return loadTextFile(cfgFile); +} + +/** + * Print currently established (outgoing) connections of this node to other nodes, similar to + * the output of the "fhgfs-net" tool. + */ +std::string MsgHelperGenericDebug::processOpNetOut(std::istringstream& commandStream, + const NodeStoreServers* mgmtNodes, const NodeStoreServers* metaNodes, + const NodeStoreServers* storageNodes) +{ + std::ostringstream responseStream; + + responseStream << printNodeStoreConns(mgmtNodes, "mgmt_nodes") << std::endl; + responseStream << printNodeStoreConns(metaNodes, "meta_nodes") << std::endl; + responseStream << printNodeStoreConns(storageNodes, "storage_nodes") << std::endl; + + return responseStream.str(); +} + +/** + * Opens a text file and reads it. Only the last part (see GENDBGMSG_TXTFILE_MAX_READ_LEN) is + * returned if the file is too large. + * + * @return file contents or human-readable error string will be returned if file cannot be read. + */ +std::string MsgHelperGenericDebug::loadTextFile(std::string path) +{ + const size_t maxLineLen = 2048; + char line[maxLineLen]; + std::ostringstream responseStream; + size_t bytesRead = 0; + + struct stat statBuf; + + std::ifstream fis(path.c_str() ); + if(!fis.is_open() || fis.fail() ) + return "Unable to open file: " + path + ". (SysErr: " + System::getErrString() + ")"; + + // seek if file too large + + int statRes = stat(path.c_str(), &statBuf); + if(statRes != 0) + return "Unable to stat file. SysErr: " + System::getErrString(); + + + if(statBuf.st_size > GENDBGMSG_TXTFILE_MAX_READ_LEN) + fis.seekg(statBuf.st_size - GENDBGMSG_TXTFILE_MAX_READ_LEN); + + + while(!fis.eof() ) + { + line[0] = 0; // make sure line is zero-terminated in case of read error + + fis.getline(line, maxLineLen); + + if(!fis.eof() && fis.fail() ) + { + responseStream << "File read error occurred. SysErr: " + System::getErrString(); + break; + } + + // the file might grow while we're reading it, so we check the amount of read data again below + size_t lineLen = strlen(line); + if( (lineLen + bytesRead + 1) > GENDBGMSG_TXTFILE_MAX_READ_LEN) // (+1 for newline) + break; + + bytesRead += lineLen + 1; // (+1 for newline) + + responseStream << line << std::endl; + } + + fis.close(); + + return responseStream.str(); +} + + +/** + * Opens a file and writes text to it. Will try to create the file if it doesn't exist yet. File + * will be truncated if it exists already. + * + * @param writeStr: The string that should be written to the file + * @return: human-readable error string will be returned if file cannot be read. + */ +std::string MsgHelperGenericDebug::writeTextFile(std::string path, std::string writeStr) +{ + std::ofstream fos(path.c_str() ); + if(!fos.is_open() || fos.fail() ) + return "Unable to open file. SysErr: " + System::getErrString(); + + fos << writeStr; + + if(fos.fail() ) + return "Error during file write. SysErr: " + System::getErrString(); + + fos.close(); + + // (note: errno doesn't work after close, so we cannot write the corresponding errString here.) + + if(fos.fail() ) + return "Error occurred during file close."; + + return "Completed successfully"; +} + +/** + * Print node name and currently established connections for each node in given store, similar to + * the "fhgfs-net" tool. + */ +std::string MsgHelperGenericDebug::printNodeStoreConns(const NodeStoreServers* nodes, + std::string headline) +{ + std::ostringstream returnStream; + + returnStream << headline << std::endl; + + returnStream << std::string(headline.length(), '=') << std::endl; + + for (const auto& node : nodes->referenceAllNodes()) + { + returnStream << node->getAlias() << " [ID: " << node->getNumID() << "]" << std::endl; + + returnStream << printNodeConns(*node); + } + + return returnStream.str(); +} + +/** + * Print currently established connections to the given node, similar to the corresponding output + * lines of the "fhgfs-net" tool. + */ +std::string MsgHelperGenericDebug::printNodeConns(Node& node) +{ + std::ostringstream returnStream; + + NodeConnPool* connPool = node.getConnPool(); + NodeConnPoolStats poolStats; + + const char* nonPrimaryTag = " [fallback route]"; // extra text to hint at non-primary conns + bool isNonPrimaryConn; + + + returnStream << " Connections: "; + + connPool->getStats(&poolStats); + + if(!(poolStats.numEstablishedStd + + poolStats.numEstablishedRDMA) ) + returnStream << ""; + else + { // print ": ();" + if(poolStats.numEstablishedStd) + { + std::string peerName; + + connPool->getFirstPeerName(NICADDRTYPE_STANDARD, &peerName, + &isNonPrimaryConn); + + returnStream << "TCP: " << poolStats.numEstablishedStd << " (" << peerName << + (isNonPrimaryConn ? nonPrimaryTag : "") << "); "; + } + + if(poolStats.numEstablishedRDMA) + { + std::string peerName; + + connPool->getFirstPeerName(NICADDRTYPE_RDMA, &peerName, + &isNonPrimaryConn); + + returnStream << "RDMA: " << poolStats.numEstablishedRDMA << " (" << peerName << + (isNonPrimaryConn ? nonPrimaryTag : "") << "); "; + } + } + + returnStream << std::endl; + + return returnStream.str(); +} + +/** + * Prints IDs with currently exceeded quota. Valid daemon types are the management daemon and the + * storage daemon. This debug command requires the parameters for QuotaDataType (uid/gid) and + * QuotaLimitType (size/inode). + * + * @param commandStream the stream with the command line parameters + * @param store the store with the exceeded quota + * + * @return the string output with the exceeded IDs + */ +std::string MsgHelperGenericDebug::processOpQuotaExceeded(std::istringstream& commandStream, + const ExceededQuotaStore* store) +{ + if (!store) + return "Invalid storage pool or target ID."; + + QuotaDataType quotaDataType = QuotaDataType_NONE; + std::string quotaDataTypeStr; + + QuotaLimitType quotaLimitType = QuotaLimitType_NONE; + std::string quotaLimitTypeStr; + + // get parameter from command string + std::string inputString; + while(!commandStream.eof() ) + { + std::getline(commandStream, inputString, ' '); + + if(inputString == "uid") + { + quotaDataType = QuotaDataType_USER; + quotaDataTypeStr = "User"; + } + else + if(inputString == "gid") + { + quotaDataType = QuotaDataType_GROUP; + quotaDataTypeStr = "Group"; + } + else + if(inputString == "size") + { + quotaLimitType = QuotaLimitType_SIZE; + quotaLimitTypeStr = "file size"; + } + else + if(inputString == "inode") + { + quotaLimitType = QuotaLimitType_INODE; + quotaLimitTypeStr = "inode"; + } + } + + // verify given parameters + if(quotaDataType == QuotaDataType_NONE) + return "Invalid or missing quota data type argument."; + if(quotaLimitType == QuotaLimitType_NONE) + return "Invalid or missing quota limit type argument."; + + + // create the string for the response message + std::ostringstream returnStream; + returnStream << quotaDataTypeStr << " IDs with exceeded " << quotaLimitTypeStr << " quota:" + << std::endl; + + UIntList idList; + store->getExceededQuota(&idList, quotaDataType, quotaLimitType); + + for(UIntListIter iter = idList.begin(); iter != idList.end(); iter++) + returnStream << *iter << std::endl; + + return returnStream.str(); +} + +/** + * Print contents of given TargetStateStorage. + */ +std::string MsgHelperGenericDebug::processOpListTargetStates(std::istringstream& commandStream, + const TargetStateStore* targetStateStore) +{ + // protocol: no arguments + + std::ostringstream returnStream; + + UInt16List targetIDs; + UInt8List targetConsistencyStates; + UInt8List targetReachabilityStates; + + targetStateStore->getStatesAsLists(targetIDs, targetReachabilityStates, targetConsistencyStates); + + for (ZipConstIterRange + iter(targetIDs, targetReachabilityStates, targetConsistencyStates); + !iter.empty(); ++iter) + { + returnStream << *iter()->first << " " + << TargetStateStore::stateToStr( (TargetReachabilityState)*iter()->second) << " " + << TargetStateStore::stateToStr( (TargetConsistencyState)*iter()->third) + << std::endl; + } + + return returnStream.str(); +} + +std::string MsgHelperGenericDebug::processOpListStoragePools(std::istringstream& commandStream, + const StoragePoolStore* storagePoolStore) +{ + // protocol: no arguments + + std::ostringstream returnStream; + + UInt16List targetIDs; + UInt8List targetConsistencyStates; + UInt8List targetReachabilityStates; + + StoragePoolPtrVec pools = storagePoolStore->getPoolsAsVec(); + + for (StoragePoolPtrVecCIter it = pools.begin(); it != pools.end(); it++) + { + returnStream + << (*it)->getId() + << " : " + << (*it)->getDescription() + << std::endl; + } + + return returnStream.str(); +} diff --git a/common/source/common/net/msghelpers/MsgHelperGenericDebug.h b/common/source/common/net/msghelpers/MsgHelperGenericDebug.h new file mode 100644 index 0000000..2350087 --- /dev/null +++ b/common/source/common/net/msghelpers/MsgHelperGenericDebug.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + + +#define GENDBGMSG_OP_VARLOGMESSAGES "varlogmessages" +#define GENDBGMSG_OP_VARLOGKERNLOG "varlogkernlog" +#define GENDBGMSG_OP_FHGFSLOG "beegfslog" +#define GENDBGMSG_OP_LOADAVG "loadavg" +#define GENDBGMSG_OP_DROPCACHES "dropcaches" +#define GENDBGMSG_OP_GETCFG "getcfg" +#define GENDBGMSG_OP_GETLOGLEVEL "getloglevel" +#define GENDBGMSG_OP_SETLOGLEVEL "setloglevel" +#define GENDBGMSG_OP_NETOUT "net" +#define GENDBGMSG_OP_QUOTAEXCEEDED "quotaexceeded" +#define GENDBGMSG_OP_USEDQUOTA "usedquota" +#define GENDBGMSG_OP_LISTMETASTATES "listmetastates" +#define GENDBGMSG_OP_LISTSTORAGESTATES "liststoragestates" +#define GENDBGMSG_OP_LISTSTORAGEPOOLS "liststoragepools" +#define GENDBGMSG_OP_SETREJECTIONRATE "setrejectionrate" + + +class MsgHelperGenericDebug +{ + public: + static std::string processOpVarLogMessages(std::istringstream& commandStream); + static std::string processOpVarLogKernLog(std::istringstream& commandStream); + static std::string processOpFhgfsLog(std::istringstream& commandStream); + static std::string processOpLoadAvg(std::istringstream& commandStream); + static std::string processOpDropCaches(std::istringstream& commandStream); + static std::string processOpCfgFile(std::istringstream& commandStream, std::string cfgFile); + static std::string processOpGetLogLevel(std::istringstream& commandStream); + static std::string processOpSetLogLevel(std::istringstream& commandStream); + static std::string processOpNetOut(std::istringstream& commandStream, + const NodeStoreServers* mgmtNodes, const NodeStoreServers* metaNodes, + const NodeStoreServers* storageNodes); + static std::string processOpQuotaExceeded(std::istringstream& commandStream, + const ExceededQuotaStore* store); + static std::string processOpListTargetStates(std::istringstream& commandStream, + const TargetStateStore* targetStateStore); + static std::string processOpListStoragePools(std::istringstream& commandStream, + const StoragePoolStore* storagePoolStore); + + static std::string loadTextFile(std::string path); + static std::string writeTextFile(std::string path, std::string writeStr); + + + private: + MsgHelperGenericDebug() {} + + static std::string printNodeStoreConns(const NodeStoreServers* nodes, std::string headline); + static std::string printNodeConns(Node& node); + + public: +}; + diff --git a/common/source/common/net/sock/Channel.h b/common/source/common/net/sock/Channel.h new file mode 100644 index 0000000..3e1b976 --- /dev/null +++ b/common/source/common/net/sock/Channel.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include +#include + +class Channel : public Pollable +{ + protected: + Channel() + : nodeType(NODETYPE_Invalid) + { + this->isDirect = true; + this->hasActivity = true; // initially active to avoid immediate disconnection + this->isAuthenticated = false; + } + + virtual ~Channel() {} + + + private: + bool isDirect; // true if used for direct work requests only (wrt to worker types) + bool hasActivity; // true if channel was not idle + bool isAuthenticated; // true if valid authentication message received + NodeType nodeType; + NumNodeID nodeID; + + public: + // getters & setters + inline bool getIsDirect() const + { + return isDirect; + } + + inline void setIsDirect(bool isDirect) + { + this->isDirect = isDirect; + } + + inline bool getHasActivity() + { + return hasActivity; + } + + inline void setHasActivity() + { + this->hasActivity = true; + } + + inline void resetHasActivity() + { + this->hasActivity = false; + } + + inline bool getIsAuthenticated() const + { + return isAuthenticated; + } + + inline void setIsAuthenticated() + { + this->isAuthenticated = true; + } + + NodeType getNodeType() const { return nodeType; } + void setNodeType(NodeType value) { nodeType = value; } + + NumNodeID getNodeID() const { return nodeID; } + void setNodeID(NumNodeID value) { nodeID = value; } +}; + + diff --git a/common/source/common/net/sock/NetworkInterfaceCard.cpp b/common/source/common/net/sock/NetworkInterfaceCard.cpp new file mode 100644 index 0000000..19edfc7 --- /dev/null +++ b/common/source/common/net/sock/NetworkInterfaceCard.cpp @@ -0,0 +1,361 @@ +#include "NetworkInterfaceCard.h" + +#include +#include +#include +#include +#include "Socket.h" +#include "RDMASocket.h" +#include +#include +#include +#include + +#define IFRSIZE(numIfcReqs) ((int)((numIfcReqs) * sizeof(struct ifreq) ) ) + + +/** + * find all network interfaces and check if they are capable of doing RDMA + * + * @return true if any usable standard interface was found + */ +bool NetworkInterfaceCard::findAll(StringList* allowedInterfacesList, bool useRDMA, + NicAddressList* outList) +{ + bool retVal = false; + + // find standard TCP/IP interfaces + if(findAllBySocketDomain(PF_INET, NICADDRTYPE_STANDARD, allowedInterfacesList, outList) ) + retVal = true; + + // find RDMA interfaces (based on TCP/IP interfaces query results) + if(useRDMA && RDMASocket::rdmaDevicesExist() ) + { + NicAddressList tcpInterfaces; + + findAllBySocketDomain(PF_INET, NICADDRTYPE_STANDARD, allowedInterfacesList, &tcpInterfaces); + + filterInterfacesForRDMA(&tcpInterfaces, outList); + } + + return retVal; +} + +/** + * find all network interfaces. This differs from findAll because here only the interfaces are + * (TCP) are detected. No check for there RDMA capability is performed. + * + * @return true if any usable standard interface was found + */ +bool NetworkInterfaceCard::findAllInterfaces(const StringList& allowedInterfacesList, + NicAddressList& outList) +{ + bool retVal = false; + + // find standard TCP/IP interfaces + if(findAllBySocketDomain(PF_INET, NICADDRTYPE_STANDARD, &allowedInterfacesList, &outList) ) + retVal = true; + + return retVal; +} + +bool NetworkInterfaceCard::findAllBySocketDomain(int domain, NicAddrType nicType, + const StringList* allowedInterfacesList, NicAddressList* outList) +{ + int sock = socket(domain, SOCK_STREAM, 0); + if(sock == -1) + return false; + + + int numIfcReqs = 1; // number of interfaces that can be stored in the current buffer (can grow) + + struct ifconf ifc; + ifc.ifc_len = IFRSIZE(numIfcReqs); + ifc.ifc_req = NULL; + + // enlarge the buffer to store all existing interfaces + do + { + numIfcReqs++; // grow bufferspace to one more interface + SAFE_FREE(ifc.ifc_req); // free previous allocation (if any) + + // alloc buffer for interfaces query + ifc.ifc_req = (ifreq*)malloc(IFRSIZE(numIfcReqs) ); + if(!ifc.ifc_req) + { + LogContext("NIC query").logErr("Out of memory"); + close(sock); + + return false; + } + + ifc.ifc_len = IFRSIZE(numIfcReqs); + + if(ioctl(sock, SIOCGIFCONF, &ifc) ) + { + LogContext("NIC query").logErr( + std::string("ioctl SIOCGIFCONF failed: ") + System::getErrString() ); + free(ifc.ifc_req); + close(sock); + + return false; + } + + /* ifc.ifc_len was updated by ioctl, so if IRSIZE<=ifc.ifc_len then there might be more + interfaces, so loop again with larger buffer... */ + + } while (IFRSIZE(numIfcReqs) <= ifc.ifc_len); + + + // foreach interface + + struct ifreq* ifr = ifc.ifc_req; // pointer to current interface + + for( ; (char*) ifr < (char*) ifc.ifc_req + ifc.ifc_len; ifr++) + { + NicAddress nicAddr; + + if(fillNicAddress(sock, nicType, ifr, &nicAddr) ) + { + ssize_t metricByListPos = 0; + + if (!allowedInterfacesList->empty() && + !ListTk::listContains(nicAddr.name, allowedInterfacesList, &metricByListPos) ) + continue; // not in the list of allowed interfaces + + outList->push_back(nicAddr); + } + } + + + free(ifc.ifc_req); + close(sock); + + return true; +} + +/** + * Checks a list of TCP/IP interfaces for RDMA-capable interfaces. + */ +void NetworkInterfaceCard::filterInterfacesForRDMA(NicAddressList* list, NicAddressList* outList) +{ + // Note: This works by binding an RDMASocket to each IP of the passed list. + + if (!RDMASocket::isRDMAAvailable()) + return; + + for(NicAddressListIter iter = list->begin(); iter != list->end(); iter++) + { + try + { + auto rdmaSock = RDMASocket::create(); + + rdmaSock->bindToAddr(iter->ipAddr.s_addr, 0); + + // interface is RDMA-capable => append to outList + + NicAddress nicAddr = *iter; + nicAddr.nicType = NICADDRTYPE_RDMA; + + outList->push_back(nicAddr); + } + catch(SocketException& e) + { + // interface is not RDMA-capable in this case + } + } +} + +/* + * Checks for RDMA-capable interfaces in a list of TCP/IP interfaces and adds the devices as + * new RDMA devices to the list + * + * @param nicList a reference to a list of TCP interfaces; RDMA interfaces will be added to the list + * + * @return true, if at least one RDMA-capable interface was found + */ +bool NetworkInterfaceCard::checkAndAddRdmaCapability(NicAddressList& nicList) +{ + // Note: This works by binding an RDMASocket to each IP of the passed list. + + NicAddressList rdmaInterfaces; + + if (RDMASocket::isRDMAAvailable()) + { + for(NicAddressListIter iter = nicList.begin(); iter != nicList.end(); iter++) + { + try + { + if (iter->nicType == NICADDRTYPE_STANDARD) + { + auto rdmaSock = RDMASocket::create(); + + rdmaSock->bindToAddr(iter->ipAddr.s_addr, 0); + + // interface is RDMA-capable => append to outList + + NicAddress nicAddr = *iter; + nicAddr.nicType = NICADDRTYPE_RDMA; + + rdmaInterfaces.push_back(nicAddr); + } + } + catch(SocketException& e) + { + // interface is not RDMA-capable in this case + } + } + } + + const bool foundRdmaInterfaces = !rdmaInterfaces.empty(); + nicList.splice(nicList.end(), rdmaInterfaces); + + return foundRdmaInterfaces; +} + +/** + * This is not needed in the actual app. + * Nevertheless, it's for some reason part of some tests. + */ +bool NetworkInterfaceCard::findByName(const char* interfaceName, NicAddress* outAddr) +{ + int sock = socket(PF_INET, SOCK_STREAM, IPPROTO_IP); + if(sock == -1) + return false; + + //struct ifreq* ifr; + struct ifreq ifrr; + //struct sockaddr_in sa; + + //ifr = &ifrr; + ifrr.ifr_addr.sa_family = AF_INET; + StringTk::strncpyTerminated(ifrr.ifr_name, interfaceName, sizeof(ifrr.ifr_name) ); + + int fillRes = false; + if(!ioctl(sock, SIOCGIFADDR, &ifrr) ) + fillRes = fillNicAddress(sock, NICADDRTYPE_STANDARD, &ifrr, outAddr); + + close(sock); + + return fillRes; +} + +/** + * @param ifr interface name and IP must be set in ifr when this method is called + */ +bool NetworkInterfaceCard::fillNicAddress(int sock, NicAddrType nicType, struct ifreq* ifr, + NicAddress* outAddr) +{ + // note: struct ifreq contains a union for everything except the name. hence, new ioctl() + // calls overwrite the old data. + + // IP address + // note: must be done at the beginning because following ioctl-calls will overwrite the data + outAddr->ipAddr = ( (struct sockaddr_in *)&ifr->ifr_addr)->sin_addr; + + // name + // note: must be done at the beginning because following ioctl-calls will overwrite the data + memcpy(outAddr->name, ifr->ifr_name, sizeof(outAddr->name)); + + // retrieve flags + if(ioctl(sock, SIOCGIFFLAGS, ifr) ) + return false; + + if(ifr->ifr_flags & IFF_LOOPBACK) + return false; // loopback interface => skip + + // skip interfaces that are not currently usable + if(!(ifr->ifr_flags & IFF_RUNNING)) + return false; + + // hardware type and address + if(ioctl(sock, SIOCGIFHWADDR, ifr) ) + return false; + else + { + // select which hardware types to process + // (see /usr/include/linux/if_arp.h for the whole the list) + switch(ifr->ifr_hwaddr.sa_family) + { + case ARPHRD_LOOPBACK: return false; + default: + break; + } + + // copy nicType + outAddr->nicType = nicType; + } + + return true; +} + +/** + * @return static string (not alloc'ed, so don't free it). + */ +const char* NetworkInterfaceCard::nicTypeToString(NicAddrType nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: return "RDMA"; + case NICADDRTYPE_STANDARD: return "TCP"; + + default: return ""; + } +} + +std::string NetworkInterfaceCard::nicAddrToString(NicAddress* nicAddr) +{ + std::string resultStr; + + resultStr += nicAddr->name; + resultStr += "["; + + resultStr += std::string("ip addr: ") + Socket::ipaddrToStr(nicAddr->ipAddr) + "; "; + resultStr += std::string("type: ") + nicTypeToString(nicAddr->nicType); + + resultStr += "]"; + + return resultStr; +} + +bool NetworkInterfaceCard::supportsRDMA(NicAddressList* nicList) +{ + for(NicAddressListIter iter = nicList->begin(); iter != nicList->end(); iter++) + { + if(iter->nicType == NICADDRTYPE_RDMA) + return true; + } + + return false; +} + +void NetworkInterfaceCard::supportedCapabilities(NicAddressList* nicList, + NicListCapabilities* outCapabilities) +{ + outCapabilities->supportsRDMA = supportsRDMA(nicList); +} + +bool IPv4Network::parse(const std::string& cidr, IPv4Network& net) +{ + std::size_t s = cidr.find('/'); + if (s > 0 && s < cidr.length() - 1) + { + std::string addr = cidr.substr(0, s); + net.prefix = std::stoi(cidr.substr(s + 1)); + if (net.prefix > 32) + return false; + + struct in_addr ina; + if (::inet_pton(AF_INET, addr.c_str(), &ina) == 1) + { + net.netmask = generateNetmask(net.prefix); + net.address.s_addr = ina.s_addr & net.netmask.s_addr; + return true; + } + else + return false; + } + else + return false; +} + diff --git a/common/source/common/net/sock/NetworkInterfaceCard.h b/common/source/common/net/sock/NetworkInterfaceCard.h new file mode 100644 index 0000000..6019e18 --- /dev/null +++ b/common/source/common/net/sock/NetworkInterfaceCard.h @@ -0,0 +1,299 @@ +#pragma once + +#include "common/app/log/Logger.h" +#include +#include + +#include +#include + +enum NicAddrType { + NICADDRTYPE_STANDARD = 0, + // removed: NICADDRTYPE_SDP = 1, + NICADDRTYPE_RDMA = 2 +}; + + +/** + * Returns true if a and b represent the same value. + */ +static inline bool operator==(const struct in_addr a, const struct in_addr b) +{ + return a.s_addr == b.s_addr; +} + +/** + * Returns true if a's value is numerically less-than b's value. + */ +static inline bool operator<(const struct in_addr a, const struct in_addr b) +{ + return a.s_addr < b.s_addr; +} + +/** + * Hash functor for in_addr. + */ +class InAddrHash +{ + public: + std::size_t operator() (const struct in_addr a) const + { + return std::hash()(a.s_addr); + } +}; + +/** + * Create an in_addr from a uint32_t. + */ +static inline struct in_addr in_addr_ctor(uint32_t a) +{ + struct in_addr r = { + .s_addr = a + }; + return r; +} + +class IPv4Network +{ +public: + struct in_addr address; + struct in_addr netmask; + uint8_t prefix; + + static struct in_addr generateNetmask(uint8_t prefix) + { + uint32_t res = static_cast(-1); + if (prefix < 32) + res = ~(res >> prefix); + struct in_addr r = { + .s_addr = ::htonl(res) + }; + return r; + }; + + /** + * @param address network address in network byte order. + * @param network prefix length, 0 - 32. + */ + IPv4Network(struct in_addr address, uint8_t prefix) + { + netmask = generateNetmask(prefix); + this->address.s_addr= address.s_addr & netmask.s_addr; + this->prefix = prefix; + } + + IPv4Network() + : IPv4Network(in_addr_ctor(0), 0) {} + + /** + * Parse CIDR and populate net with + * data in network byte order. + * @param cidr network address in CIDR format (e.g. 10.10.0.0/16) + * @param net instance to populate + * @return true if parsing was successful + */ + static bool parse(const std::string& cidr, IPv4Network& net); + + /** + * Indicate if passed addr is in the network described by + * this instance. + * @param addr address to test + * @return true if addr is in this subnet + */ + bool matches(struct in_addr addr) const + { + return (addr.s_addr & netmask.s_addr) == address.s_addr; + } + + bool operator==(const IPv4Network& o) const + { + return address == o.address && prefix == o.prefix && netmask == o.netmask; + } +}; + +typedef std::vector NetVector; + +/** + * Note: Make sure this struct can be copied with the assignment operator. + */ +struct NicAddress +{ + struct in_addr ipAddr; + NicAddrType nicType; + char name[IFNAMSIZ]; + uint8_t protocol; + + static void serialize(const NicAddress* obj, Serializer& ser) + { + // We currently only support and store ipv4 addresses internally, so we always set the + // protocol field to 4. + uint8_t protocol = 4; + ser % protocol; + + ser.putBlock(&obj->ipAddr.s_addr, sizeof(obj->ipAddr.s_addr) ); + ser.putBlock(obj->name, BEEGFS_MIN(sizeof(obj->name), SERIALIZATION_NICLISTELEM_NAME_SIZE) ); + ser % serdes::as(obj->nicType); + ser.skip(2); // PADDING + } + + static void serialize(NicAddress* obj, Deserializer& des) + { + static const unsigned minNameSize = + BEEGFS_MIN(sizeof(obj->name), SERIALIZATION_NICLISTELEM_NAME_SIZE); + + ::memset(obj, 0, sizeof(*obj) ); + + des % obj->protocol; + if (obj->protocol == 4) { + // Ipv4 address + des.getBlock(&obj->ipAddr.s_addr, sizeof(obj->ipAddr.s_addr)); + } else { + // If this is an ipv6 address, skip it as it is not supported yet. The receiver of this + // nic must check the protocol afterwards and discard it if it is not ipv4. For the usual + // list deserialization it is handled by the deserializaer specialization below. + des.skip(16); + } + + des.getBlock(obj->name, minNameSize); + obj->name[minNameSize - 1] = 0; + des % serdes::as(obj->nicType); + des.skip(2); // PADDING + } + + bool operator==(const NicAddress& o) const + { + return ipAddr.s_addr == o.ipAddr.s_addr && nicType == o.nicType + && !strncmp(name, o.name, sizeof(name)); + } +}; + +typedef struct NicListCapabilities +{ + bool supportsRDMA; +} NicListCapabilities; + + +typedef std::list NicAddressList; +typedef NicAddressList::iterator NicAddressListIter; + +// Specialization of NicAddressList serializer, forwarding to the generic list serializer +inline serdes::BackedPtrSer serdesNicAddressList(NicAddressList* const& ptr, const NicAddressList& backing) { + return serdes::backedPtr(ptr, backing); +} + +struct BackedNicAddressListDes { + NicAddressList& backing; + NicAddressList*& ptr; + + BackedNicAddressListDes(NicAddressList*& ptr, NicAddressList& backing) : backing(backing), ptr(ptr) {} + + friend Deserializer& operator%(Deserializer& des, BackedNicAddressListDes value) + { + value.backing.clear(); + + uint32_t nicListLength; + des % nicListLength; + + uint32_t nicListCount; + des % nicListCount; + + for (uint32_t i = 0; i < nicListCount; i++) { + NicAddress nic; + + des % nic; + if (nic.protocol != 4) { + LOG(GENERAL, WARNING, "Skipping incoming Ipv6 interface ", nic.name); + continue; + } + + value.backing.push_back(nic); + } + + value.ptr = &value.backing; + return des; + } +}; + +// Specialization of NicAddressList deserializer, allows skipping ipv6 interfaces while not supported. +inline BackedNicAddressListDes serdesNicAddressList(NicAddressList*& ptr, NicAddressList& backing) { + BackedNicAddressListDes backed(ptr, backing); + return backed; +} + +// used for debugging +static inline std::string NicAddressList_str(const NicAddressList& nicList) +{ + std::string r; + char buf[64]; + for (NicAddressList::const_iterator it = nicList.begin(); it != nicList.end(); ++it) + { + snprintf(buf, sizeof(buf), "name=%s type=%d addr=%x, ", it->name, it->nicType, it->ipAddr.s_addr); + r += std::string(buf); + } + return r; +} + +class NetworkInterfaceCard +{ + public: + static bool findAll(StringList* allowedInterfacesList, bool useRDMA, + NicAddressList* outList); + static bool findAllInterfaces(const StringList& allowedInterfacesList, + NicAddressList& outList); + static bool findByName(const char* interfaceName, NicAddress* outAddr); + + static const char* nicTypeToString(NicAddrType nicType); + static std::string nicAddrToString(NicAddress* nicAddr); + + static bool supportsRDMA(NicAddressList* nicList); + static void supportedCapabilities(NicAddressList* nicList, + NicListCapabilities* outCapabilities); + + static bool checkAndAddRdmaCapability(NicAddressList& nicList); + + private: + NetworkInterfaceCard() {} + + static bool fillNicAddress(int sock, NicAddrType nicType, struct ifreq* ifr, + NicAddress* outAddr); + static bool findAllBySocketDomain(int domain, NicAddrType nicType, + const StringList* allowedInterfacesList, NicAddressList* outList); + static void filterInterfacesForRDMA(NicAddressList* list, NicAddressList* outList); + + + public: + struct NicAddrComp { + // the comparison implemented here is a model of Compare only if preferences contains + // all possible names ever encountered or is unset. + const StringList* preferences = nullptr; + + explicit NicAddrComp(const StringList* preferences = nullptr): preferences(preferences) {} + + bool operator()(const NicAddress& lhs, const NicAddress& rhs) const + { + // compares the preference of NICs + // returns true if lhs is preferred compared to rhs + if (preferences) { + for (const auto& p : *preferences) { + if (p == lhs.name) + return true; + if (p == rhs.name) + return false; + } + } + + // prefer RDMA NICs + if( (lhs.nicType == NICADDRTYPE_RDMA) && (rhs.nicType != NICADDRTYPE_RDMA) ) + return true; + if( (rhs.nicType == NICADDRTYPE_RDMA) && (lhs.nicType != NICADDRTYPE_RDMA) ) + return false; + + // prefer higher ipAddr + unsigned lhsHostOrderIP = ntohl(lhs.ipAddr.s_addr); + unsigned rhsHostOrderIP = ntohl(rhs.ipAddr.s_addr); + + // this is the original IP-order version + return lhsHostOrderIP > rhsHostOrderIP; + } + }; +}; diff --git a/common/source/common/net/sock/PooledSocket.h b/common/source/common/net/sock/PooledSocket.h new file mode 100644 index 0000000..377f2c1 --- /dev/null +++ b/common/source/common/net/sock/PooledSocket.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + + +/** + * This class provides special extensions for sockets in a NodeConnPool. + */ +class PooledSocket : public Socket +{ + public: + virtual ~PooledSocket() {}; + + + protected: + PooledSocket() : expireTimeStart(true) + { + this->available = false; + this->closeOnRelease = false; + } + + + private: + bool available; // == !acquired + bool closeOnRelease; // if true, close this socket when it is released + Time expireTimeStart; // 0 means "doesn't expire", otherwise time when conn was established + + + public: + // inliners + + /** + * Tests whether this socket is set to expire and whether its expire time has been exceeded. + * + * @param expireSecs the time in seconds after which an expire-enabled socket expires. + * @return true if this socket has expired. + */ + bool getHasExpired(unsigned expireSecs) + { + if(likely(expireTimeStart.getIsZero() ) ) + return false; + + if(expireTimeStart.elapsedMS() > (expireSecs*1000) ) // "*1000" for milliseconds + return true; + + return false; + } + + // getters & setters + bool isAvailable() const + { + return available; + } + + void setAvailable(bool available) + { + this->available = available; + } + + void setExpireTimeStart() + { + expireTimeStart.setToNow(); + } + + bool getHasExpirationTimer() + { + return !expireTimeStart.getIsZero(); + } + + bool isCloseOnRelease() + { + return closeOnRelease; + } + + void setCloseOnRelease(bool v) + { + closeOnRelease = v; + } +}; + + diff --git a/common/source/common/net/sock/RDMASocket.cpp b/common/source/common/net/sock/RDMASocket.cpp new file mode 100644 index 0000000..e98856d --- /dev/null +++ b/common/source/common/net/sock/RDMASocket.cpp @@ -0,0 +1,62 @@ +#include "RDMASocket.h" + +#include + +namespace { + RDMASocket::ImplCallbacks* socket_impl; + + struct IBLibLoader { + struct dlclose { + void operator()(void* v) { ::dlclose(v); } + }; + + std::unique_ptr iblib; + + IBLibLoader() { + // RTLD_NOW to resolve all symbols at load time rather than paying as we go + iblib.reset(dlopen("libbeegfs_ib.so", RTLD_NOW)); + // no ib support if lib can't be loaded. likely because ib libs are not + // installed. + if (!iblib) + return; + + socket_impl = (RDMASocket::ImplCallbacks*) dlsym(iblib.get(), "beegfs_socket_impl"); + + if (!socket_impl) + iblib.reset(); + } + }; + + // load lib on startup, unload at exit. + IBLibLoader lib_loader; +} + +bool RDMASocket::isRDMAAvailable() +{ + return bool(lib_loader.iblib); +} + +bool RDMASocket::rdmaDevicesExist() +{ + return socket_impl && socket_impl->rdma_devices_exist(); +} + +/** + * Prepare ibverbs for multi-threading. + * Call this only once in your program. + * + * Note: There is no corresponding uninit-method that needs to be called. + */ +void RDMASocket::rdmaForkInitOnce() +{ + if (socket_impl) + return socket_impl->rdma_fork_init_once(); +} + +std::unique_ptr RDMASocket::create() +{ + if (!socket_impl) + throw std::logic_error("beegfs_rdma_socket_create called with no rdma support"); + + return std::unique_ptr(socket_impl->rdma_socket_create()); +} diff --git a/common/source/common/net/sock/RDMASocket.h b/common/source/common/net/sock/RDMASocket.h new file mode 100644 index 0000000..eb9c18e --- /dev/null +++ b/common/source/common/net/sock/RDMASocket.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include "PooledSocket.h" + + +class RDMASocket : public PooledSocket +{ + public: + struct ImplCallbacks + { + bool (*rdma_devices_exist)(); + void (*rdma_fork_init_once)(); + RDMASocket* (*rdma_socket_create)(); + }; + + static bool isRDMAAvailable(); + + static std::unique_ptr create(); + + static bool rdmaDevicesExist(); + static void rdmaForkInitOnce(); + + virtual void checkConnection() = 0; + virtual ssize_t nonblockingRecvCheck() = 0; + virtual bool checkDelayedEvents() = 0; + + virtual void setBuffers(unsigned bufNum, unsigned bufSize) = 0; + virtual void setTimeouts(int connectMS, int flowSendMS, int pollMS) = 0; + virtual void setTypeOfService(uint8_t typeOfService) = 0; + + virtual void setConnectionRejectionRate(unsigned rate) = 0; +}; + + diff --git a/common/source/common/net/sock/RoutingTable.cpp b/common/source/common/net/sock/RoutingTable.cpp new file mode 100644 index 0000000..9146e0d --- /dev/null +++ b/common/source/common/net/sock/RoutingTable.cpp @@ -0,0 +1,423 @@ +#include +#include +#include +#include +#include + +#include "RoutingTable.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +typedef std::unordered_map> InterfaceMap; + +/** + * Destnet indicates which local NIC IPs to use for a particular + * destination network. + */ +class Destnet +{ + private: + // net represents the destination network + IPv4Network net; + // sources contains the local NIC IPs that have a route to "net" + std::set sources; + + public: + + Destnet() {} + + Destnet(const IPv4Network& net) + : net(net) {} + + BEEGFS_NODISCARD const IPv4Network& getNet() const + { + return net; + } + + void addSource(struct in_addr a) + { + sources.insert(a); + } + + BEEGFS_NODISCARD const std::set& getSources() const + { + return sources; + } + + // match() indicates if the passed address is in "net" + BEEGFS_NODISCARD bool match(struct in_addr a) const + { + return net.matches(a); + } + + // hasSource() indicates if the passed address is in "sources" + BEEGFS_NODISCARD bool hasSource(struct in_addr addr) const + { + return sources.find(addr) != sources.end(); + } + + BEEGFS_NODISCARD bool operator==(const Destnet& o) const + { + return net == o.net && + sources == o.sources; + } + + /** + * Used for debugging. + */ + BEEGFS_NODISCARD std::string dump() const + { + std::string res = "Destnet"; + res += std::string(" address=") + Socket::ipaddrToStr(net.address.s_addr) + + "/" + StringTk::uintToStr(net.prefix); + for (auto s: sources) + { + res += std::string(" ") + Socket::ipaddrToStr(s.s_addr); + } + return res; + } + +}; + +/** + * Abstract definition of classes that create a DestnetMap. + */ +class RoutingTableQuery +{ + public: + virtual void init() = 0; + /** + * Perform the query. + * @return the created DestnetMap. Ownership is released to the caller. + */ + virtual DestnetMap* query() = 0; + virtual ~RoutingTableQuery() {} +}; + +/** + * Implementation of RoutingTableQuery that encapsulates use of libnl3-route + * to access the OS routing tables. + */ +class Nl3RouteQuery : public RoutingTableQuery +{ + + private: + struct nl_sock* sock; + struct nl_cache_mngr* cacheMngr; + struct nl_cache* routeCache; + struct nl_cache* addrCache; + + void destroy() + { + if (cacheMngr) + ::nl_cache_mngr_free(cacheMngr); + if (sock) + ::nl_socket_free(sock); + } + + bool addrTo_in_addr(struct nl_addr* ra, struct in_addr& addr) + { + if (ra) + { + if (::nl_addr_get_family(ra) == AF_INET) + { + void* ba = ::nl_addr_get_binary_addr(ra); + if (ba) + { + addr = *reinterpret_cast(ba); + return true; + } + } + } + return false; + } + + bool addrTo_in_addr(struct rtnl_addr* ra, struct in_addr& addr) + { + if (ra) + { + struct nl_addr* na = ::rtnl_addr_get_local(ra); + return this->addrTo_in_addr(na, addr); + } + return false; + } + + void populateInterfaces(InterfaceMap& interfaces) + { + // build map of set of interface addresses keyed by interface index + for (struct nl_object* no = ::nl_cache_get_first(addrCache); no != NULL; + no = ::nl_cache_get_next(no)) + { + struct rtnl_addr* raddr = reinterpret_cast(no); + struct in_addr ba; + if (addrTo_in_addr(raddr, ba)) + interfaces[::rtnl_addr_get_ifindex(raddr)].insert(ba); + } + } + + void populateDestnetMap(InterfaceMap& interfaces, DestnetMap& dests) + { + // populate dests from NL3 data + for (struct nl_object* no = ::nl_cache_get_first(routeCache); no != NULL; + no = ::nl_cache_get_next(no)) + { + struct rtnl_route* re = reinterpret_cast(no); + if (::rtnl_route_get_family(re) == AF_INET && + ::rtnl_route_get_type(re) == RTN_UNICAST) + { + struct nl_addr* dstaddr = ::rtnl_route_get_dst(re); + struct in_addr dstnetaddr; + if (!addrTo_in_addr(dstaddr, dstnetaddr)) + continue; + int dstpref = ::nl_addr_get_prefixlen(dstaddr); + struct in_addr key = { + .s_addr = dstnetaddr.s_addr & IPv4Network::generateNetmask(dstpref).s_addr + }; + + auto s = dests.find(key); + Destnet* t; + if (s == dests.end()) + { + t = &(dests[key]); + *t = Destnet(IPv4Network(dstnetaddr, dstpref)); + } + else + { + t = &s->second; + } + + struct nl_addr* prefsrc = ::rtnl_route_get_pref_src(re); + if (prefsrc) + { + struct in_addr sa; + if (addrTo_in_addr(prefsrc, sa)) + t->addSource(sa); + } + else + { + // this appears to happen for the default route + int nhs = ::rtnl_route_get_nnexthops(re); + for (int i = 0; i < nhs; i++) + { + struct rtnl_nexthop* nh = ::rtnl_route_nexthop_n(re, i); + if (nh) + { + int ifn = ::rtnl_route_nh_get_ifindex(nh); + for (auto& ns : interfaces[ifn]) + t->addSource(ns); + } + } + } + } + } + } + +public: + + Nl3RouteQuery() : sock(NULL), cacheMngr(NULL), + routeCache(NULL), + addrCache(NULL) {} + + ~Nl3RouteQuery() override + { + // the individual caches are owned by cacheMngr + if (cacheMngr) + ::nl_cache_mngr_free(cacheMngr); + if (sock) + ::nl_socket_free(sock); + } + + void init() override + { + int rc; + + if (sock != NULL) + throw RoutingTableException("Already initialized"); + + if ((sock = ::nl_socket_alloc()) == NULL) + throw RoutingTableException("Failed to allocate nl socket"); + + ::nl_socket_set_nonblocking(sock); + + if ((rc = ::nl_cache_mngr_alloc(sock, NETLINK_ROUTE, NL_AUTO_PROVIDE, &cacheMngr)) != 0) + throw RoutingTableException("nl_cache_mngr_alloc failed rc=" + StringTk::intToStr(rc)); + + if ((rc = ::rtnl_route_alloc_cache(sock, AF_UNSPEC, 0, &routeCache) != 0)) + throw RoutingTableException("rtnl_route_alloc_cache failed, err=" + StringTk::intToStr(rc)); + + if ((rc = ::nl_cache_mngr_add_cache(cacheMngr, routeCache, NULL, this) != 0)) + { + ::nl_cache_free(routeCache); + throw RoutingTableException("nl_cache_mngr_add_cache failed - route, err=" + StringTk::intToStr(rc)); + } + + if ((rc = ::rtnl_addr_alloc_cache(sock, &addrCache) != 0)) + throw RoutingTableException("rtnl_route_alloc_cache failed, err=" + StringTk::intToStr(rc)); + + if ((rc = ::nl_cache_mngr_add_cache(cacheMngr, addrCache, NULL, this) != 0)) + { + ::nl_cache_free(addrCache); + throw RoutingTableException("nl_cache_mngr_add_cache failed - addr, err=" + StringTk::intToStr(rc)); + } + } + + DestnetMap* query() override + { + std::unique_ptr dests = std::make_unique(); + InterfaceMap interfaces; + populateInterfaces(interfaces); + populateDestnetMap(interfaces, *dests); + return dests.release(); + } +}; + +RoutingTable::RoutingTable(std::shared_ptr destnets, + std::shared_ptr noDefaultRouteNets) + : destnets(destnets), + noDefaultRouteNets(noDefaultRouteNets) +{ +} + +bool RoutingTable::findSource(const Destnet* dn, const NicAddressList& nicList, struct in_addr& addr) const +{ + LogContext log("RoutingTable (findSource)"); + //log.log(Log_DEBUG, dn->dump()); + for (auto& s : nicList) + { + //log.log(Log_ERR, std::string("try ") + Socket::ipaddrToStr(s.ipAddr)); + if (dn->hasSource(s.ipAddr)) + { + addr = s.ipAddr; + //log.log(Log_DEBUG, std::string("net=") + Socket::ipaddrToStr(dn->getNet().address.s_addr) + " use addr " + Socket::ipaddrToStr(addr)); + return true; + } + } + return false; +} + +bool RoutingTable::match(struct in_addr addr, const NicAddressList& nicList, struct in_addr& src) const +{ + LogContext log("RoutingTable (match)"); + const Destnet* defaultRoute = NULL; + + if (destnets == nullptr) + throw RoutingTableException("RoutingTable not initialized"); + + //log.log(Log_DEBUG, std::string("addr=") + Socket::ipaddrToStr(addr)); + + for (auto ds = destnets->begin(); ds != destnets->end(); ds++) + { + auto* d = &ds->second; + if (d->getNet().address.s_addr == 0) + defaultRoute = d; + else + { + //log.log(Log_DEBUG, std::string("addr=") + Socket::ipaddrToStr(addr) + " net=" + Socket::ipaddrToStr(d->getNet().address.s_addr)); + if (d->match(addr) && findSource(d, nicList, src)) + return true; + } + } + + if (defaultRoute != NULL) + { + if (noDefaultRouteNets != nullptr) + { + for (auto& n : *noDefaultRouteNets) + { + if (n.matches(addr)) + return false; + } + } + //log.log(Log_DEBUG, std::string("trying default route, addr=") + Socket::ipaddrToStr(addr) + " net=" + Socket::ipaddrToStr(defaultRoute->getNet().address.s_addr)); + return findSource(defaultRoute, nicList, src); + } + + return false; +} + +void RoutingTableFactory::init() +{ + LogContext log("RoutingTableFactory (init)"); + log.log(Log_DEBUG, ""); + if (initialized) + throw RoutingTableException("RoutingTableFactory already initialized"); + initialized = true; +} + +bool RoutingTableFactory::load() +{ + LogContext log("RoutingTableFactory (load)"); + log.log(Log_DEBUG, ""); + + if (!initialized) + throw RoutingTableException("RoutingTableFactory not initialized"); + + std::unique_ptr q(new Nl3RouteQuery()); + q->init(); + std::unique_ptr dn(q->query()); + + std::unique_lock lock(destnetsMutex); + bool changed = (destnets == nullptr) || !(*destnets == *dn); + if (changed) + destnets = std::move(dn); + + return changed; +} + +RoutingTable RoutingTableFactory::create(std::shared_ptr noDefaultRouteNets) +{ + LogContext log("RoutingTableFactory (create)"); + log.log(Log_DEBUG, ""); + + std::unique_lock lock(destnetsMutex); + + if (destnets == nullptr) + throw RoutingTableException("RoutingTableFactory not loaded"); + + return RoutingTable(destnets, noDefaultRouteNets); +} + +bool RoutingTable::loadIpSourceMap(const NicAddressList& nicList, const NicAddressList &localNicList, + IpSourceMap& srcMap) const +{ + LogContext log("RoutingTable (loadIPSourceMap)"); + bool result = false; + + if (destnets == nullptr) + throw RoutingTableException("RoutingTable not initialized"); + + srcMap.clear(); + for (auto& s : nicList) + { + struct in_addr src; + if (match(s.ipAddr, localNicList, src)) + srcMap[s.ipAddr] = src; + } + if (srcMap.empty()) + { + std::string nics; + std::string ips; + for (auto& s : nicList) + { + nics += std::string(" ") + s.name; + ips += std::string(" ") + Socket::ipaddrToStr(s.ipAddr.s_addr); + } + log.log(Log_ERR, std::string("No routes found ") + " nicList: [" + nics + + " ] ipList: [" + ips + " ] localNicList.size: " + + StringTk::intToStr(static_cast(localNicList.size()))); + } + else + result = true; + + return result; +} + diff --git a/common/source/common/net/sock/RoutingTable.h b/common/source/common/net/sock/RoutingTable.h new file mode 100644 index 0000000..880d535 --- /dev/null +++ b/common/source/common/net/sock/RoutingTable.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include +#include +#include +#include + +DECLARE_NAMEDEXCEPTION(RoutingTableException, "RoutingTableException") + + +class Destnet; +typedef std::unordered_map DestnetMap; +typedef std::unordered_map IpSourceMap; + +/** + * Encapsulates lookup of a source address for a given destination + * address according to the OS IP routing table rules. + * + * Note: all public methods must be const. This class implements the + * immutable pattern to guarantee thread safety. + */ +class RoutingTable +{ + private: + friend class RoutingTableFactory; + std::shared_ptr destnets; + std::shared_ptr noDefaultRouteNets; + + RoutingTable(std::shared_ptr destnets, + std::shared_ptr noDefaultRouteNets); + + bool findSource(const Destnet* dn, const NicAddressList& nicList, struct in_addr& addr) const; + + public: + + RoutingTable() {}; + + /** + * Locate the address of the local NIC that should be used to communicate with + * an address. nicList determines the priority of the interfaces to test + * and which interfaces are valid. + * + * @param addr the address to communicate with + * @param nicList list of candidate NIC addresses, the local NIC list + * @param src receives the NIC address to use + * @return true if a route was found for addr and it matches an element of + * nicList. + */ + BEEGFS_NODISCARD bool match(struct in_addr addr, const NicAddressList& nicList, struct in_addr& src) const; + + /** + * Populate srcMap with the local IP address to use when communicating with IP addresses specified in + * nicList. + * + * @param nicList the NicAddress instances to route to (i.e. peer IPs) + * @param localNicList the NicAdderess instances to route from (i.e. local IPs) + * @return true if at least one route is found to the IPs in nicList. + */ + BEEGFS_NODISCARD bool loadIpSourceMap(const NicAddressList& nicList, const NicAddressList& localNicList, + IpSourceMap &srcMap) const; + +}; + +/** + * Manages collection of routing table data from the OS and creation + * of RoutingTable instances. + */ +class RoutingTableFactory +{ + + private: + bool initialized; + std::shared_ptr destnets; + std::mutex destnetsMutex; + + public: + RoutingTableFactory() + : initialized(false) {} + + /** + * Initialize this instance. Must be called before load(). + * This method is not thread safe. + */ + void init(); + + /** + * Load in routing table data from the OS. Must be called before create(). + * This method is thread safe. + * + * @return true if the data has changed + */ + bool load(); + + /** + * Create a RoutingTable instance using the currently loaded data. + * This method is thread safe. + * + * @return the instance + */ + RoutingTable create(std::shared_ptr noDefaultRouteNets); + +}; + diff --git a/common/source/common/net/sock/Socket.cpp b/common/source/common/net/sock/Socket.cpp new file mode 100644 index 0000000..4a515c9 --- /dev/null +++ b/common/source/common/net/sock/Socket.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include "Socket.h" + + +HighResolutionStats Socket::dummyStats; // (no need to initialize this) + + +/** + * @throw SocketException + */ +Socket::Socket() +{ + this->stats = &dummyStats; + + this->sockType = NICADDRTYPE_STANDARD; + + this->peerIP.s_addr = 0; + + this->bindIP.s_addr = 0; + + this->bindPort = 0; +} + +Socket::~Socket() +{ + // nothing to be done here +} + + +/** + * @throw SocketException + */ +void Socket::connect(const char* hostname, unsigned short port, + int ai_family, int ai_socktype) +{ + struct addrinfo hint; + struct addrinfo* addressList; + + memset(&hint, 0, sizeof(struct addrinfo) ); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = ai_family; + hint.ai_socktype = ai_socktype; + + int getRes = getaddrinfo(hostname, NULL, &hint, &addressList); + if(getRes) + throw SocketConnectException( + std::string("Unable to resolve hostname: ") + std::string(hostname) ); + + + + // set port and peername + ( (struct sockaddr_in*)addressList->ai_addr)->sin_port = htons(port); + + peername = (strlen(addressList->ai_canonname) ? addressList->ai_canonname : hostname); + peername += std::string(":") + StringTk::intToStr(port); + + try + { + connect(addressList->ai_addr, addressList->ai_addrlen); + + freeaddrinfo(addressList); + } + catch(...) + { + freeaddrinfo(addressList); + throw; + } + +} + +/** + * Note: Sets the protocol family type to AF_INET. + * + * @throw SocketException + */ +void Socket::connect(const struct in_addr* ipaddress, unsigned short port) +{ + struct sockaddr_in serv_addr; + + memset(&serv_addr, 0, sizeof(serv_addr) ); + + serv_addr.sin_family = AF_INET;//sockDomain; + serv_addr.sin_addr.s_addr = ipaddress->s_addr; + serv_addr.sin_port = htons(port); + + this->connect( (struct sockaddr*)&serv_addr, sizeof(serv_addr) ); +} + +/** + * @throw SocketException + */ +void Socket::bind(unsigned short port) +{ + in_addr_t ipAddr = INADDR_ANY; + + this->bindToAddr(ipAddr, port); +} + +std::string Socket::ipaddrToStr(in_addr_t addr) +{ + char buf[4*4]; + const char* a = ::inet_ntop(AF_INET, &addr, buf, sizeof(buf)); + return a != NULL? a : "error"; +} + +std::string Socket::ipaddrToStr(struct in_addr ipaddress) +{ + return Socket::ipaddrToStr(ipaddress.s_addr); +} + +std::string Socket::endpointAddrToStr(struct in_addr ipaddress, unsigned short port) +{ + return Socket::ipaddrToStr(ipaddress) + ":" + StringTk::uintToStr(port); +} + +std::string Socket::endpointAddrToStr(in_addr_t ipaddress, unsigned short port) +{ + return Socket::ipaddrToStr(ipaddress) + ":" + StringTk::uintToStr(port); +} + +std::string Socket::endpointAddrToStr(const char* hostname, unsigned short port) +{ + return std::string(hostname) + ":" + StringTk::uintToStr(port); +} + +std::string Socket::endpointAddrToStr(const struct sockaddr_in* sin) +{ + return Socket::endpointAddrToStr(sin->sin_addr, ntohs(sin->sin_port)); +} + + diff --git a/common/source/common/net/sock/Socket.h b/common/source/common/net/sock/Socket.h new file mode 100644 index 0000000..eca04ee --- /dev/null +++ b/common/source/common/net/sock/Socket.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "Channel.h" +#include "NetworkInterfaceCard.h" +#include "SocketException.h" +#include "SocketConnectException.h" +#include "SocketDisconnectException.h" +#include "SocketInterruptedPollException.h" +#include "SocketTimeoutException.h" + +#include + + +class Socket : public Channel +{ + public: + + static std::string ipaddrToStr(in_addr_t addr); + static std::string ipaddrToStr(struct in_addr ipaddress); + static std::string endpointAddrToStr(struct in_addr ipaddress, unsigned short port); + static std::string endpointAddrToStr(in_addr_t ipaddress, unsigned short port); + static std::string endpointAddrToStr(const char* hostname, unsigned short port); + static std::string endpointAddrToStr(const struct sockaddr_in* sin); + + virtual ~Socket(); + virtual void connect(const char* hostname, unsigned short port) = 0; + virtual void connect(const struct sockaddr* serv_addr, socklen_t addrlen) = 0; + virtual void bindToAddr(in_addr_t ipAddr, unsigned short port) = 0; + virtual void listen() = 0; + virtual Socket* accept(struct sockaddr* addr, socklen_t* addrlen) = 0; + virtual void shutdown() = 0; + virtual void shutdownAndRecvDisconnect(int timeoutMS) = 0; + +#ifdef BEEGFS_NVFS + virtual ssize_t read(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) = 0; + virtual ssize_t write(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) = 0; +#endif /* BEEGFS_NVFS */ + + virtual ssize_t send(const void *buf, size_t len, int flags) = 0; + virtual ssize_t sendto(const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) = 0; + + virtual ssize_t recv(void *buf, size_t len, int flags) = 0; + virtual ssize_t recvT(void *buf, size_t len, int flags, int timeoutMS) = 0; + + void connect(const struct in_addr* ipaddress, unsigned short port); + void bind(unsigned short port); + + + protected: + Socket(); + + HighResolutionStats* stats; + NicAddrType sockType; + struct in_addr peerIP; + struct in_addr bindIP; + unsigned short bindPort; // set by bindToAddr + std::string peername; + + void connect(const char* hostname, unsigned short port, int ai_family, int ai_socktype); + + + private: + static HighResolutionStats dummyStats; // used when the caller doesn't need stats + + + public: + // getters & setters + inline uint32_t getPeerIP() + { + return peerIP.s_addr; + } + + inline struct in_addr getBindIP() + { + return bindIP; + } + + inline std::string getPeername() + { + return peername; + } + + inline NicAddrType getSockType() + { + return sockType; + } + + inline void setStats(HighResolutionStats* stats) + { + this->stats = stats; + } + + inline void unsetStats() + { + this->stats = &dummyStats; + } + + inline unsigned short getBindPort() + { + return bindPort; + } + + // inliners + + /** + * @throw SocketException + */ + inline ssize_t recvExact(void *buf, size_t len, int flags) + { + ssize_t missing = len; + + do + { + ssize_t recvRes = this->recv(&((char*)buf)[len-missing], missing, flags); + missing -= recvRes; + } while(missing); + + return (ssize_t)len; + } + + /** + * @throw SocketException + */ + inline ssize_t recvExactT(void *buf, size_t len, int flags, int timeoutMS) + { + // note: this uses a soft timeout that is being reset after each received chunk + + ssize_t missing = len; + + do + { + ssize_t recvRes = recvT(&((char*)buf)[len-missing], missing, flags, timeoutMS); + missing -= recvRes; + } while(missing); + + return (ssize_t)len; + } + + /** + * @throw SocketException + */ + inline ssize_t recvMinMax(void *buf, size_t minLen, size_t maxLen, int flags) + { + size_t receivedLen = 0; + + do + { + ssize_t recvRes = this->recv(&((char*)buf)[receivedLen], maxLen-receivedLen, flags); + receivedLen += recvRes; + } while(receivedLen < minLen); + + return (ssize_t)receivedLen; + } + + /** + * @throw SocketException + */ + inline ssize_t recvMinMaxT(void *buf, ssize_t minLen, ssize_t maxLen, int flags, + int timeoutMS) + { + // note: this uses a soft timeout that is being reset after each received chunk + + ssize_t receivedLen = 0; + + do + { + ssize_t recvRes = + recvT(&((char*)buf)[receivedLen], maxLen-receivedLen, flags, timeoutMS); + receivedLen += recvRes; + } while(receivedLen < minLen); + + return (ssize_t)receivedLen; + } + +}; + + diff --git a/common/source/common/net/sock/SocketConnectException.h b/common/source/common/net/sock/SocketConnectException.h new file mode 100644 index 0000000..1de1ff0 --- /dev/null +++ b/common/source/common/net/sock/SocketConnectException.h @@ -0,0 +1,7 @@ +#pragma once + +#include "SocketException.h" +#include "common/Common.h" + +DECLARE_NAMEDSUBEXCEPTION(SocketConnectException, "SocketConnectException", SocketException) + diff --git a/common/source/common/net/sock/SocketDisconnectException.h b/common/source/common/net/sock/SocketDisconnectException.h new file mode 100644 index 0000000..a9d5357 --- /dev/null +++ b/common/source/common/net/sock/SocketDisconnectException.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "SocketException.h" + +DECLARE_NAMEDSUBEXCEPTION(SocketDisconnectException, "SocketDisconnectException", SocketException) + diff --git a/common/source/common/net/sock/SocketException.h b/common/source/common/net/sock/SocketException.h new file mode 100644 index 0000000..de6f665 --- /dev/null +++ b/common/source/common/net/sock/SocketException.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +DECLARE_NAMEDEXCEPTION(SocketException, "SocketException") + + diff --git a/common/source/common/net/sock/SocketInterruptedPollException.h b/common/source/common/net/sock/SocketInterruptedPollException.h new file mode 100644 index 0000000..ef72551 --- /dev/null +++ b/common/source/common/net/sock/SocketInterruptedPollException.h @@ -0,0 +1,8 @@ +#pragma once + +#include "SocketException.h" +#include "common/Common.h" + +DECLARE_NAMEDSUBEXCEPTION(SocketInterruptedPollException, "SocketInterruptedPollException", + SocketException) + diff --git a/common/source/common/net/sock/SocketTimeoutException.h b/common/source/common/net/sock/SocketTimeoutException.h new file mode 100644 index 0000000..c130283 --- /dev/null +++ b/common/source/common/net/sock/SocketTimeoutException.h @@ -0,0 +1,7 @@ +#pragma once + +#include "SocketException.h" +#include "common/Common.h" + +DECLARE_NAMEDSUBEXCEPTION(SocketTimeoutException, "SocketTimeoutException", SocketException) + diff --git a/common/source/common/net/sock/StandardSocket.cpp b/common/source/common/net/sock/StandardSocket.cpp new file mode 100644 index 0000000..79d1a49 --- /dev/null +++ b/common/source/common/net/sock/StandardSocket.cpp @@ -0,0 +1,850 @@ +#include +#include +#include +#include +#include +#include "StandardSocket.h" + +#include + + +#define STANDARDSOCKET_CONNECT_TIMEOUT_MS 5000 +#define STANDARDSOCKET_UDP_COOLING_SLEEP_US 10000 +#define STANDARDSOCKET_UDP_COOLING_RETRIES 5 + + +/** + * @throw SocketException + */ +StandardSocket::StandardSocket(int domain, int type, int protocol, bool epoll) + : isDgramSocket(type == SOCK_DGRAM) +{ + this->sockDomain = domain; + + this->sock = ::socket(domain, type, protocol); + if(sock == -1) + { + throw SocketException(std::string("Error during socket creation: ") + + System::getErrString() ); + } + + if (epoll) + { + this->epollFD = epoll_create(1); // "1" is just a hint (and is actually ignored) + if(epollFD == -1) + { + int sysErr = errno; + close(sock); + + throw SocketException(std::string("Error during epoll_create(): ") + + System::getErrString(sysErr) ); + } + this->addToEpoll(this); + } + else + this->epollFD = -1; +} + +/** + * Note: To be used by accept() or createSocketPair() only. + * + * @param fd will be closed by the destructor of this object + * @throw SocketException in case epoll_create fails, the caller will need to close the + * corresponding socket file descriptor (fd) + */ +StandardSocket::StandardSocket(int fd, unsigned short sockDomain, struct in_addr peerIP, + std::string peername) + : isDgramSocket(false) +{ + this->sock = fd; + this->sockDomain = sockDomain; + this->peerIP = peerIP; + this->peername = peername; + + this->epollFD = epoll_create(1); // "1" is just a hint (and is actually ignored) + if(epollFD == -1) + { + throw SocketException(std::string("Error during epoll_create(): ") + + System::getErrString() ); + } + + addToEpoll(this); +} + + +StandardSocket::~StandardSocket() +{ + if(this->epollFD != -1) + close(this->epollFD); + + close(this->sock); +} + +/** + * @throw SocketException + */ +void StandardSocket::createSocketPair(int domain, int type, int protocol, + StandardSocket** outEndpointA, StandardSocket** outEndpointB) +{ + int socket_vector[2]; + struct in_addr loopbackIP = {INADDR_LOOPBACK}; + + int pairRes = socketpair(domain, type, protocol, socket_vector); + + if(pairRes == -1) + { + throw SocketConnectException( + std::string("Unable to create local SocketPair. SysErr: ") + System::getErrString() ); + } + + *outEndpointA = NULL; + *outEndpointB = NULL; + + try + { + *outEndpointA = new StandardSocket(socket_vector[0], domain, loopbackIP, + std::string("Localhost:PeerFD#") + StringTk::intToStr(socket_vector[0]) ); + *outEndpointB = new StandardSocket(socket_vector[1], domain, loopbackIP, + std::string("Localhost:PeerFD#") + StringTk::intToStr(socket_vector[1]) ); + } + catch(SocketException& e) + { + if(*outEndpointA) + delete(*outEndpointA); + else + close(socket_vector[0]); + + if(*outEndpointB) + delete(*outEndpointB); + else + close(socket_vector[1]); + + throw; + } +} + +/** + * @throw SocketException + */ +void StandardSocket::connect(const char* hostname, unsigned short port) +{ + Socket::connect(hostname, port, sockDomain, SOCK_STREAM); +} + +/** + * @throw SocketException + */ +void StandardSocket::connect(const struct sockaddr* serv_addr, socklen_t addrlen) +{ + const int timeoutMS = STANDARDSOCKET_CONNECT_TIMEOUT_MS; + std::string peerAddr = Socket::endpointAddrToStr((const struct sockaddr_in*)serv_addr); + + LOG(SOCKLIB, DEBUG, "Connect StandardSocket", ("socket", this), ("addr", peerAddr), + ("bindIP", Socket::ipaddrToStr(bindIP))); + + // set peername if not done so already (e.g. by connect(hostname) ) + if(peername.empty() ) + peername = peerAddr; + + int flagsOrig = fcntl(sock, F_GETFL, 0); + fcntl(sock, F_SETFL, flagsOrig | O_NONBLOCK); // make the socket nonblocking + + int connRes = ::connect(sock, serv_addr, addrlen); + if(connRes) + { + if(errno == EINPROGRESS) + { // wait for "ready to send data" + struct pollfd pollStruct = {sock, POLLOUT, 0}; + int pollRes = poll(&pollStruct, 1, timeoutMS); + + if(pollRes > 0) + { // we got something (could also be an error) + + /* note: it's important to test ERR/HUP/NVAL here instead of POLLOUT only, because + POLLOUT and POLLERR can be returned together. */ + + if(pollStruct.revents & (POLLERR | POLLHUP | POLLNVAL) ) + throw SocketConnectException( + std::string("Unable to establish connection: ") + std::string(peername) ); + + // connection successfully established + + fcntl(sock, F_SETFL, flagsOrig); // set socket back to original mode + return; + } + else + if(!pollRes) + throw SocketConnectException( + std::string("Timeout connecting to: ") + std::string(peername) ); + else + throw SocketConnectException( + std::string("Error connecting to: ") + std::string(peername) + ". " + "SysErr: " + System::getErrString() ); + + } + } + else + { // immediate connect => strange, but okay... + fcntl(sock, F_SETFL, flagsOrig); // set socket back to original mode + return; + } + + throw SocketConnectException( + std::string("Unable to connect to: ") + std::string(peername) + + std::string(". SysErr: ") + System::getErrString() ); +} + + +/** + * @throw SocketException + */ +void StandardSocket::bindToAddr(in_addr_t ipAddr, unsigned short port) +{ + struct sockaddr_in localaddr_in; + memset(&localaddr_in, 0, sizeof(localaddr_in) ); + + localaddr_in.sin_family = sockDomain; + localaddr_in.sin_addr.s_addr = ipAddr; + localaddr_in.sin_port = htons(port); + + LOG(SOCKLIB, DEBUG, "Bind StandardSocket", ("socket", this), ("ipAddr", Socket::ipaddrToStr(ipAddr)), ("port", port)); + + int bindRes = ::bind(sock, (struct sockaddr *)&localaddr_in, sizeof(localaddr_in) ); + if (bindRes == -1) + throw SocketException("Unable to bind to port: " + StringTk::uintToStr(port) + + ". SysErr: " + System::getErrString() ); + + bindIP.s_addr = ipAddr; + bindPort = port; + + if (isDgramSocket) + peername = std::string("Listen(Port: ") + StringTk::uintToStr(bindPort) + std::string(")"); + +} + +/** + * @throw SocketException + */ +void StandardSocket::listen() +{ + unsigned backlog = 16; + + if(PThread::getCurrentThreadApp() ) + backlog = PThread::getCurrentThreadApp()->getCommonConfig()->getConnBacklogTCP(); + + int listenRes = ::listen(sock, backlog); + if(listenRes == -1) + throw SocketException(std::string("listen: ") + System::getErrString() ); + + peername = std::string("Listen(Port: ") + StringTk::uintToStr(bindPort) + std::string(")"); + + if(this->epollFD != -1) + { // we won't need epoll for listening sockets (we use it only for recvT/recvfromT) + close(this->epollFD); + this->epollFD = -1; + } + +} + +/** + * @throw SocketException + */ +Socket* StandardSocket::accept(struct sockaddr *addr, socklen_t *addrlen) +{ + int acceptRes = ::accept(sock, addr, addrlen); + if(acceptRes == -1) + { + throw SocketException(std::string("Error during socket accept(): ") + + System::getErrString() ); + } + + // prepare new socket object + struct in_addr acceptIP = ( (struct sockaddr_in*)addr)->sin_addr; + unsigned short acceptPort = ntohs( ( (struct sockaddr_in*)addr)->sin_port); + + std::string acceptPeername = endpointAddrToStr(acceptIP, acceptPort); + + try + { + Socket* acceptedSock = new StandardSocket(acceptRes, sockDomain, acceptIP, acceptPeername); + + return acceptedSock; + } + catch(SocketException& e) + { + close(acceptRes); + throw; + } +} + +/** + * @throw SocketException + */ +void StandardSocket::shutdown() +{ + int shutRes = ::shutdown(sock, SHUT_WR); + if(shutRes == -1) + { + throw SocketException(std::string("Error during socket shutdown(): ") + + System::getErrString() ); + } +} + +/** + * @throw SocketException + */ +void StandardSocket::shutdownAndRecvDisconnect(int timeoutMS) +{ + this->shutdown(); + + try + { + // receive until shutdown arrives + char buf[128]; + int recvRes; + do + { + recvRes = recvT(buf, sizeof(buf), 0, timeoutMS); + } while(recvRes > 0); + } + catch(SocketDisconnectException& e) + { + // just a normal thing to happen when we shutdown :) + } + +} + +#ifdef BEEGFS_NVFS +/** + * Note: This is a synchronous (blocking) version + * + * @throw SocketException + */ + +ssize_t StandardSocket::read(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + throw SocketException("Standard socket doesn't support RDMA read"); +} + +ssize_t StandardSocket::write(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey) +{ + throw SocketException("Standard socket doesn't support RDMA write"); +} +#endif /* BEEGFS_NVFS */ + +/** + * Note: This is a synchronous (blocking) version + * + * @throw SocketException + */ +ssize_t StandardSocket::send(const void *buf, size_t len, int flags) +{ + ssize_t sendRes = ::send(sock, buf, len, flags | MSG_NOSIGNAL); + if(sendRes == (ssize_t)len) + { + stats->incVals.netSendBytes += len; + return sendRes; + } + else + if(sendRes != -1) + { + throw SocketException( + std::string("send(): Sent only ") + StringTk::int64ToStr(sendRes) + + std::string(" bytes of the requested ") + StringTk::int64ToStr(len) + + std::string(" bytes of data") ); + } + + throw SocketDisconnectException( + "Disconnect during send() to: " + peername + "; " + "SysErr: " + System::getErrString() ); +} + +/** + * Note: ENETUNREACH (unreachable network) errors will be silenty discarded and not be returned to + * the caller. + * + * @throw SocketException + */ +ssize_t StandardSocket::sendto(const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen) +{ + const char* logContext = "StandardSocket::sendto"; + int tries = 0; + int errCode = 0; + long sleepUs = STANDARDSOCKET_UDP_COOLING_SLEEP_US; + + while (tries < STANDARDSOCKET_UDP_COOLING_RETRIES) + { + tries++; +#ifdef BEEGFS_DEBUG_IP + if (to != NULL) + LOG(COMMUNICATION, DEBUG, std::string("sendto"), + ("addr", Socket::endpointAddrToStr((const struct sockaddr_in*) to)), + ("bindIP", Socket::ipaddrToStr(bindIP)), + ("len", len)); +#endif + ssize_t sendRes = ::sendto(sock, buf, len, flags | MSG_NOSIGNAL, to, tolen); + if(sendRes == (ssize_t)len) + { + stats->incVals.netSendBytes += len; + return sendRes; + } + else + if(sendRes != -1) + { + throw SocketException( + std::string("send(): Sent only ") + StringTk::int64ToStr(sendRes) + + std::string(" bytes of the requested ") + StringTk::int64ToStr(len) + + std::string(" bytes of data") ); + } + + errCode = errno; + + if (errCode != EPERM) + break; + + // EPERM means UDP data is being sent too fast. This implements a + // cooling off mechanism to retry sending the data after sleeping. + // A random value is incorporated to prevent concurrent threads from + // waking up at the same time. + if (tries == 1) + { + sleepUs += rand.getNextInRange(1, STANDARDSOCKET_UDP_COOLING_SLEEP_US); + } + else + { + sleepUs += STANDARDSOCKET_UDP_COOLING_SLEEP_US; + } + + LogContext(logContext).log(Log_NOTICE, "Retry sendto " + peername + + " after sleeping for " + StringTk::intToStr((int) sleepUs) + + " us, try=" + StringTk::intToStr(tries)); + + struct timespec ns = { + .tv_sec = 0, + .tv_nsec = sleepUs * 1000 + }; + ::nanosleep(&ns, NULL); + } + + std::string toStr; + if(to) + { + toStr = Socket::endpointAddrToStr((struct sockaddr_in*)to); + } + + if(errCode == ENETUNREACH) + { + static bool netUnreachLogged = false; // to avoid log spamming + + if(!netUnreachLogged) + { // log unreachable net error once + netUnreachLogged = true; + + LogContext(logContext).log(Log_WARNING, + "Attempted to send message to unreachable network: " + toStr + "; " + + "peername: " + peername + "; " + + "(This error message will be logged only once.)"); + } + + return len; + } + + throw SocketDisconnectException("sendto(" + toStr + "): " + "Hard Disconnect from " + peername + ": " + System::getErrString(errCode) ); +} + +/** + * @throw SocketException + */ +ssize_t StandardSocket::recv(void *buf, size_t len, int flags) +{ + ssize_t recvRes = ::recv(sock, buf, len, flags); + if(recvRes > 0) + { + stats->incVals.netRecvBytes += recvRes; + return recvRes; + } + + if(recvRes == 0) + { + if (isDgramSocket) + { + LOG(COMMUNICATION, NOTICE, "Received empty UDP datagram.", peername); + return 0; + } + else + { + throw SocketDisconnectException(std::string("Soft disconnect from ") + peername); + } + } + else + { + throw SocketDisconnectException(std::string("Recv(): Hard disconnect from ") + + peername + ". SysErr: " + System::getErrString() ); + } +} + +/** + * Note: This is the default version, using epoll only => see man pages of select(2) bugs section + * + * @throw SocketException + */ +ssize_t StandardSocket::recvT(void *buf, size_t len, int flags, int timeoutMS) +{ + struct epoll_event epollEvent; + + if (epollFD == -1) + throw SocketException("recvT called on non-epoll socket instance"); + + while (true) + { + int epollRes = epoll_wait(epollFD, &epollEvent, 1, timeoutMS); + + if(likely( (epollRes > 0) && (epollEvent.events & EPOLLIN) ) ) + { + StandardSocket* s = reinterpret_cast(epollEvent.data.ptr); + return s->recv(buf, len, flags); + } + else + if(!epollRes) + { + throw SocketTimeoutException("Receive timed out from: " + peername); + break; + } + else + if(errno == EINTR) + continue; // retry, probably interrupted by gdb + else + if(epollEvent.events & EPOLLHUP) + { + throw SocketException( + "recvT(" + peername + "); Error: Hung up"); + break; + } + else + if(epollEvent.events & EPOLLERR) + { + throw SocketException( + "recvT(" + peername + "); Error condition flag set"); + break; + } + else + { + throw SocketException( + "recvT(" + peername + "); SysErr: " + System::getErrString() ); + break; + } + } +} + +/** + * @throw SocketException + */ +ssize_t StandardSocket::recvfrom(void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen) +{ + int recvRes = ::recvfrom(sock, buf, len, flags, from, fromlen); +#ifdef BEEGFS_DEBUG_IP + if (isDgramSocket && from) + LOG(COMMUNICATION, DEBUG, std::string("recvfrom"), + ("addr", Socket::endpointAddrToStr((const struct sockaddr_in*) from)), + ("bindIP", Socket::ipaddrToStr(bindIP)), + ("recvRes", recvRes)); +#endif + if(recvRes > 0) + { + stats->incVals.netRecvBytes += recvRes; + return recvRes; + } + + if(recvRes == 0) + { + if (isDgramSocket) + { + struct sockaddr_in* sin = (struct sockaddr_in*)from; + LOG(COMMUNICATION, NOTICE, "Received empty UDP datagram.", peername, + ("addr", (sin? Socket::endpointAddrToStr(sin) : std::string("null")))); + return 0; + } + else + { + throw SocketDisconnectException(std::string("Soft disconnect from ") + peername); + } + } + else + { + throw SocketDisconnectException( + std::string("Recvfrom(): Hard disconnect from ") + peername + ": " + + System::getErrString() ); + } +} + +void StandardSocket::addToEpoll(StandardSocket* other) +{ + struct epoll_event epollEvent; + epollEvent.events = EPOLLIN; + epollEvent.data.ptr = other; + + if(epoll_ctl(epollFD, EPOLL_CTL_ADD, other->sock, &epollEvent) == -1) + { + int sysErr = errno; + close(sock); + close(epollFD); + + throw SocketException(std::string("Unable to add sock to epoll set: ") + + System::getErrString(sysErr) ); + } + +} + +/** + * This is the epoll-based version. + * + * @throw SocketException + */ +ssize_t StandardSocket::recvfromT(void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, int timeoutMS) +{ + struct epoll_event epollEvent; + + if (epollFD == -1) + throw SocketException("recvfromT called on non-epoll socket instance"); + + while (true) + { + int epollRes = epoll_wait(epollFD, &epollEvent, 1, timeoutMS); + + if(likely( (epollRes > 0) && (epollEvent.events & EPOLLIN) ) ) + { + StandardSocket* s = reinterpret_cast(epollEvent.data.ptr); + int recvRes = s->recvfrom(buf, len, flags, from, fromlen); + return recvRes; + } + else + if(!epollRes) + { + throw SocketTimeoutException(std::string("Receive from ") + peername + " timed out"); + break; + } + else + if(errno == EINTR) + { + continue; // retry, probably interrupted by gdb + } + else + if(epollEvent.events & EPOLLHUP) + { + throw SocketException( + std::string("recvfromT(): epoll(): ") + peername + ": Hung up"); + break; + } + else + if(epollEvent.events & EPOLLERR) + { + throw SocketException( + std::string("recvfromT(): epoll(): ") + peername + ": Error condition"); + break; + } + else + { + throw SocketException( + std::string("recvfromT(): epoll(): ") + peername + ": " + System::getErrString() ); + break; + } + } +} + +/** + * @throw SocketException + */ +ssize_t StandardSocket::broadcast(const void *buf, size_t len, int flags, + struct in_addr* broadcastIP, unsigned short port) +{ + struct sockaddr_in broadcastAddr; + + memset(&broadcastAddr, 0, sizeof(broadcastAddr) ); + + broadcastAddr.sin_family = sockDomain; + broadcastAddr.sin_addr = *broadcastIP; + //broadcastAddr.sin_addr.s_addr = inet_addr("255.255.255.255");//htonl(INADDR_BROADCAST); + broadcastAddr.sin_port = htons(port); + + return this->sendto(buf, len, flags, + (struct sockaddr*)&broadcastAddr, sizeof(broadcastAddr) ); +} + +/** + * @throw SocketException + */ +void StandardSocket::setSoKeepAlive(bool enable) +{ + int keepAliveVal = (enable ? 1 : 0); + + int setRes = setsockopt(sock, + SOL_SOCKET, + SO_KEEPALIVE, + (char*)&keepAliveVal, + sizeof(keepAliveVal) ); + + if(setRes == -1) + throw SocketException(std::string("setSoKeepAlive: ") + System::getErrString() ); +} + +/** + * @throw SocketException + */ +void StandardSocket::setSoBroadcast(bool enable) +{ + int broadcastVal = (enable ? 1 : 0); + + int setRes = setsockopt(sock, + SOL_SOCKET, + SO_BROADCAST, + &broadcastVal, + sizeof(broadcastVal) ); + + if(setRes == -1) + throw SocketException(std::string("setSoBroadcast: ") + System::getErrString() ); +} + +/** + * @throw SocketException + */ +void StandardSocket::setSoReuseAddr(bool enable) +{ + int reuseVal = (enable ? 1 : 0); + + int setRes = setsockopt(sock, + SOL_SOCKET, + SO_REUSEADDR, + &reuseVal, + sizeof(reuseVal) ); + + if(setRes == -1) + throw SocketException(std::string("setSoReuseAddr: ") + System::getErrString() ); +} + +/** + * @throw SocketException + */ +int StandardSocket::getSoRcvBuf() +{ + int rcvBufLen; + socklen_t optlen = sizeof(rcvBufLen); + + int getRes = getsockopt(sock, + SOL_SOCKET, + SO_RCVBUF, + &rcvBufLen, + &optlen); + + if(getRes == -1) + throw SocketException(std::string("getSoRcvBuf: ") + System::getErrString() ); + + return rcvBufLen; +} + +/** + * Note: Increase only (buffer will not be set to a smaller value). + * Note: Currently, this method never throws an exception and hence doesn't return an error. (You + * could use getSoRcvBuf() if you're interested in the resulting buffer size.) + * + * @throw SocketException + */ +void StandardSocket::setSoRcvBuf(int size) +{ + /* note: according to socket(7) man page, the value given to setsockopt() is doubled and the + doubled value is returned by getsockopt() + + update 2022-05-13: the kernel doubles the value passed to setsockopt(SO_RCVBUF) to allow + for bookkeeping overhead. Halving the value is probably "not correct" but it's been this + way since 2010 and changing it will potentially do more harm than good at this point. + */ + + int halfSize = size/2; + int origBufLen = getSoRcvBuf(); + + if(origBufLen >= (size) ) + { // we don't decrease buf sizes (but this is not an error) + return; + } + + // try without force-flag (will internally reduce to global max setting without errrors) + + int setRes = setsockopt(sock, + SOL_SOCKET, + SO_RCVBUF, + &halfSize, + sizeof(halfSize) ); + + if(setRes) + LOG_DEBUG(__func__, Log_DEBUG, std::string("setSoRcvBuf error: ") + System::getErrString() ); + + // now try with force-flag (will allow root to exceed the global max setting) + // BUT: unforunately, our suse 10 build system doesn't support the SO_RCVBUFFORCE sock opt + + /* + setsockopt(sock, + SOL_SOCKET, + SO_RCVBUFFORCE, + &halfSize, + sizeof(halfSize) ); + */ +} + +/** + * @throw SocketException + */ +void StandardSocket::setTcpNoDelay(bool enable) +{ + int noDelayVal = (enable ? 1 : 0); + + int noDelayRes = setsockopt(sock, + IPPROTO_TCP, + TCP_NODELAY, + (char*)&noDelayVal, + sizeof(noDelayVal) ); + + if(noDelayRes == -1) + throw SocketException(std::string("setTcpNoDelay: ") + System::getErrString() ); +} + +/** + * @throw SocketException + */ +void StandardSocket::setTcpCork(bool enable) +{ + int corkVal = (enable ? 1 : 0); + + int setRes = setsockopt(sock, + SOL_TCP, + TCP_CORK, + &corkVal, + sizeof(corkVal) ); + + if(setRes == -1) + throw SocketException(std::string("setTcpCork: ") + System::getErrString() ); +} + + +StandardSocketGroup::StandardSocketGroup(int domain, int type, int protocol) + : StandardSocket(domain, type, protocol, true) +{ +} + +std::shared_ptr StandardSocketGroup::createSubordinate(int domain, int type, int protocol) +{ + std::shared_ptr newSock = std::make_shared(domain, type, protocol, false); + subordinates.push_back(newSock); + addToEpoll(newSock.get()); + return newSock; +} + +StandardSocketGroup::~StandardSocketGroup() +{ + // close the epollFD before the subordinates get cleared to prevent potential + // access to deleted memory in the epoll_wait handler. + ::close(epollFD); + epollFD = -1; +} diff --git a/common/source/common/net/sock/StandardSocket.h b/common/source/common/net/sock/StandardSocket.h new file mode 100644 index 0000000..e369674 --- /dev/null +++ b/common/source/common/net/sock/StandardSocket.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include "PooledSocket.h" + +class StandardSocket : public PooledSocket +{ + public: + StandardSocket(int domain, int type, int protocol=0, bool epoll = true); + virtual ~StandardSocket(); + + static void createSocketPair(int domain, int type, int protocol, + StandardSocket** outEndpointA, StandardSocket** outEndpointB); + + virtual void connect(const char* hostname, unsigned short port); + virtual void connect(const struct sockaddr* serv_addr, socklen_t addrlen); + virtual void bindToAddr(in_addr_t ipAddr, unsigned short port); + virtual void listen(); + virtual Socket* accept(struct sockaddr* addr, socklen_t* addrlen); + virtual void shutdown(); + virtual void shutdownAndRecvDisconnect(int timeoutMS); + +#ifdef BEEGFS_NVFS + virtual ssize_t read(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey); + virtual ssize_t write(const void *buf, size_t len, unsigned lkey, const uint64_t rbuf, unsigned rkey); +#endif /* BEEGFS_NVFS */ + + virtual ssize_t send(const void *buf, size_t len, int flags); + virtual ssize_t sendto(const void *buf, size_t len, int flags, + const struct sockaddr *to, socklen_t tolen); + + virtual ssize_t recv(void *buf, size_t len, int flags); + virtual ssize_t recvT(void *buf, size_t len, int flags, int timeoutMS); + + ssize_t recvfrom(void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); + ssize_t recvfromT(void *buf, size_t len, int flags, + struct sockaddr *from, socklen_t *fromlen, int timeoutMS); + + ssize_t broadcast(const void *buf, size_t len, int flags, + struct in_addr* broadcastIP, unsigned short port); + + + void setSoKeepAlive(bool enable); + void setSoBroadcast(bool enable); + void setSoReuseAddr(bool enable); + int getSoRcvBuf(); + void setSoRcvBuf(int size); + void setTcpNoDelay(bool enable); + void setTcpCork(bool enable); + + protected: + int sock; + unsigned short sockDomain; // socket domain (aka protocol family) e.g. PF_INET + const bool isDgramSocket; + int epollFD; // only valid for connected or dgram sockets, not valid (-1) for listening sockets + + StandardSocket(int fd, unsigned short sockDomain, struct in_addr peerIP, + std::string peername); + + void addToEpoll(StandardSocket* other); + + private: + RandomReentrant rand; + + public: + // getters & setters + virtual int getFD() const + { + return sock; + } + + inline unsigned short getSockDomain() + { + return sockDomain; + } + + + // inliners + + /** + * @throw SocketException + */ + inline ssize_t sendto(const void *buf, size_t len, int flags, + struct in_addr ipAddr, unsigned short port) + { + struct sockaddr_in peerAddr; + + // memset(&peerAddr, 0, sizeof(peerAddr) ); // not required (we set all fields below) + + peerAddr.sin_family = sockDomain; + peerAddr.sin_port = htons(port); + peerAddr.sin_addr.s_addr = ipAddr.s_addr; + + return this->sendto(buf, len, flags, + (struct sockaddr*)&peerAddr, sizeof(peerAddr) ); + } + + /** + * @return true if incoming data is available, false if a timeout occurred + * @throw SocketException on error + */ + inline bool waitForIncomingData(int timeoutMS) + { + struct pollfd pollStruct = {sock, POLLIN, 0}; + int pollRes = poll(&pollStruct, 1, timeoutMS); + + if( (pollRes > 0) && (pollStruct.revents & POLLIN) ) + { + return true; + } + else + if(!pollRes) + return false; + else + if(pollStruct.revents & POLLERR) + throw SocketException( + std::string("waitForIncomingData(): poll(): ") + peername + ": Error condition"); + else + if(pollStruct.revents & POLLHUP) + throw SocketException( + std::string("waitForIncomingData(): poll(): ") + peername + ": Hung up"); + else + if(pollStruct.revents & POLLNVAL) + throw SocketException( + std::string("waitForIncomingData(): poll(): ") + peername + + ": Invalid request/fd"); + else + if(errno == EINTR) + throw SocketInterruptedPollException( + std::string("waitForIncomingData(): poll(): ") + peername + ": " + + System::getErrString() ); + else + throw SocketException( + std::string("waitForIncomingData(): poll(): ") + peername + + ": " + System::getErrString() ); + } +}; + +/** + * A "main" StandardSocket (this) that manages a set of subordinate StandardSocket + * instances. + * + * The general idea is that "this" and subordinates can be used for sending. recvT() and + * recfromT() are invoked upon "this" to wait for packets from any of the sockets + * in this group. + */ +class StandardSocketGroup : public StandardSocket +{ + private: + std::vector> subordinates; + + public: + StandardSocketGroup(int domain, int type, int protocol=0); + + /** + * Create a new StandardSocket that is subordinate to "this". The socket can be used + * for anything except methods that require epoll (i.e. recvT(), recvfromT(). + */ + std::shared_ptr createSubordinate(int domain, int type, int protocol=0); + + virtual ~StandardSocketGroup(); +}; diff --git a/common/source/common/nodes/AbstractNodeStore.h b/common/source/common/nodes/AbstractNodeStore.h new file mode 100644 index 0000000..3a796d7 --- /dev/null +++ b/common/source/common/nodes/AbstractNodeStore.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +enum class NodeStoreResult +{ + Unchanged, + Added, + Updated, + Error +}; + +class AbstractNodeStore +{ + protected: + typedef std::map> NodeMap; + + public: + + virtual NodeStoreResult addOrUpdateNode(NodeHandle node) = 0; + virtual NodeStoreResult addOrUpdateNodeEx(NodeHandle node, NumNodeID* outNodeNumID) = 0; + + virtual NodeHandle referenceFirstNode() const = 0; + + virtual std::vector referenceAllNodes() const = 0; + + virtual size_t getSize() const = 0; + + + protected: + AbstractNodeStore(NodeType storeType) : storeType(storeType) {} + virtual ~AbstractNodeStore() {} + + AbstractNodeStore(const AbstractNodeStore&) = delete; + AbstractNodeStore(AbstractNodeStore&&) = delete; + AbstractNodeStore& operator=(const AbstractNodeStore&) = delete; + AbstractNodeStore& operator=(AbstractNodeStore&&) = delete; + + NodeType storeType; // will be applied to all contained nodes on addOrUpdate() + + + public: + // getters & setters + + NodeType getStoreType() const + { + return storeType; + } +}; + diff --git a/common/source/common/nodes/CapacityPoolType.h b/common/source/common/nodes/CapacityPoolType.h new file mode 100644 index 0000000..e2d9c94 --- /dev/null +++ b/common/source/common/nodes/CapacityPoolType.h @@ -0,0 +1,14 @@ +#pragma once + +/** + * note: these values are used as array index, so they must be sequential and zero-based + */ +enum CapacityPoolType +{ + CapacityPool_NORMAL=0, // lot of free space + CapacityPool_LOW, // getting low on free space + CapacityPool_EMERGENCY, // extremely low on free space or erroneous + + CapacityPool_END_DONTUSE // the final value to define array size +}; + diff --git a/common/source/common/nodes/ClientOps.cpp b/common/source/common/nodes/ClientOps.cpp new file mode 100644 index 0000000..3989688 --- /dev/null +++ b/common/source/common/nodes/ClientOps.cpp @@ -0,0 +1,185 @@ +#include "ClientOps.h" + +#include +#include +#include + +#include + +#include + +/** +* Add a list of ops belonging to a specific id (e.g. ip/user) to the store if there isn't one already +* or sum it up with the existing one +*/ +bool ClientOps::addOpsList(uint64_t id, const OpsList& opsList) +{ + const std::lock_guard lock(idOpsMapMutex); + + // make sure all idOpsLists have the same size + if(!oldIdOpsMap.empty()) + { + if(oldIdOpsMap.begin()->second.size() != opsList.size()) + { + LOG(GENERAL, ERR, "Tried to add opsList with different size than old stored ones."); + return false; + } + } + + if(!idOpsMap.empty()) + { + if(idOpsMap.begin()->second.size() != opsList.size()) + { + LOG(GENERAL, ERR, "Tried to add opsList with different size than already stored ones."); + return false; + } + } + + sumOpsListValues(sumOpsList, opsList); + sumOpsListValues(idOpsMap[id], opsList); + + return true; +} + +/** +* Sum up two lists of ops. +*/ +void ClientOps::sumOpsListValues(OpsList& list1, const OpsList& list2) const +{ + // Also initializes list1 to zero if it doesn't exist yet + list1.resize(list2.size()); + + std::transform(list1.begin(), list1.end(), list2.begin(), list1.begin(), sum); +} + + +/** +* Return the difference of all summed up op lists, mapped to their belonging id, between +* the new and the old data (before clear() was called). +*/ +ClientOps::IdOpsMap ClientOps::getDiffOpsMap() const +{ + IdOpsMap diffIdOpsMap; + + // if there isn't an old map, just return empty + if (oldIdOpsMap.empty()) + return diffIdOpsMap; + + for (auto opsMapIter = idOpsMap.begin(); opsMapIter != idOpsMap.end(); opsMapIter++) + { + const auto oldOpsMapIter = oldIdOpsMap.find(opsMapIter->first); + + if (oldOpsMapIter == oldIdOpsMap.end()) + continue; + + ASSERT(opsMapIter->second.size() == oldOpsMapIter->second.size()); + + std::transform(opsMapIter->second.begin(), opsMapIter->second.end(), + oldOpsMapIter->second.begin(), std::back_inserter(diffIdOpsMap[opsMapIter->first]), + diff); + } + + return diffIdOpsMap; +} + +/** +* Returns one op list with the sum over all diff op lists from all ids. If there isn't an old +* list, returns an empty list +*/ +ClientOps::OpsList ClientOps::getDiffSumOpsList() const +{ + OpsList diffOpsList; + + std::transform(sumOpsList.begin(), sumOpsList.end(), oldSumOpsList.begin(), + std::back_inserter(diffOpsList), diff); + + return diffOpsList; +} + +/** +* Call when all data for the current data set is collected. Moves current data to old data. +*/ +void ClientOps::clear() +{ + const std::lock_guard lock(idOpsMapMutex); + oldIdOpsMap.clear(); + std::swap(idOpsMap, oldIdOpsMap); + oldSumOpsList.clear(); + std::swap(sumOpsList, oldSumOpsList); +} + +/** +* Request client ops data from a node and returns an unordered map . +*/ +ClientOpsRequestor::IdOpsUnorderedMap ClientOpsRequestor::request(Node& node, bool perUser) +{ + uint64_t currentID = ~0ULL; + bool moreData = false; + uint64_t numOps = 0; + + IdOpsUnorderedMap resultMap; + + do + { + GetClientStatsMsg getClientStatsMsg(currentID); + + if (perUser) + getClientStatsMsg.addMsgHeaderFeatureFlag(GETCLIENTSTATSMSG_FLAG_PERUSERSTATS); + + const auto respMsg = MessagingTk::requestResponse(node, getClientStatsMsg, + NETMSGTYPE_GetClientStatsResp); + + if (!respMsg) + { + LOG(GENERAL, DEBUG, "Node is not responding: " + node.getNodeIDWithTypeStr()); + return ClientOpsRequestor::IdOpsUnorderedMap(); + } + + GetClientStatsRespMsg* respMsgCast = static_cast(respMsg.get()); + std::vector dataVector; + respMsgCast->getStatsVector().swap(dataVector); + + moreData = !!dataVector.at(NODE_OPS_POS_MORE_DATA); + numOps = dataVector.at(NODEOPS_POS_NUMOPS); + + if (dataVector.at(NODE_OPS_POS_LAYOUT_VERSION) != OPCOUNTER_VEC_LAYOUT_VERS) + { + LOG(GENERAL, ERR, "Protocol version mismatch in received Message from " + + node.getNodeIDWithTypeStr()); + return ClientOpsRequestor::IdOpsUnorderedMap(); + } + + auto iter = dataVector.begin(); + iter += NODE_OPS_POS_FIRSTDATAELEMENT; + unsigned counter = 0; + ClientOps::OpsList opsList; + + for (; iter != dataVector.end(); iter++) + { + if (counter == 0) + currentID = *iter; + else + opsList.push_back(*iter); + + counter++; + + if (counter > numOps) + { + resultMap.insert(std::make_pair(currentID, std::move(opsList))); + + opsList.clear(); + counter = 0; + } + } + + if (counter != 0) + { + LOG(GENERAL, ERR, + "Reported length of ClientStats OpsList doesn't match the actual length."); + return ClientOpsRequestor::IdOpsUnorderedMap(); + } + } + while (moreData); + + return resultMap; +} diff --git a/common/source/common/nodes/ClientOps.h b/common/source/common/nodes/ClientOps.h new file mode 100644 index 0000000..a049647 --- /dev/null +++ b/common/source/common/nodes/ClientOps.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include + +#include + + +/** +* Collects client ops data mapped to an id (ip/user) and calculates diffs and sums. +*/ +class ClientOps +{ + public: + using OpsList = std::list; + using IdOpsMap = std::map; + + bool addOpsList(uint64_t id, const OpsList& opsList); + + IdOpsMap getDiffOpsMap() const; + OpsList getDiffSumOpsList() const; + + const IdOpsMap& getAbsoluteOpsMap() const + { + return idOpsMap; + } + + const OpsList& getAbsoluteSumOpsList() const + { + return sumOpsList; + } + + void clear(); + + + protected: + void sumOpsListValues(OpsList& list1, const OpsList& list2) const; + + // helper callback functions + static uint64_t sum(uint64_t a, uint64_t b) + { + return a + b; + } + + static uint64_t diff(uint64_t a, uint64_t b) + { + return a - b; + } + + IdOpsMap idOpsMap; + IdOpsMap oldIdOpsMap; + OpsList sumOpsList; + OpsList oldSumOpsList; + Mutex idOpsMapMutex; +}; + +/** +* Helper class to request client ops from nodes. +*/ +class ClientOpsRequestor +{ + public: + typedef std::unordered_map IdOpsUnorderedMap; + + static IdOpsUnorderedMap request(Node& node, bool perUser); +}; + diff --git a/common/source/common/nodes/DynamicPoolLimits.cpp b/common/source/common/nodes/DynamicPoolLimits.cpp new file mode 100644 index 0000000..b2629a0 --- /dev/null +++ b/common/source/common/nodes/DynamicPoolLimits.cpp @@ -0,0 +1,2 @@ +#include "DynamicPoolLimits.h" + diff --git a/common/source/common/nodes/DynamicPoolLimits.h b/common/source/common/nodes/DynamicPoolLimits.h new file mode 100644 index 0000000..fd22755 --- /dev/null +++ b/common/source/common/nodes/DynamicPoolLimits.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +#include + +class DynamicPoolLimits +{ + public: + DynamicPoolLimits(int64_t lowLimit, int64_t emergencyLimit, + int64_t normalSpreadThreshold, int64_t lowSpreadThreshold, + int64_t lowDynamicLimit, int64_t emergencyDynamicLimit) : + lowLimit(lowLimit), emergencyLimit(emergencyLimit), + normalSpreadThreshold(normalSpreadThreshold), lowSpreadThreshold(lowSpreadThreshold), + lowDynamicLimit(lowDynamicLimit), emergencyDynamicLimit(emergencyDynamicLimit) + { } + + private: + const int64_t lowLimit; + const int64_t emergencyLimit; + const int64_t normalSpreadThreshold; + const int64_t lowSpreadThreshold; + const int64_t lowDynamicLimit; + const int64_t emergencyDynamicLimit; + + public: + // getters + int64_t getLowLimit() const + { + return this->lowLimit; + } + + int64_t getEmergencyLimit() const + { + return this->emergencyLimit; + } + + int64_t getNormalSpreadThreshold() const + { + return this->normalSpreadThreshold; + } + + int64_t getLowSpreadThreshold() const + { + return this->lowSpreadThreshold; + } + + int64_t getLowDynamicLimit() const + { + return this->lowDynamicLimit; + } + + int64_t getEmergencyDynamicLimit() const + { + return this->emergencyDynamicLimit; + } + + + // inliners + CapacityPoolType getPoolTypeFromFreeCapacity(int64_t freeCapacity) const + { + if(freeCapacity > this->lowLimit) + return CapacityPool_NORMAL; + + if(freeCapacity > this->emergencyLimit) + return CapacityPool_LOW; + + return CapacityPool_EMERGENCY; + } + + /** + * @return whether the dynamically raised limit is active for the "NORMAL" pool, determined + * from the given minimum/maximum free space and the normalSpreadThreshold + */ + bool demotionActiveNormalPool(const MinMaxStore& freeMinMax) const + { + int64_t spread = freeMinMax.getMax() - freeMinMax.getMin(); + return spread > this->normalSpreadThreshold; + } + + /** + * @return whether the dynamically raised limit is active for the "LOW" pool, determined from + * the given minimum/maximum free space and the lowSpreadThreshold + */ + bool demotionActiveLowPool(const MinMaxStore& freeMinMax) const + { + int64_t spread = freeMinMax.getMax() - freeMinMax.getMin(); + return spread > this->lowSpreadThreshold; + } + + /** + * @return whether a target currently assigned to the NORMAL pool should be demoted to + * the LOW pool under the assumption that demotion is active. + */ + bool demoteNormalToLow(int64_t free) const + { + return free <= this->lowDynamicLimit; + } + + /** + * @return whether a target currently assigned to the LOW pool should be demoted to + * the EMERGENCY pool under the assumption that demotion is active. + */ + bool demoteLowToEmergency(int64_t free) const + { + return free <= this->emergencyDynamicLimit; + } +}; + diff --git a/common/source/common/nodes/LocalNode.h b/common/source/common/nodes/LocalNode.h new file mode 100644 index 0000000..7769050 --- /dev/null +++ b/common/source/common/nodes/LocalNode.h @@ -0,0 +1,39 @@ +#pragma once + + +#include +#include +#include + + + +/** + * This class is used by nodes/services to represent themselves e.g. in a NodeStore. It uses a + * special conn pool for internal communication (e.g. to avoid TCP if the process is sending a msg + * to itself, like a single mds would do for a mkdir). + */ +class LocalNode : public Node +{ + public: + /** + * @param portTCP not used internally, but required when connection info is exported + */ + LocalNode(NodeType nodeType, std::string alias, NumNodeID nodeNumID, unsigned short portUDP, + unsigned short portTCP, NicAddressList& nicList): + Node(nodeType, std::move(alias), nodeNumID, portUDP) + { + this->portTCP = portTCP; + + setConnPool(new LocalNodeConnPool(*this, nicList) ); + } + + private: + unsigned short portTCP; // not used internally, but required when connection info is exported + + public: + virtual unsigned short getPortTCP() + { + return portTCP; + } +}; + diff --git a/common/source/common/nodes/LocalNodeConnPool.cpp b/common/source/common/nodes/LocalNodeConnPool.cpp new file mode 100644 index 0000000..a9dc102 --- /dev/null +++ b/common/source/common/nodes/LocalNodeConnPool.cpp @@ -0,0 +1,236 @@ +#include +#include +#include +#include +#include +#include "LocalNodeConnPool.h" + +#include + +#define NODECONNPOOL_SHUTDOWN_WAITTIMEMS 1500 + +/** + * @param nicList an internal copy will be created + */ +LocalNodeConnPool::LocalNodeConnPool(Node& parentNode, NicAddressList& nicList) : + NodeConnPool(parentNode, 0, nicList) +{ + this->nicList = nicList; + + this->establishedConns = 0; + this->availableConns = 0; + + this->numCreatedWorkers = 0; + + this->maxConns = PThread::getCurrentThreadApp()->getCommonConfig()->getConnMaxInternodeNum(); +} + +LocalNodeConnPool::~LocalNodeConnPool() +{ + LogContext log("LocalNodeConnPool::~LocalNodeConnPool"); + + UnixConnWorkerList separateConnWorkerList = connWorkerList; // because invalidateStreamSocket + // changes the original connList + + if (!separateConnWorkerList.empty()) + { + log.log(4, std::string("Closing ") + StringTk::intToStr(separateConnWorkerList.size() ) + + std::string(" connections...") ); + } + + for(UnixConnWorkerListIter iter = separateConnWorkerList.begin(); + iter != separateConnWorkerList.end(); + iter++) + { + invalidateStreamSocket( (*iter)->getClientEndpoint() ); + } + + nicList.clear(); +} + +/** + * @param allowWaiting true to allow waiting if all avaiable conns are currently in use + * @return connected socket or NULL if all conns busy and waiting is not allowed + * @throw SocketConnectException if all connection attempts fail, SocketException if other + * connection problem occurs (e.g. during hand-shake) + */ +Socket* LocalNodeConnPool::acquireStreamSocketEx(bool allowWaiting) +{ + UnixConnWorker* worker = NULL; + + { + const std::lock_guard lock(mutex); + + if(!availableConns && (establishedConns == maxConns) ) + { // wait for a conn to become available (or disconnected) + + if(!allowWaiting) + { // all conns in use and waiting not allowed => exit + return NULL; + } + + while(!availableConns && (establishedConns == maxConns) ) + changeCond.wait(&mutex); + } + + + if(likely(availableConns) ) + { + // established connection available => grab it + + UnixConnWorkerListIter iter = connWorkerList.begin(); + + while(!(*iter)->isAvailable() ) + iter++; + + worker = *iter; + worker->setAvailable(false); + + availableConns--; + + return worker->getClientEndpoint(); + } + + + // no conn available, but maxConns not reached yet => establish a new conn + + std::string contextStr = std::string("LocalNodeConn (acquire stream)"); + LogContext log(contextStr); + + establishedConns++; + + log.log(Log_DEBUG, "Establishing new stream connection to: internal"); + + try + { + worker = new LocalConnWorker(StringTk::intToStr(++numCreatedWorkers) ); + + worker->start(); + + log.log(Log_DEBUG, "Connected: internal"); + } + catch(ComponentInitException& e) + { + log.log(Log_WARNING, "Internal connect failed. Exception: " + std::string(e.what() ) ); + } + + + if(worker) + { + // success => add to list (as unavailable) + connWorkerList.push_back(worker); + } + else + { + // unable to connect + establishedConns--; + + log.logErr("Connection attempt failed. Giving up."); + } + } + + if(!worker) + throw SocketConnectException("Local connection attempt failed."); + + return worker->getClientEndpoint(); +} + +void LocalNodeConnPool::releaseStreamSocket(Socket* sock) +{ + const std::lock_guard lock(mutex); + + UnixConnWorkerListIter iter = connWorkerList.begin(); + + while( ( (*iter)->getClientEndpoint() != sock) && (iter != connWorkerList.end() ) ) + iter++; + + if(iter == connWorkerList.end() ) + { + std::string contextStr = std::string("LocalNodeConn (release stream: ") + + sock->getPeername() + ")"; + LogContext log(contextStr); + + log.logErr("Tried to release a socket that was not found in the pool"); + } + else + { + availableConns++; + + UnixConnWorker* worker = *iter; + worker->setAvailable(true); + + changeCond.signal(); + } +} + +void LocalNodeConnPool::invalidateStreamSocket(Socket* sock) +{ + std::string contextStr = std::string("LocalNodeConn (invalidate stream: ") + + sock->getPeername() + ")"; + LogContext log(contextStr); + + bool sockValid = true; + UnixConnWorker* worker = NULL; + + { + const std::lock_guard lock(mutex); + + UnixConnWorkerListIter iter = connWorkerList.begin(); + + while( ( (*iter)->getClientEndpoint() != sock) && (iter != connWorkerList.end() ) ) + iter++; + + if(iter == connWorkerList.end() ) + { + log.logErr("Tried to remove a socket that was not found in the pool"); + sockValid = false; + } + else + { + establishedConns--; + + worker = *iter; + + connWorkerList.erase(iter); + + changeCond.signal(); + } + } + + if(!sockValid) + return; + + + worker->selfTerminate(); + + try + { + worker->getClientEndpoint()->shutdownAndRecvDisconnect(NODECONNPOOL_SHUTDOWN_WAITTIMEMS); + + log.log(3, std::string("Disconnected: ") + sock->getPeername() ); + } + catch(SocketException& e) + { + log.logErr(std::string("Exceptional disconnect: ") + + sock->getPeername() + std::string(". ") + e.what() ); + } + + worker->join(); + + delete(worker); + +} + +bool LocalNodeConnPool::updateInterfaces(unsigned short streamPort, const NicAddressList& nicList) +{ + const std::lock_guard lock(nicListMutex); + // ignore streamPort + bool changed = false; + if (this->nicList.size() != nicList.size() || + !std::equal(this->nicList.begin(), this->nicList.end(), nicList.begin())) + { + changed = true; + this->nicList = nicList; + } + return changed; +} diff --git a/common/source/common/nodes/LocalNodeConnPool.h b/common/source/common/nodes/LocalNodeConnPool.h new file mode 100644 index 0000000..7fe3fae --- /dev/null +++ b/common/source/common/nodes/LocalNodeConnPool.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +typedef std::list UnixConnWorkerList; +typedef UnixConnWorkerList::iterator UnixConnWorkerListIter; + +/** + * This is the conn pool which is used when a node sends network messages to itself, e.g. like a + * single mds would do in case of an incoming mkdir msg. + * It is based on unix sockets for inter-process communication instead of network sockets and + * creates a handler thread for each established connection to process "incoming" msgs. + */ +class LocalNodeConnPool : public NodeConnPool +{ + public: + LocalNodeConnPool(Node& parentNode, NicAddressList& nicList); + virtual ~LocalNodeConnPool(); + + Socket* acquireStreamSocketEx(bool allowWaiting); + void releaseStreamSocket(Socket* sock); + void invalidateStreamSocket(Socket* sock); + + private: + NicAddressList nicList; + Mutex nicListMutex; + UnixConnWorkerList connWorkerList; + + unsigned availableConns; // available established conns + unsigned establishedConns; // not equal to connList.size!! + unsigned maxConns; + + int numCreatedWorkers; + + Mutex mutex; + Condition changeCond; + + public: + // getters & setters + + NicAddressList getNicList() + { + // lock nicListMutex instead of mutex. acquireStreamSocketEx may lock + // mutex for "long" periods + const std::lock_guard lock(nicListMutex); + return nicList; + } + + bool updateInterfaces(unsigned short streamPort, const NicAddressList& nicList); + +}; + diff --git a/common/source/common/nodes/MirrorBuddyGroup.h b/common/source/common/nodes/MirrorBuddyGroup.h new file mode 100644 index 0000000..1e80d7f --- /dev/null +++ b/common/source/common/nodes/MirrorBuddyGroup.h @@ -0,0 +1,35 @@ +#pragma once + +#include + +struct MirrorBuddyGroup +{ + uint16_t firstTargetID = 0; + uint16_t secondTargetID = 0; + + MirrorBuddyGroup() = default; + + MirrorBuddyGroup(uint16_t firstTargetID, uint16_t secondTargetID): + firstTargetID(firstTargetID), secondTargetID(secondTargetID) + { + } + + template + static void serialize(This* obj, Ctx& ctx) + { + ctx + % obj->firstTargetID + % obj->secondTargetID; + } +}; + + +typedef std::list MirrorBuddyGroupList; +typedef MirrorBuddyGroupList::iterator MirrorBuddyGroupListIter; +typedef MirrorBuddyGroupList::const_iterator MirrorBuddyGroupListCIter; + +typedef std::map MirrorBuddyGroupMap; // keys: MBG-IDs, values: MBGs +typedef MirrorBuddyGroupMap::iterator MirrorBuddyGroupMapIter; +typedef MirrorBuddyGroupMap::const_iterator MirrorBuddyGroupMapCIter; +typedef MirrorBuddyGroupMap::value_type MirrorBuddyGroupMapVal; + diff --git a/common/source/common/nodes/MirrorBuddyGroupCreator.cpp b/common/source/common/nodes/MirrorBuddyGroupCreator.cpp new file mode 100644 index 0000000..5455595 --- /dev/null +++ b/common/source/common/nodes/MirrorBuddyGroupCreator.cpp @@ -0,0 +1,527 @@ +#include +#include +#include +#include +#include +#include + +#include "MirrorBuddyGroupCreator.h" + +#include + +/** + * @return FhgfsOpsErr_... + */ +FhgfsOpsErr MirrorBuddyGroupCreator::addGroup(uint16_t primaryTargetID, uint16_t secondaryTargetID, + uint16_t forcedGroupID) +{ + LogContext log = LogContext("MirrorBuddyGroupCreator (addGroup)"); + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + // disallow meta buddy group creation if the selected secondary target would have the root inode, + // not the primary. while we could copy the root inode from secondary to primary, forcing the + // non-mirrored root inode owner to be the primary of a group is much less error-prone. + if (nodeType == NODETYPE_Meta && secondaryTargetID == metaRoot->getID().val()) + return FhgfsOpsErr_NOTOWNER; + + uint16_t newGroupID; + const FhgfsOpsErr addRes = addGroupComm(primaryTargetID, secondaryTargetID, forcedGroupID, + newGroupID); + + if (addRes != FhgfsOpsErr_SUCCESS) + { + std::string errStr; + + if (addRes == FhgfsOpsErr_INUSE) + errStr = "One of the targets is already mapped to a buddy group."; + else + if (addRes == FhgfsOpsErr_UNKNOWNTARGET || addRes == FhgfsOpsErr_UNKNOWNNODE) + errStr = boost::lexical_cast(addRes); + else + if (addRes == FhgfsOpsErr_EXISTS) + errStr = "Mirror buddy group with ID " + StringTk::uintToStr(forcedGroupID) + + " already exists."; + else + errStr = "Internal error (Please check metadata server log)."; + + const std::string logMessage("Failed to add mirror buddy group: primaryTargetID " + + StringTk::uintToStr(primaryTargetID) + "; secondaryTargetID " + + StringTk::uintToStr(secondaryTargetID) + "; Error: " + errStr); + std::cerr << logMessage << std::endl; + log.logErr(logMessage); + } + else + { + const std::string logMessage("Mirror buddy group successfully set: groupID " + + StringTk::uintToStr(newGroupID) + " -> target IDs " + + StringTk::uintToStr(primaryTargetID) + ", " + StringTk::uintToStr(secondaryTargetID) ); + std::cout << logMessage << std::endl; + log.log(Log_NOTICE, logMessage); + + retVal = FhgfsOpsErr_SUCCESS; + } + + return retVal; +} + +/* + * @param forcedGroupID 0 means create new group, otherwise create group with that ID + * @param outNewGroupID will be set by response message; will have the generated ID of a newly + * created group or the old group ID if a group was changed + * @return FhgfsOpsErr_... + */ +FhgfsOpsErr MirrorBuddyGroupCreator::addGroupComm(uint16_t primaryID, uint16_t secondaryID, + uint16_t forcedGroupID, uint16_t& outNewGroupID) const +{ + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + const auto mgmtNode = this->mgmtNodes->referenceFirstNode(); + + SetMirrorBuddyGroupRespMsg* respMsgCast; + + SetMirrorBuddyGroupMsg msg(nodeType, primaryID, secondaryID, forcedGroupID, false); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_SetMirrorBuddyGroupResp); + if(!respMsg) + { + retVal = FhgfsOpsErr_COMMUNICATION; + goto err_cleanup; + } + + respMsgCast = (SetMirrorBuddyGroupRespMsg*)respMsg.get(); + + retVal = (FhgfsOpsErr)respMsgCast->getResult(); + + if (forcedGroupID != 0) + outNewGroupID = forcedGroupID; + else + outNewGroupID = respMsgCast->getBuddyGroupID(); + +err_cleanup: + return retVal; +} + +/** + * Create all MirrorBuddyGroups which was generated by the automatic mode + * + * @param retValGeneration the return value of the MirrorBuddyGroup generation + * @param buddyGroupIDs A list with all MirrorBuddyGroupIDs to create + * @param buddyGroups A list with all MirrorBuddyGroups to create + * @return true if all MirrorBuddyGroups created successful, false if not + */ +bool MirrorBuddyGroupCreator::createMirrorBuddyGroups(FhgfsOpsErr retValGeneration, + const UInt16List* buddyGroupIDs, const MirrorBuddyGroupList* buddyGroups) +{ + if( (retValGeneration == FhgfsOpsErr_SUCCESS) || + ( (retValGeneration == FhgfsOpsErr_INVAL) && this->cfgForce) ) + { // create the MirrorBuddyGroup + if(!this->cfgDryrun) + { // but only when it is not a dryrun + for(ZipConstIterRange iter(*buddyGroupIDs, *buddyGroups); + !iter.empty(); ++iter) + { + FhgfsOpsErr retValAddGroup = addGroup( + (iter()->second)->firstTargetID, + (iter()->second)->secondTargetID, + *(iter()->first)); + + if(retValAddGroup != FhgfsOpsErr_SUCCESS) + return false; + } + } + } + + return true; +} + +/** + * Removes all nodes / targets from the node ID list which are part of a mirror buddy group. + * + * @param oldPrimaryIDs PrimaryTargetIDs from existing MirrorBuddyGroups + * @param oldSecondaryIDs SecondaryTargetIDs from existing MirrorBuddyGroups + * @return true if all targets successful removed from TargetMapper, false on error + */ +bool MirrorBuddyGroupCreator::removeTargetsFromExistingMirrorBuddyGroups( + const UInt16List* oldPrimaryIDs, const UInt16List* oldSecondaryIDs) +{ + bool retVal = true; + + for (ZipConstIterRange primSecIDIter(*oldPrimaryIDs, *oldSecondaryIDs); + !primSecIDIter.empty(); ++primSecIDIter) + { + retVal = retVal && this->localTargetMapper.unmapTarget(*(primSecIDIter()->first)); + retVal = retVal && this->localTargetMapper.unmapTarget(*(primSecIDIter()->second)); + } + + return retVal; +} + +/** + * Find the TargetNumID of the next target which should be used for a MirrorBuddyGroup. A target + * from the storage server with the most targets will be selected. It is possible to ignore the + * targets of a storage server. The selected target is removed from the given TargetMapper. + * + * @param nodeNumIdToIgnore The NodeNumID to ignore in the search or 0 to disable the ignore feature + * @param storagePoolId find only targets in this storagepool; might be (and defaults to) + * INVALID_POOL_ID; in which case it is ignored + * @return the TargetNumID to use or 0 if no target found + */ +uint16_t MirrorBuddyGroupCreator::findNextTarget(NumNodeID nodeNumIdToIgnore, + StoragePoolId storagePoolId) +{ + uint16_t retVal = 0; + size_t maxNumTargets = 0; + + for (const auto& node : this->nodes->referenceAllNodes()) + { + if (node->getNumID() == nodeNumIdToIgnore) + continue; + + UInt16List targetList; + + UInt16List availableTargets; + this->localTargetMapper.getTargetsByNode(node->getNumID(), availableTargets); + + // if a storage pool is given, filter the targets now + if (storagePoolId != StoragePoolStore::INVALID_POOL_ID) + { + const UInt16Set allowedTargets = storagePoolStore->getPool(storagePoolId)->getTargets(); + // note: set_intersection needs sorted containers, thus we sort availableTargets first; + // allowedTargets is a set, so it is sorted already + availableTargets.sort(); + + std::set_intersection(availableTargets.begin(), availableTargets.end(), + allowedTargets.begin(), allowedTargets.end(), + std::back_inserter(targetList)); + } + else + { + // simply take available targets as target list + targetList = std::move(availableTargets); // availableTargets won't be used anymore + } + + + if(targetList.size() > maxNumTargets) + { + maxNumTargets = targetList.size(); + retVal = targetList.front(); + } + } + + this->localTargetMapper.unmapTarget(retVal); + + return retVal; +} + +uint16_t MirrorBuddyGroupCreator::generateID(const UInt16List* usedMirrorBuddyGroups) +{ + const std::set used(usedMirrorBuddyGroups->begin(), usedMirrorBuddyGroups->end()); + + struct ops + { + static bool hasGap(uint16_t a, uint16_t b) + { + return a + 1 < b; + } + }; + + if (used.empty()) + return 1; + + if (*used.rbegin() < 0xFFFF) + return *used.rbegin() + 1; + + if (*used.begin() > 1) + return 1; + + const auto gap = std::adjacent_find(used.begin(), used.end(), ops::hasGap); + if (gap != used.end()) + return *gap + 1; + + return 0; +} + +uint16_t MirrorBuddyGroupCreator::generateUniqueID(const TargetMapper* targetMapper, + const UInt16List* usedMirrorBuddyGroups) const +{ + uint16_t newGroupID = 1; + + // search if the selected ID is in use + bool idFound = false; + UInt16ListConstIter groupIter = usedMirrorBuddyGroups->begin(); + for( ; groupIter != usedMirrorBuddyGroups->end(); groupIter++) + { + if(*groupIter == newGroupID) + { + idFound = true; + break; + } + } + + if (idFound || (targetMapper && targetMapper->getNodeID(newGroupID) ) ) + { + /*selected ID is in use already, so walk from there to find the next free ID. */ + for ( ;; ) + { + newGroupID++; + + // check range... (avoid reserved value "0" as newNumID) + if ( unlikely(!newGroupID || (newGroupID > MIRRORBUDDYGROUPMAPPER_MAX_GROUPIDS) ) ) + { + newGroupID = 0; + break; + } + + // search if the selected ID is in use + idFound = false; + groupIter = usedMirrorBuddyGroups->begin(); + for( ; groupIter != usedMirrorBuddyGroups->end(); groupIter++) + { + if(*groupIter == newGroupID) + { + idFound = true; + break; + } + } + + if (!idFound && targetMapper && !targetMapper->getNodeID(newGroupID)) + return newGroupID; // we found an ID that no other node uses + } + } + + return newGroupID; +} + +/** + * Check the size of the targets and prints a warning to the console if the size of all targets is + * not equal. + * + * @param mapper TargetMapper with all storage targets of the system. For checking metadata nodes, + * the mapper just needs to contain the correct node IDs, the target IDs are not + * relevant (since metanodes can't have multiple targets) + * @return false if the targets have a different size or a error occurred, + * true if all targets have the same size + */ +bool MirrorBuddyGroupCreator::checkSizeOfTargets() const +{ + bool retVal = true; + + int64_t spaceValue = 0; + + + // Retrieve the statStoragePath information from all storage servers + for (const auto& node : this->nodes->referenceAllNodes()) + { + if (!retVal) + break; + + UInt16List targets; + this->systemTargetMapper.getTargetsByNode(node->getNumID(), targets); + + // Retrieve the statStoragePath information from the storage server + for(UInt16ListIter targetIter = targets.begin(); targetIter != targets.end(); targetIter++) + { + int64_t freeSpace; + int64_t totalSpace; + int64_t freeInodes; + int64_t totalInodes; + + const FhgfsOpsErr statRes = StorageTargetInfo::statStoragePath(*node, *targetIter, + &freeSpace, &totalSpace, &freeInodes, &totalInodes); + if(statRes != FhgfsOpsErr_SUCCESS) + { + const std::string logMessage("An error occurred when connecting to server: " + + node->getNodeIDWithTypeStr() + " Error: " + + boost::lexical_cast(statRes)); + std::cerr << logMessage << std::endl; + LOG(MIRRORING, ERR, logMessage); + + return false; + } + + if(spaceValue == 0) // check of the first target + spaceValue = totalSpace; + else + if(spaceValue != totalSpace) + { + retVal = false; + break; + } + } + } + + // report a warning, because the size of all targets is not equal + if(!retVal) + { + const std::string logMessage("Target size differs. " + "Automatic mode does not take this into account. " + "It is recommended that all storage targets have the same size when using automatic mode." + ); + std::cout << "[WARNING] " << logMessage << std::endl; + LOG(MIRRORING, WARNING, logMessage); + } + + return retVal; +} + +/** + * Checks which of the both targets is the best choice for the primary target. The storage server + * with the lower primary target count is the best choice. + * + * @param primaryUsed A map with NodeNumIDs to counter of primary targets + * @param inOutPrimaryID In/Out value for the primary Target/NodeNumID + * @param inOutSecondaryID In/Out value for the secondary Target/NodeNumID + */ +void MirrorBuddyGroupCreator::selectPrimaryTarget(PrimaryTargetCounterMap* primaryUsed, + uint16_t* inOutPrimaryID, uint16_t* inOutSecondaryID) const +{ + const NumNodeID primaryServerNumID = this->systemTargetMapper.getNodeID(*inOutPrimaryID); + const NumNodeID secondaryServerNumID = this->systemTargetMapper.getNodeID(*inOutSecondaryID); + + if((*primaryUsed)[primaryServerNumID] > (*primaryUsed)[secondaryServerNumID]) + { + const uint16_t tmpID = *inOutPrimaryID; + *inOutPrimaryID = *inOutSecondaryID; + *inOutSecondaryID = tmpID; + + (*primaryUsed)[secondaryServerNumID]++; + } + else + (*primaryUsed)[primaryServerNumID]++; +} + +/** + * Generates MirrorBuddyGroups with the given targets, but do not create the MirrorBuddyGroups on + * the management daemon + * + * @param outBuddyGroupIDs A list with all new selected MirrorBuddyGroupIDs + * @param outBuddyGroups A list with all new selected MirrorBuddyGroups + * @param usedMirrorBuddyGroupIDs A list with used MirrorBuddyGroupIDs + * @return FhgfsOpsErr_SUCCESS when the selected MirrorBuddyGroups does not hurt any constraint, + * FhgfsOpsErr_INVAL at least one constraint is hurt, on error FhgfsOpsErr_... + */ +FhgfsOpsErr MirrorBuddyGroupCreator::generateMirrorBuddyGroups(UInt16List* outBuddyGroupIDs, + MirrorBuddyGroupList* outBuddyGroups, const UInt16List* usedMirrorBuddyGroupIDs) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + size_t numTargets = this->localTargetMapper.getSize(); + if(numTargets == 0) + { // check if unused targets exists + const std::string logMessage( + "No targets without a mirror group found. Mirror groups have not been changed."); + std::cout << logMessage << std::endl; + LOG(MIRRORING, ERR, logMessage); + + return FhgfsOpsErr_INTERNAL; + } + + if(numTargets % 2 != 0) + { // check for even number of targets + const std::string logMessage( + "Odd number of targets detected. One target left without a mirror group."); + std::cout << "[WARNING] " << logMessage << std::endl; + LOG(MIRRORING, WARNING, logMessage); + + retVal = FhgfsOpsErr_INVAL; + } + + // check if all targets have the same size + if(!checkSizeOfTargets() ) + retVal = FhgfsOpsErr_INVAL; + + // create a black list of MirrorBuddyGroupIDs, used MirrorBuddyGroupIDs + new MirrorBuddyGroupIDs + UInt16List blackListGroupIDs; + blackListGroupIDs.insert(blackListGroupIDs.begin(), usedMirrorBuddyGroupIDs->begin(), + usedMirrorBuddyGroupIDs->end() ); + + PrimaryTargetCounterMap primaryTargetCounter; + + while(numTargets > 1) + { // requires at least two targets for a BuddyMirrorGroup + uint16_t mirrorBuddyGroupID = 0; + uint16_t primaryTarget = findNextTarget(NumNodeID(0) ); + + StoragePoolId storagePoolId; + if (nodeType == NODETYPE_Storage) + { + // get the storage pool of the primary target, because secondary target must be in the + // same pool + const StoragePoolPtr storagePool = storagePoolStore->getPool(primaryTarget); + + if (!storagePool) + { + // should actually never happen + std::cerr << "Could not add buddy group: Requested primary target is not a member of a " + "storage pool." << std::endl; + return FhgfsOpsErr_INTERNAL; + } + + storagePoolId = storagePool->getId(); + } + else + { + storagePoolId = StoragePoolStore::INVALID_POOL_ID; + } + + // get the node of the primary target, so we can try to find a different node for the + // secondary target + const NumNodeID ignoreID = this->systemTargetMapper.getNodeID(primaryTarget); + uint16_t secondaryTarget = findNextTarget(ignoreID, storagePoolId); + + if(secondaryTarget) + { // second target on a different server found ==> good case + selectPrimaryTarget(&primaryTargetCounter, &primaryTarget, &secondaryTarget); + } + else + { // all targets are located on the same server ==> bad case + secondaryTarget = findNextTarget(NumNodeID(0), storagePoolId); + if(!secondaryTarget) + { + const std::string logMessage("No second target for mirror group found."); + std::cout << logMessage << std::endl; + LOG(MIRRORING, ERR, logMessage); + + return FhgfsOpsErr_INTERNAL; + } + const std::string logMessage("Created new buddy group using targets " + + StringTk::uintToStr(primaryTarget) + " and " + StringTk::uintToStr(secondaryTarget) + + ", but both targets are located on the same server."); + std::cout << "[WARNING] " << logMessage << std::endl; + LOG(MIRRORING, WARNING, logMessage); + + retVal = FhgfsOpsErr_INVAL; + } + + // if we have selected the owner of the root inode as secondary, just switch primary and + // secondary to fix that. + if (nodeType == NODETYPE_Meta && secondaryTarget == metaRoot->getID().val()) + std::swap(primaryTarget, secondaryTarget); + + if (this->cfgUnique) + mirrorBuddyGroupID = generateUniqueID(&systemTargetMapper, &blackListGroupIDs); + else + mirrorBuddyGroupID = generateID(&blackListGroupIDs); + + outBuddyGroupIDs->push_back(mirrorBuddyGroupID); + outBuddyGroups->push_back(MirrorBuddyGroup(primaryTarget, secondaryTarget) ); + blackListGroupIDs.push_back(mirrorBuddyGroupID); + + numTargets = this->localTargetMapper.getSize(); + } + + if(numTargets == 1) + { // odd number of targets + const uint16_t lastTarget = findNextTarget(NumNodeID(0) ); + const std::string logMessage("Storage target with targetNumID " + + StringTk::uintToStr(lastTarget) + " unused, because no second target available."); + std::cout << "[WARNING] " << logMessage << std::endl; + LOG(MIRRORING, WARNING, logMessage); + + retVal = FhgfsOpsErr_INVAL; + } + + return retVal; +} diff --git a/common/source/common/nodes/MirrorBuddyGroupCreator.h b/common/source/common/nodes/MirrorBuddyGroupCreator.h new file mode 100644 index 0000000..bb4661a --- /dev/null +++ b/common/source/common/nodes/MirrorBuddyGroupCreator.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include +#include +#include +#include + + + +// key: ServerNumID, value: counter of primary targets +typedef std::map PrimaryTargetCounterMap; +typedef PrimaryTargetCounterMap::iterator PrimaryTargetCounterMapIter; +typedef PrimaryTargetCounterMap::const_iterator PrimaryTargetCounterMapCIter; +typedef PrimaryTargetCounterMap::value_type PrimaryTargetCounterMapVal; + + +class MirrorBuddyGroupCreator +{ + public: + MirrorBuddyGroupCreator(NodeStore* mgmtNodes, NodeStore* metaNodes, const RootInfo* metaRoot, + StoragePoolStore* storagePoolStore) + : cfgForce(false), + cfgDryrun(false), + cfgUnique(false), + nodeType(NODETYPE_Invalid), + mgmtNodes(mgmtNodes), + metaNodes(metaNodes), + metaRoot(metaRoot), + storagePoolStore(storagePoolStore) + {} + + FhgfsOpsErr addGroup(uint16_t primaryTargetID, uint16_t secondaryTargetID, + uint16_t forcedGroupID); + + static uint16_t generateID(const UInt16List* usedMirrorBuddyGroups); + + protected: + bool cfgForce; // ignore warnings/errors during automatic mode + bool cfgDryrun; // only print the selected configuration + bool cfgUnique; // use unique MirrorBuddyGroupIDs. The ID is not used as storage targetNumID + + NodeType nodeType; // the node type (meta / storage) + const NodeStore* mgmtNodes; // NodeStore holding the management node + const NodeStore* metaNodes; + const RootInfo* metaRoot; + const StoragePoolStore* storagePoolStore; + + // Note: Derived class is responsible for filling the nodeStore and the targetMappers. + NodeStore* nodes; // NodeStore holding the storage / meta nodes depending on nodeType + TargetMapper systemTargetMapper; // TargetMapper with all storage targets of the system, in + // case nodeType==NODETYPE_Meta, only contains nodeIDs. + TargetMapper localTargetMapper; // Contains list of targets and the nodes they belong to in + // case nodeType == NODETYPE_Storage. + // For nodeType == NODETYPE_Meta, the map is nodeID->nodeID. + + FhgfsOpsErr addGroupComm(uint16_t primaryID, uint16_t secondaryID, + uint16_t forcedGroupID, uint16_t& outNewGroupID) const; + + bool removeTargetsFromExistingMirrorBuddyGroups(const UInt16List* oldPrimaryIDs, + const UInt16List* oldSecondaryIDs); + uint16_t findNextTarget(NumNodeID nodeNumIdToIgnore, + StoragePoolId storagePoolId = StoragePoolStore::INVALID_POOL_ID); + bool checkSizeOfTargets() const; + FhgfsOpsErr generateMirrorBuddyGroups(UInt16List* outBuddyGroupIDs, + MirrorBuddyGroupList* outBuddyGroups, const UInt16List* usedMirrorBuddyGroupIDs); + void selectPrimaryTarget(PrimaryTargetCounterMap* primaryUsed, + uint16_t* inOutPrimaryID, uint16_t* inOutSecondaryID) const; + bool createMirrorBuddyGroups(FhgfsOpsErr retValGeneration, const UInt16List* buddyGroupIDs, + const MirrorBuddyGroupList* buddyGroups); + + uint16_t generateUniqueID(const TargetMapper* targetMapper, + const UInt16List* usedMirrorBuddyGroups) const; +}; + diff --git a/common/source/common/nodes/MirrorBuddyGroupMapper.cpp b/common/source/common/nodes/MirrorBuddyGroupMapper.cpp new file mode 100644 index 0000000..9826a7a --- /dev/null +++ b/common/source/common/nodes/MirrorBuddyGroupMapper.cpp @@ -0,0 +1,478 @@ +#include +#include +#include +#include + +#include "MirrorBuddyGroupMapper.h" + +MirrorBuddyGroupMapper::MirrorBuddyGroupMapper(TargetMapper* targetMapper): + targetMapper(targetMapper), metaCapacityPools(NULL), storagePools(NULL), usableNodes(NULL), + localGroupID(0) { } + +MirrorBuddyGroupMapper::MirrorBuddyGroupMapper(): + targetMapper(NULL), metaCapacityPools(NULL), storagePools(NULL), usableNodes(NULL), + localGroupID(0) { } + +/** + * Map two target IDs to a buddyGroupID. + * + * Note: re-maps buddyGroupID if it was mapped before. + * + * @param buddyGroupID my be 0, which creates a new ID + * @param primaryTarget + * @param secondaryTarget + * @param localNodeID nodeNumID of our local node + * @param allowUpdate if buddyGroupID is given and group exists - is updating allowed? + * @param outNewBuddyGroup if new group shall be added its ID will be given here, may be NULL + * + * @return FhgfsOpsErr indicating the result + * - FhgfsOpsErr_SUCCESS successful operation + * - FhgfsOpsErr_INUSE one of the targets is already in use + * - FhgfsOpsErr_UNKNOWNTARGET one of the targets does not exist + * - FhgfsOpsErr_EXISTS the buddy group already exists and allowUpdate is false + */ +FhgfsOpsErr MirrorBuddyGroupMapper::mapMirrorBuddyGroup(uint16_t buddyGroupID, + uint16_t primaryTargetID, uint16_t secondaryTargetID, NumNodeID localNodeID, + bool allowUpdate = true, uint16_t* outNewBuddyGroup = NULL) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + // sanity checks + if ( (buddyGroupID != 0) && (!allowUpdate) + && (this->mirrorBuddyGroups.find(buddyGroupID) != this->mirrorBuddyGroups.end()) ) + { + return FhgfsOpsErr_EXISTS; + } + + // dont allow the same ID for primary and secondary + if (primaryTargetID == secondaryTargetID) + { + return FhgfsOpsErr_INVAL; + } + + if (targetMapper) + { + if (! this->targetMapper->targetExists(primaryTargetID) ) + return FhgfsOpsErr_UNKNOWNTARGET; + + if (! this->targetMapper->targetExists(secondaryTargetID) ) + return FhgfsOpsErr_UNKNOWNTARGET; + } + else if (usableNodes) + { + // for the metadata buddy group mapper we have no targetMapper to check whether node exists + // or not, so we use the nodeStore that was attached from the App + + const NodeHandle primaryNode = usableNodes->referenceNode(NumNodeID(primaryTargetID)); + if (!primaryNode) + return FhgfsOpsErr_UNKNOWNNODE; + + const NodeHandle secondaryNode = usableNodes->referenceNode(NumNodeID(secondaryTargetID)); + if (!secondaryNode) + return FhgfsOpsErr_UNKNOWNNODE; + } + + const uint16_t primaryInGroup = getBuddyGroupID(primaryTargetID); + if ( (primaryInGroup > 0) && (primaryInGroup != buddyGroupID)) + return FhgfsOpsErr_INUSE; + + const uint16_t secondaryInGroup = getBuddyGroupID(secondaryTargetID); + if ( (secondaryInGroup > 0) && (secondaryInGroup != buddyGroupID)) + return FhgfsOpsErr_INUSE; + + // add/update group + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + + // if buddyGroupID is 0, create a new groupID + if (buddyGroupID == 0) + buddyGroupID = generateID(); + + if (outNewBuddyGroup) + *outNewBuddyGroup = buddyGroupID; + + const MirrorBuddyGroup mbg(primaryTargetID, secondaryTargetID); + + mirrorBuddyGroups[buddyGroupID] = mbg; + + if (metaCapacityPools) + metaCapacityPools->addIfNotExists(buddyGroupID, CapacityPool_LOW); + + if (storagePools) + { + // automatically add the buddy group to the pool of the primary target + const StoragePoolPtr storagePool = storagePools->getPool(primaryTargetID); + if (storagePool) + { + const StoragePoolId storagePoolId = storagePool->getId(); + storagePools->addBuddyGroup(buddyGroupID, storagePoolId); + } + else + { + LOG(MIRRORING, ERR, "Primary target of buddy group is not in a storage pool", + buddyGroupID, primaryTargetID); + } + } + + // set localGroupID, if neccessary + if (localNodeID == NumNodeID(primaryTargetID) || localNodeID == NumNodeID(secondaryTargetID) ) + localGroupID = buddyGroupID; + + return retVal; +} + +/** + * @return true if the buddyGroupID was unmapped + */ +bool MirrorBuddyGroupMapper::unmapMirrorBuddyGroup(uint16_t buddyGroupID, NumNodeID localNodeID) +{ + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + + if ( localGroupID != 0 ) + if ( (localNodeID.val() == mirrorBuddyGroups[buddyGroupID].firstTargetID) + || (localNodeID.val() == mirrorBuddyGroups[buddyGroupID].secondTargetID) ) + localGroupID = 0; + + const size_t numErased = mirrorBuddyGroups.erase(buddyGroupID); + + if(numErased) + { + if (metaCapacityPools) + metaCapacityPools->remove(buddyGroupID); + + if (storagePools) + storagePools->removeBuddyGroup(buddyGroupID); + } + + return (numErased != 0); +} + +/** + * Applies the mapping from two separate lists (keys and values). + * + * NOTE: no sanity checks in here + */ +void MirrorBuddyGroupMapper::syncGroupsFromLists(UInt16List& buddyGroupIDs, + MirrorBuddyGroupList& buddyGroups, NumNodeID localNodeID) +{ + // note: we pre-create a new map and then swap elements to avoid long write-locking + MirrorBuddyGroupMap newGroups; + UInt16ListConstIter keysIter = buddyGroupIDs.begin(); + MirrorBuddyGroupListCIter valuesIter = buddyGroups.begin(); + uint16_t localGroupID = 0; + + for( ; keysIter != buddyGroupIDs.end(); keysIter++, valuesIter++) + { + newGroups[*keysIter] = *valuesIter; + + if ( (localNodeID.val() == valuesIter->firstTargetID) + || (localNodeID.val() == valuesIter->secondTargetID) ) + localGroupID = *keysIter; + } + + RWLockGuard safeWriteLock(rwlock, SafeRWLock_WRITE); + + mirrorBuddyGroups.swap(newGroups); + + if (localGroupID != 0) + setLocalGroupIDUnlocked(localGroupID); +} + +/** + * Applies the mapping from three separate lists (convenience wrapper around + * syncGroupsFromLists(key-list,value-list)). + * + * NOTE: no sanity checks in here + * + */ +void MirrorBuddyGroupMapper::syncGroupsFromLists(UInt16List& buddyGroupIDs, + UInt16List& primaryTargets, UInt16List& secondaryTargets, NumNodeID localNodeID) +{ + MirrorBuddyGroupList mbgList; + + UInt16ListConstIter primaryIter = primaryTargets.begin(); + UInt16ListConstIter secondaryIter = secondaryTargets.begin(); + + for( ; primaryIter != primaryTargets.end(); primaryIter++, secondaryIter++) + { + const MirrorBuddyGroup mbg(*primaryIter, *secondaryIter); + mbgList.push_back(mbg); + } + + syncGroupsFromLists(buddyGroupIDs, mbgList, localNodeID); +} + +/** + * Returns the mapping in two separate lists (keys and values). + */ +void MirrorBuddyGroupMapper::getMappingAsLists(UInt16List& outBuddyGroupIDs, + MirrorBuddyGroupList& outBuddyGroups) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + getMappingAsListsUnlocked(outBuddyGroupIDs, outBuddyGroups); +} + +/** + * Returns the mapping in two separate lists (keys and values). + * NOTE: unlocked, so not thread-safe without a lock in the calling function + */ +void MirrorBuddyGroupMapper::getMappingAsListsUnlocked(UInt16List& outBuddyGroupIDs, + MirrorBuddyGroupList& outBuddyGroups) const +{ + for ( MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + iter++ ) + { + outBuddyGroupIDs.push_back(iter->first); + outBuddyGroups.push_back(iter->second); + } +} + +/** + * Returns the mapping in three separate lists (buddyGroupID, primaryTargetID, secondaryTargetID). + */ +void MirrorBuddyGroupMapper::getMappingAsLists(UInt16List& outBuddyGroupIDs, + UInt16List& outPrimaryTargets, UInt16List& outSecondaryTargets) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + getMappingAsListsUnlocked(outBuddyGroupIDs, outPrimaryTargets, outSecondaryTargets); +} + +/** + * Returns the mapping in three separate lists (buddyGroupID, primaryTargetID, secondaryTargetID). + * + * Note: Caller must hold readlock. + */ +void MirrorBuddyGroupMapper::getMappingAsListsUnlocked(UInt16List& outBuddyGroupIDs, + UInt16List& outPrimaryTargets, UInt16List& outSecondaryTargets) const +{ + for ( MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + iter++ ) + { + MirrorBuddyGroup mbg = iter->second; + outBuddyGroupIDs.push_back(iter->first); + outPrimaryTargets.push_back(mbg.firstTargetID); + outSecondaryTargets.push_back(mbg.secondTargetID); + } +} + +/* + * generate a free buddy group ID. + * + * NOTE: unlocked, so not thread-safe without a lock in the calling function + */ +uint16_t MirrorBuddyGroupMapper::generateID() const +{ + typedef MirrorBuddyGroupMap::value_type Group; + const auto hasGap = [] (const Group& a, const Group& b) { + return a.first + 1 < b.first; + }; + + if (mirrorBuddyGroups.empty()) + return 1; + + if (mirrorBuddyGroups.rbegin()->first + < std::numeric_limits::max()) + return mirrorBuddyGroups.rbegin()->first + 1; + + if (mirrorBuddyGroups.begin()->first > 1) + return 1; + + const auto gap = std::adjacent_find( mirrorBuddyGroups.begin(), mirrorBuddyGroups.end(), hasGap); + if (gap != mirrorBuddyGroups.end()) + return gap->first + 1; + + return 0; +} + +/* + * @param targetID the targetID to look for + * + * @return the ID of the buddy group this target is mapped to, 0 if unmapped + */ +uint16_t MirrorBuddyGroupMapper::getBuddyGroupID(uint16_t targetID) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + for ( MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + iter++ ) + { + const MirrorBuddyGroup mbg = iter->second; + if ( (mbg.firstTargetID == targetID) || (mbg.secondTargetID == targetID) ) + { + return iter->first; + } + } + + // not found + return 0; +} + +/* + * @param targetID the targetID to look for + * @param outTargetIsPrimary true if the given targetID is the primary target, false otherwise + * + * @return the ID of the buddy group this target is mapped to, 0 if unmapped + */ +uint16_t MirrorBuddyGroupMapper::getBuddyGroupID(uint16_t targetID, bool* outTargetIsPrimary) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + return getBuddyGroupIDUnlocked(targetID, outTargetIsPrimary); +} + +/** + * Unlocked version of getBuddyGroupID(uint16_t, bool*). Caller must hold read lock. + */ +uint16_t MirrorBuddyGroupMapper::getBuddyGroupIDUnlocked(uint16_t targetID, + bool* outTargetIsPrimary) const +{ + for ( MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + iter++ ) + { + const MirrorBuddyGroup mbg = iter->second; + if (mbg.firstTargetID == targetID ) + { + *outTargetIsPrimary = true; + return iter->first; + } + else if (mbg.secondTargetID == targetID) + { + *outTargetIsPrimary = false; + return iter->first; + } + } + + // not found + *outTargetIsPrimary = false; + return 0; +} + +/* + * @param targetID the targetID to get the buddy for + * @param outTargetIsPrimary true if the given target is a primary target (may be NULL if not + * interested in) + * + * @return the other target in this buddy group, 0 if unmapped + */ +uint16_t MirrorBuddyGroupMapper::getBuddyTargetID(uint16_t targetID, + bool* outTargetIsPrimary) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + for ( MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + iter++ ) + { + const MirrorBuddyGroup mbg = iter->second; + if (mbg.firstTargetID == targetID) + { + if (outTargetIsPrimary) + *outTargetIsPrimary = true; + + return mbg.secondTargetID; + } + else if (mbg.secondTargetID == targetID) + { + if (outTargetIsPrimary) + *outTargetIsPrimary = false; + + return mbg.firstTargetID; + } + } + + // end of loop reached without finding anything + if (outTargetIsPrimary) + *outTargetIsPrimary = false; + + return 0; +} + +/* + * @param targetID the target ID to get the property for. + * + * @return MirrorBuddyState value indicating whether the target is primary, secondary or unmapped. + */ +MirrorBuddyState MirrorBuddyGroupMapper::getBuddyState(uint16_t targetID) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + for(MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.begin(); iter != mirrorBuddyGroups.end(); + ++iter) + { + const MirrorBuddyGroup& mbg = iter->second; + if (mbg.firstTargetID == targetID) + { + return BuddyState_PRIMARY; + } + else if (mbg.secondTargetID == targetID) + { + return BuddyState_SECONDARY; + } + } + + // end of loop reached without finding anything + return BuddyState_UNMAPPED; +} + +/** + * Find out whether a target is primary or secondary of a given buddy group. Since only one buddy + * group is checked, this is faster than getBuddyState(uint16_t targetID). + * + * @param targetID the target ID to get the property for. + * @param buddyGroupID the ID of the buddy group to which the target belongs. + * @return MirrorBuddyState value indicating whether the target is primary or secondary; + * BuddyState_UNMAPPED if target does not belong to this group or group does not exist. + */ +MirrorBuddyState MirrorBuddyGroupMapper::getBuddyState(uint16_t targetID, + uint16_t buddyGroupID) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + MirrorBuddyGroupMapCIter mbgIter = mirrorBuddyGroups.find(buddyGroupID); + + if (mbgIter != mirrorBuddyGroups.end() ) + { + const MirrorBuddyGroup& mbg = mbgIter->second; + + if (targetID == mbg.firstTargetID) + return BuddyState_PRIMARY; + else if (targetID == mbg.secondTargetID) + return BuddyState_SECONDARY; + } + + // not found + return BuddyState_UNMAPPED; +} + +/** + * buddy groups will automatically be added/removed from attached capacity pools when they are + * added/removed from this store. + */ +void MirrorBuddyGroupMapper::attachMetaCapacityPools(NodeCapacityPools* capacityPools) +{ + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + + this->metaCapacityPools = capacityPools; +} + +/** + * node store is only used for metadata nodes to check for their existence when adding them to a + * BMG + */ +void MirrorBuddyGroupMapper::attachNodeStore(NodeStore* usableNodes) +{ + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + + this->usableNodes = usableNodes; +} + +/** +* Buddy groups will automatically be added to the default storage pool when they are added to this +* store. +*/ +void MirrorBuddyGroupMapper::attachStoragePoolStore(StoragePoolStore* storagePools) +{ + RWLockGuard const _(rwlock, SafeRWLock_WRITE); + + this->storagePools = storagePools; +} diff --git a/common/source/common/nodes/MirrorBuddyGroupMapper.h b/common/source/common/nodes/MirrorBuddyGroupMapper.h new file mode 100644 index 0000000..3ee2164 --- /dev/null +++ b/common/source/common/nodes/MirrorBuddyGroupMapper.h @@ -0,0 +1,196 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include + + +#define MIRRORBUDDYGROUPMAPPER_MAX_GROUPIDS (USHRT_MAX-1) /* -1 for reserved value "0" */ + + +enum MirrorBuddyState +{ + BuddyState_UNMAPPED, + BuddyState_PRIMARY, + BuddyState_SECONDARY, +}; + + +class MirrorBuddyGroupMapper +{ + friend class TargetStateStore; // for atomic update of state change plus mirror group switch + friend class MgmtdTargetStateStore; // for atomic update of state change plus mirror group switch + + public: + MirrorBuddyGroupMapper(TargetMapper* targetMapper); + MirrorBuddyGroupMapper(); + + FhgfsOpsErr mapMirrorBuddyGroup(uint16_t buddyGroupID, uint16_t primaryTargetID, + uint16_t secondaryTargetID, NumNodeID localNodeID, bool allowUpdate, + uint16_t* outNewBuddyGroup); + bool unmapMirrorBuddyGroup(uint16_t buddyGroupID, NumNodeID localNodeID); + + void syncGroupsFromLists(UInt16List& buddyGroupIDs, MirrorBuddyGroupList& buddyGroups, + NumNodeID localNodeID); + void syncGroupsFromLists(UInt16List& buddyGroupIDs, UInt16List& primaryTargets, + UInt16List& secondaryTargets, NumNodeID localNodeID); + + void getMappingAsLists(UInt16List& outBuddyGroupIDs, + MirrorBuddyGroupList& outBuddyGroups) const; + void getMappingAsLists(UInt16List& outBuddyGroupIDs, UInt16List& outPrimaryTargets, + UInt16List& outSecondaryTargets) const; + + uint16_t getBuddyGroupID(uint16_t targetID) const; + uint16_t getBuddyGroupID(uint16_t targetID, bool* outTargetIsPrimary) const; + uint16_t getBuddyTargetID(uint16_t targetID, bool* outTargetIsPrimary = NULL) const; + + MirrorBuddyState getBuddyState(uint16_t targetID) const; + MirrorBuddyState getBuddyState(uint16_t targetID, uint16_t buddyGroupID) const; + + void attachMetaCapacityPools(NodeCapacityPools* capacityPools); + + void attachStoragePoolStore(StoragePoolStore* storagePools); + void attachNodeStore(NodeStore* usableNodes); + + protected: + TargetMapper* targetMapper; + NodeCapacityPools* metaCapacityPools; + + StoragePoolStore* storagePools; + // this node store is only used for metadata nodes to check for their existence when adding + // them to a BMG + NodeStore* usableNodes; + + mutable RWLock rwlock; + MirrorBuddyGroupMap mirrorBuddyGroups; + + uint16_t localGroupID; // the groupID the local node belongs to; 0 if not part of a group + + uint16_t generateID() const; + + uint16_t getBuddyGroupIDUnlocked(uint16_t targetID, bool* outTargetIsPrimary) const; + + void getMappingAsListsUnlocked(UInt16List& outBuddyGroupIDs, + MirrorBuddyGroupList& outBuddyGroups) const; + void getMappingAsListsUnlocked(UInt16List& outBuddyGroupIDs, UInt16List& outPrimaryTargets, + UInt16List& outSecondaryTargets) const; + + public: + // getters & setters + + /** + * @return a group with targets [0,0] if ID not found + */ + MirrorBuddyGroup getMirrorBuddyGroup(uint16_t mirrorBuddyGroupID) const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); // L O C K + + return getMirrorBuddyGroupUnlocked(mirrorBuddyGroupID); + } + + /** + * @return 0 if group ID not found + */ + uint16_t getPrimaryTargetID(uint16_t mirrorBuddyGroupID) const + { + return getMirrorBuddyGroup(mirrorBuddyGroupID).firstTargetID; + } + + /** + * @return 0 if group ID not found + */ + uint16_t getSecondaryTargetID(uint16_t mirrorBuddyGroupID) const + { + return getMirrorBuddyGroup(mirrorBuddyGroupID).secondTargetID; + } + + size_t getSize() const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + return mirrorBuddyGroups.size(); + } + + void getMirrorBuddyGroups(MirrorBuddyGroupMap& outMirrorBuddyGroups) const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + outMirrorBuddyGroups = mirrorBuddyGroups; + } + + uint16_t getLocalGroupID() const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + return getLocalGroupIDUnlocked(); + } + + MirrorBuddyGroup getLocalBuddyGroup() const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + + const uint16_t localGroupID = getLocalGroupIDUnlocked(); + return getMirrorBuddyGroupUnlocked(localGroupID); + } + + MirrorBuddyGroupMap getMapping() const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + return mirrorBuddyGroups; + } + + private: + + /** + * Unlocked version of getSecondaryTargetID. Caller must hold read lock. + */ + uint16_t getSecondaryTargetIDUnlocked(uint16_t mirrorBuddyGroupID) const + { + MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.find(mirrorBuddyGroupID); + if (likely(iter != mirrorBuddyGroups.end() ) ) + return iter->second.secondTargetID; + else + return 0; + } + + /** + * Unlocked version of getPrimaryTargetID. Caller must hold read lock. + */ + uint16_t getPrimaryTargetIDUnlocked(uint16_t mirrorBuddyGroupID) const + { + MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.find(mirrorBuddyGroupID); + if (likely(iter != mirrorBuddyGroups.end() ) ) + return iter->second.firstTargetID; + else + return 0; + } + + uint16_t getLocalGroupIDUnlocked() const + { + return localGroupID; + } + + void setLocalGroupIDUnlocked(uint16_t localGroupID) + { + this->localGroupID = localGroupID; + } + + /** + * Caller must hold read lock. + */ + MirrorBuddyGroup getMirrorBuddyGroupUnlocked(uint16_t mirrorBuddyGroupID) const + { + MirrorBuddyGroupMapCIter iter = mirrorBuddyGroups.find(mirrorBuddyGroupID); + if (likely(iter != mirrorBuddyGroups.end() ) ) + return iter->second; + else + return MirrorBuddyGroup(0, 0); + } +}; + diff --git a/common/source/common/nodes/Node.cpp b/common/source/common/nodes/Node.cpp new file mode 100644 index 0000000..64ad471 --- /dev/null +++ b/common/source/common/nodes/Node.cpp @@ -0,0 +1,148 @@ +#include +#include "Node.h" + +#include + +#include + +/** + * @param nodeNumID value 0 if not yet assigned + * @param portUDP value 0 if undefined + * @param portTCP value 0 if undefined + * @param nicList will be forwarded to the NodeConnPool which creates its own internal copy + */ +Node::Node(NodeType nodeType, std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP, + unsigned short portTCP, const NicAddressList& nicList) : + nodeType(nodeType), alias(nodeID) +{ + this->numID = nodeNumID; + this->portUDP = portUDP; + + this->connPool = new NodeConnPool(*this, portTCP, nicList); +} + +/** + * Constructor for derived classes that provide their own connPool, e.g. LocalNode. + * + * Note: derived classes: do not forget to set the connPool! + * + * @param portUDP value 0 if undefined + */ +Node::Node(NodeType nodeType, std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP): + nodeType(nodeType), alias(std::move(nodeID)) +{ + this->numID = nodeNumID; + this->portUDP = portUDP; + + // derived classes: do not forget to set the connPool! +} + + +Node::~Node() +{ + SAFE_DELETE_NOSET(connPool); +} + + +/** + * Updates the last heartbeat time. + */ +void Node::updateLastHeartbeatT() +{ + const std::lock_guard lock(mutex); + + updateLastHeartbeatTUnlocked(); +} + +/** + * Updates the last heartbeat time. + * + * Note: Does not lock the node mutex, but broadcasts the change condition + * => make sure the mutex is locked when calling this!! + */ +void Node::updateLastHeartbeatTUnlocked() +{ + lastHeartbeatT.setToNow(); +} + +/** + * Gets the last heartbeat time. + */ +Time Node::getLastHeartbeatT() +{ + const std::lock_guard lock(mutex); + + return lastHeartbeatT; +} + +/** + * Gets the last heartbeat time. + * + * Note: Does not lock the node mutex + * => make sure the mutex is locked when calling this! + */ +Time Node::getLastHeartbeatTUnlocked() +{ + Time t(lastHeartbeatT); + return t; +} + +/** + * @param portUDP value 0 if undefined + * @param portTCP value 0 if undefined + * @param nicList will be copied + */ +bool Node::updateInterfaces(unsigned short portUDP, unsigned short portTCP, NicAddressList& nicList) +{ + const std::lock_guard lock(mutex); + + return updateInterfacesUnlocked(portUDP, portTCP, nicList); +} + +/** + * @param portUDP value 0 if undefined + * @param portTCP value 0 if undefined + * @param nicList will be copied + */ +bool Node::updateInterfacesUnlocked(unsigned short portUDP, unsigned short portTCP, + NicAddressList& nicList) +{ + this->portUDP = portUDP ? portUDP : this->portUDP; + + return this->connPool->updateInterfaces(portTCP, nicList); +} + +/** + * Convenience-wrapper for the static version of this method. + */ +std::string Node::getTypedNodeID() const +{ + std::shared_lock lock(aliasMutex); + return getTypedNodeID(alias, numID, nodeType); +} + +std::string Node::getTypedNodeID(std::string nodeID, NumNodeID nodeNumID, NodeType nodeType) +{ + return nodeID + " [ID: " + nodeNumID.str() + "]"; +} + +/** + * Convenience-wrapper for the static version of this method. + */ +std::string Node::getNodeIDWithTypeStr() const +{ + std::shared_lock lock(aliasMutex); + return getNodeIDWithTypeStr(alias, numID, nodeType); +} + +/** + * Returns the node type dependent ID (numeric ID for servers and string ID for clients) and the + * node type in a human-readable string. + * + * This is intended as a convenient way to get a string with node ID and type for log messages. + */ +std::string Node::getNodeIDWithTypeStr(std::string nodeID, NumNodeID nodeNumID, NodeType nodeType) +{ + return str(boost::format("%1% %2%") % nodeType + % getTypedNodeID(std::move(nodeID), nodeNumID, nodeType)); +} diff --git a/common/source/common/nodes/Node.h b/common/source/common/nodes/Node.h new file mode 100644 index 0000000..76c1c6f --- /dev/null +++ b/common/source/common/nodes/Node.h @@ -0,0 +1,273 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "NodeConnPool.h" +#include "NodeType.h" +#include "common/net/sock/NetworkInterfaceCard.h" + +#include +#include + +// forward declaration +class Node; + +typedef std::shared_ptr NodeHandle; + +typedef std::list NodeList; +typedef NodeList::iterator NodeListIter; + + +/** + * This class represents a metadata, storage, client, etc node (aka service). It contains things + * like ID and feature flags of a node and also provides the connection pool to communicate with + * that node. + */ +class Node +{ + public: + Node(NodeType nodeType, std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP, + unsigned short portTCP, const NicAddressList& nicList); + virtual ~Node(); + + Node(const Node&) = delete; + Node& operator=(const Node&) = delete; + + // disabled only because it is not needed at the moment. + Node(Node&&) = delete; + Node& operator=(Node&&) = delete; + + void updateLastHeartbeatT(); + Time getLastHeartbeatT(); + bool updateInterfaces(unsigned short portUDP, unsigned short portTCP, + NicAddressList& nicList); + + std::string getTypedNodeID() const; + std::string getNodeIDWithTypeStr() const; + + // static + static std::string getTypedNodeID(std::string nodeID, NumNodeID nodeNumID, NodeType nodeType); + static std::string getNodeIDWithTypeStr(std::string nodeID, NumNodeID nodeNumID, + NodeType nodeType); + + + protected: + Mutex mutex; + + NodeType nodeType; + + Node(NodeType nodeType, std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP); + + void updateLastHeartbeatTUnlocked(); + Time getLastHeartbeatTUnlocked(); + bool updateInterfacesUnlocked(unsigned short portUDP, unsigned short portTCP, + NicAddressList& nicList); + + // getters & setters + void setConnPool(NodeConnPool* connPool) + { + this->connPool = connPool; + } + + + private: + std::string alias; // alias (formerly known as the string ID until b8.0), set by mgmtd + NumNodeID numID; // numeric ID, assigned by mgmtd server store + + NodeConnPool* connPool; + unsigned short portUDP; + + Time lastHeartbeatT; // last heartbeat receive time + mutable std::shared_mutex aliasMutex; + + + public: + // getters & setters + + std::string getAlias() const + { + std::shared_lock lock(aliasMutex); + return alias; + } + + void setAlias(std::string alias) { + std::unique_lock lock(aliasMutex); + this->alias = alias; + } + + NumNodeID getNumID() const + { + return numID; + } + + void setNumID(NumNodeID numID) + { + this->numID = numID; + } + + NicAddressList getNicList() + { + return connPool->getNicList(); + } + + NodeConnPool* getConnPool() const + { + return connPool; + } + + unsigned short getPortUDP() + { + const std::lock_guard lock(mutex); + + return this->portUDP; + } + + virtual unsigned short getPortTCP() + { + return this->connPool->getStreamPort(); + } + + NodeType getNodeType() const + { + return nodeType; + } + + + struct VectorDes + { + bool v6; + std::vector& nodes; + + Deserializer& runV6(Deserializer& des) const + { + uint32_t elemCount; + uint32_t padding; + + des + % elemCount + % padding; // PADDING + + if(!des.good()) + return des; + + nodes.clear(); + + while (nodes.size() != elemCount) + { + // PADDING + PadFieldTo pad(des, 8); + + NicAddressList nicList; + NicAddressList* nicListDummyPtr; + char nodeType = 0; + unsigned fhgfsVersion = 0; + BitStore nodeFeatureFlags; + uint16_t portTCP = 0; + uint16_t portUDP = 0; + NumNodeID nodeNumID; + std::string nodeID; + + des + % nodeFeatureFlags + % serdes::stringAlign4(nodeID) + % serdesNicAddressList(nicListDummyPtr, nicList) + % fhgfsVersion + % nodeNumID + % portUDP + % portTCP + % nodeType; + + if(unlikely(!des.good())) + break; + + nodes.push_back(std::make_shared(NodeType(nodeType), nodeID, nodeNumID, + portUDP, portTCP, nicList)); + } + + return des; + } + + Deserializer& run(Deserializer& des) const + { + uint32_t elemCount; + + des % elemCount; + + if(!des.good()) + return des; + + nodes.clear(); + + while (nodes.size() != elemCount) + { + NicAddressList nicList; + char nodeType = 0; + uint16_t portTCP = 0; + uint16_t portUDP = 0; + NumNodeID nodeNumID; + std::string nodeID; + + des + % nodeID + % nicList + % nodeNumID + % portUDP + % portTCP + % nodeType; + + if(unlikely(!des.good())) + break; + + nodes.push_back(std::make_shared(NodeType(nodeType), nodeID, nodeNumID, + portUDP, portTCP, nicList)); + } + + return des; + } + + friend Deserializer& operator%(Deserializer& des, const VectorDes& value) + { + if (value.v6) + return value.runV6(des); + else + return value.run(des); + } + }; + + static VectorDes inV6Format(std::vector& nodes) + { + return {true, nodes}; + } +}; + +inline Serializer& operator%(Serializer& ser, const std::vector& nodes) +{ + ser % uint32_t(nodes.size()); + + for (auto it = nodes.begin(), end = nodes.end(); it != end; ++it) + { + Node& node = **it; + + ser + % node.getAlias() + % node.getNicList() + % node.getNumID() + % node.getPortUDP() + % node.getPortTCP() + % char(node.getNodeType() ); + } + + return ser; +} + +inline Deserializer& operator%(Deserializer& des, std::vector& nodes) +{ + Node::VectorDes mod = { false, nodes }; + return des % mod; +} + diff --git a/common/source/common/nodes/NodeCapacityPools.cpp b/common/source/common/nodes/NodeCapacityPools.cpp new file mode 100644 index 0000000..87a9055 --- /dev/null +++ b/common/source/common/nodes/NodeCapacityPools.cpp @@ -0,0 +1,611 @@ +#include + +#include "NodeCapacityPools.h" + +/** + * @param lowSpace only used for getPoolTypeFromFreeSpace() + * @param emergencySpace only used for getPoolTypeFromFreeSpace() + */ +NodeCapacityPools::NodeCapacityPools(bool useDynamicPools, + const DynamicPoolLimits& poolLimitsSpace, const DynamicPoolLimits& poolLimitsInodes) : + poolLimitsSpace(poolLimitsSpace), + poolLimitsInodes(poolLimitsInodes), + pools(CapacityPool_END_DONTUSE) +{ + this->useDynamicPools = useDynamicPools; + + this->lastRoundRobinTarget = 0; +} + +/** + * Note: Unlocked, so caller must hold the write lock when calling this. + * + * @param keepPool targetID won't be removed from this particular pool + */ +void NodeCapacityPools::removeFromOthersUnlocked(uint16_t targetID, + CapacityPoolType keepPool) +{ + /* note: targetID can only exist in one pool, so we can stop as soon as we erased an element from + any pool */ + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + if(poolType == (unsigned)keepPool) + continue; + + const size_t numErased = pools[poolType].erase(targetID); + if(!numErased) + continue; + + // targetID existed in this pool, so we're done + return; + } + +} + +/** + * @return true if the target was either completely new or existed before but was moved to a + * different pool now + */ +bool NodeCapacityPools::addOrUpdate(uint16_t targetID, CapacityPoolType poolType) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + /* note: map::insert().second is true if the key was inserted and false if it already existed + (so we need to check other pools only if it was really inserted) */ + + const bool newInsertion = pools[poolType].insert(targetID).second; + + if(newInsertion) + removeFromOthersUnlocked(targetID, poolType); + + return newInsertion; +} + +/** + * Note: Adds the targetID only if is doesn't exist in any of the pools (not only the specified + * one). + * + * @return true if this is a new target, false if the target existed already + */ +bool NodeCapacityPools::addIfNotExists(uint16_t targetID, CapacityPoolType poolType) +{ + /* this is called often with already existing targets (e.g. for every mds heartbeat), so we have + a quick path with only a read lock */ + + bool needUpdate = true; // whether or not we need the slow path to update something + + { // lock scope + // quick read lock check-only path + RWLockGuard readLock(rwlock, SafeRWLock_READ); + + for (unsigned currentPoolType = 0; currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if (pools[currentPoolType].find(targetID) == pools[currentPoolType].end()) + continue; // not found in this pool + + // target found in this pool + + needUpdate = false; + + break; + } + } + + if (!needUpdate) + return false; + + { // lock scope + // slow write lock path (because target wasn't found in quick path) + RWLockGuard writeLock(rwlock, SafeRWLock_WRITE); + + // recheck existence before adding, because it might have been added after read lock release + + needUpdate = true; + + for (unsigned currentPoolType = 0; currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if (pools[currentPoolType].find(targetID) == pools[currentPoolType].end()) + continue; // not found in this pool + + // target found in this pool + + needUpdate = false; + + break; + } + + if (needUpdate) + pools[poolType].insert(targetID); + + return needUpdate; + } +} + + +void NodeCapacityPools::remove(uint16_t targetID) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + for(unsigned currentPoolType = 0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + const size_t numErased = pools[currentPoolType].erase(targetID); + if(!numErased) + continue; + + break; + } +} + +/* + * @param lists the capacity pool lists in a vector with mappings lists[CapacityPoolType] + */ +void NodeCapacityPools::syncPoolsFromLists(const UInt16ListVector& lists) +{ + // we fill temporary sets first without lock, then swap with lock (to keep write-lock time short) + UInt16SetVector newPools(CapacityPool_END_DONTUSE); + + for(unsigned currentPoolType=0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + newPools[currentPoolType].insert(lists[currentPoolType].begin(), + lists[currentPoolType].end()); + } + + // temporary sets are ready, now swap with internal sets + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + newPools.swap(pools); +} + +/** + * Note: More efficient than the list version, because this will swap directly. + * + * @param sets the capacity pool sets in a vector with mappings lists[CapacityPoolType]; don't + * use contents after calling this! + */ +void NodeCapacityPools::syncPoolsFromSets(UInt16SetVector& sets) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + sets.swap(pools); +} + +/** + * @return the capacity pool lists in a vector with mappings lists[CapacityPoolType] + */ +UInt16ListVector NodeCapacityPools::getPoolsAsLists() const +{ + UInt16ListVector outPools(CapacityPool_END_DONTUSE); + + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for(unsigned currentPoolType=0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + for(UInt16SetCIter iter = pools[currentPoolType].begin(); + iter != pools[currentPoolType].end(); + iter++) + { + /* note: build-in multi-element list::insert is inefficient (walks list two times), thus + we do the copy ourselves here */ + outPools[currentPoolType].push_back(*iter); + } + } + return outPools; +} + +/** + * @param numTargets number of desired targets + * @param minNumRequiredTargets the minimum number of targets the caller needs; ususally 1 + * for RAID-0 striping and 2 for mirroring (so this parameter is intended to avoid problems when + * there is only 1 target left in the normal pool and the user has mirroring turned on). + * @param preferredTargets may be NULL + */ +void NodeCapacityPools::chooseStorageTargets(unsigned numTargets, unsigned minNumRequiredTargets, + const UInt16List* preferredTargets, UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + if(!preferredTargets || preferredTargets->empty() ) + { // no preference => start with first pool that contains any targets + if (!pools[CapacityPool_NORMAL].empty()) + { + chooseStorageNodesNoPref(pools[CapacityPool_NORMAL], numTargets, outTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + } + + /* note: no "else if" here, because we want to continue with next pool if we didn't find + enough targets for minNumRequiredTargets in previous pool */ + + if (!pools[CapacityPool_LOW].empty()) + { + chooseStorageNodesNoPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + outTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + } + + chooseStorageNodesNoPref(pools[CapacityPool_EMERGENCY], numTargets, outTargets); + } + else + { + // caller has preferred targets, so we can't say a priori whether nodes will be found or not + // in a pool. our strategy here is to automatically allow non-preferred targets before + // touching the emergency pool. + + std::set chosenTargets; + + // try normal and low pool with preferred targets... + + chooseStorageNodesWithPref(pools[CapacityPool_NORMAL], numTargets, preferredTargets, false, + outTargets, chosenTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + preferredTargets, false, outTargets, chosenTargets); + + if(!outTargets->empty() ) + return; + + /* note: currently, we cannot just continue here with non-empty outTargets (even if + "outTargets->size() < minNumRequiredTargets"), because we would need a mechanism to exclude + the already chosen outTargets for that (e.g. like an inverted preferredTargets list). */ + + // no targets yet - allow non-preferred targets before using emergency pool... + + chooseStorageNodesWithPref(pools[CapacityPool_NORMAL], numTargets, preferredTargets, true, + outTargets, chosenTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + preferredTargets, true, outTargets, chosenTargets); + + if(!outTargets->empty() ) + return; + + /* still no targets available => we have to try the emergency pool (first with preference, + then without preference) */ + + chooseStorageNodesWithPref(pools[CapacityPool_EMERGENCY], numTargets, preferredTargets, false, + outTargets, chosenTargets); + if(!outTargets->empty() ) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_EMERGENCY], numTargets, preferredTargets, true, + outTargets, chosenTargets); + } +} + +/** + * Alloc storage targets in a round-robin fashion. + * + * Disadvantages: + * - no support for preferred targets + * - would be really complex for multiple clients with distinct preferred targets and per-client + * lastTarget wouldn't lead to perfect balance) + * - requires write lock + * - lastTarget is not on a per-pool basis and hence + * - would not be perfectly round-robin when targets are moved to other pools + * - doesn't work with minNumRequiredTargets as switching pools would mess with the global + * lastTarget value + * - lastTarget is not saved across server restart + * + * ...so this should only be used in special scenarios and not as a general replacement for the + * normal chooseStorageTargets() method. + */ +void NodeCapacityPools::chooseStorageTargetsRoundRobin(unsigned numTargets, + UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + // no preference settings supported => use first pool that contains any targets + if (!pools[CapacityPool_NORMAL].empty()) + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_NORMAL], numTargets, outTargets); + else if (!pools[CapacityPool_LOW].empty()) + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_LOW], numTargets, outTargets); + else + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_EMERGENCY], numTargets, outTargets); +} + + +/** + * Note: Unlocked (=> caller must hold read lock) + * + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void NodeCapacityPools::chooseStorageNodesNoPref(UInt16Set& activeTargets, + unsigned numTargets, UInt16Vector* outTargets) +{ + // note: we use the name "activeTargets" for the pool here to keep the code very similar to the + // nodes chooser in the NodeStore class + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + const unsigned activeTargetsSize = activeTargets.size(); + + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + UInt16SetCIter iter; + const int partSize = activeTargetsSize / numTargets; + + moveIterToRandomElem(activeTargets, iter); + + outTargets->reserve(numTargets); + + // for each target in numTargets + for(unsigned i=0; i < numTargets; i++) + { + const int rangeMin = i*partSize; + const int rangeMax = (i==(numTargets-1) ) ? (activeTargetsSize-1) : (rangeMin + partSize - 1); + // rangeMax decision because numTargets might not devide activeTargetsSize without a remainder + + int next = randGen.getNextInRange(rangeMin, rangeMax); + + /* move iter to the chosen target, add it to outTargets, and skip the remaining targets of + this part */ + + for(int j=rangeMin; j < next; j++) + moveIterToNextRingElem(activeTargets, iter); + + outTargets->push_back(*iter); + + for(int j=next; j < (rangeMax+1); j++) + moveIterToNextRingElem(activeTargets, iter); + } + +} + +/** + * Note: Unlocked (=> caller must hold write lock) + * + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void NodeCapacityPools::chooseStorageNodesNoPrefRoundRobin(UInt16Set& activeTargets, + unsigned numTargets, UInt16Vector* outTargets) +{ + // note: we use the name "activeTargets" for the pool here to keep the code very similar to the + // nodes chooser in the NodeStore class + + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + const unsigned activeTargetsSize = activeTargets.size(); + + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + UInt16SetCIter iter = activeTargets.lower_bound(lastRoundRobinTarget); + if(iter == activeTargets.end() ) + iter = activeTargets.begin(); + + outTargets->reserve(numTargets); + + // just add the requested number of targets sequentially from the set + for(unsigned i=0; i < numTargets; i++) + { + moveIterToNextRingElem(activeTargets, iter); + outTargets->push_back(*iter); + } + + lastRoundRobinTarget = *iter; +} + + +/** + * Note: Unlocked (=> caller must hold read lock) + * + * Note: This method assumes that there really is at least one preferred target and that those + * targets are probably active. So do not use it as a replacement for the version without preferred + * targets! + * + * @param allowNonPreferredTargets true to enable use of non-preferred targets if not enough + * preferred targets are active to satisfy numTargets + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void NodeCapacityPools::chooseStorageNodesWithPref(UInt16Set& activeTargets, + unsigned numTargets, const UInt16List* preferredTargets, bool allowNonPreferredTargets, + UInt16Vector* outTargets, std::set& chosenTargets) +{ + // note: we use the name "activeTargets" for the pool here to keep the code very similar to the + // nodes chooser in the NodeStore class + + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + const unsigned activeTargetsSize = activeTargets.size(); + + // max number of targets is limited by the number of known active targets + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + // Stage 1: add all the preferred targets that are actually available to the outTargets + + // note: we use a separate set for the outTargets here to quickly find out (in stage 2) whether + // we already added a certain node from the preferred targets (in stage 1) + + UInt16ListConstIter preferredIter; + UInt16SetIter activeTargetsIter; // (will be re-used in stage 2) + + moveIterToRandomElem(*preferredTargets, preferredIter); + + outTargets->reserve(numTargets); + + // walk over all the preferred targets and add them to outTargets when they are available + // (note: iterTmp is used to avoid calling preferredTargets->size() ) + for(UInt16ListConstIter iterTmp = preferredTargets->begin(); + (iterTmp != preferredTargets->end() ) && numTargets; + iterTmp++) + { + activeTargetsIter = activeTargets.find(*preferredIter); + + if(activeTargetsIter != activeTargets.end() && chosenTargets.insert(*preferredIter).second) + { // this preferred node is active => add to outTargets + outTargets->push_back(*preferredIter); + numTargets--; + } + + moveIterToNextRingElem(*preferredTargets, preferredIter); + } + + // Stage 2: add the remaining requested number of targets from the active targets + + // if numTargets is greater than 0 then we have some requested targets left, that could not be + // taken from the preferred targets + + // we keep it simple here, because usually there will be enough preferred targets available, + // so that this case is quite unlikely + + if(allowNonPreferredTargets && numTargets) + { + UInt16SetIter outTargetsSetIter; + + moveIterToRandomElem(activeTargets, activeTargetsIter); + + // while we haven't found the number of requested targets + while(numTargets) + { + if(chosenTargets.insert(*activeTargetsIter).second) + { + outTargets->push_back(*activeTargetsIter); + numTargets--; + } + + moveIterToNextRingElem(activeTargets, activeTargetsIter); + } + } + +} + +/** + * get the pool type to which a nodeID is currently assigned. + * + * @return false if nodeID not found. + */ +bool NodeCapacityPools::getPoolAssignment(uint16_t nodeID, CapacityPoolType* outPoolType) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + *outPoolType = CapacityPool_EMERGENCY; + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + UInt16SetCIter iter = pools[poolType].find(nodeID); + + if(iter != pools[poolType].end() ) + { + *outPoolType = (CapacityPoolType)poolType; + + return true; + } + } + + return false; +} + +/** + * Return internal state as human-readable string. + * + * Note: Very expensive, only intended for debugging. + */ +std::string NodeCapacityPools::getStateAsStr() const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + std::ostringstream stateStream; + + stateStream << " lastRoundRobinTarget: " << lastRoundRobinTarget << std::endl; + stateStream << " lowSpace: " << this->poolLimitsSpace.getLowLimit() << std::endl; + stateStream << " emergencySpace: " << this->poolLimitsSpace.getEmergencyLimit() << std::endl; + stateStream << " lowInodes: " << this->poolLimitsInodes.getLowLimit() << std::endl; + stateStream << " emergencyInodes: " << this->poolLimitsInodes.getEmergencyLimit() << std::endl; + stateStream << " dynamicLimits: " << (this->useDynamicPools ? "on" : "off") << std::endl; + + if(this->useDynamicPools) + { + stateStream << " normalSpaceSpreadThreshold: " << + this->poolLimitsSpace.getNormalSpreadThreshold() << std::endl; + stateStream << " lowSpaceSpreadThreshold: " << + this->poolLimitsSpace.getLowSpreadThreshold() << std::endl; + stateStream << " lowSpaceDynamicLimit: " << + this->poolLimitsSpace.getLowDynamicLimit() << std::endl; + stateStream << " emergencySpaceDynamicLimit: " << + this->poolLimitsSpace.getEmergencyDynamicLimit() << std::endl; + + stateStream << " normalInodesSpreadThreshold: " << + this->poolLimitsInodes.getNormalSpreadThreshold() << std::endl; + stateStream << " lowInodesSpreadThreshold: " << + this->poolLimitsInodes.getLowSpreadThreshold() << std::endl; + stateStream << " lowInodesDynamicLimit: " << + this->poolLimitsInodes.getLowDynamicLimit() << std::endl; + stateStream << " emergencyInodesDynamicLimit: " << + this->poolLimitsInodes.getEmergencyDynamicLimit() << std::endl; + } + + stateStream << std::endl; + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + stateStream << " pool " << poolTypeToStr( (CapacityPoolType) poolType) << ":" << + std::endl; + + for(UInt16SetCIter iter = pools[poolType].begin(); + iter != pools[poolType].end(); + iter++) + { + stateStream << " " << *iter; + } + + stateStream << std::endl; + } + + return stateStream.str(); +} + +/** + * @return pointer to static string buffer (no free needed) + */ +const char* NodeCapacityPools::poolTypeToStr(CapacityPoolType poolType) +{ + switch(poolType) + { + case CapacityPool_NORMAL: + { + return "Normal"; + } break; + + case CapacityPool_LOW: + { + return "Low"; + } break; + + case CapacityPool_EMERGENCY: + { + return "Emergency"; + } break; + + default: + { + return "Unknown/Invalid"; + } break; + } +} diff --git a/common/source/common/nodes/NodeCapacityPools.h b/common/source/common/nodes/NodeCapacityPools.h new file mode 100644 index 0000000..a565414 --- /dev/null +++ b/common/source/common/nodes/NodeCapacityPools.h @@ -0,0 +1,125 @@ +#pragma once + +#include +#include +#include +#include + + +/** + * Note: We also use these pools to store nodeIDs (instead of targetIDs) for meta servers. + */ +class NodeCapacityPools +{ + public: + NodeCapacityPools(bool useDynamicPools, const DynamicPoolLimits& poolLimitsSpace, + const DynamicPoolLimits& poolLimitsInodes); + + bool addOrUpdate(uint16_t targetID, CapacityPoolType poolType); + bool addIfNotExists(uint16_t targetID, CapacityPoolType poolType); + void remove(uint16_t targetID); + + void syncPoolsFromLists(const UInt16ListVector& lists); + void syncPoolsFromSets(UInt16SetVector& sets); + UInt16ListVector getPoolsAsLists() const; + + void chooseStorageTargets(unsigned numTargets, unsigned minNumRequiredTargets, + const UInt16List* preferredTargets, UInt16Vector* outTargets); + void chooseStorageTargetsRoundRobin(unsigned numTargets, UInt16Vector* outTargets); + + bool getPoolAssignment(uint16_t nodeID, CapacityPoolType* outPoolType) const; + + std::string getStateAsStr() const; + + static const char* poolTypeToStr(CapacityPoolType poolType); + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->pools; + } + + private: + mutable RWLock rwlock; + + bool useDynamicPools; + const DynamicPoolLimits poolLimitsSpace; + const DynamicPoolLimits poolLimitsInodes; + + UInt16SetVector pools; + + RandomReentrant randGen; // for random target selection + uint16_t lastRoundRobinTarget; // used for round-robin chooser + + void removeFromOthersUnlocked(uint16_t targetID, CapacityPoolType keepPool); + + void chooseStorageNodesNoPref(UInt16Set& activeTargets, unsigned numTargets, + UInt16Vector* outTargets); + void chooseStorageNodesNoPrefRoundRobin(UInt16Set& activeTargets, unsigned numTargets, + UInt16Vector* outTargets); + void chooseStorageNodesWithPref(UInt16Set& activeTargets, unsigned numTargets, + const UInt16List* preferredTargets, bool allowNonPreferredTargets, + UInt16Vector* outTargets, std::set& chosenTargets); + + public: + // getters & setters + bool getDynamicPoolsEnabled() const + { + return this->useDynamicPools; + } + + const DynamicPoolLimits& getPoolLimitsSpace() const + { + return poolLimitsSpace; + } + + const DynamicPoolLimits& getPoolLimitsInodes() const + { + return poolLimitsInodes; + } + + private: + // template inliners + + /** + * Note: static method => unlocked (=> caller must hold read lock) + */ + template + static void moveIterToNextRingElem(const TCollection& collection, + typename TCollection::const_iterator& iter) + { + if(iter != collection.end() ) + { + iter++; + if(iter == collection.end() ) + iter = collection.begin(); + } + else + { // iterator is pointing to the end element + iter = collection.begin(); // this should actually never happen + } + } + + /** + * Note: unlocked (=> caller must hold read lock) + */ + template + void moveIterToRandomElem(const TCollection& collection, + typename TCollection::const_iterator& iter) + { + iter = collection.begin(); + + int collectionSize = collection.size(); + + if(collectionSize < 2) + return; + + int randInt = randGen.getNextInRange(0, collectionSize - 1); + + while(randInt--) + iter++; + } +}; + + diff --git a/common/source/common/nodes/NodeConnPool.cpp b/common/source/common/nodes/NodeConnPool.cpp new file mode 100644 index 0000000..d990215 --- /dev/null +++ b/common/source/common/nodes/NodeConnPool.cpp @@ -0,0 +1,698 @@ +#include +#include +#include +#include +#include +#include +#include "Node.h" +#include "NodeConnPool.h" + +#include + +#include + +#define NODECONNPOOL_SHUTDOWN_WAITTIMEMS 500 + + +/** + * Note: localNicCaps should to be initialized before acquiring a stream. + * Note: The opened comm channels are direct by default. + * + * @param parentNode the node to which this store belogs (to have nodeType for log messages etc). + * @param nicList an internal copy will be created. + */ +NodeConnPool::NodeConnPool(Node& parentNode, unsigned short streamPort, const NicAddressList& nicList): + parentNode(parentNode) +{ + AbstractApp* app = PThread::getCurrentThreadApp(); + auto cfg = app->getCommonConfig(); + + this->nicList = nicList; + this->restrictOutboundInterfaces = cfg->getConnRestrictOutboundInterfaces(); + + this->establishedConns = 0; + this->availableConns = 0; + + this->maxConns = cfg->getConnMaxInternodeNum(); + this->fallbackExpirationSecs = cfg->getConnFallbackExpirationSecs(); + this->isChannelDirect = true; + + this->app = app; + this->streamPort = streamPort; + memset(&localNicCaps, 0, sizeof(localNicCaps) ); + memset(&stats, 0, sizeof(stats) ); + memset(&errState, 0, sizeof(errState) ); +} + +void NodeConnPool::setLocalNicList(const NicAddressList& localNicList, + const NicListCapabilities& localNicCaps) +{ + std::unique_lock mutexLock(mutex); + this->localNicList = localNicList; + this->localNicCaps = localNicCaps; + loadIpSourceMap(this->nicList); + mutexLock.unlock(); + invalidateAllAvailableStreams(false, true); +} + +NodeConnPool::~NodeConnPool() +{ + const char* logContext = "NodeConn (destruct)"; + + if(!connList.empty() ) + { + LogContext(logContext).log(Log_DEBUG, + "Closing " + StringTk::intToStr(connList.size() ) + " connections..."); + } + + while(!connList.empty() ) + { + ConnListIter iter = connList.begin(); + invalidateSpecificStreamSocket(*iter); + } + + nicList.clear(); +} + +bool NodeConnPool::loadIpSourceMap() +{ + if (restrictOutboundInterfaces) + { + std::unique_lock mutexLock(mutex); + return loadIpSourceMap(nicList); + } + return true; +} + +/** + * Reload ipSrcMap. Mutex must be held. + */ +bool NodeConnPool::loadIpSourceMap(const NicAddressList& nicList) +{ + bool result = false; + if (restrictOutboundInterfaces && !nicList.empty()) + { + LogContext log("NodeConn (loadIPSourceMap)"); + RoutingTable rt = app->getRoutingTable(); + result = rt.loadIpSourceMap(nicList, localNicList, ipSrcMap); + if (!result) + log.log(Log_ERR, std::string("No routes found, node: ") + parentNode.getNodeIDWithTypeStr()); + } + else + result = true; + return result; +} + +/** + * Note: Will block if no stream socket is immediately available. + * + * @return connected socket + * @throw SocketConnectException if all connection attempts fail, SocketException if other + * connection problem occurs (e.g. during hand-shake) + */ +Socket* NodeConnPool::acquireStreamSocket() +{ + return acquireStreamSocketEx(true); +} + +/** + * @param true to allow waiting if all avaiable conns are currently in use + * @return connected socket or NULL if all conns busy and waiting is not allowed + * @throw SocketConnectException if all connection attempts fail, SocketException if other + * connection problem occurs (e.g. during hand-shake) + */ +Socket* NodeConnPool::acquireStreamSocketEx(bool allowWaiting) +{ + PooledSocket* sock = NULL; + unsigned short port; + + LogContext log("NodeConn (acquire stream)"); + + std::unique_lock mutexLock(mutex); // L O C K + + if(!availableConns && (establishedConns == maxConns) ) + { // wait for a conn to become available (or disconnected) + + if(!allowWaiting) + { // all conns in use and waiting not allowed => exit + return NULL; + } + + while(!availableConns && (establishedConns == maxConns) ) + changeCond.wait(&mutex); + } + + if(likely(availableConns) ) + { + // established connection available => grab it + + ConnListIter iter = connList.begin(); + + while(!(*iter)->isAvailable() ) + iter++; + + sock = *iter; + sock->setAvailable(false); + sock->setHasActivity(); + + availableConns--; + + return sock; + } + + + // no conn available, but maxConns not reached yet => establish a new conn + + bool isPrimaryInterface = true; // used to set expiration for non-primary interfaces + // primary means: first interface in the list that is supported by client and server + + port = this->streamPort; + NicAddressList nicListCopy = this->nicList; + NicListCapabilities localNicCapsCopy = this->localNicCaps; + IpSourceMap ipSrcMapCopy = this->ipSrcMap; + + establishedConns++; + + mutexLock.unlock(); // U N L O C K + + // walk over all available NICs, create the corresponding socket and try to connect + + NicAddressListIter iter = nicListCopy.begin(); + for( ; iter != nicListCopy.end(); iter++) + { + StandardSocket* newStandardSock = NULL; // (to find out whether we need to set the + // socketOptions without runtime type info) + RDMASocket* newRDMASock = NULL; // (to find out whether we need to set the + // socketOptions without runtime type info) + + std::string endpointStr = boost::lexical_cast(parentNode.getNodeType()) + "@" + + Socket::endpointAddrToStr(iter->ipAddr, port); + + struct in_addr srcIp = { + .s_addr = 0 + }; + if (restrictOutboundInterfaces) + { + srcIp = ipSrcMapCopy[iter->ipAddr]; + if (srcIp.s_addr == 0) + { + log.log(Log_DEBUG, "Skip NIC, no route for " + endpointStr); + continue; + } + log.log(Log_DEBUG, std::string("Use ") + Socket::ipaddrToStr(srcIp) + " for " + endpointStr); + } + + if(!app->getNetFilter()->isAllowed(iter->ipAddr.s_addr) ) + continue; + + if( (iter->nicType != NICADDRTYPE_STANDARD) && + app->getTcpOnlyFilter()->isContained(iter->ipAddr.s_addr) ) + continue; + + try + { + switch(iter->nicType) + { + case NICADDRTYPE_RDMA: + { // RDMA + if(!localNicCapsCopy.supportsRDMA) + continue; + + log.log(Log_DEBUG, "Establishing new RDMA connection to: " + endpointStr); + newRDMASock = RDMASocket::create().release(); + sock = newRDMASock; + } break; + case NICADDRTYPE_STANDARD: + { // TCP + log.log(Log_DEBUG, "Establishing new TCP connection to: " + endpointStr); + newStandardSock = new StandardSocket(PF_INET, SOCK_STREAM); + sock = newStandardSock; + } break; + + default: + { // unknown + log.log(Log_WARNING, "Skipping unknown connection type to: " + endpointStr); + continue; + } + + } // end of switch + + } // end of try + catch(SocketException& e) + { + log.log(Log_NOTICE, std::string("Socket initialization failed: ") + endpointStr + ". " + "Exception: " + std::string(e.what() ) ); + + continue; + } + + if (!sock) { + LOG(GENERAL, ERR, "Failed to open socket for unknown reasons.", endpointStr); + continue; + } + + try + { + // the actual connection attempt + if(newRDMASock) + applySocketOptionsPreConnect(newRDMASock); + else + if(newStandardSock) + applySocketOptionsPreConnect(newStandardSock); + + if (srcIp.s_addr) + sock->bindToAddr(srcIp.s_addr, 0); + sock->connect(&iter->ipAddr, port); + + if(errState.shouldPrintConnectedLogMsg(sock->getPeerIP(), sock->getSockType() ) ) + { + std::string protocolStr = NetworkInterfaceCard::nicTypeToString( + sock->getSockType() ); + std::string fallbackStr = isPrimaryInterface ? "" : "; fallback route"; + + log.log(Log_NOTICE, "Connected: " + endpointStr + " " + "(protocol: " + protocolStr + fallbackStr + ")" ); + } + + if(newStandardSock) + applySocketOptionsConnected(newStandardSock); + + if(app->getCommonConfig()->getConnAuthHash() ) + authenticateChannel(sock); + + if(!isChannelDirect) + makeChannelIndirect(sock); + + if(!isPrimaryInterface) // non-primary => set expiration counter + sock->setExpireTimeStart(); + + break; + } + catch(SocketException& e) + { + if(errState.shouldPrintConnectFailedLogMsg(sock->getPeerIP(), sock->getSockType() ) ) + log.log(Log_NOTICE, std::string("Connect failed: ") + endpointStr + " " + "(protocol: " + NetworkInterfaceCard::nicTypeToString(sock->getSockType() ) + + "); Error: " + std::string(e.what() ) ); + + // the next interface is definitely non-primary + isPrimaryInterface = false; + + delete(sock); + } + } // end of connect loop + + mutexLock.lock(); // L O C K + + if(iter != nicListCopy.end() ) + { + // success => add to list (as unavailable) + connList.push_back(sock); + statsAddNic(sock->getSockType() ); + + errState.setConnSuccess(sock->getPeerIP(), sock->getSockType() ); + } + else + { + // absolutely unable to connect + sock = NULL; + establishedConns--; + + if(!errState.getWasLastTimeCompleteFail() ) + { + log.log(Log_CRITICAL, + "Connect failed on all available routes: " + parentNode.getNodeIDWithTypeStr() ); + + errState.setCompleteFail(); + } + + // we are not using this connection => notify next waiter + changeCond.signal(); + } + + if(!sock) + throw SocketConnectException("Connect failed on all available routes"); + + return sock; +} + +void NodeConnPool::releaseStreamSocket(Socket* sock) +{ + PooledSocket* pooledSock = (PooledSocket*)sock; + + if(unlikely(pooledSock->getHasExpired(fallbackExpirationSecs) ) || + unlikely(pooledSock->isCloseOnRelease() ) ) + { + // this socket just expired or is set to be closed => invalidate it. + // note: changeCond is signaled by this call + invalidateSpecificStreamSocket(sock); + return; + } + + // mark the socket as available + + const std::lock_guard lock(mutex); + + availableConns++; + + pooledSock->setAvailable(true); + + changeCond.signal(); +} + +void NodeConnPool::invalidateStreamSocket(Socket* sock) +{ + // note: we use invalidateAllAvailableStreams first here, because we want to keep other threads + // from trying to acquire an available socket immediately - and the parameter socket isn't + // availabe anyways. (we don't want to acquire a mutex here that would block the whole pool + // while we're just waiting for the sock-close response messages) + + invalidateAllAvailableStreams(false, false); + invalidateSpecificStreamSocket(sock); +} + +void NodeConnPool::invalidateSpecificStreamSocket(Socket* sock) +{ + LogContext log("NodeConn (invalidate stream)"); + + bool sockValid = true; + + { + const std::lock_guard lock(mutex); + + ConnListIter iter = connList.begin(); + + while( (*iter != ((PooledSocket*)sock) ) && (iter != connList.end() ) ) + iter++; + + if(unlikely(iter == connList.end() ) ) + { + log.logErr("Tried to remove a socket that was not found in the pool: " + + sock->getPeername() ); + sockValid = false; + } + else + { // found the socket in the pool => remove from pool list + establishedConns--; + + connList.erase(iter); + statsRemoveNic(sock->getSockType() ); + + changeCond.signal(); + } + } + + if(!sockValid) + return; + + + try + { + sock->shutdownAndRecvDisconnect(NODECONNPOOL_SHUTDOWN_WAITTIMEMS); + } + catch(SocketException& e) + { + // nothing to be done here (we wanted to invalidate the conn anyways) + } + + log.log(Log_DEBUG, std::string("Disconnected: ") + + boost::lexical_cast(parentNode.getNodeType()) + "@" + sock->getPeername() ); + + delete(sock); +} + +/** + * Invalidate (disconnect) connections that are currently not acquired. + * + * @param idleStreamsOnly invalidate only conns that are marked as idle (ie don't have the activity + * flag set). + * @param closeOnRelease if true, set every PooledSocket's closeOnRelease flag. + * @return number of invalidated streams + */ +unsigned NodeConnPool::invalidateAllAvailableStreams(bool idleStreamsOnly, bool closeOnRelease) +{ + /* note: we have TWO STAGES here, because we don't want to hold the mutex and block everything + while we're waiting for the conns to be dropped. */ + + LogContext log("NodeConn (invalidate all streams)"); + + unsigned numInvalidated = 0; // retVal + ConnectionList availableConnsList; + + { + const std::lock_guard lock(mutex); + + if(this->availableConns) + LOG_DEBUG_CONTEXT(log, Log_DEBUG, + "Currently available connections: " + StringTk::uintToStr(this->availableConns) ); + + + // STAGE 1: grab all sockets that should be disconnected + + for(ConnListIter iter = connList.begin(); iter != connList.end(); iter++) + { + PooledSocket* sock = *iter; + + if (closeOnRelease ) + sock->setCloseOnRelease(true); + + if(!sock->isAvailable() ) + continue; + + if(idleStreamsOnly && sock->getHasActivity() ) + continue; // idle-only requested and this one was not idle + + sock->setAvailable(false); + this->availableConns--; + availableConnsList.push_back(sock); + + numInvalidated++; + } + } + + + // STAGE 2: invalidate all grabbed sockets + + for(ConnListIter iter = availableConnsList.begin(); iter != availableConnsList.end(); iter++) + { + invalidateSpecificStreamSocket(*iter); + } + + return numInvalidated; +} + +/** + * Note: There is no locking around dropping and resetting afterwards, so there should only be one + * thread which calls this function (=> InternodeSyncer). + * + * @return number of disconnected streams + */ +unsigned NodeConnPool::disconnectAndResetIdleStreams() +{ + unsigned numInvalidated; + + numInvalidated = invalidateAllAvailableStreams(true, false); + + resetStreamsIdleFlag(); + + return numInvalidated; +} + +/** + * Resets the activity flag of all available connections to mark them as idle. + */ +void NodeConnPool::resetStreamsIdleFlag() +{ + const std::lock_guard lock(mutex); + + for(ConnListIter iter = connList.begin(); iter != connList.end(); iter++) + { + PooledSocket* sock = *iter; + + if(!sock->isAvailable() ) + continue; + + sock->resetHasActivity(); + } +} + + +void NodeConnPool::applySocketOptionsPreConnect(RDMASocket* sock) +{ + auto cfg = app->getCommonConfig(); + + sock->setBuffers(cfg->getConnRDMABufNum(), cfg->getConnRDMABufSize() ); + sock->setTimeouts(cfg->getConnRDMATimeoutConnect(), cfg->getConnRDMATimeoutFlowSend(), + cfg->getConnRDMATimeoutPoll()); + sock->setTypeOfService(cfg->getConnRDMATypeOfService()); +} + +void NodeConnPool::applySocketOptionsPreConnect(StandardSocket* sock) +{ + auto cfg = app->getCommonConfig(); + int bufsize = cfg->getConnTCPRcvBufSize(); + + if (bufsize > 0) + sock->setSoRcvBuf(bufsize); +} + +void NodeConnPool::applySocketOptionsConnected(StandardSocket* sock) +{ + LogContext log("NodeConn (apply socket options"); + + // apply general socket options + + try + { + sock->setTcpCork(false); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to disable TcpCork"); + } + + try + { + sock->setTcpNoDelay(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable TcpNoDelay"); + } + + try + { + sock->setSoKeepAlive(true); + } + catch(SocketException& e) + { + log.log(Log_NOTICE, "Failed to enable SoKeepAlive"); + } +} + +void NodeConnPool::authenticateChannel(Socket* sock) +{ + uint64_t authHash = app->getCommonConfig()->getConnAuthHash(); + AuthenticateChannelMsg authMsg(authHash); + const auto msgBuf = MessagingTk::createMsgVec(authMsg); + + sock->sendto(&msgBuf[0], msgBuf.size(), 0, NULL, 0); +} + +void NodeConnPool::makeChannelIndirect(Socket* sock) +{ + SetChannelDirectMsg directMsg(false); + const auto msgBuf = MessagingTk::createMsgVec(directMsg); + + sock->sendto(&msgBuf[0], msgBuf.size(), 0, NULL, 0); +} + +/** + * @param streamPort value 0 will be ignored + * @param nicList will be copied + */ +bool NodeConnPool::updateInterfaces(unsigned short streamPort, const NicAddressList& nicList) +{ + bool hasChanged = false; + { + const std::lock_guard lock(mutex); + + if(streamPort && (streamPort != this->streamPort) ) + { + LOG(GENERAL, NOTICE, "Node port has changed", ("node", parentNode.getNodeIDWithTypeStr())); + this->streamPort = streamPort; + hasChanged = true; + } + + if (!(this->nicList == nicList)) + { + LOG(GENERAL, NOTICE, "Node interfaces have changed", ("node", parentNode.getNodeIDWithTypeStr())); + hasChanged = true; + this->nicList = nicList; + loadIpSourceMap(this->nicList); + } + + } + + if(unlikely(hasChanged) ) + { + // closeOnRelease is true, all of these sockets need to be invalidated ASAP + invalidateAllAvailableStreams(false, true); + } + + return hasChanged; +} + +/** + * Gets the first socket peer name for a connection to the specified nicType. + * Only works on currently available connections. + * + * @outPeerName buffer for the peer name string (contains "n/a" if no such connection exists or is + * currently not available). + * @param outIsNonPrimary set to true if conn in outBuf has an expiration counter, false otherwise. + * @return true if peer name filled in, false if all established conns busy. + */ +bool NodeConnPool::getFirstPeerName(NicAddrType nicType, std::string* outPeerName, + bool* outIsNonPrimary) +{ + const std::lock_guard lock(mutex); + + for(ConnListIter connIter = connList.begin(); connIter != connList.end(); connIter++) + { + PooledSocket* sock = *connIter; + + if(sock->isAvailable() && (sock->getSockType() == nicType) ) + { // found a match => store in out string and stop + *outPeerName = sock->getPeername(); + + *outIsNonPrimary = sock->getHasExpirationTimer(); + + return true; + } + } + + // print "n/a" + *outPeerName = "busy"; + + *outIsNonPrimary = false; + + return false; +} + +/** + * Increase stats counter for number of established conns (by NIC type). + */ +void NodeConnPool::statsAddNic(NicAddrType nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: + { + (stats.numEstablishedRDMA)++; + } break; + + default: + { + (stats.numEstablishedStd)++; + } break; + } +} + +/** + * Decrease stats counter for number of established conns (by NIC type). + */ +void NodeConnPool::statsRemoveNic(NicAddrType nicType) +{ + switch(nicType) + { + case NICADDRTYPE_RDMA: + { + (stats.numEstablishedRDMA)--; + } break; + + default: + { + (stats.numEstablishedStd)--; + } break; + } +} diff --git a/common/source/common/nodes/NodeConnPool.h b/common/source/common/nodes/NodeConnPool.h new file mode 100644 index 0000000..28f9e13 --- /dev/null +++ b/common/source/common/nodes/NodeConnPool.h @@ -0,0 +1,200 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +typedef std::list ConnectionList; +typedef ConnectionList::iterator ConnListIter; + +// forward declaration +class AbstractApp; +class Node; + +/** + * Holds statistics about currently established connections. + */ +struct NodeConnPoolStats +{ + unsigned numEstablishedStd; + unsigned numEstablishedRDMA; +}; + +/** + * Holds state of failed connects to avoid log spamming with conn errors. + */ +struct NodeConnPoolErrorState +{ + uint32_t lastSuccessPeerIP; // the last IP that we connected to successfully + NicAddrType lastSuccessNicType; // the last nic type that we connected to successfully + bool wasLastTimeCompleteFail; // true if last attempt failed on all routes + + bool getWasLastTimeCompleteFail() + { + return this->wasLastTimeCompleteFail; + } + + void setCompleteFail() + { + this->lastSuccessPeerIP = 0; + this->wasLastTimeCompleteFail = true; + } + + void setConnSuccess(uint32_t lastSuccessPeerIP, NicAddrType lastSuccessNicType) + { + this->lastSuccessPeerIP = lastSuccessPeerIP; + this->lastSuccessNicType = lastSuccessNicType; + + this->wasLastTimeCompleteFail = false; + } + + bool equalsLastSuccess(uint32_t lastSuccessPeerIP, NicAddrType lastSuccessNicType) + { + return (this->lastSuccessPeerIP == lastSuccessPeerIP) && + (this->lastSuccessNicType == lastSuccessNicType); + } + + bool isLastSuccessInitialized() + { + return (this->lastSuccessPeerIP != 0); + } + + bool shouldPrintConnectedLogMsg(uint32_t currentPeerIP, NicAddrType currentNicType) + { + /* log only if we didn't succeed at all last time, or if we succeeded last time and it was + with a different IP/NIC pair */ + + return this->wasLastTimeCompleteFail || + !isLastSuccessInitialized() || + !equalsLastSuccess(currentPeerIP, currentNicType); + } + + bool shouldPrintConnectFailedLogMsg(uint32_t currentPeerIP, NicAddrType currentNicType) + { + /* log only if this is the first connection attempt or if we succeeded last time this this + IP/NIC pair */ + + return (!this->wasLastTimeCompleteFail && + (!isLastSuccessInitialized() || + equalsLastSuccess(currentPeerIP, currentNicType) ) ); + } +}; + +/** + * This class represents a pool of stream connections to a certain node. + */ +class NodeConnPool +{ + public: + NodeConnPool(Node& parentNode, unsigned short streamPort, const NicAddressList& nicList); + virtual ~NodeConnPool(); + + NodeConnPool(const NodeConnPool&) = delete; + NodeConnPool(NodeConnPool&&) = delete; + NodeConnPool& operator=(const NodeConnPool&) = delete; + NodeConnPool& operator=(NodeConnPool&&) = delete; + + Socket* acquireStreamSocket(); + virtual Socket* acquireStreamSocketEx(bool allowWaiting); + virtual void releaseStreamSocket(Socket* sock); + virtual void invalidateStreamSocket(Socket* sock); + + unsigned disconnectAndResetIdleStreams(); + + // returns true if the interfaces are different than those currently known + virtual bool updateInterfaces(unsigned short streamPort, const NicAddressList& nicList); + + bool getFirstPeerName(NicAddrType nicType, std::string* outPeerName, bool* outIsNonPrimary); + + + private: + NicAddressList nicList; + ConnectionList connList; + bool restrictOutboundInterfaces; + IpSourceMap ipSrcMap; + + AbstractApp* app; + Node& parentNode; // backlink to the node object to which this conn pool belongs + unsigned short streamPort; + NicListCapabilities localNicCaps; + NicAddressList localNicList; + + unsigned availableConns; // available established conns + unsigned establishedConns; // not equal to connList.size!! + unsigned maxConns; + unsigned fallbackExpirationSecs; // expiration time for conns to fallback interfaces + bool isChannelDirect; + + NodeConnPoolStats stats; + NodeConnPoolErrorState errState; + + Mutex mutex; + Condition changeCond; + + virtual void invalidateSpecificStreamSocket(Socket* sock); + virtual unsigned invalidateAllAvailableStreams(bool idleStreamsOnly, bool closeOnRelease); + void resetStreamsIdleFlag(); + void applySocketOptionsPreConnect(RDMASocket* sock); + void applySocketOptionsPreConnect(StandardSocket* sock); + void applySocketOptionsConnected(StandardSocket* sock); + void authenticateChannel(Socket* sock); + void makeChannelIndirect(Socket* sock); + + void statsAddNic(NicAddrType nicType); + void statsRemoveNic(NicAddrType nicType); + bool loadIpSourceMap(const NicAddressList& nicList); + + public: + // getters & setters + + virtual NicAddressList getNicList() + { + const std::lock_guard lock(mutex); + + return nicList; + } + + unsigned short getStreamPort() + { + const std::lock_guard lock(mutex); + + return streamPort; + } + + void setLocalNicList(const NicAddressList& localNicList, + const NicListCapabilities& localNicCaps); + + void setChannelDirect(bool isChannelDirect) + { + this->isChannelDirect = isChannelDirect; + } + + /** + * Note: This is not intended to be called under normal circumstances (it exists only for + * special tests e.g. in fhgfs_online_cfg) + */ + void setMaxConns(unsigned maxConns) + { + const std::lock_guard lock(mutex); + + this->maxConns = maxConns; + } + + void getStats(NodeConnPoolStats* outStats) + { + const std::lock_guard lock(mutex); + + *outStats = this->stats; + } + + bool loadIpSourceMap(); +}; + diff --git a/common/source/common/nodes/NodeOpStats.cpp b/common/source/common/nodes/NodeOpStats.cpp new file mode 100644 index 0000000..269bfd3 --- /dev/null +++ b/common/source/common/nodes/NodeOpStats.cpp @@ -0,0 +1,125 @@ +#include "NodeOpStats.h" + +/** + * @param cookieIP - If several transfers are required to transfer the map to the client, + * cookieIP is the last IP (in the vector) of the last transfer. We will then + * continue to tranfer the map for IP *after* cookieIP. (~0 if no cookie yet.) + * @param bufLen - maximum size to be used for serialization and so for outVec + * @param wantPerUserStats - true to query per-user instead of per-client stats. + * @param vec - The map is encoded as vector. Format is: + * numOPs, IP1, opCounter1, opCounter2, ..., opCounterN, + * IP2, opCounter1, opCounter2, ..., opCounterN, IP3, ... + */ +bool NodeOpStats::mapToUInt64Vec(uint64_t cookieIP, size_t bufLen, bool wantPerUserStats, + UInt64Vector *outVec) +{ + SafeRWLock safeLock(&lock, SafeRWLock_READ); // L O C K + + NodeOpCounterMap* counterMap = wantPerUserStats ? &userCounterMap : &clientCounterMap; + NodeOpCounterMapIter mapIter; + + // position iter right after given cookieIP (if any) + + if (cookieIP == ~0ULL) + { + // NOTE: This is a bit tricky. For some reasons we also have to deal with cookieIP = 0 + // and as it is an unsigned, we also cannot initialize with '-1'. Therefore fhgfs-ctl + // sends ~0 to notifify us that no cookie is set. + mapIter = counterMap->begin(); + } + else + mapIter = counterMap->upper_bound(cookieIP); + + if (mapIter == counterMap->end() ) + { // reached end of map => nothing to return in outVec + safeLock.unlock(); // U N L O C K + return true; + } + + // max number of IPs and their counters that fit into the vector + unsigned maxNumIPs = getMaxIPsPerVector(&mapIter->second, bufLen); + + // make the vector buffer sufficiently large + size_t numReserveIPs = BEEGFS_MIN(maxNumIPs, counterMap->size() ); + reserveVector(&mapIter->second, numReserveIPs, outVec); + + // pre-allocate the header meta-elements in the vector with zeros + for (int i=0; i < NODE_OPS_POS_FIRSTDATAELEMENT; i++) + outVec->push_back(0); + + // below we access the fields in the vector directly with vector->at(enum-POS) to allow to simply + // update the enum, without the need to rewrite all the code or to require to work in dedicated + // (error-prone) order + + // VERY FIRST ELEMENT IN THE VECTOR ARE THE NUMBER OF OPs + outVec->at(NODEOPS_POS_NUMOPS) = mapIter->second.getNumCounter(); + + outVec->at(NODE_OPS_POS_MORE_DATA) = 0; // quasi-boolean ("0" means all stats fit into vector) + + outVec->at(NODE_OPS_POS_LAYOUT_VERSION) = OPCOUNTER_VEC_LAYOUT_VERS; + + // iterate over all IPs in the map and add IP and op-counters to the vector + unsigned numIPs = 0; // number of IPs we have stored in the vector + while ( (mapIter != counterMap->end() ) && (numIPs < maxNumIPs) ) + { + // NOTE: we use push_back here, so no absolute enum based positions. Therefore + // the layout of this vector component should not change! + + // add IP itself + outVec->push_back(mapIter->first) ; + + // push_back counters belonging to that IP + mapIter->second.addCountersToVec(outVec); + + mapIter++; + numIPs++; + } + + if (mapIter != counterMap->end() ) + outVec->at(NODE_OPS_POS_MORE_DATA) = 1; + + safeLock.unlock(); // L O C K + + return true; +} + +/** + * We define a maximum transfer size for OpCounter requests of fhgfs-ctl. Several requests + * might need to be issued from fhgfs-ctl to get all OpCounters for all clients. + * This function returns the maximum number of IPs (including their OpCounter values) + * fitting into a single request (i.e. the given bufLen). + */ +int NodeOpStats::getMaxIPsPerVector(OpCounter *opCounter, size_t bufLen) +{ + // vector layout is: headerElem1..n, IP1, counterIP1_1..n, IP2, counterIP2_1..n, IP3, ... + + + // maximum number of uint64_t elements fitting into the vector + int numElems = bufLen / sizeof(uint64_t); + + // available for per-IP vectors without the space for header elements + int numAvailableIPElems = numElems - STATS_VEC_RESERVED_ELEMENTS; + + // number of opcounters of the given opCounter object for a single client IP vector + int numOpCounters = opCounter->getNumCounter(); + + int maxNumIPs = numAvailableIPElems / (numOpCounters + OPCOUNTERVECTOR_POS_FIRSTCOUNTER); + + + return maxNumIPs; +} + +/** + * Pre-alloc internal vector buffers for given number of IPs + */ +bool NodeOpStats::reserveVector(OpCounter *opCounter, size_t numIPs, UInt64Vector *outVec) +{ + int numOpCounters = opCounter->getNumCounter(); + int perIPElems = numOpCounters + OPCOUNTERVECTOR_POS_FIRSTCOUNTER; + + int totalNumElems = STATS_VEC_RESERVED_ELEMENTS + (perIPElems * numIPs); + + outVec->reserve(totalNumElems); + + return true; +} diff --git a/common/source/common/nodes/NodeOpStats.h b/common/source/common/nodes/NodeOpStats.h new file mode 100644 index 0000000..5d139b6 --- /dev/null +++ b/common/source/common/nodes/NodeOpStats.h @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include + +// layout version ofthe vector transfered to fhgfs-ctl +#define OPCOUNTER_VEC_LAYOUT_VERS 1 // only update, if the layout changes incompatibly! + +/* Element positions within the op-counter vector (one IP and its op-counter) + NOTE: Try to avoid to change this part of the vector, as it will introduce incompatibilities! */ +enum PerIPVectorPos +{ + OPCOUNTERVECTOR_POS_IP = 0, // The IP address, always MUST be the first element + OPCOUNTERVECTOR_POS_SUM = 1, // sum of all op counters +}; + +#define OPCOUNTERVECTOR_POS_FIRSTCOUNTER OPCOUNTERVECTOR_POS_SUM // sum is also the first counter + +/* Just a safety buffer so that we definitely cannot exceed the max buffer length due to + serialization overhead (this overhead is difficult to compute from here) */ +#define NODEOP_STATS_NET_MESSAGE_SAFETY_LENGTH 1024 * 10 + +/* + * Element positions in the ClientStats vector (several IPs and their op-counter in this vector) + */ +enum StatsMetaVecPos +{ + NODEOPS_POS_NUMOPS = 0, // number of operations per IP + + // 0 if all IPs and their Ops could be transferred in a single vector, 1 if there are more data + NODE_OPS_POS_MORE_DATA, + + NODE_OPS_POS_LAYOUT_VERSION, // layout version of the vector to detect incompatibilieties + + NODE_OPS_POS_NUM_RESERVED1, // reserved for later usage + NODE_OPS_POS_NUM_RESERVED2, // reserved for later usage + NODE_OPS_POS_NUM_RESERVED3, // reserved for later usage + NODE_OPS_POS_NUM_RESERVED4, // reserved for later usage + NODE_OPS_POS_NUM_RESERVED5, // reserved for later usage + + NODE_OPS_POS_FIRSTDATAELEMENT // address of the data element including the IP +}; + +/* Elements of the vector to be taken by other (meta) fields. + We can use the enum value here, as the enum start with zero */ +#define STATS_VEC_RESERVED_ELEMENTS \ + (NODE_OPS_POS_FIRSTDATAELEMENT + OPCOUNTERVECTOR_POS_FIRSTCOUNTER) + + +typedef std::map NodeOpCounterMap; // key: nodeIP/userID, val: op counters +typedef NodeOpCounterMap::value_type NodeOpCounterMapVal; +typedef NodeOpCounterMap::iterator NodeOpCounterMapIter; + +/** + * Contains operation counters for per-client and per-user statistics. + * + * This is the common basis of "MetaNodeOpStats" and "StorageNodeOpStats", which provide the method + * updateNodeOp() to update corresponding metadata and storage operation counters. + */ +class NodeOpStats +{ + public: + bool mapToUInt64Vec(uint64_t cookieIP, size_t bufLen, bool wantPerUserStats, + UInt64Vector *outVec); + + private: + int getMaxIPsPerVector(OpCounter *opCounter, size_t bufLen); + bool reserveVector(OpCounter *opCounter, size_t numIPs, UInt64Vector *outVec); + + protected: + NodeOpCounterMap clientCounterMap; // maps IPs to corresponding operation counters + NodeOpCounterMap userCounterMap; // maps userIDs to corresponding operation counters + + RWLock lock; + + public: + + /** + * Erase the given node from the map + * + * @param IP of a node + */ + void removeClientFromMap(unsigned nodeIP) + { + SafeRWLock safeLock(&lock, SafeRWLock_WRITE); + clientCounterMap.erase(nodeIP); // erase by key + safeLock.unlock(); + } + +}; + + diff --git a/common/source/common/nodes/NodeStore.h b/common/source/common/nodes/NodeStore.h new file mode 100644 index 0000000..6a123aa --- /dev/null +++ b/common/source/common/nodes/NodeStore.h @@ -0,0 +1,18 @@ +#pragma once + +/** + * New code should not use type NodeStore and this header file! + * + * This file and typedef exists only to help ease migration from the previous single NodeStore class + * to the splitted NodeStoreClients and NodeStoreServers class. + */ + + +#include "NodeStoreServers.h" + + +typedef class NodeStoreServers NodeStore; + + + + diff --git a/common/source/common/nodes/NodeStoreClients.cpp b/common/source/common/nodes/NodeStoreClients.cpp new file mode 100644 index 0000000..32a69c2 --- /dev/null +++ b/common/source/common/nodes/NodeStoreClients.cpp @@ -0,0 +1,255 @@ +#include +#include "NodeStoreClients.h" + +#include +#include + +/** + * Note: Does not initialize the localNode data (the localNode can be set later) + * + * @param channelsDirectDefault false to make all channels indirect by default (only metadata + * server should set this to true, all others to false) + */ +NodeStoreClients::NodeStoreClients(): + AbstractNodeStore(NODETYPE_Client) +{ +} + +/** + * Just forwards to the other addOrUpdateNodeEx method (to provide compatibility with the + * corresponding virtual AbstractNodeStore::addOrUpdateNode() method). + */ +NodeStoreResult NodeStoreClients::addOrUpdateNode(NodeHandle node) +{ + return addOrUpdateNodeEx(std::move(node), NULL); +} + +/** + * @param node belongs to the store after calling this method; this method will set (*node=NULL); + * so do not free it and don't use it any more afterwards (reference it from this store if you need + * it) + * @return true if the node was not in the active group yet, false otherwise + */ +NodeStoreResult NodeStoreClients::addOrUpdateNodeEx(NodeHandle node, NumNodeID* outNodeNumID) +{ + NumNodeID nodeNumID = node->getNumID(); + std::string nodeID = node->getAlias(); + + const std::lock_guard lock(mutex); + + // check if numID is given. if not, we must generate an id - only mgmt may do that, which it + // should do in a subclass. + if (!nodeNumID) + { + auto newID = generateID(*node); + if (!newID) + return NodeStoreResult::Error; + + nodeNumID = newID; + node->setNumID(nodeNumID); + } + + // is node in any of the stores already? + + const auto activeIter = activeNodes.find(nodeNumID); + NodeStoreResult result = NodeStoreResult::Unchanged; + if (activeIter != activeNodes.end()) + { // node was in the store already => update it + Node& active = *activeIter->second; + NicAddressList nicList(node->getNicList()); + + if (unlikely(active.getAlias() != nodeID)) + { // bad: numeric ID collision for two different node string IDs + LogContext(__func__).logErr( + std::string("Numeric ID collision for two different string IDs: ") + nodeID + " / " + + active.getAlias()); + + nodeNumID = NumNodeID(); // set to invalid ID, so caller hopefully checks outNodeNumID + } + else + { + active.updateLastHeartbeatT(); + if (active.updateInterfaces(node->getPortUDP(), node->getPortTCP(), nicList)) + result = NodeStoreResult::Updated; + } + + SAFE_ASSIGN(outNodeNumID, nodeNumID); + } + else + { // node not in any store yet + node->getConnPool()->setChannelDirect(false); + + activeNodes.insert({nodeNumID, std::move(node)}); + + newNodeCond.broadcast(); + + SAFE_ASSIGN(outNodeNumID, nodeNumID); + + result = NodeStoreResult::Added; + } + + return result; +} + + +/** + * @return NULL if no such node exists + */ +NodeHandle NodeStoreClients::referenceNode(NumNodeID numNodeID) const +{ + std::lock_guard lock(mutex); + + auto iter = activeNodes.find(numNodeID); + if (iter != activeNodes.end()) + return iter->second; + + return {}; +} + +/** + * @return can be NULL + */ +NodeHandle NodeStoreClients::referenceFirstNode() const +{ + std::lock_guard lock(mutex); + + auto iter = activeNodes.begin(); + if (iter != activeNodes.end()) + return iter->second; + + return {}; +} + +/** + * @return true if node was active before deletion + */ +bool NodeStoreClients::deleteNode(NumNodeID nodeNumID) +{ + std::lock_guard lock(mutex); + + auto erased = activeNodes.erase(nodeNumID); + return erased > 0; +} + +std::vector NodeStoreClients::referenceAllNodes() const +{ + std::lock_guard lock(mutex); + + std::vector result; + + result.reserve(activeNodes.size()); + + for (auto iter = activeNodes.begin(); iter != activeNodes.end(); iter++) + result.push_back(iter->second); + + return result; +} + +bool NodeStoreClients::isNodeActive(NumNodeID nodeNumID) const +{ + std::lock_guard lock(mutex); + + return activeNodes.find(nodeNumID) != activeNodes.end(); +} + +/** + * Get number of (active) nodes in store. + * + * note: returned size includes the localnode + */ +size_t NodeStoreClients::getSize() const +{ + std::lock_guard lock(mutex); + return activeNodes.size(); +} + +/** + * @param masterList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @param appLocalNode (just what you get from app->getLocalNode() ) + */ +void NodeStoreClients::syncNodes(const std::vector& masterList, + NumNodeIDList* outAddedIDs, NumNodeIDList* outRemovedIDs) +{ + // Note: We have two phases here: + // Phase 1 (locked): Identify added/removed nodes. + // Phase 2 (unlocked): Add/remove nodes from store. + // This separation is required to not break compatibility with virtual overwritten add/remove + // methods in derived classes (e.g. fhgfs_mgmtd). + + + // P H A S E 1 (Identify added/removed nodes.) + + std::string localNodeID; + std::vector addLaterNodes; // nodes to be added in phase 2 + + std::unique_lock lock(mutex); + + auto activeIter = activeNodes.begin(); + auto masterIter = masterList.begin(); + + while (activeIter != activeNodes.end() && masterIter != masterList.end()) + { + NumNodeID currentActive = activeIter->first; + NumNodeID currentMaster = (*masterIter)->getNumID(); + + if(currentMaster < currentActive) + { // currentMaster is added + outAddedIDs->push_back(currentMaster); + addLaterNodes.push_back(*masterIter); + + masterIter++; + } + else + if(currentActive < currentMaster) + { // currentActive is removed + outRemovedIDs->push_back(currentActive); + + activeIter++; + } + else + { // node unchanged + addLaterNodes.push_back(*masterIter); + + masterIter++; + activeIter++; + } + } + + // remaining masterList nodes are added + while (masterIter != masterList.end()) + { + outAddedIDs->push_back( (*masterIter)->getNumID() ); + addLaterNodes.push_back(*masterIter); + + masterIter++; + } + + // remaining active nodes are removed + for ( ; activeIter != activeNodes.end(); activeIter++ ) + outRemovedIDs->push_back(activeIter->first); + + lock.unlock(); + + + // P H A S E 2 (Add/remove nodes from store.) + + // remove nodes + NumNodeIDListIter iter = outRemovedIDs->begin(); + while(iter != outRemovedIDs->end() ) + { + NumNodeID nodeNumID = *iter; + iter++; // (removal invalidates iter) + + deleteNode(nodeNumID); + } + + + // add nodes + for (auto iter = addLaterNodes.begin(); iter != addLaterNodes.end(); iter++) + { + auto& node = *iter; + + addOrUpdateNode(node); + } +} diff --git a/common/source/common/nodes/NodeStoreClients.h b/common/source/common/nodes/NodeStoreClients.h new file mode 100644 index 0000000..9f1d5b3 --- /dev/null +++ b/common/source/common/nodes/NodeStoreClients.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include "AbstractNodeStore.h" + +class NodeStoreClients : public AbstractNodeStore +{ + public: + NodeStoreClients(); + + virtual NodeStoreResult addOrUpdateNode(NodeHandle node) override; + virtual NodeStoreResult addOrUpdateNodeEx(NodeHandle node, NumNodeID* outNodeNumID) override; + virtual bool deleteNode(NumNodeID nodeNumID); + + NodeHandle referenceNode(NumNodeID numNodeID) const; + + bool isNodeActive(NumNodeID nodeNumID) const; + virtual size_t getSize() const override; + + virtual NodeHandle referenceFirstNode() const override; + + virtual std::vector referenceAllNodes() const override; + + void syncNodes(const std::vector& masterList, NumNodeIDList* outAddedIDs, + NumNodeIDList* outRemovedIDs); + + + protected: + mutable Mutex mutex; + Condition newNodeCond; // set when a new node is added to the store (or undeleted) + + NodeMap activeNodes; + + virtual NumNodeID generateID(Node& node) + { + return {}; + } +}; + diff --git a/common/source/common/nodes/NodeStoreServers.cpp b/common/source/common/nodes/NodeStoreServers.cpp new file mode 100644 index 0000000..fc61e1b --- /dev/null +++ b/common/source/common/nodes/NodeStoreServers.cpp @@ -0,0 +1,529 @@ +#include +#include +#include "NodeStoreServers.h" + +#include +#include + +#include +#include + + +/** + * Note: Does not initialize the localNode data (the localNode can be set later) + * + * @param storeType will be applied to contained nodes on addOrUpdate() + * @param channelsDirectDefault false to make all channels indirect by default (only metadata + * server should set this to true, all others to false) + */ +NodeStoreServers::NodeStoreServers(NodeType storeType, bool channelsDirectDefault) + : AbstractNodeStore(storeType) +{ + this->channelsDirectDefault = channelsDirectDefault; + this->capacityPools = NULL; + this->targetMapper = NULL; + this->stateStore = NULL; +} + +/** + * Just forwards to the other addOrUpdateNodeEx method (to provide compatibility with the + * corresponding virtual AbstractNodeStore::addOrUpdateNode() method). + */ +NodeStoreResult NodeStoreServers::addOrUpdateNode(NodeHandle node) +{ + return addOrUpdateNodeEx(std::move(node), NULL); +} + +/** + * Locking wrapper for addOrUpdateNodeUnlocked(). + * See addOrUpdateNodeUnlocked() for description. + */ +NodeStoreResult NodeStoreServers::addOrUpdateNodeEx(NodeHandle node, NumNodeID* outNodeNumID) +{ + std::lock_guard lock(mutex); + return addOrUpdateNodeUnlocked(std::move(node), outNodeNumID); +} + +/** + * Note: unlocked, so caller must hold lock. + * + * @param node belongs to the store after calling this method; this method will set (*node=NULL); + * so do not free it and don't use it any more afterwards (reference it from this store if you need + * it) + * @param outNodeNumID will be set to the the node's numeric ID if no error occurred, 0 otherwise + * (may be NULL if caller doesn't care). + * @return true if the node was not in the active group yet and has been added to the active + * group (i.e. new node), false otherwise + */ +NodeStoreResult NodeStoreServers::addOrUpdateNodeUnlocked(NodeHandle node, NumNodeID* outNodeNumID) +{ + NumNodeID nodeNumID = node->getNumID(); + std::string nodeID(node->getAlias()); + + // Starting in b8.0 aliases (formerly string IDs) can be updated. This is currently the one + // update we allow to the local node. Aliases can be updated by the InternodeSyncer or from + // heartbeats received by the mgmtd when node changes are broadcast. Heartbeats from other nodes + // besides the mgmtd would also trigger an update, but we should never receive a heartbeat from a + // non-mgmtd node for the local node (also see the note below regarding remote node updates). + if (localNode && (nodeNumID == localNode->getNumID())) + { + if (!nodeID.empty() && nodeID != localNode->getAlias()) { + LogContext(__func__).log(3, + std::string("Updating alias for local node: ") + + localNode->getAlias() + " -> " + nodeID); + localNode->setAlias(nodeID); + } + SAFE_ASSIGN(outNodeNumID, nodeNumID); + // All other updates to the local node are not allowed as this could lead to problems during sync, etc. + return NodeStoreResult::Updated; + } + + // check if numID is given. if not, we must generate an id - only mgmt may do that, which it + // should do in a subclass. + if (!nodeNumID) + { + auto newID = generateID(*node); + if (!newID) + { + SAFE_ASSIGN(outNodeNumID, NumNodeID()); + LOG(GENERAL, ERR, "Should never happen: Rejecting nodeNumID==0.", + node->getNodeIDWithTypeStr()); + return NodeStoreResult::Error; + } + + nodeNumID = newID; + node->setNumID(nodeNumID); + } + + + // check if this is a new node or an update of an existing node + + /* note: the activeNodes.find(nodeNumID) below needs to be done even if node->getNumID() was + initially 0, because generateNodeNumID() might have found that the node already had a numID + and just didn't know about it (e.g. when it sent a registration retry message). */ + + NodeStoreResult result = NodeStoreResult::Unchanged; + + auto iter = activeNodes.find(nodeNumID); + + if (iter != activeNodes.end()) + { // node was in the active store already => update it + Node& active = *iter->second; + NicAddressList nicList(node->getNicList()); + + // Previously "numeric ID collision for two different node string IDs" was logged when string + // IDs changed. Starting in BeeGFS 8 string IDs are considered aliases and can be updated as + // needed by the mgmtd. Aliases can be updated by the InternodeSyncer or from heartbeats + // received by the mgmtd (on behalf of other nodes when it broadcasts node updates), or from + // heartbeats received directly from other nodes. Because aliases can be updated via + // heartbeats it is possible the mgmtd could update an alias for a remote node, this node + // receives that update then receives a heartbeat from the remote node with the old alias + // (before it updated its own alias from mgmtd) causing the remote node's alias to be reset on + // this node to the old value. In practice that has never been observed and is highly unlikely + // to happen unless an alias update happens at the same time something triggers a heartbeat + // from a remote node. If it did happen it would eventually be corrected by the + // InternodeSyncer and outdated aliases are not a major concern. This possibility is mentioned + // should future configuration updates be allowed via this path where it would cause an issue. + if (!nodeID.empty() && active.getAlias() != node->getAlias()) + { + LogContext(__func__).log(3, + std::string("Updating alias for node: ") + + active.getAlias() + " -> " + nodeID); + active.setAlias(nodeID); + } + active.updateLastHeartbeatT(); + if (active.updateInterfaces(node->getPortUDP(), node->getPortTCP(), nicList)) + { + result = NodeStoreResult::Updated; + } + SAFE_ASSIGN(outNodeNumID, nodeNumID); + } + else + { // node wasn't in active store yet + // New nodes should always have the alias set unless there is a bug somewhere else, probably + // mgmtd as nodes are usually added via a heartbeat or the internode syncer. If if the alias + // is empty don't add the node otherwise this could lead to more confusing issues to + // troubleshoot later on. + if (node->getAlias().empty()) { + LogContext(__func__).log(1, std::string("Refusing to add node with an empty alias: ") + node->getNodeIDWithTypeStr()); + SAFE_ASSIGN(outNodeNumID, NumNodeID()); + return NodeStoreResult::Error; + } + // make sure we don't have this alias with a different numID already + NumNodeID existingNumID = retrieveNumIDFromStringID(node->getAlias()); + if(existingNumID && existingNumID != nodeNumID) + { // reject node (alias was already associated with a different numID) + SAFE_ASSIGN(outNodeNumID, existingNumID); + } + else + { // no conflicts detected => insert new node + + // check whether node type and store type differ + if (node->getNodeType() != NODETYPE_Invalid && node->getNodeType() != storeType) + { + LogContext(__func__).logErr("Node type and store type differ. " + "Node: " + node->getNodeIDWithTypeStr() + "; " + "Store: " + boost::lexical_cast(storeType)); + throw std::runtime_error("inserted wrong node type into store"); + } + + node->getConnPool()->setChannelDirect(channelsDirectDefault); + + activeNodes.insert({nodeNumID, std::move(node)}); + + newNodeCond.broadcast(); + + SAFE_ASSIGN(outNodeNumID, nodeNumID); + + result = NodeStoreResult::Added; + } + } + + return result; +} + +/** + * @return NULL if no such node exists + */ +NodeHandle NodeStoreServers::referenceNode(NumNodeID id) const +{ + // check for invalid id 0 + #ifdef BEEGFS_DEBUG + if(!id) + LogContext(__func__).log(Log_CRITICAL, "BUG?: Attempt to reference numeric node ID '0'"); + #endif // BEEGFS_DEBUG + + std::lock_guard lock(mutex); + + auto iter = activeNodes.find(id); + if (iter != activeNodes.end()) + return iter->second; + + return {}; +} + +/** + * @return can be NULL if localNode is not included + */ +NodeHandle NodeStoreServers::referenceFirstNode() const +{ + std::lock_guard lock(mutex); + + if (!activeNodes.empty()) + return activeNodes.cbegin()->second; + + return {}; +} + +/** + * This is a helper to have only one call for the typical targetMapper->getNodeID() and following + * referenceNode() calls. + * + * @param targetMapper where to resolve the given targetID + * @param outErr will be set to FhgfsOpsErr_UNKNOWNNODE, _UNKNOWNTARGET, _SUCCESS (may be NULL) + * @return NULL if targetID is not mapped or if the mapped node does not exist in the store. + */ +NodeHandle NodeStoreServers::referenceNodeByTargetID(uint16_t targetID, TargetMapper* targetMapper, + FhgfsOpsErr* outErr) const +{ + NumNodeID nodeID = targetMapper->getNodeID(targetID); + if(unlikely(!nodeID) ) + { // no such target mapping exists + SAFE_ASSIGN(outErr, FhgfsOpsErr_UNKNOWNTARGET); + return {}; + } + + auto node = referenceNode(nodeID); + if (unlikely(!node)) + { // no node with this id exists + SAFE_ASSIGN(outErr, FhgfsOpsErr_UNKNOWNNODE); + return {}; + } + + SAFE_ASSIGN(outErr, FhgfsOpsErr_SUCCESS); + return node; +} + +/** + * @return true if node was active before deletion + */ +bool NodeStoreServers::deleteNode(NumNodeID id) +{ + std::lock_guard lock(mutex); + + if (localNode && localNode->getNumID() == id) + return false; + + auto erased = activeNodes.erase(id); + if (erased > 0) + { + // forward removal to (optionally) attached objects + + if(capacityPools) + capacityPools->remove(id.val()); // remove node from capacity pools + + if(targetMapper) + targetMapper->unmapByNodeID(id); // remove node from target mapper + + if(stateStore) + stateStore->removeTarget(id.val()); // remove node from target states + + return true; + } + + return false; +} + + +std::vector NodeStoreServers::referenceAllNodes() const +{ + std::lock_guard lock(mutex); + + std::vector result; + + result.reserve(activeNodes.size()); + + for (auto iter = activeNodes.begin(); iter != activeNodes.end(); iter++) + result.push_back(iter->second); + + return result; +} + +/** + * Check whether a node exists in the (active) store. + * + * @return true if the node exists + */ +bool NodeStoreServers::isNodeActive(NumNodeID id) const +{ + std::lock_guard lock(mutex); + + return activeNodes.find(id) != activeNodes.end(); +} + +/** + * Get number of (active) nodes in store. + * + * note: returned size includes the localnode + */ +size_t NodeStoreServers::getSize() const +{ + std::lock_guard lock(mutex); + return activeNodes.size(); +} + +/** + * Waits for the first node that is added to the store. + * + * @return true if a new node was added to the store before the timeout expired + */ +bool NodeStoreServers::waitForFirstNode(int waitTimeoutMS) const +{ + int elapsedMS; + + std::lock_guard lock(mutex); + + Time startT; + elapsedMS = 0; + + while (activeNodes.empty() && (elapsedMS < waitTimeoutMS)) + { + newNodeCond.timedwait(&mutex, waitTimeoutMS - elapsedMS); + + elapsedMS = startT.elapsedMS(); + } + + return !activeNodes.empty(); +} + +/** + * @param masterList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @param appLocalNode just what you get from app->getLocalNode(), to determine NIC capabilities + */ +void NodeStoreServers::syncNodes(const std::vector& masterList, + NumNodeIDList* outAddedNumIDs, NumNodeIDList* outRemovedNumIDs, Node* appLocalNode) +{ + // Note: We have two phases here: + // Phase 1 (locked): Identify added/removed nodes. + // Phase 2 (unlocked): Add/remove nodes from store. + // This separation is required to not break compatibility with virtual overwritten add/remove + // methods in derived classes (e.g. fhgfs_mgmtd). + + + // P H A S E 1 (Identify added/removed nodes.) + + std::unique_lock lock(mutex); + + const NumNodeID localNodeNumID(localNode ? localNode->getNumID() : NumNodeID(0)); + std::vector addLaterNodes; // nodes to be added in phase 2 + + auto activeIter = activeNodes.begin(); + auto masterIter = masterList.begin(); + + while (activeIter != activeNodes.end() && masterIter != masterList.end()) + { + NumNodeID currentActive = activeIter->first; + NumNodeID currentMaster = (*masterIter)->getNumID(); + + if(currentMaster < currentActive) + { // currentMaster is added + outAddedNumIDs->push_back(currentMaster); + addLaterNodes.push_back(*masterIter); + + masterIter++; + } + else + if(currentActive < currentMaster) + { // currentActive is removed (but don't ever remove localNode) + if(likely(localNodeNumID != currentActive) ) + outRemovedNumIDs->push_back(currentActive); + + activeIter++; + } + else + { // node unchanged + addLaterNodes.push_back(*masterIter); + + masterIter++; + activeIter++; + } + } + + // remaining masterList nodes are added + while (masterIter != masterList.end()) + { + outAddedNumIDs->push_back( (*masterIter)->getNumID() ); + addLaterNodes.push_back(*masterIter); + masterIter++; + } + + // remaining active nodes are removed + for( ; activeIter != activeNodes.end(); activeIter++) + { + if(likely(localNodeNumID != activeIter->first) ) // (don't ever remove localNode) + outRemovedNumIDs->push_back(activeIter->first); + } + + + lock.unlock(); + + + // P H A S E 2 (Add/remove nodes from store.) + + // remove nodes + NumNodeIDListIter iter = outRemovedNumIDs->begin(); + while(iter != outRemovedNumIDs->end() ) + { + NumNodeID nodeID = *iter; + iter++; // (removal invalidates iter) + + deleteNode(nodeID); + } + + + // set supported nic capabilities for added nodes + NicListCapabilities localNicCaps; + NicAddressList localNicList; + + if(appLocalNode) + { + localNicList = appLocalNode->getNicList(); + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + } + + // add nodes + for (auto iter = addLaterNodes.begin(); iter != addLaterNodes.end(); iter++) + { + auto& node = *iter; + + if(appLocalNode) + node->getConnPool()->setLocalNicList(localNicList, localNicCaps); + + addOrUpdateNodeEx(node, NULL); + } +} + +/** + * Nodes will automatically be added/removed from attached capacity pools when they are + * added/removed from this store. + */ +void NodeStoreServers::attachCapacityPools(NodeCapacityPools* capacityPools) +{ + std::lock_guard lock(mutex); + this->capacityPools = capacityPools; +} + +/** + * Nodes will automatically be removed from attached target mapper when they are + * removed from this store. + */ +void NodeStoreServers::attachTargetMapper(TargetMapper* targetMapper) +{ + std::lock_guard lock(mutex); + this->targetMapper = targetMapper; +} + +/** + * Nodes will automatically be removed from attached state store when they are + * removed from this store. + */ +void NodeStoreServers::attachStateStore(TargetStateStore* stateStore) +{ + std::lock_guard lock(mutex); + this->stateStore = stateStore; +} + +/** + * Search activeNodes for a node with the given string ID and return it's associated numeric ID. + * + * Note: Caller must hold lock. + * + * @return 0 if no node with the given stringID was found, associated numeric ID otherwise. + */ +NumNodeID NodeStoreServers::retrieveNumIDFromStringID(std::string nodeID) const +{ + for (auto iter = activeNodes.begin(); iter != activeNodes.end(); iter++) + { + Node& currentNode = *iter->second; + + if (currentNode.getAlias() == nodeID) + return currentNode.getNumID(); + } + + return NumNodeID(); +} + +/** + * Get complete node IDs and type for log messages from numerical ID. + * + * Note: This method is expensive, so use this function only in performance uncritical code paths. + * If you already have a Node object, call node->getNodeIDWithTypeStr() directly. + */ +std::string NodeStoreServers::getNodeIDWithTypeStr(NumNodeID numID) const +{ + std::lock_guard lock(mutex); + + auto iter = activeNodes.find(numID); + if (iter != activeNodes.end()) + return iter->second->getNodeIDWithTypeStr(); + + return str(boost::format("Unknown node: %1% [ID: %2%]") % storeType % numID); +} + +/** + * Get complete node IDs for log messages from numerical ID. + * + * Note: This method is expensive, so use this function only in performance uncritical code paths. + * If you already have a Node object, call node->getTypedNodeID() directly. + */ +std::string NodeStoreServers::getTypedNodeID(NumNodeID numID) const +{ + std::lock_guard lock(mutex); + + auto iter = activeNodes.find(numID); + if (iter != activeNodes.end() ) + return iter->second->getTypedNodeID(); + + return str(boost::format("Unknown node: %1% [ID: %2%]") % storeType % numID); +} diff --git a/common/source/common/nodes/NodeStoreServers.h b/common/source/common/nodes/NodeStoreServers.h new file mode 100644 index 0000000..56134d9 --- /dev/null +++ b/common/source/common/nodes/NodeStoreServers.h @@ -0,0 +1,87 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "AbstractNodeStore.h" + +class NodeStoreServers : public AbstractNodeStore +{ + public: + NodeStoreServers(NodeType storeType, bool channelsDirectDefault); + + virtual NodeStoreResult addOrUpdateNode(NodeHandle node) override; + virtual NodeStoreResult addOrUpdateNodeEx(NodeHandle node, NumNodeID* outNodeNumID) override; + virtual bool deleteNode(NumNodeID id); + + NodeHandle referenceNode(NumNodeID id) const; + NodeHandle referenceNodeByTargetID(uint16_t targetID, TargetMapper* targetMapper, + FhgfsOpsErr* outErr=NULL) const; + + virtual NodeHandle referenceFirstNode() const override; + + virtual std::vector referenceAllNodes() const override; + + bool isNodeActive(NumNodeID id) const; + virtual size_t getSize() const override; + + bool waitForFirstNode(int waitTimeoutMS) const; + + void syncNodes(const std::vector& masterList, NumNodeIDList* outAddedNumIDs, + NumNodeIDList* outRemovedNumIDs, Node* appLocalNode=NULL); + + void attachCapacityPools(NodeCapacityPools* capacityPools); + void attachTargetMapper(TargetMapper* targetMapper); + void attachStateStore(TargetStateStore* stateStore); + + std::string getNodeIDWithTypeStr(NumNodeID numID) const; + std::string getTypedNodeID(NumNodeID numID) const; + + protected: + mutable Mutex mutex; + mutable Condition newNodeCond; // set when a new node is added to the store (or undeleted) + std::shared_ptr localNode; + + bool channelsDirectDefault; // for connpools, false to make all channels indirect by default + + NodeMap activeNodes; // key is numeric node id + + NodeCapacityPools* capacityPools; // optional for auto remove (may be NULL) + TargetMapper* targetMapper; // optional for auto remove (may be NULL) + TargetStateStore* stateStore; // optional for auto remove (may be NULL) + + NodeStoreResult addOrUpdateNodeUnlocked(NodeHandle node, NumNodeID* outNodeNumID); + + virtual NumNodeID generateID(Node& node) const + { + return {}; + } + + NumNodeID retrieveNumIDFromStringID(std::string nodeID) const; + + public: + // getters & setters + + /** + * Note: this is supposed to be called before the multi-threading starts => no locking. + * + * @param localNode node object is owned by the app, not by this store. + */ + void setLocalNode(const std::shared_ptr& localNode) + { + this->localNode = localNode; + + if(capacityPools && localNode) + capacityPools->addIfNotExists(localNode->getNumID().val(), CapacityPool_LOW); + + if (localNode) + activeNodes.insert({localNode->getNumID(), localNode}); + } +}; + diff --git a/common/source/common/nodes/NodeType.h b/common/source/common/nodes/NodeType.h new file mode 100644 index 0000000..6d77d9d --- /dev/null +++ b/common/source/common/nodes/NodeType.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +enum NodeType +{ + NODETYPE_Invalid = 0, + NODETYPE_Meta = 1, + NODETYPE_Storage = 2, + NODETYPE_Client = 3, + NODETYPE_Mgmt = 4, +}; + +inline std::ostream& operator<<(std::ostream& os, NodeType nodeType) +{ + const boost::io::ios_all_saver ifs(os); + + os.flags(std::ios_base::dec); + os.width(0); + + switch (nodeType) + { + case NODETYPE_Invalid: return os << ""; + case NODETYPE_Meta: return os << "beegfs-meta"; + case NODETYPE_Storage: return os << "beegfs-storage"; + case NODETYPE_Client: return os << "beegfs-client"; + case NODETYPE_Mgmt: return os << "beegfs-mgmtd"; + default: + return os << ""; + } +} + diff --git a/common/source/common/nodes/NumNodeID.h b/common/source/common/nodes/NumNodeID.h new file mode 100644 index 0000000..80ecb2d --- /dev/null +++ b/common/source/common/nodes/NumNodeID.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +/* + * represents a numeric node ID + * + * Note: this must always be in sync with client's NumNodeId! + * + */ +enum NumNodeIDTag {}; +typedef NumericID NumNodeID; + +typedef std::list NumNodeIDList; +typedef NumNodeIDList::iterator NumNodeIDListIter; +typedef NumNodeIDList::const_iterator NumNodeIDListCIter; + +typedef std::vector NumNodeIDVector; +typedef NumNodeIDVector::iterator NumNodeIDVectorIter; +typedef NumNodeIDVector::const_iterator NumNodeIDVectorCIter; + diff --git a/common/source/common/nodes/OpCounter.h b/common/source/common/nodes/OpCounter.h new file mode 100644 index 0000000..32e73ca --- /dev/null +++ b/common/source/common/nodes/OpCounter.h @@ -0,0 +1,154 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +#define OPCOUNTER_SUM_ELEM_INDEX 0 + + +typedef std::vector AtomicUInt64Vector; +typedef AtomicUInt64Vector::iterator AtomicUInt64VectorIter; + + +/** + * Class to store filesystem operation counters. + * + * Note: OpCounterLastEnum is NOT supposed to be used at all, it just defines the last number. + * The first vector element is supposed to be OPSUM. + * Valid operation counter numbers are therefore from: 1 ... (OpCounterLastEnum - 1) + * Usually that should be automatically given, if increaseOpCounter() is called using + * enum defined values. + * + * Note: This class always uses the element at position 0(==OPCOUNTER_SUM_INDEX) as special element + * that represents the sum of all individual other operations. + */ +class OpCounter +{ + public: + + + protected: + /** + * @param numOps OpCounterLastEnum, i.e. the number of operation counters including sum + * element. + */ + OpCounter(int numOps) + { + this->numCounter = numOps; + + opCounters.resize(this->numCounter); + } + + + private: + AtomicUInt64Vector opCounters; + int numCounter; // OpCounterLastEnum, so the number of operaration counters + + + public: + + /** + * Increase counter for given opType by one and increase total ops sum. + */ + bool increaseOpCounter(int opType) + { + // Also check for == as OpCounterLastEnum is not an operation + if (unlikely(opType >= this->numCounter)) + { + LogContext log(__func__); + log.logErr("Invalid operation type given: " + StringTk::int64ToStr(opType)); + log.logBacktrace(); + + return false; + } + + #ifdef BEEGFS_DEBUG + if (unlikely(opType == OPCOUNTER_SUM_ELEM_INDEX)) + { + LogContext log(__func__); + log.logErr("opType == sumElement ( " + StringTk::int64ToStr(opType) + ")!"); + log.logBacktrace(); + + return false; + } + #endif // BEEGFS_DEBUG + + opCounters[opType].increase(); + opCounters[OPCOUNTER_SUM_ELEM_INDEX].increase(); + + return true; + } + + /** + * Increase counters of given read/write op by one, increase total ops sum, and increase the + * corresponding op-counter-bytes value by given number of bytes. + * + * @param opType only StorageOpCounter_WRITEOPS and _READOPS allowed as value. + */ + void increaseStorageOpBytes(int opType, uint64_t numBytes) + { + bool incOpRes = increaseOpCounter(opType); + if (unlikely(incOpRes == false)) + return; // something entirely wrong + + if(opType == StorageOpCounter_READOPS) + opCounters[StorageOpCounter_READBYTES].increase(numBytes); + else + if(opType == StorageOpCounter_WRITEOPS) + opCounters[StorageOpCounter_WRITEBYTES].increase(numBytes); + else + { // invalid opType given (should never happen) + LogContext log(__func__); + log.logErr("Invalid operation type given: " + StringTk::int64ToStr(opType) ); + log.logBacktrace(); + + return; + } + } + + /** + * Read current counter value. + */ + uint64_t getOpCounter(int operation) + { + return opCounters[operation].read(); + } + + /** + * Just return how many counters we have + */ + uint64_t getNumCounter() + { + return this->numCounter; + } + + /** + * Add our counters to an existing vector + */ + void addCountersToVec(UInt64Vector *vec) + { + for(AtomicUInt64VectorIter iter = opCounters.begin(); iter != opCounters.end(); iter++) + { + vec->push_back(iter->read() ); + } + } +}; + +class MetaOpCounter : public OpCounter +{ + public: + MetaOpCounter() : OpCounter(MetaOpCounter_OpCounterLastEnum) {} +}; + +class StorageOpCounter : public OpCounter +{ + public: + StorageOpCounter() : OpCounter(StorageOpCounter_OpCounterLastEnum) {} +}; + + diff --git a/common/source/common/nodes/OpCounterTypes.h b/common/source/common/nodes/OpCounterTypes.h new file mode 100644 index 0000000..8982b55 --- /dev/null +++ b/common/source/common/nodes/OpCounterTypes.h @@ -0,0 +1,181 @@ +/* + * OpCounterTypes.h - enum definitions and enum to name mapping + * + */ + +#pragma once + +#include + +/** + * Per-client metadata server operation counters. + */ +enum MetaOpCounterTypes +{ + MetaOpCounter_OPSUM = 0, // sum of all ops, simplifies search of nodes with most operations + + MetaOpCounter_ACK, + MetaOpCounter_CLOSE, + MetaOpCounter_GETENTRYINFO, + MetaOpCounter_FINDOWNER, + MetaOpCounter_MKDIR, + MetaOpCounter_MKFILE, + MetaOpCounter_READDIR, + MetaOpCounter_REFRESHENTRYINFO, + MetaOpCounter_REQUESTMETADATA, + MetaOpCounter_RMDIR, + MetaOpCounter_RMLINK, + MetaOpCounter_MOVINGDIRINSERT, + MetaOpCounter_MOVINGFILEINSERT, + MetaOpCounter_OPEN, + MetaOpCounter_RENAME, + MetaOpCounter_SETCHANNELDIRECT, + MetaOpCounter_SETATTR, + MetaOpCounter_SETDIRPATTERN, + MetaOpCounter_STAT, + MetaOpCounter_STATFS, // StatStoragePath + MetaOpCounter_TRUNCATE, + MetaOpCounter_SYMLINK, + MetaOpCounter_UNLINK, + MetaOpCounter_LOOKUPINTENT_SIMPLE, // LookupIntentMsg without extra flags + MetaOpCounter_LOOKUPINTENT_STAT, // LookupIntentMsg with stat flag + MetaOpCounter_LOOKUPINTENT_REVALIDATE, // LookupIntentMsg with revalidate flag (overrides stat) + MetaOpCounter_LOOKUPINTENT_OPEN, // LookupIntentMsg with open flag (overrides reval and stat) + MetaOpCounter_LOOKUPINTENT_CREATE, // LookupIntentMsg with create flag (overrides other flags) + MetaOpCounter_HARDLINK, + MetaOpCounter_FLOCKAPPEND, + MetaOpCounter_FLOCKENTRY, + MetaOpCounter_FLOCKRANGE, + MetaOpCounter_UPDATEDIRPARENT, + MetaOpCounter_LISTXATTR, + MetaOpCounter_GETXATTR, + MetaOpCounter_REMOVEXATTR, + MetaOpCounter_SETXATTR, + + MetaOpCounter_MIRROR, // any mirrored message. lump them all together + + // For compatibility new fields MUST be added directly above _OpCounterLastEnum + // NOTE: New types must also be added to MetaOpToStringMapping below. + + MetaOpCounter_OpCounterLastEnum // This must be the last element (to get num of valid elems) +}; + +/** + * Per-client storage server operation counters. + */ +enum StorageOpCounterTypes +{ + // sum of all operations, simplifies search of nodes with most operations + StorageOpCounter_OPSUM = 0, + + StorageOpCounter_ACK, + StorageOpCounter_SETCHANNELDIRECT, + + StorageOpCounter_GETLOCALFILESIZE, + StorageOpCounter_SETLOCALATTR, + StorageOpCounter_STATSTORAGEPATH, // STATFS + StorageOpCounter_TRUNCLOCALFILE, + + StorageOpCounter_CLOSELOCAL, + StorageOpCounter_FSYNCLOCAL, + StorageOpCounter_READOPS, + StorageOpCounter_READBYTES, // NOTE: Do NOT use directly for counting, but use READOPS + + StorageOpCounter_WRITEOPS, + StorageOpCounter_WRITEBYTES, // NOTE: Do NOT use directly for counting, but use WRITEOPS + + StorageOpCounter_GENERICDEBUG, + StorageOpCounter_HEARTBEAT, + StorageOpCounter_REMOVENODE, + + StorageOpCounter_REQUESTSTORAGEDATA, + + StorageOpCounter_UNLINK, + + // For compatibility new fields MUST be added directly above _OpCounterLastEnum + // NOTE: New types must also be added to StorageOpToStringMapping below. + + StorageOpCounter_OpCounterLastEnum // must be the last element (to get num of valid elems) +}; + + +/** + * Used by MetaOpToStringMapping, StorageOpToStringMapping & Co to convert OpCounter enum value + * into descriptive strings names. + */ +class OpToStringMapping +{ + public: + static std::string mapMetaOpNum(int opNum) + { + switch (opNum) + { + case MetaOpCounter_OPSUM: return "sum"; + case MetaOpCounter_ACK: return "ack"; + case MetaOpCounter_CLOSE: return "close"; + case MetaOpCounter_GETENTRYINFO: return "entInf"; + case MetaOpCounter_FINDOWNER: return "fndOwn"; + case MetaOpCounter_MKDIR: return "mkdir"; + case MetaOpCounter_MKFILE: return "create"; + case MetaOpCounter_READDIR: return "rddir"; + case MetaOpCounter_REFRESHENTRYINFO: return "refrEnt"; + case MetaOpCounter_REQUESTMETADATA: return "mdsInf"; + case MetaOpCounter_RMDIR: return "rmdir"; + case MetaOpCounter_RMLINK: return "rmLnk"; + case MetaOpCounter_MOVINGDIRINSERT: return "mvDirIns"; + case MetaOpCounter_MOVINGFILEINSERT: return "mvFiIns"; + case MetaOpCounter_OPEN: return "open"; + case MetaOpCounter_RENAME: return "ren"; + case MetaOpCounter_SETCHANNELDIRECT: return "sChDrct"; + case MetaOpCounter_SETATTR: return "sAttr"; + case MetaOpCounter_SETDIRPATTERN: return "sDirPat"; + case MetaOpCounter_STAT: return "stat"; + case MetaOpCounter_STATFS: return "statfs"; + case MetaOpCounter_TRUNCATE: return "trunc"; + case MetaOpCounter_SYMLINK: return "symlnk"; + case MetaOpCounter_UNLINK: return "unlnk"; + case MetaOpCounter_LOOKUPINTENT_SIMPLE: return "lookLI"; + case MetaOpCounter_LOOKUPINTENT_STAT: return "statLI"; + case MetaOpCounter_LOOKUPINTENT_REVALIDATE: return "revalLI"; + case MetaOpCounter_LOOKUPINTENT_OPEN: return "openLI"; + case MetaOpCounter_LOOKUPINTENT_CREATE: return "createLI"; + case MetaOpCounter_HARDLINK: return "hardlnk"; + case MetaOpCounter_FLOCKAPPEND: return "flckAp"; + case MetaOpCounter_FLOCKENTRY: return "flckEn"; + case MetaOpCounter_FLOCKRANGE: return "flckRg"; + case MetaOpCounter_UPDATEDIRPARENT: return "dirparent"; + case MetaOpCounter_LISTXATTR: return "listXA"; + case MetaOpCounter_GETXATTR: return "getXA"; + case MetaOpCounter_REMOVEXATTR: return "rmXA"; + case MetaOpCounter_SETXATTR: return "setXA"; + case MetaOpCounter_MIRROR: return "mirror"; + } + return StringTk::intToStr(opNum); + } + static std::string mapStorageOpNum(int opNum) + { + switch (opNum) + { + case StorageOpCounter_OPSUM: return "sum"; + case StorageOpCounter_ACK: return "ack"; + case StorageOpCounter_SETCHANNELDIRECT: return "sChDrct"; + case StorageOpCounter_GETLOCALFILESIZE: return "getFSize"; + case StorageOpCounter_SETLOCALATTR: return "sAttr"; + case StorageOpCounter_STATSTORAGEPATH: return "statfs"; + case StorageOpCounter_TRUNCLOCALFILE: return "trunc"; + case StorageOpCounter_CLOSELOCAL: return "close"; + case StorageOpCounter_FSYNCLOCAL: return "fsync"; + case StorageOpCounter_READOPS: return "ops-rd"; + case StorageOpCounter_READBYTES: return "B-rd"; + case StorageOpCounter_WRITEOPS: return "ops-wr"; + case StorageOpCounter_WRITEBYTES: return "B-wr"; + case StorageOpCounter_GENERICDEBUG: return "gendbg"; + case StorageOpCounter_HEARTBEAT: return "hrtbeat"; + case StorageOpCounter_REMOVENODE: return "remNode"; + case StorageOpCounter_REQUESTSTORAGEDATA: return "storInf"; + case StorageOpCounter_UNLINK: return "unlnk"; + } + return StringTk::intToStr(opNum); + } +}; + diff --git a/common/source/common/nodes/RootInfo.h b/common/source/common/nodes/RootInfo.h new file mode 100644 index 0000000..054be0e --- /dev/null +++ b/common/source/common/nodes/RootInfo.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +#include + +class RootInfo +{ + public: + RootInfo(): + isMirrored(false) + { + } + + NumNodeID getID() const + { + std::lock_guard const lock(mutex); + return id; + } + + bool getIsMirrored() const + { + std::lock_guard const lock(mutex); + return isMirrored; + } + + void set(NumNodeID id, bool isMirrored) + { + std::lock_guard const lock(mutex); + + this->id = id; + this->isMirrored = isMirrored; + } + + bool setIfDefault(NumNodeID id, bool isMirrored) + { + std::lock_guard const lock(mutex); + + if (this->id) + return false; + + this->id = id; + this->isMirrored = isMirrored; + return true; + } + + private: + mutable std::mutex mutex; + + NumNodeID id; + bool isMirrored; +}; + diff --git a/common/source/common/nodes/StoragePoolStore.cpp b/common/source/common/nodes/StoragePoolStore.cpp new file mode 100644 index 0000000..14bee59 --- /dev/null +++ b/common/source/common/nodes/StoragePoolStore.cpp @@ -0,0 +1,534 @@ +#include "StoragePoolStore.h" + +#include + +const StoragePoolId StoragePoolStore::DEFAULT_POOL_ID = StoragePoolId(1); +const StoragePoolId StoragePoolStore::INVALID_POOL_ID = StoragePoolId(0); +const std::string StoragePoolStore::DEFAULT_POOL_NAME = "Default"; +const StoragePoolId StoragePoolStore::MAX_POOL_ID = std::numeric_limits::max(); + +StoragePoolStore::StoragePoolStore(MirrorBuddyGroupMapper* buddyGroupMapper, + TargetMapper* targetMapper, bool skipDefPoolCreation): + buddyGroupMapper(buddyGroupMapper), targetMapper(targetMapper) +{ + if (!skipDefPoolCreation) + { + // create the default pool, which must exist at all times + createPool(DEFAULT_POOL_ID, DEFAULT_POOL_NAME); + } +} + +/* + * @param poolID the ID of the newly created pool; can be INVALID_POOL_ID, in which case a free ID + * will be chosen + * @param poolDescription a description for the pool + * @param targets a set of targets to add to the new pool; can be (and defaults to) NULL + * (which results in an empty pool) + * @param buddyGroups a set of buddyGroups to add to the new pool; can be (and defaults to) NULL + * @return a pair with the return code as first element (FhgfsOpsErr_NOSPACE if no free ID could + * be found; FhgfsOpsErr_EXISTS if ID was given and a pool with that ID already exists; + * FhgfsOpsErr_INVAL if at least one target could not be moved, because it is not in the + * default pool; FhgfsOpsErr_SUCCESS otherwise) and the ID of the inserted pool as second + * element + */ +std::pair StoragePoolStore::createPool(StoragePoolId poolId, + const std::string& poolDescription, const UInt16Set* targets, const UInt16Set* buddyGroups) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + if (poolId == INVALID_POOL_ID) + { + poolId = findFreeID(); + + LOG(STORAGEPOOLS, DEBUG, "Generated new target pool ID.", poolId); + + // pool ID still zero? -> could not find any free ID + if (poolId == INVALID_POOL_ID) + { + LOG(STORAGEPOOLS, WARNING, "Could not generate storage pool ID. " + "Probably all valid IDs are in use."); + + return std::make_pair(FhgfsOpsErr_NOSPACE, INVALID_POOL_ID); + } + } + + StoragePoolPtr newStoragePool = makePool(poolId, poolDescription); + + std::pair insertRes = + storagePools.insert(std::pair(poolId, newStoragePool)); + + if (insertRes.second) // newly inserted element + { + unsigned failedMoves = 0; + + // handle buddy groups first + if (buddyGroups) + { + // Check if buddy groups are in the default pool and only allow to put them into new pool + // if they are (and delete them from default pool). This way we don't allow a buddy group + // to be in more than one pool + + StoragePoolPtr defaultPool = storagePools[DEFAULT_POOL_ID]; + + for (auto groupIter = buddyGroups->begin(); groupIter != buddyGroups->end(); groupIter++) + { + if (defaultPool->hasBuddyGroup(*groupIter)) + { + moveBuddyGroupUnlocked(defaultPool, insertRes.first->second, *groupIter); + } + else + { + LOG(STORAGEPOOLS, WARNING, "Couldn't move buddy group to newly created storage pool," + " because buddy group ID is not a member of the default pool.", + ("newPoolId", poolId), ("buddyGroupId", *groupIter)); + + ++failedMoves; + } + } + } + + if (targets) + { + // Check if targets are in the default pool and only allow to put them into new pool if + // they are (and delete them from default pool). This way we don't allow a target to be in + // more than one pool + + StoragePoolPtr defaultPool = storagePools[DEFAULT_POOL_ID]; + + for (auto targetIter = targets->begin(); targetIter != targets->end(); targetIter++) + { + if (defaultPool->hasTarget(*targetIter)) + { + moveTargetUnlocked(defaultPool, insertRes.first->second, *targetIter); + } + else + { + LOG(STORAGEPOOLS, WARNING, "Couldn't move target to newly created storage pool, " + "because target ID is not a member of the default pool.", + ("newPoolId", poolId), ("targetId", *targetIter)); + + ++failedMoves; + } + } + } + + if (failedMoves > 0) + return std::make_pair(FhgfsOpsErr_INVAL, poolId); + else + return std::make_pair(FhgfsOpsErr_SUCCESS, poolId); + } + else + { + LOG(STORAGEPOOLS, WARNING, + "Could not insert new storage pool, because a pool with the given ID already exists.", + poolId); + + return std::make_pair(FhgfsOpsErr_EXISTS, INVALID_POOL_ID); + } +} + +/* + * moves a target from one pool to another + * + * @param fromPoolId + * @param toPoolId + * @param targetId + * + * @return FhgfsOpsErr_UNKNOWNPOOL if sourcePoolId or destinationPoolId unknown, + * FhgfsOpsErr_UNKNOWNTARGET if target is not in source pool, FhgfsOpsErr_SUCCESS on successful + * move + */ +FhgfsOpsErr StoragePoolStore::moveTarget(StoragePoolId fromPoolId, StoragePoolId toPoolId, + uint16_t targetId) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + StoragePoolPtr fromPool = getPoolUnlocked(fromPoolId); + StoragePoolPtr toPool = getPoolUnlocked(toPoolId); + + if (!fromPool || !toPool) + return FhgfsOpsErr_UNKNOWNPOOL; + + bool mvRes = moveTargetUnlocked(fromPool, toPool, targetId); + + if (!mvRes) + return FhgfsOpsErr_UNKNOWNTARGET; + + return FhgfsOpsErr_SUCCESS; +} + +bool StoragePoolStore::moveTargetUnlocked(StoragePoolPtr fromPool, StoragePoolPtr toPool, + uint16_t targetId) +{ + const bool rmRes = fromPool->removeTarget(targetId); + + if (!rmRes) + { + LOG(STORAGEPOOLS, WARNING, "Could not move target from one storage pool to another. " + "Target does not exist in old pool.", + ("oldPoolID", fromPool->getId()), ("newPoolID", toPool->getId()), targetId); + + return false; + } + + // get node mapping for target; of course this is not optimal, but considering the fact that + // this doesn't happen frequently it's ok + const NumNodeID nodeId = targetMapper->getNodeID(targetId); + toPool->addTarget(targetId, nodeId); + + return true; +} + +FhgfsOpsErr StoragePoolStore::moveBuddyGroup(StoragePoolId fromPoolId, StoragePoolId toPoolId, + uint16_t buddyGroupId) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + StoragePoolPtr fromPool = getPoolUnlocked(fromPoolId); + StoragePoolPtr toPool = getPoolUnlocked(toPoolId); + + if (!fromPool || !toPool) + return FhgfsOpsErr_UNKNOWNPOOL; + + const bool mvRes = moveBuddyGroupUnlocked(fromPool, toPool, buddyGroupId); + + if (!mvRes) + return FhgfsOpsErr_UNKNOWNTARGET; + + return FhgfsOpsErr_SUCCESS; +} + +/* + * note: when moving buddy groups, we also move around the targets of this buddy group if a + * buddyGroupMapper is set + */ +bool StoragePoolStore::moveBuddyGroupUnlocked(StoragePoolPtr fromPool, StoragePoolPtr toPool, + uint16_t buddyGroupId) +{ + // both pools are locked from the outside here, because we need to prevent races between the + // rm/add of buddy groups and targets + std::lock_guard fromPoolLock(fromPool->mutex); + std::lock_guard toPoolLock(toPool->mutex); + + const bool rmRes = fromPool->removeBuddyGroupUnlocked(buddyGroupId); + + if (!rmRes) + { + LOG(STORAGEPOOLS, WARNING, "Could not move buddy group from one storage pool to another, " + "because buddy group doesn't exist in old pool.", + ("oldPoolID", fromPool->getId()), ("newPoolID", toPool->getId()), buddyGroupId); + + return false; + } + + toPool->addBuddyGroupUnlocked(buddyGroupId); + + if (buddyGroupMapper) + { + const MirrorBuddyGroup mbg = buddyGroupMapper->getMirrorBuddyGroup(buddyGroupId); + const bool rmPrimaryRes = fromPool->removeTargetUnlocked(mbg.firstTargetID); + + if (rmPrimaryRes) + { + // get node mapping for target; of course this is not optimal, but considering the fact + // that this doesn't happen frequently it's ok + const NumNodeID nodeId = targetMapper->getNodeID(mbg.firstTargetID); + toPool->addTargetUnlocked(mbg.firstTargetID, nodeId); + } + + const bool rmSecondaryRes = fromPool->removeTargetUnlocked(mbg.secondTargetID); + + if (rmSecondaryRes) + { + // get node mapping for target; of course this is not optimal, but considering the fact + // that this doesn't happen frequently it's ok + const NumNodeID nodeId = targetMapper->getNodeID(mbg.secondTargetID); + toPool->addTargetUnlocked(mbg.secondTargetID, nodeId); + } + + if (!rmPrimaryRes || !rmSecondaryRes) + { + LOG(STORAGEPOOLS, WARNING, "Could not move targets of buddy group from one storage pool to another, " + "because at least one target doesn't exist in old pool.", + ("oldPoolID", fromPool->getId()), ("newPoolID", toPool->getId()), buddyGroupId, + ("primaryTargetId", mbg.firstTargetID), ("secondaryTargetId", mbg.secondTargetID)); + + return false; + } + } + + return true; +} + +FhgfsOpsErr StoragePoolStore::setPoolDescription(StoragePoolId poolId, + const std::string& newDescription) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + StoragePoolPtr pool = getPoolUnlocked(poolId); + + if (!pool) + return FhgfsOpsErr_UNKNOWNPOOL; + + pool->setDescription(newDescription); + + return FhgfsOpsErr_SUCCESS; +} + +/* + * add a target to the pool store + * + * @param targetId + * @param poolId add to this pool id if target is inserted for the first time; + * @param ignoreExisting if set to true it will not be considered an error if the target is already + * member of a pool; in this case the request is silently ignored + * @return FhgfsOpsErr_SUCCESS if operation was succesful + * FhgfsOpsErr_EXISTS if target is already member of a pool and !ignoreExisting + * FhgfsOpsErr_UNKNOWNPOOL if poolId doesn't belong to a valid pool + */ +FhgfsOpsErr StoragePoolStore::addTarget(uint16_t targetId, NumNodeID nodeId, StoragePoolId poolId, + bool ignoreExisting) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + // first check if this target is in any of the pools already + for (auto poolsIter = storagePools.begin(); poolsIter != storagePools.end(); poolsIter++) + { + if (poolsIter->second->hasTarget(targetId)) + { + if (!ignoreExisting) + { + LOG(STORAGEPOOLS, ERR, "Requested to add target to storage pool store, " + "but target is already member of a storage pool.", + targetId, ("storagePoolId", poolsIter->second->getId())); + + return FhgfsOpsErr_EXISTS; + } + return FhgfsOpsErr_SUCCESS; + } + } + + StoragePoolPtr pool = getPoolUnlocked(poolId); + if (!pool) + { + LOG(STORAGEPOOLS, ERR, "Request to add a target to a storage pool, which doesn't exist. ", + targetId, poolId); + + return FhgfsOpsErr_UNKNOWNPOOL; + } + + pool->addTarget(targetId, nodeId); + + return FhgfsOpsErr_SUCCESS; +} + +/* + * remove a target from the pool store (no matter in which pool it is) + * + * @param targetId + */ +StoragePoolId StoragePoolStore::removeTarget(uint16_t targetId) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + StoragePoolPtr pool = iter->second; + const bool rmRes = pool->removeTarget(targetId); + + if (rmRes) // target found; no need to search more pools + return pool->getId(); + } + + return StoragePoolStore::INVALID_POOL_ID; +} + +/* + * add a buddy group ID to the pool store + + * @param buddyGroupId + * @param poolId add to this pool id if buddyGroupId is inserted for the first time; + * @param ignoreExisting if set to true it will not be considered an error if the buddyGroupId is + * already member of a pool; in this case the request is silently ignored + * @return FhgfsOpsErr_SUCCESS if operation was succesful + * FhgfsOpsErr_EXISTS if buddy group is already member of a pool and !ignoreExisting + * FhgfsOpsErr_UNKNOWNPOOL if poolId doesn't belong to a valid pool + */ +FhgfsOpsErr StoragePoolStore::addBuddyGroup(uint16_t buddyGroupId, StoragePoolId poolId, + bool ignoreExisting) +{ + // storage pools is only accessed in a read only way, but it gets locked in "write-mode". This + // is, because it is technically possible, that two different threads try to add a buddy group + // to two different pools at the same time and both could pass the "buddy group already exists"- + // check and then both add the group. This is the easiest solution to prevent this, although + // the storagePools map gets not modified + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + // first check if this buddy Group is in any of the pools already + for (auto poolsIter = storagePools.begin(); poolsIter != storagePools.end(); poolsIter++) + { + if (poolsIter->second->hasBuddyGroup(buddyGroupId)) + { + if (!ignoreExisting) + { + LOG(STORAGEPOOLS, ERR, "Requested to add buddy group to storage pool store, " + "but buddy group is already member of a storage pool.", buddyGroupId, + ("storagePoolId", poolsIter->second->getId())); + + return FhgfsOpsErr_EXISTS; + } + return FhgfsOpsErr_SUCCESS; + } + } + + StoragePoolPtr pool = getPoolUnlocked(poolId); + if (!pool) + { + LOG(STORAGEPOOLS, ERR, "Request to add a buddy group to a storage pool, which doesn't exist.", + buddyGroupId, poolId); + + return FhgfsOpsErr_UNKNOWNPOOL; + } + + pool->addBuddyGroup(buddyGroupId); + + return FhgfsOpsErr_EXISTS; +} + +/* + * remove a buddy group ID from the pool store (no matter in which pool it is) + * + * intended to be used when a buddy group gets removed from the system + * + * @param buddyGroupId + */ +StoragePoolId StoragePoolStore::removeBuddyGroup(uint16_t buddyGroupId) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + StoragePoolPtr pool = iter->second; + const bool rmRes = pool->removeBuddyGroup(buddyGroupId); + + if (rmRes) // target found; no need to search more pools + return pool->getId(); + } + + return StoragePoolStore::INVALID_POOL_ID; +} + + +/* + * note: function has no locks, so the caller needs to hold a lock + */ +StoragePoolId StoragePoolStore::findFreeID() const +{ + for (StoragePoolId id = StoragePoolId(1); id <= MAX_POOL_ID; id++) + { + StoragePoolPtrMapCIter it = storagePools.find(id); + + if (it == storagePools.end()) + return id; + } + + // no free ID found + return INVALID_POOL_ID; +} + +/* + * @param id the ID of the storage pool to fetch + * + * @return a shared_ptr to the pool, if pool exists. If pool does not exist an empty pointer is + * returned + */ +StoragePoolPtr StoragePoolStore::getPool(StoragePoolId id) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + return getPoolUnlocked(id); +} + +/* + * @param id the ID of the storage pool to fetch + * + * @return a shared_ptr to the pool, if pool exists. If pool does not exist an empty pointer is + * returned + */ +StoragePoolPtr StoragePoolStore::getPoolUnlocked(StoragePoolId id) const +{ + try + { + StoragePoolPtr pool = storagePools.at(id); + + return pool; + } + catch (const std::out_of_range& e) + { + return {}; + } +} + +/* + * @param targetId a targetId to search for + * + * @return a shared_ptr to the pool, which contains the target (if any). If pool does not exist an + * empty pointer is returned + */ +StoragePoolPtr StoragePoolStore::getPool(uint16_t targetId) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for (StoragePoolPtrMapCIter it = storagePools.begin(); it != storagePools.end(); it++) + { + if (it->second->hasTarget(targetId)) + { + return it->second; + } + } + + return {}; // = NULL +} + + +/* + * get a vector with pointers to all pools + */ +StoragePoolPtrVec StoragePoolStore::getPoolsAsVec() const +{ + StoragePoolPtrVec outVec; + + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for (StoragePoolPtrMapCIter it = storagePools.begin(); it != storagePools.end(); it++) + outVec.push_back(it->second); + + return outVec; +} + +bool StoragePoolStore::poolExists(StoragePoolId id) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + auto it = storagePools.find(id); + + return it != storagePools.end(); +} + +void StoragePoolStore::syncFromVector(const StoragePoolPtrVec& storagePoolVec) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + storagePools.clear(); + + for (auto it = storagePoolVec.begin(); it != storagePoolVec.end(); it++) + storagePools[(*it)->getId()] = *it; +} + +size_t StoragePoolStore::getSize() const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + return storagePools.size(); +} + diff --git a/common/source/common/nodes/StoragePoolStore.h b/common/source/common/nodes/StoragePoolStore.h new file mode 100644 index 0000000..f4e6fda --- /dev/null +++ b/common/source/common/nodes/StoragePoolStore.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include + +class MirrorBuddyGroupMapper; +class TargetMapper; + +/* + * holds the storage pools and the assigned targets + */ +class StoragePoolStore +{ + public: + static const StoragePoolId DEFAULT_POOL_ID; + static const StoragePoolId INVALID_POOL_ID; + static const std::string DEFAULT_POOL_NAME; + + StoragePoolStore(MirrorBuddyGroupMapper* buddyGroupMapper, + TargetMapper* targetMapper, bool skipDefPoolCreation = false); + virtual ~StoragePoolStore() { }; + + std::pair createPool(StoragePoolId poolId, + const std::string& poolDescription, const UInt16Set* targets = NULL, + const UInt16Set* buddyGroups = NULL); + + FhgfsOpsErr moveTarget(StoragePoolId fromPoolId, StoragePoolId toPoolId, uint16_t targetId); + FhgfsOpsErr moveBuddyGroup(StoragePoolId fromPoolId, StoragePoolId toPoolId, + uint16_t buddyGroupId); + + FhgfsOpsErr setPoolDescription(StoragePoolId poolId, const std::string& newDescription); + + virtual FhgfsOpsErr addTarget(uint16_t targetId, NumNodeID nodeId, + StoragePoolId poolId = DEFAULT_POOL_ID, bool ignoreExisting = false); + virtual StoragePoolId removeTarget(uint16_t targetId); + virtual FhgfsOpsErr addBuddyGroup(uint16_t buddyGroupId, StoragePoolId poolId, + bool ignoreExisting = false); + virtual StoragePoolId removeBuddyGroup(uint16_t buddyGroupId); + + void syncFromVector(const StoragePoolPtrVec& storagePoolVec); + + StoragePoolPtr getPool(StoragePoolId id) const; + StoragePoolPtr getPool(uint16_t targetId) const; + StoragePoolPtr getPoolUnlocked(StoragePoolId id) const; + StoragePoolPtrVec getPoolsAsVec() const; + bool poolExists(StoragePoolId id) const; + + size_t getSize() const; + + protected: + static const StoragePoolId MAX_POOL_ID; + + mutable RWLock rwlock; // protecting the storage pool map + StoragePoolPtrMap storagePools; + + MirrorBuddyGroupMapper* buddyGroupMapper; + TargetMapper* targetMapper; + + StoragePoolId findFreeID() const; + bool moveTargetUnlocked(StoragePoolPtr fromPool, StoragePoolPtr toPool, uint16_t targetId); + bool moveBuddyGroupUnlocked(StoragePoolPtr fromPool, StoragePoolPtr toPool, + uint16_t buddyGroupId); + + virtual StoragePoolPtr makePool(StoragePoolId id, const std::string& description) const + { + return std::make_shared(id, description); + } + + virtual StoragePoolPtr makePool() const + { + return std::make_shared(); + } + + /* + * note: no locking in here; so make sure, caller holds lock + */ + void serialize(Serializer& ser) const + { + // simply serialize the map's values; keys can be generated again from the target pool IDs + ser % storagePools.size(); + + for (StoragePoolPtrMapCIter it = storagePools.begin(); it != storagePools.end(); it++) + ser % *(it->second); + } + + /* + * note: no locking in here; so make sure, caller holds lock + */ + void deserialize(Deserializer& des) + { + size_t elemCount; + + des % elemCount; + + storagePools.clear(); + + for (size_t i = 0; i < elemCount; i++) + { + StoragePoolPtr elemPtr = makePool(); + // we call initFromDesBuf() here instead of the regular deserializer because this store + // might hold StoragePool or StoragePoolEx elements and initFromDesBuf() is a virtual + // funtion, wrapping around the "right" deserializer + bool desResult = elemPtr->initFromDesBuf(des); + if (!desResult) + break; + + storagePools[elemPtr->getId()] = elemPtr; + } + } +}; + diff --git a/common/source/common/nodes/TargetCapacityPools.cpp b/common/source/common/nodes/TargetCapacityPools.cpp new file mode 100644 index 0000000..a8748c4 --- /dev/null +++ b/common/source/common/nodes/TargetCapacityPools.cpp @@ -0,0 +1,939 @@ +#include +#include +#include +#include "TargetCapacityPools.h" + +/** + * @param lowSpace only used for getPoolTypeFromFreeSpace() + * @param emergencySpace only used for getPoolTypeFromFreeSpace() + */ +TargetCapacityPools::TargetCapacityPools(bool useDynamicPools, + const DynamicPoolLimits& poolLimitsSpace, const DynamicPoolLimits& poolLimitsInodes) : + poolLimitsSpace(poolLimitsSpace), + poolLimitsInodes(poolLimitsInodes), + pools(CapacityPool_END_DONTUSE), groupedTargetPools(CapacityPool_END_DONTUSE) +{ + this->dynamicPoolsEnabled = useDynamicPools; + + this->lastRoundRobinTarget = 0; +} + +/** + * Note: Unlocked, so caller must hold the write lock when calling this. + * + * @param previousNodeID the node to which this target was mapped; can be 0 if it was not previously + * mapped. + * @param keepPool targetID won't be removed from this particular pool. + */ +void TargetCapacityPools::removeFromOthersUnlocked(uint16_t targetID, NumNodeID previousNodeID, + CapacityPoolType keepPool) +{ + /* note: targetID can only exist in one pool, so we can stop as soon as we erased an element from + any pool */ + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + if(poolType == (unsigned)keepPool) + continue; + + size_t numErased = pools[poolType].erase(targetID); + if(!numErased) + continue; + + // targetID existed in this pool, also remove from same poolType for groupedTargets + + if(previousNodeID) + { // previous mapping existed + groupedTargetPools[poolType][previousNodeID].erase(targetID); + + if (groupedTargetPools[poolType][previousNodeID].empty()) + groupedTargetPools[poolType].erase(previousNodeID); + } + + return; + } + +} + +/** + * @return true if the target was either completely new or existed before but was moved to a + * different pool now + */ +bool TargetCapacityPools::addOrUpdate(uint16_t targetID, NumNodeID nodeID, + CapacityPoolType poolType) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + BEEGFS_BUG_ON_DEBUG(pools.size() != CapacityPool_END_DONTUSE, + "invalid pools.size()"); + BEEGFS_BUG_ON_DEBUG(groupedTargetPools.size() != CapacityPool_END_DONTUSE, + "invalid pools.size()"); + + + bool isNewInsertion = addOrUpdateUnlocked(targetID, nodeID, poolType); + + return isNewInsertion; +} + +/** + * @return true if the target was either completely new or existed before but was moved to a + * different pool now + */ +bool TargetCapacityPools::addOrUpdateUnlocked(uint16_t targetID, NumNodeID nodeID, + CapacityPoolType poolType) +{ + /* note: map::insert().second is true if the key was inserted and false if it already existed + (so we need to check other pools only if it was really inserted) */ + + bool isNewInsertion = false; + isNewInsertion |= pools[poolType].insert(targetID).second; + isNewInsertion |= groupedTargetPools[poolType][nodeID].insert(targetID).second; + + if(isNewInsertion) + { // new insertion in given pool or for given nodeID, so find and remove previous (if any) + NumNodeID previousNodeID = getNodeIDFromTargetID(targetID, targetMap); + + removeFromOthersUnlocked(targetID, previousNodeID, poolType); + } + + targetMap[targetID] = nodeID; + + return isNewInsertion; +} + +/** + * Add the targetID only if is doesn't exist in any of the pools yet (i.e. ensure that we do not + * change the poolType), so this is typically used if caller doesn't really know the right poolType + * and just wants to add the target. + * + * @return true if this is a new target, false if the target existed already + */ +bool TargetCapacityPools::addIfNotExists(uint16_t targetID, NumNodeID nodeID, + CapacityPoolType poolType) +{ + /* note: this is called often with already existing targets, so we optimize for this case by + having a quick path with only a read lock. */ + + bool needUpdate = true; // whether or not we need the slow path to update something + + { // lock scope + // quick read lock check-only path + RWLockGuard lock(rwlock, SafeRWLock_READ); + + BEEGFS_BUG_ON_DEBUG(pools.size() != CapacityPool_END_DONTUSE, "invalid pools.size()"); + BEEGFS_BUG_ON_DEBUG(groupedTargetPools.size() != CapacityPool_END_DONTUSE, + "invalid pools.size()"); + + for (unsigned currentPoolType = 0; currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if (pools[currentPoolType].find(targetID) == pools[currentPoolType].end()) + continue; // not found in this pool + + // target found in this pool, check correct nodeID mapping + NumNodeID previousNodeID = getNodeIDFromTargetID(targetID, targetMap); + if (nodeID == previousNodeID) + needUpdate = false; + + break; + } + } + + if (!needUpdate) + return false; + + { // lock scope + // slow write lock path (because target wasn't found in quick path) + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + // recheck, because target might have been added after read lock release + + needUpdate = true; + + for (unsigned currentPoolType = 0; currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if (pools[currentPoolType].find(targetID) == pools[currentPoolType].end()) + continue; // not found in this pool + + // target found in this pool, check correct nodeID mapping + + NumNodeID previousNodeID = getNodeIDFromTargetID(targetID, targetMap); + if (nodeID == previousNodeID) + needUpdate = false; + + // save poolType, because we don't want to change previous poolType + poolType = (CapacityPoolType) currentPoolType; + + break; + } + + if (needUpdate) + addOrUpdateUnlocked(targetID, nodeID, poolType); + + return needUpdate; + } +} + + +void TargetCapacityPools::remove(uint16_t targetID) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + for(unsigned currentPoolType = 0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + size_t numErased = pools[currentPoolType].erase(targetID); + if(!numErased) + continue; + + // found in this pool, can only exist in grouped pools if contained in targetMap + NumNodeID nodeID = getNodeIDFromTargetID(targetID, targetMap); + if(!nodeID) + break; + + // found a nodeID for this target + GroupedTargetsIter groupsIter = groupedTargetPools[currentPoolType].find(nodeID); + if(groupsIter == groupedTargetPools[currentPoolType].end() ) + break; // node not found in this pool + + size_t numGroupedErased = groupsIter->second.erase(targetID); + if(!numGroupedErased) + break; // target not found in this node group + + // remove the node from this pool if this was the last target + if(groupsIter->second.empty() ) + groupedTargetPools[currentPoolType].erase(groupsIter); + + break; + } +} + +/** + * @param lists the capacity pool lists in a vector with mappings lists[CapacityPoolType] + * @param newTargetMap needed because we can't do any calls into locked TargetMap methods here, + * because we already have the lock path TargetMap lock -> TargetCapacityPools lock; this map will + * be modified, so don't use it anymore after calling this method. + */ +void TargetCapacityPools::syncPoolsFromLists(const UInt16ListVector& lists, TargetMap& newTargetMap) +{ + /* note: it can happen that not all list elements are actually contained in the newTargetsMap; + we skip those elements for groupedTargets, because we need to know nodes for chooser from + different failure domains */ + + // create temporary sets first without lock, then swap with lock (to keep write-lock time short) + + UInt16SetVector newPools(CapacityPool_END_DONTUSE); + + for(unsigned currentPoolType=0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + newPools[currentPoolType].insert(lists[currentPoolType].begin(), + lists[currentPoolType].end()); + } + + GroupedTargetsVector newGroupedTargetPools(CapacityPool_END_DONTUSE); + + groupTargetsByNode(newPools, newTargetMap, &newGroupedTargetPools); + + + // temporary sets are ready, now swap with internal sets + { // lock scope + RWLockGuard updateLock(rwlock, SafeRWLock_WRITE); + + newPools.swap(pools); + + newGroupedTargetPools.swap(groupedTargetPools); + newTargetMap.swap(targetMap); + } + + newTargetMap.clear(); // (carefully clear the map to prevent accidental further use by caller) +} + +/** + * Take pools of targetIDs and a targetMap (which maps targetIDs to nodeIDs) and fill them into + * GroupedTargetPools. + * + * note: targets, for which no nodeID is defined in the targetMap will be skipped (see comment in + * method code for reason). + */ +void TargetCapacityPools::groupTargetsByNode(const UInt16SetVector& pools, + const TargetMap& targetMap, GroupedTargetsVector* outGroupedTargetPools) +{ + for(unsigned currentPoolType=0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + // walk all targets of current pool type and group them according to targetMap + + for(UInt16SetCIter targetsIter = pools[currentPoolType].begin(); + targetsIter != pools[currentPoolType].end(); + targetsIter++) + { + NumNodeID nodeID = getNodeIDFromTargetID(*targetsIter, targetMap); + if(unlikely(!nodeID) ) + continue; /* skip, because targets without known nodeID would make it impossible to + reliably select targets in different failure domains */ + + (*outGroupedTargetPools)[currentPoolType][nodeID].insert(*targetsIter); + } + } +} + +/** + * Create a copy of groupedTargets, but leave out the nodes contained in stripNodes. + * + * Note: Obviously, creating a full copy is expensive, so use this carefully. + * + * @outGroupedTargetsStripped copy of groupedTargets without stripNodes. + */ +void TargetCapacityPools::copyAndStripGroupedTargets(const GroupedTargets& groupedTargets, + const NumNodeIDVector& stripNodes, GroupedTargets& outGroupedTargetsStripped) +{ + outGroupedTargetsStripped = groupedTargets; + + for(NumNodeIDVectorCIter iter = stripNodes.begin(); iter != stripNodes.end(); iter++) + outGroupedTargetsStripped.erase(*iter); +} + +/** + * Find nodeID for given targetID in given targetMap. + * + * Note: This is not only called with the internal targetMap (e.g. by syncPoolsFromLists() ), that's + * why we need targetMap as an argument. + * + * @return 0 if not found in targetMap, mapped nodeID otherwise. + */ +NumNodeID TargetCapacityPools::getNodeIDFromTargetID(uint16_t targetID, + const TargetMap& targetMap) +{ + TargetMapCIter targetIter = targetMap.find(targetID); + + if(unlikely(targetIter == targetMap.end() ) ) + return NumNodeID(); + + return targetIter->second; +} + +/** + * @return the capacity pool lists in a vector with mappings lists[CapacityPoolType] + */ +UInt16ListVector TargetCapacityPools::getPoolsAsLists() const +{ + UInt16ListVector outPools(CapacityPool_END_DONTUSE); + + RWLockGuard lock(rwlock, SafeRWLock_READ); + + for(unsigned currentPoolType=0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + /* note: build-in multi-element list::insert is inefficient (walks list two times), thus + we do the copy ourselves here */ + for(UInt16SetCIter iter = pools[currentPoolType].begin(); + iter != pools[currentPoolType].end(); + iter++) + { + outPools[currentPoolType].push_back(*iter); + } + } + + return outPools; +} + +/** + * @param numTargets number of desired targets + * @param minNumRequiredTargets the minimum number of targets the caller needs; ususally 1 + * for RAID-0 striping and 2 for mirroring (so this parameter is intended to avoid problems when + * there is only 1 target left in the normal pool and the user has mirroring turned on). + * @param preferredTargets may be NULL + */ +void TargetCapacityPools::chooseStorageTargets(unsigned numTargets, unsigned minNumRequiredTargets, + const UInt16List* preferredTargets, UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + if(!preferredTargets || preferredTargets->empty() ) + { // no preference => start with first pool that contains any targets + + if (!pools[CapacityPool_NORMAL].empty()) + { + chooseStorageNodesNoPref(pools[CapacityPool_NORMAL], numTargets, outTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + } + + /* note: no "else if" here, because we want to continue with next pool if we didn't find + enough targets for minNumRequiredTargets in previous pool */ + + if (!pools[CapacityPool_LOW].empty()) + { + chooseStorageNodesNoPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + outTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + } + + chooseStorageNodesNoPref(pools[CapacityPool_EMERGENCY], numTargets, outTargets); + } + else + { + // caller has preferred targets, so we can't say a priori whether nodes will be found or not + // in a pool. our strategy here is to automatically allow non-preferred targets before + // touching the emergency pool. + + std::set chosenTargets; + + // try normal and low pool with preferred targets... + + chooseStorageNodesWithPref(pools[CapacityPool_NORMAL], numTargets, preferredTargets, false, + outTargets, chosenTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + preferredTargets, false, outTargets, chosenTargets); + + if (!outTargets->empty() >= minNumRequiredTargets) + return; + + // try normal and low pool without preferred targets... + + chooseStorageNodesWithPref(pools[CapacityPool_NORMAL], numTargets, preferredTargets, true, + outTargets, chosenTargets); + + if(outTargets->size() >= minNumRequiredTargets) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_LOW], numTargets - outTargets->size(), + preferredTargets, true, outTargets, chosenTargets); + + if(!outTargets->empty() ) + return; + + /* still no targets available => we have to try the emergency pool (first with preference, + then without preference) */ + + chooseStorageNodesWithPref(pools[CapacityPool_EMERGENCY], numTargets, preferredTargets, false, + outTargets, chosenTargets); + if(!outTargets->empty() ) + return; + + chooseStorageNodesWithPref(pools[CapacityPool_EMERGENCY], numTargets, preferredTargets, true, + outTargets, chosenTargets); + } +} + +/** + * Alloc storage targets in a round-robin fashion. + * + * Disadvantages: + * - no support for preferred targets + * - would be really complex for multiple clients with distinct preferred targets and per-client + * lastTarget wouldn't lead to perfect balance) + * - requires write lock + * - lastTarget is not on a per-pool basis and hence + * - would not be perfectly round-robin when targets are moved to other pools + * - doesn't work with minNumRequiredTargets as switching pools would mess with the global + * lastTarget value + * - lastTarget is not saved across server restart + * + * ...so this should only be used in special scenarios and not as a general replacement for the + * normal chooseStorageTargets() method. + */ +void TargetCapacityPools::chooseStorageTargetsRoundRobin(unsigned numTargets, + UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + // no preference settings supported => use first pool that contains any targets + if (!pools[CapacityPool_NORMAL].empty()) + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_NORMAL], numTargets, outTargets); + else if (!pools[CapacityPool_LOW].empty()) + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_LOW], numTargets, outTargets); + else + chooseStorageNodesNoPrefRoundRobin(pools[CapacityPool_EMERGENCY], numTargets, outTargets); +} + +/** + * Select storage targets that are attached to different nodes (different failure domains). + * + * Note: The current implementation does not really try hard to deliver minNumRequiredTargets. + */ +void TargetCapacityPools::chooseTargetsInterdomain(unsigned numTargets, + unsigned minNumRequiredTargets, UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + NumNodeIDVector usedNodes; // to avoid using the same node twice from different pools + + for(unsigned currentPoolType = 0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if(groupedTargetPools[currentPoolType].empty() ) + continue; // we can't do anything with an empty pool + + GroupedTargets* groupedTargets = &groupedTargetPools[currentPoolType]; + GroupedTargets groupedTargetsStripped; + + if(!usedNodes.empty() ) + { /* create tmp groups: we already have some used nodes, so create grouped targets copy + without these nodes (expensive, but at least it works with only read-lock) */ + copyAndStripGroupedTargets(groupedTargetPools[currentPoolType], + usedNodes, groupedTargetsStripped); + groupedTargets = &groupedTargetsStripped; + } + + chooseTargetsInterdomainNoPref(*groupedTargets, + numTargets, *outTargets, usedNodes); + + if(outTargets->size() >= minNumRequiredTargets) + break; // we have enough targets for min requirement + } +} + +/** + * Select storage targets that are attached to the same node/domain. + * + * Note: The current implementation does not really try hard to deliver minNumRequiredTargets. + */ +void TargetCapacityPools::chooseTargetsIntradomain(unsigned numTargets, + unsigned minNumRequiredTargets, UInt16Vector* outTargets) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); // L O C K read + + UInt16Vector usedGroups; // to avoid using the same node twice from different pools + + for(unsigned currentPoolType = 0; + currentPoolType < CapacityPool_END_DONTUSE; + currentPoolType++) + { + if(groupedTargetPools[currentPoolType].empty() ) + continue; // we can't do anything with an empty pool + + GroupedTargets* groupedTargets = &groupedTargetPools[currentPoolType]; + GroupedTargets groupedTargetsStripped; + + chooseTargetsIntradomainNoPref(*groupedTargets, + numTargets, *outTargets, usedGroups); + + if(!usedGroups.empty() ) + break; // cancel if we have targtes (no special handling of min required currently) + } +} + +/** + * Note: Unlocked (=> caller must hold read lock) + * + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void TargetCapacityPools::chooseStorageNodesNoPref(const UInt16Set& activeTargets, + unsigned numTargets, UInt16Vector* outTargets) +{ + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + unsigned activeTargetsSize = activeTargets.size(); + + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + UInt16SetIter iter; + int partSize = activeTargetsSize / numTargets; + + moveIterToRandomElem(activeTargets, iter); + + outTargets->reserve(numTargets); + + // for each target in numTargets + for(unsigned i=0; i < numTargets; i++) + { + int rangeMin = i*partSize; + int rangeMax = (i==(numTargets-1) ) ? (activeTargetsSize-1) : (rangeMin + partSize - 1); + // rangeMax decision because numTargets might not devide activeTargetsSize without a remainder + + int next = randGen.getNextInRange(rangeMin, rangeMax); + + /* move iter to the chosen target, add it to outTargets, and skip the remaining targets of + this part */ + + for(int j=rangeMin; j < next; j++) + moveIterToNextRingElem(activeTargets, iter); + + outTargets->push_back(*iter); + + for(int j=next; j < (rangeMax+1); j++) + moveIterToNextRingElem(activeTargets, iter); + } + +} + +/** + * Note: Unlocked (=> caller must hold write lock) + * + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void TargetCapacityPools::chooseStorageNodesNoPrefRoundRobin(const UInt16Set& activeTargets, + unsigned numTargets, UInt16Vector* outTargets) +{ + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + unsigned activeTargetsSize = activeTargets.size(); + + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + UInt16SetIter iter = activeTargets.lower_bound(lastRoundRobinTarget); + if(iter == activeTargets.end() ) + iter = activeTargets.begin(); + + outTargets->reserve(numTargets); + + // just add the requested number of targets sequentially from the set + for(unsigned i=0; i < numTargets; i++) + { + moveIterToNextRingElem(activeTargets, iter); + outTargets->push_back(*iter); + } + + lastRoundRobinTarget = *iter; +} + +/** + * Select targets from different nodes (different failure domains). + * + * Note: Unlocked (=> caller must hold read lock) + * + * @param groupedTargets caller must ensure that nodes, which were previously contained in outNodes + * from a call to a higher level pool, are not contained in this set. + * @param outTargets might cotain less than numTargets if not enough targets are known. + * @param outNodes is relevant if this is called again with another pool to ensure that no targets + * from an already used node are selected. + */ +void TargetCapacityPools::chooseTargetsInterdomainNoPref(const GroupedTargets& groupedTargets, + unsigned numTargets, UInt16Vector& outTargets, NumNodeIDVector& outNodes) +{ + if(groupedTargets.empty() ) + return; // there's nothing we can do without any storage targets + + unsigned groupedTargetsSize = groupedTargets.size(); + + if(numTargets > groupedTargetsSize) + numTargets = groupedTargetsSize; + + outTargets.reserve(numTargets); + outNodes.reserve(numTargets); + + // move iter to random node, select random target from this node and go to next sequential node + + GroupedTargetsCIter groupsIter; + + moveIterToRandomElem(groupedTargets, groupsIter); + + for(unsigned i=0; i < numTargets; i++) + { + // select random target from current node + + UInt16SetCIter targetsIter; + + BEEGFS_BUG_ON_DEBUG(groupsIter->second.empty(), "found node without targets"); + + moveIterToRandomElem(groupsIter->second, targetsIter); + + outTargets.push_back(*targetsIter); + outNodes.push_back(groupsIter->first); + + // move on to next sequential node + moveIterToNextRingElem(groupedTargets, groupsIter); + } + +} + +/** + * Select targets on same node (same domain). + * + * Note: Unlocked (=> caller must hold read lock) + * + * @param groupedTargets the set of available domains. + * @param outTargets might cotain less than numTargets if not enough targets are known selected + * domain. + * @param outGroups contains the selected domain from the set of available domains. + */ +void TargetCapacityPools::chooseTargetsIntradomainNoPref(const GroupedTargets& groupedTargets, + unsigned numTargets, UInt16Vector& outTargets, UInt16Vector& outGroups) +{ + if(groupedTargets.empty() ) + return; // there's nothing we can do without any storage targets + + // move iter to random group + + GroupedTargetsCIter groupsIter; + + moveIterToRandomElem(groupedTargets, groupsIter); + + BEEGFS_BUG_ON_DEBUG(groupsIter->second.empty(), "found group without targets"); + + outGroups.push_back(groupsIter->first.val()); + + // adjust max number of targets that we can get from this group + + unsigned numTargetsInGroup = groupsIter->second.size(); + + if(numTargets > numTargetsInGroup) + numTargets = numTargetsInGroup; + + outTargets.reserve(numTargets); + + // select random first target from group and go on with next sequential targets + + UInt16SetCIter targetsIter; + + moveIterToRandomElem(groupsIter->second, targetsIter); + + outTargets.push_back(*targetsIter); + + // select remaining sequential targets from this group + + for(unsigned i=1; i < numTargets; i++) + { + moveIterToNextRingElem(groupsIter->second, targetsIter); + + outTargets.push_back(*targetsIter); + } + +} + +/** + * Note: Unlocked (=> caller must hold read lock) + * + * Note: This method assumes that there really is at least one preferred target and that those + * targets are probably active. So do not use it as a replacement for the version without preferred + * targets! + * + * @param allowNonPreferredTargets true to enable use of non-preferred targets if not enough + * preferred targets are active to satisfy numTargets + * @param outTargets might cotain less than numTargets if not enough targets are known + */ +void TargetCapacityPools::chooseStorageNodesWithPref(const UInt16Set& activeTargets, + unsigned numTargets, const UInt16List* preferredTargets, bool allowNonPreferredTargets, + UInt16Vector* outTargets, std::set& chosenTargets) +{ + if(activeTargets.empty() ) + return; // there's nothing we can do without any storage targets + + unsigned activeTargetsSize = activeTargets.size(); + + // max number of targets is limited by the number of known active targets + if(numTargets > activeTargetsSize) + numTargets = activeTargetsSize; + + // Stage 1: add all the preferred targets that are actually available to the outTargets + + UInt16ListConstIter preferredIter; + UInt16SetIter activeTargetsIter; // (will be re-used in stage 2) + + moveIterToRandomElem(*preferredTargets, preferredIter); + + outTargets->reserve(numTargets); + + // walk over all the preferred targets and add them to outTargets when they are available + // (note: iterTmp is used to avoid calling preferredTargets->size() ) + for(UInt16ListConstIter iterTmp = preferredTargets->begin(); + (iterTmp != preferredTargets->end() ) && numTargets; + iterTmp++) + { + activeTargetsIter = activeTargets.find(*preferredIter); + + if (activeTargetsIter != activeTargets.end() && chosenTargets.insert(*preferredIter).second) + { // this preferred node is active => add to outTargets and to outTargetsSet + outTargets->push_back(*preferredIter); + numTargets--; + } + + moveIterToNextRingElem(*preferredTargets, preferredIter); + } + + // Stage 2: add the remaining requested number of targets from the active targets + + // if numTargets is greater than 0 then we have some requested targets left, that could not be + // taken from the preferred targets + + // we keep it simple here, because usually there will be enough preferred targets available, + // so that this case is quite unlikely + + if(allowNonPreferredTargets && numTargets) + { + UInt16SetIter outTargetsSetIter; + + moveIterToRandomElem(activeTargets, activeTargetsIter); + + // while we haven't found the number of requested targets + while(numTargets) + { + if (chosenTargets.insert(*activeTargetsIter).second) + { + outTargets->push_back(*activeTargetsIter); + numTargets--; + } + + moveIterToNextRingElem(activeTargets, activeTargetsIter); + } + } + +} + +/** + * get the pool type to which a targetID is currently assigned. + * + * @return false if targetID not found. + */ +bool TargetCapacityPools::getPoolAssignment(uint16_t targetID, CapacityPoolType* outPoolType) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); // L O C K read + + bool targetFound = false; + + *outPoolType = CapacityPool_EMERGENCY; + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + UInt16SetCIter iter = pools[poolType].find(targetID); + + if(iter != pools[poolType].end() ) + { + *outPoolType = (CapacityPoolType)poolType; + targetFound = true; + + break; + } + } + + return targetFound; +} + +/** + * Return internal state as human-readable string. + * + * Note: Very expensive, only intended for debugging. + */ +std::string TargetCapacityPools::getStateAsStr() const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); // L O C K + + std::ostringstream stateStream; + + stateStream << " lastRoundRobinTarget: " << lastRoundRobinTarget << std::endl; + stateStream << " lowSpace: " << this->poolLimitsSpace.getLowLimit() << std::endl; + stateStream << " emergencySpace: " << this->poolLimitsSpace.getEmergencyLimit() << std::endl; + stateStream << " lowInodes: " << this->poolLimitsInodes.getLowLimit() << std::endl; + stateStream << " emergencyInodes: " << this->poolLimitsInodes.getEmergencyLimit() << std::endl; + stateStream << " dynamicLimits: " << (this->dynamicPoolsEnabled ? "on" : "off") << std::endl; + + if(this->dynamicPoolsEnabled) + { + stateStream << " normalSpaceSpreadThreshold: " << + this->poolLimitsSpace.getNormalSpreadThreshold() << std::endl; + stateStream << " lowSpaceSpreadThreshold: " << + this->poolLimitsSpace.getLowSpreadThreshold() << std::endl; + stateStream << " lowSpaceDynamicLimit: " << + this->poolLimitsSpace.getLowDynamicLimit() << std::endl; + stateStream << " emergencySpaceDynamicLimit: " << + this->poolLimitsSpace.getEmergencyDynamicLimit() << std::endl; + + stateStream << " normalInodesSpreadThreshold: " << + this->poolLimitsInodes.getNormalSpreadThreshold() << std::endl; + stateStream << " lowInodesSpreadThreshold: " << + this->poolLimitsInodes.getLowSpreadThreshold() << std::endl; + stateStream << " lowInodesDynamicLimit: " << + this->poolLimitsInodes.getLowDynamicLimit() << std::endl; + stateStream << " emergencyInodesDynamicLimit: " << + this->poolLimitsInodes.getEmergencyDynamicLimit() << std::endl; + } + + stateStream << std::endl; + + for(unsigned poolType=0; + poolType < CapacityPool_END_DONTUSE; + poolType++) + { + stateStream << " pool " << poolTypeToStr( (CapacityPoolType) poolType) << ":" << + std::endl; + + stateStream << " plain pool contents:" << std::endl; + + stateStream << " "; + + for(UInt16SetCIter iter = pools[poolType].begin(); + iter != pools[poolType].end(); + iter++) + { + stateStream << *iter << " "; + } + + stateStream << std::endl; + + stateStream << " grouped pool contents:" << std::endl; + + for(GroupedTargetsCIter groupsIter = groupedTargetPools[poolType].begin(); + groupsIter != groupedTargetPools[poolType].end(); + groupsIter++) + { + stateStream << " nodeID: " << groupsIter->first << std::endl; + + stateStream << " "; // intend before first targetID + + for(UInt16SetCIter targetsIter = groupsIter->second.begin(); + targetsIter != groupsIter->second.end(); + targetsIter++) + { + stateStream << *targetsIter << " "; + } + + stateStream << std::endl; + } + + stateStream << std::endl; + } + + return stateStream.str(); +} + +/** + * @return pointer to static string buffer (no free needed) + */ +const char* TargetCapacityPools::poolTypeToStr(CapacityPoolType poolType) +{ + switch(poolType) + { + case CapacityPool_NORMAL: + { + return "Normal"; + } break; + + case CapacityPool_LOW: + { + return "Low"; + } break; + + case CapacityPool_EMERGENCY: + { + return "Emergency"; + } break; + + default: + { + return "Unknown/Invalid"; + } break; + } +} diff --git a/common/source/common/nodes/TargetCapacityPools.h b/common/source/common/nodes/TargetCapacityPools.h new file mode 100644 index 0000000..e96b9e8 --- /dev/null +++ b/common/source/common/nodes/TargetCapacityPools.h @@ -0,0 +1,169 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +typedef std::map TargetMap; // keys: targetIDs, values: nodeNumIDs +typedef TargetMap::iterator TargetMapIter; +typedef TargetMap::const_iterator TargetMapCIter; +typedef TargetMap::value_type TargetMapVal; + +typedef std::map GroupedTargets; // keys: nodeIDs, values: node's targetIDs +typedef GroupedTargets::iterator GroupedTargetsIter; +typedef GroupedTargets::const_iterator GroupedTargetsCIter; +typedef GroupedTargets::value_type GroupedTargetsVal; + +typedef std::vector GroupedTargetsVector; // CapacityPoolType is index +typedef GroupedTargetsVector::iterator GroupedTargetsVectorIter; +typedef GroupedTargetsVector::const_iterator GroupedTargetsVectorConstIter; + +/** + * This class provides pools of targetIDs based on their free space. + * There are two internal types of pools: The general pools with all targets of the corresponding + * free space class; and a pools that are grouped by nodeIDs to provide the ability to select + * targets from different nodes (different failure domains). + * + * Note: As mappings from targetIDs to nodes can be unknown for some targets, it is possible that + * not all targets are contained in the per-node groups. + */ +class TargetCapacityPools +{ + public: + TargetCapacityPools(bool useDynamicPools, const DynamicPoolLimits& poolLimitsSpace, + const DynamicPoolLimits& poolLimitsInodes); + + bool addOrUpdate(uint16_t targetID, NumNodeID nodeID, CapacityPoolType poolType); + bool addIfNotExists(uint16_t targetID, NumNodeID nodeID, CapacityPoolType poolType); + void remove(uint16_t targetID); + + void syncPoolsFromLists(const UInt16ListVector& lists, TargetMap& newTargetMap); + UInt16ListVector getPoolsAsLists() const; + + void chooseStorageTargets(unsigned numTargets, unsigned minNumRequiredTargets, + const UInt16List* preferredTargets, UInt16Vector* outTargets); + void chooseStorageTargetsRoundRobin(unsigned numTargets, UInt16Vector* outTargets); + void chooseTargetsInterdomain(unsigned numTargets, unsigned minNumRequiredTargets, + UInt16Vector* outTargets); + void chooseTargetsIntradomain(unsigned numTargets, unsigned minNumRequiredTargets, + UInt16Vector* outTargets); + + bool getPoolAssignment(uint16_t targetID, CapacityPoolType* outPoolType) const; + + std::string getStateAsStr() const; + + static const char* poolTypeToStr(CapacityPoolType poolType); + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->pools + % obj->groupedTargetPools + % obj->targetMap; + } + + private: + mutable RWLock rwlock; + + bool dynamicPoolsEnabled; + const DynamicPoolLimits poolLimitsSpace; + const DynamicPoolLimits poolLimitsInodes; + + UInt16SetVector pools; + + GroupedTargetsVector groupedTargetPools; // targets grouped by node + TargetMap targetMap; // the basis of our groupedTargets assignments + + RandomReentrant randGen; // for random target selection + uint16_t lastRoundRobinTarget; // used for round-robin chooser + + bool addOrUpdateUnlocked(uint16_t targetID, NumNodeID nodeID, CapacityPoolType poolType); + void removeFromOthersUnlocked(uint16_t targetID, NumNodeID previousNodeID, + CapacityPoolType keepPool); + void chooseStorageNodesNoPref(const UInt16Set& activeTargets, unsigned numTargets, + UInt16Vector* outTargets); + void chooseStorageNodesNoPrefRoundRobin(const UInt16Set& activeTargets, unsigned numTargets, + UInt16Vector* outTargets); + void chooseTargetsInterdomainNoPref(const GroupedTargets& groupedTargets, unsigned numTargets, + UInt16Vector& outTargets, NumNodeIDVector& outNodes); + void chooseTargetsIntradomainNoPref(const GroupedTargets& groupedTargets, unsigned numTargets, + UInt16Vector& outTargets, UInt16Vector& outGroups); + void chooseStorageNodesWithPref(const UInt16Set& activeTargets, unsigned numTargets, + const UInt16List* preferredTargets, bool allowNonPreferredTargets, + UInt16Vector* outTargets, std::set& chosenTargets); + + static void groupTargetsByNode(const UInt16SetVector& pools, const TargetMap& targetMap, + GroupedTargetsVector* outGroupedTargetPools); + static void copyAndStripGroupedTargets(const GroupedTargets& groupedTargets, + const NumNodeIDVector& stripNodes, GroupedTargets& outGroupedTargetsStripped); + + static NumNodeID getNodeIDFromTargetID(uint16_t targetID, const TargetMap& targetMap); + + + public: + // getters & setters + + bool getDynamicPoolsEnabled() const + { + return this->dynamicPoolsEnabled; + } + + const DynamicPoolLimits& getPoolLimitsSpace() const + { + return poolLimitsSpace; + } + + const DynamicPoolLimits& getPoolLimitsInodes() const + { + return poolLimitsInodes; + } + + private: + // template inliners + + /** + * Note: static method => unlocked (=> caller must hold read lock) + */ + template + static void moveIterToNextRingElem(TCollection& collection, TIterator& iter) + { + if(iter != collection.end() ) + { + iter++; + if(iter == collection.end() ) + iter = collection.begin(); + } + else + { // iterator is pointing to the end element + iter = collection.begin(); // this should actually never happen + } + } + + /** + * Note: unlocked (=> caller must hold read lock) + */ + template + void moveIterToRandomElem(TCollection& collection, TIterator& iter) + { + iter = collection.begin(); + + int collectionSize = collection.size(); + + if(collectionSize < 2) + return; + + int randInt = randGen.getNextInRange(0, collectionSize - 1); + + while(randInt--) + iter++; + } + + +}; + diff --git a/common/source/common/nodes/TargetMapper.cpp b/common/source/common/nodes/TargetMapper.cpp new file mode 100644 index 0000000..0cff06f --- /dev/null +++ b/common/source/common/nodes/TargetMapper.cpp @@ -0,0 +1,208 @@ +#include "TargetMapper.h" + +#include +#include +#include +#include + +TargetMapper::TargetMapper(): + states(NULL), storagePools(NULL), exceededQuotaStores(NULL) { } + +/** + * Note: re-maps targetID if it was mapped before. + * + * @param storagePoolId if the target is new it gets added to this storagePoolId (note: might be + * invalid pool, in which case storage pool store has to handle this) + * + * @return pair of , with first indicating if any error occured and second + * indicating if the target was newly added (in which case true is returned) + */ +std::pair TargetMapper::mapTarget(uint16_t targetID, NumNodeID nodeID, + StoragePoolId storagePoolId) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + size_t oldSize = targets.size(); + + targets[targetID] = nodeID; + + size_t newSize = targets.size(); + + if (oldSize != newSize) // new target + { + if (storagePools) + { + FhgfsOpsErr addRes = storagePools->addTarget(targetID, nodeID, storagePoolId, true); + if (addRes != FhgfsOpsErr_SUCCESS) // target could not be mapped to storagePoolId + { // => unmap target again and return an error + targets.erase(targetID); + + return { addRes, false }; + } + } + + if (states) + states->addIfNotExists(targetID, + CombinedTargetState(TargetReachabilityState_POFFLINE, TargetConsistencyState_GOOD)); + + if (exceededQuotaStores) + exceededQuotaStores->add(targetID, false); + } + + return { FhgfsOpsErr_SUCCESS, (oldSize != newSize) }; +} + +/** + * @return true if the targetID was mapped + */ +bool TargetMapper::unmapTarget(uint16_t targetID) +{ + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + + TargetMapIter iter = targets.find(targetID); + if(iter != targets.end() ) + { // targetID found + + targets.erase(iter); + + if (storagePools) + storagePools->removeTarget(targetID); + + if(states) + states->removeTarget(targetID); + + if (exceededQuotaStores) + exceededQuotaStores->remove(targetID); + + return true; + } + + return false; +} + +/** + * @return true if the nodeID was mapped at least once + */ +bool TargetMapper::unmapByNodeID(NumNodeID nodeID) +{ + bool elemsErased = false; + + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + + for(TargetMapIter iter = targets.begin(); iter != targets.end(); /* iter inc'ed inside loop */) + { + if(iter->second == nodeID) + { + uint16_t targetID(iter->first); + + TargetMapIter eraseIter(iter); // save current iter pos + iter++; // move iter to next elem + + targets.erase(eraseIter); + + if (storagePools) + storagePools->removeTarget(targetID); + + if(states) + states->removeTarget(targetID); + + if (exceededQuotaStores) + exceededQuotaStores->remove(targetID); + + elemsErased = true; + } + else + iter++; + } + + return elemsErased; +} + +void TargetMapper::syncTargets(TargetMap newTargets) +{ + { + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + + targets.swap(newTargets); + } + + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + + for (TargetMapCIter iter = targets.begin(); iter != targets.end(); iter++) + { + // add to attached states + if (states) + { + states->addIfNotExists(iter->first, CombinedTargetState()); + } + + if (exceededQuotaStores) + { + exceededQuotaStores->add(iter->first); + } + } + } +} + +/** + * Returns the mapping in two separate lists (keys and values). + */ +void TargetMapper::getMappingAsLists(UInt16List& outTargetIDs, NumNodeIDList& outNodeIDs) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); // L O C K + + for(TargetMapCIter iter = targets.begin(); iter != targets.end(); iter++) + { + outTargetIDs.push_back(iter->first); + outNodeIDs.push_back(iter->second); + } +} + +/** + * Returns a list of targetIDs which are mapped to the given nodeID. + */ +void TargetMapper::getTargetsByNode(NumNodeID nodeID, UInt16List& outTargetIDs) const +{ + RWLockGuard safeLock(rwlock, SafeRWLock_READ); // L O C K + + for(TargetMapCIter iter = targets.begin(); iter != targets.end(); iter++) + { + if (iter->second == nodeID) + { + outTargetIDs.push_back(iter->first); + } + } +} + +/** +* Targets will automatically be added/removed from attached target states when they are +* added/removed from this store. +*/ +void TargetMapper::attachStateStore(TargetStateStore* states) +{ + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + + this->states = states; +} + +/** +* Targets will automatically be added to the default storage pool when they are added to this store. +*/ +void TargetMapper::attachStoragePoolStore(StoragePoolStore* storagePools) +{ + RWLockGuard const _(rwlock, SafeRWLock_WRITE); + + this->storagePools = storagePools; +} + +/** +* Targets will automatically be added to the ExceededQuotaPerTarget stores when they are added to +* this store. +*/ +void TargetMapper::attachExceededQuotaStores(ExceededQuotaPerTarget* exceededQuotaStores) +{ + RWLockGuard const _(rwlock, SafeRWLock_WRITE); + + this->exceededQuotaStores = exceededQuotaStores; +} diff --git a/common/source/common/nodes/TargetMapper.h b/common/source/common/nodes/TargetMapper.h new file mode 100644 index 0000000..f779128 --- /dev/null +++ b/common/source/common/nodes/TargetMapper.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include +#include +#include +#include + + +/** + * Map targetIDs to nodeIDs. + */ +class TargetMapper +{ + public: + TargetMapper(); + + std::pair mapTarget(uint16_t targetID, NumNodeID nodeID, + StoragePoolId storagePoolId); + bool unmapTarget(uint16_t targetID); + bool unmapByNodeID(NumNodeID nodeID); + + void syncTargets(TargetMap newTargets); + void getMappingAsLists(UInt16List& outTargetIDs, NumNodeIDList& outNodeIDs) const; + void getTargetsByNode(NumNodeID nodeID, UInt16List& outTargetIDs) const; + + void attachStateStore(TargetStateStore* states); + + void attachStoragePoolStore(StoragePoolStore* storagePools); + void attachExceededQuotaStores(ExceededQuotaPerTarget* exceededQuotaStores); + + protected: + mutable RWLock rwlock; + + TargetMap targets; // keys: targetIDs, values: nodeNumIDs + + TargetStateStore* states; // optional for auto add/remove on map/unmap (may be NULL) + StoragePoolStore* storagePools; // for auto add/remove on map/unmap (may be NULL) + ExceededQuotaPerTarget* exceededQuotaStores; // for auto add/remove on map/unmap (may be NULL) + + + public: + // getters & setters + + /** + * @return 0 if target not found + */ + NumNodeID getNodeID(uint16_t targetID) const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + + const auto iter = targets.find(targetID); + return iter != targets.end() + ? iter->second + : NumNodeID{}; + } + + size_t getSize() const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + return targets.size(); + } + + bool targetExists(uint16_t targetID) const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + return targets.count(targetID) != 0; + } + + TargetMap getMapping() const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + return targets; + } +}; + diff --git a/common/source/common/nodes/TargetStateInfo.h b/common/source/common/nodes/TargetStateInfo.h new file mode 100644 index 0000000..f8c6843 --- /dev/null +++ b/common/source/common/nodes/TargetStateInfo.h @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include + +// Make sure to keep this in sync with corresponding enums in client. +enum TargetReachabilityState +{ + TargetReachabilityState_ONLINE, + TargetReachabilityState_POFFLINE, // Probably offline. + TargetReachabilityState_OFFLINE +}; + +enum TargetConsistencyState +{ + TargetConsistencyState_GOOD, + TargetConsistencyState_NEEDS_RESYNC, + TargetConsistencyState_BAD +}; + +template<> +struct SerializeAs +{ + typedef uint8_t type; +}; + +template<> +struct SerializeAs +{ + typedef uint8_t type; +}; + +typedef std::vector TargetConsistencyStateVec; + +/** + * A combined target state is a pair of TargetReachabilityState and TargetConsistencyState. + */ +struct CombinedTargetState +{ + TargetReachabilityState reachabilityState; + TargetConsistencyState consistencyState; + + CombinedTargetState() + : reachabilityState(TargetReachabilityState_OFFLINE), + consistencyState(TargetConsistencyState_GOOD) + { } + + CombinedTargetState(TargetReachabilityState reachabilityState, + TargetConsistencyState consistencyState) + : reachabilityState(reachabilityState), + consistencyState(consistencyState) + { } + + bool operator!=(const CombinedTargetState& other) const + { + return ( (reachabilityState != other.reachabilityState) + || (consistencyState != other.consistencyState) ); + } + + bool operator==(const CombinedTargetState& other) const + { + return ( (reachabilityState == other.reachabilityState) + && (consistencyState == other.consistencyState) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->reachabilityState + % obj->consistencyState; + } +}; + +/** + * Extends the CombinedTargetState with a timestamp. + * + * Note: Creating objects of this type is expensive due to the Time member; thus, this is intended + * for internal use by the TargetStateStore only. + */ +struct TargetStateInfo : public CombinedTargetState +{ + Time lastChangedTime; // note: relative time, may not be synced across nodes. + + TargetStateInfo(TargetReachabilityState reachabilityState, + TargetConsistencyState consistencyState) + : CombinedTargetState(reachabilityState, consistencyState) + { } + + TargetStateInfo() + { } + + TargetStateInfo& operator=(const TargetStateInfo& other) + { + if (&other == this) + return *this; + + reachabilityState = other.reachabilityState; + consistencyState = other.consistencyState; + lastChangedTime = other.lastChangedTime; + + return *this; + } + + /** + * Assignment from a CombinedTargetState (without timestamp) updates timestamp. + */ + TargetStateInfo& operator=(const CombinedTargetState& other) + { + if (&other == this) + return *this; + + reachabilityState = other.reachabilityState; + consistencyState = other.consistencyState; + lastChangedTime.setToNow(); + + return *this; + } + + /** + * Comparison with a CombinedTargetState ignores timestamp. + */ + bool operator!=(const CombinedTargetState& other) const + { + return ( (reachabilityState != other.reachabilityState) + || (consistencyState != other.consistencyState) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->lastChangedTime; + } +}; + +typedef std::map TargetStateMap; +typedef TargetStateMap::iterator TargetStateMapIter; +typedef TargetStateMap::const_iterator TargetStateMapCIter; +typedef TargetStateMap::value_type TargetStateMapVal; + +typedef std::map TargetStateInfoMap; +typedef TargetStateInfoMap::iterator TargetStateInfoMapIter; +typedef TargetStateInfoMap::const_iterator TargetStateInfoMapConstIter; + diff --git a/common/source/common/nodes/TargetStateStore.cpp b/common/source/common/nodes/TargetStateStore.cpp new file mode 100644 index 0000000..20e565a --- /dev/null +++ b/common/source/common/nodes/TargetStateStore.cpp @@ -0,0 +1,195 @@ +#include +#include +#include "TargetStateStore.h" + +TargetStateStore::TargetStateStore(NodeType nodeType) + : nodeType(nodeType) +{ +} + +/** + * add only if target didn't exist yet, otherwise leave it unchanged. + */ +void TargetStateStore::addIfNotExists(uint16_t targetID, CombinedTargetState state) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + TargetStateInfoMapIter iter = statesMap.find(targetID); + + if (iter == statesMap.end()) + { + statesMap[targetID] = state; + LOG_DBG(STATES, DEBUG, "Adding new item to state store.", targetID, + ("New state", stateToStr(state)), nodeType, ("Called from", Backtrace<3>())); + } + else + { + LOG_DBG(STATES, DEBUG, + "Adding new item to state store failed because it already exists.", targetID, + ("Called from", Backtrace<3>())); + } +} + +void TargetStateStore::removeTarget(uint16_t targetID) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + statesMap.erase(targetID); +} + +/** + * Atomically update target states and buddy groups together. This is important to avoid races + * during a switch (e.g. where an outside viewer could see an offline primary). + * + * Of course, this implies that states and groups also have to be read atomically together via + * getStatesAndGroupsAsLists() through GetStatesAndBuddyGroupsMsg. + */ +void TargetStateStore::syncStatesAndGroups(MirrorBuddyGroupMapper* buddyGroups, + const TargetStateMap& states, MirrorBuddyGroupMap newGroups, const NumNodeID localNodeID) +{ + // create temporary states map first without lock, then swap with lock (to keep lock time short) + + TargetStateInfoMap statesMapNewTmp; + for (const auto& state : states) + { + TargetStateInfo stateInfo(state.second.reachabilityState, state.second.consistencyState); + statesMapNewTmp[state.first] = stateInfo; + LOG_DBG(STATES, DEBUG, "Syncing state.", ("targetID", state.first), + ("New state", stateToStr(stateInfo)), ("Called from", Backtrace<3>())); + } + + // pre-create a new buddy map and then swap elements (to keep lock time short) + uint16_t localGroupID = 0; + for (const auto& mapping : newGroups) + { + if (mapping.second.firstTargetID == localNodeID.val() || + mapping.second.secondTargetID == localNodeID.val()) + localGroupID = mapping.first; + } + + // now do the actual updates... + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + RWLockGuard buddyGroupsLock(buddyGroups->rwlock, SafeRWLock_WRITE); + + statesMapNewTmp.swap(statesMap); + + if (localGroupID != 0) + buddyGroups->setLocalGroupIDUnlocked(localGroupID); + + buddyGroups->mirrorBuddyGroups.swap(newGroups); +} + +/** + * Note: If you're also updating mirror buddy groups, you probably rather want to call + * syncStatesAndGroupsFromLists() instead of this. + */ +void TargetStateStore::syncStatesFromLists(const UInt16List& targetIDs, + const UInt8List& reachabilityStates, const UInt8List& consistencyStates) +{ + // we fill a temporary map first without lock, then swap with lock (to keep lock time short) + + TargetStateInfoMap statesMapNewTmp; + for (ZipConstIterRange + iter(targetIDs, reachabilityStates, consistencyStates); + !iter.empty(); ++iter) + { + TargetStateInfo stateInfo( + static_cast(*iter()->second), + static_cast(*iter()->third) ); + statesMapNewTmp[*iter()->first] = stateInfo; + LOG_DBG(STATES, DEBUG, "Assigning new state.", ("targetID", *iter()->first), + ("New state", stateToStr(stateInfo)), ("Called from", Backtrace<3>())); + } + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + statesMapNewTmp.swap(statesMap); +} + +/** + * Atomically get target states and buddy groups together. This is important to avoid races + * during a switch (e.g. where an outside viewer could see an offline primary). + * + * Of course, this implies that states and groups are also updated atomically together via + * syncStatesAndGroupsFromLists() on the other side. + */ +void TargetStateStore::getStatesAndGroups(const MirrorBuddyGroupMapper* buddyGroups, + TargetStateMap& states, MirrorBuddyGroupMap& outGroups) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + RWLockGuard buddyGroupsLock(buddyGroups->rwlock, SafeRWLock_READ); + + states = {statesMap.begin(), statesMap.end()}; + outGroups = buddyGroups->mirrorBuddyGroups; +} + + +/** + * Note: If you're also getting mirror buddy groups, you probably rather want to call + * getStatesAndGroupsAsLists() instead of this. + */ +void TargetStateStore::getStatesAsLists(UInt16List& outTargetIDs, + UInt8List& outReachabilityStates, UInt8List& outConsistencyStates) const +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + getStatesAsListsUnlocked(outTargetIDs, outReachabilityStates, outConsistencyStates); +} + +/** + * Note: Caller must hold readlock. + */ +void TargetStateStore::getStatesAsListsUnlocked(UInt16List& outTargetIDs, + UInt8List& outReachabilityStates, UInt8List& outConsistencyStates) const +{ + for (auto iter = statesMap.begin(); iter != statesMap.end(); iter++) + { + outTargetIDs.push_back(iter->first); + outReachabilityStates.push_back((uint8_t)(iter->second).reachabilityState); + outConsistencyStates.push_back((uint8_t)(iter->second).consistencyState); + } +} + +const char* TargetStateStore::stateToStr(TargetReachabilityState state) +{ + switch(state) + { + case TargetReachabilityState_ONLINE: + return "Online"; + + case TargetReachabilityState_OFFLINE: + return "Offline"; + + case TargetReachabilityState_POFFLINE: + return "Probably-offline"; + + default: + return ""; + } +} + +const char* TargetStateStore::stateToStr(TargetConsistencyState state) +{ + switch(state) + { + case TargetConsistencyState_GOOD: + return "Good"; + + case TargetConsistencyState_NEEDS_RESYNC: + return "Needs-resync"; + + case TargetConsistencyState_BAD: + return "Bad"; + + default: + return ""; + } +} + +const std::string TargetStateStore::stateToStr(const CombinedTargetState& state) +{ + std::ostringstream resStr; + resStr << stateToStr(state.reachabilityState) << " / " << stateToStr(state.consistencyState); + return resStr.str(); +} diff --git a/common/source/common/nodes/TargetStateStore.h b/common/source/common/nodes/TargetStateStore.h new file mode 100644 index 0000000..a807e92 --- /dev/null +++ b/common/source/common/nodes/TargetStateStore.h @@ -0,0 +1,130 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class MirrorBuddyGroupMapper; // forward declaration + + +class TargetStateStore +{ + public: + TargetStateStore(NodeType nodeType); + + void addIfNotExists(uint16_t targetID, CombinedTargetState state); + void removeTarget(uint16_t targetID); + + void syncStatesAndGroups(MirrorBuddyGroupMapper* buddyGroups, const TargetStateMap& states, + MirrorBuddyGroupMap newGroups, const NumNodeID localNodeID); + void syncStatesFromLists(const UInt16List& targetIDs, const UInt8List& reachabilityStates, + const UInt8List& consistencyStates); + + void getStatesAndGroups(const MirrorBuddyGroupMapper* buddyGroups, + TargetStateMap& states, MirrorBuddyGroupMap& outGroups) const; + void getStatesAsLists(UInt16List& outTargetIDs, UInt8List& outReachabilityStates, + UInt8List& outConsistencyStates) const; + + static const char* stateToStr(TargetReachabilityState state); + static const char* stateToStr(TargetConsistencyState state); + static const std::string stateToStr(const CombinedTargetState& state); + + protected: + mutable RWLock rwlock; + + TargetStateInfoMap statesMap; + + private: + void getStatesAsListsUnlocked(UInt16List& outTargetIDs, + UInt8List& outReachabilityStates, UInt8List& outConsistencyStates) const; + + public: + // getters & setters + bool getStateInfo(uint16_t targetID, TargetStateInfo& outStateInfo) const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + return getStateInfoUnlocked(targetID, outStateInfo); + } + + bool getState(uint16_t targetID, CombinedTargetState& outState) const + { + RWLockGuard safeLock(rwlock, SafeRWLock_READ); + return getStateUnlocked(targetID, outState); + } + + void setAllStates(TargetReachabilityState state) + { + LOG_DBG(STATES, DEBUG, "Setting all states.", ("New state", stateToStr(state)), + ("Called from", Backtrace<3>())); + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + setAllStatesUnlocked(state); + } + + void setState(uint16_t id, CombinedTargetState state) + { + LOG_DBG(STATES, DEBUG, "Setting new state.", id, + ("New state", stateToStr(state)), ("Called from", Backtrace<3>())); + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + statesMap[id] = state; + } + + void setReachabilityState(uint16_t id, TargetReachabilityState state) + { + LOG_DBG(STATES, DEBUG, "Setting new reachability state.", id, + ("New state", stateToStr(state)), ("Called from", Backtrace<3>())); + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + statesMap[id].reachabilityState = state; + } + + void setConsistencyState(uint16_t id, TargetConsistencyState state) + { + LOG_DBG(STATES, DEBUG, "Setting new consistency state.", id, + ("New state", stateToStr(state)), ("Called from", Backtrace<3>())); + RWLockGuard safeLock(rwlock, SafeRWLock_WRITE); + statesMap[id].consistencyState = state; + } + + + protected: + NodeType nodeType; + + bool getStateUnlocked(uint16_t targetID, CombinedTargetState& outState) const + { + TargetStateInfoMapConstIter iter = this->statesMap.find(targetID); + if (unlikely(iter == statesMap.end() ) ) + return false; + + outState = iter->second; + return true; + } + + void setAllStatesUnlocked(TargetReachabilityState state) + { + for (TargetStateInfoMapIter iter = this->statesMap.begin(); + iter != this->statesMap.end(); ++iter) + { + TargetStateInfo& currentState = iter->second; + if (currentState.reachabilityState != state) + { + currentState.reachabilityState = state; + currentState.lastChangedTime.setToNow(); + } + } + } + + + private: + bool getStateInfoUnlocked(uint16_t targetID, TargetStateInfo& outStateInfo) const + { + TargetStateInfoMapConstIter iter = this->statesMap.find(targetID); + if (unlikely(iter == statesMap.end() ) ) + return false; + + outStateInfo = iter->second; + return true; + } +}; + diff --git a/common/source/common/pch.h b/common/source/common/pch.h new file mode 100644 index 0000000..534f6b0 --- /dev/null +++ b/common/source/common/pch.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include diff --git a/common/source/common/storage/ChunksBlocksVec.h b/common/source/common/storage/ChunksBlocksVec.h new file mode 100644 index 0000000..731afbc --- /dev/null +++ b/common/source/common/storage/ChunksBlocksVec.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include + +#include + +class ChunksBlocksVec +{ + public: + + ChunksBlocksVec() + { + } + + ChunksBlocksVec(size_t numTargets) + : chunkBlocksVec(numTargets, 0) + { + } + + private: + UInt64Vector chunkBlocksVec; // number of blocks for each chunk file + + public: + // setters + + /** + * Set the number of used blocks for the chunk-file of this target + */ + void setNumBlocks(size_t target, uint64_t numBlocks, size_t numStripeTargets) + { + const char* logContext = "ChunksBlocksVec set num blocks"; + + if (this->chunkBlocksVec.empty() ) + this->chunkBlocksVec.resize(numStripeTargets, 0); + + if (unlikely(target >= this->chunkBlocksVec.size() ) ) + { + LogContext(logContext).logErr("Bug: target > vec-size"); + LogContext(logContext).logBacktrace(); + return; + } + + this->chunkBlocksVec.at(target) = numBlocks; + } + + // getters + + /** + * Get the number of blocks for the given target + */ + uint64_t getNumBlocks(size_t target) const + { + const char* logContext = "ChunksBlocksVec get number of blocks"; + + if (this->chunkBlocksVec.empty() ) + return 0; + + if (unlikely(target >= this->chunkBlocksVec.size() ) ) + { + LogContext(logContext).logErr("Bug: target number larger than stripes"); + LogContext(logContext).logBacktrace(); + return 0; + } + + return this->chunkBlocksVec.at(target); + } + + /** + * Get the sum of all used blocks of all targets + */ + uint64_t getBlockSum() const + { + return std::accumulate(this->chunkBlocksVec.begin(), this->chunkBlocksVec.end(), 0ULL); + } + + // inlined + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->chunkBlocksVec; + } + + bool operator==(const ChunksBlocksVec& other) const + { + return chunkBlocksVec == other.chunkBlocksVec; + } + + bool operator!=(const ChunksBlocksVec& other) const { return !(*this == other); } +}; + + diff --git a/common/source/common/storage/EntryInfo.cpp b/common/source/common/storage/EntryInfo.cpp new file mode 100644 index 0000000..a0bfb65 --- /dev/null +++ b/common/source/common/storage/EntryInfo.cpp @@ -0,0 +1,19 @@ +/* + * EntryInfo methods + */ + +#include +#include +#include + +#define MIN_ENTRY_ID_LEN 1 // usually A-B-C, but also "root" and "disposal" + +bool EntryInfo::operator==(const EntryInfo& other) const +{ + return ownerNodeID == other.ownerNodeID + && parentEntryID == other.parentEntryID + && entryID == other.entryID + && fileName == other.fileName + && entryType == other.entryType + && featureFlags == other.featureFlags; +} diff --git a/common/source/common/storage/EntryInfo.h b/common/source/common/storage/EntryInfo.h new file mode 100644 index 0000000..5912808 --- /dev/null +++ b/common/source/common/storage/EntryInfo.h @@ -0,0 +1,170 @@ +/* + * class EntryInfo - required information to find an inode or chunk files + * + * NOTE: If you change this file, do not forget to adjust the client side EntryInfo.h + */ + +#pragma once + +#include +#include + +#define ENTRYINFO_FEATURE_INLINED 1 // indicate inlined inode, might be outdated +#define ENTRYINFO_FEATURE_BUDDYMIRRORED 2 // indicates that the file/dir metadata is buddy mirrored + +#define EntryInfo_PARENT_ID_UNKNOWN "unknown" + + +class EntryInfo; // forward declaration + + +typedef std::list EntryInfoList; +typedef EntryInfoList::iterator EntryInfoListIter; +typedef EntryInfoList::const_iterator EntryInfoListConstIter; + + +/** + * Information about a file/directory + */ +class EntryInfo +{ + public: + EntryInfo() + : ownerNodeID(0), entryType(DirEntryType_INVALID), featureFlags(0) + { } + + EntryInfo(const NumNodeID ownerNodeID, const std::string& parentEntryID, + const std::string& entryID, const std::string& fileName, const DirEntryType entryType, + const int featureFlags) : + ownerNodeID(ownerNodeID), + parentEntryID(parentEntryID), + entryID(entryID), + fileName(fileName), + entryType(entryType), + featureFlags(featureFlags) + { } + + virtual ~EntryInfo() + { + } + + bool operator==(const EntryInfo& other) const; + + bool operator!=(const EntryInfo& other) const { return !(*this == other); } + + protected: + NumNodeID ownerNodeID; // nodeID of the metadata server that has the inode + std::string parentEntryID; // entryID of the parent dir + std::string entryID; // entryID of the actual entry + std::string fileName; // file/dir name of the actual entry + + DirEntryType entryType; + int32_t featureFlags; // feature flags (e.g. ENTRYINFO_FEATURE_INLINED) + + + public: + template + static void serialize(This obj, Ctx& ctx) + { + uint16_t padding = 0; // PADDING + + ctx + % serdes::as(obj->entryType) + % obj->featureFlags + % serdes::stringAlign4(obj->parentEntryID) + % serdes::stringAlign4(obj->entryID) + % serdes::stringAlign4(obj->fileName) + % obj->ownerNodeID + % padding; + } + + + // inliners + + void set(const NumNodeID ownerNodeID, const std::string& parentEntryID, + const std::string& entryID, const std::string& fileName, + const DirEntryType entryType, int featureFlags) + { + this->ownerNodeID = ownerNodeID; + this->parentEntryID = parentEntryID; + this->entryID = entryID; + this->fileName = fileName; + this->entryType = entryType; + this->featureFlags = featureFlags; + } + + void set(const EntryInfo* newEntryInfo) + { + this->ownerNodeID = newEntryInfo->getOwnerNodeID(); + this->parentEntryID = newEntryInfo->getParentEntryID(); + this->entryID = newEntryInfo->getEntryID(); + this->fileName = newEntryInfo->getFileName(); + this->entryType = newEntryInfo->getEntryType(); + this->featureFlags = newEntryInfo->getFeatureFlags(); + } + + void setParentEntryID(const std::string& newParentEntryID) + { + this->parentEntryID = newParentEntryID; + } + + /** + * Set or unset the ENTRYINFO_FEATURE_INLINED flag. + */ + void setInodeInlinedFlag(const bool isInlined) + { + if (isInlined) + this->featureFlags |= ENTRYINFO_FEATURE_INLINED; + else + this->featureFlags &= ~(ENTRYINFO_FEATURE_INLINED); + } + + void setBuddyMirroredFlag(const bool isBuddyMirrored) + { + if (isBuddyMirrored) + this->featureFlags |= ENTRYINFO_FEATURE_BUDDYMIRRORED; + else + this->featureFlags &= ~(ENTRYINFO_FEATURE_BUDDYMIRRORED); + } + + NumNodeID getOwnerNodeID() const + { + return this->ownerNodeID; + } + + const std::string& getParentEntryID() const + { + return this->parentEntryID; + } + + const std::string& getEntryID() const + { + return this->entryID; + } + + const std::string& getFileName() const + { + return this->fileName; + } + + DirEntryType getEntryType() const + { + return this->entryType; + } + + int getFeatureFlags() const + { + return this->featureFlags; + } + + bool getIsInlined() const + { + return (this->featureFlags & ENTRYINFO_FEATURE_INLINED) ? true : false; + } + + bool getIsBuddyMirrored() const + { + return (this->featureFlags & ENTRYINFO_FEATURE_BUDDYMIRRORED) ? true : false; + } +}; + diff --git a/common/source/common/storage/EntryInfoWithDepth.h b/common/source/common/storage/EntryInfoWithDepth.h new file mode 100644 index 0000000..46f243d --- /dev/null +++ b/common/source/common/storage/EntryInfoWithDepth.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +/** + */ +class EntryInfoWithDepth : public EntryInfo +{ + public: + EntryInfoWithDepth() : EntryInfo(), entryDepth(0) + { + } + + EntryInfoWithDepth(const NumNodeID ownerNodeID, const std::string& parentEntryID, + const std::string& entryID, const std::string& fileName, const DirEntryType entryType, + const int featureFlags, const unsigned entryDepth) : + EntryInfo(ownerNodeID, parentEntryID, entryID, fileName, entryType, featureFlags), + entryDepth(entryDepth) + { + } + + EntryInfoWithDepth(const EntryInfo& entryInfo) : EntryInfo(entryInfo), + entryDepth(0) + { + } + + virtual ~EntryInfoWithDepth() + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->entryDepth; + } + + private: + uint32_t entryDepth; // 0-based path depth (incl. root dir) + + public: + // inliners + void set(const NumNodeID ownerNodeID, const std::string& parentEntryID, + const std::string& entryID, const std::string& fileName, const DirEntryType entryType, + const int featureFlags, const unsigned entryDepth) + { + EntryInfo::set(ownerNodeID, parentEntryID, entryID, fileName, entryType, featureFlags); + + this->entryDepth = entryDepth; + } + + unsigned getEntryDepth() const + { + return entryDepth; + } + + void setEntryDepth(const unsigned entryDepth) + { + this->entryDepth = entryDepth; + } +}; + + diff --git a/common/source/common/storage/FileEvent.h b/common/source/common/storage/FileEvent.h new file mode 100644 index 0000000..3417ed2 --- /dev/null +++ b/common/source/common/storage/FileEvent.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +enum class FileEventType +{ + FLUSH = 1, + TRUNCATE = 2, + SETATTR = 3, + CLOSE_WRITE = 4, + CREATE = 5, + MKDIR = 6, + MKNOD = 7, + SYMLINK = 8, + RMDIR = 9, + UNLINK = 10, + HARDLINK = 11, + RENAME = 12, + OPEN_READ = 13, + OPEN_WRITE = 14, + OPEN_READ_WRITE = 15, + LAST_WRITER_CLOSED = 16, + OPEN_BLOCKED = 17, +}; + +template<> +struct SerializeAs +{ + typedef uint32_t type; +}; + +struct FileEvent +{ + FileEventType type; + std::string path; + bool targetValid; + std::string target; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->type + % obj->path; + + ctx % obj->targetValid; + if (obj->targetValid) + ctx % obj->target; + } +}; + diff --git a/common/source/common/storage/Metadata.h b/common/source/common/storage/Metadata.h new file mode 100644 index 0000000..10bacb2 --- /dev/null +++ b/common/source/common/storage/Metadata.h @@ -0,0 +1,28 @@ +#pragma once + +#include + + +#define META_ROOTDIR_ID_STR "root" /* initial file system entry point */ +#define META_DISPOSALDIR_ID_STR "disposal" /* for unlinked but still open files */ +#define META_MIRRORDISPOSALDIR_ID_STR "mdisposal" + +#define META_INODES_LEVEL1_SUBDIR_NUM (128) +#define META_INODES_LEVEL2_SUBDIR_NUM (128) +#define META_INODES_SUBDIR_NAME "inodes" /* contains local file system entry metadata */ + +#define META_DENTRIES_LEVEL1_SUBDIR_NUM (128) +#define META_DENTRIES_LEVEL2_SUBDIR_NUM (128) +#define META_DENTRIES_SUBDIR_NAME "dentries" /* contains file system link structure */ + +#define META_DIRENTRYID_SUB_STR "#fSiDs#" /* subdir with entryIDs for ID based file access + * this is a bit dangerous, at it might + * conflict with user file names, so we need + * to chose a good name */ + +#define META_LOSTANDFOUND_PATH "lost+found" + +#define META_BUDDYMIRROR_SUBDIR_NAME "buddymir" + + + diff --git a/common/source/common/storage/Path.h b/common/source/common/storage/Path.h new file mode 100644 index 0000000..f542fdc --- /dev/null +++ b/common/source/common/storage/Path.h @@ -0,0 +1,318 @@ +#pragma once + +#include + +#ifdef BEEGFS_DEBUG +#include +#endif + +#include +#include +#include +#include +#include + +class Path +{ + public: + Path() {} + + explicit Path(std::string str) : + pathStr(std::move(str)) + { + updateDirSeparators(); + } + + void operator=(const std::string& str) + { + pathStr = str; + updateDirSeparators(); + } + + const std::string& str() const + { + return pathStr; + } + + bool absolute() const + { + return !dirSeparators.empty() && dirSeparators.front() == 0; + } + + /** + * @returns the first component of the path. + */ + std::string front() const + { + if (dirSeparators.empty()) + return pathStr; + else if (!absolute()) + return pathStr.substr(0, dirSeparators[0]); + else if (dirSeparators.size() == 1) + return pathStr.substr(1); + else + return pathStr.substr(1, dirSeparators[1] - 1); + } + + /** + * @returns the last element of the path. + */ + std::string back() const + { + if (dirSeparators.empty()) + return pathStr; + else + return pathStr.substr(dirSeparators.back() + 1); + } + + bool empty() const + { + return pathStr.empty(); + } + + /** + * @returns the dirname of the path, i.e. the whole path excluding the last component. + */ + Path dirname() const + { + if (dirSeparators.size() > 0) + return Path(pathStr.substr(0, dirSeparators.back())); + else + return Path("."); + } + + /** + * @returns the number of components in the path. + */ + size_t size() const + { + if (pathStr.empty()) + return 0; + else if (absolute()) + return dirSeparators.size(); + else + return dirSeparators.size() + 1; + } + + /* + * @returns the index'th element of the Path. + * @throws std::out_of_range if index is out of range (i.e. index >= this->size()). + */ + std::string operator[](size_t index) const + { + if (absolute()) + { + if (index >= dirSeparators.size()) + throw std::out_of_range("Path element index out of range."); + + const std::string::size_type left = dirSeparators[index] + 1; + + if (index == dirSeparators.size() - 1) + return pathStr.substr(left); + + + const std::string::size_type right = dirSeparators[index + 1] - left; + + return pathStr.substr(left, right); + } + else + { + if (index > dirSeparators.size()) + throw std::out_of_range("Path element index out of range."); + + if (index == 0) + { + if (dirSeparators.empty()) + return pathStr; + else + return pathStr.substr(0, dirSeparators[0]); + } + + const std::string::size_type left = dirSeparators[index - 1] + 1; + + if (index == dirSeparators.size()) + return pathStr.substr(left); + + const std::string::size_type right = dirSeparators[index] - left; + + return pathStr.substr(left, right); + } + } + + /** + * Concatenates a path to the existing path. + * @throws std::invalid_argument if path to be concatenated is absolute. + */ + Path& operator/=(const Path& rhs) + { + if (rhs.absolute()) + { +#ifdef BEEGFS_DEBUG + LogContext(__func__).logBacktrace(); +#endif + throw std::invalid_argument("Can't concatenate absolute path; " + "Base: " + pathStr + " Concatenated path: " + rhs.pathStr + "."); + } + + if (empty()) + { + *this = rhs; + return *this; + } + + if (!rhs.empty()) + { + dirSeparators.push_back(pathStr.length()); + + for (auto it = rhs.dirSeparators.begin(); it != rhs.dirSeparators.end(); ++it) + dirSeparators.push_back(*it + pathStr.length() + 1); + + pathStr.reserve(pathStr.length() + 1 + rhs.pathStr.length()); + pathStr.append("/"); + pathStr.append(rhs.pathStr); + } + + return *this; + } + + Path& operator/=(const char* rhs) + { + if (empty()) + { + *this = rhs; + return *this; + } + + const size_t rhsLen = std::strlen(rhs); + if (rhsLen != 0) + { + if (rhs[0] == '/') + { +#ifdef BEEGFS_DEBUG + LogContext(__func__).logBacktrace(); +#endif + throw std::invalid_argument("Can't concatenate absolute path; " + "Base: " + pathStr + " Concatenated path: " + rhs + "."); + } + + dirSeparators.push_back(pathStr.length()); + + for (size_t i = 0; i < rhsLen; i++) + if (rhs[i] == '/') + dirSeparators.push_back(pathStr.length() + i + 1); + + pathStr.reserve(pathStr.length() + 1 + rhsLen); + pathStr.append("/"); + pathStr.append(rhs); + } + + return *this; + } + + Path& operator/=(const std::string& rhs) + { + return operator/=(rhs.c_str()); + } + + friend Path operator/(const Path& lhs, const Path& rhs) + { + Path res(lhs); + res /= rhs; + return res; + } + + friend Path operator/(const Path& lhs, const std::string& rhs) + { + Path res(lhs); + res /= rhs; + return res; + } + + friend Path operator/(const std::string& lhs, const Path& rhs) + { + Path res(lhs); + res /= rhs; + return res; + } + + friend Path operator/(const Path& lhs, const char* rhs) + { + Path res(lhs); + res /= rhs; + return res; + } + + friend Path operator/(const char* lhs, const Path& rhs) + { + Path res(lhs); + res /= rhs; + return res; + } + + friend bool operator==(const Path& lhs, const Path& rhs) + { + // No need to compare dirSeparators here + return lhs.pathStr == rhs.pathStr; + } + + friend bool operator!=(const Path& lhs, const Path& rhs) + { + return lhs.pathStr != rhs.pathStr; + } + + static void serialize(const Path* obj, Serializer& ser) + { + ser % obj->pathStr; + } + + static void serialize(Path* obj, Deserializer& des) + { + des % obj->pathStr; + obj->updateDirSeparators(); + } + + friend std::ostream& operator<<(std::ostream& ostr, const Path& p) + { + ostr << p.pathStr; + return ostr; + } + + private: + std::string pathStr; + std::vector dirSeparators; + + /** + * Updates the dirSeparators vector and compresses any consecutive directory separators + * contained in the path string. + */ + void updateDirSeparators() + { + std::string::size_type inputIndex = 0; + std::string::size_type outputIndex = 0; + + // Compress consecutive slashes + while (inputIndex < pathStr.size()) + { + pathStr[outputIndex++] = pathStr[inputIndex++]; + + if (pathStr[inputIndex - 1] == '/') + { + dirSeparators.push_back(outputIndex - 1); + while (pathStr[inputIndex] == '/') + inputIndex++; + } + } + + // Remove trailing slash if there is one + if (!pathStr.empty() && pathStr[outputIndex - 1] == '/') + { + pathStr.resize(outputIndex - 1); + dirSeparators.pop_back(); + } + else + { + pathStr.resize(outputIndex); + } + } +}; + diff --git a/common/source/common/storage/PathInfo.h b/common/source/common/storage/PathInfo.h new file mode 100644 index 0000000..7f8ea6d --- /dev/null +++ b/common/source/common/storage/PathInfo.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include + +#define PATHINFO_FEATURE_ORIG 1 /* inidicate chunks are stored with origParentUID and + and origParentEntryID, i.e. 2014.01 style layout */ +#define PATHINFO_FEATURE_ORIG_UNKNOWN 2 /* indicates _FEATURE_ORIG is unknown and needs to be + requested from the meta-inode */ +#define PATHINFO_FEATURE_IS_STUB 4 /* Indicates a stub file, just a placeholder without actual + data being present within BeeGFS filesystem */ + +class PathInfo; // forward declaration + +typedef std::list PathInfoList; +typedef PathInfoList::iterator PathInfoListIter; +typedef PathInfoList::const_iterator PathInfoListConstIter; + +/** + * class PathInfo - Represents information about a file required to locate its inode or chunk + * files. It also includes flags for various features such as original parent information and + * stub file tracking. + */ +class PathInfo +{ + public: + PathInfo() + : flags(0), origParentUID(0) + { + } + + PathInfo(unsigned origParentUID, std::string origParentEntryID, int flags) : + flags(flags), origParentUID(origParentUID), origParentEntryID(origParentEntryID) + { + // everything is set in initializer list + } + + + private: + int32_t flags; + + /* orig values do not change on file updates, such as rename or chown and are used + * to get the chunk file path */ + uint32_t origParentUID; + std::string origParentEntryID; + + public: + // inliners + + void set(unsigned origParentUID, std::string origParentEntryID, int flags) + { + this->origParentUID = origParentUID; + this->origParentEntryID = origParentEntryID; + this->flags = flags; + } + + void set(PathInfo *newPathInfo) + { + this->origParentUID = newPathInfo->getOrigUID(); + this->origParentEntryID = newPathInfo->getOrigParentEntryID(); + this->flags = newPathInfo->getFlags(); + } + + int getFlags() const + { + return this->flags; + } + + void setFlags(int flags) + { + this->flags = flags; + } + + unsigned getOrigUID() const + { + return this->origParentUID; + } + + void setOrigUID(unsigned origParentUID) + { + this->origParentUID = origParentUID; + } + + std::string getOrigParentEntryID() const + { + return this->origParentEntryID; + } + + void setOrigParentEntryID(std::string origParentEntryID) + { + this->origParentEntryID = origParentEntryID; + } + + /** + * Returns true for new 2014.01 style layout with timestamp and user dirs, false for 2012.10 + * style layout with only hashdirs. + */ + bool hasOrigFeature() const + { + return this->flags & PATHINFO_FEATURE_ORIG ? true : false; + } + + bool isStub() const + { + return this->flags & PATHINFO_FEATURE_IS_STUB ? true : false; + } + + bool operator==(const PathInfo& other) const + { + // consider this to be equal if ALL static parameters are equal + if (origParentUID != other.origParentUID) + return false; + + if (origParentEntryID != other.origParentEntryID) + return false; + + if (flags != other.flags) + return false; + + return true; + } + + bool operator!=(const PathInfo& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->flags; + + if (obj->flags & PATHINFO_FEATURE_ORIG) + { + ctx + % obj->origParentUID + % serdes::stringAlign4(obj->origParentEntryID); + } + } +}; + diff --git a/common/source/common/storage/RdmaInfo.h b/common/source/common/storage/RdmaInfo.h new file mode 100644 index 0000000..c10b459 --- /dev/null +++ b/common/source/common/storage/RdmaInfo.h @@ -0,0 +1,107 @@ +#pragma once + +#ifdef BEEGFS_NVFS + +#define RDMA_MAX_DMA_COUNT 64 + +/** + * RdmaInfo contains the information supplied by the client to specify + * information required to RDMA read/write its buffers. Public members variables + * are initialized by RDMA read/write message serialization. + * + * This class implements an iterator pattern, which allows stateful consumption + * of the buffer information. + */ +class RdmaInfo +{ + public: + // these are public to support message serialization + size_t count; + int32_t tag; + uint64_t key; + uint64_t dmaAddress[RDMA_MAX_DMA_COUNT]; + uint64_t dmaLength[RDMA_MAX_DMA_COUNT]; + uint64_t dmaOffset[RDMA_MAX_DMA_COUNT]; + int status; + + private: + size_t cur; + + public: + RdmaInfo() + { + status = 0; + count = 0; + cur = 0; + tag = 0; + key = 0; + memset(dmaAddress, 0, sizeof(dmaAddress)); + memset(dmaLength, 0, sizeof(dmaLength)); + memset(dmaOffset, 0, sizeof(dmaOffset)); + } + + /** + * Returns true if there are more buffers to iterate. + */ + inline bool more() const + { + return count > cur; + } + + /** + * Retrieve information to access a remote buffer and advance the buffer + * iterator. + * + * Returns true if there is a buffer to iterate. False indicates that the + * set of buffers has been exhausted. + * + * @param addr initialized to the remote buffer's address + * @param len initialized to the remote buffer's length + * @param offset initialized to the remote buffer's offset + */ + inline bool next(uint64_t& addr, uint64_t& len, uint64_t& offset) + { + if (!more()) + return false; + addr = dmaAddress[cur]; + len = dmaLength[cur]; + offset = dmaOffset[cur]; + cur++; + return true; + } + + /** + * Returns true if the RDMA information is valid, i.e. buffer count is sane. + */ + inline bool isValid() const + { + return count < RDMA_MAX_DMA_COUNT; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->count + % obj->tag + % obj->key; + + if (!obj->isValid()) + { + LogContext("RdmaInfo").log(Log_ERR, + "Received a request for " + StringTk::uint64ToStr(obj->count) + + " buffers, maximum is " + StringTk::intToStr(RDMA_MAX_DMA_COUNT)); + return; + } + + for (size_t i = 0; i < obj->count; i++) + { + ctx + % obj->dmaAddress[i] + % obj->dmaLength[i] + % obj->dmaOffset[i]; + } + } +}; +#endif /* BEEGFS_NVFS */ + diff --git a/common/source/common/storage/RemoteStorageTarget.h b/common/source/common/storage/RemoteStorageTarget.h new file mode 100644 index 0000000..8ee5813 --- /dev/null +++ b/common/source/common/storage/RemoteStorageTarget.h @@ -0,0 +1,149 @@ +#pragma once + +#include +#include + +class RemoteStorageTarget +{ + public: + RemoteStorageTarget() {} + + RemoteStorageTarget(std::vector rstIds) : + RemoteStorageTarget(DEFAULT_COOLDOWN_PERIOD, DEFAULT_FILE_POLICY, rstIds) + { + } + + RemoteStorageTarget(uint16_t coolDownPeriod, uint16_t policyList, + const std::vector& rstIds) : + majorVersion(DEFAULT_MAJOR_VER), + minorVersion(DEFAULT_MINOR_VER), + coolDownPeriod(coolDownPeriod), reserved(0), filePolicies(policyList), rstIdVec(rstIds) + { + } + + void set(RemoteStorageTarget* other) + { + set(*other); + } + + void set(const RemoteStorageTarget& other) + { + this->majorVersion = other.majorVersion; + this->minorVersion = other.minorVersion; + this->coolDownPeriod = other.coolDownPeriod; + this->reserved = other.reserved; + this->filePolicies = other.filePolicies; + this->rstIdVec = other.rstIdVec; + } + + // Reset all member variables to their initial/default state. + // Effectively invalidates RST object by zeroing version and clearing all parameters. + void reset() + { + majorVersion = 0; // Invalidate RST object + minorVersion = 0; + coolDownPeriod = 0; + reserved = 0; + filePolicies = 0; + rstIdVec.clear(); // Clear all remote target IDs + } + + bool hasInvalidIds() const + { + return std::find(rstIdVec.begin(), rstIdVec.end(), 0) != rstIdVec.end(); + } + + bool hasDuplicateIds() const + { + std::set uniqueIds(rstIdVec.begin(), rstIdVec.end()); + return uniqueIds.size() != rstIdVec.size(); + } + + bool hasInvalidVersion() const + { + return !majorVersion; + } + + std::pair validateWithDetails() const + { + std::string invalidReasons; + + if (hasInvalidVersion()) + invalidReasons += "Has invalid version. "; + if (hasInvalidIds()) + invalidReasons += "Contains invalid RSTId 0. "; + if (hasDuplicateIds()) + invalidReasons += "Contains duplicate RSTIds. "; + + return std::make_pair(invalidReasons.empty(), invalidReasons); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->majorVersion + % obj->minorVersion + % obj->coolDownPeriod + % obj->reserved + % obj->filePolicies + % obj->rstIdVec; + } + + std::vector getRstIdVector() const + { + return rstIdVec; + } + + uint16_t getCoolDownPeriod() const + { + return coolDownPeriod; + } + + uint16_t getFilePolicies() const + { + return filePolicies; + } + + void setRstIds(std::vector ids) + { + this->rstIdVec = ids; + } + + std::string toStr() const + { + std::stringstream ss; + ss << "majorVersion: " << std::to_string(majorVersion) << "; " + << "minorVersion: " << std::to_string(minorVersion) << "; " + << "coolDownPeriod: " << std::to_string(coolDownPeriod) << "; " + << "reserved: " << std::to_string(reserved) << "; " + << "filePolicies: " << std::to_string(filePolicies) << "; " + << "rstIds: ["; + + bool first = true; + for (auto id : rstIdVec) + { + if (!first) + ss << ","; + ss << std::to_string(id); + first = false; + } + ss << "]"; + return ss.str(); + } + + private: + uint8_t majorVersion = 0; + uint8_t minorVersion = 0; + uint16_t coolDownPeriod = 0; + uint16_t reserved = 0; + uint16_t filePolicies = 0; + std::vector rstIdVec; + + // constants (for default values) + static const uint8_t DEFAULT_MAJOR_VER = 1; + static const uint8_t DEFAULT_MINOR_VER = 0; + static const uint16_t DEFAULT_COOLDOWN_PERIOD = 120; + static const uint16_t DEFAULT_FILE_POLICY = 0x0001; +}; + diff --git a/common/source/common/storage/StatData.cpp b/common/source/common/storage/StatData.cpp new file mode 100644 index 0000000..4e6f58a --- /dev/null +++ b/common/source/common/storage/StatData.cpp @@ -0,0 +1,170 @@ +/* + * Information provided by stat() + */ + +#include +#include +#include + +/* storing the actual number of blocks means some overhead for us, so define grace blocks for the + * actual vs. caculated number of blocks. */ +#define STATDATA_SPARSE_GRACEBLOCKS (8) + + +/** + * Update values relevant for FileInodes only. + * + * Note: This version is compatible with sparse files. + */ +void StatData::updateDynamicFileAttribs(ChunkFileInfoVec& fileInfoVec, StripePattern* stripePattern) +{ + // we use the dynamic attribs only if they have been initialized (=> storageVersion != 0) + + // dynamic attrib algo: + // walk over all the stripeNodes and... + // - find last node with max number of (possibly incomplete) chunks + // - find the latest modification- and lastAccessTimeSecs + + + // scan for an initialized index... + /* note: we assume that in most cases, the user will stat not-opened files (which have + all storageVersions set to 0), so we optimize for that case. */ + + unsigned numValidDynAttribs = 0; + + size_t numStripeTargets = stripePattern->getNumStripeTargetIDs(); + + for (size_t i=0; i < fileInfoVec.size(); i++) + { + if (fileInfoVec[i].getStorageVersion() ) + { + numValidDynAttribs++; + } + } + + if (numValidDynAttribs == 0) + { // no valid dyn attrib element found => use static attribs and nothing to do + return; + } + + /* we found a valid dyn attrib index => start with first target anyways to avoid false file + length computations... */ + + unsigned lastMaxNumChunksIndex = 0; + int64_t maxNumChunks = fileInfoVec[0].getNumChunks(); + + // the first mtime on the metadata server can be newer then the mtime on the storage server due + // to minor time difference between the servers, so the ctime should be updated after every + // close, this issue should be fixed by the usage of the max(...) function + // note: only use max(MDS, first chunk) if we don't have information from all chunks, because + // if we have information from all chunks and the info on the MDS differs, info on the MDS must + // be wrong and therefore should be overwritten + int64_t newModificationTimeSecs; + int64_t newLastAccessTimeSecs; + if (numValidDynAttribs == numStripeTargets) + { + newModificationTimeSecs = fileInfoVec[0].getModificationTimeSecs(); + + newLastAccessTimeSecs = fileInfoVec[0].getLastAccessTimeSecs(); + } + else + { + newModificationTimeSecs = std::max(fileInfoVec[0].getModificationTimeSecs(), + settableFileAttribs.modificationTimeSecs); + + newLastAccessTimeSecs = std::max(fileInfoVec[0].getLastAccessTimeSecs(), + settableFileAttribs.lastAccessTimeSecs); + } + + setTargetChunkBlocks(0, fileInfoVec[0].getNumBlocks(), numStripeTargets); + + int64_t newFileSize; + + for (unsigned target = 1; target < fileInfoVec.size(); target++) + { + /* note: we cannot ignore storageVersion==0 here, because that would lead to wrong file + * length computations. + * note2: fileInfoVec will be initialized once we have read meta data from disk, so if a + * storage target does not answer or no request was sent to it, + * we can still use old data from fileInfoVec + * + * */ + + int64_t currentNumChunks = fileInfoVec[target].getNumChunks(); + + if(currentNumChunks >= maxNumChunks) + { + lastMaxNumChunksIndex = target; + maxNumChunks = currentNumChunks; + } + + int64_t chunkModificationTimeSecs = fileInfoVec[target].getModificationTimeSecs(); + if(chunkModificationTimeSecs > newModificationTimeSecs) + newModificationTimeSecs = chunkModificationTimeSecs; + + int64_t currentLastAccessTimeSecs = fileInfoVec[target].getLastAccessTimeSecs(); + if(currentLastAccessTimeSecs > newLastAccessTimeSecs) + newLastAccessTimeSecs = currentLastAccessTimeSecs; + + setTargetChunkBlocks(target, fileInfoVec[target].getNumBlocks(), numStripeTargets); + } + + if(maxNumChunks) + { // we have chunks, so "fileLength > 0" + + // note: we do all this complex filesize calculation stuff here to support sparse files + + // every target before lastMax-target must have the same number of (complete) chunks + int64_t lengthBeforeLastMaxIndex = lastMaxNumChunksIndex * maxNumChunks * + stripePattern->getChunkSize(); + + // every target after lastMax-target must have exactly one chunk less than the lastMax-target + int64_t lengthAfterLastMaxIndex = (fileInfoVec.size() - lastMaxNumChunksIndex - 1) * + (maxNumChunks - 1) * stripePattern->getChunkSize(); + + // all the prior calcs are based on the lastMax-target size, so we can simply use that one + int64_t lengthLastMaxNode = fileInfoVec[lastMaxNumChunksIndex].getFileSize(); + + int64_t totalLength = lengthBeforeLastMaxIndex + lengthAfterLastMaxIndex + lengthLastMaxNode; + + newFileSize = totalLength; + + uint64_t calcBlocks = newFileSize >> BLOCK_SHIFT; + uint64_t usedBlockSum = getNumBlocks(); + + if (usedBlockSum + STATDATA_SPARSE_GRACEBLOCKS < calcBlocks) + setSparseFlag(); + else + unsetSparseFlag(); + + } + else + { // no chunks, no filesize + newFileSize = 0; + } + + + // now update class values + + setLastAccessTimeSecs(newLastAccessTimeSecs); + + // mtime or fileSize updated, so also ctime needs to be updated + if (newModificationTimeSecs != getModificationTimeSecs() || + newFileSize != getFileSize()) + setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + + setModificationTimeSecs(newModificationTimeSecs); + setFileSize(newFileSize); +} + +bool StatData::operator==(const StatData& second) const +{ + return flags == second.flags + && settableFileAttribs == second.settableFileAttribs + && chunkBlocksVec == second.chunkBlocksVec + && creationTimeSecs == second.creationTimeSecs + && attribChangeTimeSecs == second.attribChangeTimeSecs + && fileSize == second.fileSize + && nlink == second.nlink + && metaVersion == second.metaVersion; +} diff --git a/common/source/common/storage/StatData.h b/common/source/common/storage/StatData.h new file mode 100644 index 0000000..d8848dc --- /dev/null +++ b/common/source/common/storage/StatData.h @@ -0,0 +1,463 @@ +/* + * Information provided by stat() + * + * Remember to keep this class in sync with StatData.h of fhgfs-client_module! + * However, there are a few differences to the fhgfs-client StatData as server side StatData also + * need to handle disk serialization. + */ + +#pragma once + +#include +#include +#include +#include +#include "ChunksBlocksVec.h" + +#define STATDATA_FEATURE_SPARSE_FILE 1 + +enum StatDataFormat +{ + // nibbles represent the old serialize() arguments isDiskDirInode, hasFlags, isNet + StatDataFormat_NET = 0x011, + StatDataFormat_FILEINODE = 0x010, + StatDataFormat_DENTRYV4 = 0x000, + StatDataFormat_DIRINODE = 0x110, + StatDataFormat_DIRINODE_NOFLAGS = 0x100, + + StatDataFormat_Flag_IsNet = 0x001, + StatDataFormat_Flag_HasFlags = 0x010, + StatDataFormat_Flag_DiskDirInode = 0x100, +}; + + +struct MirroredTimestamps +{ + int64_t creationTimeSecs; // real creation time + int64_t attribChangeTimeSecs; // this corresponds to unix ctime + int64_t modificationTimeSecs; // unix mtime + int64_t lastAccessTimeSecs; // unix atime + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->creationTimeSecs + % obj->attribChangeTimeSecs + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs; + } +}; + +class StatData +{ + friend class TestSerialization; + + public: + static const unsigned BLOCK_SIZE = 512; + static const unsigned BLOCK_SHIFT = 9; + + StatData() + { + this->flags = 0; + } + + StatData(int64_t fileSize, SettableFileAttribs* settableAttribs, + int64_t createTime, int64_t attribChangeTime, unsigned nlink, unsigned metaVersion) + { + this->flags = 0; + + this->fileSize = fileSize; + this->settableFileAttribs = *settableAttribs; + this->creationTimeSecs = createTime; + this->attribChangeTimeSecs = attribChangeTime; + this->nlink = nlink; + this->metaVersion = metaVersion; + } + + // used on file creation + StatData(int mode, int64_t uid, int64_t gid, size_t numTargets, + int64_t createTime = TimeAbs().getTimeval()->tv_sec): + chunkBlocksVec(numTargets) + { + this->flags = 0; + + this->fileSize = 0; + this->creationTimeSecs = createTime; + this->attribChangeTimeSecs = createTime; + this->nlink = 1; + this->metaVersion = 0; + + this->settableFileAttribs.mode = mode; + this->settableFileAttribs.userID = uid; + this->settableFileAttribs.groupID = gid; + this->settableFileAttribs.modificationTimeSecs = createTime; + this->settableFileAttribs.lastAccessTimeSecs = createTime; + + } + + private: + + uint32_t flags; + + int64_t fileSize; + int64_t creationTimeSecs; // real creation time + int64_t attribChangeTimeSecs; // this corresponds to unix ctime + uint32_t nlink; + + SettableFileAttribs settableFileAttribs; + + uint32_t metaVersion; // inc'ed when internal metadata is modified (for cache invalidation) + + ChunksBlocksVec chunkBlocksVec; // serialized to disk, but not over network + + + public: + + void updateDynamicFileAttribs(ChunkFileInfoVec& fileInfoVec, StripePattern* stripePattern); + + bool operator==(const StatData& second) const; + + bool operator!=(const StatData& other) const { return !(*this == other); } + + // setters + + /** + * Just used to init all values to something to mute compiler warnings, e.g. when we send + * this struct in a message after a stat error and thus know that the receiver will not use + * the values. + */ + void setAllFake() + { + this->flags = 0; + + this->fileSize = 0; + this->creationTimeSecs = 0; + this->attribChangeTimeSecs = 0; + this->nlink = 1; + this->metaVersion = 0; + + this->settableFileAttribs.mode = 0; + this->settableFileAttribs.userID = 0; + this->settableFileAttribs.groupID = 0; + this->settableFileAttribs.modificationTimeSecs = 0; + this->settableFileAttribs.lastAccessTimeSecs = 0; + } + + /** + * Only supposed to be used by the DirInode constructor + */ + void setInitNewDirInode(SettableFileAttribs* settableAttribs, int64_t currentTime) + { + this->fileSize = 0; // irrelevant for dirs, numSubdirs + numFiles on stat() + this->creationTimeSecs = currentTime; + this->attribChangeTimeSecs = currentTime; + this->nlink = 2; // "." and ".." for now only + + this->settableFileAttribs = *settableAttribs; + + this->metaVersion = 0; + } + + /** + * Update statData with the new given data + * + * Note: We do not update everything (such as creationTimeSecs), but only values that + * really make sense to be updated at all. + */ + void set(StatData* newStatData) + { + this->fileSize = newStatData->fileSize; + this->attribChangeTimeSecs = newStatData->attribChangeTimeSecs; + this->nlink = newStatData->nlink; + + this->settableFileAttribs = newStatData->settableFileAttribs; + + this->metaVersion = newStatData->metaVersion; + + this->flags = newStatData->flags; + this->chunkBlocksVec = newStatData->chunkBlocksVec; + } + + void setSparseFlag() + { + this->flags |= STATDATA_FEATURE_SPARSE_FILE; + } + + void unsetSparseFlag() + { + this->flags &= ~STATDATA_FEATURE_SPARSE_FILE; + } + + // getters + void get(StatData& outStatData) + { + outStatData.fileSize = this->fileSize; + outStatData.creationTimeSecs = this->creationTimeSecs; + outStatData.attribChangeTimeSecs = this->attribChangeTimeSecs; + outStatData.nlink = this->nlink; + + outStatData.settableFileAttribs = this->settableFileAttribs; + + outStatData.metaVersion = this->metaVersion; + } + + MirroredTimestamps getMirroredTimestamps() const + { + return { + creationTimeSecs, + attribChangeTimeSecs, + settableFileAttribs.modificationTimeSecs, + settableFileAttribs.lastAccessTimeSecs, + }; + } + + void setMirroredTimestamps(const MirroredTimestamps& ts) + { + creationTimeSecs = ts.creationTimeSecs; + attribChangeTimeSecs = ts.attribChangeTimeSecs; + settableFileAttribs.modificationTimeSecs = ts.modificationTimeSecs; + settableFileAttribs.lastAccessTimeSecs = ts.lastAccessTimeSecs; + } + + void setCreationTimeSecs(int64_t ctime) + { + this->creationTimeSecs = ctime; + } + + void setAttribChangeTimeSecs(int64_t attribChangeTimeSecs) + { + this->attribChangeTimeSecs = attribChangeTimeSecs; + } + + void setFileSize(int64_t fileSize) + { + this->fileSize = fileSize; + } + + void setMode(int mode) + { + this->settableFileAttribs.mode = mode; + } + + void setModificationTimeSecs(int64_t mtime) + { + this->settableFileAttribs.modificationTimeSecs = mtime; + } + + void setLastAccessTimeSecs(int64_t atime) + { + this->settableFileAttribs.lastAccessTimeSecs = atime; + } + + void setUserID(unsigned uid) + { + this->settableFileAttribs.userID = uid; + } + + void setGroupID(unsigned gid) + { + this->settableFileAttribs.groupID = gid; + } + + void setSettableFileAttribs(SettableFileAttribs newAttribs) + { + this->settableFileAttribs = newAttribs; + } + + /** + * Set the number of used blocks (of a chunk file) for the given target + */ + void setTargetChunkBlocks(unsigned target, uint64_t chunkBlocks, size_t numStripeTargets) + { + this->chunkBlocksVec.setNumBlocks(target, chunkBlocks, numStripeTargets); + } + + /** + * Return the number of blocks (of a chunk file) for the given target + */ + uint64_t getTargetChunkBlocks(unsigned target) const + { + return this->chunkBlocksVec.getNumBlocks(target); + } + + /** + * Return the number of used blocks. If the file is a sparse file we use values + * from chunkBlocksVec. If the sparse-flag is not set we estimaete the number of blocks + * from the file size. + * + * Note: Even if the file is a sparse file, some chunks might have the estimated number of + * blocks only, depending on if chunkBlocksVec was ever updated with exact values. + */ + uint64_t getNumBlocks() const + { + if (getIsSparseFile() ) + return this->chunkBlocksVec.getBlockSum(); + else + return (this->fileSize + BLOCK_SIZE - 1) >> BLOCK_SHIFT; + } + + bool getIsSparseFile() const + { + if (this->flags & STATDATA_FEATURE_SPARSE_FILE) + return true; + else + return false; + } + + void decNumHardLinks() + { + this->nlink--; + } + + /** + * Increase or decrease the link count by the given value + */ + void incDecNumHardLinks(int value) + { + this->nlink += value; + } + + void setNumHardLinks(unsigned numHardLinks) + { + this->nlink = numHardLinks; + } + + void setMetaVersionStat(unsigned metaVersion) + { + this->metaVersion = metaVersion; + } + + + // getters + int64_t getFileSize() const + { + return this->fileSize; + } + + int getCreationTimeSecs() const + { + return this->creationTimeSecs; + } + + int64_t getAttribChangeTimeSecs() const + { + return this->attribChangeTimeSecs; + } + + int64_t getModificationTimeSecs() const + { + return this->settableFileAttribs.modificationTimeSecs; + } + + int64_t getLastAccessTimeSecs() const + { + return this->settableFileAttribs.lastAccessTimeSecs; + } + + unsigned getNumHardlinks() const + { + return this->nlink; + } + + SettableFileAttribs* getSettableFileAttribs(void) + { + return &this->settableFileAttribs; + } + + unsigned getMode() const + { + return this->settableFileAttribs.mode; + } + + unsigned getUserID() const + { + return this->settableFileAttribs.userID; + } + + unsigned getGroupID() const + { + return this->settableFileAttribs.groupID; + } + + unsigned getMetaVersion() const + { + return this->metaVersion; + } + + template + static void serializeFmt(StatDataFormat format, This obj, Ctx& ctx) + { + if (format & (StatDataFormat_Flag_HasFlags | StatDataFormat_Flag_IsNet) ) + { + ctx + % obj->flags + % obj->settableFileAttribs.mode; + + if (format & StatDataFormat_Flag_IsNet) + { + // this is only serialized, because only the client reads it + uint64_t numBlocks = obj->getNumBlocks(); + ctx % numBlocks; + } + else + if (obj->getIsSparseFile() ) + ctx % obj->chunkBlocksVec; + } + + ctx + % obj->creationTimeSecs + % obj->settableFileAttribs.lastAccessTimeSecs + % obj->settableFileAttribs.modificationTimeSecs + % obj->attribChangeTimeSecs; + + if (!(format & StatDataFormat_Flag_DiskDirInode) ) + { + ctx + % obj->fileSize + % obj->nlink + % obj->metaVersion; + } + + ctx + % obj->settableFileAttribs.userID + % obj->settableFileAttribs.groupID; + + if (!(format & StatDataFormat_Flag_HasFlags) ) + ctx % obj->settableFileAttribs.mode; + } + + template + struct SerializeAs + { + StatDataFormat format; + Ptr ptr; + + friend Serializer& operator%(Serializer& ctx, SerializeAs sa) + { + StatData::serializeFmt(sa.format, sa.ptr, ctx); + return ctx; + } + + friend Deserializer& operator%(Deserializer& ctx, SerializeAs sa) + { + StatData::serializeFmt(sa.format, sa.ptr, ctx); + return ctx; + } + }; + + SerializeAs serializeAs(StatDataFormat format) const + { + SerializeAs result = { format, this }; + return result; + } + + SerializeAs serializeAs(StatDataFormat format) + { + SerializeAs result = { format, this }; + return result; + } +}; + + + diff --git a/common/source/common/storage/StorageDefinitions.h b/common/source/common/storage/StorageDefinitions.h new file mode 100644 index 0000000..8481fba --- /dev/null +++ b/common/source/common/storage/StorageDefinitions.h @@ -0,0 +1,142 @@ +#pragma once + +/* + * Remember to keep these definitions in sync with StorageDefinitions.h of fhgfs_client_module!!! + */ + +#include +#include + + +// open rw flags +#define OPENFILE_ACCESS_READ 1 +#define OPENFILE_ACCESS_WRITE 2 +#define OPENFILE_ACCESS_READWRITE 4 +// open extra flags +#define OPENFILE_ACCESS_APPEND 8 +#define OPENFILE_ACCESS_TRUNC 16 +#define OPENFILE_ACCESS_DIRECT 32 /* for direct IO */ +#define OPENFILE_ACCESS_SYNC 64 /* for sync'ed IO */ +// open masks +#define OPENFILE_ACCESS_MASK_RW \ + (OPENFILE_ACCESS_READ | OPENFILE_ACCESS_WRITE | OPENFILE_ACCESS_READWRITE) +#define OPENFILE_ACCESS_MASK_EXTRA (~OPENFILE_ACCESS_MASK_RW) + + +// set attribs flags +#define SETATTR_CHANGE_MODE 1 +#define SETATTR_CHANGE_USERID 2 +#define SETATTR_CHANGE_GROUPID 4 +#define SETATTR_CHANGE_MODIFICATIONTIME 8 +#define SETATTR_CHANGE_LASTACCESSTIME 16 + + +// entry and append lock type flags +#define ENTRYLOCKTYPE_UNLOCK 1 +#define ENTRYLOCKTYPE_EXCLUSIVE 2 +#define ENTRYLOCKTYPE_SHARED 4 +#define ENTRYLOCKTYPE_NOWAIT 8 /* operation may not block if lock not available */ +#define ENTRYLOCKTYPE_CANCEL 16 /* cancel all granted and pending locks for given handle + (normally on client file close) */ +// entry lock mask +#define ENTRYLOCKTYPE_LOCKOPS_ADD (ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED) +#define ENTRYLOCKTYPE_LOCKOPS_REMOVE (ENTRYLOCKTYPE_UNLOCK | ENTRYLOCKTYPE_CANCEL) + +struct SettableFileAttribs; +typedef struct SettableFileAttribs SettableFileAttribs; + + +enum DirEntryType +{ // don't use these directly, use the DirEntryType_IS...() macros below to check entry types + DirEntryType_INVALID = 0, DirEntryType_DIRECTORY = 1, DirEntryType_REGULARFILE = 2, + DirEntryType_SYMLINK = 3, DirEntryType_BLOCKDEV = 4, DirEntryType_CHARDEV = 5, + DirEntryType_FIFO = 6, DirEntryType_SOCKET = 7 +}; + +enum FileInodeMode +{ + MODE_INVALID = -1, + MODE_DEINLINE, + MODE_REINLINE +}; + +template<> +struct SerializeAs { + typedef uint8_t type; +}; + +inline std::ostream& operator<<(std::ostream& os, DirEntryType t) { + switch (t) { + case DirEntryType_DIRECTORY: return os << "directory"; + case DirEntryType_REGULARFILE: return os << "file"; + case DirEntryType_SYMLINK: return os << "symlink"; + case DirEntryType_BLOCKDEV: return os << "block device node"; + case DirEntryType_CHARDEV: return os << "character device node"; + case DirEntryType_FIFO: return os << "pipe"; + case DirEntryType_SOCKET: return os << "unix domain socket"; + + default: + return os << "invalid"; + } +} + +#define DirEntryType_ISVALID(dirEntryType) (dirEntryType != DirEntryType_INVALID) +#define DirEntryType_ISDIR(dirEntryType) (dirEntryType == DirEntryType_DIRECTORY) +#define DirEntryType_ISREGULARFILE(dirEntryType) (dirEntryType == DirEntryType_REGULARFILE) +#define DirEntryType_ISSYMLINK(dirEntryType) (dirEntryType == DirEntryType_SYMLINK) +#define DirEntryType_ISBLOCKDEV(dirEntryType) (dirEntryType == DirEntryType_BLOCKDEV) +#define DirEntryType_ISCHARDEV(dirEntryType) (dirEntryType == DirEntryType_CHARDEV) +#define DirEntryType_ISFIFO(dirEntryType) (dirEntryType == DirEntryType_FIFO) +#define DirEntryType_ISSOCKET(dirEntryType) (dirEntryType == DirEntryType_SOCKET) + +/* @return true for any kind of file, including symlinks and special files */ +#define DirEntryType_ISFILE(dirEntryType) ( (dirEntryType >= 2) && (dirEntryType <= 7) ) + +typedef std::list DirEntryTypeList; +typedef DirEntryTypeList::iterator DirEntryTypeListIter; +typedef DirEntryTypeList::const_iterator DirEntryTypeListConstIter; + +enum EntryLockRequestType + {EntryLockRequestType_ENTRYFLOCK=0, EntryLockRequestType_RANGEFLOCK=1, + EntryLockRequestType_ENTRYCOHERENCE=2, EntryLockRequestType_RANGECOHERENCE=3}; + + +/** + * Note: Typically used in combination with a value of SETATTR_CHANGE_...-Flags to determine which + * of the fields are actually used + */ +struct SettableFileAttribs +{ + int32_t mode; + uint32_t userID; + uint32_t groupID; + int64_t modificationTimeSecs; // unix mtime + int64_t lastAccessTimeSecs; // unix atime + + bool operator==(const SettableFileAttribs& other) const + { + return mode == other.mode + && userID == other.userID + && groupID == other.groupID + && modificationTimeSecs == other.modificationTimeSecs + && lastAccessTimeSecs == other.lastAccessTimeSecs; + } + + bool operator!=(const SettableFileAttribs& other) const { return !(*this == other); } +}; + + +// the TargetPathMap and the TargetFDMap is not required in StorageDefinitions.h of the +// fhgfs_client_module +typedef std::map TargetPathMap; // keys: targetIDs, values: paths +typedef TargetPathMap::iterator TargetPathMapIter; +typedef TargetPathMap::const_iterator TargetPathMapCIter; +typedef TargetPathMap::value_type TargetPathMapVal; + +// keys: targetIDs, values: file-descriptors +typedef std::map TargetFDMap; +typedef TargetFDMap::iterator TargetFDMapIter; +typedef TargetFDMap::const_iterator TargetFDMapCIter; +typedef TargetFDMap::value_type TargetFDMapVal; + + diff --git a/common/source/common/storage/StorageErrors.cpp b/common/source/common/storage/StorageErrors.cpp new file mode 100644 index 0000000..9b548ae --- /dev/null +++ b/common/source/common/storage/StorageErrors.cpp @@ -0,0 +1,112 @@ +#include +#include "StorageErrors.h" + +#include + +/** + * Note: This is based on the FhgfsOpsErr entries + * Note: We use EREMOTEIO as a generic error here + */ +struct FhgfsOpsErrListEntry const __FHGFSOPS_ERRLIST[] = +{ + {"Success", 0}, // FhgfsOpsErr_SUCCESS + {"Internal error", EREMOTEIO}, // FhgfsOpsErr_INTERNAL + {"Interrupted system call", EINTR}, // FhgfsOpsErr_INTERRUPTED + {"Communication error", ECOMM}, // FhgfsOpsErr_COMMUNICATION + {"Communication timeout", ETIMEDOUT}, // FhgfsOpsErr_COMMTIMEDOUT + {"Unknown node", EREMOTEIO}, // FhgfsOpsErr_UNKNOWNNODE + {"Node is not owner of entry", EREMOTEIO}, // FhgfsOpsErr_NOTOWNER + {"Entry exists already", EEXIST}, // FhgfsOpsErr_EXISTS + {"Path does not exist", ENOENT}, // FhgfsOpsErr_PATHNOTEXISTS + {"Entry is in use", EBUSY}, // FhgfsOpsErr_INUSE + {"Dynamic attributes of entry are outdated", EREMOTEIO}, // FhgfsOpsErr_INUSE + {"Removed", 999}, // former FhgfsOpsErr_PARENTTOSUBDIR, not used + {"Entry is not a directory", ENOTDIR}, // FhgfsOpsErr_NOTADIR + {"Directory is not empty", ENOTEMPTY}, // FhgfsOpsErr_NOTEMPTY + {"No space left", ENOSPC}, // FhgfsOpsErr_NOSPACE + {"Unknown storage target", EREMOTEIO}, // FhgfsOpsErr_UNKNOWNTARGET + {"Operation would block", EWOULDBLOCK}, // FhgfsOpsErr_WOULDBLOCK + {"Inode not inlined", EREMOTEIO}, // FhgfsOpsErr_INODENOTINLINED + {"Underlying file system error", EREMOTEIO}, // FhgfsOpsErr_SAVEERROR + {"Argument too large", EFBIG}, // FhgfsOpsErr_TOOBIG + {"Invalid argument", EINVAL}, // FhgfsOpsErr_INVAL + {"Bad memory address", EFAULT}, // FhgfsOpsErr_ADDRESSFAULT + {"Try again", EAGAIN}, // FhgfsOpsErr_AGAIN + {"Potential cache loss for open file handle. (Server crash detected.)" , EREMOTEIO}, /* + FhgfsOpsErr_STORAGE_SRV_CRASHED*/ + {"Permission denied", EPERM}, // FhgfsOpsErr_PERM + {"Quota exceeded", EDQUOT}, // FhgfsOpsErr_DQUOT + {"Out of memory", ENOMEM}, // FhgfsOpsErr_OUTOFMEM + {"Numerical result out of range", ERANGE}, // FhgfsOpsErr_RANGE + {"No data available", ENODATA}, // FhgfsOpsErr_NODATA + {"Operation not supported", EOPNOTSUPP}, // FhgfsOpsErr_NOTSUPP + {"Unknown storage pool", EINVAL}, // FhgfsOpsErr_UNKNOWNPOOL + {"Metadata version mismatch", ESTALE}, // FhgfsOpsErr_METAVERSIONMISMATCH + {"Inode is locked", EBUSY}, // FhgfsOpsErr_INODELOCKED + {NULL, 0} +}; + + +std::ostream& operator<<(std::ostream& os, FhgfsOpsErr errCode) +{ + size_t unsignedErrCode = (size_t)errCode; + + if(likely(unsignedErrCode < __FHGFSOPS_ERRLIST_SIZE) ) + return os << __FHGFSOPS_ERRLIST[errCode].errString; + + // error code unknown... + + LogContext log("FhgfsOpsErrTk::toErrString"); + + log.log(Log_CRITICAL, "Unknown errCode given: " + + StringTk::intToStr(errCode) + "/" + StringTk::uintToStr(errCode) + " " + "(dumping stack...)"); + + log.logBacktrace(); + + const boost::io::ios_all_saver ifs(os); + + os.flags(std::ios_base::dec); + os.width(0); + + return os << "Unknown error code " << int(errCode); +} + +/** + * @return positive linux error code + */ +int FhgfsOpsErrTk::toSysErr(FhgfsOpsErr errCode) +{ + size_t unsignedErrCode = (size_t)errCode; + + if(likely(unsignedErrCode < __FHGFSOPS_ERRLIST_SIZE) ) + return __FHGFSOPS_ERRLIST[errCode].sysErr; + + // error code unknown... + + LogContext log("FhgfsOpsErrTk::toSysErr"); + + log.log(Log_CRITICAL, "Unknown errCode given: " + + StringTk::intToStr(errCode) + "/" + StringTk::uintToStr(errCode) + " " + "(dumping stack...)"); + + log.logBacktrace(); + + return EPERM; +} + +/** + * @param errCode positive(!) system error code + * Note: Since FhgfsOpsErr <-> sysErr is not a one-to-one mapping, only some error codes are + * available here. Feel free to add more! + */ +FhgfsOpsErr FhgfsOpsErrTk::fromSysErr(const int errCode) +{ + for (auto* entry = __FHGFSOPS_ERRLIST; entry->errString; entry++) + { + if (entry->sysErr == errCode) + return FhgfsOpsErr(entry - __FHGFSOPS_ERRLIST); + } + + return FhgfsOpsErr_INTERNAL; +} diff --git a/common/source/common/storage/StorageErrors.h b/common/source/common/storage/StorageErrors.h new file mode 100644 index 0000000..d68285a --- /dev/null +++ b/common/source/common/storage/StorageErrors.h @@ -0,0 +1,98 @@ +#pragma once + +/* + * Remember to keep these definitions in sync with StorageErrors.h of fhgfs_client_module! + */ + + +#include +#include + + +#define __FHGFSOPS_ERRLIST_SIZE \ + ( (sizeof(__FHGFSOPS_ERRLIST) ) / (sizeof(struct FhgfsOpsErrListEntry) ) - 1) + /* -1 because last elem is NULL */ + +struct FhgfsOpsErrListEntry +{ + const char* errString; // human-readable error string + int sysErr; // positive linux system error code +}; + +extern struct FhgfsOpsErrListEntry const __FHGFSOPS_ERRLIST[]; + + +/** + * Note: Remember to keep this in sync with FHGFSOPS_ERRLIST + * + * Note: We need the negative dummy (-1) because some return values (like CommKit) cast this enum to + * negative int64_t and this leads to bad (positive) values when the enum isn't signed. So the dummy + * forces the compiler to make the enum a signed variable. + */ +enum FhgfsOpsErr +{ + FhgfsOpsErr_DUMMY_DONTUSEME = -1, /* see comment above */ + FhgfsOpsErr_SUCCESS = 0, + FhgfsOpsErr_INTERNAL = 1, + FhgfsOpsErr_INTERRUPTED = 2, + FhgfsOpsErr_COMMUNICATION = 3, + FhgfsOpsErr_COMMTIMEDOUT = 4, + FhgfsOpsErr_UNKNOWNNODE = 5, + FhgfsOpsErr_NOTOWNER = 6, + FhgfsOpsErr_EXISTS = 7, + FhgfsOpsErr_PATHNOTEXISTS = 8, + FhgfsOpsErr_INUSE = 9, + FhgfsOpsErr_DYNAMICATTRIBSOUTDATED = 10, + FhgfsOpsErr_PARENTTOSUBDIR = 11, + FhgfsOpsErr_NOTADIR = 12, + FhgfsOpsErr_NOTEMPTY = 13, + FhgfsOpsErr_NOSPACE = 14, + FhgfsOpsErr_UNKNOWNTARGET = 15, + FhgfsOpsErr_WOULDBLOCK = 16, + FhgfsOpsErr_INODENOTINLINED = 17, // inode is not inlined into the dentry + FhgfsOpsErr_SAVEERROR = 18, // saving to the underlying file system failed + FhgfsOpsErr_TOOBIG = 19, // corresponds to EFBIG + FhgfsOpsErr_INVAL = 20, // corresponds to EINVAL + FhgfsOpsErr_ADDRESSFAULT = 21, // corresponds to EFAULT + FhgfsOpsErr_AGAIN = 22, // corresponds to EAGAIN + FhgfsOpsErr_STORAGE_SRV_CRASHED = 23, /* Potential cache loss for open file handle. + (Server crash detected.)*/ + FhgfsOpsErr_PERM = 24, // corresponds to EPERM + FhgfsOpsErr_DQUOT = 25, // corresponds to EDQUOT (quota exceeded) + FhgfsOpsErr_OUTOFMEM = 26, // corresponds to ENOMEM (mem allocation failed) + FhgfsOpsErr_RANGE = 27, // corresponds to ERANGE (needed for xattrs) + FhgfsOpsErr_NODATA = 28, // corresponds to ENODATA==ENOATTR (xattr not found) + FhgfsOpsErr_NOTSUPP = 29, // corresponds to EOPNOTSUPP + FhgfsOpsErr_UNKNOWNPOOL = 30, // unknown storage pool + FhgfsOpsErr_METAVERSIONMISMATCH = 31, // metadata versions do not match, needed for cache invalidation + FhgfsOpsErr_INODELOCKED = 32, // inode is locked, needed for GlobalInodeLock store + FhgfsOpsErr_FILEACCESS_DENIED = 33, // file access denied due to current file state restrictions + +}; +typedef enum FhgfsOpsErr FhgfsOpsErr; + +// serialize FhgfsOpsErr as int per default +template<> +struct SerializeAs +{ + typedef int type; +}; + +typedef std::vector FhgfsOpsErrVec; +typedef FhgfsOpsErrVec::iterator FhgfsOpsErrVecIter; +typedef FhgfsOpsErrVec::const_iterator FhgfsOpsErrVecCIter; + + +class FhgfsOpsErrTk +{ + public: + static int toSysErr(FhgfsOpsErr errCode); + static FhgfsOpsErr fromSysErr(int errCode); + + + private: + FhgfsOpsErrTk() {} +}; + +std::ostream& operator<<(std::ostream& os, FhgfsOpsErr errCode); + diff --git a/common/source/common/storage/StoragePool.cpp b/common/source/common/storage/StoragePool.cpp new file mode 100644 index 0000000..2f05d58 --- /dev/null +++ b/common/source/common/storage/StoragePool.cpp @@ -0,0 +1,162 @@ +#include "StoragePool.h" + +void StoragePool::addTarget(uint16_t targetId, NumNodeID nodeId) +{ + std::lock_guard lock(mutex); + + addTargetUnlocked(targetId, nodeId); +} + +/* + * @param targetId + * @param nodeId needed for targetCapacityPools + */ +void StoragePool::addTargetUnlocked(uint16_t targetId, NumNodeID nodeId) +{ + // note: if the target was already in the set, that's fine + const auto insertRes = members.targets.insert(targetId); + + if (insertRes.second) // newly inserted + targetsCapacityPools->addIfNotExists(targetId, nodeId, CapacityPool_LOW); +} + +bool StoragePool::removeTarget(uint16_t targetId) +{ + std::lock_guard lock(mutex); + + return removeTargetUnlocked(targetId); +} + +bool StoragePool::removeTargetUnlocked(uint16_t targetId) +{ + const size_t numErased = members.targets.erase(targetId); + + if (numErased > 0) + { + targetsCapacityPools->remove(targetId); + return true; + } + else + { + return false; + } +} + +bool StoragePool::hasTarget(uint16_t targetId) const +{ + std::lock_guard lock(mutex); + + return (members.targets.find(targetId) != members.targets.end()); +} + +void StoragePool::addBuddyGroup(uint16_t buddyGroupId) +{ + std::lock_guard lock(mutex); + + addBuddyGroupUnlocked(buddyGroupId); +} + + +void StoragePool::addBuddyGroupUnlocked(uint16_t buddyGroupId) +{ + // note: if the target was already in the set, that's fine + const auto insertRes = members.buddyGroups.insert(buddyGroupId); + + if (insertRes.second) // newly inserted + buddyCapacityPools->addIfNotExists(buddyGroupId, CapacityPool_LOW); +} + + +bool StoragePool::removeBuddyGroup(uint16_t buddyGroupId) +{ + std::lock_guard lock(mutex); + + return removeBuddyGroupUnlocked(buddyGroupId); +} + + +bool StoragePool::removeBuddyGroupUnlocked(uint16_t buddyGroupId) +{ + const size_t numErased = members.buddyGroups.erase(buddyGroupId); + + if (numErased > 0) + { + buddyCapacityPools->remove(buddyGroupId); + return true; + } + else + { + return false; + } +} + + +bool StoragePool::hasBuddyGroup(uint16_t buddyGroupId) const +{ + std::lock_guard lock(mutex); + + return (members.buddyGroups.find(buddyGroupId) != members.buddyGroups.end()); +} + +void StoragePool::setDescription(const std::string& description) +{ + std::lock_guard lock(mutex); + + this->description = description; +} + +StoragePoolId StoragePool::getId() const +{ + return id; +} + +std::string StoragePool::getDescription() const +{ + std::lock_guard lock(mutex); + + return description; +} + +UInt16Set StoragePool::getTargets() const +{ + std::lock_guard lock(mutex); + + return members.targets; +} + +// remove all the targets in the pool and return a copy of them +UInt16Set StoragePool::getAndRemoveTargets() +{ + std::lock_guard lock(mutex); + + UInt16Set resultSet; + + resultSet.swap(members.targets); + + for (UInt16SetIter iter = resultSet.begin(); iter != resultSet.end(); iter++) + targetsCapacityPools->remove(*iter); + + return resultSet; +} + +UInt16Set StoragePool::getBuddyGroups() const +{ + std::lock_guard lock(mutex); + + return members.buddyGroups; +} + +// remove all the buddy groups in the pool and return a copy of them +UInt16Set StoragePool::getAndRemoveBuddyGroups() +{ + std::lock_guard lock(mutex); + + UInt16Set resultSet; + + resultSet.swap(members.buddyGroups); + + for (UInt16SetIter iter = resultSet.begin(); iter != resultSet.end(); iter++) + buddyCapacityPools->remove(*iter); + + return resultSet; +} diff --git a/common/source/common/storage/StoragePool.h b/common/source/common/storage/StoragePool.h new file mode 100644 index 0000000..5c3adbf --- /dev/null +++ b/common/source/common/storage/StoragePool.h @@ -0,0 +1,163 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +/* + * represents a storage target pool + */ +class StoragePool +{ + friend class StoragePoolStore; // for access to the internal mutex and unlocked functions + + public: + StoragePool(StoragePoolId id, const std::string& description, + const DynamicPoolLimits& capPoolLimitsStorageSpace, + const DynamicPoolLimits& capPoolLimitsStorageInodes, bool useDynamicCapPools): + id(id), description(description) + { + targetsCapacityPools = std::make_shared( + useDynamicCapPools, capPoolLimitsStorageSpace, capPoolLimitsStorageInodes); + buddyCapacityPools = std::make_shared( + false, DynamicPoolLimits(0, 0, 0, 0, 0, 0), DynamicPoolLimits(0, 0, 0, 0, 0, 0)); + } + + StoragePool(StoragePoolId id = StoragePoolId(0), const std::string& description = "") : + id(id), description(description) + { + targetsCapacityPools = std::make_shared( + false, DynamicPoolLimits(0, 0, 0, 0, 0, 0), DynamicPoolLimits(0, 0, 0, 0, 0, 0)); + buddyCapacityPools = std::make_shared( + false, DynamicPoolLimits(0, 0, 0, 0, 0, 0), DynamicPoolLimits(0, 0, 0, 0, 0, 0)); + } + + StoragePool(const StoragePool& rhs) = delete; + + virtual ~StoragePool() = default; + + StoragePoolId getId() const; + std::string getDescription() const; + void setDescription(const std::string& description); + + UInt16Set getTargets() const; + UInt16Set getAndRemoveTargets(); + void addTarget(uint16_t targetId, NumNodeID nodeId); + bool removeTarget(uint16_t targetId); + bool hasTarget(uint16_t targetId) const; + + UInt16Set getBuddyGroups() const; + UInt16Set getAndRemoveBuddyGroups(); + void addBuddyGroup(uint16_t buddyGroupId); + bool removeBuddyGroup(uint16_t buddyGroupId); + bool hasBuddyGroup(uint16_t buddyGroupId) const; + + TargetCapacityPools* getTargetCapacityPools() const + { + return targetsCapacityPools.get(); + } + + NodeCapacityPools* getBuddyCapacityPools() const + { + return buddyCapacityPools.get(); + } + + // note: capacity pool info is also serialized + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->id + % obj->description + % obj->members.targets + % obj->members.buddyGroups + % *(obj->targetsCapacityPools) + % *(obj->buddyCapacityPools); + } + + friend Serializer& operator%(Serializer& ser, const std::shared_ptr& obj) + { + ser % *obj; + + return ser; + } + + friend Deserializer& operator%(Deserializer& des, std::shared_ptr& obj) + { + obj.reset(new StoragePool); + + des % *obj; + + return des; + } + + /* + * wrapper around deserializer to perform additional tasks after deserialization in inherited + * classes + */ + virtual bool initFromDesBuf(Deserializer& des) + { + StoragePool::serialize(this, des); + + return des.good(); + } + + // comparisions are done using the numeric ID + bool operator==(const StoragePool& rhs) const + { + return (id == rhs.id); + } + + bool operator!=(const StoragePool& rhs) const + { + return (id != rhs.id); + } + + bool operator<(const StoragePool& rhs) const + { + return (id < rhs.id); + } + + bool operator>(const StoragePool& rhs) const + { + return (id > rhs.id); + } + + protected: + StoragePoolId id; // a numeric ID for the pool + std::string description; // user defined description + + mutable Mutex mutex; // protecting targets and buddyGroups + struct { + UInt16Set targets; // targets that belong to the pool + UInt16Set buddyGroups; // mirror buddygroups that belong to the pool + } members; + + std::shared_ptr targetsCapacityPools; + std::shared_ptr buddyCapacityPools; + + void addTargetUnlocked(uint16_t targetId, NumNodeID nodeId); + bool removeTargetUnlocked(uint16_t targetId); + void addBuddyGroupUnlocked(uint16_t buddyGroupId); + bool removeBuddyGroupUnlocked(uint16_t buddyGroupId); +}; + +typedef std::shared_ptr StoragePoolPtr; + +typedef std::vector StoragePoolVec; +typedef StoragePoolVec::iterator StoragePoolVecIter; +typedef StoragePoolVec::const_iterator StoragePoolVecCIter; + +typedef std::vector StoragePoolPtrVec; +typedef StoragePoolPtrVec::iterator StoragePoolPtrVecIter; +typedef StoragePoolPtrVec::const_iterator StoragePoolPtrVecCIter; + +typedef std::map StoragePoolPtrMap; +typedef StoragePoolPtrMap::iterator StoragePoolPtrMapIter; +typedef StoragePoolPtrMap::const_iterator StoragePoolPtrMapCIter; + diff --git a/common/source/common/storage/StoragePoolId.h b/common/source/common/storage/StoragePoolId.h new file mode 100644 index 0000000..1571536 --- /dev/null +++ b/common/source/common/storage/StoragePoolId.h @@ -0,0 +1,21 @@ +#pragma once + +#include + +/* + * represents a numeric storage pool ID + * + * // Note: this must always be in sync with client's StoragePoolId! + * + */ +enum StoragePoolIdTag {}; +typedef NumericID StoragePoolId; + +typedef std::list StoragePoolIdList; +typedef StoragePoolIdList::iterator StoragePoolIdListIter; +typedef StoragePoolIdList::const_iterator StoragePoolIdListCIter; + +typedef std::vector StoragePoolIdVector; +typedef StoragePoolIdVector::iterator StoragePoolIdVectorIter; +typedef StoragePoolIdVector::const_iterator StoragePoolIdVectorCIter; + diff --git a/common/source/common/storage/StorageTargetInfo.cpp b/common/source/common/storage/StorageTargetInfo.cpp new file mode 100644 index 0000000..f3670e4 --- /dev/null +++ b/common/source/common/storage/StorageTargetInfo.cpp @@ -0,0 +1,36 @@ +#include "StorageTargetInfo.h" + +#include +#include +#include +#include + + +/** + * @param targetID only required for storage servers, leave empty otherwise. + * @return false on comm error, true otherwise. + */ +FhgfsOpsErr StorageTargetInfo::statStoragePath(Node& node, uint16_t targetID, int64_t* outFree, + int64_t* outTotal, int64_t* outInodesFree, int64_t* outInodesTotal) +{ + FhgfsOpsErr retVal; + StatStoragePathRespMsg* respMsgCast; + + StatStoragePathMsg msg(targetID); + + const auto respMsg = MessagingTk::requestResponse(node, msg, NETMSGTYPE_StatStoragePathResp); + if (!respMsg) + return FhgfsOpsErr_COMMUNICATION; + + // handle result + respMsgCast = (StatStoragePathRespMsg*)respMsg.get(); + + retVal = (FhgfsOpsErr)respMsgCast->getResult(); + + *outFree = respMsgCast->getSizeFree(); + *outTotal = respMsgCast->getSizeTotal(); + *outInodesFree = respMsgCast->getInodesFree(); + *outInodesTotal = respMsgCast->getInodesTotal(); + + return retVal; +} diff --git a/common/source/common/storage/StorageTargetInfo.h b/common/source/common/storage/StorageTargetInfo.h new file mode 100644 index 0000000..8b29b66 --- /dev/null +++ b/common/source/common/storage/StorageTargetInfo.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include +#include +#include + +class StorageTargetInfo +{ + public: + StorageTargetInfo(uint16_t targetID, const std::string& pathStr, int64_t diskSpaceTotal, + int64_t diskSpaceFree, int64_t inodesTotal, int64_t inodesFree, + TargetConsistencyState consistencyState) + : targetID(targetID), + pathStr(pathStr), + diskSpaceTotal(diskSpaceTotal), + diskSpaceFree(diskSpaceFree), + inodesTotal(inodesTotal), + inodesFree(inodesFree), + consistencyState(consistencyState) + { } + + /** + * only for deserialization + */ + StorageTargetInfo() + { } + + static FhgfsOpsErr statStoragePath(Node& node, uint16_t targetID, int64_t* outFree, + int64_t* outTotal, int64_t* outInodesFree, int64_t* outInodesTotal); + + private: + uint16_t targetID; + std::string pathStr; + int64_t diskSpaceTotal; + int64_t diskSpaceFree; + int64_t inodesTotal; + int64_t inodesFree; + TargetConsistencyState consistencyState; + + public: + // getter/setter + uint16_t getTargetID() const + { + return targetID; + } + + const std::string getPathStr() const + { + return pathStr; + } + + int64_t getDiskSpaceTotal() const + { + return diskSpaceTotal; + } + + int64_t getDiskSpaceFree() const + { + return diskSpaceFree; + } + + int64_t getInodesTotal() const + { + return inodesTotal; + } + + int64_t getInodesFree() const + { + return inodesFree; + } + + TargetConsistencyState getState() const + { + return consistencyState; + } + + // operators + bool operator<(const StorageTargetInfo& other) const + { + if ( targetID < other.targetID ) + return true; + else + return false; + } + + bool operator==(const StorageTargetInfo& other) const + { + if ( targetID != other.targetID ) + return false; + else + if ( pathStr != other.pathStr ) + return false; + else + if ( diskSpaceTotal != other.diskSpaceTotal ) + return false; + else + if ( diskSpaceFree != other.diskSpaceFree ) + return false; + else + if ( inodesTotal != other.inodesTotal ) + return false; + else + if ( inodesFree != other.inodesFree ) + return false; + else + if ( consistencyState != other.consistencyState ) + return false; + else + return true; + } + + bool operator!= (const StorageTargetInfo& other) const + { + return !(operator==( other ) ); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->targetID + % serdes::stringAlign4(obj->pathStr) + % obj->diskSpaceTotal + % obj->diskSpaceFree + % obj->inodesTotal + % obj->inodesFree + % serdes::as(obj->consistencyState); + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + +typedef std::list StorageTargetInfoList; +typedef StorageTargetInfoList::iterator StorageTargetInfoListIter; +typedef StorageTargetInfoList::const_iterator StorageTargetInfoListCIter; + diff --git a/common/source/common/storage/Storagedata.h b/common/source/common/storage/Storagedata.h new file mode 100644 index 0000000..3c6f899 --- /dev/null +++ b/common/source/common/storage/Storagedata.h @@ -0,0 +1,8 @@ +#pragma once + +#define CONFIG_CHUNK_LEVEL1_SUBDIR_NUM (128) +#define CONFIG_CHUNK_LEVEL2_SUBDIR_NUM (128) +#define CONFIG_CHUNK_SUBDIR_NAME "chunks" /* chunks will be stored in this subdir */ +#define CONFIG_CHUNK_UID_PREFIX "u" /* user id prefix for user's chunk dirs */ +#define CONFIG_BUDDYMIRROR_SUBDIR_NAME "buddymir" /* chunks subdir for buddy-mirrored files */ + diff --git a/common/source/common/storage/mirroring/BuddyResyncJobStatistics.h b/common/source/common/storage/mirroring/BuddyResyncJobStatistics.h new file mode 100644 index 0000000..cdc1d22 --- /dev/null +++ b/common/source/common/storage/mirroring/BuddyResyncJobStatistics.h @@ -0,0 +1,201 @@ +#pragma once + +#include +#include + +enum BuddyResyncJobState +{ + BuddyResyncJobState_NOTSTARTED = 0, + BuddyResyncJobState_RUNNING, + BuddyResyncJobState_SUCCESS, + BuddyResyncJobState_INTERRUPTED, + BuddyResyncJobState_FAILURE, + BuddyResyncJobState_ERRORS +}; + +class BuddyResyncJobStatistics +{ + public: + BuddyResyncJobStatistics(BuddyResyncJobState state, int64_t startTime, int64_t endTime) : + state(state), + startTime(startTime), + endTime(endTime) + { } + + BuddyResyncJobStatistics() : + state(BuddyResyncJobState_NOTSTARTED), + startTime(0), + endTime(0) + { } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->state) + % obj->startTime + % obj->endTime; + } + + private: + BuddyResyncJobState state; + int64_t startTime; + int64_t endTime; + + public: + BuddyResyncJobState getState() const { return state; } + int64_t getStartTime() const { return startTime; } + int64_t getEndTime() const { return endTime; } +}; + +class StorageBuddyResyncJobStatistics : public BuddyResyncJobStatistics +{ + public: + StorageBuddyResyncJobStatistics(BuddyResyncJobState state, int64_t startTime, int64_t endTime, + uint64_t discoveredFiles, uint64_t discoveredDirs, uint64_t matchedFiles, + uint64_t matchedDirs, uint64_t syncedFiles, uint64_t syncedDirs, uint64_t errorFiles = 0, + uint64_t errorDirs = 0) : + BuddyResyncJobStatistics(state, startTime, endTime), + discoveredFiles(discoveredFiles), + discoveredDirs(discoveredDirs), + matchedFiles(matchedFiles), + matchedDirs(matchedDirs), + syncedFiles(syncedFiles), + syncedDirs(syncedDirs), + errorFiles(errorFiles), + errorDirs(errorDirs) + {} + + StorageBuddyResyncJobStatistics() : + BuddyResyncJobStatistics(), + discoveredFiles(0), + discoveredDirs(0), + matchedFiles(0), + matchedDirs(0), + syncedFiles(0), + syncedDirs(0), + errorFiles(0), + errorDirs(0) + {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->discoveredFiles + % obj->discoveredDirs + % obj->matchedFiles + % obj->matchedDirs + % obj->syncedFiles + % obj->syncedDirs + % obj->errorFiles + % obj->errorDirs; + } + + private: + uint64_t discoveredFiles; + uint64_t discoveredDirs; + uint64_t matchedFiles; + uint64_t matchedDirs; + uint64_t syncedFiles; + uint64_t syncedDirs; + uint64_t errorFiles; + uint64_t errorDirs; + + public: + uint64_t getDiscoveredDirs() const { return discoveredDirs; } + uint64_t getDiscoveredFiles() const { return discoveredFiles; } + uint64_t getMatchedDirs() const { return matchedDirs; } + uint64_t getMatchedFiles() const { return matchedFiles; } + uint64_t getSyncedDirs() const { return syncedDirs; } + uint64_t getSyncedFiles() const { return syncedFiles; } + uint64_t getErrorDirs() const { return errorDirs; } + uint64_t getErrorFiles() const { return errorFiles; } +}; + +class MetaBuddyResyncJobStatistics : public BuddyResyncJobStatistics +{ + public: + MetaBuddyResyncJobStatistics(BuddyResyncJobState state, int64_t startTime, int64_t endTime, + uint64_t dirsDiscovered, uint64_t gatherErrors, + uint64_t dirsSynced, uint64_t filesSynced, uint64_t dirErrors, uint64_t fileErrors, + uint64_t sessionsToSync, uint64_t sessionsSynced, bool sessionSyncErrors, + uint64_t modObjectsSynced, uint64_t modSyncErrors) : + BuddyResyncJobStatistics(state, startTime, endTime), + dirsDiscovered(dirsDiscovered), + gatherErrors(gatherErrors), + dirsSynced(dirsSynced), + filesSynced(filesSynced), + dirErrors(dirErrors), + fileErrors(fileErrors), + sessionsToSync(sessionsToSync), + sessionsSynced(sessionsSynced), + sessionSyncErrors(sessionSyncErrors), + modObjectsSynced(modObjectsSynced), + modSyncErrors(modSyncErrors) + {} + + MetaBuddyResyncJobStatistics() : + BuddyResyncJobStatistics(), + dirsDiscovered(0), + gatherErrors(0), + dirsSynced(0), + filesSynced(0), + dirErrors(0), + fileErrors(0), + sessionsToSync(0), + sessionsSynced(0), + sessionSyncErrors(false), + modObjectsSynced(0), + modSyncErrors(0) + {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->dirsDiscovered + % obj->gatherErrors + % obj->dirsSynced + % obj->filesSynced + % obj->dirErrors + % obj->fileErrors + % obj->sessionsToSync + % obj->sessionsSynced + % obj->sessionSyncErrors + % obj->modObjectsSynced + % obj->modSyncErrors; + } + + private: + uint64_t dirsDiscovered; + uint64_t gatherErrors; + + uint64_t dirsSynced; + uint64_t filesSynced; + uint64_t dirErrors; + uint64_t fileErrors; + + uint64_t sessionsToSync; + uint64_t sessionsSynced; + bool sessionSyncErrors; + + uint64_t modObjectsSynced; + uint64_t modSyncErrors; + + public: + uint64_t getDirsDiscovered() { return dirsDiscovered; } + uint64_t getGatherErrors() { return gatherErrors; } + uint64_t getDirsSynced() { return dirsSynced; } + uint64_t getFilesSynced() { return filesSynced; } + uint64_t getDirErrors() { return dirErrors; } + uint64_t getFileErrors() { return fileErrors; } + uint64_t getSessionsToSync() { return sessionsToSync; } + uint64_t getSessionsSynced() { return sessionsSynced; } + bool getSessionSyncErrors() { return sessionSyncErrors; } + uint64_t getModObjectsSynced() { return modObjectsSynced; } + uint64_t getModSyncErrors() { return modSyncErrors; } +}; + diff --git a/common/source/common/storage/mirroring/SyncCandidateStore.h b/common/source/common/storage/mirroring/SyncCandidateStore.h new file mode 100644 index 0000000..75a5a57 --- /dev/null +++ b/common/source/common/storage/mirroring/SyncCandidateStore.h @@ -0,0 +1,208 @@ +#pragma once + +#include +#include +#include +#include + +#include + +template +class SyncCandidateStore +{ + public: + SyncCandidateStore() + : numQueuedFiles(0), numQueuedDirs(0) + { } + + private: + typedef std::list CandidateFileList; + CandidateFileList candidatesFile; + Mutex candidatesFileMutex; + Condition filesAddedCond; + Condition filesFetchedCond; + + typedef std::list CandidateDirList; + CandidateDirList candidatesDir; + Mutex candidatesDirMutex; + Condition dirsAddedCond; + Condition dirsFetchedCond; + + // mainly used to avoid constant calling of size() method of lists + unsigned numQueuedFiles; + unsigned numQueuedDirs; + + static const unsigned MAX_QUEUE_SIZE = 50000; + + public: + void add(SyncCandidateFile entry, PThread* caller) + { + static const unsigned waitTimeoutMS = 1000; + + std::lock_guard mutexLock(candidatesFileMutex); + + // wait if list is too big + while (numQueuedFiles > MAX_QUEUE_SIZE) + { + if (caller && unlikely(caller->getSelfTerminate() ) ) + break; // ignore limit if selfTerminate was set to avoid hanging on shutdown + + filesFetchedCond.timedwait(&candidatesFileMutex, waitTimeoutMS); + } + + this->candidatesFile.push_back(std::move(entry)); + numQueuedFiles++; + + filesAddedCond.signal(); + } + + void fetch(SyncCandidateFile& outCandidate, PThread* caller) + { + static const unsigned waitTimeMS = 3000; + + std::lock_guard mutexLock(candidatesFileMutex); + + while (candidatesFile.empty() ) + { + if(caller && unlikely(caller->getSelfTerminate() ) ) + { + outCandidate = SyncCandidateFile(); + return; + } + + filesAddedCond.timedwait(&candidatesFileMutex, waitTimeMS); + } + + outCandidate = std::move(candidatesFile.front()); + candidatesFile.pop_front(); + numQueuedFiles--; + filesFetchedCond.signal(); + } + + void add(SyncCandidateDir entry, PThread* caller) + { + static const unsigned waitTimeoutMS = 3000; + + std::lock_guard mutexLock(candidatesDirMutex); + + // wait if list is too big + while (numQueuedDirs > MAX_QUEUE_SIZE) + { + if (caller && unlikely(caller->getSelfTerminate() ) ) + break; // ignore limit if selfTerminate was set to avoid hanging on shutdown + + dirsFetchedCond.timedwait(&candidatesDirMutex, waitTimeoutMS); + } + + this->candidatesDir.push_back(std::move(entry)); + numQueuedDirs++; + + dirsAddedCond.signal(); + } + + bool waitForFiles(PThread* caller) + { + static const unsigned waitTimeoutMS = 3000; + + std::lock_guard mutexLock(candidatesFileMutex); + + while (numQueuedFiles == 0) + { + if (caller && caller->getSelfTerminate()) + return false; + + filesAddedCond.timedwait(&candidatesFileMutex, waitTimeoutMS); + } + + return true; + } + + void fetch(SyncCandidateDir& outCandidate, PThread* caller) + { + static const unsigned waitTimeMS = 3000; + + std::lock_guard mutexLock(candidatesDirMutex); + + while (candidatesDir.empty() ) + { + if(caller && unlikely(caller->getSelfTerminate() ) ) + { + outCandidate = SyncCandidateDir(); + return; + } + + dirsAddedCond.timedwait(&candidatesDirMutex, waitTimeMS); + } + + outCandidate = std::move(candidatesDir.front()); + candidatesDir.pop_front(); + numQueuedDirs--; + dirsFetchedCond.signal(); + } + + bool isFilesEmpty() + { + std::lock_guard mutexLock(candidatesFileMutex); + return candidatesFile.empty(); + } + + bool isDirsEmpty() + { + std::lock_guard mutexLock(candidatesDirMutex); + return candidatesDir.empty(); + } + + void waitForFiles(const unsigned timeoutMS) + { + std::lock_guard mutexLock(candidatesFileMutex); + + if (candidatesFile.empty() ) + { + if (timeoutMS == 0) + filesAddedCond.wait(&candidatesFileMutex); + else + filesAddedCond.timedwait(&candidatesFileMutex, timeoutMS); + } + } + + void waitForDirs(const unsigned timeoutMS) + { + std::lock_guard mutexLock(candidatesDirMutex); + + if (candidatesDir.empty() ) + { + if (timeoutMS == 0) + dirsAddedCond.wait(&candidatesDirMutex); + else + dirsAddedCond.timedwait(&candidatesDirMutex, timeoutMS); + } + } + + size_t getNumFiles() + { + std::lock_guard mutexLock(candidatesFileMutex); + return candidatesFile.size(); + } + + size_t getNumDirs() + { + std::lock_guard mutexLock(candidatesDirMutex); + return candidatesDir.size(); + } + + void clear() + { + { + std::lock_guard dirMutexLock(candidatesDirMutex); + candidatesDir.clear(); + numQueuedDirs = 0; + } + + { + std::lock_guard fileMutexLock(candidatesFileMutex); + candidatesFile.clear(); + numQueuedFiles = 0; + } + } +}; + diff --git a/common/source/common/storage/quota/ExceededQuotaPerTarget.cpp b/common/source/common/storage/quota/ExceededQuotaPerTarget.cpp new file mode 100644 index 0000000..a6fc038 --- /dev/null +++ b/common/source/common/storage/quota/ExceededQuotaPerTarget.cpp @@ -0,0 +1,48 @@ +#include "ExceededQuotaPerTarget.h" + +#include + +const ExceededQuotaStorePtr ExceededQuotaPerTarget::get(uint16_t targetId) const +{ + RWLockGuard lock(rwLock, SafeRWLock_READ); + + auto iter = elements.find(targetId); + + if (iter != elements.end()) + return iter->second; + else + return {}; +} + +/* + * add an exceededQuotaStore for a specific target + * + * @param targetID + * @param ignoreExisting do not log error if store already exists + */ +const ExceededQuotaStorePtr ExceededQuotaPerTarget::add(uint16_t targetId, + bool ignoreExisting) +{ + ExceededQuotaStorePtr exStore = std::make_shared(); + + RWLockGuard lock(rwLock, SafeRWLock_WRITE); + + auto insertRes = elements.insert(std::make_pair(targetId, exStore)); + + if ((!ignoreExisting) && (!insertRes.second)) + LOG(QUOTA, NOTICE, "Tried to add exceeded quota store, which already exists.", targetId); + + return (insertRes.first)->second; +} + +/* + * remove an exceededQuotaStore of a specific target + * + * @param targetID + */ +void ExceededQuotaPerTarget::remove(uint16_t targetId) +{ + RWLockGuard lock(rwLock, SafeRWLock_WRITE); + + elements.erase(targetId); +} diff --git a/common/source/common/storage/quota/ExceededQuotaPerTarget.h b/common/source/common/storage/quota/ExceededQuotaPerTarget.h new file mode 100644 index 0000000..8ef5da5 --- /dev/null +++ b/common/source/common/storage/quota/ExceededQuotaPerTarget.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class ExceededQuotaPerTarget +{ + public: + const ExceededQuotaStorePtr get(uint16_t targetId) const; + const ExceededQuotaStorePtr add(uint16_t targetId, bool ignoreExisting = true); + void remove(uint16_t targetId); + + private: + // key: targetId, value: shared ptr to ExceededQuotaStore + std::map elements; + mutable RWLock rwLock; +}; + diff --git a/common/source/common/storage/quota/ExceededQuotaStore.cpp b/common/source/common/storage/quota/ExceededQuotaStore.cpp new file mode 100644 index 0000000..4c638ff --- /dev/null +++ b/common/source/common/storage/quota/ExceededQuotaStore.cpp @@ -0,0 +1,211 @@ +#include "ExceededQuotaStore.h" + + +#include + + +/** + * Replace the old list of IDs with exceeded quota by the new List of IDs with exceeded quota + * + * @param newIDList the old list with the IDs with exceeded quota (requires a list which was + * allocated with new) + * @param idType the type of the ID list (user or group) + * @param exType the type of exceeded quota (size or inodes) + */ +void ExceededQuotaStore::updateExceededQuota(UIntList* newIDList, QuotaDataType idType, + QuotaLimitType exType) +{ + SafeRWLock lock(&this->rwLockExceededLists, SafeRWLock_WRITE); //W R I T E L O C K + + if(idType == QuotaDataType_USER) + { + if(exType == QuotaLimitType_SIZE) + updateExceededQuotaUnlocked(newIDList, this->exceededUserQuotaSize); + else + if(exType == QuotaLimitType_INODE) + updateExceededQuotaUnlocked(newIDList, this->exceededUserQuotaInode); + } + else + if(idType == QuotaDataType_GROUP) + { + if(exType == QuotaLimitType_SIZE) + updateExceededQuotaUnlocked(newIDList, this->exceededGroupQuotaSize); + else + if(exType == QuotaLimitType_INODE) + updateExceededQuotaUnlocked(newIDList, this->exceededGroupQuotaInode); + } + + uint64_t sum = this->exceededUserQuotaSize->size() + + this->exceededUserQuotaInode->size() + + this->exceededGroupQuotaSize->size() + + this->exceededGroupQuotaInode->size(); + + this->exceededCounter.set(sum); + + lock.unlock(); //U N L O C K +} + +/** + * Checks if the quota (size and inodes) is exceeded for the given IDs + * + * @param uid the user ID to check + * @param gid the group ID to check + * + * @return the kind of exceeded quota, enum QuotaExceededErrorType + */ +QuotaExceededErrorType ExceededQuotaStore::isQuotaExceeded(unsigned uid, unsigned gid) +{ + QuotaExceededErrorType retVal = QuotaExceededErrorType_NOT_EXCEEDED; + + SafeRWLock lock(&this->rwLockExceededLists, SafeRWLock_READ); // R E A D L O C K + + if(this->isQuotaExceededUnlocked(uid, this->exceededUserQuotaSize) ) + retVal = QuotaExceededErrorType_USER_SIZE_EXCEEDED; + else + if(this->isQuotaExceededUnlocked(uid, this->exceededUserQuotaInode) ) + retVal = QuotaExceededErrorType_USER_INODE_EXCEEDED; + else + if(this->isQuotaExceededUnlocked(gid, this->exceededGroupQuotaSize) ) + retVal = QuotaExceededErrorType_GROUP_SIZE_EXCEEDED; + else + if(this->isQuotaExceededUnlocked(gid, this->exceededGroupQuotaInode) ) + retVal = QuotaExceededErrorType_GROUP_INODE_EXCEEDED; + + lock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Checks if the quota is exceeded for the given IDs of the given exceeded type (size or inodes) + * + * @param uid the user ID to check + * @param gid the group ID to check + * @param exType the type of exceeded quota (size or inodes) + * + * @return the kind of exceeded quota, enum QuotaExceededErrorType + */ +QuotaExceededErrorType ExceededQuotaStore::isQuotaExceeded(unsigned uid, unsigned gid, + QuotaLimitType exType) +{ + QuotaExceededErrorType retVal = QuotaExceededErrorType_NOT_EXCEEDED; + + SafeRWLock lock(&this->rwLockExceededLists, SafeRWLock_READ); // R E A D L O C K + + if(exType == QuotaLimitType_SIZE) + { + if(this->isQuotaExceededUnlocked(uid, this->exceededUserQuotaSize) ) + retVal = QuotaExceededErrorType_USER_SIZE_EXCEEDED; + else + if(this->isQuotaExceededUnlocked(gid, this->exceededGroupQuotaSize) ) + retVal = QuotaExceededErrorType_GROUP_SIZE_EXCEEDED; + } + else + if(exType == QuotaLimitType_INODE) + { + if(this->isQuotaExceededUnlocked(uid, this->exceededUserQuotaInode) ) + retVal = QuotaExceededErrorType_USER_INODE_EXCEEDED; + else + if(this->isQuotaExceededUnlocked(gid, this->exceededGroupQuotaInode) ) + retVal = QuotaExceededErrorType_GROUP_INODE_EXCEEDED; + } + + lock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Collects the IDs with exceeded quota of the given types + * + * @param outIDList the list with the IDs of exceeded quota + * @param idType the type of the ID list (user or group) + * @param exType the type of exceeded quota (size or inodes) + */ +void ExceededQuotaStore::getExceededQuota(UIntList* outIDList, QuotaDataType idType, + QuotaLimitType exType) const +{ + SafeRWLock lock(&this->rwLockExceededLists, SafeRWLock_READ); // R E A D L O C K + + if(idType == QuotaDataType_USER) + { + if(exType == QuotaLimitType_SIZE) + this->getExceededQuotaUnlocked(outIDList, this->exceededUserQuotaSize); + else + if(exType == QuotaLimitType_INODE) + this->getExceededQuotaUnlocked(outIDList, this->exceededUserQuotaInode); + } + else + if(idType == QuotaDataType_GROUP) + { + if(exType == QuotaLimitType_SIZE) + this->getExceededQuotaUnlocked(outIDList, this->exceededGroupQuotaSize); + else + if(exType == QuotaLimitType_INODE) + this->getExceededQuotaUnlocked(outIDList, this->exceededGroupQuotaInode); + } + + lock.unlock(); // U N L O C K +} + +/** + * Replace the old list of IDs with exceeded quota by the new List of IDs with exceeded quota, not + * synchronised + * + * @param newIDList the old list with the IDs with exceeded quota (requires a list which was + * allocated with new) + * @param listToUpdate the old list with the IDs with exceeded quota + */ +void ExceededQuotaStore::updateExceededQuotaUnlocked(UIntList* newIDList, UIntSet* listToUpdate) +{ + listToUpdate->clear(); + + for(UIntListIter iter = newIDList->begin(); iter != newIDList->end(); iter++) + listToUpdate->insert(*iter); +} + +/** + * Checks if the quota is exceeded for the given ID, not synchronised + * + * @param id the ID to check + * @param list the list of exceeded quota which could contain the given ID + * + * return true, if the quota is exceeded for the given id + */ +bool ExceededQuotaStore::isQuotaExceededUnlocked(unsigned id, UIntSet* list) +{ + bool retVal = false; + + for(UIntSetIter iter = list->begin(); iter != list->end(); iter++) + { + if(*iter == id) + { + retVal = true; + break; + } + } + + return retVal; +} + +/** + * Collects the IDs with exceeded quota, not synchronised + * + * @param outIDList the out list with the IDs of exceeded quota + * @param inIDList the in list with the IDs of exceeded quota + */ +void ExceededQuotaStore::getExceededQuotaUnlocked(UIntList* outIDList, UIntSet* inIDList) const +{ + for(UIntSetIter iter = inIDList->begin(); iter != inIDList->end(); iter++) + outIDList->push_back(*iter); +} + +/** + * Checks if the quota of at least one user or group is exceeded. + * + * @return true if at least one exceeded quota ID exists + */ +bool ExceededQuotaStore::someQuotaExceeded() +{ + return this->exceededCounter.read() != 0; +} diff --git a/common/source/common/storage/quota/ExceededQuotaStore.h b/common/source/common/storage/quota/ExceededQuotaStore.h new file mode 100644 index 0000000..30da27f --- /dev/null +++ b/common/source/common/storage/quota/ExceededQuotaStore.h @@ -0,0 +1,50 @@ +#pragma once + + +#include +#include +#include + +class ExceededQuotaStore +{ + public: + ExceededQuotaStore() + { + this->exceededUserQuotaSize = new UIntSet(); + this->exceededGroupQuotaSize = new UIntSet(); + this->exceededUserQuotaInode = new UIntSet(); + this->exceededGroupQuotaInode = new UIntSet(); + } + + ~ExceededQuotaStore() + { + SAFE_DELETE(this->exceededUserQuotaSize); + SAFE_DELETE(this->exceededGroupQuotaSize); + SAFE_DELETE(this->exceededUserQuotaInode); + SAFE_DELETE(this->exceededGroupQuotaInode); + } + + void updateExceededQuota(UIntList* newIDList, QuotaDataType idType, QuotaLimitType exType); + QuotaExceededErrorType isQuotaExceeded(unsigned uid, unsigned gid, QuotaLimitType exType); + QuotaExceededErrorType isQuotaExceeded(unsigned uid, unsigned gid); + void getExceededQuota(UIntList* outIDList, QuotaDataType idType, QuotaLimitType exType) const; + bool someQuotaExceeded(); + + + private: + UIntSet* exceededUserQuotaSize; // UIDs with exceeded size quota + UIntSet* exceededGroupQuotaSize; // GIDs with exceeded inode quota + UIntSet* exceededUserQuotaInode; // UIDs with exceeded size quota + UIntSet* exceededGroupQuotaInode; // GIDs with exceeded inode quota + + mutable RWLock rwLockExceededLists; // synchronize access to all exceeded ID sets + AtomicUInt64 exceededCounter; // number of exceeded quota IDs in all categories + + + void updateExceededQuotaUnlocked(UIntList* newIDList, UIntSet* listToUpdate); + bool isQuotaExceededUnlocked(unsigned id, UIntSet* list); + void getExceededQuotaUnlocked(UIntList* outIDList, UIntSet* inIDList) const; +}; + +typedef std::shared_ptr ExceededQuotaStorePtr; + diff --git a/common/source/common/storage/quota/GetQuotaConfig.h b/common/source/common/storage/quota/GetQuotaConfig.h new file mode 100644 index 0000000..234b32b --- /dev/null +++ b/common/source/common/storage/quota/GetQuotaConfig.h @@ -0,0 +1,22 @@ +#pragma once + +#include "QuotaConfig.h" + + +#define GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST 0 +#define GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET 1 +#define GETQUOTACONFIG_SINGLE_TARGET 2 + + +/** + * A struct for all configurations which are required to collect quota data/limits + */ +struct GetQuotaInfoConfig : public QuotaConfig +{ + unsigned cfgTargetSelection; // the kind to collect the quota data/limits: GETQUOTACONFIG_... + uint16_t cfgTargetNumID; // targetNumID if a single target is selected + bool cfgPrintUnused; // print users/groups that have no space used + bool cfgWithSystemUsersGroups;// if system users/groups should be used be considered +}; + + diff --git a/common/source/common/storage/quota/GetQuotaInfo.cpp b/common/source/common/storage/quota/GetQuotaInfo.cpp new file mode 100644 index 0000000..faa0127 --- /dev/null +++ b/common/source/common/storage/quota/GetQuotaInfo.cpp @@ -0,0 +1,309 @@ +#include +#include +#include +#include +#include "GetQuotaInfo.h" + + +typedef std::map> MutexMap; + + +/* + * send the quota limit requests to the management node and collects the responses + * + * note: the usage of storage pools here will only work if cfgTargetSelection is set to + * GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET + * + * @param mgmtNode the management node, need to be referenced + * @param workQ the MultiWorkQueue to use + * @param outQuotaResults returns the quota informations + * @param mapper the target mapper (only required for GETQUOTACONFIG_SINGLE_TARGET and + * GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET) + * @param storagePoolId the storagePool to get information for; is only relevant if + * storagePoolStore is !NULL + * @return error information, true on success, false if a error occurred + */ +bool GetQuotaInfo::requestQuotaLimitsAndCollectResponses(const NodeHandle& mgmtNode, + MultiWorkQueue* workQ, QuotaDataMapForTarget* outQuotaResults, const TargetMapper* mapper, + StoragePoolId storagePoolId) +{ + bool retVal = true; + + SynchronizedCounter counter; + + int maxMessageCount = getMaxMessageCount(); + + UInt16Vector nodeResults(maxMessageCount); + QuotaInodeSupport quotaInodeSupport; + + Mutex mutex; + + auto& resultMap = (*outQuotaResults)[QUOTADATAMAPFORTARGET_ALL_TARGETS_ID]; + + for (int messageNumber = 0; messageNumber < maxMessageCount; messageNumber++) + { + Work* work = new GetQuotaInfoWork(cfg, mgmtNode, messageNumber, &resultMap, &mutex, + &counter, &nodeResults[messageNumber], "aInodeSupport, storagePoolId); + workQ->addDirectWork(work); + } + + // wait for all work to be done + counter.waitForCount(maxMessageCount); + + for (auto iter = nodeResults.begin(); iter != nodeResults.end(); iter++) + { + if (*iter != 0) + { + // delete QuotaDataMap with invalid QuotaData + if (cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + outQuotaResults->erase(*iter); + retVal = false; + } + } + + return retVal; +} + +/* + * send the quota data requests to the storage nodes and collects the responses + * + * note: the usage of storage pools here will only work if cfgTargetSelection is set to + * GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET + * + * @param storageNodes a NodeStore with all storage servers + * @param workQ the MultiWorkQueue to use + * @param outQuotaResults returns the quota informations + * @param mapper the target mapper (only required for GETQUOTACONFIG_SINGLE_TARGET and + * GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET) + * @param storagePoolStore if !NULL the variable storagePoolId will be considered and only targets + * belonging to the given storage pool will be accounted + * @param storagePoolId the storagePool to get information for; is only relevant if + * storagePoolStore is !NULL + * @return error information, true on success, false if a error occurred + */ +bool GetQuotaInfo::requestQuotaDataAndCollectResponses(const NodeStoreServers* storageNodes, + MultiWorkQueue* workQ, QuotaDataMapForTarget* outQuotaResults, const TargetMapper* mapper, + QuotaInodeSupport* quotaInodeSupport, StoragePoolStore* storagePoolStore, + StoragePoolId storagePoolId) +{ + bool retVal = true; + + int numWorks = 0; + SynchronizedCounter counter; + + int maxMessageCount = getMaxMessageCount(); + + UInt16Vector nodeResults; + std::vector allNodes; + + MutexMap mutexMap; + + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + { /* download the used quota from the storage daemon, one request for all targets, multiple + messages will be used if the payload of the message is to small */ + + allNodes = storageNodes->referenceAllNodes(); + + nodeResults = UInt16Vector(maxMessageCount * storageNodes->getSize() ); + + // request all subranges (=> msg size limitation) from current server + for(int messageNumber = 0; messageNumber < maxMessageCount; messageNumber++) + { + for (auto nodeIt = allNodes.begin(); nodeIt != allNodes.end(); ++nodeIt) + { + //send command to the storage servers and print the response + auto& storageNode = *nodeIt; + + if(messageNumber == 0) + { + QuotaDataMap map; + outQuotaResults->insert(QuotaDataMapForTargetMapVal( + storageNode->getNumID().val(), map) ); + } + + mutexMap.insert({ storageNode->getNumID().val(), std::make_shared() }); + + Work* work = new GetQuotaInfoWork(this->cfg, storageNode, messageNumber, + &outQuotaResults->find(storageNode->getNumID().val())->second, + mutexMap[storageNode->getNumID().val()].get(), &counter, + &nodeResults[numWorks], quotaInodeSupport, storagePoolId); + workQ->addDirectWork(work); + + numWorks++; + } + } + } + else + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET) + { /* download the used quota from the storage daemon, a single request for each target, multiple + messages will be used if the payload of the message is to small */ + + allNodes = storageNodes->referenceAllNodes(); + + nodeResults = UInt16Vector(maxMessageCount * mapper->getSize() ); + + // if storage pool store is set, we need to reference the pool to check later, if received + // targets are members of the pool + // note: if pool ID doesn't exist, the whole routine works, as if no pool ID was given + StoragePoolPtr storagePool; + if (storagePoolStore) + storagePool = storagePoolStore->getPool(storagePoolId); + + // request all subranges (=> msg size limitation) from current server + for(int messageNumber = 0; messageNumber < maxMessageCount; messageNumber++) + { + for (auto nodeIt = allNodes.begin(); nodeIt != allNodes.end(); ++nodeIt) + { + auto& storageNode = *nodeIt; + + //send command to the storage servers + UInt16List targetIDs; + mapper->getTargetsByNode(storageNode->getNumID(), targetIDs); + + // request the quota data for the targets in a separate message + for(UInt16ListIter iter = targetIDs.begin(); iter != targetIDs.end(); iter++) + { + // if a storage pool was set and this target doesn't belong to the requested pool, + // then skip it + if (storagePool && !storagePool->hasTarget(*iter)) + continue; + + GetQuotaInfoConfig requestCfg = this->cfg; + requestCfg.cfgTargetNumID = *iter; + + if(messageNumber == 0) + { + QuotaDataMap map; + outQuotaResults->insert(QuotaDataMapForTargetMapVal(*iter, map) ); + } + + mutexMap.insert({ *iter, std::make_shared() }); + + Work* work = new GetQuotaInfoWork(requestCfg, storageNode, messageNumber, + &outQuotaResults->at(*iter), mutexMap[*iter].get(), &counter, + &nodeResults[numWorks], quotaInodeSupport, storagePoolId); + workQ->addDirectWork(work); + + numWorks++; + } + } + } + } + else + if(this->cfg.cfgTargetSelection == GETQUOTACONFIG_SINGLE_TARGET) + { /* download the used quota from the storage daemon, request the data only for a single target, + multiple messages will be used if the payload of the message is to small */ + NumNodeID storageNumID = mapper->getNodeID(this->cfg.cfgTargetNumID); + + allNodes.push_back(storageNodes->referenceNode(storageNumID)); + auto& storageNode = allNodes.front(); + + // use IntVector because it doesn't work with BoolVector. std::vector is not a container + nodeResults = UInt16Vector(maxMessageCount); + + QuotaDataMap map; + outQuotaResults->insert(QuotaDataMapForTargetMapVal(this->cfg.cfgTargetNumID, map) ); + + //send command to the storage servers + if(storageNode != NULL) + { + mutexMap.insert({ cfg.cfgTargetNumID, std::make_shared() }); + + // request all subranges (=> msg size limitation) from current server + for(int messageNumber = 0; messageNumber < maxMessageCount; messageNumber++) + { + Work* work = new GetQuotaInfoWork(this->cfg, storageNode, messageNumber, + &outQuotaResults->find(this->cfg.cfgTargetNumID)->second, + mutexMap[cfg.cfgTargetNumID].get(), &counter, &nodeResults[numWorks], + quotaInodeSupport, storagePoolId); + workQ->addDirectWork(work); + + numWorks++; + } + } + else + { + LogContext("GetQuotaInfo").logErr("Unknown target selection."); + nodeResults[numWorks] = 0; + } + } + + // wait for all work to be done + counter.waitForCount(numWorks); + + // merge the quota data if required + if (cfg.cfgTargetSelection == GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) + { + QuotaDataMap map = calculateQuotaSums(*outQuotaResults); + + outQuotaResults->clear(); + outQuotaResults->insert(QuotaDataMapForTargetMapVal(QUOTADATAMAPFORTARGET_ALL_TARGETS_ID, + map) ); + } + + for (UInt16VectorIter iter = nodeResults.begin(); iter != nodeResults.end(); iter++) + { + if (*iter != 0) + { + // delete QuotaDataMap with invalid QuotaData + if(this->cfg.cfgTargetSelection != GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST) { + outQuotaResults->erase(*iter); + LOG(QUOTA, ERR, "Unable to fetch quota information from storage target." + " Is the node offline?", ("Storage target id", *iter)); + } else { + LOG(QUOTA, ERR, "Unable to fetch quota information from storage server." + " Is the node offline?", ("Storage node id", *iter)); + } + + retVal = false; + } + } + + return retVal; +} + +/* + * calculate the number of messages which are required to download all quota data + */ +int GetQuotaInfo::getMaxMessageCount() +{ + + unsigned numIds = 1; + + if(this->cfg.cfgUseList || this->cfg.cfgUseAll) + { + numIds = cfg.cfgIDList.size(); + } + else if (cfg.cfgUseRange) + { + numIds = this->cfg.cfgIDRangeEnd - this->cfg.cfgIDRangeStart + 1; //inclusive range + } + + int retVal = numIds / GETQUOTAINFORESPMSG_MAX_ID_COUNT; + + if (numIds % GETQUOTAINFORESPMSG_MAX_ID_COUNT != 0) + ++retVal; + + return retVal; +} + +QuotaDataMap GetQuotaInfo::calculateQuotaSums(const QuotaDataMapForTarget& quotaMaps) +{ + QuotaDataMap map; + + for (QuotaDataMapForTargetConstIter quotaMapsIter = quotaMaps.begin(); + quotaMapsIter != quotaMaps.end(); quotaMapsIter++ ) + { + for(QuotaDataMapConstIter resultIter = quotaMapsIter->second.begin(); + resultIter != quotaMapsIter->second.end(); resultIter++) + { + QuotaDataMapIter outIter = map.find(resultIter->first); + if(outIter != map.end()) + outIter->second.mergeQuotaDataCounter(&(resultIter->second) ); + else + map.insert(QuotaDataMapVal(resultIter->first, resultIter->second) ); + } + } + + return map; +} diff --git a/common/source/common/storage/quota/GetQuotaInfo.h b/common/source/common/storage/quota/GetQuotaInfo.h new file mode 100644 index 0000000..3bd20ad --- /dev/null +++ b/common/source/common/storage/quota/GetQuotaInfo.h @@ -0,0 +1,53 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include + + + +/* + * A parent class for all classes which collects quota data + */ +class GetQuotaInfo +{ + public: + GetQuotaInfo() + { + cfg.cfgType = QuotaDataType_NONE; + cfg.cfgID = 0; + cfg.cfgUseAll = false; + cfg.cfgUseList = false; + cfg.cfgUseRange = false; + cfg.cfgCsv = false; + cfg.cfgIDRangeStart = 0; + cfg.cfgIDRangeEnd = 0; + cfg.cfgTargetSelection = GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST; + cfg.cfgTargetNumID = 0; + cfg.cfgPrintUnused = false; + cfg.cfgWithSystemUsersGroups = false; + cfg.cfgDefaultLimits = false; + } + + bool requestQuotaLimitsAndCollectResponses(const NodeHandle& mgmtNode, + MultiWorkQueue* workQ, QuotaDataMapForTarget* outQuotaResults, const TargetMapper* mapper, + StoragePoolId storagePoolId = StoragePoolStore::INVALID_POOL_ID); + + bool requestQuotaDataAndCollectResponses(const NodeStoreServers* storageNodes, + MultiWorkQueue* workQ, QuotaDataMapForTarget* outQuotaResults, const TargetMapper* mapper, + QuotaInodeSupport* quotaInodeSupport, StoragePoolStore* storagePoolStore = NULL, + StoragePoolId storagePoolId = StoragePoolStore::INVALID_POOL_ID); + + protected: + GetQuotaInfoConfig cfg; + + int getMaxMessageCount(); + QuotaDataMap calculateQuotaSums(const QuotaDataMapForTarget& quotaMaps); +}; + diff --git a/common/source/common/storage/quota/Quota.cpp b/common/source/common/storage/quota/Quota.cpp new file mode 100644 index 0000000..c488595 --- /dev/null +++ b/common/source/common/storage/quota/Quota.cpp @@ -0,0 +1,21 @@ +#include "Quota.h" + +std::ostream& operator<<(std::ostream& stream, const QuotaBlockDeviceFsType type) +{ + + switch (type) + { + case QuotaBlockDeviceFsType_UNKNOWN: + return stream << "Unknown"; + case QuotaBlockDeviceFsType_EXTX: + return stream << "Ext2/3/4"; + case QuotaBlockDeviceFsType_XFS: + return stream << "XFS"; + case QuotaBlockDeviceFsType_ZFS: + return stream << "ZFS"; + case QuotaBlockDeviceFsType_ZFSOLD: + return stream << "ZFS<0.7.4"; + } + + return stream << "Invalid"; +} diff --git a/common/source/common/storage/quota/Quota.h b/common/source/common/storage/quota/Quota.h new file mode 100644 index 0000000..a72597c --- /dev/null +++ b/common/source/common/storage/quota/Quota.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +enum QuotaBlockDeviceFsType +{ + QuotaBlockDeviceFsType_UNKNOWN=0, + QuotaBlockDeviceFsType_EXTX=1, + QuotaBlockDeviceFsType_XFS=2, + QuotaBlockDeviceFsType_ZFS=3, + QuotaBlockDeviceFsType_ZFSOLD=4 // zfs < 0.7.4 (no inode quota) +}; + +std::ostream& operator<<(std::ostream& stream, const QuotaBlockDeviceFsType type); + +enum QuotaInodeSupport +{ + QuotaInodeSupport_UNKNOWN=0, + QuotaInodeSupport_ALL_BLOCKDEVICES=1, + QuotaInodeSupport_SOME_BLOCKDEVICES=2, + QuotaInodeSupport_NO_BLOCKDEVICES=3 +}; + diff --git a/common/source/common/storage/quota/QuotaConfig.h b/common/source/common/storage/quota/QuotaConfig.h new file mode 100644 index 0000000..131441b --- /dev/null +++ b/common/source/common/storage/quota/QuotaConfig.h @@ -0,0 +1,26 @@ +#pragma once + + +#include +#include + + +/** + * A parent struct for all struct with quota configurations + */ +struct QuotaConfig +{ + QuotaDataType cfgType; // the type of the IDs: QuotaDataType_... + unsigned cfgID; // a single UID/GID + bool cfgUseAll; // true if all available UIDs/GIDs needed + bool cfgUseList; // true if a UID/GID list is given + bool cfgUseRange; // true if a UID/GID range is given + bool cfgCsv; // true if csv print (no units and csv) needed + unsigned cfgIDRangeStart; // the first UIDs/GIDs of a range to collect the quota data/limits + unsigned cfgIDRangeEnd; // the last UIDs/GIDs of a range to collect the quota data/limits + UIntList cfgIDList; // the list of UIDs/GIDs to collect the quota data/limits + bool cfgDefaultLimits; // true if default quota limits should be set/requested + StoragePoolId cfgStoragePoolId; +}; + + diff --git a/common/source/common/storage/quota/QuotaData.cpp b/common/source/common/storage/quota/QuotaData.cpp new file mode 100644 index 0000000..39107d4 --- /dev/null +++ b/common/source/common/storage/quota/QuotaData.cpp @@ -0,0 +1,237 @@ +#include +#include +#include +#include "QuotaData.h" + +/** + * Loads the quota data from a file. The map is write locked during the load. Marks the store as + * clean also when an error occurs. + * + * @param map the map with the QuotaData to load + * @param path the path to the file + * @return true if quota data are successful loaded + */ +bool QuotaData::loadQuotaDataMapForTargetFromFile(QuotaDataMapForTarget& outMap, + const std::string& path) +{ + LogContext log("LoadQuotaDataMap"); + + bool retVal = false; + boost::scoped_array buf; + size_t readRes; + + struct stat statBuf; + int retValStat; + + if(!path.length() ) + return false; + + int fd = open(path.c_str(), O_RDONLY, 0); + if(fd == -1) + { // open failed + log.log(Log_DEBUG, "Unable to open quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + retValStat = fstat(fd, &statBuf); + if(retValStat) + { // stat failed + log.log(Log_WARNING, "Unable to stat quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + + goto err_stat; + } + + buf.reset(new (std::nothrow) char[statBuf.st_size]); + if (!buf) + { + LOG(QUOTA, WARNING, "Memory allocation failed"); + goto err_stat; + } + + readRes = read(fd, buf.get(), statBuf.st_size); + if(readRes <= 0) + { // reading failed + log.log(Log_WARNING, "Unable to read quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + } + else + { // parse contents + Deserializer des(buf.get(), readRes); + des % outMap; + retVal = des.good(); + } + + if (!retVal) + log.logErr("Could not deserialize quota data from file: " + path); +#ifdef BEEGFS_DEBUG + else + { + int targetCounter = 0; + int dataCounter = 0; + + for(QuotaDataMapForTargetIter targetIter = outMap.begin(); targetIter != outMap.end(); + targetIter++) + { + targetCounter++; + + for(QuotaDataMapIter iter = targetIter->second.begin(); iter != targetIter->second.end(); + iter++) + dataCounter++; + } + + log.log(Log_DEBUG, StringTk::intToStr(dataCounter) + " quota data of " + + StringTk::intToStr(targetCounter) + " targets are loaded from file " + path); + } +#endif + +err_stat: + close(fd); + + return retVal; +} + +/** + * Stores the quota data into a file. The map is write locked during the save. + * + * @param map the map with the QuotaData to store + * @param path the path to the file + * @return true if the limits are successful stored + */ +bool QuotaData::saveQuotaDataMapForTargetToFile(const QuotaDataMapForTarget &map, + const std::string& path) +{ + LogContext log("SaveQuotaDataMap"); + + bool retVal = false; + + boost::scoped_array buf; + ssize_t bufLen; + ssize_t writeRes; + + if(!path.length() ) + return false; + + Path quotaDataPath(path); + if(!StorageTk::createPathOnDisk(quotaDataPath, true) ) + { + log.logErr("Unable to create directory for quota data: " + quotaDataPath.str() ); + return false; + } + + // create/trunc file + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(path.c_str(), openFlags, 0666); + if(fd == -1) + { // error + log.logErr("Unable to create quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + // file created => store data + bufLen = serializeIntoNewBuffer(map, buf); + if(bufLen < 0) + { + log.logErr("Unable to store quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + + goto err_closefile; + } + + writeRes = write(fd, buf.get(), bufLen); + if(writeRes != (ssize_t)bufLen) + { + log.logErr("Unable to store quota data file: " + path + ". " + + "SysErr: " + System::getErrString() ); + + goto err_closefile; + } +#ifdef BEEGFS_DEBUG + else + { + int targetCounter = 0; + int dataCounter = 0; + + for(QuotaDataMapForTargetConstIter targetIter = map.begin(); targetIter != map.end(); + targetIter++) + { + targetCounter++; + + for(QuotaDataMapConstIter iter = targetIter->second.begin(); + iter != targetIter->second.end(); + iter++) + dataCounter++; + } + + log.log(Log_DEBUG, StringTk::intToStr(dataCounter) + " quota data of " + + StringTk::intToStr(targetCounter) + " targets are stored to file " + path); + } +#endif + + retVal = true; + + // error compensation +err_closefile: + close(fd); + + return retVal; +} + +void QuotaData::quotaDataMapToString(const QuotaDataMap& map, QuotaDataType quotaDataType, + std::ostringstream* outStream) +{ + std::string quotaDataTypeStr; + + if(quotaDataType == QuotaDataType_USER) + quotaDataTypeStr = "user"; + else + if(quotaDataType == QuotaDataType_GROUP) + quotaDataTypeStr = "group"; + + + *outStream << map.size() << " used quota for " << quotaDataTypeStr << " IDs: " << std::endl; + + for(QuotaDataMapConstIter idIter = map.begin(); idIter != map.end(); idIter++) + { + *outStream << "ID: " << idIter->second.getID() + << " size: " << idIter->second.getSize() + << " inodes: " << idIter->second.getInodes() + << std::endl; + } +} + +void QuotaData::quotaDataMapForTargetToString(const QuotaDataMapForTarget& map, + QuotaDataType quotaDataType, std::ostringstream* outStream) +{ + std::string quotaDataTypeStr; + + if(quotaDataType == QuotaDataType_USER) + quotaDataTypeStr = "user"; + else + if(quotaDataType == QuotaDataType_GROUP) + quotaDataTypeStr = "group"; + + *outStream << "Quota data of " << map.size() << " targets." << std::endl; + for(QuotaDataMapForTargetConstIter iter = map.begin(); iter != map.end(); iter++) + { + uint16_t targetNumID = iter->first; + *outStream << "Used quota for " << quotaDataTypeStr << " IDs on target: " << targetNumID + << std::endl; + + QuotaData::quotaDataMapToString(iter->second, quotaDataType, outStream); + } +} + +void QuotaData::quotaDataListToString(const QuotaDataList& list, std::ostringstream* outStream) +{ + for(QuotaDataListConstIter idIter = list.begin(); idIter != list.end(); idIter++) + *outStream << "ID: " << idIter->getID() + << " size: " << idIter->getSize() + << " inodes: " << idIter->getInodes() + << std::endl; +} diff --git a/common/source/common/storage/quota/QuotaData.h b/common/source/common/storage/quota/QuotaData.h new file mode 100644 index 0000000..b1c8e95 --- /dev/null +++ b/common/source/common/storage/quota/QuotaData.h @@ -0,0 +1,334 @@ +#pragma once + + +#include +#include + +#include + + +class QuotaData; //forward declaration + +typedef std::map QuotaDataMap; +typedef QuotaDataMap::iterator QuotaDataMapIter; +typedef QuotaDataMap::const_iterator QuotaDataMapConstIter; +typedef QuotaDataMap::value_type QuotaDataMapVal; + +typedef std::map QuotaDataMapForTarget; +typedef QuotaDataMapForTarget::iterator QuotaDataMapForTargetIter; +typedef QuotaDataMapForTarget::const_iterator QuotaDataMapForTargetConstIter; +typedef QuotaDataMapForTarget::value_type QuotaDataMapForTargetMapVal; + +typedef std::list QuotaDataList; +typedef QuotaDataList::iterator QuotaDataListIter; +typedef QuotaDataList::const_iterator QuotaDataListConstIter; + + +#define QUOTADATAMAPFORTARGET_ALL_TARGETS_ID 0 + + +/** + * enum for the different quota types + */ +enum QuotaDataType +{ + QuotaDataType_NONE = 0, + QuotaDataType_USER = 1, + QuotaDataType_GROUP = 2 +}; + + +/** + * enum for the type of quota limit + */ +enum QuotaLimitType +{ + QuotaLimitType_NONE=0, + QuotaLimitType_SIZE=1, + QuotaLimitType_INODE=2 +}; + + +/** + * enum for the different quota errors (return error codes) + */ +enum QuotaExceededErrorType +{ + QuotaExceededErrorType_NOT_EXCEEDED = 0, + QuotaExceededErrorType_USER_SIZE_EXCEEDED = 1, + QuotaExceededErrorType_USER_INODE_EXCEEDED = 2, + QuotaExceededErrorType_GROUP_SIZE_EXCEEDED = 3, + QuotaExceededErrorType_GROUP_INODE_EXCEEDED = 4 +}; + + +/** + * struct with all quota related informations of a user session, extended with info about enabled + * quota enforcement + */ +struct SessionQuotaInfo +{ + /** + * Constructor, struct with all quota related informations of a user session + * + * @param useQuota true, if the client has quota enabled (part of the session) + * @param enforceQuota true, if the server has quota enforcement enabled (part of server conf) + * @param uid the UID of the session (part of the session) + * @param gid the GID of the session (part of the session) + */ + SessionQuotaInfo(bool useQuota, bool enforceQuota, unsigned uid, unsigned gid) : + useQuota(useQuota), enforceQuota(enforceQuota), uid(uid), gid(gid) + { /* all inits done in initlializer list */ } + + bool useQuota; // fhgfs-client has quota enabled + bool enforceQuota; // fhgfs-storage has quota enforcement enabled + unsigned uid; // user id + unsigned gid; // group id +}; + + +class QuotaData +{ + public: + QuotaData(unsigned id, QuotaDataType type) + { + this->id = id; + this->type = type; + this->size = 0; + this->inodes = 0; + this->valid = true; + } + + /* + * For deserialization only + */ + QuotaData() + { + + } + + static bool saveQuotaDataMapForTargetToFile(const QuotaDataMapForTarget& map, + const std::string& path); + static bool loadQuotaDataMapForTargetFromFile(QuotaDataMapForTarget& outMap, + const std::string& path); + + static void quotaDataMapToString(const QuotaDataMap& map, QuotaDataType quotaDataType, + std::ostringstream* outStream); + static void quotaDataMapForTargetToString(const QuotaDataMapForTarget& map, + QuotaDataType quotaDataType, std::ostringstream* outStream); + static void quotaDataListToString(const QuotaDataList& list, std::ostringstream* outStream); + + + protected: + + + private: + unsigned id; // UID or GID + int type; // enum QuotaDataType + uint64_t size; // defined size limit or used size (bytes) + uint64_t inodes; // defined inodes limit or used inodes (counter) + bool valid; // true, if this QuotaData are valid + + public: + //getter and setter + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->size + % obj->inodes + % obj->id + % obj->type + % obj->valid; + } + + unsigned getID() const + { + return id; + } + + void setID(unsigned id) + { + this->id = id; + } + + uint64_t getSize() const + { + return this->size; + } + + uint64_t getInodes() const + { + return this->inodes; + } + + /** + * sets the size, sets the inodes count and sets the valid flag on true + * + * @param size the size counter, the defined size limit or the used size + * @param inodes the inodes counter, the defined inodes limit or the used inodes + */ + void setQuotaData(uint64_t size, uint64_t inodes) + { + this->size = size; + this->inodes = inodes; + this->valid = true; + } + + QuotaDataType getType() const + { + return (QuotaDataType)this->type; + } + + void setType(QuotaDataType type) + { + this->type = type; + } + + bool isValid() const + { + return this->valid; + } + + void setIsValid(bool isValid) + { + this->valid = isValid; + } + + + //public inliner + + /** + * Merges the given QuotaData into this object, but only if the id and the type is the same, + * it also checks if both QuotaData counter are valid + * + * @param second the quota data to merge + */ + bool mergeQuotaDataCounter(const QuotaData* second) + { + if( (this->id != second->id) || (this->type != second->type) || + (!this->valid) || (!second->valid) ) + return false; + + this->size += second->size; + this->inodes += second->inodes; + + return true; + } + + /** + * Merges the given values into the this object + * + * @param size the size counter, the defined size limit or the used size + * @param inodes the inodes counter, the defined inodes limit or the used inodes + */ + void forceMergeQuotaDataCounter(uint64_t size, uint64_t inodes) + { + this->size += size; + this->inodes += inodes; + } + + /** + * add a QuotaData object to a QuotaDataList if the list doesn't contain a QuotaData object + * with the same ID + * + * @param quota the QuotaData object which should be added to the given list + * @param quotaList the list where the given QuotaData should be added + * @return true if quota was added successful, false if the list contains the ID and the + * given quota was ignored + */ + static bool uniqueIDAddQuotaDataToList(QuotaData quota, QuotaDataList* quotaList) + { + for(QuotaDataListIter iter = quotaList->begin(); iter != quotaList->end(); iter++) + if(iter->getID() == quota.getID()) + return false; + + quotaList->push_back(quota); + return true; + } + + static std::string QuotaLimitTypeToString(QuotaLimitType type) + { + if(type == QuotaLimitType_SIZE) + return "QuotaLimitType_SIZE"; + else + if(type == QuotaLimitType_INODE) + return "QuotaLimitType_INODE"; + else + return "QuotaLimitType_UNDEFINED"; + } + + static std::string QuotaDataTypeToString(QuotaDataType type) + { + if(type == QuotaDataType_USER) + return "QuotaDataType_USER"; + else + if(type == QuotaDataType_GROUP) + return "QuotaDataType_GROUP"; + else + if(type == QuotaDataType_NONE) + return "QuotaDataType_NONE"; + else + return "QuotaDataType_UNDEFINED"; + } + + static std::string QuotaExceededErrorTypeToString(QuotaExceededErrorType type) + { + if(type == QuotaExceededErrorType_NOT_EXCEEDED) + return "Quota not exceeded."; + else + if(type == QuotaExceededErrorType_USER_SIZE_EXCEEDED) + return "User size quota exceeded."; + else + if(type == QuotaExceededErrorType_USER_INODE_EXCEEDED) + return "User inode quota exceeded."; + else + if(type == QuotaExceededErrorType_GROUP_SIZE_EXCEEDED) + return "Group size quota exceeded."; + else + if(type == QuotaExceededErrorType_GROUP_INODE_EXCEEDED) + return "Group inode quota exceeded."; + else + return "Unknown Type"; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + +// maps serialize same as lists, deserialize with the element ID as key +inline Serializer& operator%(Serializer& ser, const QuotaDataMap& map) +{ + ser % uint32_t(map.size() ); + for(QuotaDataMapConstIter it = map.begin(), end = map.end(); it != end; ++it) + ser % it->second; + + return ser; +} + +inline Deserializer& operator%(Deserializer& des, QuotaDataMap& map) +{ + uint32_t elemCount; + + des % elemCount; + if(!des.good() ) + return des; + + map.clear(); + while(map.size() != elemCount) + { + QuotaData element; + des % element; + if(!des.good() ) + break; + + map.insert(std::make_pair(element.getID(), element) ); + } + + return des; +} + +template<> +struct MapSerializationHasLength : boost::true_type {}; + diff --git a/common/source/common/storage/quota/QuotaDefaultLimits.cpp b/common/source/common/storage/quota/QuotaDefaultLimits.cpp new file mode 100644 index 0000000..5bfde17 --- /dev/null +++ b/common/source/common/storage/quota/QuotaDefaultLimits.cpp @@ -0,0 +1,143 @@ +#include "QuotaDefaultLimits.h" + +#include +#include +#include + +bool QuotaDefaultLimits::loadFromFile() +{ + bool retVal = false; + + struct stat statBuf; + char* buf = NULL; + size_t readRes; + + if(!storePath.length() ) + return false; + + int fd = open(storePath.c_str(), O_RDONLY, 0); + if(fd == -1) + { // open failed + LOG(QUOTA, DEBUG, "Unable to open default limits file.", storePath, sysErr); + + return false; + } + + int statRes = fstat(fd, &statBuf); + if(statRes != 0) + { // stat failed + LOG(QUOTA, WARNING, "Unable to stat default limits file.", storePath, sysErr); + + goto err_closefile; + } + + buf = (char*)malloc(statBuf.st_size); + if(!buf) + { // malloc failed + LOG(QUOTA, WARNING, "Unable to allocate memory to read the default limits file.", + storePath, sysErr); + + goto err_closefile; + } + + readRes = read(fd, buf, statBuf.st_size); + if(readRes <= 0) + { // reading failed + LOG(QUOTA, WARNING, "Unable to read default limits file.", storePath, sysErr); + + goto err_freebuf; + } + else + { // parse contents + unsigned outLen; + + bool deserRes = deserialize(buf, readRes, &outLen); + + if (!deserRes) + goto err_freebuf; + } + + retVal = true; + +err_freebuf: + free(buf); + +err_closefile: + close(fd); + + if (!retVal) + LOG(QUOTA, ERR, "Could not deserialize limits from file.", storePath); + + return retVal; +} + +bool QuotaDefaultLimits::saveToFile() +{ + bool retVal = false; + + if(!storePath.length() ) + return false; + + Path quotaDataPath(storePath); + if(!StorageTk::createPathOnDisk(quotaDataPath, true) ) + { + LOG(QUOTA, ERR, "Unable to create directory for quota data.", storePath); + return false; + } + + // create/trunc file + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(storePath.c_str(), openFlags, 0666); + if(fd == -1) + { // error + LOG(QUOTA, ERR, "Unable to create default quota limits file.", storePath, sysErr); + + return false; + } + + unsigned bufLen; + ssize_t writeRes; + + // file created => store data + char* buf = (char*)malloc(serialLen() ); + if(!buf) + { + LOG(QUOTA, ERR, "Unable to allocate memory to write the default limits file.", + storePath, sysErr); + goto err_closefile; + } + + bufLen = serialize(buf); + writeRes = write(fd, buf, bufLen); + free(buf); + + if(writeRes != (ssize_t)bufLen) + { + LOG(QUOTA, ERR, "Unable to allocate memory to write the default limits file.", + storePath, sysErr); + goto err_closefile; + } + + retVal = true; + +err_closefile: + close(fd); + + return retVal; +} + +void QuotaDefaultLimits::clearLimits() +{ + defaultUserQuotaInodes = 0; + defaultUserQuotaSize = 0; + defaultGroupQuotaInodes = 0; + defaultGroupQuotaSize = 0; + + int unlinkRes = unlink(storePath.c_str()); + + if ( (unlinkRes != 0) && (errno != ENOENT) ) + { + LOG(QUOTA, WARNING, "Could not delete file", ("path", storePath), sysErr); + } +} diff --git a/common/source/common/storage/quota/QuotaDefaultLimits.h b/common/source/common/storage/quota/QuotaDefaultLimits.h new file mode 100644 index 0000000..19e0674 --- /dev/null +++ b/common/source/common/storage/quota/QuotaDefaultLimits.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +class QuotaDefaultLimits +{ + public: + QuotaDefaultLimits(): + defaultUserQuotaInodes(0), defaultUserQuotaSize(0), + defaultGroupQuotaInodes(0), defaultGroupQuotaSize(0) + {}; + + QuotaDefaultLimits(const std::string& storePath): + storePath(storePath), defaultUserQuotaInodes(0), defaultUserQuotaSize(0), + defaultGroupQuotaInodes(0), defaultGroupQuotaSize(0) + {}; + + QuotaDefaultLimits(const std::string& storePath, size_t userInodesLimit, size_t userSizeLimit, + size_t groupInodesLimit, size_t groupSizeLimit): + storePath(storePath), defaultUserQuotaInodes(userInodesLimit), + defaultUserQuotaSize(userSizeLimit), defaultGroupQuotaInodes(groupInodesLimit), + defaultGroupQuotaSize(groupSizeLimit) + {}; + + private: + std::string storePath; // path on disk to store the data + uint64_t defaultUserQuotaInodes; + uint64_t defaultUserQuotaSize; + uint64_t defaultGroupQuotaInodes; + uint64_t defaultGroupQuotaSize; + + + public: + bool loadFromFile(); + bool saveToFile(); + + void clearLimits(); + + uint64_t getDefaultUserQuotaInodes() const + { + return defaultUserQuotaInodes; + } + + uint64_t getDefaultUserQuotaSize() const + { + return defaultUserQuotaSize; + } + + uint64_t getDefaultGroupQuotaInodes() const + { + return defaultGroupQuotaInodes; + } + + uint64_t getDefaultGroupQuotaSize() const + { + return defaultGroupQuotaSize; + } + + void updateUserLimits(uint64_t inodeLimit, uint64_t sizeLimit) + { + defaultUserQuotaInodes = inodeLimit; + defaultUserQuotaSize = sizeLimit; + } + + void updateGroupLimits(uint64_t inodeLimit, uint64_t sizeLimit) + { + defaultGroupQuotaInodes = inodeLimit; + defaultGroupQuotaSize = sizeLimit; + } + + void updateLimits(QuotaDefaultLimits& limits) + { + defaultUserQuotaInodes = limits.getDefaultUserQuotaInodes(); + defaultUserQuotaSize = limits.getDefaultUserQuotaSize(); + defaultGroupQuotaInodes = limits.getDefaultGroupQuotaInodes(); + defaultGroupQuotaSize = limits.getDefaultGroupQuotaSize(); + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->defaultUserQuotaInodes + % obj->defaultUserQuotaSize + % obj->defaultGroupQuotaInodes + % obj->defaultGroupQuotaSize; + } + + unsigned serialLen() const + { + return serialize(NULL); + } + + size_t serialize(char* buf) const + { + Serializer ser(buf, -1); + ser % *this; + return ser.size(); + } + + bool deserialize(const char* buf, size_t bufLen, unsigned* outLen) + { + Deserializer des(buf, bufLen); + des % *this; + *outLen = des.size(); + return des.good(); + } +}; + diff --git a/common/source/common/storage/striping/BuddyMirrorPattern.cpp b/common/source/common/storage/striping/BuddyMirrorPattern.cpp new file mode 100644 index 0000000..bbb4ebc --- /dev/null +++ b/common/source/common/storage/striping/BuddyMirrorPattern.cpp @@ -0,0 +1,25 @@ +#include "BuddyMirrorPattern.h" + +bool BuddyMirrorPattern::updateStripeTargetIDs(StripePattern* updatedStripePattern) +{ + if ( !updatedStripePattern ) + return false; + + if ( this->mirrorBuddyGroupIDs.size() != updatedStripePattern->getStripeTargetIDs()->size() ) + return false; + + for ( unsigned i = 0; i < mirrorBuddyGroupIDs.size(); i++ ) + { + mirrorBuddyGroupIDs[i] = updatedStripePattern->getStripeTargetIDs()->at(i); + } + + return true; +} + +bool BuddyMirrorPattern::patternEquals(const StripePattern* second) const +{ + const BuddyMirrorPattern* other = (const BuddyMirrorPattern*) second; + + return defaultNumTargets == other->defaultNumTargets + && mirrorBuddyGroupIDs == other->mirrorBuddyGroupIDs; +} diff --git a/common/source/common/storage/striping/BuddyMirrorPattern.h b/common/source/common/storage/striping/BuddyMirrorPattern.h new file mode 100644 index 0000000..aea066d --- /dev/null +++ b/common/source/common/storage/striping/BuddyMirrorPattern.h @@ -0,0 +1,122 @@ +#pragma once + +#include "StripePattern.h" + +#include +#include +#include + + +#define BUDDYMIRRORPATTERN_DEFAULT_NUM_TARGETS 4 + +class BuddyMirrorPattern : public StripePattern +{ + friend class StripePattern; + + public: + /** + * @param chunkSize 0 for app-level default + * @param defaultNumTargets default number of mirror buddy groups (0 for app-level default) + * @param storagePoolId + */ + BuddyMirrorPattern(unsigned chunkSize, const UInt16Vector& mirrorBuddyGroupIDs, + unsigned defaultNumTargets = 0, + StoragePoolId storagePoolId = StoragePoolStore::DEFAULT_POOL_ID): + StripePattern(StripePatternType_BuddyMirror, chunkSize, storagePoolId), + mirrorBuddyGroupIDs(mirrorBuddyGroupIDs) + { + this->defaultNumTargets = + defaultNumTargets ? defaultNumTargets : BUDDYMIRRORPATTERN_DEFAULT_NUM_TARGETS; + } + + protected: + /** + * Note: for deserialization only + */ + BuddyMirrorPattern(unsigned chunkSize, + boost::optional storagePoolId = StoragePoolStore::DEFAULT_POOL_ID): + StripePattern(StripePatternType_BuddyMirror, chunkSize, storagePoolId) { } + + // (de)serialization + + template + static void serializeContent(This obj, Ctx& ctx) + { + ctx + % obj->defaultNumTargets + % obj->mirrorBuddyGroupIDs; + } + + virtual void serializeContent(Serializer& ser) const + { + serializeContent(this, ser); + } + + virtual void deserializeContent(Deserializer& des) + { + serializeContent(this, des); + } + + virtual bool patternEquals(const StripePattern* second) const; + + + private: + UInt16Vector mirrorBuddyGroupIDs; + uint32_t defaultNumTargets; + + public: + // getters & setters + + virtual const UInt16Vector* getStripeTargetIDs() const + { + return &mirrorBuddyGroupIDs; + } + + virtual UInt16Vector* getStripeTargetIDsModifyable() + { + return &mirrorBuddyGroupIDs; + } + + virtual bool updateStripeTargetIDs(StripePattern* updatedStripePattern); + + virtual unsigned getMinNumTargets() const + { + return getMinNumTargetsStatic(); + } + + virtual unsigned getDefaultNumTargets() const + { + return defaultNumTargets; + } + + virtual void setDefaultNumTargets(unsigned defaultNumTargets) + { + this->defaultNumTargets = defaultNumTargets; + } + + /** + * Number of targets actually assigned + */ + virtual size_t getAssignedNumTargets() const + { + return mirrorBuddyGroupIDs.size(); + } + + + // inliners + + virtual StripePattern* clone() const + { + return new auto(*this); + } + + + // static inliners + + static unsigned getMinNumTargetsStatic() + { + return 1; + } + +}; + diff --git a/common/source/common/storage/striping/ChunkFileInfo.cpp b/common/source/common/storage/striping/ChunkFileInfo.cpp new file mode 100644 index 0000000..191e051 --- /dev/null +++ b/common/source/common/storage/striping/ChunkFileInfo.cpp @@ -0,0 +1,10 @@ +#include +#include "ChunkFileInfo.h" + +bool ChunkFileInfo::operator==(const ChunkFileInfo& other) const +{ + return dynAttribs == other.dynAttribs + && chunkSize == other.chunkSize + && chunkSizeLog2 == other.chunkSizeLog2 + && numCompleteChunks == other.numCompleteChunks; +} diff --git a/common/source/common/storage/striping/ChunkFileInfo.h b/common/source/common/storage/striping/ChunkFileInfo.h new file mode 100644 index 0000000..b432fc0 --- /dev/null +++ b/common/source/common/storage/striping/ChunkFileInfo.h @@ -0,0 +1,141 @@ +#pragma once + +#include + +#include "DynamicFileAttribs.h" + + +class ChunkFileInfo; +typedef std::vector ChunkFileInfoVec; +typedef ChunkFileInfoVec::iterator ChunkFileInfoVecIter; +typedef ChunkFileInfoVec::const_iterator ChunkFileInfoVecCIter; + + +/* + * Note: the optimizations here and in other places rely on chunksize being a power of two. + */ + + +class ChunkFileInfo +{ + public: + /** + * @param chunkSize must be a power of two + * @param chunkSizeLog2 MathTk::log2(chunkSize), just provided (and not computed internally) + * because the caller probably needs to compute this value anyways and/or can re-use it. + */ + ChunkFileInfo(unsigned chunkSize, unsigned chunkSizeLog2, + const DynamicFileAttribs& dynAttribs) : + dynAttribs(dynAttribs) + { + this->chunkSize = chunkSize; + this->chunkSizeLog2 = chunkSizeLog2; + + this->numCompleteChunks = dynAttribs.fileSize >> chunkSizeLog2; + } + + /** + * For dezerialisation only + */ + ChunkFileInfo() {}; + + bool operator==(const ChunkFileInfo& other) const; + + bool operator!=(const ChunkFileInfo& other) const { return !(*this == other); } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->dynAttribs + % obj->chunkSize + % obj->chunkSizeLog2 + % obj->numCompleteChunks; + } + + private: + DynamicFileAttribs dynAttribs; + + uint32_t chunkSize; // must be a power of 2, because we rely on that with our optimizations + uint32_t chunkSizeLog2; // for bitshifting instead of division + int64_t numCompleteChunks; + + + public: + // getters & setters + + uint64_t getStorageVersion() const + { + return dynAttribs.storageVersion; + } + + int64_t getFileSize() const + { + return dynAttribs.fileSize; + } + + int64_t getModificationTimeSecs() const + { + return dynAttribs.modificationTimeSecs; + } + + int64_t getLastAccessTimeSecs() const + { + return dynAttribs.lastAccessTimeSecs; + } + + + /** + * Note: Only applies the new data if the storageVersion has increased (relies on monotonic + * increasing storageVersions) + * + * @return true if attribs updated (because of higher storageVersion) + */ + bool updateDynAttribs(const DynamicFileAttribs& newAttribs) + { + // note: we do a ">"-comparison, so newAttribs->storageVersion==0 will not be accepted + // (=> zero means invalid/uninitialized version) + + // we only apply the data if the storageVersion is higher than current + if(newAttribs.storageVersion > dynAttribs.storageVersion) + { + this->dynAttribs = newAttribs; + + this->numCompleteChunks = dynAttribs.fileSize >> chunkSizeLog2; + + return true; + } + + return false; + } + + int64_t getNumCompleteChunks() const + { + return numCompleteChunks; + } + + int64_t getNumChunks() const + { + /* note: chunkSize is a power of two, so "(dynAttribs.length & (chunkSize-1) )" equals + "(dynAttribs.length % chunkSize)". */ + + int64_t incompleteChunk = (dynAttribs.fileSize & (chunkSize-1) ) ? 1 : 0; + + return numCompleteChunks + incompleteChunk; + } + + uint64_t getNumBlocks() const + { + return dynAttribs.numBlocks; + } + + /** + * Note: This should only be used in very special cases (e.g. forced updates), because caller + * potentially ignores storageVersion check. + */ + DynamicFileAttribs* getRawDynAttribs() + { + return &dynAttribs; + } +}; + diff --git a/common/source/common/storage/striping/DynamicFileAttribs.h b/common/source/common/storage/striping/DynamicFileAttribs.h new file mode 100644 index 0000000..c989e09 --- /dev/null +++ b/common/source/common/storage/striping/DynamicFileAttribs.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include + +/* + * Note: the optimizations here and in other places rely on chunksize being a power of two. + */ + +class DynamicFileAttribs +{ + public: + DynamicFileAttribs() : storageVersion(0) + { + // we only need to init storageVersion with 0, because that means all contained attribs + // are invalid + } + + DynamicFileAttribs(uint64_t storageVersion, int64_t fileSize, uint64_t numBlocks, + int64_t modificationTimeSecs, int64_t lastAccessTimeSecs) : + storageVersion(storageVersion), + fileSize(fileSize), + numBlocks(numBlocks), + modificationTimeSecs(modificationTimeSecs), + lastAccessTimeSecs(lastAccessTimeSecs) + { + // nothing to do here (all done in initializer list) + } + + + uint64_t storageVersion; // use 0 as initial version and then only monotonic increase + + int64_t fileSize; + int64_t numBlocks; // number of used blocks by this chunk + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->storageVersion + % obj->fileSize + % obj->numBlocks + % obj->modificationTimeSecs + % obj->lastAccessTimeSecs; + } + + bool operator==(const DynamicFileAttribs& other) const + { + return storageVersion == other.storageVersion + && fileSize == other.fileSize + && numBlocks == other.numBlocks + && modificationTimeSecs == other.modificationTimeSecs + && lastAccessTimeSecs == other.lastAccessTimeSecs; + } + + bool operator!=(const DynamicFileAttribs& other) const { return !(*this == other); } +}; + +typedef std::vector DynamicFileAttribsVec; +typedef DynamicFileAttribsVec::iterator DynamicFileAttribsVecIter; +typedef DynamicFileAttribsVec::const_iterator DynamicFileAttribsVecCIter; + diff --git a/common/source/common/storage/striping/Raid0Pattern.cpp b/common/source/common/storage/striping/Raid0Pattern.cpp new file mode 100644 index 0000000..aa76417 --- /dev/null +++ b/common/source/common/storage/striping/Raid0Pattern.cpp @@ -0,0 +1,25 @@ +#include "Raid0Pattern.h" + +bool Raid0Pattern::updateStripeTargetIDs(StripePattern* updatedStripePattern) +{ + if ( !updatedStripePattern ) + return false; + + if ( this->stripeTargetIDs.size() != updatedStripePattern->getStripeTargetIDs()->size() ) + return false; + + for ( unsigned i = 0; i < stripeTargetIDs.size(); i++ ) + { + stripeTargetIDs[i] = updatedStripePattern->getStripeTargetIDs()->at(i); + } + + return true; +} + +bool Raid0Pattern::patternEquals(const StripePattern* second) const +{ + const Raid0Pattern* other = (const Raid0Pattern*) second; + + return defaultNumTargets == other->defaultNumTargets + && stripeTargetIDs == other->stripeTargetIDs; +} diff --git a/common/source/common/storage/striping/Raid0Pattern.h b/common/source/common/storage/striping/Raid0Pattern.h new file mode 100644 index 0000000..138e19b --- /dev/null +++ b/common/source/common/storage/striping/Raid0Pattern.h @@ -0,0 +1,121 @@ +#pragma once +#include "StripePattern.h" + + +#include +#include +#include + +#define RAID0PATTERN_DEFAULT_NUM_TARGETS 4 + + +class Raid0Pattern : public StripePattern +{ + friend class StripePattern; + + public: + /** + * @param chunkSize 0 for app-level default + * @param defaultNumTargets default number of targets (0 for app-level default) + * @þaram storagePoolId + */ + Raid0Pattern(unsigned chunkSize, const UInt16Vector& stripeTargetIDs, + unsigned defaultNumTargets = 0, + StoragePoolId storagePoolId = StoragePoolStore::DEFAULT_POOL_ID) : + StripePattern(StripePatternType_Raid0, chunkSize, storagePoolId), + stripeTargetIDs(stripeTargetIDs) + { + this->defaultNumTargets = + defaultNumTargets ? defaultNumTargets : RAID0PATTERN_DEFAULT_NUM_TARGETS; + } + + protected: + /** + * Note: for deserialization only + */ + Raid0Pattern(unsigned chunkSize, + boost::optional storagePoolId = StoragePoolStore::DEFAULT_POOL_ID) : + StripePattern(StripePatternType_Raid0, chunkSize, storagePoolId) { } + + // (de)serialization + + template + static void serializeContent(This obj, Ctx& ctx) + { + ctx + % obj->defaultNumTargets + % obj->stripeTargetIDs; + } + + virtual void serializeContent(Serializer& ser) const + { + serializeContent(this, ser); + } + + virtual void deserializeContent(Deserializer& des) + { + serializeContent(this, des); + } + + virtual bool patternEquals(const StripePattern* second) const; + + private: + UInt16Vector stripeTargetIDs; + uint32_t defaultNumTargets; + + public: + // getters & setters + + virtual const UInt16Vector* getStripeTargetIDs() const + { + return &stripeTargetIDs; + } + + virtual UInt16Vector* getStripeTargetIDsModifyable() + { + return &stripeTargetIDs; + } + + virtual bool updateStripeTargetIDs(StripePattern* updatedStripePattern); + + virtual unsigned getMinNumTargets() const + { + return getMinNumTargetsStatic(); + } + + virtual unsigned getDefaultNumTargets() const + { + return defaultNumTargets; + } + + virtual void setDefaultNumTargets(unsigned defaultNumTargets) + { + this->defaultNumTargets = defaultNumTargets; + } + + /** + * Number of targets actually assigned + */ + virtual size_t getAssignedNumTargets() const + { + return stripeTargetIDs.size(); + } + + + // inliners + + virtual StripePattern* clone() const + { + return new auto(*this); + } + + + // static inliners + + static unsigned getMinNumTargetsStatic() + { + return 1; + } + +}; + diff --git a/common/source/common/storage/striping/Raid10Pattern.cpp b/common/source/common/storage/striping/Raid10Pattern.cpp new file mode 100644 index 0000000..192a36b --- /dev/null +++ b/common/source/common/storage/striping/Raid10Pattern.cpp @@ -0,0 +1,26 @@ +#include "Raid10Pattern.h" + +bool Raid10Pattern::updateStripeTargetIDs(StripePattern* updatedStripePattern) +{ + if ( !updatedStripePattern ) + return false; + + if ( this->stripeTargetIDs.size() != updatedStripePattern->getStripeTargetIDs()->size() ) + return false; + + for ( unsigned i = 0; i < stripeTargetIDs.size(); i++ ) + { + stripeTargetIDs[i] = updatedStripePattern->getStripeTargetIDs()->at(i); + } + + return true; +} + +bool Raid10Pattern::patternEquals(const StripePattern* second) const +{ + const Raid10Pattern* other = (const Raid10Pattern*) second; + + return defaultNumTargets == other->defaultNumTargets + && stripeTargetIDs == other->stripeTargetIDs + && mirrorTargetIDs == other->mirrorTargetIDs; +} diff --git a/common/source/common/storage/striping/Raid10Pattern.h b/common/source/common/storage/striping/Raid10Pattern.h new file mode 100644 index 0000000..cd5ac13 --- /dev/null +++ b/common/source/common/storage/striping/Raid10Pattern.h @@ -0,0 +1,158 @@ +#pragma once + +#include +#include +#include +#include + +#include "StripePattern.h" + + +#define RAID10PATTERN_DEFAULT_NUM_TARGETS 4 + + +class Raid10Pattern : public StripePattern +{ + friend class StripePattern; + + public: + /** + * @param chunkSize 0 for app-level default + * @param mirrorTargetIDs caller is responsible for ensuring that length of stripeTargetIDs + * and length of mirrorTargetIDs are equal. + * @param defaultNumTargets default number of targets (0 for app-level default) + */ + Raid10Pattern(unsigned chunkSize, const UInt16Vector& stripeTargetIDs, + const UInt16Vector& mirrorTargetIDs, unsigned defaultNumTargets=0, + StoragePoolId storagePoolId = StoragePoolStore::DEFAULT_POOL_ID): + StripePattern(StripePatternType_Raid10, chunkSize, storagePoolId), + stripeTargetIDs(stripeTargetIDs), mirrorTargetIDs(mirrorTargetIDs) + { + this->defaultNumTargets = + defaultNumTargets ? defaultNumTargets : RAID10PATTERN_DEFAULT_NUM_TARGETS; + + // sanity check for equal vector lengths... + + #ifdef BEEGFS_DEBUG + if(unlikely(stripeTargetIDs.size() != mirrorTargetIDs.size() ) ) + { + LogContext(__func__).logErr("Unexpected: Vectors lengths differ: " + "stripeTargetIDs: " + StringTk::uintToStr(stripeTargetIDs.size() ) + "; " + "mirrorTargetIDs: " + StringTk::uintToStr(mirrorTargetIDs.size() ) ); + LogContext(__func__).logBacktrace(); + } + #endif // BEEGFS_DEBUG + + } + + + protected: + /** + * Note: for deserialization only + */ + Raid10Pattern(unsigned chunkSize, + boost::optional storagePoolId = StoragePoolStore::DEFAULT_POOL_ID): + StripePattern(StripePatternType_Raid10, chunkSize, storagePoolId) { } + + // (de)serialization + + template + static void serializeContent(This obj, Ctx& ctx) + { + ctx + % obj->defaultNumTargets + % obj->stripeTargetIDs + % obj->mirrorTargetIDs; + } + + virtual void serializeContent(Serializer& ser) const + { + serializeContent(this, ser); + } + + virtual void deserializeContent(Deserializer& des) + { + serializeContent(this, des); + } + + virtual bool patternEquals(const StripePattern* second) const; + + private: + UInt16Vector stripeTargetIDs; + UInt16Vector mirrorTargetIDs; + uint32_t defaultNumTargets; + + + public: + // getters & setters + + virtual const UInt16Vector* getStripeTargetIDs() const + { + return &stripeTargetIDs; + } + + virtual UInt16Vector* getStripeTargetIDsModifyable() + { + return &stripeTargetIDs; + } + + virtual bool updateStripeTargetIDs(StripePattern* updatedStripePattern); + + virtual const UInt16Vector* getMirrorTargetIDs() const + { + return &mirrorTargetIDs; + } + + virtual UInt16Vector* getMirrorTargetIDsModifyable() + { + return &mirrorTargetIDs; + } + + virtual unsigned getMinNumTargets() const + { + return getMinNumTargetsStatic(); + } + + virtual unsigned getDefaultNumTargets() const + { + return defaultNumTargets; + } + + virtual void setDefaultNumTargets(unsigned defaultNumTargets) + { + this->defaultNumTargets = defaultNumTargets; + } + + /** + * Number of targets actually assigned as raid0 (excluding mirroring) + */ + virtual size_t getAssignedNumTargets() const + { + return stripeTargetIDs.size(); + } + + // inliners + + virtual StripePattern* clone() const + { + Raid10Pattern* clonedPattern = new Raid10Pattern( + getChunkSize(), stripeTargetIDs, mirrorTargetIDs, defaultNumTargets); + + return clonedPattern; + } + + StripePattern* clone(const UInt16Vector& targetIDs) const + { + return new auto(*this); + } + + + // static inliners + + static unsigned getMinNumTargetsStatic() + { + return 2; + } + +}; + diff --git a/common/source/common/storage/striping/StripePattern.cpp b/common/source/common/storage/striping/StripePattern.cpp new file mode 100644 index 0000000..eb138ef --- /dev/null +++ b/common/source/common/storage/striping/StripePattern.cpp @@ -0,0 +1,93 @@ +#include +#include "BuddyMirrorPattern.h" +#include "Raid0Pattern.h" +#include "Raid10Pattern.h" +#include "StripePattern.h" + +/** + * @return outPattern; NULL on error + */ +StripePattern* StripePattern::deserialize(Deserializer& des, bool hasPoolId) +{ + StripePatternHeader header(hasPoolId); + + des % header; + if(unlikely(!des.good() || !header.chunkSize) ) + { + des.setBad(); + return NULL; + } + + StripePattern* pattern = NULL; + auto poolId = boost::make_optional(hasPoolId, header.storagePoolId); + + switch(header.type) + { + case StripePatternType_Raid0: + pattern = new Raid0Pattern(header.chunkSize, poolId); + break; + + case StripePatternType_Raid10: + pattern = new Raid10Pattern(header.chunkSize, poolId); + break; + + case StripePatternType_BuddyMirror: + pattern = new BuddyMirrorPattern(header.chunkSize, poolId); + break; + + case StripePatternType_Invalid: + des.setBad(); + return NULL; + } + + pattern->deserializeContent(des); + + if(unlikely(!des.good() ) ) + { + LogContext(__PRETTY_FUNCTION__).logErr("Deserialization failed. " + "PatternType: " + StringTk::uintToStr(header.type) ); + delete pattern; + des.setBad(); + return NULL; + } + + return pattern; +} + +/** + * Returns human-readable pattern type. + * Just a convenience wrapper for the static version with the same name. + */ +std::string StripePattern::getPatternTypeStr() const +{ + return getPatternTypeStr(header.type); +} + +/** + * Returns human-readable pattern type. + */ +std::string StripePattern::getPatternTypeStr(StripePatternType patternType) +{ + switch(patternType) + { + case StripePatternType_Raid0: + return "RAID0"; + + case StripePatternType_Raid10: + return "RAID10"; + + case StripePatternType_BuddyMirror: + return "Buddy Mirror"; + + default: + return ""; + } +} + +bool StripePattern::stripePatternEquals(const StripePattern* second) const +{ + return getPatternType() == second->getPatternType() + && getChunkSize() == second->getChunkSize() + && getStoragePoolId() == second->getStoragePoolId() + && patternEquals(second); +} diff --git a/common/source/common/storage/striping/StripePattern.h b/common/source/common/storage/striping/StripePattern.h new file mode 100644 index 0000000..57d3368 --- /dev/null +++ b/common/source/common/storage/striping/StripePattern.h @@ -0,0 +1,307 @@ +#pragma once + +#include +#include +#include +#include + +#define STRIPEPATTERN_MIN_CHUNKSIZE (1024*64) /* minimum allowed stripe pattern chunk size */ +#define STRIPEPATTERN_DEFAULT_CHUNKSIZE (1024*512) + +enum StripePatternType +{ + // 0 was used to indicate invalid stripe patterns before, don't reuse that + StripePatternType_Invalid, + StripePatternType_Raid0, + StripePatternType_Raid10, + StripePatternType_BuddyMirror, +}; + +struct StripePatternHeader +{ + StripePatternHeader(bool hasPoolId) : + hasPoolId(hasPoolId) + { + if (!hasPoolId) + storagePoolId = StoragePoolStore::DEFAULT_POOL_ID; + } + + StripePatternHeader(StripePatternType type, uint32_t chunkSize, boost::optional storagePoolId) : + length(0), type(type), chunkSize(chunkSize), + storagePoolId(storagePoolId.value_or(StoragePoolStore::DEFAULT_POOL_ID)), + hasPoolId(!!storagePoolId) + {} + + uint32_t length; + StripePatternType type; + uint32_t chunkSize; + StoragePoolId storagePoolId; + bool hasPoolId; // used for deserialization to be compatible with v6 format + + // we use some of the high bits of type to store flags in the binary format. + // HasNoPool indicates that the stripe pattern has no pool id in it, even if hasPoolId is true. + // this is required to correctly transport stripe patterns without pool ids across the network. + // in 7.0 release a few things happened: + // * the client always deserializes a pool id. + // * for patterns read from inodes the server serializes a pool id only if it was present in + // the inode + // * pattern deserialized from scratch in userspace always deserialize a pool id + // + // old metadata formats (eg 2014) do not contain pool ids. the server will then not serialize a + // pool id, but both clients and tools will expect one. we thus need a flag to indicate that no + // pool id is present. using a flag to indicate that a pool id *is* present does not work because + // "present" is the default and must be overridden in special cases. + // + // this is a change to the disk and wire format that has no effect on the in-memory layout of + // stripe patterns in userspace and client if the pattern is read correctly: + // * from an inode with a pool id: the flag will never be set + // * from an inode without a pool id: + // * if the inode was not updated, nothing has changed + // * if the inode was updated the NoPool flag is set and has no effect. + // * from a buffer with a pool id: the flag will not be set + // * from a buffer without a pool id: the flag will be set and allow deserialization at all. + // deserializers of 7.0 rejected the pattern as erroneous in this case. + static constexpr uint32_t HasNoPoolFlag = 1 << 24; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->length; + + struct { + void operator()(const StripePatternHeader* obj, Serializer& ctx) { + uint32_t typeWithFlags = uint32_t(obj->type) | (obj->hasPoolId ? 0 : HasNoPoolFlag); + + ctx % typeWithFlags; + } + + void operator()(StripePatternHeader* obj, Deserializer& ctx) { + uint32_t typeWithFlags; + + ctx % typeWithFlags; + + obj->hasPoolId &= !(typeWithFlags & HasNoPoolFlag); + obj->type = StripePatternType(typeWithFlags & ~HasNoPoolFlag); + } + } chunkTypeAndFlags; + chunkTypeAndFlags(obj, ctx); + + ctx % obj->chunkSize; + + if (obj->hasPoolId) + ctx % obj->storagePoolId; + } +}; + +class StripePattern +{ + friend class DiskMetaData; + + public: + virtual ~StripePattern() {} + + static std::string getPatternTypeStr(StripePatternType patternType); + + + std::string getPatternTypeStr() const; + + + /** + * Creates a clone of the pattern. + * + * Note: The clone must be deleted by the caller. + * Note: See derived classes implement more clone methods with additional args. + */ + virtual StripePattern* clone() const = 0; + + bool stripePatternEquals(const StripePattern* second) const; + + protected: + /** + * @param chunkSize 0 for app-level default + */ + StripePattern(StripePatternType type, unsigned chunkSize, + boost::optional storagePoolId) + : header(type, chunkSize ? chunkSize : STRIPEPATTERN_DEFAULT_CHUNKSIZE, storagePoolId) { } + + // (de)serialization + static StripePattern* deserialize(Deserializer& des, bool hasPoolId); + + virtual void serializeContent(Serializer& ser) const = 0; + virtual void deserializeContent(Deserializer& des) = 0; + + virtual bool patternEquals(const StripePattern* second) const = 0; + + protected: + StripePatternHeader header; + + public: + // getters & setters + + StripePatternType getPatternType() const + { + return header.type; + } + + unsigned getChunkSize() const + { + return header.chunkSize; + } + + void setChunkSize(unsigned chunkSize) + { + this->header.chunkSize = chunkSize; + } + + StoragePoolId getStoragePoolId() const + { + return header.storagePoolId; + } + + void setStoragePoolId(StoragePoolId storagePoolId) + { + header.storagePoolId = storagePoolId; + header.hasPoolId = true; + } + + int64_t getChunkStart(int64_t pos) const + { + // the code below is an optimization (wrt division) for the following line: + // int64_t chunkStart = pos - (pos % this->chunkSize); + + // "& chunkSize -1" instead of "%", because chunkSize is a power of two + unsigned posModChunkSize = pos & (this->header.chunkSize - 1); + + int64_t chunkStart = pos - posModChunkSize; + + return chunkStart; + } + + int64_t getNextChunkStart(int64_t pos) const + { + return getChunkStart(pos) + this->header.chunkSize; + } + + int64_t getChunkEnd(int64_t pos) const + { + return getNextChunkStart(pos) - 1; + } + + size_t getNumStripeTargetIDs() const + { + return getStripeTargetIDs()->size(); + } + + /** + * Get index of target in stripe vector for given file offset. + */ + size_t getStripeTargetIndex(int64_t pos) const + { + return (pos / getChunkSize()) % getNumStripeTargetIDs(); + } + + /** + * Get targetID for given file offset. + */ + uint16_t getStripeTargetID(int64_t pos) const + { + size_t targetIndex = getStripeTargetIndex(pos); + + return (*getStripeTargetIDs() )[targetIndex]; + } + + /** + * Note: this is called quite often, so we have a const version to enable better compiler + * code optimizations. (there are some special cases where targetIDs need to be modified, so + * we also have the non-const/modifyable version.) + */ + virtual const UInt16Vector* getStripeTargetIDs() const = 0; + + /** + * Note: Rather use the const version (getStripeTargetIDs), if the siutation allows it. This + * method is only for special use cases, as the stripe targets usually don't change after + * pattern object creation. + */ + virtual UInt16Vector* getStripeTargetIDsModifyable() = 0; + + /** + * Note: Use carefully; this is only for very special use-cases (e.g. fsck/repair) because + * stripe targets are assumed to be immutable after pattern instantiation. + */ + virtual bool updateStripeTargetIDs(StripePattern* updatedStripePattern) = 0; + + /** + * Predefined virtual method returning NULL. Will be overridden by StripePatterns + * (e.g. Raid10) that actually do have mirror targets. + * + * @return NULL for patterns that don't have mirror targets. + */ + virtual const UInt16Vector* getMirrorTargetIDs() const + { + return NULL; + } + + /** + * The minimum number of targets required to use this pattern. + */ + virtual unsigned getMinNumTargets() const = 0; + + /** + * The desired number of targets that was given when this pattern object was created. + * (Mostly used for directories to configure how many targets a new file within the diretory + * shold have.) + * + * For patterns with redundancy, this is the number includes redundancy targets. + */ + virtual unsigned getDefaultNumTargets() const = 0; + + virtual void setDefaultNumTargets(unsigned defaultNumTargets) = 0; + + /** + * Number of targets actually assigned as raid0 + */ + virtual size_t getAssignedNumTargets() const = 0; + + + + friend Serializer& operator%(Serializer& ser, const StripePattern& pattern) + { + Serializer lengthPos = ser.mark(); + unsigned lengthAtStart = ser.size(); + + ser % pattern.header; + + pattern.serializeContent(ser); + + lengthPos % (ser.size() - lengthAtStart); + + return ser; + } + + friend Serializer& operator%(Serializer& ser, const StripePattern* pattern) + { + return ser % *pattern; + } + + friend Serializer& operator%(Serializer& ser, const std::unique_ptr& pattern) + { + return ser % *pattern; + } + + friend Deserializer& operator%(Deserializer& des, StripePattern*& pattern) + { + delete pattern; + pattern = NULL; + + pattern = StripePattern::deserialize(des, true); + + return des; + } + + friend Deserializer& operator%(Deserializer& des, std::unique_ptr& pattern) + { + pattern.reset(StripePattern::deserialize(des, true)); + return des; + } +}; + diff --git a/common/source/common/system/System.cpp b/common/source/common/system/System.cpp new file mode 100644 index 0000000..73e6dbe --- /dev/null +++ b/common/source/common/system/System.cpp @@ -0,0 +1,598 @@ +#include +#include +#include +#include "System.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Mutex System::strerrorMutex; + +uid_t System::savedEffectiveUID = geteuid(); +gid_t System::savedEffectiveGID = getegid(); + + +std::string System::getErrString() +{ + int errcode = errno; + + const std::lock_guard lock(strerrorMutex); + + return strerror(errcode); +} + +std::string System::getErrString(int errcode) +{ + const std::lock_guard lock(strerrorMutex); + + return strerror(errcode); +} + +std::string System::getHostname() +{ + struct utsname utsnameBuf; + + uname(&utsnameBuf); + + std::string hostname(utsnameBuf.nodename); + + return hostname; +} + +int System::getNumOnlineCPUs() +{ + long confRes = sysconf(_SC_NPROCESSORS_ONLN); + if(confRes <= 0) + throw InvalidConfigException("Unable to query number of online CPUs via sysconf()"); + + return confRes; +} + +/** + * Returns the number of numa nodes by reading /sys/devices/system/node/nodeXY. + * + * @return number of NUMA nodes; this has no error return value (to keep the calling code simple) + * if an error occurs (e.g. the numa check path not exists) then this will return 1 and a warning + * will be printed to log. + */ +int System::getNumNumaNodes() +{ + const char* logContext = "System (get number of NUMA nodes)"; + static bool errorLogged = false; // to avoid log spamming on error + + const char* path = "/sys/devices/system/node"; + const char* searchEntry = "node"; + size_t searchEntryStrLen = strlen(searchEntry); + + int numNumaNodes = 0; + + if(!StorageTk::pathExists(path) ) + { + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, + "NUMA node check path not found: " + std::string(path) + ". " + "Assuming single NUMA node."); + + errorLogged = true; + return 1; + } + + StringList pathEntries; // path dir entries + + StorageTk::readCompleteDir(path, &pathEntries); + + for(StringListIter iter = pathEntries.begin(); iter != pathEntries.end(); iter++) + { + if(!strncmp(searchEntry, iter->c_str(), searchEntryStrLen) ) + numNumaNodes++; // found a numa node + } + + if(!numNumaNodes) + { + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, + "No NUMA nodes found in path: " + std::string(path) + ". " + "Assuming single NUMA node."); + + errorLogged = true; + return 1; + } + + return numNumaNodes; +} + +/** + * Returns the CPU cores that belong to the given numa node. + * + * @param nodeNum number of the numa node (as seen in sys/devices/system/node/nodeXY). + * @param outCpuSet will be initialized within this method. + * @return number of CPU cores that belong to the given NUMA node (may be 0 if an error occurs, e.g. + * NUMA core check path not exists). + */ +int System::getNumaCoresByNode(int nodeNum, cpu_set_t* outCpuSet) +{ + const char* logContext = "System (get NUMA cores by node)"; + static bool errorLogged = false; // to avoid log spamming on error + + std::string path = "/sys/devices/system/node/node" + StringTk::intToStr(nodeNum); + const char* searchEntry = "cpu"; + size_t searchEntryStrLen = strlen(searchEntry); + + int numCores = 0; // detected number of cores for the given numa node + + CPU_ZERO(outCpuSet); // initialize the set + + if(!StorageTk::pathExists(path) ) + { + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, "NUMA core check path not found: " + path); + + errorLogged = true; + return 0; + } + + StringList pathEntries; // path dir entries + + StorageTk::readCompleteDir(path.c_str(), &pathEntries); + + for(StringListIter iter = pathEntries.begin(); iter != pathEntries.end(); iter++) + { + if(!strncmp(searchEntry, iter->c_str(), searchEntryStrLen) ) + { // found a core for this numa node + numCores++; + + std::string coreNumStr = std::string(iter->c_str() ).substr(searchEntryStrLen); + + // note: there are other entries that start with "cpu" as well, so we check for digits: + if(coreNumStr.empty() || !isdigit(coreNumStr[0] ) ) + continue; + + int coreNum = StringTk::strToInt(coreNumStr); + + CPU_SET(coreNum, outCpuSet); + } + } + + if(!numCores) + { + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, "No cores found in path: " + path); + + errorLogged = true; + return 0; + } + + return numCores; +} + +/** + * Set affinity of current process to given NUMA zone. + * + * @param nodeNum zero-based + */ +bool System::bindToNumaNode(int nodeNum) +{ + const char* logContext = "PThread (start on NUMA node)"; + static bool errorLogged = false; // to avoid log spamming on error + + // init cpuSet with cores of give numa node + + cpu_set_t cpuSet; + + int numCores = System::getNumaCoresByNode(nodeNum, &cpuSet); + if(!numCores) + { // something went wrong with core retrieval, so fall back to running on all cores + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, + "Failed to detect CPU cores for NUMA zone. Falling back to allowing all cores. " + "Failed zone: " + StringTk::intToStr(nodeNum) ); + + errorLogged = true; + return false; + } + + int affinityRes = sched_setaffinity(getPID(), sizeof(cpuSet), &cpuSet); + if(affinityRes) + { // failed to set affinity + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, + "Failed to set process affinity to NUMA zone. " + "Failed zone: " + StringTk::intToStr(nodeNum) + "; " + "SysErr: " + System::getErrString() ); + + errorLogged = true; + return false; + } + + return true; +} + +/** + * @return linux thread ID (this is not the POSIX thread ID!) + */ +pid_t System::getTID() +{ // note: gettid() man page says "use syscall()" for this + return syscall(SYS_gettid); +} + +/** + * Increase the process limit for number of open file descriptors (also includes sockets etc). + * + * Note: This is increase only, so it will do nothing if the new limit is lower than the old one. + * + * @param outOldLimit may be NULL + * @return false on error (and errno is set on error) + */ +bool System::incProcessFDLimit(uint64_t newLimit, uint64_t* outOldLimit) +{ + const char* logContext = "Increase process FD limit"; + + struct rlimit oldRLimitData; + + int getLimitRes = getrlimit(RLIMIT_NOFILE, &oldRLimitData); + if(getLimitRes == -1) + { + LogContext(logContext).log(Log_WARNING, + std::string("Unable to get rlimit for number of files. SysErr: ") + + System::getErrString() ); + + if(outOldLimit) + *outOldLimit = 0; + + return false; + } + else + if(newLimit > oldRLimitData.rlim_cur) + { // check current limit and raise if necessary + struct rlimit newRLimitData = oldRLimitData; + + newRLimitData.rlim_cur = BEEGFS_MAX(newLimit, oldRLimitData.rlim_cur); + newRLimitData.rlim_max = BEEGFS_MAX(newLimit, oldRLimitData.rlim_max); + + LOG_DEBUG(logContext, Log_SPAM, + std::string("Setting rlimit for number of files... ") + + std::string("soft old: ") + StringTk::uint64ToStr(oldRLimitData.rlim_cur) + "; " + + std::string("soft new: ") + StringTk::uint64ToStr(newRLimitData.rlim_cur) + "; " + + std::string("hard old: ") + StringTk::uint64ToStr(oldRLimitData.rlim_max) + "; " + + std::string("hard new: ") + StringTk::uint64ToStr(newRLimitData.rlim_max) ); + + if(outOldLimit) + *outOldLimit = oldRLimitData.rlim_cur; + + int setLimitRes = setrlimit(RLIMIT_NOFILE, &newRLimitData); + if(setLimitRes == -1) + return false; + } + + return true; +} + +/** + * Get information about memory usage on the current system (in bytes). + * + * Note: if something goes wrong values will be set to 0. + */ +void System::getMemoryInfo(uint64_t *memTotal, uint64_t *memFree, uint64_t *memCached, + uint64_t *memBuffered) +{ + *memTotal = 0; + *memFree = 0; + *memCached = 0; + *memBuffered = 0; + + std::string token; + std::ifstream file("/proc/meminfo"); + while (file >> token) + { + if(token == "MemTotal:") + { + uint64_t mem = 0; + if(file >> mem) + { + *memTotal = mem * 1024; + } + else + { + *memTotal = 0; + } + } + else + if(token == "MemFree:") + { + uint64_t mem = 0; + if(file >> mem) + { + *memFree = mem * 1024; + } + else + { + *memFree = 0; + } + } + else + if(token == "Cached:") + { + uint64_t mem = 0; + if(file >> mem) + { + *memCached = mem * 1024; + } + else + { + *memCached = 0; + } + } + else + if(token == "Buffers:") + { + uint64_t mem = 0; + if(file >> mem) + { + *memBuffered = mem * 1024; + } + else + { + *memBuffered = 0; + } + } + + // ignore rest of the line + file.ignore(std::numeric_limits::max(), '\n'); + } +} + +/** + * Get the amount of usable memory (free+cached) in Byte + * + * @return the amount of usable memory in Byte + */ +uint64_t System::getUsableMemorySize() +{ + uint64_t memTotal = 0; + uint64_t memFree = 0; + uint64_t memCached = 0; + uint64_t memBuffered = 0; + + getMemoryInfo(&memTotal, &memFree, &memCached, &memBuffered); + + return memFree+memCached; +} + +/* + * Public method to get the device path for a file system mounted at mountpoint + * + * @param mountpoint the mountpoint to get the file system UUID for + * @return string the device path for the device that holds the file system mounted at mountpoint + */ + std::string System::getDevicePathFromMountpoint(std::string mountpoint) { + // Find out device numbers of underlying device + struct stat st; + + if (stat(mountpoint.c_str(), &st)) { + throw InvalidConfigException("Could not stat mountpoint directory: " + mountpoint); + } + + // look for the device path + std::ifstream mountInfo("/proc/self/mountinfo"); + + if (!mountInfo) { + throw InvalidConfigException("Could not open /proc/self/mountinfo"); + } + + auto majmin_f = boost::format("%1%:%2%") % major(st.st_dev) % minor(st.st_dev); + + std::string line, device_path, device_majmin; + while (std::getline(mountInfo, line)) { + std::istringstream is(line); + std::string dummy; + is >> dummy >> dummy >> device_majmin >> dummy >> dummy >> dummy >> dummy >> dummy >> dummy >> device_path; + + if (majmin_f.str() == device_majmin) + break; + + device_path = ""; + } + + if (device_path.empty()) { + throw InvalidConfigException("Determined the underlying device for directory " + mountpoint + " is " + majmin_f.str() + " but could not find that device in /proc/self/mountinfo"); + } + + return device_path; +} + + +/* + * resolves the given UID (user ID) to the user name. + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param uid system user ID + * @return user name or empty string if user not found + */ +std::string System::getUserNameFromUID(unsigned uid) +{ + std::string userName; + struct passwd* userData = getpwuid(uid); + + if(userData != NULL) + userName = userData->pw_name; + + return userName; +} + +/* + * resolves the given username to its numeric UID (user ID). + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param system username + * @return false if user not found + */ +bool System::getUIDFromUserName(std::string username, uid_t* outUID) +{ + struct passwd* userData = getpwnam(username.c_str() ); + + if(!userData) + return false; + + *outUID = userData->pw_uid; + return true; +} + + +/* + * resolves the given GID (group ID) to the group name + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param gid system group ID + * @return group name or empty string if group not found + */ +std::string System::getGroupNameFromGID(unsigned gid) +{ + std::string groupName; + struct group* groupData = getgrgid(gid); + + if(groupData != NULL) + groupName = groupData->gr_name; + + return groupName; +} + +/* + * resolves the given groupname to its numeric GID (group ID). + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param system groupname. + * @return false if group not found. + */ +bool System::getGIDFromGroupName(std::string groupname, gid_t* outGID) +{ + struct group* groupData = getgrnam(groupname.c_str() ); + + if(!groupData) + return false; + + *outGID = groupData->gr_gid; + return true; +} + + +/* + * collects all IDs of the users which are available on the system. + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param outUserIDs the UIDs of the known users + * @param ignoreSystemUsers if true, ignores all UIDs lower than 100 and user with the default + * shell "/sbin/nologin" or "/usr/sbin/nologin" or "/bin/false" + */ +void System::getAllUserIDs(UIntList* outUserIDs, bool ignoreSystemUsers) +{ + struct passwd* userData; + + userData = getpwent(); + + while(userData) + { + if(!ignoreSystemUsers) + outUserIDs->push_back(userData->pw_uid); + else + if( (userData->pw_uid > 100) && + strcmp(userData->pw_shell, "/sbin/nologin") != 0 && + strcmp(userData->pw_shell, "/usr/sbin/nologin") != 0 && + strcmp(userData->pw_shell, "/bin/false") != 0 ) + outUserIDs->push_back(userData->pw_uid); + + userData = getpwent(); + } + + //close the user DB + endpwent(); +} + +/* + * collects all IDs of the groups which are available on the system, + * + * WARNING: this is a non-reentrant version (libc function without _r suffix), so don't use it + * multi-threaded. + * + * @param outGroupIDs the GIDs of the known groups + * @param ignoreSystemGroups if true, ignores all GIDs lower than 100 + */ +void System::getAllGroupIDs(UIntList* outGroupIDs, bool ignoreSystemGroups) +{ + struct group* groupData; + + groupData = getgrent(); + + while(groupData) + { + if(!ignoreSystemGroups || (groupData->gr_gid > 100) ) + outGroupIDs->push_back(groupData->gr_gid); + + groupData = getgrent(); + } + + //close the group DB + endgrent(); +} + +/* + * set the UID and the GID for file-system access, the following file system access of the thread + * will be done with the given user and group, this method must be called again with the previousUID + * and previousGID to reset the UID and GID + * + * @param uid user ID to use for the file system operations which will be done after this method + * @param gid group ID to use for the file system operations which will be done after this method + * @param outPreviousUID the user ID which was used before this method was called (return value) + * @param outPreviousGID the group ID which was used before this method was called (return value) + */ +void System::setFsIDs(unsigned uid, unsigned gid, unsigned *outPreviousUID, + unsigned *outPreviousGID) +{ + *outPreviousUID = setfsuid(uid); + *outPreviousGID = setfsgid(gid); +} + +/** + * Drop effective user and group ID by setting it to real user and group ID. + * + * Note: FS UID/GID will also implicitly be set to this value. However, POSIX does not require to + * permit setting eUID to the current eUID value, so this method might not work after + * elevateEffectiveUserAndGroupFsID(). + * + * @return true on success + */ +bool System::dropUserAndGroupEffectiveID() +{ + int setUIDRes = seteuid(getuid() ); + int setGIDRes = setegid(getgid() ); + + return (!setUIDRes && !setGIDRes); +} + +/** + * Elevate the effective user and group FS ID to the saved (originally effective) user and group ID. + * + * Note: You probably want to call setFsIDs() when you are through with the privileged code part. + */ +void System::elevateUserAndGroupFsID(unsigned* outPreviousUID, unsigned* outPreviousGID) +{ + setFsIDs(savedEffectiveUID, savedEffectiveGID, outPreviousUID, outPreviousGID); +} diff --git a/common/source/common/system/System.h b/common/source/common/system/System.h new file mode 100644 index 0000000..811a8c9 --- /dev/null +++ b/common/source/common/system/System.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include + +#include +#include + + +class Mutex; // forward declaration + + +class System +{ + public: + static std::string getErrString(); + static std::string getErrString(int errcode); + static std::string getHostname(); + static int getNumOnlineCPUs(); + static int getNumNumaNodes(); + static int getNumaCoresByNode(int nodeNum, cpu_set_t* outCpuSet); + static bool bindToNumaNode(int nodeNum); + static pid_t getTID(); + static bool incProcessFDLimit(uint64_t newLimit, uint64_t* outOldLimit); + static void getMemoryInfo(uint64_t *memTotal, uint64_t *memFree, uint64_t *memCached, + uint64_t *memBuffered); + static uint64_t getUsableMemorySize(); + static std::string getDevicePathFromMountpoint(std::string mountpoint); + static std::string getFsUUID(std::string mountpoint); + static std::pair getPartUUID(std::string mountpoint); + static std::string getMachineUUID(); + + static std::string getUserNameFromUID(unsigned uid); + static bool getUIDFromUserName(std::string username, uid_t* outUID); + static std::string getGroupNameFromGID(unsigned gid); + static bool getGIDFromGroupName(std::string groupname, gid_t* outGID); + static void getAllUserIDs(UIntList* outUserIDs, bool ignoreSystemUsers); + static void getAllGroupIDs(UIntList* outGroupIDs, bool ignoreSystemGroups); + + static void setFsIDs(unsigned uid, unsigned gid, unsigned *outPreviousUID, + unsigned *outPreviousGID); + static bool dropUserAndGroupEffectiveID(); + static void elevateUserAndGroupFsID(unsigned* outPreviousUID, unsigned* outPreviousGID); + + + private: + System() + { + } + + static Mutex strerrorMutex; + + static uid_t savedEffectiveUID; + static gid_t savedEffectiveGID; + + + public: + // inliners + + /** + * @return process ID + */ + static pid_t getPID() + { + return getpid(); + } + + /** + * @return POSIX thread ID, unsigned long (this is not the linux thread ID) + */ + static pthread_t getPosixTID() + { + return pthread_self(); + } + + static uint64_t getCurrentTimeSecs() + { + return time(NULL); + } + +}; + diff --git a/common/source/common/system/UUID.h b/common/source/common/system/UUID.h new file mode 100644 index 0000000..820c764 --- /dev/null +++ b/common/source/common/system/UUID.h @@ -0,0 +1,164 @@ +#pragma once + +#include +#include + +#include + +// This is a library that is meant to be included where access to system UUIDs is needed. It is +// header-only so it doesn't get built into libbeegfs-common which would inject a dependency on +// libblkid into everything that links common. +namespace UUID { + +/* + * Public method to get the file system UUID for given mountpoint. + * + * Used to get a file system UUID that can be compared to a configured UUID to make sure services + * start only after their storage properly mounted. Will throw an InvalidConfigException if any + * error is encountered. + * + * @param mountpoint the mountpoint to get the file system UUID for + * @return string the UUID of the filesystem mounted at mountpoint + */ +std::string getFsUUID(std::string mountpoint) +{ + std::string device_path = System::getDevicePathFromMountpoint(mountpoint); + + // Lookup the fs UUID + std::unique_ptr + probe(blkid_new_probe_from_filename(device_path.data()), blkid_free_probe); + + if (!probe) { + throw InvalidConfigException("Failed to open device for probing: " + device_path + " (check BeeGFS is running with root or sufficient privileges)"); + } + + if (blkid_probe_enable_superblocks(probe.get(), 1) < 0) { + throw InvalidConfigException("Failed to enable superblock probing"); + } + + if (blkid_do_fullprobe(probe.get()) < 0) { + throw InvalidConfigException("Failed to probe device"); + } + + const char* uuid = nullptr; // gets released automatically + if (blkid_probe_lookup_value(probe.get(), "UUID", &uuid, nullptr) < 0) { + throw InvalidConfigException("Failed to lookup file system UUID"); + } + + std::string uuid_str(uuid); + return uuid_str; +} + +/* + * Public method to get a partition UUID that can be used to uniquely identify the machine + * + * This function will iterate over all partitions in the blkid cache, which is accessible as an + * unprivileged user, and return the first suitable UUID. The selection algorithm for a suitable + * UUID is: + * 1. Must be either the "UUID" or "PARTUUID" of a partition. "UUID" is preferred over "PARTUUID" + * 2. Partitions are processed in the order they appear in the cache. The order should be stable at + * least as long as the machine isn't rebooted, typically even across reboots. + * 3. Only UUIDs that are 36 characters long (like the standard UUID4 format) are considered, with + * the sole exception of the fallback (see 5.) + * 4. If the device mounted at the given mountpoint has either a "UUID" or "PARTUUID" that is 36 + * characters long, this function will always return that ID. + * 5. If no suitable UUID with a length of 36 characters has been found after iterating all + * partitions, the first non-empty "UUID" that was found will be returned as a fallback. + * + * @param mountpoint mountpoint of preferred partition + * @return pair containing either FhgfsOpsErr_SUCCESS and the uuid or an error + * code and the error reason + */ +std::pair getPartUUID(std::string mountpoint) { + std::string out, fallback; + + std::string device_path = System::getDevicePathFromMountpoint(mountpoint); + + blkid_cache cache; + int err = blkid_get_cache(&cache, NULL); + if (err) + return std::pair(FhgfsOpsErr_INTERNAL, "unable to read blkid cache to get partition uuid"); + blkid_probe_all(cache); + + auto diter = blkid_dev_iterate_begin(cache); + blkid_dev dev; + while(!blkid_dev_next(diter, &dev)) { + auto devname = blkid_dev_devname(dev); + if (devname == nullptr) + continue; + + std::string uuid; + auto uuid_raw = blkid_get_tag_value(cache, "UUID", devname); + if (uuid_raw != nullptr) + uuid = std::string(uuid_raw); + + std::string part_uuid; + auto part_uuid_raw = blkid_get_tag_value(cache, "PARTUUID", devname); + if (part_uuid_raw != nullptr) + part_uuid = std::string(part_uuid_raw); + + if(!uuid.empty()) { + out = uuid; + if(devname == device_path || uuid.length() == 36) + break; + if(fallback.empty()) + fallback = uuid; + } + if(!part_uuid.empty()) { + out = part_uuid; + if(devname == device_path || part_uuid.length() == 36) + break; + } + } + blkid_dev_iterate_end(diter); + + if(out.empty() || out.length() != 36) { + if(!fallback.empty()) + out = fallback; + else + return std::pair(FhgfsOpsErr_INTERNAL, "no usable partition IDs found in blkid cache"); + } + + return std::pair(FhgfsOpsErr_SUCCESS, out); +} + +/* + * Read the machine's dmi UUID from sysfs or fall back to the first suitable partition UUID + * + * NOTE: The dmi UUID is probably the best (not user changeable, hardware bound) machine ID we can + * get, but the file containing it is only readable as root. The meta and storage services typically + * run as root, but they can technically run as unprivileged users. If the machine ID can not be + * read from dmi, this function will return a suitable partition UUID via getPartUUID(). + * + * @return string containing the UUID or empty string on error + */ + std::string getMachineUUID() { + const char* logContext = "System (get machine UUID)"; + + char buf[37]; + bool failed = false; + + // This path should be canonical and stable across Linux distributions + std::ifstream in("/sys/class/dmi/id/product_uuid"); + if(!in.is_open()) + failed = true; + in.getline(buf, 37); + in.close(); + + std::string machineUUID = std::string(buf); + if(machineUUID.length() != 36) + failed = true; + + if(!failed) + return machineUUID; + + // Fall back to partition UUID + auto [err, res] = getPartUUID("/"); + if(err != FhgfsOpsErr_SUCCESS) { + LogContext(logContext).log(Log_WARNING, "Error while reading file system UUID: " + res); + return ""; + } + return res; +} + +} // namespace UUID diff --git a/common/source/common/threading/Atomics.h b/common/source/common/threading/Atomics.h new file mode 100644 index 0000000..051a92a --- /dev/null +++ b/common/source/common/threading/Atomics.h @@ -0,0 +1,172 @@ +#pragma once + +#include + + +template class Atomic; + +typedef Atomic AtomicSizeT; +typedef Atomic AtomicSSizeT; +typedef Atomic AtomicUInt32; +typedef Atomic AtomicUInt64; +typedef Atomic AtomicInt16; +typedef Atomic AtomicInt64; + +#if !defined(__has_feature) +# define BEEGFS_NO_TSAN +#else +# if !__has_feature(thread_sanitizer) +# define BEEGFS_NO_TSAN +# else +# define BEEGFS_NO_TSAN __attribute__((no_sanitize_thread)) +# endif +#endif + + +/* + * Atomic operations - wrapper for gcc extensions, internally it makes use of memory barriers. + * Note: We use gcc architechture preprocessor defines here and also gcc atomic extensions. + * + * @TemplateType see gcc builtin atomics documentation for types (int, int64_t, ...) + */ +template +class Atomic +{ + public: + /** + * Initialize and set to 0. + */ + Atomic() + { + this->setZero(); // initialize with zero + } + + /** + * Initialize with given value. + */ + Atomic(TemplateType value) + { + set(value); + } + + private: + TemplateType atomicValue; // atomic value we are operating on + + public: + + /** + * Set to given value. + */ + BEEGFS_NO_TSAN + void set(TemplateType value) + { + #if (defined __x86_64__ || defined __i386__ || defined __i686__) + // special assumptions for x86 and x86_64 (actually beginning with i486) + this->atomicValue = value; + #else + /* note: the strange name of __sync_lock_test_and_set() is because this method was + originally intended for spinlocks, in which case the value was set to 1 (=> lock) and + the counter-part is __sync_lock_release(), which resets the value to 0 (=> unlock). */ + + __sync_lock_test_and_set(&this->atomicValue, value); // gcc extension + #endif + } + + + /** + * Set to 0. + */ + BEEGFS_NO_TSAN + void setZero() + { + #if (defined __x86_64__ || defined __i386__ || defined __i686__) + // special assumptions for x86 and x86_64 (actually beginning with i486) + this->atomicValue = 0; + #else + // note: if you're wondering about the name __sync_lock_release(), read the set() comment + + __sync_lock_release(&this->atomicValue); // gcc extension + #endif + } + + /** + * Increase by one. + * + * @return previous value + */ + BEEGFS_NO_TSAN + TemplateType increase() + { + return __sync_fetch_and_add(&this->atomicValue, 1); // NOTE: gcc extension + } + + /** + * Increase by value. + * + * @return previous value + */ + BEEGFS_NO_TSAN + TemplateType increase(TemplateType value) + { + return __sync_fetch_and_add(&this->atomicValue, value); // NOTE: gcc extension + } + + /** + * Decrease by one. + * + * @return previous value + */ + BEEGFS_NO_TSAN + TemplateType decrease() + { + return __sync_fetch_and_sub(&this->atomicValue, 1); // NOTE: gcc extension + } + + /** + * Decrease by value. + * + * @return previous value + */ + BEEGFS_NO_TSAN + TemplateType decrease(TemplateType value) + { + return __sync_fetch_and_sub(&this->atomicValue, value); // NOTE: gcc extension + } + + /** + * Set to given value if the + */ + BEEGFS_NO_TSAN + bool compareAndSet(TemplateType value, TemplateType compareValue) + { + return __sync_bool_compare_and_swap(&this->atomicValue, compareValue, value); + } + + /** + * Get the atomic value. + */ + BEEGFS_NO_TSAN + TemplateType read() const + { + #if (defined __x86_64__ || defined __i386__ || defined __i686__) + // special assumptions for x86 and x86_64 (actually beginning with i486) + return (*(volatile TemplateType* )&(this)->atomicValue); + #else + /* There is no special read function, but we can simply add zero to get the same effect. + * Also just reading the variable was tested to work, but using __sync_fetch_and_add() + * we make absolutely sure we read the atomic value. + * + * (would be nice if gcc could optimize out this command itself, so that we wouldn't + * need the entire preprocessor construct, but disassembling a test program showed + * gcc adds a "lock xadd" command, which is slow) + */ + return __sync_fetch_and_add((TemplateType*) &this->atomicValue, 0); + #endif + } + + +}; + +#undef BEEGFS_NO_TSAN + + diff --git a/common/source/common/threading/Barrier.h b/common/source/common/threading/Barrier.h new file mode 100644 index 0000000..0d24a80 --- /dev/null +++ b/common/source/common/threading/Barrier.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +class Barrier +{ + public: + Barrier(unsigned count) + { + int res = pthread_barrier_init(&barrier, nullptr, count); + if (res != 0) + throw PThreadException(System::getErrString(res)); + } + + ~Barrier() + { + int res = pthread_barrier_destroy(&barrier); + if (res != 0) + std::terminate(); + } + + Barrier() = delete; + Barrier(const Barrier&) = delete; + Barrier(Barrier&&) = delete; + Barrier& operator=(const Barrier&) = delete; + Barrier& operator=(Barrier&&) = delete; + + private: + pthread_barrier_t barrier; + + public: + void wait() + { + pthread_barrier_wait(&barrier); + // Note: Not checking return val, because it can only fail if the barrier is not properly + // initialized, but proper initialization is guaranteed by the constructor. + } +}; + diff --git a/common/source/common/threading/Condition.cpp b/common/source/common/threading/Condition.cpp new file mode 100644 index 0000000..584d846 --- /dev/null +++ b/common/source/common/threading/Condition.cpp @@ -0,0 +1,107 @@ + +#include +#include +#include +#include "Condition.h" +#include "ConditionException.h" + +#ifdef CLOCK_MONOTONIC_COARSE + // Note: Don't forget to check on program start up if it works! + clockid_t Condition::clockID = TIME_DEFAULT_CLOCK_ID; +#else + clockid_t Condition::clockID = TIME_SAFE_CLOCK_ID; +#endif + +pthread_condattr_t Condition::condAttr; // static condAttr, initialization below + +bool Condition::initStaticCondAttr() +{ + int initRes = pthread_condattr_init(&condAttr); + if (initRes) + { + throw ConditionException(std::string("Condition initialization failed: ") + + System::getErrString(initRes) ); + return false; + } + + int setRes = pthread_condattr_setclock(&condAttr, clockID); + if (setRes) + { + throw ConditionException(std::string("Condition set clockID failed: ") + + System::getErrString(setRes) ); + return false; + } + + return true; +} + +bool Condition::destroyStaticCondAttr() +{ + int condAttrRes = pthread_condattr_destroy(&condAttr); + if (unlikely(condAttrRes) ) + { + throw MutexException(System::getErrString(condAttrRes) ); + return false; + } + + return true; +} + +/** + * Test if the current clockID works. If not try to switch to a safe clock ID + */ +bool Condition::testClockID() +{ + while (true) + { + bool testTimeRes = Time::testClockID(clockID); + if (!testTimeRes) + { + clockID = TIME_SAFE_CLOCK_ID; + continue; + } + + int setClockAttrRes = pthread_condattr_setclock(&condAttr, clockID); + if (setClockAttrRes) + { + #ifdef BEEGFS_DEBUG + // As we are in early initialization, log via syslog? + // std::cerr << "pthread_condattr_setclock does not accept clockID: "<< + // clockID << " Error: " << System::getErrString(setClockAttrRes) << std::endl; + #endif + + // using the current clock failed + if (clockID == TIME_SAFE_CLOCK_ID) + { + std::string errMsg = "pthread_condattr_setclock(" + StringTk::intToStr(clockID) + + ") does not work"; + throw TimeException(errMsg); + return false; + } + else + { + clockID = TIME_SAFE_CLOCK_ID; + bool testTimeRes = Time::testClockID(clockID); + if (!testTimeRes) + { + throw TimeException("Time::testClock(SAFE_CLOCK_ID) failed!"); + return false; + } + + continue; + } + } + else + { + #ifdef BEEGFS_DEBUG + if (clockID != TIME_SAFE_CLOCK_ID) + std::cout << "Good, your libc supports a fast clockIDs! ClockID: " + << clockID << std::endl; + #endif + return true; // all ok + } + } + + return false; // we never should end up here +} + diff --git a/common/source/common/threading/Condition.h b/common/source/common/threading/Condition.h new file mode 100644 index 0000000..f0d5c74 --- /dev/null +++ b/common/source/common/threading/Condition.h @@ -0,0 +1,89 @@ +#pragma once + +#include +#include +#include +#include "MutexException.h" +#include "Mutex.h" + +#include + + +class System; + +class Condition +{ + public: + Condition() + { + pthread_cond_init(&this->condition, &condAttr); + } + + ~Condition() + { + int condRes = pthread_cond_destroy(&condition); + if(unlikely(condRes) ) + std::terminate(); + } + + Condition(const Condition&) = delete; + Condition(Condition&&) = delete; + Condition& operator=(const Condition&) = delete; + Condition& operator=(Condition&&) = delete; + + static bool initStaticCondAttr(); + static bool destroyStaticCondAttr(); + static bool testClockID(); + + private: + pthread_cond_t condition; + static pthread_condattr_t condAttr; + + /* In principle we could use Time::getClockID(), but unfortunately, glibc developers + * forgot to support recent clockIDs. So we still let the Time:: class use the faster + * CLOCK_MONOTONIC_COARSE variant, but will auto-fail back to the slower version here + * (Well, maybe we think about to use another libc, such as musl, which does not have the + * glibc limitation). + */ + static clockid_t clockID; + + + public: + + // inliners + + bool timedwait(Mutex* lockedMutex, const int timeoutMS) + { + Time timeout; // initialized with current time + timeout.addMS(timeoutMS); + + struct timespec timeoutTimeSpec; + timeout.getTimeSpec(&timeoutTimeSpec); + + int pthreadRes = pthread_cond_timedwait(&condition, lockedMutex->getMutex(), + &timeoutTimeSpec); + if(!pthreadRes) + return true; + if(pthreadRes == ETIMEDOUT) + return false; + + throw MutexException(System::getErrString(pthreadRes) ); + } + + void signal() + { + pthread_cond_signal(&condition); + } + + void broadcast() + { + pthread_cond_broadcast(&condition); + } + + void wait(Mutex* lockedMutex) + { + pthread_cond_wait(&condition, lockedMutex->getMutex() ); + } + +}; + diff --git a/common/source/common/threading/ConditionException.h b/common/source/common/threading/ConditionException.h new file mode 100644 index 0000000..fa194ff --- /dev/null +++ b/common/source/common/threading/ConditionException.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +DECLARE_NAMEDEXCEPTION(ConditionException, "ConditionException") + + diff --git a/common/source/common/threading/LockedView.h b/common/source/common/threading/LockedView.h new file mode 100644 index 0000000..2f5de69 --- /dev/null +++ b/common/source/common/threading/LockedView.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include + +template +class LockedView +{ + T *mPtr = nullptr; + std::unique_lock mLock; + +public: + + T *operator->() const + { + return mPtr; + } + + std::unique_lock& get_unique_lock() + { + return mLock; + } + + LockedView(LockedView const& other) = delete; + + LockedView(LockedView&& other) + { + std::swap(mPtr, other.mPtr); + std::swap(mLock, other.mLock); + } + + LockedView() = delete; + + LockedView(T *ptr, std::mutex *mutex) + : mPtr(ptr), mLock(*mutex) + {} +}; + +template +class MutexProtected +{ + T value; + std::mutex mutex; + +public: + + LockedView lockedView() + { + return LockedView(&value, &mutex); + } +}; diff --git a/common/source/common/threading/Mutex.h b/common/source/common/threading/Mutex.h new file mode 100644 index 0000000..4482940 --- /dev/null +++ b/common/source/common/threading/Mutex.h @@ -0,0 +1,65 @@ +#pragma once + +#include "MutexException.h" +#include +#include + +class System; + +class Mutex +{ + public: + Mutex() + { + pthread_mutex_init(&mutex, NULL); + } + + ~Mutex() + { + // may return: + // * [EBUSY] never returned by glibc, little useful info without a lockdep tool + // * [EINVAL] never happens (mutex is properly initialized) + pthread_mutex_destroy(&mutex); + } + + Mutex(const Mutex&) = delete; + Mutex(Mutex&&) = delete; + Mutex& operator=(const Mutex&) = delete; + Mutex& operator=(Mutex&&) = delete; + + /** + * @throw MutexException + */ + void lock() + { + int pthreadRes = pthread_mutex_lock(&mutex); + + if(unlikely(pthreadRes) ) + throw MutexException(System::getErrString(pthreadRes)); + } + + bool tryLock() + { + // may return: + // * [EINVAL] never happens (mutex is properly initialized) + // * [EBUSY] not an error + // * [EAGAIN] never happens (this mutex is not recursive) + return pthread_mutex_trylock(&mutex) == 0; + } + + void unlock() + { + // may return: + // * [EINVAL] never happens (mutex is properly initialized) + // * [EPERM] never returned by glibc, little useful info without a lockdep tool + pthread_mutex_unlock(&mutex); + } + + private: + pthread_mutex_t mutex; + + public: + // getters & setters + pthread_mutex_t* getMutex() {return &mutex;} +}; + diff --git a/common/source/common/threading/MutexException.h b/common/source/common/threading/MutexException.h new file mode 100644 index 0000000..5fa40df --- /dev/null +++ b/common/source/common/threading/MutexException.h @@ -0,0 +1,7 @@ +#pragma once + +#include "SynchronizationException.h" + +DECLARE_NAMEDSUBEXCEPTION(MutexException, "MutexException", SynchronizationException) + + diff --git a/common/source/common/threading/PThread.cpp b/common/source/common/threading/PThread.cpp new file mode 100644 index 0000000..6958885 --- /dev/null +++ b/common/source/common/threading/PThread.cpp @@ -0,0 +1,270 @@ +#include +#include +#include +#include "PThread.h" + +#include +#include +#include + + +pthread_once_t PThread::nameOnceVar = PTHREAD_ONCE_INIT; +pthread_key_t PThread::nameKey; +pthread_once_t PThread::appOnceVar = PTHREAD_ONCE_INIT; +pthread_key_t PThread::appKey; + +__thread PThread* PThread::currentThread; + + +DECLARE_NAMEDEXCEPTION(SignalException, "SignalException") + + +#define PTHREAD_BACKTRACE_ARRAY_SIZE 32 + + +/** + * Block signals SIGINT and SIGTERM for the calling pthread. + * + * Note: Blocked signals will be inherited by child threads. + * + * Note: This is important to make, because Linux can direct a SIGINT to any thread that has a + * handler registered; so we need this to make sure that only the main thread (and not the worker + * threads) receive a SIGINT, e.g. if a user presses CTRL+C. + */ +bool PThread::blockInterruptSignals() +{ + sigset_t signalMask; // signals to block + + sigemptyset (&signalMask); + + sigaddset (&signalMask, SIGINT); + sigaddset (&signalMask, SIGTERM); + + int sigmaskRes = pthread_sigmask(SIG_BLOCK, &signalMask, NULL); + + return (sigmaskRes == 0); +} + +/** + * Unblock the signals (e.g. SIGINT) that were blocked with blockInterruptSignals(). + */ +bool PThread::unblockInterruptSignals() +{ + sigset_t signalMask; // signals to unblock + + sigemptyset (&signalMask); + + sigaddset (&signalMask, SIGINT); + sigaddset (&signalMask, SIGTERM); + + int sigmaskRes = pthread_sigmask(SIG_UNBLOCK, &signalMask, NULL); + + return (sigmaskRes == 0); +} + +void PThread::registerSignalHandler() +{ + signal(SIGSEGV, PThread::signalHandler); + signal(SIGFPE, PThread::signalHandler); + signal(SIGBUS, PThread::signalHandler); + signal(SIGILL, PThread::signalHandler); + signal(SIGABRT, PThread::signalHandler); +} + + +void PThread::signalHandler(int sig) +{ + AbstractApp* app = PThread::getCurrentThreadApp(); + + Logger* log = Logger::getLogger(); + const char* logContext = "PThread::signalHandler"; + + // note: this might deadlock if the signal was thrown while the logger mutex is locked + // by the current thread, but that case is very unlikely + + int backtraceLength = 0; + char** backtraceSymbols = NULL; + + void* backtraceArray[PTHREAD_BACKTRACE_ARRAY_SIZE]; + backtraceLength = backtrace(backtraceArray, PTHREAD_BACKTRACE_ARRAY_SIZE); + backtraceSymbols = backtrace_symbols(backtraceArray, backtraceLength); // note: symbols are + // malloc'ed and need to be freed later + + + switch(sig) + { + case SIGSEGV: + { + signal(sig, SIG_DFL); // reset the handler to its default + LOG(GENERAL, ERR, "Received a SIGSEGV. Trying to shut down..."); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + throw SignalException("Segmentation fault"); + } break; + + case SIGFPE: + { + signal(sig, SIG_DFL); // reset the handler to its default + LOG(GENERAL, ERR, "Received a SIGFPE. Trying to shut down..."); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + throw SignalException("Floating point exception"); + } break; + + case SIGBUS: + { + signal(sig, SIG_DFL); // reset the handler to its default + LOG(GENERAL, ERR, "Received a SIGBUS. Trying to shut down..."); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + throw SignalException("Bus error (bad memory access)"); + } break; + + case SIGILL: + { + signal(sig, SIG_DFL); // reset the handler to its default + LOG(GENERAL, ERR, "Received a SIGILL. Trying to shut down..."); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + throw SignalException("Illegal instruction"); + } break; + + case SIGABRT: + { // note: SIGABRT is special: after signal handler returns the process dies immediately + signal(sig, SIG_DFL); // reset the handler to its default + LOG(GENERAL, ERR, "Received a SIGABRT. Trying to shut down..."); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + + PThread::sleepMS(6000); // give the other components some time to quit before we go down + + throw SignalException("Abnormal termination"); + } break; + + default: + { + signal(sig, SIG_DFL); // reset the handler to its default + std::string logMsg("Received an unknown signal " + StringTk::intToStr(sig) + ". " + "Trying to shut down..."); + log->log(1, logContext, logMsg.c_str() ); + log->logBacktrace(logContext, backtraceLength, backtraceSymbols); + app->stopComponents(); + throw SignalException("Unknown signal"); + } break; + } + + + SAFE_FREE(backtraceSymbols); +} + +void PThread::nameKeyDestructor(void* value) +{ + SAFE_DELETE_NOSET( (std::string*)value); +} + +void PThread::appKeyDestructor(void* value) +{ + // nothing to be done here, because the app will be deleted by the Program class + //SAFE_DELETE( (AbstractApp*)value); +} + +/** + * Increases or decreases current thread priority (nice level) by given offset. + * + * Note: This has to be called before running the thread. +*/ +void PThread::setPriorityShift(int priorityShift) +{ + this->priorityShift = priorityShift; +} + + +/** + * Increases or decreases current thread priority by given offset. + * + * Note: This has to be called before running the thread. + * + * @param priorityOffset positive or negative number to increase or decrease priority (relative to + * current thread priority) + */ +bool PThread::applyPriorityShift(int priorityShift) +{ + errno = 0; // must be done because getpriority can legitimately return -1 + int currentPrio = getpriority(PRIO_PROCESS, System::getTID() ); + if( (currentPrio == -1) && errno) + { // error occurred + return false; + } + + int setRes = setpriority(PRIO_PROCESS, System::getTID(), currentPrio + priorityShift); + + return (setRes == 0); +} + + +/** + * Resets the shallSelfTerminate flag. + * Useful if you want to restart a thread that was ordered to self-terminate before. + */ +void PThread::resetSelfTerminate() +{ + this->shallSelfTerminateAtomic.setZero(); // (quasi-boolean) + + selfTerminateMutex.lock(); // L O C K + + this->shallSelfTerminate = false;; + + selfTerminateMutex.unlock(); // U N L O C K +} + +/** + * Starts the new thread via pthread_create with modified attributes to bind the thread to the + * cores of the given numa node. + * + * @throw PThreadCreateException, InvalidConfigException + */ +void PThread::startOnNumaNode(unsigned nodeNum) +{ + const char* logContext = "PThread (start on NUMA node)"; + static bool errorLogged = false; // to avoid log spamming on error + + // init cpuSet with cores of give numa node + + cpu_set_t cpuSet; + + int numCores = System::getNumaCoresByNode(nodeNum, &cpuSet); + if(!numCores) + { // something went wrong with core retrieval, so fall back to running on all cores + if(!errorLogged) + LogContext(logContext).log(Log_WARNING, + "Failed to detect CPU cores for NUMA zone. Falling back to allowing all cores. " + "Failed zone: " + StringTk::intToStr(nodeNum) ); + + errorLogged = true; + + start(); + return; + } + + // init pthread attributes with cpuSet cores + + pthread_attr_t attr; + + int attrRes = pthread_attr_init(&attr); + if(attrRes) + throw PThreadCreateException("pthread_attr_init failed: " + + System::getErrString(attrRes) ); + + int affinityRes = pthread_attr_setaffinity_np(&attr, sizeof(cpuSet), &cpuSet); + if(affinityRes) + throw PThreadCreateException("pthread_attr_setaffinity_np failed: " + + System::getErrString(affinityRes) ); + + // create and run thread + + int createRes = pthread_create(&threadID, &attr, &PThread::runStatic, this); + if(createRes) + throw PThreadCreateException("phtread_create (numa bind mode) failed: " + + System::getErrString(createRes) ); +} + diff --git a/common/source/common/threading/PThread.h b/common/source/common/threading/PThread.h new file mode 100644 index 0000000..68cb574 --- /dev/null +++ b/common/source/common/threading/PThread.h @@ -0,0 +1,426 @@ +#pragma once + +#include +#include +#include +#include +#include "Condition.h" +#include "PThreadCreateException.h" + +#include +#include +#include + + +#define PTHREAD_KERNELTHREADNAME_DELIMITER_CHAR '/' + + +// forward declaration +class AbstractApp; + + +/** + * PThreads represent POSIX threads based on the normal start/stop/join mechanisms plus some special + * stuff like thread local storage (for the App object) and the possiblity to kindly ask the thread + * to self-terminate. + */ +class PThread +{ + public: + virtual ~PThread() + { + pthread_attr_destroy(&this->threadAttr); + } + + PThread() = delete; + PThread(const PThread&) = delete; + PThread(PThread&&) = delete; + PThread& operator=(const PThread&) = delete; + PThread& operator=(PThread&&) = delete; + + void setPriorityShift(int priorityShift); + + void startOnNumaNode(unsigned nodeNum); + + + // inliners + + void join() + { + if (!threadID) + throw PThreadException("Cannot join invalid thread."); + + pthread_join(threadID, NULL); + } + + /** + * @return true if the component terminated within the given amount of time + */ + bool timedjoin(int timeoutMS) + { + if (!threadID) + throw PThreadException("Cannot join invalid thread."); + + + struct timespec joinEndtime; + + if(clock_gettime(CLOCK_REALTIME, &joinEndtime) ) + return false; + + joinEndtime.tv_sec += timeoutMS / 1000; + joinEndtime.tv_nsec += (timeoutMS % 1000) * 1000 * 1000; + + //prevent nanosecond overflow: add whole second from nanoseconds to seconds if exists and + //remove whole second from nanoseconds if exists + joinEndtime.tv_sec += joinEndtime.tv_nsec / (1000*1000*1000); + joinEndtime.tv_nsec = joinEndtime.tv_nsec % (1000*1000*1000); + + int joinRes = pthread_timedjoin_np(threadID, NULL, &joinEndtime); + + if(!joinRes) + return true; + + if(joinRes == ETIMEDOUT) + return false; + + throw PThreadException(System::getErrString(joinRes) ); + } + + void terminate() + { + if (!threadID) + throw PThreadException("Cannot terminate invalid thread."); + + pthread_kill(threadID, SIGTERM); + } + + void kill() + { + if (!threadID) + throw PThreadException("Cannot kill invalid thread."); + + pthread_kill(threadID, SIGKILL); + } + + /** + * Starts the new thread by calling pthread_create() and calling its run()-method. + */ + void start() + { + int createRes = pthread_create(&threadID, NULL, &PThread::runStatic, this); + if(createRes) + throw PThreadCreateException("phtread_create failed: " + + System::getErrString(createRes) ); + } + + /** + * Executes the run()-method of the PThread from the current thread without(!!) + * creating a new pthread. + * This is a special case, that makes a standard thread "become" a PThread. + */ + void startInCurrentThread() + { + threadID = pthread_self(); + + runStatic(this); + } + + /** + * Note: This is designed primarily for deeper_lib. It allows the lib to pretend they are + * beegfs threads and use the routines of beegfs_common. So we init the app and the signal + * handlers here. + */ + static void initFakeThread(AbstractApp* app) + { + // store thread-local vars + setCurrentThreadApp(app); + registerSignalHandler(); + } + + static void sleepMS(int timeoutMS) + { + if(timeoutMS > 0) + poll(NULL, 0, timeoutMS); + } + + static void yield() + { + sched_yield(); + } + + void selfTerminate() + { + shallSelfTerminateAtomic.set(1); // (quasi-boolean) + + selfTerminateMutex.lock(); // L O C K + + shallSelfTerminate = true; + shallSelfTerminateCond.broadcast(); + + selfTerminateMutex.unlock(); // U N L O C K + } + + /** + * wait for self-terminate order. + * + * @return true if the thread should self-terminate, false if timeoutMS expired + * without an incoming order for self-termination + */ + bool waitForSelfTerminateOrder(const int timeoutMS) + { + bool shallSelfTerminate; + + selfTerminateMutex.lock(); // L O C K + + if(!this->shallSelfTerminate) + shallSelfTerminateCond.timedwait(&this->selfTerminateMutex, timeoutMS); + + shallSelfTerminate = this->shallSelfTerminate; + + selfTerminateMutex.unlock(); // U N L O C K + + return shallSelfTerminate; + } + + /** + * wait infinitely for self-terminate order. + */ + void waitForSelfTerminateOrder() + { + selfTerminateMutex.lock(); // L O C K + + while(!this->shallSelfTerminate) + shallSelfTerminateCond.wait(&this->selfTerminateMutex); + + selfTerminateMutex.unlock(); // U N L O C K + } + + + protected: + PThread(std::string name): + addExePrefix(false) + { + // app will be derived from current thread + + this->threadApp = getCurrentThreadApp(); + + this->threadName = name; + this->threadID = 0; + + this->shallSelfTerminate = false; + + pthread_attr_init(&this->threadAttr); + this->priorityShift = 0; + } + + PThread(std::string name, AbstractApp* app): + addExePrefix(true) + { + this->threadApp = app; + setCurrentThreadApp(app); + + this->threadName = name; + this->threadID = 0; + + this->shallSelfTerminate = false; + + pthread_attr_init(&this->threadAttr); + this->priorityShift = 0; + } + + virtual void run() = 0; + + static bool blockInterruptSignals(); + static bool unblockInterruptSignals(); + static void registerSignalHandler(); + + void resetSelfTerminate(); + + void exit() + { + pthread_exit(NULL); + } + + + private: + static pthread_once_t nameOnceVar; + static pthread_key_t nameKey; + static pthread_once_t appOnceVar; + static pthread_key_t appKey; + + static __thread PThread* currentThread; + + std::string threadName; // stores the name before the run-method is called + AbstractApp* threadApp; // stores the app before the run-method is called + + bool addExePrefix; + std::string threadKernelNameOrig; // stores the original kernel name for the thread + // (as it was before we changed it with prctl() ) + + pthread_t threadID; + pthread_attr_t threadAttr; // for cpu affinity, priority, ... + int priorityShift; // for system priority (nice level) + + AtomicSizeT shallSelfTerminateAtomic; /* (quasi-bool) fast atomic version to avoid mutex lock + in getSelfTerminate() */ + bool shallSelfTerminate; // mutex-protected version to enable waitForSelfTerminateOrder() + Mutex selfTerminateMutex; + Condition shallSelfTerminateCond; + + static void signalHandler(int sig); + static bool applyPriorityShift(int priorityShift); + + // inliners + + static void* runStatic(void* args) + { + // args is a pointer to the PThread instance that provides the run() method + PThread::currentThread = (PThread*) args; + + // store thread-local vars + setCurrentThreadName(currentThread->threadName); + setCurrentKernelThreadName(currentThread, currentThread->threadName); + setCurrentThreadApp(currentThread->threadApp); + + applyPriorityShift(currentThread->priorityShift); + + currentThread->run(); + + return NULL; + } + + static void nameKeyCreateOnce() + { + pthread_key_create(&nameKey, nameKeyDestructor); + } + + static void nameKeyDestructor(void* value); + + static void setCurrentThreadName(std::string name) + { + // note: we use the once-var also to init the kernel thread name + + pthread_once(&nameOnceVar, nameKeyCreateOnce); + + std::string* nameValue = (std::string*)pthread_getspecific(nameKey); + if(!nameValue) + { // first-time init + nameValue = new std::string(name); + pthread_setspecific(nameKey, nameValue); + + return; + } + + *nameValue = name; + } + + static void setCurrentKernelThreadName(PThread* currentThread, std::string name) + { + if (!currentThread->addExePrefix) + { + if (name.size() > 15) + name.resize(15); + + prctl(PR_SET_NAME, name.c_str(), 0, 0, 0); + return; + } + + char threadKernelName[17]; // 16 bytes limit for names (might be unterminated) + + if(currentThread->threadKernelNameOrig.empty() ) + { // original name not initialized yet + int prctlRes = prctl(PR_GET_NAME, threadKernelName, 0, 0, 0); + if(prctlRes) + return; + + threadKernelName[16] = 0; + + // cut name at delimiter (because that part comes from a previous prctl() call to the + // parent process) + char* delimiter = strchr(threadKernelName, PTHREAD_KERNELTHREADNAME_DELIMITER_CHAR); + if(delimiter) + *delimiter = 0; + + currentThread->threadKernelNameOrig = threadKernelName; + } + + if(currentThread->threadKernelNameOrig.length() <= 14) + { // there is room for a suffix + std::string newThreadKernelName = currentThread->threadKernelNameOrig + + PTHREAD_KERNELTHREADNAME_DELIMITER_CHAR + name; + prctl(PR_SET_NAME, newThreadKernelName.c_str(), 0, 0, 0); // only 16 byte names + } + } + + static void appKeyCreateOnce() + { + pthread_key_create(&appKey, appKeyDestructor); + } + + static void appKeyDestructor(void* value); + + static void setCurrentThreadApp(AbstractApp* app) + { + pthread_once(&appOnceVar, appKeyCreateOnce); + + pthread_setspecific(appKey, app); + } + + + public: + // getters & setters + + /** + * Get name of the given thread object. + */ + std::string getName() + { + return this->threadName; + } + + /** + * Get name of the thread calling this function. + */ + static std::string getCurrentThreadName() + { + pthread_once(&nameOnceVar, nameKeyCreateOnce); + + std::string* nameValue = (std::string*)pthread_getspecific(nameKey); + if(!nameValue) + return ""; + + return *nameValue; + } + + static AbstractApp* getCurrentThreadApp() + { + pthread_once(&appOnceVar, appKeyCreateOnce); + + AbstractApp* appValue = (AbstractApp*)pthread_getspecific(appKey); + + return appValue; + } + + pthread_t getID() + { + return threadID; + } + + static pthread_t getCurrentThreadID() + { + return pthread_self(); + } + + static bool threadIDEquals(pthread_t t, pthread_t u) + { + return pthread_equal(t, u) != 0; + } + + bool getSelfTerminate() + { + return (shallSelfTerminateAtomic.read() != 0); + } + + static PThread* getCurrentThread() { return currentThread; } +}; + diff --git a/common/source/common/threading/PThreadCreateException.h b/common/source/common/threading/PThreadCreateException.h new file mode 100644 index 0000000..c06f3f9 --- /dev/null +++ b/common/source/common/threading/PThreadCreateException.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +DECLARE_NAMEDEXCEPTION(PThreadCreateException, "PThreadCreateException") + + diff --git a/common/source/common/threading/PThreadException.h b/common/source/common/threading/PThreadException.h new file mode 100644 index 0000000..bdc0c2e --- /dev/null +++ b/common/source/common/threading/PThreadException.h @@ -0,0 +1,9 @@ +#pragma once + +#include +#include + + +DECLARE_NAMEDEXCEPTION(PThreadException, "PThreadException") + + diff --git a/common/source/common/threading/RWLock.h b/common/source/common/threading/RWLock.h new file mode 100644 index 0000000..12b2539 --- /dev/null +++ b/common/source/common/threading/RWLock.h @@ -0,0 +1,253 @@ +#pragma once + +#include +#include +#include +#include +#include "RWLockException.h" + + +#define RWLOCK_READERS_SKIP_LIMIT 8 /* number of queued reader ignores before writers yield */ + +/* Retries for races, when we give up a read-lock in order to gain a write lock. + * If pthread would allow a lock-upgrade we wouldn't need this at all. Therefore 2 tries, one as + * read-lock and another one as write-lock. In between those we need to unlock() and therefore + * introduce a race that needs to be handled by the corresponding code. */ +#define RWLOCK_LOCK_UPGRADE_RACY_RETRIES 2 + + +enum RWLockLockType +{ + RWLockType_UNSET = -1, + RWLockType_READ = 1, + RWLockType_WRITE = 2 +}; + +/** + * Standard rwlocks with writer preference and special checks to avoid reader starvation. + * + * As standard pthread rwlocks cannot avoid either writer or reader starvation in situations of + * high concurrency, we set writer preference and increase a counter for waiting (queued) readers. + * A few writers are granted even when there are readers waiting, but at some point the writers will + * yield and allow the readers to get their hands on the lock. + */ +class RWLock +{ + public: + RWLock() + { + /* note: most impls of libc on Linux seem to prefer readers by default, which can lead + to writer starvation. hence we need to set writer preference explicitly (but + unfortunately, there is only a non-portable method for that). */ + + pthread_rwlockattr_t attr; + + int attrInitRes = pthread_rwlockattr_init(&attr); + if(unlikely(attrInitRes) ) + throw RWLockException("RWLock attribs init error: " + + System::getErrString(attrInitRes) ); + + /* note: always use PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP here and don't switch to + PTHREAD_RWLOCK_PREFER_WRITER_NP, because that one is buggy and won't get fixed */ + + int kindRes = pthread_rwlockattr_setkind_np(&attr, + PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); + if(unlikely(kindRes) ) + throw RWLockException("RWLock setkind error: " + System::getErrString(kindRes) ); + + int lockInitRes = pthread_rwlock_init(&rwlock, &attr); + if(unlikely(lockInitRes) ) + throw RWLockException("RWLock lock init error: " + System::getErrString(lockInitRes) ); + + lockType = RWLockType_UNSET; + } + + ~RWLock() + { + // may return + // * [EBUSY] never returned by glibc, little useful info without a lockdep tool + // * [EINVAL] never happens (mutex is properly initialized) + pthread_rwlock_destroy(&rwlock); + } + + RWLock(RWLock&&) = delete; + RWLock(const RWLock&) = delete; + RWLock& operator=(RWLock&&) = delete; + RWLock& operator=(const RWLock&) = delete; + + /** + * @throw RWLockException + */ + void writeLock() + { + const char* logContext = "RWLock::writeLock"; + int pthreadRes; + + if(numQueuedReaders.read() ) + { + if(numSkippedReaders.read() > RWLOCK_READERS_SKIP_LIMIT) + { + // Grab (and release) a read lock to avoid reader starvation. That way other + // waiting readers also will get through. + + pthreadRes = pthread_rwlock_rdlock(&rwlock); + if(unlikely(pthreadRes) ) + throw RWLockException(System::getErrString(pthreadRes) ); + + pthreadRes = pthread_rwlock_unlock(&rwlock); + if(unlikely(pthreadRes) ) + throw RWLockException(System::getErrString(pthreadRes) ); + } + else + { + /* not every single waiting reader indicates starvation, so we skip + anti-starvaion mechanism a few times for better performance */ + + numSkippedReaders.increase(); + } + } + + pthreadRes = pthread_rwlock_wrlock(&rwlock); + if(unlikely(pthreadRes) ) + { + std::string sysErrStr = System::getErrString(pthreadRes); + LogContext(logContext).logErr("Failed to get lock: " + sysErrStr); + throw RWLockException(sysErrStr); + } + + lockType = RWLockType_WRITE; + } + + /** + * @throw RWLockException + */ + void readLock() + { + const char* logContext = "RWLock::readLock"; + + if(!tryReadLock() ) + { + // inform writers about waiting readers, so they can avoid starvation + numQueuedReaders.increase(); + + int pthreadRes = pthread_rwlock_rdlock(&rwlock); + if(unlikely(pthreadRes) ) + { + std::string sysErrStr = System::getErrString(pthreadRes); + LogContext(logContext).logErr("Failed to get lock: " + sysErrStr); + LogContext(logContext).logBacktrace(); + throw RWLockException(sysErrStr); + } + + numQueuedReaders.decrease(); + numSkippedReaders.setZero(); + + lockType = RWLockType_READ; + } + } + + /** + * @timeOutSecs number of seconds to wait before giving up aquiring this lock + * + * Note: Only use this method for debugging purposes! + */ + int timedReadLock(size_t timeOutSecs) + { + if (!tryReadLock() ) + { + // inform writers about waiting readers, so they can avoid starvation + numQueuedReaders.increase(); + + // C99 style: { .tv_sec = timeOutSecs, .tv_nsec = 0 }; + struct timespec timeSpec = { (time_t) timeOutSecs, 0 }; + + int pthreadRes = pthread_rwlock_timedrdlock(&rwlock, &timeSpec); + if(!pthreadRes) + { // go the lock + numQueuedReaders.decrease(); + numSkippedReaders.setZero(); + + lockType = RWLockType_READ; + } + + return pthreadRes; + } + + // tryReadLock() succeeded + return 0; + } + + bool tryWriteLock() + { + // may return + // * [EINVAL] never happens (rwlock is properly initialized) + // * [EBUSY] not an error + if (pthread_rwlock_trywrlock(&rwlock) != 0) + return false; + + lockType = RWLockType_WRITE; + return true; + } + + bool tryReadLock() + { + // may return + // * [EINVAL] never happens (rwlock is properly initialized) + // * [EBUSY] not an error + // * [EAGAIN] not an error + if (pthread_rwlock_tryrdlock(&rwlock) != 0) + return false; + + lockType = RWLockType_READ; + return true; + } + + void unlock() + { + lockType = RWLockType_UNSET; + + // may return: + // * [EINVAL] never happens (rwlock is properly initialized) + // * [EPERM] never returned by glibc, little useful info without a lockdep tool + pthread_rwlock_unlock(&rwlock); + } + + + private: + pthread_rwlock_t rwlock; + AtomicUInt32 numQueuedReaders; // number of readers waiting for lock + AtomicUInt32 numSkippedReaders; // number of write locks that ignored queued readers + + RWLockLockType lockType; + + public: + // getters & setters + pthread_rwlock_t* getRWLock() + { + return &rwlock; + } + + /** + * Do we have the read-write lock? + */ + bool isRWLocked() + { + if (lockType == RWLockType_WRITE) + return true; + + return false; + } + + /** + * Do we have the read-only lock? + */ + bool isROLocked() + { + if (lockType == RWLockType_READ) + return true; + + return false; + } + +}; + diff --git a/common/source/common/threading/RWLockException.h b/common/source/common/threading/RWLockException.h new file mode 100644 index 0000000..842edca --- /dev/null +++ b/common/source/common/threading/RWLockException.h @@ -0,0 +1,6 @@ +#pragma once + +#include "SynchronizationException.h" + +DECLARE_NAMEDSUBEXCEPTION(RWLockException, "RWLockException", SynchronizationException) + diff --git a/common/source/common/threading/RWLockGuard.h b/common/source/common/threading/RWLockGuard.h new file mode 100644 index 0000000..d3a0676 --- /dev/null +++ b/common/source/common/threading/RWLockGuard.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include + +class RWLockGuard +{ + public: + RWLockGuard(RWLock& lock, SafeRWLockType type) + : lock(&lock) + { + if (type == SafeRWLock_READ) + lock.readLock(); + else + lock.writeLock(); + } + + ~RWLockGuard() + { + lock->unlock(); + } + + RWLockGuard(const RWLockGuard&) = delete; + RWLockGuard(RWLockGuard&&) = delete; + RWLockGuard& operator=(const RWLockGuard&) = delete; + RWLockGuard& operator=(RWLockGuard&&) = delete; + + private: + RWLock* lock; +}; + diff --git a/common/source/common/threading/SafeRWLock.cpp b/common/source/common/threading/SafeRWLock.cpp new file mode 100644 index 0000000..bf19258 --- /dev/null +++ b/common/source/common/threading/SafeRWLock.cpp @@ -0,0 +1,28 @@ +#include +#include "SafeRWLock.h" + + +void SafeRWLock::errRWLockStillLocked() +{ + LogContext log("SafeRWLock"); + log.logErr("Bug(?): Application did not unlock a rwlock!"); + log.logBacktrace(); +} + + +void SafeRWLock::errRWLockAlreadyUnlocked() +{ + LogContext log("SafeRWLock"); + log.logErr("Bug(?): Application tried to unlock a rwlock that has already been unlocked!"); + log.logBacktrace(); +} + + +void SafeRWLock::errRWLockAlreadyLocked() +{ + LogContext log("SafeRWLock"); + log.logErr("Bug(?): Application tried to lock a rwlock that has already been locked!"); + log.logBacktrace(); +} + + diff --git a/common/source/common/threading/SafeRWLock.h b/common/source/common/threading/SafeRWLock.h new file mode 100644 index 0000000..a386b46 --- /dev/null +++ b/common/source/common/threading/SafeRWLock.h @@ -0,0 +1,159 @@ +#pragma once + +#include "RWLock.h" +#include "PThread.h" + +enum SafeRWLockType + { SafeRWLock_READ = 0, SafeRWLock_WRITE = 1 }; + +/** + * This class does not replace a rwlock - it is just a wrapper around a rwlock. + * This wrapper is not thread-safe (e.g. you cannot use the same shared SafeRWLock instance in + * multiple threads). Instead, it is designed to be instantiated on the stack to give a warning + * when you forgot to unlock. + */ +class SafeRWLock +{ + public: + + /** + * Already takes the lock. + */ + SafeRWLock(RWLock* rwlock, SafeRWLockType lockType) : rwlock(rwlock), locked(true) + { + if(lockType == SafeRWLock_READ) + { + this->rwlock->readLock(); + } + else + { + this->rwlock->writeLock(); + } + } + + /** + * Does not take the lock yet. + */ + SafeRWLock(RWLock* rwlock) :rwlock(rwlock), locked(false) {} + + ~SafeRWLock() + { + #ifdef DEBUG_MUTEX_LOCKING + + if(this->locked) + { + errRWLockStillLocked(); + this->rwlock->unlock(); + } + + #else + + // nothing to be done here + + #endif // DEBUG_MUTEX_LOCKING + } + + SafeRWLock() = delete; + SafeRWLock& operator=(const SafeRWLock&) = delete; + SafeRWLock& operator=(SafeRWLock&&) = delete; + SafeRWLock(const SafeRWLock&) = delete; + SafeRWLock(SafeRWLock&&) = delete; + + void unlock() + { + #ifdef DEBUG_MUTEX_LOCKING + + if(!this->locked) + errRWLockAlreadyUnlocked(); + else + { + this->locked = false; + this->rwlock->unlock(); + } + + #else + + rwlock->unlock(); + + #endif // DEBUG_MUTEX_LOCKING + } + + void lock(SafeRWLockType lockType) + { + #ifdef DEBUG_MUTEX_LOCKING + + if(this->locked) + errRWLockAlreadyLocked(); + else + { + this->locked = true; + + if(lockType == SafeRWLock_READ) + this->rwlock->readLock(); + else + this->rwlock->writeLock(); + } + + #else + + if(lockType == SafeRWLock_READ) + this->rwlock->readLock(); + else + this->rwlock->writeLock(); + + #endif // DEBUG_MUTEX_LOCKING + } + + bool tryLock(SafeRWLockType lockType) + { + if(lockType == SafeRWLock_READ) + this->locked = this->rwlock->tryReadLock(); + else + this->locked = this->rwlock->tryWriteLock(); + + return this->locked; + } + + /** + * @timeOutSecs number of seconds to wait before giving up aquiring this lock + * @return 0 on success (we got the lock) negative linux return code otherwise + * (also see pthread_rwlock_timedrdlock) + * + * Note: Only use this method for debugging purposes! + */ + int timedReadLock(size_t timeOutSecs) + { + int retVal = 0; + #ifdef DEBUG_MUTEX_LOCKING + + if(this->locked) + errRWLockAlreadyLocked(); + else + { + + retVal = this->rwlock->timedReadLock(timeOutSecs); + if (!retVal) + this->locked = true; + } + + #else + retVal = this->rwlock->timedReadLock(timeOutSecs); + #endif // DEBUG_MUTEX_LOCKING + + return retVal; + } + + + void logDebug(std::string msg); + + private: + RWLock* rwlock; + bool locked; // note: this value is updated in debug mode only + + void errRWLockStillLocked(); + void errRWLockAlreadyUnlocked(); + void errRWLockAlreadyLocked(); + + +}; + diff --git a/common/source/common/threading/SynchronizationException.h b/common/source/common/threading/SynchronizationException.h new file mode 100644 index 0000000..88fa835 --- /dev/null +++ b/common/source/common/threading/SynchronizationException.h @@ -0,0 +1,24 @@ +#pragma once + +#include "common/toolkit/NamedException.h" +#include "common/Common.h" + +DECLARE_NAMEDEXCEPTION(SynchronizationException, "SynchronizationException") + +/* +class SynchronizationException : public NamedException +{ + public: + SynchronizationException(const char* message) : + NamedException("SynchronizationException", message) + { + } + + SynchronizationException(const std::string message) : + NamedException("SynchronizationException", message) + { + } + +}; +*/ + diff --git a/common/source/common/threading/UniqueRWLock.h b/common/source/common/threading/UniqueRWLock.h new file mode 100644 index 0000000..cb1e106 --- /dev/null +++ b/common/source/common/threading/UniqueRWLock.h @@ -0,0 +1,75 @@ +#pragma once + +#include +#include + +class UniqueRWLock +{ + public: + UniqueRWLock() + : rwlock(nullptr), locked(false) + { } + + + UniqueRWLock(RWLock& lock, SafeRWLockType type) + : rwlock(&lock), locked(true) + { + if (type == SafeRWLock_READ) + lock.readLock(); + else + lock.writeLock(); + } + + ~UniqueRWLock() + { + if (locked) + rwlock->unlock(); + } + + UniqueRWLock(UniqueRWLock&& old) + : rwlock(nullptr), locked(false) + { + swap(old); + } + + UniqueRWLock& operator=(UniqueRWLock&& other) + { + UniqueRWLock(std::move(other)).swap(*this); + return *this; + } + + UniqueRWLock(const UniqueRWLock&) = delete; + UniqueRWLock& operator=(const UniqueRWLock&) = delete; + + void unlock() + { + rwlock->unlock(); + locked = false; + } + + void lock(SafeRWLockType type) + { + if (type == SafeRWLock_READ) + rwlock->readLock(); + else + rwlock->writeLock(); + + locked = true; + } + + void swap(UniqueRWLock& other) + { + std::swap(rwlock, other.rwlock); + std::swap(locked, other.locked); + } + + private: + RWLock* rwlock; + bool locked; +}; + +inline void swap(UniqueRWLock& a, UniqueRWLock& b) +{ + a.swap(b); +} + diff --git a/common/source/common/toolkit/AcknowledgmentStore.cpp b/common/source/common/toolkit/AcknowledgmentStore.cpp new file mode 100644 index 0000000..ade2308 --- /dev/null +++ b/common/source/common/toolkit/AcknowledgmentStore.cpp @@ -0,0 +1,82 @@ +#include "AcknowledgmentStore.h" + +/** + * Note: This method does not lock the notifier->waitAcksMutex, because this is typically + * called from a context that does not yet require syncing. + * + * @param waitAcks do not access this map until you called unregister (for thread-safety) + * @param receivedAcks do not access this map until you called unregister (for thread-safety) + */ +void AcknowledgmentStore::registerWaitAcks(WaitAckMap* waitAcks, WaitAckMap* receivedAcks, + WaitAckNotification* notifier) +{ + const std::lock_guard lock(mutex); + + AckStoreEntry newStoreEntry; + + for(WaitAckMapIter iter = waitAcks->begin(); iter != waitAcks->end(); iter++) + { + WaitAck* currentWaitAck = &iter->second; + + newStoreEntry.ackID = currentWaitAck->ackID; + newStoreEntry.waitMap = waitAcks; + newStoreEntry.receivedMap = receivedAcks; + newStoreEntry.notifier = notifier; + + storeMap.insert(AckStoreMapVal(currentWaitAck->ackID, newStoreEntry) ); + } +} + +void AcknowledgmentStore::unregisterWaitAcks(WaitAckMap* waitAcks) +{ + const std::lock_guard lock(mutex); + + for(WaitAckMapIter iter = waitAcks->begin(); iter != waitAcks->end(); iter++) + { + storeMap.erase(iter->first); + } +} + +void AcknowledgmentStore::receivedAck(std::string ackID) +{ + const std::lock_guard lock(mutex); + + AckStoreMapIter storeIter = storeMap.find(ackID); + if(storeIter != storeMap.end() ) + { // ack entry exists in store + AckStoreEntry* storeEntry = &storeIter->second; + + { + const std::lock_guard waitMutexLock(storeEntry->notifier->waitAcksMutex); + + WaitAckMapIter waitIter = storeEntry->waitMap->find(ackID); + if(waitIter != storeEntry->waitMap->end() ) + { // entry exists in waitMap => move to receivedMap + + storeEntry->receivedMap->insert(WaitAckMapVal(ackID, waitIter->second) ); + storeEntry->waitMap->erase(waitIter); + + if(storeEntry->waitMap->empty() ) + { // all acks received => notify + WaitAckNotification* notifier = storeEntry->notifier; + + notifier->waitAcksCompleteCond.broadcast(); + } + } + } + + storeMap.erase(storeIter); + } +} + + +bool AcknowledgmentStore::waitForAckCompletion(WaitAckMap* waitAcks, WaitAckNotification* notifier, + int timeoutMS) +{ + const std::lock_guard waitMutexLoc(notifier->waitAcksMutex); + + if(!waitAcks->empty() ) + notifier->waitAcksCompleteCond.timedwait(¬ifier->waitAcksMutex, timeoutMS); + + return waitAcks->empty(); +} diff --git a/common/source/common/toolkit/AcknowledgmentStore.h b/common/source/common/toolkit/AcknowledgmentStore.h new file mode 100644 index 0000000..9b6615d --- /dev/null +++ b/common/source/common/toolkit/AcknowledgmentStore.h @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + + +class WaitAckNotification +{ + public: + Mutex waitAcksMutex; // this mutex also syncs access to the waitMap/receivedMap during the + // wait phase (which is between registration and deregistration) + Condition waitAcksCompleteCond; // in case all WaitAcks have been received +}; + + +class WaitAck +{ + public: + /** + * @param privateData any private data that helps the caller to identify to ack later + */ + WaitAck(std::string ackID, void* privateData) : + ackID(ackID), privateData(privateData) + { /* all inits done in initializer list */ } + + std::string ackID; + void* privateData; // caller's private data +}; + + +typedef std::map WaitAckMap; // keys are ackIDs +typedef WaitAckMap::iterator WaitAckMapIter; +typedef WaitAckMap::value_type WaitAckMapVal; + + +class AckStoreEntry +{ + public: + std::string ackID; + + WaitAckMap* waitMap; // ack will be removed from this map if it is received + WaitAckMap* receivedMap; // ack will be added to this map if it is received + + WaitAckNotification* notifier; +}; + + +typedef std::map AckStoreMap; +typedef AckStoreMap::iterator AckStoreMapIter; +typedef AckStoreMap::value_type AckStoreMapVal; + + + +class AcknowledgmentStore +{ + public: + ~AcknowledgmentStore() + { + storeMap.clear(); + } + + void registerWaitAcks(WaitAckMap* waitAcks, WaitAckMap* receivedAcks, + WaitAckNotification* notifier); + void unregisterWaitAcks(WaitAckMap* waitAcks); + void receivedAck(std::string ackID); + bool waitForAckCompletion(WaitAckMap* waitAcks, WaitAckNotification* notifier, int timeoutMS); + + + private: + AckStoreMap storeMap; + Mutex mutex; + + + public: + // inliners + + +}; + diff --git a/common/source/common/toolkit/ArrayIteration.h b/common/source/common/toolkit/ArrayIteration.h new file mode 100644 index 0000000..88f8108 --- /dev/null +++ b/common/source/common/toolkit/ArrayIteration.h @@ -0,0 +1,100 @@ +#pragma once + +// Helper classes for ArraySlice template class. +// This file can probably be removed when ArraySlice.h gets removed. + +#include // size_t + +namespace +{ + +// Pointer-based iterator that dereferences to a value copy. +// Needed to implement IterateAsValues. +template +class ValueIter +{ + T *mPtr = nullptr; +public: + T operator*() const { return *mPtr; } + ValueIter& operator++() { mPtr++; return *this; } + ValueIter& operator--() { mPtr--; return *this; } + ValueIter operator++(int) { ValueIter old = *this; mPtr++; return old; } + ValueIter operator--(int) { ValueIter old = *this; mPtr--; return old; } + bool operator==(ValueIter other) { return mPtr == other.mPtr; } + bool operator!=(ValueIter other) { return mPtr != other.mPtr; } + bool operator<(ValueIter other) { return mPtr < other.mPtr; } + bool operator>(ValueIter other) { return mPtr > other.mPtr; } + bool operator<=(ValueIter other) { return mPtr <= other.mPtr; } + bool operator>=(ValueIter other) { return mPtr >= other.mPtr; } + ValueIter() = default; + ValueIter(T *ptr) : mPtr(ptr) {} +}; + +// Wrapper over pointer that dereferences to itself. +// Needed to implement IterateAsPointers. +template +class PointerIter +{ + T *mPtr = nullptr; +public: + T *operator*() const { return mPtr; } + PointerIter& operator++() { mPtr++; return *this; } + PointerIter& operator--() { mPtr--; return *this; } + PointerIter operator++(int) { PointerIter old = *this; mPtr++; return old; } + PointerIter operator--(int) { PointerIter old = *this; mPtr--; return old; } + bool operator==(PointerIter other) { return mPtr == other.mPtr; } + bool operator!=(PointerIter other) { return mPtr != other.mPtr; } + bool operator<(PointerIter other) { return mPtr < other.mPtr; } + bool operator>(PointerIter other) { return mPtr > other.mPtr; } + bool operator<=(PointerIter other) { return mPtr <= other.mPtr; } + bool operator>=(PointerIter other) { return mPtr >= other.mPtr; } + PointerIter() = default; + PointerIter(T *ptr) : mPtr(ptr) {} +}; + +// Simple pointer-delineated range. Iteration by value (every element gets copied). +// Iterate like: +// for (T t : IterateAsValues(...)) {...} +template +class IterateAsValues +{ + T *mBegin = 0; + T *mEnd = 0; +public: + ValueIter begin() const { return ValueIter(mBegin); } + ValueIter end() const { return ValueIter(mEnd); } + IterateAsValues() = default; + IterateAsValues(T *data, size_t count) : mBegin(data), mEnd(data + count) {} +}; + +// Simple pointer-delineated range. Iteration as pointers to the elements. +// Iterate like: +// for (T *ptr : IterateAsPointers(...)) {...} +template +class IterateAsPointers +{ + T *mBegin = 0; + T *mEnd = 0; +public: + PointerIter begin() const { return PointerIter(mBegin); } + PointerIter end() const { return PointerIter(mEnd); } + IterateAsPointers() = default; + IterateAsPointers(T *data, size_t count) : mBegin(data), mEnd(data + count) {} +}; + +// Simple pointer-delineated range. Iteration as references to the elements. +// Iterate like: +// for (T& ptr : IterateAsRefs(...)) {...} +template +class IterateAsRefs +{ + T *mBegin = 0; + T *mEnd = 0; +public: + T *begin() const { return mBegin; } + T *end() const { return mEnd; } + IterateAsRefs() = default; + IterateAsRefs(T *data, size_t count) : mBegin(data), mEnd(data + count) {} +}; + +} diff --git a/common/source/common/toolkit/ArraySlice.h b/common/source/common/toolkit/ArraySlice.h new file mode 100644 index 0000000..1166516 --- /dev/null +++ b/common/source/common/toolkit/ArraySlice.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +#include + +#include "ArrayIteration.h" + +// This class provides functionality similar to std::span, with the difference that +// - it is available to use (std::span is C++20; at the time of writing we're on C++17). +// - it is probably somewhat simpler but less featureful +// - it integrates better with our other helper classes, like (untyped) Slice (see ArrayTypeTraits.h). +// +// We can consider removing it when we move to C++20, but there may also be +// reasons to keep it. + +template +class ArraySlice +{ + T *mData = 0; + size_t mCount = 0; +public: + T *data() const + { + return mData; + } + size_t count() const + { + return mCount; + } + T& operator[](size_t i) + { + assert(i < mCount); + return mData[i]; + } + + IterateAsValues iterateAsValues() const + { + return IterateAsValues(mData, mCount); + } + IterateAsPointers iterateAsPointers() const + { + return IterateAsPointers(mData, mCount); + } + IterateAsPointers iterateAsConstPointers() const + { + return IterateAsPointers(mData, mCount); + } + IterateAsRefs iterateAsRefs() const + { + return IterateAsRefs(mData, mCount); + } + IterateAsRefs iterateAsConstRefs() const + { + return IterateAsRefs(mData, mCount); + } + + ArraySlice() = default; + + ArraySlice(T *data, size_t count) : mData(data), mCount(count) {} + + template // ref-hack to accept plain C arrays + ArraySlice(T (&ref)[N]) : mData(&ref[0]), mCount(N) {} +}; diff --git a/common/source/common/toolkit/ArrayTypeTraits.h b/common/source/common/toolkit/ArrayTypeTraits.h new file mode 100644 index 0000000..0dd1685 --- /dev/null +++ b/common/source/common/toolkit/ArrayTypeTraits.h @@ -0,0 +1,70 @@ +#pragma once + +#include + +#include "ArraySlice.h" + +#include + +namespace +{ + template + class Util + { + public: + // Basic constraints that I think should be required when converting to + // RO_Slice, WO_Slice, and Slice. + // Note that these slices are type-agnostic and we copy using std::memcpy(). + static constexpr bool ro_slice_possible = std::is_trivial::value && ! std::is_volatile::value; + static constexpr bool wo_slice_possible = std::is_trivial::value && ! std::is_volatile::value + && ! std::is_const::value; + }; + + template // array-ref hack to get size automatically + static ArraySlice As_ArraySlice(T (&ref)[N]) + { + return ArraySlice(&ref[0], N); + } + + template // array-ref hack to get size automatically + static RO_Slice As_RO_Slice(T (&ref)[N]) + { + static_assert(Util::ro_slice_possible, "type must be trivial"); + return RO_Slice(&ref[0], N * sizeof (T)); + } + + template // array-ref hack to get size automatically + static WO_Slice As_WO_Slice(T (&ref)[N]) + { + static_assert(Util::wo_slice_possible, "type must be trivial and writeable"); + return WO_Slice(&ref[0], N * sizeof (T)); + } + + template // array-ref hack to get size automatically + static Slice As_Slice(T (&ref)[N]) + { + static_assert(Util::wo_slice_possible, "type must be trivial and writeable"); + return Slice(&ref[0], N * sizeof (T)); + } + + template + static RO_Slice As_RO_Slice(ArraySlice s) + { + static_assert(Util::ro_slice_possible, "type must be trivial"); + return RO_Slice(s.data(), s.count() * sizeof (T)); + } + + template + static WO_Slice As_WO_Slice(ArraySlice s) + { + static_assert(Util::wo_slice_possible, "type must be trivial and writeable"); + return WO_Slice(s.data(), s.count() * sizeof (T)); + } + + template + static Slice As_Slice(ArraySlice s) + { + static_assert(Util::ro_slice_possible, "type must be trivial and writeable"); + return Slice(s.data(), s.count() * sizeof (T)); + } +}; diff --git a/common/source/common/toolkit/AtomicObjectReferencer.h b/common/source/common/toolkit/AtomicObjectReferencer.h new file mode 100644 index 0000000..74806df --- /dev/null +++ b/common/source/common/toolkit/AtomicObjectReferencer.h @@ -0,0 +1,84 @@ +#pragma once + +#include + +template +class AtomicObjectReferencer +{ + public: + /** + * @param ownReferencedObject if set to true, this object owns the referencedObject + * (so it will delete() the referencedObject when itself is being deleted) + */ + AtomicObjectReferencer(T referencedObject, bool ownReferencedObject=true) + { + this->referencedObject = referencedObject; + this->ownReferencedObject = ownReferencedObject; + } + + ~AtomicObjectReferencer() + { + if(ownReferencedObject) + delete referencedObject; + } + + + private: + AtomicSizeT refCount; + bool ownReferencedObject; + + T referencedObject; + + public: + // inliners + + T reference() + { + refCount.increase(); + return referencedObject; + } + + int release() + { + #ifdef DEBUG_REFCOUNT + if(refCount.read() == 0) + { + LogContext log("ObjectReferencer::release"); + log.logErr("Bug: refCount is 0 and release() was called."); + log.logBacktrace(); + + return 0; + } + else + return refCount.decrease() - 1; // .decrease() returns the old value + #else + return refCount.decrease() - 1; // .decrease() returns the old value + #endif // DEBUG_REFCOUNT + } + + /** + * Be careful: This does not change the reference count! + */ + T getReferencedObject() + { + return referencedObject; + } + + int getRefCount() + { + return refCount.read(); + } + + void setOwnReferencedObject(bool enable) + { + this->ownReferencedObject = enable; + } + + bool getOwnReferencedObject() + { + return ownReferencedObject; + } + +}; + + diff --git a/common/source/common/toolkit/BitStore.cpp b/common/source/common/toolkit/BitStore.cpp new file mode 100644 index 0000000..8e8d4bd --- /dev/null +++ b/common/source/common/toolkit/BitStore.cpp @@ -0,0 +1,162 @@ +#include +#include +#include "BitStore.h" + + + +/** + * Set or unset a bit at the given index. + * + * Note: The bit operations in here are not atomic. + * + * @param isSet true to set or false to unset the corresponding bit. + */ +void BitStore::setBit(unsigned bitIndex, bool isSet) +{ + unsigned index = bitIndex / LIMB_SIZE; + unsigned indexInBitBlock = bitIndex % LIMB_SIZE; + limb_type mask = 1UL << indexInBitBlock; + + if(unlikely(bitIndex >= this->numBits) ) + return; + + if (index == 0) + { + if (isSet) + this->lowerBits = this->lowerBits | mask; + else + this->lowerBits = this->lowerBits & (~mask); + } + else + { + if (isSet) + this->higherBits[index - 1] = this->higherBits[index - 1] | mask; + else + this->higherBits[index - 1] = this->higherBits[index - 1] & (~mask); + } +} + +/** + * grow/shrink internal buffers as needed for new size. + * + * note: this method does not reset or copy bits, so consider the bits to be uninitialized after + * calling this method. thus, you might want to call clearBits() afterwards to clear all bits. + * + * @param size number of bits + */ +void BitStore::setSize(unsigned newSize) +{ + unsigned oldBitBlockCount = calculateBitBlockCount(this->numBits); + unsigned newBitBlockCount = calculateBitBlockCount(newSize); + + if(newBitBlockCount != oldBitBlockCount) + { + higherBits.reset(); + + if(newBitBlockCount > 1) + higherBits.reset(new limb_type[newBitBlockCount - 1]); + + this->numBits = newBitBlockCount * LIMB_SIZE; + } +} + +/** + * reset all bits to 0. + */ +void BitStore::clearBits() +{ + this->lowerBits = 0; + + if(this->higherBits) + { + unsigned bitBlockCount = calculateBitBlockCount(this->numBits); + + memset(higherBits.get(), 0, (bitBlockCount-1) * sizeof(limb_type)); + } +} + +/** + * release the memory of the higher bits and set the size of the bit store to the minimal value + */ +void BitStore::freeHigherBits() +{ + higherBits.reset(); + this->numBits = LIMB_SIZE; +} + +/** + * note: serialized format is 8 byte aligned. + */ +void BitStore::serialize(Serializer& ser) const +{ + ser + % numBits + % uint32_t(0) // PADDING + % lowerBits; + + unsigned blockCount = calculateBitBlockCount(this->numBits); + + for(unsigned i = 0; i < (blockCount - 1); i++) + ser % higherBits[i]; + + // PADDING to 64bit + if(blockCount & 1) + ser % uint32_t(0); +} + +void BitStore::serialize(Deserializer& des) +{ + uint32_t numBits; + uint32_t dummy; + + des + % numBits + % dummy // PADDING + % lowerBits; + + setSize(numBits); + + unsigned blockCount = calculateBitBlockCount(this->numBits); + for(unsigned i = 0; i < (blockCount - 1); i++) + des % higherBits[i]; + + // PADDING to 64bit + if(blockCount & 1) + des % dummy; +} + +/** + * compare two BitStores. + * + * @return true if both BitStores are equal + */ +bool BitStore::operator==(const BitStore& other) const +{ + if(numBits != other.numBits + || lowerBits != other.lowerBits) + return false; + + unsigned blockCount = calculateBitBlockCount(numBits); + + return std::equal(higherBits.get(), higherBits.get() + blockCount - 1, other.higherBits.get()); +} + +/** + * Update this store with values from given other store. + */ +BitStore& BitStore::operator=(const BitStore& other) +{ + setSize(other.numBits); + + this->lowerBits = other.lowerBits; + + if(!this->higherBits) + return *this; + + // we have higherBits to copy + + unsigned blockCount = calculateBitBlockCount(this->numBits); + + memcpy(higherBits.get(), other.higherBits.get(), (blockCount-1) * sizeof(limb_type)); + return *this; +} diff --git a/common/source/common/toolkit/BitStore.h b/common/source/common/toolkit/BitStore.h new file mode 100644 index 0000000..7377d3c --- /dev/null +++ b/common/source/common/toolkit/BitStore.h @@ -0,0 +1,113 @@ +#pragma once + + +#include +#include + + +/** + * A vector of bits. + */ +class BitStore +{ + friend class TestBitStore; + + public: + typedef uint32_t limb_type; + + static const unsigned int LIMB_SIZE = 32; + + public: + BitStore() + { + init(true); + } + + explicit BitStore(int size) + { + init(false); + setSize(size); + clearBits(); + } + + BitStore(const BitStore& other) + { + init(true); + *this = other; + } + + void setBit(unsigned bitIndex, bool isSet); + + void setSize(unsigned newSize); + void clearBits(); + + bool operator==(const BitStore& other) const; + + bool operator!=(const BitStore& other) const { return !(*this == other); } + + BitStore& operator=(const BitStore& other); + + void serialize(Serializer& ser) const; + void serialize(Deserializer& des); + + template + static void serialize(This obj, Ctx& ctx) + { + obj->serialize(ctx); + } + + static unsigned calculateBitBlockCount(unsigned size) + { + return size < LIMB_SIZE + ? 1 + : (size + LIMB_SIZE - 1) / LIMB_SIZE; + } + + + private: + uint32_t numBits; // max number of bits that this store can hold + limb_type lowerBits; // used to avoid overhead of extra alloc for higherBits + boost::scoped_array higherBits; // array, which is alloc'ed only when needed + + void freeHigherBits(); + + + protected: + + public: + // inliners + + void init(bool setZero) + { + this->numBits = LIMB_SIZE; + this->higherBits.reset(); + + if (setZero) + this->lowerBits = 0; + } + + /** + * Test whether the bit at the given index is set or not. + * + * Note: The bit operations in here are non-atomic. + */ + bool getBitNonAtomic(unsigned bitIndex) const + { + size_t index; + size_t indexInBitBlock; + limb_type mask; + + if (bitIndex >= this->numBits) + return false; + + index = bitIndex / LIMB_SIZE; + indexInBitBlock = bitIndex % LIMB_SIZE; + mask = 1UL << indexInBitBlock; + + if (index == 0) + return (this->lowerBits & mask) ? true : false; + else + return (this->higherBits[index - 1] & mask) ? true : false; + } +}; + diff --git a/common/source/common/toolkit/BuildTypeTk.cpp b/common/source/common/toolkit/BuildTypeTk.cpp new file mode 100644 index 0000000..ace13e3 --- /dev/null +++ b/common/source/common/toolkit/BuildTypeTk.cpp @@ -0,0 +1,10 @@ +#include "BuildTypeTk.h" + +/* Note: Do not add these functions to the header file, we want to explicitly check for values of + * the compiled library + */ + +FhgfsBuildTypeDebug BuildTypeTk::getCommonLibDebugBuildType() +{ + return BUILDTYPE_CURRENT_DEBUG; +} diff --git a/common/source/common/toolkit/BuildTypeTk.h b/common/source/common/toolkit/BuildTypeTk.h new file mode 100644 index 0000000..bc185a8 --- /dev/null +++ b/common/source/common/toolkit/BuildTypeTk.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include + + +/** + * This works by setting the build type of the common lib via getCommonLibDebugBuildType() and setting + * the build type of the program using the common lib vial getCurrentDebugBuildType(). + * + * Note: Of course, there are a number of cases that won't be detected with this (e.g. rebuilds + * of a program without a "make clean" etc.) and there is not guarantee that the check code will + * even be reached when the build types differ. + */ + +enum FhgfsBuildTypeDebug + {FhgfsBuildType_DEBUG_OFF=0, FhgfsBuildType_DEBUG_ON=1}; + +#ifdef BEEGFS_DEBUG +#define BUILDTYPE_CURRENT_DEBUG FhgfsBuildType_DEBUG_ON +#else +#define BUILDTYPE_CURRENT_DEBUG FhgfsBuildType_DEBUG_OFF +#endif + +class BuildTypeTk +{ + public: + static FhgfsBuildTypeDebug getCommonLibDebugBuildType(); + + private: + BuildTypeTk() {} + + public: + // inliners + + static inline FhgfsBuildTypeDebug getCurrentDebugBuildType() + { + return BUILDTYPE_CURRENT_DEBUG; + } + + static inline void checkDebugBuildTypes() + { + if(getCurrentDebugBuildType() != getCommonLibDebugBuildType() ) + throw InvalidConfigException("Debug build types differ. Check your release/debug make " + "settings."); + } +}; + + diff --git a/common/source/common/toolkit/DebugVariable.h b/common/source/common/toolkit/DebugVariable.h new file mode 100644 index 0000000..273c875 --- /dev/null +++ b/common/source/common/toolkit/DebugVariable.h @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +#if BEEGFS_DEBUG +# define DEBUG_ENV_VAR(type, name, value, envID) \ + static const type name = \ + ::getenv(envID) \ + ? boost::lexical_cast(::getenv(envID)) \ + : value +#else +# define DEBUG_ENV_VAR(type, name, value, envID) \ + static const type name = value +#endif + diff --git a/common/source/common/toolkit/DisposalCleaner.cpp b/common/source/common/toolkit/DisposalCleaner.cpp new file mode 100644 index 0000000..820786a --- /dev/null +++ b/common/source/common/toolkit/DisposalCleaner.cpp @@ -0,0 +1,115 @@ +#include "DisposalCleaner.h" + +#include +#include +#include +#include +#include +#include + +void DisposalCleaner::run(const std::vector& nodes, const std::function& onItem, + const std::function& onError, const std::function& abortCondition) +{ + for (const auto& node : nodes) + { + FhgfsOpsErr walkRes = walkNode(*node, onItem, abortCondition); + if (walkRes != FhgfsOpsErr_SUCCESS) + onError(*node, walkRes); + } +} + +FhgfsOpsErr DisposalCleaner::walkNode(Node& node, const std::function& onItem, + const std::function& abortCondition) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + size_t numEntriesThisRound = 0; // received during last RPC round + ListDirFromOffsetRespMsg* respMsgCast; + + FhgfsOpsErr listRes = FhgfsOpsErr_SUCCESS; + unsigned maxOutNames = 50; + StringList* entryNames; + uint64_t currentServerOffset = 0; + + do + { + for (int i = (onlyMirrored ? 1 : 0); i <= 1; i++) + { + // i == 0 -> non-mirrored metadata + // i == 1 -> mirrored metadata + // + // we have to skip mirrored metadata if the node we are looking at is the secondary if its + // group, otherwise we would list mirrored disposal file twice - and attempt to delete them + // twice. + // also, we don't have to list anything if the node is not part of a mirror group at all. + if (i == 1) + { + const uint16_t thisGroup = bgm->getBuddyGroupID(node.getNumID().val()); + + if (thisGroup == 0) + continue; + + if (bgm->getPrimaryTargetID(thisGroup) != node.getNumID().val()) + continue; + } + + std::string entryID = i == 0 + ? META_DISPOSALDIR_ID_STR + : META_MIRRORDISPOSALDIR_ID_STR; + EntryInfo entryInfo(node.getNumID(), "", entryID, entryID, DirEntryType_DIRECTORY, + i == 0 ? 0 : ENTRYINFO_FEATURE_BUDDYMIRRORED); + + ListDirFromOffsetMsg listMsg(&entryInfo, currentServerOffset, maxOutNames, true); + + const auto respMsg = MessagingTk::requestResponse(node, listMsg, + NETMSGTYPE_ListDirFromOffsetResp); + if (!respMsg) + return FhgfsOpsErr_COMMUNICATION; + + respMsgCast = (ListDirFromOffsetRespMsg*)respMsg.get(); + + listRes = (FhgfsOpsErr)respMsgCast->getResult(); + if (listRes != FhgfsOpsErr_SUCCESS) + return listRes; + + entryNames = &respMsgCast->getNames(); + + numEntriesThisRound = entryNames->size(); + currentServerOffset = respMsgCast->getNewServerOffset(); + + for (auto it = entryNames->begin(); it != entryNames->end(); ++it) + { + retVal = onItem(node, *it, i != 0); + if (retVal != FhgfsOpsErr_SUCCESS) + break; + } + + if (abortCondition()) + return retVal; + } + } while (retVal == FhgfsOpsErr_SUCCESS && numEntriesThisRound == maxOutNames); + + return retVal; +} + +FhgfsOpsErr DisposalCleaner::unlinkFile(Node& node, std::string entryName, const bool isMirrored) +{ + UnlinkFileRespMsg* respMsgCast; + + std::string entryID = isMirrored + ? META_MIRRORDISPOSALDIR_ID_STR + : META_DISPOSALDIR_ID_STR; + + EntryInfo parentInfo(node.getNumID(), "", entryID, entryID, DirEntryType_DIRECTORY, + isMirrored ? ENTRYINFO_FEATURE_BUDDYMIRRORED : 0); + + UnlinkFileMsg getInfoMsg(&parentInfo, entryName); + + const auto respMsg = MessagingTk::requestResponse(node, getInfoMsg, NETMSGTYPE_UnlinkFileResp); + if (!respMsg) + return FhgfsOpsErr_COMMUNICATION; + + respMsgCast = (UnlinkFileRespMsg*)respMsg.get(); + + return (FhgfsOpsErr)respMsgCast->getValue(); +} diff --git a/common/source/common/toolkit/DisposalCleaner.h b/common/source/common/toolkit/DisposalCleaner.h new file mode 100644 index 0000000..e777d1a --- /dev/null +++ b/common/source/common/toolkit/DisposalCleaner.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include + +class DisposalCleaner +{ + public: + typedef FhgfsOpsErr (OnItemFn)(Node& owner, const std::string& entryID, + const bool isMirrored); + typedef void (OnErrorFn)(Node& node, FhgfsOpsErr err); + + DisposalCleaner(MirrorBuddyGroupMapper& bgm, bool onlyMirrored=false): + bgm(&bgm), onlyMirrored(onlyMirrored) + { + } + + void run(const std::vector& nodes, const std::function& onItem, + const std::function& onError, + const std::function& abortCondition = [] () { return false; }); + + static FhgfsOpsErr unlinkFile(Node& node, std::string entryName, const bool isMirrored); + + private: + MirrorBuddyGroupMapper* bgm; + + FhgfsOpsErr walkNode(Node& node, const std::function& onItem, + const std::function& abortCondition); + + const bool onlyMirrored; +}; + diff --git a/common/source/common/toolkit/EntryIdTk.cpp b/common/source/common/toolkit/EntryIdTk.cpp new file mode 100644 index 0000000..ea18304 --- /dev/null +++ b/common/source/common/toolkit/EntryIdTk.cpp @@ -0,0 +1,28 @@ +#include "EntryIdTk.h" + +namespace EntryIdTk { + +bool isValidEntryIdFormat(const std::string& entryId) +{ + + const auto sep1 = entryId.find('-'); + const auto sep2 = entryId.find('-', sep1 + 1); + + return sep1 != std::string::npos && sep2 != std::string::npos + && isValidHexToken(entryId.substr(0, sep1)) + && isValidHexToken(entryId.substr(sep1 + 1, sep2 - (sep1 + 1))) + && isValidHexToken(entryId.substr(sep2 + 1)); +} + +bool isValidHexToken(const std::string& token) { + const auto notUpperHexDigit = [] (const char c) { + return !std::isxdigit(c) || std::islower(c); + }; + const auto isHex = [¬UpperHexDigit] (const std::string& s) { + return std::count_if(s.begin(), s.end(), notUpperHexDigit) == 0; + }; + + return !token.empty() && (token.size() <= 8) && isHex(token); +} + +} diff --git a/common/source/common/toolkit/EntryIdTk.h b/common/source/common/toolkit/EntryIdTk.h new file mode 100644 index 0000000..e0fdf5e --- /dev/null +++ b/common/source/common/toolkit/EntryIdTk.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include + +namespace EntryIdTk { + + bool isValidEntryIdFormat(const std::string& entryId); + + bool isValidHexToken(const std::string& token); + +} + diff --git a/common/source/common/toolkit/FDHandle.h b/common/source/common/toolkit/FDHandle.h new file mode 100644 index 0000000..2909c2c --- /dev/null +++ b/common/source/common/toolkit/FDHandle.h @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +class FDHandle +{ + private: + int m_fd = -1; + + public: + int close() + { + if (! valid()) + return 0; + + int r = ::close(m_fd); + m_fd = -1; + return r; + } + + int get() const + { + return m_fd; + } + + void reset(int fd) + { + close(); + m_fd = fd; + } + + bool valid() const + { + return m_fd >= 0; + } + + // so weird... can we get rid of this? + int operator*() const + { + return m_fd; + } + + FDHandle& operator=(const FDHandle&) = delete; + + void operator=(FDHandle&& other) + { + close(); + std::swap(m_fd, other.m_fd); + } + + FDHandle() + {} + + explicit FDHandle(int fd) + { + reset(fd); + } + + FDHandle(const FDHandle&) = delete; + + FDHandle(FDHandle&& other) + { + std::swap(m_fd, other.m_fd); + } + + ~FDHandle() + { + close(); + } +}; diff --git a/common/source/common/toolkit/FileDescriptor.h b/common/source/common/toolkit/FileDescriptor.h new file mode 100644 index 0000000..6b18064 --- /dev/null +++ b/common/source/common/toolkit/FileDescriptor.h @@ -0,0 +1,82 @@ +#pragma once + +#include +#include "poll/Pollable.h" + +class FileDescriptor : public Pollable +{ + public: + /** + * @param fd not owned by this object (will not be closed in the destructor) + * @param threadsafety true to synchronize read/write access + */ + FileDescriptor(int fd, bool threadsafety) + { + this->fd = fd; + this->threadsafety = threadsafety; + } + + virtual ~FileDescriptor() {} + + + private: + int fd; + + bool threadsafety; + Mutex mutex; + + + public: + // inliners + ssize_t readExact(void *buf, size_t count) + { + size_t missing = count; + + if(threadsafety) + mutex.lock(); + + do + { + ssize_t recvRes = ::read(fd, &((char*)buf)[count-missing], missing); + missing -= recvRes; + } while(missing); + + if(threadsafety) + mutex.unlock(); + + return (ssize_t)count; + } + + ssize_t read(void *buf, size_t count) + { + if(threadsafety) + mutex.lock(); + + ssize_t readRes = ::read(fd, buf, count); + + if(threadsafety) + mutex.unlock(); + + return readRes; + } + + ssize_t write(const void *buf, size_t count) + { + if(threadsafety) + mutex.lock(); + + ssize_t writeRes = ::write(fd, buf, count); + + if(threadsafety) + mutex.unlock(); + + return writeRes; + } + + // getters & setters + virtual int getFD() const + { + return fd; + } +}; + diff --git a/common/source/common/toolkit/FsckTk.cpp b/common/source/common/toolkit/FsckTk.cpp new file mode 100644 index 0000000..1391db0 --- /dev/null +++ b/common/source/common/toolkit/FsckTk.cpp @@ -0,0 +1,90 @@ +#include "FsckTk.h" + +#include +#include +#include +#include + +FsckDirEntryType FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType entryType) +{ + FsckDirEntryType retVal; + + if (DirEntryType_ISREGULARFILE(entryType)) + retVal = FsckDirEntryType_REGULARFILE; + else + if (DirEntryType_ISSYMLINK(entryType)) + retVal = FsckDirEntryType_SYMLINK; + else + if (DirEntryType_ISDIR(entryType)) + retVal = FsckDirEntryType_DIRECTORY; + else + if (! DirEntryType_ISVALID(entryType)) + retVal = FsckDirEntryType_INVALID; + else // is valid, but must be something special (dev, fifo, socket, ...) + retVal = FsckDirEntryType_SPECIAL; + + return retVal; +} + +/* + * @param StripePattern pointer to a fhgfs stripe pattern + * @outTargetIDVector + * @outChunkSize + * + * @return a the corresponding FsckStripePatternType + */ +FsckStripePatternType FsckTk::stripePatternToFsckStripePattern(StripePattern* stripePattern, + unsigned *outChunkSize, UInt16Vector* outTargetIDVector) +{ + // convert the pattern type + FsckStripePatternType patternType; + + switch (stripePattern->getPatternType()) + { + case StripePatternType_Raid0: + patternType = FsckStripePatternType_RAID0; + break; + case StripePatternType_BuddyMirror: + patternType = FsckStripePatternType_BUDDYMIRROR; + break; + default: + patternType = FsckStripePatternType_INVALID; + break; + } + + if ( stripePattern->getPatternType() != StripePatternType_Invalid ) + { + // get the targetIDs + if ( outTargetIDVector ) + *outTargetIDVector = *stripePattern->getStripeTargetIDs(); + + // get the chunkSize + if ( outChunkSize ) + *outChunkSize = stripePattern->getChunkSize(); + } + + return patternType; +} + +/* + * @param targetVecBuf a buffer holding the serialized targetIDVector + * @param stripePatternType the type of the pattern as FsckStripePatternType + * @param chunkSize the chunk size to set in the pattern + * + * @return a pointer to a stripe pattern; make sure to delete after use + */ +StripePattern* FsckTk::FsckStripePatternToStripePattern(FsckStripePatternType stripePatternType, + unsigned chunkSize, UInt16Vector* targetIDVector) +{ + switch(stripePatternType) + { + case (FsckStripePatternType_RAID0): + return new Raid0Pattern(chunkSize, *targetIDVector); + + case (FsckStripePatternType_BUDDYMIRROR): + return new BuddyMirrorPattern(chunkSize, *targetIDVector); + + default: + return NULL; + } +} diff --git a/common/source/common/toolkit/FsckTk.h b/common/source/common/toolkit/FsckTk.h new file mode 100644 index 0000000..18d8f8e --- /dev/null +++ b/common/source/common/toolkit/FsckTk.h @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include + +#ifndef FSCKTK_H_ +#define FSCKTK_H_ + +enum FsckStripePatternType +{ + FsckStripePatternType_INVALID = 0, FsckStripePatternType_RAID0 = 1, + FsckStripePatternType_BUDDYMIRROR = 2 +}; + +enum FetchFsckChunkListStatus +{ + FetchFsckChunkListStatus_NOTSTARTED=0, /* */ + FetchFsckChunkListStatus_RUNNING=1, /* nothing strange so far */ + FetchFsckChunkListStatus_FINISHED=2, /* all chunks are read */ + FetchFsckChunkListStatus_READERROR=3, /* a read error occured */ +}; + +class FsckTk +{ + public: + FsckTk(){ }; + virtual ~FsckTk(){ }; + + static FsckStripePatternType stripePatternToFsckStripePattern(StripePattern* stripePattern, + unsigned *outChunkSize = NULL, UInt16Vector* outTargetIDVector = NULL); + static StripePattern* FsckStripePatternToStripePattern( + FsckStripePatternType stripePatternType, unsigned chunkSize, UInt16Vector* targetIDVector); + + static FsckDirEntryType DirEntryTypeToFsckDirEntryType(DirEntryType entryType); +}; + +#endif /* FSCKTK_H_ */ diff --git a/common/source/common/toolkit/HashTk.cpp b/common/source/common/toolkit/HashTk.cpp new file mode 100644 index 0000000..4dfe702 --- /dev/null +++ b/common/source/common/toolkit/HashTk.cpp @@ -0,0 +1,109 @@ +// Paul Hsiehs hash function, available under http://www.azillionmonkeys.com/qed/hash.html + + +// Paul Hsieh OLD BSD license + +// Copyright (c) 2010, Paul Hsieh All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, are permitted +// provided that the following conditions are met: + +// Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. + +// Redistributions in binary form must reproduce the above copyright notice, this list of +// conditions and the following disclaimer in the documentation and/or other materials provided +// with the distribution. + +// Neither my name, Paul Hsieh, nor the names of any other contributors to the code use may not +// be used to endorse or promote products derived from this software without specific prior +// written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR +// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND +// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY +// WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "common/Common.h" +#include "common/toolkit/hash_library/sha256.h" + +#include "HashTk.h" + + +#define get16bits(d) (*((const uint16_t *) (d))) + +uint32_t HashTk::hsieh32(const char* data, int len) +{ + uint32_t hash = len, tmp; + int rem; + + if(unlikely(len <= 0 || data == NULL) ) + return 0; + + rem = len & 3; + len >>= 2; + + /* Main loop */ + for(; len > 0; len--) + { + hash += get16bits(data); + tmp = (get16bits(data+2) << 11) ^ hash; + hash = (hash << 16) ^ tmp; + data += 2 * sizeof(uint16_t); + hash += hash >> 11; + } + + /* Handle end cases */ + switch(rem) + { + case 3: + hash += get16bits(data); + hash ^= hash << 16; + hash ^= data[sizeof(uint16_t)] << 18; + hash += hash >> 11; + break; + case 2: + hash += get16bits(data); + hash ^= hash << 11; + hash += hash >> 17; + break; + case 1: + hash += *data; + hash ^= hash << 10; + hash += hash >> 1; + } + + /* Force "avalanching" of final 127 bits */ + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + + return hash; +} + +// Generates sha256 hash from the input data and returns the authentication secret containing the 8 +// most significant bytes of the hash in little endian order. Matches the behavior of other +// implementations. +uint64_t HashTk::authHash(const unsigned char* data, size_t len) +{ + unsigned char buf[SHA256::HashBytes]; + SHA256 h; + h.add(data, len); + h.getHash(buf); + + uint64_t res = 0; + for (int i = 7; i >= 0; --i) + { + res <<= 8; + res += buf[i]; + } + + return res; +} diff --git a/common/source/common/toolkit/HashTk.h b/common/source/common/toolkit/HashTk.h new file mode 100644 index 0000000..5df1da8 --- /dev/null +++ b/common/source/common/toolkit/HashTk.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +namespace HashTk { + uint32_t hsieh32(const char* data, int len); + uint64_t authHash(const unsigned char* data, std::size_t len); +} + diff --git a/common/source/common/toolkit/HighResolutionStats.h b/common/source/common/toolkit/HighResolutionStats.h new file mode 100644 index 0000000..1d993b4 --- /dev/null +++ b/common/source/common/toolkit/HighResolutionStats.h @@ -0,0 +1,104 @@ +#pragma once + +#include +#include + +/** + * Note: It is important to have the subvalues grouped, because the WorkQ needs to reset only a + * part of the values after each reset. + */ +struct HighResolutionStats +{ + // raw values (just copied at the end of each query interval) + struct RawVals + { + uint64_t statsTimeMS = 0; // set by the StatsCollector (result of TimeAbs.getTimeMS() ) + + unsigned busyWorkers = 0; // busy workers at the time of a stats reset (updated by WorkQ) + unsigned queuedRequests = 0; // queued requests at the time of a stats reset + // (updated by WorkQ) + } rawVals; + + // incremental values (added up after each request until end of query interval) + struct IncrementalVals + { + uint64_t diskWriteBytes = 0; // ususally updated during processing of work + uint64_t diskReadBytes = 0; // ususally updated during processing of work + uint64_t netSendBytes = 0; // ususally updated by a socket + uint64_t netRecvBytes = 0; // ususally updated by a socket + unsigned workRequests = 0; // finished work requests; usually updated by a worker + } incVals; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->rawVals.statsTimeMS + % obj->rawVals.busyWorkers + % obj->rawVals.queuedRequests + % obj->incVals.diskWriteBytes + % obj->incVals.diskReadBytes + % obj->incVals.netSendBytes + % obj->incVals.netRecvBytes + % obj->incVals.workRequests; + } +}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + +typedef std::list HighResStatsList; +typedef HighResStatsList::iterator HighResStatsListIter; +typedef HighResStatsList::reverse_iterator HighResStatsListRevIter; + +typedef std::vector HighResStatsVec; + + +class HighResolutionStatsTk +{ + private: + HighResolutionStatsTk() {} + + + public: + // inliners + + /** + * Adds new incremental stats to existing stats. + * + * Note: This is typically called after a finished work request. + */ + static void addHighResIncStats(HighResolutionStats& newStats, + HighResolutionStats& outUpdatedStats) + { + outUpdatedStats.incVals.diskWriteBytes += newStats.incVals.diskWriteBytes; + outUpdatedStats.incVals.diskReadBytes += newStats.incVals.diskReadBytes; + outUpdatedStats.incVals.netSendBytes += newStats.incVals.netSendBytes; + outUpdatedStats.incVals.netRecvBytes += newStats.incVals.netRecvBytes; + outUpdatedStats.incVals.workRequests += newStats.incVals.workRequests; + } + + /** + * Adds new raw stats to existing stats. + * + * Note: This is typically called when you add up stats for multiple nodes, so the + * statsTimeMS value is not added here. + */ + static void addHighResRawStats(HighResolutionStats& newStats, + HighResolutionStats& outUpdatedStats) + { + outUpdatedStats.rawVals.busyWorkers += newStats.rawVals.busyWorkers; + outUpdatedStats.rawVals.queuedRequests += newStats.rawVals.queuedRequests; + } + + static void resetStats(HighResolutionStats* stats) + { + *stats = {}; + } + + static void resetIncStats(HighResolutionStats* stats) + { + stats->incVals = {}; + } +}; + diff --git a/common/source/common/toolkit/ListTk.h b/common/source/common/toolkit/ListTk.h new file mode 100644 index 0000000..58e793b --- /dev/null +++ b/common/source/common/toolkit/ListTk.h @@ -0,0 +1,148 @@ +#pragma once + +#include + +#include + +class ListTk +{ + public: + + + private: + ListTk() {} + + + public: + // inliners + + /** + * @param outPos zero-based list position if found, -1 otherwise + */ + static bool listContains(const std::string& searchStr, const StringList* list, + ssize_t* outPos) + { + (*outPos) = 0; + + for(StringListConstIter iter=list->begin(); iter != list->end(); iter++) + { + if(!searchStr.compare(*iter) ) + return true; + + (*outPos)++; + } + + (*outPos) = -1; + + return false; + } + + /** + * @param outPos zero-based list position if found, -1 otherwise + */ + template + static bool listContains(T& searchValue, std::list& list, ssize_t& outPos) + { + outPos = 0; + + for(StringListIter iter=list.begin(); iter != list.end(); iter++) + { + if (*iter == searchValue) + return true; + + outPos++; + } + + outPos = -1; + + return false; + } + + template + static bool listsEqual(std::list* listA, std::list* listB) + { + // check sizes + if ( listA->size() != listB->size() ) + return false; + + // sort both lists and compare elements + listA->sort(); + listB->sort(); + + typename std::list::iterator iterA = listA->begin(); + typename std::list::iterator iterB = listB->begin(); + + while ( iterA != listA->end() ) + { + T objectA = *iterA; + T objectB = *iterB; + + if ( objectA != objectB ) + return false; + + iterA++; + iterB++; + } + + return true; + } + + /* + * remove all elements from removeList in list + */ + template + static void removeFromList(std::list& list, std::list& removeList) + { + typename std::list::iterator iter; + for (iter = removeList.begin(); iter != removeList.end(); iter++) + { + list.remove(*iter); + } + } + + /* This function is meant to replace advance (http://www.cplusplus.com/reference/std/iterator + * /advance/) from the STL in many cases. The problem with std::advance is, that if your list + * contains x elements, you can still call advance(iter, x+y) without any problem. After + * advancing to the lists end, it will just start at the beginning again. + * This version of advance will stop at the end of the list and just set iter to list::end + * + * @param list the list used to check if the end is reached (should be the list, from which + * iter was created) + * @param iter the list's iterator + * @param count how many elements to proceed + * @return the actual element count, that was advanced + */ + template + static unsigned advance(std::list& list, InputIterator& iter, unsigned count) + { + for (unsigned i=0; i + static bool erase(std::list& list, size_t pos) + { + // check size + if ( pos >= list.size()) + return false; + + typename std::list::iterator iter = list.begin(); + std::advance(iter, pos); + + list.erase(iter); + + return true; + } +}; + + diff --git a/common/source/common/toolkit/LockFD.cpp b/common/source/common/toolkit/LockFD.cpp new file mode 100644 index 0000000..3069750 --- /dev/null +++ b/common/source/common/toolkit/LockFD.cpp @@ -0,0 +1,38 @@ +#include "LockFD.h" + +#include + +#include + +#include + +nu::error_or LockFD::lock(std::string path, bool forUpdate) +{ + FDHandle fd(open(path.c_str(), O_CREAT | O_TRUNC | (forUpdate ? O_RDWR : O_RDONLY), 0644)); + if (!fd.valid()) + return std::error_code{errno, std::system_category()}; + + if (flock(*fd, LOCK_EX | LOCK_NB) == -1) + return std::error_code{errno, std::system_category()}; + + return LockFD(std::move(path), std::move(fd)); +} + +std::error_code LockFD::update(const std::string& newContent) +{ + const int truncRes = ftruncate(*fd, 0); + if (truncRes == -1) + return {errno, std::system_category()}; + + const ssize_t writeRes = pwrite(*fd, newContent.c_str(), newContent.length(), 0); + if (writeRes < 0 || size_t(writeRes) != newContent.length()) + return {errno, std::system_category()}; + + return {}; +} + +std::error_code LockFD::updateWithPID() +{ + // add newline, because some shell tools don't like it when there is no newline + return update(std::to_string(getpid()) + '\n'); +} diff --git a/common/source/common/toolkit/LockFD.h b/common/source/common/toolkit/LockFD.h new file mode 100644 index 0000000..5225465 --- /dev/null +++ b/common/source/common/toolkit/LockFD.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +#include + +class LockFD final +{ + public: + LockFD() = default; + ~LockFD() + { + // unlink and close could also happen in the other order; both effectively release the + // lock file for use by others. only unlink if the fd is valid - the path have been reused. + if (fd.valid()) + unlink(path.c_str()); + } + + LockFD(const LockFD&) = delete; + LockFD& operator=(const LockFD&) = delete; + + LockFD(LockFD&& other) + { + swap(other); + } + + LockFD& operator=(LockFD&& other) + { + LockFD(std::move(other)).swap(*this); + return *this; + } + + static nu::error_or lock(std::string path, bool forUpdate); + + std::error_code update(const std::string& newContent); + std::error_code updateWithPID(); + + bool valid() const { return fd.valid(); } + + void swap(LockFD& other) + { + using std::swap; + swap(path, other.path); + swap(fd, other.fd); + } + + friend void swap(LockFD& a, LockFD& b) + { + a.swap(b); + } + + private: + std::string path; + FDHandle fd; + + explicit LockFD(std::string path, FDHandle fd): path(std::move(path)), fd(std::move(fd)) {} +}; + diff --git a/common/source/common/toolkit/MapTk.cpp b/common/source/common/toolkit/MapTk.cpp new file mode 100644 index 0000000..6c2168b --- /dev/null +++ b/common/source/common/toolkit/MapTk.cpp @@ -0,0 +1,97 @@ +#include +#include "MapTk.h" + +#include + +/** + * Splits a line into param and value (divided by the '='-char) and adds it to + * the map. + */ +void MapTk::addLineToStringMap(std::string line, StringMap* outMap) +{ + std::string::size_type divPos = line.find_first_of("=", 0); + + if(divPos == std::string::npos) + { + stringMapRedefine(StringTk::trim(line), "", outMap); + return; + } + + std::string param = line.substr(0, divPos); + std::string value = line.substr(divPos + 1); + + stringMapRedefine(StringTk::trim(param), StringTk::trim(value), outMap); +} + +void MapTk::loadStringMapFromFile(const char* filename, StringMap* outMap) +{ + std::ifstream fis(filename); + if(!fis.is_open() || fis.fail() ) + { + throw InvalidConfigException( + std::string("Failed to load map file: ") + filename); + } + + while(!fis.eof() && !fis.fail() ) + { + std::string line; + + std::getline(fis, line); + std::string trimmedLine = StringTk::trim(line); + if(trimmedLine.length() && (trimmedLine[0] != STORAGETK_FILE_COMMENT_CHAR) ) + addLineToStringMap(trimmedLine, outMap); + } + + fis.close(); +} + +/** + * Note: Saves to a tmp file first and then renames the tmp file. + * Note: Has a special handling for keys starting with STORAGETK_FILE_COMMENT_CHAR. + */ +void MapTk::saveStringMapToFile(const char* filename, StringMap* map) +{ + std::stringstream out; + + for(StringMapCIter iter = map->begin(); (iter != map->end() ) && !out.fail(); iter++) + { + if(!iter->first.empty() && (iter->first[0] != STORAGETK_FILE_COMMENT_CHAR) ) + out << iter->first << "=" << iter->second << std::endl; + else + out << iter->first << std::endl; // only "iter->first" for comment or empty lines + } + + if(out.fail() ) + { + throw InvalidConfigException( + std::string("Failed to build data buffer for ") + filename); + } + + size_t outLen = out.str().length(); + + if(TempFileTk::storeTmpAndMove(filename, out.str().c_str(), outLen) != FhgfsOpsErr_SUCCESS) + { + throw InvalidConfigException( + std::string("Unable to write data to file ") + filename); + } +} + +void MapTk::copyUInt64VectorMap(std::map &inMap, + std::map &outMap) +{ + std::map::iterator mapIter = inMap.begin(); + + for ( ; mapIter != inMap.end(); mapIter++) + { + UInt64Vector* vector = new UInt64Vector(); + + for (UInt64VectorIter vectorIter = mapIter->second->begin(); + vectorIter != mapIter->second->end(); vectorIter++) + { + uint64_t value = *vectorIter; + vector->push_back(value); + } + + outMap.insert(std::pair(mapIter->first ,vector)); + } +} diff --git a/common/source/common/toolkit/MapTk.h b/common/source/common/toolkit/MapTk.h new file mode 100644 index 0000000..4a9cf3f --- /dev/null +++ b/common/source/common/toolkit/MapTk.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + + + +class MapTk +{ + public: + static void addLineToStringMap(std::string line, StringMap* outMap); + static void loadStringMapFromFile(const char* filename, StringMap* outMap); + static void saveStringMapToFile(const char* filename, StringMap* map); + static void copyUInt64VectorMap(std::map &inMap, + std::map &outMap); + + private: + MapTk() {} + + public: + // inliners + static void stringMapRedefine(std::string keyStr, std::string valueStr, StringMap* outMap) + { + outMap->erase(keyStr); + + outMap->insert(StringMapVal(keyStr, valueStr) ); + } + +}; + diff --git a/common/source/common/toolkit/MathTk.h b/common/source/common/toolkit/MathTk.h new file mode 100644 index 0000000..02eb1a9 --- /dev/null +++ b/common/source/common/toolkit/MathTk.h @@ -0,0 +1,98 @@ +#pragma once + +#include + +#include +#include + + +class MathTk +{ + public: + + + private: + MathTk() {}; + + + public: + // inliners + + /** + * Base 2 logarithm. + * + * Note: This is intended to optimize division, e.g. "a=b/c" becomes "a=b>>log2(c)" if c is + * guaranteed to be a 2^n number. + * + * @param value may not be 0 + * @return log2 of value + */ + static unsigned log2Int64(uint64_t value) + { + /* __builtin_clz: Count leading zeros - returns the number of leading 0-bits in x, starting + * at the most significant bit position. If x is 0, the result is undefined. */ + // (note: 8 is bits_per_byte) + unsigned result = (sizeof(value) * 8) - 1 - __builtin_clzll(value); + + return result; + } + + /** + * Base 2 logarithm. + * + * See log2Int64() for details. + */ + static unsigned log2Int32(unsigned value) + { + /* __builtin_clz: Count leading zeros - returns the number of leading 0-bits in x, starting + * at the most significant bit position. If x is 0, the result is undefined. */ + // (note: 8 is bits_per_byte) + unsigned result = (sizeof(value) * 8) - 1 - __builtin_clz(value); + + return result; + } + + /** + * Checks whether there is only a single bit set in value, in which case value is a power of + * two. + * + * @param value may not be 0 (result is undefined in that case). + * @return true if only a single bit is set (=> value is a power of two), false otherwise + */ + static bool isPowerOfTwo(unsigned value) + { + //return ( (x != 0) && !(x & (x - 1) ) ); // this version is compatible with value==0 + + return !(value & (value - 1) ); + } + + /** + * Median of a sorted vector. + * Requires "/" and "+" operators to work on T and (x+y)/2 to produce a useful average value. + * + * @param vect Sorted vector. + * @returns Median of vect, result for empty vector is boost::none. + */ + template // contained type + static boost::optional medianOfSorted(std::vector const& vect) + { + auto const size = vect.size(); + + // Trivial cases: + if(size == 0) + return boost::none; + if(size == 1) + return vect[0]; + + if (size % 2 == 0) + { + return (vect[size / 2 - 1] + vect[size / 2]) / 2; + } + else + { + return vect[size / 2]; + } + } +}; + + diff --git a/common/source/common/toolkit/MessagingTk.cpp b/common/source/common/toolkit/MessagingTk.cpp new file mode 100644 index 0000000..0cbcab6 --- /dev/null +++ b/common/source/common/toolkit/MessagingTk.cpp @@ -0,0 +1,554 @@ +#include +#include +#include +#include +#include +#include +#include +#include "MessagingTk.h" + + +#define MSGBUF_DEFAULT_SIZE (64*1024) // at least big enough to store a datagram +#define MSGBUF_MAX_SIZE (4*1024*1024) // max accepted size + +// we bet that small messages fits into an allocation of 2k. if this size is used for heartbeat +// messages and ack messages, that assumption is very likely to be true. other messages are not +// performance critical, and the overhead of a 2k allocation is small enough to ignore it. +#define MSGBUF_SMALL_SIZE 2048 + +bool MessagingTk::requestResponse(RequestResponseArgs* rrArgs) +{ + FhgfsOpsErr commRes = requestResponseComm(rrArgs); + // One retry in case the connection was already broken when we got it (e.g. peer daemon restart). + if (commRes == FhgfsOpsErr_COMMUNICATION) + { + LOG(COMMUNICATION, WARNING, "Retrying communication.", + ("peer", rrArgs->node->getNodeIDWithTypeStr()), + ("message type", rrArgs->requestMsg->getMsgTypeStr())); + commRes = requestResponseComm(rrArgs); + } + + return commRes == FhgfsOpsErr_SUCCESS; +} + +/** + * Sends a message and receives the response. + * + * @param outRespMsg response message + * @return true if communication succeeded and expected response was received + */ +std::unique_ptr MessagingTk::requestResponse(Node& node, NetMessage& requestMsg, + unsigned respMsgType) +{ + RequestResponseArgs rrArgs(&node, &requestMsg, respMsgType); + + if (requestResponse(&rrArgs)) + return std::move(rrArgs.outRespMsg); + + return nullptr; +} + +/** + * Sends a message to a node and receives a response. + * Can handle target states and mapped mirror IDs. Node does not need to be referenced by caller. + * + * If target states are provided, communication might be skipped for certain states. + * + * note: rrArgs->nodeID may optionally be provided when calling this. + * note: received message and buffer are available through rrArgs in case of success. + */ +FhgfsOpsErr MessagingTk::requestResponseNode(RequestResponseNode* rrNode, + RequestResponseArgs* rrArgs) +{ + const char* logContext = "Messaging (RPC node)"; + + NodeHandle loadedNode; + + // select the right targetID + + NumNodeID nodeID = rrNode->nodeID; // don't modify caller's nodeID + + if(rrNode->mirrorBuddies) + { // given targetID refers to a buddy mirror group + + nodeID = rrNode->useBuddyMirrorSecond ? + NumNodeID(rrNode->mirrorBuddies->getSecondaryTargetID(nodeID.val())) : + NumNodeID(rrNode->mirrorBuddies->getPrimaryTargetID(nodeID.val())); + + if(unlikely(!nodeID) ) + { + LogContext(logContext).logErr( + "Invalid node mirror buddy group ID: " + rrNode->nodeID.str() + "; " + "type: " + boost::lexical_cast(rrArgs->node ? + rrArgs->node->getNodeType() : rrNode->nodeStore->getStoreType())); + + return FhgfsOpsErr_UNKNOWNNODE; + } + } + + // check target state + + if(rrNode->targetStates) + { + CombinedTargetState targetState; + if (!rrNode->targetStates->getState(nodeID.val(), targetState) ) + { + LOG_DEBUG(logContext, Log_DEBUG, + "Node has unknown state: " + nodeID.str() ); + return FhgfsOpsErr_COMMUNICATION; + } + + rrNode->outTargetReachabilityState = targetState.reachabilityState; + rrNode->outTargetConsistencyState = targetState.consistencyState; + + if(unlikely( + (rrNode->outTargetReachabilityState != TargetReachabilityState_ONLINE) || + (rrNode->outTargetConsistencyState != TargetConsistencyState_GOOD) ) ) + { + if(rrNode->outTargetReachabilityState == TargetReachabilityState_OFFLINE) + { + LOG_DEBUG(logContext, Log_SPAM, + "Skipping communication with offline nodeID: " + nodeID.str() +"; " + "type: " + boost::lexical_cast(rrArgs->node ? + rrArgs->node->getNodeType() : rrNode->nodeStore->getStoreType())); + return FhgfsOpsErr_COMMUNICATION; // no need to wait for offline servers + } + + // states other than "good" and "needs resync" are not allowed with mirroring + if(rrNode->mirrorBuddies && + (rrNode->outTargetConsistencyState != TargetConsistencyState_NEEDS_RESYNC) ) + { + LOG_DEBUG(logContext, Log_DEBUG, + "Skipping communication with mirror nodeID: " + nodeID.str() + "; " + "node state: " + + TargetStateStore::stateToStr(rrNode->outTargetConsistencyState) + "; " + "type: " + boost::lexical_cast(rrArgs->node ? + rrArgs->node->getNodeType() : rrNode->nodeStore->getStoreType())); + return FhgfsOpsErr_COMMUNICATION; + } + } + } + + // reference node (if not provided by caller already) + + if(!rrArgs->node) + { + loadedNode = rrNode->nodeStore->referenceNode(nodeID); + if (!loadedNode) + { + LogContext(logContext).log(Log_WARNING, "Unknown nodeID: " + nodeID.str() + "; " + "type: " + boost::lexical_cast(rrNode->nodeStore->getStoreType())); + + return FhgfsOpsErr_UNKNOWNNODE; + } + + rrArgs->node = loadedNode.get(); + } + else + BEEGFS_BUG_ON_DEBUG(rrArgs->node->getNumID() != nodeID, + "Mismatch between given rrArgs->node ID and nodeID"); + + // communicate + + FhgfsOpsErr commRes = requestResponseComm(rrArgs); + // One retry in case the connection was already broken when we got it (e.g. peer daemon restart). + if (commRes == FhgfsOpsErr_COMMUNICATION) + { + LOG(COMMUNICATION, WARNING, "Retrying communication.", + ("peer", rrNode->nodeStore->getNodeIDWithTypeStr(nodeID)), + ("message type", rrArgs->requestMsg->getMsgTypeStr())); + commRes = requestResponseComm(rrArgs); + } + + if (loadedNode) + { + loadedNode.reset(); + rrArgs->node = nullptr; + } + + if (commRes == FhgfsOpsErr_WOULDBLOCK) + return FhgfsOpsErr_COMMUNICATION; + else + return commRes; +} + +/** + * Sends a message to the owner of a target and receives a response. + * Can handle target states and mapped mirror IDs. Owner node of target will be resolved and + * referenced internally. + * + * If target states are provided, communication might be skipped for certain states. + * + * note: msg header targetID is automatically set to the resolved targetID. + * + * @param rrArgs rrArgs->node must be NULL when calling this. + * @return received message and buffer are available through rrArgs in case of success. + */ +FhgfsOpsErr MessagingTk::requestResponseTarget(RequestResponseTarget* rrTarget, + RequestResponseArgs* rrArgs) +{ + const char* logContext = "Messaging (RPC target)"; + + BEEGFS_BUG_ON_DEBUG(rrArgs->node, "rrArgs->node was not NULL and will leak now"); + + // select the right targetID + + uint16_t targetID = rrTarget->targetID; // don't modify caller's targetID + + if(rrTarget->mirrorBuddies) + { // given targetID refers to a buddy mirror group + + targetID = rrTarget->useBuddyMirrorSecond ? + rrTarget->mirrorBuddies->getSecondaryTargetID(targetID) : + rrTarget->mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { + LogContext(logContext).logErr("Invalid target mirror buddy group ID: " + + StringTk::uintToStr(rrTarget->targetID) ); + + return FhgfsOpsErr_UNKNOWNTARGET; + } + } + + rrArgs->requestMsg->setMsgHeaderTargetID(targetID); + + // check target state + + if(rrTarget->targetStates) + { + CombinedTargetState targetState; + if (!rrTarget->targetStates->getState(targetID, targetState) ) + { + // maybe the target was removed, check the mapper as well to be sure + if(!rrTarget->targetMapper->targetExists(targetID) ) + return FhgfsOpsErr_UNKNOWNTARGET; + + LOG_DEBUG(logContext, Log_DEBUG, + "Target has unknown state: " + StringTk::uintToStr(targetID) ); + return FhgfsOpsErr_COMMUNICATION; + } + + rrTarget->outTargetReachabilityState = targetState.reachabilityState; + rrTarget->outTargetConsistencyState = targetState.consistencyState; + + if(unlikely( + (rrTarget->outTargetReachabilityState != TargetReachabilityState_ONLINE) || + (rrTarget->outTargetConsistencyState != TargetConsistencyState_GOOD) ) ) + { + if(rrTarget->outTargetReachabilityState == TargetReachabilityState_OFFLINE) + { + LOG_DEBUG(logContext, Log_SPAM, + "Skipping communication with offline targetID: " + + StringTk::uintToStr(targetID) ); + return FhgfsOpsErr_COMMUNICATION; // no need to wait for offline servers + } + + // states other than "good" and "resyncing" are not allowed with mirroring + if(rrTarget->mirrorBuddies && + (rrTarget->outTargetConsistencyState != TargetConsistencyState_NEEDS_RESYNC) ) + { + LOG_DEBUG(logContext, Log_DEBUG, + "Skipping communication with mirror targetID: " + + StringTk::uintToStr(targetID) + "; " + "target state: " + + TargetStateStore::stateToStr(rrTarget->outTargetConsistencyState) ); + return FhgfsOpsErr_COMMUNICATION; + } + } + } + + // reference node + auto loadedNode = rrTarget->nodeStore->referenceNodeByTargetID( + targetID, rrTarget->targetMapper); + if (!loadedNode) + { + LOG(COMMUNICATION, WARNING, "Unable to resolve storage server", targetID); + return FhgfsOpsErr_UNKNOWNNODE; + } + + rrArgs->node = loadedNode.get(); + + // communicate + + FhgfsOpsErr commRes = requestResponseComm(rrArgs); + // One retry in case the connection was already broken when we got it (e.g. peer daemon restart). + if (commRes == FhgfsOpsErr_COMMUNICATION) + { + LOG(COMMUNICATION, WARNING, "Retrying communication.", + targetID, ("message type", rrArgs->requestMsg->getMsgTypeStr())); + commRes = requestResponseComm(rrArgs); + } + + // we ignore the _AGAIN code returned by requestResponseComm here and return it to the caller. + // there are different reasons for why this is a good idea in deamons and in other userspace + // tools: + // + // daemons are written under the assumption that no network operation can block forever. more + // importantly, the internode syncers are written under the assumption that communication with + // the mgmt instance does not block for very long. this assumption is very important because + // daemons trigger state transitions (eg auto-pofflining) from the internode syncer based on + // whether they can communicate with mgmt or not. if an internode syncer loops on _AGAIN here, + // this handling can be delayed indefinitely. if an internodesyncer loops on _AGAIN because it is + // trying to push an update (eg capacities) to mgmt *while mgmt is shutting down*, the + // auto-pofflining mechanism will not fire until *after* mgmt has shut down completely. this + // defeats the purpose of the poffline timeout wait during mgmt shutdown, which was introduced + // to ensure that all nodes assume that all other nodes are poffline while mgmt is gone. + // + // userspace on the other hand should not assume that any request it sent can be transparently + // retried. this is especially true for changes initiated by the ctl tool since these changes + // are often based on the *current* state of the system, which may well change drastically + // between the first request and the first retry. as such, userspace code that *knows* it can + // retry a request should check for _AGAIN and do the retrying itself, while other parts should + // handle _AGAIN as an error or recompute the entire request. + if (commRes == FhgfsOpsErr_WOULDBLOCK) + return FhgfsOpsErr_COMMUNICATION; + else + return commRes; +} + +std::vector MessagingTk::recvMsgBuf(Socket& socket, int minTimeout) +try +{ + AbstractApp* app = PThread::getCurrentThreadApp(); + int connMsgLongTimeout = app->getCommonConfig()->getConnMsgLongTimeout(); + + DEBUG_ENV_VAR(unsigned, RECEIVE_TIMEOUT, connMsgLongTimeout, "BEEGFS_MESSAGING_RECV_TIMEOUT_MS"); + + const int recvTimeoutMS = minTimeout < 0 + ? -1 + : std::max(minTimeout, RECEIVE_TIMEOUT); + + std::vector result(MSGBUF_DEFAULT_SIZE); + + // receive at least the message header + + unsigned numReceived = socket.recvExactT(&result[0], NETMSG_MIN_LENGTH, 0, recvTimeoutMS); + + unsigned msgLength = NetMessageHeader::extractMsgLengthFromBuf(&result[0], numReceived); + + if (msgLength > MSGBUF_MAX_SIZE) + { + LOG(COMMUNICATION, ERR, "Received a message with invalid length.", + ("from", socket.getPeername())); + + return {}; + } + + result.resize(msgLength); + + // receive the rest of the message + + if (msgLength > numReceived) + socket.recvExactT(&result[numReceived], msgLength-numReceived, 0, recvTimeoutMS); + + return result; +} +catch (const std::bad_alloc&) +{ + return {}; +} + +/** + * Sends a request message to a node and receives the response. + * + * @param rrArgs: + * .node receiver + * .requestMsg the message that should be sent to the receiver + * .respMsgType expected response message type + * .outRespMsg response message if successful (must be deleted by the caller) + * @return FhgfsOpsErr_COMMUNICATION on comm error, FhgfsOpsErr_WOULDBLOCK if remote side + * encountered an indirect comm error and suggests not to try again, FhgfsOpsErr_AGAIN if other + * side is suggesting infinite retries. + */ +FhgfsOpsErr MessagingTk::requestResponseComm(RequestResponseArgs* rrArgs) +{ + const char* logContext = "Messaging (RPC)"; + + const Node& node = *rrArgs->node; + NodeConnPool* connPool = node.getConnPool(); + auto netMessageFactory = PThread::getCurrentThreadApp()->getNetMessageFactory(); + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + // cleanup init + Socket* sock = NULL; + + try + { + // connect + sock = connPool->acquireStreamSocket(); + + const auto sendBuf = createMsgVec(*rrArgs->requestMsg); + sock->send(&sendBuf[0], sendBuf.size(), 0); + + if (rrArgs->sendExtraData) + { + retVal = rrArgs->sendExtraData(sock, rrArgs->extraDataContext); + if (retVal != FhgfsOpsErr_SUCCESS) + goto err_cleanup; + } + + // receive response + auto respBuf = MessagingTk::recvMsgBuf(*sock, rrArgs->minTimeoutMS); + if (respBuf.empty()) + { // error (e.g. message too big) + LogContext(logContext).log(Log_WARNING, + "Failed to receive response from: " + node.getNodeIDWithTypeStr() + "; " + + sock->getPeername() + ". " + + "(Message type: " + rrArgs->requestMsg->getMsgTypeStr() + ")"); + + retVal = FhgfsOpsErr_COMMUNICATION; + goto err_cleanup; + } + + // got response => deserialize it + rrArgs->outRespMsg = netMessageFactory->createFromBuf(std::move(respBuf)); + + if(unlikely(rrArgs->outRespMsg->getMsgType() == NETMSGTYPE_GenericResponse) ) + { // special control msg received + retVal = handleGenericResponse(rrArgs); + if(retVal != FhgfsOpsErr_INTERNAL) + { // we can re-use the connection + connPool->releaseStreamSocket(sock); + sock = NULL; + } + + goto err_cleanup; + } + + if(unlikely(rrArgs->outRespMsg->getMsgType() != rrArgs->respMsgType) ) + { // response invalid (wrong msgType) + LogContext(logContext).logErr( + "Received invalid response type: " + rrArgs->outRespMsg->getMsgTypeStr() + "; " + "expected: " + netMessageTypeToStr(rrArgs->respMsgType) + ". " + "Disconnecting: " + node.getNodeIDWithTypeStr() + " @ " + + sock->getPeername() ); + + retVal = FhgfsOpsErr_COMMUNICATION; + goto err_cleanup; + } + + // got correct response + + connPool->releaseStreamSocket(sock); + + return FhgfsOpsErr_SUCCESS; + } + catch (const std::bad_alloc& e) + { + LOG(COMMUNICATION, ERR, "Memory allocation for send buffer failed."); + retVal = FhgfsOpsErr_OUTOFMEM; + } + catch(SocketConnectException& e) + { + if ( !(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED) ) + { + LOG(GENERAL, WARNING, "Unable to connect, is the node offline?", ("node", node.getNodeIDWithTypeStr()), + ("Message type", rrArgs->requestMsg->getMsgTypeStr())); + } + + retVal = FhgfsOpsErr_COMMUNICATION; + } + catch(SocketException& e) + { + LogContext(logContext).logErr("Communication error: " + std::string(e.what() ) + "; " + + "Peer: " + node.getNodeIDWithTypeStr() + ". " + "(Message type: " + rrArgs->requestMsg->getMsgTypeStr() + ")"); + + retVal = FhgfsOpsErr_COMMUNICATION; + } + + +err_cleanup: + + // clean up... + + if(sock) + connPool->invalidateStreamSocket(sock); + + return retVal; +} + +std::vector MessagingTk::createMsgVec(NetMessage& msg) +{ + std::vector result(MSGBUF_SMALL_SIZE); + + auto serializeRes = msg.serializeMessage(&result[0], result.size()); + + if (!serializeRes.first) + { + result.resize(serializeRes.second); + serializeRes = msg.serializeMessage(&result[0], result.size()); + } + + result.resize(serializeRes.second); + + return result; +} + +/** + * Print log message and determine appropriate return code for requestResponseComm. + * + * If FhgfsOpsErr_INTERNAL is returned, the connection should be invalidated. + * + * @return FhgfsOpsErr_COMMUNICATION on indirect comm error (retry suggested), + * FhgfsOpsErr_WOULDBLOCK if remote side encountered an indirect comm error and suggests not to + * try again, FhgfsOpsErr_AGAIN if other side is suggesting infinite retries. + */ +FhgfsOpsErr MessagingTk::handleGenericResponse(RequestResponseArgs* rrArgs) +{ + const char* logContext = "Messaging (RPC)"; + + FhgfsOpsErr retVal; + + const auto genericResp = (const GenericResponseMsg*)rrArgs->outRespMsg.get(); + + switch(genericResp->getControlCode() ) + { + case GenericRespMsgCode_TRYAGAIN: + { + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN) ) + { + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN; + + LOG(COMMUNICATION, DEBUG, "Peer is asking for a retry. Returning AGAIN to caller.", + ("peer", rrArgs->node->getNodeIDWithTypeStr()), + ("reason", genericResp->getLogStr()), + ("message type", rrArgs->requestMsg->getMsgTypeStr())); + } + + retVal = FhgfsOpsErr_AGAIN; + } break; + + case GenericRespMsgCode_INDIRECTCOMMERR: + { + if(!(rrArgs->logFlags & REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM) ) + { + rrArgs->logFlags |= REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM; + + LogContext(logContext).log(Log_NOTICE, + "Peer reported indirect communication error: " + + rrArgs->node->getNodeIDWithTypeStr() + "; " + "Details: " + genericResp->getLogStr() + ". " + "(Message type: " + rrArgs->requestMsg->getMsgTypeStr() + ")"); + } + + retVal = FhgfsOpsErr_COMMUNICATION; + } break; + + default: + { + LogContext(logContext).log(Log_NOTICE, + "Peer replied with unknown control code: " + + rrArgs->node->getNodeIDWithTypeStr() + "; " + "Code: " + StringTk::uintToStr(genericResp->getControlCode() ) + "; " + "Details: " + genericResp->getLogStr() + ". " + "(Message type: " + rrArgs->requestMsg->getMsgTypeStr() + ")"); + retVal = FhgfsOpsErr_INTERNAL; + } break; + } + + + return retVal; +} diff --git a/common/source/common/toolkit/MessagingTk.h b/common/source/common/toolkit/MessagingTk.h new file mode 100644 index 0000000..26b6bd3 --- /dev/null +++ b/common/source/common/toolkit/MessagingTk.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define MESSAGINGTK_INFINITE_RETRY_WAIT_MS (5000) // how long to wait if peer asks for retry + + +class MessagingTk +{ + public: + static bool requestResponse(RequestResponseArgs* rrArgs); + static std::unique_ptr requestResponse(Node& node, NetMessage& requestMsg, + unsigned respMsgType); + static FhgfsOpsErr requestResponseNode(RequestResponseNode* rrNode, + RequestResponseArgs* rrArgs); + static FhgfsOpsErr requestResponseTarget(RequestResponseTarget* rrTarget, + RequestResponseArgs* rrArgs); + + static std::vector recvMsgBuf(Socket& socket, int minTimeout = 0); + static std::vector createMsgVec(NetMessage& msg); + + private: + MessagingTk() {} + + static FhgfsOpsErr requestResponseComm(RequestResponseArgs* rrArgs); + static FhgfsOpsErr handleGenericResponse(RequestResponseArgs* rrArgs); +}; + diff --git a/common/source/common/toolkit/MessagingTkArgs.h b/common/source/common/toolkit/MessagingTkArgs.h new file mode 100644 index 0000000..f3c7d6a --- /dev/null +++ b/common/source/common/toolkit/MessagingTkArgs.h @@ -0,0 +1,164 @@ +#pragma once + +#include + +#include +#include +#include + + +#define REQUESTRESPONSEARGS_LOGFLAG_PEERTRYAGAIN 1 // peer asked for retry +#define REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM 2 /* peer encountered indirect comm + error, suggests retry */ +#define REQUESTRESPONSEARGS_LOGFLAG_PEERINDIRECTCOMMM_NOTAGAIN 4 /* peer encountered indirect comm + error, suggests no retry */ +#define REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED 8 /* unable to establish conn. */ +#define REQUESTRESPONSEARGS_LOGFLAG_RETRY 16 /* communication retry */ + + +/** + * This class contains arguments for requestResponse() with a certain target. + */ +struct RequestResponseTarget +{ + /** + * Constructor without targetStates and without mirror handling. + */ + RequestResponseTarget(uint16_t targetID, TargetMapper* targetMapper, + NodeStoreServers* nodeStore) : + targetID(targetID), targetMapper(targetMapper), nodeStore(nodeStore), + targetStates(NULL), mirrorBuddies(NULL), useBuddyMirrorSecond(false) + { + // see initializer list + } + + /** + * Constructor with targetStates and with mirror handling. + */ + RequestResponseTarget(uint16_t targetID, TargetMapper* targetMapper, + NodeStoreServers* nodeStore, TargetStateStore* targetStates, + MirrorBuddyGroupMapper* mirrorBuddies, bool useBuddyMirrorSecond) : + targetID(targetID), targetMapper(targetMapper), nodeStore(nodeStore), + targetStates(targetStates), mirrorBuddies(mirrorBuddies), + useBuddyMirrorSecond(useBuddyMirrorSecond) + { + // see initializer list + } + + void setTargetStates(TargetStateStore* targetStates) + { + this->targetStates = targetStates; + } + + void setMirrorInfo(MirrorBuddyGroupMapper* mirrorBuddies, bool useBuddyMirrorSecond) + { + this->mirrorBuddies = mirrorBuddies; + this->useBuddyMirrorSecond = useBuddyMirrorSecond; + } + + + uint16_t targetID; // receiver + TargetMapper* targetMapper; // for targetID -> nodeID lookup + NodeStoreServers* nodeStore; // for nodeID lookup + + TargetStateStore* targetStates; /* if !NULL, check for state "good" or fail immediately if + offline (other states handling depend on mirrorBuddies) */ + TargetReachabilityState outTargetReachabilityState; /* set to state of target by + requestResponseX() if targetStates were given and + FhgfsOpsErr_COMMUNICATION is returned */ + TargetConsistencyState outTargetConsistencyState; + + MirrorBuddyGroupMapper* mirrorBuddies; // if !NULL, the given targetID is a mirror group ID + bool useBuddyMirrorSecond; // true to use secondary instead of primary +}; + +/** + * This class contains arguments for requestResponse() with a certain node. + */ +struct RequestResponseNode +{ + /** + * Constructor without targetStates and without mirror handling. + */ + RequestResponseNode(NumNodeID nodeID, NodeStoreServers* nodeStore) : + nodeID(nodeID), nodeStore(nodeStore), targetStates(NULL), mirrorBuddies(NULL), + useBuddyMirrorSecond(false) + { + // see initializer list + } + + /** + * Constructor with targetStates and with mirror handling. + */ + RequestResponseNode(NumNodeID nodeID, NodeStoreServers* nodeStore, + TargetStateStore* targetStates, MirrorBuddyGroupMapper* mirrorBuddies, + bool useBuddyMirrorSecond) : + nodeID(nodeID), nodeStore(nodeStore), targetStates(targetStates), + mirrorBuddies(mirrorBuddies), useBuddyMirrorSecond(useBuddyMirrorSecond) + { + // see initializer list + } + + void setTargetStates(TargetStateStore* targetStates) + { + this->targetStates = targetStates; + } + + void setMirrorInfo(MirrorBuddyGroupMapper* mirrorBuddies, bool useBuddyMirrorSecond) + { + this->mirrorBuddies = mirrorBuddies; + this->useBuddyMirrorSecond = useBuddyMirrorSecond; + } + + NumNodeID nodeID; // receiver + NodeStoreServers* nodeStore; // for nodeID lookup + + TargetStateStore* targetStates; /* if !NULL, check for state "good" or fail immediately if + offline (other states handling depend on mirrorBuddies) */ + TargetReachabilityState outTargetReachabilityState; /* set to state of node by requestResponseX() + if targetStates were given and FhgfsOpsErr_COMMUNICATION is + returned */ + TargetConsistencyState outTargetConsistencyState; + + MirrorBuddyGroupMapper* mirrorBuddies; // if !NULL, the given targetID is a mirror group ID + bool useBuddyMirrorSecond; // true to use secondary instead of primary +}; + +/** + * Arguments for request-response communication. + * + * Note: Destructor will free response msg and response buf, if they are not NULL. + */ +struct RequestResponseArgs +{ + /** + * @param node may be NULL, depending on which requestResponse...() method is used. + * @param respMsgType expected type of response message (NETMSGTYPE_...) + */ + RequestResponseArgs(const Node* node, NetMessage* requestMsg, unsigned respMsgType, + FhgfsOpsErr (*sendExtraData)(Socket*, void*) = NULL, void* extraDataContext = NULL) + : node(node), requestMsg(requestMsg), respMsgType(respMsgType), + logFlags(0), minTimeoutMS(0), sendExtraData(sendExtraData), + extraDataContext(extraDataContext) + { + // see initializer list + } + + const Node* node; // receiver + + NetMessage* requestMsg; + unsigned respMsgType; // expected type of response message (NETMSGTYPE_...) + + std::unique_ptr outRespMsg; // received response message + + // internal (initialized by MessagingTk_requestResponseWithRRArgs() ) + unsigned char logFlags; // REQUESTRESPONSEARGS_LOGFLAG_... combination to avoid double-logging + + int minTimeoutMS; // minimum communication timeout. negative values disable timeouts. + + // hook to send extra data after the message + FhgfsOpsErr (*sendExtraData)(Socket*, void*); + void* extraDataContext; +}; + + diff --git a/common/source/common/toolkit/MetaStorageTk.h b/common/source/common/toolkit/MetaStorageTk.h new file mode 100644 index 0000000..56fa290 --- /dev/null +++ b/common/source/common/toolkit/MetaStorageTk.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + + +/* + * Some methods from Meta's StorageTkEx defined here, so that they are also available for + * fhgfs-ctl and other tools. + */ +class MetaStorageTk +{ + public: + + // inliners + + /** + * Get complete path to a (non-inlined) inode. + * + * @param inodePath path to inodes subdir of general storage directory + * @param fileName entryID of the inode + */ + static std::string getMetaInodePath(const std::string inodePath, const std::string fileName) + { + return StorageTk::getHashPath(inodePath, fileName, + META_INODES_LEVEL1_SUBDIR_NUM, META_INODES_LEVEL2_SUBDIR_NUM); + } + + /** + * Get directory for a (non-inlined) inode. + * + * @param inodePath path to inodes subdir of general storage directory + * @param fileName entryID of the inode + */ + static std::pair getMetaInodeHash(const std::string& fileName) + { + return StorageTk::getHash(fileName, + META_INODES_LEVEL1_SUBDIR_NUM, META_INODES_LEVEL2_SUBDIR_NUM); + } + + /** + * Get path to a contents directory (i.e. the dir containing the dentries by name). + * + * @param dirEntryID entryID of the directory for which the caller wants the contents path + */ + static std::string getMetaDirEntryPath(const std::string dentriesPath, + const std::string dirEntryID) + { + return StorageTk::getHashPath(dentriesPath, dirEntryID, + META_DENTRIES_LEVEL1_SUBDIR_NUM, META_DENTRIES_LEVEL2_SUBDIR_NUM); + } + + /** + * Get path to the IDs subdir of a contents directory (i.e. the dir containing the dentries + * by ID). + * + * @param metaDirEntryPath path to a contents dir (i.e. the dir containing dentries by name), + * typically requires calling getMetaDirEntryPath() first + */ + static std::string getMetaDirEntryIDPath(const std::string metaDirEntryPath) + { + return metaDirEntryPath + "/" META_DIRENTRYID_SUB_STR "/"; + } +}; + + diff --git a/common/source/common/toolkit/MetadataTk.cpp b/common/source/common/toolkit/MetadataTk.cpp new file mode 100644 index 0000000..b5f52f9 --- /dev/null +++ b/common/source/common/toolkit/MetadataTk.cpp @@ -0,0 +1,203 @@ +#include +#include +#include +#include +#include +#include +#include "MetadataTk.h" + +static std::shared_ptr referenceRoot(NodeStoreServers& nodes, const RootInfo& root, + MirrorBuddyGroupMapper& bgm) +{ + const auto id = root.getID(); + const auto isMirrored = root.getIsMirrored(); + if (!isMirrored) + return nodes.referenceNode(id); + + return nodes.referenceNode(NumNodeID(bgm.getPrimaryTargetID(id.val()))); +} + +/** + * Find and reference the metadata owner node of a certain entry. + * + * @param nodes metadata nodes + * @param outReferencedNode only valid if result is FhgfsOpsErr_SUCCESS (needs to be released + * through the MetaNodesStore) + */ +FhgfsOpsErr MetadataTk::referenceOwner(Path* searchPath, + NodeStoreServers* nodes, NodeHandle& outReferencedNode, EntryInfo* outEntryInfo, + const RootInfo& metaRoot, MirrorBuddyGroupMapper* metaBuddyGroupMapper) +{ + const char* logContext = "MetadataTk::referenceOwner"; + + size_t searchDepth = searchPath->size(); // 0-based incl. root + + if(!searchDepth) + { // looking for the root node (ignore referenceParent) + outReferencedNode = referenceRoot(*nodes, metaRoot, *metaBuddyGroupMapper); + if (likely(outReferencedNode)) + { // got root node + if (metaRoot.getIsMirrored()) + outEntryInfo->set(metaRoot.getID(), "", META_ROOTDIR_ID_STR, "", + DirEntryType_DIRECTORY, ENTRYINFO_FEATURE_BUDDYMIRRORED); + else + outEntryInfo->set(outReferencedNode->getNumID(), "", META_ROOTDIR_ID_STR, "", + DirEntryType_DIRECTORY, 0); + + return FhgfsOpsErr_SUCCESS; + } + else + { // no root node (yet) + LogContext(logContext).logErr("Unable to proceed without a working root metadata server"); + return FhgfsOpsErr_UNKNOWNNODE; + } + } + + FhgfsOpsErr findRes = findOwner(searchPath, searchDepth, nodes, outEntryInfo, metaRoot, + metaBuddyGroupMapper); + + if(findRes == FhgfsOpsErr_SUCCESS) + { // simple success case => try to reference the node + // in case of mirrored entry, the primary node will be referenced + NumNodeID referenceNodeID; + if ( outEntryInfo->getIsBuddyMirrored() ) + referenceNodeID = NumNodeID( + metaBuddyGroupMapper->getPrimaryTargetID(outEntryInfo->getOwnerNodeID().val() ) ); + else + referenceNodeID = outEntryInfo->getOwnerNodeID(); + + outReferencedNode = nodes->referenceNode(referenceNodeID); + if (unlikely(!outReferencedNode)) + { + LogContext(logContext).logErr("Unknown metadata node: " + referenceNodeID.str()); + + findRes = FhgfsOpsErr_UNKNOWNNODE; + } + } + + return findRes; +} + +/** + * Peforms a single node request as part of the incremental metadata owner node search. + * + * @param outInfo is only valid if return is FhgfsOpsErr_SUCCESS (outInfo strings are kalloced + * and need to be kfreed by the caller) + */ +inline FhgfsOpsErr MetadataTk::findOwnerStep(Node& node, + NetMessage* requestMsg, EntryInfoWithDepth* outEntryInfoWDepth) +{ + FindOwnerRespMsg* findResp; + FhgfsOpsErr findRes; + + const auto respMsg = MessagingTk::requestResponse(node, *requestMsg, NETMSGTYPE_FindOwnerResp); + if (!respMsg) + { // communication error + return FhgfsOpsErr_INTERNAL; + } + + // handle result + findResp = (FindOwnerRespMsg*)respMsg.get(); + findRes = (FhgfsOpsErr)findResp->getResult(); + + if(findRes == FhgfsOpsErr_SUCCESS) + *outEntryInfoWDepth = findResp->getEntryInfo(); + + return findRes; +} + +/** + * Incremental search for a metadata owner node. + * + * @param nodes metadata nodes + * @param outOwner is only valid if return is FhgfsOpsErr_SUCCESS (and needs to be kfreed by the + * caller) + */ +FhgfsOpsErr MetadataTk::findOwner(Path* searchPath, unsigned searchDepth, NodeStoreServers* nodes, + EntryInfo* outEntryInfo, const RootInfo& metaRoot, MirrorBuddyGroupMapper* metaBuddyGroupMapper) +{ + const char* logContextStr = "MetadataTk::findOwner"; + + // reference root node + auto currentNode = referenceRoot(*nodes, metaRoot, *metaBuddyGroupMapper); + if (unlikely(!currentNode)) + { + LogContext(logContextStr).logErr("Unable to proceed without a working root metadata server"); + return FhgfsOpsErr_UNKNOWNNODE; + } + + EntryInfo lastEntryInfo(currentNode->getNumID(), "", META_ROOTDIR_ID_STR, + searchPath->back(), DirEntryType_DIRECTORY, 0); + + unsigned lastEntryDepth = 0; + unsigned numSearchSteps = 0; + FhgfsOpsErr findRes; + + // walk over all the path nodes (taking shortcuts when possible) + // (note: do not exit the loop via the for-line, because a node is referenced at that point + // and it is unclear whether the outInfo strings need to be kfreed) + for( ; ; numSearchSteps++) + { + LOG_DEBUG(logContextStr, Log_SPAM, "path: " + searchPath->str() + "; " + "searchDepth: " + StringTk::uintToStr(searchDepth) + "; " + "lastEntryID: " + lastEntryInfo.getEntryID() + "; " + "currentDepth: " + StringTk::uintToStr(lastEntryDepth) + "; "); + + FindOwnerMsg requestMsg(searchPath, searchDepth, &lastEntryInfo, lastEntryDepth); + + EntryInfoWithDepth entryInfoWDepth; + findRes = findOwnerStep(*currentNode, &requestMsg, &entryInfoWDepth); + + if(findRes != FhgfsOpsErr_SUCCESS) + { // unsuccessful end of search + goto global_cleanup; + } + + if(entryInfoWDepth.getEntryDepth() == searchDepth) + { // successful end of search + outEntryInfo->set(&entryInfoWDepth); + goto global_cleanup; + } + + if(unlikely(entryInfoWDepth.getEntryDepth() <= lastEntryDepth) ) + { // something went wrong (probably because of concurrent access) + findRes = FhgfsOpsErr_INTERNAL; + goto entryinfo_cleanup; + } + + if(unlikely(numSearchSteps > METADATATK_OWNERSEARCH_MAX_STEPS) ) + { // search is taking too long - are we trapped in a circle? => better cancel + LogContext(logContextStr).logErr("Max search steps exceeded for path: " + + searchPath->str()); + findRes = FhgfsOpsErr_INTERNAL; + goto entryinfo_cleanup; + } + + // search not finished yet => prepare for next round (proceed with next node) + if ( entryInfoWDepth.getIsBuddyMirrored() ) + currentNode = nodes->referenceNode(NumNodeID( + metaBuddyGroupMapper->getPrimaryTargetID(entryInfoWDepth.getOwnerNodeID().val() ) ) ); + else + currentNode = nodes->referenceNode(entryInfoWDepth.getOwnerNodeID()); + + if(!currentNode) + { // search cannot continue because the next node is unknown + LogContext(logContextStr).logErr("Unable to retrieve metadata because of unknown node: " + + entryInfoWDepth.getOwnerNodeID().str() ); + findRes = FhgfsOpsErr_UNKNOWNNODE; + goto entryinfo_cleanup; + } + + lastEntryInfo = entryInfoWDepth; // NOLINT(cppcoreguidelines-slicing) + lastEntryDepth = entryInfoWDepth.getEntryDepth(); + } + + // clean-up +entryinfo_cleanup: + // nothing to be done here in the c++ version + +global_cleanup: + // nothing to be done here in the c++ version + + return findRes; +} diff --git a/common/source/common/toolkit/MetadataTk.h b/common/source/common/toolkit/MetadataTk.h new file mode 100644 index 0000000..9a25e7b --- /dev/null +++ b/common/source/common/toolkit/MetadataTk.h @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#define METADATATK_OWNERSEARCH_MAX_STEPS 128 + +/* + * NOTE: is serialized as uint8_t everywhere + */ +enum ModificationEventType +{ + ModificationEvent_FILECREATED = 0, ModificationEvent_FILEREMOVED = 1, + ModificationEvent_FILEMOVED = 2, ModificationEvent_DIRCREATED = 3, + ModificationEvent_DIRREMOVED = 4, ModificationEvent_DIRMOVED = 5 +}; + +class MetadataTk +{ + public: + static FhgfsOpsErr referenceOwner(Path* searchPath, + NodeStoreServers* nodes, NodeHandle& outReferencedNode, EntryInfo* outEntryInfo, + const RootInfo& metaRoot, MirrorBuddyGroupMapper* metaBuddyGroupMapper); + + private: + MetadataTk() {} + + static FhgfsOpsErr findOwnerStep(Node& node, + NetMessage* requestMsg, EntryInfoWithDepth* outEntryInfoWDepth); + + static FhgfsOpsErr findOwner(Path* searchPath, unsigned searchDepth, NodeStoreServers* nodes, + EntryInfo* outEntryInfo, const RootInfo& metaRoot, + MirrorBuddyGroupMapper* metaBuddyGroupMapper); + + public: + // inliners + static DirEntryType posixFileTypeToDirEntryType(unsigned posixFileMode) + { + if(S_ISDIR(posixFileMode) ) + return DirEntryType_DIRECTORY; + + if(S_ISREG(posixFileMode) ) + return DirEntryType_REGULARFILE; + + if(S_ISLNK(posixFileMode) ) + return DirEntryType_SYMLINK; + + if(S_ISBLK(posixFileMode) ) + return DirEntryType_BLOCKDEV; + + if(S_ISCHR(posixFileMode) ) + return DirEntryType_CHARDEV; + + if(S_ISFIFO(posixFileMode) ) + return DirEntryType_FIFO; + + if(S_ISSOCK(posixFileMode) ) + return DirEntryType_SOCKET; + + return DirEntryType_INVALID; + } +}; + diff --git a/common/source/common/toolkit/MinMaxStore.h b/common/source/common/toolkit/MinMaxStore.h new file mode 100644 index 0000000..11ff180 --- /dev/null +++ b/common/source/common/toolkit/MinMaxStore.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +/** + * Class to determine the minimum and maximum value of a set. + */ +template +class MinMaxStore { + public: + /** + * Default constructor: Sets minimum to std::numric_limits::max() and maximum to + * std::numeric_limits::min(). + */ + MinMaxStore() : min(std::numeric_limits::max() ), max(std::numeric_limits::min() ) + { } + + /** + * Constructor to initizlize the minimum and maximum to the same value. + */ + MinMaxStore(T value) : min(value), max(value) + { } + + /** + * Constructor to initialize the minimum and maximum to two different values. + * Note: max has to be greater or equali to min. + */ + MinMaxStore(T min, T max) : min(min), max(max) + { } + + + private: + T min; + T max; + + + public: + /** + * Returns the minimum of all numbers entered so far. + */ + T getMin() const + { + return this->min; + } + + /** + * Returns the maximum of all numbers entered so far. + */ + T getMax() const + { + return this->max; + } + + /** + * Enters a new number and updates internal minimum and maximum variables. + */ + void enter(const T value) + { + this->min = BEEGFS_MIN(this->min, value); + this->max = BEEGFS_MAX(this->max, value); + } + +}; + diff --git a/common/source/common/toolkit/NamedException.h b/common/source/common/toolkit/NamedException.h new file mode 100644 index 0000000..f1a82a3 --- /dev/null +++ b/common/source/common/toolkit/NamedException.h @@ -0,0 +1,67 @@ +#pragma once + +#include + +#define DECLARE_NAMEDSUBEXCEPTION(exceptionClass, exceptionName, superException) \ + class exceptionClass : public superException \ + { \ + public: \ + exceptionClass (const char* message) : \ + superException( exceptionName, message) \ + {} \ + \ + exceptionClass (const std::string message) : \ + superException( exceptionName, message) \ + {} \ + \ + virtual ~exceptionClass() throw() \ + {} \ + \ + protected: \ + exceptionClass (const char* subExceptionName, const char* message) : \ + superException ( subExceptionName, message) \ + {} \ + \ + exceptionClass (const char* subExceptionName, const std::string message) : \ + superException ( subExceptionName, message) \ + {} \ + }; + +#define DECLARE_NAMEDEXCEPTION(exceptionClass, exceptionName) \ + DECLARE_NAMEDSUBEXCEPTION(exceptionClass, exceptionName, NamedException) + +class NamedException : public std::exception +{ + protected: + NamedException(const char* exceptionName, const char* message) : + name(exceptionName), msg(message) + { + whatMsg = msg; + } + + NamedException(const char* exceptionName, const std::string message) : + name(exceptionName), msg(message) + { + whatMsg = msg; + } + + public: + virtual ~NamedException() throw() + { + } + + virtual const char* what() const throw() + { + return whatMsg.c_str(); + } + + private: + std::string name; + std::string msg; + + std::string whatMsg; /* required because the final string must not be locally + defined in the what()-method. (it would be removed from the stack as soon as the method + ends in that case) */ + +}; + diff --git a/common/source/common/toolkit/NetFilter.h b/common/source/common/toolkit/NetFilter.h new file mode 100644 index 0000000..01f5455 --- /dev/null +++ b/common/source/common/toolkit/NetFilter.h @@ -0,0 +1,137 @@ +#pragma once + +#include +#include +#include +#include + +class NetFilter +{ + private: + struct NetFilterEntry + { + in_addr_t netAddressShifted; // shifted by number of significant bits + // Note: this address is in host(!) byte order to enable correct shift operator usage + int shiftBitsNum; // for right shift + }; + + + public: + /** + * @param filename path to filter file + */ + NetFilter(std::string filename) + { + prepareArray(filename); + } + + private: + boost::scoped_array filterArray; + size_t filterArrayLen; + + // inliners + void prepareArray(std::string filename) + { + this->filterArray.reset(); + this->filterArrayLen = 0; + + if(filename.empty() ) + return; // no file specified => no filter entries + + + StringList filterList; + + ICommonConfig::loadStringListFile(filename.c_str(), filterList); + + if(!filterList.empty() ) + { // file did contain some lines + + this->filterArrayLen = filterList.size(); + this->filterArray.reset(new NetFilterEntry[filterArrayLen]); + + StringListIter iter = filterList.begin(); + size_t i = 0; + + for( ; iter != filterList.end(); iter++) + { + StringList entryParts; + + StringTk::explode(*iter, '/', &entryParts); + + if(entryParts.size() != 2) + { // ignore this faulty line (and reduce filter length) + this->filterArrayLen--; + continue; + } + + // note: we store addresses in host byte order to enable correct shift operator usage + StringListIter entryPartsIter = entryParts.begin(); + in_addr_t entryAddr = + ntohl(inet_addr(entryPartsIter->c_str() ) ); + + entryPartsIter++; + unsigned significantBitsNum = StringTk::strToUInt(*entryPartsIter); + + (this->filterArray)[i].shiftBitsNum = 32 - significantBitsNum; + (this->filterArray)[i].netAddressShifted = + entryAddr >> ( (this->filterArray)[i].shiftBitsNum); + + i++; // must be increased here because of the faulty line handling + } + + } + + } + + + public: + // inliners + + /** + * Note: Will always allow the loopback address. + */ + bool isAllowed(in_addr_t ipAddr) const + { + if(!this->filterArrayLen) + return true; + + return isContained(ipAddr); + } + + /** + * Note: Always implicitly contains the loopback address. + */ + bool isContained(in_addr_t ipAddr) const + { + const in_addr_t ipHostOrder = ntohl(ipAddr); + + if(ipHostOrder == INADDR_LOOPBACK) // (inaddr_loopback is in host byte order) + return true; + + for(size_t i = 0; i < this->filterArrayLen; i++) + { + if (filterArray[i].shiftBitsNum == 32) + return true; + + // note: stored addresses are in host byte order to enable correct shift operator usage + const in_addr_t ipHostOrderShifted = + ipHostOrder >> ( (this->filterArray)[i].shiftBitsNum); + + if( (this->filterArray)[i].netAddressShifted == ipHostOrderShifted) + { // address match + return true; + } + } + + // no match found + return false; + } + + + // setters & getters + size_t getNumFilterEntries() const + { + return filterArrayLen; + } +}; + diff --git a/common/source/common/toolkit/NodesTk.cpp b/common/source/common/toolkit/NodesTk.cpp new file mode 100644 index 0000000..9519016 --- /dev/null +++ b/common/source/common/toolkit/NodesTk.cpp @@ -0,0 +1,436 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "NodesTk.h" + +/** + * Wait for the first node to appear in the management nodes store. + * + * @param currentThread the calling thread (used to catch self-terminate order, may be NULL) + * @param hostname hostname of mgmtd service + * @param portUDP udp port of mgmtd service + * @param timeoutMS 0 for infinite + * @param nameResolutionRetries number of times to try resolving the hostname of the + * management node. 0 for infinite. + * @return true if heartbeat received, false if cancelled because of termination order + */ +bool NodesTk::waitForMgmtHeartbeat(PThread* currentThread, AbstractDatagramListener* dgramLis, + const NodeStoreServers* mgmtNodes, std::string hostname, unsigned short portUDP, + unsigned timeoutMS, unsigned nameResolutionRetries) +{ + bool gotMgmtHeartbeat = false; + + const int waitForNodeSleepMS = 750; + Time startTime; + Time lastRetryTime; + unsigned nextRetryDelayMS = 0; + + HeartbeatRequestMsg msg; + auto msgBuf = MessagingTk::createMsgVec(msg); + + + // wait for mgmt node to appear and periodically resend request + while(!currentThread || !currentThread->getSelfTerminate() ) + { + if(lastRetryTime.elapsedMS() >= nextRetryDelayMS) + { // time to send request again + const auto ipAddr = SocketTk::getHostByName(hostname.c_str()); + if(ipAddr) + { + dgramLis->sendto(&msgBuf[0], msgBuf.size(), 0, ipAddr.value(), portUDP); + } + else + { + LOG(COMMUNICATION, ERR, "Failed to resolve hostname.", hostname, + ("System error", ipAddr.error())); + if (nameResolutionRetries != 0 && --nameResolutionRetries == 0) + break; + } + + lastRetryTime.setToNow(); + nextRetryDelayMS = getRetryDelayMS(startTime.elapsedMS() ); + } + + /* note: we can't sleep in waitForFirstNode() for too long, because we need to check + getSelfTerminate() every now and then */ + + gotMgmtHeartbeat = mgmtNodes->waitForFirstNode(waitForNodeSleepMS); + if(gotMgmtHeartbeat) + break; // got management node in store + + if(timeoutMS && (startTime.elapsedMS() >= timeoutMS) ) + break; // caller-given timeout expired + } + + return gotMgmtHeartbeat; +} + + +/** + * @param hostname hostname of node + * @param port tcp port of node + * @param timeoutMS -1 for infinite + */ +std::shared_ptr NodesTk::downloadNodeInfo(const std::string& hostname, + unsigned short port, uint64_t connAuthHash, AbstractNetMessageFactory* netMessageFactory, + NodeType nodeType, int timeoutMS) +{ + const Time started; + + while (timeoutMS < 0 || started.elapsedMS() < (unsigned) timeoutMS) + { + try + { + StandardSocket socket(AF_INET, SOCK_STREAM); + + socket.connect(hostname.c_str(), port); + + if (connAuthHash) + { + AuthenticateChannelMsg msg(connAuthHash); + auto msgBuf = MessagingTk::createMsgVec(msg); + socket.send(&msgBuf[0], msgBuf.size(), 0); + } + + { + HeartbeatRequestMsg msg; + auto msgBuf = MessagingTk::createMsgVec(msg); + socket.send(&msgBuf[0], msgBuf.size(), 0); + } + + // wait for a while - if the connection is not reset, we are very likely to receive a reply + auto respBuf = MessagingTk::recvMsgBuf(socket, std::min(1000, timeoutMS)); + + if (respBuf.empty()) + continue; + + auto respMsg = netMessageFactory->createFromBuf(std::move(respBuf)); + if (respMsg->getMsgType() != NETMSGTYPE_Heartbeat) + return nullptr; + + auto& hb = static_cast(*respMsg.get()); + + if (hb.getNodeType() != nodeType) + return nullptr; + + return std::make_shared(hb.getNodeType(), hb.getNodeID(), + hb.getNodeNumID(), hb.getPortUDP(), + hb.getPortTCP(), hb.getNicList()); + } + catch (const SocketException& e) + { + // ignore error, try again until timeout elapses. sleep for a bit to avoid busy-looping + // when connect returns immediatly. + ::usleep(100 * 1000); + } + } + + return nullptr; +} + +/** + * Downloads node list from given sourceNode. + * + * Note: If you intend to connect to these nodes, you probably want to call + * NodesTk::applyLocalNicListToList() on the result list before connecting. + * + * @param sourceNode the node from which node you want to download + * @param nodeType which type of node list you want to download + * @param outNodeList caller is responsible for the deletion of the received nodes + * @param outRootNumID numeric ID of root mds, may be NULL if caller is not interested + * @param outRootIsBuddyMirrored is root directory on mds buddy mirrored, may be NULL if caller is + * not interested + * @return true if download successful + */ +bool NodesTk::downloadNodes(Node& sourceNode, NodeType nodeType, std::vector& outNodes, + bool silenceLog, NumNodeID* outRootNumID, bool* outRootIsBuddyMirrored) +{ + GetNodesMsg msg(nodeType); + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetNodesResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + // connect & communicate + bool commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return false; + + // handle result + GetNodesRespMsg* respMsgCast = static_cast(rrArgs.outRespMsg.get()); + + respMsgCast->getNodeList().swap(outNodes); + + if(outRootNumID) + *outRootNumID = respMsgCast->getRootNumID(); + + if(outRootIsBuddyMirrored) + *outRootIsBuddyMirrored = respMsgCast->getRootIsBuddyMirrored(); + + return true; +} + +/** + * Downloads target mappings from given sourceNode. + * + * @param sourceNode - the node to query about targets (usually management) + * @return true if download successful + */ +std::pair NodesTk::downloadTargetMappings(const Node& sourceNode, bool silenceLog) +{ + GetTargetMappingsMsg msg; + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetTargetMappingsResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + // connect & communicate + bool commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return {false, {}}; + + // handle result + GetTargetMappingsRespMsg* respMsgCast = + static_cast(rrArgs.outRespMsg.get()); + + return {true, respMsgCast->releaseMappings()}; +} + +/* + * note: outBuddyGroupIDs, outPrimaryTargetIDs and outSecondaryTargetIDs match 1:1 + * + * @param nodeType the type of group to download. + */ +bool NodesTk::downloadMirrorBuddyGroups(const Node& sourceNode, NodeType nodeType, + UInt16List* outBuddyGroupIDs, UInt16List* outPrimaryTargetIDs, UInt16List* outSecondaryTargetIDs, + bool silenceLog) +{ + GetMirrorBuddyGroupsMsg msg(nodeType); + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetMirrorBuddyGroupsResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + // connect & communicate + bool commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return false; + + // handle result + GetMirrorBuddyGroupsRespMsg* respMsgCast = + static_cast(rrArgs.outRespMsg.get()); + + respMsgCast->getBuddyGroupIDs().swap(*outBuddyGroupIDs); + respMsgCast->getPrimaryTargetIDs().swap(*outPrimaryTargetIDs); + respMsgCast->getSecondaryTargetIDs().swap(*outSecondaryTargetIDs); + + return true; +} + +/* + * note: outTargetIDs, outReachabilityStates and outConsistencyStates match 1:1 + * + * @param nodeType the type of states which should be downloaded (meta, storage). + */ +bool NodesTk::downloadTargetStates(Node& sourceNode, NodeType nodeType, UInt16List* outTargetIDs, + UInt8List* outReachabilityStates, UInt8List* outConsistencyStates, bool silenceLog) +{ + GetTargetStatesMsg msg(nodeType); + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetTargetStatesResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + bool commRes; + + // connect & communicate + commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return false; + + // handle result + auto* respMsgCast = static_cast(rrArgs.outRespMsg.get()); + + if (outTargetIDs) + respMsgCast->getTargetIDs().swap(*outTargetIDs); + + if (outReachabilityStates) + respMsgCast->getReachabilityStates().swap(*outReachabilityStates); + + if (outConsistencyStates) + respMsgCast->getConsistencyStates().swap(*outConsistencyStates); + + return true; +} + +/** + * Note: outBuddyGroupIDs, outPrimaryTargetIDs, and outSecondaryTargetIDs match 1:1; + * outTargetIDs and outTargetStates match 1:1. + */ +bool NodesTk::downloadStatesAndBuddyGroups(Node& sourceNode, NodeType nodeType, + MirrorBuddyGroupMap& outBuddyGroups, TargetStateMap& outStates, bool silenceLog) +{ + GetStatesAndBuddyGroupsMsg msg(nodeType); + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetStatesAndBuddyGroupsResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + bool commRes; + + // connect & communicate + commRes = MessagingTk::requestResponse(&rrArgs); + + if (!commRes) + return false; + + // handle result + auto* respMsgCast = static_cast(rrArgs.outRespMsg.get()); + + outBuddyGroups = respMsgCast->releaseGroups(); + outStates = respMsgCast->releaseStates(); + + return true; +} + +bool NodesTk::downloadStoragePools(Node& sourceNode, StoragePoolPtrVec& outStoragePools, + bool silenceLog) +{ + GetStoragePoolsMsg msg; + RequestResponseArgs rrArgs(&sourceNode, &msg, NETMSGTYPE_GetStoragePoolsResp); + +#ifndef BEEGFS_DEBUG + if (silenceLog) + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + // connect & communicate + bool commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return false; + + // handle result + const auto respMsgCast = static_cast(rrArgs.outRespMsg.get()); + StoragePoolPtrVec& storagePools = respMsgCast->getStoragePools(); + + storagePools.swap(outStoragePools); + + return true; +} + +/** + * Node: This is probably only useful in very rare cases, because normally you would want to + * use NodeStore::syncNodes() instead of this add-only method. + * + * @param nodeList All the nodes will belong to the store afterwards, so don't use them anymore + * after calling this method. + */ +void NodesTk::moveNodesFromListToStore(std::vector& nodes, AbstractNodeStore* nodeStore) +{ + for (auto iter = nodes.begin(); iter != nodes.end(); iter++) + nodeStore->addOrUpdateNode(*iter); + + nodes.clear(); +} + +/** + * Note: Useful if you got the list e.g. via downloadNodes(), because that doesn't set the + * localNicList and localNicCaps. + * Note: Make sure you didn't use the connPools of the nodes before calling this. + */ +void NodesTk::applyLocalNicListToList(Node& localNode, const std::vector& nodes) +{ + // set local nic capabilities + NicAddressList localNicList(localNode.getNicList() ); + NicListCapabilities localNicCaps; + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + applyLocalNicListToList(localNicList, localNicCaps, nodes); +} + +/** + * Note: Useful if you got the list e.g. via downloadNodes(), because that doesn't set the + * localNicList and localNicCaps. + * Note: Make sure you didn't use the connPools of the nodes before calling this. + */ +void NodesTk::applyLocalNicListToList(const NicAddressList& localNicList, + const NicListCapabilities& localNicCaps, const std::vector& nodes) +{ + // set local nic capabilities + for (auto& node : nodes) + { + node->getConnPool()->setLocalNicList(localNicList, localNicCaps); + } +} + +/** + * Returns the timeout to wait before the next (communication) retry based on the time that the + * caller already spent waiting for a reply. + * This provides a convenient way to get increasing timeouts between retries. + * + * @param elapsedMS the time that the caller already waited for a reply, including all previous + * retries. + * @return number of milliseconds to wait before the next retry. + */ +unsigned NodesTk::getRetryDelayMS(int elapsedMS) +{ + if(elapsedMS < 1000*3) + { + return 1000*1 + Random().getNextInRange(-100, 100); + } + else + if(elapsedMS < 1000*10) + { + return 1000*2; + } + else + if(elapsedMS < 1000*30) + { + return 1000*5; + } + else + if(elapsedMS < 1000*60) + { + return 1000*7; + } + else + if(elapsedMS < 1000*300) + { + return 1000*10; + } + else + { + return 1000*30 + Random().getNextInRange(-2000, 2000); + } +} diff --git a/common/source/common/toolkit/NodesTk.h b/common/source/common/toolkit/NodesTk.h new file mode 100644 index 0000000..e573957 --- /dev/null +++ b/common/source/common/toolkit/NodesTk.h @@ -0,0 +1,47 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class NodesTk +{ + public: + static bool waitForMgmtHeartbeat(PThread* currentThread, AbstractDatagramListener* dgramLis, + const NodeStoreServers* mgmtNodes, std::string hostname, unsigned short portUDP, + unsigned timeoutMS, unsigned nameResolutionRetries); + static std::shared_ptr downloadNodeInfo(const std::string& hostname, + unsigned short port, uint64_t connAuthHash, AbstractNetMessageFactory* netMessageFactory, + NodeType nodeType, int timeoutMS); + static bool downloadNodes(Node& sourceNode, NodeType nodeType, + std::vector& outNodes, bool silenceLog, NumNodeID* outRootNumID=NULL, + bool* outRootIsBuddyMirrored=NULL); + static std::pair downloadTargetMappings(const Node& sourceNode, + bool silenceLog); + static bool downloadMirrorBuddyGroups(const Node& sourceNode, NodeType nodeType, + UInt16List* outBuddyGroupIDs, UInt16List* outPrimaryTargetIDs, + UInt16List* outSecondaryTargetIDs, bool silenceLog); + static bool downloadTargetStates(Node& sourceNode, NodeType nodeType, UInt16List* outTargetIDs, + UInt8List* outReachabilityStates, UInt8List* outConsistencyStates, bool silenceLog); + static bool downloadStatesAndBuddyGroups(Node& sourceNode, NodeType nodeType, + MirrorBuddyGroupMap& outBuddyGroups, TargetStateMap& outStates, + bool silenceLog); + static bool downloadStoragePools(Node& sourceNode, StoragePoolPtrVec& outStoragePools, + bool silenceLog); + + static void moveNodesFromListToStore(std::vector& nodes, + AbstractNodeStore* nodeStore); + static void applyLocalNicListToList(Node& localNode, const std::vector& nodes); + static void applyLocalNicListToList(const NicAddressList& localNicList, + const NicListCapabilities& localNicCaps, const std::vector& nodes); + + static unsigned getRetryDelayMS(int elapsedMS); + + private: + NodesTk() {} +}; + + diff --git a/common/source/common/toolkit/ObjectReferencer.h b/common/source/common/toolkit/ObjectReferencer.h new file mode 100644 index 0000000..7d7cd6f --- /dev/null +++ b/common/source/common/toolkit/ObjectReferencer.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +template +class ObjectReferencer +{ + public: + /** + * @param ownReferencedObject if set to true, this object owns the referencedObject + * (so it will delete() the referencedObject when itself is being deleted) + */ + ObjectReferencer(T referencedObject, bool ownReferencedObject=true) + { + this->referencedObject = referencedObject; + this->ownReferencedObject = ownReferencedObject; + + this->refCount = 0; + } + + ~ObjectReferencer() + { + if(ownReferencedObject) + delete referencedObject; + } + + + private: + int refCount; + bool ownReferencedObject; + + T referencedObject; + + public: + // inliners + + T reference() + { + refCount++; + return referencedObject; + } + + int release() + { + #ifdef DEBUG_REFCOUNT + if(!refCount) + { + LogContext log("ObjectReferencer::release"); + log.logErr("Bug: refCount is 0 and release() was called"); + log.logBacktrace(); + + return 0; + } + else + return --refCount; + #else + return(--refCount); + #endif // DEBUG_REFCOUNT + } + + /** + * Be careful: This does not change the reference count! + */ + T getReferencedObject() + { + return referencedObject; + } + + int getRefCount() + { + return refCount; + } + + void setOwnReferencedObject(bool enable) + { + this->ownReferencedObject = enable; + } + + bool getOwnReferencedObject() + { + return ownReferencedObject; + } + +}; + + diff --git a/common/source/common/toolkit/OfflineWaitTimeoutTk.h b/common/source/common/toolkit/OfflineWaitTimeoutTk.h new file mode 100644 index 0000000..e51a021 --- /dev/null +++ b/common/source/common/toolkit/OfflineWaitTimeoutTk.h @@ -0,0 +1,41 @@ +#pragma once + +/** + * Calculates the offline wait timeout from config variables. It consists of: + * 5 sec InternodeSyncer syncLoop interval. + * 3 * update interval: + * until target gets pofflined (2x) + * end of offline timeout until next target state update (worst case) + * plus the actual target offline timeout. + * + * Templated for the Config type because Storage and Meta server have different Config classes. + */ +template +class OfflineWaitTimeoutTk +{ +public: + static unsigned int calculate(Cfg* cfg) + { + const unsigned updateTargetStatesSecs = cfg->getSysUpdateTargetStatesSecs(); + + if (updateTargetStatesSecs != 0) + { + // If sysUpdateTargetStateSecs is set in config, use that value. + return ( + ( 5 + + 3 * updateTargetStatesSecs + + cfg->getSysTargetOfflineTimeoutSecs() + ) * 1000); + } + else + { + // If sysUpdateTargetStatesSecs hasn't been set in config, it defaults to 1/3 the value + // of sysTargetOfflineTimeoutSecs -> we use 3 * 1/3 sysTargetOfflineTimeoutSecs. + return ( + ( 5 + + 2 * cfg->getSysTargetOfflineTimeoutSecs() + ) * 1000); + } + } +}; + diff --git a/common/source/common/toolkit/Pipe.h b/common/source/common/toolkit/Pipe.h new file mode 100644 index 0000000..1e4cc6c --- /dev/null +++ b/common/source/common/toolkit/Pipe.h @@ -0,0 +1,69 @@ +#pragma once + +#include "FileDescriptor.h" +#include + +#define PIPE_READFD_INDEX 0 +#define PIPE_WRITEFD_INDEX 1 + +class Pipe +{ + public: + Pipe(bool threadsafeReadside, bool threadsafeWriteside) + { + filedes[PIPE_READFD_INDEX] = -1; + filedes[PIPE_WRITEFD_INDEX] = -1; + + int pipeRes = pipe(filedes); + IGNORE_UNUSED_VARIABLE(pipeRes); + + this->readFD = new FileDescriptor(filedes[PIPE_READFD_INDEX], threadsafeReadside); + this->writeFD = new FileDescriptor(filedes[PIPE_WRITEFD_INDEX], threadsafeWriteside); + } + + ~Pipe() + { + delete(writeFD); + delete(readFD); + + close(filedes[PIPE_WRITEFD_INDEX]); + close(filedes[PIPE_READFD_INDEX]); + } + + private: + int filedes[2]; + FileDescriptor* readFD; + FileDescriptor* writeFD; + + public: + // getters & setters + FileDescriptor* getReadFD() const + { + return readFD; + } + + FileDescriptor* getWriteFD() const + { + return writeFD; + } + + /** + * @return true if incoming data is available or error occurred, false if a timeout occurred + */ + bool waitForIncomingData(int timeoutMS) + { + struct pollfd pollStruct = {readFD->getFD(), POLLIN, 0}; + int pollRes = poll(&pollStruct, 1, timeoutMS); + + if( (pollRes > 0) && (pollStruct.revents & POLLIN) ) + { + return true; + } + else + if(!pollRes) + return false; + else + return true; //error occurred, user gets the error by a read on the pipe + } +}; + diff --git a/common/source/common/toolkit/PreallocatedFile.h b/common/source/common/toolkit/PreallocatedFile.h new file mode 100644 index 0000000..ea2a069 --- /dev/null +++ b/common/source/common/toolkit/PreallocatedFile.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include + +#include +#include +#include + +#include +#include + +namespace detail { +template +struct PreallocatedFileDefaultSize +{ + static constexpr size_t value = + std::enable_if< + std::is_trivial::value, + std::integral_constant>::type::value; +}; +} + +/** + * Preallocates a file on disk for guaranteed writes. The type T must be serializable and all + * serialized representations of T must fit into a buffer of size Size. When the file is created + * it is filled with Size+1 zeroes, where the first byte is used as a tag and the remaining Size + * bytes are used for user data. If the file was never written, the tag byte will be 0 and read() + * will return none. If it was written, the tag byte will be 1 and read() will return a value or + * throw. + * + * Preallocation nominally guarantees (on Linux, anyway) that no write operation within the + * preallocated bounds will fail with any error other than EIO. It is recommended to use this class + * only for files on local disks since network filesystems may return EIO without being fatally + * broken. + * + * The buffer used for reading/writing will be placed on the stack, thus the user must ensure that + * the stack can hold at least Size+1 bytes of data. + */ +template::value> +class PreallocatedFile +{ + static_assert(Size <= std::numeric_limits::max(), "Size too large"); + + public: + /** + * Opens a file and preallocates space for guaranteed writes. If the file exists, it is + * truncated to Size+1. If the file does not exist, it is created with mode and then truncated + * to Size+1. + * + * @param path path of the file + * @param mode mode to use when creating path + */ + PreallocatedFile(const std::string& path, mode_t mode) + { + fd = FDHandle(open(path.c_str(), O_CREAT | O_RDWR, mode)); + if (!fd.valid()) + throw std::system_error(errno, std::system_category(), path); + + const int fallocateRes = posix_fallocate(*fd, 0, Size + 1); + if (fallocateRes != 0) + throw std::system_error(fallocateRes, std::system_category(), path); + } + + /** + * Serializes value and writes the result to the file at offset 1. The serialized value must + * not exceed Size bytes, otherwise an exception is thrown. The file is *not* fsynced, even + * if the write operation was successful. + * + * @param value value to write to the file + * + * @throws std::runtime_error if value does not fit into Size bytes after serialization + * @throws std::system_error if the write operation fails + */ + void write(const T& value) + { + char buf[Size + 1] = {}; + buf[0] = 1; + Serializer ser(buf + 1, Size); + + ser % value; + if (!ser.good()) + throw std::runtime_error("value too large for buffer"); + + if (pwrite(*fd, buf, Size + 1, 0) != Size + 1) + throw std::system_error(errno, std::system_category()); + } + + /** + * Read the byte at offset 0, and if the byte is not 0, also read Size bytes from the file at + * offset 1 and deserializes the result. If deserialization fails, an exception is thrown. + * + * @returns the deserialized value for the file contents up to Size bytes + * + * @throws std::system_error if the file contains less than Size+1 bytes + * @throws std::runtime_error if the file contents could not be deserialized + */ + boost::optional read() const + { + char buf[Size + 1]; + + if (pread(*fd, buf, Size + 1, 0) != Size + 1) + throw std::system_error(errno, std::system_category()); + + if (buf[0] == 0) + return boost::none; + + Deserializer des(buf + 1, Size); + T result; + + des % result; + if (!des.good()) + throw std::runtime_error("deserialization failed"); + + return result; + } + + private: + FDHandle fd; +}; + diff --git a/common/source/common/toolkit/Random.h b/common/source/common/toolkit/Random.h new file mode 100644 index 0000000..dc1f9ad --- /dev/null +++ b/common/source/common/toolkit/Random.h @@ -0,0 +1,47 @@ +#pragma once + +#include + +class Random +{ + public: + Random() + { + struct timeval tv; + gettimeofday(&tv, NULL); + + seed = tv.tv_usec; + } + + Random(unsigned seed) + { + this->seed = seed; + } + + /** + * @return positive (incl. zero) random number + */ + int getNextInt() + { + int randVal = rand_r(&seed); + + // Note: -randVal (instead of ~randVal) wouldn't work for INT_MIN + + return (randVal < 0) ? ~randVal : randVal; + } + + /** + * @param min inclusive min value + * @param max inclusive max value + */ + int getNextInRange(int min, int max) + { + int randVal = getNextInt() % (max - min + 1); + + return(min + randVal); + } + + private: + unsigned seed; +}; + diff --git a/common/source/common/toolkit/RandomReentrant.h b/common/source/common/toolkit/RandomReentrant.h new file mode 100644 index 0000000..3909af6 --- /dev/null +++ b/common/source/common/toolkit/RandomReentrant.h @@ -0,0 +1,58 @@ +#pragma once + +#include + +/* + * Note: This is not perfectly reentrant in the sense that it would use a mutex or atomic ops + * for seed updates, but it is safe for multi-threaded use and tries to make it unlikely that two + * simultaneous threads generate random numbers based on the same seed. + */ + +class RandomReentrant +{ + public: + RandomReentrant() + { + struct timeval tv; + gettimeofday(&tv, NULL); + + seed = tv.tv_usec; + } + + RandomReentrant(unsigned seed) + { + this->seed = seed; + } + + /** + * @return positive (incl. zero) random number + */ + int getNextInt() + { + unsigned seedTmp = seed++; /* "++" to make it unlikely that another thread uses the same + seed */ + + int randVal = rand_r(&seedTmp); + + seed = seedTmp; + + // Note: -randVal (instead of ~randVal) wouldn't work for INT_MIN + + return (randVal < 0) ? ~randVal : randVal; + } + + /** + * @param min inclusive min value + * @param max inclusive max value + */ + int getNextInRange(int min, int max) + { + int randVal = getNextInt() % (max - min + 1); + + return(min + randVal); + } + + private: + volatile unsigned seed; +}; + diff --git a/common/source/common/toolkit/SessionTk.h b/common/source/common/toolkit/SessionTk.h new file mode 100644 index 0000000..ccaf909 --- /dev/null +++ b/common/source/common/toolkit/SessionTk.h @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include + + +class SessionTk +{ + public: + + private: + SessionTk() {} + + public: + // inliners + + /** + * note: fileHandleID format: # + * note2: not supposed to be be on meta servers, use EntryInfo there + */ + static std::string fileIDFromHandleID(std::string fileHandleID) + { + std::string::size_type divPos = fileHandleID.find_first_of("#", 0); + + if(unlikely(divPos == std::string::npos) ) + { // this case should never happen + return fileHandleID; + } + + return fileHandleID.substr(divPos + 1); + } + + /** + * note: fileHandleID format: # + */ + static unsigned ownerFDFromHandleID(std::string fileHandleID) + { + std::string::size_type divPos = fileHandleID.find_first_of("#", 0); + + if(unlikely(divPos == std::string::npos) ) + { // this case should never happen + return 0; + } + + std::string ownerFDStr = fileHandleID.substr(0, divPos); + + return StringTk::strHexToUInt(ownerFDStr); + } + + /** + * note: fileHandleID format: # + */ + static std::string generateFileHandleID(unsigned ownerFD, std::string fileID) + { + return StringTk::uintToHexStr(ownerFD) + '#' + fileID; + } + + static int sysOpenFlagsFromFhgfsAccessFlags(unsigned accessFlags) + { + int openFlags = O_LARGEFILE; + + if(accessFlags & OPENFILE_ACCESS_READWRITE) + openFlags |= O_RDWR; + else + if(accessFlags & OPENFILE_ACCESS_WRITE) + openFlags |= O_WRONLY; + else + openFlags |= O_RDONLY; + + if(accessFlags & OPENFILE_ACCESS_DIRECT) + openFlags |= O_DIRECT; + + if(accessFlags & OPENFILE_ACCESS_SYNC) + openFlags |= O_SYNC; + + return openFlags; + } +}; + + diff --git a/common/source/common/toolkit/SocketTk.cpp b/common/source/common/toolkit/SocketTk.cpp new file mode 100644 index 0000000..d50d4b5 --- /dev/null +++ b/common/source/common/toolkit/SocketTk.cpp @@ -0,0 +1,86 @@ +#include "SocketTk.h" + +#include +#include + +#define SOCKETTK_MAX_HOSTNAME_RESOLVE_LEN 128 + +namespace { +struct GetAddrInfoError : public std::error_category +{ + [[nodiscard]] + const char* name() const noexcept override + { + return "Name Resolution Error"; + } + + [[nodiscard]] + std::string message(int condition) const override + { + return gai_strerror(condition); + } +}; + +GetAddrInfoError gaic; +}; + +const std::error_category& gai_category() { return gaic; } + +nu::error_or SocketTk::getHostByName(const std::string& hostname) +{ + struct addrinfo hint; + struct addrinfo* addressList; + + memset(&hint, 0, sizeof(struct addrinfo) ); + hint.ai_flags = AI_CANONNAME; + hint.ai_family = PF_INET; + hint.ai_socktype = SOCK_DGRAM; + + int getRes = getaddrinfo(hostname.c_str(), NULL, &hint, &addressList); + if(getRes) + return make_gai_error_code(getRes, errno); + + std::unique_ptr + addressListPtr(addressList, freeaddrinfo); + + struct sockaddr_in* inetAddr = (struct sockaddr_in*)(addressList->ai_addr); + return inetAddr->sin_addr; +} + +/** + * Resolve hostname for a given IP address. + * + * @param nameRequired true if hostname is required an no numeric representation is allowed to be + * returned. + * @param fullyQualified true if the fully qualified domain name should be returned. + * @return resolved hostname for given IP; empty string on error, IP address as string if hostname + * couldn't be resolved. + */ +std::string SocketTk::getHostnameFromIP(struct in_addr* ipAddr, bool nameRequired, + bool fullyQualified) +{ + char hostname[SOCKETTK_MAX_HOSTNAME_RESOLVE_LEN]; + + struct sockaddr_in serv_addr; + + memset(&serv_addr, 0, sizeof(serv_addr) ); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = ipAddr->s_addr; + + int getInfoFlags = 0; + + if(nameRequired) + getInfoFlags |= NI_NAMEREQD; + + if(!fullyQualified) + getInfoFlags |= NI_NOFQDN; + + int getRes = getnameinfo( (struct sockaddr*)&serv_addr, sizeof(serv_addr), hostname, + sizeof(hostname), NULL, 0, getInfoFlags); + + if(!getRes) + return hostname; + + return ""; +} diff --git a/common/source/common/toolkit/SocketTk.h b/common/source/common/toolkit/SocketTk.h new file mode 100644 index 0000000..73a827e --- /dev/null +++ b/common/source/common/toolkit/SocketTk.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +const std::error_category& gai_category(); + +inline std::error_code make_gai_error_code(int condition, int err) +{ + if (condition == EAI_SYSTEM) + return std::error_code(err, std::system_category()); + else + return std::error_code(condition, gai_category()); +} + +class SocketTk +{ + private: + SocketTk() {} + + public: + static nu::error_or getHostByName(const std::string& hostname); + static std::string getHostnameFromIP(struct in_addr* ipAddr, bool nameRequired, + bool fullyQualified); +}; + diff --git a/common/source/common/toolkit/StorageTk.cpp b/common/source/common/toolkit/StorageTk.cpp new file mode 100644 index 0000000..1927169 --- /dev/null +++ b/common/source/common/toolkit/StorageTk.cpp @@ -0,0 +1,1031 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "StorageTk.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define STORAGETK_DIR_LOCK_FILENAME "lock.pid" + +#define STORAGETK_FORMAT_KEY_VERSION "version" + +#define STORAGETK_TMPFILE_EXT ".tmp" /* tmp extension for saved files until rename */ + +#define STORAGETK_DEFAULT_FILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH) + +#define STORAGETK_DEFAULT_DIRMODE (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) + +#define STORAGETK_FREESPACE_FILENAME "free_space.override" +#define STORAGETK_FREEINODES_FILENAME "free_inodes.override" + +#ifndef BTRFS_SUPER_MAGIC +#define BTRFS_SUPER_MAGIC 0x9123683E /* , which isn't available on older distros */ +#endif + + +/* see generateFileID() for comments */ +AtomicUInt64 StorageTk::idCounter( + System::getCurrentTimeSecs() << STORAGETK_FILEID_TIMESTAMP_SHIFTBITS); + + +/** + * Init hash sub directories. + */ +void StorageTk::initHashPaths(Path& basePath, int maxLevel1, int maxLevel2) +{ + // inodes directores + + // entries subdirs + std::string level1LastName = StringTk::uintToHexStr(maxLevel1 - 1); + Path level1LastSubDirPath = basePath / level1LastName; + + std::string level2LastName = StringTk::uintToHexStr(maxLevel2 - 1); + Path level2LastSubDirPath = level1LastSubDirPath / level2LastName; + + if(!StorageTk::pathExists(level2LastSubDirPath.str() ) ) + { // last subdir does not exist yet, so we need to create the dirs + + for (int level1=0; level1 < maxLevel1; level1++) + { + std::string level1SubDirName = StringTk::uintToHexStr(level1); + Path level1SubDirPath = basePath / level1SubDirName; + + if(!StorageTk::createPathOnDisk(level1SubDirPath, false) ) + throw InvalidConfigException("Unable to create subdir: " + + level1SubDirPath.str() ); + + for (int level2=0; level2 < maxLevel2; level2++) + { + std::string level2SubDirName = StringTk::uintToHexStr(level2); + Path level2SubDirPath = level1SubDirPath / level2SubDirName; + + if(!StorageTk::createPathOnDisk(level2SubDirPath, false) ) + throw InvalidConfigException("Unable to create subdir: " + + level2SubDirPath.str() ); + } + } + } + +} + +/** + * Create path directories. + * + * Note: Existing directories are ignored (i.e. not an error). + * + * @param excluseLastElement true if the last element should not be created. + * @param inMode: NULL unless the caller sets it + * @return true on success, false otherwise and errno is set from mkdir() in this case. + */ +bool StorageTk::createPathOnDisk(const Path& path, bool excludeLastElement, mode_t* inMode) +{ + std::string pathStr = path.absolute() ? "/" : ""; + unsigned pathCreateDepth = excludeLastElement ? path.size() - 1 : path.size(); + + mode_t mkdirMode = (inMode) ? *inMode : STORAGETK_DEFAULT_DIRMODE; + + int mkdirErrno = 0; + + for(size_t i=0; i < pathCreateDepth; i++) + { + pathStr += path[i]; + + int mkRes = mkdir(pathStr.c_str(), mkdirMode); + mkdirErrno = errno; + if(mkRes && (mkdirErrno == EEXIST) ) + { // path exists already => check whether it is a directory + struct stat statStruct; + int statRes = stat(pathStr.c_str(), &statStruct); + if(statRes || !S_ISDIR(statStruct.st_mode) ) + return false; + + } + else + if(mkRes) + { // error + return false; + } + + // prepare next loop + pathStr += "/"; + } + + if (mkdirErrno == EEXIST) + chmod(pathStr.c_str(), mkdirMode); // ensure the right path permissions for the last element + + + return true; +} + +/** + * Create path directories, relative to a file descriptor + * + * Note: Existing directories are ignored (i.e. not an error). + * + * @param fd file descriptor of the "start directory" + * @param relativePath path to create (inside fd) + * @param excluseLastElement true if the last element should not be created. + * @param inMode: NULL unless the caller sets it + * @return true on success, false otherwise and errno is set from mkdir() in this case. + */ +bool StorageTk::createPathOnDisk(int fd, Path& relativePath, bool excludeLastElement, + mode_t* inMode) +{ + std::string pathStr = relativePath.absolute() ? "/" : ""; + unsigned pathCreateDepth = excludeLastElement ? relativePath.size() -1 : relativePath.size(); + + mode_t mkdirMode = (inMode) ? *inMode : STORAGETK_DEFAULT_DIRMODE; + + for ( unsigned i = 0; i < pathCreateDepth; i++ ) + { + pathStr += relativePath[i]; + + int mkRes = mkdirat(fd, pathStr.c_str(), mkdirMode); + if ( mkRes != 0 ) + { + if ( errno == EEXIST ) + { // path exists already => check whether it is a directory + int pathFD = openat(fd, pathStr.c_str(), O_RDONLY); + if ( pathFD == -1 ) // something went terribly wrong here + return false; + + struct stat statStruct; + int statRes = fstat(pathFD, &statStruct); + + if ( statRes || !S_ISDIR(statStruct.st_mode)) + { + close(pathFD); + return false; + } + + // it is a existing directory; ensure the right path permissions + fchmod(pathFD, mkdirMode); + + close(pathFD); + } + else + return false; + } + + // prepare next loop + pathStr += "/"; + } + + return true; +} + +/** + * @param keepDepth the 1-based depth of the path that should not be deleted + */ +bool StorageTk::removePathDirsFromDisk(Path& path, unsigned keepDepth) +{ + StringVector pathElems; + pathElems.reserve(path.size()); + for (size_t i = 0; i < path.size(); i++) + pathElems.push_back(path[i]); + + std::string pathStr = path.absolute() ? "/" : ""; + return removePathDirsRec(pathElems.begin(), pathStr, 1, pathElems.size(), keepDepth); +} + +/** + * @param currentDepth 1-based path depth + */ +bool StorageTk::removePathDirsRec(StringVectorIter dirNameIter, std::string pathStr, + unsigned currentDepth, unsigned numPathElems, unsigned keepDepth) +{ + pathStr += *dirNameIter; + + if(currentDepth < numPathElems) + { // we're not at the end yet + if(!removePathDirsRec(++StringVectorIter(dirNameIter), pathStr+"/", + currentDepth+1, numPathElems, keepDepth) ) + return false; + } + + if(currentDepth <= keepDepth) + return true; + + int rmRes = rmdir(pathStr.c_str() ); + + return(!rmRes || (errno == ENOENT) ); +} + + +/** + * Retrieve free/used space info for a mounted file system based on a path to a (sub)directory of + * the mount point. + * + * Note: For ext3/4 it makes a difference whether this process is run by root or not + * (f_bavail/f_bfree), so we use geteuid() in here to also detect this. + * + * @return false on error (and errno is set in this case) + */ +bool StorageTk::statStoragePath(const std::string path, int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree) +{ + /* note: don't use statvfs() here, because it requires a special lookup in "/proc" for the + f_flag field. */ + + struct statfs statBuf; + + int statRes = statfs(path.c_str(), &statBuf); + + if(statRes) + return false; + + // success => copy out-values + + /* note: outSizeFree depends on whether this process is running with superuser privileges or not + (e.g. on ext3/4 file systems) */ + + *outSizeTotal = statBuf.f_blocks * statBuf.f_bsize; + + if ( ( (unsigned)statBuf.f_type) != ( (unsigned)BTRFS_SUPER_MAGIC) ) + *outSizeFree = (geteuid() ? statBuf.f_bavail : statBuf.f_bfree) * statBuf.f_bsize; + else + { + // f_bfree includes raid redundancy blocks on btrfs + *outSizeFree = statBuf.f_bavail * statBuf.f_bsize; + } + + *outInodesTotal = statBuf.f_files; + *outInodesFree = statBuf.f_ffree; + + return true; +} + +/** + * Retrieve free/used space info for a mounted file system based on a path to a (sub)directory of + * the mount point. + * + * Note: For ext3/4 it makes a difference whether this process is run by root or not + * (f_bavail/f_bfree), so we use geteuid() in here to also detect this. + * + * @return false on error (and errno is set in this case) + */ +bool StorageTk::statStoragePath(Path& path, bool excludeLastElement, int64_t* outSizeTotal, + int64_t* outSizeFree, int64_t* outInodesTotal, int64_t* outInodesFree) +{ + std::string pathStr = path.absolute() ? "/" : ""; + unsigned pathCreateDepth = excludeLastElement ? path.size() - 1 : path.size(); + + for(unsigned i=0; i < pathCreateDepth; i++) + { + pathStr += path[i] + "/"; + } + + return statStoragePath(pathStr, outSizeTotal, outSizeFree, outInodesTotal, outInodesFree); +} + +/** + * Read free space info from override file, it if exists. + * + * @param path does not include filename of override file. + * @param outSizeFree size read from override file if it exists (value will be left untouched + * otherwise). + * @param outInodesFree free inode count from override file if it exists (value will be left + * untouched otherwise). + * + * @return true if either outSizeFree or inodeSizeFree or both values were set, false otherwise + */ +bool StorageTk::statStoragePathOverride(std::string pathStr, int64_t* outSizeFree, + int64_t* outInodesFree) +{ + Path storageDirPath(pathStr); + Path spaceFilePath = storageDirPath / STORAGETK_FREESPACE_FILENAME; + Path inodesFilePath = storageDirPath / STORAGETK_FREEINODES_FILENAME; + + bool valueChanged = false; + + StringList spaceList; // actually, each of the files would contain only a single line/value + StringList inodesList; + + bool spaceFilePathExists = StorageTk::pathExists(spaceFilePath.str() ); + if(spaceFilePathExists) + { + ICommonConfig::loadStringListFile(spaceFilePath.str().c_str(), spaceList); + if(!(spaceList.empty() || spaceList.begin()->empty() ) ) + { + *outSizeFree = UnitTk::strHumanToInt64(spaceList.begin()->c_str() ); + valueChanged = true; + } + } + + bool inodesFilePathExists = StorageTk::pathExists(inodesFilePath.str() ); + if(inodesFilePathExists) + { + ICommonConfig::loadStringListFile(inodesFilePath.str().c_str(), inodesList); + if(!(inodesList.empty() || inodesList.begin()->empty() ) ) + { + *outInodesFree = UnitTk::strHumanToInt64(inodesList.begin()->c_str() ); + valueChanged = true; + } + } + + return valueChanged; +} + +/** + * Returns the parent path for the given path by stripping the last entry name. + * + * Note: we have this for convenience, because ::dirname() might modify the passed buffer. + */ +std::string StorageTk::getPathDirname(std::string path) +{ + char* tmpPath = strdup(path.c_str() ); + + std::string dirnameRes = ::dirname(tmpPath); + + free(tmpPath); + + return dirnameRes; +} + +/** + * Returns the last entry of the given path by stripping the parent path. + * + * Note: we have this for convenience, because ::basename() might modify the passed buffer. + */ +std::string StorageTk::getPathBasename(std::string path) +{ + char* tmpPath = strdup(path.c_str() ); + + std::string dirnameRes = ::basename(tmpPath); + + free(tmpPath); + + return dirnameRes; +} + +/** + * Note: Works by non-exclusively creating of a lock file (incl. path if necessary) and then locking + * it non-blocking via flock(2). + * Note: The PID inside the file might no longer be up-to-date if this is called before a daemon + * forks to background mode, so don't rely on it. + * + * @param pathStr path to a directory (not including a filename) + * @return invalid on error, valid on success + */ +LockFD StorageTk::lockWorkingDirectory(const std::string& pathStr) +{ + Path dirPath(pathStr); + bool createPathRes = StorageTk::createPathOnDisk(dirPath, false); + if(!createPathRes) + { + int errCode = errno; + std::cerr << "Unable to create working directory: " << pathStr << "; " << + "SysErr: " << System::getErrString(errCode) << std::endl; + + return {}; + } + + Path filePath = dirPath / STORAGETK_DIR_LOCK_FILENAME; + std::string filePathStr = filePath.str(); + + LockFD fd = createAndLockPIDFile(filePathStr, false); + if (!fd.valid()) + { + std::cerr << "Unable to lock working directory. Check if the directory is already in use: " << + pathStr << std::endl; + } + + return fd; +} + +/** + * Note: Works by non-exclusive creation of a pid file and then trying to lock it non-blocking via + * flock(2). + * + * @param pathStr path (including a filename) of the pid file, parent dirs will not be created + * @param writePIDToFile whether or not you want to write the PID to the file (if false, the file + * will just be truncated to 0 size); false is useful before a daemon forked to background mode. + * @return invalid on error, valid on success + */ +LockFD StorageTk::createAndLockPIDFile(const std::string& pathStr, bool writePIDToFile) +{ + return LockFD::lock(pathStr, writePIDToFile) + .reduce( + [&] (LockFD lock) -> LockFD { + const auto updateErr = lock.updateWithPID(); + if (writePIDToFile && updateErr) + { + std::cerr << "Problem encountered during PID file update: " << updateErr.message() + << std::endl; + return {}; + } + + return lock; + }, + [&] (const std::error_code& error) -> LockFD { + if (error.value() == EWOULDBLOCK) + std::cerr << "Unable to lock PID file: " << pathStr << " (already locked)." + << std::endl << "Check if service is already running." << std::endl; + else + std::cerr << "Unable to lock working directory lock file: " << pathStr + << "; " << "SysErr: " << error.message() << std::endl; + + return {}; + }); +} + +bool StorageTk::writeFormatFile(const std::string& path, int formatVersion, + const StringMap* formatProperties) +{ + Path dirPath(path); + Path filePath = dirPath / STORAGETK_FORMAT_FILENAME; + + std::stringstream contents; + + contents << "# This file was auto-generated. Do not modify it!" << std::endl; + contents << STORAGETK_FORMAT_KEY_VERSION << "=" << formatVersion << std::endl; + + if (formatProperties) + { + for (auto it = formatProperties->begin(); it != formatProperties->end(); ++it) + { + if (it->first != STORAGETK_FORMAT_KEY_VERSION) + contents << it->first << "=" << it->second << std::endl; + } + } + + const auto contentsStr = contents.str(); + + const auto saveRes = TempFileTk::storeTmpAndMove(filePath.str(), contentsStr); + return saveRes == FhgfsOpsErr_SUCCESS; +} + +/** + * Note: Creates the file only if it does not exist yet. + * + * @param pathStr path to the main storage working directory (not including a filename) + * @param formatProperties arbitrary key/value pairs that should be stored in the format file + * (may be NULL); does not need to contain formatVersion key/value. + * @return true if format file was created or existed already + */ +bool StorageTk::createStorageFormatFile(const std::string pathStr, int formatVersion, + StringMap* formatProperties) +{ + Path dirPath(pathStr); + Path filePath = dirPath / STORAGETK_FORMAT_FILENAME; + std::string filePathStr = filePath.str(); + + /* note: we must not touch an existing format file to make sure we don't overwrite any values + that are meant to be persistent */ + if(pathExists(filePathStr.c_str() ) ) + return true; + + return writeFormatFile(pathStr, formatVersion, formatProperties); +} + +void StorageTk::loadStorageFormatFile(const std::string pathStr, int minVersion, int maxVersion, + int& fileVersion, StringMap& outFormatProperties) +{ + Path dirPath(pathStr); + Path filePath = dirPath / STORAGETK_FORMAT_FILENAME; + std::string filePathStr = filePath.str(); + + StringMap formatFileMap; + + MapTk::loadStringMapFromFile(filePathStr.c_str(), &formatFileMap); + + if(formatFileMap.empty() ) + throw InvalidConfigException(std::string("Storage format file is empty: ") + filePathStr); + + StringMapIter iter = formatFileMap.find(STORAGETK_FORMAT_KEY_VERSION); + if(iter == formatFileMap.end() ) + { + throw InvalidConfigException( + std::string("Storage format file does not contain version info: ") + filePathStr); + } + + fileVersion = StringTk::strToInt(iter->second.c_str() ); + + if(fileVersion < minVersion || fileVersion > maxVersion) + { + throw InvalidConfigException("Incompatible storage format: " + + StringTk::intToStr(fileVersion) + + " (required min: " + StringTk::intToStr(minVersion) + + " required max : " + StringTk::intToStr(maxVersion) + ")"); + } + + outFormatProperties.swap(formatFileMap); +} + +/** + * @param pathStr path to the main storage working directory (not including a filename) + * @throws exception if format file was not valid (eg didn't exist or contained wrong version). + * @return all key/value pairs that were stored in the format file + */ +StringMap StorageTk::loadAndUpdateStorageFormatFile(const std::string pathStr, int minVersion, + int currentVersion) +{ + StringMap formatFileMap; + int fileVersion; + + loadStorageFormatFile(pathStr, minVersion, currentVersion, fileVersion, formatFileMap); + + if (fileVersion < currentVersion) + { + if (!writeFormatFile(pathStr, currentVersion, &formatFileMap)) + throw InvalidConfigException("Failed to update " STORAGETK_FORMAT_FILENAME "!"); + } + + return formatFileMap; +} + +/** + * Note: This is intended to check e.g. whether a storage directory has been initialized already. + * + * @param pathStr path to the main storage working directory (not including a filename) + * @return true if format file exists already + */ +bool StorageTk::checkStorageFormatFileExists(const std::string pathStr) +{ + Path dirPath(pathStr); + Path filePath = dirPath / STORAGETK_FORMAT_FILENAME; + std::string filePathStr = filePath.str(); + + return pathExists(filePathStr.c_str() ); +} + +/** + * Note: This is intended to check whether a storage directory was cleanly shut down, i.e. the + * sessions file was created. In case of a server crash, there will be no sessions file. + * + * @param pathStr path to the main storage working directory (not including a filename) + * @return true if sessions file exists + */ +bool StorageTk::checkSessionFileExists(const std::string& pathStr) +{ + Path dirPath(pathStr); + Path filePath = dirPath / STORAGETK_SESSIONS_BACKUP_FILE_NAME; + std::string filePathStr = filePath.str(); + + return pathExists(filePathStr.c_str() ); +} + +/** + * Writes out a deprecation notice to the old string ID and original string ID files. Rename the + * files to end with ".v7". Nothing is written if these files no longer exist. Errors writing to the + * files are ignored. + */ +void StorageTk::deprecateNodeStringIDFiles(const std::string pathStr) { + auto deprecate = [](Path path) { + const std::string appendContent = + "\n# String IDs have been deprecated starting with BeeGFS 8." + "\n# This file can be safely deleted once you are sure you do not need to downgrade back to BeeGFS 7." + "\n# If you do need to downgrade, remove this comment and rename the file to remove the .v7 suffix.\n"; + + if (StorageTk::pathExists(path.str())) { + const std::string newPathString = path.str() + ".v7"; + std::rename(path.str().c_str(), newPathString.c_str()); + + std::ofstream o; + o.open(newPathString, std::ios::ios_base::app); + o << appendContent; + o.close(); + } + }; + + Path basePath(pathStr); + deprecate(basePath / STORAGETK_NODEID_FILENAME); + deprecate(basePath / STORAGETK_ORIGINALNODEID_FILENAME); +} + +/** + * Reads the old targetID for the given path. + * + * @param pathStr path to the storage working directory (not including a filename) + */ +void StorageTk::readTargetIDFile(const std::string pathStr, std::string* outTargetID) +{ + // check if file exists already and read it, otherwise create it with new targetID + + Path storageDirPath(pathStr); + Path targetIDPath = storageDirPath / STORAGETK_TARGETID_FILENAME; + + StringList targetIDList; // actually, the file would contain only a single line + + bool targetIDPathExists = StorageTk::pathExists(targetIDPath.str() ); + if(targetIDPathExists) + ICommonConfig::loadStringListFile(targetIDPath.str().c_str(), targetIDList); + + std::string oldTargetID; + + if(!targetIDList.empty() ) + oldTargetID = *targetIDList.begin(); + + if(oldTargetID.empty() ) + return; + + + *outTargetID = oldTargetID; +} + +/** + * Reads the old targetID for the given path or generates a new one and stores it. + * + * @param pathStr path to the storage working directory (not including a filename) + * @param localNodeID is part of the new targetID (if it needs to be generated) + */ +void StorageTk::readOrCreateTargetIDFile(const std::string pathStr, const NumNodeID localNodeID, + std::string* outTargetID) +{ + // check if file exists already and read it, otherwise create it with new targetID + + std::string oldTargetID; + + readTargetIDFile(pathStr, &oldTargetID); + + if(!oldTargetID.empty() ) + { // targetID is already defined => we're done + *outTargetID = oldTargetID; + + return; + } + + // no oldTargetID file yet => create file and generate new target ID + + Path storageDirPath(pathStr); + Path targetIDPath = storageDirPath / STORAGETK_TARGETID_FILENAME; + + // note: yes, new target IDs are generated the same way as file IDs + const auto newTargetID = StorageTk::generateFileID(localNodeID); + + const auto writeRes = TempFileTk::storeTmpAndMove(targetIDPath.str(), newTargetID + "\n"); + if (writeRes != FhgfsOpsErr_SUCCESS) + throw InvalidConfigException("Unable to create file for storage target ID " + + targetIDPath.str()); + + *outTargetID = newTargetID; +} + +/** + * Reads the old targetNumID for the given path. + * + * @param pathStr path to the storage working directory (not including a filename) + * @outTargetNumID 0 if not set yet (e.g. no such file exists), loaded ID otherwise + */ +void StorageTk::readNumIDFile(const std::string pathStr, const std::string filename, + NumNodeID* outNodeNumID) +{ + auto res = readNumFromFile(pathStr, filename); + if (res.first == FhgfsOpsErr_SUCCESS) + *outNodeNumID = res.second; + else + *outNodeNumID = NumNodeID(0); +} + +/* + * @return the numeric value contained in the targetIDFile or 0 if file doesn't exist or error + * occured + */ +void StorageTk::readNumTargetIDFile(const std::string pathStr, const std::string filename, + uint16_t* outTargetNumID) +{ + auto res = readNumFromFile(pathStr, filename); + if (res.first == FhgfsOpsErr_SUCCESS) + *outTargetNumID = res.second; + else + *outTargetNumID = 0; +} + +StoragePoolId StorageTk::readNumStoragePoolIDFile(const std::string pathStr, + const std::string filename) +{ + auto res = readNumFromFile(pathStr, filename); + if (res.first == FhgfsOpsErr_SUCCESS) + return res.second; + else + return StoragePoolStore::DEFAULT_POOL_ID; +} + +/** + * Create file for numeric target ID and write given ID into it. + * + * @param pathStr path to the storage working directory (not including a filename) + */ +void StorageTk::createNumIDFile(const std::string pathStr, const std::string filename, + uint16_t targetID) +{ + // TODO: cast to unsigned long is a workaround for a bug in gcc 4.4 + const auto targetIDStr = std::to_string((unsigned long long) targetID) + "\n"; + + Path storageDirPath(pathStr); + Path targetIDPath = storageDirPath / filename; + //check if the file already exists, create file only if it does not exists + auto res = readNumFromFile(pathStr, filename); + if ( res.first == FhgfsOpsErr_PATHNOTEXISTS ) + { + const auto writeRes = TempFileTk::storeTmpAndMove(targetIDPath.str(), targetIDStr); + if (writeRes != FhgfsOpsErr_SUCCESS) + throw InvalidConfigException("Unable to create file for storage target ID " + + std::to_string((unsigned long long) targetID)); + } + else if ( res.second != targetID ) + { + throw InvalidConfigException("storage target ID mismatch"); + } +} + +/** + * Calls system readdir() and skips entries "." and ".." and META_DIRENTRYID_SUB_STR ("#fSiDs#"). + * + * @return same as system readdir(). + */ +struct dirent* StorageTk::readdirFiltered(DIR* dirp) +{ + return readdirFilteredEx(dirp, true, true); +} + +/** + * Calls system readdir() and optionally skips certain entries. + * + * @param filterDots true if you want to filter "." and "..". + * @param filterFSIDsDir true if you want to filter META_DIRENTRYID_SUB_STR ("#fSiDs#"). + * @return same as system readdir() except that this will never return the filtered entries. + */ +struct dirent* StorageTk::readdirFilteredEx(DIR* dirp, bool filterDots, + bool filterFSIDsDir) +{ + // loop to skip filtered entries + for( ; ; ) + { + errno = 0; // recommended by posix (readdir(3p) ) + + struct dirent* dirEntry = readdir(dirp); + + if(!dirEntry) + return dirEntry; + + if( (!filterDots || strcmp(dirEntry->d_name, ".") != 0 ) && + (!filterDots || strcmp(dirEntry->d_name, "..") != 0 ) && + (!filterFSIDsDir || strcmp(dirEntry->d_name, META_DIRENTRYID_SUB_STR) != 0 ) ) + { + return dirEntry; + } + } +} + +/** + * Read all directory entries into given string list. + * + * Warning: This list can be big when the dir has lots of entries, so use this carefully. + * + * @throw InvalidConfigException on error (e.g. path not exists) + */ +void StorageTk::readCompleteDir(const char* path, StringList* outNames) +{ + errno = 0; // recommended by posix (readdir(3p) ) + + DIR* dirHandle = opendir(path); + if(!dirHandle) + throw InvalidConfigException(std::string("Unable to open directory: ") + path + "; " + "SysErr: " + System::getErrString() ); + + struct dirent* dirEntry; + + while( (dirEntry = StorageTk::readdirFiltered(dirHandle) ) ) + { + outNames->push_back(dirEntry->d_name); + } + + if(errno) + { + throw InvalidConfigException( + std::string("Unable to fetch directory entry from: ") + path + "; " + "SysErr: " + System::getErrString() ); + } + + closedir(dirHandle); +} + + + +/** + * Get path to chunk file and additional Path to chunk file (not including file name), both + * relative to chunks subdir. + * + * If hasOrigFeature is set then the returned path is 2014.01-style layout with user and timestamp + * dirs, otherwise it's the previous 2012.10 style layout with only hash dirs. + * + * @param hasOrigFeature true for 2014.01 style, false for 2012.10 style. + * @param outChunkDirPathpath to parent dir of chunk file (filled-in for >V2 only). + * @param outChunkFilePathStr path to chunk file. + */ +void StorageTk::getChunkDirChunkFilePath(const PathInfo* pathInfo, std::string entryID, + bool hasOrigFeature, Path& outChunkDirPath, std::string& outChunkFilePathStr) +{ + if (!hasOrigFeature) + { // old 2012.10 style storage layout + outChunkFilePathStr = StorageTk::getFileChunkPathV2(entryID, &outChunkDirPath); + } + else + { // new 2014.01 style storage layout + StorageTk::getChunkDirV3Path(pathInfo, outChunkDirPath); + outChunkFilePathStr = outChunkDirPath.str() + '/' + entryID; + } +} + +bool StorageTk::removeDirRecursive(const std::string& dir) +{ + struct ops + { + static int visit(const char* path, const struct stat*, int type, struct FTW*) + { + if(type == FTW_F) + return ::unlink(path); + else + return ::rmdir(path); + } + }; + + return ::nftw(dir.c_str(), ops::visit, 10, FTW_DEPTH) == 0; +} + +/** + * Finds the longest prefix of path that is a mountpoint. path must be an absolute pathname, but + * it need not exist. Symlink resolution is not performed on path. + * + * @param rawPath path to resolve to its longest-prefix mountpoint + * @param mtab a std::istream with contents in the format of /etc/mtab or /proc/mounts + */ +std::pair StorageTk::findLongestMountedPrefix(const std::string& rawPath, + std::istream& mtab) +{ + if (rawPath.empty() || rawPath[0] != '/') + return {false, {}}; + + Mount currentMount = {}; + + // note: this code parses the mtab input directly instead of calling getmntent because getmnt + // is not reentrant and getmntent_r is limited by the buffer passed to it (without giving useful + // indications that the buffer is too small). + // we will also want to switch to the mountinfo format (of /proc/self/mountinfo) at some point + // in the future, which is not supported by getmntent at all, but parses much the same as here. + while (mtab) { + std::string device, mount, type; + std::string::size_type pos; + + // mtab format files have these fields: + // SP SP NL + // devname and mountpoint escape the [ \t\n\\] characters with octal sequences + getline(mtab, device, ' '); + getline(mtab, mount, ' '); + getline(mtab, type, ' '); + if (!mtab) + break; + while ((pos = mount.find("\\040")) != std::string::npos) + mount.replace(pos, 4, " "); + while ((pos = mount.find("\\011")) != std::string::npos) + mount.replace(pos, 4, "\t"); + while ((pos = mount.find("\\012")) != std::string::npos) + mount.replace(pos, 4, "\n"); + while ((pos = mount.find("\\134")) != std::string::npos) + mount.replace(pos, 4, "\\"); + + mtab.ignore(std::numeric_limits::max(), '\n'); + + if (mount == rawPath) + return {true, Mount{std::move(device), std::move(mount), std::move(type)}}; + if (rawPath.size() <= mount.size()) + continue; + if (rawPath.compare(0, mount.size(), mount) != 0) + continue; + if (*mount.rbegin() != '/' && rawPath[mount.size()] != '/') + continue; + if (mount.size() < currentMount.path.size()) + continue; + + currentMount = {std::move(device), std::move(mount), std::move(type)}; + } + + if (mtab.eof() && !currentMount.path.empty()) + return {true, std::move(currentMount)}; + else + return {false, {}}; +} + +/** + * Finds the longest prefix of path that is a mountpoint. path must be an absolute pathname, but + * it need not exist. Symlink resolution is performed on path up to an including the last component. + * Mountpoints are looked up from /proc/self/mounts. + * + * @param path path to resolve to its longest-prefix mountpoint + */ +std::pair StorageTk::findMountForPath(const std::string& path) +{ + std::ifstream mounts("/proc/self/mounts"); + + if (!mounts) + return {false, {}}; + + std::string resolvedPath; + + { + char* resolved = ::realpath(path.c_str(), nullptr); + if (!resolved) + return {false, {}}; + + resolvedPath = resolved; + free(resolved); + } + + return findLongestMountedPrefix(resolvedPath, mounts); +} + +/** + * Reads a file into a buffer, up to the size specified. + * @param path The path to be read + * @param maxSize The maximum number of bytes to be read + * @returns Pair of FhfgfsOpsError and a char vector. + * In case of error, empty vector is returned and Error code is set as follows: + * FhgfsOpsErr_RANGE - File is larger than maxSize + * Appropriate error (via fromSysErr) if open() fails + * Otherwise, the error code is set to FhgfsOpsErr_SUCCESS and vector is filled + * with file contents. + */ +std::pair> StorageTk::readFile(const std::string& filename, + int maxSize) +{ + FDHandle fd(::open(filename.c_str(), O_RDONLY)); + if (!fd.valid()) + return { FhgfsOpsErrTk::fromSysErr(errno), {} }; + + auto size = ::lseek(fd.get(), 0, SEEK_END); + if (size > maxSize) + return { FhgfsOpsErr_RANGE, {} }; + + if (size == -1) + return { FhgfsOpsErrTk::fromSysErr(errno), {} }; + + auto seekRes = ::lseek(fd.get(), 0, SEEK_SET); + if (seekRes == -1) + return { FhgfsOpsErrTk::fromSysErr(errno), {} }; + + std::vector buf(size); + auto readRes = ::read(fd.get(), &buf[0], size); + + if (readRes == -1) + return { FhgfsOpsErrTk::fromSysErr(errno), {} }; + + return { FhgfsOpsErr_SUCCESS, buf }; +} + +/* + * reads a numeric value from a file (Note: the file must only contain the numeric value) + * + * @param pathStr dirname to look into + * @filename the file to read + * @outLines the file contents as list (one element per line) + * + * @return a air of FhfgfsOpsErr and the numeric value. + * In case of error, 0 is returned and Error code is set as follows: + * FhgfsOpsErr_PATHNOTEXISTS if file doesn't + * FhgfsOpsErr_INVAL if the cast from string to num failed + * FhgfsOpsErr_NODATA if file exists, but is empty + */ +template +std::pair StorageTk::readNumFromFile(const std::string& pathStr, + const std::string& filename) +{ + // check if file exists and read it + Path dirPath(pathStr); + Path filePath = dirPath / filename; + + bool fileExists = StorageTk::pathExists(filePath.str() ); + if (!fileExists) + return { FhgfsOpsErr_PATHNOTEXISTS, NumericT(0) }; + + // read the file + StringList contents; + ICommonConfig::loadStringListFile(filePath.str().c_str(), contents); + + if ( contents.empty() ) + return { FhgfsOpsErr_NODATA, NumericT(0) }; + + // we are only interested in the first element of contents, as the file should only contain + // one numeric value + std::stringstream ss(contents.front()); + NumericT value; + + ss >> value; + + if (!ss.fail()) + return { FhgfsOpsErr_SUCCESS, value }; + else + return { FhgfsOpsErr_INVAL, NumericT(0) }; +} diff --git a/common/source/common/toolkit/StorageTk.h b/common/source/common/toolkit/StorageTk.h new file mode 100644 index 0000000..e2c4252 --- /dev/null +++ b/common/source/common/toolkit/StorageTk.h @@ -0,0 +1,515 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define STORAGETK_FORMAT_FILENAME "format.conf" +#define STORAGETK_BACKUP_EXT ".bak" /* extension for old file backups */ + +#define STORAGETK_FILE_COMMENT_CHAR '#' +#define STORAGETK_ORIGINALNODEID_FILENAME "originalNodeID" /* contains first-run nodeID */ +#define STORAGETK_NODEID_FILENAME "nodeID" /* to force a certain nodeID */ +#define STORAGETK_NODENUMID_FILENAME "nodeNumID" /* contains first-run numeric node ID */ +#define STORAGETK_TARGETID_FILENAME "targetID" /* contains first-run targetID */ +#define STORAGETK_TARGETNUMID_FILENAME "targetNumID" /* contains first-run targetNumID */ +#define STORAGETK_STORAGEPOOLID_FILENAME "storagePoolID" /* contains first-run storagePoolID */ +#define STORAGETK_SESSIONS_BACKUP_FILE_NAME "sessions" /* contains session backup information */ +#define STORAGETK_MSESSIONS_BACKUP_FILE_NAME "mirroredSessions" /* mirror session backup information */ + + +#define STORAGETK_FILEID_TIMESTAMP_SHIFTBITS 32 /* timestamp is shifted by this number of bits */ + +// positions within 'Path chunkDirPath' +#define STORAGETK_CHUNKDIR_VEC_UIDPOS 0 + +/* RPOS -> reverse pos, so from the end of the string, 16^n below, as this is char 'F' time stamps + * We use fixed string positions, which only approximately correspond to the right time, + * STORAGETK_TIMESTAMP_YEAR_RPOS changes approximately every 1/2 year instead of every year, but + * the goal is not to make it exact, but to get object storage directories based on the string. + * Note: string counting starts with 0, but size = pos + 1 and we need 16^size + * */ +#define STORAGETK_TIMESTAMP_DAY_RPOS 3 // 1d = 86400s; 16^4 = 65,536 +#define STORAGETK_TIMESTAMP_MONTH_RPOS 4 // 1m = 30d = 2,529,000s; 16^5 = 1,048,576 +#define STORAGETK_TIMESTAMP_YEAR_RPOS 5 // 1y = 365d = 31,536,000s; 16^6 = 16,777,216 + +struct Mount +{ + std::string device; + std::string path; + std::string type; + + bool operator==(const Mount& other) const + { + return std::tie(device, path, type) == std::tie(other.device, other.path, other.type); + } + + bool operator<(const Mount& other) const + { + return std::tie(device, path, type) < std::tie(other.device, other.path, other.type); + } +}; + +class StorageTk +{ + public: + static void initHashPaths(Path& basePath, int maxLevel1, int maxLevel2); + + static bool createPathOnDisk(const Path& path, bool excludeLastElement, mode_t* inMode = NULL); + static bool createPathOnDisk(int fd, Path& relativePath, bool excludeLastElement, + mode_t* inMode = NULL); + static bool removePathDirsFromDisk(Path& path, unsigned keepDepth); + + static bool statStoragePath(const std::string path, int64_t* outSizeTotal, + int64_t* outSizeFree, int64_t* outInodesTotal, int64_t* outInodesFree); + static bool statStoragePath(Path& path, bool excludeLastElement, int64_t* outSizeTotal, + int64_t* outSizeFree, int64_t* outInodesTotal, int64_t* outInodesFree); + static bool statStoragePathOverride(std::string pathStr, int64_t* outSizeFree, + int64_t* outInodesFree); + + static std::string getPathDirname(std::string path); + static std::string getPathBasename(std::string path); + + static LockFD lockWorkingDirectory(const std::string& pathStr); + static LockFD createAndLockPIDFile(const std::string& pathStr, bool writePIDToFile); + + static bool writeFormatFile(const std::string& path, int formatVersion, + const StringMap* formatProperties = nullptr); + + static bool createStorageFormatFile(const std::string pathStr, int formatVersion, + StringMap* formatProperties = NULL); + static void loadStorageFormatFile(const std::string pathStr, int minVersion, int maxVersion, + int& fileVersion, StringMap& outFormatProperties); + static StringMap loadAndUpdateStorageFormatFile(const std::string pathStr, int minVersion, + int currentVersion); + static bool checkStorageFormatFileExists(const std::string pathStr); + static bool checkSessionFileExists(const std::string& pathStr); + + static void deprecateNodeStringIDFiles(const std::string pathStr); + + static void readTargetIDFile(const std::string pathStr, std::string* outTargetID); + static void readOrCreateTargetIDFile(const std::string pathStr, const NumNodeID localNodeID, + std::string* outTargetID); + static void readNumIDFile(const std::string pathStr, const std::string filename, + NumNodeID* outNodeNumID); + static void readNumTargetIDFile(const std::string pathStr, const std::string filename, + uint16_t* outTargetNumID); + static StoragePoolId readNumStoragePoolIDFile(const std::string pathStr, + const std::string filename); + static void createNumIDFile(const std::string pathStr, const std::string filename, + uint16_t targetID); + + /** + * Deleter that can be used to automatically close directory handles (DIR*), e.g. in + * combination with unique_ptr. + */ + struct CloseDirDeleter + { + void operator()(DIR* value) const + { + ::closedir(value); + } + }; + + static struct dirent* readdirFiltered(DIR* dirp); + static struct dirent* readdirFilteredEx(DIR* dirp, bool filterDots, bool filterFSIDsDir); + static void readCompleteDir(const char* path, StringList* outNames); + + static void updateDynamicFileInodeAttribs(ChunkFileInfoVec& fileInfoVec, + StripePattern* stripePattern, StatData* outStatData); + + static void getChunkDirChunkFilePath(const PathInfo* pathInfo, std::string entryID, + bool hasOrigFeature, Path& outChunkDirPath, std::string& outChunkFilePathStr); + + static bool removeDirRecursive(const std::string& dir); + + static std::pair findLongestMountedPrefix(const std::string& rawPath, + std::istream& mtab); + static std::pair findMountForPath(const std::string& path); + + static std::pair> readFile(const std::string& filename, + int maxSize); + + private: + StorageTk() {} + + static AtomicUInt64 idCounter; // high 32bit are timestamp, low 32bits are sequential counter + + static bool removePathDirsRec(StringVectorIter dirNameIter, std::string pathStr, + unsigned currentDepth, unsigned numPathElems, unsigned keepDepth); + + template static std::pair readNumFromFile + (const std::string& pathStr, const std::string& filename); + + static FhgfsOpsErr readFileAsList(const std::string& pathStr, const std::string& filename, + StringList& outLines); + + public: + // inliners + + /** + * Check if given path exists. + */ + static bool pathExists(std::string path) + { + int statRes = access(path.c_str(), F_OK); + + return statRes ? false : true; + } + + /* + * Check if given path exists, but path is relative to a file descriptor + */ + static bool pathExists(int dirfd, std::string relativePath) + { + int statRes = faccessat(dirfd, relativePath.c_str(), F_OK, 0); + + return statRes ? false : true; + } + + /* + * check if a path contains anything + * + * @return true if path has any contents, false if not or if it is not a dir or doesn't exist + */ + static bool pathHasChildren(const std::string& path) + { + bool retVal; + struct dirent *d; + + DIR *dir = opendir(path.c_str()); + + if (dir == NULL) // not a directory or doesn't exist + return false; + + if ((d = readdirFilteredEx(dir, true, false)) != NULL) + retVal = true; // something is there + else + retVal = false; + + closedir(dir); + + return retVal; + } + + /** + * Generate ID for new fs entry (i.e. file or dir). + */ + static std::string generateFileID(const NumNodeID localNodeID) + { + /* note: we assume here that the clock doesn't jump backwards between restarts of + the daemon (and that there always is at least one second between restarts) and that we + don't need more than 2^32 IDs per second (sustained) */ + + uint64_t nextID = idCounter.increase(); + + // note on idCounter value: high 32bits are timestamp, low 32bits are sequential counter + + /* note on switching time/counter in string representation: having the timestamp first is + bad for strcmp() and such things, which the underlying fs might need to do - because in + that order, the first characters of entryIDs/filenames would always be similar. */ + + uint32_t counterPart = (uint32_t) nextID; + uint32_t timestampPart = (uint32_t) (nextID >> STORAGETK_FILEID_TIMESTAMP_SHIFTBITS); + + return StringTk::uintToHexStr(counterPart) + "-" + StringTk::uintToHexStr(timestampPart) + + "-" + localNodeID.strHex(); + } + + /** + * Set the id counter to the current time stamp and reset the sequential counter to 0 + * + * Note: time is only updated if the clock didn't jump backwards, according to the previous + * id counter value. + * + * Note: make sure that this is only called by a single thread and only used very carefully + * to avoid races and ID collisions. + */ + static void resetIDCounterToNow() + { + uint32_t currentSysTimeSecs = System::getCurrentTimeSecs(); + + uint32_t idCounterTimestampPart = + (uint32_t)(idCounter.read() >> STORAGETK_FILEID_TIMESTAMP_SHIFTBITS); + + if(unlikely(currentSysTimeSecs <= idCounterTimestampPart) ) + return; // don't do anything if system clock seems to have jumped backwards + + idCounter.set( (uint64_t)currentSysTimeSecs << STORAGETK_FILEID_TIMESTAMP_SHIFTBITS); + } + + /** + * According to linux-src/include/linux/fs.h we can convert from the file mode as provided in + * 'struct stat' by the field 'st_mode' to the file type as given by + * 'struct dirent' in the field 'd_type' by some bit magic operations + */ + static int modeToDentryType(int mode) + { + int dentryType = (mode >> 12) & 15; + + return dentryType; + } + + static void getHashes(const std::string hashStr, size_t numHashesLevel1, + size_t numHashesLevel2, uint16_t& outHashLevel1, uint16_t& outHashLevel2) + { + uint32_t checksum = HashTk::hsieh32(hashStr.c_str(), hashStr.length() ); + + outHashLevel1 = ((uint16_t)(checksum >> 16) ) % numHashesLevel1; + outHashLevel2 = ((uint16_t) checksum) % numHashesLevel2; + } + + + static unsigned getChunkHash(const std::string id, + size_t numHashesLevel1, size_t numHashesLevel2) + { + uint16_t hashLevel1; + uint16_t hashLevel2; + + getHashes(id, numHashesLevel1, numHashesLevel2, hashLevel1, hashLevel2); + + unsigned outHash = ( (unsigned (hashLevel1)) << 16) + hashLevel2; + + return outHash; + } + + /** + * Get complete path to entryID for chunk files. + * + * Note: computes hash based on entryID. + * + * @param outChunkDirPath may be NULL + * @return hashDir1/hashDir2/entryID + */ + static std::string getFileChunkPathV2(const std::string entryID, Path* outChunkDirPath) + { + std::string outChunkFilePath = getBaseHashPath(entryID, + CONFIG_CHUNK_LEVEL1_SUBDIR_NUM, CONFIG_CHUNK_LEVEL2_SUBDIR_NUM); + + if (outChunkDirPath) + { + // convert the path into a vector + *outChunkDirPath = outChunkFilePath; + *outChunkDirPath = outChunkDirPath->dirname(); + } + + return outChunkFilePath; + } + + static void timeStampToPath(std::string& timeStamp, + std::string& outYearMonthStr, std::string& outDayChar) + { + const size_t timeStampLastPos = timeStamp.length() - 1; + const ssize_t timeStampDayPos = timeStampLastPos - STORAGETK_TIMESTAMP_DAY_RPOS; + + if (unlikely(timeStampDayPos < 0) ) + outDayChar = "0"; + else + outDayChar = timeStamp.substr(timeStampLastPos - STORAGETK_TIMESTAMP_DAY_RPOS, 1); + + if (timeStampDayPos == 0) + outYearMonthStr = "0"; + else + { + // note: timeStampDayPos == timeStampYearMonthLen + outYearMonthStr = timeStamp.substr(0, timeStampDayPos); + } + } + + + /** + * Get complete path to entryID for chunk files. + * + * Note: computes hash based on pathInfo and entryID. + * + * @return + * chunkPathV2 (no orig feature): + * hashDir1/hashDir2/entryID + * chunkPathV3: + * uidX/level1/level2/parentID/entryID + */ + static std::string getFileChunkPath(const PathInfo* pathInfo, const std::string entryID) + { + std::string chunkPath; + + if (!pathInfo->hasOrigFeature() ) + { // 2012.10 style hash dir + chunkPath = getFileChunkPathV2(entryID, NULL); // old storage format + } + else + { // 2014.01 style path with user and timestamp dirs + + std::string uidStr = StringTk::uintToHexStr(pathInfo->getOrigUID() ); // uid-string + + std::string timeStamp; + StringTk::timeStampFromEntryID(pathInfo->getOrigParentEntryID(), timeStamp); + + std::string dayChar; + std::string yearMonthStr; + timeStampToPath(timeStamp, yearMonthStr, dayChar); + + chunkPath = + CONFIG_CHUNK_UID_PREFIX + uidStr + "/" + + yearMonthStr + "/" + // level 1 - approximately yymm + dayChar + "/" + // level 2 - approximately dd + pathInfo->getOrigParentEntryID() + "/" + + entryID; + } + + return chunkPath; + } + + + + /** + * Fill in outPath with the V3 chunk base path elements. + * (uid + time1Str + time2Str + parentID) + */ + static void getChunkDirV3Path(const PathInfo* pathInfo, Path& outPath) + { + std::string uidStr = StringTk::uintToHexStr(pathInfo->getOrigUID() ); // uid-string + + std::string timeStamp; + StringTk::timeStampFromEntryID(pathInfo->getOrigParentEntryID(), timeStamp); + + std::string dayChar; + std::string yearMonthStr; + timeStampToPath(timeStamp, yearMonthStr, dayChar); + + outPath /= CONFIG_CHUNK_UID_PREFIX + uidStr; + outPath /= yearMonthStr; + outPath /= dayChar; + outPath /= pathInfo->getOrigParentEntryID(); + } + + + /** + * @return path/hashDir1/hashDir2/fileName + */ + static std::string getHashPath(const std::string path, const std::string entryID, + size_t numHashesLevel1, size_t numHashesLevel2) + { + return path + "/" + getBaseHashPath(entryID, numHashesLevel1, numHashesLevel2); + } + + /** + * @return (hashDir1, hashDir2) + */ + static std::pair getHash(const std::string& entryID, + size_t numHashesLevel1, size_t numHashesLevel2) + { + uint16_t hashLevel1; + uint16_t hashLevel2; + + getHashes(entryID, numHashesLevel1, numHashesLevel2, hashLevel1, hashLevel2); + + return {hashLevel1, hashLevel2}; + } + + /** + * @return hashDir1/hashDir2/entryID + */ + static std::string getBaseHashPath(const std::string entryID, + size_t numHashesLevel1, size_t numHashesLevel2) + { + uint16_t hashLevel1; + uint16_t hashLevel2; + + getHashes(entryID, numHashesLevel1, numHashesLevel2, hashLevel1, hashLevel2); + + return StringTk::uint16ToHexStr(hashLevel1) + "/" + StringTk::uint16ToHexStr(hashLevel2) + + "/" + entryID; + } + + static void splitHashDirs(unsigned hashDirs, unsigned* firstLevelHashDirOut, + unsigned* secondLevelHashDirOut) + { + // from the incoming hash dir, we create a first level hash dir and a second level hash + // dir, by splitting the 32-bit integer into two halfs + *firstLevelHashDirOut = hashDirs >> 16; + *secondLevelHashDirOut = hashDirs & 65535; // cut of at 16-bit + } + + static unsigned mergeHashDirs(unsigned firstLevelhashDirNum, unsigned secondLevelHashDirNum) + { + // 32 bit integer; first 16 bit for first level hash, second 16 bit for second level + // this is enough space for the currently used hash dirs + unsigned hashDirNum = firstLevelhashDirNum << 16; // add first level + hashDirNum = hashDirNum + secondLevelHashDirNum; // add second level + + return hashDirNum; + } + + + /* + * convert from a entry type given from struct dirent to a BeeGFS DirEntryType + */ + static DirEntryType direntToDirEntryType(unsigned char d_type) + { + DirEntryType entryType; + + switch(d_type) + { + case DT_DIR: + entryType = DirEntryType_DIRECTORY; break; + case DT_REG: + entryType = DirEntryType_REGULARFILE; break; + case DT_BLK: + entryType = DirEntryType_BLOCKDEV; break; + case DT_CHR: + entryType = DirEntryType_CHARDEV; break; + case DT_FIFO: + entryType = DirEntryType_FIFO; break; + case DT_LNK: + entryType = DirEntryType_SYMLINK; break; + case DT_SOCK: + entryType = DirEntryType_SOCKET; break; + default: + entryType = DirEntryType_INVALID; break; + } + + return entryType; + } + + /** + * @param outFileExisted may be NULL, will be set to true if the file was actually created, + * false on error or if it already existed before. + */ + static bool createFile(std::string path, int* outErrno = NULL, bool* outFileCreated = NULL) + { + SAFE_ASSIGN(outFileCreated, false); + + int fd = open(path.c_str(), O_CREAT | O_EXCL, S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH); + + if(fd == -1) + { // error + SAFE_ASSIGN(outErrno, errno); + + if(errno == EEXIST) // exists is no error + return true; + + return false; + } + + close(fd); + + SAFE_ASSIGN(outFileCreated, true); + + return true; + } + +}; + diff --git a/common/source/common/toolkit/StringTk.cpp b/common/source/common/toolkit/StringTk.cpp new file mode 100644 index 0000000..55bc6ce --- /dev/null +++ b/common/source/common/toolkit/StringTk.cpp @@ -0,0 +1,424 @@ +#include "StringTk.h" +#include "Random.h" + +std::string StringTk::trim(const std::string s) +{ + ssize_t sLength = s.length(); + + ssize_t firstLeft = 0; + while( (firstLeft < sLength) && + (s[firstLeft]==' ' || s[firstLeft]=='\n' || s[firstLeft]=='\r' || s[firstLeft]=='\t') ) + firstLeft++; + + if(firstLeft == sLength) + return ""; // the string is empty or contains only space chars + + // the string contains at least one non-space char + + ssize_t lastRight = sLength - 1; + while(s[lastRight]==' ' || s[lastRight]=='\n' || s[lastRight]=='\r' || s[lastRight]=='\t') + lastRight--; + + return s.substr(firstLeft, lastRight - firstLeft + 1); +} + +/** + * Note: Does not perform any trimming or length checks to avoid empty elements, use explodeEx + * if you need trimming. + */ +void StringTk::explode(const std::string s, char delimiter, StringList* outList) +{ + explodeEx(s, delimiter, false, outList); +} + +/** + * Note: Does not perform any trimming or length checks to avoid empty elements, use explodeEx + * if you need trimming. + */ +void StringTk::explode(const std::string s, char delimiter, StringVector* outVec) +{ + explodeEx(s, delimiter, false, outVec); +} + +/** + * @param useTrim true to leave out empty elements and trim spaces + */ +void StringTk::explodeEx(const std::string s, char delimiter, bool useTrim, StringList* outList) +{ + std::string::size_type lastPos = (s.length() && (s[0] == delimiter) ) ? 0 : -1; + + // note: we ignore the first (empty) string if the input string starts with a delimiter. + // well actually, we ignore any emptry string... + + for(std::string::size_type currentPos = 1; ; ) + { + currentPos = s.find(delimiter, lastPos+1); + + if(currentPos == std::string::npos) + { + // add rest + std::string newElem = s.substr(lastPos+1); + + if(useTrim) + newElem = trim(newElem); + + if(!newElem.empty() ) + outList->push_back(newElem); + return; + } + + // add substring to outList (leave the delimiter positions out) + std::string newElem = s.substr(lastPos+1, currentPos-lastPos-1); // -1=last delimiter + + if(useTrim) + newElem = trim(newElem); + + if(!newElem.empty() ) + outList->push_back(newElem); + + lastPos = currentPos; + } +} + +/** + * @param useTrim true to leave out empty elements and trim spaces + */ +void StringTk::explodeEx(const std::string s, char delimiter, bool useTrim, StringVector* outVec) +{ + std::string::size_type lastPos = (s.length() && (s[0] == delimiter) ) ? 0 : -1; + + // note: we ignore the first (empty) string if the input string starts with a delimiter. + // well actually, we ignore any emptry string... + + for(std::string::size_type currentPos = 1; ; ) + { + currentPos = s.find(delimiter, lastPos+1); + + if(currentPos == std::string::npos) + { + // add rest + std::string newElem = s.substr(lastPos+1); + + if(useTrim) + newElem = trim(newElem); + + if(!newElem.empty() ) + outVec->push_back(newElem); + return; + } + + // add substring to outList (leave the delimiter positions out) + std::string newElem = s.substr(lastPos+1, currentPos-lastPos-1); // -1=last delimiter + + if(useTrim) + newElem = trim(newElem); + + if(!newElem.empty() ) + outVec->push_back(newElem); + + lastPos = currentPos; + } +} + +/* + * creates a delimiter-seperated list from a StringList + * + * @param delimiter a char to be used as delimiter + * @param inList a StringList + * @param useTrim true to leave out empty elements and trim spaces + */ +std::string StringTk::implode(char delimiter, StringList& inList, bool useTrim) +{ + std::string outStr; + + if(inList.empty() ) + { + return outStr; + } + + for(StringListIter iter = inList.begin(); iter != inList.end(); iter++) + { + std::string currentElem;; + + if(!useTrim) + currentElem = *iter; + else + { // trim and skip empty lines + currentElem = trim(*iter); + + if(currentElem.empty() ) + continue; + } + + outStr += currentElem; + outStr += delimiter; + } + + // resize string to strip the trailing delimiter + if(!outStr.empty() ) + outStr.resize(outStr.length()-1); + + return outStr; +} + +int StringTk::strToInt(const char* s) +{ + return atoi(s); // NOLINT users depend on shady error behaviour +} + +unsigned StringTk::strToUInt(const char* s) +{ + unsigned retVal = 0; + + sscanf(s, "%u", &retVal); // NOLINT users depend on shady error behaviour + + return retVal; +} + +unsigned StringTk::strHexToUInt(const char* s) +{ + unsigned retVal = 0; + + sscanf(s, "%X", &retVal); // NOLINT users depend on shady error behaviour + + return retVal; +} + +unsigned StringTk::strOctalToUInt(const char* s) +{ + unsigned retVal = 0; + + sscanf(s, "%o", &retVal); // NOLINT users depend on shady error behaviour + + return retVal; +} + + +int64_t StringTk::strToInt64(const char* s) +{ + return atoll(s); // NOLINT users depend on shady error behaviour +} + +uint64_t StringTk::strToUInt64(const char* s) +{ + unsigned long long retVal = 0; + + sscanf(s, "%Lu", &retVal); // NOLINT users depend on shady error behaviour + + return retVal; +} + +/** + * note: retruns true for empty strings, which is important for command line parsing, e.g. is user + * gives an argument like "enableXY" (with an implicit empty string) as short-hand for + * "enableXY=true" + */ +bool StringTk::strToBool(const char* s) +{ + return !(s[0]) || + !strcmp(s, "1") || + !strcasecmp(s, "y") || + !strcasecmp(s, "on") || + !strcasecmp(s, "yes") || + !strcasecmp(s, "true"); +} + +std::string StringTk::intToStr(int a) +{ + char aStr[24]; + snprintf(aStr, 24, "%d", a); + + return aStr; +} + +std::string StringTk::uintToStr(unsigned a) +{ + char aStr[24]; + snprintf(aStr, 24, "%u", a); + + return aStr; +} + +std::string StringTk::uintToHexStr(unsigned a) +{ + char aStr[24]; + snprintf(aStr, 24, "%X", a); + + return aStr; +} + +std::string StringTk::uint16ToHexStr(uint16_t a) +{ + char aStr[24]; + snprintf(aStr, 24, "%X", a); + + return aStr; +} + +std::string StringTk::int64ToStr(int64_t a) +{ + char aStr[24]; + snprintf(aStr, 24, "%qd", (long long)a); + + return aStr; +} + +std::string StringTk::uint64ToStr(uint64_t a) +{ + char aStr[24]; + snprintf(aStr, 24, "%qu", (unsigned long long)a); + + return aStr; +} + +std::string StringTk::uint64ToHexStr(uint64_t a) +{ + char aStr[24]; + snprintf(aStr, 24, "%qX", (unsigned long long)a); + + return aStr; +} + +std::string StringTk::doubleToStr(double a) +{ + char aStr[32]; + snprintf(aStr, 32, "%0.3lf", a); + + return aStr; +} + +/** + * @param precision number of digits after comma + */ +std::string StringTk::doubleToStr(double a, int precision) +{ + char aStr[32]; + snprintf(aStr, 32, "%.*lf", precision, a); + + return aStr; +} + +std::string StringTk::timespanToStr(int64_t timespan_seconds) +{ + int seconds = 0; + int minutes = 0; + int hours = 0; + int days = 0; + + std::string outStr; + + if (timespan_seconds >= 60) + { + minutes = (int)(timespan_seconds/60); + seconds = seconds % 60; + } + else + { + seconds = timespan_seconds; + } + + + outStr += intToStr(seconds) + " seconds"; + + + if (minutes >= 60) + { + hours = (int)(minutes/60); + minutes = minutes % 60; + } + + outStr.insert(0, intToStr(minutes) + " minutes, "); + + + if (hours >= 24) + { + days = (int)(hours/24); + hours = hours % 24; + } + + outStr.insert(0, intToStr(hours) + " hours, "); + outStr.insert(0, intToStr(days) + " days, "); + return outStr; +} + +std::string StringTk::uint16VecToStr(const UInt16Vector* vec) +{ + char delimiter = ','; + + std::string outStr; + + if(vec->empty()) + { + return outStr; + } + + for(UInt16VectorConstIter iter = vec->begin(); iter != vec->end(); iter++) + { + outStr += StringTk::uintToStr(*iter); + outStr += delimiter; + } + + outStr.resize(outStr.length()-1); + + return outStr; +} + +void StringTk::strToUint16Vec(std::string& s, UInt16Vector* outVec) +{ + char delimiter = ','; + + std::string::size_type lastPos = (s.length() && (s[0] == delimiter) ) ? 0 : -1; + + // note: empty strings are ignored + for(std::string::size_type currentPos = 1; ; ) + { + currentPos = s.find(delimiter, lastPos+1); + + if(currentPos == std::string::npos) + { + // add rest + std::string newElem = s.substr(lastPos+1); + if(newElem.length() > 0) + outVec->push_back((int16_t)StringTk::strToUInt(newElem)); + return; + } + + // add substring to outList (leave the delimiter positions out) + std::string newElem = s.substr(lastPos+1, currentPos-lastPos-1); // -1=last delimiter + if(newElem.length() > 0) + outVec->push_back((int16_t)StringTk::strToUInt(newElem)); + + lastPos = currentPos; + } +} + +/** + * Test whether a string only contains numeric characters. + * + * Note: Only tests, 0..9, not the negative charachter "-". + * + * @return true if string is not empty and contains only numeric characters. + */ +bool StringTk::isNumeric(const std::string testString) +{ + if(testString.empty() ) + return false; + + if(testString.find_first_not_of("0123456789") != std::string::npos) + return false; + + return true; +} + +void StringTk::genRandomAlphaNumericString(std::string& inOutString, const int length) +{ + Random rand; + + static const std::string possibleValues = + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz"; + + for (int i = 0; i < length; ++i) + inOutString.push_back(possibleValues[rand.getNextInRange(0, possibleValues.size() - 1) ] ); +} diff --git a/common/source/common/toolkit/StringTk.h b/common/source/common/toolkit/StringTk.h new file mode 100644 index 0000000..21abcac --- /dev/null +++ b/common/source/common/toolkit/StringTk.h @@ -0,0 +1,204 @@ +#pragma once + +#include +#include + +#include + +#define STRINGTK_STRING_NOT_FOUND_RET (std::string::npos) +#define STRINGTK_ID_SEPARATOR "-" + + +class StringTk +{ + public: + // manipulation + static std::string trim(const std::string s); + static void explode(const std::string s, char delimiter, StringList* outList); + static void explode(const std::string s, char delimiter, StringVector* outVec); + static void explodeEx(const std::string s, char delimiter, bool useTrim, StringList* outList); + static void explodeEx(const std::string s, char delimiter, bool useTrim, StringVector* outVec); + static std::string implode(char delimiter, StringList& inList, bool useTrim); + + // transformation + static int strToInt(const char* s); + static unsigned strToUInt(const char* s); + static unsigned strHexToUInt(const char* s); + static unsigned strOctalToUInt(const char* s); + static int64_t strToInt64(const char* s); + static uint64_t strToUInt64(const char* s); + static bool strToBool(const char* s); + static std::string intToStr(int a); + static std::string uintToStr(unsigned a); + static std::string uintToHexStr(unsigned a); + static std::string uint16ToHexStr(uint16_t a); + static std::string int64ToStr(int64_t a); + static std::string uint64ToHexStr(uint64_t a); + static std::string uint64ToStr(uint64_t a); + static std::string doubleToStr(double a); + static std::string doubleToStr(double a, int precision); + static std::string timespanToStr(int64_t timespan_seconds); + + static std::string uint16VecToStr(const UInt16Vector* vec); + static void strToUint16Vec(std::string& s, UInt16Vector* outVec); + + // inspection + static bool isNumeric(const std::string testString); + static void genRandomAlphaNumericString(std::string& inOutString, const int length); + + private: + StringTk() {} + + public: + // inliners + + static int strToInt(const std::string s) + { + return strToInt(s.c_str() ); + } + + static unsigned strToUInt(const std::string s) + { + return strToUInt(s.c_str() ); + } + + static unsigned strOctalToUInt(const std::string s) + { + return strOctalToUInt(s.c_str() ); + } + + static unsigned strHexToUInt(const std::string s) + { + return strHexToUInt(s.c_str() ); + } + + static int64_t strToInt64(const std::string s) + { + return strToInt64(s.c_str() ); + } + + static bool strToBool(const std::string s) + { + return strToBool(s.c_str() ); + } + + static double strToDouble(const std::string s) + { + return atof(s.c_str() ); + } + + static char* strncpyTerminated(char* dest, const char* src, size_t count) + { + // Note: The problem with strncpy() is that dest is not guaranteed to be zero-terminated. + + size_t srcLen = strlen(src); + + if(likely(count) ) + { + size_t copyLen = (srcLen >= count) ? (count - 1) : srcLen; + + memcpy(dest, src, copyLen); + dest[copyLen] = '\0'; + } + + return dest; + } + + /** + * Get the time-stamp part from an EntryID + */ + static FhgfsOpsErr timeStampFromEntryID(const std::string& inEntryID, std::string &outEntryID) + { + size_t firstSepPos = inEntryID.find(STRINGTK_ID_SEPARATOR); + if (firstSepPos == (size_t) STRINGTK_STRING_NOT_FOUND_RET) + { // special entryID, such as 'root' and 'lost+found', we return it as it is + outEntryID = inEntryID; + return FhgfsOpsErr_SUCCESS; + } + + size_t secondSepPos = inEntryID.find(STRINGTK_ID_SEPARATOR, firstSepPos + 1); + if (secondSepPos == (size_t) STRINGTK_STRING_NOT_FOUND_RET) + { // That should not happen, return it as it is, but the caller should log an error + outEntryID = inEntryID; + return FhgfsOpsErr_INTERNAL; + } + + // +1 and -1 to remove the separator '-' + outEntryID = inEntryID.substr(firstSepPos + 1, secondSepPos - firstSepPos - 1); + + return FhgfsOpsErr_SUCCESS; + } + + template + static std::string implode(const char* const delimiter, const std::vector& inVec) + { + size_t numElements = inVec.size(); + + if (numElements == 0) + return ""; + else + { + std::ostringstream os; + + if (numElements == 1) + { + os << inVec[0]; + } + else + { + std::copy(inVec.begin(), inVec.end() - 1, + std::ostream_iterator(os, delimiter)); + os << *inVec.rbegin(); + } + return os.str(); + } + } + + /* + * creates a delimiter-seperated list from an input container of elements and sptils lines if + * longer than maxLineLength + * + * @param delimiter a char to be used as delimiter + * @param elements the input container + * @param maxLineLength + * @return a vector of string representing lines (at least one element with an empty line is + * contained if input was empty) + */ + template + static StringVector implodeMultiLine(const Container& elements, char delimiter, + unsigned maxLineLength) + { + StringVector result; + std::string line; + + // add first element + auto iter = elements.begin(); + if (iter != elements.end()) + { + std::stringstream elementStr; + elementStr << *iter; + line += elementStr.str(); + ++iter; + } + + for ( ; iter != elements.end(); iter++) + { + line += delimiter; + + std::stringstream elementStr; + elementStr << *iter; + + if (line.length() + elementStr.tellp() + 1 > maxLineLength) + { + result.push_back(line); + line.clear(); + } + + line += elementStr.str(); + } + + result.push_back(line); + return result; + } +}; + diff --git a/common/source/common/toolkit/SynchronizedCounter.h b/common/source/common/toolkit/SynchronizedCounter.h new file mode 100644 index 0000000..3275377 --- /dev/null +++ b/common/source/common/toolkit/SynchronizedCounter.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include + +#include + +class SynchronizedCounter +{ + public: + SynchronizedCounter() + { + this->count = 0; + } + + private: + unsigned count; + + Mutex mutex; + Condition cond; + + public: + // inliners + void waitForCount(unsigned waitCount) + { + const std::lock_guard lock(mutex); + + while(count != waitCount) + cond.wait(&mutex); + } + + bool timedWaitForCount(unsigned waitCount, int timeoutMS) + { + const std::lock_guard lock(mutex); + + if (count != waitCount) + if ( (!cond.timedwait(&mutex, timeoutMS)) || (count < waitCount) ) + return false; + + return true; + } + + void incCount() + { + const std::lock_guard lock(mutex); + + count++; + + cond.broadcast(); + } + + void resetUnsynced() + { + count = 0; + } +}; + diff --git a/common/source/common/toolkit/TempFileTk.cpp b/common/source/common/toolkit/TempFileTk.cpp new file mode 100644 index 0000000..a039701 --- /dev/null +++ b/common/source/common/toolkit/TempFileTk.cpp @@ -0,0 +1,128 @@ +#include +#include + +#include +#include + +#include "TempFileTk.h" + +namespace TempFileTk { + +/** + * Stores data to a temporary file first, and then moves it over to the final filename. This + * ensures that there's always a consistent file on disk, even if the program is aborted halfway + * through the write process. + * @param filename the name of the final file. If the file exists, it will be overwritten. + * @param contents data to write to the file + * @param size size of contents (in bytes) + */ +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const void* contents, size_t size) +{ + // Need a copy of the string, because the Xs will be replaced by mkstemp. + std::string tmpname = filename + ".tmp-XXXXXX" + '\0'; // terminating 0 needed for c library calls + + LOG(GENERAL, DEBUG, "Storing file using temporary file.", filename, tmpname); + + // Open temp file + FDHandle fd(mkstemp(&tmpname[0])); + if (!fd.valid()) + { + auto e = errno; + LOG(GENERAL, ERR, "Could not open temporary file.", tmpname, sysErr); + return FhgfsOpsErrTk::fromSysErr(e); + } + + { + struct DeleteOnExit { + const char* file; + + ~DeleteOnExit() { + if (!file) + return; + + if (unlink(file) == 0) + return; + + LOG(GENERAL, ERR, "Failed to unlink tmpfile after error.", ("tmpname", file), sysErr); + } + } deleteOnExit = { tmpname.c_str() }; + + // Write to temp file + size_t written = 0; + while (written < size) + { + ssize_t writeRes = write(*fd, ((const char*) contents) + written, size - written); + if (writeRes == -1) + { + auto e = errno; + LOG(GENERAL, ERR, "Could not write to temporary file", tmpname, sysErr); + return FhgfsOpsErrTk::fromSysErr(e); + } + + written += writeRes; + } + + if (fsync(*fd) < 0) + { + LOG(GENERAL, ERR, "Could not write to temporary file", tmpname, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + // Move temp file over final file + int renameRes = rename(tmpname.c_str(), filename.c_str()); + if (renameRes == -1) + { + auto e = errno; + LOG(GENERAL, ERR, "Renaming failed.", ("from", tmpname), ("to", filename), sysErr); + return FhgfsOpsErrTk::fromSysErr(e); + } + + deleteOnExit.file = nullptr; + } + + // Sync directory + char* dirName = dirname(&tmpname[0]); // manpage says: dirname may modify the contents + FDHandle dirFd(open(dirName, O_DIRECTORY | O_RDONLY)); + if (!dirFd.valid()) + { + auto e = errno; + LOG(GENERAL, ERR, "Could not write to temporary file", tmpname, sysErr); + return FhgfsOpsErrTk::fromSysErr(e); + } + + if (fsync(*dirFd) < 0) + { + LOG(GENERAL, WARNING, "fsync of directory failed.", tmpname, sysErr); + // do not return an error here. the alternative would be to try and restore the old state, + // but that too might fail. it's easier and more reliable to just hope for the best now. + return FhgfsOpsErr_SUCCESS; + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Stores data to a temporary file first, and then moves it over to the final filename. This + * ensures that there's always a consistent file on disk, even if the program is aborted halfway + * through the write process. + * @param filename the name of the final file. If the file exists, it will be overwritten. + * @param contents data to write to the file + */ +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const std::vector& contents) +{ + return storeTmpAndMove(filename, &contents[0], contents.size()); +} + +/** + * Stores data to a temporary file first, and then moves it over to the final filename. This + * ensures that there's always a consistent file on disk, even if the program is aborted halfway + * through the write process. + * @param filename the name of the final file. If the file exists, it will be overwritten. + * @param contents data to write to the file + */ +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const std::string& contents) +{ + return storeTmpAndMove(filename, &contents[0], contents.size()); +} + +}; diff --git a/common/source/common/toolkit/TempFileTk.h b/common/source/common/toolkit/TempFileTk.h new file mode 100644 index 0000000..3570105 --- /dev/null +++ b/common/source/common/toolkit/TempFileTk.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace TempFileTk { + +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const void* contents, size_t size); + +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const std::vector& contents); +FhgfsOpsErr storeTmpAndMove(const std::string& filename, const std::string& contents); + +}; + diff --git a/common/source/common/toolkit/Time.cpp b/common/source/common/toolkit/Time.cpp new file mode 100644 index 0000000..fb8e5f9 --- /dev/null +++ b/common/source/common/toolkit/Time.cpp @@ -0,0 +1,50 @@ + +#include +#include "Time.h" +#include "TimeException.h" + +clockid_t Time::clockID = TIME_DEFAULT_CLOCK_ID; + + +/** + * Test the default clock type and switch to another type if it does not work + */ +bool Time::testClock() +{ + struct timespec now; + + while (true) + { + int getTimeRes = clock_gettime(clockID, &now); + if (getTimeRes) + { + if (clockID != TIME_SAFE_CLOCK_ID) + { + clockID = TIME_SAFE_CLOCK_ID; + continue; + } + + std::string sysErr = System::getErrString(); + throw TimeException("clock_gettime() does not work! Error: " + sysErr); + return false; // getting the time does not work, we need to throw an exception + } + else + return true; + } +} + +/** + * Test if the given clockID works. + */ +bool Time::testClockID(clockid_t clockID) +{ + struct timespec now; + + while (true) + { + int getTimeRes = clock_gettime(clockID, &now); + return getTimeRes == 0; + } +} + + diff --git a/common/source/common/toolkit/Time.h b/common/source/common/toolkit/Time.h new file mode 100644 index 0000000..ba705ec --- /dev/null +++ b/common/source/common/toolkit/Time.h @@ -0,0 +1,189 @@ +#pragma once + +#include +#include +#include + +#define TIME_SAFE_CLOCK_ID CLOCK_MONOTONIC // a clock-id we expect to always work + +#ifdef CLOCK_MONOTONIC_COARSE + #define TIME_DEFAULT_CLOCK_ID CLOCK_MONOTONIC_COARSE +#else + #define TIME_DEFAULT_CLOCK_ID CLOCK_MONOTONIC +#endif + + +/** + * This time class is based on a monotonic clock. + * If the system supports it, this class uses a fast, but coarse-grained clock source, which is only + * updated every few milliseconds. + * If you need a more fine-grained clock (with sub-millisecond precision), use class TimeFine. + */ +class Time +{ + public: + Time() + { + clock_gettime(clockID, &this->now); + } + + /** + * @param setZero true to set this time to zero, e.g. to mark as uninitialized or + * "very long ago"; false to skip time init completely (which is only useful for derived + * classes, which provide their own initialization for "this->now"). + */ + Time(bool setZero) + { + if(!setZero) + return; + + now.tv_sec = 0; + now.tv_nsec = 0; + } + + Time(struct timespec* t) + { + this->now = *t; + } + + Time(const Time& t) + { + this->now = t.now; + } + + virtual ~Time() {} // nothing to be done, just need a virtual destructor for derived classes + + + static bool testClock(); + static bool testClockID(clockid_t clockID); + + + protected: + struct timespec now; + + + private: + static clockid_t clockID; // clockID for normal time requests + + + public: + // inliners + bool operator == (const Time& t) const + { + return (now.tv_nsec == t.now.tv_nsec) && (now.tv_sec == t.now.tv_sec); + } + + bool operator != (const Time& t) const + { + return (now.tv_nsec != t.now.tv_nsec) || (now.tv_sec != t.now.tv_sec); + } + + Time& operator = (const Time& t) + { + if(this != &t) + this->now = t.now; + + return *this; + } + + bool operator < (const Time& t) const + { + if (now.tv_sec < t.now.tv_sec) + return true; + + if ((now.tv_sec == t.now.tv_sec) && (now.tv_nsec < t.now.tv_nsec)) + return true; + + return false; + } + + bool operator > (const Time& t) const + { + return t < *this; + } + + bool getIsZero() const + { + return !now.tv_sec && !now.tv_nsec; + } + + /** + * Add the given milli seconds to the current time. Used to calculate timeouts. + */ + void addMS(const int addTimeMS) + { + struct timespec newTime; + + newTime.tv_sec = now.tv_sec + addTimeMS / 1000; + newTime.tv_nsec = now.tv_nsec + (addTimeMS % 1000) * 1000000; + + // now add tv_nsec > 1000 to tv_sec + newTime.tv_sec = newTime.tv_sec + newTime.tv_nsec / (1000*1000*1000); + newTime.tv_nsec = newTime.tv_nsec % (1000*1000*1000); + + now = newTime; + } + + virtual void setToNow() + { + clock_gettime(clockID, &this->now); + } + + /** + * @return elapsed millisecs + */ + unsigned elapsedSinceMS(const Time* earlierT) const + { + unsigned secs = (now.tv_sec - earlierT->now.tv_sec) * 1000; + int micros = (now.tv_nsec - earlierT->now.tv_nsec) / 1000000; /* can also be negative, + so this must be signed */ + + return secs + micros; + } + + /** + * Note: Be careful with this to avoid integer overflows + * + * @return elapsed microsecs + */ + unsigned elapsedSinceMicro(const Time* earlierT) const + { + unsigned secs = (now.tv_sec - earlierT->now.tv_sec) * 1000000; + int micros = (now.tv_nsec - earlierT->now.tv_nsec) / 1000; /* can also be negative, + so this must be signed */ + + return secs + micros; + } + + /** + * @return elapsed millisecs + */ + virtual unsigned elapsedMS() const + { + return Time().elapsedSinceMS(this); + } + + virtual unsigned elapsedMicro() const + { + return Time().elapsedSinceMicro(this); + } + + static clockid_t getClockID() + { + return clockID; + } + + void getTimeSpec(struct timespec* outTime) + { + *outTime = now; + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->now.tv_sec) + % serdes::as(obj->now.tv_nsec); + } +}; + diff --git a/common/source/common/toolkit/TimeAbs.h b/common/source/common/toolkit/TimeAbs.h new file mode 100644 index 0000000..7104719 --- /dev/null +++ b/common/source/common/toolkit/TimeAbs.h @@ -0,0 +1,123 @@ +#pragma once + +#include +#include + +/** + * This time class is based on a non-monotonic clock (based on the epoch since 1970) and thus + * might jump forwards or backwards if the admin or a tool updates the system time. + */ +class TimeAbs +{ + public: + TimeAbs() + { + gettimeofday(&this->now, NULL); + } + + TimeAbs(struct timeval* t) + { + this->now = *t; + } + + TimeAbs(const TimeAbs& t) + { + this->now = t.now; + } + + + private: + struct timeval now; + + + public: + // inliners + bool operator == (const TimeAbs& t) const + { + return (now.tv_usec == t.now.tv_usec) && (now.tv_sec == t.now.tv_sec); + } + + bool operator != (const TimeAbs& t) const + { + return (now.tv_usec != t.now.tv_usec) || (now.tv_sec != t.now.tv_sec); + } + + TimeAbs& operator = (const TimeAbs& t) + { + if(this != &t) + this->now = t.now; + + return *this; + } + + void setToNow() + { + gettimeofday(&now, NULL); + } + + /** + * @return elapsed millisecs + */ + unsigned elapsedSinceMS(const TimeAbs* earlierT) const + { + unsigned secs = (now.tv_sec - earlierT->now.tv_sec) * 1000; + int micros = (now.tv_usec - earlierT->now.tv_usec) / 1000; /* can also be negative, + so this must be signed */ + + return secs + micros; + } + + /** + * Note: Be careful with this to avoid integer overflows + * + * @return elapsed microsecs + */ + unsigned elapsedSinceMicro(const TimeAbs* earlierT) const + { + unsigned secs = (now.tv_sec - earlierT->now.tv_sec) * 1000000; + int micros = (now.tv_usec - earlierT->now.tv_usec); /* can also be negative, + so this must be signed */ + + return secs + micros; + } + + /** + * @return elapsed millisecs + */ + unsigned elapsedMS() const + { + return TimeAbs().elapsedSinceMS(this); + } + + // getters & setters + struct timeval* getTimeval() + { + return &now; + } + + /** + * @return seconds since the epoch + */ + uint64_t getTimeS() + { + return now.tv_sec; + } + + /** + * @return milliseconds since the epoch + */ + uint64_t getTimeMS() + { + return (now.tv_sec * 1000LL) + (now.tv_usec / 1000LL); + } + + /** + * get microseconds part of the current second + */ + uint64_t getTimeMicroSecPart() + { + return now.tv_usec; + } + +}; + diff --git a/common/source/common/toolkit/TimeException.h b/common/source/common/toolkit/TimeException.h new file mode 100644 index 0000000..8e9d51a --- /dev/null +++ b/common/source/common/toolkit/TimeException.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +DECLARE_NAMEDEXCEPTION(TimeException, "TimeException") + + diff --git a/common/source/common/toolkit/TimeFine.h b/common/source/common/toolkit/TimeFine.h new file mode 100644 index 0000000..f061151 --- /dev/null +++ b/common/source/common/toolkit/TimeFine.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include + + +/** + * This time class is based on a fine-grained monotonic clock with sub-millisecond precision. + */ +class TimeFine : public Time +{ + public: + TimeFine() : Time(false) + { + clock_gettime(CLOCK_MONOTONIC, &this->now); + } + + + private: + + + public: + // inliners + + virtual void setToNow() + { + clock_gettime(CLOCK_MONOTONIC, &this->now); + } + + /** + * @return elapsed millisecs + */ + virtual unsigned elapsedMS() const + { + return TimeFine().elapsedSinceMS(this); + } + + virtual unsigned elapsedMicro() const + { + return TimeFine().elapsedSinceMicro(this); + } + +}; + diff --git a/common/source/common/toolkit/TimeTk.h b/common/source/common/toolkit/TimeTk.h new file mode 100644 index 0000000..787d7e4 --- /dev/null +++ b/common/source/common/toolkit/TimeTk.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +namespace TimeTk { + +using highest_resolution_steady_clock = + std::conditional::type; + +//static_assert (highest_resolution_steady_clock::is_steady, "steady clock should be steady"); + +namespace detail { + template + struct as_double_duration; + + template + struct as_double_duration> + { + using type = std::chrono::duration; + }; +} + +template + using as_double_duration = typename detail::as_double_duration::type; + +namespace detail { + + template + inline std::string chrono_unit_for_period () + { + if (Period::den == 1) + { + return "[" + std::to_string (Period::num) + "]s"; + } + else + { + return "[" + std::to_string (Period::num) + "/" + + std::to_string (Period::den) + "]s"; + } + } + +#define CHRONO_UNIT_FOR_SI_PERIOD(symbol_, ratio_) \ + template<> \ + inline std::string chrono_unit_for_period () \ + { \ + return #symbol_; \ + } + + CHRONO_UNIT_FOR_SI_PERIOD (ns, std::chrono::nanoseconds::period); + CHRONO_UNIT_FOR_SI_PERIOD (µs, std::chrono::microseconds::period); + CHRONO_UNIT_FOR_SI_PERIOD (ms, std::chrono::milliseconds::period); + CHRONO_UNIT_FOR_SI_PERIOD (s, std::chrono::seconds::period); + CHRONO_UNIT_FOR_SI_PERIOD (min, std::chrono::minutes::period); + CHRONO_UNIT_FOR_SI_PERIOD (hr, std::chrono::hours::period); + +#undef CHRONO_UNIT_FOR_SI_PERIOD +} // namespace detail + +template +std::ostream& operator<< (std::ostream& os, std::chrono::duration duration) +{ + return os << duration.count() << " " << detail::chrono_unit_for_period(); +} + + +template +struct print_nice_time_t { + Duration const& _duration; + print_nice_time_t (Duration const& duration) : _duration (duration) {} +}; + + +template +std::ostream& operator<< (std::ostream& os, print_nice_time_t const& x) { +#define NICE_CASE(unit_, count_) \ + if (x._duration > std::chrono::unit_ (count_)) \ + return os << std::chrono::duration_cast> (x._duration) + + NICE_CASE(hours, 3); + NICE_CASE(minutes, 3); + NICE_CASE(seconds, 3); + NICE_CASE(milliseconds, 3); + NICE_CASE(microseconds, 3); + +#undef NICE_CASE + + return os << x._duration; +} + +template +print_nice_time_t print_nice_time (Duration const& duration) { + return {duration}; +} + +} // namespace TimeTk diff --git a/common/source/common/toolkit/UiTk.cpp b/common/source/common/toolkit/UiTk.cpp new file mode 100644 index 0000000..9ff2f15 --- /dev/null +++ b/common/source/common/toolkit/UiTk.cpp @@ -0,0 +1,46 @@ +#include "UiTk.h" + +#include + +bool uitk::userYNQuestion(const std::string& question, + boost::optional defaultAnswer, + std::istream& input) +{ + while (true) { + std::cout << question + << " [" + << (defaultAnswer.value_or(false) ? 'Y' : 'y') + << "/" + << (defaultAnswer.value_or(true) ? 'n' : 'N') + << "] ? " << std::flush; + + // read answer from input stream + const auto answer = [&input] () { + std::string answer; + std::getline(input, answer); + boost::to_upper(answer); + boost::trim(answer); + return answer; + }(); + + if (answer.empty()) + { + if (defaultAnswer.is_initialized()) + { + return defaultAnswer.get(); + } + else + { + std::cin.clear(); + std::cout << std::endl; + } + } + + if ("Y" == answer || "YES" == answer) + return true; + + if ("N" == answer || "NO" == answer) + return false; + } + +} diff --git a/common/source/common/toolkit/UiTk.h b/common/source/common/toolkit/UiTk.h new file mode 100644 index 0000000..9447394 --- /dev/null +++ b/common/source/common/toolkit/UiTk.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include +#include + +namespace uitk { + +bool userYNQuestion(const std::string& question, + boost::optional defaultAnswer=boost::none, + std::istream& input=std::cin); + +} + diff --git a/common/source/common/toolkit/UnitTk.cpp b/common/source/common/toolkit/UnitTk.cpp new file mode 100644 index 0000000..54a64db --- /dev/null +++ b/common/source/common/toolkit/UnitTk.cpp @@ -0,0 +1,399 @@ +#include +#include "UnitTk.h" + +#include + + +const int64_t UNITTK_SIZE_CONVERT_FACTOR = 1024; +const int64_t UNITTK_ONE_KIBIBYTE = UNITTK_SIZE_CONVERT_FACTOR; +const int64_t UNITTK_ONE_MEBIBYTE = UNITTK_ONE_KIBIBYTE * UNITTK_SIZE_CONVERT_FACTOR; +const int64_t UNITTK_ONE_GIBIBYTE = UNITTK_ONE_MEBIBYTE * UNITTK_SIZE_CONVERT_FACTOR; +const int64_t UNITTK_ONE_TEBIBYTE = UNITTK_ONE_GIBIBYTE * UNITTK_SIZE_CONVERT_FACTOR; +const int64_t UNITTK_ONE_PEBIBYTE = UNITTK_ONE_TEBIBYTE * UNITTK_SIZE_CONVERT_FACTOR; +const int64_t UNITTK_ONE_EXBIBYTE = UNITTK_ONE_PEBIBYTE * UNITTK_SIZE_CONVERT_FACTOR; + +const int64_t UNITTK_ONE_MINUTE = 60; +const int64_t UNITTK_ONE_HOUR = UNITTK_ONE_MINUTE * 60; +const int64_t UNITTK_ONE_DAY = UNITTK_ONE_HOUR * 24; + + + +int64_t UnitTk::pebibyteToByte(double pebibyte) +{ + int64_t b = (int64_t) (pebibyte * UNITTK_ONE_PEBIBYTE); + return b; +} + +int64_t UnitTk::tebibyteToByte(double tebibyte) +{ + int64_t b = (int64_t) (tebibyte * UNITTK_ONE_TEBIBYTE); + return b; +} + +int64_t UnitTk::gibibyteToByte(double gibibyte) +{ + int64_t b = (int64_t) (gibibyte * UNITTK_ONE_GIBIBYTE); + return b; +} + +int64_t UnitTk::mebibyteToByte(double mebibyte) +{ + int64_t b = (int64_t) (mebibyte * UNITTK_ONE_MEBIBYTE); + return b; +} + +int64_t UnitTk::kibibyteToByte(double kibibyte) +{ + int64_t b = (int64_t) (kibibyte * UNITTK_ONE_KIBIBYTE); + return b; +} + +double UnitTk::byteToMebibyte(double byte) +{ + return (byte / UNITTK_ONE_MEBIBYTE); +} + +double UnitTk::byteToXbyte(int64_t bytes, std::string *outUnit) +{ + return byteToXbyte(bytes, outUnit, false); +} + +/** + * Convert number of bytes without a unit to the number of bytes with a unit (e.g. MiB for mebibyte) + */ +double UnitTk::byteToXbyte(int64_t bytes, std::string *outUnit, bool round) +{ + double res = bytes; + + short count = 0; + while ( (res > UNITTK_SIZE_CONVERT_FACTOR) && (count < 6) ) + { + res = res / UNITTK_SIZE_CONVERT_FACTOR; + count++; + } + + switch(count) + { + case 0: + *outUnit = "Byte"; + break; + case 1: + *outUnit = "KiB"; + break; + case 2: + *outUnit = "MiB"; + break; + case 3: + *outUnit = "GiB"; + break; + case 4: + *outUnit = "TiB"; + break; + case 5: + *outUnit = "PiB"; + break; + case 6: + *outUnit = "EiB"; + break; + } + + if(round) + { + res = floor(res * 10 + 0.5) / 10; + } + + return res; +} + +double UnitTk::mebibyteToXbyte(int64_t mebibyte, std::string *unit) +{ + return mebibyteToXbyte(mebibyte, unit, false); +} + +double UnitTk::mebibyteToXbyte(int64_t mebibyte, std::string *unit, bool round) +{ + double res = mebibyte; + + short count = 0; + while (res > UNITTK_SIZE_CONVERT_FACTOR && count<4) + { + res = (double)res / UNITTK_SIZE_CONVERT_FACTOR; + count++; + } + + switch(count) + { + case 0: + *unit = "MiB"; + break; + case 1: + *unit = "GiB"; + break; + case 2: + *unit = "TiB"; + break; + case 3: + *unit = "PiB"; + break; + case 4: + *unit = "EiB"; + break; + } + + if(round) + { + res = floor(res * 10 + 0.5) / 10; + } + + return res; +} + +/** + * Convert number of bytes with a given unit (e.g. mebibytes) to the number of bytes without a unit. + */ +int64_t UnitTk::xbyteToByte(double xbyte, std::string unit) +{ + double res = xbyte; + + if (unit == "KiB") + { + res = kibibyteToByte(res); + } + else + if (unit == "MiB") + { + res = mebibyteToByte(res); + } + else + if (unit == "GiB") + { + res = gibibyteToByte(res); + } + else + if (unit == "TiB") + { + res = tebibyteToByte(res); + } + else + if (unit == "PiB") + { + res = pebibyteToByte(res); + } + + return (int64_t)res; +} + +/** + * Transforms an integer with a unit appended to its non-human equivalent, + * e.g. "1K" will return 1024. + */ +int64_t UnitTk::strHumanToInt64(const char* s) +{ + size_t sLen = strlen(s); + + if(sLen < 2) + return StringTk::strToInt64(s); + + char unit = s[sLen-1]; + + if( (unit == 'P') || (unit == 'p') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_PEBIBYTE; + } + else + if( (unit == 'T') || (unit == 't') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_TEBIBYTE; + } + else + if( (unit == 'G') || (unit == 'g') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_GIBIBYTE; + } + else + if( (unit == 'M') || (unit == 'm') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_MEBIBYTE; + } + if( (unit == 'K') || (unit == 'k') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_KIBIBYTE; + } + else + return StringTk::strToInt64(s); +} + + +/** + * Prints the number with a unit appended, but only if it matches without a remainder + * (e.g. 1025 will not be printed as 1k) + */ +std::string UnitTk::int64ToHumanStr(int64_t a) +{ + char aStr[24]; + + if( ( (a >= UNITTK_ONE_EXBIBYTE) || (-a >= UNITTK_ONE_EXBIBYTE) ) && + ( (a % UNITTK_ONE_EXBIBYTE) == 0) ) + { + sprintf(aStr, "%qdE", (long long)a / UNITTK_ONE_EXBIBYTE); + } + else + if( ( (a >= UNITTK_ONE_PEBIBYTE) || (-a >= UNITTK_ONE_PEBIBYTE) ) && + ( (a % UNITTK_ONE_PEBIBYTE) == 0) ) + { + sprintf(aStr, "%qdP", (long long)a / UNITTK_ONE_PEBIBYTE); + } + else + if( ( (a >= UNITTK_ONE_TEBIBYTE) || (-a >= UNITTK_ONE_TEBIBYTE) ) && + ( (a % UNITTK_ONE_TEBIBYTE) == 0) ) + { + sprintf(aStr, "%qdT", (long long)a / UNITTK_ONE_TEBIBYTE); + } + else + if( ( (a >= UNITTK_ONE_GIBIBYTE) || (-a >= UNITTK_ONE_GIBIBYTE) ) && + ( (a % UNITTK_ONE_GIBIBYTE) == 0) ) + { + sprintf(aStr, "%qdG", (long long)a / UNITTK_ONE_GIBIBYTE); + } + else + if( ( (a >= UNITTK_ONE_MEBIBYTE) || (-a >= UNITTK_ONE_MEBIBYTE) ) && + ( (a % UNITTK_ONE_MEBIBYTE) == 0) ) + { + sprintf(aStr, "%qdM", (long long)a / UNITTK_ONE_MEBIBYTE); + } + else + if( ( (a >= UNITTK_ONE_KIBIBYTE) || (-a >= UNITTK_ONE_KIBIBYTE) ) && + ( (a % UNITTK_ONE_KIBIBYTE) == 0) ) + { + sprintf(aStr, "%qdK", (long long)a / UNITTK_ONE_KIBIBYTE); + } + else + sprintf(aStr, "%qd", (long long)a); + + return aStr; +} + +/** + * checks if the given human string is valid an can processed by strHumanToInt64() + * + * @param humanString the human string to validate + */ +bool UnitTk::isValidHumanString(std::string humanString) +{ + size_t humanStringLen = humanString.length(); + if(!humanStringLen) + return false; + + if(StringTk::isNumeric(humanString)) + return true; + + char unit = humanString.at(humanStringLen-1); + std::string value = humanString.substr(0, humanStringLen-1); + return StringTk::isNumeric(value) && ( (unit == 'E') || (unit == 'e') || + (unit == 'P') || (unit == 'p') || (unit == 'T') || (unit == 't') || + (unit == 'G') || (unit == 'g') || (unit == 'M') || (unit == 'm') || + (unit == 'K') || (unit == 'k') ); +} + +/** + * Transforms an integer with a time unit appended to its non-human equivalent in seconds, + * e.g. "1h" will return 3600. + */ +int64_t UnitTk::timeStrHumanToInt64(const char* s) +{ + size_t sLen = strlen(s); + + if(sLen < 2) + return StringTk::strToInt64(s); + + char unit = s[sLen-1]; + + if( (unit == 'D') || (unit == 'd') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_DAY; + } + else + if( (unit == 'H') || (unit == 'h') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_HOUR; + } + else + if( (unit == 'M') || (unit == 'm') ) + { + std::string sNoUnit = s; + sNoUnit.resize(sLen-1); + + return StringTk::strToInt64(sNoUnit) * UNITTK_ONE_MINUTE; + } + else + return StringTk::strToInt64(s); +} + +/** + * checks if the given human string is a valid time and can processed by timeStrHumanToInt64() + * + * @param humanString the human string to validate + */ +bool UnitTk::isValidHumanTimeString(std::string humanString) +{ + size_t humanStringLen = humanString.length(); + if(!humanStringLen) + return false; + + if(StringTk::isNumeric(humanString)) + return true; + + char unit = humanString.at(humanStringLen-1); + std::string value = humanString.substr(0, humanStringLen-1); + return StringTk::isNumeric(value) && ( (unit == 'd') || (unit == 'D') || (unit == 'H') || + (unit == 'h') || (unit == 'M') || (unit == 'm') || (unit == 'S') || (unit == 's') ); +} + +uint64_t UnitTk::quotaBlockCountToByte(uint64_t quotaBlockCount, QuotaBlockDeviceFsType type) +{ + static uint64_t quotaBlockSizeXFS = 512; + + // the quota block size for XFS is 512 bytes, for ext4 the quota block size is 1 byte + if(type == QuotaBlockDeviceFsType_XFS) + { + quotaBlockCount = quotaBlockCount * quotaBlockSizeXFS; + } + + return quotaBlockCount; +} + +/* + * Prints the quota block count as a number with a unit appended, but only if it matches without a + * remainder. A quota block has a size of 1024 B. + */ +std::string UnitTk::quotaBlockCountToHumanStr(uint64_t quotaBlockCount, QuotaBlockDeviceFsType type) +{ + std::string unit; + + quotaBlockCount = quotaBlockCountToByte(quotaBlockCount, type); + + double value = byteToXbyte(quotaBlockCount, &unit); + + return StringTk::doubleToStr(value) + " " + unit; +} diff --git a/common/source/common/toolkit/UnitTk.h b/common/source/common/toolkit/UnitTk.h new file mode 100644 index 0000000..1823980 --- /dev/null +++ b/common/source/common/toolkit/UnitTk.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include + + + +class UnitTk +{ + public: + static int64_t pebibyteToByte(double pebibyte); + static int64_t tebibyteToByte(double tebibyte); + static int64_t gibibyteToByte(double gibibyte); + static int64_t mebibyteToByte(double mebibyte); + static int64_t kibibyteToByte(double kibibyte); + static double byteToMebibyte(double byte); + static double byteToXbyte(int64_t bytes, std::string *outUnit); + static double byteToXbyte(int64_t bytes, std::string *outUnit, bool round); + static double mebibyteToXbyte(int64_t mebibyte, std::string *unit); + static double mebibyteToXbyte(int64_t mebibyte, std::string *unit, bool round); + static int64_t xbyteToByte(double xbyte, std::string unit); + + static int64_t strHumanToInt64(const char* s); + static std::string int64ToHumanStr(int64_t a); + static bool isValidHumanString(std::string humanString); + + static int64_t timeStrHumanToInt64(const char* s); + static bool isValidHumanTimeString(std::string humanString); + + static uint64_t quotaBlockCountToByte(uint64_t quotaBlockCount, QuotaBlockDeviceFsType type); + static std::string quotaBlockCountToHumanStr(uint64_t quotaBlockCount, + QuotaBlockDeviceFsType type); + + private: + UnitTk() {}; + + public: + // inliners + static int64_t strHumanToInt64(std::string s) + { + return strHumanToInt64(s.c_str() ); + } + + static int64_t timeStrHumanToInt64(std::string s) + { + return timeStrHumanToInt64(s.c_str() ); + } +}; + + diff --git a/common/source/common/toolkit/ZipIterator.h b/common/source/common/toolkit/ZipIterator.h new file mode 100644 index 0000000..6d8d6ec --- /dev/null +++ b/common/source/common/toolkit/ZipIterator.h @@ -0,0 +1,408 @@ +#pragma once + +#include + +/** + * Triple helper struct to be used as value_type of ZipIterator with three elements. + */ +template +struct triple +{ + typedef F first_type; + typedef S second_type; + typedef T third_type; + + F first; + S second; + T third; + + triple() + { } + + triple(F first, S second, T third) + : first(first), second(second), third(third) + { } + + triple& operator=(const triple& other) + { + first = other.first; + second = other.second; + third = other.third; + + return *this; + } + + bool operator!=(const triple& rhs) + { + return !(*this == rhs); + } + + bool operator<(const triple& rhs) + { + if (this->first < rhs.first) + return true; + else + if (rhs.first < this->first) + return false; + else + if (this->second < rhs.second) + return true; + else + if (rhs.second < this->second) + return false; + else + if (this->third < rhs.third) + return true; + else + return false; + } + + bool operator<=(const triple& rhs) + { + return (*this == rhs) || (*this < rhs); + } + + bool operator>(const triple& rhs) + { + return !(*this <= rhs); + } + + bool operator>=(const triple& rhs) + { + return !(*this < rhs); + } +}; + +/** + * 3-valued zip iterator. + * Holds a triple of pointers to value_type of the contained iterators. + */ +template +class ZipIterator +{ + public: + // Public typedefs. + typedef std::ptrdiff_t difference_type; + typedef triple + value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::input_iterator_tag iterator_category; + + ZipIterator(F first_iter, S second_iter, T third_iter) + : first_iter(first_iter), + second_iter(second_iter), + third_iter(third_iter) + { } + + private: + F first_iter; + S second_iter; + T third_iter; + + // The most recently constructed value triple. + value_type value; + + public: + ZipIterator& operator++() + { + ++first_iter; + ++second_iter; + ++third_iter; + + return *this; + } + + bool operator==(const ZipIterator& other) const + { + return first_iter == other.first_iter + && second_iter == other.second_iter + && third_iter == other.third_iter; + } + + bool operator!=(const ZipIterator& other) const + { + return !(*this == other); + } + + reference operator*() + { + value = value_type(&*first_iter, &*second_iter, &*third_iter); + return value; + } + + pointer operator->() + { + return &operator*(); + } +}; + +/** + * 2-valued zip itarator. + * Holds a pair of pointers to value_types of the contained iterators. + */ +template +class ZipIterator +{ + public: + // Public typedefs. + typedef std::ptrdiff_t difference_type; + typedef std::pair value_type; + typedef value_type* pointer; + typedef value_type& reference; + typedef std::input_iterator_tag iterator_category; + + ZipIterator(F first_iter, S second_iter) + : first_iter(first_iter), second_iter(second_iter) + { } + + private: + F first_iter; + S second_iter; + + // Most recently constructed value pair. + value_type value; + + public: + ZipIterator& operator++() + { + ++first_iter; + ++second_iter; + + return *this; + } + + bool operator==(const ZipIterator& other) const + { + return first_iter == other.first_iter && second_iter == other.second_iter; + } + + bool operator!=(const ZipIterator& other) const + { + return !(*this == other); + } + + reference operator*() + { + value = value_type(&*first_iter, &*second_iter); + return value; + } + + pointer operator->() + { + return &operator*(); + } +}; + +/** + * Iterator range for 3-valued zip iterator. + */ +template +class ZipIterRange +{ + public: + typedef ZipIterator + value_type; + + /** + * Construct from container types - initializes the range with ZipIterator(3)'s to begin() and + * end() of the containers. + */ + ZipIterRange(F& first, S& second, T& third) + : it(first.begin(), second.begin(), third.begin() ), + end(first.end(), second.end(), third.end() ) + { } + + private: + value_type it; + value_type end; + + public: + /** + * Convenience access method to the front of the range. + */ + value_type& operator()() + { + return it; + } + + /** + * Check for empty range. + */ + bool empty() + { + return it == end; + } + + /** + * Increment front of range. + * Using this, a for-loop over the range can be implemented easily: + * for (ZipIterRange range(l1, l2, l3); !range.empty(); ++range) + * doSomethingWithElement(*(range()->first), *(range()->second), *(range()->third) ); + */ + ZipIterRange& operator++() + { + ++it; + return *this; + } +}; + +/** + * Iterator range for 2-valued zip iterator. + */ +template +class ZipIterRange +{ + public: + typedef ZipIterator value_type; + + /** + * Construct from container types - initializes the range with ZipIter(2)'s to begin() and + * end() of the containers. + */ + ZipIterRange(F& first, S& second) + : it(first.begin(), second.begin() ), + end(first.end(), second.end() ) + { } + + private: + value_type it; + value_type end; + + public: + /** + * Convenience access method to the front of the range. + */ + value_type& operator()() + { + return it; + } + + /** + * Check for empty range. + */ + bool empty() + { + return it == end; + } + + /** + * Increment front of range. + * Using this, a for-loop over the range can be implemented easily: + * for (ZipIterRange range(l1, l2, l3); !range.empty(); ++range) + * doSomethingWithElement(*(range()->first), *(range()->second), *(range()->third)); + */ + ZipIterRange& operator++() + { + ++it; + return *this; + } +}; + +/** + * Iterator range for 3-valued zip iterator consisting of const_iterators. + */ +template +class ZipConstIterRange +{ + public: + typedef ZipIterator + value_type; + + /** + * Construct from container types - initializes the range with ZipIterator(3)'s to begin() and + * end() of the containers. + */ + ZipConstIterRange(const F& first, const S& second, const T& third) + : it(first.begin(), second.begin(), third.begin() ), + end(first.end(), second.end(), third.end() ) + { } + + private: + value_type it; + value_type end; + + public: + /** + * Convenience access method to the front of the range. + */ + value_type& operator()() + { + return it; + } + + /** + * Check for empty range. + */ + bool empty() + { + return it == end; + } + + /** + * Increment front of range. + * Using this, a for-loop over the range can be implemented easily: + * for (ZipIterRange range(l1, l2, l3); !range.empty(); ++range) + * doSomethingWithElement(*(range()->first), *(range()->second), *(range()->third) ); + */ + ZipConstIterRange& operator++() + { + ++it; + return *this; + } +}; + +/** + * Iterator range for 2-valued zip iterator. + */ +template +class ZipConstIterRange +{ + public: + typedef ZipIterator value_type; + + /** + * Construct from container types - initializes the range with ZipIter(2)'s to begin() and + * end() of the containers. + */ + ZipConstIterRange(const F& first, const S& second) + : it(first.begin(), second.begin() ), + end(first.end(), second.end() ) + { } + + private: + value_type it; + value_type end; + + public: + /** + * Convenience access method to the front of the range. + */ + value_type& operator()() + { + return it; + } + + /** + * Check for empty range. + */ + bool empty() + { + return it == end; + } + + /** + * Increment front of range. + * Using this, a for-loop over the range can be implemented easily: + * for (ZipIterRange range(l1, l2, l3); !range.empty(); ++range) + * doSomethingWithElement(*(range()->first), *(range()->second), *(range()->third)); + */ + ZipConstIterRange& operator++() + { + ++it; + return *this; + } +}; + + diff --git a/common/source/common/toolkit/hash_library/sha256.cpp b/common/source/common/toolkit/hash_library/sha256.cpp new file mode 100644 index 0000000..0c6138c --- /dev/null +++ b/common/source/common/toolkit/hash_library/sha256.cpp @@ -0,0 +1,411 @@ +// ////////////////////////////////////////////////////////// +// sha256.cpp +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#include "sha256.h" + +// big endian architectures need #define __BYTE_ORDER __BIG_ENDIAN +#ifndef _MSC_VER +#include +#endif + + +/// same as reset() +SHA256::SHA256() +{ + reset(); +} + + +/// restart +void SHA256::reset() +{ + m_numBytes = 0; + m_bufferSize = 0; + + // according to RFC 1321 + m_hash[0] = 0x6a09e667; + m_hash[1] = 0xbb67ae85; + m_hash[2] = 0x3c6ef372; + m_hash[3] = 0xa54ff53a; + m_hash[4] = 0x510e527f; + m_hash[5] = 0x9b05688c; + m_hash[6] = 0x1f83d9ab; + m_hash[7] = 0x5be0cd19; +} + + +namespace +{ + inline uint32_t rotate(uint32_t a, uint32_t c) + { + return (a >> c) | (a << (32 - c)); + } + + inline uint32_t swap(uint32_t x) + { +#if defined(__GNUC__) || defined(__clang__) + return __builtin_bswap32(x); +#endif +#ifdef MSC_VER + return _byteswap_ulong(x); +#endif + + return (x >> 24) | + ((x >> 8) & 0x0000FF00) | + ((x << 8) & 0x00FF0000) | + (x << 24); + } + + // mix functions for processBlock() + inline uint32_t f1(uint32_t e, uint32_t f, uint32_t g) + { + uint32_t term1 = rotate(e, 6) ^ rotate(e, 11) ^ rotate(e, 25); + uint32_t term2 = (e & f) ^ (~e & g); //(g ^ (e & (f ^ g))) + return term1 + term2; + } + + inline uint32_t f2(uint32_t a, uint32_t b, uint32_t c) + { + uint32_t term1 = rotate(a, 2) ^ rotate(a, 13) ^ rotate(a, 22); + uint32_t term2 = ((a | b) & c) | (a & b); //(a & (b ^ c)) ^ (b & c); + return term1 + term2; + } +} + + +/// process 64 bytes +void SHA256::processBlock(const void* data) +{ + // get last hash + uint32_t a = m_hash[0]; + uint32_t b = m_hash[1]; + uint32_t c = m_hash[2]; + uint32_t d = m_hash[3]; + uint32_t e = m_hash[4]; + uint32_t f = m_hash[5]; + uint32_t g = m_hash[6]; + uint32_t h = m_hash[7]; + + // data represented as 16x 32-bit words + const uint32_t* input = (uint32_t*) data; + // convert to big endian + uint32_t words[64]; + int i; + for (i = 0; i < 16; i++) +#if defined(__BYTE_ORDER) && (__BYTE_ORDER != 0) && (__BYTE_ORDER == __BIG_ENDIAN) + words[i] = input[i]; +#else + words[i] = swap(input[i]); +#endif + + uint32_t x,y; // temporaries + + // first round + x = h + f1(e,f,g) + 0x428a2f98 + words[ 0]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x71374491 + words[ 1]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb5c0fbcf + words[ 2]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xe9b5dba5 + words[ 3]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x3956c25b + words[ 4]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x59f111f1 + words[ 5]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x923f82a4 + words[ 6]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xab1c5ed5 + words[ 7]; y = f2(b,c,d); e += x; a = x + y; + + // secound round + x = h + f1(e,f,g) + 0xd807aa98 + words[ 8]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x12835b01 + words[ 9]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x243185be + words[10]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x550c7dc3 + words[11]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x72be5d74 + words[12]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x80deb1fe + words[13]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x9bdc06a7 + words[14]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc19bf174 + words[15]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 24 words + for (; i < 24; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // third round + x = h + f1(e,f,g) + 0xe49b69c1 + words[16]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xefbe4786 + words[17]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x0fc19dc6 + words[18]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x240ca1cc + words[19]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x2de92c6f + words[20]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4a7484aa + words[21]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5cb0a9dc + words[22]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x76f988da + words[23]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 32 words + for (; i < 32; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fourth round + x = h + f1(e,f,g) + 0x983e5152 + words[24]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa831c66d + words[25]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xb00327c8 + words[26]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xbf597fc7 + words[27]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xc6e00bf3 + words[28]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd5a79147 + words[29]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x06ca6351 + words[30]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x14292967 + words[31]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 40 words + for (; i < 40; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // fifth round + x = h + f1(e,f,g) + 0x27b70a85 + words[32]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x2e1b2138 + words[33]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x4d2c6dfc + words[34]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x53380d13 + words[35]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x650a7354 + words[36]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x766a0abb + words[37]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x81c2c92e + words[38]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x92722c85 + words[39]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 48 words + for (; i < 48; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // sixth round + x = h + f1(e,f,g) + 0xa2bfe8a1 + words[40]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0xa81a664b + words[41]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0xc24b8b70 + words[42]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0xc76c51a3 + words[43]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0xd192e819 + words[44]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xd6990624 + words[45]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xf40e3585 + words[46]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x106aa070 + words[47]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 56 words + for (; i < 56; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // seventh round + x = h + f1(e,f,g) + 0x19a4c116 + words[48]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x1e376c08 + words[49]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x2748774c + words[50]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x34b0bcb5 + words[51]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x391c0cb3 + words[52]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0x4ed8aa4a + words[53]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0x5b9cca4f + words[54]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0x682e6ff3 + words[55]; y = f2(b,c,d); e += x; a = x + y; + + // extend to 64 words + for (; i < 64; i++) + words[i] = words[i-16] + + (rotate(words[i-15], 7) ^ rotate(words[i-15], 18) ^ (words[i-15] >> 3)) + + words[i-7] + + (rotate(words[i- 2], 17) ^ rotate(words[i- 2], 19) ^ (words[i- 2] >> 10)); + + // eigth round + x = h + f1(e,f,g) + 0x748f82ee + words[56]; y = f2(a,b,c); d += x; h = x + y; + x = g + f1(d,e,f) + 0x78a5636f + words[57]; y = f2(h,a,b); c += x; g = x + y; + x = f + f1(c,d,e) + 0x84c87814 + words[58]; y = f2(g,h,a); b += x; f = x + y; + x = e + f1(b,c,d) + 0x8cc70208 + words[59]; y = f2(f,g,h); a += x; e = x + y; + x = d + f1(a,b,c) + 0x90befffa + words[60]; y = f2(e,f,g); h += x; d = x + y; + x = c + f1(h,a,b) + 0xa4506ceb + words[61]; y = f2(d,e,f); g += x; c = x + y; + x = b + f1(g,h,a) + 0xbef9a3f7 + words[62]; y = f2(c,d,e); f += x; b = x + y; + x = a + f1(f,g,h) + 0xc67178f2 + words[63]; y = f2(b,c,d); e += x; a = x + y; + + // update hash + m_hash[0] += a; + m_hash[1] += b; + m_hash[2] += c; + m_hash[3] += d; + m_hash[4] += e; + m_hash[5] += f; + m_hash[6] += g; + m_hash[7] += h; +} + + +/// add arbitrary number of bytes +void SHA256::add(const void* data, size_t numBytes) +{ + const uint8_t* current = (const uint8_t*) data; + + if (m_bufferSize > 0) + { + while (numBytes > 0 && m_bufferSize < BlockSize) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } + } + + // full buffer + if (m_bufferSize == BlockSize) + { + processBlock(m_buffer); + m_numBytes += BlockSize; + m_bufferSize = 0; + } + + // no more data ? + if (numBytes == 0) + return; + + // process full blocks + while (numBytes >= BlockSize) + { + processBlock(current); + current += BlockSize; + m_numBytes += BlockSize; + numBytes -= BlockSize; + } + + // keep remaining bytes in buffer + while (numBytes > 0) + { + m_buffer[m_bufferSize++] = *current++; + numBytes--; + } +} + + +/// process final block, less than 64 bytes +void SHA256::processBuffer() +{ + // the input bytes are considered as bits strings, where the first bit is the most significant bit of the byte + + // - append "1" bit to message + // - append "0" bits until message length in bit mod 512 is 448 + // - append length as 64 bit integer + + // number of bits + size_t paddedLength = m_bufferSize * 8; + + // plus one bit set to 1 (always appended) + paddedLength++; + + // number of bits must be (numBits % 512) = 448 + size_t lower11Bits = paddedLength & 511; + if (lower11Bits <= 448) + paddedLength += 448 - lower11Bits; + else + paddedLength += 512 + 448 - lower11Bits; + // convert from bits to bytes + paddedLength /= 8; + + // only needed if additional data flows over into a second block + unsigned char extra[BlockSize]; + + // append a "1" bit, 128 => binary 10000000 + if (m_bufferSize < BlockSize) + m_buffer[m_bufferSize] = 128; + else + extra[0] = 128; + + size_t i; + for (i = m_bufferSize + 1; i < BlockSize; i++) + m_buffer[i] = 0; + for (; i < paddedLength; i++) + extra[i - BlockSize] = 0; + + // add message length in bits as 64 bit number + uint64_t msgBits = 8 * (m_numBytes + m_bufferSize); + // find right position + unsigned char* addLength; + if (paddedLength < BlockSize) + addLength = m_buffer + paddedLength; + else + addLength = extra + paddedLength - BlockSize; + + // must be big endian + *addLength++ = (unsigned char)((msgBits >> 56) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 48) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 40) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 32) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 24) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 16) & 0xFF); + *addLength++ = (unsigned char)((msgBits >> 8) & 0xFF); + *addLength = (unsigned char)( msgBits & 0xFF); + + // process blocks + processBlock(m_buffer); + // flowed over into a second block ? + if (paddedLength > BlockSize) + processBlock(extra); +} + + +/// return latest hash as 64 hex characters +std::string SHA256::getHash() +{ + // compute hash (as raw bytes) + unsigned char rawHash[HashBytes]; + getHash(rawHash); + + // convert to hex string + std::string result; + result.reserve(2 * HashBytes); + for (int i = 0; i < HashBytes; i++) + { + static const char dec2hex[16+1] = "0123456789abcdef"; + result += dec2hex[(rawHash[i] >> 4) & 15]; + result += dec2hex[ rawHash[i] & 15]; + } + + return result; +} + + +/// return latest hash as bytes +void SHA256::getHash(unsigned char buffer[SHA256::HashBytes]) +{ + // save old hash if buffer is partially filled + uint32_t oldHash[HashValues]; + for (int i = 0; i < HashValues; i++) + oldHash[i] = m_hash[i]; + + // process remaining bytes + processBuffer(); + + unsigned char* current = buffer; + for (int i = 0; i < HashValues; i++) + { + *current++ = (m_hash[i] >> 24) & 0xFF; + *current++ = (m_hash[i] >> 16) & 0xFF; + *current++ = (m_hash[i] >> 8) & 0xFF; + *current++ = m_hash[i] & 0xFF; + + // restore old hash + m_hash[i] = oldHash[i]; + } +} + + +/// compute SHA256 of a memory block +std::string SHA256::operator()(const void* data, size_t numBytes) +{ + reset(); + add(data, numBytes); + return getHash(); +} + + +/// compute SHA256 of a string, excluding final zero +std::string SHA256::operator()(const std::string& text) +{ + reset(); + add(text.c_str(), text.size()); + return getHash(); +} diff --git a/common/source/common/toolkit/hash_library/sha256.h b/common/source/common/toolkit/hash_library/sha256.h new file mode 100644 index 0000000..aeaf314 --- /dev/null +++ b/common/source/common/toolkit/hash_library/sha256.h @@ -0,0 +1,78 @@ +// ////////////////////////////////////////////////////////// +// sha256.h +// Copyright (c) 2014,2015 Stephan Brumme. All rights reserved. +// see http://create.stephan-brumme.com/disclaimer.html +// + +#pragma once + +//#include "hash.h" +#include + +// define fixed size integer types +#ifdef _MSC_VER +// Windows +typedef unsigned __int8 uint8_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#else +// GCC +#include +#endif + + +/// compute SHA256 hash +/** Usage: + SHA256 sha256; + std::string myHash = sha256("Hello World"); // std::string + std::string myHash2 = sha256("How are you", 11); // arbitrary data, 11 bytes + + // or in a streaming fashion: + + SHA256 sha256; + while (more data available) + sha256.add(pointer to fresh data, number of new bytes); + std::string myHash3 = sha256.getHash(); + */ +class SHA256 //: public Hash +{ +public: + /// split into 64 byte blocks (=> 512 bits), hash is 32 bytes long + enum { BlockSize = 512 / 8, HashBytes = 32 }; + + /// same as reset() + SHA256(); + + /// compute SHA256 of a memory block + std::string operator()(const void* data, size_t numBytes); + /// compute SHA256 of a string, excluding final zero + std::string operator()(const std::string& text); + + /// add arbitrary number of bytes + void add(const void* data, size_t numBytes); + + /// return latest hash as 64 hex characters + std::string getHash(); + /// return latest hash as bytes + void getHash(unsigned char buffer[HashBytes]); + + /// restart + void reset(); + +private: + /// process 64 bytes + void processBlock(const void* data); + /// process everything left in the internal buffer + void processBuffer(); + + /// size of processed data in bytes + uint64_t m_numBytes; + /// valid bytes in m_buffer + size_t m_bufferSize; + /// bytes not processed yet + uint8_t m_buffer[BlockSize]; + + enum { HashValues = HashBytes / 4 }; + /// hash, stored as integers + uint32_t m_hash[HashValues]; +}; diff --git a/common/source/common/toolkit/poll/PollList.cpp b/common/source/common/toolkit/poll/PollList.cpp new file mode 100644 index 0000000..c849089 --- /dev/null +++ b/common/source/common/toolkit/poll/PollList.cpp @@ -0,0 +1,26 @@ +#include "PollList.h" + +void PollList::add(Pollable* pollable) +{ + pollMap.insert(PollMapVal(pollable->getFD(), pollable) ); +} + +void PollList::remove(Pollable* pollable) +{ + removeByFD(pollable->getFD() ); +} + +void PollList::removeByFD(int fd) +{ + pollMap.erase(fd); +} + +Pollable* PollList::getPollableByFD(int fd) +{ + PollMapIter iter = pollMap.find(fd); + + if(iter == pollMap.end() ) + return NULL; + + return iter->second; +} diff --git a/common/source/common/toolkit/poll/PollList.h b/common/source/common/toolkit/poll/PollList.h new file mode 100644 index 0000000..9daec9e --- /dev/null +++ b/common/source/common/toolkit/poll/PollList.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +#include + +typedef std::map PollMap; +typedef PollMap::iterator PollMapIter; +typedef PollMap::value_type PollMapVal; + +class PollList +{ + public: + void add(Pollable* pollable); + void remove(Pollable* pollable); + void removeByFD(int fd); + Pollable* getPollableByFD(int fd); + + private: + PollMap pollMap; + + public: + // getters & setters + PollMap* getPollMap() + { + return &pollMap; + } + + +}; + diff --git a/common/source/common/toolkit/poll/Pollable.h b/common/source/common/toolkit/poll/Pollable.h new file mode 100644 index 0000000..e0aefa6 --- /dev/null +++ b/common/source/common/toolkit/poll/Pollable.h @@ -0,0 +1,13 @@ +#pragma once + +class Pollable +{ + public: + virtual ~Pollable() {} + + /** + * @return the filedescriptor that can be used with poll() and select() + */ + virtual int getFD() const = 0; +}; + diff --git a/common/source/common/toolkit/serialization/Byteswap.h b/common/source/common/toolkit/serialization/Byteswap.h new file mode 100644 index 0000000..dbd8374 --- /dev/null +++ b/common/source/common/toolkit/serialization/Byteswap.h @@ -0,0 +1,54 @@ +/* + * Big- and little-endian byte swapping. + */ + +#pragma once + +#include + +// byte order transformation macros for 16-, 32-, 64-bit types + +inline uint16_t byteswap16(uint16_t u) +{ + uint16_t high = u >> 8; + uint16_t low = u & 0xFF; + + return high | (low << 8); +} + +inline uint32_t byteswap32(uint32_t u) +{ + uint32_t high = u >> 16; + uint32_t low = u & 0xFFFF; + + return byteswap16(high) | (uint32_t(byteswap16(low) ) << 16); +} + +inline uint64_t byteswap64(uint64_t u) +{ + uint64_t high = u >> 32; + uint64_t low = u & 0xFFFFFFFF; + + return byteswap32(high) | (uint64_t(byteswap32(low) ) << 32); +} + +#if BYTE_ORDER == BIG_ENDIAN // BIG_ENDIAN + + #define HOST_TO_LE_16(value) (::byteswap16(value) ) + #define HOST_TO_LE_32(value) (::byteswap32(value) ) + #define HOST_TO_LE_64(value) (::byteswap64(value) ) + +#else // LITTLE_ENDIAN + + #define HOST_TO_LE_16(value) (value) + #define HOST_TO_LE_32(value) (value) + #define HOST_TO_LE_64(value) (value) + +#endif // BYTE_ORDER + + +#define LE_TO_HOST_16(value) HOST_TO_LE_16(value) +#define LE_TO_HOST_32(value) HOST_TO_LE_32(value) +#define LE_TO_HOST_64(value) HOST_TO_LE_64(value) + + diff --git a/common/source/common/toolkit/serialization/Serialization.h b/common/source/common/toolkit/serialization/Serialization.h new file mode 100644 index 0000000..bba72e4 --- /dev/null +++ b/common/source/common/toolkit/serialization/Serialization.h @@ -0,0 +1,991 @@ +#pragma once + +#include +#include + +#include "Byteswap.h" + +#include +#include +#include +#include +#include +#include + +#define SERIALIZATION_NICLISTELEM_NAME_SIZE (16) +#define SERIALIZATION_CHUNKINFOLISTELEM_ID_SIZE (96) +#define SERIALIZATION_CHUNKINFOLISTELEM_PATHSTR_SIZE (255) +#define SERIALIZATION_FILEINFOLISTELEM_OWNERNODE_SIZE (255) + + +class EntryInfo; +class Node; +class Path; +class QuotaData; +class StorageTargetInfo; +struct HighResolutionStats; +struct NicAddress; + +template +struct ListSerializationHasLength : boost::true_type {}; + +template<> +struct ListSerializationHasLength : boost::false_type {}; + +template +struct MapSerializationHasLength : boost::false_type {}; + +template +struct IsSerdesPrimitive { + typedef typename boost::decay::type value_type; + enum { + value = + boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + || boost::is_same::value + }; +}; + +template +struct SerializeAs {}; + +class Serializer +{ + public: + Serializer() + : buffer(NULL), bufferSize(-1), bufferOffset(0) + {} + + Serializer(void* buffer, unsigned bufferSize) + : buffer((char*) buffer), bufferSize(bufferSize), bufferOffset(0) + {} + + Serializer(Serializer&& other) + : buffer(NULL), bufferSize(-1), bufferOffset(0) + { + swap(other); + } + + Serializer& operator=(Serializer&& other) + { + Serializer(std::move(other)).swap(*this); + return *this; + } + + private: + char* buffer; + unsigned bufferSize; + unsigned bufferOffset; + + template + struct has_serializer : boost::true_type {}; + + Serializer(char* buffer, unsigned bufferSize, unsigned bufferOffset) + : buffer(buffer), bufferSize(bufferSize), bufferOffset(bufferOffset) + { + } + + public: + bool isReading() const { return false; } + bool isWriting() const { return true; } + + unsigned size() const + { + return this->bufferOffset; + } + + bool good() const + { + return this->bufferSize && this->bufferOffset <= this->bufferSize; + } + + void swap(Serializer& other) + { + std::swap(buffer, other.buffer); + std::swap(bufferSize, other.bufferSize); + std::swap(bufferOffset, other.bufferOffset); + } + + Serializer mark() const + { + return {buffer, bufferSize, bufferOffset}; + } + + void putBlock(const void* source, size_t length) + { + if(this->buffer + && likely( + this->bufferOffset + length >= this->bufferOffset + && this->bufferOffset + length <= this->bufferSize) ) + { + if (length > 0) // fixes memcpy nonnull warning + std::memcpy(this->buffer + this->bufferOffset, source, length); + } + else + this->bufferSize = 0; + + this->bufferOffset += length; + } + + void skip(unsigned size) + { + while(size > 0) + { + putBlock("\0\0\0\0\0\0\0\0", std::min(size, 8) ); + size -= std::min(size, 8); + } + } + + template + typename boost::enable_if_c< + IsSerdesPrimitive::value, + Serializer&>::type + operator%(Value value) + { + putIntegral(value); + return *this; + } + + template + typename boost::enable_if_c< + !IsSerdesPrimitive::value + && has_serializer::value, + Serializer&>::type + operator%(const Value& value) + { + Value::serialize(&value, *this); + return *this; + } + + template::type> + Serializer& operator%(const Value& value); + + public: + template + Serializer& operator%(const std::list& value) + { + putCollection::value>(value.begin(), value.end()); + return *this; + } + + template + Serializer& operator%(const std::vector& value) + { + putCollection::value>(value.begin(), value.end()); + return *this; + } + + template + Serializer& operator%(const std::set& value) + { + putCollection::value>(value.begin(), value.end()); + return *this; + } + + template + Serializer& operator%(const std::map& map) + { + putCollection::value>(map.begin(), map.end()); + return *this; + } + + template + Serializer& operator%(const std::pair& pair) + { + return *this + % pair.first + % pair.second; + } + + template + Serializer& operator%(const std::tuple& tuple) + { + putTuple(tuple, std::integral_constant()); + return *this; + } + + private: + void putIntegral(bool value) { putRaw(value ? 1 : 0); } + void putIntegral(char value) { putRaw(value); } + void putIntegral(uint8_t value) { putRaw(value); } + void putIntegral(int16_t value) { putRaw(HOST_TO_LE_16(value) ); } + void putIntegral(uint16_t value) { putRaw(HOST_TO_LE_16(value) ); } + void putIntegral(int32_t value) { putRaw(HOST_TO_LE_32(value) ); } + void putIntegral(uint32_t value) { putRaw(HOST_TO_LE_32(value) ); } + void putIntegral(int64_t value) { putRaw(HOST_TO_LE_64(value) ); } + void putIntegral(uint64_t value) { putRaw(HOST_TO_LE_64(value) ); } + + template + void putRaw(Raw value) + { + putBlock(&value, sizeof(value) ); + } + + template + void putCollection(const Iter begin, const Iter end) + { + Serializer atStart = mark(); + unsigned offsetAtStart = this->bufferOffset; + uint32_t elements = 0; // list is slow for size() + + if (HasLengthField) + *this % uint32_t(0); + + *this % uint32_t(0); // number of elements + + for(Iter it = begin; it != end; ++it) + { + *this % *it; + elements += 1; + } + + if (HasLengthField) + atStart % uint32_t(this->bufferOffset - offsetAtStart); + + atStart % elements; + } + + template + void putTuple(const std::tuple& tuple, std::integral_constant) + { + *this % std::get(tuple); + putTuple(tuple, std::integral_constant()); + } + + template + void putTuple(const std::tuple& tuple, std::integral_constant) + { + } +}; + +inline void swap(Serializer& a, Serializer& b) +{ + a.swap(b); +} + +class Deserializer +{ + public: + Deserializer(const void* buffer, unsigned bufferSize) + : buffer((const char*) buffer), bufferSize(bufferSize), bufferOffset(0) + {} + + Deserializer(Deserializer&& other) + : buffer(NULL), bufferSize(-1), bufferOffset(0) + { + swap(other); + } + + Deserializer& operator=(Deserializer&& other) + { + Deserializer(std::move(other)).swap(*this); + return *this; + } + + private: + const char* buffer; + unsigned bufferSize; + unsigned bufferOffset; + + template + struct has_deserializer : boost::true_type {}; + + Deserializer(char* buffer, unsigned bufferSize, unsigned bufferOffset) + : buffer(buffer), bufferSize(bufferSize), bufferOffset(bufferOffset) + { + } + + public: + bool isReading() const { return true; } + bool isWriting() const { return false; } + + const char* currentPosition() const + { + return this->buffer + this->bufferOffset; + } + + unsigned size() const + { + return this->bufferOffset; + } + + bool good() const + { + return this->bufferSize && this->bufferOffset <= this->bufferSize; + } + + void swap(Deserializer& other) + { + std::swap(buffer, other.buffer); + std::swap(bufferSize, other.bufferSize); + std::swap(bufferOffset, other.bufferOffset); + } + + void setBad() + { + this->bufferSize = 0; + } + + // Added this in 2024 -- isn't there an existing better way? + void parseEof() + { + if (bufferOffset != bufferSize) + setBad(); + } + + void getBlock(void* dest, size_t length) + { + if(likely(this->bufferOffset + length >= this->bufferOffset + && this->bufferOffset + length <= this->bufferSize) ) + { + if (length > 0) // fixes memcpy nonnull warning + std::memcpy(dest, this->buffer + this->bufferOffset, length); + } + else + setBad(); + + this->bufferOffset += length; + } + + template + typename boost::enable_if_c< + IsSerdesPrimitive::value, + Deserializer&>::type + operator%(Value& value) + { + getIntegral(value); + return *this; + } + + template + typename boost::enable_if_c< + !IsSerdesPrimitive::value + && has_deserializer::value, + Deserializer&>::type + operator%(Value& value) + { + Value::serialize(&value, *this); + return *this; + } + + template::type> + Deserializer& operator%(Value& value); + + void skip(size_t length) + { + if(unlikely(this->bufferOffset + length < this->bufferOffset + || this->bufferOffset + length > this->bufferSize) ) + setBad(); + + this->bufferOffset += length; + } + + public: + template + Deserializer& operator%(std::list& value) + { + getCollection(value); + return *this; + } + + template + Deserializer& operator%(std::vector& value) + { + getCollection(value); + return *this; + } + + template + Deserializer& operator%(std::set& value) + { + getCollection(value); + return *this; + } + + template + Deserializer& operator%(std::map& map) + { + getMap(map); + return *this; + } + + template + Deserializer& operator%(std::pair& pair) + { + return *this + % pair.first + % pair.second; + } + + template + Deserializer& operator%(std::tuple& tuple) + { + getTuple(tuple, std::integral_constant()); + return *this; + } + + private: + void getIntegral(bool& value) { value = getRaw(); } + void getIntegral(char& value) { value = getRaw(); } + void getIntegral(uint8_t& value) { value = getRaw(); } + void getIntegral(uint16_t& value) { value = LE_TO_HOST_16(getRaw() ); } + void getIntegral(uint32_t& value) { value = LE_TO_HOST_32(getRaw() ); } + void getIntegral(uint64_t& value) { value = LE_TO_HOST_64(getRaw() ); } + + void getIntegral(int16_t& value) + { + value = bitcast(LE_TO_HOST_16(getRaw() ) ); + } + + void getIntegral(int32_t& value) + { + value = bitcast(LE_TO_HOST_32(getRaw() ) ); + } + + void getIntegral(int64_t& value) + { + value = bitcast(LE_TO_HOST_64(getRaw() ) ); + } + + template + Raw getRaw() + { + Raw value = 0; + getBlock(&value, sizeof(value) ); + return value; + } + + template + static Out bitcast(In in) + { + BOOST_STATIC_ASSERT(sizeof(In) == sizeof(Out) ); + Out out; + memcpy(&out, &in, sizeof(in) ); + return out; + } + + private: + template + void getCollection(Collection& value) + { + unsigned offsetAtStart = this->bufferOffset; + + uint32_t totalLen; + uint32_t size; + + if(ListSerializationHasLength::value) + *this % totalLen; + + *this % size; + + if(unlikely(!good() ) ) + return; + + value.clear(); + + while(size > 0) + { + typename Collection::value_type item; + + *this % item; + if (unlikely(!good())) + return; + value.insert(value.end(), item); + + size -= 1; + } + + if(unlikely(!good() ) ) + return; + + if(!ListSerializationHasLength::value) + return; + + if(unlikely(totalLen != this->bufferOffset - offsetAtStart) ) + setBad(); + } + + template + void getMap(std::map& value) + { + unsigned offsetAtStart = this->bufferOffset; + + uint32_t totalLen; + uint32_t size; + + if (MapSerializationHasLength::value) + *this % totalLen; + + *this % size; + + if (unlikely(!good())) + return; + + value.clear(); + + while (size > 0) + { + KeyT key; + + *this % key; + if (unlikely(!good())) + return; + *this % value[key]; + if (unlikely(!good())) + return; + + size -= 1; + } + + if (unlikely(!good())) + return; + + if (!MapSerializationHasLength::value) + return; + + if (unlikely(totalLen != this->bufferOffset - offsetAtStart)) + setBad(); + } + + template + void getTuple(std::tuple& tuple, std::integral_constant) + { + *this % std::get(tuple); + getTuple(tuple, std::integral_constant()); + } + + template + void getTuple(std::tuple& tuple, std::integral_constant) + { + } +}; + +inline void swap(Deserializer& a, Deserializer& b) +{ + a.swap(b); +} + +template +class PadFieldTo +{ + public: + PadFieldTo(Direction& target, unsigned padding) + : target(target), offsetAtStart(target.size() ), padding(padding) + {} + + ~PadFieldTo() + { + unsigned rangeSize = target.size() - this->offsetAtStart; + if(rangeSize % this->padding) + target.skip(this->padding - rangeSize % this->padding); + } + + private: + Direction& target; + size_t offsetAtStart; + unsigned padding; + + PadFieldTo(const PadFieldTo&); + PadFieldTo& operator=(const PadFieldTo&); +}; + + + +namespace serdes { + +template +struct AsSer +{ + const Value* value; + + AsSer(const Value& value) : value(&value) {} + + friend Serializer& operator%(Serializer& ser, AsSer value) + { + return ser % Raw(*value.value); + } +}; + +template +struct AsDes +{ + Value* value; + + AsDes(Value& value) : value(&value) {} + + friend Deserializer& operator%(Deserializer& des, AsDes value) + { + Raw raw; + + des % raw; + *value.value = Value(raw); + return des; + } +}; + +template +inline AsSer as(const Value& value) +{ + return AsSer(value); +} + +template +inline AsDes as(Value& value) +{ + return AsDes(value); +} + + + +struct RawStringSer +{ + const char* data; + unsigned size; + unsigned align; + + RawStringSer(const char* data, unsigned size, unsigned align) + : data(data), size(size), align(align) + {} + + friend Serializer& operator%(Serializer& ser, const RawStringSer& str) + { + PadFieldTo field(ser, str.align); + ser % uint32_t(str.size); + ser.putBlock(str.data, str.size); + ser % char(0); + return ser; + } +}; + +struct RawStringDes +{ + const char** data; + unsigned* size; + unsigned align; + + RawStringDes(const char*& data, unsigned& size, unsigned align) + : data(&data), size(&size), align(align) + {} + + friend Deserializer& operator%(Deserializer& des, const RawStringDes& str) + { + PadFieldTo field(des, str.align); + uint32_t length; + + des % length; + if(unlikely(!des.good() ) ) + return des; + + *str.size = length; + *str.data = des.currentPosition(); + + des.skip(length + 1); + if(unlikely(!des.good() ) ) + return des; + + if( (*str.data)[length] != 0) + des.setBad(); + + return des; + } +}; + +inline RawStringSer rawString(const char* source, const unsigned& length, unsigned align = 1) +{ + return RawStringSer(source, length, align); +} + +inline RawStringDes rawString(const char*& source, unsigned& length, unsigned align = 1) +{ + return RawStringDes(source, length, align); +} + + + +struct StdString +{ + std::string* data; + unsigned align; + + StdString(std::string& data, unsigned align) : data(&data), align(align) {} + + friend Deserializer& operator%(Deserializer& des, const StdString& value) + { + const char* str; + unsigned len; + + // silence compiler warning. len will always be properly initialized if raw string read + // succeeds (i.e. des is good afterwards) + str = NULL; + len = 0; + + des % RawStringDes(str, len, value.align); + if(likely(des.good() ) ) + *value.data = std::string(str, str + len); + else + value.data->clear(); + + return des; + } +}; + +inline StdString stringAlign4(std::string& str) +{ + return StdString(str, 4); +} + +inline RawStringSer stringAlign4(const std::string& str) +{ + return RawStringSer(str.c_str(), str.size(), 4); +} + + + +template +struct BackedPtrSer +{ + const Object* source; + + BackedPtrSer(const Object& source) + : source(&source) + {} + + friend Serializer& operator%(Serializer& ser, const BackedPtrSer& value) + { + return ser % *value.source; + } +}; + +template::type> +struct BackedPtrDes +{ + Object** ptr; + Back* backing; + + BackedPtrDes(Object*& ptr, Back& backing) + : ptr(&ptr), backing(&backing) + {} + + static Object* get(Object& backing) + { + return &backing; + } + + template + static Object* get(std::unique_ptr& backing) + { + return backing.get(); + } + + friend Deserializer& operator%(Deserializer& des, BackedPtrDes value) + { + des % *value.backing; + *value.ptr = BackedPtrDes::get(*value.backing); + return des; + } +}; + +template +inline BackedPtrSer backedPtr(Source* const& source, const Backing& backing) +{ + return BackedPtrSer(*source); +} + +template +inline BackedPtrSer backedPtr(const Source* const& source, const Backing& backing) +{ + return BackedPtrSer(*source); +} + +template +inline BackedPtrDes backedPtr(Object*& ptr, Object& backing) +{ + return BackedPtrDes(ptr, backing); +} + +template +inline BackedPtrDes backedPtr(const Object*& ptr, Object& backing) +{ + return BackedPtrDes(ptr, backing); +} + +template +inline BackedPtrDes> + backedPtr(Object*& ptr, std::unique_ptr& backing) +{ + return BackedPtrDes>(ptr, backing); +} + + + +struct RawBlockSer +{ + const char* source; + uint32_t size; + + RawBlockSer(const char* source, uint32_t size) + : source(source), size(size) + {} + + friend Serializer& operator%(Serializer& ser, const RawBlockSer& block) + { + ser.putBlock(block.source, block.size); + return ser; + } +}; + +struct RawBlockDes +{ + const char** dest; + const uint32_t* size; + + RawBlockDes(const char*& block, const uint32_t& size) + : dest(&block), size(&size) + {} + + friend Deserializer& operator%(Deserializer& des, const RawBlockDes& block) + { + *block.dest = des.currentPosition(); + des.skip(*block.size); + return des; + } +}; + +inline RawBlockSer rawBlock(const char*const& block, const uint32_t& size) +{ + return RawBlockSer(block, size); +} + +inline RawBlockDes rawBlock(const char*& block, uint32_t& size) +{ + return RawBlockDes(block, size); +} + + + +template +struct AtomicAsSer +{ + Atomic const* value; + + AtomicAsSer(const Atomic& value) + : value(&value) + {} + + friend Serializer& operator%(Serializer& ser, const AtomicAsSer& value) + { + return ser % as(value.value->read() ); + } +}; + +template +struct AtomicAsDes +{ + Atomic* value; + + AtomicAsDes(Atomic& value) + : value(&value) + {} + + friend Deserializer& operator%(Deserializer& des, const AtomicAsDes& value) + { + As raw; + des % raw; + value.value->set(raw); + return des; + } +}; + +template +inline AtomicAsSer atomicAs(const Atomic& value) +{ + return AtomicAsSer(value); +} + +template +inline AtomicAsDes atomicAs(Atomic& value) +{ + return AtomicAsDes(value); +} + + + +template +Base& base(Derived* value) +{ + return *value; +} + +template +const Base& base(const Derived* value) +{ + return *value; +} + +} + +inline Serializer& operator%(Serializer& ser, const std::string& value) +{ + return ser % serdes::rawString(value.c_str(), value.size() ); +} + +inline Deserializer& operator%(Deserializer& des, std::string& value) +{ + return des % serdes::StdString(value, 1); +} + + + +template +Serializer& Serializer::operator%(const Value& value) +{ + return *this % serdes::as::type>(value); +} + +template +Deserializer& Deserializer::operator%(Value& value) +{ + return *this % serdes::as::type>(value); +} + + + +Serializer& operator%(Serializer& ser, const std::list& list); +Deserializer& operator%(Deserializer& des, std::list& list); + +Serializer& operator%(Serializer& ser, const std::vector& vec); +Deserializer& operator%(Deserializer& des, std::vector& vec); + +Serializer& operator%(Serializer& ser, const std::set& set); +Deserializer& operator%(Deserializer& des, std::set& set); + + + +template +inline ssize_t serializeIntoNewBuffer(const Object& object, boost::scoped_array& result) +{ + Serializer ser; + ser % object; + + ssize_t bufferLength = ser.size(); + + boost::scoped_array buffer(new (std::nothrow) char[bufferLength]); + if (!buffer) + return -1; + + ser = Serializer(buffer.get(), bufferLength); + ser % object; + + if (!ser.good()) + return -1; + + buffer.swap(result); + return ser.size(); +} + diff --git a/common/source/common/toolkit/serialization/SerializeStr.cpp b/common/source/common/toolkit/serialization/SerializeStr.cpp new file mode 100644 index 0000000..ff0672c --- /dev/null +++ b/common/source/common/toolkit/serialization/SerializeStr.cpp @@ -0,0 +1,108 @@ +#include "Serialization.h" + +template +static void serializeStringCollection(Serializer& ser, const Collection& value) +{ + Serializer atStart = ser.mark(); + uint32_t sizeAtStart = ser.size(); + uint32_t elements = 0; // list is slow for size() + + ser + % uint32_t(0) // total length field + % uint32_t(0); // number of elements + + for(typename Collection::const_iterator it = value.begin(), end = value.end(); it != end; ++it) + { + ser.putBlock(it->c_str(), it->size() ); + ser % char(0); + elements += 1; + } + + atStart + % uint32_t(ser.size() - sizeAtStart) + % elements; +} + +template +static void deserializeStringCollection(Deserializer& des, Collection& value) +{ + unsigned sizeAtStart = des.size(); + + uint32_t totalLen; + uint32_t size; + + des + % totalLen + % size; + + if(unlikely(!des.good() ) ) + return; + + value.clear(); + + uint32_t lengthRemaining = totalLen - (des.size() - sizeAtStart); + const char* rawStringArray = des.currentPosition(); + + if(lengthRemaining) + { + des.skip(lengthRemaining); + + if(unlikely(!des.good() ) ) + return; + + if(unlikely(rawStringArray[lengthRemaining - 1]) ) + { + des.setBad(); + return; + } + } + + while(size > 0 && lengthRemaining > 0) + { + std::string item(rawStringArray); + lengthRemaining -= item.size() + 1; + rawStringArray += item.size() + 1; + value.insert(value.end(), item); + size -= 1; + } + + if(unlikely(size > 0 || lengthRemaining > 0) ) + des.setBad(); +} + + +Serializer& operator%(Serializer& ser, const std::list& list) +{ + serializeStringCollection(ser, list); + return ser; +} + +Deserializer& operator%(Deserializer& des, std::list& list) +{ + deserializeStringCollection(des, list); + return des; +} + +Serializer& operator%(Serializer& ser, const std::vector& vec) +{ + serializeStringCollection(ser, vec); + return ser; +} + +Deserializer& operator%(Deserializer& des, std::vector& vec) +{ + deserializeStringCollection(des, vec); + return des; +} + +Serializer& operator%(Serializer& ser, const std::set& set) +{ + serializeStringCollection(ser, set); + return ser; +} + +Deserializer& operator%(Deserializer& des, std::set& set) +{ + deserializeStringCollection(des, set); + return des; +} diff --git a/common/tests/TestBitStore.cpp b/common/tests/TestBitStore.cpp new file mode 100644 index 0000000..b62cc80 --- /dev/null +++ b/common/tests/TestBitStore.cpp @@ -0,0 +1,251 @@ +#include +#include + +#include + +#include + + +#define TESTBITSTORE_MAX_BITSTORE_SIZE 242 +#define TESTBITSTORE_RANDOM_VALUES_COUNT 75 + +struct TestBitStoreBlockCountResult +{ + int blockCount; // the needed blockCount +}; + +/** + * The number of blocks required to store a certain number of bits. + */ +struct TestBitStoreBlockCountResult const __TESTBITSTORE_BLOCK_COUNT_RESULTS[] = +{ + {1 + ( ( (32*1)-1) / BitStore::LIMB_SIZE) }, // result for bits 1 to 32 + {1 + ( ( (32*2)-1) / BitStore::LIMB_SIZE) }, // result for bits 33 to 64 + {1 + ( ( (32*3)-1) / BitStore::LIMB_SIZE) }, // result for bits 65 to 96 + {1 + ( ( (32*4)-1) / BitStore::LIMB_SIZE) }, // result for bits 97 to 128 + {1 + ( ( (32*5)-1) / BitStore::LIMB_SIZE) }, // result for bits 129 to 160 + {1 + ( ( (32*6)-1) / BitStore::LIMB_SIZE) }, // result for bits 161 to 192 + {1 + ( ( (32*7)-1) / BitStore::LIMB_SIZE) }, // result for bits 193 to 224 + {1 + ( ( (32*8)-1) / BitStore::LIMB_SIZE) }, // result for bits 125 to 256 +}; + + + +class TestBitStore : public ::testing::Test { + protected: + BitStore::limb_type& lowerBits(BitStore& store) { return store.lowerBits; } + BitStore::limb_type* higherBits(BitStore& store) { return store.higherBits.get(); } +}; + +TEST_F(TestBitStore, calculateBitBlockCount) +{ + unsigned blockCount; + unsigned resultIndex = 0; + + for(int size = 1; size < TESTBITSTORE_MAX_BITSTORE_SIZE; size++) + { + if(size < 33) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[0].blockCount; + else + if(size < 65) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[1].blockCount; + else + if(size < 97) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[2].blockCount; + else + if(size < 129) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[3].blockCount; + else + if(size < 161) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[4].blockCount; + else + if(size < 193) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[5].blockCount; + else + if(size < 225) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[6].blockCount; + else + if(size < 257) + resultIndex = __TESTBITSTORE_BLOCK_COUNT_RESULTS[7].blockCount; + + blockCount = BitStore::calculateBitBlockCount(size); + + EXPECT_EQ(blockCount, resultIndex); + } +} + +TEST_F(TestBitStore, setSizeAndReset) +{ + const int initValue = 32896; + int size = 1; + bool reverse = false; + bool finished = false; + + BitStore store(size); + + while(finished) + { + int higherBitsBlockCount = BitStore::calculateBitBlockCount(size) - 1; + + //set some data to the bit store + lowerBits(store) = initValue; + + for(int blockIndex = 0; blockIndex < higherBitsBlockCount; blockIndex++) + { + higherBits(store)[blockIndex] = initValue; + } + + // set the new size and reset all values + store.setSize(size); + store.clearBits(); + + //check the lower bits + EXPECT_EQ(lowerBits(store), 0u); + + //check the higher bits + for(int blockIndex = 0; blockIndex < higherBitsBlockCount; blockIndex++) + EXPECT_EQ(higherBits(store)[blockIndex], 0u); + + if(size == TESTBITSTORE_MAX_BITSTORE_SIZE) + reverse = true; + + if(reverse) + size--; + else + size++; + + // + + // finish test or set the new size + if(reverse && size == 0) + finished = true; + } +} + +TEST_F(TestBitStore, getter) +{ + BitStore store(TESTBITSTORE_MAX_BITSTORE_SIZE); + + // check the getter for all bits + for(int index = 0; index < TESTBITSTORE_MAX_BITSTORE_SIZE; index++) + { + // set a bit + int blockIndexOfBit = index / BitStore::LIMB_SIZE; + BitStore::limb_type value = 1UL << (index % BitStore::LIMB_SIZE); + + if(blockIndexOfBit == 0) + lowerBits(store) = value; + else + higherBits(store)[blockIndexOfBit - 1] = value; + + // check if all bits contain the correct value + for(int testIndex = 0; testIndex < TESTBITSTORE_MAX_BITSTORE_SIZE; testIndex++) + { + if(testIndex != index) + EXPECT_FALSE(store.getBitNonAtomic(testIndex)); + else + EXPECT_TRUE(store.getBitNonAtomic(testIndex)); + } + + store.clearBits(); + } +} + +TEST_F(TestBitStore, setter) +{ + BitStore store(TESTBITSTORE_MAX_BITSTORE_SIZE); + int higherBitsBlockCount = BitStore::calculateBitBlockCount(TESTBITSTORE_MAX_BITSTORE_SIZE) - 1; + + // check the setter for all bits + for(int index = 0; index < TESTBITSTORE_MAX_BITSTORE_SIZE; index++) + { + store.setBit(index, true); + + // check if all bit blocks contains the correct value + int blockIndexOfBit = index / BitStore::LIMB_SIZE; + BitStore::limb_type neededValue = 1UL << (index % BitStore::LIMB_SIZE); + + // check lower bit block + if(blockIndexOfBit == 0) + EXPECT_EQ(lowerBits(store), neededValue); + else + EXPECT_EQ(lowerBits(store), 0u); + + // check higher bit blocks + for(int blockIndex = 0; blockIndex < higherBitsBlockCount; blockIndex++) + { + if(blockIndex != (blockIndexOfBit - 1) ) + EXPECT_EQ(higherBits(store)[blockIndex], 0u); + else + EXPECT_EQ(higherBits(store)[blockIndex], neededValue); + } + + store.clearBits(); + } +} + +static void checkSerialization(BitStore* store) +{ + BitStore secondStore; + + unsigned serialLen; + { + Serializer ser; + ser % *store; + serialLen = ser.size(); + } + + boost::scoped_array buf(new char[serialLen]); + + unsigned serializationRes; + { + Serializer ser(buf.get(), serialLen); + ser % *store; + serializationRes = ser.size(); + } + + ASSERT_EQ(serialLen, serializationRes); + + unsigned deserLen; + bool success; + { + Deserializer des(buf.get(), serialLen); + des % secondStore; + deserLen = des.size(); + success = des.good(); + } + + EXPECT_TRUE(success) << "BitStore deserialization failed."; + EXPECT_EQ(deserLen, serialLen); + EXPECT_EQ(*store, secondStore); +} + +TEST_F(TestBitStore, serialization) +{ + // check serialization for different BitStore size + for(int size = 1; size <= TESTBITSTORE_MAX_BITSTORE_SIZE; size++) + { + BitStore store(size); + + // check serialization for all bits, only one bit is set + for(int index = 0; index < size; index++) + { + store.setBit(index, true); + + checkSerialization(&store); + store.clearBits(); + } + } + + // check serialization for BitStore with random values + BitStore store(TESTBITSTORE_MAX_BITSTORE_SIZE); + Random rand; + + for(int count = 0; count < TESTBITSTORE_RANDOM_VALUES_COUNT; count++) + { + int randIndex = rand.getNextInRange(0, TESTBITSTORE_MAX_BITSTORE_SIZE - 1); + store.setBit(randIndex, true); + } + + checkSerialization(&store); +} diff --git a/common/tests/TestEntryIdTk.cpp b/common/tests/TestEntryIdTk.cpp new file mode 100644 index 0000000..a84b806 --- /dev/null +++ b/common/tests/TestEntryIdTk.cpp @@ -0,0 +1,29 @@ +#include + +#include + +TEST(EntryIdTk, isValidHexToken) +{ + using EntryIdTk::isValidHexToken; + + EXPECT_TRUE(isValidHexToken("0")); + EXPECT_FALSE(isValidHexToken("")); + EXPECT_TRUE(isValidHexToken(std::string(8, '0'))); + EXPECT_FALSE(isValidHexToken(std::string(9, '0'))); + EXPECT_TRUE(isValidHexToken("123ADB")); + EXPECT_FALSE(isValidHexToken("123ADBS")); +} + +TEST(EntryIdTk, isValidEntryIdFormat) +{ + using EntryIdTk::isValidEntryIdFormat; + + EXPECT_TRUE(isValidEntryIdFormat("0-59F03330-1")); + + EXPECT_TRUE(isValidEntryIdFormat("0-0-0")); + EXPECT_FALSE(isValidEntryIdFormat("0-0-0-0")); + EXPECT_FALSE(isValidEntryIdFormat("0-0")); + EXPECT_FALSE(isValidEntryIdFormat("-0")); + EXPECT_FALSE(isValidEntryIdFormat("0-")); + EXPECT_FALSE(isValidEntryIdFormat("--")); +} diff --git a/common/tests/TestIPv4Network.cpp b/common/tests/TestIPv4Network.cpp new file mode 100644 index 0000000..d3537fa --- /dev/null +++ b/common/tests/TestIPv4Network.cpp @@ -0,0 +1,99 @@ +#include + +#include + + +struct TestIPv4NetworkParseResult +{ + const char* cidr; + bool valid; + struct in_addr address; + uint8_t prefix; + struct in_addr netmask; +}; + +struct TestIPv4NetworkParseResult const __TESTIPV4NETWORK_PARSE_RESULTS[] = +{ + {"0.0.0.0/0", true, in_addr_ctor(0), 0, in_addr_ctor(0)}, + {"10.0.0.0/8", true, in_addr_ctor(0x0a), 8, in_addr_ctor(0x0ff)}, + {"10.11.0.0/16", true, in_addr_ctor(0x0b0a), 16, in_addr_ctor(0x0ffff)}, + {"10.11.12.0/24", true, in_addr_ctor(0x0c0b0a), 24, in_addr_ctor(0x0ffffff)}, + {"10.11.12.1/32", true, in_addr_ctor(0x010c0b0a), 32, in_addr_ctor(0x0ffffffff)}, + {"10.11.12.1/", false}, + {"10.11.12.1/33", false}, + {"10.11.12.1/-1", false}, + {"foo.com/16", false}, + {"", false}, + {"/", false}, + {"10.11.12.1/16", true, in_addr_ctor(0x0b0a), 16, in_addr_ctor(0x0ffff)}, + {NULL} +}; + + + +class TestIPv4Network : public ::testing::Test +{ +}; + +TEST_F(TestIPv4Network, parseCidr) +{ + for (int i = 0; __TESTIPV4NETWORK_PARSE_RESULTS[i].cidr != NULL; ++i) + { + auto& r = __TESTIPV4NETWORK_PARSE_RESULTS[i]; + IPv4Network n; + //std::cerr << "parse " << r.cidr << std::endl; + bool v = IPv4Network::parse(r.cidr, n); + EXPECT_EQ(r.valid, v) << r.cidr << " should be " << (r.valid? "valid" : "invalid"); + if (v) + { + EXPECT_EQ(r.address, n.address) << r.cidr << " address should be " << std::hex << r.address.s_addr; + EXPECT_EQ(r.netmask, n.netmask) << r.cidr << " netmask should be " << std::hex << r.netmask.s_addr; + EXPECT_EQ(r.prefix, n.prefix) << r.cidr << " prefix should be " << (int) r.prefix; + } + } +} + +TEST_F(TestIPv4Network, matches24) +{ + IPv4Network net; + struct in_addr ina; + EXPECT_EQ(true, IPv4Network::parse("192.168.1.0/24", net)); + for (int i = 1; i <= 255; ++i) + { + std::string a = std::string("192.168.1.") + std::to_string(i); + EXPECT_EQ(1, inet_pton(AF_INET, a.c_str(), &ina)); + EXPECT_EQ(true, net.matches(ina)); + } + for (int i = 1; i <= 255; ++i) + { + std::string a = std::string("192.168.2.") + std::to_string(i); + EXPECT_EQ(1, inet_pton(AF_INET, a.c_str(), &ina)); + EXPECT_EQ(false, net.matches(ina)); + } +} + +TEST_F(TestIPv4Network, matches32) +{ + IPv4Network net; + struct in_addr ina; + EXPECT_EQ(true, IPv4Network::parse("192.168.1.128/32", net)); + for (int i = 1; i <= 255; ++i) + { + std::string a = std::string("192.168.1.") + std::to_string(i); + EXPECT_EQ(1, inet_pton(AF_INET, a.c_str(), &ina)); + EXPECT_EQ(i == 128, net.matches(ina)); + } +} + +TEST_F(TestIPv4Network, matches0) +{ + IPv4Network net; + struct in_addr ina; + EXPECT_EQ(true, IPv4Network::parse("0.0.0.0/0", net)); + for (int i = 1; i <= 255; ++i) + { + std::string a = std::string("192.168.1.") + std::to_string(i); + EXPECT_EQ(1, inet_pton(AF_INET, a.c_str(), &ina)); + EXPECT_EQ(true, net.matches(ina)); + } +} diff --git a/common/tests/TestListTk.cpp b/common/tests/TestListTk.cpp new file mode 100644 index 0000000..f2631dc --- /dev/null +++ b/common/tests/TestListTk.cpp @@ -0,0 +1,23 @@ +#include + +#include + +TEST(ListTk, advance) +{ + IntList list; + IntListIter iter; + + for (int i=0; i<10;i++) + { + list.push_back(i); + } + + iter = list.begin(); + ListTk::advance(list, iter, 5); + + ASSERT_EQ(*iter, 5); + + ListTk::advance(list, iter, 44); + + ASSERT_EQ(iter, list.end()); +} diff --git a/common/tests/TestLockFD.cpp b/common/tests/TestLockFD.cpp new file mode 100644 index 0000000..1a3ce6c --- /dev/null +++ b/common/tests/TestLockFD.cpp @@ -0,0 +1,99 @@ +#include +#include + +#include +#include +#include + +#include + +class TestLockFD : public ::testing::Test { + protected: + std::string tmpDir; + + void SetUp() override + { + tmpDir = "tmpXXXXXX"; + tmpDir += '\0'; + ASSERT_NE(mkdtemp(&tmpDir[0]), nullptr); + tmpDir.resize(tmpDir.size() - 1); + } + + void TearDown() override + { + StorageTk::removeDirRecursive(tmpDir); + } +}; + +TEST_F(TestLockFD, testInitialLock) +{ + const auto path = tmpDir + "/initial"; + + auto lock = LockFD::lock(path, false); + ASSERT_TRUE(lock); + + FDHandle fd(open(path.c_str(), O_RDONLY)); + ASSERT_TRUE(fd.valid()); + + ASSERT_EQ(flock(*fd, LOCK_EX | LOCK_NB), -1); + ASSERT_EQ(errno, EWOULDBLOCK); +} + +TEST_F(TestLockFD, testLockTwice) +{ + const auto path = tmpDir + "/twice"; + + auto lock1 = LockFD::lock(path, false); + ASSERT_TRUE(lock1); + + auto lock2 = LockFD::lock(path, false); + ASSERT_FALSE(lock2); + ASSERT_EQ(lock2.error().value(), EWOULDBLOCK); +} + +TEST_F(TestLockFD, testDoesUnlink) +{ + const auto path = tmpDir + "/unlinks"; + + auto lock = LockFD::lock(path, false); + ASSERT_TRUE(lock); + lock = {}; + + ASSERT_EQ(access(path.c_str(), R_OK), -1); +} + +TEST_F(TestLockFD, testUpdate) +{ + const auto path = tmpDir + "/update"; + + { + auto lock = LockFD::lock(path, false).release_value(); + ASSERT_TRUE(lock.update("test")); + ASSERT_TRUE(lock.updateWithPID()); + } + + { + auto lock = LockFD::lock(path, true).release_value(); + + { + ASSERT_FALSE(lock.update("test")); + std::ifstream file(path); + char buf[10]; + file.read(buf, 5); + ASSERT_TRUE(file.eof()); + ASSERT_EQ(file.gcount(), 4); + ASSERT_EQ(memcmp(buf, "test", 4), 0); + } + + { + ASSERT_FALSE(lock.updateWithPID()); + std::ifstream file(path); + char buf[100]; + const auto expected = std::to_string(getpid()) + '\n'; + file.read(buf, 100); + ASSERT_TRUE(file.eof()); + ASSERT_EQ(size_t(file.gcount()), expected.size()); + ASSERT_EQ(memcmp(buf, &expected[0], expected.size()), 0); + } + } +} diff --git a/common/tests/TestNIC.cpp b/common/tests/TestNIC.cpp new file mode 100644 index 0000000..5187de9 --- /dev/null +++ b/common/tests/TestNIC.cpp @@ -0,0 +1,17 @@ +#include + +#include + +TEST(NIC, nic) +{ + NicAddressList list; + StringList allowedInterfaces; + + ASSERT_TRUE(NetworkInterfaceCard::findAll(&allowedInterfaces, true, &list)); + + ASSERT_FALSE(list.empty()); + + NicAddress nicAddr; + + ASSERT_TRUE(NetworkInterfaceCard::findByName(list.begin()->name, &nicAddr)); +} diff --git a/common/tests/TestNetFilter.cpp b/common/tests/TestNetFilter.cpp new file mode 100644 index 0000000..b418c26 --- /dev/null +++ b/common/tests/TestNetFilter.cpp @@ -0,0 +1,88 @@ +#include + +#include + +class TestNetFilter : public ::testing::Test { + protected: + std::string fileName; + + void createFile(const char* content) + { + fileName = "/tmp/XXXXXX"; + int fd = mkstemp(&fileName[0]); + ASSERT_GE(fd, 0); + ASSERT_EQ(write(fd, content, strlen(content)), ssize_t(strlen(content))); + close(fd); + } + + void TearDown() override + { + unlink(fileName.c_str()); + } +}; + +TEST_F(TestNetFilter, noFile) +{ + NetFilter filter(""); + + ASSERT_EQ(filter.getNumFilterEntries(), 0u); + + // always allow loopback + EXPECT_TRUE(filter.isAllowed(htonl(INADDR_LOOPBACK))); + EXPECT_TRUE(filter.isContained(htonl(INADDR_LOOPBACK))); + + // can't check for all ips, try a random one + EXPECT_TRUE(filter.isAllowed(htonl(0xd5c1a899))); + EXPECT_FALSE(filter.isContained(htonl(0xd5c1a899))); +} + +TEST_F(TestNetFilter, netmask0) +{ + createFile("10.0.0.0/0"); + + NetFilter filter(fileName); + + ASSERT_EQ(filter.getNumFilterEntries(), 1u); + + // always allow loopback + EXPECT_TRUE(filter.isAllowed(htonl(INADDR_LOOPBACK))); + EXPECT_TRUE(filter.isContained(htonl(INADDR_LOOPBACK))); + + // 10.0.0.x should definitely be allowed + EXPECT_TRUE(filter.isAllowed(htonl(0x0a010203))); + EXPECT_TRUE(filter.isContained(htonl(0x0a010203))); + + // netfilter /0 says everything else is allowed too + EXPECT_TRUE(filter.isAllowed(htonl(0xd5c1a899))); + EXPECT_TRUE(filter.isContained(htonl(0xd5c1a899))); +} + +TEST_F(TestNetFilter, netmaskExclusive) +{ + createFile("10.0.0.0/24\n10.1.0.0/24"); + + NetFilter filter(fileName); + + ASSERT_EQ(filter.getNumFilterEntries(), 2u); + + // always allow loopback + EXPECT_TRUE(filter.isAllowed(htonl(INADDR_LOOPBACK))); + EXPECT_TRUE(filter.isContained(htonl(INADDR_LOOPBACK))); + + // 10.0.0.x should definitely be allowed + EXPECT_TRUE(filter.isAllowed(htonl(0x0a000003))); + EXPECT_TRUE(filter.isContained(htonl(0x0a000003))); + // 10.0.1.x should not be allowed + EXPECT_FALSE(filter.isAllowed(htonl(0x0a000103))); + EXPECT_FALSE(filter.isContained(htonl(0x0a000103))); + + // same thing for 10.1.0.x + EXPECT_TRUE(filter.isAllowed(htonl(0x0a010003))); + EXPECT_TRUE(filter.isContained(htonl(0x0a010003))); + EXPECT_FALSE(filter.isAllowed(htonl(0x0a010103))); + EXPECT_FALSE(filter.isContained(htonl(0x0a010103))); + + // other addresses are forbidden + EXPECT_FALSE(filter.isAllowed(htonl(0xd5c1a899))); + EXPECT_FALSE(filter.isContained(htonl(0xd5c1a899))); +} diff --git a/common/tests/TestPath.cpp b/common/tests/TestPath.cpp new file mode 100644 index 0000000..758c94d --- /dev/null +++ b/common/tests/TestPath.cpp @@ -0,0 +1,23 @@ +#include + +#include + +TEST(Path, path) +{ + std::vector origPathElems = { + "xyz", + "subdir", + "file", + }; + + std::string pathStr; + for (auto iter = origPathElems.begin(); iter != origPathElems.end(); iter++) + pathStr += "/" + *iter; + + Path path(pathStr); + + ASSERT_EQ(path.size(), origPathElems.size()); + + for (size_t i = 0; i < path.size(); i++) + ASSERT_EQ(path[i], origPathElems[i]); +} diff --git a/common/tests/TestPreallocatedFile.cpp b/common/tests/TestPreallocatedFile.cpp new file mode 100644 index 0000000..9fa4742 --- /dev/null +++ b/common/tests/TestPreallocatedFile.cpp @@ -0,0 +1,65 @@ +#include +#include + +#include +#include +#include + +#include + +class TestPreallocatedFile : public ::testing::Test { + protected: + std::string tmpDir; + + void SetUp() override + { + tmpDir = "tmpXXXXXX"; + tmpDir += '\0'; + ASSERT_NE(mkdtemp(&tmpDir[0]), nullptr); + tmpDir.resize(tmpDir.size() - 1); + } + + void TearDown() override + { + StorageTk::removeDirRecursive(tmpDir); + } +}; + +TEST_F(TestPreallocatedFile, allocation) +{ + const auto path = tmpDir + "/file"; + + PreallocatedFile file(path, 0600); + + struct stat st; + ASSERT_EQ(::stat(path.c_str(), &st), 0); + ASSERT_EQ(st.st_size, 1024*1024+1); + ASSERT_GE(st.st_blocks * 512, 1024*1024+1); +} + +TEST_F(TestPreallocatedFile, allocationFailure) +{ + // This should throw because filesystems can't provide 2**63 writable bytes in a single file as + // of writing this test and probably won't for a long time after. + ASSERT_THROW(([this] { + PreallocatedFile::max()> file(tmpDir + "/file", 0600); + })(), + std::system_error); +} + +TEST_F(TestPreallocatedFile, readWrite) +{ + const auto goodPath = tmpDir + "/good"; + + PreallocatedFile good(goodPath, 0600); + PreallocatedFile bad(tmpDir + "/bad", 0600); + + good.write(0x0102030405060708ULL); + ASSERT_THROW(bad.write(0), std::runtime_error); + + ASSERT_EQ(*good.read(), 0x0102030405060708ULL); + ASSERT_EQ(bad.read(), boost::none); + + ASSERT_EQ(::truncate(goodPath.c_str(), 0), 0); + ASSERT_THROW(good.read(), std::system_error); +} diff --git a/common/tests/TestRWLock.cpp b/common/tests/TestRWLock.cpp new file mode 100644 index 0000000..033df47 --- /dev/null +++ b/common/tests/TestRWLock.cpp @@ -0,0 +1,834 @@ +#include +#include +#include +#include +#include + +#include "TestRWLock.h" + +// rwlock tests are disabled by default, probably due to long runtime. +#define RWLOCK_TEST(id) TEST_F(TestRWLock, DISABLED_ ## id) + + +/* + * sorts all threads by the lock timestamp, make it easier to verify the execution order + */ +void TestRWLock::sortThreadsInLockTimestamp(TestLockThread* threads, + Time* startTime) +{ + bool changesDone = false; + + do + { + changesDone = false; + + for (int id = 0; id < (TestRWLock_THREAD_COUNT -1); id++) + { + int elapsedTimeID = threads[id].getLockTimestamp(). + elapsedSinceMS(startTime); + int elapsedTimeNextID = threads[id + 1].getLockTimestamp(). + elapsedSinceMS(startTime); + + if (elapsedTimeID > elapsedTimeNextID) + { + TestLockThread tmpTestLockThread; + tmpTestLockThread.copy(&threads[id]); + threads[id].copy(&threads[id + 1]); + threads[id + 1 ].copy(&tmpTestLockThread); + changesDone = true; + } + } + } + while (changesDone); +} + +/* + * analyze the run time for random thread execution, if the run time was long enough + */ +void TestRWLock::checkRandomRuntime(TestLockThread* threads, int runtimeMS) +{ + int minimalRuntimeMS = 0; + int longestSleepMS = 0; + + for (int id = 0; id < TestRWLock_THREAD_COUNT; id++) + { + // ignore threads which didn't get a lock + if (!threads[id].getLockSuccess()) + { + continue; + } + + if (threads[id].getDoReadLock()) + { // reader thread, find the longest sleep time + for (int nextId = id + 1; nextId < TestRWLock_THREAD_COUNT; nextId++) + { + // check all reader threads which was executed at the same time + if(threads[nextId].getDoReadLock()) + { // next thread was a reader, find the longest sleep time + if (threads[nextId].getLockSuccess() && + (longestSleepMS < threads[nextId].getSleepTimeMS())) + { + longestSleepMS = threads[nextId].getSleepTimeMS(); + } + } + else + { // next thread was a writer, stop searching of longest sleep time + id = nextId - 1; + break; + } + } + minimalRuntimeMS = minimalRuntimeMS + longestSleepMS; + } + else + { // writer thread, add the the sleep time + minimalRuntimeMS = minimalRuntimeMS + threads[id].getSleepTimeMS(); + } + } + + // check if the sleep time is bigger then the runtime + if (minimalRuntimeMS > runtimeMS) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * analyze the execution order for random thread execution + */ +void TestRWLock::checkRandomExecutionOrder(TestLockThread* threads) +{ + for (int id = 1; id < (TestRWLock_THREAD_COUNT); id++) + { + // ignore threads which didn't get a lock + if(!threads[id].getLockSuccess()) + { + continue; + } + + // check if the unlock was successful + if(!threads[id].getUnlockSuccess()) + { + FAIL() << "Test thread didn't unlock the lock."; + } + + // check the execution order + if(threads[id].getDoReadLock()) + { + checkRandomExecutionOrderReader(threads, id); + } + else + { + checkRandomExecutionOrderWriter(threads, id); + } + } +} + +/* + * analyze the execution order for random thread execution for a reader thread + */ +void TestRWLock::checkRandomExecutionOrderReader(TestLockThread* threads, int threadID) +{ + for (int beforeId = threadID - 1; beforeId >= 0; beforeId--) + { + if(threads[beforeId].getLockSuccess()) + { + Time unlockTimeBefore = threads[beforeId].getUnlockTimestamp(); + Time lockTime = threads[threadID].getLockTimestamp(); + + // check if the thread before was a writer and check if the thread before has unlocked + // the rwlock before this reader thread locks the rwlock + // if the thread before was a reader it is OK to get the lock without a unlock + if((!threads[beforeId].getDoReadLock()) && (unlockTimeBefore > lockTime)) + { + std::cerr << "execution order failed, time diff in micro sec: " << StringTk::uintToStr( + unlockTimeBefore.elapsedSinceMicro(&lockTime)) << std::endl; + FAIL() << "Test thread got the lock, but it wasn't possible to get the lock."; + } + } + } +} + +/* + * analyze the execution order for random thread execution for a writer thread + */ +void TestRWLock::checkRandomExecutionOrderWriter(TestLockThread* threads, int threadID) +{ + for (int beforeId = threadID - 1; beforeId >= 0; beforeId--) + { + if(threads[beforeId].getLockSuccess()) + { + Time unlockTimeBefore = threads[beforeId].getUnlockTimestamp(); + Time lockTime = threads[threadID].getLockTimestamp(); + + // check if the thread before has unlocked the rwlock before this writer thread locks + // the rwlock + if(unlockTimeBefore > lockTime) + { + std::cerr << "execution order failed, time diff in micro sec: " << StringTk::uintToStr( + unlockTimeBefore.elapsedSinceMicro(&lockTime)) << std::endl; + FAIL() << "Test thread got the lock, but it wasn't possible to get the lock."; + } + } + } +} + +/* + * tests a reader thread on a read lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(readerOnReader) +{ + // creates a read lock + RWLock lock; + lock.readLock(); + + Time startTime; + + // creates a read lock + TestLockThread thread(&lock, true, false); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (!thread.getLockSuccess()) + { + FAIL() << "The test thread didn't get the lock, but it was possible to get the lock."; + } + + if (!thread.getUnlockSuccess()) + { + FAIL() << "The test thread didn't unlock the lock."; + } + + if (TestRWLock_SLEEP_TIME_MS > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a reader thread on a write lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(readerOnWriter) +{ + // creates a write lock + RWLock lock; + lock.writeLock(); + + Time startTime; + + // creates a read lock + TestLockThread thread(&lock, true, false); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (!thread.getLockSuccess()) + { + FAIL() << "The test thread didn't get the lock, but it was possible to get the lock."; + } + + if (!thread.getUnlockSuccess()) + { + FAIL() << "The test thread didn't unlock the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS * 2) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a writer thread on a read lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(writerOnReader) +{ + // creates a read lock + RWLock lock; + lock.readLock(); + + Time startTime; + + // creates a write lock + TestLockThread thread(&lock, false, false); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (!thread.getLockSuccess()) + { + FAIL() << "The test thread didn't get the lock, but it was possible to get the lock."; + } + + if (!thread.getUnlockSuccess()) + { + FAIL() << "The test thread didn't unlock the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS * 2) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a writer thread on a write lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(writerOnWriter) +{ + // creates a write lock + RWLock lock; + lock.writeLock(); + + Time startTime; + + // creates a write lock + TestLockThread thread(&lock, false, false); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (!thread.getLockSuccess()) + { + FAIL() << "The test thread didn't get the lock, but it was possible to get the lock."; + } + + if (!thread.getUnlockSuccess()) + { + FAIL() << "The test thread didn't unlock the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS * 2) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests reader threads and writer thread on a read lock, checks massive amount of lock operations + */ +RWLOCK_TEST(randomOnReader) +{ + RWLock lock; + Random randomizer; + TestLockThread threadList[TestRWLock_THREAD_COUNT]; + + int id = 0; + + threadList[0].init(&lock, true, false, TestRWLock_SLEEP_TIME_MS, 1); + + // create all threads for the test + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + bool tmpDoReadLock; + + // random initialization: is reader or writer + tmpDoReadLock = randomizer.getNextInRange(0, 1) == 1; + + // random initialization: sleep time + int tmpSleepTimeMS = randomizer.getNextInRange( + TestRWLock_RANDOM_SLEEP_TIME_MIN_MS, + TestRWLock_RANDOM_SLEEP_TIME_MAX_MS); + + // random initialization: start delay time + int tmpLockDelayMS = randomizer.getNextInRange( + TestRWLock_RANDOM_LOCK_DELAY_MIN_MS, + TestRWLock_RANDOM_LOCK_DELAY_MAX_MS); + + threadList[id].init(&lock, tmpDoReadLock, false, tmpSleepTimeMS, tmpLockDelayMS); + } + + Time startTime; + + // start all threads + threadList[0].start(); + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + threadList[id].start(); + } + + bool notTimedOut = true; + Time startTimeout; + + // collect all threads and check timeout + for (id = 0; id < TestRWLock_THREAD_COUNT; id++) + { + int nextTimeout = TestRWLock_MULTI_TIMEOUT_MS - startTimeout.elapsedMS(); + if (nextTimeout < 500) + { + nextTimeout = 500; + } + + if (!threadList[id].timedjoin(nextTimeout)) + { + notTimedOut = false; + } + } + + int runtimeMS = startTime.elapsedMS(); + + // check the constraints + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + sortThreadsInLockTimestamp(threadList, &startTime); + checkRandomExecutionOrder(threadList); + checkRandomRuntime(threadList, runtimeMS); +} + +/* + * tests reader threads and writer thread on a write lock, checks massive amount of lock operations + */ +RWLOCK_TEST(randomOnWriter) +{ + RWLock lock; + Random randomizer; + TestLockThread threadList[TestRWLock_THREAD_COUNT]; + + int id = 0; + + threadList[0].init(&lock, false, false, 5000, 1); + + // create all threads for the test + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + bool tmpDoReadLock; + + // random initialization: is reader or writer + tmpDoReadLock = randomizer.getNextInRange(0, 1) == 1; + + // random initialization: sleep time + int tmpSleepTimeMS = randomizer.getNextInRange( + TestRWLock_RANDOM_SLEEP_TIME_MIN_MS, + TestRWLock_RANDOM_SLEEP_TIME_MAX_MS); + + // random initialization: start delay time + int tmpLockDelayMS = randomizer.getNextInRange( + TestRWLock_RANDOM_LOCK_DELAY_MIN_MS, + TestRWLock_RANDOM_LOCK_DELAY_MAX_MS); + + threadList[id].init(&lock, tmpDoReadLock, false, tmpSleepTimeMS, tmpLockDelayMS); + } + + Time startTime; + + // start all threads + threadList[0].start(); + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + threadList[id].start(); + } + + bool notTimedOut = true; + Time startTimeout; + + // collect all threads and check timeout + for (id = 0; id < TestRWLock_THREAD_COUNT; id++) + { + int nextTimeout = TestRWLock_MULTI_TIMEOUT_MS - startTimeout.elapsedMS(); + if (nextTimeout < 500) + { + nextTimeout = 500; + } + + if (!threadList[id].timedjoin(nextTimeout)) + { + notTimedOut = false; + } + } + + int runtimeMS = startTime.elapsedMS(); + + // check the constraints + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + sortThreadsInLockTimestamp(threadList, &startTime); + checkRandomExecutionOrder(threadList); + checkRandomRuntime(threadList, runtimeMS); +} + +/* + * tests a tryReadLock on a read lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(tryReadOnReader) +{ + // creates a read lock with try method + RWLock lock; + + bool success = lock.tryReadLock(); + if (!success) + { + FAIL() << "Couldn't get initial lock."; + } + + Time startTime; + + // creates a read lock with try method + TestLockThread thread(&lock, true, true); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (!thread.getLockSuccess()) + { + FAIL() << "The test thread didn't get the lock, but it was possible to get the lock."; + } + + if (!thread.getUnlockSuccess()) + { + FAIL() << "The test thread didn't unlock the lock."; + } + + if (TestRWLock_SLEEP_TIME_MS > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a tryReadLock on a write lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(tryReadOnWriter) +{ + // creates a write lock with try method + RWLock lock; + + bool success = lock.tryWriteLock(); + if (!success) + { + FAIL() << "Couldn't get initial lock."; + } + + Time startTime; + + // creates a read lock with try method + TestLockThread thread(&lock, true, true); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (thread.getLockSuccess()) + { + FAIL() << "The test thread got the lock, but it wasn't possible to get the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a tryWriteLock on a read lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(tryWriteOnReader) +{ + // creates a read lock with try method + RWLock lock; + + bool success = lock.tryReadLock(); + if (!success) + { + FAIL() << "Couldn't get initial lock."; + } + + Time startTime; + + // creates a write lock with try method + TestLockThread thread(&lock, false, true); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (thread.getLockSuccess()) + { + FAIL() << "The test thread got the lock, but it wasn't possible to get the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests a tryWriteLock on a write lock, checks the basic functions of a rwlock + */ +RWLOCK_TEST(tryWriteOnWriter) +{ + // creates a write lock with try method + RWLock lock; + + bool success = lock.tryWriteLock(); + if (!success) + { + FAIL() << "Couldn't get initial lock."; + } + + Time startTime; + + // creates a write lock with try method + TestLockThread thread(&lock, false, true); + thread.start(); + + // wait a few second before unlock the lock + PThread::sleepMS(TestRWLock_SLEEP_TIME_MS); + + lock.unlock(); + + // wait for timeout + bool notTimedOut = thread.timedjoin(2 * TestRWLock_SINGLE_TIMEOUT_MS); + int runtime = startTime.elapsedMS(); + + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + if (thread.getLockSuccess()) + { + FAIL() << "The test thread got the lock, but it wasn't possible to get the lock."; + } + + if ((TestRWLock_SLEEP_TIME_MS) > runtime) + { + FAIL() << "Runtime is to short. A lock didn't work."; + } +} + +/* + * tests tryReadLock and tryWriteLock on a read lock, checks massive amount of lock operations + */ +RWLOCK_TEST(randomTryOnReader) +{ + RWLock lock; + Random randomizer; + TestLockThread threadList[TestRWLock_THREAD_COUNT]; + + int id = 0; + + threadList[0].init(&lock, true, true, 5000, 1); + + // create all threads for the test + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + bool tmpDoReadLock; + + // random initialization: is reader or writer + tmpDoReadLock = randomizer.getNextInRange(0, 1) == 1; + + // random initialization: sleep time + int tmpSleepTimeMS = randomizer.getNextInRange( + TestRWLock_RANDOM_SLEEP_TIME_MIN_MS, + TestRWLock_RANDOM_SLEEP_TIME_MAX_MS); + + // random initialization: start delay time + int tmpLockDelayMS = randomizer.getNextInRange( + TestRWLock_RANDOM_LOCK_DELAY_MIN_MS, + TestRWLock_RANDOM_LOCK_DELAY_MAX_MS); + + threadList[id].init(&lock, tmpDoReadLock, true, tmpSleepTimeMS, tmpLockDelayMS); + } + + Time startTime; + + // start all threads + threadList[0].start(); + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + threadList[id].start(); + } + + bool notTimedOut = true; + Time startTimeout; + + // collect all threads and check timeout + for (id = 0; id < TestRWLock_THREAD_COUNT; id++) + { + int nextTimeout = TestRWLock_MULTI_TIMEOUT_MS - startTimeout.elapsedMS(); + if (nextTimeout < 500) + { + nextTimeout = 500; + } + + if (!threadList[id].timedjoin(nextTimeout)) + { + notTimedOut = false; + } + } + + int runtimeMS = startTime.elapsedMS(); + + // check the constraints + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + sortThreadsInLockTimestamp(threadList, &startTime); + checkRandomExecutionOrder(threadList); + checkRandomRuntime(threadList, runtimeMS); +} + +/* +* tests tryReadLock and tryWriteLock on a write lock, checks massive amount of lock operations + */ +RWLOCK_TEST(randomTryOnWriter) +{ + RWLock lock; + Random randomizer; + TestLockThread threadList[TestRWLock_THREAD_COUNT]; + + int id = 0; + + threadList[0].init(&lock, false, true, 5000, 1); + + // create all threads for the test + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + bool tmpDoReadLock; + + // random initialization: is reader or writer + tmpDoReadLock = randomizer.getNextInRange(0, 1) == 1; + + // random initialization: sleep time + int tmpSleepTimeMS = randomizer.getNextInRange( + TestRWLock_RANDOM_SLEEP_TIME_MIN_MS, + TestRWLock_RANDOM_SLEEP_TIME_MAX_MS); + + // random initialization: start delay time + int tmpLockDelayMS = randomizer.getNextInRange( + TestRWLock_RANDOM_LOCK_DELAY_MIN_MS, + TestRWLock_RANDOM_LOCK_DELAY_MAX_MS); + + threadList[id].init(&lock, tmpDoReadLock, true, tmpSleepTimeMS, tmpLockDelayMS); + } + + Time startTime; + + // start all threads + threadList[0].start(); + for (id = 1; id < TestRWLock_THREAD_COUNT; id++) + { + threadList[id].start(); + } + + bool notTimedOut = true; + Time startTimeout; + + // collect all threads and check timeout + for (id = 0; id < TestRWLock_THREAD_COUNT; id++) + { + int nextTimeout = TestRWLock_MULTI_TIMEOUT_MS - startTimeout.elapsedMS(); + if (nextTimeout < 500) + { + nextTimeout = 500; + } + + if (!threadList[id].timedjoin(nextTimeout)) + { + notTimedOut = false; + } + } + + int runtimeMS = startTime.elapsedMS(); + + // check the constraints + if (!notTimedOut) + { + FAIL() << "Test ran into a timeout. Maybe a dead-lock"; + } + + sortThreadsInLockTimestamp(threadList, &startTime); + checkRandomExecutionOrder(threadList); + checkRandomRuntime(threadList, runtimeMS); +} diff --git a/common/tests/TestRWLock.h b/common/tests/TestRWLock.h new file mode 100644 index 0000000..b53b71c --- /dev/null +++ b/common/tests/TestRWLock.h @@ -0,0 +1,230 @@ +#pragma once + + +#include +#include +#include + +#include +#include + + +// configuration for the tests +#define TestRWLock_THREAD_COUNT 50 +#define TestRWLock_LOCK_DELAY_MS 1000 +#define TestRWLock_SLEEP_TIME_S 3 +#define TestRWLock_SLEEP_TIME_MS (TestRWLock_SLEEP_TIME_S * 1000) +#define TestRWLock_RANDOM_SLEEP_TIME_MIN_MS 1000 +#define TestRWLock_RANDOM_SLEEP_TIME_MAX_MS 4000 +#define TestRWLock_RANDOM_LOCK_DELAY_MIN_MS 2000 +#define TestRWLock_RANDOM_LOCK_DELAY_MAX_MS 8000 +#define TestRWLock_SINGLE_TIMEOUT_MS 15000 +#define TestRWLock_MULTI_TIMEOUT_MS (TestRWLock_SINGLE_TIMEOUT_MS * TestRWLock_THREAD_COUNT) + + +class TestRWLock: public ::testing::Test +{ + protected: + /** + * Thread for testing the concurrent access to a shared resource which is secured by + * an rwlock + */ + class TestLockThread: public PThread + { + public: + TestLockThread() : PThread("RWLockTester") + { + this->lock = NULL; + + this->sleepTimeMS = TestRWLock_SLEEP_TIME_MS; + this->lockDelayMS = TestRWLock_LOCK_DELAY_MS; + + this->doReadLock = true; + this->doTry = false; + + this->lockSuccess = false; + this->unlockSuccess = false; + + this->lockTimestamp.setToNow(); + this->unlockTimestamp.setToNow(); + this->initTimestamp.setToNow(); + } + + TestLockThread(RWLock* lock, bool doReadLock, bool doTry) : PThread("RWLockTester") + { + this->lock = lock; + + this->sleepTimeMS = TestRWLock_SLEEP_TIME_MS; + this->lockDelayMS = TestRWLock_LOCK_DELAY_MS; + + this->doReadLock = doReadLock; + this->doTry = doTry; + + this->lockSuccess = false; + this->unlockSuccess = false; + + this->lockTimestamp.setToNow(); + this->unlockTimestamp.setToNow(); + this->initTimestamp.setToNow(); + } + + TestLockThread(RWLock* lock, bool doReadLock, bool doTry, int sleepTimeMS, + int lockDelayMS) : PThread("RWLockTester") + { + this->lock = lock; + + this->sleepTimeMS = sleepTimeMS; + this->lockDelayMS = lockDelayMS; + + this->doReadLock = doReadLock; + this->doTry = doTry; + + this->lockSuccess = false; + this->unlockSuccess = false; + + this->lockTimestamp.setToNow(); + this->unlockTimestamp.setToNow(); + this->initTimestamp.setToNow(); + } + + protected: + + private: + RWLock* lock; // the rwlock which all threads are use + + int sleepTimeMS; // amount of to time to wait before start locking the rwlock + int lockDelayMS; // the delay between the lock and the unlock + + bool doReadLock; // true if this thread tries a read lock + bool doTry; // true if a try*Lock will be used + + bool lockSuccess; // true if the lock was successful + bool unlockSuccess; // true if the unlock was successful + + Time lockTimestamp; // the timestamp of the successful lock + Time unlockTimestamp; // the timestamp of the successful unlock + Time initTimestamp; // the timestamp at the end of the initialization + + public: + //public inliner + + void init(RWLock* lock, bool doReadLock, bool doTry, int sleepTimeMS, int lockDelayMS) + { + this->lock = lock; + + this->sleepTimeMS = sleepTimeMS; + this->lockDelayMS = lockDelayMS; + + this->doReadLock = doReadLock; + this->doTry = doTry; + + this->lockTimestamp.setToNow(); + this->unlockTimestamp.setToNow(); + this->initTimestamp.setToNow(); + } + + void copy(TestLockThread* origin) + { + this->lock = origin->lock; + + this->sleepTimeMS = origin->sleepTimeMS; + this->lockDelayMS = origin->lockDelayMS; + + this->doReadLock = origin->doReadLock; + this->doTry = origin->doTry; + + this->lockSuccess = origin->lockSuccess; + this->unlockSuccess = origin->unlockSuccess; + + this->lockTimestamp = Time(origin->lockTimestamp); + this->unlockTimestamp = Time(origin->unlockTimestamp); + this->initTimestamp = Time(origin->initTimestamp); + } + + bool getDoReadLock() + { + return this->doReadLock; + } + + bool getSleepTimeMS() + { + return this->sleepTimeMS; + } + + bool getLockSuccess() + { + return this->lockSuccess; + } + + bool getUnlockSuccess() + { + return this->unlockSuccess; + } + + Time getLockTimestamp() + { + return this->lockTimestamp; + } + + Time getUnlockTimestamp() + { + return this->unlockTimestamp; + } + + void run() + { + // start delay for random start of the threads, this is needed for a random + // execution order of the test threads, is necessary for a good test + sleepMS(this->lockDelayMS); + + if (this->doReadLock) + { + if(this->doTry) + { + this->lockSuccess = this->lock->tryReadLock(); + if (this->lockSuccess) + this->lockTimestamp.setToNow(); + } + else + { + this->lock->readLock(); + this->lockTimestamp.setToNow(); + this->lockSuccess = true; + } + } + else + { + if(this->doTry) + { + this->lockSuccess = this->lock->tryWriteLock(); + if (this->lockSuccess) + this->lockTimestamp.setToNow(); + } + else + { + this->lock->writeLock(); + this->lockTimestamp.setToNow(); + this->lockSuccess = true; + } + } + + // delay between lock and unlock for random execution order of the locking, + // is necessary for a good test + sleepMS(this->sleepTimeMS); + + if (this->lockSuccess) + { + this->unlockTimestamp.setToNow(); + this->lock->unlock(); + this->unlockSuccess = true; + } + } + }; + + void sortThreadsInLockTimestamp(TestLockThread* threads, Time* startTime); + void checkRandomRuntime(TestLockThread* threads, int runtimeMS); + void checkRandomExecutionOrder(TestLockThread* threads); + void checkRandomExecutionOrderReader(TestLockThread* threads, int threadID); + void checkRandomExecutionOrderWriter(TestLockThread* threads, int threadID); +}; + diff --git a/common/tests/TestSerialization.cpp b/common/tests/TestSerialization.cpp new file mode 100644 index 0000000..5534216 --- /dev/null +++ b/common/tests/TestSerialization.cpp @@ -0,0 +1,1140 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +TEST(Serialization, byteswap) +{ + ASSERT_EQ(byteswap16(0x0102), 0x0201); + ASSERT_EQ(byteswap32(0x01020304), 0x04030201u); + ASSERT_EQ(byteswap64(0x0102030405060708ULL), 0x0807060504030201ULL); +} + + + +static bool memoryEquals(const void* first, const void* second, size_t size) +{ + const char* a = (const char*) first; + const char* b = (const char*) second; + + size_t pos = 0; + while (pos < size) + { + if(a[pos] == b[pos]) + { + pos++; + continue; + } + + std::cerr << "\nfound mismatch at position " << pos << ": \n"; + + for(int dumped = 0; dumped < 16 && pos + dumped < size; dumped++) + std::cerr << int(unsigned(a[pos + dumped]) ) << " "; + + std::cerr << "\n"; + + for(int dumped = 0; dumped < 16 && pos + dumped < size; dumped++) + std::cerr << int(unsigned(b[pos + dumped]) ) << " "; + + return false; + } + + return true; +} + +struct NoModifier +{ + template const T& operator()(const T& t) { return t; } + template T& operator()( T& t) { return t; } +}; + +template> +void testObject(Object& obj, const char* expBegin, const char* expEnd, Modifier mod = Modifier(), + Eq eq = Eq()) +{ + SCOPED_TRACE("testObject"); + + const size_t Size = expEnd - expBegin; + + { + Serializer ser; + ser % mod((const Object&) obj); + ASSERT_EQ(ser.size(), Size); + } + + // have valgrind catch buffer overflows + std::vector outBuf(Size); + + { + Serializer ser(&outBuf[0], Size); + + ser % mod((const Object&) obj); + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), Size); + ASSERT_TRUE(memoryEquals(&outBuf[0], expBegin, Size)); + } + + { + Deserializer des(&outBuf[0], Size); + Object output; + + des % mod(output); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), Size); + ASSERT_TRUE(eq(obj, output)); + } + + { + Deserializer des(&outBuf[0], Size - 1); + Object output; + + des % mod(output); + ASSERT_FALSE(des.good()); + } +} + +template> +void testObject(Object& obj, const char (&expected)[Size], Modifier mod = Modifier(), Eq eq = Eq()) +{ + testObject(obj, expected, expected + Size, mod, eq); +} + +template +void testSimpleSerializer(T value, const char (&expected)[Size]) +{ + testObject(value, expected, NoModifier(), std::equal_to()); +} + + + +TEST(Serialization, isDirection) +{ + { + Serializer ser; + ASSERT_TRUE(ser.isWriting()); + ASSERT_FALSE(ser.isReading()); + } + + { + Deserializer des(0, 0); + ASSERT_FALSE(des.isWriting()); + ASSERT_TRUE(des.isReading()); + } +} + +TEST(Serialization, primitiveTypes) +{ + { + const char expectedFalse[] = { 0 }; + const char expectedTrue[] = { 1 }; + testSimpleSerializer(false, expectedFalse); + testSimpleSerializer(true, expectedTrue); + } + + { + const char expected[] = { 'X' }; + testSimpleSerializer('X', expected); + } + + { + const char expected[] = { '\xff' }; + testSimpleSerializer(255, expected); + } + + { + const char expected[] = { 2, 1 }; + testSimpleSerializer(258, expected); + } + + { + const char expected[] = { '\xff', '\xff' }; + testSimpleSerializer(-1, expected); + } + + { + const char expected[] = { 0x01, 0x02, 0x03, 0x04 }; + testSimpleSerializer(0x04030201, expected); + } + + { + const char expected[] = { '\xff', '\xff', '\xff', '\xff' }; + testSimpleSerializer(-1, expected); + } + + { + const char expected[] = { 1, 2, 3, 4, 5, 6, 7, 8 }; + testSimpleSerializer(0x0807060504030201LL, expected); + } + + { + const char expected[] = { 1, 2, 3, 4, 5, 6, 7, '\xff' }; + testSimpleSerializer(0xff07060504030201ULL, expected); + } +} + +TEST(Serialization, pair) +{ + const char expected[] = { + 1, 0, 0, 0, + 1 }; + + testSimpleSerializer( + std::pair(1, 1), + expected); +} + +TEST(Serialization, tuple) +{ + const char expected[] = { + 1, 0, 0, 0, + 1, + 's' }; + + testSimpleSerializer( + std::tuple(1, 1, 's'), + expected); +} + +template +static void testPadFieldTo(Ctx& ctx, uint8_t& a, uint16_t& b, uint32_t& c) +{ + { + PadFieldTo _(ctx, 3); + ctx % a; + } + + { + PadFieldTo _(ctx, 2); + ctx % b; + } + + { + PadFieldTo _(ctx, 8); + } + + { + PadFieldTo _(ctx, 2); + ctx % c; + } + + { + PadFieldTo _(ctx, 8); + ctx % c; + } +} + +TEST(Serialization, padFieldTo) +{ + const char expected[] = { + 1, 0, 0, + 1, 1, + // field empty + 1, 1, 1, 1, + 1, 1, 1, 1, 0, 0, 0, 0, }; + + uint8_t a = 0x01; + uint16_t b = 0x0101; + uint32_t c = 0x01010101; + + { + Serializer ser; + ::testPadFieldTo(ser, a, b, c); + ASSERT_EQ(ser.size(), sizeof(expected)); + } + + { + char outBuf[sizeof(expected)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ::testPadFieldTo(ser, a, b, c); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expected, sizeof(expected))); + } + + { + Deserializer des(expected, sizeof(expected)); + uint8_t a = 0; + uint16_t b = 0; + uint32_t c = 0; + + ::testPadFieldTo(des, a, b, c); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(a, 0x01); + ASSERT_EQ(b, 0x0101); + ASSERT_EQ(c, 0x01010101u); + } + + { + Deserializer des(expected, sizeof(expected) - 1); + uint8_t a = 0; + uint16_t b = 0; + uint32_t c = 0; + + ::testPadFieldTo(des, a, b, c); + ASSERT_FALSE(des.good()); + } +} + +namespace { +enum class TestEnum1 { first = 1, second = -2, }; +GCC_COMPAT_ENUM_CLASS_OPEQNEQ(TestEnum1) + +struct EnumAsMod { + auto operator()(TestEnum1& value) + -> decltype(serdes::as(value)) + { + return serdes::as(value); + } + + auto operator()(const TestEnum1& value) + -> decltype(serdes::as(value)) + { + return serdes::as(value); + } +}; +} + +template<> +struct SerializeAs { + typedef int16_t type; +}; + +TEST(Serialization, serializeAs) +{ + { + const char expected[] = { + 1, 0, + '\xfe', '\xff', }; + + testSimpleSerializer(std::make_tuple(TestEnum1::first, TestEnum1::second), expected); + } + + { + const char expected[] = { 1, 0, 0, 0, 0, 0, 0, 0, }; + + TestEnum1 value = TestEnum1::first; + testObject(value, expected, EnumAsMod()); + } +} + +TEST(Serialization, rawString) +{ + { + const char expected[] = { + 2, 0, 0, 0, + 'a', 'b', 0, }; + const char* input = expected + 4; + + { + Serializer ser; + ser % serdes::rawString(input, 2); + ASSERT_EQ(ser.size(), sizeof(expected)); + } + + { + char outBuf[sizeof(expected)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ser % serdes::rawString(input, 2); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expected, sizeof(expected))); + } + + { + Deserializer des(expected, sizeof(expected)); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(data, input); + ASSERT_EQ(length, 2u); + } + + // short length field + { + Deserializer des(expected, 1); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length); + ASSERT_FALSE(des.good()); + } + + // short data + { + Deserializer des(expected, sizeof(expected) - 1); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length); + ASSERT_FALSE(des.good()); + } + } + + { + const char expectedAligned[] = { + 2, 0, 0, 0, + 'a', 'b', 0, 0, }; + const char* input = expectedAligned + 4; + + { + Serializer ser; + ser % serdes::rawString(input, 2, 4); + ASSERT_EQ(ser.size(), sizeof(expectedAligned)); + } + + { + char outBuf[sizeof(expectedAligned)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ser % serdes::rawString(input, 2, 4); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expectedAligned, sizeof(expectedAligned))); + } + + { + Deserializer des(expectedAligned, sizeof(expectedAligned)); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length, 4); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expectedAligned)); + ASSERT_EQ(data, input); + ASSERT_EQ(length, 2u); + } + + // short length field + { + Deserializer des(expectedAligned, 1); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length, 4); + ASSERT_FALSE(des.good()); + } + + // short data + { + Deserializer des(expectedAligned, sizeof(expectedAligned) - 1); + const char* data = nullptr; + unsigned length = 0; + + des % serdes::rawString(data, length, 4); + ASSERT_FALSE(des.good()); + } + } + + { + const char badTerminator[] = { + 2, 0, 0, 0, + 'a', 'b', 1, }; + + Deserializer des(badTerminator, sizeof(badTerminator)); + const char* data = nullptr; + unsigned length = 0; + des % serdes::rawString(data, length); + ASSERT_FALSE(des.good()); + } +} + +namespace { +struct StringAlign4 +{ + serdes::RawStringSer operator()(const std::string& s) { return serdes::stringAlign4(s); } + serdes::StdString operator()( std::string& s) { return serdes::stringAlign4(s); } +}; +} + +TEST(Serialization, str) +{ + { + const char* input = "test"; + const char expected[] = { + 4, 0, 0, 0, + 't', 'e', 's', 't', 0, }; + + testSimpleSerializer(std::string(input), expected); + } + { + const char* input = "test"; + const char expected[] = { + 4, 0, 0, 0, + 't', 'e', 's', 't', 0, + 0, 0, 0, }; + + char outBuf[sizeof(expected)]; + + { + Serializer ser(outBuf, sizeof(expected)); + ser % serdes::rawString(input, strlen(input), 4); + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), sizeof(expected)); + ASSERT_EQ(memcmp(outBuf, expected, sizeof(expected)), 0); + } + + { + Deserializer des(outBuf, sizeof(expected)); + const char* output = nullptr; + unsigned outLen = 0; + + des % serdes::rawString(output, outLen, 4); + ASSERT_TRUE(des.good()); + ASSERT_EQ(outLen, strlen(input)); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(strcmp(input, output), 0); + } + + { + Deserializer des(outBuf, sizeof(expected) - 1); + const char* output = nullptr; + unsigned outLen = 0; + + des % serdes::rawString(output, outLen, 4); + ASSERT_FALSE(des.good()); + } + + { + std::string str = input; + testObject(str, expected, StringAlign4()); + } + + // one more, but with one byte of prefix. without the prefix, the result should still be the + // same + { + const char expected[] = { + 1, + 4, 0, 0, 0, + 't', 'e', 's', 't', 0, + 0, 0, 0, }; + + char outBuf[sizeof(expected)]; + + { + Serializer ser(outBuf, sizeof(expected)); + ser + % char(1) + % serdes::stringAlign4(input); + + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), sizeof(expected)); + } + + ASSERT_EQ(memcmp(outBuf, expected, sizeof(expected) ), 0); + + { + Deserializer des(outBuf, sizeof(expected)); + const char* output = nullptr; + unsigned outLen = 0; + + des.skip(1); + des % serdes::rawString(output, outLen, 4); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(strcmp(input, output), 0); + } + + { + Deserializer des(outBuf, sizeof(expected) - 1); + const char* output = nullptr; + unsigned outLen = 0; + + des.skip(1); + des % serdes::rawString(output, outLen, 4); + ASSERT_FALSE(des.good()); + } + } + } +} + +namespace { +struct HeapU32 +{ + uint32_t value; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } +}; + +inline Deserializer& operator%(Deserializer& des, std::unique_ptr& ptr) +{ + ptr.reset(new HeapU32); + return des % *ptr; +} +} + +TEST(Serialization, backedPtr) +{ + const char expected[] = { 1, 0, 0, 0 }; + + uint32_t value = 1; + uint32_t* ptr = &value; + + { + Serializer ser; + ser % serdes::backedPtr(ptr, (const uint32_t&) value); + ASSERT_EQ(ser.size(), sizeof(expected)); + } + + { + char outBuf[sizeof(expected)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ser % serdes::backedPtr(ptr, (const uint32_t&) value); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expected, sizeof(expected))); + } + + { + char outBuf[sizeof(expected)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ser % serdes::backedPtr((const uint32_t*) ptr, (const uint32_t&) value); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expected, sizeof(expected))); + } + + { + Deserializer des(expected, sizeof(expected)); + uint32_t* ptr = nullptr; + + des % serdes::backedPtr(ptr, value); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(*ptr, 1u); + ASSERT_EQ(ptr, &value); + } + + { + Deserializer des(expected, sizeof(expected)); + const uint32_t* ptr = nullptr; + + des % serdes::backedPtr(ptr, value); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(*ptr, 1u); + ASSERT_EQ(ptr, &value); + } + + { + Deserializer des(expected, sizeof(expected)); + HeapU32 value{1}; + HeapU32* ptr = &value; + std::unique_ptr backing; + + des % serdes::backedPtr(ptr, backing); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(ptr->value, 1u); + ASSERT_EQ(ptr, backing.get()); + } + + { + Deserializer des(expected, sizeof(expected) - 1); + uint32_t* ptr = nullptr; + + des % serdes::backedPtr(ptr, value); + ASSERT_FALSE(des.good()); + } +} + +TEST(Serialization, rawBlock) +{ + const char expected[] = { 1, 0, 0, 0 }; + + { + Serializer ser; + ser % serdes::rawBlock(expected, 4); + ASSERT_EQ(ser.size(), sizeof(expected)); + } + + { + char outBuf[sizeof(expected)]; + + Serializer ser(outBuf, sizeof(outBuf)); + ser % serdes::rawBlock(expected, 4); + ASSERT_EQ(ser.size(), sizeof(expected)); + ASSERT_TRUE(ser.good()); + ASSERT_TRUE(memoryEquals(outBuf, expected, sizeof(expected))); + } + + { + Deserializer des(expected, sizeof(expected)); + const char* data; + uint32_t size = 4; + + des % serdes::rawBlock(data, size); + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), sizeof(expected)); + ASSERT_EQ(data, expected); + } + + { + Deserializer des(expected, sizeof(expected) - 1); + const char* data; + uint32_t size = 4; + + des % serdes::rawBlock(data, size); + ASSERT_FALSE(des.good()); + } +} + +TEST(Serialization, block) +{ + // wrapping size_t invalidates serializer + { + char buffer; + Serializer ser(&buffer, 1); + ser % char(1); + ser.putBlock(nullptr, size_t(-1)); + ASSERT_FALSE(ser.good()); + } + { + char buffer; + Deserializer des(&buffer, 1); + des % buffer; + des.getBlock(nullptr, size_t(-1)); + ASSERT_FALSE(des.good()); + } + { + char buffer; + Deserializer des(&buffer, 1); + des % buffer; + des.skip(size_t(-1)); + ASSERT_FALSE(des.good()); + } + + // going past end invalidates serializer + { + char buffer; + Serializer ser(&buffer, 1); + ser % uint32_t(1); + ASSERT_FALSE(ser.good()); + } + { + char buffer; + Deserializer des(&buffer, 1); + des % buffer; + des.skip(1); + ASSERT_FALSE(des.good()); + } + + // skipping nothing does nothing + { + char buffer; + Serializer ser(&buffer, 1); + ASSERT_EQ(ser.size(), 0u); + ASSERT_TRUE(ser.good()); + ser.skip(0); + ASSERT_EQ(ser.size(), 0u); + ASSERT_TRUE(ser.good()); + } + { + char buffer; + Deserializer des(&buffer, 1); + des % buffer; + ASSERT_EQ(des.size(), 1u); + ASSERT_TRUE(des.good()); + des.skip(0); + ASSERT_EQ(des.size(), 1u); + ASSERT_TRUE(des.good()); + } + + // blocks zeroes memory + { + char buffer[128]; + + for (unsigned i = 0; i < sizeof(buffer); i++) + buffer[i] = 1; + + Serializer ser(buffer, sizeof(buffer)); + ser.skip(sizeof(buffer)); + ASSERT_EQ(ser.size(), sizeof(buffer)); + ASSERT_TRUE(ser.good()); + + for (unsigned i = 0; i < sizeof(buffer); i++) + ASSERT_EQ(buffer[i], 0); + } +} + +namespace { +struct NonPrimitive +{ + uint32_t data; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->data; + } + + friend bool operator==(NonPrimitive a, NonPrimitive b) + { + return a.data == b.data; + } +}; +} + +TEST(Serialization, nonPrimitive) +{ + NonPrimitive value{1}; + const char expected[] = { 1, 0, 0, 0 }; + + testSimpleSerializer(value, expected); +} + +namespace { +template +struct CollectionItem +{ + uint8_t value; + + CollectionItem() = default; + + CollectionItem(uint8_t value) : value(value) {} + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->value; + } + + friend bool operator<(CollectionItem a, CollectionItem b) + { + return a.value < b.value; + } + + friend bool operator==(CollectionItem a, CollectionItem b) + { + return a.value == b.value; + } +}; + +typedef CollectionItem ItemWithLength; +typedef CollectionItem ItemWithoutLength; +} + +template<> +struct ListSerializationHasLength : std::true_type {}; + +template<> +struct MapSerializationHasLength : std::true_type {}; + +template<> +struct ListSerializationHasLength : std::false_type {}; + +template<> +struct MapSerializationHasLength : std::false_type {}; + +template +static void testCollection(const char (&content)[N], + const typename CollectionWithLength::value_type& withLength, + const typename CollectionWithoutLength::value_type& withoutLength) +{ + SCOPED_TRACE(__PRETTY_FUNCTION__); + + // without length, empty + { + std::vector expected = {0, 0, 0, 0}; + CollectionWithoutLength empty; + testObject(empty, &expected.front(), &expected.back() + 1); + } + + // with length, empty + { + std::vector expected = { + 8, 0, 0, 0, + 0, 0, 0, 0}; + CollectionWithLength empty; + testObject(empty, &expected.front(), &expected.back() + 1); + } + + // without length, value + { + std::vector expected = {1, 0, 0, 0}; + expected.insert(expected.end(), content, content + N); + CollectionWithoutLength collection{{withoutLength}}; + testObject(collection, &expected.front(), &expected.back() + 1); + } + + // with length, value + { + // 64 to ensure that the result is well within the range of signed char + static_assert(8 + N < 64); + + std::vector expected = { + 8 + N, 0, 0, 0, + 1, 0, 0, 0}; + expected.insert(expected.end(), content, content + N); + CollectionWithLength collection{{withLength}}; + testObject(collection, &expected.front(), &expected.back() + 1); + } + + // with incorrect length + { + std::vector expected = { + 7, 0, 0, 0, + 0, 0, 0, 0}; + + CollectionWithLength value; + Deserializer des(&expected[0], expected.size()); + des % value; + ASSERT_FALSE(des.good()); + } + + // with corrupted length + { + std::vector expected = {7, 0, 0}; + + CollectionWithLength value; + Deserializer des(&expected[0], expected.size()); + des % value; + ASSERT_FALSE(des.good()); + } + + // with length, corrupted size + { + std::vector expected = { + 8, 0, 0, 0, + 1, 0, 0}; + + CollectionWithLength value; + Deserializer des(&expected[0], expected.size()); + des % value; + ASSERT_FALSE(des.good()); + } + + // without length, corrupted size + { + std::vector expected = {1, 0, 0}; + + CollectionWithoutLength value; + Deserializer des(&expected[0], expected.size()); + des % value; + ASSERT_FALSE(des.good()); + } +} + +TEST(Serialization, collections) +{ + { + const char expected[] = {1}; + testCollection< + std::list, + std::list>(expected, 1, 1); + testCollection< + std::vector, + std::vector>(expected, 1, 1); + testCollection< + std::set, + std::set>(expected, 1, 1); + } + { + const char expected[] = {1, 2}; + testCollection< + std::map, + std::map>(expected, {1, 2}, {1, 2}); + } +} + +namespace { +template +struct AtomicMod +{ + template + serdes::AtomicAsSer operator()(const Atomic& t) const + { + return serdes::atomicAs(t); + } + + template + serdes::AtomicAsDes operator()(Atomic& t) const + { + return serdes::atomicAs(t); + } +}; + +struct AtomicEq +{ + template + bool operator()(const Atomic& a, const Atomic& b) const + { + return a.read() == b.read(); + } +}; +} + +TEST(Serialization, atomic) +{ + const char expected[] = {1, 0, 0, 0}; + Atomic value(1); + testObject(value, expected, AtomicMod(), AtomicEq()); +} + +namespace { +struct BaseType +{ + uint8_t a; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx % obj->a; + } +}; + +struct DerivedType : BaseType +{ + uint32_t b; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::base(obj) + % obj->b; + } + + friend bool operator==(DerivedType a, DerivedType b) + { + return a.a == b.a && a.b == b.b; + } +}; +} + +TEST(Serialization, base) +{ + DerivedType value; + value.a = 1; + value.b = 2; + + const char expected[] = { + 1, + 2, 0, 0, 0 + }; + + testSimpleSerializer(value, expected); +} + +template class Collection, size_t N> +static void testStringCollection(const char (&expected)[N]) +{ + SCOPED_TRACE(__PRETTY_FUNCTION__); + + // empty + { + const char expected[] = { + 8, 0, 0, 0, + 0, 0, 0, 0}; + Collection empty; + testSimpleSerializer(empty, expected); + } + + // values b, a + { + Collection values; + values.insert(values.end(), "b"); + values.insert(values.end(), "a"); + testSimpleSerializer(values, expected); + } + + // corrupt length field + { + const char input[] = { 0 }; + Deserializer des(input, sizeof(input)); + Collection value; + des % value; + ASSERT_FALSE(des.good()); + } + + // corrupt size field + { + const char input[] = { + 8, 0, 0, 0, + 0, 0, 0}; + Deserializer des(input, sizeof(input)); + Collection value; + des % value; + ASSERT_FALSE(des.good()); + } + + // too many items + { + const char input[] = { + 8, 0, 0, 0, + 1, 0, 0, 0}; + Deserializer des(input, sizeof(input)); + Collection value; + des % value; + ASSERT_FALSE(des.good()); + } + + // too much length + { + const char input[] = { + 9, 0, 0, 0, + 0, 0, 0, 0}; + Deserializer des(input, sizeof(input)); + Collection value; + des % value; + ASSERT_FALSE(des.good()); + } + + // dump not properly terminated + { + const char input[] = { + 10, 0, 0, 0, + 1, 0, 0, 0, + 'a', 'a'}; + Deserializer des(input, sizeof(input)); + Collection value; + des % value; + ASSERT_FALSE(des.good()); + } +} + +TEST(Serialization, stringCollections) +{ + { + const char expected[] = { + 12, 0, 0, 0, + 2, 0, 0, 0, + 'b', 0, 'a', 0}; + + testStringCollection(expected); + testStringCollection(expected); + } + { + const char expected[] = { + 12, 0, 0, 0, + 2, 0, 0, 0, + 'a', 0, 'b', 0}; + + testStringCollection(expected); + } +} diff --git a/common/tests/TestSocket.cpp b/common/tests/TestSocket.cpp new file mode 100644 index 0000000..5d1bcfb --- /dev/null +++ b/common/tests/TestSocket.cpp @@ -0,0 +1,20 @@ +#include + +#include + + +class TestSocket : public ::testing::Test +{ +}; + +TEST_F(TestSocket, endpointAddrToStrSockaddr) +{ + std::string addr = "10.20.30.40"; + short port = 1234; + struct sockaddr_in sin; + + sin.sin_port = htons(port); + EXPECT_EQ(1, inet_pton(AF_INET, addr.c_str(), &sin.sin_addr)); + EXPECT_EQ(addr + ":" + std::to_string(port), Socket::endpointAddrToStr(&sin)); +} + diff --git a/common/tests/TestStorageTk.cpp b/common/tests/TestStorageTk.cpp new file mode 100644 index 0000000..26c4545 --- /dev/null +++ b/common/tests/TestStorageTk.cpp @@ -0,0 +1,90 @@ +#include + +#include + +// full set of characters allowed in path components, properly escaped as by /proc/mounts +#define FULL_SET "\001\002\003\004\005\006\007\010\\011\\012\013\014\015\016\017\020\021" \ + "\022\023\024\025\026\027\030\031\032\033\034\035\036\037\\040!\"#$%&'()*+,-.0123456789:;<=>?" \ + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\134]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204" \ + "\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233" \ + "\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262" \ + "\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311" \ + "\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340" \ + "\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367" \ + "\370\371\372\373\374\375\376\377" +#define FULL_SET_RAW "\001\002\003\004\005\006\007\010\011\012\013\014\015\016\017\020\021" \ + "\022\023\024\025\026\027\030\031\032\033\034\035\036\037\040!\"#$%&'()*+,-.0123456789:;<=>?" \ + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\134]^_`abcdefghijklmnopqrstuvwxyz{|}~\177\200\201\202\203\204" \ + "\205\206\207\210\211\212\213\214\215\216\217\220\221\222\223\224\225\226\227\230\231\232\233" \ + "\234\235\236\237\240\241\242\243\244\245\246\247\250\251\252\253\254\255\256\257\260\261\262" \ + "\263\264\265\266\267\270\271\272\273\274\275\276\277\300\301\302\303\304\305\306\307\310\311" \ + "\312\313\314\315\316\317\320\321\322\323\324\325\326\327\330\331\332\333\334\335\336\337\340" \ + "\341\342\343\344\345\346\347\350\351\352\353\354\355\356\357\360\361\362\363\364\365\366\367" \ + "\370\371\372\373\374\375\376\377" + +TEST(StorageTk, findMountedPrefix) +{ + static const char* inputFile = + "/dev/root / tmpfs rw 0 0\n" + "/dev/sda1 /test/foo/bar xfs rw 0 0\n" + "/dev/sda1 /test/fo ext4 rw 0 0\n" + "/dev/sda1 /test/" FULL_SET " ext4 rw 0 0\n" + "/dev/sda1 /test/foo ext4 rw 0 0\n"; + + { + std::stringstream file; + ASSERT_FALSE(StorageTk::findLongestMountedPrefix("", file).first); + } + + { + std::stringstream file; + ASSERT_FALSE(StorageTk::findLongestMountedPrefix("relative", file).first); + } + + { + std::stringstream file; + file.setstate(std::stringstream::failbit); + ASSERT_FALSE(StorageTk::findLongestMountedPrefix("/", file).first); + } + + { + std::stringstream file; + auto res = StorageTk::findLongestMountedPrefix("/test", file); + ASSERT_FALSE(res.first); + } + + { + std::stringstream file(inputFile); + auto res = StorageTk::findLongestMountedPrefix("/test/foo1", file); + ASSERT_TRUE(res.first); + ASSERT_EQ(res.second, (Mount{"/dev/root", "/", "tmpfs"})); + } + + { + std::stringstream file(inputFile); + auto res = StorageTk::findLongestMountedPrefix("/test/foo/bar/baz", file); + ASSERT_TRUE(res.first); + ASSERT_EQ(res.second, (Mount{"/dev/sda1", "/test/foo/bar", "xfs"})); + } + + { + std::stringstream file(inputFile); + auto res = StorageTk::findLongestMountedPrefix("/test/foo", file); + ASSERT_TRUE(res.first); + ASSERT_EQ(res.second, (Mount{"/dev/sda1", "/test/foo", "ext4"})); + } + + { + std::stringstream file(inputFile); + auto res = StorageTk::findLongestMountedPrefix("/test/" FULL_SET_RAW, file); + ASSERT_TRUE(res.first); + ASSERT_EQ(res.second, (Mount{"/dev/sda1", "/test/" FULL_SET_RAW, "ext4"})); + } + + { + std::stringstream file(inputFile); + auto res = StorageTk::findLongestMountedPrefix("/test/" FULL_SET_RAW "/sub", file); + ASSERT_TRUE(res.first); + ASSERT_EQ(res.second, (Mount{"/dev/sda1", "/test/" FULL_SET_RAW, "ext4"})); + } +} diff --git a/common/tests/TestStringTk.cpp b/common/tests/TestStringTk.cpp new file mode 100644 index 0000000..fd8dfb3 --- /dev/null +++ b/common/tests/TestStringTk.cpp @@ -0,0 +1,15 @@ +#include + +#include +#include +#include +#include + +#include + +TEST(StringTk, implode) +{ + EXPECT_EQ(std::string(""), StringTk::implode(",", std::vector())); + EXPECT_EQ(std::string("1"), StringTk::implode(",", std::vector({1}))); + EXPECT_EQ(std::string("1,2,3"), StringTk::implode(",", std::vector({1,2,3}))); +} diff --git a/common/tests/TestStripePattern.cpp b/common/tests/TestStripePattern.cpp new file mode 100644 index 0000000..115c2af --- /dev/null +++ b/common/tests/TestStripePattern.cpp @@ -0,0 +1,40 @@ +#include +#include +#include + +#include + + +class Raid0PatternTest : public testing::TestWithParam +{ +}; + +INSTANTIATE_TEST_CASE_P(Name, Raid0PatternTest, + ::testing::Values( + 64*1024, + 1024*1024*1024 + ) + ); + +TEST_P(Raid0PatternTest, chunkSizes) +{ + const auto chunkSize = GetParam(); + const auto targetPattern = std::vector({0,1,2,3}); + const Raid0Pattern p(chunkSize, targetPattern); + + ASSERT_EQ(p.getChunkSize(), chunkSize); + + for(size_t i=0; i<10*targetPattern.size(); ++i) { + + const auto chunkPos = int64_t(i) * chunkSize; + const auto chunkEnd = chunkPos + chunkSize -1; + + ASSERT_EQ(p.getStripeTargetIndex(chunkPos), i % targetPattern.size()); + ASSERT_EQ(p.getStripeTargetIndex(chunkEnd), i % targetPattern.size()); + + + ASSERT_EQ(p.getChunkStart(chunkPos), chunkPos); + ASSERT_EQ(p.getChunkStart(chunkEnd), chunkPos); + } +} + diff --git a/common/tests/TestTargetCapacityPools.cpp b/common/tests/TestTargetCapacityPools.cpp new file mode 100644 index 0000000..5cc7349 --- /dev/null +++ b/common/tests/TestTargetCapacityPools.cpp @@ -0,0 +1,18 @@ +#include + +#include + +TEST(TargetCapacityPools, interdomainWithEmptyGroups) +{ + TargetCapacityPools pools(false, {0, 0, 0, 0, 0, 0}, {0, 0, 0, 0, 0, 0}); + + pools.addOrUpdate(1, NumNodeID(1), CapacityPool_NORMAL); + // moves target from LOW to NORMAL, must remove the NORMAL group from chooser + pools.addOrUpdate(1, NumNodeID(1), CapacityPool_LOW); + + std::vector chosen; + pools.chooseTargetsInterdomain(4, 1, &chosen); + + EXPECT_EQ(chosen.size(), 1u); + ASSERT_EQ(chosen[0], 1); +} diff --git a/common/tests/TestTimerQueue.cpp b/common/tests/TestTimerQueue.cpp new file mode 100644 index 0000000..4763b5c --- /dev/null +++ b/common/tests/TestTimerQueue.cpp @@ -0,0 +1,68 @@ +#include +#include + +#include + +class TestTimerQueue : public ::testing::Test { + protected: + std::unique_ptr queue; + + void SetUp() override + { + queue.reset(new TimerQueue(0, 20)); + queue->start(); + } +}; + +namespace { + struct EnqueueCancelFn + { + AtomicSizeT* count; + + void operator()() + { + count->increase(); + } + }; +} + +TEST_F(TestTimerQueue, enqueueCancel) +{ + AtomicSizeT count(0); + + auto handle = queue->enqueue(std::chrono::milliseconds(100), EnqueueCancelFn{&count}); + handle.cancel(); + + sleep(1); + ASSERT_EQ(count.read(), 0u); +} + +namespace { + struct EnqueueManyLongFn + { + void operator()(AtomicSizeT& count) + { + sleep(1); + count.increase(); + } + }; +} + +TEST_F(TestTimerQueue, enqueueManyLong) +{ + AtomicSizeT count(0); + EnqueueManyLongFn fn; + + for (int i = 0; i < 42; i++) + queue->enqueue( + std::chrono::milliseconds(10), + std::bind(&EnqueueManyLongFn::operator(), &fn, std::ref(count))); + + Time begin; + while (count.read() != 42) + sleep(1); + + Time end; + + ASSERT_LT(end.elapsedSinceMS(&begin), 20 * 1000u); +} diff --git a/common/tests/TestUiTk.cpp b/common/tests/TestUiTk.cpp new file mode 100644 index 0000000..2088cc3 --- /dev/null +++ b/common/tests/TestUiTk.cpp @@ -0,0 +1,33 @@ +#include + +#include + +#include + +bool testQuestion(const std::string& answer, boost::optional defaultAnswer=boost::none) +{ + std::stringstream input(answer); + return uitk::userYNQuestion("Test", defaultAnswer, input); +} + +TEST(UiTk, userYNQuestion) +{ + // basic input + ASSERT_TRUE( testQuestion("Y")); + ASSERT_FALSE(testQuestion("N")); + + ASSERT_TRUE( testQuestion("y")); + ASSERT_FALSE(testQuestion("n")); + + ASSERT_TRUE( testQuestion("Yes")); + ASSERT_FALSE(testQuestion("No")); + + // default answers + ASSERT_TRUE( testQuestion("", true)); + ASSERT_FALSE(testQuestion("", false)); + + ASSERT_TRUE( testQuestion("Y", false)); + ASSERT_TRUE( testQuestion("Y", false)); + ASSERT_FALSE(testQuestion("N", true)); + ASSERT_FALSE(testQuestion("N", true)); +} diff --git a/common/tests/TestUnitTk.cpp b/common/tests/TestUnitTk.cpp new file mode 100644 index 0000000..f46d465 --- /dev/null +++ b/common/tests/TestUnitTk.cpp @@ -0,0 +1,151 @@ +#include + +#include + +TEST(UnitTk, gigabyteToByte) +{ + double gbValue = 1.0; + int64_t byteValueExpected = 1073741824LL; + int64_t byteValueCalc = UnitTk::gibibyteToByte(gbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + gbValue = 10.0; + byteValueExpected = 10737418240LL; + byteValueCalc = UnitTk::gibibyteToByte(gbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + gbValue = 10.598; + byteValueExpected = 11379515850LL; + + byteValueCalc = UnitTk::gibibyteToByte(gbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); +} + +TEST(UnitTk, megabyteToByte) +{ + double mbValue = 1.0; + int64_t byteValueExpected = 1048576LL; + int64_t byteValueCalc = UnitTk::mebibyteToByte(mbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + mbValue = 10.0; + byteValueExpected = 10485760LL; + byteValueCalc = UnitTk::mebibyteToByte(mbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + mbValue = 10.598; + byteValueExpected = 11112808LL; + byteValueCalc = UnitTk::mebibyteToByte(mbValue);; + ASSERT_EQ(byteValueExpected, byteValueCalc); +} + +TEST(UnitTk, kilobyteToByte) +{ + double kbValue = 1.0; + int64_t byteValueExpected = 1024LL; + int64_t byteValueCalc = UnitTk::kibibyteToByte(kbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + kbValue = 10.0; + byteValueExpected = 10240LL; + byteValueCalc = UnitTk::kibibyteToByte(kbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + kbValue = 10.598; + byteValueExpected = 10852LL; + byteValueCalc = UnitTk::kibibyteToByte(kbValue); + ASSERT_EQ(byteValueExpected, byteValueCalc); +} + +TEST(UnitTk, byteToXbyte) +{ + std::string unit; + int64_t value = 2048LL; + double valueExpected = 2.0; + double valueCalc = UnitTk::byteToXbyte(value, &unit); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("KiB"), 0); + + value = 10240LL; + valueExpected = 10.0; + valueCalc = UnitTk::byteToXbyte(value, &unit); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("KiB"), 0); + + value = 10843LL; + valueExpected = 10.6; + valueCalc = UnitTk::byteToXbyte(value, &unit, true); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("KiB"), 0); + + value = 1073741824LL; + valueExpected = 1024.0; + valueCalc = UnitTk::byteToXbyte(value, &unit); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("MiB"), 0); + + value = 10737418240LL; + valueExpected = 10.0; + valueCalc = UnitTk::byteToXbyte(value, &unit); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("GiB"), 0); + + value = 11446087843LL; + valueExpected = 10.7; + valueCalc = UnitTk::byteToXbyte(value, &unit, true); + ASSERT_EQ(valueExpected, valueCalc); + ASSERT_EQ(unit.compare("GiB"), 0); +} + +TEST(UnitTk, xbyteToByte) +{ + std::string unit = "KiB"; + double value = 1.0; + int64_t byteValueExpected = 1024LL; + int64_t byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.0; + byteValueExpected = 10240LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.598; + byteValueExpected = 10852LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + + unit = "MiB"; + value = 1.0; + byteValueExpected = 1048576LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.0; + byteValueExpected = 10485760LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.598; + byteValueExpected = 11112808LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + + unit = "GiB"; + value = 1.0; + byteValueExpected = 1073741824LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.0; + byteValueExpected = 10737418240LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); + + value = 10.598; + byteValueExpected = 11379515850LL; + byteValueCalc = UnitTk::xbyteToByte(value, unit); + ASSERT_EQ(byteValueExpected, byteValueCalc); +} diff --git a/debian/beegfs-client.lintian-overrides b/debian/beegfs-client.lintian-overrides new file mode 100644 index 0000000..0b2edf7 --- /dev/null +++ b/debian/beegfs-client.lintian-overrides @@ -0,0 +1,4 @@ +shell-script-fails-syntax-check +init.d-script-does-not-source-init-functions +init.d-script-has-unknown-lsb-keyword +script-not-executable diff --git a/debian/beegfs-client.postinst b/debian/beegfs-client.postinst new file mode 100755 index 0000000..47bea08 --- /dev/null +++ b/debian/beegfs-client.postinst @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +mkdir -p /var/lib/beegfs/client/ +touch /var/lib/beegfs/client/force-auto-build + +#DEBHELPER# diff --git a/debian/beegfs-client.postrm b/debian/beegfs-client.postrm new file mode 100755 index 0000000..73a10f1 --- /dev/null +++ b/debian/beegfs-client.postrm @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e +rm -rf /lib/modules/*/updates/fs/beegfs_autobuild + +#DEBHELPER# diff --git a/debian/beegfs-helperd.lintian-overrides b/debian/beegfs-helperd.lintian-overrides new file mode 100644 index 0000000..0e9dd95 --- /dev/null +++ b/debian/beegfs-helperd.lintian-overrides @@ -0,0 +1,2 @@ +init.d-script-does-not-source-init-functions +init.d-script-has-unknown-lsb-keyword diff --git a/debian/beegfs-meta.lintian-overrides b/debian/beegfs-meta.lintian-overrides new file mode 100644 index 0000000..0e9dd95 --- /dev/null +++ b/debian/beegfs-meta.lintian-overrides @@ -0,0 +1,2 @@ +init.d-script-does-not-source-init-functions +init.d-script-has-unknown-lsb-keyword diff --git a/debian/beegfs-opentk-lib.lintian-overrides b/debian/beegfs-opentk-lib.lintian-overrides new file mode 100644 index 0000000..61d258a --- /dev/null +++ b/debian/beegfs-opentk-lib.lintian-overrides @@ -0,0 +1 @@ +binary-without-manpage diff --git a/debian/beegfs-storage.lintian-overrides b/debian/beegfs-storage.lintian-overrides new file mode 100644 index 0000000..0e9dd95 --- /dev/null +++ b/debian/beegfs-storage.lintian-overrides @@ -0,0 +1,2 @@ +init.d-script-does-not-source-init-functions +init.d-script-has-unknown-lsb-keyword diff --git a/debian/beegfs-utils.lintian-overrides b/debian/beegfs-utils.lintian-overrides new file mode 100644 index 0000000..8051f33 --- /dev/null +++ b/debian/beegfs-utils.lintian-overrides @@ -0,0 +1,2 @@ +binary-without-manpage +description-is-pkg-name diff --git a/debian/beeond.lintian-overrides b/debian/beeond.lintian-overrides new file mode 100644 index 0000000..e13f9d4 --- /dev/null +++ b/debian/beeond.lintian-overrides @@ -0,0 +1,2 @@ +binary-without-manpage +script-not-executable diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..e8f2419 --- /dev/null +++ b/debian/changelog @@ -0,0 +1,6 @@ +beegfs (0-dev) unstable; urgency=low + + * please see http://www.beegfs.com for changelog + + -- BeeGFS Maintainers Thu, 01 Jan 1970 01:00:00 +0100 + diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..48082f7 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +12 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..c0681af --- /dev/null +++ b/debian/control @@ -0,0 +1,113 @@ +Source: beegfs +Section: misc +Priority: optional +Maintainer: BeeGFS Maintainers +Build-Depends: debhelper (>= 12.0.0), lsb-release, librdmacm-dev, libibverbs-dev, kmod, + dkms (<= 3.0.0) | dh-dkms (>=3.0.0), pkg-config, zlib1g-dev, libnl-3-dev, + libnl-route-3-dev, libcurl-dev +Standards-Version: 4.1.4.1 + + + +Package: libbeegfs-ib +Architecture: any +Depends: ${misc:Depends}, ${shlibs:Depends} +Description: BeeGFS InfiniBand support + This package contains support libraries for InfiniBand. + + + +Package: beegfs-meta +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: libbeegfs-ib (= ${binary:Version}) +Description: BeeGFS metadata server daemon + The package contains the BeeGFS metadata daemon. + + + +Package: beegfs-storage +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: libbeegfs-ib (= ${binary:Version}) +Description: BeeGFS storage server daemon + This package contains the BeeGFS storage server binaries. + + + +Package: beegfs-mon +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: libbeegfs-ib (= ${binary:Version}) +Description: BeeGFS mon daemon + The package contains the BeeGFS mon daemon. + + + +Package: beegfs-mon-grafana +Architecture: all +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: BeeGFS mon dashboards for Grafana + This package contains the BeeGFS mon dashboards to display monitoring data in Grafana. + The default dashboard setup requires both Grafana, and InfluxDB. + + + +Package: beegfs-utils +Architecture: any +Depends: ${shlibs:Depends}, ${misc:Depends} +Recommends: libbeegfs-ib (= ${binary:Version}) +Description: BeeGFS utils + This package contains BeeGFS utilities. + + + +Package: beegfs-utils-dev +Architecture: any +Depends: ${misc:Depends} +Description: BeeGFS utils devel files + This package contains BeeGFS development files and examples. + +Package: beeond +Architecture: all +Depends: ${misc:Depends}, + beegfs-utils (= ${binary:Version}), beegfs-mgmtd (= ${binary:Version}), beegfs-meta (= ${binary:Version}), + beegfs-storage (= ${binary:Version}), + beegfs-client (= ${binary:Version}) | beegfs-client-dkms (= ${binary:Version}), + psmisc +Description: BeeOND + This package contains BeeOND. + + + +Package: beegfs-client +Architecture: all +Depends: ${misc:Depends}, build-essential +Conflicts: beegfs-client-dkms +Recommends: linux-headers-amd64 +Description: BeeGFS client + This package contains scripts, config and source files to build and + start beegfs-client. + +Package: beegfs-client-dkms +Architecture: all +Depends: ${misc:Depends}, dkms +Conflicts: beegfs-client +Recommends: linux-headers-amd64 +Description: BeeGFS client (DKMS version) + This package contains scripts, config and source files to build and + start beegfs-client. It uses DKMS to build the kernel module. + +Package: beegfs-client-compat +Architecture: all +Description: BeeGFS client compat module, allows one to run two different client versions. + This package allows one to build and to run a compatibility client + kernel module on a system that has a newer beegfs-client version + installed. +Depends: ${misc:Depends}, build-essential + +Package: beegfs-client-dev +Architecture: any +Depends: ${misc:Depends} +Description: BeeGFS client development files + This package contains BeeGFS client development files. diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..60c1c12 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,121 @@ +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Source: https://github.com/ThinkParQ/beegfs + +Files: * +Copyright: 2009 Fraunhofer ITWM, 2022 ThinkParQ GmbH +License: BeeGFS EULA + The complete text of this license can be found at + https://www.beegfs.io/docs/BeeGFS_EULA.txt + +Files: client_module/* +Copyright: 2009 Fraunhofer ITWM, 2022 ThinkParQ GmbH +License: GPL-2 + +Files: + common/source/common/toolkit/BufferTk.cpp + client_module/source/common/toolkit/HashTk.c +Comment: Contain code for a hash function distributed under the following license +License: Paul Hsieh OLD BSD license + Copyright (c) 2010, Paul Hsieh All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + + Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + Neither my name, Paul Hsieh, nor the names of any other contributors to + the code use may be used to endorse or promote products derived from this + software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + WARRANTIES OF MERCHANTABILITY FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR SEQUENTIAL DAMAGES (INCLUDING, + BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF , + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + OR OTHERWISE) ARISING ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: thirdparty/source/boost-1_61_0/* +License: Boost Software License - Version 1.0 - August 17th, 2003 + Permission is hereby granted, free of charge, to any person or organization + obtaining a copy of the software and accompanying documentation covered by + this license (the "Software") to use, reproduce, display, distribute, + execute, and transmit the Software, and to prepare derivative works of the + Software, and to permit third-parties to whom the Software is furnished to + do so, all subject to the following: + + The copyright notices in the Software and this entire statement, including + the above license grant, this restriction and the following disclaimer, + must be included in all copies of the Software, in whole or in part, and + all derivative works of the Software, unless such copies or derivative + works are solely in the form of machine-executable object code generated by + a source language processor. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT + SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE + FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, + ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. + +Files: thirdparty/source/datastax/cassandra.h +License: Apache + +Files: thirdparty/source/gtest-1.8.0/* +Copyright: 2008, Google Inc. +License: + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following disclaimer + in the documentation and/or other materials provided with the + distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Files: thirdparty/source/nu +Copyright: 2017 Phoebe Buckheister +License: + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..158ec5b --- /dev/null +++ b/debian/rules @@ -0,0 +1,175 @@ +#!/usr/bin/make -f + +# this is so awful +PAREN = ) +BEEGFS_VERSION = $(shell head -n1 debian/changelog | grep -P --only-matching '(?<=\d:)[^$(PAREN)~]+') +BEEGFS_MAJOR = $(shell head -n1 debian/changelog | grep -P --only-matching '(?<=\d:)\d+') + +BEEGFS_NVFS=1 +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) + NUMJOBS = $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) +endif + +%: + dh $@ --parallel --with dkms + +override_dh_auto_build: + make -j$(NUMJOBS) common-all daemons utils + +# no build-runnable tests exist (yet) +override_dh_auto_test: + +# package is split +override_dh_auto_install: + +override_dh_install: + @# libbeeggfs-ib + install -D common/build/libbeegfs_ib.so \ + debian/libbeegfs-ib/opt/beegfs/lib/libbeegfs_ib.so + @# daemons + make -j$(NUMJOBS) DESTDIR=debian/beegfs-meta meta-install + make -j$(NUMJOBS) DESTDIR=debian/beegfs-storage storage-install + make -j$(NUMJOBS) DESTDIR=debian/beegfs-mon mon-install + @# setup scripts + install -D storage/build/dist/sbin/beegfs-setup-storage \ + debian/beegfs-storage/opt/beegfs/sbin/beegfs-setup-storage + install -D meta/build/dist/sbin/beegfs-setup-meta \ + debian/beegfs-meta/opt/beegfs/sbin/beegfs-setup-meta + @# daemon configs and defaults + install -D -m644 storage/build/dist/etc/default/beegfs-storage \ + debian/beegfs-storage.default + install -D -m644 storage/build/dist/etc/beegfs-storage.conf \ + debian/beegfs-storage/etc/beegfs/beegfs-storage.conf + install -D -m644 meta/build/dist/etc/default/beegfs-meta \ + debian/beegfs-meta.default + install -D -m644 meta/build/dist/etc/beegfs-meta.conf \ + debian/beegfs-meta/etc/beegfs/beegfs-meta.conf + install -D -m644 mon/build/dist/etc/default/beegfs-mon \ + debian/beegfs-mon.default + install -D -m644 mon/build/dist/etc/beegfs-mon.conf \ + debian/beegfs-mon/etc/beegfs/beegfs-mon.conf + install -D -m600 mon/build/dist/etc/beegfs-mon.auth \ + debian/beegfs-mon/etc/beegfs/beegfs-mon.auth + @# mon-grafana + mkdir -p debian/beegfs-mon-grafana/opt/beegfs/scripts/grafana + cp -a mon/scripts/grafana/* debian/beegfs-mon-grafana/opt/beegfs/scripts/grafana + @# utils + make -j$(NUMJOBS) DESTDIR=debian/beegfs-utils fsck-install event_listener-install + mkdir -p debian/beegfs-utils/sbin + cp -a utils/scripts/fsck.beegfs debian/beegfs-utils/sbin + mkdir -p debian/beegfs-utils/usr/bin + mkdir -p debian/beegfs-utils/opt/beegfs/lib + mkdir -p debian/beegfs-utils/usr/sbin + ln -s /opt/beegfs/sbin/beegfs-fsck debian/beegfs-utils/usr/sbin/beegfs-fsck + @# utils-dev + mkdir -p debian/beegfs-utils-dev/usr/include + cp -a event_listener/include/* debian/beegfs-utils-dev/usr/include/ + mkdir -p debian/beegfs-utils-dev/usr/share/doc/beegfs-utils-dev/examples/beegfs-event-listener/source + cp -a event_listener/source/beegfs-event-listener.cpp \ + debian/beegfs-utils-dev/usr/share/doc/beegfs-utils-dev/examples/beegfs-event-listener/source + mkdir -p debian/beegfs-utils-dev/usr/share/doc/beegfs-utils-dev/examples/beegfs-event-listener/build + cp -a event_listener/build/Makefile \ + debian/beegfs-utils-dev/usr/share/doc/beegfs-utils-dev/examples/beegfs-event-listener/build/Makefile + @# beeond + mkdir -p debian/beeond/opt/beegfs/lib + mkdir -p debian/beeond/opt/beegfs/sbin + mkdir -p debian/beeond/usr/bin + install -D beeond/source/beeond debian/beeond/opt/beegfs/sbin/beeond + install -D beeond/source/beeond-cp debian/beeond/opt/beegfs/sbin/beeond-cp + cp beeond/scripts/lib/* debian/beeond/opt/beegfs/lib/ + ln -s /opt/beegfs/sbin/beeond debian/beeond/usr/bin/beeond + ln -s /opt/beegfs/sbin/beeond-cp debian/beeond/usr/bin/beeond-cp + @# client + make -j$(NUMJOBS) -C client_module/build \ + RELEASE_PATH=../../debian/beegfs-client/opt/beegfs/src/client \ + BEEGFS_VERSION=$(BEEGFS_VERSION) prepare_release + # for compat package + mkdir -p debian/beegfs-client-compat/opt/beegfs/src/client + cp -a debian/beegfs-client/opt/beegfs/src/client/client_module_$(BEEGFS_MAJOR) \ + debian/beegfs-client-compat/opt/beegfs/src/client/client_compat_module_$(BEEGFS_MAJOR) + # set the compat fstype + echo beegfs-$(BEEGFS_MAJOR) > \ + debian/beegfs-client-compat/opt/beegfs/src/client/client_compat_module_$(BEEGFS_MAJOR)/build/beegfs.fstype + install -D client_module/build/dist/sbin/beegfs-client.init \ + debian/beegfs-client/opt/beegfs/sbin/beegfs-client + mkdir -p debian/beegfs-client/etc/init.d + ln -s /opt/beegfs/sbin/beegfs-client debian/beegfs-client/etc/init.d/beegfs-client + install -D -m644 client_module/build/dist/etc/default/beegfs-client \ + debian/beegfs-client.default + install -D -m644 client_module/build/dist/etc/beegfs-client.conf \ + debian/beegfs-client/etc/beegfs/beegfs-client.conf + install -D -m644 client_module/build/dist/etc/beegfs-client-autobuild.conf \ + debian/beegfs-client/etc/beegfs/beegfs-client-autobuild.conf + install -D -m644 client_module/build/dist/etc/beegfs-mounts.conf \ + debian/beegfs-client/etc/beegfs/beegfs-mounts.conf + install -D -m644 client_module/scripts/etc/beegfs/lib/init-multi-mode.beegfs-client \ + debian/beegfs-client/etc/beegfs/lib/init-multi-mode.beegfs-client + install -D client_module/build/dist/sbin/mount.beegfs \ + debian/beegfs-client/sbin/mount.beegfs + install -D client_module/build/dist/sbin/beegfs-setup-client \ + debian/beegfs-client/opt/beegfs/sbin/beegfs-setup-client + install -D client_module/build/dist/etc/beegfs-client-mount-hook.example \ + debian/beegfs-client/etc/beegfs/beegfs-client-mount-hook.example + @# client-dkms + mkdir -p debian/beegfs-client-dkms/usr/src/beegfs-$(BEEGFS_VERSION) + cp -r client_module/build debian/beegfs-client-dkms/usr/src/beegfs-$(BEEGFS_VERSION) + cp -r client_module/source debian/beegfs-client-dkms/usr/src/beegfs-$(BEEGFS_VERSION) + cp -r client_module/include debian/beegfs-client-dkms/usr/src/beegfs-$(BEEGFS_VERSION) + rm -Rf debian/beegfs-client-dkms/usr/src/beegfs-$(BEEGFS_VERSION)/build/dist + sed -e 's/__VERSION__/$(BEEGFS_VERSION)/g' -e 's/__NAME__/beegfs/g' -e 's/__MODNAME__/beegfs/g' \ + < client_module/dkms.conf.in \ + > debian/beegfs-client-dkms.dkms + install -D -m644 client_module/build/dist/etc/beegfs-client.conf \ + debian/beegfs-client-dkms/etc/beegfs/beegfs-client.conf + install -D -m644 client_module/build/dist/etc/beegfs-client-build.mk \ + debian/beegfs-client-dkms/etc/beegfs/beegfs-client-build.mk + install -D client_module/build/dist/sbin/mount.beegfs \ + debian/beegfs-client-dkms/sbin/mount.beegfs + @# client-dev + mkdir -p debian/beegfs-client-dev/usr/include + cp -a client_devel/include/beegfs debian/beegfs-client-dev/usr/include/ + cp -a client_module/include/uapi/* debian/beegfs-client-dev/usr/include/beegfs/ + sed -i '~s~uapi/beegfs_client~beegfs/beegfs_client~g' debian/beegfs-client-dev/usr/include/beegfs/*.h + mkdir -p debian/beegfs-client-dev/usr/share/doc/beegfs-client-dev/examples/ + cp -a client_devel/build/dist/usr/share/doc/beegfs-client-devel/examples/* \ + debian/beegfs-client-dev/usr/share/doc/beegfs-client-dev/examples/ + +# Before dbgsym packages, package maintainers had to manually create their -dbg packages listed +# in the debian/control file. --dbgsym-migration=package-relation This option is used to migrate +# from a manual "-dbg" package (created with --dbg-package) to an automatic generated debug symbol package. +override_dh_strip: + dh_strip -pbeegfs-meta --dbgsym-migration='beegfs-meta-dbg (<< $(BEEGFS_VERSION)~)' + dh_strip -pbeegfs-mon --dbgsym-migration='beegfs-mon-dbg (<< $(BEEGFS_VERSION)~)' + dh_strip -pbeegfs-storage --dbgsym-migration='beegfs-storage-dbg (<< $(BEEGFS_VERSION)~)' + dh_strip -plibbeegfs-ib --dbgsym-migration='libbeegfs-ib-dbg (<< $(BEEGFS_VERSION)~)' + +# avoid auto-start because default config is not useful +override_dh_installinit: + for component in storage meta mon; do \ + cp $$component/build/dist/usr/lib/systemd/system/beegfs-$$component.service debian/; \ + install -D -m644 $$component/build/dist/usr/lib/systemd/system/beegfs-$$component@.service \ + debian/beegfs-$$component/lib/systemd/system/beegfs-$$component@.service; \ + dh_installsystemd -p beegfs-$$component --no-enable --name=beegfs-$$component@ beegfs-$$component@.service; \ + done + cp client_module/build/dist/usr/lib/systemd/system/beegfs-client.service debian/ + cp client_module/build/dist/usr/lib/systemd/system/beegfs-client@.service debian/ + dh_installsystemd + # + dh_installinit -pbeegfs-mon --no-stop-on-upgrade --no-start -- start 90 2 3 4 5 . stop 90 0 1 6 . + dh_installinit -pbeegfs-storage --no-stop-on-upgrade --no-start -- start 99 2 3 4 5 . stop 99 0 1 6 . + dh_installinit -pbeegfs-meta --no-stop-on-upgrade --no-start -- start 90 2 3 4 5 . stop 90 0 1 6 . + dh_installinit -pbeegfs-client --no-stop-on-upgrade --no-start -- start 99 2 3 4 5 . stop 99 0 1 6 . + dh_installinit -pbeegfs-client-compat --no-stop-on-upgrade --no-start -- start 99 2 3 4 5 . stop 99 0 1 6 . + +# Debhelper 12.3 and dwz on Debian 10 error out with "dwz: Too few files for multifile optimization." +# We need to skip dh_dwz only for the beegfs-utils package. +# This can be removed once we stop supporting Debian 10 (Buster). The issue is fixed in Debian 11 (Bullseye). +override_dh_dwz: + for pkg in $(dh_listpackages); do \ + if [ "$$pkg" != "beegfs-utils" ]; then \ + dh_dwz -p $$pkg; \ + fi \ + done diff --git a/event_listener/CMakeLists.txt b/event_listener/CMakeLists.txt new file mode 100644 index 0000000..31764fd --- /dev/null +++ b/event_listener/CMakeLists.txt @@ -0,0 +1,22 @@ +include_directories( + include +) + +add_executable( + beegfs-event-listener + source/beegfs-event-listener.cpp + include/beegfs/beegfs_file_event_log.hpp +) + +install( + TARGETS beegfs-event-listener + DESTINATION "usr/sbin" + COMPONENT "event-listener" +) + +install( + FILES "include/beegfs/beegfs_file_event_log.hpp" + DESTINATION "usr/include/beegfs" + COMPONENT "event-listener" +) + diff --git a/event_listener/build/.gitignore b/event_listener/build/.gitignore new file mode 100644 index 0000000..24c92e3 --- /dev/null +++ b/event_listener/build/.gitignore @@ -0,0 +1 @@ +beegfs-event-listener diff --git a/event_listener/build/Makefile b/event_listener/build/Makefile new file mode 100644 index 0000000..ecff6cd --- /dev/null +++ b/event_listener/build/Makefile @@ -0,0 +1,29 @@ +SHELL = /bin/bash + +EXE = beegfs-event-listener + +CXXFLAGS = -std=c++17 -I ../include +LDFLAGS = + +SOURCES = $(shell find ../source -name '*.cpp') +OBJECTS = $(SOURCES:.cpp=.o) +DEPENDENCY_FILES = $(shell find ../source -name '*.d') +CLEANUP_OBJECTS = $(shell find ../source -name '*.o') $(DEPENDENCY_FILES) + +all: $(SOURCES) $(EXE) + +$(EXE): $(OBJECTS) + $(CXX) -o $(EXE) $(OBJECTS) $(LDFLAGS) + +.cpp.o: + $(CXX) $(CXXFLAGS) -c $(@:.o=.cpp) -E -MMD -MF$(@:.o=.d) -MT$(@) -o/dev/null + $(CXX) $(CXXFLAGS) -o$(@) -c $(@:.o=.cpp) + +clean: + rm -rf $(CLEANUP_OBJECTS) $(DEPENDENCY_FILES) $(EXE) + + +# Include dependency files +ifneq ($(DEPENDENCY_FILES),) +include $(DEPENDENCY_FILES) +endif diff --git a/event_listener/documentation_start.md b/event_listener/documentation_start.md new file mode 100644 index 0000000..2d5b04a --- /dev/null +++ b/event_listener/documentation_start.md @@ -0,0 +1,61 @@ +FileEvent Documentation +----------------------- + +When `sysFileEventLogTarget` is configured, the metadata server tries to +connect to the specified UNIX socket path which must be listened on by a client +known as a "listener". Note that because the metadata server here is the one +that is attempting connect to a known peer, technically, it is a "client" and +the listener is a "server". Internally, the metadata server logs messages about +file system operations (i.e., "events") to an on-disk event queue located at +`sysFileEventPersistDirectory`. Listeners interact with this event queue using +a low level API defined in `FileEventLogger.cpp` by sending and receiving +packets defined by the `PacketType` enum. + +Starting in BeeGFS 8, the file event protocol was overhauled (referred to as v2) to support a +sequence-based stream of 2.x event packets addressable by message sequence number (MSN). The +protocol introduces new control packets to manage negotiation and streaming. + +# Connection Sequence + +``` ++----------+ +-----------+ +| Listener | | Metadata | +| | | Server | ++----------+ +-----------+ + | | + | ----------- Handshake_Request ----------------> | + | <---------- Handshake_Response ---------------- | + | | + | --- Request_Message_Newest or _Range ---------> | + | <--- Send_Message_Newest or _Range ------------ | + | --- Request_Message_Stream_Start -------------> | + | <--------- Send_Message (streamed) ------------ | + | ... (repeated messages) | + | --- Close_Request (optional graceful) --------> | + | <---------------- Send_Close ------------------ | + | | + | [or connection is closed] | +``` + + +Connections may be terminated by either party. Breaking the connection causes the metadata server to +close the socket and return to its listening state. Alternatively, either side can initiate a +graceful shutdown using `Close_Request` (listener) or `Send_Close` (server). + +# Event Messages + +Each `Send_Message` packet contains a serialized BeeGFS file event `packet`, defined in +`beegfs_file_event_log.hpp`. See the user documentation for detailed field descriptions: +https://doc.beegfs.io/latest/advanced_topics/filesystem_modification_events.html. + +# Getting Started + +[BeeGFS Watch](https://github.com/ThinkParQ/beegfs-go/tree/main/watch) is a production-ready +listener that supports fanning out events to multiple subscribers over high-level protocols such as +protocol buffers and gRPC. Its internal +[implementation](https://github.com/ThinkParQ/beegfs-go/blob/d2e70aee5396151a9b553526fa562b1948ee7105/watch/internal/metadata/manager.go#L264-L279) +for managing v2 connections and serializing/deserializing event packets serves as a useful reference +for developers implementing custom listeners. + +A simpler example listener is also included in this repository. It can be built by running `make` in +the `event_listener/build` directory, and the binary is packaged with `beegfs-utils`. diff --git a/event_listener/include/beegfs/beegfs_file_event_log.hpp b/event_listener/include/beegfs/beegfs_file_event_log.hpp new file mode 100644 index 0000000..86d55dc --- /dev/null +++ b/event_listener/include/beegfs/beegfs_file_event_log.hpp @@ -0,0 +1,107 @@ +#ifndef BEEGFSFILEVENTLOG_H_ +#define BEEGFSFILEVENTLOG_H_ + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +#define BEEGFS_EVENTLOG_FORMAT_VERSION 2 + +namespace BeeGFS { + +enum class FileEventType : uint32_t +{ + FLUSH = 1, + TRUNCATE = 2, + SETATTR = 3, + CLOSE_WRITE = 4, + CREATE = 5, + MKDIR = 6, + MKNOD = 7, + SYMLINK = 8, + RMDIR = 9, + UNLINK = 10, + HARDLINK = 11, + RENAME = 12, + OPEN_READ = 13, + OPEN_WRITE = 14, + OPEN_READ_WRITE = 15, + LAST_WRITER_CLOSED = 16, + OPEN_BLOCKED = 17, +}; + + +std::string to_string(const FileEventType& fileEvent); + +struct packet +{ + uint16_t formatVersion; + uint32_t eventFlags; + uint64_t linkCount; + FileEventType type; + std::string path; + std::string entryId; + std::string parentEntryId; + std::string targetPath; + std::string targetParentId; + uint32_t msgUserId; + int64_t timestamp; +}; + +enum class PacketReadErrorCode +{ + Success, + ReadFailed, + VersionMismatch, + InvalidSize +}; + +std::pair read_packet_from_raw(void * data, size_t bytesRead); + + + + + +class FileEventReceiver +{ + FileEventReceiverNewProtocol *receiver; + +public: + struct exception : std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + std::pair read(); + std::vector readSerializedData(); + + FileEventReceiver(FileEventReceiver const& other) = delete; + + FileEventReceiver(const std::string& socketPath); + ~FileEventReceiver(); +}; + +} // namespace BeeGFS + +#endif // BEEGFSFILEVENTLOG_H_ + diff --git a/event_listener/include/beegfs/seqpacket-reader-new-protocol.hpp b/event_listener/include/beegfs/seqpacket-reader-new-protocol.hpp new file mode 100644 index 0000000..3466b02 --- /dev/null +++ b/event_listener/include/beegfs/seqpacket-reader-new-protocol.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace BeeGFS +{ + +struct FileEventReceiverNewProtocol; + +FileEventReceiverNewProtocol *FileEventReceiverNewProtocolCreate(int argc, const char **argv); +void FileEventReceiverNewProtocolDestroy(FileEventReceiverNewProtocol *r); + +// Tries to communicate until another event is received. +bool receive_event(FileEventReceiverNewProtocol *r); + + +struct Read_Event +{ + void *buffer; + size_t size; +}; + +// returns reference to packet buffer allocated internally. This is only valid +// after receive_event() succeeded and is invalidated by the next receive_event(). +Read_Event get_event(FileEventReceiverNewProtocol *r); + +} diff --git a/event_listener/source/beegfs-event-listener.cpp b/event_listener/source/beegfs-event-listener.cpp new file mode 100644 index 0000000..b743d81 --- /dev/null +++ b/event_listener/source/beegfs-event-listener.cpp @@ -0,0 +1,194 @@ +#include +#include +#include + +#include "beegfs/beegfs_file_event_log.hpp" + +/* The client needs a list of events to be logged in the config file + * (beegfs-client.conf): + * sysFileEventLogMask = flush,trunc,setattr,close,link-op,read + * (Or any combination of the above) + */ + + +/** + * @brief Trivial JSON writer + */ +class JsonObject { + + public: + + template + JsonObject& keyValue(const std::string& key, const T& value) { + if(!isFirstItem) { + stream << ", "; + + } else { + isFirstItem = false; + } + + printValue(key); + stream << ": "; + printValue(value); + return *this; + } + + operator std::string() const { + return str(); + } + + std::string str() const + { + return "{ " + stream.str() + " }"; + } + + protected: + + std::stringstream stream; + bool isFirstItem=true; + + template + void printValue(const T& value) { + stream << value; + } + + void printValue(const std::string& value) { + stream << "\""; + writeEscaped(stream, value); + stream << "\""; + } + + void printValue(const char* value) { + printValue(std::string(value)); + } + + void printValue(const JsonObject& json) { + stream << json.str(); + } + + void writeEscaped(std::ostream& stream, const std::string& s) + { + for(const auto& x: s) + { + switch (x) { + case 0x08: + stream << "\\b"; + break; + case 0x0c: + stream << "\\f"; + break; + case '\n': + stream << "\\n"; + break; + case '\\': + stream << "\\\\"; + break; + case '\t': + stream << "\\t"; + break; + case '\r': + stream << "\\r"; + break; + case '\"': + stream << "\\\""; + break; + case '/': + stream << "\\/"; + break; + default: + stream << x; + } + } + } + +}; + +std::ostream& operator<<(std::ostream& os, const BeeGFS::packet& p) +{ + JsonObject json; + json + .keyValue("FormatVersion", p.formatVersion) + .keyValue("EventFlags", p.eventFlags) + .keyValue("NumLinks", p.linkCount) + .keyValue("Event", + JsonObject() + .keyValue("Type", to_string(p.type)) + .keyValue("Path", p.path) + .keyValue("EntryId", p.entryId) + .keyValue("ParentEntryId", p.parentEntryId) + .keyValue("TargetPath", p.targetPath) + .keyValue("TargetParentId", p.targetParentId) + .keyValue("UserID", p.msgUserId) + .keyValue("Timestamp", p.timestamp) + + ); + + os << json.str(); + return os; +} + +void shutdown(int) +{ + exit(0); +} + +int main(const int argc, const char** argv) +{ + if (argc < 2) { + std::cout << "BeeGFS File Event Listener\n" + "MODE ARGUMENTS:\n" + " Mandatory:\n" + " \n" + "\n" + "Usage:\n" + " beegfs-event-listener \n\n" + " The medatada server has to be pointed to the socket, so that it knows where to\n" + " send the event log. Set\n" + " sysFileEventLogTarget = unix://\n" + " in beegfs-meta.conf.\n" + " The clients need a list of events to be logged in the config file (beegfs-client.conf):\n" + " sysFileEventLogMask = flush,trunc,setattr,close,link-op,read\n" + " Example:\n" + " To use beegfs-event-listener \n" + " Enter the following line in beegfs-meta.conf:\n" + " sysFileEventLogTarget = unix:///tmp/beegfslog\n" + + << std::endl; + + return EXIT_FAILURE; + } + + signal(SIGINT, shutdown); + + BeeGFS::FileEventReceiver receiver(argv[1]); + + std::cout << JsonObject().keyValue("EventListener", + JsonObject().keyValue("Socket", argv[1]) + .keyValue("FormatVersion", BEEGFS_EVENTLOG_FORMAT_VERSION) + ).str() << std::endl; + + while (true) + { + using BeeGFS::FileEventReceiver; + + const auto data = receiver.read(); + + switch (data.first) { + case BeeGFS::PacketReadErrorCode::Success: + std::cout << data.second << std::endl; + break; + case BeeGFS::PacketReadErrorCode::VersionMismatch: + std::cerr << "Invalid Packet Version" << std::endl; + break; + case BeeGFS::PacketReadErrorCode::InvalidSize: + std::cerr << "Invalid Packet Size" << std::endl; + break; + case BeeGFS::PacketReadErrorCode::ReadFailed: + std::cerr << "Read Failed" << std::endl; + break; + } + } + + std::cout << "Exit listener" << std::endl; + return 0; +} diff --git a/event_listener/source/beegfs-file-event-log.cpp b/event_listener/source/beegfs-file-event-log.cpp new file mode 100644 index 0000000..0d46c8b --- /dev/null +++ b/event_listener/source/beegfs-file-event-log.cpp @@ -0,0 +1,244 @@ +#include + +#include + +namespace BeeGFS +{ + +class Reader +{ + public: + explicit Reader(const void *data, const ssize_t size): + position((const char *) data), + end((const char *) data + size) + {} + + template + T read() + { + return readRaw(); + } + + protected: + const char* position; + const char* const end; + + template + T readRaw() + { + if (position + sizeof(T) > end) + throw std::out_of_range("Read past buffer end"); + + T value; + std::memcpy(&value, position, sizeof(T)); + position += sizeof(T); + return value; + } +}; + +template<> +inline bool Reader::read() +{ + return (0 != readRaw()); +} + +template<> +inline uint32_t Reader::read() +{ + return le32toh(readRaw()); +} + +template<> +inline uint64_t Reader::read() +{ + return le64toh(readRaw()); +} + +template<> +inline int64_t Reader::read() +{ + return le64toh(readRaw()); +} + +template <> +inline std::string Reader::read() +{ + const auto len = read(); + + if (position + len > end) + throw std::out_of_range("Read past buffer end"); + + const auto value = std::string(position, position + len); + position += len + 1; + return value; +} + +template +inline Reader& operator>>(Reader& r, T& value) +{ + value = r.read(); + return r; +} + +static inline Reader& operator>>(Reader& r, FileEventType& value) +{ + value = static_cast(r.read::type>()); + return r; +} + + +std::string to_string(const FileEventType& fileEvent) +{ + switch (fileEvent) + { + case FileEventType::FLUSH: + return "Flush"; + case FileEventType::TRUNCATE: + return "Truncate"; + case FileEventType::SETATTR: + return "SetAttr"; + case FileEventType::CLOSE_WRITE: + return "CloseAfterWrite"; + case FileEventType::CREATE: + return "Create"; + case FileEventType::MKDIR: + return "MKdir"; + case FileEventType::MKNOD: + return "MKnod"; + case FileEventType::SYMLINK: + return "Symlink"; + case FileEventType::RMDIR: + return "RMdir"; + case FileEventType::UNLINK: + return "Unlink"; + case FileEventType::HARDLINK: + return "Hardlink"; + case FileEventType::RENAME: + return "Rename"; + case FileEventType::OPEN_READ: + return "OpenRead"; + case FileEventType::OPEN_WRITE: + return "OpenWrite"; + case FileEventType::OPEN_READ_WRITE: + return "OpenReadWrite"; + case FileEventType::LAST_WRITER_CLOSED: + return "LastWriterClosed"; + case FileEventType::OPEN_BLOCKED: + return "OpenBlocked"; + } + return ""; +} + +std::pair read_packet_from_raw(void * data, size_t bytesRead) +{ + packet res; + Reader reader(data, bytesRead); + + reader >> res.formatVersion; + + if (res.formatVersion != BEEGFS_EVENTLOG_FORMAT_VERSION) + return { PacketReadErrorCode::VersionMismatch, {} }; + + reader >> res.eventFlags + >> res.linkCount + >> res.type + >> res.entryId + >> res.parentEntryId + >> res.path + >> res.targetPath + >> res.targetParentId + >> res.msgUserId + >> res.timestamp; + + return { PacketReadErrorCode::Success, res }; +} + +static std::vector read_serialized_data(int fd) +{ + packet res; + std::vector buf(65536); + + const auto bytesRead = recv(fd, buf.data(), buf.capacity(), 0); + + const auto headerSize = sizeof(packet::formatVersion); + + if (bytesRead < headerSize) + { + buf.clear(); + return buf; + } + + buf.resize(bytesRead); //resize it to actual bytes read + + Reader reader(buf.data(), bytesRead); + + reader >> res.formatVersion; + + if (res.formatVersion != BEEGFS_EVENTLOG_FORMAT_VERSION) + { + buf.clear(); + return buf; + } + + reader >> res.eventFlags + >> res.linkCount + >> res.type + >> res.entryId + >> res.parentEntryId + >> res.path + >> res.targetPath + >> res.targetParentId + >> res.msgUserId + >> res.timestamp; + + return buf; +} + +std::pair FileEventReceiver::read() +{ + if (! receive_event(receiver)) + { + std::pair out; + out.first = PacketReadErrorCode::ReadFailed; + return out; + } + + Read_Event event = get_event(receiver); + return read_packet_from_raw(event.buffer, event.size); +} + +std::vector FileEventReceiver::readSerializedData() +{ + std::vector out; + + if (! receive_event(receiver)) + return out; + + Read_Event event = get_event(receiver); + + out.resize(event.size); + memcpy(out.data(), event.buffer, event.size); + + return out; +} + + + +FileEventReceiver::FileEventReceiver(const std::string& socketPath) +{ + const char *address = socketPath.c_str(); + receiver = FileEventReceiverNewProtocolCreate(1, &address); + if (! receiver) + throw exception("Failed to init"); +} + +FileEventReceiver::~FileEventReceiver() +{ + if (receiver) + { + FileEventReceiverNewProtocolDestroy(receiver); + receiver = nullptr; + } +} + +} diff --git a/event_listener/source/seqpacket-reader-new-protocol.cpp b/event_listener/source/seqpacket-reader-new-protocol.cpp new file mode 100644 index 0000000..019b4ed --- /dev/null +++ b/event_listener/source/seqpacket-reader-new-protocol.cpp @@ -0,0 +1,821 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + + +#include + + +namespace BeeGFS +{ + + +static void fatal_f(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + fprintf(stderr, "FATAL_ERROR: "); + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); + va_end(ap); + exit(1); +} + +static void msg_fv(const char *fmt, va_list ap) +{ + vfprintf(stderr, fmt, ap); + fprintf(stderr, "\n"); +} + +static void __attribute__((format(printf, 1, 2))) msg_f(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + msg_fv(fmt, ap); + va_end(ap); +} + + + + +struct Time +{ + uint64_t nanoseconds = 0; + + Time(uint64_t nanoseconds) + : nanoseconds(nanoseconds) + { + } + + Time(struct timespec ts) + { + nanoseconds = ts.tv_nsec + ts.tv_sec * 1000000000ull; + } + + Time operator-(Time const other) const + { + return Time(nanoseconds - other.nanoseconds); + } + + bool operator<(Time const& other) { return nanoseconds < other.nanoseconds; } + bool operator>(Time const& other) { return nanoseconds > other.nanoseconds; } + bool operator<=(Time const& other) { return nanoseconds <= other.nanoseconds; } + bool operator>=(Time const& other) { return nanoseconds >= other.nanoseconds; } + bool operator==(Time const& other) { return nanoseconds == other.nanoseconds; } + bool operator!=(Time const& other) { return nanoseconds != other.nanoseconds; } + + uint64_t to_millis() const + { + return nanoseconds / 1000000ull; + } + + static Time Milliseconds(uint64_t millis) + { + return Time(millis * 1000000ull); + } +}; + +static Time get_thread_time() +{ + struct timespec ts; + if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) < 0) + { + fatal_f("Failed to clock_gettime(CLOCK_THREAD_CPUTIME_ID, ...): %s", + strerror(errno)); + } + return Time(ts); +} + + + +// Packet types +// NOTE: for future extension, we can consider introducing messages to request +// certain capabilities. We allow for extensibility by introducing an error +// packet indicating that a request was not understood. +enum class Packet_Type : uint8_t +{ + Handshake_Request = 1, + Handshake_Response, + Request_Message_Newest, + Send_Message_Newest, + Request_Message_Range, + Send_Message_Range, + // Client requests message stream. + // Server follows up with message stream. + Request_Message_Stream_Start, + // Server sends an (event) message to client + Send_Message, + // Client requests orderly connection close. Can be sent mid-stream. + Request_Close, + // Server informs client that connection will be closed. + // This is the last message sent by the server. + // Includes a ConnTerminateReason + Send_Close, +}; + +enum class ConnTerminateReason +{ + Ack_Close, + + // This gets sent when the stream stopped without solicitation of the peer + // that requested the stream. The connection is in an error state and will + // be shut down by the server. + Stream_Crashed, + + Socket_Error, + + Protocol_Error, + + Subscriber_Closed_Unexpectedly, +}; + +static const char *packet_type_string(Packet_Type packetType) +{ +#define PTSTRING(x) case Packet_Type::x: return #x + switch (packetType) + { + PTSTRING(Handshake_Request); + PTSTRING(Handshake_Response); + PTSTRING(Request_Message_Newest); + PTSTRING(Send_Message_Newest); + PTSTRING(Request_Message_Range); + PTSTRING(Send_Message_Range); + PTSTRING(Request_Message_Stream_Start); + PTSTRING(Send_Message); + PTSTRING(Request_Close); + PTSTRING(Send_Close); + default: return "(invalid packet type)"; + } +} + + +enum Report_Type +{ + Report_Type_None, + Report_Type_Print_Message, + Report_Type_Interactive_Count, +}; + + +struct FileEventReceiverOptions +{ + const char *unix_socket_path = nullptr; + + Report_Type report_type = Report_Type_None; + + std::optional startmsn; + std::optional nmsgs; +}; + + +struct Packet_Buffer +{ + char data[1024]; + size_t size = 0; + // also doubling as a packet writer for now + bool bad = false; +}; + + + +struct Conn_State +{ + FileEventReceiverOptions options; + bool handshake_sent = false; + uint8_t server_version_major = 0; + uint8_t server_version_minor = 0; + uint32_t meta_id = 0; + uint16_t meta_mirror_id = 0; + bool handshake_received = false; + unsigned conn_id = 0; + int client_sock = -1; + uint64_t curmsn = 0; + uint64_t nmsgs = 0; + Time last_time = 0; + + Packet_Buffer receive_packet; + + bool requested_msn = false; + bool requested_stream = false; + + std::optional startmsn; +}; + +void write_data(Packet_Buffer *packet, const void *data, size_t size) +{ + if (packet->size + size >= sizeof packet->data) + { + packet->bad = true; + return; + } + memcpy(packet->data + packet->size, data, size); + packet->size += size; +} + +void write_header(Packet_Buffer *packet, Packet_Type type) +{ + assert(packet->size == 0); + packet->data[0] = (char) type; + memcpy(packet->data + 1, "events", 7); + packet->size = 8; +} + +void write_u8(Packet_Buffer *packet, uint8_t value) +{ + write_data(packet, &value, sizeof value); +} + +void write_u16(Packet_Buffer *packet, uint16_t value) +{ + write_data(packet, &value, sizeof value); +} + +void write_u64(Packet_Buffer *packet, uint64_t value) +{ + write_data(packet, &value, sizeof value); +} + +static void report_count(Conn_State *conn) +{ + Time now = get_thread_time(); + if (now - conn->last_time >= Time::Milliseconds(10)) + { + conn->last_time = now; + msg_f("\x1b[A\x1b[K" "msg #%" PRIu64, conn->curmsn); + } +} + +static bool send_packet(Conn_State *conn, Packet_Buffer const *packet) +{ + if (packet->bad) + { + msg_f("Can't send: bad packet"); + return false; + } + + ssize_t nr = send(conn->client_sock, packet->data, packet->size, 0); + + if (nr == -1) + { + msg_f("Failed to send packet of size %zu: %s", packet->size, strerror(errno)); + return false; + } + + assert(nr >= 0); + if ((size_t) nr != packet->size) + { + msg_f("Failed to send packet, short write"); + return false; + } + + return true; +} + +static bool do_message(Conn_State *conn) +{ + if (! conn->handshake_sent) + { + uint16_t protocol_version_major = 2; + uint16_t protocol_version_minor = 0; + Packet_Buffer packet; + write_header(&packet, Packet_Type::Handshake_Request); + write_u16(&packet, protocol_version_major); + write_u16(&packet, protocol_version_minor); + if (! send_packet(conn, &packet)) + return false; + conn->handshake_sent = true; + } + + if (conn->handshake_received) + { + if (! conn->startmsn.has_value()) + { + if (! conn->requested_msn) + { + Packet_Buffer packet; + write_header(&packet, Packet_Type::Request_Message_Range); + if (! send_packet(conn, &packet)) + return false; + //msg_f("send Request_Message_Range"); + conn->requested_msn = true; + } + } + else if (! conn->requested_stream) + { + Packet_Buffer packet; + write_header(&packet, Packet_Type::Request_Message_Stream_Start); + write_u64(&packet, conn->startmsn.value()); + if (! send_packet(conn, &packet)) + return false; + //msg_f("send Request_Message_Stream_Start"); + conn->curmsn = conn->startmsn.value(); + conn->requested_stream = true; + } + } + + ssize_t nr = read(conn->client_sock, + conn->receive_packet.data, sizeof conn->receive_packet.data); + + if (nr < 0) + { + fatal_f("Error from read(): ", strerror(errno)); + } + + if (nr == 0) + { + msg_f("Conn %u was shut down", conn->conn_id); + return false; + } + + conn->receive_packet.size = nr; + char *buf = conn->receive_packet.data; + + if (nr < 8 || memcmp(buf + 1, "events", 7) != 0) + { + for (ssize_t i = 0; i < nr; i++) + { + if (buf[i] < 32 || buf[i] > 126) + printf(" 0x%.2x", buf[i]); + else + putchar(buf[i]); + } + printf("\n"); + msg_f("Received bad packet of size %zd: %.*s!", nr, (int) nr, buf); + return false; + } + + Packet_Type packet_type = (Packet_Type) buf[0]; + + //msg_f("Received packet (type %d) of size %zu", (int) packet_type, (size_t) nr); + //msg_f("Contents: %.*s", (int) nr, buf); + + if (! conn->handshake_received && packet_type != Packet_Type::Handshake_Response) + { + msg_f("Bad packet from server: expected handshake packet but got: %s", + packet_type_string(packet_type)); + return false; + } + + if (conn->handshake_received && packet_type == Packet_Type::Handshake_Response) + { + msg_f("Bad packet from server: unexpected handshake packet"); + return false; + } + + if (! conn->requested_stream) + { + if (conn->requested_msn) + { + if (packet_type != Packet_Type::Send_Message_Range) + { + msg_f("Bad packet from server: expected message range but got: %s", + packet_type_string(packet_type)); + return false; + } + } + + if (! conn->requested_msn || conn->startmsn.has_value()) + { + if (packet_type == Packet_Type::Send_Message_Range) + { + msg_f("Bad packet from server: Unexpected packet type, got: %s", + packet_type_string(packet_type)); + return false; + } + } + } + + switch (packet_type) + { + case Packet_Type::Handshake_Response: + { + ssize_t expected_bytes = 18; + //msg_f("Received handshake"); + if (nr != expected_bytes) + { + msg_f("Unexpected size of handshake packet. Expected %d, got: %d", + (int) expected_bytes, (int) nr); + return false; + } + conn->server_version_major = *(uint16_t *) (buf + 8); + conn->server_version_minor = *(uint16_t *) (buf + 10); + conn->meta_id = *(uint32_t *) (buf + 12); + conn->meta_mirror_id = *(uint16_t *) (buf + 16); + + if (conn->server_version_major != 2 || conn->server_version_minor != 0) + { + msg_f("Unexpected version sent by server: %d.%d\n", + conn->server_version_major, conn->server_version_minor); + return false; + } + + conn->handshake_received = true; + return true; + } + case Packet_Type::Send_Message_Range: + { + if (nr != 24) + { + msg_f("Bad packet!"); + return false; + } + uint64_t msn = *(uint64_t *) (buf + 8); + uint64_t msn_oldest = *(uint64_t *) (buf + 16); + conn->startmsn.emplace(msn); + msg_f("Meta ID: %" PRIu32 ", Meta Mirror ID: %" PRIu16 ", Newest MSN: %" PRIu64 ", oldest MSN: %" PRIu64, + conn->meta_id, conn->meta_mirror_id, msn, msn_oldest); + conn->curmsn = msn; + //msg_f("Received message range! %" PRIu64, conn->startmsn.value()); + return true; + } + case Packet_Type::Send_Message: + { + //msg_f("Received message!"); + break; + } + case Packet_Type::Send_Close: + { + //msg_f("Connection terminated by server (TODO: decode reason!)"); + /* + case Packet_Type::Send_Message_Stream_Crashed: + { + msg_f("Server reports internal error!"); + return false; + } + */ + return false; + break; + } + default: + { + msg_f("Unexpected packet type %d", (int) packet_type); + return false; + } + } + + switch (conn->options.report_type) + { + case Report_Type_Print_Message: + { + for (int i = 0; i < (int) nr; i++) + { + if ((unsigned) buf[i] < 32 + || (unsigned) buf[i] >= 127) + buf[i] = '.'; + } + msg_f("Got msg %" PRIu64 " (size %zd): %.*s", + conn->curmsn, nr, (int) nr, buf); + } + break; + case Report_Type_Interactive_Count: + { + report_count(conn); + } + break; + default: + { + if (conn->curmsn % 1024 == 0) + { + msg_f("msg: %" PRIu64 ", size: %d", conn->curmsn, (int) nr); + } + } + break; + } + + conn->nmsgs++; + conn->curmsn++; + + if (conn->options.nmsgs.has_value()) + { + if (conn->nmsgs == conn->options.nmsgs.value()) + return false; + } + + return true; +} + +class Arg_Reader +{ + int argc = 0; + const char **argv = nullptr; + int index = 0; +public: + bool eof() const + { + return index == argc; + } + const char *get() const + { + assert(! eof()); + return argv[index]; + } + void consume() + { + assert(! eof()); + ++ index; + } + bool parse_u64(uint64_t *value) + { + if (eof()) + { + msg_f("ERROR: End of command-line arguments reached while expecting 64-bit unsigned number"); + return false; + } + const char *arg = get(); + if (sscanf(arg, "%" SCNu64, value) != 1) + { + msg_f("ERROR: Expected 64-bit unsigned number at command-line position %d. Got: %s", + index, arg); + return false; + } + consume(); + return true; + } + Arg_Reader(int argc, const char **argv) + : argc(argc), argv(argv), index(0) + { + } +}; + +static bool parse_options(int argc, const char **argv, FileEventReceiverOptions *options) +{ + Arg_Reader arg_reader(argc, argv); + + if (arg_reader.eof()) + return false; + + options->unix_socket_path = arg_reader.get(); + arg_reader.consume(); + + while (! arg_reader.eof()) + { + const char *arg = arg_reader.get(); + + if (! strcmp(arg, "-print")) + { + options->report_type = Report_Type_Print_Message; + } + else if (! strcmp(arg, "-count")) + { + options->report_type = Report_Type_Interactive_Count; + } + else if (! strcmp(arg, "-startmsn")) + { + arg_reader.consume(); + uint64_t value; + if (! arg_reader.parse_u64(&value)) + return false; + options->startmsn.emplace(value); + } + else if (! strcmp(arg, "-nmsgs")) + { + arg_reader.consume(); + uint64_t value; + if (! arg_reader.parse_u64(&value)) + return false; + options->nmsgs.emplace(value); + } + else + { + msg_f("Invalid arg: '%s'", arg); + return false; + } + } + return true; +} + + +struct FileEventReceiverNewProtocol +{ + int server_sock = -1; + int client_sock = -1; + unsigned conn_id = 0; + Conn_State conn; +}; + + +bool receive_event(FileEventReceiverNewProtocol *r) +{ + Conn_State *conn = &r->conn; + for (;;) + { + uint64_t n = conn->nmsgs; + + while (conn->nmsgs == n) + { + if (! do_message(conn)) + { + return false; + } + } + + // we have another event + return true; + } +} + +Read_Event get_event(FileEventReceiverNewProtocol *r) +{ + Packet_Buffer *packet = &r->conn.receive_packet; + Read_Event out; + // 8 byte packet header (type Send_Message) + // Send_Message packet: + // 8 byte msn + // 2 byte message size (should be removed) + int skip_bytes = 8 + 8 + 2; + out.buffer = packet->data + skip_bytes; + out.size = packet->size - skip_bytes; + return out; +} + + + + +static bool initFileEventReceiver(FileEventReceiverNewProtocol *r, FileEventReceiverOptions const& options) +{ + // Initialize server socket / connection + { + struct sockaddr_un sun = {}; + sun.sun_family = AF_UNIX; + if (snprintf(sun.sun_path, sizeof sun.sun_path, "%s", options.unix_socket_path) + > (int) sizeof sun.sun_path) + { + fatal_f("Socket Path is too long!"); + } + + // In case there is another socket at that place (likely from an earlier + // run) we remove that rudely, without asking. This avoids EADDRINUSE + // error. This might fail because there is no such socket, or because of + // other error like EPERM. We're not checking. + unlink(options.unix_socket_path); + + r->server_sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); + + if (r->server_sock == -1) + { + fatal_f("Failed to socket(): %s", strerror(errno)); + } + + if (bind(r->server_sock, (struct sockaddr *) &sun, sizeof sun) == -1) + { + fatal_f("Failed to bind() server_sock: %s", strerror(errno)); + } + + if (listen(r->server_sock, 1) == -1) + { + fatal_f("Failed to listen() server_sock: %s", strerror(errno)); + } + } + + // Initialize client socket / connection. We could make this repeatable + { + r->client_sock = accept(r->server_sock, NULL, NULL); + + if (r->client_sock == -1) + { + fatal_f("Failed to accept() connection from server_sock: %s", + strerror(errno)); + return false; + } + + unsigned conn_id = r->conn_id++; + msg_f("Connection %u accepted", conn_id); + + Conn_State conn; + conn.conn_id = conn_id; + conn.client_sock = r->client_sock; + conn.options = options; + conn.last_time = get_thread_time(); + conn.startmsn = options.startmsn; + + r->conn = conn; + } + + return true; +} + +static void terminateFileEventReceiver(FileEventReceiverNewProtocol *r) +{ + close(r->server_sock); + r->server_sock = -1; + + close(r->client_sock); + r->client_sock = -1; +} + +FileEventReceiverNewProtocol *FileEventReceiverNewProtocolCreate(int argc, const char **argv) +{ + FileEventReceiverOptions options; + + if (! parse_options(argc, argv, &options)) + { + //msg_f("Usage: ./seqpacket-reader [-startmsn ] [-print]"); + msg_f("Failed to parse options for FileEventReceiver. Syntax: [-startmsn ]"); + return nullptr; + } + + FileEventReceiverNewProtocol *r = new FileEventReceiverNewProtocol(); + + if (! initFileEventReceiver(r, options)) + { + delete r; + return nullptr; + } + + return r; +} + +void FileEventReceiverNewProtocolDestroy(FileEventReceiverNewProtocol *r) +{ + terminateFileEventReceiver(r); + delete r; +} + +} + +#ifdef SEQPACKET_READER_WITH_MAIN +int main(int argc, const char **argv) +{ + FileEventReceiverOptions options; + + if (! parse_options(argc - 1, argv + 1, &options)) + { + msg_f("Usage: ./seqpacket-reader [-startmsn ] [-print]"); + return 1; + } + + struct sockaddr_un sun = {}; + sun.sun_family = AF_UNIX; + if (snprintf(sun.sun_path, sizeof sun.sun_path, "%s", options.unix_socket_path) + > (int) sizeof sun.sun_path) + { + fatal_f("Socket Path is too long!"); + } + + int server_sock = socket(AF_UNIX, SOCK_SEQPACKET, 0); + + if (server_sock == -1) + { + fatal_f("Failed to socket(): %s", strerror(errno)); + } + + if (bind(server_sock, (struct sockaddr *) &sun, sizeof sun) == -1) + { + fatal_f("Failed to bind() server_sock: %s", strerror(errno)); + } + + if (listen(server_sock, 1) == -1) + { + fatal_f("Failed to listen() server_sock: %s", strerror(errno)); + } + + for (unsigned conn_id = 0; ; ++ conn_id) + { + int client_sock = accept(server_sock, NULL, NULL); + + if (client_sock == -1) + { + fatal_f("Failed to accept() connection from server_sock: %s", + strerror(errno)); + } + + msg_f("Connection %u accepted", conn_id); + + Conn_State conn; + conn.conn_id = conn_id; + conn.client_sock = client_sock; + conn.options = options; + conn.last_time = get_thread_time(); + conn.startmsn = options.startmsn; + + for (;;) + { + if (! do_message(&conn)) + break; + } + report_count(&conn); + + msg_f("Received %" PRIu64 " msgs total", conn.nmsgs); + + close(client_sock); + + break; // !!! do we still need a loop here? + } + + return 0; +} +#endif diff --git a/fsck/CMakeLists.txt b/fsck/CMakeLists.txt new file mode 100644 index 0000000..f5eed63 --- /dev/null +++ b/fsck/CMakeLists.txt @@ -0,0 +1,153 @@ +include_directories( + source +) + +add_library( + fsck STATIC + ./source/toolkit/DatabaseTk.cpp + ./source/toolkit/FsckDefinitions.h + ./source/toolkit/FsckException.h + ./source/toolkit/FsckDefinitions.cpp + ./source/toolkit/FsckTkEx.cpp + ./source/toolkit/DatabaseTk.h + ./source/toolkit/FsckTkEx.h + ./source/database/VectorSource.h + ./source/database/DirInode.h + ./source/database/FsckDBTable.cpp + ./source/database/Select.h + ./source/database/EntryID.h + ./source/database/Union.h + ./source/database/SetFragmentCursor.h + ./source/database/FsID.h + ./source/database/Set.h + ./source/database/FsckDB.cpp + ./source/database/SetFragment.h + ./source/database/Group.h + ./source/database/FsckDB.h + ./source/database/FsckDBChecks.cpp + ./source/database/Table.h + ./source/database/Cursor.h + ./source/database/UsedTarget.h + ./source/database/FsckDBException.cpp + ./source/database/FileInode.h + ./source/database/ModificationEvent.h + ./source/database/LeftJoinEq.h + ./source/database/Filter.h + ./source/database/FsckDBException.h + ./source/database/DirEntry.h + ./source/database/Chunk.h + ./source/database/FsckDBTable.h + ./source/database/ContDir.h + ./source/database/StripeTargets.h + ./source/database/Buffer.h + ./source/database/Distinct.h + ./source/database/DiskList.h + ./source/net/message/NetMessageFactory.h + ./source/net/message/NetMessageFactory.cpp + ./source/net/message/nodes/HeartbeatMsgEx.cpp + ./source/net/message/nodes/HeartbeatMsgEx.h + ./source/net/message/testing/DummyMsgEx.h + ./source/net/message/testing/DummyMsgEx.cpp + ./source/net/message/fsck/FsckModificationEventMsgEx.cpp + ./source/net/message/fsck/FsckModificationEventMsgEx.h + ./source/net/msghelpers/MsgHelperRepair.h + ./source/net/msghelpers/MsgHelperRepair.cpp + ./source/components/DatagramListener.h + ./source/components/ModificationEventHandler.h + ./source/components/InternodeSyncer.h + ./source/components/InternodeSyncer.cpp + ./source/components/DataFetcher.cpp + ./source/components/ModificationEventHandler.cpp + ./source/components/DatagramListener.cpp + ./source/components/worker/RetrieveDirEntriesWork.h + ./source/components/worker/RetrieveInodesWork.h + ./source/components/worker/AdjustChunkPermissionsWork.h + ./source/components/worker/AdjustChunkPermissionsWork.cpp + ./source/components/worker/RetrieveFsIDsWork.cpp + ./source/components/worker/RetrieveChunksWork.cpp + ./source/components/worker/RetrieveChunksWork.h + ./source/components/worker/RetrieveInodesWork.cpp + ./source/components/worker/RetrieveFsIDsWork.h + ./source/components/worker/RetrieveDirEntriesWork.cpp + ./source/components/DataFetcher.h + ./source/modes/ModeHelp.cpp + ./source/modes/Mode.h + ./source/modes/ModeCheckFS.cpp + ./source/modes/Mode.cpp + ./source/modes/ModeEnableQuota.cpp + ./source/modes/ModeHelp.h + ./source/modes/ModeEnableQuota.h + ./source/modes/ModeCheckFS.h + ./source/program/Program.h + ./source/program/Program.cpp + ./source/program/Main.cpp + ./source/app/App.h + ./source/app/App.cpp + ./source/app/config/Config.h + ./source/app/config/Config.cpp +) + +target_link_libraries( + fsck + beegfs-common + dl + pthread +) + +add_executable( + fsck.beegfs + source/program/Main.cpp +) + +target_link_libraries( + fsck.beegfs + fsck +) + +if(NOT BEEGFS_SKIP_TESTS) + # This is a dirty workaround used for a hardcoded path in + # "fsck/tests/TestConfig.cpp". Once the "defaultConfigFile" test does + # not steal the config file from "client_module" anymore, this + # workaround can be removed. + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DTHIS_IS_A_CMAKE_BUILD=1") + + add_executable( + test-fsck + ./tests/TestFsckTk.cpp + ./tests/TestDatabase.cpp + ./tests/TestConfig.h + ./tests/TestSet.cpp + ./tests/TestCursors.cpp + ./tests/TestTable.cpp + ./tests/FlatTest.h + ./tests/TestSetFragment.cpp + ./tests/TestSerialization.cpp + ./tests/FlatTest.cpp + ./tests/TestTable.h + ./tests/TestConfig.cpp + ./tests/TestDatabase.h + ) + + target_link_libraries( + test-fsck + fsck + gtest_main + ) + + # required for a test + file( + COPY ${CMAKE_CURRENT_SOURCE_DIR}/../client_module/build/dist/etc/beegfs-client.conf + DESTINATION dist/etc/ + ) + + add_test( + NAME test-fsck + COMMAND test-fsck --compiler + ) +endif() + +install( + TARGETS fsck.beegfs + DESTINATION "sbin" + COMPONENT "utils" +) diff --git a/fsck/build/Makefile b/fsck/build/Makefile new file mode 100755 index 0000000..3ece69d --- /dev/null +++ b/fsck/build/Makefile @@ -0,0 +1,28 @@ +include ../../build/Makefile + +main := ../source/program/Main.cpp +sources := $(filter-out $(main), $(shell find ../source -iname '*.cpp')) + +$(call build-static-library,\ + Fsck,\ + $(sources),\ + common dl nl3-route,\ + ../source) + +$(call define-dep-lib,\ + Fsck,\ + -I ../source,\ + $(build_dir)/libFsck.a) + +$(call build-executable,\ + beegfs-fsck,\ + $(main),\ + Fsck common dl nl3-route) + +$(call build-test,\ + test-runner,\ + $(shell find ../tests -name '*.cpp'),\ + Fsck common dl nl3-route,\ + ../tests) + +CXXFLAGS += -DBOOST_RESULT_OF_USE_DECLTYPE diff --git a/fsck/source/app/App.cpp b/fsck/source/app/App.cpp new file mode 100644 index 0000000..2368567 --- /dev/null +++ b/fsck/source/app/App.cpp @@ -0,0 +1,562 @@ +#include +#include + +#include + +#include +#include + +#define APP_WORKERS_DIRECT_NUM 1 +#define APP_SYSLOG_IDENTIFIER "beegfs-fsck" + +App::App(int argc, char** argv) +{ + this->argc = argc; + this->argv = argv; + + this->appResult = APPCODE_NO_ERROR; + + this->cfg = NULL; + this->netFilter = NULL; + this->tcpOnlyFilter = NULL; + this->log = NULL; + this->mgmtNodes = NULL; + this->metaNodes = NULL; + this->storageNodes = NULL; + this->internodeSyncer = NULL; + this->targetMapper = NULL; + this->targetStateStore = NULL; + this->buddyGroupMapper = NULL; + this->metaBuddyGroupMapper = NULL; + this->workQueue = NULL; + this->ackStore = NULL; + this->netMessageFactory = NULL; + this->dgramListener = NULL; + this->modificationEventHandler = NULL; + this->runMode = NULL; + +} + +App::~App() +{ + // Note: Logging of the common lib classes is not working here, because this is called + // from class Program (so the thread-specific app-pointer isn't set in this context). + + workersDelete(); + + SAFE_DELETE(this->dgramListener); + SAFE_DELETE(this->netMessageFactory); + SAFE_DELETE(this->ackStore); + SAFE_DELETE(this->workQueue); + SAFE_DELETE(this->storageNodes); + SAFE_DELETE(this->metaNodes); + SAFE_DELETE(this->mgmtNodes); + this->localNode.reset(); + SAFE_DELETE(this->buddyGroupMapper); + SAFE_DELETE(this->metaBuddyGroupMapper); + SAFE_DELETE(this->targetStateStore); + SAFE_DELETE(this->targetMapper); + SAFE_DELETE(this->internodeSyncer); + SAFE_DELETE(this->log); + SAFE_DELETE(this->tcpOnlyFilter); + SAFE_DELETE(this->netFilter); + SAFE_DELETE(this->cfg); + SAFE_DELETE(this->runMode); + + Logger::destroyLogger(); + closelog(); +} + +void App::run() +{ + try + { + openlog(APP_SYSLOG_IDENTIFIER, LOG_NDELAY | LOG_PID | LOG_CONS, LOG_DAEMON); + + this->cfg = new Config(argc, argv); + + runNormal(); + } + catch (InvalidConfigException& e) + { + std::cerr << std::endl; + std::cerr << "Error: " << e.what() << std::endl; + std::cerr << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + this->appResult = APPCODE_INVALID_CONFIG; + return; + } + catch (std::exception& e) + { + std::cerr << std::endl; + std::cerr << "Unrecoverable error: " << e.what() << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + appResult = APPCODE_RUNTIME_ERROR; + return; + } +} + +void App::runNormal() +{ + bool componentsStarted = false; + + auto runModeEnum = Program::getApp()->getConfig()->determineRunMode(); + + if (runModeEnum == RunMode_CHECKFS || runModeEnum == RunMode_ENABLEQUOTA) + { + // check if running as root + if(geteuid()) + { + std::cerr << std::endl + << "Running beegfs-fsck requires root privileges." + << std::endl << std::endl << std::endl; + ModeHelp().execute(); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + if (runModeEnum == RunMode_CHECKFS) + runMode = new ModeCheckFS(); + else if (runModeEnum == RunMode_ENABLEQUOTA) + runMode = new ModeEnableQuota(); + } + else if (runModeEnum == RunMode_HELP) + { + appResult = ModeHelp().execute(); + return; + } + else + { + ModeHelp().execute(); + appResult = APPCODE_INVALID_CONFIG; + return; + } + + initDataObjects(argc, argv); + + // wait for mgmtd + if ( !cfg->getSysMgmtdHost().length() ) + throw InvalidConfigException("Management host undefined"); + + bool mgmtWaitRes = waitForMgmtNode(); + if(!mgmtWaitRes) + { // typically user just pressed ctrl+c in this case + log->logErr("Waiting for beegfs-mgmtd canceled"); + appResult = APPCODE_RUNTIME_ERROR; + return; + } + + // init components + try + { + initComponents(); + } + catch (ComponentInitException& e) + { + FsckTkEx::fsckOutput(e.what(), OutputOptions_DOUBLELINEBREAK | OutputOptions_STDERR); + + FsckTkEx::fsckOutput("A hard error occurred. BeeGFS Fsck will abort.", + OutputOptions_LINEBREAK | OutputOptions_STDERR); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + // log system and configuration info + + logInfos(); + + // start component threads + startComponents(); + componentsStarted = true; + + try + { + appResult = runMode->execute(); + } + catch (InvalidConfigException& e) + { + ModeHelp modeHelp; + modeHelp.execute(); + + appResult = APPCODE_INVALID_CONFIG; + } + catch (std::exception &e) + { + FsckTkEx::fsckOutput("Unrecoverable error. BeeGFS Fsck will abort.", + OutputOptions_LINEBREAK | OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_STDERR); + FsckTkEx::fsckOutput(e.what(), OutputOptions_LINEBREAK | OutputOptions_STDERR); + } + + // self-termination + if(componentsStarted) + { + stopComponents(); + joinComponents(); + } +} + +void App::initDataObjects(int argc, char** argv) +{ + this->netFilter = new NetFilter(cfg->getConnNetFilterFile() ); + this->tcpOnlyFilter = new NetFilter(cfg->getConnTcpOnlyFilterFile() ); + + Logger::createLogger(cfg->getLogLevel(), cfg->getLogType(), cfg->getLogNoDate(), + cfg->getLogStdFile(), cfg->getLogNumLines(), cfg->getLogNumRotatedFiles()); + + this->log = new LogContext("App"); + + std::string interfacesFilename = this->cfg->getConnInterfacesFile(); + if ( interfacesFilename.length() ) + Config::loadStringListFile(interfacesFilename.c_str(), this->allowedInterfaces); + + this->targetMapper = new TargetMapper(); + this->targetStateStore = new TargetStateStore(NODETYPE_Storage); + this->buddyGroupMapper = new MirrorBuddyGroupMapper(targetMapper); + this->metaBuddyGroupMapper = new MirrorBuddyGroupMapper(); + + this->mgmtNodes = new NodeStoreServers(NODETYPE_Mgmt, false); + this->metaNodes = new NodeStoreServers(NODETYPE_Meta, false); + this->storageNodes = new NodeStoreServers(NODETYPE_Storage, false); + + this->workQueue = new MultiWorkQueue(); + this->ackStore = new AcknowledgmentStore(); + + initLocalNodeInfo(); + + registerSignalHandler(); + + this->netMessageFactory = new NetMessageFactory(); +} + +void App::findAllowedRDMAInterfaces(NicAddressList& outList) const +{ + Config* cfg = this->getConfig(); + + if(cfg->getConnUseRDMA() && RDMASocket::rdmaDevicesExist() ) + { + bool foundRdmaInterfaces = NetworkInterfaceCard::checkAndAddRdmaCapability(outList); + if (foundRdmaInterfaces) + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); // re-sort the niclist + } +} + +void App::findAllowedInterfaces(NicAddressList& outList) const +{ + // discover local NICs and filter them + NetworkInterfaceCard::findAllInterfaces(allowedInterfaces, outList); + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); +} + +void App::initLocalNodeInfo() +{ + + findAllowedInterfaces(localNicList); + findAllowedRDMAInterfaces(localNicList); + + if ( this->localNicList.empty() ) + throw InvalidConfigException("Couldn't find any usable NIC"); + + initRoutingTable(); + updateRoutingTable(); + + std::string nodeID = System::getHostname(); + + this->localNode = std::make_shared(NODETYPE_Client, nodeID, NumNodeID(), 0, 0, + this->localNicList); +} + +void App::initComponents() +{ + this->log->log(Log_DEBUG, "Initializing components..."); + + // Note: We choose a random udp port here to avoid conflicts with the client + unsigned short udpListenPort = 0; + + this->dgramListener = new DatagramListener(netFilter, localNicList, ackStore, udpListenPort, + this->cfg->getConnRestrictOutboundInterfaces()); + + // update the local node info with udp port + this->localNode->updateInterfaces(dgramListener->getUDPPort(), 0, this->localNicList); + + this->internodeSyncer = new InternodeSyncer(); + + workersInit(); + + this->log->log(Log_DEBUG, "Components initialized."); +} + +void App::startComponents() +{ + log->log(Log_SPAM, "Starting up components..."); + + // make sure child threads don't receive SIGINT/SIGTERM (blocked signals are inherited) + PThread::blockInterruptSignals(); + + dgramListener->start(); + + internodeSyncer->start(); + internodeSyncer->waitForServers(); + + workersStart(); + + PThread::unblockInterruptSignals(); // main app thread may receive SIGINT/SIGTERM + + log->log(Log_DEBUG, "Components running."); +} + +void App::stopComponents() +{ + // note: this method may not wait for termination of the components, because that could + // lead to a deadlock (when calling from signal handler) + workersStop(); + + if(this->internodeSyncer) + this->internodeSyncer->selfTerminate(); + + if ( dgramListener ) + { + dgramListener->selfTerminate(); + dgramListener->sendDummyToSelfUDP(); + } + + this->selfTerminate(); +} + +void App::joinComponents() +{ + log->log(4, "Joining component threads..."); + + this->internodeSyncer->join(); + + /* (note: we need one thread for which we do an untimed join, so this should be a quite reliably + terminating thread) */ + this->dgramListener->join(); + + workersJoin(); +} + +void App::workersInit() +{ + unsigned numWorkers = cfg->getTuneNumWorkers(); + + for(unsigned i=0; i < numWorkers; i++) + { + Worker* worker = new Worker( + std::string("Worker") + StringTk::intToStr(i+1), workQueue, QueueWorkType_INDIRECT); + workerList.push_back(worker); + } + + for(unsigned i=0; i < APP_WORKERS_DIRECT_NUM; i++) + { + Worker* worker = new Worker( + std::string("DirectWorker") + StringTk::intToStr(i+1), workQueue, QueueWorkType_DIRECT); + workerList.push_back(worker); + } +} + +void App::workersStart() +{ + for ( WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++ ) + { + (*iter)->start(); + } +} + +void App::workersStop() +{ + // need two loops because we don't know if the worker that handles the work will be the same that + // received the self-terminate-request + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + (*iter)->selfTerminate(); + } + + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + workQueue->addDirectWork(new DummyWork() ); + } +} + +void App::workersDelete() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + delete(*iter); + } + + workerList.clear(); +} + +void App::workersJoin() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + waitForComponentTermination(*iter); + } +} + +void App::logInfos() +{ + // print software version (BEEGFS_VERSION) + log->log(Log_NOTICE, std::string("Version: ") + BEEGFS_VERSION); + + // print debug version info + LOG_DEBUG_CONTEXT(*log, Log_CRITICAL, "--DEBUG VERSION--"); + + // print local nodeID + log->log(Log_NOTICE, std::string("LocalNode: ") + localNode->getTypedNodeID() ); + + // list usable network interfaces + NicAddressList nicList(localNode->getNicList()); + logUsableNICs(log, nicList); + + // print net filters + if ( netFilter->getNumFilterEntries() ) + { + this->log->log(2, + std::string("Net filters: ") + StringTk::uintToStr(netFilter->getNumFilterEntries())); + } + + if(tcpOnlyFilter->getNumFilterEntries() ) + { + this->log->log(Log_WARNING, std::string("TCP-only filters: ") + + StringTk::uintToStr(tcpOnlyFilter->getNumFilterEntries() ) ); + } +} + +/** + * Request mgmt heartbeat and wait for the mgmt node to appear in nodestore. + * + * @return true if mgmt heartbeat received, false on error or thread selftermination order + */ +bool App::waitForMgmtNode() +{ + const unsigned waitTimeoutMS = 0; // infinite wait + const unsigned nameResolutionRetries = 3; + + // choose a random udp port here + unsigned udpListenPort = 0; + unsigned udpMgmtdPort = cfg->getConnMgmtdPort(); + std::string mgmtdHost = cfg->getSysMgmtdHost(); + + RegistrationDatagramListener regDGramLis(this->netFilter, this->localNicList, this->ackStore, + udpListenPort, this->cfg->getConnRestrictOutboundInterfaces()); + + regDGramLis.start(); + + log->log(Log_CRITICAL, "Waiting for beegfs-mgmtd@" + + mgmtdHost + ":" + StringTk::uintToStr(udpMgmtdPort) + "..."); + + bool gotMgmtd = NodesTk::waitForMgmtHeartbeat( + this, ®DGramLis, this->mgmtNodes, mgmtdHost, udpMgmtdPort, waitTimeoutMS, + nameResolutionRetries); + + regDGramLis.selfTerminate(); + regDGramLis.sendDummyToSelfUDP(); // for faster termination + + regDGramLis.join(); + + return gotMgmtd; +} + +void App::updateLocalNicList(NicAddressList& localNicList) +{ + std::vector allNodes({ mgmtNodes, metaNodes, storageNodes}); + updateLocalNicListAndRoutes(log, localNicList, allNodes); + localNode->updateInterfaces(0, 0, localNicList); + dgramListener->setLocalNicList(localNicList); +} + +/* + * Handles expections that lead to the termination of a component. + * Initiates an application shutdown. + */ + +void App::handleComponentException(std::exception& e) +{ + const char* logContext = "App (component exception handler)"; + LogContext log(logContext); + + const auto componentName = PThread::getCurrentThreadName(); + + log.logErr( + "The component [" + componentName + "] encountered an unrecoverable error. " + + std::string("[SysErr: ") + System::getErrString() + "] " + + std::string("Exception message: ") + e.what() ); + + log.log(2, "Shutting down..."); + + shallAbort.set(1); + stopComponents(); +} + +void App::handleNetworkInterfaceFailure(const std::string& devname) +{ + LOG(GENERAL, ERR, "Network interface failure.", + ("Device", devname)); + internodeSyncer->setForceCheckNetwork(); +} + +void App::registerSignalHandler() +{ + signal(SIGINT, App::signalHandler); + signal(SIGTERM, App::signalHandler); +} + +void App::signalHandler(int sig) +{ + App* app = Program::getApp(); + + Logger* log = Logger::getLogger(); + const char* logContext = "App::signalHandler"; + + // note: this might deadlock if the signal was thrown while the logger mutex is locked by the + // application thread (depending on whether the default mutex style is recursive). but + // even recursive mutexes are not acceptable in this case. + // we need something like a timed lock for the log mutex. if it succeeds within a + // few seconds, we know that we didn't hold the mutex lock. otherwise we simply skip the + // log message. this will only work if the mutex is non-recusive (which is unknown for + // the default mutex style). + // but it is very unlikely that the application thread holds the log mutex, because it + // joins the component threads and so it doesn't do anything else but sleeping! + + switch(sig) + { + case SIGINT: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received a SIGINT. Shutting down..."); + } + break; + + case SIGTERM: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received a SIGTERM. Shutting down..."); + } + break; + + default: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received an unknown signal. Shutting down..."); + } + break; + } + + app->abort(); +} + +void App::abort() +{ + shallAbort.set(1); + stopComponents(); +} diff --git a/fsck/source/app/App.h b/fsck/source/app/App.h new file mode 100644 index 0000000..f0b702d --- /dev/null +++ b/fsck/source/app/App.h @@ -0,0 +1,223 @@ +#ifndef APP_H_ +#define APP_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef BEEGFS_VERSION + #error BEEGFS_VERSION undefined +#endif + +// program return codes +#define APPCODE_NO_ERROR 0 +#define APPCODE_INVALID_CONFIG 1 +#define APPCODE_INITIALIZATION_ERROR 2 +#define APPCODE_COMMUNICATION_ERROR 3 +#define APPCODE_RUNTIME_ERROR 4 +#define APPCODE_USER_ABORTED 5 + +typedef std::list WorkerList; +typedef WorkerList::iterator WorkerListIter; + + +// forward declarations +class LogContext; + +class App : public AbstractApp +{ + public: + App(int argc, char** argv); + virtual ~App(); + + virtual void run() override; + + void abort(); + virtual void handleComponentException(std::exception& e) override; + virtual void handleNetworkInterfaceFailure(const std::string& devname) override; + + bool getShallAbort() + { + return (shallAbort.read() != 0); + } + + private: + int appResult; + int argc; + char** argv; + + AtomicSizeT shallAbort; + + Config* cfg; + LogContext* log; + + NetFilter* netFilter; + NetFilter* tcpOnlyFilter; // for IPs that allow only plain TCP (no RDMA etc) + std::list allowedInterfaces; + + std::shared_ptr localNode; + NodeStore* mgmtNodes; + NodeStore* metaNodes; + NodeStore* storageNodes; + RootInfo metaRoot; + InternodeSyncer* internodeSyncer; + TargetMapper* targetMapper; + TargetStateStore* targetStateStore; + MirrorBuddyGroupMapper* buddyGroupMapper; + MirrorBuddyGroupMapper* metaBuddyGroupMapper; + MultiWorkQueue* workQueue; + AcknowledgmentStore* ackStore; + NetMessageFactory* netMessageFactory; + + DatagramListener* dgramListener; + WorkerList workerList; + + ModificationEventHandler* modificationEventHandler; + + Mode* runMode; + + void runNormal(); + + void workersInit(); + void workersStart(); + void workersStop(); + void workersDelete(); + void workersJoin(); + void initDataObjects(int argc, char** argv); + void initLocalNodeInfo(); + void initComponents(); + bool initRunMode(); + void startComponents(); + void joinComponents(); + virtual void stopComponents() override; + void logInfos(); + bool waitForMgmtNode(); + void registerSignalHandler(); + static void signalHandler(int sig); + + public: + virtual const ICommonConfig* getCommonConfig() const override + { + return cfg; + } + + virtual const AbstractNetMessageFactory* getNetMessageFactory() const override + { + return netMessageFactory; + } + + virtual const NetFilter* getNetFilter() const override + { + return netFilter; + } + + virtual const NetFilter* getTcpOnlyFilter() const override + { + return tcpOnlyFilter; + } + + Config* getConfig() const + { + return cfg; + } + + DatagramListener* getDatagramListener() const + { + return dgramListener; + } + + int getAppResult() const + { + return appResult; + } + + NodeStore* getMgmtNodes() const + { + return mgmtNodes; + } + + NodeStore* getMetaNodes() + { + return metaNodes; + } + + NodeStore* getStorageNodes() const + { + return storageNodes; + } + + Node& getLocalNode() const + { + return *localNode; + } + + void updateLocalNicList(NicAddressList& localNicList); + + MultiWorkQueue* getWorkQueue() + { + return workQueue; + } + + InternodeSyncer* getInternodeSyncer() + { + return internodeSyncer; + } + + TargetMapper* getTargetMapper() + { + return targetMapper; + } + + TargetStateStore* getTargetStateStore() + { + return targetStateStore; + } + + MirrorBuddyGroupMapper* getMirrorBuddyGroupMapper() + { + return buddyGroupMapper; + } + + MirrorBuddyGroupMapper* getMetaMirrorBuddyGroupMapper() + { + return metaBuddyGroupMapper; + } + + ModificationEventHandler* getModificationEventHandler() + { + return modificationEventHandler; + } + + void setModificationEventHandler(ModificationEventHandler* handler) + { + modificationEventHandler = handler; + } + + const RootInfo& getMetaRoot() const { return metaRoot; } + RootInfo& getMetaRoot() { return metaRoot; } + void findAllowedInterfaces(NicAddressList& outList) const; + void findAllowedRDMAInterfaces(NicAddressList& outList) const; +}; + +#endif // APP_H_ diff --git a/fsck/source/app/config/Config.cpp b/fsck/source/app/config/Config.cpp new file mode 100644 index 0000000..483887f --- /dev/null +++ b/fsck/source/app/config/Config.cpp @@ -0,0 +1,317 @@ +#include +#include "Config.h" + +#define IGNORE_CONFIG_CLIENT_VALUE(keyStr) /* to be used in applyConfigMap() */ \ + if(testConfigMapKeyMatch(iter, keyStr, addDashes) ) \ + ; \ + else + +// Note: Keep in sync with enum RunMode +RunModesElem const __RunModes[] = +{ + { "--checkfs", RunMode_CHECKFS }, + { "--enablequota", RunMode_ENABLEQUOTA }, + { NULL, RunMode_INVALID } +}; + +Config::Config(int argc, char** argv) : + AbstractConfig(argc, argv) +{ + initConfig(argc, argv, false, true); + logType = LogType_LOGFILE; +} + +/** + * Determine RunMode from config. + * If a valid RunMode exists in the config, the corresponding config element will be erased. + */ +enum RunMode Config::determineRunMode() +{ + /* test for given help argument, e.g. in case the user wants to see mode-specific help with + arguments "--help --". */ + + StringMapIter iter = configMap.find(RUNMODE_HELP_KEY_STRING); + if(iter != configMap.end() ) + { // user did specify "--help" + /* note: it's important to remove the help arg here, because mode help will call this again + to find out whether user wants to see mode-specific help. */ + eraseFromConfigMap(iter); + return RunMode_HELP; + } + + // walk all defined modes to check whether we find any of them in the config + + for(int i=0; __RunModes[i].modeString != NULL; i++) + { + iter = configMap.find(__RunModes[i].modeString); + if(iter != configMap.end() ) + { // we found a valid mode in the config + eraseFromConfigMap(iter); + return __RunModes[i].runMode; + } + } + + // no valid mode found + + return RunMode_INVALID; +} + +/** + * Sets the default values for each configurable in the configMap. + * + * @param addDashes true to prepend "--" to all config keys. + */ +void Config::loadDefaults(bool addDashes) +{ + AbstractConfig::loadDefaults(addDashes); + + // re-definitions + configMapRedefine("cfgFile", createDefaultCfgFilename(), addDashes); + + // own definitions + configMapRedefine("connInterfacesFile", "", addDashes); + + configMapRedefine("tuneNumWorkers", "32", addDashes); + configMapRedefine("tunePreferredNodesFile", "", addDashes); + configMapRedefine("tuneDbFragmentSize", "0", addDashes); + configMapRedefine("tuneDentryCacheSize", "0", addDashes); + + configMapRedefine("runDaemonized", "false", addDashes); + + configMapRedefine("databasePath", CONFIG_DEFAULT_DBPATH, addDashes); + + configMapRedefine("overwriteDbFile", "false", addDashes); + + configMapRedefine("testDatabasePath", CONFIG_DEFAULT_TESTDBPATH, addDashes); + + configMapRedefine("DatabaseNumMaxConns", "16", addDashes); + + configMapRedefine("overrideRootMDS", "", addDashes); + + configMapRedefine("logStdFile", CONFIG_DEFAULT_LOGFILE, addDashes); + configMapRedefine("logOutFile", CONFIG_DEFAULT_OUTFILE, addDashes); + configMapRedefine("logNoDate", "false", addDashes); + + configMapRedefine("readOnly", "false", addDashes); + + configMapRedefine("noFetch", "false", addDashes); + + configMapRedefine("automatic", "false", addDashes); + + configMapRedefine("runOffline", "false", addDashes); + + configMapRedefine("forceRestart", "false", addDashes); + + configMapRedefine("quotaEnabled", "false", addDashes); + + configMapRedefine("ignoreDBDiskSpace", "false", addDashes); +} + +/** + * @param addDashes true to prepend "--" to tested config keys for matching. + */ +void Config::applyConfigMap(bool enableException, bool addDashes) +{ + AbstractConfig::applyConfigMap(false, addDashes); + + for (StringMapIter iter = configMap.begin(); iter != configMap.end();) + { + bool unknownElement = false; + + IGNORE_CONFIG_CLIENT_VALUE("logClientID") + IGNORE_CONFIG_CLIENT_VALUE("logType") + IGNORE_CONFIG_CLIENT_VALUE("connNumCommRetries") + IGNORE_CONFIG_CLIENT_VALUE("connUnmountRetries") + IGNORE_CONFIG_CLIENT_VALUE("connCommRetrySecs") + IGNORE_CONFIG_CLIENT_VALUE("connMaxConcurrentAttempts") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAInterfacesFile") + IGNORE_CONFIG_CLIENT_VALUE("connTCPFallbackEnabled") + IGNORE_CONFIG_CLIENT_VALUE("connMessagingTimeouts") + IGNORE_CONFIG_CLIENT_VALUE("connInterfacesList") + IGNORE_CONFIG_CLIENT_VALUE("connRDMATimeouts") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAFragmentSize") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAKeyType") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAMetaFragmentSize") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAMetaBufNum") + IGNORE_CONFIG_CLIENT_VALUE("connRDMAMetaBufSize") + IGNORE_CONFIG_CLIENT_VALUE("tuneFileCacheType") + IGNORE_CONFIG_CLIENT_VALUE("tunePagedIOBufSize") + IGNORE_CONFIG_CLIENT_VALUE("tunePagedIOBufNum") + IGNORE_CONFIG_CLIENT_VALUE("tuneFileCacheBufSize") + IGNORE_CONFIG_CLIENT_VALUE("tuneFileCacheBufNum") + IGNORE_CONFIG_CLIENT_VALUE("tunePageCacheValidityMS") + IGNORE_CONFIG_CLIENT_VALUE("tuneAttribCacheValidityMS") + IGNORE_CONFIG_CLIENT_VALUE("tuneMaxWriteWorks") + IGNORE_CONFIG_CLIENT_VALUE("tuneMaxReadWorks") + IGNORE_CONFIG_CLIENT_VALUE("tuneAllowMultiSetWrite") + IGNORE_CONFIG_CLIENT_VALUE("tuneAllowMultiSetRead") + IGNORE_CONFIG_CLIENT_VALUE("tunePathBufSize") + IGNORE_CONFIG_CLIENT_VALUE("tunePathBufNum") + IGNORE_CONFIG_CLIENT_VALUE("tuneMaxReadWriteNum") + IGNORE_CONFIG_CLIENT_VALUE("tuneMaxReadWriteNodesNum") + IGNORE_CONFIG_CLIENT_VALUE("tuneMsgBufSize") + IGNORE_CONFIG_CLIENT_VALUE("tuneMsgBufNum") + IGNORE_CONFIG_CLIENT_VALUE("tuneRemoteFSync") + IGNORE_CONFIG_CLIENT_VALUE("tunePreferredMetaFile") + IGNORE_CONFIG_CLIENT_VALUE("tunePreferredStorageFile") + IGNORE_CONFIG_CLIENT_VALUE("tuneUseGlobalFileLocks") + IGNORE_CONFIG_CLIENT_VALUE("tuneRefreshOnGetAttr") + IGNORE_CONFIG_CLIENT_VALUE("tuneInodeBlockBits") + IGNORE_CONFIG_CLIENT_VALUE("tuneInodeBlockSize") + IGNORE_CONFIG_CLIENT_VALUE("tuneMaxClientMirrorSize") // was removed, kept here for compat + IGNORE_CONFIG_CLIENT_VALUE("tuneEarlyCloseResponse") + IGNORE_CONFIG_CLIENT_VALUE("tuneUseGlobalAppendLocks") + IGNORE_CONFIG_CLIENT_VALUE("tuneUseBufferedAppend") + IGNORE_CONFIG_CLIENT_VALUE("tuneStatFsCacheSecs") + IGNORE_CONFIG_CLIENT_VALUE("sysCacheInvalidationVersion") + IGNORE_CONFIG_CLIENT_VALUE("sysCreateHardlinksAsSymlinks") + IGNORE_CONFIG_CLIENT_VALUE("sysMountSanityCheckMS") + IGNORE_CONFIG_CLIENT_VALUE("sysSyncOnClose") + IGNORE_CONFIG_CLIENT_VALUE("sysSessionCheckOnClose") + IGNORE_CONFIG_CLIENT_VALUE("sysSessionChecksEnabled") + IGNORE_CONFIG_CLIENT_VALUE("sysTargetOfflineTimeoutSecs") + IGNORE_CONFIG_CLIENT_VALUE("sysInodeIDStyle") + IGNORE_CONFIG_CLIENT_VALUE("sysACLsEnabled") + IGNORE_CONFIG_CLIENT_VALUE("sysXAttrsEnabled") + IGNORE_CONFIG_CLIENT_VALUE("sysBypassFileAccessCheckOnMeta") + IGNORE_CONFIG_CLIENT_VALUE("sysXAttrsCheckCapabilities") + IGNORE_CONFIG_CLIENT_VALUE("tuneDirSubentryCacheValidityMS") + IGNORE_CONFIG_CLIENT_VALUE("tuneFileSubentryCacheValidityMS") + IGNORE_CONFIG_CLIENT_VALUE("tuneENOENTCacheValidityMS") + IGNORE_CONFIG_CLIENT_VALUE("tuneCoherentBuffers") + IGNORE_CONFIG_CLIENT_VALUE("sysFileEventLogMask") + IGNORE_CONFIG_CLIENT_VALUE("sysRenameEbusyAsXdev") + IGNORE_CONFIG_CLIENT_VALUE("tuneNumRetryWorkers") + IGNORE_CONFIG_CLIENT_VALUE("connHelperdPortTCP") // was removed, kept here for compat + + if (testConfigMapKeyMatch(iter, "connInterfacesFile", addDashes)) + connInterfacesFile = iter->second; + else if (testConfigMapKeyMatch(iter, "tuneNumWorkers", addDashes)) + tuneNumWorkers = StringTk::strToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "tunePreferredNodesFile", addDashes)) + tunePreferredNodesFile = iter->second; + else if (testConfigMapKeyMatch(iter, "tuneDbFragmentSize", addDashes)) + tuneDbFragmentSize = StringTk::strToUInt64(iter->second.c_str()); + else if (testConfigMapKeyMatch(iter, "tuneDentryCacheSize", addDashes)) + tuneDentryCacheSize = StringTk::strToUInt64(iter->second.c_str()); + else if (testConfigMapKeyMatch(iter, "runDaemonized", addDashes)) + runDaemonized = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "databasePath", addDashes)) + databasePath = iter->second; + else if (testConfigMapKeyMatch(iter, "overwriteDbFile", addDashes)) + overwriteDbFile = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "testDatabasePath", addDashes)) + testDatabasePath = iter->second; + else if (testConfigMapKeyMatch(iter, "databaseNumMaxConns", addDashes)) + databaseNumMaxConns = StringTk::strHexToUInt(iter->second); + else if (testConfigMapKeyMatch(iter, "overrideRootMDS", addDashes)) + overrideRootMDS = iter->second; + else if (testConfigMapKeyMatch(iter, "logStdFile", addDashes)) + logStdFile = iter->second; + else if (testConfigMapKeyMatch(iter, "logOutFile", addDashes)) + logOutFile = iter->second; + else if (testConfigMapKeyMatch(iter, "readOnly", addDashes)) + readOnly = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "noFetch", addDashes)) + noFetch = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "automatic", addDashes)) + automatic = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "runOffline", addDashes)) + runOffline = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "forceRestart", addDashes)) + forceRestart = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "quotaEnabled", addDashes)) + quotaEnabled = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "ignoreDBDiskSpace", addDashes)) + ignoreDBDiskSpace = StringTk::strToBool(iter->second); + else if (testConfigMapKeyMatch(iter, "checkMalformedChunk", addDashes)) + checkFsActions.set(CHECK_MALFORMED_CHUNK); + else if (testConfigMapKeyMatch(iter, "checkFilesWithMissingTargets", addDashes)) + checkFsActions.set(CHECK_FILES_WITH_MISSING_TARGETS); + else if (testConfigMapKeyMatch(iter, "checkOrphanedDentryByIDFiles", addDashes)) + checkFsActions.set(CHECK_ORPHANED_DENTRY_BYIDFILES); + else if (testConfigMapKeyMatch(iter, "checkDirEntriesWithBrokenIDFile", addDashes)) + checkFsActions.set(CHECK_DIRENTRIES_WITH_BROKENIDFILE); + else if (testConfigMapKeyMatch(iter, "checkOrphanedChunk", addDashes)) + checkFsActions.set(CHECK_ORPHANED_CHUNK); + else if (testConfigMapKeyMatch(iter, "checkChunksInWrongPath", addDashes)) + checkFsActions.set(CHECK_CHUNKS_IN_WRONGPATH); + else if (testConfigMapKeyMatch(iter, "checkWrongInodeOwner", addDashes)) + checkFsActions.set(CHECK_WRONG_INODE_OWNER); + else if (testConfigMapKeyMatch(iter, "checkWrongOwnerInDentry", addDashes)) + checkFsActions.set(CHECK_WRONG_OWNER_IN_DENTRY); + else if (testConfigMapKeyMatch(iter, "checkOrphanedContDir", addDashes)) + checkFsActions.set(CHECK_ORPHANED_CONT_DIR); + else if (testConfigMapKeyMatch(iter, "checkOrphanedDirInode", addDashes)) + checkFsActions.set(CHECK_ORPHANED_DIR_INODE); + else if (testConfigMapKeyMatch(iter, "checkOrphanedFileInode", addDashes)) + checkFsActions.set(CHECK_ORPHANED_FILE_INODE); + else if (testConfigMapKeyMatch(iter, "checkDanglingDentry", addDashes)) + checkFsActions.set(CHECK_DANGLING_DENTRY); + else if (testConfigMapKeyMatch(iter, "checkMissingContDir", addDashes)) + checkFsActions.set(CHECK_MISSING_CONT_DIR); + else if (testConfigMapKeyMatch(iter, "checkWrongFileAttribs", addDashes)) + checkFsActions.set(CHECK_WRONG_FILE_ATTRIBS); + else if (testConfigMapKeyMatch(iter, "checkWrongDirAttribs", addDashes)) + checkFsActions.set(CHECK_WRONG_DIR_ATTRIBS); + else if (testConfigMapKeyMatch(iter, "checkOldStyledHardlinks", addDashes)) + checkFsActions.set(CHECK_OLD_STYLED_HARDLINKS); + else + { + // unknown element occurred + unknownElement = true; + + if (enableException) + { + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + } + + // advance iterator (and remove handled element) + + if (unknownElement) + { + // just skip the unknown element + iter++; + } + else + { + // remove this element from the map + iter = eraseFromConfigMap(iter); + } + } +} + +void Config::initImplicitVals() +{ + // tuneNumWorkers + if (!tuneNumWorkers) + tuneNumWorkers = BEEGFS_MAX(System::getNumOnlineCPUs() * 2, 4); + + if (!tuneDbFragmentSize) + tuneDbFragmentSize = uint64_t(sysconf(_SC_PHYS_PAGES) ) * sysconf(_SC_PAGESIZE) / 2; + + // just blindly assume that 384 bytes will be enough for a single cache entry. should be + if (!tuneDentryCacheSize) + tuneDentryCacheSize = tuneDbFragmentSize / 384; + + // read in connAuthFile only if we are running as root. + // if not root, the program will abort anyway + if(!geteuid()) + { + AbstractConfig::initConnAuthHash(connAuthFile, &connAuthHash); + } +} + +std::string Config::createDefaultCfgFilename() const +{ + struct stat statBuf; + + const int statRes = stat(CONFIG_DEFAULT_CFGFILENAME, &statBuf); + + if (!statRes && S_ISREG(statBuf.st_mode)) + return CONFIG_DEFAULT_CFGFILENAME; // there appears to be a config file + + return ""; // no default file otherwise +} + diff --git a/fsck/source/app/config/Config.h b/fsck/source/app/config/Config.h new file mode 100644 index 0000000..6d992a8 --- /dev/null +++ b/fsck/source/app/config/Config.h @@ -0,0 +1,224 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#include +#include + +#define CONFIG_DEFAULT_CFGFILENAME "/etc/beegfs/beegfs-client.conf" +#define CONFIG_DEFAULT_LOGFILE "/var/log/beegfs-fsck.log" +#define CONFIG_DEFAULT_OUTFILE "/var/log/beegfs-fsck.out" +#define CONFIG_DEFAULT_DBPATH "/var/lib/beegfs/" +#define CONFIG_DEFAULT_TESTDBPATH "/tmp/beegfs-fsck/" + +#define RUNMODE_HELP_KEY_STRING "--help" /* key for usage help */ +#define __RUNMODES_SIZE \ + ( (sizeof(__RunModes) ) / (sizeof(RunModesElem) ) - 1) + /* -1 because last elem is NULL */ + +enum CheckFsActions +{ + CHECK_MALFORMED_CHUNK = 0, + CHECK_FILES_WITH_MISSING_TARGETS = 1, + CHECK_ORPHANED_DENTRY_BYIDFILES = 2, + CHECK_DIRENTRIES_WITH_BROKENIDFILE = 3, + CHECK_ORPHANED_CHUNK = 4, + CHECK_CHUNKS_IN_WRONGPATH = 5, + CHECK_WRONG_INODE_OWNER = 6, + CHECK_WRONG_OWNER_IN_DENTRY = 7, + CHECK_ORPHANED_CONT_DIR = 8, + CHECK_ORPHANED_DIR_INODE = 9, + CHECK_ORPHANED_FILE_INODE = 10, + CHECK_DANGLING_DENTRY = 11, + CHECK_MISSING_CONT_DIR = 12, + CHECK_WRONG_FILE_ATTRIBS = 13, + CHECK_WRONG_DIR_ATTRIBS = 14, + CHECK_OLD_STYLED_HARDLINKS = 15, + CHECK_FS_ACTIONS_COUNT = 16 +}; + +// Note: Keep in sync with __RunModes array +enum RunMode +{ + RunMode_CHECKFS = 0, + RunMode_ENABLEQUOTA = 1, + RunMode_HELP = 2, + RunMode_INVALID = 3 /* not valid as index in RunModes array */ +}; + +struct RunModesElem +{ + const char* modeString; + enum RunMode runMode; +}; + + +extern RunModesElem const __RunModes[]; + +class Config : public AbstractConfig +{ + public: + Config(int argc, char** argv); + + enum RunMode determineRunMode(); + + private: + + // configurables + + std::string connInterfacesFile; + + unsigned tuneNumWorkers; + std::string tunePreferredNodesFile; + size_t tuneDbFragmentSize; + size_t tuneDentryCacheSize; + + bool runDaemonized; + + std::string databasePath; + bool overwriteDbFile; + + // only relevant for unit testing, to give the used databasePath + std::string testDatabasePath; + + unsigned databaseNumMaxConns; + + std::string overrideRootMDS; // not tested well, should only be used by developers + + // file for fsck output (not the log messages, but the output, which is also on the console) + std::string logOutFile; + + bool readOnly; + bool noFetch; + bool automatic; + bool runOffline; + bool forceRestart; + bool quotaEnabled; + bool ignoreDBDiskSpace; + + std::bitset checkFsActions; + + // internals + virtual void loadDefaults(bool addDashes) override; + virtual void applyConfigMap(bool enableException, bool addDashes) override; + virtual void initImplicitVals() override; + std::string createDefaultCfgFilename() const; + + public: + // getters & setters + const StringMap* getUnknownConfigArgs() const + { + return getConfigMap(); + } + + const std::string& getConnInterfacesFile() const + { + return connInterfacesFile; + } + + unsigned getTuneNumWorkers() const + { + return tuneNumWorkers; + } + + size_t getTuneDbFragmentSize() const + { + return tuneDbFragmentSize; + } + + size_t getTuneDentryCacheSize() const + { + return tuneDentryCacheSize; + } + + const std::string& getTunePreferredNodesFile() const + { + return tunePreferredNodesFile; + } + + bool getRunDaemonized() const + { + return runDaemonized; + } + + const std::string& getDatabasePath() const + { + return databasePath; + } + + bool getOverwriteDbFile() const + { + return overwriteDbFile; + } + + const std::string& getTestDatabasePath() const + { + return testDatabasePath; + } + + unsigned getDatabaseNumMaxConns() const + { + return databaseNumMaxConns; + } + + std::string getOverrideRootMDS() const + { + return overrideRootMDS; + } + + bool getReadOnly() const + { + return readOnly; + } + + bool getNoFetch() const + { + return noFetch; + } + + bool getAutomatic() const + { + return automatic; + } + + const std::string& getLogOutFile() const + { + return logOutFile; + } + + bool getRunOffline() const + { + return runOffline; + } + + bool getForceRestart() const + { + return forceRestart; + } + + bool getQuotaEnabled() const + { + return quotaEnabled; + } + + bool getIgnoreDBDiskSpace() const + { + return ignoreDBDiskSpace; + } + + std::bitset getCheckFsActions() const + { + return checkFsActions; + } + + void disableAutomaticRepairMode() + { + this->automatic = false; + } + + void setReadOnly() + { + this->readOnly = true; + } +}; + +#endif /*CONFIG_H_*/ diff --git a/fsck/source/components/DataFetcher.cpp b/fsck/source/components/DataFetcher.cpp new file mode 100644 index 0000000..59f2ea3 --- /dev/null +++ b/fsck/source/components/DataFetcher.cpp @@ -0,0 +1,203 @@ +#include "DataFetcher.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +DataFetcher::DataFetcher(FsckDB& db, bool forceRestart) + : database(&db), + workQueue(Program::getApp()->getWorkQueue() ), + generatedPackages(0), + forceRestart(forceRestart) +{ +} + +FhgfsOpsErr DataFetcher::execute() +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + NodeStore* metaNodeStore = Program::getApp()->getMetaNodes(); + auto metaNodeList = metaNodeStore->referenceAllNodes(); + + printStatus(); + + retrieveDirEntries(metaNodeList); + retrieveInodes(metaNodeList); + const bool retrieveRes = retrieveChunks(); + + if (!retrieveRes) + { + retVal = FhgfsOpsErr_INUSE; + Program::getApp()->abort(); + } + + // wait for all packages to finish, because we cannot proceed if not all data was fetched + // BUT : update output each OUTPUT_INTERVAL_MS ms + while (!finishedPackages.timedWaitForCount(generatedPackages, DATAFETCHER_OUTPUT_INTERVAL_MS)) + { + printStatus(); + + if (retVal != FhgfsOpsErr_INUSE && Program::getApp()->getShallAbort()) + { + // setting retVal to INTERRUPTED + // but still, we needed to wait for the workers to terminate, because of the + // SynchronizedCounter (this object cannnot be destroyed before all workers terminate) + retVal = FhgfsOpsErr_INTERRUPTED; + } + } + + if (retVal == FhgfsOpsErr_SUCCESS && fatalErrorsFound.read() > 0) + retVal = FhgfsOpsErr_INTERNAL; + + if(retVal == FhgfsOpsErr_SUCCESS) + { + std::set allUsedTargets; + + while(!this->usedTargets.empty() ) + { + allUsedTargets.insert(this->usedTargets.front().begin(), this->usedTargets.front().end() ); + this->usedTargets.pop_front(); + } + + std::list usedTargetsList(allUsedTargets.begin(), allUsedTargets.end() ); + + this->database->getUsedTargetIDsTable()->insert(usedTargetsList, + this->database->getUsedTargetIDsTable()->newBulkHandle() ); + } + + printStatus(true); + + FsckTkEx::fsckOutput(""); // just a new line + + return retVal; +} + +void DataFetcher::retrieveDirEntries(const std::vector& nodes) +{ + for (auto nodeIter = nodes.begin(); nodeIter != nodes.end(); nodeIter++) + { + Node& node = **nodeIter; + + int requestsPerNode = 2; + + unsigned hashDirsPerRequest = (unsigned)(META_DENTRIES_LEVEL1_SUBDIR_NUM/requestsPerNode); + + unsigned hashDirStart = 0; + unsigned hashDirEnd = 0; + + do + { + hashDirEnd = hashDirStart + hashDirsPerRequest; + + // fetch DirEntries + + // before we create a package we increment the generated packages counter + this->generatedPackages++; + this->usedTargets.insert(this->usedTargets.end(), std::set() ); + + this->workQueue->addIndirectWork( + new RetrieveDirEntriesWork(database, node, &finishedPackages, fatalErrorsFound, + hashDirStart, BEEGFS_MIN(hashDirEnd, META_DENTRIES_LEVEL1_SUBDIR_NUM - 1), + &numDentriesFound, &numFileInodesFound, usedTargets.back())); + + // fetch fsIDs + + // before we create a package we increment the generated packages counter + this->generatedPackages++; + + this->workQueue->addIndirectWork( + new RetrieveFsIDsWork(database, node, &finishedPackages, fatalErrorsFound, hashDirStart, + BEEGFS_MIN(hashDirEnd, META_DENTRIES_LEVEL1_SUBDIR_NUM - 1))); + + hashDirStart = hashDirEnd + 1; + } while (hashDirEnd < META_DENTRIES_LEVEL1_SUBDIR_NUM); + } +} + +void DataFetcher::retrieveInodes(const std::vector& nodes) +{ + for (auto nodeIter = nodes.begin(); nodeIter != nodes.end(); nodeIter++) + { + Node& node = **nodeIter; + + int requestsPerNode = 2; + + unsigned hashDirsPerRequest = (unsigned)(META_INODES_LEVEL1_SUBDIR_NUM/ requestsPerNode); + + unsigned hashDirStart = 0; + unsigned hashDirEnd = 0; + + do + { + // before we create a package we increment the generated packages counter + this->generatedPackages++; + this->usedTargets.insert(this->usedTargets.end(), std::set() ); + + hashDirEnd = hashDirStart + hashDirsPerRequest; + + this->workQueue->addIndirectWork( + new RetrieveInodesWork(database, node, &finishedPackages, fatalErrorsFound, + hashDirStart, BEEGFS_MIN(hashDirEnd, META_INODES_LEVEL1_SUBDIR_NUM - 1), + &numFileInodesFound, &numDirInodesFound, usedTargets.back())); + + hashDirStart = hashDirEnd + 1; + } while (hashDirEnd < META_INODES_LEVEL1_SUBDIR_NUM); + } +} + +/** + * Add the RetrieveChunksWork for each storage node to the work queue. + * @returns true if successful, false if work can't be started because another fsck is already + * running or was aborted prematurely + */ +bool DataFetcher::retrieveChunks() +{ + App* app = Program::getApp(); + + NodeStore* storageNodes = app->getStorageNodes(); + + // for each server create a work package to retrieve chunks + for (const auto& node : storageNodes->referenceAllNodes()) + { + // before we create a package we increment the generated packages counter + this->generatedPackages++; + + RetrieveChunksWork* retrieveWork = new RetrieveChunksWork(database, node, &finishedPackages, + &numChunksFound, forceRestart); + + // node will be released inside of work package + workQueue->addIndirectWork(retrieveWork); + + bool started; + retrieveWork->waitForStarted(&started); + if (!started) + return false; + } + + return true; +} + +void DataFetcher::printStatus(bool toLogFile) +{ + uint64_t dentryCount = numDentriesFound.read(); + uint64_t fileInodeCount = numFileInodesFound.read(); + uint64_t dirInodeCount = numDirInodesFound.read(); + uint64_t chunkCount = numChunksFound.read(); + + std::string outputStr = "Fetched data > Directory entries: " + StringTk::uint64ToStr(dentryCount) + + " | Inodes: " + StringTk::uint64ToStr(fileInodeCount+dirInodeCount) + " | Chunks: " + + StringTk::uint64ToStr(chunkCount); + + int outputFlags = OutputOptions_LINEDELETE; + + if (!toLogFile) + outputFlags = outputFlags | OutputOptions_NOLOG; + + FsckTkEx::fsckOutput(outputStr, outputFlags); +} diff --git a/fsck/source/components/DataFetcher.h b/fsck/source/components/DataFetcher.h new file mode 100644 index 0000000..a5a71a9 --- /dev/null +++ b/fsck/source/components/DataFetcher.h @@ -0,0 +1,43 @@ +#ifndef DATAFETCHER_H_ +#define DATAFETCHER_H_ + +#include +#include +#include +#include + +#define DATAFETCHER_OUTPUT_INTERVAL_MS 2000 + +class DataFetcher +{ + private: + FsckDB* database; + + MultiWorkQueue* workQueue; + + AtomicUInt64 numDentriesFound; + AtomicUInt64 numFileInodesFound; + AtomicUInt64 numDirInodesFound; + AtomicUInt64 numChunksFound; + + public: + DataFetcher(FsckDB& db, bool forceRestart); + + FhgfsOpsErr execute(); + + private: + SynchronizedCounter finishedPackages; + AtomicUInt64 fatalErrorsFound; + unsigned generatedPackages; + bool forceRestart; + + std::list > usedTargets; + + void retrieveDirEntries(const std::vector& nodes); + void retrieveInodes(const std::vector& nodes); + bool retrieveChunks(); + + void printStatus(bool toLogFile = false); +}; + +#endif /* DATAFETCHER_H_ */ diff --git a/fsck/source/components/DatagramListener.cpp b/fsck/source/components/DatagramListener.cpp new file mode 100644 index 0000000..f15d140 --- /dev/null +++ b/fsck/source/components/DatagramListener.cpp @@ -0,0 +1,53 @@ +#include "DatagramListener.h" + +#include + +DatagramListener::DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, bool restrictOutboundInterfaces) : + AbstractDatagramListener("DGramLis", netFilter, localNicList, ackStore, udpPort, + restrictOutboundInterfaces) +{ +} + +DatagramListener::~DatagramListener() +{ +} + +void DatagramListener::handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) +{ + HighResolutionStats stats; // currently ignored + std::shared_ptr sock = findSenderSock(fromAddr->sin_addr); + if (sock == nullptr) + { + log.log(Log_WARNING, "Could not handle incoming message: no socket"); + return; + } + + NetMessage::ResponseContext rctx(fromAddr, sock.get(), sendBuf, DGRAMMGR_SENDBUF_SIZE, &stats); + + const auto messageType = netMessageTypeToStr(msg->getMsgType()); + + switch(msg->getMsgType() ) + { + // valid messages within this context + case NETMSGTYPE_Heartbeat: + case NETMSGTYPE_FsckModificationEvent: + { + if(!msg->processIncoming(rctx) ) + { + LOG(GENERAL, WARNING, + "Problem encountered during handling of incoming message.", messageType); + } + } break; + + default: + { // valid, but not within this context + log.logErr( + "Received a message that is invalid within the current context " + "from: " + Socket::ipaddrToStr(fromAddr->sin_addr) + "; " + "type: " + messageType ); + } break; + }; +} + + diff --git a/fsck/source/components/DatagramListener.h b/fsck/source/components/DatagramListener.h new file mode 100644 index 0000000..3a0e824 --- /dev/null +++ b/fsck/source/components/DatagramListener.h @@ -0,0 +1,22 @@ +#ifndef DATAGRAMLISTENER_H_ +#define DATAGRAMLISTENER_H_ + +#include + +class DatagramListener : public AbstractDatagramListener +{ + public: + DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces); + virtual ~DatagramListener(); + + + protected: + virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg); + + private: + +}; + +#endif /*DATAGRAMLISTENER_H_*/ diff --git a/fsck/source/components/InternodeSyncer.cpp b/fsck/source/components/InternodeSyncer.cpp new file mode 100644 index 0000000..7c2855b --- /dev/null +++ b/fsck/source/components/InternodeSyncer.cpp @@ -0,0 +1,491 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "InternodeSyncer.h" + +#include + +#include + +InternodeSyncer::InternodeSyncer() : + PThread("XNodeSync"), + log("XNodeSync"), + serversDownloaded(false) +{ +} + +InternodeSyncer::~InternodeSyncer() +{ +} + + +void InternodeSyncer::run() +{ + try + { + registerSignalHandler(); + + // download all nodes,mappings,states and buddy groups + NumNodeIDList addedStorageNodes; + NumNodeIDList removedStorageNodes; + NumNodeIDList addedMetaNodes; + NumNodeIDList removedMetaNodes; + bool syncRes = downloadAndSyncNodes(addedStorageNodes, removedStorageNodes, addedMetaNodes, + removedMetaNodes); + if (!syncRes) + { + log.logErr("Error downloading nodes from mgmtd."); + Program::getApp()->abort(); + return; + } + + syncRes = downloadAndSyncTargetMappings(); + if (!syncRes) + { + log.logErr("Error downloading target mappings from mgmtd."); + Program::getApp()->abort(); + return; + } + + originalTargetMap = Program::getApp()->getTargetMapper()->getMapping(); + + syncRes = downloadAndSyncTargetStates(); + if (!syncRes) + { + log.logErr("Error downloading target states from mgmtd."); + Program::getApp()->abort(); + return; + } + + syncRes = downloadAndSyncMirrorBuddyGroups(); + if ( !syncRes ) + { + log.logErr("Error downloading mirror buddy groups from mgmtd."); + Program::getApp()->abort(); + return; + } + + Program::getApp()->getMirrorBuddyGroupMapper()->getMirrorBuddyGroups( + originalMirrorBuddyGroupMap); + + syncRes = downloadAndSyncMetaMirrorBuddyGroups(); + if ( !syncRes ) + { + log.logErr("Error downloading metadata mirror buddy groups from mgmtd."); + Program::getApp()->abort(); + return; + } + + Program::getApp()->getMetaMirrorBuddyGroupMapper()->getMirrorBuddyGroups( + originalMetaMirrorBuddyGroupMap); + + { + std::lock_guard lock(serversDownloadedMutex); + serversDownloaded = true; + serversDownloadedCondition.signal(); + } + + syncLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void InternodeSyncer::syncLoop() +{ + const int sleepIntervalMS = 3*1000; // 3sec + const unsigned downloadNodesAndStatesIntervalMS = 30000; // 30 sec + const unsigned checkNetworkIntervalMS = 60*1000; // 1 minute + + Time lastDownloadNodesAndStatesT; + Time lastCheckNetworkT; + + while(!waitForSelfTerminateOrder(sleepIntervalMS) ) + { + // download & sync nodes + if (lastDownloadNodesAndStatesT.elapsedMS() > downloadNodesAndStatesIntervalMS) + { + NumNodeIDList addedStorageNodes; + NumNodeIDList removedStorageNodes; + NumNodeIDList addedMetaNodes; + NumNodeIDList removedMetaNodes; + bool syncRes = downloadAndSyncNodes(addedStorageNodes, removedStorageNodes, addedMetaNodes, + removedMetaNodes); + + if (!syncRes) + { + log.logErr("Error downloading nodes from mgmtd."); + Program::getApp()->abort(); + break; + } + + handleNodeChanges(NODETYPE_Meta, addedMetaNodes, removedMetaNodes); + + handleNodeChanges(NODETYPE_Storage, addedStorageNodes, removedStorageNodes); + + syncRes = downloadAndSyncTargetMappings(); + if (!syncRes) + { + log.logErr("Error downloading target mappings from mgmtd."); + Program::getApp()->abort(); + break; + } + + handleTargetMappingChanges(); + + syncRes = downloadAndSyncTargetStates(); + if (!syncRes) + { + log.logErr("Error downloading target states from mgmtd."); + Program::getApp()->abort(); + break; + } + + syncRes = downloadAndSyncMirrorBuddyGroups(); + if ( !syncRes ) + { + log.logErr("Error downloading mirror buddy groups from mgmtd."); + Program::getApp()->abort(); + break; + } + + handleBuddyGroupChanges(); + + lastDownloadNodesAndStatesT.setToNow(); + } + + bool checkNetworkForced = getAndResetForceCheckNetwork(); + + if( checkNetworkForced || + (lastCheckNetworkT.elapsedMS() > checkNetworkIntervalMS)) + { + checkNetwork(); + lastCheckNetworkT.setToNow(); + } + + } +} + +/** + * @return false on error. + */ +bool InternodeSyncer::downloadAndSyncTargetStates() +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + TargetStateStore* targetStateStore = app->getTargetStateStore(); + + auto node = mgmtNodes->referenceFirstNode(); + if(!node) + return false; + + UInt16List targetIDs; + UInt8List reachabilityStates; + UInt8List consistencyStates; + + bool downloadRes = NodesTk::downloadTargetStates(*node, NODETYPE_Storage, + &targetIDs, &reachabilityStates, &consistencyStates, false); + + if(downloadRes) + targetStateStore->syncStatesFromLists(targetIDs, reachabilityStates, + consistencyStates); + + return downloadRes; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncNodes(NumNodeIDList& addedStorageNodes, + NumNodeIDList& removedStorageNodes, NumNodeIDList& addedMetaNodes, + NumNodeIDList& removedMetaNodes) +{ + const char* logContext = "Nodes sync"; + + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + NodeStoreServers* metaNodes = app->getMetaNodes(); + NodeStoreServers* storageNodes = app->getStorageNodes(); + Node& localNode = app->getLocalNode(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + return false; + + { // storage nodes + std::vector storageNodesList; + + bool storageRes = + NodesTk::downloadNodes(*mgmtNode, NODETYPE_Storage, storageNodesList, false); + if(!storageRes) + return false; + + storageNodes->syncNodes(storageNodesList, &addedStorageNodes, &removedStorageNodes, + &localNode); + printSyncNodesResults(NODETYPE_Storage, &addedStorageNodes, &removedStorageNodes); + } + + { // metadata nodes + std::vector metaNodesList; + NumNodeID rootNodeID; + bool rootIsBuddyMirrored; + + bool metaRes = + NodesTk::downloadNodes(*mgmtNode, NODETYPE_Meta, metaNodesList, false, &rootNodeID, + &rootIsBuddyMirrored); + if(!metaRes) + return false; + + metaNodes->syncNodes(metaNodesList, &addedMetaNodes, &removedMetaNodes, &localNode); + + if (app->getMetaRoot().setIfDefault(rootNodeID, rootIsBuddyMirrored)) + { + LogContext(logContext).log(Log_CRITICAL, + "Root NodeID (from sync results): " + rootNodeID.str() ); + } + + printSyncNodesResults(NODETYPE_Meta, &addedMetaNodes, &removedMetaNodes); + } + + return true; +} + +void InternodeSyncer::printSyncNodesResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes) +{ + const char* logContext = "Sync results"; + + if (!addedNodes->empty()) + LogContext(logContext).log(Log_WARNING, std::string("Nodes added: ") + + StringTk::uintToStr(addedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); + + if (!removedNodes->empty()) + LogContext(logContext).log(Log_WARNING, std::string("Nodes removed: ") + + StringTk::uintToStr(removedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncTargetMappings() +{ + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + TargetMapper* targetMapper = app->getTargetMapper(); + + bool retVal = true; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + auto mappings = NodesTk::downloadTargetMappings(*mgmtNode, false); + if (mappings.first) + targetMapper->syncTargets(std::move(mappings.second)); + else + retVal = false; + + return retVal; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncMirrorBuddyGroups() +{ + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + bool retVal = true; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + UInt16List buddyGroupIDs; + UInt16List primaryTargetIDs; + UInt16List secondaryTargetIDs; + + bool downloadRes = NodesTk::downloadMirrorBuddyGroups(*mgmtNode, NODETYPE_Storage, + &buddyGroupIDs, &primaryTargetIDs, &secondaryTargetIDs, false); + + if(downloadRes) + buddyGroupMapper->syncGroupsFromLists(buddyGroupIDs, primaryTargetIDs, secondaryTargetIDs, + NumNodeID() ); + else + retVal = false; + + return retVal; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncMetaMirrorBuddyGroups() +{ + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMetaMirrorBuddyGroupMapper(); + + bool retVal = true; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + UInt16List buddyGroupIDs; + UInt16List primaryTargetIDs; + UInt16List secondaryTargetIDs; + + bool downloadRes = NodesTk::downloadMirrorBuddyGroups(*mgmtNode, NODETYPE_Meta, &buddyGroupIDs, + &primaryTargetIDs, &secondaryTargetIDs, false); + + if(downloadRes) + buddyGroupMapper->syncGroupsFromLists(buddyGroupIDs, primaryTargetIDs, secondaryTargetIDs, + NumNodeID() ); + else + retVal = false; + + return retVal; +} + +void InternodeSyncer::handleNodeChanges(NodeType nodeType, NumNodeIDList& addedNodes, + NumNodeIDList& removedNodes) +{ + const char* logContext = "handleNodeChanges"; + + if (!addedNodes.empty()) + LogContext(logContext).log(Log_WARNING, + std::string("Nodes added while beegfs-fsck was running: ") + + StringTk::uintToStr(addedNodes.size()) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); + + if (!removedNodes.empty()) + { + // removed nodes must lead to fsck stoppage + LogContext(logContext).log(Log_WARNING, + std::string("Nodes removed while beegfs-fsck was running: ") + + StringTk::uintToStr(removedNodes.size()) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); + + Program::getApp()->abort(); + } +} + +void InternodeSyncer::handleTargetMappingChanges() +{ + const char* logContext = "handleTargetMappingChanges"; + + TargetMap newTargetMap = Program::getApp()->getTargetMapper()->getMapping(); + + for ( TargetMapIter originalMapIter = originalTargetMap.begin(); + originalMapIter != originalTargetMap.end(); originalMapIter++ ) + { + uint16_t targetID = originalMapIter->first; + NumNodeID oldNodeID = originalMapIter->second; + + TargetMapIter newMapIter = newTargetMap.find(targetID); + + if ( newMapIter == newTargetMap.end() ) + { + LogContext(logContext).log(Log_WARNING, + "Target removed while beegfs-fsck was running; beegfs-fsck can't continue; targetID: " + + StringTk::uintToStr(targetID)); + + Program::getApp()->abort(); + } + else + { + NumNodeID newNodeID = newMapIter->second; + if ( oldNodeID != newNodeID ) + { + LogContext(logContext).log(Log_WARNING, + "Target re-mapped while beegfs-fsck was running; beegfs-fsck can't continue; " + "targetID: " + StringTk::uintToStr(targetID)); + + Program::getApp()->abort(); + } + } + } +} + +void InternodeSyncer::handleBuddyGroupChanges() +{ + const char* logContext = "handleBuddyGroupChanges"; + + MirrorBuddyGroupMap newMirrorBuddyGroupMap; + Program::getApp()->getMirrorBuddyGroupMapper()->getMirrorBuddyGroups(newMirrorBuddyGroupMap); + + for ( MirrorBuddyGroupMapIter originalMapIter = originalMirrorBuddyGroupMap.begin(); + originalMapIter != originalMirrorBuddyGroupMap.end(); originalMapIter++ ) + { + uint16_t buddyGroupID = originalMapIter->first; + MirrorBuddyGroup oldBuddyGroup = originalMapIter->second; + + MirrorBuddyGroupMapIter newMapIter = newMirrorBuddyGroupMap.find(buddyGroupID); + + if ( newMapIter == newMirrorBuddyGroupMap.end() ) + { + LogContext(logContext).log(Log_WARNING, + "Mirror buddy group removed while beegfs-fsck was running; beegfs-fsck can't continue; " + "groupID: " + StringTk::uintToStr(buddyGroupID)); + + Program::getApp()->abort(); + } + else + { + MirrorBuddyGroup newBuddyGroup = newMapIter->second; + if ( oldBuddyGroup.firstTargetID != newBuddyGroup.firstTargetID ) + { + LogContext(logContext).log(Log_WARNING, + "Primary of mirror buddy group changed while beegfs-fsck was running; beegfs-fsck " + "can't continue; groupID: " + StringTk::uintToStr(buddyGroupID)); + + Program::getApp()->abort(); + } + } + } +} + +/** + * Blocks until list of servers has been downloaded from management node + */ +void InternodeSyncer::waitForServers() +{ + std::lock_guard lock(serversDownloadedMutex); + while (!serversDownloaded) + serversDownloadedCondition.wait(&serversDownloadedMutex); +} + +/** + * Inspect the available and allowed network interfaces for any changes. + */ +bool InternodeSyncer::checkNetwork() +{ + App* app = Program::getApp(); + NicAddressList newLocalNicList; + bool res = false; + + app->findAllowedInterfaces(newLocalNicList); + app->findAllowedRDMAInterfaces(newLocalNicList); + if (!std::equal(newLocalNicList.begin(), newLocalNicList.end(), app->getLocalNicList().begin())) + { + log.log(Log_NOTICE, "checkNetwork: local interfaces have changed"); + app->updateLocalNicList(newLocalNicList); + res = true; + } + + return res; +} diff --git a/fsck/source/components/InternodeSyncer.h b/fsck/source/components/InternodeSyncer.h new file mode 100644 index 0000000..2ed8e7e --- /dev/null +++ b/fsck/source/components/InternodeSyncer.h @@ -0,0 +1,95 @@ +#ifndef INTERNODESYNCER_H_ +#define INTERNODESYNCER_H_ + +#include +#include +#include +#include +#include + +#include + +class InternodeSyncer : public PThread +{ + public: + InternodeSyncer(); + virtual ~InternodeSyncer(); + + bool downloadAndSyncNodes(NumNodeIDList& addedStorageNodes, + NumNodeIDList& removedStorageNodes, NumNodeIDList& addedMetaNodes, + NumNodeIDList& removedMetaNodes); + bool downloadAndSyncTargetMappings(); + bool downloadAndSyncMirrorBuddyGroups(); + bool downloadAndSyncMetaMirrorBuddyGroups(); + bool downloadAndSyncTargetStates(); + + private: + LogContext log; + Mutex forceNodesAndTargetStatesUpdateMutex; + bool forceNodesAndTargetStatesUpdate; + Mutex forceCheckNetworkMutex; + bool forceCheckNetwork; // true to force check of network interfaces + + TargetMap originalTargetMap; + MirrorBuddyGroupMap originalMirrorBuddyGroupMap; + MirrorBuddyGroupMap originalMetaMirrorBuddyGroupMap; + + virtual void run(); + void syncLoop(); + void handleNodeChanges(NodeType nodeType, NumNodeIDList& addedNodes, + NumNodeIDList& removedNodes); + void handleTargetMappingChanges(); + void handleBuddyGroupChanges(); + + bool getAndResetForceCheckNetwork() + { + const std::lock_guard lock(forceCheckNetworkMutex); + + bool retVal = this->forceCheckNetwork; + + this->forceCheckNetwork = false; + + return retVal; + } + + bool checkNetwork(); + + Condition serversDownloadedCondition; + Mutex serversDownloadedMutex; + bool serversDownloaded; + + public: + void waitForServers(); + + void setForceCheckNetwork() + { + const std::lock_guard lock(forceCheckNetworkMutex); + + this->forceCheckNetwork = true; + } + + private: + static void printSyncNodesResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes); + + void setForceNodesAndTargetStatesUpdate() + { + const std::lock_guard lock(forceNodesAndTargetStatesUpdateMutex); + + this->forceNodesAndTargetStatesUpdate = true; + } + + bool getAndResetForceNodesAndTargetStatesUpdate() + { + const std::lock_guard lock(forceNodesAndTargetStatesUpdateMutex); + + bool retVal = this->forceNodesAndTargetStatesUpdate; + + this->forceNodesAndTargetStatesUpdate = false; + + return retVal; + } +}; + + +#endif /* INTERNODESYNCER_H_ */ diff --git a/fsck/source/components/ModificationEventHandler.cpp b/fsck/source/components/ModificationEventHandler.cpp new file mode 100644 index 0000000..4bda819 --- /dev/null +++ b/fsck/source/components/ModificationEventHandler.cpp @@ -0,0 +1,98 @@ +#include "ModificationEventHandler.h" + +#include +#include + +#include + +#include + +ModificationEventHandler::ModificationEventHandler(FsckDBModificationEventsTable& table) + : PThread("ModificationEventHandler"), + table(&table) +{ +} + +void ModificationEventHandler::run() +{ + FsckDBModificationEventsTable::BulkHandle bulkHandle(table->newBulkHandle() ); + while ( !getSelfTerminate() ) + { + std::unique_lock bufferListSafeLock(bufferListMutex); // LOCK BUFFER + + // make sure to group at least MODHANDLER_MINSIZE_FLUSH flush elements (to not bother the DB + // with every single event) + if (bufferList.size() < MODHANDLER_MINSIZE_FLUSH) + { + bufferListSafeLock.unlock(); // UNLOCK BUFFER + const std::lock_guard lock(eventsAddedMutex); + eventsAddedCond.timedwait(&eventsAddedMutex, 2000); + continue; + } + else + { + // create a copy of the buffer list and flush this to DB, so that the buffer will become + // free immediately and the incoming messages do not have to wait for DB + FsckModificationEventList bufferListCopy; + + bufferListCopy.splice(bufferListCopy.begin(), bufferList); + + bufferListSafeLock.unlock(); // UNLOCK BUFFER + + table->insert(bufferListCopy, bulkHandle); + } + } + + // a last flush after component stopped + FsckModificationEventList bufferListCopy; + + { + const std::lock_guard bufferListLock(bufferListMutex); + bufferListCopy.splice(bufferListCopy.begin(), bufferList); + } + + table->insert(bufferListCopy, bulkHandle); +} + +bool ModificationEventHandler::add(UInt8List& eventTypeList, StringList& entryIDList) +{ + const char* logContext = "ModificationEventHandler (add)"; + + if ( unlikely(eventTypeList.size() != entryIDList.size()) ) + { + LogContext(logContext).logErr("Unable to add events. The lists do not have equal sizes."); + return false; + } + + while ( true ) + { + { + const std::lock_guard lock(bufferListMutex); + if (this->bufferList.size() < MODHANDLER_MAXSIZE_EVENTLIST) + { + break; + } + } + { + const std::lock_guard lock(eventsFlushedMutex); + this->eventsFlushedCond.timedwait(&eventsFlushedMutex, 2000); + } + } + + ZipIterRange eventTypeEntryIDIter(eventTypeList, entryIDList); + + { + const std::lock_guard bufferListLock(bufferListMutex); + + for ( ; !eventTypeEntryIDIter.empty(); ++eventTypeEntryIDIter) + { + FsckModificationEvent event((ModificationEventType)*(eventTypeEntryIDIter()->first), + *(eventTypeEntryIDIter()->second) ); + this->bufferList.push_back(event); + } + } + + this->eventsAddedCond.signal(); + + return true; +} diff --git a/fsck/source/components/ModificationEventHandler.h b/fsck/source/components/ModificationEventHandler.h new file mode 100644 index 0000000..8452d12 --- /dev/null +++ b/fsck/source/components/ModificationEventHandler.h @@ -0,0 +1,44 @@ +#ifndef MODIFICATIONEVENTHANDLER_H +#define MODIFICATIONEVENTHANDLER_H + +#include +#include +#include + + +#define MODHANDLER_MAXSIZE_EVENTLIST 50000 +#define MODHANDLER_MINSIZE_FLUSH 200 + +class ModificationEventHandler: public PThread +{ + public: + ModificationEventHandler(FsckDBModificationEventsTable& table); + + virtual void run(); + + bool add(UInt8List& eventTypeList, StringList& entryIDList); + + private: + FsckDBModificationEventsTable* table; + + FsckModificationEventList bufferList; + + Mutex bufferListMutex; + Mutex bufferListCopyMutex; + Mutex flushMutex; + + Mutex eventsAddedMutex; + Condition eventsAddedCond; + Mutex eventsFlushedMutex; + Condition eventsFlushedCond; + + public: + void stop() + { + selfTerminate(); + eventsAddedCond.signal(); + join(); + } +}; + +#endif /* MODIFICATIONEVENTHANDLER_H */ diff --git a/fsck/source/components/worker/AdjustChunkPermissionsWork.cpp b/fsck/source/components/worker/AdjustChunkPermissionsWork.cpp new file mode 100644 index 0000000..151d2e2 --- /dev/null +++ b/fsck/source/components/worker/AdjustChunkPermissionsWork.cpp @@ -0,0 +1,97 @@ +#include "AdjustChunkPermissionsWork.h" +#include +#include +#include +#include +#include + +AdjustChunkPermissionsWork::AdjustChunkPermissionsWork(Node& node, SynchronizedCounter* counter, + AtomicUInt64* fileCount, AtomicUInt64* errorCount) + : log("AdjustChunkPermissionsWork"), + node(node), + counter(counter), + fileCount(fileCount), + errorCount(errorCount) +{ +} + +AdjustChunkPermissionsWork::~AdjustChunkPermissionsWork() +{ +} + +void AdjustChunkPermissionsWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + log.log(4, "Processing AdjustChunkPermissionsWork"); + + try + { + doWork(false); + doWork(true); + // work package finished => increment counter + this->counter->incCount(); + } + catch (std::exception &e) + { + // exception thrown, but work package is finished => increment counter + this->counter->incCount(); + + // after incrementing counter, re-throw exception + throw; + } + + log.log(4, "Processed AdjustChunkPermissionsWork"); +} + +void AdjustChunkPermissionsWork::doWork(bool isBuddyMirrored) +{ + for ( unsigned firstLevelhashDirNum = 0; + firstLevelhashDirNum <= META_DENTRIES_LEVEL1_SUBDIR_NUM - 1; firstLevelhashDirNum++ ) + { + for ( unsigned secondLevelhashDirNum = 0; + secondLevelhashDirNum < META_DENTRIES_LEVEL2_SUBDIR_NUM; secondLevelhashDirNum++ ) + { + unsigned hashDirNum = StorageTk::mergeHashDirs(firstLevelhashDirNum, + secondLevelhashDirNum); + + int64_t hashDirOffset = 0; + int64_t contDirOffset = 0; + std::string currentContDirID; + unsigned resultCount = 0; + + do + { + AdjustChunkPermissionsMsg adjustChunkPermissionsMsg(hashDirNum, currentContDirID, + ADJUST_AT_ONCE, hashDirOffset, contDirOffset, isBuddyMirrored); + + const auto respMsg = MessagingTk::requestResponse(node, adjustChunkPermissionsMsg, + NETMSGTYPE_AdjustChunkPermissionsResp); + + if (respMsg) + { + auto* adjustChunkPermissionsRespMsg = (AdjustChunkPermissionsRespMsg*) respMsg.get(); + + // set new parameters + currentContDirID = adjustChunkPermissionsRespMsg->getCurrentContDirID(); + hashDirOffset = adjustChunkPermissionsRespMsg->getNewHashDirOffset(); + contDirOffset = adjustChunkPermissionsRespMsg->getNewContDirOffset(); + resultCount = adjustChunkPermissionsRespMsg->getCount(); + + this->fileCount->increase(resultCount); + + if (adjustChunkPermissionsRespMsg->getErrorCount() > 0) + this->errorCount->increase(adjustChunkPermissionsRespMsg->getErrorCount()); + } + else + { + throw FsckException("Communication error occured with node " + node.getAlias()); + } + + // if any of the worker threads threw an exception, we should stop now! + if ( Program::getApp()->getSelfTerminate() ) + return; + + } while ( resultCount > 0 ); + } + } +} diff --git a/fsck/source/components/worker/AdjustChunkPermissionsWork.h b/fsck/source/components/worker/AdjustChunkPermissionsWork.h new file mode 100644 index 0000000..6b9ca57 --- /dev/null +++ b/fsck/source/components/worker/AdjustChunkPermissionsWork.h @@ -0,0 +1,32 @@ +#ifndef ADJUSTCHUNKPERMISSIONSWORK_H +#define ADJUSTCHUNKPERMISSIONSWORK_H + +#include +#include +#include + +#include + +// the size of one packet, i.e. how many files are adjusted at once; basically just +// limited to have some control and give the user feedback +#define ADJUST_AT_ONCE 50 + +class AdjustChunkPermissionsWork : public Work +{ + public: + AdjustChunkPermissionsWork(Node& node, SynchronizedCounter* counter, AtomicUInt64* fileCount, + AtomicUInt64* errorCount); + virtual ~AdjustChunkPermissionsWork(); + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + LogContext log; + Node& node; + SynchronizedCounter* counter; + AtomicUInt64* fileCount; + AtomicUInt64* errorCount; + + void doWork(bool isBuddyMirrored); +}; + +#endif /* ADJUSTCHUNKPERMISSIONSWORK_H */ diff --git a/fsck/source/components/worker/RetrieveChunksWork.cpp b/fsck/source/components/worker/RetrieveChunksWork.cpp new file mode 100644 index 0000000..fdfb3be --- /dev/null +++ b/fsck/source/components/worker/RetrieveChunksWork.cpp @@ -0,0 +1,152 @@ +#include "RetrieveChunksWork.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +RetrieveChunksWork::RetrieveChunksWork(FsckDB* db, NodeHandle node, SynchronizedCounter* counter, + AtomicUInt64* numChunksFound, bool forceRestart) : + log("RetrieveChunksWork"), node(std::move(node)), counter(counter), + numChunksFound(numChunksFound), + chunks(db->getChunksTable()), chunksHandle(chunks->newBulkHandle()), + malformedChunks(db->getMalformedChunksList()), + forceRestart(forceRestart), + started(false), startedBarrier(2) +{ +} + +RetrieveChunksWork::~RetrieveChunksWork() +{ +} + +void RetrieveChunksWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + log.log(Log_DEBUG, "Processing RetrieveChunksWork"); + + try + { + doWork(); + // flush buffers before signaling completion + chunks->flush(chunksHandle); + // work package finished => increment counter + this->counter->incCount(); + + } + catch (std::exception& e) + { + // exception thrown, but work package is finished => increment counter + this->counter->incCount(); + + // after incrementing counter, re-throw exception + throw; + } + + log.log(Log_DEBUG, "Processed RetrieveChunksWork"); +} + +void RetrieveChunksWork::doWork() +{ + // take the node associated with the current target and send a RetrieveChunksMsg to + // that node; the chunks are retrieved incrementally + if ( node ) + { + std::string nodeID = node->getAlias(); + FetchFsckChunkListStatus status = FetchFsckChunkListStatus_NOTSTARTED; + unsigned resultCount = 0; + + do + { + FetchFsckChunkListMsg fetchFsckChunkListMsg(RETRIEVE_CHUNKS_PACKET_SIZE, status, + forceRestart); + + const auto respMsg = MessagingTk::requestResponse(*node, fetchFsckChunkListMsg, + NETMSGTYPE_FetchFsckChunkListResp); + + if (respMsg) + { + auto* fetchFsckChunkListRespMsg = (FetchFsckChunkListRespMsg*) respMsg.get(); + + FsckChunkList& chunks = fetchFsckChunkListRespMsg->getChunkList(); + resultCount = chunks.size(); + + status = fetchFsckChunkListRespMsg->getStatus(); + + // check entry IDs + for (auto it = chunks.begin(); it != chunks.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first + && it->getSavedPath()->str().size() <= db::Chunk::SAVED_PATH_SIZE) + { + ++it; + continue; + } + + ++it; + malformedChunks->append(*std::prev(it)); + chunks.erase(std::prev(it)); + } + + if (status == FetchFsckChunkListStatus_NOTSTARTED) + { + // Another fsck run is still in progress or was aborted, and --forceRestart was not + // set - this means we can't start a new chunk fetcher. + started = false; + startedBarrier.wait(); + startedBarrier.wait(); + return; + } + else if (status == FetchFsckChunkListStatus_READERROR) + { + throw FsckException("Read error occured while fetching chunks from node; nodeID: " + + nodeID); + } + + if (!started) + { + started = true; + startedBarrier.wait(); + startedBarrier.wait(); + } + + this->chunks->insert(chunks, this->chunksHandle); + + numChunksFound->increase(resultCount); + } + else + { + throw FsckException("Communication error occured with node; nodeID: " + nodeID); + } + + if ( Program::getApp()->getShallAbort() ) + break; + + } while ( (resultCount > 0) || (status == FetchFsckChunkListStatus_RUNNING) ); + } + else + { + // basically this should never ever happen + log.logErr("Requested node does not exist"); + throw FsckException("Requested node does not exist"); + } +} + +/** + * Waits until started conditin is is signalled and returns the value of stared + * @param isStarted ptr to boolean which is set to whether the server replied it actually started + * the process. Note: this is a ptr and not a return to ensure the member variable + * started is no longer accessed after doWork is finished and the object possibly + * already deleted. + */ +void RetrieveChunksWork::waitForStarted(bool* isStarted) +{ + startedBarrier.wait(); + *isStarted = started; + startedBarrier.wait(); +} diff --git a/fsck/source/components/worker/RetrieveChunksWork.h b/fsck/source/components/worker/RetrieveChunksWork.h new file mode 100644 index 0000000..47b2dd6 --- /dev/null +++ b/fsck/source/components/worker/RetrieveChunksWork.h @@ -0,0 +1,57 @@ +#ifndef RETRIEVECHUNKSWORK_H +#define RETRIEVECHUNKSWORK_H + +/* + * retrieve all chunks from one storage server and save them to DB + */ + +#include +#include +#include +#include +#include +#include + +// the size of one response packet, i.e. how many chunks are asked for at once +#define RETRIEVE_CHUNKS_PACKET_SIZE 400 + +class RetrieveChunksWork : public Work +{ + public: + /* + * @param db database instance + * @param node pointer to the node to retrieve data from + * @param counter a pointer to a Synchronized counter; this is incremented by one at the end + * and the calling thread can wait for the counter + * @param numChunksFound + * @param forceRestart In case the storage servers' chunk fetchers still have data from a + * previous run, force a restart instead of aborting with an error + */ + RetrieveChunksWork(FsckDB* db, NodeHandle node, SynchronizedCounter* counter, + AtomicUInt64* numChunksFound, bool forceRestart); + virtual ~RetrieveChunksWork(); + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + void waitForStarted(bool* isStarted); + + + private: + LogContext log; + NodeHandle node; + SynchronizedCounter* counter; + AtomicUInt64* numChunksFound; + + FsckDBChunksTable* chunks; + FsckDBChunksTable::BulkHandle chunksHandle; + + DiskList* malformedChunks; + + bool forceRestart; + + void doWork(); + + bool started; + Barrier startedBarrier; +}; + +#endif /* RETRIEVECHUNKSWORK_H */ diff --git a/fsck/source/components/worker/RetrieveDirEntriesWork.cpp b/fsck/source/components/worker/RetrieveDirEntriesWork.cpp new file mode 100644 index 0000000..c64b908 --- /dev/null +++ b/fsck/source/components/worker/RetrieveDirEntriesWork.cpp @@ -0,0 +1,222 @@ +#include "RetrieveDirEntriesWork.h" +#include +#include +#include +#include +#include +#include + +#include + +#include + +RetrieveDirEntriesWork::RetrieveDirEntriesWork(FsckDB* db, Node& node, SynchronizedCounter* counter, + AtomicUInt64& errors, unsigned hashDirStart, unsigned hashDirEnd, + AtomicUInt64* numDentriesFound, AtomicUInt64* numFileInodesFound, + std::set& usedTargets) : + log("RetrieveDirEntriesWork"), node(node), counter(counter), errors(&errors), + numDentriesFound(numDentriesFound), numFileInodesFound(numFileInodesFound), + usedTargets(&usedTargets), hashDirStart(hashDirStart), hashDirEnd(hashDirEnd), + dentries(db->getDentryTable()), dentriesHandle(dentries->newBulkHandle()), + files(db->getFileInodesTable()), filesHandle(files->newBulkHandle()), + contDirs(db->getContDirsTable()), contDirsHandle(contDirs->newBulkHandle()) +{ +} + +void RetrieveDirEntriesWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + log.log(4, "Processing RetrieveDirEntriesWork"); + + try + { + doWork(false); + doWork(true); + // flush buffers before signaling completion + dentries->flush(dentriesHandle); + files->flush(filesHandle); + contDirs->flush(contDirsHandle); + // work package finished => increment counter + this->counter->incCount(); + } + catch (std::exception &e) + { + // exception thrown, but work package is finished => increment counter + this->counter->incCount(); + + // after incrementing counter, re-throw exception + throw; + } + + log.log(4, "Processed RetrieveDirEntriesWork"); +} + +void RetrieveDirEntriesWork::doWork(bool isBuddyMirrored) +{ + for ( unsigned firstLevelhashDirNum = hashDirStart; firstLevelhashDirNum <= hashDirEnd; + firstLevelhashDirNum++ ) + { + for ( unsigned secondLevelhashDirNum = 0; + secondLevelhashDirNum < META_DENTRIES_LEVEL2_SUBDIR_NUM; secondLevelhashDirNum++ ) + { + unsigned hashDirNum = StorageTk::mergeHashDirs(firstLevelhashDirNum, + secondLevelhashDirNum); + + int64_t hashDirOffset = 0; + int64_t contDirOffset = 0; + std::string currentContDirID; + int resultCount = 0; + + do + { + RetrieveDirEntriesMsg retrieveDirEntriesMsg(hashDirNum, currentContDirID, + RETRIEVE_DIR_ENTRIES_PACKET_SIZE, hashDirOffset, contDirOffset, isBuddyMirrored); + + const auto respMsg = MessagingTk::requestResponse(node, retrieveDirEntriesMsg, + NETMSGTYPE_RetrieveDirEntriesResp); + + if (respMsg) + { + auto* retrieveDirEntriesRespMsg = (RetrieveDirEntriesRespMsg*) respMsg.get(); + + // set new parameters + currentContDirID = retrieveDirEntriesRespMsg->getCurrentContDirID(); + hashDirOffset = retrieveDirEntriesRespMsg->getNewHashDirOffset(); + contDirOffset = retrieveDirEntriesRespMsg->getNewContDirOffset(); + + // parse directory entries + FsckDirEntryList& dirEntries = retrieveDirEntriesRespMsg->getDirEntries(); + // this is the actual result count we are interested in, because if no dirEntries + // were read, there is nothing left on the server + + resultCount = dirEntries.size(); + + // check dentry entry IDs + for (auto it = dirEntries.begin(); it != dirEntries.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first + && db::EntryID::tryFromStr(it->getParentDirID()).first) + { + ++it; + continue; + } + + LOG(GENERAL, ERR, "Found dentry with invalid entry IDs.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID()), + ("parentEntryID", it->getParentDirID())); + + ++it; + errors->increase(); + dirEntries.erase(std::prev(it)); + } + + this->dentries->insert(dirEntries, this->dentriesHandle); + + numDentriesFound->increase(resultCount); + + // parse inlined file inodes + FsckFileInodeList& inlinedFileInodes = + retrieveDirEntriesRespMsg->getInlinedFileInodes(); + + // check inode entry IDs + for (auto it = inlinedFileInodes.begin(); it != inlinedFileInodes.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first + && db::EntryID::tryFromStr(it->getParentDirID()).first + && (!it->getPathInfo()->hasOrigFeature() + || db::EntryID::tryFromStr( + it->getPathInfo()->getOrigParentEntryID()).first)) + { + ++it; + continue; + } + + LOG(GENERAL, ERR, "Found inode with invalid entry IDs.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID()), + ("parentEntryID", it->getParentDirID()), + ("origParent", it->getPathInfo()->getOrigParentEntryID())); + + ++it; + errors->increase(); + inlinedFileInodes.erase(std::prev(it)); + } + + struct ops + { + static bool dentryCmp(const FsckDirEntry& a, const FsckDirEntry& b) + { + return a.getID() < b.getID(); + } + + static bool inodeCmp(const FsckFileInode& a, const FsckFileInode& b) + { + return a.getID() < b.getID(); + } + }; + + dirEntries.sort(ops::dentryCmp); + inlinedFileInodes.sort(ops::inodeCmp); + + this->files->insert(inlinedFileInodes, this->filesHandle); + + numFileInodesFound->increase(inlinedFileInodes.size()); + + // add used targetIDs + for ( FsckFileInodeListIter iter = inlinedFileInodes.begin(); + iter != inlinedFileInodes.end(); iter++ ) + { + FsckTargetIDType fsckTargetIDType; + + if (iter->getStripePatternType() == FsckStripePatternType_BUDDYMIRROR) + fsckTargetIDType = FsckTargetIDType_BUDDYGROUP; + else + fsckTargetIDType = FsckTargetIDType_TARGET; + + for (auto targetsIter = iter->getStripeTargets().begin(); + targetsIter != iter->getStripeTargets().end(); targetsIter++) + { + this->usedTargets->insert(FsckTargetID(*targetsIter, fsckTargetIDType) ); + } + } + + // parse all new cont. directories + FsckContDirList& contDirs = retrieveDirEntriesRespMsg->getContDirs(); + + // check entry IDs + for (auto it = contDirs.begin(); it != contDirs.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first) + { + ++it; + continue; + } + + LOG(GENERAL, ERR, "Found content directory with invalid entry ID.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID())); + + ++it; + errors->increase(); + contDirs.erase(std::prev(it)); + } + + this->contDirs->insert(contDirs, this->contDirsHandle); + } + else + { + throw FsckException("Communication error occured with node " + node.getAlias()); + } + + // if any of the worker threads threw an exception, we should stop now! + if ( Program::getApp()->getShallAbort() ) + return; + + } while ( resultCount > 0 ); + } + } +} diff --git a/fsck/source/components/worker/RetrieveDirEntriesWork.h b/fsck/source/components/worker/RetrieveDirEntriesWork.h new file mode 100644 index 0000000..3e58d0a --- /dev/null +++ b/fsck/source/components/worker/RetrieveDirEntriesWork.h @@ -0,0 +1,62 @@ +#ifndef RETRIEVEDIRENTRIESWORK_H +#define RETRIEVEDIRENTRIESWORK_H + +/* + * retrieve all dir entries from one node, inside a specified range of hashDirs and save them to DB + */ + +#include +#include +#include + +#include +#include + +// the size of one response packet, i.e. how many dentries are asked for at once +#define RETRIEVE_DIR_ENTRIES_PACKET_SIZE 500 + +class RetrieveDirEntriesWork : public Work +{ + public: + /* + * @param db database instance + * @param node the node to retrieve data from + * @param counter a pointer to a Synchronized counter; this is incremented by one at the end + * and the calling thread can wait for the counter + * @param hashDirStart the first top-level hashDir to open + * @param hashDirEnd the last top-level hashDir to open + * @param numDentriesFound + * @param numFileInodesFound + */ + RetrieveDirEntriesWork(FsckDB* db, Node& node, SynchronizedCounter* counter, + AtomicUInt64& errors, unsigned hashDirStart, unsigned hashDirEnd, + AtomicUInt64* numDentriesFound, AtomicUInt64* numFileInodesFound, + std::set& usedTargets); + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + LogContext log; + Node& node; + SynchronizedCounter* counter; + AtomicUInt64* errors; + AtomicUInt64* numDentriesFound; + AtomicUInt64* numFileInodesFound; + std::set* usedTargets; + + unsigned hashDirStart; + unsigned hashDirEnd; + + FsckDBDentryTable* dentries; + FsckDBDentryTable::BulkHandle dentriesHandle; + + FsckDBFileInodesTable* files; + FsckDBFileInodesTable::BulkHandle filesHandle; + + FsckDBContDirsTable* contDirs; + FsckDBContDirsTable::BulkHandle contDirsHandle; + + void doWork(bool isBuddyMirrored); +}; + +#endif /* RETRIEVEDIRENTRIESWORK_H */ diff --git a/fsck/source/components/worker/RetrieveFsIDsWork.cpp b/fsck/source/components/worker/RetrieveFsIDsWork.cpp new file mode 100644 index 0000000..9647c31 --- /dev/null +++ b/fsck/source/components/worker/RetrieveFsIDsWork.cpp @@ -0,0 +1,125 @@ +#include "RetrieveFsIDsWork.h" +#include +#include +#include +#include +#include +#include + +#include + + +RetrieveFsIDsWork::RetrieveFsIDsWork(FsckDB* db, Node& node, SynchronizedCounter* counter, + AtomicUInt64& errors, unsigned hashDirStart, unsigned hashDirEnd) : + log("RetrieveFsIDsWork"), node(node), counter(counter), errors(&errors), + hashDirStart(hashDirStart), hashDirEnd(hashDirEnd), + table(db->getFsIDsTable()), bulkHandle(table->newBulkHandle()) +{ +} + +RetrieveFsIDsWork::~RetrieveFsIDsWork() +{ +} + +void RetrieveFsIDsWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + log.log(4, "Processing RetrieveFsIDsWork"); + + try + { + doWork(false); + doWork(true); + table->flush(bulkHandle); + // work package finished => increment counter + this->counter->incCount(); + + } + catch (std::exception &e) + { + // exception thrown, but work package is finished => increment counter + this->counter->incCount(); + + // after incrementing counter, re-throw exception + throw; + } + + log.log(4, "Processed RetrieveFsIDsWork"); +} + +void RetrieveFsIDsWork::doWork(bool isBuddyMirrored) +{ + for ( unsigned firstLevelhashDirNum = hashDirStart; firstLevelhashDirNum <= hashDirEnd; + firstLevelhashDirNum++ ) + { + for ( unsigned secondLevelhashDirNum = 0; + secondLevelhashDirNum < META_DENTRIES_LEVEL2_SUBDIR_NUM; secondLevelhashDirNum++ ) + { + unsigned hashDirNum = StorageTk::mergeHashDirs(firstLevelhashDirNum, + secondLevelhashDirNum); + + int64_t hashDirOffset = 0; + int64_t contDirOffset = 0; + std::string currentContDirID; + int resultCount = 0; + + do + { + RetrieveFsIDsMsg retrieveFsIDsMsg(hashDirNum, isBuddyMirrored, currentContDirID, + RETRIEVE_FSIDS_PACKET_SIZE, hashDirOffset, contDirOffset); + + const auto respMsg = MessagingTk::requestResponse(node, retrieveFsIDsMsg, + NETMSGTYPE_RetrieveFsIDsResp); + + if (respMsg) + { + auto* retrieveFsIDsRespMsg = (RetrieveFsIDsRespMsg*) respMsg.get(); + + // set new parameters + currentContDirID = retrieveFsIDsRespMsg->getCurrentContDirID(); + hashDirOffset = retrieveFsIDsRespMsg->getNewHashDirOffset(); + contDirOffset = retrieveFsIDsRespMsg->getNewContDirOffset(); + + // parse FS-IDs + FsckFsIDList& fsIDs = retrieveFsIDsRespMsg->getFsIDs(); + + // this is the actual result count we are interested in, because if no fsIDs + // were read, there is nothing left on the server + resultCount = fsIDs.size(); + + // check entry IDs + for (auto it = fsIDs.begin(); it != fsIDs.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first + && db::EntryID::tryFromStr(it->getParentDirID()).first) + { + ++it; + continue; + } + + LOG(GENERAL, ERR, "Found fsid file with invalid entry IDs.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID()), + ("parentEntryID", it->getParentDirID())); + + ++it; + errors->increase(); + fsIDs.erase(std::prev(it)); + } + + this->table->insert(fsIDs, this->bulkHandle); + } + else + { + throw FsckException("Communication error occured with node " + node.getAlias()); + } + + // if any of the worker threads threw an exception, we should stop now! + if ( Program::getApp()->getShallAbort() ) + return; + + } while ( resultCount > 0 ); + } + } +} diff --git a/fsck/source/components/worker/RetrieveFsIDsWork.h b/fsck/source/components/worker/RetrieveFsIDsWork.h new file mode 100644 index 0000000..ad06ae6 --- /dev/null +++ b/fsck/source/components/worker/RetrieveFsIDsWork.h @@ -0,0 +1,49 @@ +#ifndef RETRIEVEFSIDSWORK_H +#define RETRIEVEFSIDSWORK_H + +/* + * retrieve all FS-IDs from one node, inside a specified range of hashDirs and save them to DB + */ + +#include +#include +#include + +#include +#include + +// the size of one response packet, i.e. how many fsids are asked for at once +#define RETRIEVE_FSIDS_PACKET_SIZE 1000 + +class RetrieveFsIDsWork : public Work +{ + public: + /* + * @param db database instance + * @param node the node to retrieve data from + * @param counter a pointer to a Synchronized counter; this is incremented by one at the end + * and the calling thread can wait for the counter + * @param hashDirStart the first top-level hashDir to open + * @param hashDirEnd the last top-level hashDir to open + */ + RetrieveFsIDsWork(FsckDB* db, Node& node, SynchronizedCounter* counter, AtomicUInt64& errors, + unsigned hashDirStart, unsigned hashDirEnd); + virtual ~RetrieveFsIDsWork(); + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + LogContext log; + Node& node; + SynchronizedCounter* counter; + AtomicUInt64* errors; + + unsigned hashDirStart; + unsigned hashDirEnd; + + FsckDBFsIDsTable* table; + FsckDBFsIDsTable::BulkHandle bulkHandle; + + void doWork(bool isBuddyMirrored); +}; + +#endif /* RETRIEVEFSIDSWORK_H */ diff --git a/fsck/source/components/worker/RetrieveInodesWork.cpp b/fsck/source/components/worker/RetrieveInodesWork.cpp new file mode 100644 index 0000000..12df2bd --- /dev/null +++ b/fsck/source/components/worker/RetrieveInodesWork.cpp @@ -0,0 +1,187 @@ +#include "RetrieveInodesWork.h" +#include +#include +#include +#include +#include +#include + +#include + +#include + +RetrieveInodesWork::RetrieveInodesWork(FsckDB* db, Node& node, SynchronizedCounter* counter, + AtomicUInt64& errors, unsigned hashDirStart, unsigned hashDirEnd, + AtomicUInt64* numFileInodesFound, AtomicUInt64* numDirInodesFound, + std::set& usedTargets) : + log("RetrieveInodesWork"), node(node), counter(counter), errors(&errors), + usedTargets(&usedTargets), hashDirStart(hashDirStart), hashDirEnd(hashDirEnd), + numFileInodesFound(numFileInodesFound), numDirInodesFound(numDirInodesFound), + files(db->getFileInodesTable()), filesHandle(files->newBulkHandle()), + dirs(db->getDirInodesTable()), dirsHandle(dirs->newBulkHandle()) +{ +} + +void RetrieveInodesWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + log.log(4, "Processing RetrieveInodesWork"); + + try + { + doWork(false); + doWork(true); + // flush buffers before signaling completion + files->flush(filesHandle); + dirs->flush(dirsHandle); + // work package finished => increment counter + this->counter->incCount(); + + } + catch (std::exception &e) + { + // exception thrown, but work package is finished => increment counter + this->counter->incCount(); + + // after incrementing counter, re-throw exception + throw; + } + + log.log(4, "Processed RetrieveInodesWork"); +} + +void RetrieveInodesWork::doWork(bool isBuddyMirrored) +{ + const NumNodeID& metaRootID = Program::getApp()->getMetaRoot().getID(); + const NumNodeID& nodeID = node.getNumID(); + const NumNodeID nodeBuddyGroupID = NumNodeID(Program::getApp()->getMetaMirrorBuddyGroupMapper() + ->getBuddyGroupID(node.getNumID().val())); + + for ( unsigned firstLevelhashDirNum = hashDirStart; firstLevelhashDirNum <= hashDirEnd; + firstLevelhashDirNum++ ) + { + for ( unsigned secondLevelhashDirNum = 0; + secondLevelhashDirNum < META_DENTRIES_LEVEL2_SUBDIR_NUM; secondLevelhashDirNum++ ) + { + unsigned hashDirNum = StorageTk::mergeHashDirs(firstLevelhashDirNum, + secondLevelhashDirNum); + + int64_t lastOffset = 0; + size_t fileInodeCount; + size_t dirInodeCount; + + do + { + RetrieveInodesMsg retrieveInodesMsg(hashDirNum, lastOffset, + RETRIEVE_INODES_PACKET_SIZE, isBuddyMirrored); + + const auto respMsg = MessagingTk::requestResponse(node, retrieveInodesMsg, + NETMSGTYPE_RetrieveInodesResp); + if (respMsg) + { + auto* retrieveInodesRespMsg = (RetrieveInodesRespMsg*) respMsg.get(); + + // set new parameters + lastOffset = retrieveInodesRespMsg->getLastOffset(); + + // parse all file inodes + FsckFileInodeList& fileInodes = retrieveInodesRespMsg->getFileInodes(); + + // check inode entry IDs + for (auto it = fileInodes.begin(); it != fileInodes.end(); ) + { + if (db::EntryID::tryFromStr(it->getID()).first + && db::EntryID::tryFromStr(it->getParentDirID()).first + && (!it->getPathInfo()->hasOrigFeature() + || db::EntryID::tryFromStr( + it->getPathInfo()->getOrigParentEntryID()).first)) + { + ++it; + continue; + } + + LOG(GENERAL, ERR, "Found inode with invalid entry IDs.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID()), + ("parentEntryID", it->getParentDirID()), + ("origParent", it->getPathInfo()->getOrigParentEntryID())); + + ++it; + errors->increase(); + fileInodes.erase(std::prev(it)); + } + + // add targetIDs + for (auto iter = fileInodes.begin(); iter != fileInodes.end(); iter++) + { + FsckTargetIDType fsckTargetIDType; + + if (iter->getStripePatternType() == FsckStripePatternType_BUDDYMIRROR) + fsckTargetIDType = FsckTargetIDType_BUDDYGROUP; + else + fsckTargetIDType = FsckTargetIDType_TARGET; + + for (auto targetsIter = iter->getStripeTargets().begin(); + targetsIter != iter->getStripeTargets().end(); targetsIter++) + { + this->usedTargets->insert(FsckTargetID(*targetsIter, fsckTargetIDType) ); + } + } + + // parse all directory inodes + FsckDirInodeList& dirInodes = retrieveInodesRespMsg->getDirInodes(); + + // check inode entry IDs + for (auto it = dirInodes.begin(); it != dirInodes.end(); ) + { + auto entryIDPair = db::EntryID::tryFromStr(it->getID()); + if (!entryIDPair.first || + !db::EntryID::tryFromStr(it->getParentDirID()).first) + { + LOG(GENERAL, ERR, "Found inode with invalid entry IDs.", + ("node", it->getSaveNodeID()), + ("isBuddyMirrored", it->getIsBuddyMirrored()), + ("entryID", it->getID()), + ("parentEntryID", it->getParentDirID())); + + ++it; + errors->increase(); + dirInodes.erase(std::prev(it)); + continue; + } + + // remove root inodes from non root metas + if (entryIDPair.second.isRootDir() && + ((it->getIsBuddyMirrored() && nodeBuddyGroupID != metaRootID) + || (!it->getIsBuddyMirrored() && nodeID != metaRootID))) + { + ++it; + dirInodes.erase(std::prev(it)); + continue; + } + ++it; + } + + fileInodeCount = fileInodes.size(); + dirInodeCount = dirInodes.size(); + + this->files->insert(fileInodes, this->filesHandle); + this->dirs->insert(dirInodes, this->dirsHandle); + + numFileInodesFound->increase(fileInodeCount); + numDirInodesFound->increase(dirInodeCount); + } + else + { + throw FsckException("Communication error occured with node " + node.getAlias()); + } + + // if any of the worker threads threw an exception, we should stop now! + if ( Program::getApp()->getShallAbort() ) + return; + + } while ( (fileInodeCount + dirInodeCount) > 0 ); + } + } +} diff --git a/fsck/source/components/worker/RetrieveInodesWork.h b/fsck/source/components/worker/RetrieveInodesWork.h new file mode 100644 index 0000000..4a44cc7 --- /dev/null +++ b/fsck/source/components/worker/RetrieveInodesWork.h @@ -0,0 +1,58 @@ +#ifndef RETRIEVEINODESWORK_H +#define RETRIEVEINODESWORK_H + +/* + * retrieve all inodes from one node, inside a specified range of hashDirs and save them to DB + + */ + +#include +#include +#include +#include +#include + +// the size of one response packet, i.e. how many inodes are asked for at once +#define RETRIEVE_INODES_PACKET_SIZE 500 + +class RetrieveInodesWork : public Work +{ + public: + /* + * @param db database instance + * @param node the node to retrieve data from + * @param counter a pointer to a Synchronized counter; this is incremented by one at the end + * and the calling thread can wait for the counter + * @param hashDirStart the first top-level hashDir to open + * @param hashDirEnd the last top-level hashDir to open + */ + RetrieveInodesWork(FsckDB* db, Node& node, SynchronizedCounter* counter, + AtomicUInt64& errors, unsigned hashDirStart, unsigned hashDirEnd, + AtomicUInt64* numFileInodesFound, AtomicUInt64* numDirInodesFound, + std::set& usedTargets); + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + private: + LogContext log; + Node& node; + SynchronizedCounter* counter; + AtomicUInt64* errors; + std::set* usedTargets; + + unsigned hashDirStart; + unsigned hashDirEnd; + + AtomicUInt64* numFileInodesFound; + AtomicUInt64* numDirInodesFound; + + FsckDBFileInodesTable* files; + FsckDBFileInodesTable::BulkHandle filesHandle; + + FsckDBDirInodesTable* dirs; + FsckDBDirInodesTable::BulkHandle dirsHandle; + + void doWork(bool isBuddyMirrored); +}; + +#endif /* RETRIEVEINODESWORK_H */ diff --git a/fsck/source/database/Buffer.h b/fsck/source/database/Buffer.h new file mode 100644 index 0000000..d4eae97 --- /dev/null +++ b/fsck/source/database/Buffer.h @@ -0,0 +1,47 @@ +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include + +template +class Buffer { + private: + Set* set; + SetFragment* currentFragment; + size_t fragmentSize; + + Buffer(const Buffer&); + Buffer& operator=(const Buffer&); + + public: + Buffer(Set& set, size_t fragmentSize) + : set(&set), currentFragment(NULL), fragmentSize(fragmentSize) + {} + + ~Buffer() + { + flush(); + } + + void flush() + { + if(currentFragment) + { + currentFragment->flush(); + currentFragment = NULL; + } + } + + void append(const Data& data) + { + if(!currentFragment) + currentFragment = set->newFragment(); + + currentFragment->append(data); + + if(currentFragment->size() * sizeof(Data) >= fragmentSize) + flush(); + } +}; + +#endif diff --git a/fsck/source/database/Chunk.h b/fsck/source/database/Chunk.h new file mode 100644 index 0000000..de33c85 --- /dev/null +++ b/fsck/source/database/Chunk.h @@ -0,0 +1,55 @@ +#ifndef CHUNK_H_ +#define CHUNK_H_ + +#include +#include + +#include +#include + +namespace db { + +struct Chunk { + static const unsigned SAVED_PATH_SIZE = 43; + + EntryID id; /* 12 */ + uint32_t targetID; /* 16 */ + uint32_t buddyGroupID; /* 20 */ + + /* savedPath layout is ... weird. + * we have two layouts: + * - // + * - u/<(ts >> 16) _b16>/<(ts >> 12) & 0xF _b16>/ + * + * where u16 are random 16 bit unsigneds each. that gives maximum path lengths of + * - 2 + 1 + 2 + 26 + * - 9 + 1 + 4 + 1 + 1 + 1 + 26 + * add a terminating NUL to each, the maximum path length becomes 44 bytes, + * which is also conveniently aligned for uint_64t followers */ + char savedPath[SAVED_PATH_SIZE + 1]; /* 64 */ + + int64_t fileSize; /* 72 */ + int64_t usedBlocks; /* 80 */ + + uint32_t uid; /* 84 */ + uint32_t gid; /* 88 */ + + typedef boost::tuple KeyType; + + KeyType pkey() const + { + return KeyType(id, targetID, buddyGroupID); + } + + operator FsckChunk() const + { + Path path(savedPath); + + return FsckChunk(id.str(), targetID, path, fileSize, usedBlocks, 0, 0, 0, uid, + gid, buddyGroupID); + } +}; + +} + +#endif diff --git a/fsck/source/database/ContDir.h b/fsck/source/database/ContDir.h new file mode 100644 index 0000000..084b728 --- /dev/null +++ b/fsck/source/database/ContDir.h @@ -0,0 +1,26 @@ +#ifndef CONTDIR_H_ +#define CONTDIR_H_ + +#include +#include + +namespace db { + +struct ContDir { + EntryID id; /* 12 */ + uint32_t saveNodeID; /* 16 */ + uint32_t isBuddyMirrored:1; /* 20 */ + + typedef EntryID KeyType; + + KeyType pkey() const { return id; } + + operator FsckContDir() const + { + return FsckContDir(id.str(), NumNodeID(saveNodeID), isBuddyMirrored); + } +}; + +} + +#endif diff --git a/fsck/source/database/Cursor.h b/fsck/source/database/Cursor.h new file mode 100644 index 0000000..1c76992 --- /dev/null +++ b/fsck/source/database/Cursor.h @@ -0,0 +1,67 @@ +#ifndef CURSOR_H_ +#define CURSOR_H_ + +#include +#include + +template +class Cursor +{ + public: + typedef Obj ElementType; + + private: + class SourceBase + { + public: + virtual ~SourceBase() {} + + virtual bool step() = 0; + virtual ElementType* get() = 0; + }; + + template + class Source : public SourceBase + { + public: + Source(Inner inner) + : inner(inner) + { + } + + bool step() + { + return inner.step(); + } + + ElementType* get() + { + return inner.get(); + } + + private: + Inner inner; + }; + + public: + template + explicit Cursor(Inner inner) + : source(boost::make_shared >(inner) ) + { + } + + bool step() + { + return source->step(); + } + + ElementType* get() + { + return source->get(); + } + + private: + boost::shared_ptr source; +}; + +#endif diff --git a/fsck/source/database/DirEntry.h b/fsck/source/database/DirEntry.h new file mode 100644 index 0000000..92eaefe --- /dev/null +++ b/fsck/source/database/DirEntry.h @@ -0,0 +1,67 @@ +#ifndef DIRENTRY_H_ +#define DIRENTRY_H_ + +#include +#include + +namespace db { + +struct DirEntry +{ + EntryID id; /* 12 */ + EntryID parentDirID; /* 24 */ + + uint32_t entryOwnerNodeID; /* 28 */ + uint32_t inodeOwnerNodeID; /* 32 */ + + uint32_t saveNodeID; /* 36 */ + int32_t saveDevice; /* 40 */ + uint64_t saveInode; /* 48 */ + + union { + struct { + char text[16]; /* +16 */ + } inlined; + struct { + /* take name from a specific offset in some other file, + * identified by a u64 for convenience */ + uint64_t fileID; /* +8 */ + uint64_t fileOffset; /* +16 */ + } extended; + } name; /* 64 */ + + // the identifying key for dir entries is actually (parent, name). since storing the name as + // part of the key is very inefficient, and existence of duplicate names per parent can be + // easily excluded, we instead number all dir entries to create a surrogate key (seqNo), which + // will also work as (parent, seqNo). + // + // the actual primary key of the table is (id, parent, GFID, seqNo) though. this is because + // most querys only need the id, a few queries need the full fsid linking identifier + // (id, parent, GFID), and deletion always need (parent, seqNo). this also means that the + // deletion tracker set for dir entries will be rather large per entry, but it does maek queries + // more efficient. + uint64_t seqNo; /* 72 */ + + uint32_t entryType:16; + uint32_t fileNameInlined:1; + uint32_t hasInlinedInode:1; + uint32_t isBuddyMirrored:1; /* 76 */ + + typedef boost::tuple KeyType; + + KeyType pkey() const + { + return KeyType(id, parentDirID, saveNodeID, saveDevice, saveInode, seqNo); + } + + operator FsckDirEntry() const + { + return FsckDirEntry(id.str(), "[]", parentDirID.str(), + NumNodeID(entryOwnerNodeID), NumNodeID(inodeOwnerNodeID), FsckDirEntryType(entryType), + hasInlinedInode, NumNodeID(saveNodeID), saveDevice, saveInode, isBuddyMirrored, seqNo); + } +}; + +} + +#endif diff --git a/fsck/source/database/DirInode.h b/fsck/source/database/DirInode.h new file mode 100644 index 0000000..17a7f3d --- /dev/null +++ b/fsck/source/database/DirInode.h @@ -0,0 +1,40 @@ +#ifndef DIRINODE_H_ +#define DIRINODE_H_ + +#include +#include + +namespace db { + +struct DirInode { + EntryID id; /* 12 */ + EntryID parentDirID; /* 24 */ + + uint32_t parentNodeID; /* 28 */ + uint32_t ownerNodeID; /* 32 */ + uint32_t saveNodeID; /* 36 */ + + uint32_t stripePatternType:16; + uint32_t readable:1; + uint32_t isBuddyMirrored:1; + uint32_t isMismirrored:1; /* 40 */ + + uint64_t size; /* 48 */ + uint64_t numHardlinks; /* 56 */ + + typedef EntryID KeyType; + + EntryID pkey() const { return id; } + + operator FsckDirInode() const + { + return FsckDirInode(id.str(), parentDirID.str(), NumNodeID(parentNodeID), + NumNodeID(ownerNodeID), size, numHardlinks, UInt16Vector(), + FsckStripePatternType(stripePatternType), NumNodeID(saveNodeID), isBuddyMirrored, + readable, isMismirrored); + } +}; + +} + +#endif diff --git a/fsck/source/database/DiskList.h b/fsck/source/database/DiskList.h new file mode 100644 index 0000000..35713e0 --- /dev/null +++ b/fsck/source/database/DiskList.h @@ -0,0 +1,149 @@ +#ifndef DISKLIST_H_ +#define DISKLIST_H_ + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +struct DiskListDoesNotExist : public std::runtime_error +{ + DiskListDoesNotExist(const std::string& file) + : runtime_error(file) + {} +}; + +template +class DiskList; + +template +class DiskListCursor +{ + friend class DiskList; + + public: + bool step() + { + uint64_t itemLen; + + { + ssize_t readRes = ::pread(fd, &itemLen, sizeof(itemLen), offset); + + if (readRes == 0) + return false; + + if (readRes < (ssize_t) sizeof(itemLen)) + throw std::runtime_error("could not read from disk list file: " + std::string(strerror(errno))); + } + + itemLen = LE_TO_HOST_64(itemLen); + + std::unique_ptr itemBuf(new char[itemLen]); + + ssize_t readRes = ::pread(fd, itemBuf.get(), itemLen, offset + sizeof(itemLen)); + + if (readRes < (ssize_t) itemLen) + throw std::runtime_error("could not read from disk list file: " + std::string(strerror(errno))); + + Deserializer des(itemBuf.get(), itemLen); + + des % item; + if (!des.good()) + throw std::runtime_error("could not read from disk list file: " + std::string(strerror(errno))); + + offset += sizeof(itemLen) + itemLen; + return true; + } + + Data* get() + { + return &item; + } + + private: + int fd; + size_t offset; + Data item; + + DiskListCursor(int fd): + fd(fd), offset(0) + { + } +}; + +template +class DiskList { + private: + std::string file; + int fd; + uint64_t itemCount; + Mutex mtx; + + DiskList(const DiskList&); + DiskList& operator=(const DiskList&); + + public: + DiskList(const std::string& file, bool allowCreate = true) : + file(file), itemCount(0) + { + fd = ::open(file.c_str(), O_RDWR | (allowCreate ? O_CREAT : 0), 0660); + if(fd < 0) + { + int eno = errno; + if(!allowCreate && errno == ENOENT) + throw DiskListDoesNotExist(file); + else + throw std::runtime_error("could not open disk list file " + file + ": " + strerror(eno)); + } + } + + ~DiskList() + { + if(fd >= 0) + close(fd); + } + + const std::string filename() const { return file; } + + void append(const Data& data) + { + std::lock_guard lock(mtx); + + boost::scoped_array buffer; + + ssize_t size = serializeIntoNewBuffer(data, buffer); + if (size < 0) + throw std::runtime_error("error serializing disk list item"); + + uint64_t bufSize = HOST_TO_LE_64(size); + if (::write(fd, &bufSize, sizeof(bufSize)) < (ssize_t) sizeof(bufSize)) + throw std::runtime_error("error writing disk list item: " + std::string(strerror(errno))); + + if (::write(fd, buffer.get(), size) < size) + throw std::runtime_error("error writing disk list item: " + std::string(strerror(errno))); + } + + DiskListCursor cursor() + { + return DiskListCursor(fd); + } + + void clear() + { + if (fd >= 0) + { + if (ftruncate(fd, 0) < 0) + throw std::runtime_error("error clearing disk list: " + std::string(strerror(errno))); + } + } +}; + +#endif diff --git a/fsck/source/database/Distinct.h b/fsck/source/database/Distinct.h new file mode 100644 index 0000000..2409670 --- /dev/null +++ b/fsck/source/database/Distinct.h @@ -0,0 +1,88 @@ +#ifndef DISTINCT_H_ +#define DISTINCT_H_ + +#include +#include + +template +class Distinct +{ + private: + typedef typename boost::decay< + typename boost::result_of::type + >::type KeyType; + + public: + typedef typename Source::ElementType ElementType; + typedef typename Source::MarkerType MarkerType; + + public: + Distinct(Source source, KeyExtract keyExtract) + : source(source), keyExtract(keyExtract), hasKey(false) + {} + + bool step() + { + if(!this->source.step() ) + return false; + + while(this->hasKey && this->key == this->keyExtract(*this->source.get() ) ) + { + if(!this->source.step() ) + return false; + } + + this->hasKey = true; + this->key = this->keyExtract(*this->source.get() ); + return true; + } + + ElementType* get() + { + return this->source.get(); + } + + MarkerType mark() const + { + return this->source.mark(); + } + + void restore(MarkerType mark) + { + this->source.restore(mark); + this->hasKey = true; + this->key = this->keyExtract(*this->source.get() ); + } + + private: + Source source; + KeyType key; + KeyExtract keyExtract; + bool hasKey; +}; + + +namespace db { + +template +struct DistinctOp +{ + KeyExtract keyExtract; + + template + friend Distinct operator|(Source source, const DistinctOp& op) + { + return Distinct(source, op.keyExtract); + } +}; + +template +inline DistinctOp distinctBy(KeyExtract keyExtract) +{ + DistinctOp result = {keyExtract}; + return result; +} + +} + +#endif diff --git a/fsck/source/database/EntryID.h b/fsck/source/database/EntryID.h new file mode 100644 index 0000000..de9197c --- /dev/null +++ b/fsck/source/database/EntryID.h @@ -0,0 +1,163 @@ +#ifndef ENTRYID_H_ +#define ENTRYID_H_ + +#include + +#include +#include +#include +#include + +#include +#include + +namespace db { + +struct EntryID { + unsigned sequence; + unsigned timestamp; + unsigned nodeID; + + // required for queries, since they use boost::tuple to avoid writing out huge comparison + // operators + EntryID() + : sequence(0), timestamp(0), nodeID(0) + {} + + EntryID(unsigned sequence, unsigned timestamp, unsigned nodeID) + : sequence(sequence), timestamp(timestamp), nodeID(nodeID) + { + if(nodeID == 0 && (timestamp != 0 || sequence > 3) ) + throw std::exception(); + } + + bool isSpecial() const { return nodeID == 0; } + + bool operator<(const EntryID& other) const + { + return boost::make_tuple(sequence, timestamp, nodeID) + < boost::make_tuple(other.sequence, other.timestamp, other.nodeID); + } + + bool operator==(const EntryID& other) const + { + return this->sequence == other.sequence + && this->timestamp == other.timestamp + && this->nodeID == other.nodeID; + } + + bool operator!=(const EntryID& other) const + { + return !(*this == other); + } + + bool isDisposalDir() const + { + return *this == disposal() || *this == mdisposal(); + } + + bool isRootDir() const + { + return *this == root(); + } + + std::string str() const + { + if(isSpecial() ) + { + if(*this == anchor() ) + return ""; + + if(*this == root() ) + return META_ROOTDIR_ID_STR; + + if(*this == disposal() ) + return META_DISPOSALDIR_ID_STR; + + if(*this == mdisposal()) + return META_MIRRORDISPOSALDIR_ID_STR; + + throw std::exception(); + } + + char buffer[3*8 + 2 + 1]; + sprintf(buffer, "%X-%X-%X", sequence, timestamp, nodeID); + return buffer; + } + + static EntryID anchor() { return EntryID(0, 0, 0); } + static EntryID root() { return EntryID(1, 0, 0); } + static EntryID disposal() { return EntryID(2, 0, 0); } + static EntryID mdisposal() { return EntryID(3, 0, 0); } + + static EntryID fromStr(const std::string& str) + { + auto pair = tryFromStr(str); + if (!pair.first) + throw std::exception(); + + return pair.second; + } + + static std::pair tryFromStr(const std::string& str) + { + if (str.empty()) + return {true, anchor()}; + + if (str == META_ROOTDIR_ID_STR) + return {true, root()}; + + if (str == META_DISPOSALDIR_ID_STR) + return {true, disposal()}; + + if (str == META_MIRRORDISPOSALDIR_ID_STR) + return {true, mdisposal()}; + + std::string::size_type sep1; + std::string::size_type sep2; + + sep1 = str.find('-'); + sep2 = str.find('-', sep1 + 1); + + if (sep1 == str.npos || sep2 == str.npos) + return {false, {}}; + + std::string seqStr = str.substr(0, sep1); + std::string tsStr = str.substr(sep1 + 1, sep2 - (sep1 + 1)); + std::string nodeStr = str.substr(sep2 + 1); + + if (seqStr.empty() || tsStr.empty() || nodeStr.empty() || + seqStr.size() > 8 || tsStr.size() > 8 || nodeStr.size() > 8) + return {false, {}}; + + struct + { + static bool notHex(char c) + { + return !std::isxdigit(c); + } + + bool operator()(const std::string& str) const + { + return std::count_if(str.begin(), str.end(), notHex) == 0; + } + } onlyHex; + + if (!onlyHex(seqStr) || !onlyHex(tsStr) || !onlyHex(nodeStr) ) + return {false, {}}; + + unsigned seq; + unsigned ts; + unsigned node; + + std::sscanf(seqStr.c_str(), "%x", &seq); + std::sscanf(tsStr.c_str(), "%x", &ts); + std::sscanf(nodeStr.c_str(), "%x", &node); + + return {true, EntryID(seq, ts, node)}; + } +}; + +} + +#endif diff --git a/fsck/source/database/FileInode.h b/fsck/source/database/FileInode.h new file mode 100644 index 0000000..11abffa --- /dev/null +++ b/fsck/source/database/FileInode.h @@ -0,0 +1,99 @@ +#ifndef FILEINODE_H_ +#define FILEINODE_H_ + +#include +#include + +namespace db { + +struct FileInode { + EntryID id; /* 12 */ + EntryID parentDirID; /* 24 */ + EntryID origParentEntryID; /* 36 */ + + uint32_t parentNodeID; /* 40 */ + uint32_t saveNodeID; /* 44 */ + uint32_t origParentUID; /* 48 */ + + uint32_t uid; /* 52 */ + uint32_t gid; /* 56 */ + + uint64_t fileSize; /* 64 */ + uint64_t usedBlocks; /* 72 */ + + uint64_t numHardlinks; /* 80 */ + + uint64_t saveInode; /* 88 */ + int32_t saveDevice; /* 92 */ + + uint32_t chunkSize; /* 96 */ + + enum { + NTARGETS = 6 + }; + uint16_t targets[NTARGETS]; /* 108 */ + + uint32_t isInlined:1; + uint32_t pathInfoFlags:4; + uint32_t stripePatternType:4; + uint32_t stripePatternSize:20; + uint32_t readable:1; + uint32_t isBuddyMirrored:1; + uint32_t isMismirrored:1; /* 112 */ + + typedef EntryID KeyType; + + EntryID pkey() const { return id; } + + FsckFileInode toInodeWithoutStripes() const + { + PathInfo info(origParentUID, origParentEntryID.str(), pathInfoFlags); + + return FsckFileInode(id.str(), parentDirID.str(), NumNodeID(parentNodeID), info, uid, gid, + fileSize, numHardlinks, usedBlocks, {}, FsckStripePatternType(stripePatternType), + chunkSize, NumNodeID(saveNodeID), saveInode, saveDevice, isInlined, isBuddyMirrored, + readable, isMismirrored); + } + + friend std::ostream& operator<<(std::ostream& os, FileInode const& obj) + { + os << "-------------" << "\n"; + os << "FsckFileInode" << "\n"; + os << "-------------" << "\n"; + os << "EntryID: " << obj.id.str() << "\n"; + os << "Parent dir's entryID: " << obj.parentDirID.str() << "\n"; + os << "Orig parent dir's entryID: " << obj.origParentEntryID.str() << "\n"; + os << "parent nodeID: " << obj.parentNodeID << "\n"; + os << "save nodeID: " << obj.saveNodeID << "\n"; + os << "origParentUID: " << obj.origParentUID << "\n"; + os << "uid: " << obj.uid << "\n"; + os << "gid: " << obj.gid << "\n"; + os << "fileSize: " << obj.fileSize << "\n"; + os << "usedBlocks: " << obj.usedBlocks << "\n"; + os << "numHardlinks: " << obj.numHardlinks << "\n"; + os << "saveInode: " << obj.saveInode << "\n"; + os << "saveDevice: " << obj.saveDevice << "\n"; + os << "chunkSize: " << obj.chunkSize << "\n"; + + os << "Stripe targets: [ "; + + for (int i=0; i< NTARGETS; i++) + { + os << obj.targets[i] << " "; + } + os << "]\n"; + + os << "isInlined: " << obj.isInlined << "\n"; + os << "pathInfoFlags: " << obj.pathInfoFlags << "\n"; + os << "stripePatternType: " << obj.stripePatternType << "\n"; + os << "stripePatternSize: " << obj.stripePatternSize << "\n"; + os << "readable: " << obj.readable << "\n"; + os << "isBuddyMirrored: " << obj.isBuddyMirrored << "\n"; + os << "isMismirrored: " << obj.isMismirrored << "\n\n"; + return os; + } +}; + +} + +#endif diff --git a/fsck/source/database/Filter.h b/fsck/source/database/Filter.h new file mode 100644 index 0000000..92c17dd --- /dev/null +++ b/fsck/source/database/Filter.h @@ -0,0 +1,72 @@ +#ifndef FILTER_H_ +#define FILTER_H_ + +template +class Filter +{ + public: + typedef typename Source::ElementType ElementType; + typedef typename Source::MarkerType MarkerType; + + public: + Filter(Source source, Pred pred) + : source(source), pred(pred) + { + } + + bool step() + { + while(this->source.step() ) + { + if(this->pred(*get() ) ) + return true; + } + + return false; + } + + ElementType* get() + { + return this->source.get(); + } + + MarkerType mark() const + { + return this->source.mark(); + } + + void restore(MarkerType mark) + { + this->source.restore(mark); + } + + private: + Source source; + Pred pred; +}; + + +namespace db { + +template +struct FilterOp +{ + Pred pred; + + template + friend Filter operator|(Source source, const FilterOp& op) + { + return Filter(source, op.pred); + } +}; + +template +inline FilterOp where(Pred pred) +{ + FilterOp result = {pred}; + return result; +} + +} + +#endif diff --git a/fsck/source/database/FsID.h b/fsck/source/database/FsID.h new file mode 100644 index 0000000..8309855 --- /dev/null +++ b/fsck/source/database/FsID.h @@ -0,0 +1,38 @@ +#ifndef FSID_H_ +#define FSID_H_ + +#include +#include + +#include +#include + +namespace db { + +struct FsID { + EntryID id; /* 12 */ + EntryID parentDirID; /* 24 */ + + uint32_t saveNodeID; /* 28 */ + int32_t saveDevice; /* 32 */ + uint64_t saveInode; /* 40 */ + + uint64_t isBuddyMirrored:1; /* 48 */ + + typedef boost::tuple KeyType; + + KeyType pkey() const + { + return KeyType(id, parentDirID, saveNodeID, saveDevice, saveInode); + } + + operator FsckFsID() const + { + return FsckFsID(id.str(), parentDirID.str(), NumNodeID(saveNodeID), saveDevice, saveInode, + isBuddyMirrored); + } +}; + +} + +#endif diff --git a/fsck/source/database/FsckDB.cpp b/fsck/source/database/FsckDB.cpp new file mode 100644 index 0000000..f6662d1 --- /dev/null +++ b/fsck/source/database/FsckDB.cpp @@ -0,0 +1,40 @@ +#include "FsckDB.h" + +#include +#include +#include +#include +#include +#include + +#include + +FsckDB::FsckDB(const std::string& databasePath, size_t fragmentSize, size_t nameCacheLimit, + bool allowCreate) + : log("FsckDB"), + databasePath(databasePath), + dentryTable(new FsckDBDentryTable(databasePath, fragmentSize, nameCacheLimit, allowCreate) ), + fileInodesTable(new FsckDBFileInodesTable(databasePath, fragmentSize, allowCreate) ), + dirInodesTable(new FsckDBDirInodesTable(databasePath, fragmentSize, allowCreate) ), + chunksTable(new FsckDBChunksTable(databasePath, fragmentSize, allowCreate) ), + contDirsTable(new FsckDBContDirsTable(databasePath, fragmentSize, allowCreate) ), + fsIDsTable(new FsckDBFsIDsTable(databasePath, fragmentSize, allowCreate) ), + usedTargetIDsTable(new FsckDBUsedTargetIDsTable(databasePath, fragmentSize, allowCreate) ), + modificationEventsTable(new FsckDBModificationEventsTable(databasePath, fragmentSize, + allowCreate) ), + malformedChunks(databasePath + "/malformedChunks") +{ +} + +void FsckDB::clear() +{ + this->dentryTable->clear(); + this->fileInodesTable->clear(); + this->dirInodesTable->clear(); + this->chunksTable->clear(); + this->contDirsTable->clear(); + this->fsIDsTable->clear(); + this->usedTargetIDsTable->clear(); + this->modificationEventsTable->clear(); + this->malformedChunks.clear(); +} diff --git a/fsck/source/database/FsckDB.h b/fsck/source/database/FsckDB.h new file mode 100644 index 0000000..ef34a68 --- /dev/null +++ b/fsck/source/database/FsckDB.h @@ -0,0 +1,178 @@ +/* + * This class is intended to be the interface to the sqlite DB. It can be created once in the + * applicationm, and can then be used in all threads, as sqlite claims to be thread-safe in + * "serialized" mode (which is the default, http://sqlite.org/threadsafe.html). + */ + +#ifndef FSCKDB_H_ +#define FSCKDB_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +class FsckDBDentryTable; +class FsckDBFileInodesTable; +class FsckDBDirInodesTable; +class FsckDBChunksTable; +class FsckDBContDirsTable; +class FsckDBFsIDsTable; +class FsckDBUsedTargetIDsTable; +class FsckDBModificationEventsTable; +class FsckDBDentryErrorTable; +class FsckDBDirInodeErrorTable; +class FsckDBFsIDErrorTable; +class FsckDBFileInodeErrorTable; +class FsckDBChunkErrorTable; +class FsckDBContDirErrorTable; +class FsckDBStripeTargetErrorTable; + +namespace checks { + +struct InodeAttribs +{ + uint64_t size; + uint64_t nlinks; +}; + +struct OptionalInodeAttribs +{ + boost::optional size; + boost::optional nlinks; + + void reset() { + size.reset(); + nlinks.reset(); + } +}; + +typedef std::pair> DuplicatedInode; +} + +class FsckDB +{ + friend class TestDatabase; + + public: + // FsckDB.cpp + FsckDB(const std::string& databasePath, size_t fragmentSize, size_t nameCacheLimit, + bool allowCreate); + + void clear(); + + // FsckDBChecks.cpp + Cursor findDuplicateInodeIDs(); + Cursor > findDuplicateChunks(); + Cursor> findDuplicateContDirs(); + + Cursor findMismirroredDentries(); + Cursor findMismirroredDirectories(); + Cursor findMismirroredFiles(); + + Cursor findDanglingDirEntries(); + Cursor findDirEntriesWithBrokenByIDFile(); + Cursor findOrphanedFsIDFiles(); + Cursor findInodesWithWrongOwner(); + Cursor > findDirEntriesWithWrongOwner(); + Cursor findOrphanedDirInodes(); + Cursor findOrphanedFileInodes(); + Cursor findOrphanedChunks(); + + Cursor findInodesWithoutContDir(); + + Cursor findOrphanedContDirs(); + + Cursor > findWrongInodeFileAttribs(); + + Cursor > findWrongInodeDirAttribs(); + + Cursor findFilesWithMissingStripeTargets(TargetMapper* targetMapper, + MirrorBuddyGroupMapper* buddyGroupMapper); + Cursor > findChunksWithWrongPermissions(); + Cursor > findChunksInWrongPath(); + Cursor findFilesWithMultipleHardlinks(); + + private: + LogContext log; + std::string databasePath; + + boost::scoped_ptr dentryTable; + boost::scoped_ptr fileInodesTable; + boost::scoped_ptr dirInodesTable; + boost::scoped_ptr chunksTable; + boost::scoped_ptr contDirsTable; + boost::scoped_ptr fsIDsTable; + boost::scoped_ptr usedTargetIDsTable; + boost::scoped_ptr modificationEventsTable; + DiskList malformedChunks; + + public: + FsckDBDentryTable* getDentryTable() + { + return this->dentryTable.get(); + } + + FsckDBFileInodesTable* getFileInodesTable() + { + return this->fileInodesTable.get(); + } + + FsckDBDirInodesTable* getDirInodesTable() + { + return this->dirInodesTable.get(); + } + + FsckDBChunksTable* getChunksTable() + { + return this->chunksTable.get(); + } + + FsckDBContDirsTable* getContDirsTable() + { + return this->contDirsTable.get(); + } + + FsckDBFsIDsTable* getFsIDsTable() + { + return this->fsIDsTable.get(); + } + + FsckDBUsedTargetIDsTable* getUsedTargetIDsTable() + { + return this->usedTargetIDsTable.get(); + } + + FsckDBModificationEventsTable* getModificationEventsTable() + { + return this->modificationEventsTable.get(); + } + + DiskList* getMalformedChunksList() + { + return &malformedChunks; + } +}; + +#endif /* FSCKDB_H_ */ diff --git a/fsck/source/database/FsckDBChecks.cpp b/fsck/source/database/FsckDBChecks.cpp new file mode 100644 index 0000000..2e6cda2 --- /dev/null +++ b/fsck/source/database/FsckDBChecks.cpp @@ -0,0 +1,1419 @@ +#include "FsckDB.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +namespace { + +struct SelectFirstFn +{ + template + Left operator()(std::pair& pair) const + { + return pair.first; + } +} first; + +struct SecondIsNullFn +{ + template + bool operator()(std::pair& pair) const + { + return pair.second == NULL; + } +} secondIsNull; + +struct ObjectIDFn +{ + template + db::EntryID operator()(Obj& obj) const + { + return obj.id; + } +} objectID; + +struct FirstObjectIDFn +{ + template + db::EntryID operator()(std::pair& obj) const + { + return obj.first.id; + } +} firstObjectID; + +struct SecondIsNotNullFn +{ + template + bool operator()(std::pair& pair) const + { + return pair.second != NULL; + } +} secondIsNotNull; + +template +Filter, SecondIsNotNullFn> joinBy(Fn fn, Left left, Right right) +{ + return + db::leftJoinBy(fn, left, right) + | db::where(SecondIsNotNullFn() ); +} + +template +struct ConvertTo +{ + template + To operator()(From& from) const + { + return To(from); + } +}; + +template +static db::SelectOp > convertTo() +{ + return db::select(ConvertTo() ); +} + +template +static Cursor cursor(Inner inner) +{ + return Cursor(inner); +} + +struct IgnoreByID +{ + SetFragmentCursor modified; + + template + friend Select< + Filter< + LeftJoinEq, ObjectIDFn>, + SecondIsNullFn>, + SelectFirstFn> + operator|(Source source, IgnoreByID ignore) + { + return + leftJoinBy( + objectID, + source, + ignore.modified) + | db::where(secondIsNull) + | db::select(first); + } +}; + +IgnoreByID ignoreByID(SetFragmentCursor ids) +{ + IgnoreByID result = { ids }; + return result; +} + +struct IDFn +{ + template + T operator()(T t) const { return t; } +} id; + +struct StripeSet +{ + std::vector targets; + + void clear() + { + targets.clear(); + } + + void add(db::FileInode& inode) + { + if (targets.empty()) + { + targets.resize(inode.stripePatternSize); + std::copy( + inode.targets, + inode.targets + std::min(inode.NTARGETS, inode.stripePatternSize), + targets.begin()); + } + } + + void add(db::StripeTargets& moreTargets) + { + unsigned count = std::min(moreTargets.NTARGETS, + targets.size() - moreTargets.firstTargetIndex); + + std::copy( + moreTargets.targets, + moreTargets.targets + count, + targets.begin() + moreTargets.firstTargetIndex); + } +}; + +} + + + +static bool dentryIsDirectory(db::DirEntry& dentry) +{ + return dentry.entryType == FsckDirEntryType_DIRECTORY; +} + +static bool dentryIsNotDirectory(db::DirEntry& dentry) +{ + return dentry.entryType != FsckDirEntryType_DIRECTORY; +} + +static bool isNotDisposal(db::DirInode& inode) +{ + return !inode.id.isDisposalDir(); +} + +namespace { +struct DuplicateIDGroup +{ + typedef db::EntryID KeyType; + typedef db::EntryID ProjType; + typedef std::set GroupType; + + bool hasTarget; + FsckDuplicateInodeInfo lastTarget; + GroupType group; + + DuplicateIDGroup() + { + reset(); + } + + void reset() + { + hasTarget = false; + group.clear(); + } + + KeyType key(std::pair pair) + { + return pair.first; + } + + ProjType project(std::pair pair) + { + return pair.first; + } + + void step(std::pair pair) + { + if (!hasTarget) + { + lastTarget = pair.second; + hasTarget = true; + return; + } + + if (lastTarget.getSaveNodeID() != 0) + { + group.insert(lastTarget); + lastTarget.setSaveNodeID(0); + } + group.insert(pair.second); + } + + GroupType finish() + { + GroupType result; + + result.swap(group); + reset(); + return result; + } +}; +} + +Cursor FsckDB::findDuplicateInodeIDs() +{ + struct ops + { + static std::pair idAndTargetF(const db::FileInode& file) + { + return std::make_pair(file.id, FsckDuplicateInodeInfo(file.id.str(), file.parentDirID.str(), + file.saveNodeID, file.isInlined, file.isBuddyMirrored, DirEntryType_REGULARFILE)); + } + + static std::pair idAndTargetD(const db::DirInode& file) + { + return std::make_pair(file.id, FsckDuplicateInodeInfo(file.id.str(), file.parentDirID.str(), + file.saveNodeID, false, file.isBuddyMirrored, DirEntryType_DIRECTORY)); + } + + static bool hasDuplicateID(const checks::DuplicatedInode& dInode) + { + return !dInode.second.empty(); + } + }; + + return cursor( + db::unionBy( + id, + this->dirInodesTable->get() + | db::where(isNotDisposal) + | ignoreByID(this->modificationEventsTable->get()) + | db::select(ops::idAndTargetD), + this->fileInodesTable->getInodes() + | ignoreByID(this->modificationEventsTable->get()) + | db::select(ops::idAndTargetF) ) + | db::groupBy(DuplicateIDGroup() ) + | db::where(ops::hasDuplicateID) ); +} + +namespace { +struct DuplicateChunkGroup +{ + typedef std::pair KeyType; + typedef db::EntryID ProjType; + typedef std::list GroupType; + + bool hasChunk; + db::Chunk chunk; + GroupType group; + + DuplicateChunkGroup() + { + reset(); + } + + void reset() + { + hasChunk = false; + group.clear(); + } + + KeyType key(db::Chunk& chunk) + { + return std::make_pair(chunk.id, chunk.targetID); + } + + ProjType project(db::Chunk& chunk) + { + return chunk.id; + } + + void step(db::Chunk& chunk) + { + if(!hasChunk) + { + this->chunk = chunk; + hasChunk = true; + return; + } + + if(group.empty() ) + group.push_back(this->chunk); + + group.push_back(chunk); + } + + GroupType finish() + { + GroupType result; + + result.swap(group); + reset(); + return result; + } +}; +} + +Cursor > FsckDB::findDuplicateChunks() +{ + struct ops + { + static bool hasDuplicateChunks(std::pair >& pair) + { + return !pair.second.empty(); + } + + static std::list second(std::pair >& pair) + { + return pair.second; + } + }; + + return cursor( + this->chunksTable->get() + | ignoreByID(this->modificationEventsTable->get()) + | db::groupBy(DuplicateChunkGroup()) + | db::where(ops::hasDuplicateChunks) + | db::select(ops::second)); +} + +namespace { +struct DuplicateContDirGroup +{ + typedef db::EntryID KeyType; + typedef db::EntryID ProjType; + typedef std::list GroupType; + + bool hasDir; + db::ContDir lastDir; + GroupType group; + + DuplicateContDirGroup() + { + reset(); + } + + void reset() + { + hasDir = false; + group.clear(); + } + + KeyType key(db::ContDir& dir) { return dir.id; } + + ProjType project(db::ContDir& dir) { return dir.id; } + + void step(db::ContDir& dir) + { + if (!hasDir) + { + lastDir = dir; + hasDir = true; + return; + } + + if (group.empty()) + group.push_back(lastDir); + + group.push_back(dir); + } + + GroupType finish() + { + GroupType result; + + result.swap(group); + reset(); + return result; + } +}; +} + +Cursor> FsckDB::findDuplicateContDirs() +{ + struct ops + { + static bool duplicated(std::pair>& pair) + { + return !pair.second.empty(); + } + + static std::list second(std::pair>& pair) + { + return pair.second; + } + + static bool isNotDisposal(db::ContDir& dir) + { + return dir.id != db::EntryID::disposal() && dir.id != db::EntryID::mdisposal(); + } + }; + + return cursor( + contDirsTable->get() + | db::where(ops::isNotDisposal) + | db::groupBy(DuplicateContDirGroup()) + | db::where(ops::duplicated) + | db::select(ops::second)); +} + +Cursor FsckDB::findMismirroredDentries() +{ + struct ops + { + static bool fileMismirrored(const std::pair& row) + { + return row.first.isBuddyMirrored != row.second->isBuddyMirrored; + } + + static bool dirMismirrored(const std::pair& row) + { + return row.first.isBuddyMirrored != row.second->isBuddyMirrored; + } + }; + + return cursor( + db::unionBy( + objectID, + db::leftJoinBy( + objectID, + dentryTable->get() + | ignoreByID(modificationEventsTable->get()) + | db::where(dentryIsNotDirectory), + fileInodesTable->getInodes()) + | db::where(secondIsNotNull) + | db::where(ops::fileMismirrored) + | db::select(first), + db::leftJoinBy( + objectID, + dentryTable->get() + | ignoreByID(modificationEventsTable->get()) + | db::where(dentryIsDirectory), + dirInodesTable->get()) + | db::where(secondIsNotNull) + | db::where(ops::dirMismirrored) + | db::select(first))); +} + +Cursor FsckDB::findMismirroredFiles() +{ + struct ops + { + static bool mismirrored(db::FileInode& file) + { + return file.isMismirrored; + } + }; + + return cursor( + fileInodesTable->getInodes() + | ignoreByID(modificationEventsTable->get()) + | db::where(ops::mismirrored)); +} + +Cursor FsckDB::findMismirroredDirectories() +{ + struct ops + { + static bool dirMismirrored(const std::pair& row) + { + return row.first.isMismirrored || row.first.isBuddyMirrored != row.second->isBuddyMirrored; + } + }; + + return cursor( + db::leftJoinBy( + objectID, + dirInodesTable->get() + | ignoreByID(modificationEventsTable->get()), + contDirsTable->get()) + | db::where(secondIsNotNull) + | db::where(ops::dirMismirrored) + | db::select(first)); +} + +/* + * looks for dir entries, for which no inode was found (dangling dentries) and directly inserts + * them into the database + */ +Cursor FsckDB::findDanglingDirEntries() +{ + return cursor( + db::unionBy( + objectID, + db::leftJoinBy( + objectID, + this->dentryTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(dentryIsNotDirectory), + this->fileInodesTable->getInodes() ) + | db::where(secondIsNull) + | db::select(first), + db::leftJoinBy( + objectID, + this->dentryTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(dentryIsDirectory), + this->dirInodesTable->get() ) + | db::where(secondIsNull) + | db::select(first) ) + | db::distinctBy(objectID) ); +} + +/* + * looks for dir entries, for which the inode was found on a different host as expected (according + * to inodeOwner info in dentry) + */ +Cursor > FsckDB::findDirEntriesWithWrongOwner() +{ + struct ops + { + static bool fileInodeOwnerIncorrect(std::pair& pair) + { + return pair.first.inodeOwnerNodeID != pair.second->saveNodeID; + } + + static bool dirInodeOwnerIncorrect(std::pair& pair) + { + return pair.first.inodeOwnerNodeID != pair.second->saveNodeID; + } + + static std::pair resultF( + std::pair& pair) + { + return std::make_pair(pair.first, NumNodeID(pair.second->saveNodeID) ); + } + + static std::pair resultD( + std::pair& pair) + { + return std::make_pair(pair.first, NumNodeID(pair.second->saveNodeID) ); + } + }; + + return cursor( + db::unionBy( + firstObjectID, + joinBy( + objectID, + this->dentryTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(dentryIsNotDirectory), + this->fileInodesTable->getInodes() ) + | db::where(ops::fileInodeOwnerIncorrect) + | db::select(ops::resultF), + joinBy( + objectID, + this->dentryTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(dentryIsDirectory), + this->dirInodesTable->get() ) + | db::where(ops::dirInodeOwnerIncorrect) + | db::select(ops::resultD) ) + | db::distinctBy(firstObjectID) ); +} + +/* + * looks for dir inodes, for which the owner node information is not correct and directly inserts + * them into the database + */ +Cursor FsckDB::findInodesWithWrongOwner() +{ + struct ops + { + static bool inodeHasWrongOwner(db::DirInode& inode) + { + return inode.ownerNodeID != inode.saveNodeID + && !inode.id.isDisposalDir(); + } + }; + + return Cursor( + this->dirInodesTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(ops::inodeHasWrongOwner) + | convertTo() ); +} + +namespace { +struct JoinDirEntriesWithBrokenByIDFile +{ + typedef boost::tuple< + db::EntryID, // ID + db::EntryID, // parentDirID + int, // device + NumNodeID, // saveNodeID + uint64_t // saveInode + > result_type; + + template + result_type operator()(Obj& obj) const + { + return result_type(obj.id, obj.parentDirID, obj.saveDevice, NumNodeID(obj.saveNodeID), + obj.saveInode); + } +}; +} + +/* + * looks for dir entries, for which no corresponding dentry-by-id file was found in #fsids# and + * directly inserts them into the database (only dir entries with inlined inodes are relevant) + */ +Cursor FsckDB::findDirEntriesWithBrokenByIDFile() +{ + struct ops + { + static bool fsidFileMissing(std::pair& pair) + { + return pair.first.hasInlinedInode + && !pair.first.parentDirID.isDisposalDir() + && pair.second == NULL; + } + }; + + return cursor( + db::leftJoinBy( + JoinDirEntriesWithBrokenByIDFile(), + this->dentryTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->fsIDsTable->get() ) + | db::where(ops::fsidFileMissing) + | db::select(first) ); +} + +namespace { +struct JoinOrphanedFsIDs +{ + typedef boost::tuple< + db::EntryID, // ID + db::EntryID, // parentDirID + NumNodeID // saveNodeID + > result_type; + + template + result_type operator()(Obj& obj) const + { + return result_type(obj.id, obj.parentDirID, NumNodeID(obj.saveNodeID) ); + } +}; +} + +/* + * looks for dentry-by-ID files in #fsids#, for which no corresponding dentry file was found and + * directly inserts them into the database + */ +Cursor FsckDB::findOrphanedFsIDFiles() +{ + return Cursor( + db::leftJoinBy( + JoinOrphanedFsIDs(), + this->fsIDsTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->dentryTable->get() ) + | db::where(secondIsNull) + | db::select(first) + | convertTo() ); +} + +/* + * looks for dir inodes, for which no dir entry exists + */ +Cursor FsckDB::findOrphanedDirInodes() +{ + struct ops + { + static bool inodeHasNoDentry(std::pair& pair) + { + return pair.first.id != db::EntryID::root() + && !pair.first.id.isDisposalDir() + && pair.second == NULL; + } + }; + + return Cursor( + db::leftJoinBy( + objectID, + this->dirInodesTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->dentryTable->get() ) + | db::where(ops::inodeHasNoDentry) + | db::select(first) + | convertTo() ); +} + +/* + * looks for file inodes, for which no dir entry exists + */ +Cursor FsckDB::findOrphanedFileInodes() +{ + struct ops + { + static FsckFileInode fileInodeToFsckFileInode(const db::FileInode& inode) + { + return inode.toInodeWithoutStripes(); + } + }; + + return Cursor( + db::leftJoinBy( + objectID, + this->fileInodesTable->getInodes() + | ignoreByID(this->modificationEventsTable->get() ), + this->dentryTable->get() ) + | db::where(secondIsNull) + | db::select(first) + | db::select(ops::fileInodeToFsckFileInode) ); +} + +namespace { +struct StripePatternGroup +{ + typedef db::EntryID KeyType; + typedef db::FileInode ProjType; + typedef std::vector GroupType; + + StripeSet pattern; + + KeyType key(std::pair& pair) + { + return pair.first.id; + } + + ProjType project(std::pair& pair) + { + return pair.first; + } + + void step(std::pair& pair) + { + pattern.add(pair.first); + if (pair.second) + pattern.add(*pair.second); + } + + GroupType finish() + { + std::vector result = pattern.targets; + + pattern.clear(); + + std::sort(result.begin(), result.end()); + return result; + } +}; + +struct OrphanedChunksJoin +{ + db::EntryID operator()(db::Chunk& chunk) const { return chunk.id; } + db::EntryID operator()(std::pair>& pair) + { + return pair.first.id; + } +}; +} + +/* + * looks for chunks, for which no inode exists + */ +Cursor FsckDB::findOrphanedChunks() +{ + struct ops + { + static bool isOrphaned( + std::pair>*>& pair) + { + if (!pair.second) + return true; + + const db::Chunk& chunk = pair.first; + const db::FileInode& inode = pair.second->first; + const std::vector& targets = pair.second->second; + + if (inode.stripePatternType == FsckStripePatternType_BUDDYMIRROR) + return !std::binary_search(targets.begin(), targets.end(), chunk.buddyGroupID); + else + return !std::binary_search(targets.begin(), targets.end(), chunk.targetID); + } + }; + + return Cursor( + db::leftJoinBy( + OrphanedChunksJoin(), + this->chunksTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + db::leftJoinBy( + objectID, + fileInodesTable->getInodes(), + fileInodesTable->getTargets()) + | db::groupBy(StripePatternGroup())) + | db::where(ops::isOrphaned) + | db::select(first) + | convertTo() ); +} + +/* + * looks for dir inodes, for which no .cont directory exists + */ +Cursor FsckDB::findInodesWithoutContDir() +{ + return Cursor( + db::leftJoinBy( + objectID, + this->dirInodesTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->contDirsTable->get() ) + | db::where(secondIsNull) + | db::select(first) + | convertTo() ); +} + +/* + * looks for content directories, for which no inode exists + */ +Cursor FsckDB::findOrphanedContDirs() +{ + return Cursor( + db::leftJoinBy( + objectID, + this->contDirsTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->dirInodesTable->get() ) + | db::where(secondIsNull) + | db::select(first) + | convertTo() ); +} + +namespace { +struct FindWrongInodeFileAttribsGrouperDentry +{ + typedef db::EntryID KeyType; + typedef db::FileInode ProjType; + typedef uint64_t GroupType; + + GroupType count; + + FindWrongInodeFileAttribsGrouperDentry() + : count(0) + {} + + KeyType key(std::pair& pair) + { + return pair.first.id; + } + + ProjType project(std::pair& pair) + { + return pair.first; + } + + void step(std::pair& pair) + { + this->count++; + } + + GroupType finish() + { + uint64_t result = this->count; + this->count = 0; + return result; + } +}; + +struct FindWrongInodeFileAttribsGrouperChunks +{ + typedef db::EntryID KeyType; + typedef db::FileInode ProjType; + typedef uint64_t GroupType; + + int64_t chunkSizeSum; + int64_t expectedFileSize; + StripeSet pattern; + std::map fileChunks; + unsigned chunkSize; + + FindWrongInodeFileAttribsGrouperChunks() + { + reset(); + } + + void reset() + { + this->chunkSizeSum = 0; + this->expectedFileSize = 0; + this->pattern.clear(); + this->fileChunks.clear(); + this->chunkSize = 0; + } + + KeyType key(std::pair, db::Chunk*>& pair) + { + return pair.first.first.id; + } + + ProjType project(std::pair, db::Chunk*>& pair) + { + return pair.first.first; + } + + void step(std::pair, db::Chunk*>& pair) + { + db::FileInode& inode = pair.first.first; + db::StripeTargets* targets = pair.first.second; + + if(pair.second == NULL) + { + this->chunkSizeSum = 0; + this->expectedFileSize = 1; + return; + } + + this->chunkSizeSum += pair.second->fileSize; + + this->fileChunks[pair.second->targetID] = *pair.second; + + if(!this->chunkSize) + { + this->chunkSize = inode.chunkSize; + this->expectedFileSize = inode.fileSize; + + pattern.add(inode); + } + + if(targets != NULL) + pattern.add(*targets); + } + + GroupType finish() + { + // chunk file sizes match expected size from inode, don't have to calculate actual file size + // from chunk sizes + if(this->chunkSizeSum == this->expectedFileSize) + { + uint64_t result = this->chunkSizeSum; + reset(); + return result; + } + + // otherwise perform size check + ChunkFileInfoVec chunkFileInfoVec; + + for (auto it = pattern.targets.begin(); it != pattern.targets.end(); it++) + { + std::map::const_iterator chunk = this->fileChunks.find(*it); + + int64_t fileSize = 0; + uint64_t usedBlocks = 0; + + if(chunk != this->fileChunks.end() ) + { + fileSize = chunk->second.getFileSize(); + usedBlocks = chunk->second.getUsedBlocks(); + } + + DynamicFileAttribs fileAttribs(1, fileSize, usedBlocks, 0, 0); + ChunkFileInfo stripeNodeFileInfo(this->chunkSize, MathTk::log2Int32(this->chunkSize), + fileAttribs); + chunkFileInfoVec.push_back(stripeNodeFileInfo); + } + + // now perform the actual file size check; this check is still pretty expensive and + // should be optimized + + // this might be a buddy mirror pattern, but we are not interested in mirror targets + // here, so we just create it as RAID0 pattern + StripePattern* stripePattern = FsckTk::FsckStripePatternToStripePattern( + FsckStripePatternType_RAID0, this->chunkSize, &pattern.targets); + StatData statData; + + statData.setAllFake(); + + statData.updateDynamicFileAttribs(chunkFileInfoVec, stripePattern); + + SAFE_DELETE(stripePattern); + + reset(); + return statData.getFileSize(); + } +}; + +// +struct FindWrongInodeFileAttribsGrouper +{ + typedef db::EntryID KeyType; + typedef FsckFileInode ProjType; + typedef checks::OptionalInodeAttribs GroupType; + + GroupType state; + + FindWrongInodeFileAttribsGrouper() + { + reset(); + } + + void reset() + { + state.reset(); + } + + KeyType key(std::pair& pair) + { + return pair.first.id; + } + + ProjType project(std::pair& pair) + { + return pair.first.toInodeWithoutStripes(); + } + + void step(std::pair& pair) + { + if (pair.second.size) { + if (state.size) + *state.size += *pair.second.size; + else + state.size = pair.second.size; + } + + if (pair.second.nlinks) { + if (state.nlinks) + *state.nlinks += *pair.second.nlinks; + else + state.nlinks = pair.second.nlinks; + } + } + + GroupType finish() + { + GroupType result = state; + reset(); + return result; + } +}; + +struct JoinWithStripedInode +{ + typedef db::EntryID result_type; + + db::EntryID operator()(const db::DirEntry& d) const { return d.id; } + db::EntryID operator()(const db::Chunk& c) const { return c.id; } + + db::EntryID operator()(const std::pair& p) const + { + return p.first.id; + } +}; +} + +/* + * looks for file inodes, for which the saved attribs (e.g. filesize) are not + * equivalent to those of the primary chunks + */ +Cursor > FsckDB::findWrongInodeFileAttribs() +{ + // Note: ignore dentries in disposal as they are not counted in numHardlinks in the inodes + + struct ops + { + static bool fileAttribsIncorrect(std::pair& pair) + { + return pair.first.fileSize != pair.second; + } + + static std::pair fileAttribs( + std::pair& pair) + { + checks::OptionalInodeAttribs attribs = { pair.second, {} }; + return std::make_pair(pair.first, attribs); + } + + static bool linkCountIncorrect(std::pair& pair) + { + return pair.first.numHardlinks != pair.second; + } + + static std::pair linkCount( + std::pair& pair) + { + checks::OptionalInodeAttribs attribs = { {}, pair.second }; + return std::make_pair(pair.first, attribs); + } + + static bool dentryNotInDisposal(db::DirEntry& dentry) + { + return !dentry.parentDirID.isDisposalDir(); + } + }; + + return cursor( + db::unionBy( + firstObjectID, + db::leftJoinBy( + JoinWithStripedInode(), + db::leftJoinBy( + objectID, + this->fileInodesTable->getInodes() + | ignoreByID(this->modificationEventsTable->get() ), + this->fileInodesTable->getTargets() ), + this->chunksTable->get() ) + | db::groupBy(FindWrongInodeFileAttribsGrouperChunks() ) + | db::where(ops::fileAttribsIncorrect) + | db::select(ops::fileAttribs), + db::leftJoinBy( + objectID, + this->fileInodesTable->getInodes() + | ignoreByID(this->modificationEventsTable->get() ), + this->dentryTable->get() + | db::where(ops::dentryNotInDisposal) ) + | db::groupBy(FindWrongInodeFileAttribsGrouperDentry() ) + | db::where(ops::linkCountIncorrect) + | db::select(ops::linkCount) ) + | db::groupBy(FindWrongInodeFileAttribsGrouper() ) ); +} + +namespace { +struct WrongDirInodeGrouper +{ + typedef std::pair InRowType; + + typedef db::EntryID KeyType; + typedef db::DirInode ProjType; + typedef checks::InodeAttribs GroupType; + + GroupType state; + + WrongDirInodeGrouper() + { + memset(&state, 0, sizeof(state) ); + } + + KeyType key(InRowType& row) + { + return row.first.id; + } + + ProjType project(InRowType& row) + { + return row.first; + } + + void step(InRowType& row) + { + if(row.second == NULL) + return; + + this->state.size += 1; + + if(row.second->entryType == FsckDirEntryType_DIRECTORY) + this->state.nlinks += 1; + } + + GroupType finish() + { + GroupType result = this->state; + result.nlinks += 2; // for . and .. + memset(&state, 0, sizeof(state) ); + return result; + } +}; + +struct JoinWrongInodeDirAttribs +{ + typedef db::EntryID result_type; + + db::EntryID operator()(db::DirInode& inode) const { return inode.id; } + db::EntryID operator()(FsckDBDentryTable::ByParent& dentry) const { return dentry.parent; } +}; +} + +/* + * looks for dir inodes, for which the saved attribs (e.g. size) are not correct + */ +Cursor > FsckDB::findWrongInodeDirAttribs() +{ + struct ops + { + static bool dirAttributesIncorrect(std::pair& row) + { + db::DirInode& inode = row.first; + checks::InodeAttribs data = row.second; + + return inode.size != data.size || inode.numHardlinks != data.nlinks; + } + }; + + return cursor( + db::leftJoinBy( + JoinWrongInodeDirAttribs(), + this->dirInodesTable->get() + | ignoreByID(this->modificationEventsTable->get() ) + | db::where(isNotDisposal), + this->dentryTable->getByParent() ) + | db::groupBy(WrongDirInodeGrouper() ) + | db::where(ops::dirAttributesIncorrect) + | convertTo >() ); +} + +namespace { +struct HasMissingTarget +{ + const std::set missingTargets; + const std::set missingBuddyGroups; + + HasMissingTarget(const std::set& missingTargets, + const std::set& missingBuddyGroups) + : missingTargets(missingTargets), missingBuddyGroups(missingBuddyGroups) + {} + + bool operator()(std::pair*>& pair) + { + db::FileInode& inode = pair.second->first; + db::StripeTargets* targets = pair.second->second; + + const std::set* missingTargets = NULL; + + switch(inode.stripePatternType) + { + case FsckStripePatternType_RAID0: + missingTargets = &this->missingTargets; + break; + + case FsckStripePatternType_BUDDYMIRROR: + missingTargets = &this->missingBuddyGroups; + break; + + default: + return false; + } + + unsigned inodeStripes = std::min(inode.stripePatternSize, inode.NTARGETS); + for(unsigned i = 0; i < inodeStripes; i++) + { + if(missingTargets->count(inode.targets[i]) ) + return true; + } + + if(targets) + { + unsigned limit = std::min(targets->NTARGETS, + inode.stripePatternSize - targets->firstTargetIndex); + + for(unsigned i = 0; i < limit; i++) + { + if(missingTargets->count(targets->targets[i]) ) + return true; + } + } + + return false; + } +}; +} + +/* + * looks for file inodes, which have a stripe target set, which does not exist + */ +Cursor FsckDB::findFilesWithMissingStripeTargets(TargetMapper* targetMapper, + MirrorBuddyGroupMapper* buddyGroupMapper) +{ + FsckTargetIDList usedTargets; + std::set missingTargets; + std::set missingBuddyGroups; + + { + SetFragmentCursor c = this->usedTargetIDsTable->get(); + while(c.step() ) + usedTargets.push_back(*c.get() ); + } + + for(FsckTargetIDListIter it = usedTargets.begin(); it != usedTargets.end(); ++it) + { + switch(it->getTargetIDType() ) + { + case FsckTargetIDType_TARGET: + if(!targetMapper->targetExists(it->getID() ) ) + missingTargets.insert(it->getID() ); + break; + + case FsckTargetIDType_BUDDYGROUP: { + MirrorBuddyGroup group = buddyGroupMapper->getMirrorBuddyGroup(it->getID() ); + if(group.firstTargetID == 0 && group.secondTargetID == 0) + missingBuddyGroups.insert(it->getID() ); + + break; + } + } + } + + return cursor( + joinBy( + JoinWithStripedInode(), + this->dentryTable->get(), + db::leftJoinBy( + objectID, + this->fileInodesTable->getInodes(), + this->fileInodesTable->getTargets() ) ) + | db::where(HasMissingTarget(missingTargets, missingBuddyGroups) ) + | db::select(first) + | db::distinctBy(objectID) ); +} + +/* + * looks for chunks, which have wrong owner/group + * + * important for quotas + */ +Cursor > FsckDB::findChunksWithWrongPermissions() +{ + struct ops + { + static bool chunkHasWrongPermissions(std::pair& pair) + { + return pair.first.uid != pair.second->uid + || pair.first.gid != pair.second->gid; + } + + static std::pair result(std::pair& pair) + { + return std::make_pair(pair.first, pair.second->toInodeWithoutStripes() ); + } + }; + + return cursor( + joinBy( + objectID, + this->chunksTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->fileInodesTable->getInodes() ) + | db::where(ops::chunkHasWrongPermissions) + | db::select(ops::result) ); +} + +/* + * looks for chunks, which are saved in the wrong place + */ +Cursor > FsckDB::findChunksInWrongPath() +{ + struct ops + { + static bool chunkInWrongPath(std::pair& pair) + { + db::Chunk& chunk = pair.first; + db::FileInode& file = *pair.second; + + return chunk.savedPath != + DatabaseTk::calculateExpectedChunkPath(chunk.id.str(), file.origParentUID, + file.origParentEntryID.str(), file.pathInfoFlags); + } + + static std::pair result(std::pair& pair) + { + return std::make_pair(pair.first, pair.second->toInodeWithoutStripes() ); + } + }; + + return cursor( + joinBy( + objectID, + this->chunksTable->get() + | ignoreByID(this->modificationEventsTable->get() ), + this->fileInodesTable->getInodes() ) + | db::where(ops::chunkInWrongPath) + | db::select(ops::result) ); +} + +/** + * Find file inode(s) which are inlined and have linkCount > 1 (i.e. old styled links) + */ +Cursor FsckDB::findFilesWithMultipleHardlinks() +{ + struct ops + { + static bool hasMultipleHardlinks(db::FileInode& inode) + { + return (inode.numHardlinks > 1 && inode.isInlined); + } + }; + + return cursor( + fileInodesTable->getInodes() + | ignoreByID(modificationEventsTable->get()) + | db::where(ops::hasMultipleHardlinks)); +} diff --git a/fsck/source/database/FsckDBException.cpp b/fsck/source/database/FsckDBException.cpp new file mode 100644 index 0000000..68c25d1 --- /dev/null +++ b/fsck/source/database/FsckDBException.cpp @@ -0,0 +1,2 @@ +#include "FsckDBException.h" + diff --git a/fsck/source/database/FsckDBException.h b/fsck/source/database/FsckDBException.h new file mode 100644 index 0000000..fba2d30 --- /dev/null +++ b/fsck/source/database/FsckDBException.h @@ -0,0 +1,8 @@ +#ifndef FSCKDBEXCEPTION_H +#define FSCKDBEXCEPTION_H + +#include + +DECLARE_NAMEDEXCEPTION(FsckDBException, "FsckDBException") + +#endif /*FSCKDBEXCEPTION_H*/ diff --git a/fsck/source/database/FsckDBTable.cpp b/fsck/source/database/FsckDBTable.cpp new file mode 100644 index 0000000..7162ea1 --- /dev/null +++ b/fsck/source/database/FsckDBTable.cpp @@ -0,0 +1,509 @@ +#include "FsckDBTable.h" + +#include +#include + +#include + +static db::DirEntry fsckDirEntryToDbDirEntry(const FsckDirEntry& dentry) +{ + db::DirEntry result = { + db::EntryID::fromStr(dentry.getID() ), + db::EntryID::fromStr(dentry.getParentDirID() ), + + dentry.getEntryOwnerNodeID().val(), + dentry.getInodeOwnerNodeID().val(), + + dentry.getSaveNodeID().val(), dentry.getSaveDevice(), dentry.getSaveInode(), + + {}, + + dentry.getInternalID(), + + dentry.getEntryType(), + dentry.getName().size() < sizeof(result.name.inlined.text), + dentry.getHasInlinedInode(), + dentry.getIsBuddyMirrored(), + }; + + if(result.fileNameInlined) { + ::memset(result.name.inlined.text, '\0', sizeof(result.name.inlined.text)); + ::strncpy(result.name.inlined.text, dentry.getName().c_str(), + sizeof(result.name.inlined.text) - 1); + } + + return result; +} + +void FsckDBDentryTable::insert(FsckDirEntryList& dentries, const BulkHandle* handle) +{ + NameBuffer& names = handle ? *handle->nameBuffer : getNameBuffer(0); + + for(FsckDirEntryListIter it = dentries.begin(), end = dentries.end(); it != end; ++it) + { + db::DirEntry dentry = fsckDirEntryToDbDirEntry(*it); + ByParent link = { dentry.parentDirID, dentry.id, dentry.entryType }; + + if(!dentry.fileNameInlined) + { + dentry.name.extended.fileID = names.id(); + dentry.name.extended.fileOffset = names.put(it->getName() ); + } + + if(handle) + { + handle->dentries->append(dentry); + handle->byParent->append(link); + } + else + { + this->table.insert(dentry); + this->byParent.insert(link); + } + } +} + +void FsckDBDentryTable::updateFieldsExceptParent(FsckDirEntryList& dentries) +{ + for(FsckDirEntryListIter it = dentries.begin(), end = dentries.end(); it != end; ++it) + { + db::DirEntry dentry = fsckDirEntryToDbDirEntry(*it); + + this->table.remove(dentry.pkey() ); + this->table.insert(dentry); + } +} + +void FsckDBDentryTable::remove(FsckDirEntryList& dentries) +{ + for(FsckDirEntryListIter it = dentries.begin(), end = dentries.end(); it != end; ++it) + this->table.remove(fsckDirEntryToDbDirEntry(*it).pkey() ); +} + +Table::QueryType FsckDBDentryTable::get() +{ + this->table.commitChanges(); + return this->table.cursor(); +} + +Table::QueryType FsckDBDentryTable::getByParent() +{ + this->byParent.commitChanges(); + return this->byParent.cursor(); +} + +std::pair FsckDBDentryTable::getAnyFor(db::EntryID id) +{ + this->table.commitChanges(); + + struct ops + { + static db::EntryID onlyID(const db::DirEntry::KeyType& key) { return boost::get<0>(key); } + }; + + return this->table.getByKeyProjection(id, ops::onlyID); +} + +std::string FsckDBDentryTable::getNameOf(const db::DirEntry& dentry) +{ + if(dentry.fileNameInlined) + return dentry.name.inlined.text; + + return getNameBuffer(dentry.name.extended.fileID).get(dentry.name.extended.fileOffset); +} + +std::string FsckDBDentryTable::getPathOf(const db::DirEntry& dentry) +{ + std::string result = getNameOf(dentry); + + db::EntryID parent = dentry.parentDirID; + unsigned depth = 0; + + while(!parent.isSpecial() ) + { + depth++; + if(depth > 255) + return "[]/" + result; + + const NameCacheEntry* cacheEntry = getFromCache(parent); + if(cacheEntry) + { + result = cacheEntry->name + "/" + result; + parent = cacheEntry->parent; + continue; + } + + std::pair item = getAnyFor(parent); + if(!item.first) + return "[]/" + result; + + std::string parentName = getNameOf(item.second); + + result = parentName + "/" + result; + parent = item.second.parentDirID; + + addToCache(item.second, parentName); + } + + return getNameOf(parent) + "/" + result; +} + + + +void FsckDBFileInodesTable::insert(FsckFileInodeList& fileInodes, const BulkHandle* handle) +{ + for(FsckFileInodeListIter it = fileInodes.begin(), end = fileInodes.end(); it != end; ++it) + { + const std::vector& stripes = it->getStripeTargets(); + + db::FileInode inode = { + db::EntryID::fromStr(it->getID() ), + db::EntryID::fromStr(it->getParentDirID() ), + db::EntryID::fromStr(it->getPathInfo()->getOrigParentEntryID() ), + + it->getParentNodeID().val(), + it->getSaveNodeID().val(), + it->getPathInfo()->getOrigUID(), + + it->getUserID(), it->getGroupID(), + + (uint64_t) it->getFileSize(), it->getUsedBlocks(), + + it->getNumHardLinks(), + + it->getSaveInode(), + it->getSaveDevice(), + + it->getChunkSize(), + + {}, + + it->getIsInlined(), + uint32_t(it->getPathInfo()->getFlags() ), + it->getStripePatternType(), + uint32_t(stripes.size() ), + it->getReadable(), + it->getIsBuddyMirrored(), + it->getIsMismirrored(), + }; + + db::StripeTargets extraTargets = { inode.id, {}, 0 }; + + for(size_t i = 0; i < stripes.size(); i++) + { + if(i < inode.NTARGETS) + inode.targets[i] = stripes[i]; + else + { + size_t offsetInPattern = (i - inode.NTARGETS) % extraTargets.NTARGETS; + + if(offsetInPattern == 0) + extraTargets.firstTargetIndex = i; + + extraTargets.targets[offsetInPattern] = stripes[i]; + + if(offsetInPattern == extraTargets.NTARGETS - 1) + { + if(handle) + std::get<1>(*handle)->append(extraTargets); + else + this->targets.insert(extraTargets); + + extraTargets.firstTargetIndex = 0; + } + } + } + + if(handle) + { + std::get<0>(*handle)->append(inode); + if(extraTargets.firstTargetIndex != 0) + std::get<1>(*handle)->append(extraTargets); + } + else + { + this->inodes.insert(inode); + if(extraTargets.firstTargetIndex != 0) + this->targets.insert(extraTargets); + } + } +} + +void FsckDBFileInodesTable::update(FsckFileInodeList& inodes) +{ + for(FsckFileInodeListIter it = inodes.begin(), end = inodes.end(); it != end; ++it) + { + FsckFileInodeList list(1, *it); + + remove(list); + insert(list); + } +} + +void FsckDBFileInodesTable::remove(FsckFileInodeList& fileInodes) +{ + for(FsckFileInodeListIter it = fileInodes.begin(), end = fileInodes.end(); it != end; ++it) + { + this->inodes.remove(db::EntryID::fromStr(it->getID() ) ); + this->targets.remove(db::EntryID::fromStr(it->getID() ) ); + } +} + +FsckDBFileInodesTable::InodesQueryType FsckDBFileInodesTable::getInodes() +{ + this->inodes.commitChanges(); + return + this->inodes.cursor() + | db::groupBy(UniqueInlinedInode() ) + | db::select(SelectFirst() ); +} + +Table::QueryType FsckDBFileInodesTable::getTargets() +{ + this->targets.commitChanges(); + return this->targets.cursor(); +} + +UInt16Vector FsckDBFileInodesTable::getStripeTargetsByKey(const db::EntryID& id) +{ + UInt16Vector allStripeTargets; + std::pair result = this->get(id.str()); + + if (!result.first) + return allStripeTargets; + + // firstly copy stripeTargets from FileInode object + auto inode = result.second; + uint32_t targetArrSize = inode.NTARGETS; + uint32_t numTargets = (inode.stripePatternSize > targetArrSize) ? targetArrSize + : inode.stripePatternSize; + + std::copy(inode.targets, inode.targets + numTargets, std::back_inserter(allStripeTargets)); + + // if extraTargets are present then get them from targets table + uint32_t numExtraTargets = inode.stripePatternSize - numTargets; + if (numExtraTargets) + { + this->targets.commitChanges(); + auto dbRes = this->targets.getAllByKey(id); + + while (dbRes.step()) + { + auto elem = dbRes.get(); + + for (int i=0; iNTARGETS; i++) + { + allStripeTargets.push_back(elem->targets[i]); + numExtraTargets -= 1; + if (numExtraTargets == 0) break; + } + + if (numExtraTargets == 0) break; + } + } + + return allStripeTargets; +} + +std::pair FsckDBFileInodesTable::get(std::string id) +{ + this->inodes.commitChanges(); + return this->inodes.getByKey(db::EntryID::fromStr(id) ); +} + + + +void FsckDBDirInodesTable::insert(FsckDirInodeList& dirInodes, const BulkHandle* handle) +{ + for(FsckDirInodeListIter it = dirInodes.begin(), end = dirInodes.end(); it != end; ++it) + { + db::DirInode inode = { + db::EntryID::fromStr(it->getID() ), + db::EntryID::fromStr(it->getParentDirID() ), + + it->getParentNodeID().val(), + it->getOwnerNodeID().val(), + it->getSaveNodeID().val(), + + it->getStripePatternType(), + it->getReadable(), + it->getIsBuddyMirrored(), + it->getIsMismirrored(), + + (uint64_t) it->getSize(), + it->getNumHardLinks(), + }; + + if(handle) + (*handle)->append(inode); + else + this->table.insert(inode); + } +} + +void FsckDBDirInodesTable::update(FsckDirInodeList& inodes) +{ + for(FsckDirInodeListIter it = inodes.begin(), end = inodes.end(); it != end; ++it) + { + FsckDirInodeList list(1, *it); + + remove(list); + insert(list); + } +} + +void FsckDBDirInodesTable::remove(FsckDirInodeList& dirInodes) +{ + for(FsckDirInodeListIter it = dirInodes.begin(), end = dirInodes.end(); it != end; ++it) + this->table.remove(db::EntryID::fromStr(it->getID() ) ); +} + +Table::QueryType FsckDBDirInodesTable::get() +{ + this->table.commitChanges(); + return this->table.cursor(); +} + +std::pair FsckDBDirInodesTable::get(std::string id) +{ + this->table.commitChanges(); + return this->table.getByKey(db::EntryID::fromStr(id) ); +} + + + +static db::Chunk fsckChunkToDbChunk(FsckChunk& chunk) +{ + db::Chunk result = { + db::EntryID::fromStr(chunk.getID() ), + chunk.getTargetID(), chunk.getBuddyGroupID(), + {}, + chunk.getFileSize(), chunk.getUsedBlocks(), + chunk.getUserID(), chunk.getGroupID(), + }; + + strncpy(result.savedPath, chunk.getSavedPath()->str().c_str(), + sizeof(result.savedPath) - 1); + + return result; +} + +void FsckDBChunksTable::insert(FsckChunkList& chunks, const BulkHandle* handle) +{ + for(FsckChunkListIter it = chunks.begin(), end = chunks.end(); it != end; ++it) + { + if(handle) + (*handle)->append(fsckChunkToDbChunk(*it) ); + else + this->table.insert(fsckChunkToDbChunk(*it) ); + } +} + +void FsckDBChunksTable::update(FsckChunkList& chunks) +{ + for(FsckChunkListIter it = chunks.begin(), end = chunks.end(); it != end; ++it) + { + db::Chunk chunk = fsckChunkToDbChunk(*it); + + this->table.remove(chunk.pkey() ); + this->table.insert(chunk); + } +} + +void FsckDBChunksTable::remove(const db::Chunk::KeyType& id) +{ + this->table.remove(id); +} + +Table::QueryType FsckDBChunksTable::get() +{ + this->table.commitChanges(); + return this->table.cursor(); +} + + + +static db::ContDir fsckContDirToDbContDir(FsckContDir& contDir) +{ + return { + db::EntryID::fromStr(contDir.getID()), + contDir.getSaveNodeID().val(), + contDir.getIsBuddyMirrored(), + }; +} + +void FsckDBContDirsTable::insert(FsckContDirList& contDirs, const BulkHandle* handle) +{ + for(FsckContDirListIter it = contDirs.begin(), end = contDirs.end(); it != end; ++it) + { + if(handle) + (*handle)->append(fsckContDirToDbContDir(*it) ); + else + this->table.insert(fsckContDirToDbContDir(*it) ); + } +} + +Table::QueryType FsckDBContDirsTable::get() +{ + this->table.commitChanges(); + return this->table.cursor(); +} + + + +static db::FsID fsckFsIDToDbFsID(FsckFsID& id) +{ + return { + db::EntryID::fromStr(id.getID() ), + db::EntryID::fromStr(id.getParentDirID() ), + id.getSaveNodeID().val(), id.getSaveDevice(), id.getSaveInode(), + id.getIsBuddyMirrored(), + }; +} + +void FsckDBFsIDsTable::insert(FsckFsIDList& fsIDs, const BulkHandle* handle) +{ + for(FsckFsIDListIter it = fsIDs.begin(), end = fsIDs.end(); it != end; ++it) + { + if(handle) + (*handle)->append(fsckFsIDToDbFsID(*it) ); + else + this->table.insert(fsckFsIDToDbFsID(*it) ); + } +} + +void FsckDBFsIDsTable::remove(FsckFsIDList& fsIDs) +{ + for(FsckFsIDListIter it = fsIDs.begin(), end = fsIDs.end(); it != end; ++it) + this->table.remove(fsckFsIDToDbFsID(*it).pkey() ); +} + +Table::QueryType FsckDBFsIDsTable::get() +{ + this->table.commitChanges(); + return this->table.cursor(); +} + + + +void FsckDBUsedTargetIDsTable::insert(FsckTargetIDList& targetIDs, const BulkHandle& handle) +{ + for(FsckTargetIDListIter it = targetIDs.begin(), end = targetIDs.end(); it != end; ++it) + { + db::UsedTarget target = { it->getID(), it->getTargetIDType() }; + handle->append(target); + } +} + + + +void FsckDBModificationEventsTable::insert(const FsckModificationEventList& events, + const BulkHandle& handle) +{ + for(std::list::const_iterator it = events.begin(), end = events.end(); + it != end; ++it) + { + db::ModificationEvent event = { db::EntryID::fromStr(it->getEntryID() ) }; + handle->append(event); + } +} diff --git a/fsck/source/database/FsckDBTable.h b/fsck/source/database/FsckDBTable.h new file mode 100644 index 0000000..4cd1c61 --- /dev/null +++ b/fsck/source/database/FsckDBTable.h @@ -0,0 +1,641 @@ +#ifndef FsckDBTable_h_drBbZVcUQetDB8UVnMd1Uj +#define FsckDBTable_h_drBbZVcUQetDB8UVnMd1Uj + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class FsckDBDentryTable +{ + public: + struct ByParent + { + db::EntryID parent; /* 12 */ + db::EntryID target; /* 24 */ + uint32_t entryType; /* 28 */ + + typedef std::pair KeyType; + + KeyType pkey() const { return KeyType(parent, target); } + }; + + class NameBuffer + { + public: + NameBuffer(const std::string& path, unsigned id): + fileID(id) + { + fd = ::open(path.c_str(), O_RDWR | O_CREAT, 0660); + if(fd < 0) + throw std::runtime_error("could not open name file"); + + streamBuf.reserve(4096 * 1024); + + writePos = ::lseek(fd, 0, SEEK_END); + if (writePos < 0) + { + close(fd); + fd = -1; + throw std::runtime_error("could not open name file"); + } + } + + NameBuffer(NameBuffer&&) = delete; + NameBuffer& operator=(NameBuffer&&) = delete; + + ~NameBuffer() + { + if (fd >= 0) + { + flush(); + close(fd); + } + } + + uint64_t put(const std::string& name) + { + if (streamBuf.size() + name.size() + 1 > streamBuf.capacity()) + flush(); + + if (name.size() > streamBuf.capacity()) + streamBuf.reserve(name.size()); + + uint64_t result = writePos + streamBuf.size(); + streamBuf.insert(streamBuf.end(), name.begin(), name.end()); + streamBuf.push_back(0); + + return result; + } + + std::string get(uint64_t offset) + { + flush(); + + char buffer[255 + 1]; + ssize_t readRes = ::pread(fd, buffer, sizeof(buffer), offset); + // if the read did not fill the full buffer, we can + // a) have an error, or + // b) have a short read. + // errors should be reported directly. short reads are likely caused be trying to + // read past the end of the file. since we cannot possible have a sane read that + // stretches beyond writePos here (as we have flushed the write buffer), we can + // reliably detect both. + if (readRes < 0 || ssize_t(offset) + readRes > writePos) + throw std::runtime_error("could not read name file: " + std::string(strerror(errno))); + + return buffer; + } + + unsigned id() const { return fileID; } + + private: + unsigned fileID; + std::vector streamBuf; + + int fd; + ssize_t writePos; + + void flush() + { + ssize_t writeRes = ::pwrite(fd, &streamBuf[0], streamBuf.size(), writePos); + if (writeRes < 0 || size_t(writeRes) < streamBuf.size()) + throw std::runtime_error("error in flush: " + std::string(strerror(errno))); + + writePos += writeRes; + streamBuf.resize(0); + } + }; + + struct BulkHandle + { + boost::shared_ptr > dentries; + boost::shared_ptr > byParent; + NameBuffer* nameBuffer; + }; + + private: + struct NameCacheEntry + { + db::EntryID id; + db::EntryID parent; + std::string name; + + std::list::iterator lruLink; + }; + + public: + FsckDBDentryTable(const std::string& dbPath, size_t fragmentSize, size_t nameCacheLimit, + bool allowCreate) + : dbPath(dbPath), + table(dbPath + "/dentries", fragmentSize, allowCreate), + byParent(dbPath + "/dentriesbyparent", fragmentSize, allowCreate), + insertSeqNo(0), + nameCacheLimit(nameCacheLimit) + { + } + + void insert(FsckDirEntryList& dentries, const BulkHandle* handle = NULL); + void updateFieldsExceptParent(FsckDirEntryList& dentries); + void remove(FsckDirEntryList& dentries); + + Table::QueryType get(); + std::pair getAnyFor(db::EntryID id); + + Table::QueryType getByParent(); + + std::string getNameOf(const db::DirEntry& dentry); + std::string getPathOf(const db::DirEntry& dentry); + + private: + std::string dbPath; + Table table; + Table byParent; + + std::map > nameBuffers; + uint64_t insertSeqNo; + + std::map nameCache; + std::list nameCacheLRU; + size_t nameCacheLimit; + + NameBuffer& getNameBuffer(unsigned id) + { + if(this->nameBuffers.count(id) ) + return *this->nameBuffers[id]; + + const std::string& nextNameFile = dbPath + "/dentrynames." + + StringTk::intToStr(id); + + boost::shared_ptr buf = boost::make_shared( + nextNameFile, id); + this->nameBuffers[id] = buf; + return *buf; + } + + void addToCache(const db::DirEntry& entry, const std::string& name) + { + if(nameCache.size() >= nameCacheLimit) + { + nameCache.erase(nameCacheLRU.front()->id); + nameCacheLRU.pop_front(); + } + + NameCacheEntry cacheEntry = { entry.id, entry.parentDirID, name, nameCacheLRU.end() }; + std::pair::iterator, bool> pos = nameCache.insert( + std::make_pair(entry.id, cacheEntry) ); + + if(!pos.second) + return; + + pos.first->second.lruLink = nameCacheLRU.insert(nameCacheLRU.end(), &pos.first->second); + } + + const NameCacheEntry* getFromCache(db::EntryID id) + { + std::map::iterator pos = nameCache.find(id); + if(pos == nameCache.end() ) + return NULL; + + nameCacheLRU.splice(nameCacheLRU.end(), nameCacheLRU, pos->second.lruLink); + return &pos->second; + } + + public: + void clear() + { + this->table.clear(); + this->byParent.clear(); + this->insertSeqNo = 0; + this->nameCache.clear(); + this->nameCacheLRU.clear(); + } + + BulkHandle newBulkHandle() + { + BulkHandle result = { + this->table.bulkInsert(), + this->byParent.bulkInsert(), + &getNameBuffer(this->nameBuffers.size() ), + }; + return result; + } + + void flush(BulkHandle& handle) + { + handle = {{}, {}, nullptr}; + } + + void insert(FsckDirEntryList& dentries, const BulkHandle& handle) + { + insert(dentries, &handle); + } + + std::string getNameOf(db::EntryID id) + { + if(id == db::EntryID::anchor() || id == db::EntryID::root() ) + return ""; + else + if(id == db::EntryID::disposal() ) + return "[]"; + else + if(id == db::EntryID::mdisposal() ) + return "[]"; + + std::pair item = getAnyFor(id); + if(!item.first) + return "[]"; + else + return getNameOf(item.second); + } + + std::string getPathOf(db::EntryID id) + { + std::pair item = getAnyFor(id); + if(!item.first) + return "[]"; + else + return getPathOf(item.second); + } + + void commitChanges() + { + table.commitChanges(); + } +}; + +class FsckDBFileInodesTable +{ + public: + typedef std::tuple< + boost::shared_ptr >, + boost::shared_ptr > > BulkHandle; + + struct UniqueInlinedInode + { + typedef boost::tuple KeyType; + typedef db::FileInode ProjType; + typedef int GroupType; + + KeyType key(const db::FileInode& fi) + { + return boost::make_tuple(fi.id, fi.saveInode, fi.saveNodeID, fi.saveDevice); + } + + db::FileInode project(const db::FileInode& fi) + { + return fi; + } + + void step(const db::FileInode& fi) {} + + GroupType finish() + { + return 0; + } + }; + + struct SelectFirst + { + typedef db::FileInode result_type; + + db::FileInode operator()(std::pair& pair) const + { + return pair.first; + } + }; + + typedef + Select< + Group< + Table::QueryType, + UniqueInlinedInode>, + SelectFirst> InodesQueryType; + + public: + FsckDBFileInodesTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : inodes(dbPath + "/fileinodes", fragmentSize, allowCreate), + targets(dbPath + "/filetargets", fragmentSize, allowCreate) + { + } + + void insert(FsckFileInodeList& fileInodes, const BulkHandle* handle = NULL); + void update(FsckFileInodeList& inodes); + void remove(FsckFileInodeList& fileInodes); + + InodesQueryType getInodes(); + Table::QueryType getTargets(); + UInt16Vector getStripeTargetsByKey(const db::EntryID& id); + + std::pair get(std::string id); + + private: + Table inodes; + Table targets; + + public: + void clear() + { + inodes.clear(); + targets.clear(); + } + + void remove(db::EntryID id) + { + inodes.remove(id); + targets.remove(id); + } + + BulkHandle newBulkHandle() + { + return BulkHandle(this->inodes.bulkInsert(), this->targets.bulkInsert() ); + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + void insert(FsckFileInodeList& fileInodes, const BulkHandle& handle) + { + insert(fileInodes, &handle); + } + + void commitChanges() + { + inodes.commitChanges(); + targets.commitChanges(); + } +}; + +class FsckDBDirInodesTable +{ + public: + typedef boost::shared_ptr > BulkHandle; + + public: + FsckDBDirInodesTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : table(dbPath + "/dirinodes", fragmentSize, allowCreate) + { + } + + void insert(FsckDirInodeList& dirInodes, const BulkHandle* handle = NULL); + void update(FsckDirInodeList& inodes); + void remove(FsckDirInodeList& dirInodes); + + Table::QueryType get(); + std::pair get(std::string id); + + private: + Table table; + + public: + void clear() + { + this->table.clear(); + } + + BulkHandle newBulkHandle() + { + return this->table.bulkInsert(); + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + void insert(FsckDirInodeList& fileInodes, const BulkHandle& handle) + { + insert(fileInodes, &handle); + } + + void commitChanges() + { + table.commitChanges(); + } +}; + +class FsckDBChunksTable +{ + public: + typedef boost::shared_ptr > BulkHandle; + + public: + FsckDBChunksTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : table(dbPath + "/chunks", fragmentSize, allowCreate) + { + } + + void insert(FsckChunkList& chunks, const BulkHandle* handle = NULL); + void update(FsckChunkList& chunks); + void remove(const db::Chunk::KeyType& id); + + Table::QueryType get(); + + private: + Table table; + + public: + void clear() + { + table.clear(); + } + + BulkHandle newBulkHandle() + { + return this->table.bulkInsert(); + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + void insert(FsckChunkList& chunks, const BulkHandle& handle) + { + insert(chunks, &handle); + } + + void commitChanges() + { + table.commitChanges(); + } +}; + +class FsckDBContDirsTable +{ + public: + typedef boost::shared_ptr > BulkHandle; + + public: + FsckDBContDirsTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : table(dbPath + "/contdirs", fragmentSize, allowCreate) + { + } + + void insert(FsckContDirList& contDirs, const BulkHandle* handle = NULL); + + Table::QueryType get(); + + private: + Table table; + + public: + void clear() + { + table.clear(); + } + + BulkHandle newBulkHandle() + { + return this->table.bulkInsert(); + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + void insert(FsckContDirList& contDirs, const BulkHandle& handle) + { + insert(contDirs, &handle); + } + + void commitChanges() + { + table.commitChanges(); + } +}; + +class FsckDBFsIDsTable +{ + public: + typedef boost::shared_ptr > BulkHandle; + + public: + FsckDBFsIDsTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : table(dbPath + "/fsids", fragmentSize, allowCreate) + { + } + + void insert(FsckFsIDList& fsIDs, const BulkHandle* handle = NULL); + void remove(FsckFsIDList& fsIDs); + + Table::QueryType get(); + + private: + Table table; + + public: + void clear() + { + this->table.clear(); + } + + BulkHandle newBulkHandle() + { + return this->table.bulkInsert(); + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + void insert(FsckFsIDList& fsIDs, const BulkHandle& handle) + { + insert(fsIDs, &handle); + } + + void commitChanges() + { + table.commitChanges(); + } +}; + +template +class FsckUniqueTable +{ + public: + typedef boost::shared_ptr > BulkHandle; + + protected: + FsckUniqueTable(const std::string& name, size_t bufferSize, bool allowCreate) + : set(name, allowCreate), bufferSize(bufferSize) + {} + + private: + Set set; + size_t bufferSize; + std::vector > > bulkHandles; + + public: + void clear() + { + set.clear(); + bulkHandles.clear(); + } + + BulkHandle newBulkHandle() + { + BulkHandle result(new Buffer(set, bufferSize) ); + bulkHandles.push_back(result); + return result; + } + + void flush(BulkHandle& handle) + { + handle = {}; + } + + SetFragmentCursor get() + { + if(!bulkHandles.empty() ) + { + for(size_t i = 0; i < bulkHandles.size(); i++) + { + if(!bulkHandles[i].expired() ) + throw std::runtime_error("still has open bulk handles"); + } + + bulkHandles.clear(); + set.makeUnique(); + } + + return set.cursor(); + } +}; + +class FsckDBUsedTargetIDsTable : public FsckUniqueTable +{ + public: + FsckDBUsedTargetIDsTable(const std::string& dbPath, size_t fragmentSize, bool allowCreate) + : FsckUniqueTable(dbPath + "/usedtargets", fragmentSize, allowCreate) + { + } + + void insert(FsckTargetIDList& targetIDs, const BulkHandle& handle); +}; + +class FsckDBModificationEventsTable : public FsckUniqueTable +{ + public: + FsckDBModificationEventsTable(const std::string& dbPath, size_t fragmentSize, + bool allowCreate) + : FsckUniqueTable(dbPath + "/modevents", fragmentSize, allowCreate) + { + } + + void insert(const FsckModificationEventList& events, const BulkHandle& handle); +}; + +#endif diff --git a/fsck/source/database/Group.h b/fsck/source/database/Group.h new file mode 100644 index 0000000..3e9ccd1 --- /dev/null +++ b/fsck/source/database/Group.h @@ -0,0 +1,131 @@ +#ifndef GROUP_H_ +#define GROUP_H_ + +#include + +#include + +/* + * requires Ops : struct { + * typedef KeyType; + * typedef ProjType; + * typedef GroupType; + * + * KeyType key(Source::ElementType&); + * ProjType project(Source::ElementType&); + * void step(Source::ElementType&); + * + * GroupType finish(); + * } + * + * yields sequence (Source::ElementType, ) + */ +template +class Group +{ + private: + typedef typename Ops::KeyType KeyType; + typedef typename Ops::ProjType ProjType; + typedef typename Ops::GroupType GroupType; + + public: + typedef std::pair ElementType; + typedef typename Source::MarkerType MarkerType; + + public: + Group(Source source, Ops ops) + : source(source), ops(ops), sourceIsActive(false) + { + } + + bool step() + { + if(!this->sourceIsActive && !this->source.step() ) + return false; + + groupCurrentSet(); + return true; + } + + ElementType* get() + { + return ¤t; + } + + MarkerType mark() const + { + return this->currentMark; + } + + void restore(MarkerType mark) + { + this->source.restore(mark); + groupCurrentSet(); + } + + private: + void groupCurrentSet() + { + typename Source::ElementType* row = this->source.get(); + + this->currentMark = this->source.mark(); + this->current.first = this->ops.project(*row); + this->ops.step(*row); + + KeyType currentKey = this->ops.key(*row); + + while(true) + { + if(!this->source.step() ) + { + this->sourceIsActive = false; + break; + } + + row = this->source.get(); + + if(!(currentKey == this->ops.key(*row) ) ) + { + this->sourceIsActive = true; + break; + } + + this->ops.step(*row); + } + + this->current.second = this->ops.finish(); + } + + private: + Source source; + Ops ops; + ElementType current; + typename Source::MarkerType currentMark; + bool sourceIsActive; +}; + + +namespace db { + +template +struct GroupOp +{ + Ops ops; + + template + friend Group operator|(Source source, const GroupOp& fn) + { + return Group(source, fn.ops); + } +}; + +template +inline GroupOp groupBy(Ops ops) +{ + GroupOp result = {ops}; + return result; +} + +} + +#endif diff --git a/fsck/source/database/LeftJoinEq.h b/fsck/source/database/LeftJoinEq.h new file mode 100644 index 0000000..ad0f980 --- /dev/null +++ b/fsck/source/database/LeftJoinEq.h @@ -0,0 +1,189 @@ +#ifndef LEFTJOINEQ_H_ +#define LEFTJOINEQ_H_ + +#include + +#include +#include + +#include +#include + +template +class LeftJoinEq +{ + private: + enum State { + s_next_left, + s_first_right, + s_next_right, + s_only_left, + }; + + typedef typename boost::decay< + typename boost::result_of::type + >::type LeftKey; + typedef typename boost::decay< + typename boost::result_of::type + >::type RightKey; + + public: + typedef std::pair ElementType; + + struct MarkerType + { + typename Left::MarkerType leftMark; + typename Right::MarkerType rightMark; + State state; + + bool hasRightMark; + typename Right::MarkerType innerRightMark; + }; + + public: + LeftJoinEq(Left left, Right right, KeyExtract keyExtract) + : left(left), right(right), keyExtract(keyExtract), state(s_next_left), + hasRightMark(false) + { + if(!this->right.step() ) + this->state = s_only_left; + } + + bool step() + { + switch(this->state) + { + case s_only_left: + if(!this->left.step() ) + return false; + + return makeCurrent(false); + + case s_next_left: + if(!this->left.step() ) + { + this->state = s_only_left; + return false; + } + + if(this->hasRightMark && + this->keyExtract(*this->left.get() ) == this->rightKeyAtMark) + this->right.restore(this->rightMark); + else + this->hasRightMark = false; + + BEEGFS_FALLTHROUGH; + + case s_first_right: { + while(rightKey() < leftKey() ) + { + if(!this->right.step() ) + { + this->state = s_only_left; + return makeCurrent(false); + } + } + + if(leftKey() == rightKey() ) + { + this->state = s_next_right; + this->hasRightMark = true; + this->rightKeyAtMark = rightKey(); + this->rightMark = this->right.mark(); + return makeCurrent(true); + } + + this->state = s_next_left; + return makeCurrent(false); + } + + case s_next_right: { + if(!this->right.step() ) + { + this->state = s_next_left; + return step(); + } + + if(leftKey() == rightKey() ) + { + this->state = s_next_right; + return makeCurrent(true); + } + + this->state = s_next_left; + return step(); + } + } + + return false; + } + + ElementType* get() + { + return ¤t; + } + + MarkerType mark() const + { + MarkerType result = { this->left.mark(), this->right.mark(), this->state, + this->hasRightMark, this->rightMark }; + return result; + } + + void restore(MarkerType mark) + { + this->hasRightMark = mark.hasRightMark; + if(mark.hasRightMark) + { + this->rightMark = mark.innerRightMark; + this->right.restore(mark.innerRightMark); + this->rightKeyAtMark = rightKey(); + } + + this->left.restore(mark.leftMark); + this->right.restore(mark.rightMark); + this->state = mark.state; + + if(this->state == s_only_left) + makeCurrent(false); + else + makeCurrent(leftKey() == rightKey() ); + } + + private: + bool makeCurrent(bool hasRight) + { + if(hasRight) + this->current = ElementType(*this->left.get(), this->right.get() ); + else + this->current = ElementType(*this->left.get(), nullptr); + + return true; + } + + LeftKey leftKey() { return this->keyExtract(*this->left.get() ); } + RightKey rightKey() { return this->keyExtract(*this->right.get() ); } + + private: + Left left; + Right right; + KeyExtract keyExtract; + ElementType current; + State state; + + bool hasRightMark; + RightKey rightKeyAtMark; + typename Right::MarkerType rightMark; +}; + +namespace db { + +template +inline LeftJoinEq leftJoinBy(KeyExtract ex, Left left, Right right) +{ + return LeftJoinEq(left, right, ex); +} + +} + +#endif diff --git a/fsck/source/database/ModificationEvent.h b/fsck/source/database/ModificationEvent.h new file mode 100644 index 0000000..914c0f4 --- /dev/null +++ b/fsck/source/database/ModificationEvent.h @@ -0,0 +1,18 @@ +#ifndef MODFICATIONEVENT_H_ +#define MODFICATIONEVENT_H_ + +#include + +namespace db { + +struct ModificationEvent +{ + EntryID id; + + typedef EntryID KeyType; + EntryID pkey() const { return id; } +}; + +} + +#endif diff --git a/fsck/source/database/Select.h b/fsck/source/database/Select.h new file mode 100644 index 0000000..1f8d7fe --- /dev/null +++ b/fsck/source/database/Select.h @@ -0,0 +1,74 @@ +#ifndef SELECT_H_ +#define SELECT_H_ + +#include + +template +class Select +{ + public: + typedef typename boost::result_of::type ElementType; + typedef typename Source::MarkerType MarkerType; + + public: + Select(Source source, Fn fn) + : source(source), fn(fn) + { + } + + bool step() + { + if(!this->source.step() ) + return false; + + this->current = this->fn(*this->source.get() ); + return true; + } + + ElementType* get() + { + return ¤t; + } + + MarkerType mark() const + { + return this->source.mark(); + } + + void restore(MarkerType mark) + { + this->source.restore(mark); + this->current = this->fn(*this->source.get() ); + } + + private: + Source source; + Fn fn; + ElementType current; +}; + + +namespace db { + +template +struct SelectOp +{ + Pred pred; + + template + friend Select operator|(Source source, const SelectOp& op) + { + return Select(source, op.pred); + } +}; + +template +inline SelectOp select(Pred pred) +{ + SelectOp result = {pred}; + return result; +} + +} + +#endif diff --git a/fsck/source/database/Set.h b/fsck/source/database/Set.h new file mode 100644 index 0000000..ff4cb18 --- /dev/null +++ b/fsck/source/database/Set.h @@ -0,0 +1,380 @@ +#ifndef SET_H_ +#define SET_H_ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +template +class Set { + public: + typedef SetFragment Fragment; + typedef SetFragmentCursor Cursor; + + private: + static const unsigned VERSION = 1; + + std::string basename; + unsigned nextID; + std::map openFragments; + bool dropped; + + Mutex mutex; + + typedef typename std::map::iterator FragmentIter; + + Set(const Set&); + Set& operator=(const Set&); + + std::string fragmentName(unsigned id) + { + std::stringstream ss; + ss << basename << '.' << std::setw(5) << std::setfill('0') << id; + return ss.str(); + } + + Fragment* getFragment(const std::string& file, bool allowCreate) + { + if(openFragments.count(file) ) + return openFragments[file]; + + Fragment* fragment = new Fragment(file, allowCreate); + openFragments[file] = fragment; + return fragment; + } + + void acceptFragment(Fragment* foreign) + { + const unsigned newID = nextID++; + const std::string& file = fragmentName(newID); + + foreign->rename(file); + + openFragments[file] = foreign; + saveConfig(); + } + + void removeFragment(const std::string& file) + { + Fragment* fragment = openFragments[file]; + openFragments.erase(file); + fragment->drop(); + delete fragment; + } + + void loadConfig(bool allowCreate) + { + std::ifstream in( (basename + ".t").c_str() ); + + if(!in) + { + if(allowCreate) + return; + + throw std::runtime_error("could not read set description file " + basename); + } + + { + char code; + unsigned version; + in >> code >> version; + if (code != 'v' || version != VERSION) { + throw std::runtime_error(str( + boost::format("cannot read set %1% with version %2%") + % basename + % version)); + } + } + + in >> nextID; + + unsigned count = 0; + in >> count; + + in.ignore(1, '\n'); + + while(count > 0) + { + std::string file; + + std::getline(in, file); + + if(!in) + break; + + getFragment(basename + file, false); + count--; + } + + if(!in || in.peek() != std::ifstream::traits_type::eof() ) + throw std::runtime_error("bad set description for " + basename); + } + + void saveConfig() + { + std::ofstream out((basename + ".t").c_str(), std::ostream::trunc); + + out << 'v' << VERSION << '\n'; + out << nextID << '\n'; + out << openFragments.size() << '\n'; + + for (FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) { + if (it->second->filename().find(basename) != 0) + throw std::runtime_error("set corrupted: " + basename); + + out << it->second->filename().substr(basename.size()) << '\n'; + } + } + + static std::string makeAbsolute(const std::string& path) + { + std::string::size_type spos = path.find_last_of('/'); + if (spos == path.npos) + spos = 0; + else + spos += 1; + + // can't/don't want to use ::basename because it may return static buffers + const std::string basename = path.substr(spos); + + if (basename.empty() || basename == "." || basename == "..") + throw std::runtime_error("bad path"); + + if (path[0] == '/') + return path; + + char cwd[PATH_MAX]; + + if (::getcwd(cwd, sizeof(cwd)) == NULL) + throw std::runtime_error("bad path"); + + return cwd + ('/' + path); + } + + public: + Set(const std::string& basename, bool allowCreate = true) + : basename(makeAbsolute(basename) ), nextID(0), dropped(false) + { + loadConfig(allowCreate); + } + + ~Set() + { + if(dropped) + return; + + saveConfig(); + + for(FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) + delete it->second; + } + + size_t size() + { + size_t result = 0; + + for(FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) + result += it->second->size(); + + return result; + } + + Fragment* newFragment() + { + const std::lock_guard lock(mutex); + + Fragment* result = getFragment(fragmentName(nextID++), true); + saveConfig(); + + return result; + } + + void sort() + { + if(openFragments.empty() ) + newFragment(); + + std::multimap sortedFragments; + + for(FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) + it->second->flush(); + + for(FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) + { + it->second->sort(); + sortedFragments.insert(std::make_pair(it->second->size(), it->second) ); + } + + static const unsigned MERGE_WIDTH = 4; + + if(sortedFragments.size() == 1) + return; + + saveConfig(); + + while(sortedFragments.size() > 1) + { + Fragment* inputs[MERGE_WIDTH] = {}; + unsigned inputCount = 0; + + for(inputCount = 0; inputCount < MERGE_WIDTH; inputCount++) + { + if(sortedFragments.empty() ) + break; + + inputs[inputCount] = sortedFragments.begin()->second; + sortedFragments.erase(sortedFragments.begin()); + } + + SetFragment* merged = getFragment(fragmentName(nextID++), true); + + struct op + { + static typename Data::KeyType key(const Data& d) { return d.pkey(); } + }; + typedef typename Data::KeyType (*key_t)(const Data&); + typedef Union L1Union; + + switch(inputCount) + { + case 2: { + L1Union u(Cursor(*inputs[0]), (Cursor(*inputs[1]) ), op::key); + while(u.step() ) + merged->append(*u.get() ); + break; + } + + case 3: { + Union, key_t> u( + L1Union(Cursor(*inputs[0]), (Cursor(*inputs[1]) ), op::key), + Cursor(*inputs[2]), + op::key); + while(u.step() ) + merged->append(*u.get() ); + break; + } + + case 4: { + Union u( + L1Union(Cursor(*inputs[0]), (Cursor(*inputs[1]) ), op::key), + L1Union(Cursor(*inputs[2]), (Cursor(*inputs[3]) ), op::key), + op::key); + while(u.step() ) + merged->append(*u.get() ); + break; + } + + default: + throw std::runtime_error(""); + } + + merged->flush(); + + sortedFragments.insert(std::make_pair(merged->size(), merged) ); + + for (unsigned i = 0; i < inputCount; i++) + removeFragment(inputs[i]->filename() ); + + saveConfig(); + } + } + + Cursor cursor() + { + sort(); + + return Cursor(*openFragments.begin()->second); + } + + template + std::pair getByKeyProjection(const Key& key, Fn fn) + { + sort(); + + if(openFragments.empty() ) + return std::make_pair(false, Data() ); + + return openFragments.begin()->second->getByKeyProjection(key, fn); + } + + std::pair getByKey(const typename Data::KeyType& key) + { + struct ops + { + static typename Data::KeyType key(const typename Data::KeyType& key) { return key; } + }; + + return getByKeyProjection(key, ops::key); + } + + void clear() + { + while(!openFragments.empty() ) + removeFragment(openFragments.begin()->first); + + nextID = 0; + saveConfig(); + } + + void drop() + { + clear(); + if(::unlink( (basename + ".t").c_str() ) ) + throw std::runtime_error("could not unlink set file " + basename + ": " + std::string(strerror(errno))); + + dropped = true; + } + + void mergeInto(Set& other) + { + for(FragmentIter it = openFragments.begin(), end = openFragments.end(); it != end; ++it) + other.acceptFragment(it->second); + + openFragments.clear(); + saveConfig(); + } + + void makeUnique() + { + sort(); + + Fragment* input = openFragments.begin()->second; + Cursor cursor(*input); + typename Data::KeyType key; + + if(!cursor.step() ) + return; + + Fragment* unique = newFragment(); + unique->append(*cursor.get() ); + key = cursor.get()->pkey(); + + while(cursor.step() ) + { + if(key == cursor.get()->pkey() ) + continue; + + unique->append(*cursor.get() ); + key = cursor.get()->pkey(); + } + + unique->flush(); + + removeFragment(input->filename() ); + saveConfig(); + } +}; + +#endif diff --git a/fsck/source/database/SetFragment.h b/fsck/source/database/SetFragment.h new file mode 100644 index 0000000..059d84e --- /dev/null +++ b/fsck/source/database/SetFragment.h @@ -0,0 +1,325 @@ +#ifndef SETFRAGMENT_H_ +#define SETFRAGMENT_H_ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +struct FragmentDoesNotExist : public std::runtime_error +{ + FragmentDoesNotExist(const std::string& file) + : runtime_error(file) + {} +}; + +template +class SetFragment { + public: + static const unsigned CONFIG_AREA_SIZE = 4096; + static const size_t BUFFER_SIZE = 4ULL * 1024 * 1024; + + private: + std::string file; + int fd; + size_t itemCount; + bool sorted; + + std::vector buffer; + size_t firstBufferedItem; + ssize_t firstDirtyItem; + ssize_t lastDirtyItem; + + typename Data::KeyType lastItemKey; + + SetFragment(const SetFragment&); + SetFragment& operator=(const SetFragment&); + + size_t readBlock(Data* into, size_t count, size_t from) + { + size_t total = 0; + count *= sizeof(Data); + from *= sizeof(Data); + + from += CONFIG_AREA_SIZE; + + char* buf = (char*) into; + + while(total < count) + { + ssize_t current = ::pread(fd, buf + total, count - total, from + total); + total += current; + + if (current < 0) + throw std::runtime_error("read failed: " + std::string(strerror(errno))); + + if (current == 0) + break; + } + + return total / sizeof(Data); + } + + size_t writeBlock(const Data* source, size_t count, size_t from) + { + size_t total = 0; + count *= sizeof(Data); + from *= sizeof(Data); + + from += CONFIG_AREA_SIZE; + + const char* buf = (const char*) source; + + while(total < count) + { + ssize_t current = ::pwrite(fd, buf + total, count - total, from + total); + total += current; + + if (current < 0) + throw std::runtime_error("write failed: " + std::string(strerror(errno))); + + if (current == 0) + break; + } + + return total / sizeof(Data); + } + + void flushBuffer() + { + if(firstDirtyItem < 0) + return; + + const Data* first = &buffer[firstDirtyItem - firstBufferedItem]; + + writeBlock(first, lastDirtyItem - firstDirtyItem + 1, firstDirtyItem); + + buffer.clear(); + firstDirtyItem = -1; + } + + void bufferFileRange(size_t begin, size_t end) + { + flushBuffer(); + + const size_t firstAddress = begin; + const size_t windowSize = end == size_t(-1) ? end : (end - begin + 1); + const size_t totalRead = std::min(windowSize, BUFFER_SIZE / sizeof(Data) ); + + buffer.resize(totalRead); + + size_t read = readBlock(&buffer[0], totalRead, firstAddress); + + firstBufferedItem = begin; + buffer.resize(read); + } + + public: + SetFragment(const std::string& file, bool allowCreate = true) + : file(file), + itemCount(0), + firstBufferedItem(0), firstDirtyItem(-1), lastDirtyItem(0) + { + fd = ::open(file.c_str(), O_RDWR | (allowCreate ? O_CREAT : 0), 0660); + if(fd < 0) + { + int eno = errno; + if(!allowCreate && errno == ENOENT) + throw FragmentDoesNotExist(file); + else + throw std::runtime_error("could not open fragment file " + file + ": " + strerror(eno)); + } + + off_t totalSize = ::lseek(fd, 0, SEEK_END); + + if(totalSize > 0) + { + int eno = errno; + if (totalSize < (off_t) CONFIG_AREA_SIZE) + throw std::runtime_error("error while opening fragment file " + file + ": " + strerror(eno)); + else + if ( (totalSize - CONFIG_AREA_SIZE) % sizeof(Data) ) + throw std::runtime_error("error while opening fragment file " + file + ": " + strerror(eno)); + } + + if(totalSize == 0) + { + sorted = true; + itemCount = 0; + } + else + { + if (::pread(fd, &sorted, sizeof(sorted), 0) != sizeof(sorted)) { + int eno = errno; + throw std::runtime_error("error while opening fragment file " + file + ": " + strerror(eno)); + } + + itemCount = (totalSize - CONFIG_AREA_SIZE) / sizeof(Data); + } + + if(itemCount > 0) + lastItemKey = (*this)[itemCount - 1].pkey(); + } + + ~SetFragment() + { + if(fd >= 0) + { + flush(); + close(fd); + } + } + + const std::string filename() const { return file; } + size_t size() const { return itemCount; } + + void append(const Data& data) + { + if(itemCount > firstBufferedItem + buffer.size() ) + { + flushBuffer(); + firstBufferedItem = itemCount; + } + + if(firstDirtyItem < 0) + firstDirtyItem = itemCount; + + lastDirtyItem = itemCount; + + if(itemCount > 0 && sorted) + sorted = lastItemKey < data.pkey(); + + buffer.push_back(data); + itemCount++; + lastItemKey = data.pkey(); + + if(buffer.size() * sizeof(Data) >= BUFFER_SIZE) + flushBuffer(); + } + + Data& operator[](size_t offset) + { + if(offset < firstBufferedItem || offset >= firstBufferedItem + buffer.size() ) + bufferFileRange(offset == 0 ? 0 : offset - 1, -1); + + return buffer[offset - firstBufferedItem]; + } + + void flush() + { + if(firstDirtyItem < 0) + return; + + flushBuffer(); + buffer = std::vector(); + + if (::pwrite(fd, &sorted, sizeof(sorted), 0) < 0) { + int eno = errno; + throw std::runtime_error("error in flush of " + file + ": " + strerror(eno)); + } + + // truncate to CONFIG_AREA_SIZE (for reopen) + if (itemCount == 0 && ::ftruncate(fd, CONFIG_AREA_SIZE) < 0) { + int eno = errno; + throw std::runtime_error("error in flush of " + file + ": " + strerror(eno)); + } + } + + void sort() + { + flush(); + + if(sorted) + return; + + boost::scoped_array data(new Data[size()]); + + if(readBlock(data.get(), size(), 0) < size() ) + throw 42; + + struct ops + { + static bool compare(const Data& l, const Data& r) + { + return l.pkey() < r.pkey(); + } + }; + + std::sort(data.get(), data.get() + size(), ops::compare); + + writeBlock(data.get(), size(), 0); + + sorted = true; + + flush(); + } + + void drop() + { + flush(); + if (::unlink(file.c_str()) < 0) { + int eno = errno; + throw std::runtime_error("could not unlink fragment file " + file + ": " + strerror(eno)); + } + + close(fd); + fd = -1; + } + + void rename(const std::string& to) + { + if (::rename(file.c_str(), to.c_str()) < 0) { + int eno = errno; + throw std::runtime_error("could not rename fragment file " + file + ": " + strerror(eno)); + } + + file = to; + } + + template + std::pair getByKeyProjection(const Key& key, Fn fn) + { + if(size() == 0) + return std::make_pair(false, Data() ); + + size_t first = 0; + size_t last = size() - 1; + + while(first != last) + { + size_t midIdx = first + (last - first) / 2; + + bufferFileRange(midIdx, midIdx + 1); + Data& mid = (*this)[midIdx]; + + if (fn(mid.pkey() ) < key) + first = midIdx + 1; + else + last = midIdx; + } + + if(fn( (*this)[first].pkey() ) == key) + return std::make_pair(true, (*this)[first]); + else + return std::make_pair(false, Data() ); + } + + std::pair getByKey(const typename Data::KeyType& key) + { + struct ops + { + static typename Data::KeyType key(const typename Data::KeyType& key) { return key; } + }; + + return getByKeyProjection(key, ops::key); + } +}; + +#endif diff --git a/fsck/source/database/SetFragmentCursor.h b/fsck/source/database/SetFragmentCursor.h new file mode 100644 index 0000000..948796a --- /dev/null +++ b/fsck/source/database/SetFragmentCursor.h @@ -0,0 +1,47 @@ +#ifndef SETFRAGMENTCURSOR_H_ +#define SETFRAGMENTCURSOR_H_ + +#include + +template +class SetFragmentCursor { + public: + typedef Data ElementType; + typedef size_t MarkerType; + + private: + SetFragment* fragment; + size_t currentGetIndex; + + public: + explicit SetFragmentCursor(SetFragment& fragment) + : fragment(&fragment), currentGetIndex(-1) + {} + + bool step() + { + if (currentGetIndex + 1 >= fragment->size() ) + return false; + + currentGetIndex++; + return true; + } + + Data* get() + { + return &(*fragment)[currentGetIndex]; + } + + MarkerType mark() const + { + return currentGetIndex; + } + + void restore(MarkerType mark) + { + currentGetIndex = mark - 1; + step(); + } +}; + +#endif diff --git a/fsck/source/database/StripeTargets.h b/fsck/source/database/StripeTargets.h new file mode 100644 index 0000000..5ec1168 --- /dev/null +++ b/fsck/source/database/StripeTargets.h @@ -0,0 +1,45 @@ +#ifndef STRIPETARGETS_H_ +#define STRIPETARGETS_H_ + +#include + +#include + +namespace db { + +struct StripeTargets +{ + EntryID id; /* 12 */ + + enum { + NTARGETS = 7 + }; + uint16_t targets[NTARGETS]; /* 26 */ + uint16_t firstTargetIndex; /* 28 */ + + typedef EntryID KeyType; + + EntryID pkey() const { return id; } + + friend std::ostream& operator<<(std::ostream& os, StripeTargets const& obj) + { + os << "-------------" << "\n"; + os << "StripeTargets" << "\n"; + os << "-------------" << "\n"; + os << "EntryID: " << obj.id.str() << "\n"; + os << "Stripe targets: [ "; + + for (int i=0; i +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +template +class Table { + private: + struct Key + { + typedef typename Data::KeyType KeyType; + + KeyType value; + + Key() {} + + Key(const KeyType& key) : value(key) {} + + KeyType pkey() const { return value; } + + bool operator<(const Key& r) const { return value < r.value; } + bool operator==(const Key& r) const { return value == r.value; } + }; + + struct GetKey + { + typedef typename Data::KeyType result_type; + + result_type operator()(const Data& data) const { return data.pkey(); } + result_type operator()(const Key& data) const { return data.value; } + }; + + struct SecondIsNull + { + typedef bool result_type; + + bool operator()(std::pair& p) const { return p.second == NULL; } + }; + + struct First + { + typedef Data result_type; + + Data operator()(std::pair& p) const { return p.first; } + }; + + struct HasMatchingKey + { + HasMatchingKey(const typename Data::KeyType& k) : key(k) {} + typename Data::KeyType key; + + bool operator()(std::pair& p) const + { + return (p.first.pkey() == key); + } + }; + + private: + std::string basename; + size_t fragmentSize; + + Set base; + Set inserts; + Set deletes; + + typename Data::KeyType lastChangeKey; + Set insertsPending; + Set deletesPending; + + boost::scoped_ptr > insertBuffer; + boost::scoped_ptr > deleteBuffer; + + enum { + ms_none, + ms_ordered_only_insert, + ms_ordered_only_delete, + ms_ordered_at_insert, + ms_ordered_at_delete, + ms_unordered_insert, + ms_unordered_delete, + ms_bulk_insert, + } modificationState; + + bool dropped; + bool insertsIntoBase; + + std::vector > > bulkInserters; + + void assertNoBulkInsert() + { + for(size_t i = 0; i < bulkInserters.size(); i++) + { + if(!bulkInserters[i].expired() ) + throw std::runtime_error("bulk insert still running"); + } + + bulkInserters.clear(); + } + + void collapseChanges() + { + deleteBuffer.reset(); + insertBuffer.reset(); + insertsIntoBase = false; + + if(deletesPending.size() ) + { + Set insertsTemp(basename + ".it"); + + LeftJoinEq< + SetFragmentCursor, + SetFragmentCursor, + GetKey> + cleanedInserts = db::leftJoinBy( + GetKey(), + inserts.cursor(), + deletesPending.cursor() ); + + // sequence will always be sorted, no need to fragment and sort again + SetFragment* fragment = insertsTemp.newFragment(); + while(cleanedInserts.step() ) + { + if (cleanedInserts.get()->second == nullptr) + fragment->append(cleanedInserts.get()->first); + } + + inserts.clear(); + insertsTemp.mergeInto(inserts); + insertsTemp.drop(); + + deletesPending.mergeInto(deletes); + deletes.makeUnique(); + } + + if(insertsPending.size() ) + insertsPending.mergeInto(inserts); + + modificationState = ms_none; + } + + void setupBaseBuffer() + { + insertBuffer.reset(new Buffer(base, fragmentSize)); + insertsIntoBase = true; + } + + public: + Table(const std::string& basename, size_t fragmentSize, bool allowCreate = true) + : basename(basename), fragmentSize(fragmentSize), + base(basename + ".b", allowCreate), + inserts(basename + ".i", allowCreate), + deletes(basename + ".d", allowCreate), + insertsPending(basename + ".ip", true), + deletesPending(basename + ".dp", true), + modificationState(ms_none), + dropped(false), insertsIntoBase(false) + { + if (base.size() == 0) + setupBaseBuffer(); + } + + ~Table() + { + if(dropped) + return; + + collapseChanges(); + + deletesPending.drop(); + insertsPending.drop(); + } + + boost::shared_ptr > bulkInsert() + { + if(inserts.size() || deletes.size() || insertsPending.size() || deletesPending.size() ) + throw std::runtime_error("unordered operation"); + + switch(modificationState) + { + case ms_none: + case ms_ordered_only_insert: + case ms_bulk_insert: + break; + + default: + throw std::runtime_error("unordered operation"); + } + + modificationState = ms_bulk_insert; + boost::shared_ptr > buffer = boost::make_shared >( + boost::ref(base), fragmentSize); + + bulkInserters.push_back(buffer); + return buffer; + } + + void insert(const Data& data) + { + using namespace std::rel_ops; + + switch(modificationState) + { + case ms_bulk_insert: + assertNoBulkInsert(); + BEEGFS_FALLTHROUGH; + + case ms_none: + modificationState = ms_ordered_only_insert; + break; + + case ms_ordered_only_insert: + if(lastChangeKey >= data.pkey() ) + modificationState = ms_unordered_insert; + + break; + + case ms_unordered_delete: + throw std::runtime_error("unordered operation"); + + case ms_unordered_insert: + break; + + case ms_ordered_at_delete: + case ms_ordered_only_delete: + if(lastChangeKey <= data.pkey() ) + modificationState = ms_ordered_at_insert; + else + throw std::runtime_error("unordered operation"); + + break; + + case ms_ordered_at_insert: + if(lastChangeKey < data.pkey() + || (!AllowDuplicateKeys && lastChangeKey == data.pkey() ) ) + throw std::runtime_error("unordered operation"); + + break; + } + + lastChangeKey = data.pkey(); + + if(!insertBuffer) + insertBuffer.reset(new Buffer(insertsPending, fragmentSize) ); + + insertBuffer->append(data); + } + + void remove(const typename Data::KeyType& key) + { + using namespace std::rel_ops; + + if(modificationState == ms_bulk_insert) + assertNoBulkInsert(); + + // if deletes happen during initial insert, complete the base set. delete tracking + // cannot cope otherwise + if(insertsIntoBase) + { + insertBuffer.reset(); + insertsIntoBase = false; + modificationState = ms_none; + } + + switch(modificationState) + { + case ms_none: + case ms_bulk_insert: + modificationState = ms_ordered_only_delete; + break; + + case ms_ordered_only_delete: + if(lastChangeKey >= key) + modificationState = ms_unordered_delete; + + break; + + case ms_unordered_delete: + break; + + case ms_unordered_insert: + throw std::runtime_error("unordered operation"); + + case ms_ordered_at_delete: + case ms_ordered_at_insert: + case ms_ordered_only_insert: + if(key <= lastChangeKey) + throw std::runtime_error("unordered operation"); + + modificationState = ms_ordered_at_delete; + break; + } + + lastChangeKey = key; + + if(!deleteBuffer) + deleteBuffer.reset(new Buffer(deletesPending, fragmentSize) ); + + deleteBuffer->append(key); + } + + void commitChanges() + { + collapseChanges(); + base.sort(); + inserts.sort(); + deletes.sort(); + } + + typedef Union< + Select< + Filter< + LeftJoinEq< + SetFragmentCursor, + SetFragmentCursor, + GetKey>, + SecondIsNull>, + First>, + SetFragmentCursor, + GetKey> QueryType; + + QueryType cursor() + { + return db::unionBy( + GetKey(), + db::leftJoinBy( + GetKey(), + base.cursor(), + deletes.cursor() ) + | db::where(SecondIsNull() ) + | db::select(First() ), + inserts.cursor() ); + } + + typedef Union< + Select< + Filter< + Filter< + LeftJoinEq< + SetFragmentCursor, + SetFragmentCursor, + GetKey>, + SecondIsNull>, + HasMatchingKey>, + First>, + SetFragmentCursor, + GetKey> MultiRowResultType; + + MultiRowResultType getAllByKey(const typename Data::KeyType& key) + { + return db::unionBy( + GetKey(), + db::leftJoinBy( + GetKey(), + base.cursor(), + deletes.cursor() ) + | db::where(SecondIsNull() ) + | db::where(HasMatchingKey(key) ) + | db::select(First() ), + inserts.cursor() ); + } + + template + std::pair getByKeyProjection(const Key& key, Fn fn) + { + Data dummy = {}; + + std::pair insIdx = inserts.getByKeyProjection(key, fn); + if(insIdx.first) + return insIdx; + + std::pair baseIdx = base.getByKeyProjection(key, fn); + if(!baseIdx.first) + return std::make_pair(false, dummy); + + std::pair delIdx = deletes.getByKey(baseIdx.second.pkey() ); + if(delIdx.first) + return std::make_pair(false, dummy); + + return baseIdx; + } + + std::pair getByKey(const typename Data::KeyType& key) + { + struct ops + { + static typename Data::KeyType key(const typename Data::KeyType& key) { return key; } + }; + + return getByKeyProjection(key, ops::key); + } + + void drop() + { + insertBuffer.reset(); + deleteBuffer.reset(); + + base.drop(); + inserts.drop(); + deletes.drop(); + insertsPending.drop(); + deletesPending.drop(); + dropped = true; + } + + void clear() + { + insertBuffer.reset(); + deleteBuffer.reset(); + + base.clear(); + inserts.clear(); + deletes.clear(); + insertsPending.clear(); + deletesPending.clear(); + + setupBaseBuffer(); + } +}; + +#endif diff --git a/fsck/source/database/Union.h b/fsck/source/database/Union.h new file mode 100644 index 0000000..669ff2e --- /dev/null +++ b/fsck/source/database/Union.h @@ -0,0 +1,130 @@ +#ifndef UNION_H_ +#define UNION_H_ + +#include + +template +class Union +{ + public: + typedef typename Left::ElementType ElementType; + + struct MarkerType + { + typename Left::MarkerType leftMark; + typename Right::MarkerType rightMark; + bool leftEnded; + bool rightEnded; + bool nextStepLeft; + }; + + public: + Union(Left left, Right right, KeyExtract keyExtract): + left(left), right(right), keyExtract(keyExtract), leftEnded(false), nextStepLeft(true), + currentAtLeft(false) + { + this->rightEnded = !this->right.step(); + } + + bool step() + { + if(this->leftEnded && this->rightEnded) + return false; + + if(this->leftEnded) + { + this->rightEnded = !this->right.step(); + return resetCurrent(); + } + + if(this->rightEnded) + { + this->leftEnded = !this->left.step(); + return resetCurrent(); + } + + if(this->nextStepLeft) + this->leftEnded = !this->left.step(); + else + this->rightEnded = !this->right.step(); + + return resetCurrent(); + } + + ElementType* get() + { + if(this->currentAtLeft) + return this->left.get(); + else + return this->right.get(); + } + + MarkerType mark() const + { + MarkerType result = { this->left.mark(), this->right.mark(), this->leftEnded, + this->rightEnded, this->nextStepLeft }; + return result; + } + + void restore(MarkerType mark) + { + this->leftEnded = mark.leftEnded; + this->rightEnded = mark.rightEnded; + this->nextStepLeft = mark.nextStepLeft; + + if(!this->leftEnded) + this->left.restore(mark.leftMark); + + if(!this->rightEnded) + this->right.restore(mark.rightMark); + + resetCurrent(); + } + + private: + bool resetCurrent() + { + if(this->leftEnded && this->rightEnded) + return false; + + if(this->leftEnded) + this->currentAtLeft = false; + else + if(this->rightEnded) + this->currentAtLeft = true; + else + if(this->keyExtract(*this->left.get() ) < this->keyExtract(*this->right.get() ) ) + { + this->currentAtLeft = true; + this->nextStepLeft = true; + } + else + { + this->currentAtLeft = false; + this->nextStepLeft = false; + } + + return true; + } + + private: + Left left; + Right right; + KeyExtract keyExtract; + bool leftEnded; + bool rightEnded; + bool nextStepLeft; + bool currentAtLeft; +}; + +namespace db { + +template +inline Union unionBy(KeyExtract keyExtract, Left left, Right right) +{ + return Union(left, right, keyExtract); +} + +} + +#endif diff --git a/fsck/source/database/UsedTarget.h b/fsck/source/database/UsedTarget.h new file mode 100644 index 0000000..8a10556 --- /dev/null +++ b/fsck/source/database/UsedTarget.h @@ -0,0 +1,25 @@ +#ifndef USEDTARGET_H_ +#define USEDTARGET_H_ + +#include + +namespace db { + +struct UsedTarget +{ + uint32_t id; + uint32_t type; + + typedef std::pair KeyType; + + KeyType pkey() const { return std::make_pair(id, type); } + + operator FsckTargetID() const + { + return FsckTargetID(id, FsckTargetIDType(type) ); + } +}; + +} + +#endif diff --git a/fsck/source/database/VectorSource.h b/fsck/source/database/VectorSource.h new file mode 100644 index 0000000..72f6fde --- /dev/null +++ b/fsck/source/database/VectorSource.h @@ -0,0 +1,52 @@ +#ifndef VECTORSOURCE_H_ +#define VECTORSOURCE_H_ + +#include + +#include +#include + +template +class VectorSource +{ + public: + typedef Obj ElementType; + typedef typename std::vector::const_iterator MarkerType; + + public: + explicit VectorSource(std::vector& data) + : content(boost::make_shared >() ), index(-1) + { + data.swap(*content); + } + + bool step() + { + if(this->index == this->content->size() - 1) + return false; + + this->index += 1; + return true; + } + + ElementType* get() + { + return &(*this->content)[this->index]; + } + + MarkerType mark() const + { + return this->content->begin() + this->index; + } + + void restore(MarkerType mark) + { + this->index = mark - this->content->begin(); + } + + private: + boost::shared_ptr > content; + size_t index; +}; + +#endif diff --git a/fsck/source/modes/Mode.cpp b/fsck/source/modes/Mode.cpp new file mode 100644 index 0000000..53a211d --- /dev/null +++ b/fsck/source/modes/Mode.cpp @@ -0,0 +1,23 @@ +#include "Mode.h" + + +/** + * Check given config for a remaining arg and print it as invalid. + * + * @return true if a remaining invalid argument was found in the given config. + */ +bool Mode::checkInvalidArgs(const StringMap* cfg) +{ + if(cfg->empty() ) + return false; + + if(cfg->size() == 1) + std::cerr << "Invalid argument: " << cfg->begin()->first << std::endl; + else + { // multiple invalid args + std::cerr << "Found " << cfg->size() << " invalid arguments. One of them is: " << + cfg->begin()->first << std::endl; + } + + return true; +} diff --git a/fsck/source/modes/Mode.h b/fsck/source/modes/Mode.h new file mode 100644 index 0000000..f749373 --- /dev/null +++ b/fsck/source/modes/Mode.h @@ -0,0 +1,24 @@ +#ifndef MODE_H_ +#define MODE_H_ + +#include +#include + +class Mode +{ + public: + virtual ~Mode() {} + + /** + * Executes the mode inside the calling thread. + * + * @return APPCODE_... + */ + virtual int execute() = 0; + + protected: + Mode() {} + bool checkInvalidArgs(const StringMap* cfg); +}; + +#endif /*MODE_H_*/ diff --git a/fsck/source/modes/ModeCheckFS.cpp b/fsck/source/modes/ModeCheckFS.cpp new file mode 100644 index 0000000..d6fd711 --- /dev/null +++ b/fsck/source/modes/ModeCheckFS.cpp @@ -0,0 +1,2008 @@ +#include "ModeCheckFS.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +template +UserPrompter::UserPrompter(const FsckRepairAction (&possibleActions)[Actions], + FsckRepairAction defaultRepairAction) + : askForAction(true), possibleActions(possibleActions, possibleActions + Actions), + repairAction(FsckRepairAction_UNDEFINED) +{ + if(Program::getApp()->getConfig()->getReadOnly() ) + askForAction = false; + else + if(Program::getApp()->getConfig()->getAutomatic() ) + { + askForAction = false; + repairAction = defaultRepairAction; + } +} + +FsckRepairAction UserPrompter::chooseAction(const std::string& prompt) +{ + if(askForAction) + FsckTkEx::fsckOutput("> " + prompt, OutputOptions_LINEBREAK | OutputOptions_NOLOG); + + FsckTkEx::fsckOutput("> " + prompt, OutputOptions_NOSTDOUT); + + while(askForAction) + { + for(size_t i = 0; i < possibleActions.size(); i++) + { + FsckTkEx::fsckOutput( + " " + StringTk::uintToStr(i + 1) + ") " + + FsckTkEx::getRepairActionDesc(possibleActions[i]), OutputOptions_NOLOG | + OutputOptions_LINEBREAK); + } + + for(size_t i = 0; i < possibleActions.size(); i++) + { + FsckTkEx::fsckOutput( + " " + StringTk::uintToStr(i + possibleActions.size() + 1) + ") " + + FsckTkEx::getRepairActionDesc(possibleActions[i]) + " (apply for all)", + OutputOptions_NOLOG | OutputOptions_LINEBREAK); + } + + std::string inputStr; + getline(std::cin, inputStr); + + unsigned input = StringTk::strToUInt(inputStr); + + if( (input > 0) && (input <= possibleActions.size() ) ) + { + // user chose for this error + repairAction = possibleActions[input - 1]; + break; + } + + if( (input > possibleActions.size() ) && (input <= possibleActions.size() * 2) ) + { + // user chose for all errors => do not ask again + askForAction = false; + repairAction = possibleActions[input - possibleActions.size() - 1]; + break; + } + } + + FsckTkEx::fsckOutput(" - [ra: " + FsckTkEx::getRepairActionDesc(repairAction, true) + "]", + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + + return repairAction; +} + +static FhgfsOpsErr handleDisposalItem(Node& owner, const std::string& entryID, + const bool isMirrored) +{ + FhgfsOpsErr err = DisposalCleaner::unlinkFile(owner, entryID, isMirrored); + if (err == FhgfsOpsErr_INUSE) + return FhgfsOpsErr_SUCCESS; + else + return err; +} + + + +ModeCheckFS::ModeCheckFS() + : log("ModeCheckFS") +{ +} + +ModeCheckFS::~ModeCheckFS() +{ +} + +void ModeCheckFS::printHelp() +{ + std::cout << + "MODE ARGUMENTS:\n" + " Optional:\n" + " --readOnly Check only, skip repairs.\n" + " --runOffline Run in offline mode, which disables the modification\n" + " logging. Use this only if you are sure there is no user\n" + " access to the file system while the check is running.\n" + " --automatic Do not prompt for repair actions and automatically use\n" + " reasonable default actions.\n" + " --noFetch Do not build a new database from the servers and\n" + " instead work on an existing database (e.g. from a\n" + " previous read-only run).\n" + " --quotaEnabled Enable checks for quota support.\n" + " --databasePath= Path to store for the database files. On systems with \n" + " many files, the database can grow to a size of several \n" + " 100 GB.\n" + " (Default: " CONFIG_DEFAULT_DBPATH ")\n" + " --overwriteDbFile Overwrite an existing database file without prompt.\n" + " --ignoreDBDiskSpace Ignore free disk space check for database file.\n" + " --logOutFile= Path to the fsck output file, which contains a copy of\n" + " the console output.\n" + " (Default: " CONFIG_DEFAULT_OUTFILE ")\n" + " --logStdFile= Path to the program error log file, which contains e.g.\n" + " network error messages.\n" + " (Default: " CONFIG_DEFAULT_LOGFILE ")\n" + " --forceRestart Restart check even though another instance seems to be running.\n" + " Use only if a previous run was aborted, and make sure no other\n" + " fsck is running on the same file system at the same time.\n" + "\n" + "USAGE:\n" + " This mode performs a full check and optional repair of a BeeGFS file system\n" + " instance by building a database of the current file system contents on the\n" + " local machine.\n" + "\n" + " The fsck gathers information from all BeeGFS server daemons in parallel through\n" + " their configured network interfaces. All server components of the file\n" + " system have to be running to start a check.\n" + "\n" + " If the fsck is running with the \"--runoffline\" argument, users may not\n" + " access the file system during a run (otherwise false errors might be reported).\n" + "\n" + " Example: Check for errors, but skip repairs\n" + " $ beegfs-fsck --checkfs --readonly\n"; + + std::cout << std::flush; +} + +int ModeCheckFS::execute() +{ + App* app = Program::getApp(); + Config *cfg = app->getConfig(); + std::string databasePath = cfg->getDatabasePath(); + + if ( this->checkInvalidArgs(cfg->getUnknownConfigArgs()) ) + return APPCODE_INVALID_CONFIG; + + FsckTkEx::printVersionHeader(false); + printHeaderInformation(); + + if (cfg->getNoFetch() && !Program::getApp()->getConfig()->getReadOnly()) { + FsckTkEx::fsckOutput( + " WARNING: RISK OF DATA LOSS!\n" + " NEVER USE THE SAME DATABASE TWICE!\n\n" + + "After running fsck, any database obtained previously no longer matches the state\n" + "of the filesystem. A new database MUST be obtained before running fsck again.\n\n" + + "You are seeing this warning because you are using the option --nofetch.", + OutputOptions_DOUBLELINEBREAK | OutputOptions_ADDLINEBREAKBEFORE); + + if (!uitk::userYNQuestion("Do you wish to continue?")) + return APPCODE_USER_ABORTED; + } + + + if ( !FsckTkEx::checkReachability() ) + return APPCODE_COMMUNICATION_ERROR; + + if (!FsckTkEx::checkConsistencyStates()) + { + FsckTkEx::fsckOutput("At least one meta or storage target is in NEEDS_RESYNC state. " + "To prevent interference between fsck and resync operations, fsck will abort now. " + "Please make sure that no resyncs are currently pending or running.", + OutputOptions_DOUBLELINEBREAK | OutputOptions_ADDLINEBREAKBEFORE); + return APPCODE_RUNTIME_ERROR; + } + + if(cfg->getNoFetch() ) + { + try { + this->database.reset(new FsckDB(databasePath + "/fsckdb", cfg->getTuneDbFragmentSize(), + cfg->getTuneDentryCacheSize(), false) ); + } catch (const FragmentDoesNotExist& e) { + std::string err = "Database was found to be incomplete in path " + databasePath; + log.logErr(err); + FsckTkEx::fsckOutput(err); + return APPCODE_RUNTIME_ERROR; + } + } + else + { + int initDBRes = initDatabase(); + if (initDBRes) + return initDBRes; + + disposeUnusedFiles(); + + boost::scoped_ptr modificationEventHandler; + + if (!cfg->getRunOffline()) + { + modificationEventHandler.reset( + new ModificationEventHandler(*this->database->getModificationEventsTable() ) ); + modificationEventHandler->start(); + Program::getApp()->setModificationEventHandler(modificationEventHandler.get() ); + + // start modification logging + auto startLogRes = FsckTkEx::startModificationLogging(app->getMetaNodes(), + app->getLocalNode(), cfg->getForceRestart()); + + if (startLogRes != FhgfsOpsErr_SUCCESS) + { + std::string errStr; + switch (startLogRes) + { + case FhgfsOpsErr_INUSE: + errStr = "Another instance of beegfs-fsck is still running or was aborted. " + "Cannot start a new one unless --forceRestart command line switch is used. " + "Do this only after making sure there is no other instance of beefs-fsck " + "running."; + + // stop the modification event handler + Program::getApp()->setModificationEventHandler(NULL); + modificationEventHandler->selfTerminate(); + modificationEventHandler->join(); + break; + + default: + errStr = "Unable to start file system modification logging. " + "Fsck cannot proceed."; + } + + log.logErr(errStr); + FsckTkEx::fsckOutput(errStr); + + return APPCODE_RUNTIME_ERROR; + } + } + + FhgfsOpsErr gatherDataRes = gatherData(cfg->getForceRestart()); + + if (!cfg->getRunOffline()) + { + // stop modification logging + bool eventLoggingOK = FsckTkEx::stopModificationLogging(app->getMetaNodes()); + // stop mod event handler (to make it flush for the last time + Program::getApp()->setModificationEventHandler(NULL); + modificationEventHandler->selfTerminate(); + modificationEventHandler->join(); + + // if event logging is not OK (i.e. that not all events might have been processed), go + // into read-only mode + if ( !eventLoggingOK ) + { + Program::getApp()->getConfig()->disableAutomaticRepairMode(); + Program::getApp()->getConfig()->setReadOnly(); + + FsckTkEx::fsckOutput("-----", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + "WARNING: Fsck did not get all modification events from metadata servers.", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + "For instance, this might have happened due to network timeouts.", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + "This means beegfs-fsck is not aware of all changes in the filesystem and it is " + "not safe to execute repair actions. " + "In the worst case executing repair actions can result in data loss.", + OutputOptions_FLUSH | OutputOptions_DOUBLELINEBREAK); + FsckTkEx::fsckOutput( + "Thus, read-only mode was automatically enabled. " + "You can still force repair by running beegfs-fsck again on the existing " + "database with the -noFetch option.", + OutputOptions_FLUSH | OutputOptions_DOUBLELINEBREAK); + FsckTkEx::fsckOutput("Please press any key to continue.", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput("-----", OutputOptions_FLUSH | OutputOptions_DOUBLELINEBREAK); + + std::cin.get(); + } + + } + + if (gatherDataRes != FhgfsOpsErr_SUCCESS) + { + std::string errStr; + switch (gatherDataRes) + { + case FhgfsOpsErr_INUSE: + errStr = "Another instance of beegfs-fsck is still running or was aborted. " + "Cannot start a new one unless --forceRestart command line switch is used. " + "Do this only after making sure there is no other instance of beefs-fsck " + "running."; + break; + default: + errStr = "An error occured while fetching data from servers. Fsck cannot proceed. " + "Please see log file for more information."; + } + + log.logErr(errStr); + FsckTkEx::fsckOutput(errStr); + + return APPCODE_RUNTIME_ERROR; + } + } + + checkAndRepair(); + + return APPCODE_NO_ERROR; +} + +/* + * initializes the database, this is done here and not inside the main app, because we do not want + * it to effect the help modes; DB file shall only be created if user really runs a check + * + * @return integer value symbolizing an appcode + */ +int ModeCheckFS::initDatabase() +{ + Config* cfg = Program::getApp()->getConfig(); + + // create the database path + Path dbPath(cfg->getDatabasePath() + "/fsckdb"); + + if ( !StorageTk::createPathOnDisk(dbPath, false) ) + { + FsckTkEx::fsckOutput("Could not create path for database files: " + + dbPath.str()); + return APPCODE_INITIALIZATION_ERROR; + } + + // check disk space + if ( !FsckTkEx::checkDiskSpace(dbPath) ) + return APPCODE_INITIALIZATION_ERROR; + + // check if DB file path already exists and is not empty + if ( StorageTk::pathExists(dbPath.str()) + && StorageTk::pathHasChildren(dbPath.str()) && (!cfg->getOverwriteDbFile()) ) + { + FsckTkEx::fsckOutput("The database path already exists: " + dbPath.str()); + FsckTkEx::fsckOutput("If you continue now any existing database files in that path will be " + "deleted."); + + std::string input; + + while (input.size() != 1 || !strchr("YyNn", input[0])) + { + FsckTkEx::fsckOutput("Do you want to continue? (Y/N)"); + std::getline(std::cin, input); + } + + switch (input[0]) + { + case 'Y': + case 'y': + break; // just do nothing and go ahead + case 'N': + case 'n': + default: + // abort here + return APPCODE_USER_ABORTED; + } + } + + struct ops + { + static int visit(const char* path, const struct stat*, int type, struct FTW* state) + { + if(state->level == 0) + return 0; + else + if(type == FTW_F || type == FTW_SL) + return ::unlink(path); + else + return ::rmdir(path); + } + }; + + int ftwRes = ::nftw(dbPath.str().c_str(), ops::visit, 10, FTW_DEPTH | FTW_PHYS); + if(ftwRes) + { + FsckTkEx::fsckOutput("Could not empty path for database files: " + dbPath.str() ); + return APPCODE_INITIALIZATION_ERROR; + } + + try { + this->database.reset( + new FsckDB(dbPath.str(), cfg->getTuneDbFragmentSize(), + cfg->getTuneDentryCacheSize(), true) ); + } catch (const std::runtime_error& e) { + FsckTkEx::fsckOutput("Database " + dbPath.str() + " is corrupt"); + return APPCODE_RUNTIME_ERROR; + } + + return 0; +} + +void ModeCheckFS::printHeaderInformation() +{ + Config* cfg = Program::getApp()->getConfig(); + + // get the current time and create some nice output, so the user can see at which time the run + // was started (especially nice to find older runs in the log file) + time_t t; + time(&t); + std::string timeStr = std::string(ctime(&t)); + FsckTkEx::fsckOutput( + "Started BeeGFS fsck in forward check mode [" + timeStr.substr(0, timeStr.length() - 1) + + "]\nLog will be written to " + cfg->getLogStdFile() + "\nDatabase will be saved in " + + cfg->getDatabasePath(), OutputOptions_LINEBREAK | OutputOptions_HEADLINE); + + if (Program::getApp()->getMetaMirrorBuddyGroupMapper()->getSize() > 0) + { + FsckTkEx::fsckOutput( + "IMPORTANT NOTICE: The initiation of a repair action on a metadata mirror group " + "will temporarily set the consistency state\nof the secondary node to bad to prevent " + "interaction during the repair. After fsck finishes, it will set the states " + "of all\naffected nodes to needs-resync, so the repaired data will be synced from their " + "primary. \n\nIf beegfs-fsck is aborted prematurely, a resync needs to be initiated " + "manually so all targets are consistent (good) again." , + OutputOptions_DOUBLELINEBREAK | OutputOptions_HEADLINE); + } +} + +void ModeCheckFS::disposeUnusedFiles() +{ + Config* cfg = Program::getApp()->getConfig(); + + if (cfg->getReadOnly() || cfg->getNoFetch()) + return; + + FsckTkEx::fsckOutput("Step 2: Delete unused files from disposal: ", OutputOptions_NONE); + + using namespace std::placeholders; + + uint64_t errors = 0; + + DisposalCleaner dc(*Program::getApp()->getMetaMirrorBuddyGroupMapper()); + dc.run(Program::getApp()->getMetaNodes()->referenceAllNodes(), + handleDisposalItem, + [&] (const auto&, const auto&) { errors += 1; }); + + if (errors > 0) + FsckTkEx::fsckOutput("Some files could not be deleted."); + else + FsckTkEx::fsckOutput("Finished."); +} + +FhgfsOpsErr ModeCheckFS::gatherData(bool forceRestart) +{ + FsckTkEx::fsckOutput("Step 3: Gather data from nodes: ", OutputOptions_DOUBLELINEBREAK); + + DataFetcher dataFetcher(*this->database, forceRestart); + const FhgfsOpsErr retVal = dataFetcher.execute(); + + FsckTkEx::fsckOutput("", OutputOptions_LINEBREAK); + + return retVal; +} + +template +FsckErrCount ModeCheckFS::checkAndRepairGeneric(Cursor cursor, + void (ModeCheckFS::*repair)(Obj&, FsckErrCount&, State&), State& state) +{ + FsckErrCount errorCount; + + while(cursor.step() ) + { + Obj* entry = cursor.get(); + + (this->*repair)(*entry, errorCount, state); + } + + + if (errorCount.getTotalErrors()) + { + database->getDentryTable()->commitChanges(); + database->getFileInodesTable()->commitChanges(); + database->getDirInodesTable()->commitChanges(); + database->getChunksTable()->commitChanges(); + database->getContDirsTable()->commitChanges(); + database->getFsIDsTable()->commitChanges(); + + FsckTkEx::fsckOutput(">>> Found " + StringTk::int64ToStr(errorCount.getTotalErrors()) + + " errors. Detailed information can also be found in " + + Program::getApp()->getConfig()->getLogOutFile() + ".", + OutputOptions_DOUBLELINEBREAK); + + } + else + { + FsckTkEx::fsckOutput(">>> None found", OutputOptions_FLUSH | OutputOptions_LINEBREAK); + } + + return errorCount; +} + +FsckErrCount ModeCheckFS::checkAndRepairDanglingDentry() +{ + FsckRepairAction fileActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_DELETEDENTRY, + }; + + FsckRepairAction dirActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_DELETEDENTRY, + FsckRepairAction_CREATEDEFAULTDIRINODE, + }; + + UserPrompter forFiles(fileActions, FsckRepairAction_DELETEDENTRY); + UserPrompter forDirs(dirActions, FsckRepairAction_CREATEDEFAULTDIRINODE); + + std::pair prompt(&forFiles, &forDirs); + + FsckTkEx::fsckOutput("* Checking: Dangling directory entry (dentry) ...", + OutputOptions_FLUSH |OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findDanglingDirEntries(), + &ModeCheckFS::repairDanglingDirEntry, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairWrongInodeOwner() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_CORRECTOWNER, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_CORRECTOWNER); + + FsckTkEx::fsckOutput("* Checking: Wrong owner node saved in inode ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findInodesWithWrongOwner(), + &ModeCheckFS::repairWrongInodeOwner, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairWrongOwnerInDentry() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_CORRECTOWNER, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_CORRECTOWNER); + + FsckTkEx::fsckOutput("* Checking: Dentry points to inode on wrong node ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findDirEntriesWithWrongOwner(), + &ModeCheckFS::repairWrongInodeOwnerInDentry, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairOrphanedContDir() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_CREATEDEFAULTDIRINODE, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_CREATEDEFAULTDIRINODE); + + FsckTkEx::fsckOutput("* Checking: Content directory without an inode ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findOrphanedContDirs(), + &ModeCheckFS::repairOrphanedContDir, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairOrphanedDirInode() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_LOSTANDFOUND, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_LOSTANDFOUND); + + FsckTkEx::fsckOutput("* Checking: Dir inode without a dentry pointing to it (orphaned inode) ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + auto result = checkAndRepairGeneric(this->database->findOrphanedDirInodes(), + &ModeCheckFS::repairOrphanedDirInode, prompt); + + releaseLostAndFound(); + return result; +} + +FsckErrCount ModeCheckFS::checkAndRepairOrphanedFileInode() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_DELETEINODE, + //FsckRepairAction_LOSTANDFOUND, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_DELETEINODE); + + FsckTkEx::fsckOutput("* Checking: File inode without a dentry pointing to it (orphaned inode) ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findOrphanedFileInodes(), + &ModeCheckFS::repairOrphanedFileInode, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairDuplicateInodes() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_REPAIRDUPLICATEINODE, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_REPAIRDUPLICATEINODE); + + FsckTkEx::fsckOutput("* Checking: Duplicate inodes ...", OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findDuplicateInodeIDs(), + &ModeCheckFS::repairDuplicateInode, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairOrphanedChunk() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_NOTHING); + RepairChunkState state = { &prompt, "", FsckRepairAction_UNDEFINED }; + + FsckTkEx::fsckOutput("* Checking: Chunk without an inode pointing to it (orphaned chunk) ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findOrphanedChunks(), + &ModeCheckFS::repairOrphanedChunk, state); +} + +FsckErrCount ModeCheckFS::checkAndRepairMissingContDir() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_CREATECONTDIR, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_CREATECONTDIR); + + FsckTkEx::fsckOutput("* Checking: Directory inode without a content directory ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findInodesWithoutContDir(), + &ModeCheckFS::repairMissingContDir, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairWrongFileAttribs() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_UPDATEATTRIBS, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_UPDATEATTRIBS); + + FsckTkEx::fsckOutput("* Checking: Attributes of file inode are wrong ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findWrongInodeFileAttribs(), + &ModeCheckFS::repairWrongFileAttribs, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairWrongDirAttribs() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_UPDATEATTRIBS, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_UPDATEATTRIBS); + + FsckTkEx::fsckOutput("* Checking: Attributes of dir inode are wrong ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findWrongInodeDirAttribs(), + &ModeCheckFS::repairWrongDirAttribs, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairFilesWithMissingTargets() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_DELETEFILE, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_NOTHING); + + FsckTkEx::fsckOutput("* Checking: File has a missing target in stripe pattern ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric( + this->database->findFilesWithMissingStripeTargets( + Program::getApp()->getTargetMapper(), Program::getApp()->getMirrorBuddyGroupMapper() ), + &ModeCheckFS::repairFileWithMissingTargets, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairDirEntriesWithBrokeByIDFile() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_RECREATEFSID, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_RECREATEFSID); + + FsckTkEx::fsckOutput("* Checking: Dentry-by-ID file is broken or missing ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findDirEntriesWithBrokenByIDFile(), + &ModeCheckFS::repairDirEntryWithBrokenByIDFile, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairOrphanedDentryByIDFiles() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_RECREATEDENTRY, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_RECREATEDENTRY); + + FsckTkEx::fsckOutput("* Checking: Dentry-by-ID file is present, but no corresponding dentry ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findOrphanedFsIDFiles(), + &ModeCheckFS::repairOrphanedDentryByIDFile, prompt); +} + +FsckErrCount ModeCheckFS::checkAndRepairChunksWithWrongPermissions() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_FIXPERMISSIONS, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_FIXPERMISSIONS); + + FsckTkEx::fsckOutput("* Checking: Chunk has wrong permissions ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findChunksWithWrongPermissions(), + &ModeCheckFS::repairChunkWithWrongPermissions, prompt); +} + +// no repair at the moment +FsckErrCount ModeCheckFS::checkAndRepairChunksInWrongPath() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_MOVECHUNK, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_MOVECHUNK); + + FsckTkEx::fsckOutput("* Checking: Chunk is saved in wrong path ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findChunksInWrongPath(), + &ModeCheckFS::repairWrongChunkPath, prompt); +} + +FsckErrCount ModeCheckFS::checkAndUpdateOldStyledHardlinks() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_UPDATEOLDTYLEDHARDLINKS, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_UPDATEOLDTYLEDHARDLINKS); + + FsckTkEx::fsckOutput("* Checking: Files having an inlined inode with multiple hardlinks ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(this->database->findFilesWithMultipleHardlinks(), + &ModeCheckFS::updateOldStyledHardlinks, prompt); +} + +void ModeCheckFS::logDuplicateInodeID(checks::DuplicatedInode& dups, int&) +{ + FsckTkEx::fsckOutput(">>> Found duplicated ID " + dups.first.str(), + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + + db::EntryID entryID = dups.first; + std::string filePath = this->database->getDentryTable()->getPathOf(entryID); + FsckTkEx::fsckOutput(" File path: " + filePath, + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + + for(const auto& it : dups.second) + { + std::string parentDirID = it.getParentDirID(); + unsigned ownerNodeID = it.getSaveNodeID(); + bool isInlined = it.getIsInlined(); + bool isBuddyMirrored = it.getIsBuddyMirrored(); + + std::string metaFilePath; + if (isInlined) + { + std::string dentriesPath; + dentriesPath += isBuddyMirrored ? META_BUDDYMIRROR_SUBDIR_NAME : ""; + dentriesPath += std::string("/") + META_DENTRIES_SUBDIR_NAME; + + metaFilePath = StorageTk::getHashPath(dentriesPath, parentDirID, META_DENTRIES_LEVEL1_SUBDIR_NUM, META_DENTRIES_LEVEL2_SUBDIR_NUM); + metaFilePath += std::string("/") + META_DIRENTRYID_SUB_STR + "/" + entryID.str(); + } + else + { + std::string inodesPath; + inodesPath += isBuddyMirrored ? META_BUDDYMIRROR_SUBDIR_NAME : ""; + inodesPath += std::string("/") + META_INODES_SUBDIR_NAME; + + metaFilePath = StorageTk::getHashPath(inodesPath, entryID.str(), META_INODES_LEVEL1_SUBDIR_NUM, META_INODES_LEVEL2_SUBDIR_NUM); + } + + FsckTkEx::fsckOutput(" * Found on " + std::string(isBuddyMirrored ? "buddy group " : "node ") + + StringTk::uintToStr(ownerNodeID) + "; Metapath: " + metaFilePath, OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + } +} + +FsckErrCount ModeCheckFS::checkDuplicateChunks() +{ + FsckTkEx::fsckOutput("* Checking: Duplicated chunks ...", OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + int dummy = 0; + return checkAndRepairGeneric(this->database->findDuplicateChunks(), + &ModeCheckFS::logDuplicateChunk, dummy); +} + +void ModeCheckFS::logDuplicateChunk(std::list& dups, FsckErrCount& errCount, int&) +{ + FsckTkEx::fsckOutput(">>> Found duplicated Chunks for ID " + dups.begin()->getID(), + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + + errCount.unfixableErrors++; + + for(std::list::iterator it = dups.begin(), end = dups.end(); + it != end; ++it) + { + FsckTkEx::fsckOutput(" * Found on target " + StringTk::uintToStr(it->getTargetID() ) + + (it->getBuddyGroupID() + ? ", buddy group " + StringTk::uintToStr(it->getBuddyGroupID() ) + : "") + + " in path " + it->getSavedPath()->str(), + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + } +} + +FsckErrCount ModeCheckFS::checkDuplicateContDirs() +{ + FsckTkEx::fsckOutput("* Checking: Duplicated content directories ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + int dummy = 0; + return checkAndRepairGeneric(this->database->findDuplicateContDirs(), + &ModeCheckFS::logDuplicateContDir, dummy); +} + +void ModeCheckFS::logDuplicateContDir(std::list& dups, FsckErrCount& errCount, int&) +{ + errCount.unfixableErrors++; + + FsckTkEx::fsckOutput(">>> Found duplicated content directories for ID " + dups.front().id.str(), + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + for (auto it = dups.begin(), end = dups.end(); it != end; ++it) + { + FsckTkEx::fsckOutput(" * Found on " + + std::string(it->isBuddyMirrored ? "buddy group " : "node ") + + StringTk::uintToStr(it->saveNodeID), + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + } +} + +FsckErrCount ModeCheckFS::checkMismirroredDentries() +{ + FsckTkEx::fsckOutput("* Checking: Bad target mirror information in dentry ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + int dummy = 0; + return checkAndRepairGeneric(this->database->findMismirroredDentries(), + &ModeCheckFS::logMismirroredDentry, dummy); +} + +void ModeCheckFS::logMismirroredDentry(db::DirEntry& entry, FsckErrCount& errCount, int&) +{ + errCount.unfixableErrors++; + + FsckTkEx::fsckOutput(">>> Found mismirrored dentry " + + database->getDentryTable()->getNameOf(entry) + " on " + + (entry.isBuddyMirrored ? "buddy group " : "node ") + + StringTk::uintToStr(entry.saveNodeID) + " " + StringTk::uintToStr(entry.entryOwnerNodeID)); +} + +FsckErrCount ModeCheckFS::checkMismirroredDirectories() +{ + FsckTkEx::fsckOutput("* Checking: Bad content mirror information in dir inode ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + int dummy = 0; + return checkAndRepairGeneric(this->database->findMismirroredDirectories(), + &ModeCheckFS::logMismirroredDirectory, dummy); +} + +void ModeCheckFS::logMismirroredDirectory(db::DirInode& dir, FsckErrCount& errCount, int&) +{ + errCount.unfixableErrors++; + + FsckTkEx::fsckOutput(">>> Found mismirrored directory " + dir.id.str() + " on " + + (dir.isBuddyMirrored ? "buddy group " : "node ") + + StringTk::uintToStr(dir.saveNodeID)); +; +} + +FsckErrCount ModeCheckFS::checkMismirroredFiles() +{ + FsckTkEx::fsckOutput("* Checking: Bad content mirror information in file inode ...", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + int dummy = 0; + return checkAndRepairGeneric(this->database->findMismirroredFiles(), + &ModeCheckFS::logMismirroredFile, dummy); +} + +void ModeCheckFS::logMismirroredFile(db::FileInode& file, FsckErrCount& errCount, int&) +{ + errCount.unfixableErrors++; + + FsckTkEx::fsckOutput(">>> Found mismirrored file " + file.id.str() + " on " + + std::string(file.isBuddyMirrored ? "buddy group " : "node ") + + StringTk::uintToStr(file.saveNodeID)); +} + +FsckErrCount ModeCheckFS::checkAndRepairMalformedChunk() +{ + FsckRepairAction possibleActions[] = { + FsckRepairAction_NOTHING, + FsckRepairAction_DELETECHUNK, + }; + + UserPrompter prompt(possibleActions, FsckRepairAction_DELETECHUNK); + + FsckTkEx::fsckOutput("* Checking: Malformed chunk ...", OutputOptions_FLUSH | OutputOptions_LINEBREAK); + + return checkAndRepairGeneric(Cursor(database->getMalformedChunksList()->cursor()), + &ModeCheckFS::repairMalformedChunk, prompt); +} + +void ModeCheckFS::repairMalformedChunk(FsckChunk& chunk, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Chunk ID: " + chunk.getID() + " on " + + (chunk.getBuddyGroupID() + ? "buddy group " + StringTk::uintToStr(chunk.getBuddyGroupID()) + : "target " + StringTk::uintToStr(chunk.getTargetID()))); + + switch (action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_DELETECHUNK: { + FhgfsOpsErr refErr; + NodeStore* storageNodeStore = Program::getApp()->getStorageNodes(); + auto node = storageNodeStore->referenceNodeByTargetID(chunk.getTargetID(), + Program::getApp()->getTargetMapper(), &refErr); + + if(!node) + { + FsckTkEx::fsckOutput("could not get storage target " + + StringTk::uintToStr(chunk.getTargetID() ), OutputOptions_LINEBREAK); + return; + } + + FsckChunkList chunks(1, chunk); + FsckChunkList failed; + + MsgHelperRepair::deleteChunks(*node, &chunks, &failed); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::checkAndRepair() +{ + FsckTkEx::fsckOutput("Step 4: Check for errors... ", OutputOptions_DOUBLELINEBREAK); + + Config* cfg = Program::getApp()->getConfig(); + + FsckErrCount errorCount; + + errorCount += checkAndRepairDuplicateInodes(); + errorCount += checkDuplicateChunks(); + errorCount += checkDuplicateContDirs(); + errorCount += checkMismirroredDentries(); + errorCount += checkMismirroredDirectories(); + errorCount += checkMismirroredFiles(); + + if (errorCount.unfixableErrors) + { + FsckTkEx::fsckOutput("Found errors beegfs-fsck cannot fix. Please consult the log for " + "more information.", OutputOptions_LINEBREAK); + return; + } + + std::bitset checkFsActions = cfg->getCheckFsActions(); + if (checkFsActions.none()) + { + // enable all checks if user didn't specified specific checks to run + checkFsActions.set(); + } + + if (checkFsActions.test(CHECK_MALFORMED_CHUNK)) + errorCount += checkAndRepairMalformedChunk(); + + if (checkFsActions.test(CHECK_FILES_WITH_MISSING_TARGETS)) + errorCount += checkAndRepairFilesWithMissingTargets(); + + if (checkFsActions.test(CHECK_ORPHANED_DENTRY_BYIDFILES)) + errorCount += checkAndRepairOrphanedDentryByIDFiles(); + + if (checkFsActions.test(CHECK_DIRENTRIES_WITH_BROKENIDFILE)) + errorCount += checkAndRepairDirEntriesWithBrokeByIDFile(); + + if (checkFsActions.test(CHECK_ORPHANED_CHUNK)) + errorCount += checkAndRepairOrphanedChunk(); + + if (checkFsActions.test(CHECK_CHUNKS_IN_WRONGPATH)) + errorCount += checkAndRepairChunksInWrongPath(); + + if (checkFsActions.test(CHECK_WRONG_INODE_OWNER)) + errorCount += checkAndRepairWrongInodeOwner(); + + if (checkFsActions.test(CHECK_WRONG_OWNER_IN_DENTRY)) + errorCount += checkAndRepairWrongOwnerInDentry(); + + if (checkFsActions.test(CHECK_ORPHANED_CONT_DIR)) + errorCount += checkAndRepairOrphanedContDir(); + + if (checkFsActions.test(CHECK_ORPHANED_DIR_INODE)) + errorCount += checkAndRepairOrphanedDirInode(); + + if (checkFsActions.test(CHECK_ORPHANED_FILE_INODE)) + errorCount += checkAndRepairOrphanedFileInode(); + + if (checkFsActions.test(CHECK_DANGLING_DENTRY)) + errorCount += checkAndRepairDanglingDentry(); + + if (checkFsActions.test(CHECK_MISSING_CONT_DIR)) + errorCount += checkAndRepairMissingContDir(); + + if (checkFsActions.test(CHECK_WRONG_FILE_ATTRIBS)) + errorCount += checkAndRepairWrongFileAttribs(); + + if (checkFsActions.test(CHECK_WRONG_DIR_ATTRIBS)) + errorCount += checkAndRepairWrongDirAttribs(); + + if (checkFsActions.test(CHECK_OLD_STYLED_HARDLINKS)) + errorCount += checkAndUpdateOldStyledHardlinks(); + + if ( cfg->getQuotaEnabled()) + { + errorCount += checkAndRepairChunksWithWrongPermissions(); + } + + if ( cfg->getReadOnly() ) + { + FsckTkEx::fsckOutput( + "Found " + StringTk::int64ToStr(errorCount.getTotalErrors()) + + " errors. Detailed information can also be found in " + + Program::getApp()->getConfig()->getLogOutFile() + ".", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_LINEBREAK); + return; + } + + for (auto it = secondariesSetBad.begin(); it != secondariesSetBad.end(); ++it) + { + auto secondary = *it; + + FsckTkEx::fsckOutput(">>> Setting metadata node " + StringTk::intToStr(secondary.val()) + + " to needs-resync", OutputOptions_LINEBREAK); + + auto setRes = MsgHelperRepair::setNodeState(secondary, TargetConsistencyState_NEEDS_RESYNC); + if (setRes != FhgfsOpsErr_SUCCESS) + FsckTkEx::fsckOutput("Failed: " + boost::lexical_cast(setRes), + OutputOptions_LINEBREAK); + } + + if (errorCount.getTotalErrors()) + FsckTkEx::fsckOutput(">>> Found " + StringTk::int64ToStr(errorCount.getTotalErrors()) + " errors <<< ", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_LINEBREAK); +} + +////////////////////////// +// internals /////// +///////////////////////// +void ModeCheckFS::repairDanglingDirEntry(db::DirEntry& entry, FsckErrCount& errCount, + std::pair& prompt) +{ + errCount.fixableErrors++; + + FsckDirEntry fsckEntry = entry; + + FsckRepairAction action; + std::string promptText = "Entry ID: " + fsckEntry.getID() + "; Path: " + + this->database->getDentryTable()->getPathOf(entry) + "; " + + (entry.isBuddyMirrored ? "Buddy group: " : "Node: ") + + StringTk::uintToStr(entry.saveNodeID); + + if(fsckEntry.getEntryType() == FsckDirEntryType_DIRECTORY) + action = prompt.second->chooseAction(promptText); + else + action = prompt.first->chooseAction(promptText); + + fsckEntry.setName(this->database->getDentryTable()->getNameOf(entry) ); + + FsckDirEntryList entries(1, fsckEntry); + FsckDirEntryList failedEntries; + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_DELETEDENTRY: { + MsgHelperRepair::deleteDanglingDirEntries(fsckEntry.getSaveNodeID(), + fsckEntry.getIsBuddyMirrored(), &entries, &failedEntries, + secondariesSetBad); + + if(failedEntries.empty() ) + { + this->database->getDentryTable()->remove(entries); + this->deleteFsIDsFromDB(entries); + } + + break; + } + + case FsckRepairAction_CREATEDEFAULTDIRINODE: { + FsckDirInodeList createdInodes; + + // create mirrored inodes iff the dentry was mirrored. if a contdir with the same id exists, + // a previous check will have created an inode for it, leaving this dentry not dangling. + MsgHelperRepair::createDefDirInodes(fsckEntry.getInodeOwnerNodeID(), fsckEntry.getIsBuddyMirrored(), + {std::make_tuple(fsckEntry.getID(), fsckEntry.getIsBuddyMirrored())}, &createdInodes, + secondariesSetBad); + + this->database->getDirInodesTable()->insert(createdInodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairWrongInodeOwner(FsckDirInode& inode, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Entry ID: " + inode.getID() + + "; Path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(inode.getID() ) ) ); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_CORRECTOWNER: { + inode.setOwnerNodeID(inode.getSaveNodeID() ); + + FsckDirInodeList inodes(1, inode); + FsckDirInodeList failed; + + MsgHelperRepair::correctInodeOwners(inode.getSaveNodeID(), inode.getIsBuddyMirrored(), + &inodes, &failed, secondariesSetBad); + + if(failed.empty() ) + this->database->getDirInodesTable()->update(inodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairWrongInodeOwnerInDentry(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckDirEntry fsckEntry = error.first; + + FsckRepairAction action = prompt.chooseAction("File ID: " + fsckEntry.getID() + + "; Path: " + this->database->getDentryTable()->getPathOf(error.first) ); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_CORRECTOWNER: { + fsckEntry.setName(this->database->getDentryTable()->getNameOf(error.first) ); + + FsckDirEntryList dentries(1, fsckEntry); + NumNodeIDList owners(1, NumNodeID(error.second) ); + FsckDirEntryList failed; + + MsgHelperRepair::correctInodeOwnersInDentry(fsckEntry.getSaveNodeID(), + fsckEntry.getIsBuddyMirrored(), &dentries, &owners, &failed, + secondariesSetBad); + + if(failed.empty() ) + this->database->getDentryTable()->updateFieldsExceptParent(dentries); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairOrphanedDirInode(FsckDirInode& inode, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Directory ID: " + inode.getID() + "; " + + (inode.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + inode.getSaveNodeID().str()); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_LOSTANDFOUND: { + if(!ensureLostAndFoundExists() ) + { + log.logErr("Orphaned dir inodes could not be linked to lost+found, because lost+found " + "directory could not be created"); + return; + } + + FsckDirInodeList inodes(1, inode); + FsckDirEntryList created; + FsckDirInodeList failed; + + MsgHelperRepair::linkToLostAndFound(*this->lostAndFoundNode, &this->lostAndFoundInfo, &inodes, + &failed, &created, secondariesSetBad); + + if(failed.empty() ) + this->database->getDentryTable()->insert(created); + + // on server side, each dentry also created a dentry-by-ID file + FsckFsIDList createdFsIDs; + for(FsckDirEntryListIter iter = created.begin(); iter != created.end(); iter++) + { + FsckFsID fsID(iter->getID(), iter->getParentDirID(), iter->getSaveNodeID(), + iter->getSaveDevice(), iter->getSaveInode(), iter->getIsBuddyMirrored()); + createdFsIDs.push_back(fsID); + } + + this->database->getFsIDsTable()->insert(createdFsIDs); + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairOrphanedFileInode(FsckFileInode& inode, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("File ID: " + inode.getID() + "; " + + (inode.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + inode.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_DELETEINODE: { + FsckFileInodeList inodes(1, inode); + StringList failed; + + MsgHelperRepair::deleteFileInodes(inode.getSaveNodeID(), inode.getIsBuddyMirrored(), inodes, + failed, secondariesSetBad); + + if(failed.empty() ) + this->database->getFileInodesTable()->remove(inodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairDuplicateInode(checks::DuplicatedInode& dupInode, FsckErrCount& errCount, + UserPrompter& prompt) +{ + int dummy = 0; + logDuplicateInodeID(dupInode, dummy); + + // repair is not possible using FSCK for duplicate inodes if: + // (a) inodes are of type directory OR + // (b) inodes exists on different metadata nodes + // (c) both inodes are inlined + + bool isDir = false; + for (const auto& it : dupInode.second) + { + if (it.getDirEntryType() == DirEntryType_DIRECTORY) + { + isDir = true; + break; + } + } + + std::set nodeIDs; + for (const auto& it : dupInode.second) + nodeIDs.insert(it.getSaveNodeID()); + + bool bothInlined = true; + for (const auto& it : dupInode.second) + bothInlined &= it.getIsInlined(); + + if (nodeIDs.size() != 1 || isDir || bothInlined) + { + errCount.unfixableErrors++; + FsckTkEx::fsckOutput("> Repair is not allowed for above case using fsck. Please contact support team.", + OutputOptions_LINEBREAK | OutputOptions_NOSTDOUT); + return; + } + else + { + errCount.fixableErrors++; + } + + FsckRepairAction action = prompt.chooseAction("EntryID: " + dupInode.first.str()); + + FsckDuplicateInodeInfo item; + + for (const auto& it : dupInode.second) + { + // select item from duplicate inode list which has inlined flag + // set (because parent's entryID is available (non-empty) there) + if (it.getIsInlined()) + { + item = it; + break; + } + } + + switch (action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_REPAIRDUPLICATEINODE: + { + FsckDuplicateInodeInfoVector dupInodes{item}; + StringList failedEntries; + + MsgHelperRepair::deleteDuplicateFileInodes(NumNodeID(item.getSaveNodeID()), + item.getIsBuddyMirrored(), dupInodes, failedEntries, secondariesSetBad); + + if (failedEntries.empty()) + { + auto res = this->database->getFileInodesTable()->get(dupInode.first.str()); + + if (res.first) + { + auto inode = res.second; + FsckFileInode fsckInode = inode.toInodeWithoutStripes(); + fsckInode.setIsInlined(true); + + // now set all stripe targets into FsckFileInode object + fsckInode.setStripeTargets( + this->database->getFileInodesTable()->getStripeTargetsByKey(dupInode.first) + ); + + FsckFileInodeList fsckInodeList{fsckInode}; + this->database->getFileInodesTable()->update(fsckInodeList); + } + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairOrphanedChunk(FsckChunk& chunk, FsckErrCount& errCount, RepairChunkState& state) +{ + errCount.fixableErrors++; + + // ask for repair action only once per chunk id, not once per chunk id and node + if(state.lastID != chunk.getID() ) + state.lastChunkAction = state.prompt->chooseAction("Chunk ID: " + chunk.getID() + " on " + + (chunk.getBuddyGroupID() + ? "buddy group " + StringTk::uintToStr(chunk.getBuddyGroupID()) + : "target " + StringTk::uintToStr(chunk.getTargetID()))); + + state.lastID = chunk.getID(); + + switch(state.lastChunkAction) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_DELETECHUNK: { + FhgfsOpsErr refErr; + NodeStore* storageNodeStore = Program::getApp()->getStorageNodes(); + auto node = storageNodeStore->referenceNodeByTargetID(chunk.getTargetID(), + Program::getApp()->getTargetMapper(), &refErr); + + if(!node) + { + FsckTkEx::fsckOutput("could not get storage target " + + StringTk::uintToStr(chunk.getTargetID() ), OutputOptions_LINEBREAK); + return; + } + + FsckChunkList chunks(1, chunk); + FsckChunkList failed; + + MsgHelperRepair::deleteChunks(*node, &chunks, &failed); + + if(failed.empty() ) + this->database->getChunksTable()->remove( + {db::EntryID::fromStr(chunk.getID()), chunk.getTargetID(), chunk.getBuddyGroupID()}); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairMissingContDir(FsckDirInode& inode, FsckErrCount& errCount, + UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Directory ID: " + inode.getID() + + "; Path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(inode.getID())) + "; " + + (inode.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + inode.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_CREATECONTDIR: { + FsckDirInodeList inodes(1, inode); + StringList failed; + + MsgHelperRepair::createContDirs(inode.getSaveNodeID(), inode.getIsBuddyMirrored(), &inodes, + &failed, secondariesSetBad); + + if(failed.empty() ) + { + // create a list of cont dirs from dir inode + FsckContDirList contDirs(1, + FsckContDir(inode.getID(), inode.getSaveNodeID(), inode.getIsBuddyMirrored())); + + this->database->getContDirsTable()->insert(contDirs); + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairOrphanedContDir(FsckContDir& dir, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Directory ID: " + dir.getID() + "; " + + (dir.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + dir.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_CREATEDEFAULTDIRINODE: { + FsckDirInodeList createdInodes; + MsgHelperRepair::createDefDirInodes(dir.getSaveNodeID(), dir.getIsBuddyMirrored(), + {std::make_tuple(dir.getID(), dir.getIsBuddyMirrored())}, &createdInodes, + secondariesSetBad); + + this->database->getDirInodesTable()->insert(createdInodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairWrongFileAttribs(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("File ID: " + error.first.getID() + "; Path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(error.first.getID())) + + "; " + (error.first.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + + error.first.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_UPDATEATTRIBS: { + if (error.second.size) + error.first.setFileSize(*error.second.size); + if (error.second.nlinks) + error.first.setNumHardLinks(*error.second.nlinks); + + FsckFileInodeList inodes(1, error.first); + FsckFileInodeList failed; + + MsgHelperRepair::updateFileAttribs(error.first.getSaveNodeID(), + error.first.getIsBuddyMirrored(), &inodes, &failed, secondariesSetBad); + + if(failed.empty() ) + this->database->getFileInodesTable()->update(inodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairWrongDirAttribs(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + std::string filePath = this->database->getDentryTable()->getPathOf( + db::EntryID::fromStr(error.first.getID() ) ); + FsckRepairAction action = prompt.chooseAction("Directory ID: " + error.first.getID() + + "; Path: " + filePath + "; " + + (error.first.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + + error.first.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_UPDATEATTRIBS: { + FsckDirInodeList inodes(1, error.first); + FsckDirInodeList failed; + + // update the file attribs in the inode objects (even though they may not be used + // on the server side, we need updated values here to update the DB + error.first.setSize(error.second.size); + error.first.setNumHardLinks(error.second.nlinks); + + MsgHelperRepair::updateDirAttribs(error.first.getSaveNodeID(), + error.first.getIsBuddyMirrored(), &inodes, &failed, secondariesSetBad); + + if(failed.empty() ) + this->database->getDirInodesTable()->update(inodes); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairFileWithMissingTargets(db::DirEntry& entry, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckDirEntry fsckEntry = entry; + + FsckRepairAction action = prompt.chooseAction("Entry ID: " + fsckEntry.getID() + + "; Path: " + this->database->getDentryTable()->getPathOf(entry) + "; " + + (entry.isBuddyMirrored ? "Buddy group: " : "Node: ") + + StringTk::uintToStr(entry.entryOwnerNodeID)); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + return; + + case FsckRepairAction_DELETEFILE: { + fsckEntry.setName(this->database->getDentryTable()->getNameOf(entry) ); + + FsckDirEntryList dentries(1, fsckEntry); + FsckDirEntryList failed; + + MsgHelperRepair::deleteFiles(fsckEntry.getSaveNodeID(), fsckEntry.getIsBuddyMirrored(), + &dentries, &failed); + + if(failed.empty() ) + this->deleteFilesFromDB(dentries); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairDirEntryWithBrokenByIDFile(db::DirEntry& entry, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckDirEntry fsckEntry = entry; + + FsckRepairAction action = prompt.chooseAction("Entry ID: " + fsckEntry.getID() + + "; Path: " + this->database->getDentryTable()->getPathOf(entry) + "; " + + (entry.isBuddyMirrored ? "Buddy group: " : "Node: ") + + StringTk::uintToStr(entry.entryOwnerNodeID)); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_RECREATEFSID: { + fsckEntry.setName(this->database->getDentryTable()->getNameOf(entry) ); + + FsckDirEntryList dentries(1, fsckEntry); + FsckDirEntryList failed; + + MsgHelperRepair::recreateFsIDs(fsckEntry.getSaveNodeID(), fsckEntry.getIsBuddyMirrored(), + &dentries, &failed, secondariesSetBad); + + if(failed.empty() ) + { + // create a FsID list from the dentry + FsckFsIDList idList(1, + FsckFsID(fsckEntry.getID(), fsckEntry.getParentDirID(), fsckEntry.getSaveNodeID(), + fsckEntry.getSaveDevice(), fsckEntry.getSaveInode(), + fsckEntry.getIsBuddyMirrored())); + + this->database->getFsIDsTable()->insert(idList); + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairOrphanedDentryByIDFile(FsckFsID& id, FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction( + "Entry ID: " + id.getID() + + "; Path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(id.getID())) + "; " + + (id.getIsBuddyMirrored() ? "Buddy group: " : "Node: ") + id.getSaveNodeID().str()); + + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_RECREATEDENTRY: { + FsckFsIDList fsIDs(1, id); + FsckFsIDList failed; + FsckDirEntryList createdDentries; + FsckFileInodeList createdInodes; + + MsgHelperRepair::recreateDentries(id.getSaveNodeID(), id.getIsBuddyMirrored(), &fsIDs, + &failed, &createdDentries, &createdInodes, secondariesSetBad); + + if(failed.empty() ) + { + this->database->getDentryTable()->insert(createdDentries); + this->database->getFileInodesTable()->insert(createdInodes); + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairChunkWithWrongPermissions(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt) +{ + errCount.fixableErrors++; + FsckRepairAction action = prompt.chooseAction( + "Chunk ID: " + error.first.getID() + "; " + + (error.first.getBuddyGroupID() + ? "Buddy group: " + StringTk::uintToStr(error.first.getBuddyGroupID()) + : "Target: " + StringTk::uintToStr(error.first.getTargetID())) + + "; File path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(error.first.getID()))); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_FIXPERMISSIONS: { + NodeStore* nodeStore = Program::getApp()->getStorageNodes(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + + // we will need the PathInfo later to send the SetAttr message and we don't have it in the + // chunk + PathInfoList pathInfoList(1, *error.second.getPathInfo() ); + + // set correct permissions + error.first.setUserID(error.second.getUserID() ); + error.first.setGroupID(error.second.getGroupID() ); + + FsckChunkList chunkList(1, error.first); + FsckChunkList failed; + + auto storageNode = nodeStore->referenceNode( + targetMapper->getNodeID(error.first.getTargetID() ) ); + MsgHelperRepair::fixChunkPermissions(*storageNode, chunkList, pathInfoList, failed); + + if(failed.empty() ) + this->database->getChunksTable()->update(chunkList); + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::repairWrongChunkPath(std::pair& error, FsckErrCount& errCount, + UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Entry ID: " + error.first.getID() + + "; " + (error.first.getBuddyGroupID() + ? "Group: " + StringTk::uintToStr(error.first.getBuddyGroupID()) + : "Target: " + StringTk::uintToStr(error.first.getTargetID())) + + "; Chunk path: " + error.first.getSavedPath()->str() + + "; File path: " + + this->database->getDentryTable()->getPathOf(db::EntryID::fromStr(error.first.getID() ) ) ); + + switch(action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_MOVECHUNK: { + NodeStore* nodeStore = Program::getApp()->getStorageNodes(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + + auto storageNode = nodeStore->referenceNode( + targetMapper->getNodeID(error.first.getTargetID() ) ); + + std::string moveToPath = DatabaseTk::calculateExpectedChunkPath(error.first.getID(), + error.second.getPathInfo()->getOrigUID(), + error.second.getPathInfo()->getOrigParentEntryID(), + error.second.getPathInfo()->getFlags() ); + + if(MsgHelperRepair::moveChunk(*storageNode, error.first, moveToPath, false) ) + { + FsckChunkList chunks(1, error.first); + this->database->getChunksTable()->update(chunks); + break; + } + + // chunk file exists at the correct location + FsckTkEx::fsckOutput("Chunk file for " + error.first.getID() + + " already exists at the correct location. ", OutputOptions_NONE); + + if(Program::getApp()->getConfig()->getAutomatic() ) + { + FsckTkEx::fsckOutput("Will not attempt automatic repair.", OutputOptions_LINEBREAK); + break; + } + + char chosen = 0; + + while(chosen != 'y' && chosen != 'n') + { + FsckTkEx::fsckOutput("Move anyway? (y/n) ", OutputOptions_NONE); + + std::string line; + std::getline(std::cin, line); + + if(line.size() == 1) + chosen = line[0]; + } + + if(chosen != 'y') + break; + + if(MsgHelperRepair::moveChunk(*storageNode, error.first, moveToPath, true) ) + { + FsckChunkList chunks(1, error.first); + this->database->getChunksTable()->update(chunks); + } + else + { + FsckTkEx::fsckOutput("Repair failed, see log file for more details.", + OutputOptions_LINEBREAK); + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::updateOldStyledHardlinks(db::FileInode& inode, FsckErrCount& errCount, + UserPrompter& prompt) +{ + errCount.fixableErrors++; + + FsckRepairAction action = prompt.chooseAction("Entry ID: " + inode.id.str() + + "; link count: " + std::to_string(inode.numHardlinks)); + + std::string linkName = this->database->getDentryTable()->getNameOf(inode.id); + switch (action) + { + case FsckRepairAction_UNDEFINED: + case FsckRepairAction_NOTHING: + break; + + case FsckRepairAction_UPDATEOLDTYLEDHARDLINKS: + { + StringList failedEntries; + EntryInfo entryInfo(NumNodeID(inode.saveNodeID), inode.parentDirID.str(), inode.id.str(), + linkName, DirEntryType_REGULARFILE, 0); + entryInfo.setInodeInlinedFlag(inode.isInlined); + entryInfo.setBuddyMirroredFlag(inode.isBuddyMirrored); + + MsgHelperRepair::deinlineFileInode(entryInfo.getOwnerNodeID(), &entryInfo, failedEntries, + secondariesSetBad); + + if (failedEntries.empty()) + { + FsckFileInode fsckInode = inode.toInodeWithoutStripes(); + fsckInode.setIsInlined(false); + + fsckInode.setStripeTargets(this->database->getFileInodesTable()->getStripeTargetsByKey(inode.id)); + FsckFileInodeList fsckInodeList{fsckInode}; + this->database->getFileInodesTable()->update(fsckInodeList); + } + + break; + } + + default: + throw std::runtime_error("bad repair action"); + } +} + +void ModeCheckFS::deleteFsIDsFromDB(FsckDirEntryList& dentries) +{ + // create the fsID list + FsckFsIDList fsIDs; + + for ( FsckDirEntryListIter iter = dentries.begin(); iter != dentries.end(); iter++ ) + { + FsckFsID fsID(iter->getID(), iter->getParentDirID(), iter->getSaveNodeID(), + iter->getSaveDevice(), iter->getSaveInode(), iter->getIsBuddyMirrored()); + fsIDs.push_back(fsID); + } + + this->database->getFsIDsTable()->remove(fsIDs); +} + +void ModeCheckFS::deleteFilesFromDB(FsckDirEntryList& dentries) +{ + for ( FsckDirEntryListIter dentryIter = dentries.begin(); dentryIter != dentries.end(); + dentryIter++ ) + { + std::pair inode = this->database->getFileInodesTable()->get( + dentryIter->getID() ); + + if(inode.first) + { + this->database->getFileInodesTable()->remove(inode.second.id); + this->database->getChunksTable()->remove(inode.second.id); + } + } + + this->database->getDentryTable()->remove(dentries); + + // delete the dentry-by-id files + this->deleteFsIDsFromDB(dentries); +} + +bool ModeCheckFS::ensureLostAndFoundExists() +{ + this->lostAndFoundNode = MsgHelperRepair::referenceLostAndFoundOwner(&this->lostAndFoundInfo); + + if(!this->lostAndFoundNode) + { + if(!MsgHelperRepair::createLostAndFound(this->lostAndFoundNode, this->lostAndFoundInfo) ) + return false; + } + + std::pair dbLaF = this->database->getDirInodesTable()->get( + this->lostAndFoundInfo.getEntryID() ); + + if(dbLaF.first) + this->lostAndFoundInode.reset(new FsckDirInode(dbLaF.second) ); + + return true; +} + +void ModeCheckFS::releaseLostAndFound() +{ + if(!this->lostAndFoundNode) + return; + + if(this->lostAndFoundInode) + { + FsckDirInodeList updates(1, *this->lostAndFoundInode); + this->database->getDirInodesTable()->update(updates); + } + + this->lostAndFoundInode.reset(); +} diff --git a/fsck/source/modes/ModeCheckFS.h b/fsck/source/modes/ModeCheckFS.h new file mode 100644 index 0000000..36b8609 --- /dev/null +++ b/fsck/source/modes/ModeCheckFS.h @@ -0,0 +1,170 @@ +#ifndef MODECHECKFS_H +#define MODECHECKFS_H + +#include +#include + +#include +#include +#include + +class UserPrompter +{ + public: + template + UserPrompter(const FsckRepairAction (&possibleActions)[Actions], + FsckRepairAction defaultRepairAction); + + FsckRepairAction chooseAction(const std::string& prompt); + + private: + bool askForAction; + std::vector possibleActions; + FsckRepairAction repairAction; +}; + +struct RepairChunkState +{ + UserPrompter* prompt; + std::string lastID; + FsckRepairAction lastChunkAction; +}; + +struct FsckErrCount +{ + FsckErrCount() : unfixableErrors(0), fixableErrors(0) + { + } + + FsckErrCount operator+(const FsckErrCount& other) + { + unfixableErrors += other.unfixableErrors; + fixableErrors += other.fixableErrors; + return *this; + } + + void operator+=(const FsckErrCount& other) + { + *this = *this + other; + } + + uint64_t getTotalErrors() const + { + return unfixableErrors + fixableErrors; + } + + uint64_t unfixableErrors; + uint64_t fixableErrors; +}; + +class ModeCheckFS : public Mode +{ + public: + ModeCheckFS(); + virtual ~ModeCheckFS(); + + static void printHelp(); + + virtual int execute(); + + private: + boost::scoped_ptr database; + + LogContext log; + + NodeHandle lostAndFoundNode; + EntryInfo lostAndFoundInfo; + boost::shared_ptr lostAndFoundInode; + + std::set secondariesSetBad; + + int initDatabase(); + void printHeaderInformation(); + void disposeUnusedFiles(); + FhgfsOpsErr gatherData(bool forceRestart); + + template + FsckErrCount checkAndRepairGeneric(Cursor cursor, + void (ModeCheckFS::*repair)(Obj&, FsckErrCount&, State&), State& state); + + FsckErrCount checkAndRepairDanglingDentry(); + FsckErrCount checkAndRepairWrongInodeOwner(); + FsckErrCount checkAndRepairWrongOwnerInDentry(); + FsckErrCount checkAndRepairOrphanedContDir(); + FsckErrCount checkAndRepairOrphanedDirInode(); + FsckErrCount checkAndRepairOrphanedFileInode(); + FsckErrCount checkAndRepairDuplicateInodes(); + FsckErrCount checkAndRepairOrphanedChunk(); + FsckErrCount checkAndRepairMissingContDir(); + FsckErrCount checkAndRepairWrongFileAttribs(); + FsckErrCount checkAndRepairWrongDirAttribs(); + FsckErrCount checkAndRepairFilesWithMissingTargets(); + FsckErrCount checkAndRepairDirEntriesWithBrokeByIDFile(); + FsckErrCount checkAndRepairOrphanedDentryByIDFiles(); + FsckErrCount checkAndRepairChunksWithWrongPermissions(); + FsckErrCount checkMissingMirrorChunks(); + FsckErrCount checkMissingPrimaryChunks(); + FsckErrCount checkDifferingChunkAttribs(); + FsckErrCount checkAndRepairChunksInWrongPath(); + FsckErrCount checkAndUpdateOldStyledHardlinks(); + + void logDuplicateInodeID(checks::DuplicatedInode& dups, int&); + + FsckErrCount checkDuplicateChunks(); + void logDuplicateChunk(std::list& dups, FsckErrCount& errCount, int&); + + FsckErrCount checkDuplicateContDirs(); + void logDuplicateContDir(std::list& dups, FsckErrCount& errCount, int&); + + FsckErrCount checkMismirroredDentries(); + void logMismirroredDentry(db::DirEntry& entry, FsckErrCount& errCount, int&); + + FsckErrCount checkMismirroredDirectories(); + void logMismirroredDirectory(db::DirInode& dir, FsckErrCount& errCount, int&); + + FsckErrCount checkMismirroredFiles(); + void logMismirroredFile(db::FileInode& file, FsckErrCount& errCount, int&); + + FsckErrCount checkAndRepairMalformedChunk(); + void repairMalformedChunk(FsckChunk& chunk, FsckErrCount& errCount, UserPrompter& prompt); + + void checkAndRepair(); + + void repairDanglingDirEntry(db::DirEntry& entry, FsckErrCount& errCount, + std::pair& prompt); + void repairWrongInodeOwner(FsckDirInode& inode, FsckErrCount& errCount, + UserPrompter& prompt); + void repairWrongInodeOwnerInDentry(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt); + void repairOrphanedDirInode(FsckDirInode& inode, FsckErrCount& errCount, + UserPrompter& prompt); + void repairOrphanedFileInode(FsckFileInode& inode, FsckErrCount& errCount, + UserPrompter& prompt); + void repairDuplicateInode(checks::DuplicatedInode& dupInode, FsckErrCount& errCount, + UserPrompter& prompt); + void repairOrphanedChunk(FsckChunk& chunk, FsckErrCount& errCount, RepairChunkState& state); + void repairMissingContDir(FsckDirInode& inode, FsckErrCount& errCount, UserPrompter& prompt); + void repairOrphanedContDir(FsckContDir& dir, FsckErrCount& errCount, UserPrompter& prompt); + void repairWrongFileAttribs(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt); + void repairWrongDirAttribs(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt); + void repairFileWithMissingTargets(db::DirEntry& entry, FsckErrCount& errCount, + UserPrompter& prompt); + void repairDirEntryWithBrokenByIDFile(db::DirEntry& entry, FsckErrCount& errCount, + UserPrompter& prompt); + void repairOrphanedDentryByIDFile(FsckFsID& id, FsckErrCount& errCount, UserPrompter& prompt); + void repairChunkWithWrongPermissions(std::pair& error, + FsckErrCount& errCount, UserPrompter& prompt); + void repairWrongChunkPath(std::pair& error, FsckErrCount& errCount, + UserPrompter& prompt); + void updateOldStyledHardlinks(db::FileInode& inode, FsckErrCount& errCount, UserPrompter& prompt); + + void deleteFsIDsFromDB(FsckDirEntryList& dentries); + void deleteFilesFromDB(FsckDirEntryList& dentries); + + bool ensureLostAndFoundExists(); + void releaseLostAndFound(); +}; + +#endif /* MODECHECKFS_H */ diff --git a/fsck/source/modes/ModeEnableQuota.cpp b/fsck/source/modes/ModeEnableQuota.cpp new file mode 100644 index 0000000..e8fc0ab --- /dev/null +++ b/fsck/source/modes/ModeEnableQuota.cpp @@ -0,0 +1,116 @@ +#include "ModeEnableQuota.h" + +#include +#include +#include +#include +#include + +#include + +ModeEnableQuota::ModeEnableQuota() + : log("ModeEnableQuota") +{ +} + +void ModeEnableQuota::printHelp() +{ + std::cout << "MODE ARGUMENTS:" << std::endl; + std::cout << " Optional:" << std::endl; + std::cout << " --databasePath= Path to store the database files." << std::endl; + std::cout << " (Default: " << CONFIG_DEFAULT_DBPATH << ")" << std::endl; + std::cout << " --overwriteDbFile Overwrite an existing database file without prompt." << std::endl; + std::cout << " --logOutFile= Path to the fsck output file, which contains a copy of" << std::endl; + std::cout << " the console output." << std::endl; + std::cout << " (Default: " << CONFIG_DEFAULT_OUTFILE << ")" << std::endl; + std::cout << " --logStdFile= Path to the program error log file, which contains e.g." << std::endl; + std::cout << " network error messages." << std::endl; + std::cout << " (Default: " << CONFIG_DEFAULT_LOGFILE << ")" << std::endl; + std::cout << std::endl; + std::cout << "USAGE:" << std::endl; + std::cout << " This mode sets quota information on an existing file system." << std::endl; + std::cout << std::endl; + std::cout << " This is useful when quota support is being enabled on a file system instance" << std::endl; + std::cout << " that was previously used without quota support." << std::endl; + std::cout << std::endl; + std::cout << " Example: Set quota information" << std::endl; + std::cout << " $ beegfs-fsck --enablequota" << std::endl; +} + +int ModeEnableQuota::execute() +{ + App* app = Program::getApp(); + Config *cfg = app->getConfig(); + + if(this->checkInvalidArgs(cfg->getUnknownConfigArgs())) + return APPCODE_INVALID_CONFIG; + + FsckTkEx::printVersionHeader(false); + printHeaderInformation(); + + if ( !FsckTkEx::checkReachability() ) + return APPCODE_COMMUNICATION_ERROR; + + fixPermissions(); + + return APPCODE_NO_ERROR; +} + +void ModeEnableQuota::printHeaderInformation() +{ + Config* cfg = Program::getApp()->getConfig(); + + // get the current time and create some nice output, so the user can see at which time the run + // was started (especially nice to find older runs in the log file) + time_t t; + time(&t); + std::string timeStr = std::string(ctime(&t)); + FsckTkEx::fsckOutput( + "Started BeeGFS fsck in enableQuota mode [" + timeStr.substr(0, timeStr.length() - 1) + + "]\nLog will be written to " + cfg->getLogStdFile() + + "\nDatabase files will be saved in " + cfg->getDatabasePath(), + OutputOptions_LINEBREAK | OutputOptions_HEADLINE); +} + +void ModeEnableQuota::fixPermissions() +{ + SynchronizedCounter finishedWork; + unsigned generatedWork = 0; + + AtomicUInt64 fileCount; + AtomicUInt64 errorCount; + + NodeStore* metaNodes = Program::getApp()->getMetaNodes(); + + auto nodes = metaNodes->referenceAllNodes(); + + for (auto iter = nodes.begin(); iter != nodes.end(); iter++) + { + generatedWork++; + Program::getApp()->getWorkQueue()->addIndirectWork(new AdjustChunkPermissionsWork(**iter, + &finishedWork, &fileCount, &errorCount)); + } + + FsckTkEx::fsckOutput("Processed 0 entries", OutputOptions_ADDLINEBREAKBEFORE | + OutputOptions_LINEDELETE | OutputOptions_NOLOG); + + // wait for all packages to finish, because we cannot proceed if not all data was fetched + // BUT : update output each OUTPUT_INTERVAL_MS ms + while (! finishedWork.timedWaitForCount(generatedWork, MODEENABLEQUOTA_OUTPUT_INTERVAL_MS) ) + { + FsckTkEx::fsckOutput("Processed " + StringTk::uint64ToStr(fileCount.read()) + " entries", + OutputOptions_LINEDELETE | OutputOptions_NOLOG); + } + + FsckTkEx::fsckOutput("Processed " + StringTk::uint64ToStr(fileCount.read()) + " entries", + OutputOptions_LINEDELETE | OutputOptions_NOLOG | OutputOptions_LINEBREAK); + + if ( errorCount.read() > 0 ) + { + FsckTkEx::fsckOutput("", OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + StringTk::uint64ToStr(errorCount.read()) + + " errors occurred. Please see Metadata server logs for more details.", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_LINEBREAK); + } +} diff --git a/fsck/source/modes/ModeEnableQuota.h b/fsck/source/modes/ModeEnableQuota.h new file mode 100644 index 0000000..fec270b --- /dev/null +++ b/fsck/source/modes/ModeEnableQuota.h @@ -0,0 +1,27 @@ +#ifndef MODEENABLEQUOTA_H +#define MODEENABLEQUOTA_H + +#include +#include +#include + +#define MODEENABLEQUOTA_OUTPUT_INTERVAL_MS 5000 + +class ModeEnableQuota : public Mode +{ + public: + ModeEnableQuota(); + + static void printHelp(); + + virtual int execute(); + + private: + LogContext log; + + void printHeaderInformation(); + + void fixPermissions(); +}; + +#endif /* MODEENABLEQUOTA_H */ diff --git a/fsck/source/modes/ModeHelp.cpp b/fsck/source/modes/ModeHelp.cpp new file mode 100644 index 0000000..57d305f --- /dev/null +++ b/fsck/source/modes/ModeHelp.cpp @@ -0,0 +1,99 @@ +#include "ModeHelp.h" + +#include + +#include +#include + +int ModeHelp::execute() +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + RunMode runMode = cfg->determineRunMode(); + + if(runMode == RunMode_INVALID) + printGeneralHelp(); + else + printSpecificHelp(runMode); + + std::cout << std::endl; + + return APPCODE_NO_ERROR; +} + +void ModeHelp::printGeneralHelp() +{ + std::cout << "BeeGFS File System Check (http://www.beegfs.com)" << std::endl; + std::cout << "Version: " << BEEGFS_VERSION << std::endl; + std::cout << std::endl; + std::cout << "GENERAL USAGE:" << std::endl; + std::cout << " $ beegfs-fsck -- --help" << std::endl; + std::cout << " $ beegfs-fsck -- [mode_arguments] [client_arguments]" << std::endl; + std::cout << std::endl; + std::cout << "MODES:" << std::endl; + std::cout << " --checkfs => Perform a full check and optional repair of " << std::endl; + std::cout << " a BeeGFS instance." << std::endl; + std::cout << " --enablequota => Set attributes needed for quota support in FhGFS." << std::endl; + std::cout << " Can be used to enable quota support on an existing" << std::endl; + std::cout << " system." << std::endl; + std::cout << std::endl; + std::cout << "USAGE:" << std::endl; + std::cout << " This is the BeeGFS file system consistency check and repair tool." << std::endl; + std::cout << std::endl; + std::cout << " Choose a mode from the list above and use the parameter \"--help\"" << std::endl; + std::cout << " to show arguments and usage examples for that particular mode." << std::endl; + std::cout << std::endl; + std::cout << " (Running beegfs-fsck requires root privileges.)" << std::endl; + std::cout << std::endl; + std::cout << " Example: Show help for mode \"--checkfs\"" << std::endl; + std::cout << " $ beegfs-fsck --checkfs --help" << std::endl; +} + + +/** + * Note: don't call this with RunMode_INVALID. + */ +void ModeHelp::printSpecificHelp(RunMode runMode) +{ + printSpecificHelpHeader(); // print general usage and client options info + + switch(runMode) + { + case RunMode_CHECKFS: + { + ModeCheckFS::printHelp(); + } break; + + case RunMode_ENABLEQUOTA: + { + ModeEnableQuota::printHelp(); + } break; + + default: + { + std::cerr << "Error: Unhandled mode specified. Mode number: " << runMode << std::endl; + std::cout << std::endl; + + printGeneralHelp(); + } break; + } + +} + +/** + * Print the help message header that applies to any specific mode help. Contains general usage + * and client options info. + */ +void ModeHelp::printSpecificHelpHeader() +{ + std::cout << "GENERAL USAGE:" << std::endl; + std::cout << " $ beegfs-fsck -- [mode_arguments] [client_arguments]" << std::endl; + std::cout << std::endl; + std::cout << "CLIENT ARGUMENTS:" << std::endl; + std::cout << " --cfgFile= Path to BeeGFS client config file." << std::endl; + std::cout << " (Default: " CONFIG_DEFAULT_CFGFILENAME ")" << std::endl; + std::cout << " --= Any setting from the client config file to override" << std::endl; + std::cout << " the config file values (e.g. \"--logLevel=5\")." << std::endl; + std::cout << std::endl; +} diff --git a/fsck/source/modes/ModeHelp.h b/fsck/source/modes/ModeHelp.h new file mode 100644 index 0000000..b65b7f9 --- /dev/null +++ b/fsck/source/modes/ModeHelp.h @@ -0,0 +1,24 @@ +#ifndef MODEHELP_H_ +#define MODEHELP_H_ + +#include +#include + +#include "Mode.h" + + +class ModeHelp : public Mode +{ + public: + ModeHelp() {}; + + virtual int execute(); + + private: + void printGeneralHelp(); + void printSpecificHelp(RunMode runMode); + void printSpecificHelpHeader(); +}; + + +#endif /*MODEHELP_H_*/ diff --git a/fsck/source/net/message/NetMessageFactory.cpp b/fsck/source/net/message/NetMessageFactory.cpp new file mode 100644 index 0000000..42d6c29 --- /dev/null +++ b/fsck/source/net/message/NetMessageFactory.cpp @@ -0,0 +1,125 @@ +// control messages +#include + +// fsck messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// nodes messages +#include +#include +#include +#include +#include +#include + +// storage messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// general includes +#include +#include + +#include "NetMessageFactory.h" + +/** + * @return NetMessage that must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr NetMessageFactory::createFromMsgType(unsigned short msgType) const +{ + NetMessage* msg; + + switch(msgType) + { + // The following lines are grouped by "type of the message" and ordered alphabetically inside + // the groups. There should always be one message per line to keep a clear layout (although + // this might lead to lines that are longer than usual) + + // control messages + case NETMSGTYPE_GenericResponse: { msg = new GenericResponseMsg(); } break; + + // nodes messages + case NETMSGTYPE_GetNodesResp: { msg = new GetNodesRespMsg(); } break; + case NETMSGTYPE_Heartbeat: { msg = new HeartbeatMsgEx(); } break; + case NETMSGTYPE_GetMirrorBuddyGroupsResp: { msg = new GetMirrorBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetTargetMappingsResp: { msg = new GetTargetMappingsRespMsg(); } break; + case NETMSGTYPE_GetTargetStatesResp: { msg = new GetTargetStatesRespMsg(); } break; + case NETMSGTYPE_SetTargetConsistencyStatesResp: { msg = new SetTargetConsistencyStatesRespMsg(); } break; + + // storage messages + case NETMSGTYPE_FindOwnerResp: { msg = new FindOwnerRespMsg(); } break; + case NETMSGTYPE_ListDirFromOffsetResp: { msg = new ListDirFromOffsetRespMsg(); } break; + case NETMSGTYPE_RmDirEntryResp: { msg = new RmDirEntryRespMsg(); } break; + case NETMSGTYPE_GetEntryInfoResp: { msg = new GetEntryInfoRespMsg(); } break; + case NETMSGTYPE_StatResp: { msg = new StatRespMsg(); } break; + case NETMSGTYPE_StatStoragePathResp: { msg = new StatStoragePathRespMsg(); } break; + case NETMSGTYPE_MkDirResp: { msg = new MkDirRespMsg(); } break; + case NETMSGTYPE_SetLocalAttrResp: { msg = new SetLocalAttrRespMsg(); } break; + case NETMSGTYPE_UnlinkFileResp: { msg = new UnlinkFileRespMsg(); } break; + case NETMSGTYPE_MoveFileInodeResp: {msg = new MoveFileInodeRespMsg(); } break; + + //fsck + case NETMSGTYPE_RetrieveDirEntriesResp: { msg = new RetrieveDirEntriesRespMsg(); } break; + case NETMSGTYPE_RetrieveInodesResp: { msg = new RetrieveInodesRespMsg(); } break; + case NETMSGTYPE_RetrieveFsIDsResp: { msg = new RetrieveFsIDsRespMsg(); } break; + case NETMSGTYPE_DeleteDirEntriesResp: { msg = new DeleteDirEntriesRespMsg(); } break; + case NETMSGTYPE_CreateDefDirInodesResp: { msg = new CreateDefDirInodesRespMsg(); } break; + case NETMSGTYPE_FixInodeOwnersResp: { msg = new FixInodeOwnersRespMsg(); } break; + case NETMSGTYPE_FixInodeOwnersInDentryResp: { msg = new FixInodeOwnersInDentryRespMsg(); } break; + case NETMSGTYPE_LinkToLostAndFoundResp : { msg = new LinkToLostAndFoundRespMsg(); } break; + case NETMSGTYPE_DeleteChunksResp: { msg = new DeleteChunksRespMsg(); } break; + case NETMSGTYPE_CreateEmptyContDirsResp: { msg = new CreateEmptyContDirsRespMsg(); } break; + case NETMSGTYPE_UpdateFileAttribsResp: { msg = new UpdateFileAttribsRespMsg(); } break; + case NETMSGTYPE_UpdateDirAttribsResp: { msg = new UpdateDirAttribsRespMsg(); } break; + case NETMSGTYPE_RecreateFsIDsResp: { msg = new RecreateFsIDsRespMsg(); } break; + case NETMSGTYPE_RecreateDentriesResp: { msg = new RecreateDentriesRespMsg(); } break; + case NETMSGTYPE_FsckSetEventLoggingResp: { msg = new FsckSetEventLoggingRespMsg(); } break; + case NETMSGTYPE_FsckModificationEvent: { msg = new FsckModificationEventMsgEx(); } break; + case NETMSGTYPE_AdjustChunkPermissionsResp: { msg = new AdjustChunkPermissionsRespMsg(); } break; + case NETMSGTYPE_FetchFsckChunkListResp: { msg = new FetchFsckChunkListRespMsg(); } break; + case NETMSGTYPE_MoveChunkFileResp: { msg = new MoveChunkFileRespMsg(); } break; + case NETMSGTYPE_RemoveInodesResp: { msg = new RemoveInodesRespMsg(); } break; + case NETMSGTYPE_CheckAndRepairDupInodeResp: { msg = new CheckAndRepairDupInodeRespMsg(); } break; + + //testing + case NETMSGTYPE_Dummy: { msg = new DummyMsgEx(); } break; + + default: + { + msg = new SimpleMsg(NETMSGTYPE_Invalid); + } break; + } + + return std::unique_ptr(msg); +} + diff --git a/fsck/source/net/message/NetMessageFactory.h b/fsck/source/net/message/NetMessageFactory.h new file mode 100644 index 0000000..0d0e61e --- /dev/null +++ b/fsck/source/net/message/NetMessageFactory.h @@ -0,0 +1,16 @@ +#ifndef NETMESSAGEFACTORY_H_ +#define NETMESSAGEFACTORY_H_ + +#include +#include + +class NetMessageFactory : public AbstractNetMessageFactory +{ + public: + NetMessageFactory() {} + + protected: + virtual std::unique_ptr createFromMsgType(unsigned short msgType) const override; +} ; + +#endif /*NETMESSAGEFACTORY_H_*/ diff --git a/fsck/source/net/message/fsck/FsckModificationEventMsgEx.cpp b/fsck/source/net/message/fsck/FsckModificationEventMsgEx.cpp new file mode 100644 index 0000000..6bb410d --- /dev/null +++ b/fsck/source/net/message/fsck/FsckModificationEventMsgEx.cpp @@ -0,0 +1,26 @@ +#include + +#include "FsckModificationEventMsgEx.h" + +bool FsckModificationEventMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("FsckModificationEventMsg incoming"); + + App* app = Program::getApp(); + ModificationEventHandler *eventHandler = app->getModificationEventHandler(); + + StringList& entryIDList = this->getEntryIDList(); + + bool addRes = eventHandler->add(getModificationEventTypeList(), entryIDList); + if (!addRes) + { + log.logErr("Unable to add modification event to database"); + return false; + } + + bool ackRes = acknowledge(ctx); + if (!ackRes) + log.logErr("Unable to send ack to metadata server"); + + return true; +} diff --git a/fsck/source/net/message/fsck/FsckModificationEventMsgEx.h b/fsck/source/net/message/fsck/FsckModificationEventMsgEx.h new file mode 100644 index 0000000..b8237af --- /dev/null +++ b/fsck/source/net/message/fsck/FsckModificationEventMsgEx.h @@ -0,0 +1,12 @@ +#ifndef FSCKMODIFICATIONEVENTMSGEX_H +#define FSCKMODIFICATIONEVENTMSGEX_H + +#include + +class FsckModificationEventMsgEx : public FsckModificationEventMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + +#endif /*FSCKMODIFICATIONEVENTMSGEX_H*/ diff --git a/fsck/source/net/message/nodes/HeartbeatMsgEx.cpp b/fsck/source/net/message/nodes/HeartbeatMsgEx.cpp new file mode 100644 index 0000000..ee2f17a --- /dev/null +++ b/fsck/source/net/message/nodes/HeartbeatMsgEx.cpp @@ -0,0 +1,102 @@ +#include +#include +#include "HeartbeatMsgEx.h" + +#include + +bool HeartbeatMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Heartbeat incoming"); + + App* app = Program::getApp(); + + bool isNodeNew; + + // construct node + NicAddressList& nicList = getNicList(); + + auto node = std::make_shared(getNodeType(), getNodeID(), getNodeNumID(), getPortUDP(), + getPortTCP(), nicList); + + // set local nic capabilities + Node& localNode = app->getLocalNode(); + NicAddressList localNicList(localNode.getNicList()); + NicListCapabilities localNicCaps; + + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + node->getConnPool()->setLocalNicList(localNicList, localNicCaps); + + std::string nodeIDWithTypeStr = node->getNodeIDWithTypeStr(); + + + // add/update node in store + NodeStore* nodes = NULL; + + switch(getNodeType() ) + { + case NODETYPE_Meta: + nodes = app->getMetaNodes(); break; + + case NODETYPE_Storage: + nodes = app->getStorageNodes(); break; + + + case NODETYPE_Mgmt: + nodes = app->getMgmtNodes(); break; + + default: + { + LOG(GENERAL, ERR, "Invalid node type.", + ("Node Type", getNodeType()), + ("Sender", ctx.peerName()), + ("NodeID", getNodeNumID()), + ("Port (UDP)", getPortUDP()), + ("Port (TCP)", getPortTCP()) + ); + + goto ack_resp; + } break; + } + + + isNodeNew = (nodes->addOrUpdateNode(std::move(node)) == NodeStoreResult::Added); + if(isNodeNew) + { + bool supportsRDMA = NetworkInterfaceCard::supportsRDMA(&nicList); + + log.log(Log_WARNING, std::string("New node: ") + + nodeIDWithTypeStr + "; " + + std::string(supportsRDMA ? "RDMA; " : "") + + std::string("Source: ") + ctx.peerName() ); + + log.log(Log_DEBUG, std::string("Number of nodes in the system: ") + + StringTk::intToStr(nodes->getSize() ) + + std::string(" (Type: ") + boost::lexical_cast(getNodeType()) + ")"); + } + + + processIncomingRoot(); + +ack_resp: + acknowledge(ctx); + + return true; +} + +/** + * Handles the contained root information. + */ +void HeartbeatMsgEx::processIncomingRoot() +{ + LogContext log("Heartbeat incoming"); + + // check whether root info is defined + if( (getNodeType() != NODETYPE_Meta) || !getRootNumID() ) + return; + + // try to apply the contained root info + if(Program::getApp()->getMetaRoot().setIfDefault(getRootNumID(), getRootIsBuddyMirrored())) + { + log.log(Log_CRITICAL, "Root (by Heartbeat): " + getRootNumID().str() ); + } +} diff --git a/fsck/source/net/message/nodes/HeartbeatMsgEx.h b/fsck/source/net/message/nodes/HeartbeatMsgEx.h new file mode 100644 index 0000000..9552f08 --- /dev/null +++ b/fsck/source/net/message/nodes/HeartbeatMsgEx.h @@ -0,0 +1,15 @@ +#ifndef HEARTBEATMSGEX_H_ +#define HEARTBEATMSGEX_H_ + +#include + +class HeartbeatMsgEx : public HeartbeatMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + void processIncomingRoot(); +}; + +#endif /*HEARTBEATMSGEX_H_*/ diff --git a/fsck/source/net/message/testing/DummyMsgEx.cpp b/fsck/source/net/message/testing/DummyMsgEx.cpp new file mode 100644 index 0000000..18f1eec --- /dev/null +++ b/fsck/source/net/message/testing/DummyMsgEx.cpp @@ -0,0 +1,9 @@ +#include +#include "DummyMsgEx.h" + +bool DummyMsgEx::processIncoming(ResponseContext& ctx) +{ + ctx.sendResponse(DummyMsg() ); + + return true; +} diff --git a/fsck/source/net/message/testing/DummyMsgEx.h b/fsck/source/net/message/testing/DummyMsgEx.h new file mode 100644 index 0000000..f9c6488 --- /dev/null +++ b/fsck/source/net/message/testing/DummyMsgEx.h @@ -0,0 +1,20 @@ +#ifndef DUMMYMSGEX_H +#define DUMMYMSGEX_H + +#include + +/* + * this is intended for testing purposes only + * just sends another DummyMsg back to the sender + */ + +class DummyMsgEx : public DummyMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + void processIncomingRoot(); +}; + +#endif /*DUMMYMSGEX_H*/ diff --git a/fsck/source/net/msghelpers/MsgHelperRepair.cpp b/fsck/source/net/msghelpers/MsgHelperRepair.cpp new file mode 100644 index 0000000..060be4d --- /dev/null +++ b/fsck/source/net/msghelpers/MsgHelperRepair.cpp @@ -0,0 +1,951 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "MsgHelperRepair.h" + +#include + +template +static bool setSecondaryBad(uint16_t groupID, std::set& secondariesWithRepair, + const std::list& args, std::list& failed) +{ + auto* bgm = Program::getApp()->getMetaMirrorBuddyGroupMapper(); + NumNodeID secondary(bgm->getSecondaryTargetID(groupID)); + + if (secondariesWithRepair.count(secondary)) + return true; + + auto setRes = MsgHelperRepair::setNodeState(secondary, TargetConsistencyState_BAD); + if (setRes == FhgfsOpsErr_SUCCESS) + { + secondariesWithRepair.insert(NumNodeID(bgm->getSecondaryTargetID(groupID))); + return true; + } + + LOG(GENERAL, ERR, "Failed to set secondary consistency state, not attempting repair action.", + groupID, setRes); + failed = args; + return false; +} + + + +FhgfsOpsErr MsgHelperRepair::setNodeState(NumNodeID node, TargetConsistencyState state) +{ + std::list targets(1, node.val()); + std::list states(1, state); + SetTargetConsistencyStatesMsg msg(NODETYPE_Meta, &targets, &states, false); + + { + auto secondary = Program::getApp()->getMetaNodes()->referenceNode(node); + + MessagingTk::requestResponse(*secondary, msg, NETMSGTYPE_SetTargetConsistencyStatesResp); + } + + { + auto mgmt = Program::getApp()->getMgmtNodes()->referenceFirstNode(); + + const auto respMsg = MessagingTk::requestResponse(*mgmt, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + if (!respMsg) + return FhgfsOpsErr_COMMUNICATION; + + return static_cast(*respMsg).getResult(); + } +} + +void MsgHelperRepair::deleteDanglingDirEntries(NumNodeID node, bool isBuddyMirrored, + FsckDirEntryList* dentries, FsckDirEntryList* failedDeletes, + std::set& secondariesWithRepair) +{ + DeleteDirEntriesMsg deleteDirEntriesMsg(dentries); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &deleteDirEntriesMsg, NETMSGTYPE_DeleteDirEntriesResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *dentries, *failedDeletes)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* deleteDirEntriesRespMsg = (DeleteDirEntriesRespMsg*) rrArgs.outRespMsg.get(); + + deleteDirEntriesRespMsg->getFailedEntries().swap(*failedDeletes); + + if (! failedDeletes->empty()) + { + for (auto iter = failedDeletes->begin(); iter != failedDeletes->end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to delete directory entry from metadata node.", + node, isBuddyMirrored, ("entryID", iter->getID())); + } + } + else + { + *failedDeletes = *dentries; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } +} + +void MsgHelperRepair::createDefDirInodes(NumNodeID node, bool isBuddyMirrored, + const std::vector>& entries, FsckDirInodeList* createdInodes, + std::set& secondariesWithRepair) +{ + StringList failedInodeIDs; + + CreateDefDirInodesMsg createDefDirInodesMsgEx(entries); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &createDefDirInodesMsgEx, NETMSGTYPE_CreateDefDirInodesResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, {}, *createdInodes)) + { + for (auto it = entries.begin(); it != entries.end(); ++it) + failedInodeIDs.push_back(std::get<0>(*it)); + return; + } + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* createDefDirInodesRespMsg = (CreateDefDirInodesRespMsg*) rrArgs.outRespMsg.get(); + + createDefDirInodesRespMsg->getFailedInodeIDs().swap(failedInodeIDs); + createDefDirInodesRespMsg->getCreatedInodes().swap(*createdInodes); + } + else + { + for (auto it = entries.begin(); it != entries.end(); ++it) + failedInodeIDs.push_back(std::get<0>(*it)); + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if (! failedInodeIDs.empty()) + { + for (StringListIter iter = failedInodeIDs.begin(); iter != failedInodeIDs.end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to create default directory inode.", node, isBuddyMirrored, + ("entryID", *iter)); + } +} + +void MsgHelperRepair::correctInodeOwnersInDentry(NumNodeID node, bool isBuddyMirrored, + FsckDirEntryList* dentries, NumNodeIDList* owners, FsckDirEntryList* failedCorrections, + std::set& secondariesWithRepair) +{ + FixInodeOwnersInDentryMsg fixInodeOwnersMsg(*dentries, *owners); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &fixInodeOwnersMsg, NETMSGTYPE_FixInodeOwnersInDentryResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *dentries, *failedCorrections)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* fixInodeOwnersRespMsg = (FixInodeOwnersInDentryRespMsg*) rrArgs.outRespMsg.get(); + + fixInodeOwnersRespMsg->getFailedEntries().swap(*failedCorrections); + } + else + { + *failedCorrections = *dentries; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if (! failedCorrections->empty()) + { + for (auto iter = failedCorrections->begin(); iter != failedCorrections->end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to correct inode owner information in dentry.", + node, isBuddyMirrored, ("entryID", iter->getID())); + } +} + +void MsgHelperRepair::correctInodeOwners(NumNodeID node, bool isBuddyMirrored, + FsckDirInodeList* dirInodes, FsckDirInodeList* failedCorrections, + std::set& secondariesWithRepair) +{ + FixInodeOwnersMsg fixInodeOwnersMsg(dirInodes); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &fixInodeOwnersMsg, NETMSGTYPE_FixInodeOwnersResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *dirInodes, *failedCorrections)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* fixInodeOwnersRespMsg = (FixInodeOwnersRespMsg*) rrArgs.outRespMsg.get(); + + fixInodeOwnersRespMsg->getFailedInodes().swap(*failedCorrections); + } + else + { + *failedCorrections = *dirInodes; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if (! failedCorrections->empty()) + { + for (auto iter = failedCorrections->begin(); iter != failedCorrections->end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to correct inode owner information.", node, isBuddyMirrored, + ("entryID", iter->getID())); + } +} + +void MsgHelperRepair::deleteFiles(NumNodeID node, bool isBuddyMirrored, FsckDirEntryList* dentries, + FsckDirEntryList* failedDeletes) +{ + const char* logContext = "MsgHelperRepair (deleteFiles)"; + + for ( FsckDirEntryListIter iter = dentries->begin(); iter != dentries->end(); iter++ ) + { + EntryInfo parentInfo(node, "", iter->getParentDirID(), "", DirEntryType_DIRECTORY, 0); + + std::string entryName = iter->getName(); + UnlinkFileMsg unlinkFileMsg(&parentInfo, entryName); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &unlinkFileMsg, NETMSGTYPE_UnlinkFileResp); + + if (isBuddyMirrored) + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + const auto unlinkFileRespMsg = (const UnlinkFileRespMsg*) rrArgs.outRespMsg.get(); + + // get result + int unlinkRes = unlinkFileRespMsg->getValue(); + + if ( unlinkRes ) + { + LogContext(logContext).log(Log_CRITICAL, + "Failed to delete file; entryID: " + iter->getID()); + failedDeletes->push_back(*iter); + } + } + else + { + failedDeletes->push_back(*iter); + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + } +} + +void MsgHelperRepair::deleteChunks(Node& node, FsckChunkList* chunks, FsckChunkList* failedDeletes) +{ + const char* logContext = "MsgHelperRepair (deleteChunks)"; + + DeleteChunksMsg deleteChunksMsg(chunks); + + const auto respMsg = MessagingTk::requestResponse(node, deleteChunksMsg, + NETMSGTYPE_DeleteChunksResp); + + if (respMsg) + { + DeleteChunksRespMsg* deleteChunksRespMsg = (DeleteChunksRespMsg*) respMsg.get(); + + deleteChunksRespMsg->getFailedChunks().swap(*failedDeletes); + + if (! failedDeletes->empty()) + { + for (FsckChunkListIter iter = failedDeletes->begin(); iter != failedDeletes->end(); + iter++) + { + LogContext(logContext).log(Log_CRITICAL, "Failed to delete chunk entry. targetID: " + + StringTk::uintToStr(iter->getTargetID()) + " chunkID: " + iter->getID()); + } + } + } + else + { + *failedDeletes = *chunks; + + LogContext(logContext).logErr("Communication error occured with node: " + node.getAlias()); + } +} + +NodeHandle MsgHelperRepair::referenceLostAndFoundOwner(EntryInfo* outLostAndFoundEntryInfo) +{ + const char* logContext = "MsgHelperRepair (referenceLostAndFoundOwner)"; + App* app = Program::getApp(); + +// find owner node + Path path("/" + std::string(META_LOSTANDFOUND_PATH)); + + NodeHandle ownerNode; + + FhgfsOpsErr findRes = MetadataTk::referenceOwner(&path, app->getMetaNodes(), ownerNode, + outLostAndFoundEntryInfo, app->getMetaRoot(), app->getMetaMirrorBuddyGroupMapper()); + + if ( findRes != FhgfsOpsErr_SUCCESS ) + { + LogContext(logContext).log(Log_DEBUG, "No owner node found for lost+found. Directory does not" + " seem to exist (yet)"); + } + + return ownerNode; +} + +bool MsgHelperRepair::createLostAndFound(NodeHandle& outReferencedNode, + EntryInfo& outLostAndFoundEntryInfo) +{ + const char* logContext = "MsgHelperRepair (createLostAndFound)"; + App* app = Program::getApp(); + NodeStore* metaNodes = app->getMetaNodes(); + bool retVal = false; + + // get root owner node and entryInfo + NodeHandle rootNode; + Path rootPath(""); + // rootPath.setAbsolute(true); + EntryInfo rootEntryInfo; + + FhgfsOpsErr findRes = MetadataTk::referenceOwner(&rootPath, metaNodes, + rootNode, &rootEntryInfo, app->getMetaRoot(), app->getMetaMirrorBuddyGroupMapper()); + + if ( findRes != FhgfsOpsErr_SUCCESS ) + { + LogContext(logContext).log(Log_CRITICAL, "Unable to reference metadata node for root " + "directory"); + return false; + } + + // create the directory + std::string lostFoundPathStr = META_LOSTANDFOUND_PATH; + UInt16List preferredNodes; + + MkDirMsg mkDirMsg(&rootEntryInfo, lostFoundPathStr , 0, 0, S_IFDIR | S_IRWXU | S_IRWXG, 0000, + &preferredNodes); + + RequestResponseNode rrNode(rootEntryInfo.getOwnerNodeID(), Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &mkDirMsg, NETMSGTYPE_MkDirResp); + + if (rootEntryInfo.getIsBuddyMirrored()) + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes != FhgfsOpsErr_SUCCESS) + return false; + + auto* mkDirRespMsg = (MkDirRespMsg*) rrArgs.outRespMsg.get(); + + FhgfsOpsErr mkDirRes = (FhgfsOpsErr) mkDirRespMsg->getResult(); + if ( mkDirRes != FhgfsOpsErr_SUCCESS ) + { + LogContext(logContext).log(Log_CRITICAL, + "Node encountered an error: " + boost::lexical_cast(mkDirRes)); + return false; + } + + // create seems to have succeeded + // copy is created because delete is called on mkDirRespMsg, but we still need this object + outLostAndFoundEntryInfo = *(mkDirRespMsg->getEntryInfo()); + + outReferencedNode = outLostAndFoundEntryInfo.getIsBuddyMirrored() + ? metaNodes->referenceNode( + NumNodeID( + app->getMetaMirrorBuddyGroupMapper()->getPrimaryTargetID( + outLostAndFoundEntryInfo.getOwnerNodeID().val()))) + : metaNodes->referenceNode(outLostAndFoundEntryInfo.getOwnerNodeID()); + retVal = true; + + return retVal; +} + +void MsgHelperRepair::linkToLostAndFound(Node& lostAndFoundNode, EntryInfo* lostAndFoundInfo, + FsckDirInodeList* dirInodes, FsckDirInodeList* failedInodes, FsckDirEntryList* createdDentries, + std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (linkToLostAndFound)"; + + if (lostAndFoundInfo->getIsBuddyMirrored() && + !setSecondaryBad(lostAndFoundInfo->getOwnerNodeID().val(), secondariesWithRepair, + {}, *createdDentries)) + { + return; + } + + LinkToLostAndFoundMsg linkToLostAndFoundMsg(lostAndFoundInfo, dirInodes); + + const auto respMsg = MessagingTk::requestResponse(lostAndFoundNode, linkToLostAndFoundMsg, + NETMSGTYPE_LinkToLostAndFoundResp); + + if (respMsg) + { + auto* linkToLostAndFoundRespMsg = (LinkToLostAndFoundRespMsg*) respMsg.get(); + + linkToLostAndFoundRespMsg->getFailedDirInodes().swap(*failedInodes); + linkToLostAndFoundRespMsg->getCreatedDirEntries().swap(*createdDentries); + } + else + { + *failedInodes = *dirInodes; + + LogContext(logContext).logErr("Communication error occured with node: " + + lostAndFoundNode.getAlias()); + } + + if (! failedInodes->empty()) + { + for (FsckDirInodeListIter iter = failedInodes->begin(); iter != failedInodes->end(); + iter++) + { + LogContext(logContext).log(Log_CRITICAL, "Failed to link directory inode to lost+found. " + "entryID: " + iter->getID()); + } + } +} + +void MsgHelperRepair::linkToLostAndFound(Node& lostAndFoundNode, EntryInfo* lostAndFoundInfo, + FsckFileInodeList* fileInodes, FsckFileInodeList* failedInodes, + FsckDirEntryList* createdDentries, std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (linkToLostAndFound)"; + + if (lostAndFoundInfo->getIsBuddyMirrored() && + !setSecondaryBad(lostAndFoundInfo->getOwnerNodeID().val(), secondariesWithRepair, + *fileInodes, *failedInodes)) + { + return; + } + + LinkToLostAndFoundMsg linkToLostAndFoundMsg(lostAndFoundInfo, fileInodes); + + const auto respMsg = MessagingTk::requestResponse(lostAndFoundNode, linkToLostAndFoundMsg, + NETMSGTYPE_LinkToLostAndFoundResp); + + if (respMsg) + { + auto* linkToLostAndFoundRespMsg = (LinkToLostAndFoundRespMsg*) respMsg.get(); + + linkToLostAndFoundRespMsg->getFailedFileInodes().swap(*failedInodes); + linkToLostAndFoundRespMsg->getCreatedDirEntries().swap(*createdDentries); + } + else + { + *failedInodes = *fileInodes; + + LogContext(logContext).logErr("Communication error occured with node: " + + lostAndFoundNode.getAlias()); + } + + if (! failedInodes->empty()) + { + for (FsckFileInodeListIter iter = failedInodes->begin(); iter != failedInodes->end(); + iter++) + { + LogContext(logContext).log(Log_CRITICAL, "Failed to link file inode to lost+found. " + "entryID: " + iter->getID()); + } + } +} + +void MsgHelperRepair::createContDirs(NumNodeID node, bool isBuddyMirrored, FsckDirInodeList* inodes, + StringList* failedCreates, std::set& secondariesWithRepair) +{ + // create a string list with the IDs + std::vector items; + for (FsckDirInodeListIter iter = inodes->begin(); iter != inodes->end(); iter++) + items.emplace_back(iter->getID(), iter->getIsBuddyMirrored()); + + CreateEmptyContDirsMsg createContDirsMsg(std::move(items)); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &createContDirsMsg, NETMSGTYPE_CreateEmptyContDirsResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, {}, *failedCreates)) + { + for (auto it = inodes->begin(); it != inodes->end(); ++it) + failedCreates->push_back(it->getID()); + return; + } + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* createContDirsRespMsg = (CreateEmptyContDirsRespMsg*) rrArgs.outRespMsg.get(); + + createContDirsRespMsg->getFailedIDs().swap(*failedCreates); + } + else + { + failedCreates->clear(); + for (auto it = inodes->begin(); it != inodes->end(); ++it) + failedCreates->push_back(it->getID()); + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if ( !failedCreates->empty() ) + { + for ( StringListIter iter = failedCreates->begin(); iter != failedCreates->end(); iter++ ) + LOG(GENERAL, CRITICAL, "Failed to create empty content directory.", ("dirID", *iter)); + } +} + +void MsgHelperRepair::updateFileAttribs(NumNodeID node, bool isBuddyMirrored, FsckFileInodeList* inodes, + FsckFileInodeList* failedUpdates, std::set& secondariesWithRepair) +{ + UpdateFileAttribsMsg updateFileAttribsMsg(inodes); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &updateFileAttribsMsg, NETMSGTYPE_UpdateFileAttribsResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *inodes, *failedUpdates)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* updateFileAttribsRespMsg = (UpdateFileAttribsRespMsg*) rrArgs.outRespMsg.get(); + + updateFileAttribsRespMsg->getFailedInodes().swap(*failedUpdates); + } + else + { + *failedUpdates = *inodes; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if ( !failedUpdates->empty() ) + { + for (auto iter = failedUpdates->begin(); iter != failedUpdates->end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to update attributes of file inode.", + ("entryID", iter->getID())); + } +} + +void MsgHelperRepair::updateDirAttribs(NumNodeID node, bool isBuddyMirrored, + FsckDirInodeList* inodes, FsckDirInodeList* failedUpdates, + std::set& secondariesWithRepair) +{ + UpdateDirAttribsMsg updateDirAttribsMsg(inodes); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &updateDirAttribsMsg, NETMSGTYPE_UpdateDirAttribsResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *inodes, *failedUpdates)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* updateDirAttribsRespMsg = (UpdateDirAttribsRespMsg*) rrArgs.outRespMsg.get(); + + updateDirAttribsRespMsg->getFailedInodes().swap(*failedUpdates); + } + else + { + *failedUpdates = *inodes; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if ( !failedUpdates->empty() ) + { + for (auto iter = failedUpdates->begin(); iter != failedUpdates->end(); iter++) + LOG(GENERAL, CRITICAL, "Failed to update attributes of directory inode.", + ("entryID", iter->getID())); + } +} + +void MsgHelperRepair::recreateFsIDs(NumNodeID node, bool isBuddyMirrored, + FsckDirEntryList* dentries, FsckDirEntryList* failedEntries, + std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (recreateFsIDs)"; + + RecreateFsIDsMsg recreateFsIDsMsg(dentries); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &recreateFsIDsMsg, NETMSGTYPE_RecreateFsIDsResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *dentries, *failedEntries)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + RecreateFsIDsRespMsg* recreateFsIDsRespMsg = (RecreateFsIDsRespMsg*) rrArgs.outRespMsg.get(); + + recreateFsIDsRespMsg->getFailedEntries().swap(*failedEntries); + } + else + { + *failedEntries = *dentries; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if ( !failedEntries->empty() ) + { + for ( FsckDirEntryListIter iter = failedEntries->begin(); iter != failedEntries->end(); + iter++ ) + { + LogContext(logContext).log(Log_CRITICAL, "Failed to recreate dentry-by-ID file link." + " entryID: " + iter->getID()); + } + } +} + +void MsgHelperRepair::recreateDentries(NumNodeID node, bool isBuddyMirrored, FsckFsIDList* fsIDs, + FsckFsIDList* failedCreates, FsckDirEntryList* createdDentries, FsckFileInodeList* createdInodes, + std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (recreateDentries)"; + + RecreateDentriesMsg recreateDentriesMsg(fsIDs); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &recreateDentriesMsg, NETMSGTYPE_RecreateDentriesResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, *fsIDs, *failedCreates)) + return; + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto* recreateDentriesMsg = (RecreateDentriesRespMsg*) rrArgs.outRespMsg.get(); + + recreateDentriesMsg->getFailedCreates().swap(*failedCreates); + recreateDentriesMsg->getCreatedDentries().swap(*createdDentries); + recreateDentriesMsg->getCreatedInodes().swap(*createdInodes); + } + else + { + *failedCreates = *fsIDs; + + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + } + + if ( !failedCreates->empty() ) + { + for ( FsckFsIDListIter iter = failedCreates->begin(); iter != failedCreates->end(); + iter++ ) + { + LogContext(logContext).log(Log_CRITICAL, "Failed to recreate dentry." + " entryID: " + iter->getID()); + } + } +} + +void MsgHelperRepair::fixChunkPermissions(Node& node, FsckChunkList& chunkList, + PathInfoList& pathInfoList, FsckChunkList& failedChunks) +{ + const char* logContext = "MsgHelperRepair (fixChunkPermissions)"; + + if ( chunkList.size() != pathInfoList.size() ) + { + LogContext(logContext).logErr( + "Failed to set uid/gid for chunks. Size of lists does not match."); + return; + } + + FsckChunkListIter chunksIter = chunkList.begin(); + PathInfoListIter pathInfoIter = pathInfoList.begin(); + for ( ; chunksIter != chunkList.end(); chunksIter++, pathInfoIter++ ) + { + std::string chunkID = chunksIter->getID(); + uint16_t targetID = chunksIter->getTargetID(); + int validAttribs = SETATTR_CHANGE_USERID | SETATTR_CHANGE_GROUPID; // only interested in these + SettableFileAttribs attribs = {0, 0, 0, 0, 0}; + attribs.userID = chunksIter->getUserID(); + attribs.groupID = chunksIter->getGroupID(); + + bool enableCreation = false; + + PathInfo pathInfo = *pathInfoIter; + SetLocalAttrMsg setLocalAttrMsg(chunkID, targetID, &pathInfo, validAttribs, &attribs, + enableCreation); + setLocalAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_USE_QUOTA); + + const auto respMsg = MessagingTk::requestResponse(node, setLocalAttrMsg, + NETMSGTYPE_SetLocalAttrResp); + + if (respMsg) + { + SetLocalAttrRespMsg* setLocalAttrRespMsg = (SetLocalAttrRespMsg*) respMsg.get(); + + if ( setLocalAttrRespMsg->getResult() != FhgfsOpsErr_SUCCESS ) + { + LogContext(logContext).logErr( + "Failed to set uid/gid for chunk. chunkID: " + chunkID + "; targetID: " + + StringTk::uintToStr(targetID)); + + failedChunks.push_back(*chunksIter); + } + } + else + { + LogContext(logContext).logErr("Communication error occured with node: " + node.getAlias()); + + failedChunks.push_back(*chunksIter); + } + } +} + +/* + * NOTE: chunk gets modified! (new savedPath is set) + */ +bool MsgHelperRepair::moveChunk(Node& node, FsckChunk& chunk, const std::string& moveTo, + bool allowOverwrite) +{ + const char* logContext = "MsgHelperRepair (moveChunks)"; + bool result; + + MoveChunkFileMsg moveChunkFileMsg(chunk.getID(), chunk.getTargetID(), + chunk.getBuddyGroupID() != 0, chunk.getSavedPath()->str(), moveTo, allowOverwrite); + + const auto respMsg = MessagingTk::requestResponse(node, moveChunkFileMsg, + NETMSGTYPE_MoveChunkFileResp); + + if (respMsg) + { + MoveChunkFileRespMsg* moveChunkFileRespMsg = (MoveChunkFileRespMsg*) respMsg.get(); + + result = moveChunkFileRespMsg->getValue() == FhgfsOpsErr_SUCCESS; + + if(!result) + { + LogContext(logContext).logErr( + "Failed to move chunk. chunkID: " + chunk.getID() + "; targetID: " + + StringTk::uintToStr(chunk.getTargetID() ) + "; fromPath: " + + chunk.getSavedPath()->str() + "; toPath: " + + moveTo); + } + else + { + // set newPath in chunk + chunk.setSavedPath(Path(moveTo)); + } + } + else + { + LogContext(logContext).logErr("Communication error occured with node: " + node.getAlias()); + result = false; + } + + return result; +} + +void MsgHelperRepair::deleteFileInodes(NumNodeID node, bool isBuddyMirrored, + FsckFileInodeList& inodes, StringList& failedDeletes, std::set& secondariesWithRepair) +{ + std::vector items; + + for (auto it = inodes.begin(); it != inodes.end(); ++it) + items.emplace_back(it->getID(), DirEntryType_REGULARFILE, it->getIsBuddyMirrored()); + + RemoveInodesMsg removeInodesMsg(std::move(items)); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &removeInodesMsg, NETMSGTYPE_RemoveInodesResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, {}, failedDeletes)) + { + for (auto it = inodes.begin(); it != inodes.end(); ++it) + failedDeletes.push_back(it->getID()); + return; + } + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + RemoveInodesRespMsg* removeInodesRespMsg = (RemoveInodesRespMsg*) rrArgs.outRespMsg.get(); + failedDeletes = removeInodesRespMsg->releaseFailedEntryIDList(); + + for ( StringListIter iter = failedDeletes.begin(); iter != failedDeletes.end(); iter++ ) + LOG(GENERAL, ERR, "Failed to delete file inode.", node, isBuddyMirrored, + ("entryID", *iter)); + } + else + { + LOG(GENERAL, ERR, "Communication error occured.", node, isBuddyMirrored, commRes); + + failedDeletes.clear(); + for (auto it = inodes.begin(); it != inodes.end(); ++it) + failedDeletes.push_back(it->getID()); + } +} + +void MsgHelperRepair::deleteDuplicateFileInodes(NumNodeID node, bool isBuddyMirrored, + FsckDuplicateInodeInfoVector& dupInodes, StringList& failedEntries, std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (check and repair duplicate inode)"; + + CheckAndRepairDupInodeMsg repairDupInodeMsg(&dupInodes); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &repairDupInodeMsg, NETMSGTYPE_CheckAndRepairDupInodeResp); + + if (isBuddyMirrored) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, {}, failedEntries)) + { + for (auto it = dupInodes.begin(); it != dupInodes.end(); ++it) + failedEntries.push_back(it->getID()); + return; + } + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes == FhgfsOpsErr_SUCCESS) + { + CheckAndRepairDupInodeRespMsg* repairDupInodeRespMsg = (CheckAndRepairDupInodeRespMsg*) rrArgs.outRespMsg.get(); + failedEntries = repairDupInodeRespMsg->releaseFailedEntryIDList(); + + for (StringListIter iter = failedEntries.begin(); iter != failedEntries.end(); ++iter) + { + LogContext(logContext).logErr("Failed to repair duplicate inode, entryID: " + *iter); + } + } + else + { + LogContext(logContext).logErr("Communication error occured with node: " + std::to_string(node.val())); + } +} + +void MsgHelperRepair::deinlineFileInode(NumNodeID node, EntryInfo* entryInfo, StringList& failedEntries, + std::set& secondariesWithRepair) +{ + const char* logContext = "MsgHelperRepair (Deinline File Inode)"; + + MoveFileInodeMsg msg(entryInfo, MODE_DEINLINE); + + RequestResponseNode rrNode(node, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &msg, NETMSGTYPE_MoveFileInodeResp); + + if (entryInfo->getIsBuddyMirrored()) + { + rrNode.setMirrorInfo(Program::getApp()->getMetaMirrorBuddyGroupMapper(), false); + if (!setSecondaryBad(node.val(), secondariesWithRepair, {}, failedEntries)) + { + failedEntries.push_back(entryInfo->getEntryID()); + return; + } + } + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + if (commRes == FhgfsOpsErr_SUCCESS) + { + auto respMsg = (MoveFileInodeRespMsg*) rrArgs.outRespMsg.get(); + if (respMsg->getResult() != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to migrate hardlink metadata to new version, entryID: " + entryInfo->getEntryID()); + failedEntries.push_back(entryInfo->getEntryID()); + } + } + else + { + failedEntries.push_back(entryInfo->getEntryID()); + LogContext(logContext).logErr("Communication error occured with node: " + std::to_string(node.val())); + } +} diff --git a/fsck/source/net/msghelpers/MsgHelperRepair.h b/fsck/source/net/msghelpers/MsgHelperRepair.h new file mode 100644 index 0000000..ab979a3 --- /dev/null +++ b/fsck/source/net/msghelpers/MsgHelperRepair.h @@ -0,0 +1,77 @@ +#ifndef MSGHELPERREPAIR_H_ +#define MSGHELPERREPAIR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +class MsgHelperRepair +{ + public: + static FhgfsOpsErr setNodeState(NumNodeID node, TargetConsistencyState state); + + static void deleteDanglingDirEntries(NumNodeID node, bool isBuddyMirrored, + FsckDirEntryList* dentries, FsckDirEntryList* failedDeletes, + std::set& secondariesWithRepair); + + static void createDefDirInodes(NumNodeID node, bool isBuddyMirrored, + const std::vector>& entries, + FsckDirInodeList* createdInodes, + std::set& secondariesWithRepair); + + static void correctInodeOwnersInDentry(NumNodeID node, bool isBuddyMirrored, + FsckDirEntryList* dentries, NumNodeIDList* owners, FsckDirEntryList* failedCorrections, + std::set& secondariesWithRepair); + static void correctInodeOwners(NumNodeID node, bool isBuddyMirrored, + FsckDirInodeList* dirInodes, FsckDirInodeList* failedCorrections, + std::set& secondariesWithRepair); + + static void deleteFiles(NumNodeID node, bool isBuddyMirrored, FsckDirEntryList* dentries, + FsckDirEntryList* failedDeletes); + static void deleteChunks(Node& node, FsckChunkList* chunks, FsckChunkList* failedDeletes); + + static NodeHandle referenceLostAndFoundOwner(EntryInfo* outLostAndFoundEntryInfo); + static bool createLostAndFound(NodeHandle& outReferencedNode, + EntryInfo& outLostAndFoundEntryInfo); + static void linkToLostAndFound(Node& lostAndFoundNode, EntryInfo* lostAndFoundInfo, + FsckDirInodeList* dirInodes, FsckDirInodeList* failedInodes, + FsckDirEntryList* createdDentries, std::set& secondariesWithRepair); + static void linkToLostAndFound(Node& lostAndFoundNode, EntryInfo* lostAndFoundInfo, + FsckFileInodeList* fileInodes, FsckFileInodeList* failedInodes, + FsckDirEntryList* createdDentries, std::set& secondariesWithRepair); + static void createContDirs(NumNodeID node, bool isBuddyMirrored, FsckDirInodeList* inodes, + StringList* failedCreates, std::set& secondariesWithRepair); + static void updateFileAttribs(NumNodeID node, bool isBuddyMirrored, FsckFileInodeList* inodes, + FsckFileInodeList* failedUpdates, std::set& secondariesWithRepair); + static void updateDirAttribs(NumNodeID node, bool isBuddyMirrored, FsckDirInodeList* inodes, + FsckDirInodeList* failedUpdates, std::set& secondariesWithRepair); + static void recreateFsIDs(NumNodeID node, bool isBuddyMirrored, FsckDirEntryList* dentries, + FsckDirEntryList* failedEntries, std::set& secondariesWithRepair); + static void recreateDentries(NumNodeID node, bool isBuddyMirrored, FsckFsIDList* fsIDs, + FsckFsIDList* failedCreates, FsckDirEntryList* createdDentries, + FsckFileInodeList* createdInodes, std::set& secondariesWithRepair); + static void fixChunkPermissions(Node& node, FsckChunkList& chunkList, + PathInfoList& pathInfoList, FsckChunkList& failedChunks); + static bool moveChunk(Node& node, FsckChunk& chunk, const std::string& moveTo, + bool allowOverwrite); + static void deleteFileInodes(NumNodeID node, bool isBuddyMirrored, FsckFileInodeList& inodes, + StringList& failedDeletes, std::set& secondariesWithRepair); + static void deleteDuplicateFileInodes(NumNodeID node, bool isBuddyMirrored, + FsckDuplicateInodeInfoVector& dupInodes, StringList& failedEntries, std::set& secondariesWithRepair); + static void deinlineFileInode(NumNodeID node, EntryInfo* entryInfo, StringList& failedEntries, + std::set& secondariesWithRepair); + + + private: + MsgHelperRepair() {} + + public: + // inliners +}; + +#endif /* MSGHELPERREPAIR_H_ */ diff --git a/fsck/source/program/Main.cpp b/fsck/source/program/Main.cpp new file mode 100644 index 0000000..e989ceb --- /dev/null +++ b/fsck/source/program/Main.cpp @@ -0,0 +1,7 @@ +#include "Program.h" + +int main(int argc, char** argv) +{ + return Program::main(argc, argv); +} + diff --git a/fsck/source/program/Program.cpp b/fsck/source/program/Program.cpp new file mode 100644 index 0000000..de99ef6 --- /dev/null +++ b/fsck/source/program/Program.cpp @@ -0,0 +1,23 @@ +#include +#include "Program.h" + +App* Program::app = NULL; + +int Program::main(int argc, char** argv) +{ + BuildTypeTk::checkDebugBuildTypes(); + + AbstractApp::runTimeInitsAndChecks(); // must be called before creating a new App + + app = new App(argc, argv); + + app->startInCurrentThread(); + + int appRes = app->getAppResult(); + + delete app; + + return appRes; +} + + diff --git a/fsck/source/program/Program.h b/fsck/source/program/Program.h new file mode 100644 index 0000000..53b5e73 --- /dev/null +++ b/fsck/source/program/Program.h @@ -0,0 +1,27 @@ +#ifndef PROGRAM_H_ +#define PROGRAM_H_ + +#include + +class Program +{ + public: + static int main(int argc, char** argv); + + + private: + Program() {} + + static App* app; + + + public: + // getters & setters + static App* getApp() + { + return app; + } + +}; + +#endif /*PROGRAM_H_*/ diff --git a/fsck/source/toolkit/DatabaseTk.cpp b/fsck/source/toolkit/DatabaseTk.cpp new file mode 100644 index 0000000..ffd24f1 --- /dev/null +++ b/fsck/source/toolkit/DatabaseTk.cpp @@ -0,0 +1,273 @@ +#include "DatabaseTk.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +DatabaseTk::DatabaseTk() +{ +} + +DatabaseTk::~DatabaseTk() +{ +} + +FsckDirEntry DatabaseTk::createDummyFsckDirEntry(FsckDirEntryType entryType) +{ + FsckDirEntryList list; + createDummyFsckDirEntries(7, 1, &list, entryType); + return list.front(); +} + +void DatabaseTk::createDummyFsckDirEntries(uint amount, FsckDirEntryList* outList, + FsckDirEntryType entryType) +{ + createDummyFsckDirEntries(0,amount, outList, entryType); +} + +void DatabaseTk::createDummyFsckDirEntries(uint from, uint amount, FsckDirEntryList* outList, + FsckDirEntryType entryType) +{ + for ( uint i = from; i < (from+amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + + std::string id = index + "-1-1"; + std::string name = "dentryName" + index; + std::string parentID = index + "-2-1"; + NumNodeID entryOwnerNodeID(i + 1000); + NumNodeID inodeOwnerNodeID(i + 2000); + NumNodeID saveNodeID(i + 3000); + int saveDevice = i; + uint64_t saveInode = i; + + FsckDirEntry dentry(id, name, parentID, entryOwnerNodeID, inodeOwnerNodeID, entryType, true, + saveNodeID, saveDevice, saveInode, false); + + outList->push_back(dentry); + } +} + +FsckFileInode DatabaseTk::createDummyFsckFileInode() +{ + FsckFileInodeList list; + createDummyFsckFileInodes(7, 1, &list); + return list.front(); +} + +void DatabaseTk::createDummyFsckFileInodes(uint amount, FsckFileInodeList* outList) +{ + createDummyFsckFileInodes(0,amount,outList); +} + +void DatabaseTk::createDummyFsckFileInodes(uint from, uint amount, FsckFileInodeList* outList) +{ + for ( uint i = from; i < (from + amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + UInt16Vector targetIDs; + while (targetIDs.size() < i) + targetIDs.push_back(i + 1000 * (targetIDs.size() + 1) ); + Raid0Pattern stripePatternIn(1024, targetIDs); + + std::string id = index + "-1-1"; + std::string parentDirID = index + "-2-1"; + NumNodeID parentNodeID(i + 1000); + + unsigned userID = 1000; + unsigned groupID = 1000; + + uint64_t usedBlocks = 0; + int64_t fileSize = usedBlocks*512; + unsigned numHardLinks = 1; + + PathInfo pathInfo(userID, parentDirID, PATHINFO_FEATURE_ORIG); + + FsckStripePatternType stripePatternType = FsckTk::stripePatternToFsckStripePattern( + &stripePatternIn); + unsigned chunkSize = 524288; + + NumNodeID saveNodeID(i + 2000); + + FsckFileInode fileInode(id, parentDirID, parentNodeID, pathInfo, userID, groupID, fileSize, + numHardLinks, usedBlocks, targetIDs, stripePatternType, chunkSize, saveNodeID, + 1000 + i, 42, true, false, true, false); + + outList->push_back(fileInode); + } +} + +FsckDirInode DatabaseTk::createDummyFsckDirInode() +{ + FsckDirInodeList list; + createDummyFsckDirInodes(7, 1, &list); + return list.front(); +} + +void DatabaseTk::createDummyFsckDirInodes(uint amount, FsckDirInodeList* outList) +{ + createDummyFsckDirInodes(0, amount, outList); +} + +void DatabaseTk::createDummyFsckDirInodes(uint from, uint amount, FsckDirInodeList* outList) +{ + for ( uint i = from; i < (from + amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + UInt16Vector targetIDs; + Raid0Pattern stripePatternIn(1024, targetIDs); + + std::string id = index + "-1-1"; + std::string parentDirID = index + "-2-1"; + NumNodeID parentNodeID(i + 1000); + NumNodeID ownerNodeID(i + 2000); + + int64_t size = 10; + unsigned numHardLinks = 12; + + FsckStripePatternType stripePatternType = FsckTk::stripePatternToFsckStripePattern( + &stripePatternIn); + NumNodeID saveNodeID(i + 3000); + + FsckDirInode dirInode(id, parentDirID, parentNodeID, ownerNodeID, size, numHardLinks, + targetIDs, stripePatternType, saveNodeID, false, true, false); + outList->push_back(dirInode); + } +} + +FsckChunk DatabaseTk::createDummyFsckChunk() +{ + FsckChunkList list; + createDummyFsckChunks(7, 1, &list); + return list.front(); +} + +void DatabaseTk::createDummyFsckChunks(uint amount, FsckChunkList* outList) +{ + createDummyFsckChunks(amount, 1, outList); +} + +void DatabaseTk::createDummyFsckChunks(uint amount, uint numTargets, FsckChunkList* outList) +{ + createDummyFsckChunks(0, amount, numTargets, outList); +} + +void DatabaseTk::createDummyFsckChunks(uint from, uint amount, uint numTargets, + FsckChunkList* outList) +{ + for ( uint i = from; i < (from + amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + + for ( uint j = 0; j < numTargets; j++ ) + { + std::string id = index + "-1-1"; + uint16_t targetID = j; + + PathInfo pathInfo(0, META_ROOTDIR_ID_STR, PATHINFO_FEATURE_ORIG); + Path chunkDirPath; // ignored! + std::string chunkFilePathStr; + StorageTk::getChunkDirChunkFilePath(&pathInfo, id, true, chunkDirPath, + chunkFilePathStr); + Path savedPath(chunkFilePathStr); + + uint64_t usedBlocks = 300; + int64_t fileSize = usedBlocks*512; + + FsckChunk chunk(id, targetID, savedPath, fileSize, usedBlocks, 0, 0, 0, 0, 0); + outList->push_back(chunk); + } + } +} + +FsckContDir DatabaseTk::createDummyFsckContDir() +{ + FsckContDirList list; + createDummyFsckContDirs(7, 1, &list); + return list.front(); +} + +void DatabaseTk::createDummyFsckContDirs(uint amount, FsckContDirList* outList) +{ + createDummyFsckContDirs(0, amount, outList); +} + +void DatabaseTk::createDummyFsckContDirs(uint from, uint amount, FsckContDirList* outList) +{ + for ( uint i = from; i < (from + amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + + std::string id = index + "-1-1"; + NumNodeID saveNodeID(i); + + FsckContDir contDir(id, saveNodeID, false); + outList->push_back(contDir); + } +} + +FsckFsID DatabaseTk::createDummyFsckFsID() +{ + FsckFsIDList list; + createDummyFsckFsIDs(7, 1, &list); + return list.front(); +} + +void DatabaseTk::createDummyFsckFsIDs(uint amount, FsckFsIDList* outList) +{ + createDummyFsckFsIDs(0, amount, outList); +} + +void DatabaseTk::createDummyFsckFsIDs(uint from, uint amount, FsckFsIDList* outList) +{ + for ( uint i = from; i < (from + amount); i++ ) + { + std::string index = StringTk::uintToHexStr(i); + + std::string id = index + "-1-1"; + std::string parentDirID = index + "-2-1"; + NumNodeID saveNodeID(i); + int saveDevice = i; + uint64_t saveInode = i; + + FsckFsID fsID(id, parentDirID, saveNodeID, saveDevice, saveInode, false); + outList->push_back(fsID); + } +} + +std::string DatabaseTk::calculateExpectedChunkPath(std::string entryID, unsigned origParentUID, + std::string origParentEntryID, int pathInfoFlags) +{ + std::string resStr; + + bool hasOrigFeature; + hasOrigFeature = pathInfoFlags == PATHINFO_FEATURE_ORIG; + + PathInfo pathInfo(origParentUID, origParentEntryID, pathInfoFlags); + Path chunkDirPath; + std::string chunkFilePath; + StorageTk::getChunkDirChunkFilePath(&pathInfo, entryID, hasOrigFeature, chunkDirPath, + chunkFilePath); + + if (hasOrigFeature) // we can use the Path chunkDirPath + resStr = chunkDirPath.str(); + else + { + /* chunk in old format => Path chunkDirPath is not set in getChunkDirChunkFilePath(), + * so it is a bit more tricky */ + + // we need to strip the filename itself (including the '/') + size_t startPos = 0; + size_t len = chunkFilePath.find_last_of('/'); + + // generate a substring as result + resStr = chunkFilePath.substr(startPos, len); + } + + return resStr; +} diff --git a/fsck/source/toolkit/DatabaseTk.h b/fsck/source/toolkit/DatabaseTk.h new file mode 100644 index 0000000..963e621 --- /dev/null +++ b/fsck/source/toolkit/DatabaseTk.h @@ -0,0 +1,62 @@ +#ifndef DATABASETK_H_ +#define DATABASETK_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +class FsckDB; + +class DatabaseTk +{ + public: + DatabaseTk(); + virtual ~DatabaseTk(); + + static FsckDirEntry createDummyFsckDirEntry( + FsckDirEntryType entryType = FsckDirEntryType_REGULARFILE); + static void createDummyFsckDirEntries(uint amount, FsckDirEntryList* outList, + FsckDirEntryType entryType = FsckDirEntryType_REGULARFILE); + static void createDummyFsckDirEntries(uint from, uint amount, FsckDirEntryList* outList, + FsckDirEntryType entryType = FsckDirEntryType_REGULARFILE); + + + static FsckFileInode createDummyFsckFileInode(); + static void createDummyFsckFileInodes(uint amount, FsckFileInodeList* outList); + static void createDummyFsckFileInodes(uint from, uint amount, FsckFileInodeList* outList); + + static FsckDirInode createDummyFsckDirInode(); + static void createDummyFsckDirInodes(uint amount, FsckDirInodeList* outList); + static void createDummyFsckDirInodes(uint from, uint amount, FsckDirInodeList* outList); + + static FsckChunk createDummyFsckChunk(); + static void createDummyFsckChunks(uint amount, FsckChunkList* outList); + static void createDummyFsckChunks(uint amount, uint numTargets, FsckChunkList* outList); + static void createDummyFsckChunks(uint from, uint amount, uint numTargets, + FsckChunkList* outList); + + static FsckContDir createDummyFsckContDir(); + static void createDummyFsckContDirs(uint amount, FsckContDirList* outList); + static void createDummyFsckContDirs(uint from, uint amount, FsckContDirList* outList); + + static FsckFsID createDummyFsckFsID(); + static void createDummyFsckFsIDs(uint amount, FsckFsIDList* outList); + static void createDummyFsckFsIDs(uint from, uint amount, FsckFsIDList* outList); + + static std::string calculateExpectedChunkPath(std::string entryID, unsigned origParentUID, + std::string origParentEntryID, int pathInfoFlags); +}; + +#endif /* DATABASETK_H_ */ diff --git a/fsck/source/toolkit/FsckDefinitions.cpp b/fsck/source/toolkit/FsckDefinitions.cpp new file mode 100644 index 0000000..e02e658 --- /dev/null +++ b/fsck/source/toolkit/FsckDefinitions.cpp @@ -0,0 +1,2 @@ +#include "FsckDefinitions.h" + diff --git a/fsck/source/toolkit/FsckDefinitions.h b/fsck/source/toolkit/FsckDefinitions.h new file mode 100644 index 0000000..97f2927 --- /dev/null +++ b/fsck/source/toolkit/FsckDefinitions.h @@ -0,0 +1,75 @@ +#ifndef FSCKDBTYPES_H_ +#define FSCKDBTYPES_H_ + +#include +#include +#include + +#define TABLE_NAME_DIR_ENTRIES "dirEntries" +#define TABLE_NAME_FILE_INODES "fileInodes" +#define TABLE_NAME_DIR_INODES "dirInodes" +#define TABLE_NAME_CHUNKS "chunks" +#define TABLE_NAME_STRIPE_PATTERNS "stripePatterns" +#define TABLE_NAME_CONT_DIRS "contDirs" +#define TABLE_NAME_FSIDS "fsIDs" +#define TABLE_NAME_USED_TARGETS "usedTargets" + +#define TABLE_NAME_MODIFICATION_EVENTS "modificationEvents" + +enum FsckRepairAction +{ + FsckRepairAction_DELETEDENTRY = 0, + FsckRepairAction_DELETEFILE = 1, + FsckRepairAction_CREATEDEFAULTDIRINODE = 2, + FsckRepairAction_CORRECTOWNER = 3, + FsckRepairAction_LOSTANDFOUND = 4, + FsckRepairAction_CREATECONTDIR = 5, + FsckRepairAction_DELETEINODE = 7, + FsckRepairAction_DELETECHUNK = 8, + FsckRepairAction_DELETECONTDIR = 9, + FsckRepairAction_UPDATEATTRIBS = 10, + FsckRepairAction_CHANGETARGET = 11, + FsckRepairAction_RECREATEFSID = 12, + FsckRepairAction_RECREATEDENTRY = 13, + FsckRepairAction_FIXPERMISSIONS = 14, + FsckRepairAction_MOVECHUNK = 15, + FsckRepairAction_REPAIRDUPLICATEINODE = 16, + FsckRepairAction_UPDATEOLDTYLEDHARDLINKS = 17, + FsckRepairAction_NOTHING = 18, + FsckRepairAction_UNDEFINED = 19 +}; + +struct FsckRepairActionElem +{ + const char* actionShortDesc; + const char* actionDesc; + enum FsckRepairAction action; +}; + +// Note: Keep in sync with enum FsckErrorCode +FsckRepairActionElem const __FsckRepairActions[] = +{ + {"DeleteDentry", "Delete directory entry", FsckRepairAction_DELETEDENTRY}, + {"DeleteFile", "Delete file", FsckRepairAction_DELETEFILE}, + {"CreateDefDirInode", "Create a directory inode with default values", + FsckRepairAction_CREATEDEFAULTDIRINODE}, + {"CorrectOwner", "Correct owner node", FsckRepairAction_CORRECTOWNER}, + {"LostAndFound", "Link to lost+found", FsckRepairAction_LOSTANDFOUND}, + {"CreateContDir", "Create an empty content directory", FsckRepairAction_CREATECONTDIR}, + {"DeleteInode", "Delete inodes", FsckRepairAction_DELETEINODE}, + {"DeleteChunk", "Delete chunk", FsckRepairAction_DELETECHUNK}, + {"DeleteContDir", "Delete content directory", FsckRepairAction_DELETECONTDIR}, + {"UpdateAttribs", "Update attributes", FsckRepairAction_UPDATEATTRIBS}, + {"ChangeTarget", "Change target ID in stripe patterns", FsckRepairAction_CHANGETARGET}, + {"RecreateFsID", "Recreate dentry-by-id file", FsckRepairAction_RECREATEFSID}, + {"RecreateDentry", "Recreate directory entry file", FsckRepairAction_RECREATEDENTRY}, + {"FixPermissions", "Fix permissions", FsckRepairAction_FIXPERMISSIONS}, + {"MoveChunk", "Move chunk", FsckRepairAction_MOVECHUNK}, + {"RepairDuplicateInode", "Repair duplicate inode", FsckRepairAction_REPAIRDUPLICATEINODE}, + {"UpdateOldStyledHardlinks", "Update metadata of old styled hardlinks to new format", + FsckRepairAction_UPDATEOLDTYLEDHARDLINKS}, + {"Nothing", "Do nothing", FsckRepairAction_NOTHING}, + {0, 0, FsckRepairAction_UNDEFINED} +}; + +#endif /* FSCKDBTYPES_H_ */ diff --git a/fsck/source/toolkit/FsckException.h b/fsck/source/toolkit/FsckException.h new file mode 100644 index 0000000..79df78c --- /dev/null +++ b/fsck/source/toolkit/FsckException.h @@ -0,0 +1,16 @@ +/* + * FsckException.h + * + * This exception is intended for things that can happen in fsck, that prevents it from running + * further, e.g. if a server goes down fsck should abort + * + */ + +#ifndef FSCKEXCEPTION_H +#define FSCKEXCEPTION_H + +#include + +DECLARE_NAMEDEXCEPTION(FsckException, "FsckException") + +#endif /* FSCKEXCEPTION_H */ diff --git a/fsck/source/toolkit/FsckTkEx.cpp b/fsck/source/toolkit/FsckTkEx.cpp new file mode 100644 index 0000000..6a10d5b --- /dev/null +++ b/fsck/source/toolkit/FsckTkEx.cpp @@ -0,0 +1,604 @@ +#include "FsckTkEx.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +char FsckTkEx::progressChar = '-'; +Mutex FsckTkEx::outputMutex; + +/* + * check the reachability of all nodes in the system + */ +bool FsckTkEx::checkReachability() +{ + NodeStore* metaNodes = Program::getApp()->getMetaNodes(); + NodeStore* storageNodes = Program::getApp()->getStorageNodes(); + + StringList errors; + bool commSuccess = true; + + FsckTkEx::fsckOutput("Step 1: Check reachability of nodes: ", OutputOptions_FLUSH); + + if ( metaNodes->getSize() == 0 ) + { + errors.push_back("No metadata nodes found"); + commSuccess = false; + } + + if ( storageNodes->getSize() == 0 ) + { + errors.push_back("No storage nodes found"); + commSuccess = false; + } + + for (const auto& node : metaNodes->referenceAllNodes()) + { + if ( !FsckTkEx::checkReachability(*node, NODETYPE_Meta) ) + { + errors.push_back("Communication with metadata node failed: " + node->getAlias()); + commSuccess = false; + } + } + + for (const auto& node : storageNodes->referenceAllNodes()) + { + if ( !FsckTkEx::checkReachability(*node, NODETYPE_Storage) ) + { + errors.push_back("Communication with storage node failed: " + node->getAlias()); + commSuccess = false; + } + } + + if ( commSuccess ) + FsckTkEx::fsckOutput("Finished", OutputOptions_LINEBREAK); + else + { + for ( StringListIter iter = errors.begin(); iter != errors.end(); iter++ ) + { + FsckTkEx::fsckOutput(*iter, + OutputOptions_NONE | OutputOptions_LINEBREAK | OutputOptions_STDERR); + } + } + + return commSuccess; +} + +/* + * check reachability of a single node + */ +bool FsckTkEx::checkReachability(Node& node, NodeType nodetype) +{ + bool retVal = false; + HeartbeatRequestMsg heartbeatRequestMsg; + std::string realNodeID = node.getAlias(); + + const auto respMsg = MessagingTk::requestResponse(node, heartbeatRequestMsg, + NETMSGTYPE_Heartbeat); + if (respMsg) + { + HeartbeatMsg *heartbeatMsg = (HeartbeatMsg *) respMsg.get(); + std::string receivedNodeID = heartbeatMsg->getNodeID(); + retVal = receivedNodeID.compare(realNodeID) == 0; + } + + return retVal; +} + +void FsckTkEx::fsckOutput(std::string text, int optionFlags) +{ + const std::lock_guard lock(outputMutex); + + static bool fileErrLogged = false; // to make sure we print logfile open err only once + + Config* cfg = Program::getApp()->getConfig(); // might be NULL on app init failure + bool toLog = cfg && (!(OutputOptions_NOLOG & optionFlags)); // true if write to log file + + FILE *logFile = NULL; + + if (likely(toLog)) + { + std::string logFilePath = cfg->getLogOutFile(); + + logFile = fopen(logFilePath.c_str(),"a+"); + if (logFile == NULL) + { + toLog = false; + + if(!fileErrLogged) + { + std::cerr << "Cannot open output file for writing: '" << logFilePath << "'" + << std::endl; + + fileErrLogged = true; + } + } + } + + const char* colorNormal = OutputColor_NORMAL; + const char* color = OutputColor_NORMAL; + + FILE *outFile = stdout; + + if (OutputOptions_STDERR & optionFlags) + { + outFile = stderr; + } + else if (OutputOptions_NOSTDOUT & optionFlags) + { + outFile = NULL; + } + + bool outFileIsTty; + + if (outFile) + outFileIsTty = isatty(fileno(outFile)); + else + outFileIsTty = false; + + if (OutputOptions_COLORGREEN & optionFlags) + { + color = OutputColor_GREEN; + } + else if (OutputOptions_COLORRED & optionFlags) + { + color = OutputColor_RED; + } + else + { + color = OutputColor_NORMAL; + } + + if (OutputOptions_LINEDELETE & optionFlags) + { + optionFlags = optionFlags | OutputOptions_FLUSH; + SAFE_FPRINTF(outFile, "\r"); + SAFE_FPRINTF(outFile, " "); + SAFE_FPRINTF(outFile, "\r"); + } + + if (OutputOptions_ADDLINEBREAKBEFORE & optionFlags) + { + SAFE_FPRINTF(outFile, "\n"); + if (likely(toLog)) SAFE_FPRINTF(logFile,"\n"); + } + + if (OutputOptions_HEADLINE & optionFlags) + { + SAFE_FPRINTF(outFile, "\n--------------------------------------------------------------------\n"); + + if (likely(toLog)) + SAFE_FPRINTF(logFile,"\n--------------------------------------------------------------------\n"); + + if (likely(outFileIsTty)) + SAFE_FPRINTF(outFile, "%s%s%s",color,text.c_str(),colorNormal); + else + SAFE_FPRINTF(outFile, "%s",text.c_str()); + + if (likely(toLog)) + SAFE_FPRINTF(logFile,"%s",text.c_str()); + + SAFE_FPRINTF(outFile, "\n--------------------------------------------------------------------\n") ; + + if (likely(toLog)) + SAFE_FPRINTF(logFile,"\n--------------------------------------------------------------------\n"); + } + else + { + if (likely(outFileIsTty)) + SAFE_FPRINTF(outFile, "%s%s%s",color,text.c_str(),colorNormal); + else + SAFE_FPRINTF(outFile, "%s",text.c_str()); + + if (likely(toLog)) SAFE_FPRINTF(logFile,"%s",text.c_str()); + } + + if (OutputOptions_LINEBREAK & optionFlags) + { + SAFE_FPRINTF(outFile, "\n"); + if (likely(toLog)) + SAFE_FPRINTF(logFile,"\n"); + } + + if (OutputOptions_DOUBLELINEBREAK & optionFlags) + { + SAFE_FPRINTF(outFile, "\n\n"); + if (likely(toLog)) + SAFE_FPRINTF(logFile,"\n\n"); + } + + if (OutputOptions_FLUSH & optionFlags) + { + fflush(outFile); + if (likely(toLog)) + fflush(logFile); + } + + if (logFile != NULL) + { + fclose(logFile); + } +} + +void FsckTkEx::printVersionHeader(bool toStdErr, bool noLogFile) +{ + int optionFlags = OutputOptions_LINEBREAK; + if (toStdErr) + { + optionFlags = OutputOptions_LINEBREAK | OutputOptions_STDERR; + } + if (noLogFile) + { + optionFlags = optionFlags | OutputOptions_NOLOG; + } + + FsckTkEx::fsckOutput("\n", optionFlags); + FsckTkEx::fsckOutput("BeeGFS File System Check Version : " + std::string(BEEGFS_VERSION), + optionFlags); + FsckTkEx::fsckOutput("----", optionFlags); +} + +void FsckTkEx::progressMeter() +{ + const std::lock_guard lock(outputMutex); + + printf("\b%c",progressChar); + fflush(stdout); + + switch(progressChar) + { + case '-' : + { + progressChar = '\\'; + break; + } + case '\\' : + { + progressChar = '|'; + break; + } + case '|' : + { + progressChar = '/'; + break; + } + case '/' : + { + progressChar = '-'; + break; + } + default: + { + progressChar = '-'; + break; + } + + } +} + +/* + * this is only a rough approximation + */ +int64_t FsckTkEx::calcNeededSpace() +{ + const char* logContext = "FsckTkEx (calcNeededSpace)"; + int64_t neededSpace = 0; + + // get used inodes from all meta data servers and sum them up + NodeStore* metaNodes = Program::getApp()->getMetaNodes(); + + for (const auto& metaNode : metaNodes->referenceAllNodes()) + { + NumNodeID nodeID = metaNode->getNumID(); + + StatStoragePathMsg statStoragePathMsg(0); + + const auto respMsg = MessagingTk::requestResponse(*metaNode, statStoragePathMsg, + NETMSGTYPE_StatStoragePathResp); + if (respMsg) + { + StatStoragePathRespMsg* statStoragePathRespMsg = (StatStoragePathRespMsg *) respMsg.get(); + int64_t usedInodes = statStoragePathRespMsg->getInodesTotal() + - statStoragePathRespMsg->getInodesFree(); + neededSpace += usedInodes * NEEDED_DISKSPACE_META_INODE; + } + else + { + LogContext(logContext).logErr( + "Unable to calculate needed disk space; Communication error with node: " + + nodeID.str()); + return 0; + } + } + + // get used inodes from all storage servers and sum them up + NodeStore* storageNodes = Program::getApp()->getStorageNodes(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + + for (const auto& storageNode : storageNodes->referenceAllNodes()) + { + NumNodeID nodeID = storageNode->getNumID(); + UInt16List targetIDs; + targetMapper->getTargetsByNode(nodeID, targetIDs); + + for ( UInt16ListIter targetIDIter = targetIDs.begin(); targetIDIter != targetIDs.end(); + targetIDIter++ ) + { + uint16_t targetID = *targetIDIter; + StatStoragePathMsg statStoragePathMsg(targetID); + + const auto respMsg = MessagingTk::requestResponse(*storageNode, statStoragePathMsg, + NETMSGTYPE_StatStoragePathResp); + if (respMsg) + { + auto* statStoragePathRespMsg = (StatStoragePathRespMsg *) respMsg.get(); + int64_t usedInodes = statStoragePathRespMsg->getInodesTotal() + - statStoragePathRespMsg->getInodesFree(); + neededSpace += usedInodes * NEEDED_DISKSPACE_STORAGE_INODE; + } + else + { + LogContext(logContext).logErr( + "Unable to calculate needed disk space; Communication error with node: " + + nodeID.str()); + return -1; + } + } + } + + // now we take the calculated approximation and double it to have a lot of space for errors + return neededSpace*2; +} + +bool FsckTkEx::checkDiskSpace(Path& dbPath) +{ + int64_t neededDiskSpace = FsckTkEx::calcNeededSpace(); + + if ( unlikely(neededDiskSpace < 0) ) + { + FsckTkEx::fsckOutput("Could not determine needed disk space. Aborting now.", + OutputOptions_LINEBREAK | OutputOptions_ADDLINEBREAKBEFORE); + return false; + } + + int64_t sizeTotal; + int64_t sizeFree; + int64_t inodesTotal; + int64_t inodesFree; + bool statRes = StorageTk::statStoragePath(dbPath, true, &sizeTotal, &sizeFree, + &inodesTotal, &inodesFree); + + if (!statRes) + { + FsckTkEx::fsckOutput( + "Could not stat database file path to determine free space; database file: " + + dbPath.str()); + return false; + } + + if ( neededDiskSpace >= sizeFree ) + { + std::string neededDiskSpaceUnit; + double neededDiskSpaceValue = UnitTk::byteToXbyte(neededDiskSpace, &neededDiskSpaceUnit); + + std::string sizeFreeUnit; + double sizeFreeValue = UnitTk::byteToXbyte(sizeFree, &sizeFreeUnit); + + FsckTkEx::fsckOutput( + "Not enough disk space to create database file: " + dbPath.str() + + "; Recommended free space: " + StringTk::doubleToStr(neededDiskSpaceValue) + + neededDiskSpaceUnit + "; Free space: " + StringTk::doubleToStr(sizeFreeValue) + + sizeFreeUnit, OutputOptions_LINEBREAK | OutputOptions_ADDLINEBREAKBEFORE); + + bool ignoreDBDiskSpace = Program::getApp()->getConfig()->getIgnoreDBDiskSpace(); + if(!ignoreDBDiskSpace) + return false; + } + + return true; +} + +std::string FsckTkEx::getRepairActionDesc(FsckRepairAction repairAction, bool shortDesc) +{ + for (size_t i = 0; __FsckRepairActions[i].actionDesc != nullptr; i++) + { + if( repairAction == __FsckRepairActions[i].action ) + { // we have a match + if (shortDesc) + return __FsckRepairActions[i].actionShortDesc; + else + return __FsckRepairActions[i].actionDesc; + } + } + + return ""; +} + +FhgfsOpsErr FsckTkEx::startModificationLogging(NodeStore* metaNodes, Node& localNode, + bool forceRestart) +{ + const char* logContext = "FsckTkEx (startModificationLogging)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + NicAddressList localNicList = localNode.getNicList(); + unsigned localPortUDP = localNode.getPortUDP(); + + FsckTkEx::fsckOutput("-----", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + "Waiting for metadata servers to start modification logging. This might take some time.", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput("-----", OutputOptions_FLUSH | OutputOptions_DOUBLELINEBREAK); + + NumNodeIDList nodeIDs; + + auto metaNodeList = metaNodes->referenceAllNodes(); + + for (auto iter = metaNodeList.begin(); iter != metaNodeList.end(); iter++) + { + auto node = metaNodes->referenceNode((*iter)->getNumID()); + + NicAddressList nicList; + FsckSetEventLoggingMsg fsckSetEventLoggingMsg(true, localPortUDP, + &localNicList, forceRestart); + + const auto respMsg = MessagingTk::requestResponse(*node, fsckSetEventLoggingMsg, + NETMSGTYPE_FsckSetEventLoggingResp); + + if (respMsg) + { + auto* fsckSetEventLoggingRespMsg = (FsckSetEventLoggingRespMsg*) respMsg.get(); + + bool started = fsckSetEventLoggingRespMsg->getLoggingEnabled(); + + if (!started) // EventFlusher was already running on this node! + { + LogContext(logContext).logErr("Modification logging already running on node: " + + node->getAlias()); + + retVal = FhgfsOpsErr_INUSE; + break; + } + } + else + { + LogContext(logContext).logErr("Communication error occured with node: " + node->getAlias()); + retVal = FhgfsOpsErr_COMMUNICATION; + } + } + + return retVal; +} + +bool FsckTkEx::stopModificationLogging(NodeStore* metaNodes) +{ + const char* logContext = "FsckTkEx (stopModificationLogging)"; + + bool retVal = true; + + FsckTkEx::fsckOutput("-----", + OutputOptions_ADDLINEBREAKBEFORE | OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput( + "Waiting for metadata servers to stop modification logging. This might take some time.", + OutputOptions_FLUSH | OutputOptions_LINEBREAK); + FsckTkEx::fsckOutput("-----", OutputOptions_FLUSH | OutputOptions_DOUBLELINEBREAK); + + NumNodeIDList nodeIDs; + + auto metaNodeList = metaNodes->referenceAllNodes(); + + for (auto iter = metaNodeList.begin(); iter != metaNodeList.end(); iter++) + nodeIDs.push_back((*iter)->getNumID()); + + NumNodeIDListIter nodeIdIter = nodeIDs.begin(); + + while (! nodeIDs.empty()) + { + NumNodeID nodeID = *nodeIdIter; + + auto node = metaNodes->referenceNode(nodeID); + + NicAddressList nicList; + FsckSetEventLoggingMsg fsckSetEventLoggingMsg(false, 0, &nicList, false); + + const auto respMsg = MessagingTk::requestResponse(*node, fsckSetEventLoggingMsg, + NETMSGTYPE_FsckSetEventLoggingResp); + + if (respMsg) + { + auto* fsckSetEventLoggingRespMsg = (FsckSetEventLoggingRespMsg*) respMsg.get(); + + bool result = fsckSetEventLoggingRespMsg->getResult(); + bool missedEvents = fsckSetEventLoggingRespMsg->getMissedEvents(); + + if ( result ) + { + nodeIdIter = nodeIDs.erase(nodeIdIter); + if ( missedEvents ) + { + retVal = false; + } + } + else + nodeIdIter++; // keep in list and try again later + } + else + { + LogContext(logContext).logErr("Communication error occurred with node: " + node->getAlias()); + retVal = false; + } + + if (nodeIdIter == nodeIDs.end()) + { + nodeIdIter = nodeIDs.begin(); + sleep(5); + } + } + + return retVal; +} + +bool FsckTkEx::checkConsistencyStates() +{ + auto mgmtdNode = Program::getApp()->getMgmtNodes()->referenceFirstNode(); + if (!mgmtdNode) + throw std::runtime_error("Management node not found"); + + std::list storageReachabilityStates; + std::list storageConsistencyStates; + std::list storageTargetIDs; + std::list metaReachabilityStates; + std::list metaConsistencyStates; + std::list metaTargetIDs; + + if (!NodesTk::downloadTargetStates(*mgmtdNode, NODETYPE_Storage, &storageTargetIDs, + &storageReachabilityStates, &storageConsistencyStates, false) + || !NodesTk::downloadTargetStates(*mgmtdNode, NODETYPE_Meta, &metaTargetIDs, + &metaReachabilityStates, &metaConsistencyStates, false)) + { + throw std::runtime_error("Could not download target states from management."); + } + + + bool result = true; + + { + auto idIt = storageTargetIDs.begin(); + auto stateIt = storageConsistencyStates.begin(); + for (; idIt != storageTargetIDs.end() && stateIt != storageConsistencyStates.end(); + idIt++, stateIt++) + { + if (*stateIt == TargetConsistencyState_NEEDS_RESYNC) + { + FsckTkEx::fsckOutput("Storage target " + StringTk::uintToStr(*idIt) + " is set to " + "NEEDS_RESYNC.", OutputOptions_LINEBREAK); + result = false; + } + } + } + + { + auto idIt = metaTargetIDs.begin(); + auto stateIt = metaConsistencyStates.begin(); + for (; idIt != metaTargetIDs.end() && stateIt != metaConsistencyStates.end(); + idIt++, stateIt++) + { + if (*stateIt == TargetConsistencyState_NEEDS_RESYNC) + { + FsckTkEx::fsckOutput("Meta node " + StringTk::uintToStr(*idIt) + " is set to " + "NEEDS_RESYNC.", OutputOptions_LINEBREAK); + result = false; + } + } + } + + return result; +} diff --git a/fsck/source/toolkit/FsckTkEx.h b/fsck/source/toolkit/FsckTkEx.h new file mode 100644 index 0000000..593908d --- /dev/null +++ b/fsck/source/toolkit/FsckTkEx.h @@ -0,0 +1,103 @@ +#ifndef FSCKTKEX_H_ +#define FSCKTKEX_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* OutputOption Flags for fsckOutput */ +#define OutputOptions_NONE 0 +#define OutputOptions_LINEBREAK 1 +#define OutputOptions_DOUBLELINEBREAK 1 << 1 +#define OutputOptions_HEADLINE 1 << 2 +#define OutputOptions_FLUSH 1 << 3 +#define OutputOptions_ADDLINEBREAKBEFORE 1 << 4 +#define OutputOptions_COLORGREEN 1 << 5 +#define OutputOptions_COLORRED 1 << 6 +#define OutputOptions_LINEDELETE 1 << 7 +#define OutputOptions_NOSTDOUT 1 << 8 +#define OutputOptions_NOLOG 1 << 9 +#define OutputOptions_STDERR 1 << 10 + +#define OutputColor_NORMAL "\033[0m"; +#define OutputColor_GREEN "\033[32m"; +#define OutputColor_RED "\033[31m"; + +#define SAFE_FPRINTF(stream, fmt, args...) \ + do{ if(stream) {fprintf(stream, fmt, ##args);} } while(0) + +/* + * calculating with: + * DirEntry 76+256+28 Byte (space for dentry + longest name + one index) + * FileInode 96 Byte | + * DirInode 56 Byte # only the larger of these two counts, even though files are inlined + * ContDir 16 Byte + * FsID 40 Byte + * chunk 88 Byte + * + */ +#define NEEDED_DISKSPACE_META_INODE 512 +#define NEEDED_DISKSPACE_STORAGE_INODE 88 + +class FsckTkEx +{ + public: + // check the reachability of all nodes + static bool checkReachability(); + // check the reachability of a given node by sending a heartbeat message + static bool checkReachability(Node& node, NodeType nodetype); + + /* + * a formatted output for fsck + * + * @param text The text to be printed + * @param optionFlags OutputOptions_... flags (mainly for formatiing) + */ + static void fsckOutput(std::string text, int optionFlags); + // just print a formatted header with the version to the console + static void printVersionHeader(bool toStdErr = false, bool noLogFile = false); + // print the progress meter which goes round and round (-\|/-) + static void progressMeter(); + + static int64_t calcNeededSpace(); + static bool checkDiskSpace(Path& dbPath); + + static std::string getRepairActionDesc(FsckRepairAction repairAction, bool shortDesc = false); + + static FhgfsOpsErr startModificationLogging(NodeStore* metaNodes, Node& localNode, + bool forceRestart); + static bool stopModificationLogging(NodeStore* metaNodes); + + static bool checkConsistencyStates(); + + private: + FsckTkEx() {} + + // saves the last char output by the progress meter + static char progressChar; + // a mutex that is locked by output functions to make sure the output does not get messed up + // by two threads doing output at the same time + static Mutex outputMutex; + + + public: + // inliners + static void fsckOutput(std::string text) + { + fsckOutput(text, OutputOptions_LINEBREAK); + } +}; + +#endif /* FSCKTKEX_H_ */ diff --git a/fsck/tests/FlatTest.cpp b/fsck/tests/FlatTest.cpp new file mode 100644 index 0000000..f2e693b --- /dev/null +++ b/fsck/tests/FlatTest.cpp @@ -0,0 +1,21 @@ +#include "FlatTest.h" + +#include +#include + +FlatTest::FlatTest() +{ + dirName = "./fsck.test.dir/"; + this->fileName = this->dirName + "set"; +} + +void FlatTest::SetUp() +{ + if(::mkdir(this->dirName.c_str(), 0770) < 0 && errno != EEXIST) + throw std::runtime_error("could not create dir"); +} + +void FlatTest::TearDown() +{ + StorageTk::removeDirRecursive(this->dirName); +} diff --git a/fsck/tests/FlatTest.h b/fsck/tests/FlatTest.h new file mode 100644 index 0000000..d183019 --- /dev/null +++ b/fsck/tests/FlatTest.h @@ -0,0 +1,31 @@ +#ifndef FLATTEST_H_ +#define FLATTEST_H_ + +#include + +#include + +#include + +class FlatTest : public ::testing::Test +{ + public: + FlatTest(); + + void SetUp(); + void TearDown(); + + struct Data { + uint64_t id; + char dummy[1024 - sizeof(uint64_t)]; + + typedef uint64_t KeyType; + uint64_t pkey() const { return id; } + }; + + protected: + std::string dirName; + std::string fileName; +}; + +#endif diff --git a/fsck/tests/TestConfig.cpp b/fsck/tests/TestConfig.cpp new file mode 100644 index 0000000..ef09790 --- /dev/null +++ b/fsck/tests/TestConfig.cpp @@ -0,0 +1,64 @@ +#include "TestConfig.h" + +void TestConfig::SetUp() +{ + emptyConfigFile = DUMMY_EMPTY_CONFIG_FILE; + this->dummyConfigFile = DUMMY_NOEXIST_CONFIG_FILE; +} + +void TestConfig::TearDown() +{ + // delete generated config file + if ( StorageTk::pathExists(this->emptyConfigFile) ) + { + // return value of remove is ignored now + remove(this->emptyConfigFile.c_str()); + } +} + +TEST_F(TestConfig, defaultConfigFile) +{ + log.log(Log_DEBUG, "testDefaultConfigFile started"); + + // get the path where the binary resides + int BUFSIZE = 255; + char exePathBuf[BUFSIZE]; + // read only BUFSIZE-1, as we need to terminate the string manually later + ssize_t len = readlink("/proc/self/exe", exePathBuf, BUFSIZE-1); + + /* In case of an error, failure will indicate the error */ + if (len < 0) + FAIL() << "Internal error"; + + /* in case of insufficient buffer size, failure will indicate the error */ + if (len >= BUFSIZE) + FAIL() << "Internal error"; + + // readlink does NOT null terminate the string, so we do it here to be safe + exePathBuf[len] = '\0'; + + // construct the path to the default config file + std::string defaultFileName = std::string(dirname(exePathBuf)) + + "/" + DEFAULT_CONFIG_FILE_RELATIVE; + + // create config with the default file and see what happens while parsing + int argc = 2; + char* argv[2]; + + std::string appNameStr = APP_NAME; + appNameStr += '\0'; + + std::string cfgLineStr = "cfgFile=" + defaultFileName; + cfgLineStr += '\0'; + + argv[0] = &appNameStr[0]; + argv[1] = &cfgLineStr[0]; + + try { + Config config(argc, argv); + } catch (ConnAuthFileException& e) { + return; + } + + log.log(Log_DEBUG, "testDefaultConfigFile finished"); +} diff --git a/fsck/tests/TestConfig.h b/fsck/tests/TestConfig.h new file mode 100644 index 0000000..133e311 --- /dev/null +++ b/fsck/tests/TestConfig.h @@ -0,0 +1,29 @@ +#ifndef TESTCONFIG_H_ +#define TESTCONFIG_H_ + +#include + +#include +#include +#include + +#define DUMMY_NOEXIST_CONFIG_FILE "/tmp/nonExistantConfigFile.fsck" +#define DUMMY_EMPTY_CONFIG_FILE "/tmp/emptyConfigFile.fsck" +#define DEFAULT_CONFIG_FILE_RELATIVE "dist/etc/beegfs-client.conf" +#define APP_NAME "beegfs-fsck"; + +class TestConfig: public ::testing::Test +{ + protected: + void SetUp(); + void TearDown(); + + LogContext log; + + // we have these filenames as member variables because + // we might need to delete them in tearDown function + std::string dummyConfigFile; + std::string emptyConfigFile; +}; + +#endif /* TESTCONFIG_H_ */ diff --git a/fsck/tests/TestCursors.cpp b/fsck/tests/TestCursors.cpp new file mode 100644 index 0000000..03ab41f --- /dev/null +++ b/fsck/tests/TestCursors.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +template +static VectorSource vectorSource(const Obj (&data)[Size]) +{ + std::vector vector(data, data + Size); + + return VectorSource(vector); +} + +template +static void expect(const Source& source, const Data (&data)[Size], EqFn eqFn) +{ + BOOST_STATIC_ASSERT(Size > 2); + + for(size_t markPos = 0; markPos < Size; markPos++) + { + boost::shared_ptr mark; + Source current = source; + + for(size_t i = 0; i < Size; i++) + { + ASSERT_TRUE(current.step()); + + if(i == markPos) + mark = boost::make_shared(current.mark() ); + + ASSERT_TRUE(eqFn(*current.get(), data[i])); + } + + ASSERT_FALSE(current.step()); + + current.restore(*mark); + + for(size_t i = markPos; i < Size; i++) + { + ASSERT_TRUE(eqFn(*current.get(), data[i])); + + if(i < Size - 1) { + ASSERT_TRUE(current.step()); + } + } + + ASSERT_FALSE(current.step()); + } +} + +template +static void expect(Source source, const Data (&data)[Size]) +{ + expect(source, data, std::equal_to() ); +} + +TEST(Cursors, vectorSource) +{ + const int data[] = { 1, 2, 3 }; + + VectorSource source = vectorSource(data); + + expect(source, data); +} + +TEST(Cursors, union) +{ + const std::pair data1[] = { + std::make_pair(1, 1), + std::make_pair(1, 2), + std::make_pair(2, 2) + }; + + const std::pair data2[] = { + std::make_pair(1, 3), + std::make_pair(2, 3) + }; + + const std::pair expected[] = { + std::make_pair(1, 3), + std::make_pair(1, 1), + std::make_pair(1, 2), + std::make_pair(2, 3), + std::make_pair(2, 2) + }; + + struct Fn + { + static int key(std::pair pair) + { + return pair.first; + } + }; + + expect( + db::unionBy( + Fn::key, + vectorSource(data1), + vectorSource(data2) ), + expected); +} + +TEST(Cursors, select) +{ + const int data[] = { 1, 2, 3, 4 }; + + const int expected[] = { -1, -2, -3, -4 }; + + struct Fn + { + static int op(int i) + { + return -i; + } + }; + + expect( + vectorSource(data) + | db::select(Fn::op), + expected); +} + +TEST(Cursors, leftJoinEq) +{ + const int data1[] = { 1, 2, 2, 3, 4 }; + const int data2[] = { 1, 2, 3, 3, 5 }; + + const std::pair expected[] = { + std::make_pair(1, 1), + std::make_pair(2, 2), + std::make_pair(2, 2), + std::make_pair(3, 3), + std::make_pair(3, 3), + std::make_pair(4, 0), + }; + + struct Fn + { + static bool eq(std::pair left, std::pair right) + { + return left.first == right.first && + ( (right.second == 0 && left.second == NULL) || + (right.second != 0 && left.second != NULL && right.second == *left.second) ); + } + + static int key(int i) + { + return i; + } + }; + + expect( + db::leftJoinBy( + Fn::key, + vectorSource(data1), + vectorSource(data2) ), + expected, + Fn::eq); +} + +struct TestGroupOps +{ + typedef int KeyType; + typedef int ProjType; + typedef int GroupType; + + int count; + + TestGroupOps() + : count(0) + {} + + KeyType key(int i) + { + return i; + } + + ProjType project(int i) + { + return -i; + } + + void step(int i) + { + this->count += 1; + } + + int finish() + { + int result = this->count; + this->count = 0; + return result; + } +}; + +TEST(Cursors, group) +{ + const int data[] = { 1, 2, 2, 3 }; + const std::pair expected[] = { + std::make_pair(-1, 1), + std::make_pair(-2, 2), + std::make_pair(-3, 1) + }; + + expect( + vectorSource(data) + | db::groupBy(TestGroupOps() ), + expected); +} + +TEST(Cursors, filter) +{ + const int data[] = { 1, 2, 2, 3 }; + const int expected[] = { 2, 2, 3 }; + + struct Fn + { + static bool fn(int i) + { + return i > 1; + } + }; + + expect( + vectorSource(data) + | db::where(Fn::fn), + expected); +} + +TEST(Cursors, distinct) +{ + const int data[] = { 1, 2, 2, 3 }; + const int expected[] = { 1, 2, 3 }; + + struct Fn + { + static int fn(int i) + { + return i; + } + }; + + expect( + vectorSource(data) + | db::distinctBy(Fn::fn), + expected); +} diff --git a/fsck/tests/TestDatabase.cpp b/fsck/tests/TestDatabase.cpp new file mode 100644 index 0000000..492ff5d --- /dev/null +++ b/fsck/tests/TestDatabase.cpp @@ -0,0 +1,1772 @@ +#include "TestDatabase.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// these tests currently require lots of friend access to fsck classes from common. put all the +// code into TestDatabase to allow these friend accesses. +#define DB_TEST(id) \ + TEST_F(TestDatabase, id) { TestDatabase::id(); } \ + void TestDatabase::id() + +TestDatabase::TestDatabase() +{ + databasePath = "./fsckdb.test.dir"; +} + +TestDatabase::~TestDatabase() +{ +} + +void TestDatabase::SetUp() +{ + // create a new database + Path dbPath(databasePath); + bool createRes = StorageTk::createPathOnDisk(dbPath, false); + if (!createRes) + throw FsckDBException("Unable to create path to database"); + + this->db.reset(new FsckDB(databasePath, 1024 * 1024, 1000, true) ); + this->db->clear(); +} + +void TestDatabase::TearDown() +{ + this->db.reset(); + StorageTk::removeDirRecursive(this->databasePath); +} + +DB_TEST(testInsertAndReadSingleDentry) +{ + FsckDirEntryList dentryListIn(1, DatabaseTk::createDummyFsckDirEntry() ); + + // test with small name, long name, and another long name at different offset in the name dump + // file + dentryListIn.front().setName("0"); + for(int i = 0; i < 3; i++) + { + this->db->getDentryTable()->clear(); + this->db->getDentryTable()->insert(dentryListIn); + + FsckDirEntryList dentryListOut = drain(this->db->getDentryTable()->get() ); + + // names are not resolved by a simple load + dentryListOut.front().setName( + this->db->getDentryTable()->getNameOf( + this->db->getDentryTable()->getAnyFor( + db::EntryID::fromStr(dentryListIn.front().getID() ) ).second) ); + + ASSERT_EQ(dentryListIn.front(), dentryListOut.front()); + + dentryListIn.front().setName(std::string(255, "abc"[i]) ); + } +} + +DB_TEST(testUpdateDentries) +{ + unsigned NUM_ROWS = 5; + + FsckDirEntryList dentryListIn; + + DatabaseTk::createDummyFsckDirEntries(NUM_ROWS, &dentryListIn); + + this->db->getDentryTable()->insert(dentryListIn); + + for (FsckDirEntryListIter iter = dentryListIn.begin(); iter != dentryListIn.end(); iter++) + iter->setInodeOwnerNodeID(NumNodeID(iter->getInodeOwnerNodeID().val() + 1000) ); + + this->db->getDentryTable()->updateFieldsExceptParent(dentryListIn); + + FsckDirEntryList dentryListOut = drain(this->db->getDentryTable()->get() ); + // names are not resolved by a simple load + for(FsckDirEntryListIter it = dentryListOut.begin(); it != dentryListOut.end(); ++it) + it->setName( + this->db->getDentryTable()->getNameOf( + this->db->getDentryTable()->getAnyFor( + db::EntryID::fromStr(it->getID() ) ).second) ); + + bool compareRes = ListTk::listsEqual(&dentryListIn, &dentryListOut); + + ASSERT_TRUE(compareRes) << "Comparision of dirEntries failed"; +} + +DB_TEST(testUpdateDirInodes) +{ + unsigned NUM_ROWS = 5; + + FsckDirInodeList inodeListIn; + + DatabaseTk::createDummyFsckDirInodes(NUM_ROWS, &inodeListIn); + + this->db->getDirInodesTable()->insert(inodeListIn); + + for (FsckDirInodeListIter iter = inodeListIn.begin(); iter != inodeListIn.end(); iter++) + iter->setParentNodeID(NumNodeID(iter->getParentNodeID().val() + 1000) ); + + this->db->getDirInodesTable()->update(inodeListIn); + + FsckDirInodeList inodeListOut = drain(this->db->getDirInodesTable()->get() ); + + bool compareRes = ListTk::listsEqual(&inodeListIn, &inodeListOut); + ASSERT_TRUE(compareRes) << "Comparision of dir inodes failed"; +} + +DB_TEST(testUpdateFileInodes) +{ + unsigned NUM_ROWS = 15; + + FsckFileInodeList inodeListIn; + + DatabaseTk::createDummyFsckFileInodes(NUM_ROWS, &inodeListIn); + + this->db->getFileInodesTable()->insert(inodeListIn); + + for (FsckFileInodeListIter iter = inodeListIn.begin(); iter != inodeListIn.end(); iter++) + iter->parentNodeID = NumNodeID(iter->getParentNodeID().val() + 1000); + + this->db->getFileInodesTable()->update(inodeListIn); + + std::list inodeListOut; + std::list targetsListOut; + + drainToList(this->db->getFileInodesTable()->getInodes(), inodeListOut); + drainToList(this->db->getFileInodesTable()->getTargets(), targetsListOut); + + while (!inodeListIn.empty() ) + { + db::FileInode inode = inodeListOut.front(); + std::vector targetsFound; + + ASSERT_EQ(inode.stripePatternSize, inode.id.sequence); + int inlineTargets = std::min(inode.id.sequence, inode.NTARGETS); + + targetsFound.resize(inode.stripePatternSize); + std::copy(inode.targets, inode.targets + inlineTargets, targetsFound.begin() ); + + inodeListIn.front().stripeTargets.clear(); + + ASSERT_EQ(inodeListIn.front(), inode.toInodeWithoutStripes()); + + while (!targetsListOut.empty() && targetsListOut.front().id == inode.id) + { + db::StripeTargets& st = targetsListOut.front(); + + int outlineTargets = std::min( + inode.stripePatternSize - st.firstTargetIndex, + st.NTARGETS); + + std::copy( + st.targets, st.targets + outlineTargets, + targetsFound.begin() + st.firstTargetIndex); + + targetsListOut.pop_front(); + } + + for (unsigned i = 0; i < targetsFound.size(); i++) + ASSERT_EQ(targetsFound[i], inode.id.sequence + 1000 * (i + 1)); + + inodeListIn.pop_front(); + inodeListOut.pop_front(); + } +} + +DB_TEST(testUpdateChunks) +{ + unsigned NUM_ROWS = 5; + + FsckChunkList chunkListIn; + + DatabaseTk::createDummyFsckChunks(NUM_ROWS, &chunkListIn); + + this->db->getChunksTable()->insert(chunkListIn); + + for (FsckChunkListIter iter = chunkListIn.begin(); iter != chunkListIn.end(); iter++) + iter->setUserID(iter->getUserID() + 1000); + + this->db->getChunksTable()->update(chunkListIn); + + FsckChunkList chunkListOut = drain(this->db->getChunksTable()->get() ); + + bool compareRes = ListTk::listsEqual(&chunkListIn, &chunkListOut); + ASSERT_TRUE(compareRes) << "Comparision of chunks failed"; +} + +DB_TEST(testInsertAndReadSingleFileInode) +{ + FsckFileInodeList inodeListIn(1, DatabaseTk::createDummyFsckFileInode() ); + + this->db->getFileInodesTable()->insert(inodeListIn); + + std::list inodeListOut; + std::list targetsListOut; + + drainToList(this->db->getFileInodesTable()->getInodes(), inodeListOut); + drainToList(this->db->getFileInodesTable()->getTargets(), targetsListOut); + + ASSERT_EQ(inodeListOut.size(), 1u); + ASSERT_EQ(targetsListOut.size(), 1u); + + db::FileInode inode = inodeListOut.front(); + + ASSERT_EQ(inode.stripePatternSize, 7u); + ASSERT_EQ(inode.targets[0], 7 + 1000); + ASSERT_EQ(inode.targets[1], 7 + 2000); + ASSERT_EQ(inode.targets[2], 7 + 3000); + ASSERT_EQ(inode.targets[3], 7 + 4000); + ASSERT_EQ(inode.targets[4], 7 + 5000); + ASSERT_EQ(inode.targets[5], 7 + 6000); + + inodeListIn.front().stripeTargets.clear(); + + ASSERT_EQ(inodeListIn.front(), inode.toInodeWithoutStripes()); + + ASSERT_EQ(targetsListOut.front().firstTargetIndex, 6); + ASSERT_EQ(targetsListOut.front().id, inode.id); + ASSERT_EQ(targetsListOut.front().targets[0], 7 + 7000); +} + +DB_TEST(testInsertAndReadSingleDirInode) +{ + FsckDirInodeList dirInodeListIn(1, DatabaseTk::createDummyFsckDirInode() ); + + this->db->getDirInodesTable()->insert(dirInodeListIn); + + FsckDirInodeList dirInodeListOut = drain(this->db->getDirInodesTable()->get() ); + + FsckDirInode inode = dirInodeListOut.front(); + + bool compareRes = ListTk::listsEqual(&dirInodeListIn, &dirInodeListOut); + ASSERT_TRUE(compareRes) << "Comparision of dir inodes failed"; +} + +DB_TEST(testInsertAndReadDirInodes) +{ + uint NUM_ROWS = 5; + + FsckDirInodeList dirInodeListIn; + + DatabaseTk::createDummyFsckDirInodes(NUM_ROWS, &dirInodeListIn); + + this->db->getDirInodesTable()->insert(dirInodeListIn); + + FsckDirInodeList dirInodeListOut = drain(this->db->getDirInodesTable()->get() ); + + bool compareRes = ListTk::listsEqual(&dirInodeListIn, &dirInodeListOut); + ASSERT_TRUE(compareRes) << "Comparision of dir inodes failed"; +} + +DB_TEST(testInsertAndReadSingleChunk) +{ + FsckChunkList chunkListIn(1, DatabaseTk::createDummyFsckChunk() ); + + this->db->getChunksTable()->insert(chunkListIn); + + FsckChunkList chunkListOut = drain(this->db->getChunksTable()->get() ); + + ASSERT_EQ(chunkListIn.size(), chunkListOut.size()); + + bool compareRes = ListTk::listsEqual(&chunkListIn, &chunkListOut); + ASSERT_TRUE(compareRes) << "Comparision of chunks failed"; +} + +DB_TEST(testInsertAndReadChunks) +{ + uint NUM_ROWS = 5; + + FsckChunkList chunkListIn; + + DatabaseTk::createDummyFsckChunks(NUM_ROWS, &chunkListIn); + + this->db->getChunksTable()->insert(chunkListIn); + + FsckChunkList chunkListOut = drain(this->db->getChunksTable()->get() ); + + bool compareRes = ListTk::listsEqual(&chunkListIn, &chunkListOut); + ASSERT_TRUE(compareRes) << "Comparision of chunks failed"; +} + +DB_TEST(testInsertAndReadSingleContDir) +{ + FsckContDirList contDirListIn(1, DatabaseTk::createDummyFsckContDir() ); + + this->db->getContDirsTable()->insert(contDirListIn); + + FsckContDirList contDirListOut = drain(this->db->getContDirsTable()->get() ); + + bool compareRes = ListTk::listsEqual(&contDirListIn, &contDirListOut); + ASSERT_TRUE(compareRes) << "Comparision of cont dirs failed"; +} + +DB_TEST(testInsertAndReadContDirs) +{ + unsigned NUM_ROWS = 5; + + FsckContDirList contDirListIn; + + DatabaseTk::createDummyFsckContDirs(NUM_ROWS, &contDirListIn); + + this->db->getContDirsTable()->insert(contDirListIn); + + FsckContDirList contDirListOut = drain(this->db->getContDirsTable()->get() ); + + bool compareRes = ListTk::listsEqual(&contDirListIn, &contDirListOut); + ASSERT_TRUE(compareRes) << "Comparision of cont dirs failed"; +} + +DB_TEST(testInsertAndReadSingleFsID) +{ + FsckFsIDList listIn(1, DatabaseTk::createDummyFsckFsID() ); + + this->db->getFsIDsTable()->insert(listIn); + + FsckFsIDList listOut = drain(this->db->getFsIDsTable()->get() ); + + bool compareRes = ListTk::listsEqual(&listIn, &listOut); + ASSERT_TRUE(compareRes) << "Comparision of fs ids failed"; +} + +DB_TEST(testInsertAndReadFsIDs) +{ + unsigned NUM_ROWS = 5; + + FsckFsIDList listIn; + + DatabaseTk::createDummyFsckFsIDs(NUM_ROWS, &listIn); + + this->db->getFsIDsTable()->insert(listIn); + + FsckFsIDList listOut = drain(this->db->getFsIDsTable()->get() ); + + bool compareRes = ListTk::listsEqual(&listIn, &listOut); + ASSERT_TRUE(compareRes) << "Comparision of fs ids failed"; +} + +DB_TEST(testFindDuplicateInodeIDs) +{ + FsckDirInodeList dirs; + FsckDirInodeList disposal; + FsckFileInodeList files; + + DatabaseTk::createDummyFsckDirInodes(4, &dirs); + DatabaseTk::createDummyFsckFileInodes(1, &files); + files.back().saveNodeID = NumNodeID(2); + + DatabaseTk::createDummyFsckDirInodes(2, 1, &dirs); + dirs.back().saveNodeID = NumNodeID(2); + + DatabaseTk::createDummyFsckFileInodes(4, 4, &files); + DatabaseTk::createDummyFsckFileInodes(6, 1, &files); + files.back().saveNodeID = NumNodeID(3); + + // create a duplicate inlined inode entry. we see those when hardlinks are used + DatabaseTk::createDummyFsckFileInodes(42, 1, &files); + files.push_back(files.back() ); + + disposal.push_back( + FsckDirInode("disposal", "", NumNodeID(1), NumNodeID(1), 0, 0, UInt16Vector(), + FsckStripePatternType_RAID0, NumNodeID(1), false, true, false)); + disposal.push_back( + FsckDirInode("disposal", "", NumNodeID(2), NumNodeID(2), 0, 0, UInt16Vector(), + FsckStripePatternType_RAID0, NumNodeID(2), false, true, false)); + + this->db->getDirInodesTable()->insert(dirs); + this->db->getDirInodesTable()->insert(disposal); + this->db->getFileInodesTable()->insert(files); + + Cursor c = this->db->findDuplicateInodeIDs(); + + ASSERT_TRUE(c.step()); + { + std::set nodes; + nodes.insert(FsckDuplicateInodeInfo(std::string("0-1-1"), std::string("0-2-1"), 3000, false, false, DirEntryType_DIRECTORY)); + nodes.insert(FsckDuplicateInodeInfo(std::string("0-1-1"), std::string("0-2-1"), 2, true, false, DirEntryType_REGULARFILE)); + + ASSERT_EQ(c.get()->first, db::EntryID(0, 1, 1)); + ASSERT_EQ(c.get()->second.size(), 2u); + ASSERT_EQ(c.get()->second, nodes); + } + + ASSERT_TRUE(c.step()); + { + std::set nodes; + nodes.insert(FsckDuplicateInodeInfo(std::string("2-1-1"), std::string("2-2-1"), 3002, false, false, DirEntryType_DIRECTORY)); + nodes.insert(FsckDuplicateInodeInfo(std::string("2-1-1"), std::string("2-2-1"), 2, false, false, DirEntryType_DIRECTORY)); + + ASSERT_EQ(c.get()->first, db::EntryID(2, 1, 1)); + ASSERT_EQ(c.get()->second.size(), 2u); + ASSERT_EQ(c.get()->second, nodes); + } + + ASSERT_TRUE(c.step()); + { + std::set nodes; + nodes.insert(FsckDuplicateInodeInfo(std::string("6-1-1"), std::string("6-2-1"), 2006, true, false, DirEntryType_REGULARFILE)); + nodes.insert(FsckDuplicateInodeInfo(std::string("6-1-1"), std::string("6-2-1"), 3, true, false, DirEntryType_REGULARFILE)); + + ASSERT_EQ(c.get()->first, db::EntryID(6, 1, 1)); + ASSERT_EQ(c.get()->second.size(), 2u); + ASSERT_EQ(c.get()->second, nodes); + } + + ASSERT_FALSE(c.step()); +} + +DB_TEST(testFindDuplicateChunks) +{ + FsckChunkList chunks; + + DatabaseTk::createDummyFsckChunks(0, 1, 1, &chunks); + DatabaseTk::createDummyFsckChunks(0, 1, 1, &chunks); + chunks.back().buddyGroupID = 1; + + DatabaseTk::createDummyFsckChunks(10, 1, 1, &chunks); + DatabaseTk::createDummyFsckChunks(10, 1, 1, &chunks); + + this->db->getChunksTable()->insert(chunks); + + Cursor > c = this->db->findDuplicateChunks(); + + ASSERT_TRUE(c.step()); + { + std::list dups = *c.get(); + + ASSERT_EQ(dups.size(), 2u); + ASSERT_EQ(dups.front().getID(), "0-1-1"); + ASSERT_TRUE( + (dups.front().getBuddyGroupID() == 0) ^ (dups.back().getBuddyGroupID() == 0) ); + } + + ASSERT_TRUE(c.step()); + { + std::list dups = *c.get(); + + ASSERT_EQ(dups.size(), 2u); + ASSERT_EQ(dups.front().getID(), "A-1-1"); + } + + ASSERT_FALSE(c.step()); +} + +DB_TEST(testFindDuplicateContDirs) +{ + std::list contDirs = { + {"1-0-1", {}, false}, + + {"2-0-1", NumNodeID(0), false}, + {"2-0-1", NumNodeID(1), false}, + + {"3-0-1", {}, false}, + {"3-0-1", {}, true}, + }; + + db->getContDirsTable()->insert(contDirs); + + auto duplicate = db->findDuplicateContDirs(); + + ASSERT_TRUE(duplicate.step()); + ASSERT_EQ(duplicate.get()->front().id.str(), "2-0-1"); + ASSERT_EQ(duplicate.get()->size(), 2u); + + ASSERT_TRUE(duplicate.step()); + ASSERT_EQ(duplicate.get()->front().id.str(), "3-0-1"); + ASSERT_EQ(duplicate.get()->size(), 2u); + + ASSERT_FALSE(duplicate.step()); +} + +DB_TEST(testFindMismirroredDentries) +{ + std::list dentries = { + {"1-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, false}, + {"2-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, false}, + {"3-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, false}, + {"4-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, true}, + {"5-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, true}, + {"6-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, true}, + + {"7-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, false}, + {"8-0-1", {}, {}, {}, {}, FsckDirEntryType_REGULARFILE, false, {}, 0, 0, true}, + + {"11-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, false}, + {"12-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, false}, + {"13-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, false}, + {"14-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, true}, + {"15-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, true}, + {"16-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, true}, + + {"17-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, false}, + {"18-0-1", {}, {}, {}, {}, FsckDirEntryType_DIRECTORY, false, {}, 0, 0, true}, + }; + + std::list files = { + {"1-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + false, true, false}, + {"2-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + true, true, false}, + {"4-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + true, true, false}, + {"5-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + false, true, false}, + + {"7-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + true, true, false}, + {"8-0-1", {}, {}, {}, 0, 0, 0, 0, 0, {}, FsckStripePatternType_RAID0, 0, {}, 0, 0, false, + false, true, false}, + }; + + std::list dirs = { + {"11-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + {"12-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"14-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"15-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + + {"17-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"18-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + }; + + std::list events = { + {ModificationEvent_FILEMOVED, "7-0-1"}, + {ModificationEvent_FILEMOVED, "8-0-1"}, + + {ModificationEvent_DIRMOVED, "17-0-1"}, + {ModificationEvent_DIRMOVED, "18-0-1"}, + }; + + db->getDentryTable()->insert(dentries); + db->getFileInodesTable()->insert(files); + db->getDirInodesTable()->insert(dirs); + db->getModificationEventsTable()->insert(events, + db->getModificationEventsTable()->newBulkHandle()); + + auto mismirrored = db->findMismirroredDentries(); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "2-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "5-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "12-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "15-0-1"); + + ASSERT_FALSE(mismirrored.step()); +} + +DB_TEST(testFindMismirroredDirectories) +{ + std::list dirs = { + {"1-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + {"2-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"3-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + + {"4-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + {"5-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"6-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + + {"7-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + {"8-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, false}, + {"9-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, false}, + + {"10-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, false, true, true}, + {"11-0-1", {}, {}, {}, 0, 0, {}, FsckStripePatternType_RAID0, {}, true, true, true}, + }; + + std::list contDirs = { + {"1-0-1", {}, false}, + {"2-0-1", {}, true}, + + {"4-0-1", {}, true}, + {"5-0-1", {}, false}, + + {"7-0-1", {}, true}, + {"8-0-1", {}, false}, + + {"10-0-1", {}, false}, + {"11-0-1", {}, true}, + }; + + std::list events = { + {ModificationEvent_DIRMOVED, "7-0-1"}, + {ModificationEvent_DIRMOVED, "8-0-1"}, + {ModificationEvent_DIRMOVED, "9-0-1"}, + }; + + db->getDirInodesTable()->insert(dirs); + db->getContDirsTable()->insert(contDirs); + db->getModificationEventsTable()->insert(events, + db->getModificationEventsTable()->newBulkHandle()); + + auto mismirrored = db->findMismirroredDirectories(); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "4-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "5-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "10-0-1"); + + ASSERT_TRUE(mismirrored.step()); + ASSERT_EQ(mismirrored.get()->id.str(), "11-0-1"); + + ASSERT_FALSE(mismirrored.step()); +} + +DB_TEST(testCheckForAndInsertDanglingDirEntries) +{ + unsigned NUM_DENTRIES_DIRS = 5; + unsigned NUM_DENTRIES_FILES = 5; + + unsigned MISSING_EACH = 2; + + // create dentries for dirs + FsckDirEntryList dentriesDirs; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES_DIRS, &dentriesDirs, + FsckDirEntryType_DIRECTORY); + + // create dentries for files (start with ID after the NUM_DENTRIES_DIRS) + FsckDirEntryList dentriesFiles; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES_DIRS, NUM_DENTRIES_FILES, &dentriesFiles); + + // create dir inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_DENTRIES_DIRS-MISSING_EACH - 1, &dirInodes); + + // create file inodes + FsckFileInodeList fileInodes; + DatabaseTk::createDummyFsckFileInodes(NUM_DENTRIES_DIRS, NUM_DENTRIES_FILES-MISSING_EACH - 1, + &fileInodes); + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, dentriesFiles.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, dentriesDirs.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getDentryTable()->insert(dentriesDirs); + this->db->getDentryTable()->insert(dentriesFiles); + + // insert the inodes + this->db->getFileInodesTable()->insert(fileInodes); + this->db->getDirInodesTable()->insert(dirInodes); + + // now find all dentries without an inode into the errors table + FsckDirEntryList entries; + drainToList(this->db->findDanglingDirEntries(), entries); + + ASSERT_EQ(entries.size(), MISSING_EACH*2); +} + +DB_TEST(testCheckForAndInsertInodesWithWrongOwner) +{ + unsigned NUM_INODES = 10; + unsigned NUM_WRONG = 4; + + // create the inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_INODES, &dirInodes); + + // for all of the inodes set the same ownerNodeID and saveNodeID + for ( FsckDirInodeListIter dirInodeIter = dirInodes.begin(); dirInodeIter != dirInodes.end(); + dirInodeIter++ ) + { + dirInodeIter->ownerNodeID = NumNodeID(1000); + dirInodeIter->saveNodeID = NumNodeID(1000); + } + + // now set wrong ownerInfo for NUM_WRONG inodes + unsigned i = 0; + FsckDirInodeListIter iter = dirInodes.begin(); + while ( (iter != dirInodes.end()) && (i < NUM_WRONG) ) + { + iter->ownerNodeID = NumNodeID(iter->ownerNodeID.val() + 1000); + iter++; + i++; + } + + iter->ownerNodeID = NumNodeID(iter->ownerNodeID.val() + 1000); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, iter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + // save them to DB + this->db->getDirInodesTable()->insert(dirInodes); + + // get cursor to the entries + FsckDirInodeList inodes; + + drainToList(this->db->findInodesWithWrongOwner(), inodes); + + ASSERT_EQ(inodes.size(), NUM_WRONG); +} + +DB_TEST(testCheckForAndInsertDirEntriesWithWrongOwner) +{ + unsigned NUM_DENTRIES_DIRS = 5; + unsigned NUM_DENTRIES_FILES = 5; + + unsigned WRONG_EACH = 2; + + // create dentries for dirs + FsckDirEntryList dentriesDirs; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES_DIRS, &dentriesDirs, + FsckDirEntryType_DIRECTORY); + + // create dentries for files (start with ID after the NUM_DENTRIES_DIRS) + FsckDirEntryList dentriesFiles; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES_DIRS, NUM_DENTRIES_FILES, &dentriesFiles); + + // create dir inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_DENTRIES_DIRS, &dirInodes); + + // for all of the inodes set the same saveNodeID + for (FsckDirInodeListIter dirInodeIter = dirInodes.begin(); dirInodeIter != dirInodes.end(); + dirInodeIter++) + { + dirInodeIter->saveNodeID = NumNodeID(1000); + } + + // create file inodes + FsckFileInodeList fileInodes; + DatabaseTk::createDummyFsckFileInodes(NUM_DENTRIES_DIRS, NUM_DENTRIES_FILES, + &fileInodes); + + // for all of the inodes set the same saveNodeID + for (FsckFileInodeListIter fileInodeIter = fileInodes.begin(); fileInodeIter != fileInodes.end(); + fileInodeIter++) + { + fileInodeIter->saveNodeID = NumNodeID(1000); + } + + // now manipulate the first WRONG_EACH dir entries and file entries to have wrong owner + // information and the rest to be correct + unsigned i = 0; + + FsckDirEntryListIter iter = dentriesDirs.begin(); + while ( ( iter != dentriesDirs.end() ) && (i < WRONG_EACH) ) + { + iter->inodeOwnerNodeID = NumNodeID(iter->inodeOwnerNodeID.val() + 1000); + iter++; + i++; + } + + iter->inodeOwnerNodeID = NumNodeID(iter->inodeOwnerNodeID.val() + 1000); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, iter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + ++iter; + + while ( iter != dentriesDirs.end() ) + { + iter->inodeOwnerNodeID = NumNodeID(1000); + iter++; + i++; + } + + i = 0; + iter = dentriesFiles.begin(); + while ( ( iter != dentriesFiles.end() ) && (i < WRONG_EACH) ) + { + iter->inodeOwnerNodeID = NumNodeID(iter->inodeOwnerNodeID.val() + 1000); + iter++; + i++; + } + + iter->inodeOwnerNodeID = NumNodeID(iter->inodeOwnerNodeID.val() + 1000); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, iter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + ++iter; + + while ( iter != dentriesFiles.end() ) + { + iter->inodeOwnerNodeID = NumNodeID(1000); + iter++; + i++; + } + + // insert the dentries + this->db->getDentryTable()->insert(dentriesDirs); + this->db->getDentryTable()->insert(dentriesFiles); + + // insert the inodes + this->db->getFileInodesTable()->insert(fileInodes); + this->db->getDirInodesTable()->insert(dirInodes); + + ASSERT_EQ(countCursor(this->db->findDirEntriesWithWrongOwner()), WRONG_EACH*2); +} + +DB_TEST(testCheckForAndInsertMissingDentryByIDFile) +{ + unsigned NUM_DENTRIES = 50; + unsigned NUM_DELETED_IDS = 5; + unsigned NUM_WRONG_NODE_IDS = 5; + unsigned NUM_WRONG_DEVICE_IDS = 5; + unsigned NUM_WRONG_INODE_IDS = 5; + + unsigned ERRORS_TOTAL = NUM_DELETED_IDS + NUM_WRONG_NODE_IDS + NUM_WRONG_DEVICE_IDS + + NUM_WRONG_INODE_IDS; + + // create dentries + FsckDirEntryList dentries; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES, &dentries); + + // create IDs + FsckFsIDList fsIDs; + DatabaseTk::createDummyFsckFsIDs(NUM_DENTRIES, &fsIDs); + + // make sure the data is correct + FsckDirEntryListIter dentryIter = dentries.begin(); + FsckFsIDListIter fsIDIter = fsIDs.begin(); + for (uint i = 0; i< NUM_DENTRIES; i++) + { + dentryIter->saveNodeID = NumNodeID(i); + dentryIter->parentDirID = "ff-ff-ff"; + dentryIter->saveDevice = i+1000; + dentryIter->saveInode = i+2000; + + fsIDIter->saveNodeID = NumNodeID(i); + fsIDIter->parentDirID = "ff-ff-ff"; + fsIDIter->saveDevice = i+1000; + fsIDIter->saveInode = i+2000; + + dentryIter++; + fsIDIter++; + } + + // now manipulate the first NUM_WRONG_NODE_IDS fsids + + fsIDIter = fsIDs.begin(); + for (unsigned i = 0 ; i < NUM_WRONG_NODE_IDS; i++) + { + if (fsIDIter == fsIDs.end()) + FAIL() << "End of list reached! Most likely a programmer's error!"; + + fsIDIter->saveNodeID = NumNodeID(fsIDIter->getSaveNodeID().val() + 1000); + fsIDIter++; + } + + // now manipulate NUM_WRONG_DEVICE_IDS fsids + for (unsigned i = 0 ; i < NUM_WRONG_DEVICE_IDS; i++) + { + if (fsIDIter == fsIDs.end()) + FAIL() << "End of list reached! Most likely a programmer's error!"; + + fsIDIter->saveDevice = fsIDIter->getSaveDevice() + 1000; + fsIDIter++; + } + + // now manipulate NUM_WRONG_INODE_IDS fsids + for (unsigned i = 0 ; i < NUM_WRONG_INODE_IDS; i++) + { + if (fsIDIter == fsIDs.end()) + FAIL() << "End of list reached! Most likely a programmer's error!"; + + fsIDIter->saveInode = fsIDIter->getSaveInode() + 1000; + fsIDIter++; + } + + // now delete NUM_DELETE_IDS fsids + for (unsigned i = 0 ; i < NUM_DELETED_IDS; i++) + { + if (fsIDIter == fsIDs.end()) + FAIL() << "End of list reached! Most likely a programmer's error!"; + + fsIDIter = fsIDs.erase(fsIDIter); + } + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, fsIDIter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + fsIDs.erase(fsIDIter); + + this->db->getDentryTable()->insert(dentries); + this->db->getFsIDsTable()->insert(fsIDs); + + FsckDirEntryList entries; + drainToList(this->db->findDirEntriesWithBrokenByIDFile(), entries); + + ASSERT_EQ(entries.size(), ERRORS_TOTAL); +} + +DB_TEST(testCheckForAndInsertOrphanedDentryByIDFiles) +{ + unsigned NUM_DENTRIES = 50; + unsigned NUM_DELETES = 5; + + // create dentries + FsckDirEntryList dentries; + DatabaseTk::createDummyFsckDirEntries(NUM_DENTRIES, &dentries); + + // create IDs + FsckFsIDList fsIDs; + DatabaseTk::createDummyFsckFsIDs(NUM_DENTRIES, &fsIDs); + + // make sure the data is correct + FsckDirEntryListIter dentryIter = dentries.begin(); + FsckFsIDListIter fsIDIter = fsIDs.begin(); + for (uint i = 0; i< NUM_DENTRIES; i++) + { + dentryIter->saveNodeID = NumNodeID(i); + dentryIter->parentDirID = "ff-ff-ff"; + dentryIter->saveDevice = i+1000; + dentryIter->saveInode = i+2000; + + fsIDIter->saveNodeID = NumNodeID(i); + fsIDIter->parentDirID = "ff-ff-ff"; + fsIDIter->saveDevice = i+1000; + fsIDIter->saveInode = i+2000; + + dentryIter++; + fsIDIter++; + } + + // now delete NUM_DELETES dentry, plus one for online modification + dentryIter = dentries.begin(); + for (unsigned i = 0 ; i < NUM_DELETES + 1; i++) + { + if (dentryIter == dentries.end()) + FAIL() << "End of list reached! Most likely a programmer's error!"; + + if(i == NUM_DELETES) + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, dentryIter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + dentryIter = dentries.erase(dentryIter); + } + + this->db->getDentryTable()->insert(dentries); + this->db->getFsIDsTable()->insert(fsIDs); + + fsIDs.clear(); + drainToList(this->db->findOrphanedFsIDFiles(), fsIDs); + + ASSERT_EQ(fsIDs.size(), NUM_DELETES); +} + +DB_TEST(testCheckForAndInsertOrphanedDirInodes) +{ + unsigned NUM_INODES = 5; + unsigned MISSING_DENTRIES = 2; + + // create dir inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_INODES, &dirInodes); + + // create dentries for dirs + FsckDirEntryList dentriesDirs; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES-MISSING_DENTRIES - 1, &dentriesDirs, + FsckDirEntryType_DIRECTORY); + + // create some dentries for files (just to see what happens if they are present) + FsckDirEntryList dentriesFiles; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES, NUM_INODES+NUM_INODES, &dentriesFiles); + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, dirInodes.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + // insert the dentries + this->db->getDentryTable()->insert(dentriesDirs); + this->db->getDentryTable()->insert(dentriesFiles); + this->db->getDirInodesTable()->insert(dirInodes); + + FsckDirInodeList inodes; + drainToList(this->db->findOrphanedDirInodes(), inodes); + + ASSERT_EQ(inodes.size(), MISSING_DENTRIES); +} + +DB_TEST(testCheckForAndInsertOrphanedFileInodes) +{ + unsigned NUM_INODES = 5; + unsigned MISSING_DENTRIES = 2; + + // create file inodes + FsckFileInodeList fileInodes; + DatabaseTk::createDummyFsckFileInodes(NUM_INODES, &fileInodes); + + // create dentries for files + FsckDirEntryList dentriesFiles; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES-MISSING_DENTRIES - 1, &dentriesFiles); + + // create some dentries for dirs (just to see what happens if they are present) + FsckDirEntryList dentriesDirs; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES, NUM_INODES+NUM_INODES, &dentriesDirs, + FsckDirEntryType_DIRECTORY); + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, fileInodes.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + // insert the dentries + this->db->getDentryTable()->insert(dentriesDirs); + this->db->getDentryTable()->insert(dentriesFiles); + this->db->getFileInodesTable()->insert(fileInodes); + + FsckFileInodeList inodes; + drainToList(this->db->findOrphanedFileInodes(), inodes); + + ASSERT_EQ(inodes.size(), MISSING_DENTRIES); +} + +DB_TEST(testCheckForAndInsertOrphanedChunks) +{ + static_assert(db::FileInode::NTARGETS < 20, "enlarge 1-0-1 stripe pattern"); + + std::list inodes = { + { "0-0-1", "root", NumNodeID(), PathInfo(), 0, 0, 0, 0, 0, {1, 2}, + FsckStripePatternType_RAID0, 0, NumNodeID(), 0, 0, false, false, false, false }, + { "1-0-1", "root", NumNodeID(), PathInfo(), 0, 0, 0, 0, 0, {11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30}, FsckStripePatternType_BUDDYMIRROR, + 0, NumNodeID(), 0, 0, false, false, false, false }, + + { "2-0-1", "root", NumNodeID(), PathInfo(), 0, 0, 0, 0, 0, {1, 2}, + FsckStripePatternType_RAID0, 0, NumNodeID(), 0, 0, false, false, false, false }, + }; + + std::list goodChunks = { + { "0-0-1", 1, Path(), 0, 0, 0, 0, 0, 0, 0, 0 }, + { "0-0-1", 2, Path(), 0, 0, 0, 0, 0, 0, 0, 0 }, + + { "1-0-1", 0, Path(), 0, 0, 0, 0, 0, 0, 0, 11 }, + { "1-0-1", 0, Path(), 0, 0, 0, 0, 0, 0, 0, 12 }, + }; + + std::list badChunks = { + { "0-0-1", 3, Path(), 0, 0, 0, 0, 0, 0, 0, 0 }, + { "1-0-1", 0, Path(), 0, 0, 0, 0, 0, 0, 0, 10 }, + { "3-0-1", 1, Path(), 0, 0, 0, 0, 0, 0, 0, 0 }, + { "3-0-1", 2, Path(), 0, 0, 0, 0, 0, 0, 0, 0 }, + }; + + db->getFileInodesTable()->insert(inodes); + db->getChunksTable()->insert(goodChunks); + db->getChunksTable()->insert(badChunks); + + db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + {ModificationEvent_FILEMOVED, "2-0-1"}), + db->getModificationEventsTable()->newBulkHandle()); + + std::list result; + drainToList(db->findOrphanedChunks(), result); + + ASSERT_EQ(result, badChunks); +} + +DB_TEST(testCheckForAndInsertInodesWithoutContDir) +{ + unsigned NUM_INODES = 5; + unsigned MISSING_CONTDIRS = 2; + + // create dir inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_INODES, &dirInodes); + + // create cont dirs + FsckContDirList contDirs; + DatabaseTk::createDummyFsckContDirs(NUM_INODES-MISSING_CONTDIRS - 1, &contDirs); + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, dirInodes.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getDirInodesTable()->insert(dirInodes); + this->db->getContDirsTable()->insert(contDirs); + + FsckDirInodeList inodes; + drainToList(this->db->findInodesWithoutContDir(), inodes); + + ASSERT_EQ(inodes.size(), MISSING_CONTDIRS); +} + +DB_TEST(testCheckForAndInsertOrphanedContDirs) +{ + unsigned NUM_CONTDIRS = 5; + unsigned MISSING_INODES = 2; + + // create cont dirs + FsckContDirList contDirs; + DatabaseTk::createDummyFsckContDirs(NUM_CONTDIRS, &contDirs); + + // create dir inodes + FsckDirInodeList dirInodes; + DatabaseTk::createDummyFsckDirInodes(NUM_CONTDIRS-MISSING_INODES - 1, &dirInodes); + + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_DIRMOVED, contDirs.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getContDirsTable()->insert(contDirs); + this->db->getDirInodesTable()->insert(dirInodes); + + contDirs.clear(); + drainToList(this->db->findOrphanedContDirs(), contDirs); + + ASSERT_EQ(contDirs.size(), MISSING_INODES); +} + +DB_TEST(testCheckForAndInsertFileInodesWithWrongAttribs) +{ + unsigned NUM_INODES = 10; + unsigned NUM_CHUNKS_PER_INODE = 2; + + unsigned WRONG_DATA_INODES_SIZE = 2; + unsigned WRONG_DATA_INODES_HARDLINKS = 2; + unsigned WRONG_DATA_NO_CHUNKS = 1; + + unsigned WRONG_DATA_INODES = WRONG_DATA_INODES_SIZE + WRONG_DATA_INODES_HARDLINKS + + WRONG_DATA_NO_CHUNKS; + + int64_t EXPECTED_FILE_SIZE = 100000; // take a value that can be divided by NUM_CHUNKS_PER_INODE + unsigned EXPECTED_HARDLINKS = 1; + + // create file inodes + FsckFileInodeList inodesIn; + DatabaseTk::createDummyFsckFileInodes(NUM_INODES, &inodesIn); + + // create chunks + FsckChunkList chunks; + DatabaseTk::createDummyFsckChunks(NUM_INODES, NUM_CHUNKS_PER_INODE, &chunks); + + // create dentries + FsckDirEntryList dentries; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES, &dentries); + + // add another dentry with the ID of the last dentry to simulate a hardlink + FsckDirEntry dentry = dentries.back(); + dentry.name = "hardlink"; + + dentries.push_back(dentry); + + // make sure stat attrs do match + for ( FsckFileInodeListIter iter = inodesIn.begin(); iter != inodesIn.end(); iter++ ) + { + iter->fileSize = EXPECTED_FILE_SIZE; + iter->numHardLinks = EXPECTED_HARDLINKS; + + if (iter->getID() == dentry.getID()) + iter->numHardLinks = iter->getNumHardLinks()+1; + } + + for ( FsckChunkListIter iter = chunks.begin(); iter != chunks.end(); iter++ ) + { + FsckChunk chunk = *iter; + iter->fileSize = EXPECTED_FILE_SIZE/NUM_CHUNKS_PER_INODE; + } + + // we hold a list of modifed IDs; each time we modify attributes for a chunk we add its ID + // we do this to make sure, that if we modify x inodes, they are x different ones + StringList modifiedIDs; + + // now manipulate data of WRONG_DATA_INODES_SIZE chunks + FsckChunkListIter chunkIter = chunks.begin(); + // only manipulate chunks from one target + uint16_t targetID = chunkIter->getTargetID(); + unsigned i = 0; + while (i < WRONG_DATA_INODES_SIZE) + { + if (chunkIter == chunks.end()) + break; + + if (chunkIter->getTargetID() == targetID) + { + StringListIter tmpIter = std::find(modifiedIDs.begin(), modifiedIDs.end(), + chunkIter->getID()); + + if (tmpIter == modifiedIDs.end()) + { + modifiedIDs.push_back(chunkIter->getID()); + chunkIter->fileSize = chunkIter->fileSize*2; + i++; + } + } + + chunkIter++; + } + + // now manipulate data for WRONG_DATA_INODES_HARDLINKS inodes + FsckFileInodeListIter inodeIter = inodesIn.begin(); + + i = 0; + while ( i < WRONG_DATA_INODES_HARDLINKS ) + { + if ( inodeIter == inodesIn.end() ) + break; + + StringListIter tmpIter = std::find(modifiedIDs.begin(), modifiedIDs.end(), + inodeIter->getID()); + + if ( tmpIter == modifiedIDs.end() ) + { + modifiedIDs.push_back(inodeIter->getID()); + inodeIter->numHardLinks = i == 0 ? 0 : 100; + i++; + } + + inodeIter++; + } + + i = 0; + while(i < WRONG_DATA_NO_CHUNKS) + { + const std::string& id = inodeIter->getID(); + + for(FsckChunkListIter it = chunks.begin(); it != chunks.end(); ++it) + { + if(it->getID() == id) + { + FsckChunkListIter cur = it; + ++it; + + chunks.erase(cur); + --it; + } + } + + modifiedIDs.push_back(id); + inodeIter++; + i++; + } + + inodeIter->fileSize = inodeIter->getFileSize() + 1; + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, inodeIter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getFileInodesTable()->insert(inodesIn); + this->db->getChunksTable()->insert(chunks); + this->db->getDentryTable()->insert(dentries); + + ASSERT_EQ(countCursor(this->db->findWrongInodeFileAttribs()), WRONG_DATA_INODES); +} + +DB_TEST(testCheckForAndInsertDirInodesWithWrongAttribs) +{ + unsigned NUM_INODES = 10; + + unsigned WRONG_DATA_SIZE = 2; + unsigned WRONG_DATA_LINKS = 2; + + unsigned WRONG_DATA_INODES = WRONG_DATA_SIZE + WRONG_DATA_LINKS; + + int64_t EXPECTED_SIZE = 2; + unsigned EXPECTED_LINKS = 2; // no subdirs present, so 2 links expected + + // create dir inodes + FsckDirInodeList inodes; + DatabaseTk::createDummyFsckDirInodes(NUM_INODES, &inodes); + + // create dentries + FsckDirEntryList dentries; + DatabaseTk::createDummyFsckDirEntries(NUM_INODES*EXPECTED_LINKS, &dentries); + + // set parent info, so that it matches + FsckDirEntryListIter dentryIter = dentries.begin(); + FsckDirInodeListIter inodeIter = inodes.begin(); + + while ( inodeIter != inodes.end() ) + { + inodeIter->setSize(EXPECTED_SIZE); + inodeIter->setNumHardLinks(EXPECTED_LINKS); + + for ( int i = 0; i < EXPECTED_SIZE; i++ ) + { + dentryIter->setParentDirID(inodeIter->getID()); + dentryIter++; + } + inodeIter++; + } + + // we hold a list of modifed IDs; each time we modify attributes we add the ID of the inode + // we do this to make sure, that if we modify x inodes, they are x different ones + StringList modifiedIDs; + + // now manipulate the size + inodeIter = inodes.begin(); + + unsigned i = 0; + while ( i < WRONG_DATA_SIZE ) + { + if ( inodeIter == inodes.end() ) + break; + + StringListIter tmpIter = std::find(modifiedIDs.begin(), modifiedIDs.end(), + inodeIter->getID()); + + if ( tmpIter == modifiedIDs.end() ) + { + modifiedIDs.push_back(inodeIter->getID()); + // set new size + inodeIter->setSize(inodeIter->getSize()*10); + i++; + } + + inodeIter++; + } + + // now manipulate link count + i = 0; + while ( i < WRONG_DATA_LINKS ) + { + if ( inodeIter == inodes.end() ) + break; + + StringListIter tmpIter = std::find(modifiedIDs.begin(), modifiedIDs.end(), + inodeIter->getID()); + + if ( tmpIter == modifiedIDs.end() ) + { + modifiedIDs.push_back(inodeIter->getID()); + // set new numLinks + inodeIter->setNumHardLinks(inodeIter->getNumHardLinks()*10); + i++; + } + + inodeIter++; + } + + // last one is bad, but changed on-line + inodeIter->setSize(inodeIter->getSize() + 1); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, inodeIter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getDirInodesTable()->insert(inodes); + this->db->getDentryTable()->insert(dentries); + + ASSERT_EQ(countCursor(this->db->findWrongInodeDirAttribs()), WRONG_DATA_INODES); +} + +DB_TEST(testCheckForAndInsertFilesWithMissingStripeTargets) +{ + // we need a TargetMapper and a MirrorBuddyGroupMapper to test this + TargetMapper targetMapper; + MirrorBuddyGroupMapper buddyGroupMapper(&targetMapper); + FsckTargetIDList usedTargets, goodTargets; + FsckTargetIDList usedGroups, goodGroups; + + targetMapper.mapTarget(1, NumNodeID(1), StoragePoolStore::INVALID_POOL_ID); + targetMapper.mapTarget(2, NumNodeID(1), StoragePoolStore::INVALID_POOL_ID); + buddyGroupMapper.mapMirrorBuddyGroup(3, 1, 2, NumNodeID(0), false, NULL); + + goodTargets.push_back(FsckTargetID(1, FsckTargetIDType_TARGET) ); + goodTargets.push_back(FsckTargetID(2, FsckTargetIDType_TARGET) ); + goodGroups.push_back(FsckTargetID(3, FsckTargetIDType_BUDDYGROUP) ); + + usedTargets.insert(usedTargets.end(), goodTargets.begin(), goodTargets.end() ); + usedTargets.push_back(FsckTargetID(4, FsckTargetIDType_TARGET) ); + + usedGroups.insert(usedGroups.end(), goodGroups.begin(), goodGroups.end() ); + usedGroups.push_back(FsckTargetID(5, FsckTargetIDType_BUDDYGROUP) ); + + // insert the used targets + this->db->getUsedTargetIDsTable()->insert(usedTargets, + this->db->getUsedTargetIDsTable()->newBulkHandle() ); + this->db->getUsedTargetIDsTable()->insert(usedGroups, + this->db->getUsedTargetIDsTable()->newBulkHandle() ); + + // create some files {good, bad} x {plain, mirrored}, plus a bad one that has changed on-line + FsckFileInodeList inodes; + DatabaseTk::createDummyFsckFileInodes(5, &inodes); + + FsckTargetIDList* patterns[5] = { &goodTargets, &goodGroups, &usedTargets, &usedGroups, + &usedTargets }; + + FsckDirEntryList dentries; + + unsigned i = 0; + for (FsckFileInodeListIter file = inodes.begin(); file != inodes.end(); file++, i++) + { + FsckTargetIDList* pattern = patterns[i]; + UInt16Vector stripeTargets; + + for(FsckTargetIDListIter it = pattern->begin(); it != pattern->end(); ++it) + stripeTargets.push_back(it->getID() ); + + file->stripeTargets = stripeTargets; + file->stripePatternType = pattern->front().getTargetIDType() == FsckTargetIDType_BUDDYGROUP + ? FsckStripePatternType_BUDDYMIRROR + : FsckStripePatternType_RAID0; + + dentries.push_back( + FsckDirEntry(file->getID(), "file_" + file->getID(), file->getParentDirID(), + file->getSaveNodeID(), file->getSaveNodeID(), FsckDirEntryType_REGULARFILE, false, + file->getSaveNodeID(), 1, 1, false) ); + + if(i == 4) + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, file->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + } + + this->db->getFileInodesTable()->insert(inodes); + this->db->getDentryTable()->insert(dentries); + + FsckDirEntryList badFiles; + drainToList(this->db->findFilesWithMissingStripeTargets(&targetMapper, + &buddyGroupMapper), badFiles); + + ASSERT_EQ(badFiles.size(), 3u); +} + +DB_TEST(testCheckForAndInsertChunksWithWrongPermissions) +{ + unsigned NUMBER_CHUNKS = 500; + unsigned DIFF_UID = 50; + unsigned DIFF_GID = 50; + unsigned DIFF_TOTAL = DIFF_UID + DIFF_GID; + + FsckFileInodeList fileInodes; + DatabaseTk::createDummyFsckFileInodes(NUMBER_CHUNKS, &fileInodes); + + FsckChunkList chunks; + DatabaseTk::createDummyFsckChunks(NUMBER_CHUNKS, &chunks); + + FsckFileInodeListIter iter = fileInodes.begin(); + + for (unsigned i = 0; i < DIFF_UID; i++) + { + iter->userID = 1000; + iter++; + } + + for (unsigned i = 0; i < DIFF_GID; i++) + { + iter->groupID = 1000; + iter++; + } + + iter->userID = 1; + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEMOVED, iter->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + ++iter; + + //rest is owned by root + while (iter != fileInodes.end()) + { + iter->userID = 0; + iter->groupID = 0; + iter++; + } + + this->db->getFileInodesTable()->insert(fileInodes); + this->db->getChunksTable()->insert(chunks); + + ASSERT_EQ(countCursor(this->db->findChunksWithWrongPermissions()), DIFF_TOTAL); +} + +DB_TEST(testCheckForAndInsertChunksInWrongPath) +{ + unsigned NUM_TARGETS = 2; + unsigned NUM_CHUNKS_PER_TARGET = 5; + unsigned NUM_WRONG_PATHS = 3; + uint16_t nodeID = 1; + + FsckFileInodeList fileInodeList; + FsckChunkList chunkList; + + for (unsigned i = 0; i < NUM_CHUNKS_PER_TARGET; i++) + { + std::string fileID = StorageTk::generateFileID(NumNodeID(nodeID)); + std::string parentDirID = StorageTk::generateFileID(NumNodeID(nodeID)); + + // create a file inode for that + unsigned origParentUID = i; + PathInfo pathInfo(origParentUID, parentDirID, PATHINFO_FEATURE_ORIG); + + FsckFileInode fileInode = DatabaseTk::createDummyFsckFileInode(); + + fileInode.id = fileID; + fileInode.parentDirID = parentDirID; + fileInode.parentNodeID = NumNodeID(nodeID); + fileInode.pathInfo = pathInfo; + UInt16Vector stripeTargets; + for (unsigned j = 1;j <= NUM_TARGETS; j++) + stripeTargets.push_back(j); + fileInode.stripeTargets = stripeTargets; + + fileInodeList.push_back(fileInode); + + // create chunks + for (unsigned j = 1;j <= NUM_TARGETS; j++) + { + FsckChunk chunk = DatabaseTk::createDummyFsckChunk(); + chunk.id = fileID; + chunk.targetID = j; + Path chunkDirPath; + std::string chunkFilePath; // will be ignored + StorageTk::getChunkDirChunkFilePath(&pathInfo, fileID, true, chunkDirPath, chunkFilePath); + + chunk.savedPath = Path(chunkDirPath.str()); + + chunkList.push_back(chunk); + } + } + + // take the first NUM_WRONG_PATHS in the chunkList and set them to something stupid + FsckChunkListIter iter = chunkList.begin(); + for (unsigned i=0; isavedPath = Path("/this/path/is/wrong"); + iter++; + } + + chunkList.rbegin()->savedPath = Path("/wrong"); + this->db->getModificationEventsTable()->insert( + FsckModificationEventList(1, + FsckModificationEvent(ModificationEvent_FILEREMOVED, chunkList.rbegin()->getID() ) ), + this->db->getModificationEventsTable()->newBulkHandle() ); + + this->db->getFileInodesTable()->insert(fileInodeList); + this->db->getChunksTable()->insert(chunkList); + + ASSERT_EQ(countCursor(this->db->findChunksInWrongPath()), NUM_WRONG_PATHS); +} + +DB_TEST(testDeleteDentries) +{ + unsigned NUM_ELEMENTS = 10; + unsigned NUM_DELETES = 4; + + // create some dentries + FsckDirEntryList dentries; + DatabaseTk::createDummyFsckDirEntries(NUM_ELEMENTS, &dentries); + + // insert them into the DB + this->db->getDentryTable()->insert(dentries); + + // take the first NUM_DELETES and delete them in DB + FsckDirEntryList dentriesDelete; + FsckDirEntryListIter dentriesIter = dentries.begin(); + + for (unsigned i = 0; i < NUM_DELETES; i++) + { + if (dentriesIter != dentries.end()) + { + dentriesDelete.push_back(*dentriesIter); + dentriesIter++; + } + } + + this->db->getDentryTable()->remove(dentriesDelete); + + FsckDirEntryList table = drain(this->db->getDentryTable()->get() ); + + ASSERT_EQ(table.size(), NUM_ELEMENTS - NUM_DELETES); +} + +DB_TEST(testDeleteChunks) +{ + unsigned NUM_ELEMENTS = 10; + unsigned NUM_DELETES = 4; + + // create some dentries + FsckChunkList chunks; + DatabaseTk::createDummyFsckChunks(NUM_ELEMENTS, &chunks); + + //insert them into the DB + this->db->getChunksTable()->insert(chunks); + + // take the first NUM_DELETES and delete them in DB + FsckChunkListIter chunksIter = chunks.begin(); + + for (unsigned i = 0; i < NUM_DELETES; i++, chunksIter++) + this->db->getChunksTable()->remove(db::EntryID::fromStr(chunksIter->getID() ) ); + + size_t count = countCursor(this->db->getChunksTable()->get() ); + + ASSERT_EQ(count, NUM_ELEMENTS - NUM_DELETES); +} + +DB_TEST(testDeleteFsIDs) +{ + unsigned NUM_ELEMENTS = 10; + unsigned NUM_DELETES = 4; + + // create some fsIDs + FsckFsIDList fsIDs; + DatabaseTk::createDummyFsckFsIDs(NUM_ELEMENTS, &fsIDs); + + //insert them into the DB + this->db->getFsIDsTable()->insert(fsIDs); + + // take the first NUM_DELETES and delete them in DB + FsckFsIDList elemsDelete; + FsckFsIDListIter iter = fsIDs.begin(); + + for (unsigned i = 0; i < NUM_DELETES; i++) + { + if (iter != fsIDs.end()) + { + elemsDelete.push_back(*iter); + iter++; + } + } + + this->db->getFsIDsTable()->remove(elemsDelete); + + FsckFsIDList table = drain(this->db->getFsIDsTable()->get() ); + + ASSERT_EQ(table.size(), NUM_ELEMENTS - NUM_DELETES); +} + +DB_TEST(testGetFullPath) +{ + // first test => PASS OK + FsckDirEntryList dirEntries; + + const char* const id_A = "1-1-1"; + const char* const id_B = "2-1-1"; + const char* const id_C = "3-1-1"; + const char* const id_D = "4-1-1"; + const char* const fileID = "5-1-1"; + + FsckDirEntry fileDirEntry = DatabaseTk::createDummyFsckDirEntry(); + fileDirEntry.setName("file"); + fileDirEntry.setID(fileID); + fileDirEntry.setParentDirID(META_ROOTDIR_ID_STR); + dirEntries.push_back(fileDirEntry); + + this->db->getDentryTable()->insert(dirEntries); + + ASSERT_EQ( + this->db->getDentryTable()->getPathOf(db::EntryID::fromStr(fileDirEntry.getID())), + "/file"); + + // second test => PASS OK + this->db->getDentryTable()->clear(); + + dirEntries.clear(); + + fileDirEntry.setName("file"); + fileDirEntry.setID(fileID); + fileDirEntry.setParentDirID(id_D); + dirEntries.push_back(fileDirEntry); + + FsckDirEntry dirEntry = DatabaseTk::createDummyFsckDirEntry(FsckDirEntryType_DIRECTORY); + + dirEntry.setName("D"); + dirEntry.setID(id_D); + dirEntry.setParentDirID(id_C); + dirEntries.push_back(dirEntry); + + dirEntry.setName("C"); + dirEntry.setID(id_C); + dirEntry.setParentDirID(id_B); + dirEntries.push_back(dirEntry); + + dirEntry.setName("B"); + dirEntry.setID(id_B); + dirEntry.setParentDirID(id_A); + dirEntries.push_back(dirEntry); + + dirEntry.setName("A"); + dirEntry.setID(id_A); + dirEntry.setParentDirID(META_ROOTDIR_ID_STR); + dirEntries.push_back(dirEntry); + + this->db->getDentryTable()->insert(dirEntries); + + ASSERT_EQ( + this->db->getDentryTable()->getPathOf(db::EntryID::fromStr(fileDirEntry.getID())), + "/A/B/C/D/file"); + + //==========// + // third test => create a path which could not be fully resolved + //=========// + this->db->getDentryTable()->clear(); + + dirEntries.clear(); + + dirEntries.push_back(fileDirEntry); + + dirEntry.setName("D"); + dirEntry.setID(id_D); + dirEntry.setParentDirID(id_C); + dirEntries.push_back(dirEntry); + + dirEntry.setName("C"); + dirEntry.setID(id_C); + dirEntry.setParentDirID(id_B); + dirEntries.push_back(dirEntry); + + dirEntry.setName("A"); + dirEntry.setID(id_A); + dirEntry.setParentDirID(META_ROOTDIR_ID_STR); + dirEntries.push_back(dirEntry); + this->db->getDentryTable()->insert(dirEntries); + + ASSERT_EQ( + this->db->getDentryTable()->getPathOf(db::EntryID::fromStr(fileDirEntry.getID())), + "[]/C/D/file"); + + //==========// + // fourth test => create a loop + //=========// + this->db->getDentryTable()->clear(); + + dirEntries.clear(); + + dirEntries.push_back(fileDirEntry); + + dirEntry.setName("D"); + dirEntry.setID(id_D); + dirEntry.setParentDirID(id_C); + dirEntries.push_back(dirEntry); + + dirEntry.setName("C"); + dirEntry.setID(id_C); + dirEntry.setParentDirID(id_B); + dirEntries.push_back(dirEntry); + + dirEntry.setName("B"); + dirEntry.setID(id_B); + dirEntry.setParentDirID(id_A); + dirEntries.push_back(dirEntry); + + dirEntry.setName("A"); + dirEntry.setID(id_A); + dirEntry.setParentDirID(id_D); + dirEntries.push_back(dirEntry); + + this->db->getDentryTable()->insert(dirEntries); + + ASSERT_NE( + this->db->getDentryTable()->getPathOf(db::EntryID::fromStr(fileDirEntry.getID())), + "[]"); +} + diff --git a/fsck/tests/TestDatabase.h b/fsck/tests/TestDatabase.h new file mode 100644 index 0000000..4c71b15 --- /dev/null +++ b/fsck/tests/TestDatabase.h @@ -0,0 +1,115 @@ +#ifndef TESTDATABASE_H_ +#define TESTDATABASE_H_ + +#include +#include + +#include +#include + +class TestDatabase: public ::testing::Test +{ + public: + TestDatabase(); + virtual ~TestDatabase(); + + void SetUp(); + void TearDown(); + + protected: + std::string databasePath; + boost::scoped_ptr db; + + void testInsertAndReadSingleDentry(); + void testInsertAndReadDentries(); + + void testUpdateDentries(); + void testUpdateDirInodes(); + void testUpdateFileInodes(); + void testUpdateChunks(); + + void testInsertAndReadSingleFileInode(); + + void testInsertAndReadSingleDirInode(); + void testInsertAndReadDirInodes(); + + void testInsertAndReadSingleChunk(); + void testInsertAndReadChunks(); + + void testInsertAndReadSingleContDir(); + void testInsertAndReadContDirs(); + + void testInsertAndReadSingleFsID(); + void testInsertAndReadFsIDs(); + + void testFindDuplicateInodeIDs(); + void testFindDuplicateChunks(); + void testFindDuplicateContDirs(); + void testFindMismirroredDentries(); + void testFindMismirroredDirectories(); + + void testCheckForAndInsertDanglingDirEntries(); + void testCheckForAndInsertInodesWithWrongOwner(); + void testCheckForAndInsertDirEntriesWithWrongOwner(); + void testCheckForAndInsertMissingDentryByIDFile(); + void testCheckForAndInsertOrphanedDentryByIDFiles(); + void testCheckForAndInsertOrphanedDirInodes(); + void testCheckForAndInsertOrphanedFileInodes(); + void testCheckForAndInsertOrphanedChunks(); + void testCheckForAndInsertInodesWithoutContDir(); + void testCheckForAndInsertOrphanedContDirs(); + void testCheckForAndInsertFileInodesWithWrongAttribs(); + void testCheckForAndInsertDirInodesWithWrongAttribs(); + void testCheckForAndInsertFilesWithMissingStripeTargets(); + void testCheckForAndInsertChunksWithWrongPermissions(); + void testCheckForAndInsertChunksInWrongPath(); + + void testDeleteDentries(); + void testDeleteChunks(); + void testDeleteFsIDs(); + + void testGetRowCount(); + + void testGetFullPath(); + + template + static bool setRepairActions(Table* table, std::list& items, FsckRepairAction action) + { + for (typename std::list::iterator it = items.begin(), end = items.end(); + it != end; + ++it) + { + if (!table->setRepairAction(*it, action) ) + return false; + } + + return true; + } + + template + static void drainToList(Source source, std::list& list) + { + list.clear(); + while(source.step() ) + list.push_back(*source.get() ); + } + + template + static std::list drain(Source source) + { + std::list list; + drainToList(source, list); + return list; + } + + template + static size_t countCursor(Source source) + { + size_t result = 0; + while(source.step() ) + result++; + return result; + } +}; + +#endif /* TESTDATABASE_H_ */ diff --git a/fsck/tests/TestFsckTk.cpp b/fsck/tests/TestFsckTk.cpp new file mode 100644 index 0000000..35b3fa1 --- /dev/null +++ b/fsck/tests/TestFsckTk.cpp @@ -0,0 +1,35 @@ +#include +#include +#include +#include + +#include + +TEST(FsckTk, dirEntryTypeConversion) +{ + FsckDirEntryType entryTypeOut; + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_INVALID); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_INVALID); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_REGULARFILE); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_REGULARFILE); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_SYMLINK); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_SYMLINK); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_DIRECTORY); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_DIRECTORY); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_BLOCKDEV); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_SPECIAL); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_CHARDEV); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_SPECIAL); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_FIFO); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_SPECIAL); + + entryTypeOut = FsckTk::DirEntryTypeToFsckDirEntryType(DirEntryType_SOCKET); + ASSERT_EQ(entryTypeOut, FsckDirEntryType_SPECIAL); +} diff --git a/fsck/tests/TestSerialization.cpp b/fsck/tests/TestSerialization.cpp new file mode 100644 index 0000000..23b4109 --- /dev/null +++ b/fsck/tests/TestSerialization.cpp @@ -0,0 +1,77 @@ +#include +#include +#include +#include +#include + +#include + +template +static void testObjectRoundTrip(Obj& data) +{ + Serializer ser; + ser % data; + + std::vector buffer(ser.size()); + + ser = Serializer(&buffer[0], buffer.size()); + ser % data; + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), buffer.size()); + + Deserializer des(&buffer[0], buffer.size()); + + Obj read; + + des % read; + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), buffer.size()); + + ser = Serializer(); + ser % read; + ASSERT_EQ(ser.size(), buffer.size()); + + std::vector rtBuffer(buffer.size()); + + ser = Serializer(&rtBuffer[0], rtBuffer.size()); + ser % read; + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), rtBuffer.size()); + ASSERT_EQ(buffer, rtBuffer); +} + +TEST(Serialization, fsckDirEntrySerialization) +{ + FsckDirEntry dirEntryIn = DatabaseTk::createDummyFsckDirEntry(); + testObjectRoundTrip(dirEntryIn); +} + +TEST(Serialization, fsckDirInodeSerialization) +{ + FsckDirInode dirInodeIn = DatabaseTk::createDummyFsckDirInode(); + testObjectRoundTrip(dirInodeIn); +} + +TEST(Serialization, fsckFileInodeSerialization) +{ + FsckFileInode fileInodeIn = DatabaseTk::createDummyFsckFileInode(); + testObjectRoundTrip(fileInodeIn); +} + +TEST(Serialization, fsckChunkSerialization) +{ + FsckChunk chunkIn = DatabaseTk::createDummyFsckChunk(); + testObjectRoundTrip(chunkIn); +} + +TEST(Serialization, fsckContDirSerialization) +{ + FsckContDir contDirIn = DatabaseTk::createDummyFsckContDir(); + testObjectRoundTrip(contDirIn); +} + +TEST(Serialization, fsckFsIDSerialization) +{ + FsckFsID fsIDIn = DatabaseTk::createDummyFsckFsID(); + testObjectRoundTrip(fsIDIn); +} diff --git a/fsck/tests/TestSet.cpp b/fsck/tests/TestSet.cpp new file mode 100644 index 0000000..48bda98 --- /dev/null +++ b/fsck/tests/TestSet.cpp @@ -0,0 +1,177 @@ +#include +#include + +#include "FlatTest.h" + +class TestSet : public FlatTest { +}; + +TEST_F(TestSet, open) +{ + // break config + int cfgFile = ::creat((fileName + ".t").c_str(), 0660); + ASSERT_GE(cfgFile, 0); + ASSERT_EQ(::truncate( (this->fileName + ".t").c_str(), 5), 0); + do { + try { + Set set(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("cannot read set"), std::string::npos); + ASSERT_NE(std::string(e.what()).find("with version 0"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::write(cfgFile, "v1\n1\n", 5), 5); + do { + try { + Set set(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("bad set description"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::close(cfgFile), 0); + ASSERT_EQ(::unlink((this->fileName + ".t").c_str()), 0); + + // reopen with saved config + { + Set set(this->fileName); + } + { + Set set(this->fileName); + } + + ASSERT_EQ(::truncate((this->fileName + ".t").c_str(), 5000), 0); + do { + try { + Set set(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("bad set description"), std::string::npos); + break; + } + + FAIL(); + } while (false); +} + +TEST_F(TestSet, drop) +{ + { + Set set(this->fileName); + set.newFragment(); + } + { + Set set(this->fileName); + set.drop(); + } + + ASSERT_EQ(::rmdir(this->dirName.c_str()), 0); +} + +TEST_F(TestSet, clear) +{ + { + Set set(this->fileName); + set.newFragment(); + } + { + Set set(this->fileName); + set.clear(); + } + + ASSERT_EQ(::unlink( (this->fileName + ".t").c_str()), 0); + ASSERT_EQ(::rmdir(this->dirName.c_str()), 0); +} + +TEST_F(TestSet, merge) +{ + Data d = {0, {}}; + + { + Set set1(this->fileName + "1"); + set1.newFragment()->append(d); + + Set set2(this->fileName + "2"); + set2.newFragment()->append(d); + } + + { + Set set1(this->fileName + "1"); + Set set2(this->fileName + "2"); + set2.mergeInto(set1); + } + + { + Set set1(this->fileName + "1"); + set1.drop(); + } + + ASSERT_EQ(::unlink((this->fileName + "2.t").c_str()), 0); + ASSERT_EQ(::rmdir(this->dirName.c_str()), 0); +} + +TEST_F(TestSet, dataOps) +{ + static const unsigned FRAG_COUNT = 2 * 3 * 4; + + Set set(this->fileName); + + ASSERT_FALSE(set.cursor().step()); + + { + for(unsigned i = 0; i < FRAG_COUNT; i++) + { + Data d = { i, {} }; + SetFragment* frag = set.newFragment(); + + frag->append(d); + frag->append(d); + + ASSERT_EQ(set.size(), 2 * (i + 1)); + } + } + + { + Set::Cursor cursor = set.cursor(); + + for(unsigned i = 0; i < FRAG_COUNT; i++) { + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, i); + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, i); + } + + ASSERT_FALSE(cursor.step()); + } + + set.makeUnique(); + ASSERT_EQ(set.size(), FRAG_COUNT); + + { + Set::Cursor cursor = set.cursor(); + + for(unsigned i = 0; i < FRAG_COUNT; i++) { + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, i); + } + + ASSERT_FALSE(cursor.step()); + } + + ASSERT_TRUE(set.getByKey(FRAG_COUNT / 2).first); + ASSERT_EQ(set.getByKey(FRAG_COUNT / 2).second.id, FRAG_COUNT / 2); + + ASSERT_FALSE(set.getByKey(FRAG_COUNT * 2).first); + + struct ops + { + static int64_t key(uint64_t key) { return key + 6; } + }; + + ASSERT_TRUE(set.getByKeyProjection(7, ops::key).first); + ASSERT_EQ(set.getByKeyProjection(7, ops::key).second.id, 1u); +} diff --git a/fsck/tests/TestSetFragment.cpp b/fsck/tests/TestSetFragment.cpp new file mode 100644 index 0000000..a9f623e --- /dev/null +++ b/fsck/tests/TestSetFragment.cpp @@ -0,0 +1,203 @@ +#include +#include + +#include "FlatTest.h" + +class TestSetFragment : public FlatTest { +}; + +TEST_F(TestSetFragment, open) +{ + if(::getuid() == 0) + { + std::cerr << "Test run as root, skipping DAC tests\n"; + } + else + { + // bad directory permissions (can't open or create) + ASSERT_EQ(::chmod(this->dirName.c_str(), 0), 0); + do { + try { + SetFragment frag(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("could not open fragment file"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::chmod(this->dirName.c_str(), 0770), 0); + } + + // not a regular file + ASSERT_EQ(::mkfifo(this->fileName.c_str(), 0660), 0); + do { + try { + SetFragment frag(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("error while opening fragment file"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::unlink(this->fileName.c_str()), 0); + + // corrupted config area + ASSERT_EQ(::close(::creat(this->fileName.c_str(), 0660)), 0); + ASSERT_EQ(::truncate(this->fileName.c_str(), SetFragment::CONFIG_AREA_SIZE - 1), 0); + do { + try { + SetFragment frag(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("error while opening fragment file"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::unlink(this->fileName.c_str()), 0); + + // corrupted data + ASSERT_EQ(::close(::creat(this->fileName.c_str(), 0660)), 0); + ASSERT_EQ(::truncate(this->fileName.c_str(), + SetFragment::CONFIG_AREA_SIZE + sizeof(Data) - 1), 0); + do { + try { + SetFragment frag(this->fileName); + } catch (const std::runtime_error& e) { + ASSERT_NE(std::string(e.what()).find("error while opening fragment file"), std::string::npos); + break; + } + + FAIL(); + } while (false); + ASSERT_EQ(::unlink(this->fileName.c_str()), 0); + + // should work + SetFragment frag(this->fileName); +} + +TEST_F(TestSetFragment, drop) +{ + struct stat stat; + + { + SetFragment frag(this->fileName); + } + + ASSERT_EQ(::stat(this->fileName.c_str(), &stat), 0); + + { + SetFragment frag(this->fileName); + frag.drop(); + } + + ASSERT_EQ(::stat(this->fileName.c_str(), &stat), -1); + ASSERT_EQ(errno, ENOENT); +} + +TEST_F(TestSetFragment, flush) +{ + SetFragment frag(this->fileName); + Data d = {0, {}}; + struct stat stat; + + frag.append(d); + + ASSERT_EQ(::stat(this->fileName.c_str(), &stat), 0); + ASSERT_EQ(stat.st_size, 0); + + frag.flush(); + + ASSERT_EQ(::stat(this->fileName.c_str(), &stat), 0); + ASSERT_EQ(size_t(stat.st_size), frag.CONFIG_AREA_SIZE + sizeof(d)); +} + +TEST_F(TestSetFragment, appendAndAccess) +{ + struct ops + { + static void fill(SetFragment& frag, unsigned limit) + { + Data d = {0, {}}; + + for (unsigned i = 0; i < limit; i++) { + d.id = i; + frag.append(d); + } + } + + static void check(SetFragment& frag, unsigned limit) + { + ASSERT_EQ(frag.size(), limit); + + for (unsigned i = 0; i < limit; i++) { + ASSERT_EQ(frag[i].id, i); + ASSERT_EQ( + size_t(std::count( + frag[i].dummy, + frag[i].dummy + sizeof(frag[i].dummy), + 0)), + sizeof(frag[i].dummy)); + } + } + }; + + static const unsigned LIMIT = 2 * SetFragment::BUFFER_SIZE / sizeof(Data); + + { + SetFragment frag(this->fileName); + + ops::fill(frag, LIMIT); + ops::check(frag, LIMIT); + } + + // read everything again, with a new instance + { + SetFragment frag(this->fileName); + + ops::check(frag, LIMIT); + } +} + +TEST_F(TestSetFragment, sortAndGet) +{ + SetFragment frag(this->fileName); + + // generate a pseudo-random sequence as {0, 1, ...} * 4001 mod 4003 (both prime) + for (unsigned i = 0; i < 4003; i++) + { + Data d = { i * 4001 % 4003, {} }; + frag.append(d); + } + + frag.sort(); + + for (unsigned i = 0; i < 4003; i++) + ASSERT_EQ(frag[i].id, i); + + ASSERT_TRUE(frag.getByKey(17).first); + ASSERT_EQ(frag.getByKey(17).second.id, 17u); + + ASSERT_TRUE(!frag.getByKey(170000).first); + + struct ops + { + static int64_t key(uint64_t key) { return key + 6; } + }; + + ASSERT_TRUE(frag.getByKeyProjection(23, ops::key).first); + ASSERT_EQ(frag.getByKeyProjection(23, ops::key).second.id, 17u); +} + +TEST_F(TestSetFragment, rename) +{ + SetFragment frag(this->fileName); + + frag.rename(this->fileName + "1"); + + ASSERT_EQ(::close(::open( (this->fileName + "1").c_str(), O_RDWR)), 0); + + frag.rename(this->fileName); +} diff --git a/fsck/tests/TestTable.cpp b/fsck/tests/TestTable.cpp new file mode 100644 index 0000000..b480f11 --- /dev/null +++ b/fsck/tests/TestTable.cpp @@ -0,0 +1,182 @@ +#include "TestTable.h" + +void TestTable::SetUp() +{ + FlatTest::SetUp(); + this->table.reset(new Table(this->fileName, 4096) ); +} + +void TestTable::TearDown() +{ + if(this->table) + this->table->drop(); + + FlatTest::TearDown(); +} + +TEST_F(TestTable, dataHandling) +{ + Data d = {0, {}}; + + // first, only ordered inserts + d.id = 0; + this->table->insert(d); + d.id = 1; + this->table->insert(d); + + // complete the base set + this->table->commitChanges(); + + // insert "in order" + d.id = 2; + this->table->insert(d); + + // unordered delete should fail + ASSERT_THROW(this->table->remove(1), std::runtime_error); + + // ordered delete should work + this->table->remove(3); + + // reinsert of same id should work + d.id = 3; + this->table->insert(d); + + // unordered insert should now fail + ASSERT_THROW(this->table->insert(d), std::runtime_error); + + // reset changes + this->table->commitChanges(); + + // unordered insert should work + d.id = 0; + this->table->insert(d); + d.id = 4; + this->table->insert(d); + d.id = 1; + this->table->insert(d); + + // delete should fail + ASSERT_THROW(this->table->remove(1), std::runtime_error); + + // reset changes + this->table->commitChanges(); + + // ordered delete should work + this->table->remove(1); + this->table->remove(2); + this->table->remove(3); + + // unordered insert should fail + d.id = 2; + ASSERT_THROW(this->table->insert(d), std::runtime_error); + + // unordered delete should work + this->table->remove(3); + + // insert should fail after unordered delete + d.id = 10; + ASSERT_THROW(this->table->insert(d), std::runtime_error); + + // uncomitted changes should yield the sequence [0, 0, 1, 1, 2, 3, 4] + { + Table::QueryType cursor = this->table->cursor(); + + unsigned expect[7] = { 0, 0, 1, 1, 2, 3, 4 }; + for(unsigned i = 0; i < 7; i++) + { + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, expect[i]); + } + + ASSERT_FALSE(cursor.step()); + } + + // commited should contain the sequence [0, 0, 4] + this->table->commitChanges(); + { + Table::QueryType cursor = this->table->cursor(); + + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 0u); + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 0u); + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 4u); + ASSERT_FALSE(cursor.step()); + } + + ASSERT_TRUE(this->table->getByKey(4).first); + ASSERT_EQ(this->table->getByKey(4).second.id, 4u); + + ASSERT_FALSE(this->table->getByKey(3).first); + + struct ops + { + static int64_t key(uint64_t key) { return key + 6; } + }; + + ASSERT_TRUE(this->table->getByKeyProjection(10, ops::key).first); + ASSERT_EQ(this->table->getByKeyProjection(10, ops::key).second.id, 4u); +} + +TEST_F(TestTable, bulkInsert) +{ + { + boost::shared_ptr > buf1, buf2; + + buf1 = this->table->bulkInsert(); + buf2 = this->table->bulkInsert(); + + Data d = { 0, {} }; + + d.id = 1; + buf1->append(d); + + d.id = 2; + buf2->append(d); + } + + // all insert must have gone to base, queryable immediatly + { + Table::QueryType cursor = this->table->cursor(); + + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 1u); + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 2u); + ASSERT_FALSE(cursor.step()); + } + + // another bulk insert is fine, no modifications happened yet + { + boost::shared_ptr > buf; + + buf = this->table->bulkInsert(); + + Data d = { 3, {} }; + buf->append(d); + + // can't do normal insert or remove during bulk insert + ASSERT_THROW(this->table->insert(d), std::runtime_error); + ASSERT_THROW(this->table->remove(4), std::runtime_error); + } + + // all insert must have gone to base, queryable immediatly + { + Table::QueryType cursor = this->table->cursor(); + + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 1u); + ASSERT_TRUE(cursor.step()); + ASSERT_EQ(cursor.get()->id, 2u); + ASSERT_TRUE(cursor.step() ); + ASSERT_EQ(cursor.get()->id, 3u); + ASSERT_FALSE(cursor.step()); + } + + this->table->remove(1); + ASSERT_THROW(this->table->bulkInsert(), std::runtime_error); + + this->table->commitChanges(); + ASSERT_THROW(this->table->bulkInsert(), std::runtime_error); +} diff --git a/fsck/tests/TestTable.h b/fsck/tests/TestTable.h new file mode 100644 index 0000000..2caa4b7 --- /dev/null +++ b/fsck/tests/TestTable.h @@ -0,0 +1,20 @@ +#ifndef TESTTABLE_H_ +#define TESTTABLE_H_ + +#include +#include "FlatTest.h" + +#include +#include + +class TestTable : public FlatTest +{ + public: + void SetUp(); + void TearDown(); + + protected: + boost::scoped_ptr > table; +}; + +#endif diff --git a/meta/CMakeLists.txt b/meta/CMakeLists.txt new file mode 100644 index 0000000..8376668 --- /dev/null +++ b/meta/CMakeLists.txt @@ -0,0 +1,369 @@ +include_directories( + source +) + +add_library( + meta STATIC + ./source/toolkit/StorageTkEx.cpp + ./source/toolkit/BuddyCommTk.cpp + ./source/toolkit/BuddyCommTk.h + ./source/toolkit/XAttrTk.h + ./source/toolkit/XAttrTk.cpp + ./source/toolkit/StorageTkEx.h + ./source/net/message/mon/RequestMetaDataMsgEx.h + ./source/net/message/mon/RequestMetaDataMsgEx.cpp + ./source/net/message/control/AckMsgEx.h + ./source/net/message/control/SetChannelDirectMsgEx.cpp + ./source/net/message/control/AckMsgEx.cpp + ./source/net/message/control/SetChannelDirectMsgEx.h + ./source/net/message/NetMessageFactory.h + ./source/net/message/session/opening/OpenFileMsgEx.cpp + ./source/net/message/session/opening/CloseFileMsgEx.h + ./source/net/message/session/opening/OpenFileMsgEx.h + ./source/net/message/session/opening/CloseFileMsgEx.cpp + ./source/net/message/session/BumpFileVersionMsgEx.h + ./source/net/message/session/GetFileVersionMsgEx.h + ./source/net/message/session/locking/FLockEntryMsgEx.h + ./source/net/message/session/locking/FLockRangeMsgEx.h + ./source/net/message/session/locking/FLockRangeMsgEx.cpp + ./source/net/message/session/locking/FLockAppendMsgEx.h + ./source/net/message/session/locking/FLockEntryMsgEx.cpp + ./source/net/message/session/locking/FLockAppendMsgEx.cpp + ./source/net/message/session/GetFileVersionMsgEx.cpp + ./source/net/message/session/BumpFileVersionMsgEx.cpp + ./source/net/message/session/AckNotifyMsgEx.h + ./source/net/message/NetMessageFactory.cpp + ./source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp + ./source/net/message/nodes/GetNodesMsgEx.h + ./source/net/message/nodes/GetNodeCapacityPoolsMsgEx.cpp + ./source/net/message/nodes/RefreshCapacityPoolsMsgEx.cpp + ./source/net/message/nodes/GetTargetMappingsMsgEx.h + ./source/net/message/nodes/GetClientStatsMsgEx.h + ./source/net/message/nodes/MapTargetsMsgEx.cpp + ./source/net/message/nodes/RefreshTargetStatesMsgEx.cpp + ./source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h + ./source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp + ./source/net/message/nodes/HeartbeatMsgEx.cpp + ./source/net/message/nodes/HeartbeatRequestMsgEx.h + ./source/net/message/nodes/GetClientStatsMsgEx.cpp + ./source/net/message/nodes/GetTargetMappingsMsgEx.cpp + ./source/net/message/nodes/PublishCapacitiesMsgEx.h + ./source/net/message/nodes/GenericDebugMsgEx.cpp + ./source/net/message/nodes/GetNodesMsgEx.cpp + ./source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h + ./source/net/message/nodes/PublishCapacitiesMsgEx.cpp + ./source/net/message/nodes/RemoveNodeMsgEx.cpp + ./source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h + ./source/net/message/nodes/HeartbeatRequestMsgEx.cpp + ./source/net/message/nodes/MapTargetsMsgEx.h + ./source/net/message/nodes/HeartbeatMsgEx.h + ./source/net/message/nodes/GenericDebugMsgEx.h + ./source/net/message/nodes/RemoveNodeMsgEx.h + ./source/net/message/nodes/GetNodeCapacityPoolsMsgEx.h + ./source/net/message/nodes/RefreshTargetStatesMsgEx.h + ./source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp + ./source/net/message/nodes/RefreshCapacityPoolsMsgEx.h + ./source/net/message/MirroredMessage.h + ./source/net/message/storage/moving/RenameV2MsgEx.cpp + ./source/net/message/storage/moving/RenameV2MsgEx.h + ./source/net/message/storage/moving/MovingFileInsertMsgEx.cpp + ./source/net/message/storage/moving/MovingDirInsertMsgEx.h + ./source/net/message/storage/moving/MovingFileInsertMsgEx.h + ./source/net/message/storage/moving/MovingDirInsertMsgEx.cpp + ./source/net/message/storage/GetHighResStatsMsgEx.h + ./source/net/message/storage/creating/MkFileWithPatternMsgEx.cpp + ./source/net/message/storage/creating/MkLocalDirMsgEx.cpp + ./source/net/message/storage/creating/MkFileWithPatternMsgEx.h + ./source/net/message/storage/creating/RmDirEntryMsgEx.h + ./source/net/message/storage/creating/UnlinkFileMsgEx.h + ./source/net/message/storage/creating/MkFileMsgEx.cpp + ./source/net/message/storage/creating/RmDirMsgEx.cpp + ./source/net/message/storage/creating/MkLocalDirMsgEx.h + ./source/net/message/storage/creating/MkFileMsgEx.h + ./source/net/message/storage/creating/MkDirMsgEx.cpp + ./source/net/message/storage/creating/UnlinkFileMsgEx.cpp + ./source/net/message/storage/creating/MkDirMsgEx.h + ./source/net/message/storage/creating/HardlinkMsgEx.h + ./source/net/message/storage/creating/HardlinkMsgEx.cpp + ./source/net/message/storage/creating/RmLocalDirMsgEx.cpp + ./source/net/message/storage/creating/RmDirEntryMsgEx.cpp + ./source/net/message/storage/creating/RmLocalDirMsgEx.h + ./source/net/message/storage/creating/RmDirMsgEx.h + ./source/net/message/storage/creating/MoveFileInodeMsgEx.cpp + ./source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.cpp + ./source/net/message/storage/TruncFileMsgEx.h + ./source/net/message/storage/mirroring/ResyncRawInodesMsgEx.cpp + ./source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp + ./source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h + ./source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.h + ./source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.h + ./source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.cpp + ./source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.h + ./source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.cpp + ./source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.cpp + ./source/net/message/storage/mirroring/ResyncRawInodesMsgEx.h + ./source/net/message/storage/attribs/RemoveXAttrMsgEx.cpp + ./source/net/message/storage/attribs/UpdateDirParentMsgEx.h + ./source/net/message/storage/attribs/RefreshEntryInfoMsgEx.h + ./source/net/message/storage/attribs/StatMsgEx.h + ./source/net/message/storage/attribs/RemoveXAttrMsgEx.h + ./source/net/message/storage/attribs/ListXAttrMsgEx.h + ./source/net/message/storage/attribs/GetEntryInfoMsgEx.h + ./source/net/message/storage/attribs/SetAttrMsgEx.h + ./source/net/message/storage/attribs/SetXAttrMsgEx.h + ./source/net/message/storage/attribs/SetDirPatternMsgEx.h + ./source/net/message/storage/attribs/ListXAttrMsgEx.cpp + ./source/net/message/storage/attribs/SetDirPatternMsgEx.cpp + ./source/net/message/storage/attribs/GetXAttrMsgEx.cpp + ./source/net/message/storage/attribs/StatMsgEx.cpp + ./source/net/message/storage/attribs/RefreshEntryInfoMsg.cpp + ./source/net/message/storage/attribs/GetEntryInfoMsgEx.cpp + ./source/net/message/storage/attribs/GetXAttrMsgEx.h + ./source/net/message/storage/attribs/UpdateDirParentMsgEx.cpp + ./source/net/message/storage/attribs/SetAttrMsgEx.cpp + ./source/net/message/storage/attribs/SetXAttrMsgEx.cpp + ./source/net/message/storage/attribs/SetFilePatternMsgEx.cpp + ./source/net/message/storage/TruncFileMsgEx.cpp + ./source/net/message/storage/GetHighResStatsMsgEx.cpp + ./source/net/message/storage/lookup/FindOwnerMsgEx.cpp + ./source/net/message/storage/lookup/FindLinkOwnerMsgEx.cpp + ./source/net/message/storage/lookup/FindOwnerMsgEx.h + ./source/net/message/storage/lookup/LookupIntentMsgEx.h + ./source/net/message/storage/lookup/FindLinkOwnerMsgEx.h + ./source/net/message/storage/lookup/LookupIntentMsgEx.cpp + ./source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.cpp + ./source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.cpp + ./source/net/message/storage/StatStoragePathMsgEx.h + ./source/net/message/storage/StatStoragePathMsgEx.cpp + ./source/net/message/storage/listing/ListDirFromOffsetMsgEx.h + ./source/net/message/storage/listing/ListDirFromOffsetMsgEx.cpp + ./source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp + ./source/net/message/storage/quota/SetExceededQuotaMsgEx.h + ./source/net/message/storage/attribs/SetFilePatternMsgEx.h + ./source/net/message/storage/attribs/SetFilePatternMsgEx.cpp + ./source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.h + ./source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.cpp + ./source/net/message/storage/creating/MoveFileInodeMsgEx.h + ./source/net/message/storage/creating/MoveFileInodeMsgEx.cpp + ./source/net/message/fsck/UpdateFileAttribsMsgEx.cpp + ./source/net/message/fsck/RemoveInodesMsgEx.h + ./source/net/message/fsck/AdjustChunkPermissionsMsgEx.h + ./source/net/message/fsck/FixInodeOwnersMsgEx.cpp + ./source/net/message/fsck/AdjustChunkPermissionsMsgEx.cpp + ./source/net/message/fsck/FsckSetEventLoggingMsgEx.h + ./source/net/message/fsck/UpdateDirAttribsMsgEx.cpp + ./source/net/message/fsck/FixInodeOwnersMsgEx.h + ./source/net/message/fsck/UpdateFileAttribsMsgEx.h + ./source/net/message/fsck/UpdateDirAttribsMsgEx.h + ./source/net/message/fsck/FixInodeOwnersInDentryMsgEx.cpp + ./source/net/message/fsck/RetrieveDirEntriesMsgEx.cpp + ./source/net/message/fsck/DeleteDirEntriesMsgEx.h + ./source/net/message/fsck/RetrieveFsIDsMsgEx.h + ./source/net/message/fsck/CreateEmptyContDirsMsgEx.h + ./source/net/message/fsck/RetrieveInodesMsgEx.cpp + ./source/net/message/fsck/LinkToLostAndFoundMsgEx.h + ./source/net/message/fsck/RetrieveInodesMsgEx.h + ./source/net/message/fsck/RecreateDentriesMsgEx.cpp + ./source/net/message/fsck/CreateDefDirInodesMsgEx.h + ./source/net/message/fsck/LinkToLostAndFoundMsgEx.cpp + ./source/net/message/fsck/RecreateFsIDsMsgEx.cpp + ./source/net/message/fsck/FixInodeOwnersInDentryMsgEx.h + ./source/net/message/fsck/FsckSetEventLoggingMsgEx.cpp + ./source/net/message/fsck/RetrieveDirEntriesMsgEx.h + ./source/net/message/fsck/DeleteDirEntriesMsgEx.cpp + ./source/net/message/fsck/CreateDefDirInodesMsgEx.cpp + ./source/net/message/fsck/RecreateDentriesMsgEx.h + ./source/net/message/fsck/RecreateFsIDsMsgEx.h + ./source/net/message/fsck/RemoveInodesMsgEx.cpp + ./source/net/message/fsck/RetrieveFsIDsMsgEx.cpp + ./source/net/message/fsck/CreateEmptyContDirsMsgEx.cpp + ./source/net/message/fsck/CheckAndRepairDupInodeMsgEx.h + ./source/net/message/fsck/CheckAndRepairDupInodeMsgEx.cpp + ./source/net/msghelpers/MsgHelperMkFile.h + ./source/net/msghelpers/MsgHelperTrunc.cpp + ./source/net/msghelpers/MsgHelperXAttr.cpp + ./source/net/msghelpers/MsgHelperStat.h + ./source/net/msghelpers/MsgHelperLocking.h + ./source/net/msghelpers/MsgHelperUnlink.h + ./source/net/msghelpers/MsgHelperXAttr.h + ./source/net/msghelpers/MsgHelperOpen.cpp + ./source/net/msghelpers/MsgHelperClose.cpp + ./source/net/msghelpers/MsgHelperClose.h + ./source/net/msghelpers/MsgHelperTrunc.h + ./source/net/msghelpers/MsgHelperUnlink.cpp + ./source/net/msghelpers/MsgHelperMkFile.cpp + ./source/net/msghelpers/MsgHelperStat.cpp + ./source/net/msghelpers/MsgHelperOpen.h + ./source/net/msghelpers/MsgHelperLocking.cpp + ./source/components/FileEventLogger.h + ./source/components/DisposalGarbageCollector.h + ./source/components/DatagramListener.h + ./source/components/InternodeSyncer.h + ./source/components/ModificationEventFlusher.cpp + ./source/components/ModificationEventFlusher.h + ./source/components/InternodeSyncer.cpp + ./source/components/DatagramListener.cpp + ./source/components/worker/GetChunkFileAttribsWork.cpp + ./source/components/worker/SetChunkFileAttribsWork.h + ./source/components/worker/SetChunkFileAttribsWork.cpp + ./source/components/worker/UnlinkChunkFileWork.h + ./source/components/worker/BarrierWork.h + ./source/components/worker/CloseChunkFileWork.h + ./source/components/worker/GetChunkFileAttribsWork.h + ./source/components/worker/UnlinkChunkFileWork.cpp + ./source/components/worker/TruncChunkFileWork.cpp + ./source/components/worker/CloseChunkFileWork.cpp + ./source/components/worker/LockEntryNotificationWork.h + ./source/components/worker/LockEntryNotificationWork.cpp + ./source/components/worker/LockRangeNotificationWork.h + ./source/components/worker/LockRangeNotificationWork.cpp + ./source/components/worker/TruncChunkFileWork.h + ./source/components/FileEventLogger.cpp + ./source/components/DisposalGarbageCollector.cpp + ./source/components/buddyresyncer/BuddyResyncer.cpp + ./source/components/buddyresyncer/BuddyResyncJob.h + ./source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.cpp + ./source/components/buddyresyncer/SessionStoreResyncer.cpp + ./source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp + ./source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.h + ./source/components/buddyresyncer/SyncCandidate.h + ./source/components/buddyresyncer/BuddyResyncerGatherSlave.h + ./source/components/buddyresyncer/SessionStoreResyncer.h + ./source/components/buddyresyncer/BuddyResyncJob.cpp + ./source/components/buddyresyncer/SyncSlaveBase.cpp + ./source/components/buddyresyncer/SyncSlaveBase.h + ./source/components/buddyresyncer/BuddyResyncerModSyncSlave.h + ./source/components/buddyresyncer/BuddyResyncerModSyncSlave.cpp + ./source/components/buddyresyncer/BuddyResyncer.h + ./source/session/LockingNotifier.cpp + ./source/session/EntryLock.h + ./source/session/EntryLockStore.cpp + ./source/session/EntryLockStore.h + ./source/session/SessionFile.h + ./source/session/LockingNotifier.h + ./source/session/Session.h + ./source/session/SessionStore.cpp + ./source/session/SessionFileStore.h + ./source/session/SessionFile.cpp + ./source/session/Session.cpp + ./source/session/MirrorMessageResponseState.h + ./source/session/SessionStore.h + ./source/session/SessionFileStore.cpp + ./source/session/MirrorMessageResponseState.cpp + ./source/program/Program.h + ./source/program/Program.cpp + ./source/program/Main.cpp + ./source/app/App.h + ./source/app/App.cpp + ./source/app/config/Config.h + ./source/app/config/Config.cpp + ./source/nodes/MetaNodeOpStats.h + ./source/storage/DirInode.h + ./source/storage/IncompleteInode.cpp + ./source/storage/MetadataEx.h + ./source/storage/Locking.h + ./source/storage/DirEntryStore.cpp + ./source/storage/DentryStoreData.h + ./source/storage/FileInodeStoreData.h + ./source/storage/InodeFileStore.cpp + ./source/storage/PosixACL.cpp + ./source/storage/IncompleteInode.h + ./source/storage/Locking.cpp + ./source/storage/MkFileDetails.h + ./source/storage/MetaStore.cpp + ./source/storage/InodeFileStore.h + ./source/storage/FileInode.cpp + ./source/storage/DiskMetaData.cpp + ./source/storage/FileInode.h + ./source/storage/InodeDirStore.cpp + ./source/storage/DirEntry.cpp + ./source/storage/SyncedDiskAccessPath.h + ./source/storage/DirEntryStore.h + ./source/storage/MetaStore.h + ./source/storage/MetaStoreRename.cpp + ./source/storage/NodeOfflineWait.h + ./source/storage/InodeDirStore.h + ./source/storage/DirInode.cpp + ./source/storage/DiskMetaData.h + ./source/storage/DirEntry.h + ./source/storage/MetaFileHandle.h + ./source/storage/FileInodeStoreData.cpp + ./source/storage/PosixACL.h +) + +target_link_libraries( + meta + beegfs-common + dl + pthread + blkid +) + +add_executable( + beegfs-meta + source/program/Main.cpp +) + +target_link_libraries( + beegfs-meta + meta +) + +if(NOT BEEGFS_SKIP_TESTS) + add_executable( + test-meta + ./tests/TestConfig.h + ./tests/TestSerialization.h + ./tests/TestSerialization.cpp + ./tests/TestConfig.cpp + ./tests/TestBuddyMirroring.cpp + ) + + target_link_libraries( + test-meta + meta + gtest_main + ) + + # required for a test + file( + COPY ${CMAKE_CURRENT_SOURCE_DIR}/build/dist/etc/beegfs-meta.conf + DESTINATION dist/etc/ + ) + + add_test( + NAME test-meta + COMMAND test-meta --compiler + ) +endif() + +install( + TARGETS beegfs-meta + DESTINATION "usr/sbin" + COMPONENT "meta" +) + +install( + PROGRAMS "build/dist/sbin/beegfs-setup-meta" + DESTINATION "usr/sbin" + COMPONENT "meta" +) + +install( + FILES "build/dist/usr/lib/systemd/system/beegfs-meta.service" "build/dist/usr/lib/systemd/system/beegfs-meta@.service" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/system" + COMPONENT "meta" +) + +install( + FILES "build/dist/etc/beegfs-meta.conf" + DESTINATION "etc/beegfs" + COMPONENT "meta" +) + +install( + PROGRAMS "build/beegfs-meta.sh" + RENAME "beegfs-meta" + DESTINATION "opt/beegfs/sbin" + COMPONENT "meta" +) diff --git a/meta/build/Makefile b/meta/build/Makefile new file mode 100644 index 0000000..8a47c5a --- /dev/null +++ b/meta/build/Makefile @@ -0,0 +1,31 @@ +include ../../build/Makefile + +main := ../source/program/Main.cpp +sources := $(filter-out $(main), $(shell find ../source -iname '*.cpp')) + +$(call build-static-library,\ + Meta,\ + $(sources),\ + common dl blkid uuid nl3-route,\ + ../source) + +$(call define-dep-lib,\ + Meta,\ + -I ../source,\ + $(build_dir)/libMeta.a) + +$(call build-executable,\ + beegfs-meta,\ + $(main),\ + Meta common dl blkid uuid nl3-route) + +$(call build-test,\ + test-runner,\ + $(shell find ../tests -name '*.cpp'),\ + Meta common dl blkid uuid nl3-route,\ + ../tests) + +# enable special reference DirInode debug code +ifneq ($(BEEGFS_DEBUG_RELEASE_DIR),) # extra release dir debugging + CXXFLAGS += -DBEEGFS_DEBUG_RELEASE_DIR +endif diff --git a/meta/build/dist/etc/beegfs-meta.conf b/meta/build/dist/etc/beegfs-meta.conf new file mode 100644 index 0000000..5c9ac0b --- /dev/null +++ b/meta/build/dist/etc/beegfs-meta.conf @@ -0,0 +1,614 @@ +# This is a config file for BeeGFS metadata nodes. +# http://www.beegfs.com + + +# --- [Table of Contents] --- +# +# 1) Settings +# 2) Command Line Arguments +# 3) Basic Settings Documentation +# 4) Advanced Settings Documentation + + +# +# --- Section 1.1: [Basic Settings] --- +# + +sysMgmtdHost = + +storeMetaDirectory = +storeAllowFirstRunInit = true +storeFsUUID = + + +# +# --- Section 1.2: [Advanced Settings] --- +# + +connAuthFile = /etc/beegfs/conn.auth +connDisableAuthentication = false +connBacklogTCP = 128 +connFallbackExpirationSecs = 900 +connInterfacesFile = +connMaxInternodeNum = 32 + +connMetaPort = 8005 +connMgmtdPort = 8008 +connPortShift = 0 + +connNetFilterFile = + +connUseRDMA = true +connRDMATypeOfService = 0 +connTcpOnlyFilterFile = + +logType = syslog +logLevel = 3 +logNoDate = false +logNumLines = 50000 +logNumRotatedFiles = 5 +logStdFile = /var/log/beegfs-meta.log + +runDaemonized = true + +storeClientXAttrs = false +storeClientACLs = false +storeUseExtendedAttribs = true + +sysTargetAttachmentFile = +sysTargetOfflineTimeoutSecs = 180 +sysAllowUserSetPattern = false + +tuneBindToNumaZone = +tuneNumStreamListeners = 1 +tuneNumWorkers = 0 +tuneTargetChooser = randomized +tuneUseAggressiveStreamPoll = false +tuneUsePerUserMsgQueues = false + +# +# --- Section 2: [Command Line Arguments] --- +# + +# Use the command line argument "cfgFile=/etc/anotherconfig.conf" to +# specify a different config file for beegfs-meta. +# All other options in this file can also be used as command line +# arguments, overriding the corresponding config file values. + + +# +# --- Section 3: [Basic Settings Documentation] --- +# + +# [sysMgmtdHost] +# Hostname (or IP) of the host running the management service. +# (See also "connMgmtdPort") +# Default: + +# [storeMetaDirectory] +# The absolute path and name of a directory where the file system can store its +# metadata. +# Default: + +# [storeFsUUID] +# Requires the underlying file system of the metadata directory to have the same +# UUID as set here. This prevents the meta node from accidentaly starting from the +# wrong device, e.g. when it is not properly mounted. To find the UUID to +# put here, you can, for example, use blkid: +# +# blkid -s UUID +# +# This will output all devices on the host with their file systems UUID (if there +# is one). Choose the correct one and copy it here. This command needs to be run +# as root. +# +# If left empty, the check is skipped. It is highly recommended to enable this check +# after installation to prevent data corruption. +# Default: + +# [storeAllowFirstRunInit] +# Enables or disables daemon startup with an uninitialized storage directory. +# This can be used to make sure that the daemon does not run when the storage +# partition is not mounted (e.g. because it needs repair after a power outage). +# Note: This setting must be enabled during first startup of the daemon, but +# may be disabled afterwards. +# Default: true + + +# +# --- Section 4: [Advanced Settings Documentation] --- +# + +# +# --- Section 4.1: [Connections & Communication] --- +# + +# [connAuthFile] +# The path to a file that contains a shared secret for connection based +# authentication. Only peers that use the same shared secret will be able to +# connect. +# Default: + +# [connDisableAuthentication] +# If set to true, explicitly disables connection authentication and allow the +# service to run without a connAuthFile. Running BeeGFS without connection +# authentication is considered insecure and is not recommended. +# Default: false + +# [connBacklogTCP] +# The TCP listen backlog. +# Default: 128 + +# [connFallbackExpirationSecs] +# The time in seconds after which a connection to a fallback interface expires. +# When a fallback connection expires, the system will try to establish a new +# connection to the other hosts primary interface (falling back to another +# interface again if necessary). +# Note: The priority of node interfaces can be configured using the +# "connInterfacesFile" parameter. +# Default: 900 + +# [connInterfacesFile] +# The path to a text file that specifies the names of the interfaces which +# may be used for communication by other nodes. One interface per line. The +# line number also defines the priority of an interface. +# Example: "ib0" in the first line, "eth0" in the second line. +# Values: This setting is optional. If unspecified, all available interfaces +# will be published and priorities will be assigned automatically. +# Note: This information is sent to other hosts to inform them about possible +# communication paths. See connRestrictOutboundInterfaces for this +# configuration's potential effect on outbound connections. +# Default: + +# [connInterfacesList] +# Comma-separated list of interface names. Performs the same function as +# connInterfacesFile. +# Default: + +# [connRestrictOutboundInterfaces] +# The default behavior of BeeGFS is to use any available network interface +# to establish an outbound connection to a node, according to the TCP/IP +# configuration of the operating system. When connRestrictOutboundInterfaces +# is set to true, the network interfaces used for outbound connections are +# limited to the values specified by connInterfacesFile or connInterfacesList. +# The operating system routing tables are consulted to determine which +# interface to use for a particular node's IP address. If there is no +# route from the configured interfaces that is suitable for a node's IP +# addresses then the connection will fail to be established. +# Default: false + +# [connNoDefaultRoute] +# When connRestrictOutboundInterfaces is true, the routing logic would use +# the default route for a Node's IP address when no specific route for that +# address is found in the routing tables. This can be problematic during a +# failure situation, as the default route is not appropriate to use for a +# subnet that is accessible from an interface that has failed. +# connNoDefaultRoute is a comma-separated list of CIDRs that should never +# be accessed via the default route. +# Default: 0.0.0.0/0. This prevents the default route from ever being used. + +# [connMaxInternodeNum] +# The maximum number of simultaneous connections to the same node. +# Default: 32 + +# [connMetaPort] +# The UDP and TCP port of the metadata node. +# Default: 8005 + +# [connMgmtdPort] +# The UDP and TCP port of the management node. +# Default: 8008 + +# [connPortShift] +# Shifts all following UDP and TCP ports according to the specified value. +# Intended to make port configuration easier in case you do not want to +# configure each port individually. +# Default: 0 + +# [connNetFilterFile] +# The path to a text file that specifies allowed IP subnets, which may be used +# for outgoing communication. One subnet per line in classless notation (IP +# address and number of significant bits). +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. If unspecified, all addresses are allowed +# for outgoing communication. +# Default: + +# [connTCPRcvBufSize], [connUDPRcvBufSize] +# Sets the size for TCP and UDP socket receive buffers (SO_RCVBUF). The maximum +# allowed value is determined by sysctl net.core.rmem_max. This value is +# ignored if it is less than the default value determined by +# net.core.rmem_default. +# For legacy reasons, the default value 0 indicates that the buffer size is set +# to connRDMABufNum * connRDMABufSize. +# -1 indicates that the buffer size should be left at the system default. +# Default: 0 + +# [connUseRDMA] +# Enables the use of Remote Direct Memory Access (RDMA) for Infiniband. +# This setting only has effect if libbeegfs-ib is installed. +# Default: true + +# [connRDMABufNum], [connRDMABufSize] +# Infiniband RDMA buffer settings. +# connRDMABufSize is the maximum size of a buffer (in bytes) that will be sent +# over the network; connRDMABufNum is the number of available buffers that can +# be in flight for a single connection. These client settings are also applied +# on the server side for each connection. +# Note: RAM usage per connection is connRDMABufSize x connRDMABufNum x 2. Keep +# resulting RAM usage (x connMaxInternodeNum x number_of_clients) on the +# server in mind when increasing these values. +# Note: The client needs to allocate physically contiguous pages for +# connRDMABufSize, so this setting shouldn't be higher than a few kbytes. +# Default: 8192, 70 + +# [connRDMATypeOfService] +# Infiniband provides the option to set a type of service for an application. +# This type of service can be used by your subnet manager to provide Quality of +# Service functionality (e.g. setting different service levels). +# In openSM the service type will be mapped to the parameter qos-class, which +# can be handled in your QoS configuration. +# See +# www.openfabrics.org/downloads/OFED/ofed-1.4/OFED-1.4-docs/ +# QoS_management_in_OpenSM.txt +# for more information on how to configure openSM for QoS. +# This parameter sets the type of service for all outgoing connections of this +# daemon. +# Default: 0 (Max: 255) + +# [connTcpOnlyFilterFile] +# The path to a text file that specifies IP address ranges to which no RDMA +# connection should be established. This is useful e.g. for environments where +# all hosts support RDMA, but some hosts cannot connect via RDMA to some other +# hosts. +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. +# Default: + +# [connMessagingTimeouts] +# These constants are used to set some of the connection timeouts for sending +# and receiving data between services in the cluster. They used to be hard-coded +# (CONN_LONG_TIMEOUT, CONN_MEDIUM_TIMEOUT and CONN_SHORT_TIMEOUT) but are now +# made configurable for experimentation purposes. +# This option takes three integer values of milliseconds, separated by a comma +# in the order long, medium, short. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 600000,90000,30000 + +# [connRDMATimeouts] +# These constants are used to set some of the timeouts for sending and receiving +# data between services in the cluster via RDMA. They used to be +# hard-coded IBVSOCKET_CONN_TIMEOUT_MS, IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS +# and a 10000 literal for poll timeout but are now made configurable for +# experimentation purposes. +# This option takes three integer values of milliseconds, separated by a comma +# in the order connectMS, flowSendMS and pollMS. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 3000,180000,7500 + + +# +# --- Section 4.2: [Logging] --- +# + +# [logType] +# Defines the logger type. This can either be "syslog" to send log messages to +# the general system logger or "logfile". If set to logfile logs will be written +# to logStdFile. +# Default: logfile + +# [logLevel] +# Defines the amount of output messages. The higher this level, the more +# detailed the log messages will be. +# Note: Levels above 3 might decrease performance. +# Default: 3 (Max: 5) + +# [logNoDate] +# Defines whether "date & time" (=false) or the current "time only" (=true) +# should be logged. +# Default: false + +# [logNumLines] +# The maximum number of lines per log file. +# Default: 50000 + +# [logNumRotatedFiles] +# The number of old files to keep when "logNumLines" is reached and the log file +# is rewritten (log rotation). +# Default: 5 + +# [logStdFile] +# The path and filename of the log file for standard log messages. The parameter +# will be considered only if logType value is not equal to syslog. If no name +# is specified, the messages will be written to the console. +# Default: /var/log/beegfs-meta.log + + +# +# --- Section 4.3: [Startup] --- +# + +# [runDaemonized] +# Detach the process from its parent (and from stdin/-out/-err). +# Default: true + + +# +# --- Section 4.4: [Storage] --- +# + +# [storeClientXAttrs] +# Enables client-side extended attributes. +# Note: Can only be enabled if the underlying file system supports extended +# attributes. +# Note: This setting has to be explicitly enabled on the clients as well. +# Default: false + +# [storeClientACLs] +# Enables the handling and storage of client-side access control lists. +# As ACLs are stored as extended attributes, this setting mainly concerns the +# enforcement and server-side propagation of directory default ACLs. +# Note: This setting can only be enabled if storeClientXAttrs is set to true. +# Note: This setting has to be explicitly enabled on all clients as well. +# Note: Enabling this setting can affect metadata performance. +# Default: false + +# [storeUseExtendedAttribs] +# Controls whether BeeGFS metadata is stored as normal file contents (=false) +# or as extended attributes (=true) on the underlying files system. Depending on +# the type and version of your underlying local file system, extended attributes +# typically are significantly faster. +# Note: This setting can only be configured at first startup and cannot be +# changed afterwards. +# Default: true + + +# +# --- Section 4.5: [System Settings] --- +# + +# [sysTargetAttachmentFile] +# This file provides a specification of which targets should be grouped within +# the same domain for randominternode target chooser. This is useful +# e.g. if randominternode is used with multiple storage daemon +# instances running on the same physical hosts when files should be striped +# across different physical hosts. +# Format: Line-separated = definition. +# Example: "101=1" in first line, "102=1" in second line, "201=2" in third +# line to define that targets "101" and "102" are part of the same +# domain "1", while target "201" is part of a different domain "2". The +# domain IDs in this file are arbitrary values in range 1..65535, the +# targetIDs are actual targetIDs as in "beegfs-ctl --listtargets". +# Default: + +# [sysTargetOfflineTimeoutSecs] +# Timeout until the metadata nodes and storage targets are considered offline +# when no target state updates can be fetched from that node. +# Note: This must be the same value as in the /etc/beegfs/beegfs-mgmtd.conf on +# the management node. +# Values: time in seconds +# Default: 180 + +# [sysAllowUserSetPattern] +# If set to true, non-privileged users are allowed to modify stripe pattern +# settings for directories they own. +# Default: false + +# [sysFileEventLogTarget] +# If set, the metadata server will log modification events (which it receives +# from clients) to a Unix Socket specified here. External tools may listen on +# this socket and process the information. +# Note: Each event will be logged in the following format: +# droppedSeqNo (64bit) - missedSeqNo (64bit) - eventType (32bit) - ModifiedPath +# Increased dropped sequence numbers indicate communication errors. Increased +# missed sequence numbers indicate that a event could not be properly reported. +# The following event types are possible: +# 0(file contents flushed), 1(truncate), 2(set attribute), 3(file closed), +# 4(create), 5(mkdir), 6(mknode), 7(create symlink), 8(rmdir), 9(unlink), +# 10(create hardlink), 11(rename), 12(read) +# Default: +# Example: sysFileEventLogTarget = unix:/run/beegfs/eventlog + +# [sysFileEventPersistDirectory] +# If set, the metadata server will persist modification events to this +# directory. If unset, will persist to "eventq" subdirectory under +# metadata-root. +# When the metadata server starts up, it will try to create that directory +# (non-recursive mkdir()) and initialize a new event persist store in this +# directory. +# If creating the directory fails with EEXIST (directory exists) it will assume +# an existing persist store and try to load it. +# Default: (storeMetaDirectory + "/eventq") + +# [sysFileEventPersistSize] +# If event logging is enabled (see sysFileEventLogTarget), this control +# explicitly sets the size (in bytes) for creating the chunk-store file in the +# eventq directory (sysFileEventPersistDirectory). +# The chunk-store is a file containing a ringbuffer where events get +# persisted. The events will eventually be delivered to downstream services +# (for example bee-watch, hive-index). +# Note that this value has no effect when loading an existing queue directory. +# Default: 0 (use internal defaults / guesswork) +# Example: sysFileEventPersistSize = 2g # 2 Gigabytes +# Note: the value will be rounded up to the next power of 2. + +# +# --- Section 4.6: [Tuning] --- +# + +# [tuneBindToNumaZone] +# Defines the zero-based NUMA zone number to which all threads of this process +# should be bound. If unset, all available CPU cores may be used. +# Zone binding is especially useful if the corresponding devices (e.g. storage +# controller and network card) are also attached to the same zone. +# Note: The Linux kernel shows NUMA zones at /sys/devices/system/node/nodeXY +# Default: + +# [tuneNumStreamListeners] +# The number of threads waiting for incoming data events. Connections with +# incoming data will be handed over to the worker threads for actual message +# processing. +# Default: 1 + +# [tuneNumWorkers] +# The number of worker threads. Higher number of workers allows the server to +# handle more client requests in parallel. On dedicated metadata servers, this +# is typically set to a value between four and eight times the number of CPU +# cores. +# Note: 0 means use twice the number of CPU cores (but at least 4). +# Default: 0 + +# [tuneTargetChooser] +# The algorithm to choose storage targets for file creation. +# Values: +# * randomized: choose targets in a random fashion. +# * roundrobin: choose targets in a deterministic round-robin fashion. +# (Use this only for benchmarking of large-file streaming throughput.) +# * randomrobin: randomized round-robin; choose targets in a deterministic +# round-robin fashion, but random shuffle the result targets list. +# * randominternode: choose random targets that are assigned to different +# storage nodeIDs. (See sysTargetAttachmentFile if multiple storage +# storage daemon instances are running on the same physical host.) +# Note: Only the randomized chooser honors client's preferred nodes/targets +# settings. +# Default: randomized + +# [tuneUseAggressiveStreamPoll] +# If set to true, the StreamListener component, which waits for incoming +# requests, will keep actively polling for events instead of sleeping until +# an event occurs. Active polling will reduce latency for processing of +# incoming requests at the cost of higher CPU usage. +# Default: false + +# [tuneUsePerUserMsgQueues] +# If set to true, per-user queues will be used to decide which of the pending +# requests is handled by the next available worker thread. If set to false, +# a single queue will be used and incoming requests will be processed in +# first-come, first-served order. +# Per-user queues are intended to improve fairness in multi-user environments. +# Default: false + +# [tuneWorkerBufSize] +# The buffer size, which is allocated twice by each worker thread for IO and +# network data buffering. +# Note: For optimal performance, this value must be at least 128k. +# Default: 1m + +# [tuneNumResyncSlaves] +# The number of threads used to perform the bulk synchronizations for a buddy +# mirror resync. +# Default: 12 + + +# +# --- Section 4.7: [Quota settings] --- +# + +# [quotaEnableEnforcement] +# Enables enforcement of user and group quota limits by periodically checking +# if the limits are exceeded. +# Note: This uses quota information provided by the underlying local file +# systems of the storage targets. +# Note: Set quota limits with "beegfs-ctl --setquota". +# Note: If this option is true, performance might be slightly decreased due to +# extra information tracking. +# Note: Must be set to the same value in storage servers and mgmtd to be +# effective. +# Default: false + + +# +# --- Section 5: [Expert options] --- +# + +# [storeSelfHealEmptyFiles] +# Delete metadata entries with no content and handle them as though they had +# not existed. Metadata entries with no content can be created by backup +# software that has incorrectly saved or restored metadata. +# Default: true + +# [tuneNumCommSlaves] +# Number of threads dedicated to parallel communication with other nodes. +# Default: 2 * tuneNumWorkers + +# [tuneCommSlaveBufSize] +# Buffer size used by communication threads, analogous to tuneWorkerBufSize. +# Default: 1m + +# [tuneDefaultChunkSize], [tuneDefaultNumStripeTargets] +# Chunk size and number of targets to use when creating the root directory. +# Files and directories inherit these setting from their parents. +# Default: 512k, 4 + +# [tuneProcessFDLimit] +# Sets the maximum number of files the server can open. If the process rlimit +# is already larger than this number the limit will not be decreased. +# Default: 50000 + +# [tuneWorkerNumaAffinity] +# Distributes worker threads equally among NUMA nodes on the system when set. +# Default: false + +# [tuneListenerNumaAffinity] +# Distributes listener threads equally among NUMA nodes on the system when set. +# Default: false + +# [tuneListenerPrioShift] +# Applies a niceness offset to listener threads. Negative values will decrease +# niceness (increse priority), positive values will increase niceness (decrease +# priority). +# Default: -1 + +# [tuneDirMetadataCacheLimit] +# Number of recently used directory structures to keep in memory. +# Increasing this value may reduce memory allocations and disk I/O. +# Default: 1024 + +# [tuneLockGrantWaitMS], [tuneLockGrantNumRetries] +# Acknowledgement wait parameters for lock grant messages. +# Locks that are granted asynchronously (ie a client is waiting on the lock) +# notify waiting clients with UDP packets. For each waiter a notification +# packet is sent and the server waits for tuneLockGrantWaitMS to receive an +# acknowledgement from the client. This process is repeated up to +# tuneLockGrantNumRetries times, +# Default: 333, 15 + +# [tuneRotateMirrorTargets] +# Choose mirror targets for RAID10 patterns by rotating the selected targets. +# Default: false + +# [tuneEarlyUnlinkResponse] +# Respond to unlink messages before chunk files have been unlinked. +# Default: true + +# [tuneMirrorTimestamps] +# When buddy mirroring, mirror timestamps as exactly as possible. When this is +# set to `false` timestamps of mirrored files may be incorrect after a failover +# has occured. Disabling timestamp mirroring gives a slight performance boost. +# Default: true + +# [tuneDisposalGCPeriod] +# If > 0, disposal files will not be removed instantly. Insead a garbage collector +# will run on each meta node. This sets the Wait time in seconds between runs. +# Default: 0 + +# [quotaEarlyChownResponse] +# Respond to client chown() requests before chunk files have been changed. +# Quota relies on chunk files having the owner and group information stored in +# metadata. Therefore, setting this to true creates a short time window after +# a chown where the application and the servers have a different view on quota. +# Default: true + +# [pidFile] +# Creates a PID file for the daemon when set. Set by init scripts. +# Default: diff --git a/meta/build/dist/etc/default/beegfs-meta b/meta/build/dist/etc/default/beegfs-meta new file mode 100644 index 0000000..da4892c --- /dev/null +++ b/meta/build/dist/etc/default/beegfs-meta @@ -0,0 +1,29 @@ +# BeeGFS metadata service configuration. + +# Note: This file is only used together with sysV init scripts. +# If your system uses systemd, this file is ignored. +# In this case: +# +# - use `systemctl enable / disable` to activate / decativate a service +# +# - systemd service templates are used for multimode +# (See https://www.beegfs.io/wiki/MultiMode) +# +# +# Set to "NO" to disable start of the BeeGFS metadata daemon via the init +# script. +START_SERVICE="YES" + +# Set to "YES" if you want to start multiple metadata daemons with different +# configuration files on this machine. +# +# Create a subdirectory with the ending ".d" in "/etc/beegfs/" for every config +# file. The subdirectory name will be used to identify a particular server +# instance for init script start/stop. +# +# Note: The original config file in /etc/beegfs will not be used when multi-mode +# is enabled. +# +# Example: /etc/beegfs/scratch.d/beegfs-meta.conf +# $ /etc/init.d/beegfs-meta start scratch +MULTI_MODE="NO" diff --git a/meta/build/dist/sbin/beegfs-setup-meta b/meta/build/dist/sbin/beegfs-setup-meta new file mode 100755 index 0000000..a3ac0b1 --- /dev/null +++ b/meta/build/dist/sbin/beegfs-setup-meta @@ -0,0 +1,247 @@ +#!/bin/bash + +# License: BeeGFS EULA + +# constant definitions +# (for configurables see below) + +DEFAULT_CFG_PATH="/etc/beegfs/beegfs-meta.conf" +STORAGE_PATH_CFG_KEY="storeMetaDirectory" +MGMTD_CFG_KEY="sysMgmtdHost" +ALLOW_INIT_CFG_KEY="storeAllowFirstRunInit" +FS_UUID_CFG_KEY="storeFsUUID" +SERVER_NUMID_FILE="nodeNumID" +FORMAT_FILENAME="format.conf" +FORMAT_FILE_VERSION="4" +XATTR_CFG_KEY="storeUseExtendedAttribs" + +print_usage() +{ + echo + echo "DESCRIPTION: Initialize metadata storage directory for beegfs-meta server daemon" + echo "and update the beegfs-meta config file." + echo + echo "USAGE: `basename $0` -p [options]" + echo + echo " Mandatory Options:" + echo + echo " -p - Path to metadata storage directory that is to be initialized." + echo " (Path will also be added to config file.)" + echo + echo " Recommended Options:" + echo + echo " -s - Assign the given numeric ID to the server of this storage" + echo " directory (range 1..65535). (Default: Randomly select a free ID.)" + echo + echo " -m - Hostname (or IP address) of management server." + echo " (Will be stored in server config file.)" + echo + echo " Other Options:" + echo + echo " -C - Do not update server config file." + echo + echo " -c - Path to server config file." + echo " (Default: ${DEFAULT_CFG_PATH})" + echo + echo " -f - Force actions, ignore warnings." + echo + echo " -h - Print this help." + echo + echo " -u - Do not disable usage of uninitialized storage directory in config" + echo " file and do not store the UUID of the underlying fs." + echo + echo " -x - Do not store metadata as extended attributes." + echo + echo "NOTES:" + echo " * All given IDs must be unique in their service class for the whole file system" + echo " instance, i.e. there can only be one beegfs-meta service with ID 2, but there" + echo " can also be a beegfs-storage service with ID 2 in the file system." + echo + echo " * BeeGFS servers can also run without pre-initializing storage directories, if" + echo " storeAllowFirstRunInit=true is set in the server config files (which is" + echo " usually not recommended)." + echo + echo "EXAMPLES:" + echo " * Numeric IDs can generally be chosen arbitrarily. However, it is usually a" + echo " good idea to pick a numeric ID that matches the hostname, e.g. if the" + echo " hostname is \"meta02\", you would use \"2\" as numeric ID for the beegfs-meta" + echo " service on this server." + echo + echo " * Example 1) Initialize metadata storage directory of first metadata server and" + echo " set \"storage01\" as management daemon host in config file:" + echo " $ `basename $0` -p /mnt/myraid1/beegfs-meta -s 1 -m storage01" + echo +} + +# initialize storage directory (if enabled) +init_storage_dir() +{ + # check if storage path is defined + + if [ -z "${STORAGE_PATH}" ]; then + return 0 + fi + + # create storage path + + echo "Preparing storage directory: ${STORAGE_PATH}" + mkdir -p "${STORAGE_PATH}" + + # make sure storage dir is empty + + if [ -z "${FORCE_ACTIONS}" ] && [ "$(ls -AI lost+found ${STORAGE_PATH} )" ]; then + echo " * ERROR: Storage directory is not empty. Initialization of non-empty" \ + "directories can lead to data loss or orphaned data. ('-f' disables this check.)" + exit 1 + fi + + # create format file + + echo " * Creating ${FORMAT_FILENAME} file..." + + FORMAT_FILE_PATH="${STORAGE_PATH}/${FORMAT_FILENAME}" + echo "# This file was auto-generated. Do not modify it!" >> ${FORMAT_FILE_PATH} + echo "version=${FORMAT_FILE_VERSION}" >> ${FORMAT_FILE_PATH} + echo "xattr=${FORMAT_USE_XATTR}" >> ${FORMAT_FILE_PATH} + + # create ID files + + if [ -n "${SERVER_NUMID}" ]; then + echo " * Creating server numeric ID file: ${STORAGE_PATH}/${SERVER_NUMID_FILE}" + echo "${SERVER_NUMID}" > "${STORAGE_PATH}/${SERVER_NUMID_FILE}" + fi +} + +# update config file (if enabled) +update_config_file() +{ + # check if config file is defined + + if [ -z "${CFG_PATH}" ]; then + return 0 + fi + + echo "Updating config file: ${CFG_PATH}" + + if [ ! -f "${CFG_PATH}" ]; then + echo " * ERROR: Config file not found: ${CFG_PATH}" + exit 1 + fi + + if [ -n "${MGMTD_HOST}" ]; then + echo " * Setting management host: ${MGMTD_HOST}" + sed -i "s/\(^${MGMTD_CFG_KEY}.*=\).*/\1 ${MGMTD_HOST}/" ${CFG_PATH} + fi + + if [ -n "${STORAGE_PATH}" ]; then + echo " * Setting storage directory in config file..." + sed -i "s|\(^${STORAGE_PATH_CFG_KEY}.*=\).*$|\1 ${STORAGE_PATH}|" ${CFG_PATH} + fi + + if [ -n "${DISABLE_UNINITED_TARGETS}" ] && [ -n "${STORAGE_PATH}" ]; then + echo " * Disabling usage of uninitialized storage directory in config file..." + sed -i "s/\(^${ALLOW_INIT_CFG_KEY}.*=\).*/\1 false/" ${CFG_PATH} + + echo " * Fetching the underlying device..." + DEVICE=$(df "${STORAGE_PATH}" | tail -n1 | cut -d\ -f1) + echo "Underlying device detected: ${DEVICE}" + echo "Fetching UUID of the file system on that device..." + UUID=$(blkid -s UUID ${DEVICE} | cut -d= -f2 | sed "s/\"//g") + echo "Found UUID ${UUID}" + echo "Writing UUID to config file..." + sed -i "s|\(^${FS_UUID_CFG_KEY}.*=\).*$|\1 ${UUID}|" ${CFG_PATH} + + fi + + if [ -n "${STORAGE_PATH}" ]; then + echo " * Setting usage of extended attributes to: ${FORMAT_USE_XATTR}" + sed -i "s/\(^${XATTR_CFG_KEY}.*=\).*/\1 ${FORMAT_USE_XATTR}/" ${CFG_PATH} + fi +} + +################## end of function definitions ############## + + +# configurable values and their defaults +# (for constants see above) + +CFG_PATH="$DEFAULT_CFG_PATH" # empty path means "don't update cfg file" +FORCE_ACTIONS="" +FORMAT_USE_XATTR="true" +MGMTD_HOST="" +SERVER_NUMID="" +STORAGE_PATH="" +DISABLE_UNINITED_TARGETS="1" + +# parse command line arguments +# (see print_usage() for description of parameters) + +while getopts "Cc:fhm:p:S:s:ux" opt; do + case $opt in + C) + CFG_PATH="" + ;; + c) + CFG_PATH="$OPTARG" + ;; + f) + FORCE_ACTIONS="1" + ;; + h) + print_usage + exit 0 + ;; + m) + MGMTD_HOST="$OPTARG" + ;; + p) + STORAGE_PATH="$OPTARG" + ;; + S) + echo "WARNING: The -S flag previously used to specify a string ID been deprecated and now has no effect. Starting in BeeGFS string IDs were replaced with aliases configured using BeeGFS CTL." + ;; + s) + SERVER_NUMID="$OPTARG" + ;; + u) + DISABLE_UNINITED_TARGETS="" + ;; + x) + FORMAT_USE_XATTR="false" + ;; + *) + echo "ERROR: Invalid argument" >&2 + print_usage + exit 1 + ;; + esac +done + +set -e + +# don't do anything if no arguments are provided + +if [ $# -eq 0 ]; then + print_usage + exit 1 +fi + +# make sure storage dir is defined + +if [ -z "${STORAGE_PATH}" ]; then + echo "ERROR: Storage directory is undefined." >&2 + echo + print_usage + exit 1 +fi + +# initialize storage directory + +init_storage_dir + +# update config file + +update_config_file + + +echo "All done." diff --git a/meta/build/dist/usr/lib/systemd/system/beegfs-meta.service b/meta/build/dist/usr/lib/systemd/system/beegfs-meta.service new file mode 100644 index 0000000..62c6aff --- /dev/null +++ b/meta/build/dist/usr/lib/systemd/system/beegfs-meta.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Metadata Server +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-meta cfgFile=/etc/beegfs/beegfs-meta.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/meta/build/dist/usr/lib/systemd/system/beegfs-meta@.service b/meta/build/dist/usr/lib/systemd/system/beegfs-meta@.service new file mode 100644 index 0000000..31bb036 --- /dev/null +++ b/meta/build/dist/usr/lib/systemd/system/beegfs-meta@.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Metadata Server (multimode) +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-meta cfgFile=/etc/beegfs/%I.d/beegfs-meta.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/meta/source/app/App.cpp b/meta/source/app/App.cpp new file mode 100644 index 0000000..a593746 --- /dev/null +++ b/meta/source/app/App.cpp @@ -0,0 +1,1656 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "App.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +// this magic number is not available on all supported platforms. specifically, rhel5 does not have +// linux/magic.h (which is where this constant is found). +#ifndef EXT3_SUPER_MAGIC + #define EXT3_SUPER_MAGIC 0xEF53 +#endif + + +#define APP_WORKERS_DIRECT_NUM 1 +#define APP_SYSLOG_IDENTIFIER "beegfs-meta" + + +App::App(int argc, char** argv) +{ + this->argc = argc; + this->argv = argv; + + this->appResult = APPCODE_NO_ERROR; + + this->cfg = NULL; + this->netFilter = NULL; + this->tcpOnlyFilter = NULL; + this->log = NULL; + this->mgmtNodes = NULL; + this->metaNodes = NULL; + this->storageNodes = NULL; + this->clientNodes = NULL; + this->metaCapacityPools = NULL; + this->targetMapper = NULL; + this->storageBuddyGroupMapper = NULL; + this->metaBuddyGroupMapper = NULL; + this->targetStateStore = NULL; + this->metaStateStore = NULL; + this->metaBuddyCapacityPools = NULL; + this->workQueue = NULL; + this->commSlaveQueue = NULL; + this->disposalDir = NULL; + this->buddyMirrorDisposalDir = NULL; + this->rootDir = NULL; + this->metaStore = NULL; + this->ackStore = NULL; + this->sessions = NULL; + this->mirroredSessions = NULL; + this->nodeOperationStats = NULL; + this->netMessageFactory = NULL; + this->inodesPath = NULL; + this->dentriesPath = NULL; + this->buddyMirrorInodesPath = NULL; + this->buddyMirrorDentriesPath = NULL; + this->dgramListener = NULL; + this->connAcceptor = NULL; + this->statsCollector = NULL; + this->internodeSyncer = NULL; + this->modificationEventFlusher = NULL; + this->timerQueue = new TimerQueue(1, 1); + this->gcQueue = new TimerQueue(1, 1); + this->buddyResyncer = NULL; + + this->nextNumaBindTarget = 0; +} + +App::~App() +{ + // Note: Logging of the common lib classes is not working here, because this is called + // from class Program (so the thread-specific app-pointer isn't set in this context). + + commSlavesDelete(); + workersDelete(); + + SAFE_DELETE(this->buddyResyncer); + SAFE_DELETE(this->timerQueue); + SAFE_DELETE(this->modificationEventFlusher); + SAFE_DELETE(this->internodeSyncer); + SAFE_DELETE(this->statsCollector); + SAFE_DELETE(this->connAcceptor); + + streamListenersDelete(); + + SAFE_DELETE(this->dgramListener); + SAFE_DELETE(this->dentriesPath); + SAFE_DELETE(this->inodesPath); + SAFE_DELETE(this->buddyMirrorDentriesPath); + SAFE_DELETE(this->buddyMirrorInodesPath); + SAFE_DELETE(this->netMessageFactory); + SAFE_DELETE(this->nodeOperationStats); + SAFE_DELETE(this->sessions); + SAFE_DELETE(this->mirroredSessions); + SAFE_DELETE(this->ackStore); + if(this->disposalDir && this->metaStore) + this->metaStore->releaseDir(this->disposalDir->getID() ); + if(this->buddyMirrorDisposalDir && this->metaStore) + this->metaStore->releaseDir(this->buddyMirrorDisposalDir->getID() ); + if(this->rootDir && this->metaStore) + this->metaStore->releaseDir(this->rootDir->getID() ); + SAFE_DELETE(this->metaStore); + SAFE_DELETE(this->commSlaveQueue); + SAFE_DELETE(this->workQueue); + SAFE_DELETE(this->clientNodes); + SAFE_DELETE(this->storageNodes); + SAFE_DELETE(this->metaNodes); + SAFE_DELETE(this->mgmtNodes); + this->localNode.reset(); + SAFE_DELETE(this->metaBuddyCapacityPools); + SAFE_DELETE(this->storageBuddyGroupMapper); + SAFE_DELETE(this->metaBuddyGroupMapper); + SAFE_DELETE(this->targetMapper); + SAFE_DELETE(this->metaStateStore); + SAFE_DELETE(this->targetStateStore); + SAFE_DELETE(this->metaCapacityPools); + SAFE_DELETE(this->log); + SAFE_DELETE(this->tcpOnlyFilter); + SAFE_DELETE(this->netFilter); + + SAFE_DELETE(this->cfg); + + delete timerQueue; + + fileEventLogger.reset(); + + Logger::destroyLogger(); + closelog(); +} + +/** + * Initialize config and run app either in normal mode or in special unit tests mode. + */ +void App::run() +{ + try + { + openlog(APP_SYSLOG_IDENTIFIER, LOG_NDELAY | LOG_PID | LOG_CONS, LOG_DAEMON); + + this->cfg = new Config(argc, argv); + + runNormal(); + } + catch (InvalidConfigException& e) + { + std::cerr << std::endl; + std::cerr << "Error: " << e.what() << std::endl; + std::cerr << std::endl; + std::cerr << "[BeeGFS Metadata Node Version: " << BEEGFS_VERSION << std::endl; + std::cerr << "Refer to the default config file (/etc/beegfs/beegfs-meta.conf)" << std::endl; + std::cerr << "or visit http://www.beegfs.com to find out about configuration options.]" + << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + appResult = APPCODE_INVALID_CONFIG; + return; + } + catch (std::exception& e) + { + std::cerr << std::endl; + std::cerr << "Unrecoverable error: " << e.what() << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + appResult = APPCODE_RUNTIME_ERROR; + return; + } +} + +/** + * @throw InvalidConfigException on error + */ +void App::runNormal() +{ + // numa binding (as early as possible) + + if(cfg->getTuneBindToNumaZone() != -1) // -1 means disable binding + { + bool bindRes = System::bindToNumaNode(cfg->getTuneBindToNumaZone() ); + if(!bindRes) + throw InvalidConfigException("Unable to bind to this NUMA zone: " + + StringTk::intToStr(cfg->getTuneBindToNumaZone() ) ); + } + + + // init basic data objects & storage + NumNodeID localNodeNumID; + + // locks working dir => call before anything else that accesses the disk + const bool targetNew = preinitStorage(); + + initLogging(); + + checkTargetUUID(); + initLocalNodeIDs(localNodeNumID); + initDataObjects(); + initBasicNetwork(); + + initStorage(); + initXAttrLimit(); + initRootDir(localNodeNumID); + initDisposalDir(); + + registerSignalHandler(); + + // ACLs need enabled client side XAttrs in order to work. + if (cfg->getStoreClientACLs() && !cfg->getStoreClientXAttrs() ) + throw InvalidConfigException( + "Client ACLs are enabled in config file, but extended attributes are not. " + "ACLs cannot be stored without extended attributes."); + + + // detach process + if(cfg->getRunDaemonized() ) + daemonize(); + + log->log(Log_NOTICE, "Built " +#ifdef BEEGFS_NVFS + "with" +#else + "without" +#endif + " NVFS RDMA support."); + + // find RDMA interfaces (based on TCP/IP interfaces) + + // note: we do this here, because when we first create an RDMASocket (and this is done in this + // check), the process opens the verbs device. Recent OFED versions have a check if the + // credentials of the opening process match those of the calling process (not only the values + // are compared, but the pointer is checked for equality). Thus, the first open needs to happen + // after the fork, because we need to access the device in the child process. + findAllowedRDMAInterfaces(localNicList); + + // Find MgmtNode + bool mgmtWaitRes = waitForMgmtNode(); + if(!mgmtWaitRes) + { // typically user just pressed ctrl+c in this case + log->logErr("Waiting for beegfs-mgmtd canceled"); + appResult = APPCODE_RUNTIME_ERROR; + return; + } + + // retrieve localNodeNumID from management node (if we don't have it yet) + if(!localNodeNumID) + { // no local num ID yet => try to retrieve one from mgmt + + bool preregisterRes = preregisterNode(localNodeNumID); + if(!preregisterRes) + throw InvalidConfigException("Pre-registration at management node canceled"); + } + + if (!localNodeNumID) // just a sanity check that should never fail + throw InvalidConfigException("Failed to retrieve numeric local node ID from mgmtd"); + + // we have all local node data now => init localNode + + initLocalNode(localNodeNumID); + initLocalNodeNumIDFile(localNodeNumID); + + // Keeps the local node state from the static call to the InternodeSyncer method so we can pass + // it when we construct the actual object. + TargetConsistencyState initialConsistencyState; + + bool downloadRes = downloadMgmtInfo(initialConsistencyState); + if (!downloadRes) + { + log->log(1, "Downloading target states from management node failed. Shutting down..."); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + // Initialize File Event Logger if fileEventLogTarget is set + if (!cfg->getFileEventLogTarget().empty()) + { + // Creates FileEventLogger instance which: + // 1. Sets up Persistent Message Queue (PMQ) for event storage + // 2. Initializes Unix socket for downstream event listeners (e.g., beegfs-event-listener) + // 3. Starts a dedicated PThread (EventQ-Sender) that runs continuously to: + // a) Read events from the PMQ + // b) Send events to the configured listener socket + // c) Handle reconnections and periodic queue flushing + // Note: EventQ-Sender thread will continue to run until the FileEventLogger is destroyed. + + uint32_t nodeId = this->getLocalNode().getNumID().val(); + uint16_t buddyGroupId = 0; + + if (nodeId <= UINT16_MAX) { + buddyGroupId = this->getMetaBuddyGroupMapper()->getBuddyGroupID( + static_cast(nodeId)); + } else { + LOG(EVENTLOGGER, WARNING, "Node ID exceeds 16-bit range - cannot determine buddy group", + ("nodeID", nodeId)); + // we can think about raising an error here + } + + FileEventLoggerParams params = {}; + params.address = cfg->getFileEventLogTarget(); + params.ids.nodeId = nodeId; + params.ids.buddyGroupId = buddyGroupId; + + fileEventLogger.reset(createFileEventLogger(params)); + } + + // Check for the sessions file. If there is none, it's either the first run, or we crashed so we + // need a resync. + bool sessionFilePresent = StorageTk::checkSessionFileExists(metaPathStr); + if (!targetNew && !sessionFilePresent) + initialConsistencyState = TargetConsistencyState_NEEDS_RESYNC; + + // init components + + BuddyCommTk::prepareBuddyNeedsResyncState(*mgmtNodes->referenceFirstNode(), + *metaBuddyGroupMapper, *timerQueue, localNode->getNumID()); + + try + { + initComponents(initialConsistencyState); + } + catch(ComponentInitException& e) + { + log->logErr(e.what() ); + log->log(Log_CRITICAL, "A hard error occurred. Shutting down..."); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + // restore sessions from last clean shut down + restoreSessions(); + + // log system and configuration info + + logInfos(); + + // start component threads and join them + + startComponents(); + + // session restore is finished so delete old session files + // clean shutdown will generate a new session file + deleteSessionFiles(); + + // wait for termination + + joinComponents(); + + // clean shutdown (at least no cache loss) => generate a new session file + if(sessions) + storeSessions(); + + // close all client sessions + InternodeSyncer::syncClients({}, false); + + log->log(Log_CRITICAL, "All components stopped. Exiting now!"); +} + +void App::initLogging() +{ + // check absolute log path to avoid chdir() problems + Path logStdPath(cfg->getLogStdFile() ); + + if(!logStdPath.empty() && !logStdPath.absolute()) + { + throw InvalidConfigException("Path to log file must be absolute"); + } + + Logger::createLogger(cfg->getLogLevel(), cfg->getLogType(), cfg->getLogNoDate(), + cfg->getLogStdFile(), cfg->getLogNumLines(), cfg->getLogNumRotatedFiles()); + this->log = new LogContext("App"); +} + +/** + * Init basic shared objects like work queues, node stores etc. + */ +void App::initDataObjects() +{ + this->mgmtNodes = new NodeStoreServers(NODETYPE_Mgmt, true); + this->metaNodes = new NodeStoreServers(NODETYPE_Meta, true); + this->storageNodes = new NodeStoreServers(NODETYPE_Storage, false); + this->clientNodes = new NodeStoreClients(); + + NicAddressList nicList; + + this->targetMapper = new TargetMapper(); + this->storageNodes->attachTargetMapper(targetMapper); + + this->storageBuddyGroupMapper = new MirrorBuddyGroupMapper(targetMapper); + this->metaBuddyGroupMapper = new MirrorBuddyGroupMapper(); + + this->metaCapacityPools = new NodeCapacityPools( + false, DynamicPoolLimits(0, 0, 0, 0, 0, 0), DynamicPoolLimits(0, 0, 0, 0, 0, 0) ); + this->metaNodes->attachCapacityPools(metaCapacityPools); + + this->metaBuddyCapacityPools = new NodeCapacityPools( + false, DynamicPoolLimits(0, 0, 0, 0, 0, 0), DynamicPoolLimits(0, 0, 0, 0, 0, 0) ); + this->metaBuddyGroupMapper->attachMetaCapacityPools(metaBuddyCapacityPools); + + this->targetStateStore = new TargetStateStore(NODETYPE_Storage); + this->targetMapper->attachStateStore(targetStateStore); + + this->metaStateStore = new TargetStateStore(NODETYPE_Meta); + this->metaNodes->attachStateStore(metaStateStore); + + this->storagePoolStore = boost::make_unique(storageBuddyGroupMapper, + targetMapper); + // add newly mapped targets and buddy groups to storage pool store + this->targetMapper->attachStoragePoolStore(storagePoolStore.get()); + this->storageBuddyGroupMapper->attachStoragePoolStore(storagePoolStore.get()); + + this->targetMapper->attachExceededQuotaStores(&exceededQuotaStores); + + this->workQueue = new MultiWorkQueue(); + this->commSlaveQueue = new MultiWorkQueue(); + + if(cfg->getTuneUsePerUserMsgQueues() ) + workQueue->setIndirectWorkList(new UserWorkContainer() ); + + this->ackStore = new AcknowledgmentStore(); + + this->sessions = new SessionStore(); + this->mirroredSessions = new SessionStore(); + + this->nodeOperationStats = new MetaNodeOpStats(); + + this->isRootBuddyMirrored = false; +} + +void App::findAllowedRDMAInterfaces(NicAddressList& outList) const +{ + Config* cfg = this->getConfig(); + + if(cfg->getConnUseRDMA() && RDMASocket::rdmaDevicesExist() ) + { + bool foundRdmaInterfaces = NetworkInterfaceCard::checkAndAddRdmaCapability(outList); + if (foundRdmaInterfaces) + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); // re-sort the niclist + } +} + +void App::findAllowedInterfaces(NicAddressList& outList) const +{ + // discover local NICs and filter them + NetworkInterfaceCard::findAllInterfaces(allowedInterfaces, outList); + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); +} + +/** + * Init basic networking data structures. + * + * Note: no RDMA is detected here, because this needs to be done later + */ +void App::initBasicNetwork() +{ + // check if management host is defined + if(!cfg->getSysMgmtdHost().length() ) + throw InvalidConfigException("Management host undefined"); + + // prepare filter for outgoing packets/connections + this->netFilter = new NetFilter(cfg->getConnNetFilterFile() ); + this->tcpOnlyFilter = new NetFilter(cfg->getConnTcpOnlyFilterFile() ); + + // prepare filter for interfaces + std::string interfacesList = cfg->getConnInterfacesList(); + if(!interfacesList.empty() ) + { + log->log(Log_DEBUG, "Allowed interfaces: " + interfacesList); + StringTk::explodeEx(interfacesList, ',', true, &allowedInterfaces); + } + + findAllowedInterfaces(localNicList); + + if(localNicList.empty() ) + throw InvalidConfigException("Couldn't find any usable NIC"); + + noDefaultRouteNets = std::make_shared(); + if(!initNoDefaultRouteList(noDefaultRouteNets.get())) + throw InvalidConfigException("Failed to parse connNoDefaultRoute"); + + initRoutingTable(); + updateRoutingTable(); + + // prepare factory for incoming messages + this->netMessageFactory = new NetMessageFactory(); +} + +/** + * Loads node num ID from disk if it was set. + * Also handles writing out the deprecation notice for to the old string ID files. + */ +void App::initLocalNodeIDs(NumNodeID& outLocalNumID) +{ + StorageTk::deprecateNodeStringIDFiles(metaPathStr); + + Path metaPath(metaPathStr); + // load nodeNumID file + StorageTk::readNumIDFile(metaPath.str(), STORAGETK_NODENUMID_FILENAME, &outLocalNumID); + + // note: localNodeNumID is still 0 here if it wasn't loaded from the file +} + +/** + * create and attach the localNode object, store numID in storage dir + */ +void App::initLocalNode(NumNodeID localNodeNumID) +{ + unsigned port = cfg->getConnMetaPort(); + NicAddressList nicList = getLocalNicList(); + + // create localNode object. Note the alias (formerly string ID) is not known at this stage so it + // is set to an empty string. It will be set later by downloadMgmtInfo(). + localNode = std::make_shared(NODETYPE_Meta, "", localNodeNumID, port, + port, nicList); + + // attach to metaNodes store + metaNodes->setLocalNode(this->localNode); +} + +/** + * Store numID file in storage directory + */ +void App::initLocalNodeNumIDFile(NumNodeID localNodeNumID) +{ + StorageTk::createNumIDFile(metaPathStr, STORAGETK_NODENUMID_FILENAME, localNodeNumID.val()); +} + +/** + * this contains things that would actually live inside initStorage() but need to be + * done at an earlier stage (like working dir locking before log file creation). + * + * note: keep in mind that we don't have the logger here yet, because logging can only be + * initialized after the working dir has been locked within this method. + * + * @returns true if there was no storageFormatFile before (target was uninitialized) + */ +bool App::preinitStorage() +{ + Path metaPath(cfg->getStoreMetaDirectory() ); + this->metaPathStr = metaPath.str(); // normalize + + if(metaPath.empty() ) + throw InvalidConfigException("No metadata storage directory specified"); + + if(!metaPath.absolute() ) /* (check to avoid problems after chdir later) */ + throw InvalidConfigException("Path to storage directory must be absolute: " + metaPathStr); + + const bool formatFileExists = StorageTk::checkStorageFormatFileExists(metaPathStr); + + if(!cfg->getStoreAllowFirstRunInit() && + !formatFileExists) + throw InvalidConfigException("Storage directory not initialized and " + "initialization has been disabled: " + metaPathStr); + + this->pidFileLockFD = createAndLockPIDFile(cfg->getPIDFile() ); // ignored if pidFile not defined + + if(!StorageTk::createPathOnDisk(metaPath, false) ) + throw InvalidConfigException("Unable to create metadata directory: " + metaPathStr + + " (" + System::getErrString(errno) + ")" ); + + this->workingDirLockFD = StorageTk::lockWorkingDirectory(cfg->getStoreMetaDirectory() ); + if (!workingDirLockFD.valid()) + throw InvalidConfigException("Unable to lock working directory: " + metaPathStr); + + return !formatFileExists; +} + +void App::initStorage() +{ + // change working dir to meta directory + int changeDirRes = chdir(metaPathStr.c_str() ); + if(changeDirRes) + { // unable to change working directory + throw InvalidConfigException("Unable to change working directory to: " + metaPathStr + " " + "(SysErr: " + System::getErrString() + ")"); + } + + // storage format file + if(!StorageTkEx::createStorageFormatFile(metaPathStr) ) + throw InvalidConfigException("Unable to create storage format file in: " + + cfg->getStoreMetaDirectory() ); + + StorageTkEx::checkStorageFormatFile(metaPathStr); + + // dentries directory + dentriesPath = new Path(META_DENTRIES_SUBDIR_NAME); + StorageTk::initHashPaths(*dentriesPath, + META_DENTRIES_LEVEL1_SUBDIR_NUM, META_DENTRIES_LEVEL2_SUBDIR_NUM); + + // buddy mirrored dentries directory + buddyMirrorDentriesPath = new Path(META_BUDDYMIRROR_SUBDIR_NAME "/" META_DENTRIES_SUBDIR_NAME); + StorageTk::initHashPaths(*buddyMirrorDentriesPath, + META_DENTRIES_LEVEL1_SUBDIR_NUM, META_DENTRIES_LEVEL2_SUBDIR_NUM); + + // inodes directory + inodesPath = new Path(META_INODES_SUBDIR_NAME); + if(!StorageTk::createPathOnDisk(*this->inodesPath, false) ) + throw InvalidConfigException("Unable to create directory: " + inodesPath->str() ); + StorageTk::initHashPaths(*inodesPath, + META_INODES_LEVEL1_SUBDIR_NUM, META_INODES_LEVEL2_SUBDIR_NUM); + + // buddy mirrored inodes directory + buddyMirrorInodesPath = new Path(META_BUDDYMIRROR_SUBDIR_NAME "/" META_INODES_SUBDIR_NAME); + if(!StorageTk::createPathOnDisk(*this->buddyMirrorInodesPath, false) ) + throw InvalidConfigException( + "Unable to create directory: " + buddyMirrorInodesPath->str()); + StorageTk::initHashPaths(*buddyMirrorInodesPath, + META_INODES_LEVEL1_SUBDIR_NUM, META_INODES_LEVEL2_SUBDIR_NUM); + + // raise file descriptor limit + if(cfg->getTuneProcessFDLimit() ) + { + uint64_t oldLimit; + + bool setFDLimitRes = System::incProcessFDLimit(cfg->getTuneProcessFDLimit(), &oldLimit); + if(!setFDLimitRes) + log->log(Log_CRITICAL, std::string("Unable to increase process resource limit for " + "number of file handles. Proceeding with default limit: ") + + StringTk::uintToStr(oldLimit) + " " + + "(SysErr: " + System::getErrString() + ")"); + } +} + +void App::initXAttrLimit() +{ + // check whether the filesystem supports overly many amounts of xattrs (>64kb list size). + // of the filesystems we support, this is currently only xfs. + // also check for filesystems mounted beneath the metadata root dir, if any are found, limit the + // xattrs too (it's probably not worth it to check the fs types here, since the setup should be + // rare.) + if (!cfg->getStoreUseExtendedAttribs()) + return; + + cfg->setLimitXAttrListLength(true); + + struct statfs metaRootStat; + + if (::statfs(cfg->getStoreMetaDirectory().c_str(), &metaRootStat)) + { + LOG(GENERAL, CRITICAL, "Could not statfs() meta root directory.", sysErr); + throw InvalidConfigException("Could not statfs() meta root directory."); + } + + // ext3 and ext4 have the same magic, and are currently the only "safe" filesystems officially + // supported. + if (metaRootStat.f_type == EXT3_SUPER_MAGIC) + cfg->setLimitXAttrListLength(false); + else + { + LOG(GENERAL, NOTICE, "Limiting number of xattrs per inode."); + return; + } + + // the metadata root directory does not support overly long xattrs. check for filesystems mounted + // beneath the metadata root, and enable xattrs limiting if any are found. + std::string metaRootPath(PATH_MAX, '\0'); + + if (!realpath(cfg->getStoreMetaDirectory().c_str(), &metaRootPath[0])) + { + LOG(GENERAL, CRITICAL, "Could not check meta root dir for xattr compatibility.", sysErr); + throw InvalidConfigException("Could not check meta root dir for xattr compatibility."); + } + + metaRootPath.resize(strlen(metaRootPath.c_str())); + metaRootPath += '/'; + + FILE* mounts = setmntent("/etc/mtab", "r"); + if (!mounts) + { + LOG(GENERAL, CRITICAL, "Could not open mtab.", sysErr); + throw InvalidConfigException("Could not open mtab."); + } + + struct mntent mountBuf; + char buf[PATH_MAX * 4]; + + struct mntent* mount; + + errno = 0; + while ((mount = getmntent_r(mounts, &mountBuf, buf, sizeof(buf)))) + { + if (strstr(mount->mnt_dir, metaRootPath.c_str()) == mount->mnt_dir) + { + cfg->setLimitXAttrListLength(true); + break; + } + } + + endmntent(mounts); + + if (errno) + { + LOG(GENERAL, ERR, "Could not read mtab.", sysErr); + throw InvalidConfigException("Could not read mtab."); + } + + if (cfg->getLimitXAttrListLength()) + LOG(GENERAL, NOTICE, "Limiting number of xattrs per inode."); +} + +void App::initRootDir(NumNodeID localNodeNumID) +{ + // try to load root dir from disk (through metaStore) or create a new one + + this->metaStore = new MetaStore(); + + // try to reference root directory with buddy mirroring + rootDir = this->metaStore->referenceDir(META_ROOTDIR_ID_STR, true, true); + + // if that didn't work try to reference non-buddy-mirrored root dir + if (!rootDir) + { + rootDir = this->metaStore->referenceDir(META_ROOTDIR_ID_STR, false, true); + } + + if(rootDir) + { // loading succeeded (either with or without mirroring => init rootNodeID + this->log->log(Log_NOTICE, "Root directory loaded."); + + NumNodeID rootDirOwner = rootDir->getOwnerNodeID(); + bool rootIsBuddyMirrored = rootDir->getIsBuddyMirrored(); + + // try to set rootDirOwner as root node + if (rootDirOwner && metaRoot.setIfDefault(rootDirOwner, rootIsBuddyMirrored)) + { // new root node accepted (check if rootNode is localNode) + NumNodeID primaryRootDirOwner; + if (rootIsBuddyMirrored) + primaryRootDirOwner = NumNodeID( + metaBuddyGroupMapper->getPrimaryTargetID(rootDirOwner.val() ) ); + else + primaryRootDirOwner = rootDirOwner; + + if(localNodeNumID == primaryRootDirOwner) + { + log->log(Log_CRITICAL, "I got root (by possession of root directory)"); + if (rootIsBuddyMirrored) + log->log(Log_CRITICAL, "Root directory is mirrored"); + } + else + log->log(Log_CRITICAL, + "Root metadata server (by possession of root directory): " + rootDirOwner.str()); + } + } + else + { // failed to load root directory => create a new rootDir (not mirrored) + this->log->log(Log_CRITICAL, + "This appears to be a new storage directory. Creating a new root dir."); + + UInt16Vector stripeTargets; + unsigned defaultChunkSize = this->cfg->getTuneDefaultChunkSize(); + unsigned defaultNumStripeTargets = this->cfg->getTuneDefaultNumStripeTargets(); + Raid0Pattern stripePattern(defaultChunkSize, stripeTargets, defaultNumStripeTargets); + + DirInode newRootDir(META_ROOTDIR_ID_STR, + S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO, 0, 0, NumNodeID(), stripePattern, false); + + this->metaStore->makeDirInode(newRootDir); + this->rootDir = this->metaStore->referenceDir(META_ROOTDIR_ID_STR, false, true); + + if(!this->rootDir) + { // error + this->log->logErr("Failed to store root directory. Unable to proceed."); + throw InvalidConfigException("Failed to store root directory"); + } + } + +} + +void App::initDisposalDir() +{ + // try to load disposal dir from disk (through metaStore) or create a new one + + this->disposalDir = this->metaStore->referenceDir(META_DISPOSALDIR_ID_STR, false, true); + if(this->disposalDir) + { // loading succeeded + this->log->log(Log_DEBUG, "Disposal directory loaded."); + } + else + { // failed to load disposal directory => create a new one + this->log->log(Log_DEBUG, "Creating a new disposal directory."); + + UInt16Vector stripeTargets; + unsigned defaultChunkSize = this->cfg->getTuneDefaultChunkSize(); + unsigned defaultNumStripeTargets = this->cfg->getTuneDefaultNumStripeTargets(); + Raid0Pattern stripePattern(defaultChunkSize, stripeTargets, defaultNumStripeTargets); + + DirInode newDisposalDir(META_DISPOSALDIR_ID_STR, + S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO, 0, 0, NumNodeID(), stripePattern, false); + + this->metaStore->makeDirInode(newDisposalDir); + this->disposalDir = this->metaStore->referenceDir(META_DISPOSALDIR_ID_STR, false, true); + + if(!this->disposalDir) + { // error + this->log->logErr("Failed to store disposal directory. Unable to proceed."); + throw InvalidConfigException("Failed to store disposal directory"); + } + + } + + buddyMirrorDisposalDir = metaStore->referenceDir(META_MIRRORDISPOSALDIR_ID_STR, true, true); + if(buddyMirrorDisposalDir) + { // loading succeeded + log->log(Log_DEBUG, "Mirrored disposal directory loaded."); + } + else + { // failed to load disposal directory => create a new one + log->log(Log_DEBUG, "Creating a new mirrored disposal directory."); + + UInt16Vector stripeTargets; + unsigned defaultChunkSize = cfg->getTuneDefaultChunkSize(); + unsigned defaultNumStripeTargets = cfg->getTuneDefaultNumStripeTargets(); + Raid0Pattern stripePattern(defaultChunkSize, stripeTargets, defaultNumStripeTargets); + + DirInode newDisposalDir(META_MIRRORDISPOSALDIR_ID_STR, + S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO, 0, 0, NumNodeID(), stripePattern, true); + + metaStore->makeDirInode(newDisposalDir); + buddyMirrorDisposalDir = metaStore->referenceDir(META_MIRRORDISPOSALDIR_ID_STR, true, true); + + if(!buddyMirrorDisposalDir) + { // error + log->logErr("Failed to store mirrored disposal directory. Unable to proceed."); + throw InvalidConfigException("Failed to store mirrored disposal directory"); + } + + } + +} + +void App::initComponents(TargetConsistencyState initialConsistencyState) +{ + this->log->log(Log_DEBUG, "Initializing components..."); + + NicAddressList nicList = getLocalNicList(); + this->dgramListener = new DatagramListener( + netFilter, nicList, ackStore, cfg->getConnMetaPort(), + this->cfg->getConnRestrictOutboundInterfaces() ); + if(cfg->getTuneListenerPrioShift() ) + dgramListener->setPriorityShift(cfg->getTuneListenerPrioShift() ); + + streamListenersInit(); + + unsigned short listenPort = cfg->getConnMetaPort(); + + this->connAcceptor = new ConnAcceptor(this, nicList, listenPort); + + this->statsCollector = new StatsCollector(workQueue, STATSCOLLECTOR_COLLECT_INTERVAL_MS, + STATSCOLLECTOR_HISTORY_LENGTH); + + this->buddyResyncer = new BuddyResyncer(); + + this->internodeSyncer = new InternodeSyncer(initialConsistencyState); + + this->modificationEventFlusher = new ModificationEventFlusher(); + + workersInit(); + commSlavesInit(); + + this->log->log(Log_DEBUG, "Components initialized."); +} + +void App::startComponents() +{ + log->log(Log_DEBUG, "Starting up components..."); + + // make sure child threads don't receive SIGINT/SIGTERM (blocked signals are inherited) + PThread::blockInterruptSignals(); + + timerQueue->start(); + gcQueue->start(); + + this->dgramListener->start(); + + // wait for nodes list download before we start handling client requests + + PThread::unblockInterruptSignals(); // temporarily unblock interrupt, so user can cancel waiting + + PThread::blockInterruptSignals(); // reblock signals for next child threads + + streamListenersStart(); + + this->connAcceptor->start(); + + this->statsCollector->start(); + + this->internodeSyncer->start(); + + timerQueue->enqueue(std::chrono::minutes(5), + [] { InternodeSyncer::downloadAndSyncClients(true); }); + + this->modificationEventFlusher->start(); + + if(const auto wait = getConfig()->getTuneDisposalGCPeriod()) { + this->gcQueue->enqueue(std::chrono::seconds(wait), disposalGarbageCollector); + } + + workersStart(); + commSlavesStart(); + + PThread::unblockInterruptSignals(); // main app thread may receive SIGINT/SIGTERM + + log->log(Log_DEBUG, "Components running."); +} + +void App::stopComponents() +{ + + SAFE_DELETE(this->gcQueue); + + // note: this method may not wait for termination of the components, because that could + // lead to a deadlock (when calling from signal handler) + + // note: no commslave stop here, because that would keep workers from terminating + + if(modificationEventFlusher) // The modificationEventFlusher has to be stopped before the + // workers, because it tries to notify all the workers about the + // changed modification state. + modificationEventFlusher->selfTerminate(); + + // resyncer wants to control the workers, so any running resync must be finished or aborted + // before the workers are stopped. + if(buddyResyncer) + buddyResyncer->shutdown(); + + workersStop(); + + if(internodeSyncer) + internodeSyncer->selfTerminate(); + + if(statsCollector) + statsCollector->selfTerminate(); + + if(connAcceptor) + connAcceptor->selfTerminate(); + + streamListenersStop(); + + if(dgramListener) + { + dgramListener->selfTerminate(); + dgramListener->sendDummyToSelfUDP(); // for faster termination + } + + this->selfTerminate(); /* this flag can be noticed by thread-independent methods and is also + required e.g. to let App::waitForMgmtNode() know that it should cancel */ +} + +/** + * Handles expections that lead to the termination of a component. + * Initiates an application shutdown. + */ +void App::handleComponentException(std::exception& e) +{ + const char* logContext = "App (component exception handler)"; + LogContext log(logContext); + + const auto componentName = PThread::getCurrentThreadName(); + + log.logErr( + "The component [" + componentName + "] encountered an unrecoverable error. " + + std::string("[SysErr: ") + System::getErrString() + "] " + + std::string("Exception message: ") + e.what() ); + + log.log(Log_WARNING, "Shutting down..."); + + stopComponents(); +} + +/** + * Called when a network device failure has been detected. + */ +void App::handleNetworkInterfaceFailure(const std::string& devname) +{ + LOG(GENERAL, ERR, "Network interface failure.", + ("Device", devname)); + internodeSyncer->setForceCheckNetwork(); +} + +void App::updateLocalNicList(NicAddressList& localNicList) +{ + std::vector allNodes({ mgmtNodes, metaNodes, storageNodes, clientNodes}); + updateLocalNicListAndRoutes(log, localNicList, allNodes); + localNode->updateInterfaces(0, 0, localNicList); + dgramListener->setLocalNicList(localNicList); + connAcceptor->updateLocalNicList(localNicList); +} + +void App::joinComponents() +{ + log->log(Log_DEBUG, "Joining component threads..."); + + /* (note: we need one thread for which we do an untimed join, so this should be a quite reliably + terminating thread) */ + statsCollector->join(); + + workersJoin(); + + waitForComponentTermination(modificationEventFlusher); + waitForComponentTermination(dgramListener); + waitForComponentTermination(connAcceptor); + + streamListenersJoin(); + + waitForComponentTermination(internodeSyncer); + + commSlavesStop(); // placed here because otherwise it would keep workers from terminating + commSlavesJoin(); +} + +void App::streamListenersInit() +{ + this->numStreamListeners = cfg->getTuneNumStreamListeners(); + + for(unsigned i=0; i < numStreamListeners; i++) + { + StreamListenerV2* listener = new StreamListenerV2( + std::string("StreamLis") + StringTk::uintToStr(i+1), this, workQueue); + + if(cfg->getTuneListenerPrioShift() ) + listener->setPriorityShift(cfg->getTuneListenerPrioShift() ); + + if(cfg->getTuneUseAggressiveStreamPoll() ) + listener->setUseAggressivePoll(); + + streamLisVec.push_back(listener); + } +} + +void App::workersInit() +{ + unsigned numWorkers = cfg->getTuneNumWorkers(); + + for(unsigned i=0; i < numWorkers; i++) + { + Worker* worker = new Worker( + std::string("Worker") + StringTk::uintToStr(i+1), workQueue, QueueWorkType_INDIRECT); + + worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); + + workerList.push_back(worker); + } + + for(unsigned i=0; i < APP_WORKERS_DIRECT_NUM; i++) + { + Worker* worker = new Worker( + std::string("DirectWorker") + StringTk::uintToStr(i+1), workQueue, QueueWorkType_DIRECT); + + worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); + + workerList.push_back(worker); + } +} + +void App::commSlavesInit() +{ + unsigned numCommSlaves = cfg->getTuneNumCommSlaves(); + + for(unsigned i=0; i < numCommSlaves; i++) + { + Worker* worker = new Worker( + std::string("CommSlave") + StringTk::uintToStr(i+1), commSlaveQueue, QueueWorkType_DIRECT); + + worker->setBufLens(cfg->getTuneCommSlaveBufSize(), cfg->getTuneCommSlaveBufSize() ); + + commSlaveList.push_back(worker); + } +} + +void App::streamListenersStart() +{ + unsigned numNumaNodes = System::getNumNumaNodes(); + + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + if(cfg->getTuneListenerNumaAffinity() ) + (*iter)->startOnNumaNode( (++nextNumaBindTarget) % numNumaNodes); + else + (*iter)->start(); + } +} + +void App::workersStart() +{ + unsigned numNumaNodes = System::getNumNumaNodes(); + + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + if(cfg->getTuneWorkerNumaAffinity() ) + (*iter)->startOnNumaNode( (++nextNumaBindTarget) % numNumaNodes); + else + (*iter)->start(); + } +} + +void App::commSlavesStart() +{ + unsigned numNumaNodes = System::getNumNumaNodes(); + + for(WorkerListIter iter = commSlaveList.begin(); iter != commSlaveList.end(); iter++) + { + if(cfg->getTuneWorkerNumaAffinity() ) + (*iter)->startOnNumaNode( (++nextNumaBindTarget) % numNumaNodes); + else + (*iter)->start(); + } +} + +void App::streamListenersStop() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + (*iter)->selfTerminate(); + } +} + +void App::workersStop() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + (*iter)->selfTerminate(); + + // add dummy work to wake up the worker immediately for faster self termination + PersonalWorkQueue* personalQ = (*iter)->getPersonalWorkQueue(); + workQueue->addPersonalWork(new DummyWork(), personalQ); + } +} + +void App::commSlavesStop() +{ + // need two loops because we don't know if the worker that handles the work will be the same that + // received the self-terminate-request + for(WorkerListIter iter = commSlaveList.begin(); iter != commSlaveList.end(); iter++) + { + (*iter)->selfTerminate(); + } + + for(WorkerListIter iter = commSlaveList.begin(); iter != commSlaveList.end(); iter++) + { + commSlaveQueue->addDirectWork(new DummyWork() ); + } +} + +void App::streamListenersDelete() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + delete(*iter); + } + + streamLisVec.clear(); +} + +void App::workersDelete() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + delete(*iter); + } + + workerList.clear(); +} + +void App::commSlavesDelete() +{ + for(WorkerListIter iter = commSlaveList.begin(); iter != commSlaveList.end(); iter++) + { + delete(*iter); + } + + commSlaveList.clear(); +} + +void App::streamListenersJoin() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + waitForComponentTermination(*iter); + } +} + +void App::workersJoin() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + waitForComponentTermination(*iter); + } +} + +void App::commSlavesJoin() +{ + for(WorkerListIter iter = commSlaveList.begin(); iter != commSlaveList.end(); iter++) + { + waitForComponentTermination(*iter); + } +} + +void App::logInfos() +{ + // print software version (BEEGFS_VERSION) + log->log(Log_CRITICAL, std::string("Version: ") + BEEGFS_VERSION); + + // print debug version info + LOG_DEBUG_CONTEXT(*log, Log_CRITICAL, "--DEBUG VERSION--"); + + // print local nodeIDs + log->log(Log_WARNING, "LocalNode: " + localNode->getNodeIDWithTypeStr() ); + + // list usable network interfaces + NicAddressList nicList = getLocalNicList(); + logUsableNICs(log, nicList); + + // print net filters + if(netFilter->getNumFilterEntries() ) + { + log->log(Log_WARNING, std::string("Net filters: ") + + StringTk::uintToStr(netFilter->getNumFilterEntries() ) ); + } + + if(tcpOnlyFilter->getNumFilterEntries() ) + { + this->log->log(Log_WARNING, std::string("TCP-only filters: ") + + StringTk::uintToStr(tcpOnlyFilter->getNumFilterEntries() ) ); + } + + // print numa info + // (getTuneBindToNumaZone==-1 means disable binding) + if(cfg->getTuneListenerNumaAffinity() || cfg->getTuneWorkerNumaAffinity() || + (cfg->getTuneBindToNumaZone() != -1) ) + { + unsigned numNumaNodes = System::getNumNumaNodes(); + + /* note: we use the term "numa areas" instead of "numa nodes" in log messages to avoid + confusion with cluster "nodes" */ + + log->log(Log_NOTICE, std::string("NUMA areas: ") + StringTk::uintToStr(numNumaNodes) ); + + for(unsigned nodeNum=0; nodeNum < numNumaNodes; nodeNum++) + { // print core list for each numa node + cpu_set_t cpuSet; + + System::getNumaCoresByNode(nodeNum, &cpuSet); + + // create core list for current numa node + + std::string coreListStr; + for(unsigned coreNum = 0; coreNum < CPU_SETSIZE; coreNum++) + { + if(CPU_ISSET(coreNum, &cpuSet) ) + coreListStr += StringTk::uintToStr(coreNum) + " "; + } + + log->log(Log_SPAM, "NUMA area " + StringTk::uintToStr(nodeNum) + " cores: " + coreListStr); + } + } +} + +void App::daemonize() +{ + int nochdir = 1; // 1 to keep working directory + int noclose = 0; // 1 to keep stdin/-out/-err open + + log->log(Log_DEBUG, std::string("Detaching process...") ); + + int detachRes = daemon(nochdir, noclose); + if(detachRes == -1) + throw InvalidConfigException(std::string("Unable to detach process. SysErr: ") + + System::getErrString() ); + + updateLockedPIDFile(pidFileLockFD); // ignored if pidFileFD is -1 +} + +void App::registerSignalHandler() +{ + signal(SIGINT, App::signalHandler); + signal(SIGTERM, App::signalHandler); +} + +void App::signalHandler(int sig) +{ + App* app = Program::getApp(); + + Logger* log = Logger::getLogger(); + const char* logContext = "App::signalHandler"; + + // note: this might deadlock if the signal was thrown while the logger mutex is locked by the + // application thread (depending on whether the default mutex style is recursive). but + // even recursive mutexes are not acceptable in this case. + // we need something like a timed lock for the log mutex. if it succeeds within a + // few seconds, we know that we didn't hold the mutex lock. otherwise we simply skip the + // log message. this will only work if the mutex is non-recusive (which is unknown for + // the default mutex style). + // but it is very unlikely that the application thread holds the log mutex, because it + // joins the component threads and so it doesn't do anything else but sleeping! + + switch(sig) + { + case SIGINT: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(Log_CRITICAL, logContext, "Received a SIGINT. Shutting down..."); + } break; + + case SIGTERM: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(Log_CRITICAL, logContext, "Received a SIGTERM. Shutting down..."); + } break; + + default: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(Log_CRITICAL, logContext, "Received an unknown signal. Shutting down..."); + } break; + } + + app->stopComponents(); +} + +/** + * Request mgmt heartbeat and wait for the mgmt node to appear in nodestore. + * + * @return true if mgmt heartbeat received, false on error or thread selftermination order + */ +bool App::waitForMgmtNode() +{ + const unsigned waitTimeoutMS = 0; // infinite wait + const unsigned nameResolutionRetries = 3; + + unsigned udpListenPort = cfg->getConnMetaPort(); + unsigned udpMgmtdPort = cfg->getConnMgmtdPort(); + std::string mgmtdHost = cfg->getSysMgmtdHost(); + NicAddressList nicList = getLocalNicList(); + + RegistrationDatagramListener regDGramLis(netFilter, nicList, ackStore, udpListenPort, + this->cfg->getConnRestrictOutboundInterfaces()); + regDGramLis.start(); + + log->log(Log_CRITICAL, "Waiting for beegfs-mgmtd@" + + mgmtdHost + ":" + StringTk::uintToStr(udpMgmtdPort) + "..."); + + + bool gotMgmtd = NodesTk::waitForMgmtHeartbeat( + this, ®DGramLis, mgmtNodes, mgmtdHost, udpMgmtdPort, waitTimeoutMS, nameResolutionRetries); + + regDGramLis.selfTerminate(); + regDGramLis.sendDummyToSelfUDP(); // for faster termination + + regDGramLis.join(); + + return gotMgmtd; +} + +/** + * Pre-register node to get a numeric ID from mgmt. + * + * @return true if pre-registration successful and localNodeNumID set. + */ +bool App::preregisterNode(NumNodeID& outLocalNodeNumID) +{ + const char* logContext = "Preregister node"; + static bool registrationFailureLogged = false; // to avoid log spamming + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + { + LogContext(logContext).logErr( + "Unexpected: No management node found in store during node pre-registration."); + return false; + } + + NumNodeID rootNodeID = metaRoot.getID(); + NicAddressList nicList = getLocalNicList(); + // In BeeGFS 8 string IDs were replaced with aliases. The mgmtd now ignores the alias provided in + // the RegisterNodeMsg for meta nodes so just it can just be set to an empty string. It will be + // set later on for the local node as part of downloadMgmtInfo(). + RegisterNodeMsg msg("", outLocalNodeNumID, NODETYPE_Meta, &nicList, + cfg->getConnMetaPort(), cfg->getConnMetaPort() ); + msg.setRootNumID(rootNodeID); + auto uuid = UUID::getMachineUUID(); + if (uuid.empty()) { + LogContext(logContext).log(Log_CRITICAL, + "Couldn't determine UUID for machine. Node registration not possible."); + return false; + } + msg.setMachineUUID(uuid); + + Time startTime; + Time lastRetryTime; + unsigned nextRetryDelayMS = 0; + + // wait for mgmt node to appear and periodically resend request + /* note: we usually expect not to loop here, because we already waited for mgmtd in + waitForMgmtNode(), so mgmt should respond immediately. */ + + while(!outLocalNodeNumID && !getSelfTerminate() ) + { + if(lastRetryTime.elapsedMS() < nextRetryDelayMS) + { // wait some time between retries + waitForSelfTerminateOrder(nextRetryDelayMS); + if(getSelfTerminate() ) + break; + } + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_RegisterNodeResp); + + if (respMsg) + { // communication successful + RegisterNodeRespMsg* respMsgCast = (RegisterNodeRespMsg*)respMsg.get(); + + outLocalNodeNumID = respMsgCast->getNodeNumID(); + + if(!outLocalNodeNumID) + { // mgmt rejected our registration + LogContext(logContext).logErr( + "ID reservation request was rejected by this management node: " + + mgmtNode->getTypedNodeID() ); + } + else + LogContext(logContext).log(Log_WARNING, "Node ID reservation successful."); + + break; + } + + // comm failed => log status message + + if(!registrationFailureLogged) + { + LogContext(logContext).log(Log_CRITICAL, + "Node ID reservation failed. Management node offline? Will keep on trying..."); + registrationFailureLogged = true; + } + + // calculate next retry wait time + + lastRetryTime.setToNow(); + nextRetryDelayMS = NodesTk::getRetryDelayMS(startTime.elapsedMS() ); + } + + return bool(outLocalNodeNumID); +} + +/** + * Downloads the list of nodes, targets and buddy groups (for meta and storage servers) from the + * mgmtd. + * + * @param outInitialConsistencyState The consistency state the local meta node has on the mgmtd + * before any state reports are sent. + */ +bool App::downloadMgmtInfo(TargetConsistencyState& outInitialConsistencyState) +{ + Config* cfg = this->getConfig(); + + int retrySleepTimeMS = 10000; // 10sec + unsigned udpListenPort = cfg->getConnMetaPort(); + bool allSuccessful = false; + NicAddressList nicList = getLocalNicList(); + + // start temporary registration datagram listener + RegistrationDatagramListener regDGramLis(netFilter, nicList, ackStore, udpListenPort, + this->cfg->getConnRestrictOutboundInterfaces() ); + regDGramLis.start(); + + // loop until we're registered and everything is downloaded (or until we got interrupted) + do + { + // register ourselves + // (note: node registration needs to be done before downloads to get notified of updates) + if (!InternodeSyncer::registerNode(®DGramLis) ) + continue; + + // download all mgmt info the HBM cares for + if (!InternodeSyncer::downloadAndSyncNodes() || + !InternodeSyncer::downloadAndSyncTargetMappings() || + !InternodeSyncer::downloadAndSyncStoragePools() || + !InternodeSyncer::downloadAndSyncTargetStatesAndBuddyGroups() || + !InternodeSyncer::updateMetaCapacityPools() || + !InternodeSyncer::updateMetaBuddyCapacityPools()) + continue; + + InternodeSyncer::downloadAndSyncClients(false); + + // ...and then the InternodeSyncer's part. + if (!InternodeSyncer::updateMetaStatesAndBuddyGroups(outInitialConsistencyState, false) ) + continue; + + if(!InternodeSyncer::downloadAllExceededQuotaLists(storagePoolStore->getPoolsAsVec())) + continue; + + allSuccessful = true; + break; + + } while(!waitForSelfTerminateOrder(retrySleepTimeMS) ); + + // stop temporary registration datagram listener + regDGramLis.selfTerminate(); + regDGramLis.sendDummyToSelfUDP(); // for faster termination + + regDGramLis.join(); + + if(allSuccessful) + log->log(Log_NOTICE, "Registration and management info download complete."); + + return allSuccessful; +} + +bool App::restoreSessions() +{ + bool retVal = true; + + std::string path = this->metaPathStr + "/" + std::string(STORAGETK_SESSIONS_BACKUP_FILE_NAME); + std::string mpath = this->metaPathStr + "/" + std::string(STORAGETK_MSESSIONS_BACKUP_FILE_NAME); + + bool pathRes = StorageTk::pathExists(path); + bool mpathRes = StorageTk::pathExists(mpath); + if (!pathRes && !mpathRes) + return false; + + if (pathRes) + { + bool loadRes = this->sessions->loadFromFile(path, *metaStore); + if (!loadRes) + { + log->logErr("Could not restore all sessions"); + retVal = false; + } + } + + if (mpathRes) + { + bool loadRes = this->mirroredSessions->loadFromFile(mpath, *metaStore); + if (!loadRes) + { + log->logErr("Could not restore all mirrored sessions"); + retVal = false; + } + } + + log->log(Log_NOTICE, "Restored " + + StringTk::uintToStr(sessions->getSize()) + " sessions and " + + StringTk::uintToStr(mirroredSessions->getSize()) + " mirrored sessions"); + + return retVal; +} + +bool App::storeSessions() +{ + bool retVal = true; + + std::string path = this->metaPathStr + "/" + std::string(STORAGETK_SESSIONS_BACKUP_FILE_NAME); + std::string mpath = this->metaPathStr + "/" + std::string(STORAGETK_MSESSIONS_BACKUP_FILE_NAME); + + if (StorageTk::pathExists(path)) + log->log(Log_WARNING, "Overwriting existing session file"); + + bool saveRes = this->sessions->saveToFile(path); + if(!saveRes) + { + this->log->logErr("Could not store all sessions to file " + path); + retVal = false; + } + + if (StorageTk::pathExists(mpath)) + log->log(Log_WARNING, "Overwriting existing mirror session file"); + + saveRes = this->mirroredSessions->saveToFile(mpath); + if(!saveRes) + { + this->log->logErr("Could not store all mirror sessions to file " + mpath); + retVal = false; + } + + if (retVal) + log->log(Log_NOTICE, "Stored " + + StringTk::uintToStr(sessions->getSize()) + " sessions and " + + StringTk::uintToStr(mirroredSessions->getSize()) + " mirrored sessions"); + + return retVal; +} + +bool App::deleteSessionFiles() +{ + bool retVal = true; + + std::string path = this->metaPathStr + "/" + std::string(STORAGETK_SESSIONS_BACKUP_FILE_NAME); + std::string mpath = this->metaPathStr + "/" + std::string(STORAGETK_MSESSIONS_BACKUP_FILE_NAME); + + bool pathRes = StorageTk::pathExists(path); + bool mpathRes = StorageTk::pathExists(mpath); + if (!pathRes && !mpathRes) + return retVal; + + if (pathRes && remove(path.c_str())) + { + log->logErr("Could not remove sessions file"); + retVal = false; + } + + if (mpathRes && remove(mpath.c_str())) + { + log->logErr("Could not remove mirrored sessions file"); + retVal = false; + } + + return retVal; +} + +void App::checkTargetUUID() +{ + if (!cfg->getStoreFsUUID().empty()) + { + Path metaPath(cfg->getStoreMetaDirectory() ); + + auto uuid_str = UUID::getFsUUID(metaPath.str()); + + if (cfg->getStoreFsUUID() != uuid_str) + { + throw InvalidConfigException("UUID of the metadata file system (" + uuid_str + + ") does not match the one configured (" + cfg->getStoreFsUUID() + ")"); + } + } + else + { + LOG(GENERAL, WARNING, "UUID of underlying file system has not been configured and will " + "therefore not be checked. To prevent starting the server accidentally with the wrong " + "data, it is strongly recommended to set the storeFsUUID config parameter to " + "the appropriate UUID."); + } + +} diff --git a/meta/source/app/App.h b/meta/source/app/App.h new file mode 100644 index 0000000..39598de --- /dev/null +++ b/meta/source/app/App.h @@ -0,0 +1,511 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#ifndef BEEGFS_VERSION + #error BEEGFS_VERSION undefined +#endif + +// program return codes +#define APPCODE_NO_ERROR 0 +#define APPCODE_INVALID_CONFIG 1 +#define APPCODE_INITIALIZATION_ERROR 2 +#define APPCODE_RUNTIME_ERROR 3 + + +typedef std::list WorkerList; +typedef WorkerList::iterator WorkerListIter; + +typedef std::vector StreamLisVec; +typedef StreamLisVec::iterator StreamLisVecIter; + + +// forward declarations +class LogContext; +class ModificationEventFlusher; + + +class App : public AbstractApp +{ + public: + App(int argc, char** argv); + virtual ~App(); + + virtual void run() override; + + virtual void stopComponents() override; + virtual void handleComponentException(std::exception& e) override; + virtual void handleNetworkInterfaceFailure(const std::string& devname) override; + + void handleNetworkInterfacesChanged(NicAddressList nicList); + + + private: + int appResult; + int argc; + char** argv; + + Config* cfg; + LogContext* log; + std::list allowedInterfaces; + + LockFD pidFileLockFD; + LockFD workingDirLockFD; + + NetFilter* netFilter; // empty filter means "all nets allowed" + NetFilter* tcpOnlyFilter; // for IPs that allow only plain TCP (no RDMA etc) + std::shared_ptr localNode; + + NodeStoreServers* mgmtNodes; + NodeStoreServers* metaNodes; + NodeStoreServers* storageNodes; + NodeStoreClients* clientNodes; + + RootInfo metaRoot; + + NodeCapacityPools* metaCapacityPools; + NodeCapacityPools* metaBuddyCapacityPools; + + TargetMapper* targetMapper; + MirrorBuddyGroupMapper* storageBuddyGroupMapper; // maps storage targets to buddy groups + MirrorBuddyGroupMapper* metaBuddyGroupMapper; // maps meta nodes to buddy groups + TargetStateStore* targetStateStore; // map storage targets to a state + TargetStateStore* metaStateStore; // map mds targets (i.e. nodeIDs) to a state + std::unique_ptr storagePoolStore; // stores (category) storage pools + + MultiWorkQueue* workQueue; + MultiWorkQueue* commSlaveQueue; + NetMessageFactory* netMessageFactory; + MetaStore* metaStore; + + DirInode* rootDir; + bool isRootBuddyMirrored; + DirInode* disposalDir; + DirInode* buddyMirrorDisposalDir; + + SessionStore* sessions; + SessionStore* mirroredSessions; + AcknowledgmentStore* ackStore; + MetaNodeOpStats* nodeOperationStats; // file system operation statistics + + std::string metaPathStr; // the general parent directory for all saved data + Path* inodesPath; // contains the actualy file/directory metadata + Path* dentriesPath; // contains the file/directory structural links + Path* buddyMirrorInodesPath; // contains the inodes for buddy mirrored inodes + Path* buddyMirrorDentriesPath; // contains the dentries for buddy mirrored dentries + + DatagramListener* dgramListener; + ConnAcceptor* connAcceptor; + StatsCollector* statsCollector; + InternodeSyncer* internodeSyncer; + ModificationEventFlusher* modificationEventFlusher; + TimerQueue* timerQueue; + TimerQueue* gcQueue; + + unsigned numStreamListeners; // value copied from cfg (for performance) + StreamLisVec streamLisVec; + + WorkerList workerList; + WorkerList commSlaveList; // used by workers for parallel comm tasks + + BuddyResyncer* buddyResyncer; + + ExceededQuotaPerTarget exceededQuotaStores; + + std::unique_ptr fileEventLogger { nullptr, &destroyFileEventLogger }; + + unsigned nextNumaBindTarget; // the numa node to which we will bind the next component thread + + void runNormal(); + + void streamListenersInit(); + void streamListenersStart(); + void streamListenersStop(); + void streamListenersDelete(); + void streamListenersJoin(); + + void workersInit(); + void workersStart(); + void workersStop(); + void workersDelete(); + void workersJoin(); + + void commSlavesInit(); + void commSlavesStart(); + void commSlavesStop(); + void commSlavesDelete(); + void commSlavesJoin(); + + void initLogging(); + void initDataObjects(); + void initBasicNetwork(); + void initLocalNodeIDs(NumNodeID& outLocalNumID); + void initLocalNode(NumNodeID localNodeNumID); + void initLocalNodeNumIDFile(NumNodeID localNodeNumID); + bool preinitStorage(); + void checkTargetUUID(); + void initStorage(); + void initXAttrLimit(); + void initRootDir(NumNodeID localNodeNumID); + void initDisposalDir(); + void initComponents(TargetConsistencyState initialConsistencyState); + + void startComponents(); + void joinComponents(); + + bool waitForMgmtNode(); + bool preregisterNode(NumNodeID& outLocalNodeNumID); + bool downloadMgmtInfo(TargetConsistencyState& outInitialConsistencyState); + + void logInfos(); + + void daemonize(); + + void registerSignalHandler(); + static void signalHandler(int sig); + + bool restoreSessions(); + bool storeSessions(); + bool deleteSessionFiles(); + + + public: + // inliners + + /** + * @return NULL for invalid node types + */ + NodeStoreServers* getServerStoreFromType(NodeType nodeType) const + { + switch(nodeType) + { + case NODETYPE_Meta: + return metaNodes; + + case NODETYPE_Storage: + return storageNodes; + + case NODETYPE_Mgmt: + return mgmtNodes; + + default: + return NULL; + } + } + + /** + * @return NULL for invalid node types + */ + AbstractNodeStore* getAbstractNodeStoreFromType(NodeType nodeType) const + { + switch(nodeType) + { + case NODETYPE_Meta: + return metaNodes; + + case NODETYPE_Storage: + return storageNodes; + + case NODETYPE_Client: + return clientNodes; + + case NODETYPE_Mgmt: + return mgmtNodes; + + default: + return NULL; + } + } + + /** + * Get one of the available stream listeners based on the socket file descriptor number. + * This is to load-balance the sockets over all available stream listeners and ensure that + * sockets are not bouncing between different stream listeners. + * + * Note that IB connections eat two fd numbers, so 2 and multiples of 2 might not be a good + * value for number of stream listeners. + */ + virtual StreamListenerV2* getStreamListenerByFD(int fd) override + { + return streamLisVec[fd % numStreamListeners]; + } + + + // getters & setters + + virtual const ICommonConfig* getCommonConfig() const override + { + return cfg; + } + + virtual const NetFilter* getNetFilter() const override + { + return netFilter; + } + + virtual const NetFilter* getTcpOnlyFilter() const override + { + return tcpOnlyFilter; + } + + virtual const AbstractNetMessageFactory* getNetMessageFactory() const override + { + return netMessageFactory; + } + + AcknowledgmentStore* getAckStore() const + { + return ackStore; + } + + Config* getConfig() const + { + return cfg; + } + + void updateLocalNicList(NicAddressList& localNicList); + + /* + * this is just a convenience wrapper for now; old code used to have the localNodeNumID as a + * member of App, but localNodeNumID and the numID in localNode are duplicates + */ + NumNodeID getLocalNodeNumID() const + { + return localNode->getNumID(); + } + + Node& getLocalNode() const + { + return *localNode; + } + + NodeStoreServers* getMgmtNodes() const + { + return mgmtNodes; + } + + NodeStoreServers* getMetaNodes() const + { + return metaNodes; + } + + NodeStoreServers* getStorageNodes() const + { + return storageNodes; + } + + NodeStoreClients* getClientNodes() const + { + return clientNodes; + } + + NodeCapacityPools* getMetaCapacityPools() const + { + return metaCapacityPools; + } + + TargetMapper* getTargetMapper() const + { + return targetMapper; + } + + MirrorBuddyGroupMapper* getStorageBuddyGroupMapper() const + { + return storageBuddyGroupMapper; + } + + MirrorBuddyGroupMapper* getMetaBuddyGroupMapper() const + { + return metaBuddyGroupMapper; + } + + TargetStateStore* getTargetStateStore() const + { + return targetStateStore; + } + + TargetStateStore* getMetaStateStore() const + { + return metaStateStore; + } + + NodeCapacityPools* getMetaBuddyCapacityPools() const + { + return metaBuddyCapacityPools; + } + + MultiWorkQueue* getWorkQueue() const + { + return workQueue; + } + + MultiWorkQueue* getCommSlaveQueue() const + { + return commSlaveQueue; + } + + MetaStore* getMetaStore() const + { + return metaStore; + } + + DirInode* getRootDir() const + { + return rootDir; + } + + DirInode* getDisposalDir() const + { + return disposalDir; + } + + DirInode* getBuddyMirrorDisposalDir() const + { + return buddyMirrorDisposalDir; + } + + SessionStore* getSessions() const + { + return sessions; + } + + SessionStore* getMirroredSessions() const + { + return mirroredSessions; + } + + std::string getMetaPath() const + { + return metaPathStr; + } + + MetaNodeOpStats* getNodeOpStats() const + { + return nodeOperationStats; + } + + const Path* getInodesPath() const + { + return inodesPath; + } + + const Path* getDentriesPath() const + { + return dentriesPath; + } + + const Path* getBuddyMirrorInodesPath() const + { + return buddyMirrorInodesPath; + } + + const Path* getBuddyMirrorDentriesPath() const + { + return buddyMirrorDentriesPath; + } + + DatagramListener* getDatagramListener() const + { + return dgramListener; + } + + const StreamLisVec* getStreamListenerVec() const + { + return &streamLisVec; + } + + StatsCollector* getStatsCollector() const + { + return statsCollector; + } + + InternodeSyncer* getInternodeSyncer() const + { + return internodeSyncer; + } + + TimerQueue* getTimerQueue() const + { + return timerQueue; + } + + TimerQueue* getGcQueue() const + { + return gcQueue; + } + + ModificationEventFlusher* getModificationEventFlusher() const + { + return modificationEventFlusher; + } + + WorkerList* getWorkers() + { + return &workerList; + } + + BuddyResyncer* getBuddyResyncer() + { + return this->buddyResyncer; + } + + int getAppResult() const + { + return appResult; + } + + const ExceededQuotaPerTarget* getExceededQuotaStores() const + { + return &exceededQuotaStores; + } + + StoragePoolStore* getStoragePoolStore() const + { + return storagePoolStore.get(); + } + + FileEventLogger* getFileEventLogger() + { + return fileEventLogger.get(); + } + + const RootInfo& getMetaRoot() const { return metaRoot; } + RootInfo& getMetaRoot() { return metaRoot; } + + void findAllowedInterfaces(NicAddressList& outList) const; + void findAllowedRDMAInterfaces(NicAddressList& outList) const; +}; + + diff --git a/meta/source/app/config/Config.cpp b/meta/source/app/config/Config.cpp new file mode 100644 index 0000000..f20175f --- /dev/null +++ b/meta/source/app/config/Config.cpp @@ -0,0 +1,326 @@ +#include +#include +#include +#include +#include "Config.h" + +#define CONFIG_DEFAULT_CFGFILENAME "/etc/beegfs/beegfs-meta.conf" + +#define TARGETCHOOSERTYPE_RANDOMIZED_STR "randomized" +#define TARGETCHOOSERTYPE_ROUNDROBIN_STR "roundrobin" +#define TARGETCHOOSERTYPE_RANDOMROBIN_STR "randomrobin" +#define TARGETCHOOSERTYPE_RANDOMINTERNODE_STR "randominternode" +#define TARGETCHOOSERTYPE_RANDOMINTRANODE_STR "randomintranode" + + +Config::Config(int argc, char** argv): + AbstractConfig(argc, argv) +{ + sysTargetAttachmentMap = NULL; + + initConfig(argc, argv, true); +} + +Config::~Config() +{ + SAFE_DELETE(sysTargetAttachmentMap); +} + +/** + * Sets the default values for each configurable in the configMap. + * + * @param addDashes currently unused + */ +void Config::loadDefaults(bool addDashes) +{ + AbstractConfig::loadDefaults(); + + // re-definitions + configMapRedefine("cfgFile", ""); + configMapRedefine("connMaxInternodeNum", "16"); + + // own definitions + configMapRedefine("connInterfacesFile", ""); + configMapRedefine("connInterfacesList", ""); + + configMapRedefine("storeMetaDirectory", ""); + configMapRedefine("storeFsUUID", ""); + configMapRedefine("storeAllowFirstRunInit", "true"); + configMapRedefine("storeUseExtendedAttribs", "true"); + configMapRedefine("storeSelfHealEmptyFiles", "true"); + + configMapRedefine("storeClientXAttrs", "false"); + configMapRedefine("storeClientACLs", "false"); + + configMapRedefine("sysTargetAttachmentFile", ""); + configMapRedefine("tuneNumStreamListeners", "1"); + configMapRedefine("tuneNumWorkers", "0"); + configMapRedefine("tuneWorkerBufSize", "1m"); + configMapRedefine("tuneNumCommSlaves", "0"); + configMapRedefine("tuneCommSlaveBufSize", "1m"); + configMapRedefine("tuneDefaultChunkSize", "512k"); + configMapRedefine("tuneDefaultNumStripeTargets","4"); + configMapRedefine("tuneProcessFDLimit", "50000"); + configMapRedefine("tuneWorkerNumaAffinity", "false"); + configMapRedefine("tuneListenerNumaAffinity", "false"); + configMapRedefine("tuneBindToNumaZone", ""); + configMapRedefine("tuneListenerPrioShift", "-1"); + configMapRedefine("tuneDirMetadataCacheLimit", "1024"); + configMapRedefine("tuneTargetChooser", TARGETCHOOSERTYPE_RANDOMIZED_STR); + configMapRedefine("tuneLockGrantWaitMS", "333"); + configMapRedefine("tuneLockGrantNumRetries", "15"); + configMapRedefine("tuneRotateMirrorTargets", "false"); + configMapRedefine("tuneEarlyUnlinkResponse", "true"); + configMapRedefine("tuneUsePerUserMsgQueues", "false"); + configMapRedefine("tuneUseAggressiveStreamPoll","false"); + configMapRedefine("tuneNumResyncSlaves", "12"); + configMapRedefine("tuneMirrorTimestamps", "true"); + configMapRedefine("tuneDisposalGCPeriod", "0"); + + configMapRedefine("quotaEarlyChownResponse", "true"); + configMapRedefine("quotaEnableEnforcement", "false"); + + configMapRedefine("sysTargetOfflineTimeoutSecs","180"); + configMapRedefine("sysAllowUserSetPattern", "false"); + configMapRedefine("sysFileEventLogTarget", ""); + configMapRedefine("sysFileEventPersistDirectory", ""); + configMapRedefine("sysFileEventPersistSize", "0"); + + configMapRedefine("runDaemonized", "false"); + + configMapRedefine("pidFile", ""); +} + +/** + * @param addDashes currently usused + */ +void Config::applyConfigMap(bool enableException, bool addDashes) +{ + AbstractConfig::applyConfigMap(false); + + for (StringMapIter iter = configMap.begin(); iter != configMap.end();) + { + bool unknownElement = false; + + if (iter->first == std::string("logType")) + { + if (iter->second == "syslog") + { + logType = LogType_SYSLOG; + } + else if (iter->second == "logfile") + { + logType = LogType_LOGFILE; + } + else + { + throw InvalidConfigException("The value of config argument logType is invalid."); + } + } + else if (iter->first == std::string("connInterfacesFile")) + connInterfacesFile = iter->second; + else if (iter->first == std::string("connInterfacesList")) + connInterfacesList = iter->second; + else if (iter->first == std::string("storeMetaDirectory")) + storeMetaDirectory = iter->second; + else if (iter->first == std::string("storeFsUUID")) + storeFsUUID = iter->second; + else if (iter->first == std::string("storeAllowFirstRunInit")) + storeAllowFirstRunInit = StringTk::strToBool(iter->second); + else if (iter->first == std::string("storeUseExtendedAttribs")) + storeUseExtendedAttribs = StringTk::strToBool(iter->second); + else if (iter->first == std::string("storeSelfHealEmptyFiles")) + storeSelfHealEmptyFiles = StringTk::strToBool(iter->second); + else if (iter->first == std::string("storeClientXAttrs")) + storeClientXAttrs = StringTk::strToBool(iter->second); + else if (iter->first == std::string("storeClientACLs")) + storeClientACLs = StringTk::strToBool(iter->second); + else if (iter->first == std::string("sysTargetAttachmentFile")) + sysTargetAttachmentFile = iter->second; + else if (iter->first == std::string("tuneNumStreamListeners")) + tuneNumStreamListeners = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneNumWorkers")) + tuneNumWorkers = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneWorkerBufSize")) + tuneWorkerBufSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneNumCommSlaves")) + tuneNumCommSlaves = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneCommSlaveBufSize")) + tuneCommSlaveBufSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneDefaultChunkSize")) + tuneDefaultChunkSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneDefaultNumStripeNodes")) // old "...Nodes" kept for compat + tuneDefaultNumStripeTargets = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneDefaultNumStripeTargets")) + tuneDefaultNumStripeTargets = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneProcessFDLimit")) + tuneProcessFDLimit = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneWorkerNumaAffinity")) + tuneWorkerNumaAffinity = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneListenerNumaAffinity")) + tuneListenerNumaAffinity = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneBindToNumaZone")) + { + if (iter->second.empty()) // not defined => disable + tuneBindToNumaZone = -1; // -1 means disable binding + else + tuneBindToNumaZone = StringTk::strToInt(iter->second); + } + else if (iter->first == std::string("tuneListenerPrioShift")) + tuneListenerPrioShift = StringTk::strToInt(iter->second); + else if (iter->first == std::string("tuneDirMetadataCacheLimit")) + tuneDirMetadataCacheLimit = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneTargetChooser")) + tuneTargetChooser = iter->second; + else if (iter->first == std::string("tuneLockGrantWaitMS")) + tuneLockGrantWaitMS = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneLockGrantNumRetries")) + tuneLockGrantNumRetries = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneRotateMirrorTargets")) + tuneRotateMirrorTargets = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneEarlyUnlinkResponse")) + tuneEarlyUnlinkResponse = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneUseAggressiveStreamPoll")) + tuneUseAggressiveStreamPoll = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneNumResyncSlaves")) + this->tuneNumResyncSlaves = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("quotaEarlyChownResponse")) + quotaEarlyChownResponse = StringTk::strToBool(iter->second); + else if (iter->first == std::string("quotaEnableEnforcement")) + quotaEnableEnforcement = StringTk::strToBool(iter->second); + else if (iter->first == std::string("sysTargetOfflineTimeoutSecs")) + { + sysTargetOfflineTimeoutSecs = StringTk::strToUInt(iter->second); + + if (sysTargetOfflineTimeoutSecs < 30) + { + throw InvalidConfigException("Invalid sysTargetOfflineTimeoutSecs value " + + iter->second + " (must be at least 30)"); + } + } + else if (iter->first == std::string("sysAllowUserSetPattern")) + sysAllowUserSetPattern = StringTk::strToBool(iter->second.c_str()); + else if (iter->first == std::string("tuneUsePerUserMsgQueues")) + tuneUsePerUserMsgQueues = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneMirrorTimestamps")) + tuneMirrorTimestamps = StringTk::strToBool(iter->second); + else if(iter->first == std::string("tuneDisposalGCPeriod")) + tuneDisposalGCPeriod = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("sysFileEventLogTarget")) + sysFileEventLogTarget = iter->second; + else if (iter->first == std::string("sysFileEventPersistDirectory")) + sysFileEventPersistDirectory = iter->second; + else if (iter->first == std::string("sysFileEventPersistSize")) + sysFileEventPersistSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("runDaemonized")) + runDaemonized = StringTk::strToBool(iter->second); + else if (iter->first == std::string("pidFile")) + pidFile = iter->second; + else + { + // unknown element occurred + unknownElement = true; + + if (enableException) + { + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + } + + if (unknownElement) + { + // just skip the unknown element + iter++; + } + else + { + // remove this element from the map + iter = eraseFromConfigMap(iter); + } + } +} + +void Config::initImplicitVals() +{ + // tuneNumWorkers (note: twice the number of cpu cores is default, but at least 4) + if(!tuneNumWorkers) + tuneNumWorkers = BEEGFS_MAX(System::getNumOnlineCPUs()*2, 4); + + // tuneNumCommSlaves + if(!tuneNumCommSlaves) + tuneNumCommSlaves = tuneNumWorkers * 2; + + // tuneTargetChooserNum + initTuneTargetChooserNum(); + + // connInterfacesList(/File) + AbstractConfig::initInterfacesList(connInterfacesFile, connInterfacesList); + + AbstractConfig::initSocketBufferSizes(); + + // connAuthHash + AbstractConfig::initConnAuthHash(connAuthFile, &connAuthHash); + + // sysTargetAttachmentMap + initSysTargetAttachmentMap(); +} + +void Config::initSysTargetAttachmentMap() +{ + if(sysTargetAttachmentFile.empty() ) + return; // no file given => nothing to do here + + // check if file exists + + if(!StorageTk::pathExists(sysTargetAttachmentFile) ) + throw InvalidConfigException("sysTargetAttachmentFile not found: " + + sysTargetAttachmentFile); + + // load as string map + + StringMap attachmentStrMap; + + MapTk::loadStringMapFromFile(sysTargetAttachmentFile.c_str(), &attachmentStrMap); + + // convert from string map to target map + + sysTargetAttachmentMap = new TargetMap(); + + for(StringMapCIter iter = attachmentStrMap.begin(); iter != attachmentStrMap.end(); iter++) + { + (*sysTargetAttachmentMap)[StringTk::strToUInt(iter->first)] = + NumNodeID(StringTk::strToUInt(iter->second) ); + } +} + +void Config::initTuneTargetChooserNum() +{ + if (this->tuneTargetChooser == TARGETCHOOSERTYPE_RANDOMIZED_STR) + this->tuneTargetChooserNum = TargetChooserType_RANDOMIZED; + else if (this->tuneTargetChooser == TARGETCHOOSERTYPE_ROUNDROBIN_STR) + this->tuneTargetChooserNum = TargetChooserType_ROUNDROBIN; + else if (this->tuneTargetChooser == TARGETCHOOSERTYPE_RANDOMROBIN_STR) + this->tuneTargetChooserNum = TargetChooserType_RANDOMROBIN; + else if (this->tuneTargetChooser == TARGETCHOOSERTYPE_RANDOMINTERNODE_STR) + this->tuneTargetChooserNum = TargetChooserType_RANDOMINTERNODE; + // Don't allow RANDOMINTRANODE Target Chooser + else + { + // invalid chooser specified + throw InvalidConfigException("Invalid storage target chooser specified: " + + tuneTargetChooser); + } +} + +std::string Config::createDefaultCfgFilename() const +{ + struct stat statBuf; + + const int statRes = stat(CONFIG_DEFAULT_CFGFILENAME, &statBuf); + + if(!statRes && S_ISREG(statBuf.st_mode) ) + return CONFIG_DEFAULT_CFGFILENAME; // there appears to be a config file + + return ""; // no default file otherwise +} + diff --git a/meta/source/app/config/Config.h b/meta/source/app/config/Config.h new file mode 100644 index 0000000..ca8d9da --- /dev/null +++ b/meta/source/app/config/Config.h @@ -0,0 +1,294 @@ +#pragma once + +#include +#include + + +enum TargetChooserType +{ + TargetChooserType_RANDOMIZED = 0, + TargetChooserType_ROUNDROBIN = 1, // round-robin in ID order + TargetChooserType_RANDOMROBIN = 2, // randomized round-robin (round-robin, but shuffle result) + TargetChooserType_RANDOMINTERNODE = 3, // select random targets from different nodes/domains + TargetChooserType_RANDOMINTRANODE = 4, // select random targets from the same node/domain +}; + + +class Config : public AbstractConfig +{ + public: + Config(int argc, char** argv); + virtual ~Config(); + + private: + + // configurables + + std::string connInterfacesFile; // implicitly generates connInterfacesList + std::string connInterfacesList; // comma-separated list + + std::string storeMetaDirectory; + std::string storeFsUUID; + bool storeAllowFirstRunInit; + bool storeUseExtendedAttribs; + bool storeSelfHealEmptyFiles; + + bool storeClientXAttrs; + bool storeClientACLs; + + std::string sysTargetAttachmentFile; // used by randominternode target chooser + TargetMap* sysTargetAttachmentMap; /* implicitly by sysTargetAttachmentFile, NULL if + unset */ + + unsigned tuneNumStreamListeners; + unsigned tuneNumWorkers; // 0 means automatic + unsigned tuneWorkerBufSize; + unsigned tuneNumCommSlaves; // 0 means automatic + unsigned tuneCommSlaveBufSize; + unsigned tuneDefaultChunkSize; + unsigned tuneDefaultNumStripeTargets; + unsigned tuneProcessFDLimit; // 0 means "don't touch limit" + bool tuneWorkerNumaAffinity; + bool tuneListenerNumaAffinity; + int tuneBindToNumaZone; // bind all threads to this zone, -1 means no binding + int tuneListenerPrioShift; // inc/dec thread priority of listener components + unsigned tuneDirMetadataCacheLimit; + std::string tuneTargetChooser; + TargetChooserType tuneTargetChooserNum; // auto-generated based on tuneTargetChooser + unsigned tuneLockGrantWaitMS; // time to wait for an ack per retry + unsigned tuneLockGrantNumRetries; // number of lock grant send retries until ack recv + bool tuneRotateMirrorTargets; // true to use rotated targets list as mirrors + bool tuneEarlyUnlinkResponse; // true to send response before chunk files unlink + bool tuneUsePerUserMsgQueues; // true to use UserWorkContainer for MultiWorkQueue + bool tuneUseAggressiveStreamPoll; // true to not sleep on epoll in streamlisv2 + unsigned tuneNumResyncSlaves; + bool tuneMirrorTimestamps; + unsigned tuneDisposalGCPeriod; // sleep between disposal garbage collector runs [seconds], 0 = disabled + + bool quotaEarlyChownResponse; // true to send response before chunk files chown + bool quotaEnableEnforcement; + + unsigned sysTargetOfflineTimeoutSecs; + bool sysAllowUserSetPattern; + + bool runDaemonized; + + std::string pidFile; + + bool limitXAttrListLength; + std::string sysFileEventLogTarget; + std::string sysFileEventPersistDirectory; + int64_t sysFileEventPersistSize; + + + // internals + + virtual void loadDefaults(bool addDashes) override; + virtual void applyConfigMap(bool enableException, bool addDashes) override; + virtual void initImplicitVals() override; + void initSysTargetAttachmentMap(); + void initTuneTargetChooserNum(); + std::string createDefaultCfgFilename() const; + + public: + // getters & setters + const std::string& getConnInterfacesList() const + { + return connInterfacesList; + } + + const std::string& getStoreMetaDirectory() const + { + return storeMetaDirectory; + } + + const std::string& getStoreFsUUID() const + { + return storeFsUUID; + } + + bool getStoreAllowFirstRunInit() const + { + return storeAllowFirstRunInit; + } + + bool getStoreUseExtendedAttribs() const + { + return storeUseExtendedAttribs; + } + + bool getStoreSelfHealEmptyFiles() const + { + return storeSelfHealEmptyFiles; + } + + bool getStoreClientXAttrs() const + { + return storeClientXAttrs; + } + + bool getStoreClientACLs() const + { + return storeClientACLs; + } + + const std::string& getSysTargetAttachmentFile() const + { + return sysTargetAttachmentFile; + } + + const TargetMap* getSysTargetAttachmentMap() const + { + return sysTargetAttachmentMap; + } + + unsigned getTuneNumStreamListeners() const + { + return tuneNumStreamListeners; + } + + unsigned getTuneNumWorkers() const + { + return tuneNumWorkers; + } + + unsigned getTuneWorkerBufSize() const + { + return tuneWorkerBufSize; + } + + unsigned getTuneNumCommSlaves() const + { + return tuneNumCommSlaves; + } + + unsigned getTuneCommSlaveBufSize() const + { + return tuneCommSlaveBufSize; + } + + unsigned getTuneDefaultChunkSize() const + { + return tuneDefaultChunkSize; + } + + unsigned getTuneDefaultNumStripeTargets() const + { + return tuneDefaultNumStripeTargets; + } + + unsigned getTuneProcessFDLimit() const + { + return tuneProcessFDLimit; + } + + bool getTuneWorkerNumaAffinity() const + { + return tuneWorkerNumaAffinity; + } + + bool getTuneListenerNumaAffinity() const + { + return tuneListenerNumaAffinity; + } + + int getTuneBindToNumaZone() const + { + return tuneBindToNumaZone; + } + + int getTuneListenerPrioShift() const + { + return tuneListenerPrioShift; + } + + unsigned getTuneDirMetadataCacheLimit() const + { + return tuneDirMetadataCacheLimit; + } + + TargetChooserType getTuneTargetChooserNum() const + { + return tuneTargetChooserNum; + } + + unsigned getTuneLockGrantWaitMS() const + { + return tuneLockGrantWaitMS; + } + + unsigned getTuneLockGrantNumRetries() const + { + return tuneLockGrantNumRetries; + } + + bool getTuneRotateMirrorTargets() const + { + return tuneRotateMirrorTargets; + } + + bool getTuneEarlyUnlinkResponse() const + { + return tuneEarlyUnlinkResponse; + } + + bool getTuneUsePerUserMsgQueues() const + { + return tuneUsePerUserMsgQueues; + } + + bool getTuneUseAggressiveStreamPoll() const + { + return tuneUseAggressiveStreamPoll; + } + + unsigned getTuneNumResyncSlaves() const + { + return tuneNumResyncSlaves; + } + + bool getQuotaEarlyChownResponse() const + { + return quotaEarlyChownResponse; + } + + bool getQuotaEnableEnforcement() const + { + return quotaEnableEnforcement; + } + + void setQuotaEnableEnforcement(bool doQuotaEnforcement) + { + quotaEnableEnforcement = doQuotaEnforcement; + } + + unsigned getSysTargetOfflineTimeoutSecs() const + { + return sysTargetOfflineTimeoutSecs; + } + + bool getRunDaemonized() const + { + return runDaemonized; + } + + const std::string& getPIDFile() const + { + return pidFile; + } + + bool getTuneMirrorTimestamps() const { return tuneMirrorTimestamps; } + + unsigned getTuneDisposalGCPeriod() const { return tuneDisposalGCPeriod; } + + bool getSysAllowUserSetPattern() const { return sysAllowUserSetPattern; } + + bool getLimitXAttrListLength() const { return limitXAttrListLength; } + + void setLimitXAttrListLength(bool value) { limitXAttrListLength = value; } + + const std::string& getFileEventLogTarget() const { return sysFileEventLogTarget; } + const std::string& getFileEventPersistDirectory() const { return sysFileEventPersistDirectory; } + uint64_t getFileEventPersistSize() const { return sysFileEventPersistSize; } +}; + diff --git a/meta/source/components/DatagramListener.cpp b/meta/source/components/DatagramListener.cpp new file mode 100644 index 0000000..0873695 --- /dev/null +++ b/meta/source/components/DatagramListener.cpp @@ -0,0 +1,57 @@ +#include "DatagramListener.h" + +#include + +DatagramListener::DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, bool restrictOutboundInterfaces): + AbstractDatagramListener("DGramLis", netFilter, localNicList, ackStore, udpPort, + restrictOutboundInterfaces) +{ +} + +void DatagramListener::handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) +{ + HighResolutionStats stats; // currently ignored + std::shared_ptr sock = findSenderSock(fromAddr->sin_addr); + if (sock == nullptr) + { + log.log(Log_WARNING, "Could not handle incoming message: no socket"); + return; + } + + NetMessage::ResponseContext rctx(fromAddr, sock.get(), sendBuf, DGRAMMGR_SENDBUF_SIZE, &stats); + + const auto messageType = netMessageTypeToStr(msg->getMsgType()); + switch(msg->getMsgType() ) + { + // valid messages within this context + case NETMSGTYPE_Ack: + case NETMSGTYPE_Dummy: + case NETMSGTYPE_HeartbeatRequest: + case NETMSGTYPE_Heartbeat: + case NETMSGTYPE_MapTargets: + case NETMSGTYPE_PublishCapacities: + case NETMSGTYPE_RemoveNode: + case NETMSGTYPE_RefreshCapacityPools: + case NETMSGTYPE_RefreshStoragePools: + case NETMSGTYPE_RefreshTargetStates: + case NETMSGTYPE_SetMirrorBuddyGroup: + { + if(!msg->processIncoming(rctx) ) + { + LOG(GENERAL, WARNING, + "Problem encountered during handling of incoming message.", messageType); + } + } break; + + default: + { // valid, but not within this context + log.logErr( + "Received a message that is invalid within the current context " + "from: " + Socket::ipaddrToStr(fromAddr->sin_addr) + "; " + "type: " + messageType ); + } break; + }; +} + + diff --git a/meta/source/components/DatagramListener.h b/meta/source/components/DatagramListener.h new file mode 100644 index 0000000..95391ba --- /dev/null +++ b/meta/source/components/DatagramListener.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class DatagramListener : public AbstractDatagramListener +{ + public: + DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces); + + protected: + virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg); + + private: + +}; + diff --git a/meta/source/components/DisposalGarbageCollector.cpp b/meta/source/components/DisposalGarbageCollector.cpp new file mode 100644 index 0000000..de3bca7 --- /dev/null +++ b/meta/source/components/DisposalGarbageCollector.cpp @@ -0,0 +1,51 @@ +#include "DisposalGarbageCollector.h" +#include "app/App.h" +#include "program/Program.h" +#include + + +FhgfsOpsErr deleteFile(unsigned& unlinked, Node& owner, const std::string& entryID, const bool isMirrored) { + + const auto err = DisposalCleaner::unlinkFile(owner, entryID, isMirrored); + + if (err == FhgfsOpsErr_COMMUNICATION) + LOG(GENERAL, ERR, "Communication error", entryID, isMirrored); + else if (err == FhgfsOpsErr_INUSE) + LOG(GENERAL, ERR, "File in use", entryID, isMirrored); + else if (err != FhgfsOpsErr_SUCCESS) + LOG(GENERAL, ERR, "Error", entryID, isMirrored, err); + else + (unlinked)++; + + return FhgfsOpsErr_SUCCESS; +} + +void handleError(Node&, FhgfsOpsErr err) { + LOG(GENERAL, ERR, "Disposal garbage collection run failed", err); +} + +void disposalGarbageCollector() { + LOG(GENERAL, NOTICE, "Disposal garbage collection started"); + auto app = Program::getApp(); + + unsigned unlinked = 0; + + const std::vector nodes = {app->getMetaNodes()->referenceNode(app->getLocalNode().getNumID())}; + DisposalCleaner dc(*app->getMetaBuddyGroupMapper(), true); + dc.run(nodes, + [&unlinked] (auto&& owner, auto&& entryID, auto&& isMirrored) { + return deleteFile(unlinked, owner, entryID, isMirrored); + }, + handleError, + [&app] () { return app->getGcQueue()->getSelfTerminate(); } + ); + + LOG(GENERAL, NOTICE, "Disposal garbage collection finished", unlinked); + + if(const auto wait = app->getConfig()->getTuneDisposalGCPeriod()) { + if(auto* queue = app->getGcQueue()) { + queue->enqueue(std::chrono::seconds(wait), disposalGarbageCollector); + } + } +} + diff --git a/meta/source/components/DisposalGarbageCollector.h b/meta/source/components/DisposalGarbageCollector.h new file mode 100644 index 0000000..f2815f9 --- /dev/null +++ b/meta/source/components/DisposalGarbageCollector.h @@ -0,0 +1,4 @@ +#pragma once + +void disposalGarbageCollector(); + diff --git a/meta/source/components/FileEventLogger.cpp b/meta/source/components/FileEventLogger.cpp new file mode 100644 index 0000000..80e2284 --- /dev/null +++ b/meta/source/components/FileEventLogger.cpp @@ -0,0 +1,1582 @@ +#include "FileEventLogger.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +using TimePoint = std::chrono::time_point; +using Milliseconds = std::chrono::milliseconds; + +static TimePoint getNow() +{ + return std::chrono::steady_clock::now(); +} + +class Timer +{ + bool mSet = false; + TimePoint mBeginTime; + Milliseconds mDuration; + +public: + + bool isSet() const + { + return mSet; + } + + void clear() + { + mSet = false; + } + + TimePoint getBeginTime() const + { + return mBeginTime; + } + + TimePoint getEndTime() const + { + return mBeginTime + mDuration; + } + + Milliseconds getRemainingTime(TimePoint now) const + { + return std::chrono::duration_cast(mBeginTime + mDuration - now); + } + + bool hasElapsed(TimePoint now) const + { + return mSet && now >= getEndTime(); + } + + void restartFrom(TimePoint now) + { + mBeginTime = now; + mSet = true; + } + + void reset(TimePoint now, Milliseconds duration) + { + mDuration = duration; + restartFrom(now); + } + + Timer() + { + mDuration = Milliseconds(0); + } + + Timer(Milliseconds duration) + { + mDuration = duration; + } + + Timer(TimePoint now, Milliseconds duration) + { + reset(now, duration); + } +}; + + +class UnixAddr +{ + sockaddr_un addr = {}; + +public: + const char *getPath() const + { + return addr.sun_path; + } + + const struct sockaddr *getSockaddr() const + { + return (struct sockaddr *) &addr; + } + + socklen_t size() const + { + return sizeof addr; + } + + bool setPath(StringZ path) + { + RO_Slice source = path.sliceZ(); + WO_Slice dest = As_WO_Slice(addr.sun_path); + if (source.sizeBytes() > dest.sizeBytes()) + { + LOG(EVENTLOGGER, ERR, "Path too long, can't use as socket address:", path.c_str()); + return false; + } + sliceCopyFill0(dest, source); + addr.sun_family = AF_UNIX; + return true; + } +}; + +struct SocketState +{ + UnixAddr unixAddr; + + FDHandle sockFd; + + // TODO: we try to avoid blocking when writing to the sockFd. So we might + // have to set the socket non-blocking and/or keep track of the free space + // in the kernel's socket buffer. + int lastConnectError = -1; + + // sockReconnectTimer: time of next reconnect attempt + // sockReconnectMsgTimer: when next to log a message that we're still reconnecting + Timer sockReconnectTimer; + Timer sockReconnectMsgTimer; + + bool haveConnected = false; + bool haveError = false; + bool haveEof = false; + + bool connected() const { return haveConnected; } + + [[nodiscard]] bool init(StringZ address); + bool tryConnect(); +}; + +bool SocketState::init(StringZ address) +{ + if (! address.startswith("unix:"_sz)) + { + LOG(EVENTLOGGER, ERR, + (std::string("Invalid FileEventLogger address: '") + + std::string(address.c_str()) + + "FileEventLogger supports only unix addresses (starting with 'unix:')")); + return false; + } + + if (! unixAddr.setPath(address.offsetBytes(5))) + return false; + + // Initially try to reconnect immediately + // Every 10 minutes we will send a follow-up message to the log, indicating + // that we're still connecting + LOG(EVENTLOGGER, NOTICE, "Trying to connect to event listener socket..."); + auto now = getNow(); + sockReconnectTimer.reset(now, Milliseconds(0)); + sockReconnectMsgTimer.reset(now, Milliseconds(600000)); + return true; +} + +bool SocketState::tryConnect() +{ + assert(! haveConnected); // don't call when already connected + auto now = getNow(); + + if (sockReconnectMsgTimer.hasElapsed(now)) + { + LOG(EVENTLOGGER, NOTICE, "still reconnecting..."); + sockReconnectMsgTimer.restartFrom(now); + lastConnectError = -1; // allow another connect error msg too + } + + sockFd.reset(socket(AF_UNIX, SOCK_SEQPACKET, 0)); + + if (! sockFd.valid()) + { + LOG(EVENTLOGGER, ERR, "Failed to create FileEventLogger socket.", sysErr); + return false; + } + + /* Note that we should try hard to avoid blocking anywhere since we have to + * do multiple different time-critical things in the same thread. + * + * Note: Even though the socket is set to blocking mode, the connect() + * _usually_ doesn't block since we're connecting to a Unix socket. On some + * OSes, possibly including Linux, it will block though when the connection + * queue of the server socket is full. (On other OSes, an error will be + * returned immediately in this case). + */ + if (connect(sockFd.get(), unixAddr.getSockaddr(), unixAddr.size()) == -1) + { + sockFd.close(); + + // avoid spamming reconnect failures + int e = errno; + if (e != lastConnectError) + { + lastConnectError = e; + LOG(EVENTLOGGER, WARNING, "Failed to connect to FileEventLogger listener socket.", sysErr, + ("sockPath", unixAddr.getPath())); + if (e == ENOENT) + { + LOG(EVENTLOGGER, WARNING, "(this probably means that nobody is listening on the configured socket path)."); + } + else if (e == ECONNREFUSED) + { + LOG(EVENTLOGGER, WARNING, "(this probably means that a listener on the configured socket path terminated without cleaning up the socket file)"); + } + LOG(EVENTLOGGER, NOTICE, "Note: still attempting to connect. Repetitions of this error won't be logged."); + } + + // retry in 1000ms + { + sockReconnectTimer.reset(now, Milliseconds(1000)); + } + + return false; + } + + // clear timer to indicate that socket works + sockReconnectTimer.clear(); + + haveConnected = true; + LOG(EVENTLOGGER, NOTICE, "Reconnected."); + return true; +} + +// Packet types +// NOTE: for future extension, we can consider introducing messages to request +// certain capabilities. We allow for extensibility by introducing an error +// packet indicating that a request was not understood. +enum class PacketType : uint8_t +{ + Handshake_Request = 1, + Handshake_Response, + Request_Message_Newest, + Send_Message_Newest, + Request_Message_Range, + Send_Message_Range, + // Client requests message stream. + // Server follows up with message stream. + Request_Message_Stream_Start, + // Server sends an (event) message to client + Send_Message, + // Client requests orderly connection close. Can be sent mid-stream. + Close_Request, + // Server informs client that connection will be closed. + // This is the last message sent by the server. + // Includes a ConnTerminateReason + Send_Close, +}; + +enum class ConnTerminateReason +{ + Ack_Close, + + // This gets sent when the stream stopped without solicitation of the peer + // that requested the stream. The connection is in an error state and will + // be shut down by the server. + Stream_Crashed, + + Socket_Error, + + Protocol_Error, + + Subscriber_Closed_Unexpectedly, +}; + +static const char *packetTypeString(PacketType packetType) +{ +#define PTSTRING(x) case PacketType::x: return #x + switch (packetType) + { + PTSTRING(Handshake_Request); + PTSTRING(Handshake_Response); + PTSTRING(Request_Message_Newest); + PTSTRING(Send_Message_Newest); + PTSTRING(Request_Message_Range); + PTSTRING(Send_Message_Range); + PTSTRING(Request_Message_Stream_Start); + PTSTRING(Send_Message); + PTSTRING(Close_Request); + PTSTRING(Send_Close); + default: return "(invalid packet type)"; + } +} + + +class PacketBuffer +{ + size_t mSize = 0; + std::vector mBuffer; + +public: + + void reallocate(size_t capacity) + { + mBuffer.resize(capacity); + mBuffer.shrink_to_fit(); + } + + void drop() + { + // better not use resize/shrink. The following should guarantee the + // memory is released: + mBuffer = std::vector(); + mSize = 0; + } + + void clear() + { + mSize = 0; + } + + void *data() + { + return mBuffer.data(); + } + + size_t size() const + { + return mSize; + } + + size_t capacity() const + { + return mBuffer.capacity(); + } + + void setSize(size_t size) + { + assert(size < mBuffer.capacity()); + mSize = size; + } + + PacketBuffer& operator=(PacketBuffer&& other) noexcept + { + drop(); + std::swap(mSize, other.mSize); + std::swap(mBuffer, other.mBuffer); + return *this; + } + + PacketBuffer() + { + reallocate(16 * 1024); + } + + // I don't think we need copy constructor / copy assignment operator. + PacketBuffer& operator=(PacketBuffer const& other) = delete; + PacketBuffer(PacketBuffer const& other) = delete; + + PacketBuffer(PacketBuffer&& other) noexcept + { + *this = std::forward(other); + } +}; + + + +// Queue of packets -- used for both RX and TX queues. Any queue (RX or TX) has +// 2 sides (enqueue/dequeue). To enqueue, an API with 2 operations is needed to +// "get" (begin) enqueuing a free packet and then to submit it (end enqueuing). +// To dequeue, it's the same -- so 4 operations total for any queue (RX or TX). +// +// I chose to design the "begin" as an idempotent operation -- such that a +// second begin without an "end" in between will return the same packet, +// invalidating the first "begin". +class PacketQueue +{ + uint32_t count = 0; + uint32_t rd = 0; // read position + uint32_t wr = 0; // write position + + std::vector queue; + + PacketBuffer *getPacket(uint32_t pos) + { + uint32_t index = pos & (count - 1); // == (pos % count) provided that count == 2^n + return &queue.at(index); + } + +public: + void init(uint32_t packetcount) // packetcount must be a power of 2 + { + assert((packetcount & (packetcount - 1)) == 0); + queue.resize(packetcount); + count = packetcount; + } + + PacketBuffer *enqueueBegin() + { + if (wr - rd == count) + return nullptr; // full + return getPacket(wr); + } + + void enqueueEnd() + { + assert(wr - rd < count); + wr++; + } + + PacketBuffer *dequeueBegin() + { + if (wr - rd == 0) + return nullptr; //empty + return getPacket(rd); + } + + void dequeueEnd() + { + assert(wr - rd > 0); + rd++; + } +}; + + + +static void sendPackets(PacketQueue *txQueue, SocketState *socketState) +{ + int fd = socketState->sockFd.get(); + + for (;;) + { + PacketBuffer *packet = txQueue->dequeueBegin(); + if (! packet) + break; + + ssize_t n = ::send(fd, packet->data(), packet->size(), MSG_NOSIGNAL | MSG_DONTWAIT); + + if (n == -1) + { + int e = errno; + if (e == EAGAIN || errno == EWOULDBLOCK) + { + // try again later + } + else if (e == EMSGSIZE) + { + LOG(EVENTLOGGER, WARNING, "Message too large to send over fileevent socket. Ignoring", packet->size()); + } + else + { + LOG(EVENTLOGGER, ERR, "Failed to send message over fileevent socket.", sysErr); + socketState->haveError = true; + } + break; + } + + txQueue->dequeueEnd(); + } +} + +static void recvPackets(PacketQueue *rxQueue, SocketState *socketState) +{ + int fd = socketState->sockFd.get(); + + for (;;) + { + PacketBuffer *packet = rxQueue->enqueueBegin(); + if (! packet) + break; + + ssize_t nr = ::recv(fd, packet->data(), packet->capacity(), MSG_DONTWAIT); + + if (nr == -1) + { + int e = errno; + if (e == EAGAIN || errno == EWOULDBLOCK) + { + } + else + { + LOG(EVENTLOGGER, ERR, "Failed to recv message over seqpacket socket.", sysErr); + socketState->haveError = true; + } + break; + } + else if (nr == 0) + { + // peer closed their write end. NOTE: In principle a 0 return value can also + // be the consequence of the peer sending a 0-sized packet. It's not obvious + // to me how to distinguish the two cases easily. I'll posit that + // 0-sized packets shouldn't be sent and will be interpreted as EOF. + socketState->haveEof = true; + break; + } + + packet->setSize((size_t) nr); + rxQueue->enqueueEnd(); + } +} + + + + + + + +// Serializer for packet writing +struct PacketWriter +{ + PacketQueue *txQueue; + PacketBuffer *packet; + Serializer serializer; +}; + +static bool write_begin(PacketWriter& writer, PacketQueue *txQueue) +{ + PacketBuffer *packet = txQueue->enqueueBegin(); + if (! packet) + return false; + writer.txQueue = txQueue; + writer.packet = packet; + writer.serializer = Serializer(writer.packet->data(), writer.packet->capacity()); + return true; +} + +static bool write_end(PacketWriter& writer) +{ + if (! writer.serializer.good()) + return false; + writer.packet->setSize(writer.serializer.size()); + writer.txQueue->enqueueEnd(); + return true; +} + +static void write_u8(PacketWriter& writer, uint8_t const& x) +{ + writer.serializer % x; +} + +static void write_u16(PacketWriter& writer, uint16_t const& x) +{ + writer.serializer % x; +} + +static void write_u32(PacketWriter& writer, uint32_t const& x) +{ + writer.serializer % x; +} + +static void write_u64(PacketWriter& writer, uint64_t const& x) +{ + writer.serializer % x; +} + +static void write_slice(PacketWriter& writer, RO_Slice slice) +{ + writer.serializer.putBlock(slice.data(), slice.sizeBytes()); +} + +static void write_packet_header(PacketWriter& writer, PacketType packetType) +{ + write_u8(writer, (uint8_t) packetType); + write_slice(writer, RO_Slice("events", 7)); +} + + +// Deserializer for packet reading. We currently "duplicate" the serializer code. +// I believe that, at least at the moment, this is better than adding a bad +// abstraction that overcomplicates things. +struct PacketReader +{ + PacketBuffer *packet; + Deserializer deserializer; + + bool good() const { return deserializer.good(); } + + PacketType packetType; + + PacketReader(PacketBuffer *packet) + // NOTE for the reader we consider the size of the packet instead of the capacity... + // It's a bit of a weird asymmetrical abstraction + : packet(packet), deserializer(packet->data(), packet->size()) + {} +}; + +static bool read_end(PacketReader& reader) +{ + reader.deserializer.parseEof(); + return reader.deserializer.good(); +} + +static void read_u8(PacketReader& reader, uint8_t& x) +{ + reader.deserializer % x; +} + +static void read_u16(PacketReader& reader, uint16_t& x) +{ + reader.deserializer % x; +} + +/* currently unused +static void read_u32(PacketReader& reader, uint32_t & x) +{ + reader.deserializer % x; +} +*/ + +static void read_u64(PacketReader& reader, uint64_t& x) +{ + reader.deserializer % x; +} + +static void read_slice(PacketReader& reader, WO_Slice slice) +{ + reader.deserializer.getBlock(slice.data(), slice.sizeBytes()); +} + + +// Message input stream -- buffers messages read from a PMQ +struct MessageStream +{ + PMQ_Reader_Handle reader; + PMQ_Read_Result lastPmqError = PMQ_Read_Result_Success; + // Currently buffering 1 message only. + bool msgIsPresent = false; + size_t msgSize = 0; + uint64_t msgMsn = 0; + MallocBuffer msgBuffer; + +public: + bool haveMessage() const { return msgIsPresent; } + uint64_t getMsgMsn() const { return msgMsn; } + const void *getMsgData() const { return msgBuffer.data(); } + size_t getMsgSize() const { return msgSize; } + PMQ_Read_Result getError() const { return lastPmqError; } + bool haveError() const { return lastPmqError != PMQ_Read_Result_Success && lastPmqError != PMQ_Read_Result_EOF; } + + bool init(PMQ *pmq); + void seek(uint64_t msn); + bool checkMsg(); + void clearMsg() { msgIsPresent = false; } +}; + +bool MessageStream::init(PMQ *pmq) +{ + if (! msgBuffer.reset(16 * 1024)) + return false; + reader = pmq_reader_create(pmq); + return reader.get() != nullptr; +} + +void MessageStream::seek(uint64_t msn) +{ + if (lastPmqError != PMQ_Read_Result_Success && lastPmqError != PMQ_Read_Result_EOF) + return; // should we even get here? + + lastPmqError = pmq_reader_seek_to_msg(reader.get(), msn); + + switch (lastPmqError) + { + case PMQ_Read_Result_Success: + break; + case PMQ_Read_Result_EOF: + assert(false); // this shouldn't happen anymore + // fallthrough, although we really should never get here. + default: + LOG(EVENTLOGGER, WARNING, "pmq_reader_seek_to_msg() returns", lastPmqError); + LOG(EVENTLOGGER, WARNING, "Failed to seek to requested MSN:", msn); + break; + } +} + +// Lock the queue and read the next message from the queue. +// While there is no message, we also make sure that the queue gets flushed as scheduled. +// We try to minimize the time where the queue is locked. +bool MessageStream::checkMsg() +{ + if (msgIsPresent) + return true; + + msgSize = 0; + msgMsn = pmq_reader_get_current_msn(reader); + + lastPmqError = pmq_read_msg(reader, msgBuffer.data(), msgBuffer.capacity(), &msgSize); + + switch (lastPmqError) + { + // The message was successfully read back. + case PMQ_Read_Result_Success: + msgIsPresent = true; + LOG(EVENTLOGGER, SPAM, "pmq_read_msg() returns PMQ_Read_Result_Success"); + break; + + // How to handle all the other cases? + case PMQ_Read_Result_Buffer_Too_Small: + LOG(EVENTLOGGER, SPAM, "pmq_read_msg() returns PMQ_Read_Result_Buffer_Too_Small"); + break; + + case PMQ_Read_Result_EOF: + // why have we been called then? + //LOG(EVENTLOGGER, SPAM, "pmq_read_msg() returns PMQ_Read_Result_EOF"); + break; + + case PMQ_Read_Result_Out_Of_Bounds: + LOG(EVENTLOGGER, DEBUG, "pmq_read_msg() returns PMQ_Read_Result_Out_Of_Bounds"); + break; + + case PMQ_Read_Result_IO_Error: + LOG(EVENTLOGGER, ERR, "Failed to read message from queue: I/O error happened while reading message"); + break; + + case PMQ_Read_Result_Integrity_Error: + LOG(EVENTLOGGER, ERR, "Failed to read message from queue: detected database corruption"); + break; + + default: + assert(0); + break; + } + + return msgIsPresent; +} + +struct Subscriber +{ + bool handshakeReceived = false; + bool handshakeSent = false; + bool streaming = false; + bool haveMessageRangeRequest = false; + bool haveMessageNewestRequest = false; + bool closeRequested = false; + bool terminated = false; + + uint16_t peerMajorVersion = 0; // valid if handshakeReceived + uint16_t peerMinorVersion = 0; + + FileEventLoggerIds ids; + + MessageStream messageStream; + SocketState socketState; + + PacketQueue rxQueue; + PacketQueue txQueue; +}; + + +__attribute__((format(printf, 2, 3))) +static void malformedPacket(Subscriber *s, const char *fmt, ...); // NOLINT + + +static bool packetBegin(Subscriber *s, PacketReader& reader) +{ + uint8_t packetType; + char check[7]; + + read_u8(reader, packetType); + read_slice(reader, WO_Slice(check, sizeof check)); + + if (! reader.good()) + { + malformedPacket(s, "Invalid packet received: Failed to parse header"); + return false; + } + + if (strcmp(check, "events") != 0) + { + malformedPacket(s, "Invalid packet received: mismatch of magic string"); + return false; + } + + reader.packetType = (PacketType) packetType; + return true; +} + +static bool packetEnd(Subscriber *s, PacketReader& reader) +{ + if (! read_end(reader)) + { + malformedPacket(s, "too long. Expected end of %s", + packetTypeString(reader.packetType)); + return false; + } + return true; +} + + + +[[nodiscard]] +static bool resetSubscriber(Subscriber *s, PMQ *pmq, StringZ address, FileEventLoggerIds ids) +{ + // clean way to make a new object without new'ing. + // It may be a bit unfortunate that the packet queues will be reallocated. + *s = Subscriber(); + + if (! s->messageStream.init(pmq)) + goto error; + + if (! s->socketState.init(address)) + goto error; + + s->ids = ids; + + s->rxQueue.init(8); + s->txQueue.init(8); + + return true; + +error: + LOG(EVENTLOGGER, ERR, "Failed to reset Subscriber state"); + return false; +} + +// helper function to process a single packet. +// returns true if packet can be considered "processed" i.e. thrown away. +static void processPacket(Subscriber *s, PacketBuffer *packet) +{ + PacketReader reader(packet); + + if (! packetBegin(s, reader)) + return; + + if (s->handshakeReceived) + { + if (reader.packetType == PacketType::Handshake_Request) + { + malformedPacket(s, "Duplicate handshake received from subscriber"); + return; + } + } + else + { + if (reader.packetType != PacketType::Handshake_Request) + { + malformedPacket(s, "Invalid packet received from subscriber: expected handshake packet"); + return; + } + } + + switch (reader.packetType) + { + case PacketType::Handshake_Request: + { + uint16_t major; + uint16_t minor; + + read_u16(reader, major); + read_u16(reader, minor); + + if (! packetEnd(s, reader)) + { + malformedPacket(s, "Handshake request packet invalid"); + return; + } + + if (major != 2 || minor != 0) + { + char version[16]; + snprintf(version, sizeof version, "%u.%u", major, minor); + LOG(EVENTLOGGER, WARNING, "Unsupported protocol version requested subscriber: 2.0 required, got:", version); + } + + s->peerMajorVersion = major; + s->peerMinorVersion = minor; + s->handshakeReceived = true; + } + break; + case PacketType::Request_Message_Newest: + { + if (! packetEnd(s, reader)) + { + malformedPacket(s, "Invalid Request_Message_Range packet"); + return; + } + + s->haveMessageNewestRequest = true; + } + break; + case PacketType::Request_Message_Range: + { + if (! packetEnd(s, reader)) + { + malformedPacket(s, "Invalid Request_Message_Range packet"); + return; + } + + s->haveMessageRangeRequest = true; + } + break; + case PacketType::Request_Message_Stream_Start: + { + uint64_t msn; + + read_u64(reader, msn); + + if (! packetEnd(s, reader)) + return; + + // Position reader right away (this is a synchronous operation) + // Otherwise we'd need more state variables. + s->messageStream.seek(msn); + s->streaming = true; + } + break; + case PacketType::Close_Request: + { + if (! packetEnd(s, reader)) + return; + + s->closeRequested = true; + } + break; + default: + break; + } + return; +} + +static void subscriberTerminate(Subscriber *s, ConnTerminateReason reason) +{ + if (s->terminated) + return; // send only 1 terminate packet + + // send an error packet + if (PacketWriter writer; + write_begin(writer, &s->txQueue)) + { + write_packet_header(writer, PacketType::Send_Close); + write_u8(writer, (uint8_t) reason); + write_end(writer); + } + + s->terminated = true; +} + +__attribute__((format(printf, 2, 3))) +static void malformedPacket(Subscriber *s, const char *fmt, ...) // NOLINT +{ + va_list ap; + va_start(ap, fmt); + { + char msg[128]; + vsnprintf(msg, sizeof msg, fmt, ap); + msg[sizeof msg - 1] = 0; + LOG(EVENTLOGGER, WARNING, msg); + } + va_end(ap); + + subscriberTerminate(s, ConnTerminateReason::Protocol_Error); +} + +static void subscriberDoNonIOWork(Subscriber *s) +{ + if (! s->handshakeSent) + { + if (PacketWriter writer; + write_begin(writer, &s->txQueue)) + { + write_packet_header(writer, PacketType::Handshake_Response); + write_u16(writer, 2); // major + write_u16(writer, 0); // minor + write_u32(writer, s->ids.nodeId); + write_u16(writer, s->ids.buddyGroupId); + if (write_end(writer)) + { + s->handshakeSent = true; + } + } + } + + if (! s->handshakeSent) + { + return; + } + + for (;;) + { + PacketBuffer *packet = s->rxQueue.dequeueBegin(); + if (! packet) + break; // all packets processed, queue empty + processPacket(s, packet); + s->rxQueue.dequeueEnd(); + } + + if (s->messageStream.haveError()) + { + subscriberTerminate(s, ConnTerminateReason::Stream_Crashed); + return; + } + + if (s->closeRequested) + { + subscriberTerminate(s, ConnTerminateReason::Ack_Close); + return; + } + + if (s->haveMessageRangeRequest) + { + if (PacketWriter writer; + write_begin(writer, &s->txQueue)) + { + PMQ_Reader *pmq_reader = s->messageStream.reader.get(); + PMQ *pmq = pmq_reader_get_pmq(pmq_reader); + + PMQ_Persist_Info persistInfo = pmq_get_persist_info(pmq); + uint64_t new_msn = persistInfo.wal_msn; + uint64_t old_msn = pmq_reader_find_old_msn(pmq_reader); + + write_packet_header(writer, PacketType::Send_Message_Range); + write_u64(writer, new_msn); + write_u64(writer, old_msn); + + if (write_end(writer)) + { + s->haveMessageRangeRequest = false; + } + } + } + else if (s->haveMessageNewestRequest) + { + if (PacketWriter writer; + write_begin(writer, &s->txQueue)) + { + PMQ_Reader *pmq_reader = s->messageStream.reader.get(); + PMQ *pmq = pmq_reader_get_pmq(pmq_reader); + + PMQ_Persist_Info persistInfo = pmq_get_persist_info(pmq); + uint64_t new_msn = persistInfo.wal_msn; + + write_packet_header(writer, PacketType::Send_Message_Newest); + write_u64(writer, new_msn); + + if (write_end(writer)) + { + s->haveMessageNewestRequest = false; + } + } + } + + if (s->streaming) + { + // While there are more messages to read and the packet tx queue isn't + // full, send more packets. + // TODO more elaborate data flow control? + for (; + s->messageStream.checkMsg(); + s->messageStream.clearMsg()) + { + PacketWriter writer; + if (! write_begin(writer, &s->txQueue)) + break; + + uint64_t msn = s->messageStream.getMsgMsn(); + const void *msgData = s->messageStream.getMsgData(); + size_t msgSize = s->messageStream.getMsgSize(); + + write_packet_header(writer, PacketType::Send_Message); + write_u64(writer, msn); + write_u16(writer, msgSize); + write_slice(writer, RO_Slice(msgData, msgSize)); + + if (! write_end(writer)) + { + // can this ever happen? + LOG(EVENTLOGGER, ERR, "Failed to serialize message of size", msgSize); + } + } + } +} + + +static void subscriberDoWork(Subscriber *s) +{ + assert(! s->terminated); // we should not have been called. + + if (! s->socketState.connected()) + { + if (! s->socketState.tryConnect()) + return; + } + + recvPackets(&s->rxQueue, &s->socketState); + + subscriberDoNonIOWork(s); + + sendPackets(&s->txQueue, &s->socketState); + + if (s->socketState.haveEof) + { + if (! s->closeRequested) + { + subscriberTerminate(s, ConnTerminateReason::Subscriber_Closed_Unexpectedly); + return; + } + } + + if (s->socketState.haveError) + { + subscriberTerminate(s, ConnTerminateReason::Socket_Error); + return; + } +} + + +// State shared between enqueuer threads and the "EventQ-Worker" thread. +struct EventLoggerShared +{ + std::condition_variable readerCond; // the unique reader thread (network bound) is waiting on this + std::condition_variable writerCond; // any event producer thread is waiting on this + + int numWritersWaiting = 0; + int numReadersWaiting = 0; + + Timer flushTimer; + + bool shuttingDown = false; // termination signal for worker thread +}; + + +// State of the worker thread which has multiple responsibilities: +// - flushing/syncing the PMQ so that new messages are safely persisted to disk. +// - reading messages from the PMQ and forwarding them to downstream services +struct EventLoggerWorker +{ + PMQ *pmq = nullptr; + MutexProtected *shared = nullptr; + FileEventLoggerIds ids; + + MallocString address; + + // only 1 simultaneous subscriber currently. + Subscriber subscriber; +}; + +struct EventLoggerWorkerParams +{ + PMQ *pmq; + MutexProtected *shared; + FileEventLoggerIds ids; + StringZ address; +}; + +[[nodiscard]] +static bool workerResetSubscriber(EventLoggerWorker *worker) +{ + return resetSubscriber(&worker->subscriber, + worker->pmq, worker->address, worker->ids); +} + +static bool initWorker(EventLoggerWorker *worker, EventLoggerWorkerParams const& params) +{ + worker->pmq = params.pmq; + worker->shared = params.shared; + if (! worker->address.reset(params.address)) + return false; + worker->ids = params.ids; + return workerResetSubscriber(worker); +} + +// Flush/sync the PMQ +static void workerFlush(EventLoggerWorker *worker) +{ + { + auto shared = worker->shared->lockedView(); + shared->flushTimer.clear(); + } + + //LOG_DBG(EVENTLOGGER, SPAM, "worker thread flush"); + if (! pmq_sync(worker->pmq)) + LOG_DBG(EVENTLOGGER, ERR, "Failed to flush PMQ: pmq_sync() failed"); + + { + auto shared = worker->shared->lockedView(); + if (shared->numWritersWaiting > 0) + shared->writerCond.notify_all(); + } +} + +struct Worker_Wait_Result +{ + bool shuttingDown = false; + bool haveMessage = false; + bool mustFlush = false; + bool mustReconnect = false; +}; + +static void workerWait(EventLoggerWorker *worker, Subscriber const *subscriber, Worker_Wait_Result *result) +{ + *result = Worker_Wait_Result(); + + auto now = getNow(); + + LockedView shared = worker->shared->lockedView(); + + if (shared->shuttingDown) + { + result->shuttingDown = true; + return; + } + + result->haveMessage = subscriber->streaming && ! pmq_reader_eof(subscriber->messageStream.reader.get()); + result->mustFlush = shared->flushTimer.hasElapsed(now); + result->mustReconnect = subscriber->socketState.sockReconnectTimer.hasElapsed(now); + + if (result->haveMessage) return; + if (result->mustFlush) return; + if (result->mustReconnect) return; + + // Nothing to do. + // indicate to writer threads that we want to be woken up + // TODO: is this a necessary optimization? Need to check, maybe condition + // variables know on their own if anybody is waiting -- so the enqueuer threads + // could always call notify_one() unconditionally. + + ++ shared->numReadersWaiting; + { + TimePoint waitEndTime = now + Milliseconds(50); + + if (shared->flushTimer.isSet()) + waitEndTime = std::min(waitEndTime, shared->flushTimer.getEndTime()); + + if (subscriber->socketState.sockReconnectTimer.isSet()) + waitEndTime = std::min(waitEndTime, subscriber->socketState.sockReconnectTimer.getEndTime()); + + shared->readerCond.wait_until(shared.get_unique_lock(), waitEndTime); + } + -- shared->numReadersWaiting; +} + +static void workerThreadRoutine(EventLoggerWorker *worker) +{ + LOG(EVENTLOGGER, NOTICE, "EventQ-Worker thread starting"); + for (;;) + { + Worker_Wait_Result waitResult; + + workerWait(worker, &worker->subscriber, &waitResult); + + if (waitResult.shuttingDown) + { + break; + } + + if (waitResult.mustFlush) + { + workerFlush(worker); + } + + subscriberDoWork(&worker->subscriber); + + if (worker->subscriber.terminated) + { + if (! workerResetSubscriber(worker)) + { + // What should we do? Probably this shouldn't even happen so... + } + } + } + LOG(EVENTLOGGER, NOTICE, "EventQ-Worker thread terminating"); +} + + +// I think the PThread class is... weird. So I'm working a bit around what I +// perceive as problems. For example Pthread it doesn't allow us to easily +// check if a thread was started, and trying to join will throw an exception if +// nothing was started. The actual error values from pthread_*() are not +// reported at least in some cases. +// +// And to be able to pass arguments to the started thread, PThread design +// assumes that it's a good idea to require the implementation to inherit from +// PThread. I disagree, and favour separating the functional implementation +// from the thread state management. + +class EventQ_Worker_Thread : public PThread +{ + EventLoggerWorker worker; + bool mWorkerValid = false; + bool mThreadRunning = false; + +public: + + void run() override + { + if (mWorkerValid) + workerThreadRoutine(&worker); + } + + void startWorkerThread(EventLoggerWorkerParams const& params) + { + mWorkerValid = initWorker(&worker, params); + + start(); + mThreadRunning = true; + } + + void join() + { + PThread::join(); + mThreadRunning = false; + } + + bool joinable() + { + return mThreadRunning; + } + + EventQ_Worker_Thread() : PThread("EventQ-Worker") + { + } +}; + +static PMQ *initPmq() +{ + auto config = Program::getApp()->getConfig(); + + std::string dirpath = config->getFileEventPersistDirectory(); + + if (dirpath.empty()) + { + dirpath = config->getStoreMetaDirectory(); + + if (dirpath.empty()) + { + LOG(EVENTLOGGER, ERR, std::string("Unexpected: storeMetaDirectory configuration is empty")); + return nullptr; + } + + if (dirpath.back() != '/') + dirpath += "/"; + + dirpath += "eventq"; + } + + int64_t persistSize = config->getFileEventPersistSize(); + if (persistSize < 0) + { + LOG(EVENTLOGGER, ERR, std::string("sysFileEventPersistSize is negative.")); + return nullptr; + } + + PMQ_Init_Params params = {}; + params.create_size = persistSize; + params.basedir_path = dirpath.c_str(); + + return pmq_create(¶ms); +} + +struct FileEventLogger +{ + PMQ_Handle pmq; + + MutexProtected shared; + + EventQ_Worker_Thread workerThread; +}; + + + + + +FileEventLogger *createFileEventLogger(FileEventLoggerParams const& params) +{ + assert(! params.address.empty()); + StringZ address = StringZ::fromZeroTerminated(params.address.c_str(), params.address.size()); + + std::unique_ptr logger { nullptr, destroyFileEventLogger }; + + logger.reset(new FileEventLogger); + + logger->pmq = initPmq(); + + if (! logger->pmq) + { + LOG(EVENTLOGGER, ERR, "Failed to initialize File Event Queue"); + throw std::bad_alloc(); + } + + EventLoggerWorkerParams workerParams = {}; + workerParams.pmq = logger->pmq.get(); + workerParams.shared = &logger->shared; + workerParams.address = address; + workerParams.ids = params.ids; + + logger->workerThread.startWorkerThread(workerParams); + + return logger.release(); +} + +void destroyFileEventLogger(FileEventLogger *logger) +{ + LOG(EVENTLOGGER, NOTICE, "Requesting EventQ-Worker thread to shut down"); + { + LockedView shared = logger->shared.lockedView(); + shared->shuttingDown = true; + shared->readerCond.notify_one(); + } + + if (logger->workerThread.joinable()) + { + try { + logger->workerThread.join(); + } + catch (...) { + // An exception would mean that the thread wasn't started, + // contradicting the joinable() test. + assert(false); + } + } + + delete logger; +} + +struct FileEventLogItem +{ + const static constexpr uint16_t formatVersion = 2; + uint32_t eventFlags; + uint64_t numHardlinks; + struct FileEventData + { + FileEventType type; + std::string entryId; + std::string parentId; + std::string path; + std::string targetPath; + std::string targetParentId; + unsigned msgUserId; + int64_t timestamp; + + static void serialize(const FileEventData* obj, Serializer& ctx); + }; + + FileEventData event; + + static void serialize(const FileEventLogItem* obj, Serializer& ctx); +}; + +static constexpr size_t EVENT_BUFFER_SIZE = sizeof (FileEventLogItem::numHardlinks) + + sizeof (FileEventLogItem::formatVersion) + + sizeof (FileEventLogItem::eventFlags) + + sizeof (FileEventLogItem::FileEventData::type) + + sizeof (FileEventLogItem::FileEventData::msgUserId) + + sizeof (FileEventLogItem::FileEventData::timestamp) + + 4096 + sizeof (u_int32_t) // entryId + + 4096 + sizeof (u_int32_t) // parentEntryId + + 4096 + sizeof (u_int32_t) // path + + 4096 + sizeof (u_int32_t) // targetPath + + 4096 + sizeof (u_int32_t); // targetParentId + + +void logEvent(FileEventLogger *logger, const FileEvent& event, const EventContext& eventCtx) +{ + LockedView shared = logger->shared.lockedView(); + + FileEventLogItem logItem = { + eventCtx.eventFlags, + eventCtx.linkCount, + {event.type, + eventCtx.entryId, + eventCtx.parentId, + event.path, + event.targetValid ? event.target : std::string(), + eventCtx.targetParentId, + eventCtx.msgUserId, + eventCtx.timestamp + } + }; + + std::vector itemBuf(EVENT_BUFFER_SIZE); + unsigned itemSize; + + do { + Serializer ser(itemBuf.data(), itemBuf.size()); + ser % logItem; + itemSize = ser.size(); + + if (ser.good()) + break; + + LOG(EVENTLOGGER, WARNING, "Received a file event that exceeds the reserved buffer size.", + ser.size()); + + itemBuf.resize(ser.size()); + ser % logItem; + itemSize = ser.size(); + + if (ser.good()) + break; + + LOG(EVENTLOGGER, ERR, "Could not serialize file event."); + return; + } while (false); + + while (! pmq_enqueue_msg(logger->pmq, itemBuf.data(), itemSize)) + { + LOG(EVENTLOGGER, SPAM, "Producer flushing queue"); + // TODO make sure it was actually due to a full queue, and not some other error. + if (! pmq_sync(logger->pmq)) + { + LOG_DBG(EVENTLOGGER, ERR, "Failed to flush PMQ: pmq_sync() failed"); + return; + } + } + + LOG(EVENTLOGGER, SPAM, "Enqueued message:", itemSize); + // make sure that new messages get flushed regularly. + { + if (!shared->flushTimer.isSet()) + { + auto now = getNow(); + shared->flushTimer.reset(now, Milliseconds(1000)); + } + } + + if (shared->numReadersWaiting > 0) + { + //LOG(EVENTLOGGER, SPAM, "Notifying reader"); + shared->readerCond.notify_all(); + } +} + +void FileEventLogItem::serialize(const FileEventLogItem* obj, Serializer& ctx) +{ + ctx + % FileEventLogItem::formatVersion + % obj->eventFlags + % obj->numHardlinks + % obj->event; +} + +void FileEventLogItem::FileEventData::serialize(const FileEventData* obj, Serializer& ctx) +{ + ctx % obj->type + % obj->entryId + % obj->parentId + % obj->path + % obj->targetPath + % obj->targetParentId + % obj->msgUserId + % obj->timestamp; +} + + + +EventContext makeEventContext(EntryInfo* entryInfo, std::string parentId, unsigned msgUserId, + std::string targetParentId, unsigned linkCount, bool isSecondary) +{ + uint32_t eventFlags = EventContext::EVENTFLAG_NONE; + if (entryInfo->getIsBuddyMirrored()) + eventFlags |= EventContext::EVENTFLAG_MIRRORED; + if (isSecondary) + eventFlags |= EventContext::EVENTFLAG_SECONDARY; + + EventContext eventContext = {}; + eventContext.entryId = entryInfo->getEntryID(); + eventContext.parentId = std::move(parentId); + eventContext.msgUserId = msgUserId; + eventContext.targetParentId = std::move(targetParentId); + eventContext.linkCount = linkCount; + eventContext.timestamp = std::chrono::duration_cast(std::chrono::system_clock::now().time_since_epoch()).count(); + eventContext.eventFlags = eventFlags; + + return eventContext; +} diff --git a/meta/source/components/FileEventLogger.h b/meta/source/components/FileEventLogger.h new file mode 100644 index 0000000..cfa2442 --- /dev/null +++ b/meta/source/components/FileEventLogger.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include + +struct EventContext +{ + static constexpr uint32_t EVENTFLAG_NONE = 0; + static constexpr uint32_t EVENTFLAG_MIRRORED = (1 << 0); // Event is for a mirrored entry + static constexpr uint32_t EVENTFLAG_SECONDARY = (1 << 1); // Event generated by secondary node + + std::string entryId; + std::string parentId; + unsigned msgUserId; + std::string targetParentId; + unsigned linkCount; + int64_t timestamp; + uint32_t eventFlags; // bitwise OR of EVENTFLAG_ values above. +}; + +EventContext makeEventContext(EntryInfo* entryInfo, std::string parentId, unsigned msgUserId, + std::string targetParentId, unsigned linkCount, bool isSecondary); + + +struct FileEventLoggerIds +{ + uint32_t nodeId; + uint16_t buddyGroupId; +}; + +struct FileEventLoggerParams +{ + std::string address; + FileEventLoggerIds ids; +}; + +struct FileEventLogger; + +FileEventLogger *createFileEventLogger(FileEventLoggerParams const& params); +void destroyFileEventLogger(FileEventLogger *logger); +void logEvent(FileEventLogger *logger, FileEvent const& event, EventContext const& eventCtx); diff --git a/meta/source/components/InternodeSyncer.cpp b/meta/source/components/InternodeSyncer.cpp new file mode 100644 index 0000000..8776f5f --- /dev/null +++ b/meta/source/components/InternodeSyncer.cpp @@ -0,0 +1,1343 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "InternodeSyncer.h" + +#include + +// forward declaration +namespace UUID { + std::string getMachineUUID(); +} + +InternodeSyncer::InternodeSyncer(TargetConsistencyState initialConsistencyState) + : PThread("XNodeSync"), + log("XNodeSync"), + forcePoolsUpdate(true), + forceTargetStatesUpdate(true), + forcePublishCapacities(true), + forceStoragePoolsUpdate(true), + offlineWait(Program::getApp()->getConfig() ), + nodeConsistencyState(initialConsistencyState), + buddyResyncInProgress(false) +{ + MirrorBuddyGroupMapper* mbg = Program::getApp()->getMetaBuddyGroupMapper(); + MirrorBuddyState buddyState = mbg->getBuddyState(Program::getApp()->getLocalNodeNumID().val() ); + + if ((buddyState == BuddyState_PRIMARY) + && (nodeConsistencyState == TargetConsistencyState_NEEDS_RESYNC)) + offlineWait.startTimer(); +} + +void InternodeSyncer::run() +{ + try + { + registerSignalHandler(); + + syncLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void InternodeSyncer::syncLoop() +{ + const App* app = Program::getApp(); + const Config* cfg = app->getConfig(); + + const int sleepIntervalMS = 3*1000; // 3sec + + // If (undocumented) sysUpdateTargetStatesSecs is set in config, use that value, otherwise + // default to 1/6 sysTargetOfflineTimeoutSecs. + const unsigned updateTargetStatesMS = + (cfg->getSysUpdateTargetStatesSecs() != 0) + ? cfg->getSysUpdateTargetStatesSecs() * 1000 + : cfg->getSysTargetOfflineTimeoutSecs() * 166; + + const unsigned updateCapacityPoolsMS = 4 * updateTargetStatesMS; + + const unsigned metaCacheSweepNormalMS = 5*1000; // 5sec + const unsigned metaCacheSweepStressedMS = 2*1000; // 2sec + const unsigned idleDisconnectIntervalMS = 70*60*1000; /* 70 minutes (must be less than half the + streamlis idle disconnect interval to avoid cases where streamlis disconnects first) */ + const unsigned updateIDTimeMS = 60 * 1000; // 1 min + const unsigned downloadNodesIntervalMS = 300000; // 5 min + const unsigned updateStoragePoolsMS = downloadNodesIntervalMS; + const unsigned checkNetworkIntervalMS = 60*1000; // 1 minute + + Time lastCapacityUpdateT; + Time lastMetaCacheSweepT; + Time lastIdleDisconnectT; + Time lastTimeIDSet; + Time lastTargetStatesUpdateT; + Time lastDownloadNodesT; + Time lastStoragePoolsUpdateT; + Time lastCapacityPublishedT; + Time lastCheckNetworkT; + bool doRegisterLocalNode = false; + + unsigned currentCacheSweepMS = metaCacheSweepNormalMS; // (adapted inside the loop below) + + + while(!waitForSelfTerminateOrder(sleepIntervalMS) ) + { + const bool capacityPoolsUpdateForced = forcePoolsUpdate.exchange(false); + const bool doCapacityPoolsUpdate = capacityPoolsUpdateForced + || (lastCapacityUpdateT.elapsedMS() > updateCapacityPoolsMS); + const bool doTargetStatesUpdate = forceTargetStatesUpdate.exchange(false) + || (lastTargetStatesUpdateT.elapsedMS() > updateTargetStatesMS); + const bool doPublishCapacities = forcePublishCapacities.exchange(false) + || (lastCapacityPublishedT.elapsedMS() > updateTargetStatesMS); + const bool doStoragePoolsUpdate = forceStoragePoolsUpdate.exchange(false) + || (lastStoragePoolsUpdateT.elapsedMS() > updateStoragePoolsMS); + const bool doCheckNetwork = forceCheckNetwork.exchange(false) + || (lastCheckNetworkT.elapsedMS() > checkNetworkIntervalMS); + + if (doCheckNetwork) + { + if (checkNetwork()) + doRegisterLocalNode = true; + lastCheckNetworkT.setToNow(); + } + + if (doRegisterLocalNode) + doRegisterLocalNode = !registerNode(app->getDatagramListener()); + + // download & sync nodes + if (lastDownloadNodesT.elapsedMS() > downloadNodesIntervalMS) + { + downloadAndSyncNodes(); + downloadAndSyncTargetMappings(); + + lastDownloadNodesT.setToNow(); + } + + if (doStoragePoolsUpdate) + { + downloadAndSyncStoragePools(); + + lastStoragePoolsUpdateT.setToNow(); + } + + if(doCapacityPoolsUpdate) + { + updateMetaCapacityPools(); + updateMetaBuddyCapacityPools(); + + if ( (capacityPoolsUpdateForced) && (!doStoragePoolsUpdate) ) + { // capacity pools changed, but storage pools were not downloaded (i.e. no update on + // storage capactity pools was made) + updateStorageCapacityPools(); + updateTargetBuddyCapacityPools(); + } + + lastCapacityUpdateT.setToNow(); + } + + if(lastMetaCacheSweepT.elapsedMS() > currentCacheSweepMS) + { + bool flushTriggered = app->getMetaStore()->cacheSweepAsync(); + currentCacheSweepMS = (flushTriggered ? metaCacheSweepStressedMS : metaCacheSweepNormalMS); + + lastMetaCacheSweepT.setToNow(); + } + + if(lastIdleDisconnectT.elapsedMS() > idleDisconnectIntervalMS) + { + dropIdleConns(); + lastIdleDisconnectT.setToNow(); + } + + if(lastTimeIDSet.elapsedMS() > updateIDTimeMS) + { + StorageTk::resetIDCounterToNow(); + lastTimeIDSet.setToNow(); + } + + if(doTargetStatesUpdate) + { + if (this->offlineWait.hasTimeout() ) + { + // if we're waiting to be offlined, set our local state to needs-resync and don't report + // anything to the mgmtd + setNodeConsistencyState(TargetConsistencyState_NEEDS_RESYNC); + } + else + { + TargetConsistencyState newConsistencyState; + if (updateMetaStatesAndBuddyGroups(newConsistencyState, true)) + setNodeConsistencyState(newConsistencyState); + downloadAndSyncTargetStatesAndBuddyGroups(); + } + + lastTargetStatesUpdateT.setToNow(); + } + + if (doPublishCapacities) + { + publishNodeCapacity(); + lastCapacityPublishedT.setToNow(); + } + } +} + +/** + * Inspect the available and allowed network interfaces for any changes. + */ +bool InternodeSyncer::checkNetwork() +{ + App* app = Program::getApp(); + NicAddressList newLocalNicList; + bool res = false; + + app->findAllowedInterfaces(newLocalNicList); + app->findAllowedRDMAInterfaces(newLocalNicList); + if (!std::equal(newLocalNicList.begin(), newLocalNicList.end(), app->getLocalNicList().begin())) + { + log.log(Log_NOTICE, "checkNetwork: local interfaces have changed"); + app->updateLocalNicList(newLocalNicList); + res = true; + } + + return res; +} + +bool InternodeSyncer::updateMetaCapacityPools() +{ + NodeCapacityPools* pools = Program::getApp()->getMetaCapacityPools(); + + auto downloadRes = downloadCapacityPools(CapacityPoolQuery_META); + + if(!downloadRes.first) + return false; + + // poolsMap does only contain one element with INVALID_POOL_ID in this case + const auto& capacityPoolLists = downloadRes.second[StoragePoolStore::INVALID_POOL_ID]; + + pools->syncPoolsFromLists(capacityPoolLists); + return true; +} + +bool InternodeSyncer::updateMetaBuddyCapacityPools() +{ + NodeCapacityPools* pools = Program::getApp()->getMetaBuddyCapacityPools(); + + auto downloadRes = downloadCapacityPools(CapacityPoolQuery_METABUDDIES); + + if(!downloadRes.first) + return false; + + // poolsMap does only contain one element with INVALID_POOL_ID in this case + const auto& capacityPoolLists = downloadRes.second[StoragePoolStore::INVALID_POOL_ID]; + + pools->syncPoolsFromLists(capacityPoolLists); + return true; +} + +bool InternodeSyncer::updateStorageCapacityPools() +{ + const App* app = Program::getApp(); + const Config* cfg = app->getConfig(); + const TargetMapper* targetMapper = app->getTargetMapper(); + + const auto downloadRes = downloadCapacityPools(CapacityPoolQuery_STORAGE); + + if(!downloadRes.first) + return false; + + TargetMap targetMap; + + if(cfg->getSysTargetAttachmentMap() ) + targetMap = *cfg->getSysTargetAttachmentMap(); // user-provided custom assignment map + else + targetMap = targetMapper->getMapping(); + + const GetNodeCapacityPoolsRespMsg::PoolsMap& poolsMap = downloadRes.second; + + bool failed = false; + for (auto iter = poolsMap.begin(); iter != poolsMap.end(); iter++) + { + StoragePoolPtr storagePool = app->getStoragePoolStore()->getPool(iter->first); + if (!storagePool) + { + LOG(CAPACITY, ERR, "Received capacity pools for unknown storage pool.", + ("storagePoolId", iter->first)); + + failed = true; + continue; + } + + storagePool->getTargetCapacityPools()->syncPoolsFromLists(iter->second, targetMap); + } + + return !failed; +} + +bool InternodeSyncer::updateTargetBuddyCapacityPools() +{ + const App* app = Program::getApp(); + + auto downloadRes = downloadCapacityPools(CapacityPoolQuery_STORAGEBUDDIES); + + if(!downloadRes.first) + return false; + + const GetNodeCapacityPoolsRespMsg::PoolsMap& poolsMap = downloadRes.second; + + bool failed = false; + for (auto iter = poolsMap.begin(); iter != poolsMap.end(); iter++) + { + StoragePoolPtr storagePool = app->getStoragePoolStore()->getPool(iter->first); + if (!storagePool) + { + LOG(CAPACITY, ERR, "Received capacity pools for unknown storage pool.", + ("storagePoolId", iter->first)); + + failed = true; + continue; + } + + storagePool->getBuddyCapacityPools()->syncPoolsFromLists(iter->second); + } + + return !failed; +} + +/** + * @return a pair, with first being false on error/true on success and second being the downloaded + * map of capacity pools sorted by storage pool + */ +std::pair InternodeSyncer::downloadCapacityPools( + CapacityPoolQueryType poolType) +{ + LOG(STATES, DEBUG, "Downloading capacity pools.", + ("Pool type", GetNodeCapacityPoolsMsg::queryTypeToStr(poolType))); + + NodeStore* mgmtNodes = Program::getApp()->getMgmtNodes(); + + auto node = mgmtNodes->referenceFirstNode(); + if(!node) + return std::make_pair(false, GetNodeCapacityPoolsRespMsg::PoolsMap()); + + GetNodeCapacityPoolsMsg msg(poolType); + RequestResponseArgs rrArgs(node.get(), &msg, NETMSGTYPE_GetNodeCapacityPoolsResp); + GetNodeCapacityPoolsRespMsg* respMsgCast; + +#ifndef BEEGFS_DEBUG + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + // connect & communicate + bool commRes = MessagingTk::requestResponse(&rrArgs); + if(!commRes) + return std::make_pair(false, GetNodeCapacityPoolsRespMsg::PoolsMap()); + + // handle result + respMsgCast = (GetNodeCapacityPoolsRespMsg*)rrArgs.outRespMsg.get(); + + GetNodeCapacityPoolsRespMsg::PoolsMap poolsMap = respMsgCast->getPoolsMap(); + + return std::make_pair(true, poolsMap); +} + +/** + * @return true if an ack was received for the heartbeat, false otherwise + */ +bool InternodeSyncer::registerNode(AbstractDatagramListener* dgramLis) +{ + const char* logContext = "Register node"; + static bool registrationFailureLogged = false; // to avoid log spamming + + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + Config* cfg = app->getConfig(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + Node& localNode = Program::getApp()->getLocalNode(); + NumNodeID localNodeNumID = localNode.getNumID(); + NumNodeID rootNodeID = app->getMetaRoot().getID(); + bool rootIsBuddyMirrored = app->getMetaRoot().getIsMirrored(); + NicAddressList nicList(localNode.getNicList()); + + HeartbeatMsg msg(localNode.getAlias(), localNodeNumID, NODETYPE_Meta, &nicList); + msg.setRootNumID(rootNodeID); + msg.setRootIsBuddyMirrored(rootIsBuddyMirrored); + msg.setPorts(cfg->getConnMetaPort(), cfg->getConnMetaPort() ); + auto uuid = UUID::getMachineUUID(); + if (uuid.empty()) { + LogContext(logContext).log(Log_CRITICAL, + "Couldn't determine UUID for machine. Node registration not possible."); + return false; + } + msg.setMachineUUID(uuid); + + bool nodeRegistered = dgramLis->sendToNodeUDPwithAck(mgmtNode, &msg); + + if(nodeRegistered) + LogContext(logContext).log(Log_WARNING, "Node registration successful."); + else + if(!registrationFailureLogged) + { + LogContext(logContext).log(Log_CRITICAL, + "Node registration not successful. Management node offline? Will keep on trying..."); + registrationFailureLogged = true; + } + + return nodeRegistered; +} + +/** + * Download and sync metadata server target states and mirror buddy groups. + * + * @param outConsistencyState The new node consistency state. + */ +bool InternodeSyncer::updateMetaStatesAndBuddyGroups(TargetConsistencyState& outConsistencyState, + bool publish) +{ + LOG(STATES, DEBUG, "Starting state update."); + + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + TargetStateStore* metaStateStore = app->getMetaStateStore(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMetaBuddyGroupMapper(); + + static bool downloadFailedLogged = false; // to avoid log spamming + static bool publishFailedLogged = false; + + NumNodeID localNodeID = app->getLocalNodeNumID(); + + auto node = mgmtNodes->referenceFirstNode(); + if(!node) + { + LOG(STATES, ERR, "Management node not defined."); + return false; + } + + unsigned numRetries = 10; // If publishing states fails 10 times, give up (-> POFFLINE). + + // Note: Publishing fails if between downloadStatesAndBuddyGroups and + // publishLocalTargetStateChanges, a state on the mgmtd is changed (e.g. because the primary + // sets NEEDS_RESYNC for the secondary). In that case, we will retry. + + LOG(STATES, DEBUG, "Beginning target state update..."); + bool publishSuccess = false; + + while (!publishSuccess && (numRetries--) ) + { + MirrorBuddyGroupMap buddyGroups; + TargetStateMap states; + + bool downloadRes = NodesTk::downloadStatesAndBuddyGroups(*node, NODETYPE_Meta, buddyGroups, + states, true); + + if (!downloadRes) + { + if (!downloadFailedLogged) + { + LOG(STATES, WARNING, + "Downloading target states from management node failed. " + "Setting all target states to probably-offline."); + downloadFailedLogged = true; + } + + metaStateStore->setAllStates(TargetReachabilityState_POFFLINE); + + break; + } + + downloadFailedLogged = false; + + // Sync buddy groups here, because decideResync depends on it. + metaStateStore->syncStatesAndGroups(buddyGroupMapper, states, + std::move(buddyGroups), localNodeID); + + CombinedTargetState newStateFromMgmtd; + // Find local state which was sent by mgmtd + for (const auto& state : states) + { + if (state.first == localNodeID.val()) + { + newStateFromMgmtd = CombinedTargetState(state.second.reachabilityState, + state.second.consistencyState); + } + } + + TargetConsistencyState localChangedState = decideResync(newStateFromMgmtd); + outConsistencyState = localChangedState; + + if (!publish) + { + metaStateStore->setState(localNodeID.val(), + CombinedTargetState(TargetReachabilityState_ONLINE, localChangedState) ); + + return true; + } + + + // Note: In this case "old" means "before we changed it locally". + TargetConsistencyState oldState = newStateFromMgmtd.consistencyState; + + publishSuccess = publishNodeStateChange(oldState, localChangedState); + + if (publishSuccess) + { + metaStateStore->setState(localNodeID.val(), + CombinedTargetState(TargetReachabilityState_ONLINE, localChangedState) ); + + BuddyCommTk::checkBuddyNeedsResync(); + } + } + + if (!publishSuccess) + { + if (!publishFailedLogged) + { + LOG(STATES, WARNING, "Pushing local state to management node failed."); + publishFailedLogged = true; + } + } + else + publishFailedLogged = false; + + return true; +} + +/** + * Synchronize local client sessions with registered mgmtd clients to release orphaned sessions. + * + * @param clientsList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @param allowRemoteComm usually true; setting this to false is only useful when called during + * app shutdown to avoid communication; if false, unlocking of user locks, closing of storage server + * files and disposal of unlinked files won't be performed + */ +void InternodeSyncer::syncClients(const std::vector& clientsList, bool allowRemoteComm) +{ + const char* logContext = "Sync clients"; + App* app = Program::getApp(); + MetaStore* metaStore = Program::getApp()->getMetaStore(); + SessionStore* sessions = app->getSessions(); + SessionStore* mirroredSessions = app->getMirroredSessions(); + + SessionList removedSessions; + NumNodeIDList unremovableSessions; + + sessions->syncSessions(clientsList, removedSessions, unremovableSessions); + mirroredSessions->syncSessions(clientsList, removedSessions, unremovableSessions); + + // print client session removal results (upfront) + if(!removedSessions.empty() || !unremovableSessions.empty()) + { + std::ostringstream logMsgStream; + logMsgStream << "Removing " << removedSessions.size() << " client sessions."; + + if(unremovableSessions.empty() ) + LogContext(logContext).log(Log_DEBUG, logMsgStream.str() ); // no unremovable sessions + else + { // unremovable sessions found => log warning + logMsgStream << " (" << unremovableSessions.size() << " are unremovable)"; + LogContext(logContext).log(Log_WARNING, logMsgStream.str() ); + } + } + + + // walk over all removed sessions (to cleanup the contained files) + + SessionListIter sessionIter = removedSessions.begin(); + for( ; sessionIter != removedSessions.end(); sessionIter++) + { // walk over all client sessions: cleanup each session + Session* session = *sessionIter; + NumNodeID sessionID = session->getSessionID(); + SessionFileStore* sessionFiles = session->getFiles(); + + SessionFileList removedSessionFiles; + UIntList referencedSessionFiles; + + sessionFiles->removeAllSessions(&removedSessionFiles, &referencedSessionFiles); + + /* note: referencedSessionFiles should always be empty, because otherwise the reference holder + would also hold a reference to the client session (and we woudn't be here if the client + session had any references) */ + + + // print session files results (upfront) + + if (!removedSessionFiles.empty() || !referencedSessionFiles.empty()) + { + std::ostringstream logMsgStream; + logMsgStream << "Removing " << removedSessionFiles.size() << " file sessions. (" + << referencedSessionFiles.size() << " are unremovable). clientNumID: " << sessionID; + if (referencedSessionFiles.empty()) + LogContext(logContext).log(Log_SPAM, logMsgStream.str() ); + else + LogContext(logContext).log(Log_NOTICE, logMsgStream.str() ); + } + + + // walk over all files in the current session (to clean them up) + + SessionFileListIter fileIter = removedSessionFiles.begin(); + + for( ; fileIter != removedSessionFiles.end(); fileIter++) + { // walk over all files: unlock user locks, close meta, close local, dispose unlinked + SessionFile* sessionFile = *fileIter; + unsigned ownerFD = sessionFile->getSessionID(); + unsigned accessFlags = sessionFile->getAccessFlags(); + unsigned numHardlinks; + unsigned numInodeRefs; + bool lastWriterClosed; // ignored here! + + MetaFileHandle inode = sessionFile->releaseInode(); + std::string fileID = inode->getEntryID(); + + std::string fileHandleID = SessionTk::generateFileHandleID(ownerFD, fileID); + + // save nodeIDs for later + StripePattern* pattern = inode->getStripePattern(); + int maxUsedNodesIndex = pattern->getStripeTargetIDs()->size() - 1; + + // unlock all user locks + auto appendGranted = inode->flockAppendCancelByClientID(sessionID); + auto flockGranted = inode->flockEntryCancelByClientID(sessionID); + auto rangeGranted = inode->flockRangeCancelByClientID(sessionID); + + if(allowRemoteComm) + { + LockingNotifier::notifyWaitersEntryLock(LockEntryNotifyType_APPEND, + inode->getReferenceParentID(), inode->getEntryID(), inode->getIsBuddyMirrored(), + std::move(appendGranted)); + LockingNotifier::notifyWaitersEntryLock(LockEntryNotifyType_FLOCK, + inode->getReferenceParentID(), inode->getEntryID(), inode->getIsBuddyMirrored(), + std::move(flockGranted)); + LockingNotifier::notifyWaitersRangeLock(inode->getReferenceParentID(), + inode->getEntryID(), inode->getIsBuddyMirrored(), std::move(rangeGranted)); + } + + EntryInfo* entryInfo = sessionFile->getEntryInfo(); + + FileIDLock lock(sessions->getEntryLockStore(), entryInfo->getEntryID(), true); + + if(allowRemoteComm) + MsgHelperClose::closeChunkFile(sessionID, fileHandleID.c_str(), + maxUsedNodesIndex, *inode, entryInfo, NETMSG_DEFAULT_USERID); + + LogContext(logContext).log(Log_NOTICE, "closing file. ParentID: " + + entryInfo->getParentEntryID() + " FileName: " + entryInfo->getFileName() ); + + metaStore->closeFile(entryInfo, std::move(inode), accessFlags, &numHardlinks, + &numInodeRefs, lastWriterClosed); + + if(allowRemoteComm && !numHardlinks && !numInodeRefs) + MsgHelperClose::unlinkDisposableFile(fileID, NETMSG_DEFAULT_USERID, + entryInfo->getIsBuddyMirrored()); + + delete sessionFile; + } // end of files loop + + + delete(session); + + } // end of client sessions loop +} + +bool InternodeSyncer::downloadAndSyncNodes() +{ + LOG(STATES, DEBUG, "Starting node list sync."); + + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + Node& localNode = app->getLocalNode(); + NodeStore* metaNodes = app->getMetaNodes(); + NodeStore* storageNodes = app->getStorageNodes(); + + // metadata nodes + + std::vector metaNodesList; + NumNodeIDList addedMetaNodes; + NumNodeIDList removedMetaNodes; + NumNodeID rootNodeID; + bool rootIsBuddyMirrored; + + if(NodesTk::downloadNodes(*mgmtNode, NODETYPE_Meta, metaNodesList, true, &rootNodeID, + &rootIsBuddyMirrored) ) + { + metaNodes->syncNodes(metaNodesList, &addedMetaNodes, &removedMetaNodes, &localNode); + if (app->getMetaRoot().setIfDefault(rootNodeID, rootIsBuddyMirrored)) + { + LOG(STATES, CRITICAL, + std::string("Root node ID (from sync results): ") + rootNodeID.str()); + app->getRootDir()->setOwnerNodeID(rootNodeID); + } + + printSyncResults(NODETYPE_Meta, &addedMetaNodes, &removedMetaNodes); + } + + // storage nodes + + std::vector storageNodesList; + NumNodeIDList addedStorageNodes; + NumNodeIDList removedStorageNodes; + + if(NodesTk::downloadNodes(*mgmtNode, NODETYPE_Storage, storageNodesList, true) ) + { + storageNodes->syncNodes(storageNodesList, &addedStorageNodes, &removedStorageNodes, + &localNode); + printSyncResults(NODETYPE_Storage, &addedStorageNodes, &removedStorageNodes); + } + + return true; +} + +void InternodeSyncer::downloadAndSyncClients(bool requeue) +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + NodeStoreClients* clientNodes = app->getClientNodes(); + TimerQueue* timerQ = app->getTimerQueue(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return; + + std::vector clientNodesList; + NumNodeIDList addedClientNodes; + NumNodeIDList removedClientNodes; + + if(NodesTk::downloadNodes(*mgmtNode, NODETYPE_Client, clientNodesList, true) ) + { + clientNodes->syncNodes(clientNodesList, &addedClientNodes, &removedClientNodes); + printSyncResults(NODETYPE_Client, &addedClientNodes, &removedClientNodes); + + syncClients(clientNodesList, true); // sync client sessions + } + + if (requeue) + timerQ->enqueue(std::chrono::minutes(5), + [] { InternodeSyncer::downloadAndSyncClients(true); }); +} + +bool InternodeSyncer::downloadAndSyncTargetMappings() +{ + LOG(STATES, DEBUG, "Syncing target mappings."); + + App* app = Program::getApp(); + TargetMapper* targetMapper = app->getTargetMapper(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + auto mappings = NodesTk::downloadTargetMappings(*mgmtNode, true); + if (mappings.first) + targetMapper->syncTargets(std::move(mappings.second)); + + return true; +} + +bool InternodeSyncer::downloadAndSyncStoragePools() +{ + LOG(STORAGEPOOLS, DEBUG, "Syncing storage pools."); + + const App* app = Program::getApp(); + const NodeStore* mgmtNodes = app->getMgmtNodes(); + StoragePoolStore* storagePoolStore = app->getStoragePoolStore(); + + const auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + StoragePoolPtrVec storagePools; + + // note: storage pool download does include capacity pools + const bool downloadStoragePoolsRes = + NodesTk::downloadStoragePools(*mgmtNode, storagePools, true); + if (!downloadStoragePoolsRes) + return false; + + storagePoolStore->syncFromVector(storagePools); + + return true; +} + +/** + * Download and sync storage target states and mirror buddy groups. + */ +bool InternodeSyncer::downloadAndSyncTargetStatesAndBuddyGroups() +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getStorageBuddyGroupMapper(); + TargetStateStore* targetStates = app->getTargetStateStore(); + + LOG(STATES, DEBUG, "Downloading target states and buddy groups"); + + static bool downloadFailedLogged = false; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + TargetStateMap states; + + MirrorBuddyGroupMap buddyGroups; + + bool downloadRes = NodesTk::downloadStatesAndBuddyGroups(*mgmtNode, NODETYPE_Storage, + buddyGroups, states, true); + + if ( downloadRes ) + { + targetStates->syncStatesAndGroups(buddyGroupMapper, states, + std::move(buddyGroups), app->getLocalNode().getNumID()); + + downloadFailedLogged = false; + } + else + { // download failed, so we don't know actual status => carefully set all to poffline + + if(!downloadFailedLogged) + { + LOG(STATES, WARNING, "Download from management node failed. " + "Setting all targets to probably-offline."); + downloadFailedLogged = true; + } + + targetStates->setAllStates(TargetReachabilityState_POFFLINE); + } + + return true; +} + +void InternodeSyncer::printSyncResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes) +{ + Logger* const logger = Logger::getLogger(); + const int logLevel = nodeType == NODETYPE_Client ? Log_DEBUG : Log_WARNING; + + if (!addedNodes->empty()) + logger->log(LogTopic_STATES, logLevel, __func__, + std::string("Nodes added (sync results): ") + + StringTk::uintToStr(addedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); + + if (!removedNodes->empty()) + logger->log(LogTopic_STATES, logLevel, __func__, + std::string("Nodes removed (sync results): ") + + StringTk::uintToStr(removedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); +} + +/** + * Decides new consistency state based on local state, buddy group membership and state fetched + * from management node. + * + * @param newState New state from the management node. + * @returns The new target consistency state. + */ +TargetConsistencyState InternodeSyncer::decideResync(const CombinedTargetState newState) +{ + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + InternodeSyncer* internodeSyncer = app->getInternodeSyncer(); + NumNodeID localNodeID = app->getLocalNodeNumID(); + + MirrorBuddyState buddyState = metaBuddyGroupMapper->getBuddyState(localNodeID.val()); + + if (buddyState == BuddyState_UNMAPPED) + return TargetConsistencyState_GOOD; + + // If the consistency state is BAD, it stays BAD until admin intervenes. + if (internodeSyncer && // during early startup, INS is not constructed yet. + internodeSyncer->getNodeConsistencyState() == TargetConsistencyState_BAD) + return TargetConsistencyState_BAD; + + const bool isResyncing = newState.consistencyState == TargetConsistencyState_NEEDS_RESYNC; + const bool isBad = newState.consistencyState == TargetConsistencyState_BAD; + + if (internodeSyncer && + internodeSyncer->getNodeConsistencyState() == TargetConsistencyState_NEEDS_RESYNC) + { + // If we're already (locally) maked as needs resync, this state can only be left when our + // (primary) buddy tells us the resync is finished. + return TargetConsistencyState_NEEDS_RESYNC; + } + else if (isResyncing || isBad) + { + return TargetConsistencyState_NEEDS_RESYNC; + } + else + { + // If mgmtd reports the target is (P)OFFLINE, then the meta server knows better and we set our + // state to GOOD / ONLINE. Otherwise we accept the state reported by the mgmtd. + if ( (newState.reachabilityState == TargetReachabilityState_OFFLINE) + || (newState.reachabilityState == TargetReachabilityState_POFFLINE) ) + return TargetConsistencyState_GOOD; + else + return newState.consistencyState; + } +} + +bool InternodeSyncer::publishNodeStateChange(const TargetConsistencyState oldState, + const TargetConsistencyState newState) +{ + const char* logContext = "Publish node state"; + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + const NumNodeID localNodeID = app->getLocalNodeNumID(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + LogContext(logContext).logErr("Management node not defined."); + return false; + } + + bool res; + + UInt8List oldStateList(1, oldState); + UInt8List newStateList(1, newState); + UInt16List nodeIDList(1, localNodeID.val()); + + ChangeTargetConsistencyStatesMsg msg(NODETYPE_Meta, &nodeIDList, &oldStateList, &newStateList); + RequestResponseArgs rrArgs(mgmtNode.get(), &msg, NETMSGTYPE_ChangeTargetConsistencyStatesResp); + +#ifndef BEEGFS_DEBUG + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + bool sendRes = MessagingTk::requestResponse(&rrArgs); + + static bool failureLogged = false; + + if (!sendRes) + { + if (!failureLogged) + LogContext(logContext).log(Log_CRITICAL, "Pushing node state to management node failed."); + + res = false; + failureLogged = true; + } + else + { + const auto respMsgCast = + static_cast(rrArgs.outRespMsg.get()); + + if ( (FhgfsOpsErr)respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_CRITICAL, "Management node did not accept node state change."); + res = false; + } + else + { + res = true; + } + + failureLogged = false; + } + + return res; +} + +/** + * Send local node free space / free inode info to management node. + */ +void InternodeSyncer::publishNodeCapacity() +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.logErr("Management node not defined."); + return; + } + + int64_t sizeTotal = 0; + int64_t sizeFree = 0; + int64_t inodesTotal = 0; + int64_t inodesFree = 0; + + std::string metaPath = app->getMetaPath(); + getStatInfo(&sizeTotal, &sizeFree, &inodesTotal, &inodesFree); + + StorageTargetInfo targetInfo(app->getLocalNodeNumID().val(), metaPath, sizeTotal, sizeFree, + inodesTotal, inodesFree, getNodeConsistencyState()); + // Note: As long as we don't have meta-HA, consistency state will always be GOOD. + + StorageTargetInfoList targetInfoList(1, targetInfo); + + SetStorageTargetInfoMsg msg(NODETYPE_Meta, &targetInfoList); + RequestResponseArgs rrArgs(mgmtNode.get(), &msg, NETMSGTYPE_SetStorageTargetInfoResp); + +#ifndef BEEGFS_DEBUG + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif // BEEGFS_DEBUG + + bool sendRes = MessagingTk::requestResponse(&rrArgs); + + static bool failureLogged = false; + if (!sendRes) + { + if (!failureLogged) + log.log(Log_CRITICAL, "Pushing node free space to management node failed."); + + failureLogged = true; + return; + } + else + { + const auto respMsgCast = (const SetStorageTargetInfoRespMsg*)rrArgs.outRespMsg.get(); + failureLogged = false; + + if ( (FhgfsOpsErr)respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + log.log(Log_CRITICAL, "Management node did not accept free space info message."); + return; + } + + } + + // If we were just started and are publishing our capacity for the first time, force a pool + // refresh on the mgmtd so we're not stuck in the emergency pool until the first regular + // pool refresh. + static bool firstTimePubilished = true; + if (firstTimePubilished) + { + forceMgmtdPoolsRefresh(); + firstTimePubilished = false; + } + +} + +/** + * Drop/reset idle conns from all server stores. + */ +void InternodeSyncer::dropIdleConns() +{ + App* app = Program::getApp(); + + unsigned numDroppedConns = 0; + + numDroppedConns += dropIdleConnsByStore(app->getMgmtNodes() ); + numDroppedConns += dropIdleConnsByStore(app->getMetaNodes() ); + numDroppedConns += dropIdleConnsByStore(app->getStorageNodes() ); + + if(numDroppedConns) + { + log.log(Log_DEBUG, "Dropped idle connections: " + StringTk::uintToStr(numDroppedConns) ); + } +} + +/** + * Walk over all nodes in the given store and drop/reset idle connections. + * + * @return number of dropped connections + */ +unsigned InternodeSyncer::dropIdleConnsByStore(NodeStoreServers* nodes) +{ + App* app = Program::getApp(); + + unsigned numDroppedConns = 0; + + for (const auto& node : nodes->referenceAllNodes()) + { + /* don't do any idle disconnect stuff with local node + (the LocalNodeConnPool doesn't support and doesn't need this kind of treatment) */ + + if (node.get() != &app->getLocalNode()) + { + NodeConnPool* connPool = node->getConnPool(); + + numDroppedConns += connPool->disconnectAndResetIdleStreams(); + } + } + + return numDroppedConns; +} + +void InternodeSyncer::getStatInfo(int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree) +{ + const char* logContext = "GetStorageTargetInfoMsg (stat path)"; + + std::string targetPathStr = Program::getApp()->getMetaPath(); + + bool statSuccess = StorageTk::statStoragePath(targetPathStr, outSizeTotal, outSizeFree, + outInodesTotal, outInodesFree); + + if(unlikely(!statSuccess) ) + { // error + LogContext(logContext).logErr("Unable to statfs() storage path: " + targetPathStr + + " (SysErr: " + System::getErrString() ); + + *outSizeTotal = -1; + *outSizeFree = -1; + *outInodesTotal = -1; + *outInodesFree = -1; + } + + // read and use value from manual free space override file (if it exists) + StorageTk::statStoragePathOverride(targetPathStr, outSizeFree, outInodesFree); +} + +/** + * Tell mgmtd to update its capacity pools. + */ +void InternodeSyncer::forceMgmtdPoolsRefresh() +{ + App* app = Program::getApp(); + DatagramListener* dgramLis = app->getDatagramListener(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.log(Log_DEBUG, "Management node not defined."); + return; + } + + RefreshCapacityPoolsMsg msg; + + bool ackReceived = dgramLis->sendToNodeUDPwithAck(mgmtNode, &msg); + + if (!ackReceived) + log.log(Log_DEBUG, "Management node did not accept pools refresh request."); +} + + + +/** + * @return false on error + */ +bool InternodeSyncer::downloadExceededQuotaList(StoragePoolId storagePoolId, QuotaDataType idType, + QuotaLimitType exType, UIntList* outIDList, FhgfsOpsErr& error) +{ + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + bool retVal = false; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + RequestExceededQuotaMsg msg(idType, exType, storagePoolId); + + RequestExceededQuotaRespMsg* respMsgCast = NULL; + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_RequestExceededQuotaResp); + if (!respMsg) + goto err_exit; + + // handle result + respMsgCast = (RequestExceededQuotaRespMsg*)respMsg.get(); + + respMsgCast->getExceededQuotaIDs()->swap(*outIDList); + error = respMsgCast->getError(); + + retVal = true; + +err_exit: + return retVal; +} + +bool InternodeSyncer::downloadAllExceededQuotaLists(const StoragePoolPtrVec& storagePools) +{ + bool retVal = true; + + // note: this is fairly inefficient, but it is done only once on startup + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + if (!downloadAllExceededQuotaLists(*iter)) + retVal = false; + } + + return retVal; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAllExceededQuotaLists(const StoragePoolPtr storagePool) +{ + const char* logContext = "Exceeded quota sync"; + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + UInt16Set targets = storagePool->getTargets(); + + bool retVal = true; + + UIntList tmpExceededUIDsSize; + UIntList tmpExceededGIDsSize; + UIntList tmpExceededUIDsInode; + UIntList tmpExceededGIDsInode; + + FhgfsOpsErr error; + + if (downloadExceededQuotaList(storagePool->getId(), QuotaDataType_USER, QuotaLimitType_SIZE, + &tmpExceededUIDsSize, error) ) + { + // update exceeded store for every target in the pool + for (auto iter = targets.begin(); iter != targets.end(); iter++) + { + uint16_t targetId = *iter; + ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + if (!exceededQuotaStore) + { + LOG(STORAGEPOOLS, ERR, "Could not access exceeded quota store in file size quota for users.", targetId); + retVal = false; + break; + } + exceededQuotaStore->updateExceededQuota(&tmpExceededUIDsSize, QuotaDataType_USER, + QuotaLimitType_SIZE); + } + + // enable or disable quota enforcement + if (error == FhgfsOpsErr_NOTSUPP) + { + if (cfg->getQuotaEnableEnforcement()) + { + LogContext(logContext).log(Log_DEBUG, + "Quota enforcement is enabled in the configuration of this metadata server, " + "but not on the management daemon. " + "The configuration from the management daemon overrides the local setting."); + } + else + { + LogContext(logContext).log(Log_DEBUG, "Quota enforcement disabled by management daemon."); + } + + cfg->setQuotaEnableEnforcement(false); + return true; + } + else + { + if (!cfg->getQuotaEnableEnforcement()) + { + LogContext(logContext).log(Log_DEBUG, + "Quota enforcement is enabled on the management daemon, " + "but not in the configuration of this metadata server. " + "The configuration from the management daemon overrides the local setting."); + } + else + { + LogContext(logContext).log(Log_DEBUG, "Quota enforcement enabled by management daemon."); + } + + cfg->setQuotaEnableEnforcement(true); + } + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file size quota for users."); + retVal = false; + } + + if (downloadExceededQuotaList(storagePool->getId(), QuotaDataType_GROUP, QuotaLimitType_SIZE, + &tmpExceededGIDsSize, error)) + { + for (auto iter = targets.begin(); iter != targets.end(); iter++) + { + uint16_t targetId = *iter; + ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + if (!exceededQuotaStore) + { + LOG(STORAGEPOOLS, ERR, "Could not access exceeded quota store in file size quota for groups.", targetId); + retVal = false; + break; + } + exceededQuotaStore->updateExceededQuota(&tmpExceededGIDsSize, QuotaDataType_GROUP, + QuotaLimitType_SIZE); + } + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file size quota for groups."); + retVal = false; + } + + if (downloadExceededQuotaList(storagePool->getId(), QuotaDataType_USER, QuotaLimitType_INODE, + &tmpExceededUIDsInode, error)) + { + for (auto iter = targets.begin(); iter != targets.end(); iter++) + { + uint16_t targetId = *iter; + ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + if (!exceededQuotaStore) + { + LOG(STORAGEPOOLS, ERR, "Could not access exceeded quota store in file number quota for users.", targetId); + retVal = false; + break; + } + exceededQuotaStore->updateExceededQuota(&tmpExceededUIDsInode, QuotaDataType_USER, + QuotaLimitType_INODE); + } + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file number quota for users."); + retVal = false; + } + + if (downloadExceededQuotaList(storagePool->getId(), QuotaDataType_USER, QuotaLimitType_INODE, + &tmpExceededGIDsInode, error)) + { + for (auto iter = targets.begin(); iter != targets.end(); iter++) + { + uint16_t targetId = *iter; + ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + if (!exceededQuotaStore) + { + LOG(STORAGEPOOLS, ERR, "Could not access exceeded quota store in file number quota for groups.", targetId); + retVal = false; + break; + } + exceededQuotaStore->updateExceededQuota(&tmpExceededGIDsInode, QuotaDataType_GROUP, + QuotaLimitType_INODE); + } + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file number quota for groups."); + retVal = false; + } + + return retVal; +} diff --git a/meta/source/components/InternodeSyncer.h b/meta/source/components/InternodeSyncer.h new file mode 100644 index 0000000..939b176 --- /dev/null +++ b/meta/source/components/InternodeSyncer.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class AbstractDatagramListener; + +class InternodeSyncer : public PThread +{ + public: + InternodeSyncer(TargetConsistencyState initialConsistencyState); + virtual ~InternodeSyncer() { } + + static bool registerNode(AbstractDatagramListener* dgramLis); + + static bool updateMetaStatesAndBuddyGroups(TargetConsistencyState& outConsistencyState, + bool publish); + static void syncClients(const std::vector& clientsList, bool allowRemoteComm); + static bool downloadAndSyncNodes(); + static bool downloadAndSyncTargetMappings(); + static bool downloadAndSyncStoragePools(); + static bool downloadAndSyncTargetStatesAndBuddyGroups(); + + static void downloadAndSyncClients(bool requeue); + + static bool updateMetaCapacityPools(); + static bool updateMetaBuddyCapacityPools(); + + static bool downloadAllExceededQuotaLists(const StoragePoolPtrVec& storagePools); + static bool downloadExceededQuotaList(StoragePoolId storagePoolId, QuotaDataType idType, + QuotaLimitType exType, UIntList* outIDList, FhgfsOpsErr& error); + + static void printSyncResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes); + + private: + LogContext log; + +#if ATOMIC_BOOL_LOCK_FREE != 2 +# warn atomic is not always lock-free +#endif + std::atomic forcePoolsUpdate; // true to force update of capacity pools + std::atomic forceTargetStatesUpdate; // true to force update of node state + std::atomic forcePublishCapacities; // true to force publishing free capacity + std::atomic forceStoragePoolsUpdate; // true to force update of storage pools + std::atomic forceCheckNetwork; // true to force checking of network changes + + // Keeps track of the timeout during which the node may not send state reports because it is + // waiting to be offlined by the mgmtd. + NodeOfflineWait offlineWait; + + Mutex nodeConsistencyStateMutex; + TargetConsistencyState nodeConsistencyState; // Node's own consistency state. + // Note: This is initialized when updateMetaStates... is called from App::downloadMgmtInfo. + AtomicUInt32 buddyResyncInProgress; + + virtual void run(); + void syncLoop(); + + static bool updateStorageCapacityPools(); + static bool updateTargetBuddyCapacityPools(); + static std::pair downloadCapacityPools( + CapacityPoolQueryType poolType); + void publishNodeCapacity(); + + void forceMgmtdPoolsRefresh(); + + // returns true if the local interfaces have changed + bool checkNetwork(); + void dropIdleConns(); + unsigned dropIdleConnsByStore(NodeStoreServers* nodes); + + void getStatInfo(int64_t* outSizeTotal, int64_t* outSizeFree, int64_t* outInodesTotal, + int64_t* outInodesFree); + + static TargetConsistencyState decideResync(const CombinedTargetState newState); + static bool publishNodeStateChange(const TargetConsistencyState oldState, + const TargetConsistencyState newState); + + static bool downloadAllExceededQuotaLists(const StoragePoolPtr storagePool); + + public: + // inliners + void setForcePoolsUpdate() + { + forcePoolsUpdate = true; + } + + void setForceTargetStatesUpdate() + { + forceTargetStatesUpdate = true; + } + + void setForcePublishCapacities() + { + forcePublishCapacities = true; + } + + void setForceStoragePoolsUpdate() + { + forceStoragePoolsUpdate = true; + } + + void setForceCheckNetwork() + { + forceCheckNetwork = true; + } + + TargetConsistencyState getNodeConsistencyState() + { + std::lock_guard lock(nodeConsistencyStateMutex); + return nodeConsistencyState; + } + + void setNodeConsistencyState(TargetConsistencyState newState) + { + std::lock_guard lock(nodeConsistencyStateMutex); + nodeConsistencyState = newState; + } + + void setResyncInProgress(bool resyncInProgress) + { + this->buddyResyncInProgress.set(resyncInProgress); + } + + bool getResyncInProgress() + { + return this->buddyResyncInProgress.read(); + } +}; diff --git a/meta/source/components/ModificationEventFlusher.cpp b/meta/source/components/ModificationEventFlusher.cpp new file mode 100644 index 0000000..27613b1 --- /dev/null +++ b/meta/source/components/ModificationEventFlusher.cpp @@ -0,0 +1,145 @@ +#include "ModificationEventFlusher.h" + +#include +#include +#include + +#include + +#include + +ModificationEventFlusher::ModificationEventFlusher() + : PThread("ModificationEventFlusher"), + log("ModificationEventFlusher"), + dGramLis(Program::getApp()->getDatagramListener() ), + workerList(Program::getApp()->getWorkers() ), + fsckMissedEvent(false) +{ + NicAddressList nicList; + this->fsckNode = std::make_shared(NODETYPE_Invalid, "fsck", NumNodeID(), 0, 0, nicList); + NicListCapabilities localNicCaps; + NicAddressList localNicList = Program::getApp()->getLocalNicList(); + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + this->fsckNode->getConnPool()->setLocalNicList(localNicList, localNicCaps); +} + +void ModificationEventFlusher::run() +{ + try + { + registerSignalHandler(); + + while ( !this->getSelfTerminate() ) + { + while ( this->eventTypeBufferList.empty() ) + { + { + const std::lock_guard lock(eventsAddedMutex); + this->eventsAddedCond.timedwait(&eventsAddedMutex, 2000); + } + + if ( this->getSelfTerminate() ) + goto stop_component; + } + + // buffer list not empty... go ahead and send it + this->sendToFsck(); + } + +stop_component: + log.log(Log_DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +bool ModificationEventFlusher::add(ModificationEventType eventType, const std::string& entryID) +{ + while (true) + { + { + const std::lock_guard lock(mutex); + + if (this->eventTypeBufferList.size() < MODFLUSHER_MAXSIZE_EVENTLIST) + break; + } + + // queue too long + // wait if something is flushed + { + const std::lock_guard lock(eventsFlushedMutex); + this->eventsFlushedCond.timedwait(&eventsFlushedMutex, 5000); + } + } + + { + const std::lock_guard lock(mutex); + this->eventTypeBufferList.push_back((uint8_t)eventType); + this->entryIDBufferList.push_back(entryID); + } + + { + const std::lock_guard lock(eventsAddedMutex); + this->eventsAddedCond.broadcast(); + } + + return true; +} + +void ModificationEventFlusher::sendToFsck() +{ + if (!fsckNode) + { + log.logErr("Fsck modification events are present, but fsck node is not set."); + this->fsckMissedEvent = true; + + // stop logging + this->disableLoggingLocally(false); + + return; + } + + // get the first MODFLUSHER_SEND_AT_ONCE entries from each list and send them to fsck + + // only have the mutex on the lists as long as we really need it + UInt8List eventTypeListCopy; + StringList entryIDListCopy; + + { + const std::lock_guard lock(mutex); + + UInt8ListIter eventTypeStart = this->eventTypeBufferList.begin(); + UInt8ListIter eventTypeEnd = this->eventTypeBufferList.begin(); + ListTk::advance(eventTypeBufferList, eventTypeEnd, MODFLUSHER_SEND_AT_ONCE); + + StringListIter entryIDStart = this->entryIDBufferList.begin(); + StringListIter entryIDEnd = this->entryIDBufferList.begin(); + ListTk::advance(entryIDBufferList, entryIDEnd, MODFLUSHER_SEND_AT_ONCE); + + eventTypeListCopy.splice(eventTypeListCopy.begin(), this->eventTypeBufferList, eventTypeStart, + eventTypeEnd); + entryIDListCopy.splice(entryIDListCopy.begin(), this->entryIDBufferList, entryIDStart, + entryIDEnd); + } + + FsckModificationEventMsg fsckModificationEventMsg(&eventTypeListCopy, &entryIDListCopy, + this->fsckMissedEvent); + + bool ackReceived = this->dGramLis->sendToNodeUDPwithAck(fsckNode, &fsckModificationEventMsg, + MODFLUSHER_WAIT_FOR_ACK_MS, MODFLUSHER_WAIT_FOR_ACK_RETRIES); + + if (!ackReceived) + { + log.log(Log_CRITICAL, + "Did not receive an ack from fsck for a FsckModificationEventMsg"); + this->fsckMissedEvent = true; + + // stop logging + this->disableLoggingLocally(false); + } + + const std::lock_guard lock(eventsFlushedMutex); + eventsFlushedCond.broadcast(); +} diff --git a/meta/source/components/ModificationEventFlusher.h b/meta/source/components/ModificationEventFlusher.h new file mode 100644 index 0000000..5b5abe3 --- /dev/null +++ b/meta/source/components/ModificationEventFlusher.h @@ -0,0 +1,184 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define MODFLUSHER_MAXSIZE_EVENTLIST 10000 +#define MODFLUSHER_SEND_AT_ONCE 10 // only very few events, because msg is UDP +#define MODFLUSHER_FLUSH_MAX_INTERVAL_MS 5000 +#define MODFLUSHER_WAIT_FOR_ACK_MS 1000 +#define MODFLUSHER_WAIT_FOR_ACK_RETRIES 100 + +/* + * Note: this class is only used by fsck at the moment; therefore it is designed for fsck + */ +class ModificationEventFlusher: public PThread +{ + public: + ModificationEventFlusher(); + + virtual void run(); + + bool add(ModificationEventType eventType, const std::string& entryID); + + private: + LogContext log; + + DatagramListener* dGramLis; + std::list* workerList; + + UInt8List eventTypeBufferList; + StringList entryIDBufferList; + + // general mutex used to lock the buffer and the notification enabling and disabling + Mutex mutex; + + Mutex eventsFlushedMutex; + Condition eventsFlushedCond; + + Mutex eventsAddedMutex; + Condition eventsAddedCond; + + AtomicSizeT loggingEnabled; // 1 if enabled + + Mutex fsckMutex; + + NodeHandle fsckNode; + bool fsckMissedEvent; + + void sendToFsck(); + + public: + // inliners + + /** + * @returns true if logging was enabled, false if it was alredy running + */ + bool enableLogging(unsigned fsckPortUDP, NicAddressList& fsckNicList, bool forceRestart) + { + std::unique_lock lock(mutex); + + if (!forceRestart && loggingEnabled.read() > 0) + return false; + + eventTypeBufferList.clear(); + entryIDBufferList.clear(); + this->loggingEnabled.set(1); + + // set fsckParameters + setFsckParametersUnlocked(fsckPortUDP, fsckNicList); + + lock.unlock(); + + // make sure all workers have noticed the changed loggingEnabled flag + stallAllWorkers(true, false); + + return true; + } + + bool disableLogging() + { + return disableLoggingLocally(true); + } + + bool isLoggingEnabled() + { + return (this->loggingEnabled.read() != 0); + } + + bool getFsckMissedEvent() + { + const std::lock_guard lock(fsckMutex); + + return this->fsckMissedEvent; + } + + private: + /* + * Note: if logging is already disabled, this function basically does nothing, but returns + * if the buffer is empty or not + * @param fromWorker set to true if this is called from a worker thread. Otherwise, the worker + * calling this will deadlock + * @return true if buffer is empty, false otherwise + */ + bool disableLoggingLocally(bool fromWorker) + { + loggingEnabled.setZero(); + + stallAllWorkers(fromWorker, true); + + std::lock_guard lock(mutex); + + // make sure list is empty and no worker is logging anymore + return this->eventTypeBufferList.empty(); + } + + void setFsckParametersUnlocked(unsigned portUDP, NicAddressList& nicList) + { + this->fsckMissedEvent = false; + + this->fsckNode->updateInterfaces(portUDP, 0, nicList); + } + + /** + * @param fromWorker This is called from a worker thread. In that case, this function blocks + * only until n-1 workers have reached the counter work item - because one + * of the workers is already blocked inside this function. + * @param flush Flush the modification event queue. Do this when stopping the modification + * event logger, because otherwise, workers might lock up trying to enqueue items + * which will never be sent to the Fsck. + */ + void stallAllWorkers(bool fromWorker, bool flush) + { + App* app = Program::getApp(); + MultiWorkQueue* workQueue = app->getWorkQueue(); + pthread_t threadID = PThread::getCurrentThreadID(); + + SynchronizedCounter notified; + for (auto workerIt = workerList->begin(); workerIt != workerList->end(); ++workerIt) + { + // don't enqueue it in the worker that processes this message (this would deadlock) + if (!PThread::threadIDEquals((*workerIt)->getID(), threadID) || !fromWorker) + { + PersonalWorkQueue* personalQ = (*workerIt)->getPersonalWorkQueue(); + workQueue->addPersonalWork(new IncSyncedCounterWork(¬ified), personalQ); + } + } + + while (true) + { + const bool done = notified.timedWaitForCount(workerList->size() - (fromWorker ? 1 : 0), + 1000); + + if (done) + { + break; + } + else if (flush) + { + { + const std::lock_guard lock(mutex); + this->eventTypeBufferList.clear(); + this->entryIDBufferList.clear(); + } + + { + const std::lock_guard lock(eventsFlushedMutex); + eventsFlushedCond.broadcast(); + } + } + } + } +}; + diff --git a/meta/source/components/buddyresyncer/BuddyResyncJob.cpp b/meta/source/components/buddyresyncer/BuddyResyncJob.cpp new file mode 100644 index 0000000..ac24b56 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncJob.cpp @@ -0,0 +1,530 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "BuddyResyncJob.h" + +BuddyResyncJob::BuddyResyncJob() : + PThread("BuddyResyncJob"), + state(BuddyResyncJobState_NOTSTARTED), + startTime(0), endTime(0), + gatherSlave(boost::make_unique(&syncCandidates)) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + buddyNodeID = + NumNodeID(app->getMetaBuddyGroupMapper()->getBuddyTargetID(app->getLocalNodeNumID().val())); + + const unsigned numSyncSlaves = std::max(cfg->getTuneNumResyncSlaves(), 1); + + for (size_t i = 0; i < numSyncSlaves; i++) + bulkSyncSlaves.emplace_back( + boost::make_unique(*this, &syncCandidates, i, buddyNodeID)); + + sessionStoreResyncer = boost::make_unique(buddyNodeID); + modSyncSlave = boost::make_unique(*this, &syncCandidates, 1, buddyNodeID); +} + +BuddyResyncJob::~BuddyResyncJob() = default; + +void BuddyResyncJob::run() +{ + const char* logContext = "Run resync job"; + + InternodeSyncer* internodeSyncer = Program::getApp()->getInternodeSyncer(); + App* app = Program::getApp(); + WorkerList* workers = app->getWorkers(); + NodeStore* metaNodes = app->getMetaNodes(); + const std::string metaPath = app->getMetaPath(); + const std::string metaBuddyMirPath = app->getMetaPath() + "/" + CONFIG_BUDDYMIRROR_SUBDIR_NAME; + Barrier workerBarrier(workers->size() + 1); + bool workersStopped = false; + + startTime = time(NULL); + + syncCandidates.clear(); + + auto buddyNode = metaNodes->referenceNode(buddyNodeID); + + if (!buddyNode) + { + LOG(MIRRORING, ERR, "Unable to resolve buddy node. Resync will not start."); + setState(BuddyResyncJobState_FAILURE); + goto cleanup; + } + + DEBUG_ENV_VAR(unsigned, DIE_AT_RESYNC_N, 0, "BEEGFS_RESYNC_DIE_AT_N"); + if (DIE_AT_RESYNC_N) { + static unsigned resyncs = 0; + // for #479: terminating a server at this point caused the workers to terminate before the + // resyncer had communicated with them, causing a deadlock on shutdown + if (++resyncs == DIE_AT_RESYNC_N) { + ::kill(0, SIGTERM); + sleep(4); + } + } + stopAllWorkersOn(workerBarrier); + { + // Notify buddy that resync started and wait for confirmation + StorageResyncStartedMsg msg(buddyNodeID.val()); + const auto respMsg = MessagingTk::requestResponse(*buddyNode, msg, + NETMSGTYPE_StorageResyncStartedResp); + + if (!respMsg) + { + LogContext(logContext).logErr("Unable to notify buddy about resync attempt. " + "Resync will not start."); + setState(BuddyResyncJobState_FAILURE); + workerBarrier.wait(); + goto cleanup; + } + + // resync could have been aborted before we got here. if so, exit as soon as possible without + // setting the resync job state to something else. + { + std::unique_lock lock(stateMutex); + + if (state == BuddyResyncJobState_INTERRUPTED) + { + lock.unlock(); + workerBarrier.wait(); + goto cleanup; + } + + state = BuddyResyncJobState_RUNNING; + } + internodeSyncer->setResyncInProgress(true); + + const bool startGatherSlaveRes = startGatherSlaves(); + if (!startGatherSlaveRes) + { + setState(BuddyResyncJobState_FAILURE); + workerBarrier.wait(); + goto cleanup; + } + + const bool startResyncSlaveRes = startSyncSlaves(); + if (!startResyncSlaveRes) + { + setState(BuddyResyncJobState_FAILURE); + workerBarrier.wait(); + goto cleanup; + } + } + workerBarrier.wait(); + + LOG_DEBUG(__func__, Log_DEBUG, "Going to join gather slaves."); + joinGatherSlaves(); + LOG_DEBUG(__func__, Log_DEBUG, "Joined gather slaves."); + + LOG_DEBUG(__func__, Log_DEBUG, "Going to join sync slaves."); + + // gather slaves have finished. Tell sync slaves to stop when work packages are empty and wait. + for (auto it = bulkSyncSlaves.begin(); it != bulkSyncSlaves.end(); ++it) + { + (*it)->setOnlyTerminateIfIdle(true); + (*it)->selfTerminate(); + } + + for (auto it = bulkSyncSlaves.begin(); it != bulkSyncSlaves.end(); ++it) + (*it)->join(); + + // here we can be in one of two situations: + // 1. bulk resync has succeeded. we then totally stop the workers: the session store must be in + // a quiescent state for resync, so for simplicitly, we suspend all client operations here. + // we do not want to do this any earlier than this point, because bulk syncers may take a + // very long time to complete. + // 2. bulk resync has failed. in this case, the bulk syncers have aborted the currently running + // job, and the mod syncer is either dead or in the process of dying. here we MUST NOT stop + // the workers, because they are very likely blocked on the mod sync queue already and will + // not unblock before the queue is cleared. + if (getState() == BuddyResyncJobState_RUNNING) + { + stopAllWorkersOn(workerBarrier); + workersStopped = true; + } + + modSyncSlave->setOnlyTerminateIfIdle(true); + modSyncSlave->selfTerminate(); + modSyncSlave->join(); + + // gatherers are done and the workers have been stopped, we can safely resync the session now. + + LOG_DEBUG(__func__, Log_DEBUG, "Joined sync slaves."); + + // Perform session store resync + // the job may have been aborted or terminated by errors. in this case, do not resync the session + // store. end the sync as quickly as possible. + if (getState() == BuddyResyncJobState_RUNNING) + sessionStoreResyncer->doSync(); + + // session store is now synced, and future actions can be forwarded safely. we do not restart + // the workers here because the resync may still enter FAILED state, and we don't want to forward + // to the secondary in this case. + +cleanup: + bool syncErrors = false; + + { + std::lock_guard lock(gatherSlave->stateMutex); + while (gatherSlave->isRunning) + gatherSlave->isRunningChangeCond.wait(&gatherSlave->stateMutex); + + syncErrors |= gatherSlave->getStats().errors != 0; + } + + for (auto it = bulkSyncSlaves.begin(); it != bulkSyncSlaves.end(); ++it) + { + BuddyResyncerBulkSyncSlave* slave = it->get(); + std::lock_guard lock(slave->stateMutex); + while (slave->isRunning) + slave->isRunningChangeCond.wait(&slave->stateMutex); + + syncErrors |= slave->getStats().dirErrors != 0; + syncErrors |= slave->getStats().fileErrors != 0; + } + + syncErrors |= sessionStoreResyncer->getStats().errors; + + { + while (modSyncSlave->isRunning) + modSyncSlave->isRunningChangeCond.wait(&modSyncSlave->stateMutex); + + syncErrors |= modSyncSlave->getStats().errors != 0; + } + + + if (getState() == BuddyResyncJobState_RUNNING || getState() == BuddyResyncJobState_INTERRUPTED) + { + if (syncErrors) + setState(BuddyResyncJobState_ERRORS); + else if (getState() == BuddyResyncJobState_RUNNING) + setState(BuddyResyncJobState_SUCCESS); + + // delete timestamp override file if it exists. + BuddyCommTk::setBuddyNeedsResync(metaPath, false); + + const TargetConsistencyState buddyState = newBuddyState(); + informBuddy(buddyState); + informMgmtd(buddyState); + + const bool interrupted = getState() != BuddyResyncJobState_SUCCESS; + LOG(MIRRORING, WARNING, "Resync finished.", interrupted, syncErrors); + } + + internodeSyncer->setResyncInProgress(false); + endTime = time(NULL); + + // restart all the worker threads + if (workersStopped) + workerBarrier.wait(); + + // if the resync was aborted, the mod sync queue may still contain items. additionally, workers + // may be waiting for a changeset slot, or they may have started executing after the resync was + // aborted by the sync slaves, but before the resync was officially set to "not running". + // we cannot set the resync to "not running" in abort() because we have no upper bound for the + // number of worker threads. even if we did set the resync to "not running" in abort() and + // cleared the sync queues at the same time, there may still be an arbitrary number of threads + // waiting for a changeset slot. + // instead, we have to wait for each thread to "see" that the resync is over, and periodically + // clear the sync queue to unblock those workers that are still waiting for slots. + if (syncErrors) + { + SynchronizedCounter counter; + + for (auto it = workers->begin(); it != workers->end(); ++it) + { + auto& worker = **it; + + worker.getWorkQueue()->addPersonalWork( + new IncSyncedCounterWork(&counter), + worker.getPersonalWorkQueue()); + } + + while (!counter.timedWaitForCount(workers->size(), 100)) + { + while (!syncCandidates.isFilesEmpty()) + { + MetaSyncCandidateFile candidate; + syncCandidates.fetch(candidate, this); + candidate.signal(); + } + } + } +} + +void BuddyResyncJob::stopAllWorkersOn(Barrier& barrier) +{ + WorkerList* workers = Program::getApp()->getWorkers(); + + for (WorkerListIter workerIt = workers->begin(); workerIt != workers->end(); ++workerIt) + { + Worker* worker = *workerIt; + PersonalWorkQueue* personalQ = worker->getPersonalWorkQueue(); + MultiWorkQueue* workQueue = worker->getWorkQueue(); + workQueue->addPersonalWork(new BarrierWork(&barrier), personalQ); + } + + barrier.wait(); // Wait until all workers are blocked. +} + +void BuddyResyncJob::abort(bool wait_for_completion) +{ + setState(BuddyResyncJobState_INTERRUPTED); + + gatherSlave->selfTerminate(); + + // set onlyTerminateIfIdle on the slaves to false - they will be stopped by the main loop then. + for (auto it = bulkSyncSlaves.begin(); it != bulkSyncSlaves.end(); ++it) + { + BuddyResyncerBulkSyncSlave* slave = it->get(); + slave->setOnlyTerminateIfIdle(false); + } + + modSyncSlave->selfTerminate(); + + int retry = 600; + /* Wait till all on-going thread events are fetched or max 30mins. + * (fetch waits for 3secs if there are no files to be fetched) + */ + if (wait_for_completion) + { + modSyncSlave->join(); + while (threadCount > 0 && retry) + { + LOG(MIRRORING, WARNING, "Wait for pending worker threads to finish"); + if (!syncCandidates.isFilesEmpty()) + { + MetaSyncCandidateFile candidate; + syncCandidates.fetch(candidate, this); + candidate.signal(); + } + retry--; + } + if (threadCount) + LOG(MIRRORING, ERR, "Cleanup of aborted resync failed: I/O worker threads" + " did not finish properly: ", + ("threadCount", threadCount.load())); + } +} + +bool BuddyResyncJob::startGatherSlaves() +{ + try + { + gatherSlave->resetSelfTerminate(); + gatherSlave->start(); + gatherSlave->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what()); + return false; + } + + return true; +} + +bool BuddyResyncJob::startSyncSlaves() +{ + App* app = Program::getApp(); + const NumNodeID localNodeID = app->getLocalNodeNumID(); + const NumNodeID buddyNodeID( + app->getMetaBuddyGroupMapper()->getBuddyTargetID(localNodeID.val(), NULL) ); + + for (size_t i = 0; i < bulkSyncSlaves.size(); i++) + { + try + { + bulkSyncSlaves[i]->resetSelfTerminate(); + bulkSyncSlaves[i]->start(); + bulkSyncSlaves[i]->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what() ); + + for (size_t j = 0; j < i; j++) + bulkSyncSlaves[j]->selfTerminate(); + + return false; + } + } + + try + { + modSyncSlave->resetSelfTerminate(); + modSyncSlave->start(); + modSyncSlave->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what() ); + + for (size_t j = 0; j < bulkSyncSlaves.size(); j++) + bulkSyncSlaves[j]->selfTerminate(); + + return false; + } + + return true; +} + +void BuddyResyncJob::joinGatherSlaves() +{ + gatherSlave->join(); +} + +MetaBuddyResyncJobStatistics BuddyResyncJob::getJobStats() +{ + std::lock_guard lock(stateMutex); + + BuddyResyncerGatherSlave::Stats gatherStats = gatherSlave->getStats(); + const uint64_t dirsDiscovered = gatherStats.dirsDiscovered; + const uint64_t gatherErrors = gatherStats.errors; + + uint64_t dirsSynced = 0; + uint64_t filesSynced = 0; + uint64_t dirErrors = 0; + uint64_t fileErrors = 0; + + for (auto syncerIt = bulkSyncSlaves.begin(); syncerIt != bulkSyncSlaves.end(); ++syncerIt) + { + BuddyResyncerBulkSyncSlave::Stats bulkSyncStats = (*syncerIt)->getStats(); + dirsSynced += bulkSyncStats.dirsSynced; + filesSynced += bulkSyncStats.filesSynced; + dirErrors += bulkSyncStats.dirErrors; + fileErrors += bulkSyncStats.fileErrors; + } + + SessionStoreResyncer::Stats sessionSyncStats = sessionStoreResyncer->getStats(); + const uint64_t sessionsToSync = sessionSyncStats.sessionsToSync; + const uint64_t sessionsSynced = sessionSyncStats.sessionsSynced; + const bool sessionSyncErrors = sessionSyncStats.errors; + + BuddyResyncerModSyncSlave::Stats modSyncStats = modSyncSlave->getStats(); + uint64_t modObjectsSynced = modSyncStats.objectsSynced; + uint64_t modSyncErrors = modSyncStats.errors; + + return MetaBuddyResyncJobStatistics( + state, startTime, endTime, + dirsDiscovered, gatherErrors, + dirsSynced, filesSynced, dirErrors, fileErrors, + sessionsToSync, sessionsSynced, sessionSyncErrors, + modObjectsSynced, modSyncErrors); +} + +/** + * Determine the state for the buddy after the end of a resync job. + * @returns the new state to be set on the buddy accroding to this job's JobState. + */ +TargetConsistencyState BuddyResyncJob::newBuddyState() +{ + switch (getState()) + { + case BuddyResyncJobState_ERRORS: + case BuddyResyncJobState_INTERRUPTED: + case BuddyResyncJobState_FAILURE: + return TargetConsistencyState_BAD; + + case BuddyResyncJobState_SUCCESS: + return TargetConsistencyState_GOOD; + + default: + LOG(MIRRORING, ERR, "Undefined resync state.", state); + return TargetConsistencyState_BAD; + } +} + +void BuddyResyncJob::informBuddy(const TargetConsistencyState newTargetState) +{ + App* app = Program::getApp(); + NodeStore* metaNodes = app->getMetaNodes(); + MirrorBuddyGroupMapper* buddyGroups = app->getMetaBuddyGroupMapper(); + + NumNodeID buddyNodeID = + NumNodeID(buddyGroups->getBuddyTargetID(app->getLocalNodeNumID().val())); + auto metaNode = metaNodes->referenceNode(buddyNodeID); + + if (!metaNode) + { + LOG(MIRRORING, ERR, "Unable to inform buddy about finished resync", buddyNodeID.str()); + return; + } + + UInt16List nodeIDs(1, buddyNodeID.val()); + UInt8List states(1, newTargetState); + SetTargetConsistencyStatesMsg msg(NODETYPE_Meta, &nodeIDs, &states, false); + + const auto respMsg = MessagingTk::requestResponse(*metaNode, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + if (!respMsg) + { + LogContext(__func__).logErr( + "Unable to inform buddy about finished resync. " + "BuddyNodeID: " + buddyNodeID.str() + "; " + "error: Communication Error"); + return; + } + + { + auto* respMsgCast = static_cast(respMsg.get()); + FhgfsOpsErr result = respMsgCast->getResult(); + + if (result != FhgfsOpsErr_SUCCESS) + { + LogContext(__func__).logErr( + "Error while informing buddy about finished resync. " + "BuddyNodeID: " + buddyNodeID.str() + "; " + "error: " + boost::lexical_cast(result) ); + } + } +} + +void BuddyResyncJob::informMgmtd(const TargetConsistencyState newTargetState) +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + + if (!mgmtNode) + { + LOG(MIRRORING, ERR, "Unable to communicate with management node."); + return; + } + + UInt16List nodeIDs(1, buddyNodeID.val()); + UInt8List states(1, newTargetState); + SetTargetConsistencyStatesMsg msg(NODETYPE_Meta, &nodeIDs, &states, false); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + if (!respMsg) + { + LOG(MIRRORING, ERR, + "Unable to inform management node about finished resync: Communication error."); + return; + } + + { + auto* respMsgCast = static_cast(respMsg.get()); + FhgfsOpsErr result = respMsgCast->getResult(); + + if (result != FhgfsOpsErr_SUCCESS) + LOG(MIRRORING, ERR, "Error informing management node about finished resync.", result); + } +} diff --git a/meta/source/components/buddyresyncer/BuddyResyncJob.h b/meta/source/components/buddyresyncer/BuddyResyncJob.h new file mode 100644 index 0000000..4182ee7 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncJob.h @@ -0,0 +1,92 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +class BuddyResyncerBulkSyncSlave; +class BuddyResyncerModSyncSlave; + +class BuddyResyncJob : public PThread +{ + public: + BuddyResyncJob(); + ~BuddyResyncJob(); + + virtual void run(); + + void abort(bool wait_for_completion); + MetaBuddyResyncJobStatistics getJobStats(); + std::atomic threadCount{ 0 }; + + private: + BuddyResyncJobState state; + Mutex stateMutex; + + int64_t startTime; + int64_t endTime; + + NumNodeID buddyNodeID; + + MetaSyncCandidateStore syncCandidates; + + std::unique_ptr gatherSlave; + std::vector> bulkSyncSlaves; + std::unique_ptr modSyncSlave; + std::unique_ptr sessionStoreResyncer; + + bool startGatherSlaves(); + bool startSyncSlaves(); + void joinGatherSlaves(); + + public: + BuddyResyncJobState getState() + { + std::lock_guard lock(stateMutex); + return state; + } + + bool isRunning() + { + std::lock_guard lock(stateMutex); + return state == BuddyResyncJobState_RUNNING; + } + + void enqueue(MetaSyncCandidateFile syncCandidate, PThread* caller) + { + syncCandidates.add(std::move(syncCandidate), caller); + } + + void registerOps() + { + this->threadCount += 1; + } + + void unregisterOps() + { + this->threadCount -= 1; + } + + private: + void setState(const BuddyResyncJobState state) + { + LOG_DEBUG(__func__, Log_DEBUG, "Setting state: " + + StringTk::uintToStr(static_cast(state) ) ); + std::lock_guard lock(stateMutex); + this->state = state; + } + + TargetConsistencyState newBuddyState(); + void informBuddy(const TargetConsistencyState newTargetState); + void informMgmtd(const TargetConsistencyState newTargetState); + + void stopAllWorkersOn(Barrier& barrier); +}; + diff --git a/meta/source/components/buddyresyncer/BuddyResyncer.cpp b/meta/source/components/buddyresyncer/BuddyResyncer.cpp new file mode 100644 index 0000000..5cff8ac --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncer.cpp @@ -0,0 +1,89 @@ +#include "BuddyResyncer.h" + +#include + +__thread MetaSyncCandidateFile* BuddyResyncer::currentThreadChangeSet = 0; + +BuddyResyncer::~BuddyResyncer() +{ + if (job) + { + job->abort(false); + job->join(); + + SAFE_DELETE(job); + } +} + +FhgfsOpsErr BuddyResyncer::startResync() +{ + std::lock_guard lock(jobMutex); + + if (noNewResyncs) + return FhgfsOpsErr_INTERRUPTED; + + if (!job) + { + job = new BuddyResyncJob(); + job->start(); + return FhgfsOpsErr_SUCCESS; + } + + switch (job->getState()) + { + case BuddyResyncJobState_NOTSTARTED: + case BuddyResyncJobState_RUNNING: + return FhgfsOpsErr_INUSE; + + default: + // a job must never be started more than once. to ensure this, we create a new job for + // every resync process, because doing so allows us to use NOTSTARTED and RUNNING as + // "job is currently active" values. otherwise, a second resync may see state SUCCESS and + // allow duplicate resyncer activity. + // if a job is still active, don't wait for very long - it may take a while to finish. the + // internode syncer will retry periodically, so this will work fine. + if (!job->timedjoin(10)) + return FhgfsOpsErr_INUSE; + + delete job; + job = new BuddyResyncJob(); + job->start(); + return FhgfsOpsErr_SUCCESS; + } +} + +void BuddyResyncer::shutdown() +{ + std::unique_ptr job; + + { + std::lock_guard lock(jobMutex); + + job.reset(this->job); + this->job = nullptr; + noNewResyncs = true; + } + + if (job) + { + job->abort(false); + job->join(); + } +} + +void BuddyResyncer::commitThreadChangeSet() +{ + BEEGFS_BUG_ON(!currentThreadChangeSet, "no change set active"); + + auto* job = Program::getApp()->getBuddyResyncer()->getResyncJob(); + + std::unique_ptr candidate(currentThreadChangeSet); + currentThreadChangeSet = nullptr; + + Barrier syncDone(2); + + candidate->prepareSignal(syncDone); + + job->enqueue(std::move(*candidate), PThread::getCurrentThread()); + syncDone.wait(); +} diff --git a/meta/source/components/buddyresyncer/BuddyResyncer.h b/meta/source/components/buddyresyncer/BuddyResyncer.h new file mode 100644 index 0000000..9c18601 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncer.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include + +#include + +/** + * This component does not represent a thread by itself. Instead, it manages a group of "slave + * threads" that are started and stopped when needed. + * + * Other components should only use this component as an interface and not access the slave threads + * directly. + */ +class BuddyResyncer +{ + public: + BuddyResyncer() + : job(NULL), noNewResyncs(false) + { } + + ~BuddyResyncer(); + + FhgfsOpsErr startResync(); + void shutdown(); + + static void commitThreadChangeSet(); + + private: + BuddyResyncJob* job; // Note: In the Storage Server, this is a Map. Here it's just one pointer + // that's set to NULL when no job is present. + Mutex jobMutex; + + public: + BuddyResyncJob* getResyncJob() + { + std::lock_guard lock(jobMutex); + return job; + } + + static void registerSyncChangeset() + { + BEEGFS_BUG_ON(currentThreadChangeSet, "current changeset not nullptr"); + + currentThreadChangeSet = new MetaSyncCandidateFile; + } + + static void abandonSyncChangeset() + { + delete currentThreadChangeSet; + currentThreadChangeSet = nullptr; + } + + static MetaSyncCandidateFile* getSyncChangeset() + { + return currentThreadChangeSet; + } + + private: + static __thread MetaSyncCandidateFile* currentThreadChangeSet; + + bool noNewResyncs; + + // No copy allowed + BuddyResyncer(const BuddyResyncer&); + BuddyResyncer& operator=(const BuddyResyncer&); +}; + diff --git a/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.cpp b/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.cpp new file mode 100644 index 0000000..2a97998 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.cpp @@ -0,0 +1,234 @@ +#include "BuddyResyncerBulkSyncSlave.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +BuddyResyncerBulkSyncSlave::BuddyResyncerBulkSyncSlave(BuddyResyncJob& parentJob, + MetaSyncCandidateStore* syncCandidates, uint8_t slaveID, const NumNodeID& buddyNodeID) : + SyncSlaveBase("BuddyResyncerBulkSyncSlave_" + StringTk::uintToStr(slaveID), parentJob, + buddyNodeID), + syncCandidates(syncCandidates) +{ +} + +void BuddyResyncerBulkSyncSlave::syncLoop() +{ + EntryLockStore* const lockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + while (!getSelfTerminateNotIdle()) + { + MetaSyncCandidateDir candidate; + syncCandidates->fetch(candidate, this); + + // the sync candidate we have retrieved may be invalid if this thread was ordered to + // terminate and the sync candidate store has no more directories queued for us. + // in this case, we may end the sync because we have no more candidates, and the resync job + // guarantees that all gather threads have completed before the bulk syncers are ordered to + // finish. + if (syncCandidates->isDirsEmpty() && candidate.getRelativePath().empty() && + getSelfTerminate()) + return; + + if (candidate.getType() == MetaSyncDirType::InodesHashDir || + candidate.getType() == MetaSyncDirType::DentriesHashDir) + { + // lock the hash path in accordance with MkLocalDir, RmLocalDir and RmDir. + const auto& hashDir = candidate.getRelativePath(); + auto slash1 = hashDir.find('/'); + auto slash2 = hashDir.find('/', slash1 + 1); + auto hash1 = StringTk::strHexToUInt(hashDir.substr(slash1 + 1, slash2 - slash1 - 1)); + auto hash2 = StringTk::strHexToUInt(hashDir.substr(slash2 + 1)); + HashDirLock hashLock = {lockStore, {hash1, hash2}}; + + const FhgfsOpsErr resyncRes = resyncDirectory(candidate, ""); + if (resyncRes == FhgfsOpsErr_SUCCESS) + continue; + + numDirErrors.increase(); + parentJob->abort(false); + return; + } + + // not a hash dir, so it must be a content directory. sync the #fSiDs# first, then the actual + // content directory. we lock the directory inode the content directory belongs to because we + // must not allow a concurrent meta action to delete the content directory while we are + // resyncing it. concurrent modification of directory contents could be allowed, though. + + const std::string dirInodeID = Path(candidate.getRelativePath()).back(); + const std::string fullPath = META_BUDDYMIRROR_SUBDIR_NAME "/" + candidate.getRelativePath(); + + FileIDLock dirLock(lockStore, dirInodeID, false); + + // first ensure that the directory still exists - a concurrent modification may have deleted + // it. this would not be an error; bulk resync should not touch it, an modification sync + // would remove it completely. + if (::access(fullPath.c_str(), F_OK) != 0 && errno == ENOENT) + { + numDirsSynced.increase(); // Count it anyway, so the sums match up. + continue; + } + + MetaSyncCandidateDir fsIDs( + candidate.getRelativePath() + "/" + META_DIRENTRYID_SUB_STR, + MetaSyncDirType::InodesHashDir); + + FhgfsOpsErr resyncRes = resyncDirectory(fsIDs, dirInodeID); + if (resyncRes == FhgfsOpsErr_SUCCESS) + resyncRes = resyncDirectory(candidate, dirInodeID); + + if (resyncRes != FhgfsOpsErr_SUCCESS) + { + numDirErrors.increase(); + parentJob->abort(false); + return; + } + else + { + numDirsSynced.increase(); + } + } +} + +FhgfsOpsErr BuddyResyncerBulkSyncSlave::resyncDirectory(const MetaSyncCandidateDir& root, + const std::string& inodeID) +{ + StreamCandidateArgs args(*this, root, inodeID); + + return resyncAt(Path(root.getRelativePath()), true, streamCandidateDir, &args); +} + +FhgfsOpsErr BuddyResyncerBulkSyncSlave::streamCandidateDir(Socket& socket, + const MetaSyncCandidateDir& candidate, const std::string& inodeID) +{ + EntryLockStore* const lockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + Path candidatePath(META_BUDDYMIRROR_SUBDIR_NAME "/" + candidate.getRelativePath()); + + std::unique_ptr dir(opendir(candidatePath.str().c_str())); + + if (!dir) + { + LOG(MIRRORING, ERR, "Could not open candidate directory.", candidatePath, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + int dirFD = ::dirfd(dir.get()); + if (dirFD < 0) + { + LOG(MIRRORING, ERR, "Could not open candidate directory.", candidatePath, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + while (true) + { + struct dirent* entry; + +#if USE_READDIR_P + struct dirent entryBuf; + int err = ::readdir_r(dir.get(), &entryBuf, &entry); +#else + errno = 0; + entry = readdir(dir.get()); + int err = entry ? 0 : errno; +#endif + if (err > 0) + { + LOG(MIRRORING, ERR, "Could not read candidate directory.", candidatePath, sysErr); + numDirErrors.increase(); + break; + } + + if (!entry) + break; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + struct stat statData; + if (::fstatat(dirFD, entry->d_name, &statData, AT_SYMLINK_NOFOLLOW) < 0) + { + // the file/directory may have gone away. this is not an error, and the secondary will + // delete the file/directory as well. + if (errno == ENOENT) + continue; + + LOG(MIRRORING, ERR, "Could not stat resync candidate.", + candidatePath, entry->d_name, sysErr); + numFileErrors.increase(); + continue; + } + + if (!S_ISDIR(statData.st_mode) && !S_ISREG(statData.st_mode)) + { + LOG(MIRRORING, ERR, "Resync candidate is neither file nor directory.", + candidatePath, entry->d_name, statData.st_mode); + numFileErrors.increase(); + continue; + } + + if (candidate.getType() == MetaSyncDirType::ContentDir) + { + // if it's in a content directory and a directory, it can really only be the fsids dir. + // locking for this case is already sorted, so we only have to transfer the (empty) + // inode metadata to tell the secondary that the directory may stay. + if (S_ISDIR(statData.st_mode)) + { + const FhgfsOpsErr streamRes = streamInode(socket, Path(entry->d_name), true); + if (streamRes != FhgfsOpsErr_SUCCESS) + return streamRes; + } + else + { + ParentNameLock dentryLock(lockStore, inodeID, entry->d_name); + + const auto streamRes = streamDentry(socket, Path(), entry->d_name); + if (streamRes != FhgfsOpsErr_SUCCESS) + { + numFileErrors.increase(); + return streamRes; + } + else + { + numFilesSynced.increase(); + } + } + + continue; + } + + // we are now either in a fsids (file inode) directory or a second-level inode hash-dir, + // which may contain either file or directory inodes. taking a lock unnecessarily is stilll + // cheaper than reading the inode from disk to determine its type, so just lock the inode id + // as file + FileIDLock dirLock(lockStore, entry->d_name, true); + + // access the file once more, because it may have been deleted in the meantime. a new entry + // with the same name cannot appear in a sane filesystem (that would indicate an ID being + // reused). + if (faccessat(dirFD, entry->d_name, F_OK, 0) < 0 && errno == ENOENT) + continue; + + const FhgfsOpsErr streamRes = streamInode(socket, Path(entry->d_name), + S_ISDIR(statData.st_mode)); + if (streamRes != FhgfsOpsErr_SUCCESS) + { + numFileErrors.increase(); + return streamRes; + } + else + { + numFilesSynced.increase(); + } + } + + return sendResyncPacket(socket, std::tuple<>()); +} diff --git a/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.h b/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.h new file mode 100644 index 0000000..dec5151 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerBulkSyncSlave.h @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "SyncSlaveBase.h" + +class DirEntry; + +class BuddyResyncerBulkSyncSlave : public SyncSlaveBase +{ + friend class BuddyResyncer; + friend class BuddyResyncJob; + + public: + BuddyResyncerBulkSyncSlave(BuddyResyncJob& parentJob, MetaSyncCandidateStore* syncCandidates, uint8_t slaveID, + const NumNodeID& buddyNodeID); + + struct Stats + { + uint64_t dirsSynced; + uint64_t filesSynced; + uint64_t dirErrors; + uint64_t fileErrors; + }; + + Stats getStats() + { + return Stats{ numDirsSynced.read(), numFilesSynced.read(), + numDirErrors.read(), numFileErrors.read() }; + } + + private: + MetaSyncCandidateStore* syncCandidates; + + AtomicUInt64 numDirsSynced; + AtomicUInt64 numFilesSynced; + AtomicUInt64 numDirErrors; + AtomicUInt64 numFileErrors; + + void syncLoop(); + + FhgfsOpsErr resyncDirectory(const MetaSyncCandidateDir& root, const std::string& inodeID); + + FhgfsOpsErr streamCandidateDir(Socket& socket, const MetaSyncCandidateDir& candidate, + const std::string& inodeID); + + + private: + typedef std::tuple< + BuddyResyncerBulkSyncSlave&, + const MetaSyncCandidateDir&, + const std::string&> StreamCandidateArgs; + + static FhgfsOpsErr streamCandidateDir(Socket* socket, void* context) + { + using std::get; + + auto& args = *(StreamCandidateArgs*) context; + return get<0>(args).streamCandidateDir(*socket, get<1>(args), get<2>(args)); + } +}; + diff --git a/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp b/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp new file mode 100644 index 0000000..22ffe3d --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include + +#include "BuddyResyncerGatherSlave.h" + +BuddyResyncerGatherSlave::BuddyResyncerGatherSlave(MetaSyncCandidateStore* syncCandidates) : + PThread("BuddyResyncerGatherSlave"), + isRunning(false), + syncCandidates(syncCandidates) +{ + metaBuddyPath = Program::getApp()->getMetaPath() + "/" CONFIG_BUDDYMIRROR_SUBDIR_NAME; +} + +void BuddyResyncerGatherSlave::run() +{ + setIsRunning(true); + + try + { + LOG(MIRRORING, DEBUG, "Component started"); + registerSignalHandler(); + workLoop(); + LOG(MIRRORING, DEBUG, "Component stopped"); + } + catch (std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +void BuddyResyncerGatherSlave::workLoop() +{ + crawlDir(metaBuddyPath + "/" META_INODES_SUBDIR_NAME, MetaSyncDirType::InodesHashDir); + crawlDir(metaBuddyPath + "/" META_DENTRIES_SUBDIR_NAME, MetaSyncDirType::DentriesHashDir); +} + +void BuddyResyncerGatherSlave::crawlDir(const std::string& path, const MetaSyncDirType type, + const unsigned level) +{ + LOG_DBG(MIRRORING, DEBUG, "Entering hash dir.", level, path); + + std::unique_ptr dirHandle(::opendir(path.c_str())); + + if (!dirHandle) + { + LOG(MIRRORING, ERR, "Unable to open path", path, sysErr); + numErrors.increase(); + return; + } + + while (!getSelfTerminate()) + { + struct dirent* entry; + +#if USE_READDIR_R + struct dirent buffer; + const int readRes = ::readdir_r(dirHandle.get(), &buffer, &entry); +#else + errno = 0; + entry = ::readdir(dirHandle.get()); + const int readRes = entry ? 0 : errno; +#endif + if (readRes != 0) + { + LOG(MIRRORING, ERR, "Could not read dir entry.", path, sysErr(readRes)); + numErrors.increase(); + return; + } + + if (!entry) + break; + + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) + continue; + + const std::string& candidatePath = path + "/" + entry->d_name; + + struct stat statBuf; + const int statRes = ::stat(candidatePath.c_str(), &statBuf); + if (statRes) + { + // in a 2nd level dentry hashdir, content directories may disappear - this is not an error, + // it was most likely caused by an rmdir issued by a user. + if (!(errno == ENOENT && type == MetaSyncDirType::DentriesHashDir && level == 2)) + { + LOG(MIRRORING, ERR, "Could not stat dir entry.", candidatePath, sysErr); + numErrors.increase(); + } + + continue; + } + + if (!S_ISDIR(statBuf.st_mode)) + { + LOG(MIRRORING, ERR, "Found a non-dir where only directories are expected.", candidatePath, + oct(statBuf.st_mode)); + numErrors.increase(); + continue; + } + + // layout is: (dentries|inodes)/l1/l2/... + // -> level 0 correlates with type + // -> level 1 is not very interesting, except for reporting + // -> level 2 must be synced. if it is a dentry hashdir, its contents must also be crawled. + if (level == 0) + { + crawlDir(candidatePath, type, level + 1); + continue; + } + + if (level == 1) + { + LOG_DBG(MIRRORING, DEBUG, "Adding hashdir sync candidate.", candidatePath); + addCandidate(candidatePath, type); + + if (type == MetaSyncDirType::DentriesHashDir) + crawlDir(candidatePath, type, level + 1); + + continue; + } + + // so here we read a 2nd level dentry hashdir. crawl that too, add sync candidates for each + // entry we find - non-directories have already been reported, and the bulk resyncer will + // take care of the fsids directories. + numDirsDiscovered.increase(); + LOG_DBG(MIRRORING, DEBUG, "Adding contdir sync candidate.", candidatePath); + addCandidate(candidatePath, MetaSyncDirType::ContentDir); + } +} diff --git a/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.h b/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.h new file mode 100644 index 0000000..6d36843 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerGatherSlave.h @@ -0,0 +1,73 @@ +#pragma once + +#include +#include +#include + +#include + +class BuddyResyncerGatherSlave : public PThread +{ + // Grant access to internal mutex + friend class BuddyResyncer; + friend class BuddyResyncJob; + + public: + BuddyResyncerGatherSlave(MetaSyncCandidateStore* syncCandidates); + + void workLoop(); + + private: + Mutex stateMutex; + Condition isRunningChangeCond; + + AtomicUInt64 numDirsDiscovered; + AtomicUInt64 numErrors; + + std::string metaBuddyPath; + + bool isRunning; + + MetaSyncCandidateStore* syncCandidates; + + virtual void run(); + + void crawlDir(const std::string& path, const MetaSyncDirType type, const unsigned level = 0); + + public: + bool getIsRunning() + { + std::lock_guard lock(stateMutex); + return this->isRunning; + } + + struct Stats + { + uint64_t dirsDiscovered; + uint64_t errors; + }; + + Stats getStats() + { + return Stats{ numDirsDiscovered.read(), numErrors.read() }; + } + + + private: + void setIsRunning(const bool isRunning) + { + std::lock_guard lock(stateMutex); + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } + + void addCandidate(const std::string& path, const MetaSyncDirType type) + { + const std::string& relPath = path.substr(metaBuddyPath.size() + 1); + syncCandidates->add(MetaSyncCandidateDir(relPath, type), this); + } +}; + +typedef std::vector BuddyResyncerGatherSlaveVec; +typedef BuddyResyncerGatherSlaveVec::iterator BuddyResyncerGatherSlaveVecIter; + diff --git a/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.cpp b/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.cpp new file mode 100644 index 0000000..655aab4 --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.cpp @@ -0,0 +1,142 @@ +#include "BuddyResyncerModSyncSlave.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +BuddyResyncerModSyncSlave::BuddyResyncerModSyncSlave(BuddyResyncJob& parentJob, + MetaSyncCandidateStore* syncCandidates, uint8_t slaveID, const NumNodeID& buddyNodeID) : + SyncSlaveBase("BuddyResyncerModSyncSlave_" + StringTk::uintToStr(slaveID), parentJob, + buddyNodeID), + syncCandidates(syncCandidates) +{ +} + +void BuddyResyncerModSyncSlave::syncLoop() +{ + while (!getSelfTerminateNotIdle()) + { + if (syncCandidates->waitForFiles(this)) + resyncAt(Path(), false, streamCandidates, this); + else if (getOnlyTerminateIfIdle()) + break; + } +} + +namespace { +struct CandidateSignaler +{ + void operator()(MetaSyncCandidateFile* candidate) const + { + candidate->signal(); + } +}; + +bool resyncElemCmp(const MetaSyncCandidateFile::Element& a, const MetaSyncCandidateFile::Element& b) +{ + // we must sync deletions before updates and inodes before everything else: + // + // deletions may fail on the secondary, so they *can* be synced first to begin with. + // any item that is deleted and then recreated with an update must be deleted first. + // we also guarantee that no item is created and deleted in the same changeset. + // + // inodes must be synced before dentries because the dentries may link to inodes in the same + // changeset - and if the secondary does not have the appropriate inode yet, the changeset + // must create it. + if (a.isDeletion && !b.isDeletion) + return true; + + if (a.type == MetaSyncFileType::Inode && b.type != MetaSyncFileType::Inode) + return true; + + return std::make_pair(int(a.type), a.path) < std::make_pair(int(b.type), b.path); +} +} + +FhgfsOpsErr BuddyResyncerModSyncSlave::streamCandidates(Socket& socket) +{ + DEBUG_ENV_VAR(unsigned, DEBUG_FAIL_MODSYNC, 0, "BEEGFS_DEBUG_FAIL_MODSYNC"); + + while (!getSelfTerminateNotIdle()) + { + if (syncCandidates->isFilesEmpty()) + break; + + MetaSyncCandidateFile candidate; + syncCandidates->fetch(candidate, this); + + // signal the candidate at the end of this loop iteration. + // do it like this because we have a few exit points and also have exceptions to take into + // account. + std::unique_ptr signaler(&candidate); + + auto resyncElems = candidate.releaseElements(); + + std::sort(resyncElems.begin(), resyncElems.end(), resyncElemCmp); + + for (auto it = resyncElems.begin(); it != resyncElems.end(); ++it) + { + const auto& element = *it; + + // element.path is relative to the meta root, so we have to chop off the buddymir/ prefix + const Path itemPath(element.path.substr(strlen(META_BUDDYMIRROR_SUBDIR_NAME) + 1)); + + FhgfsOpsErr resyncRes; + + LOG_DBG(MIRRORING, DEBUG, "Syncing one modification.", element.path, element.isDeletion, + int(element.type)); + + switch (element.type) + { + case MetaSyncFileType::Dentry: + resyncRes = element.isDeletion + ? deleteDentry(socket, itemPath.dirname(), itemPath.back()) + : streamDentry(socket, itemPath.dirname(), itemPath.back()); + break; + + case MetaSyncFileType::Directory: + case MetaSyncFileType::Inode: + resyncRes = element.isDeletion + ? deleteInode(socket, itemPath, element.type == MetaSyncFileType::Directory) + : streamInode(socket, itemPath, element.type == MetaSyncFileType::Directory); + break; + + default: + LOG(MIRRORING, ERR, "this should never happen"); + return FhgfsOpsErr_INTERNAL; + } + + if (resyncRes != FhgfsOpsErr_SUCCESS || DEBUG_FAIL_MODSYNC) + { + LOG(MIRRORING, ERR, "Modification resync failed.", element.path, element.isDeletion, + resyncRes); + numErrors.increase(); + + // Since this error prevents the resync from reaching a GOOD state on the secondary, + // we abort here. + parentJob->abort(true); + + // terminate the current stream, start a new one if necessary. we could (in theory) + // reuse the current stream, but terminating a stream that has seen an error is simpler + // to handle than keeping it open. also, bulk resync would like "fail on error" + // semantics very much. + sendResyncPacket(socket, std::tuple<>()); + return FhgfsOpsErr_SUCCESS; + } + else + { + numObjectsSynced.increase(); + } + } + } + + sendResyncPacket(socket, std::tuple<>()); + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.h b/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.h new file mode 100644 index 0000000..08be08d --- /dev/null +++ b/meta/source/components/buddyresyncer/BuddyResyncerModSyncSlave.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "SyncSlaveBase.h" + +class DirEntry; + +class BuddyResyncerModSyncSlave : public SyncSlaveBase +{ + friend class BuddyResyncer; + friend class BuddyResyncJob; + + public: + BuddyResyncerModSyncSlave(BuddyResyncJob& parentJob, MetaSyncCandidateStore* syncCandidates, + uint8_t slaveID, const NumNodeID& buddyNodeID); + + struct Stats + { + uint64_t objectsSynced; + uint64_t errors; + }; + + Stats getStats() + { + return Stats{ numObjectsSynced.read(), numErrors.read() }; + } + + private: + MetaSyncCandidateStore* syncCandidates; + + AtomicUInt64 numObjectsSynced; + AtomicUInt64 numErrors; + + void syncLoop(); + + FhgfsOpsErr streamCandidates(Socket& socket); + + private: + static FhgfsOpsErr streamCandidates(Socket* socket, void* context) + { + return static_cast(context)->streamCandidates(*socket); + } +}; + diff --git a/meta/source/components/buddyresyncer/SessionStoreResyncer.cpp b/meta/source/components/buddyresyncer/SessionStoreResyncer.cpp new file mode 100644 index 0000000..1112a23 --- /dev/null +++ b/meta/source/components/buddyresyncer/SessionStoreResyncer.cpp @@ -0,0 +1,59 @@ +#include "SessionStoreResyncer.h" + +#include +#include +#include +#include +#include +#include + +#include + +SessionStoreResyncer::SessionStoreResyncer(const NumNodeID& buddyNodeID) + : buddyNodeID(buddyNodeID) {} + +void SessionStoreResyncer::doSync() +{ + App* app = Program::getApp(); + SessionStore* sessions = app->getMirroredSessions(); + NodeStoreServers* metaNodes = app->getMetaNodes(); + const uint64_t numSessions = sessions->getSize(); + + numSessionsToSync.set(numSessions); + + // Serialize sessions store into buffer + std::pair, size_t> sessionStoreSerBuf = sessions->serializeToBuf(); + + if (sessionStoreSerBuf.second == 0) + { + // Serialization failed. + errors.set(1); + return; + } + + LOG(MIRRORING, DEBUG, "Serialized session store", ("size", sessionStoreSerBuf.second)); + + ResyncSessionStoreMsg msg(sessionStoreSerBuf.first.get(), sessionStoreSerBuf.second); + RequestResponseArgs rrArgs(NULL, &msg, NETMSGTYPE_ResyncSessionStoreResp); + RequestResponseNode rrNode(buddyNodeID, metaNodes); + msg.registerStreamoutHook(rrArgs); + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (requestRes != FhgfsOpsErr_SUCCESS) + { + errors.set(1); + LOG(MIRRORING, ERR, "Request failed.", requestRes); + return; + } + + ResyncSessionStoreRespMsg* resp = (ResyncSessionStoreRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr retVal = resp->getResult(); + + LOG(MIRRORING, DEBUG, "ResyncSessionStoreRespMsg", retVal); + + if (retVal != FhgfsOpsErr_SUCCESS) + errors.set(1); + else + numSessionsSynced.set(numSessions); +} diff --git a/meta/source/components/buddyresyncer/SessionStoreResyncer.h b/meta/source/components/buddyresyncer/SessionStoreResyncer.h new file mode 100644 index 0000000..9eb7fd3 --- /dev/null +++ b/meta/source/components/buddyresyncer/SessionStoreResyncer.h @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +class SessionStoreResyncer +{ + friend class BuddyResyncer; + friend class BuddyResyncJob; + + public: + SessionStoreResyncer(const NumNodeID& buddyNodeID); + + struct Stats + { + uint64_t sessionsToSync; + uint64_t sessionsSynced; + bool errors; + }; + + Stats getStats() + { + return Stats{ numSessionsToSync.read(), numSessionsSynced.read(), errors.read() != 0 }; + } + + private: + NumNodeID buddyNodeID; + + AtomicUInt64 numSessionsToSync; + AtomicUInt64 numSessionsSynced; + AtomicSizeT errors; // 0 / 1 + + void doSync(); +}; + diff --git a/meta/source/components/buddyresyncer/SyncCandidate.h b/meta/source/components/buddyresyncer/SyncCandidate.h new file mode 100644 index 0000000..a14c028 --- /dev/null +++ b/meta/source/components/buddyresyncer/SyncCandidate.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#include + +enum class MetaSyncDirType +{ + InodesHashDir, + DentriesHashDir, + ContentDir, +}; +GCC_COMPAT_ENUM_CLASS_OPEQNEQ(MetaSyncDirType) + +class MetaSyncCandidateDir +{ + public: + MetaSyncCandidateDir(const std::string& relativePath, MetaSyncDirType type): + relPath(relativePath), type(type) + {} + + MetaSyncCandidateDir() = default; + + private: + std::string relPath; + MetaSyncDirType type; + + public: + const std::string& getRelativePath() const { return relPath; } + MetaSyncDirType getType() const { return type; } +}; + +enum class MetaSyncFileType +{ + Inode, + Dentry, + Directory, +}; +GCC_COMPAT_ENUM_CLASS_OPEQNEQ(MetaSyncFileType) + +template<> +struct SerializeAs { + typedef uint8_t type; +}; + +class MetaSyncCandidateFile +{ + public: + struct Element + { + std::string path; + MetaSyncFileType type; + bool isDeletion; + }; + + MetaSyncCandidateFile(): barrier(nullptr) {} + + MetaSyncCandidateFile(MetaSyncCandidateFile&& src): + barrier(nullptr) + { + swap(src); + } + + MetaSyncCandidateFile& operator=(MetaSyncCandidateFile&& other) + { + MetaSyncCandidateFile(std::move(other)).swap(*this); + return *this; + } + + void swap(MetaSyncCandidateFile& other) + { + paths.swap(other.paths); + std::swap(barrier, other.barrier); + } + + void signal() + { + barrier->wait(); + } + + friend void swap(MetaSyncCandidateFile& a, MetaSyncCandidateFile& b) + { + a.swap(b); + } + + private: + std::vector paths; + Barrier* barrier; + + public: + const std::vector& getElements() const { return paths; } + std::vector releaseElements() { return std::move(paths); } + + void addModification(std::string path, MetaSyncFileType type) + { + paths.push_back(Element{std::move(path), type, false}); + } + + void addDeletion(std::string path, MetaSyncFileType type) + { + paths.push_back(Element{std::move(path), type, true}); + } + + void prepareSignal(Barrier& barrier) + { + this->barrier = &barrier; + } +}; + +typedef SyncCandidateStore MetaSyncCandidateStore; + diff --git a/meta/source/components/buddyresyncer/SyncSlaveBase.cpp b/meta/source/components/buddyresyncer/SyncSlaveBase.cpp new file mode 100644 index 0000000..e5cc36e --- /dev/null +++ b/meta/source/components/buddyresyncer/SyncSlaveBase.cpp @@ -0,0 +1,249 @@ +#include "SyncSlaveBase.h" + +#include +#include +#include +#include +#include + +void SyncSlaveBase::run() +{ + setIsRunning(true); + + try + { + LOG(MIRRORING, DEBUG, "Component started"); + + registerSignalHandler(); + + syncLoop(); + + LOG(MIRRORING, DEBUG, "Component stopped"); + } + catch (std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +FhgfsOpsErr SyncSlaveBase::receiveAck(Socket& socket) +{ + auto resp = MessagingTk::recvMsgBuf(socket); + if (resp.empty()) + return FhgfsOpsErr_INTERNAL; + + const auto respMsg = PThread::getCurrentThreadApp()->getNetMessageFactory()->createFromBuf( + std::move(resp)); + if (respMsg->getMsgType() != NETMSGTYPE_ResyncRawInodesResp) + return FhgfsOpsErr_COMMUNICATION; + + return static_cast(*respMsg).getResult(); +} + +FhgfsOpsErr SyncSlaveBase::resyncAt(const Path& basePath, bool wholeDirectory, + FhgfsOpsErr (*streamFn)(Socket*, void*), void* context) +{ + const bool sendXAttrs = Program::getApp()->getConfig()->getStoreClientXAttrs(); + + this->basePath = META_BUDDYMIRROR_SUBDIR_NAME / basePath; + + ResyncRawInodesMsgEx msg(basePath, sendXAttrs, wholeDirectory); + + RequestResponseNode rrNode(buddyNodeID, Program::getApp()->getMetaNodes()); + RequestResponseArgs rrArgs(nullptr, &msg, NETMSGTYPE_ResyncRawInodesResp, + streamFn, context); + + // resync processing may take a very long time for each step, eg if a very large directory must + // be cleaned out on the secondary. do not use timeouts for resync communication right now. + + rrArgs.minTimeoutMS = -1; + + const auto commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (commRes != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Error during communication with secondary.", commRes); + return commRes; + } + + const auto resyncRes = static_cast(*rrArgs.outRespMsg).getResult(); + + if (resyncRes != FhgfsOpsErr_SUCCESS) + LOG(MIRRORING, ERR, "Error while resyncing directory.", basePath, resyncRes); + + return resyncRes; +} + +FhgfsOpsErr SyncSlaveBase::streamDentry(Socket& socket, const Path& contDirRelPath, + const std::string& name) +{ + std::unique_ptr dentry( + DirEntry::createFromFile((basePath / contDirRelPath).str(), name)); + if (!dentry) + { + LOG(MIRRORING, ERR, "Could not open dentry.", basePath, contDirRelPath, name); + return FhgfsOpsErr_INTERNAL; + } + + if (dentry->getIsInodeInlined()) + { + auto err = sendResyncPacket(socket, LinkDentryInfo( + MetaSyncFileType::Dentry, + (contDirRelPath / name).str(), + true, + dentry->getID(), + false)); + if (err != FhgfsOpsErr_SUCCESS) + return err; + + return receiveAck(socket); + } + + std::vector dentryContent; + + { + Serializer ser; + dentry->serializeDentry(ser); + dentryContent.resize(ser.size()); + + ser = Serializer(&dentryContent[0], dentryContent.size()); + dentry->serializeDentry(ser); + if (!ser.good()) + { + LOG(MIRRORING, ERR, "Could not serialize dentry for secondary."); + return FhgfsOpsErr_INTERNAL; + } + } + + const FhgfsOpsErr sendRes = sendResyncPacket(socket, FullDentryInfo( + MetaSyncFileType::Dentry, + (contDirRelPath / name).str(), + false, + dentryContent, + false)); + if (sendRes != FhgfsOpsErr_SUCCESS) + return sendRes; + + return receiveAck(socket); +} + +FhgfsOpsErr SyncSlaveBase::streamInode(Socket& socket, const Path& inodeRelPath, + const bool isDirectory) +{ + const Path fullPath(basePath / inodeRelPath); + + MetaStore& store = *Program::getApp()->getMetaStore(); + + // Map to store attribute name and its data + std::map> contents; + + if (!isDirectory) + { + std::vector attrData; + FhgfsOpsErr readRes; + + // Helper function to read and store attribute data in map + auto readAndStoreMetaAttribute = [&](const std::string& attrName) + { + attrData.clear(); + readRes = store.getRawMetadata(fullPath, attrName.c_str(), attrData); + if (readRes != FhgfsOpsErr_SUCCESS) + return false; + contents.insert(std::make_pair(attrName, std::move(attrData))); + return true; + }; + + // Handle META_XATTR_NAME ("user.fhgfs") separately because it can be stored as either + // file contents or an extended attribute, depending on the 'storeUseExtendedAttribs' + // configuration setting in the meta config. In contrast, all other metadata-specific + // attributes are strictly stored as extended attributes and do not have the option to + // be stored as file contents. + if (!readAndStoreMetaAttribute(META_XATTR_NAME)) + return readRes; + + // Now handle all remaining metadata attributes + std::pair> listXAttrs = XAttrTk::listXAttrs(fullPath.str()); + if (listXAttrs.first != FhgfsOpsErr_SUCCESS) + return listXAttrs.first; + + for (auto const& attrName : listXAttrs.second) + { + // Process all metadata-specific attributes except META_XATTR_NAME (already handled above) + // This approach ensures we only process attribute(s) that: + // 1. Exist on the inode. + // 2. Are listed in METADATA_XATTR_NAME_LIST, our collection of known metadata attributes. + // 3. Is not META_XATTR_NAME, to prevent duplicate processing. + if (std::find(METADATA_XATTR_NAME_LIST.begin(), METADATA_XATTR_NAME_LIST.end(), attrName) + != METADATA_XATTR_NAME_LIST.end() && (attrName != META_XATTR_NAME)) + { + if (!readAndStoreMetaAttribute(attrName)) + return readRes; + } + } + } + + const FhgfsOpsErr sendRes = sendResyncPacket(socket, InodeInfo( + isDirectory + ? MetaSyncFileType::Directory + : MetaSyncFileType::Inode, + inodeRelPath.str(), + contents, + false)); + if (sendRes != FhgfsOpsErr_SUCCESS) + return sendRes; + + if (Program::getApp()->getConfig()->getStoreClientXAttrs()) + { + auto xattrs = XAttrTk::listUserXAttrs(fullPath.str()); + if (xattrs.first != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Could not list resync candidate xattrs.", fullPath, ("error", xattrs.first)); + xattrs.second.clear(); + return FhgfsOpsErr_INTERNAL; + } + + MsgHelperXAttr::StreamXAttrState state(fullPath.str(), std::move(xattrs.second)); + + const FhgfsOpsErr xattrRes = MsgHelperXAttr::StreamXAttrState::streamXattrFn(&socket, &state); + if (xattrRes != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Error while sending xattrs to secondary.", fullPath, xattrRes); + return FhgfsOpsErr_INTERNAL; + } + } + + return receiveAck(socket); +} + +FhgfsOpsErr SyncSlaveBase::deleteDentry(Socket& socket, const Path& contDirRelPath, + const std::string& name) +{ + auto err = sendResyncPacket(socket, LinkDentryInfo( + MetaSyncFileType::Dentry, + (contDirRelPath / name).str(), + true, + {}, + true)); + if (err != FhgfsOpsErr_SUCCESS) + return err; + + return receiveAck(socket); +} + +FhgfsOpsErr SyncSlaveBase::deleteInode(Socket& socket, const Path& inodeRelPath, + const bool isDirectory) +{ + auto err = sendResyncPacket(socket, InodeInfo( + isDirectory + ? MetaSyncFileType::Directory + : MetaSyncFileType::Inode, + inodeRelPath.str(), + {}, + true)); + if (err != FhgfsOpsErr_SUCCESS) + return err; + + return receiveAck(socket); +} diff --git a/meta/source/components/buddyresyncer/SyncSlaveBase.h b/meta/source/components/buddyresyncer/SyncSlaveBase.h new file mode 100644 index 0000000..5eda720 --- /dev/null +++ b/meta/source/components/buddyresyncer/SyncSlaveBase.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include +#include +#include + +class DirEntry; + +class SyncSlaveBase : public PThread +{ + public: + bool getIsRunning() + { + std::lock_guard lock(stateMutex); + return this->isRunning; + } + + void setOnlyTerminateIfIdle(bool value) + { + onlyTerminateIfIdle.set(value); + } + + bool getOnlyTerminateIfIdle() + { + return onlyTerminateIfIdle.read(); + } + + protected: + BuddyResyncJob* parentJob; + + NumNodeID buddyNodeID; + + Mutex stateMutex; + Condition isRunningChangeCond; + + AtomicSizeT onlyTerminateIfIdle; + + bool isRunning; + + Path basePath; + + SyncSlaveBase(const std::string& threadName, BuddyResyncJob& parentJob, + const NumNodeID buddyNodeID): + PThread(threadName), parentJob(&parentJob), buddyNodeID(buddyNodeID), isRunning(false) + { + } + + virtual void run() override; + virtual void syncLoop() = 0; + + FhgfsOpsErr resyncAt(const Path& basePath, bool wholeDirectory, + FhgfsOpsErr (*streamFn)(Socket*, void*), void* context); + + FhgfsOpsErr streamDentry(Socket& socket, const Path& contDirRelPath, const std::string& name); + FhgfsOpsErr streamInode(Socket& socket, const Path& inodeRelPath, const bool isDirectory); + + FhgfsOpsErr deleteDentry(Socket& socket, const Path& contDirRelPath, const std::string& name); + FhgfsOpsErr deleteInode(Socket& socket, const Path& inodeRelPath, const bool isDirectory); + + void setIsRunning(bool isRunning) + { + std::lock_guard lock(stateMutex); + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } + + bool getSelfTerminateNotIdle() + { + return getSelfTerminate() && !getOnlyTerminateIfIdle(); + } + + template + static FhgfsOpsErr sendResyncPacket(Socket& socket, const ValueT& value) + { + Serializer ser; + ser % value; + + const unsigned packetSize = ser.size(); + const unsigned totalSize = packetSize + sizeof(uint32_t); + + const std::tuple packet(packetSize, value); + + std::unique_ptr buffer(new (std::nothrow) char[totalSize]); + if (!buffer) + { + LOG(MIRRORING, ERR, "Could not allocate memory for resync packet."); + return FhgfsOpsErr_OUTOFMEM; + } + + ser = {buffer.get(), totalSize}; + ser % packet; + if (!ser.good()) + { + LOG(MIRRORING, ERR, "Serialization of resync packet failed."); + return FhgfsOpsErr_INTERNAL; + } + + socket.send(buffer.get(), totalSize, 0); + return FhgfsOpsErr_SUCCESS; + } + + static FhgfsOpsErr receiveAck(Socket& socket); + + private: + typedef std::tuple< + MetaSyncFileType, + const std::string&, // relative path + bool, // is hardlink? + const std::string&, // link target entry id + bool // is deletion? + > LinkDentryInfo; + + typedef std::tuple< + MetaSyncFileType, + const std::string&, // relative path + bool, // is hardlink? + const std::vector&, // dentry raw content + bool // is deletion? + > FullDentryInfo; + + typedef std::tuple< + MetaSyncFileType, + const std::string&, // relative path + std::map>, // metadata specific attribute's raw contents + bool // is deletion? + > InodeInfo; +}; + diff --git a/meta/source/components/worker/BarrierWork.h b/meta/source/components/worker/BarrierWork.h new file mode 100644 index 0000000..f430f60 --- /dev/null +++ b/meta/source/components/worker/BarrierWork.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +/** + * Work item intended to stop all worker threads temporarily, detect that all are stopped using a + * barrier, and restarting them using the same barrier. + * Example: + * Barrier workerBarrier(numWorkers + 1); + * + * workerBarrier.wait(); // Wait for all workers to stop + * + * workerBarrier.wait(); // restart the workers + */ +class BarrierWork : public Work +{ + public: + BarrierWork(Barrier* barrier) : + barrier(barrier) + { } + + virtual ~BarrierWork() { } + + void process(char*, unsigned, char*, unsigned) + { + LOG_DBG(WORKQUEUES, DEBUG, "Start blocking."); + barrier->wait(); + barrier->wait(); + LOG_DBG(WORKQUEUES, DEBUG, "Done."); + } + + private: + Barrier* barrier; +}; + diff --git a/meta/source/components/worker/CloseChunkFileWork.cpp b/meta/source/components/worker/CloseChunkFileWork.cpp new file mode 100644 index 0000000..4d91060 --- /dev/null +++ b/meta/source/components/worker/CloseChunkFileWork.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include +#include +#include +#include +#include "CloseChunkFileWork.h" + +#include + + +void CloseChunkFileWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + FhgfsOpsErr commRes = communicate(); + *outResult = commRes; + + counter->incCount(); +} + +FhgfsOpsErr CloseChunkFileWork::communicate() +{ + const char* logContext = "Close chunk file work"; + + App* app = Program::getApp(); + + // prepare request message + + CloseChunkFileMsg closeMsg(sessionID, fileHandleID, targetID, pathInfoPtr); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + { + closeMsg.addMsgHeaderFeatureFlag(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR); + + if(useBuddyMirrorSecond) + { + closeMsg.addMsgHeaderFeatureFlag(CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS); + closeMsg.addMsgHeaderFeatureFlag(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + } + + closeMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), useBuddyMirrorSecond); + + RequestResponseArgs rrArgs(NULL, &closeMsg, NETMSGTYPE_CloseChunkFileResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "Session: " + sessionID.str() + "; " + "FileHandle: " + fileHandleID); + + return requestRes; + } + + // correct response type received + CloseChunkFileRespMsg* closeRespMsg = (CloseChunkFileRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr closeRemoteRes = closeRespMsg->getResult(); + + // set current dynamic attribs (even if result not success, because then storageVersion==0) + if(outDynAttribs) + { + DynamicFileAttribs currentDynAttribs(closeRespMsg->getStorageVersion(), + closeRespMsg->getFileSize(), closeRespMsg->getAllocedBlocks(), + closeRespMsg->getModificationTimeSecs(), closeRespMsg->getLastAccessTimeSecs() ); + + *outDynAttribs = currentDynAttribs; + } + + if(closeRemoteRes != FhgfsOpsErr_SUCCESS) + { // error: chunk file not closed + int logLevel = Log_WARNING; + + if(closeRemoteRes == FhgfsOpsErr_INUSE) + logLevel = Log_DEBUG; // happens on ctrl+c, so don't irritate user with these log msgs + + LogContext(logContext).log(logLevel, + "Closing chunk file on target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "Error: " + boost::lexical_cast(closeRemoteRes) + "; " + "Session: " + sessionID.str() + "; " + "FileHandle: " + std::string(fileHandleID) ); + + return closeRemoteRes; + } + + // success: chunk file closed + + LOG_DEBUG(logContext, Log_DEBUG, + "Closed chunk file on target. " + + std::string( (pattern->getPatternType() == StripePatternType_BuddyMirror) ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "Session: " + sessionID.str() + "; " + "FileHandle: " + fileHandleID); + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/components/worker/CloseChunkFileWork.h b/meta/source/components/worker/CloseChunkFileWork.h new file mode 100644 index 0000000..84887ff --- /dev/null +++ b/meta/source/components/worker/CloseChunkFileWork.h @@ -0,0 +1,69 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +class CloseChunkFileWork : public Work +{ + public: + /** + * @param outDynAttribs may be NULL if caller is not interested + */ + CloseChunkFileWork(const NumNodeID sessionID, const std::string& fileHandleID, + StripePattern* pattern, uint16_t targetID, PathInfo* pathInfo, + DynamicFileAttribs *outDynAttribs, FhgfsOpsErr* outResult, SynchronizedCounter* counter) : + sessionID(sessionID), fileHandleID(fileHandleID), pattern(pattern), targetID(targetID), + pathInfoPtr(pathInfo), outDynAttribs(outDynAttribs), outResult(outResult), + counter(counter), useBuddyMirrorSecond(false), + msgUserID(NETMSG_DEFAULT_USERID) + { + // all assignments done in initializer list + } + + virtual ~CloseChunkFileWork() {} + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + NumNodeID sessionID; + std::string fileHandleID; + + StripePattern* pattern; + uint16_t targetID; + PathInfo* pathInfoPtr; // to find chunk files + + DynamicFileAttribs* outDynAttribs; + FhgfsOpsErr* outResult; + SynchronizedCounter* counter; + + bool useBuddyMirrorSecond; + + unsigned msgUserID; + + + FhgfsOpsErr communicate(); + + + public: + // getters & setters + + void setMsgUserID(unsigned msgUserID) + { + this->msgUserID = msgUserID; + } + + void setUseBuddyMirrorSecond() + { + this->useBuddyMirrorSecond = true; + } + +}; + diff --git a/meta/source/components/worker/GetChunkFileAttribsWork.cpp b/meta/source/components/worker/GetChunkFileAttribsWork.cpp new file mode 100644 index 0000000..2399991 --- /dev/null +++ b/meta/source/components/worker/GetChunkFileAttribsWork.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include +#include +#include +#include +#include "GetChunkFileAttribsWork.h" + +void GetChunkFileAttribsWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + FhgfsOpsErr commRes = communicate(); + *outResult = commRes; + + counter->incCount(); +} + +/** + * @return true if communication successful + */ +FhgfsOpsErr GetChunkFileAttribsWork::communicate() +{ + const char* logContext = "Stat chunk file work"; + + App* app = Program::getApp(); + + GetChunkFileAttribsMsg getSizeMsg(entryID, targetID, pathInfo); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + { + getSizeMsg.addMsgHeaderFeatureFlag(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR); + + if(useBuddyMirrorSecond) + getSizeMsg.addMsgHeaderFeatureFlag(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR_SECOND); + } + + getSizeMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), useBuddyMirrorSecond); + + RequestResponseArgs rrArgs(NULL, &getSizeMsg, NETMSGTYPE_GetChunkFileAttribsResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return requestRes; + } + + // correct response type received + auto* getSizeRespMsg = (GetChunkFileAttribsRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr getSizeResult = getSizeRespMsg->getResult(); + if(getSizeResult != FhgfsOpsErr_SUCCESS) + { // error: chunk file not unlinked + LogContext(logContext).log(Log_WARNING, + "Getting chunk file attributes from target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return getSizeResult; + } + + // success: chunk file dynamic attribs refreshed + + DynamicFileAttribs currentDynAttribs(getSizeRespMsg->getStorageVersion(), + getSizeRespMsg->getSize(), getSizeRespMsg->getAllocedBlocks(), + getSizeRespMsg->getModificationTimeSecs(), getSizeRespMsg->getLastAccessTimeSecs() ); + + *outDynAttribs = currentDynAttribs; + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/components/worker/GetChunkFileAttribsWork.h b/meta/source/components/worker/GetChunkFileAttribsWork.h new file mode 100644 index 0000000..126edc7 --- /dev/null +++ b/meta/source/components/worker/GetChunkFileAttribsWork.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +class GetChunkFileAttribsWork : public Work +{ + public: + + /** + * @param pathInfo: Only as reference pointer, not owned by this object + */ + GetChunkFileAttribsWork(const std::string& entryID, StripePattern* pattern, uint16_t targetID, + PathInfo* pathInfo, DynamicFileAttribs *outDynAttribs, FhgfsOpsErr* outResult, + SynchronizedCounter* counter) : entryID(entryID), pattern(pattern), targetID(targetID), + pathInfo(pathInfo), outDynAttribs(outDynAttribs), outResult(outResult), counter(counter), + useBuddyMirrorSecond(false), msgUserID(NETMSG_DEFAULT_USERID) + { + // all assignments done in initializer list + } + + virtual ~GetChunkFileAttribsWork() + { + } + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + std::string entryID; + StripePattern* pattern; + uint16_t targetID; + PathInfo *pathInfo; // only as reference ptr, not owned by this object! + DynamicFileAttribs* outDynAttribs; + FhgfsOpsErr* outResult; + SynchronizedCounter* counter; + + bool useBuddyMirrorSecond; + + unsigned msgUserID; // only used for msg header info + + FhgfsOpsErr communicate(); + + public: + void setMsgUserID(unsigned msgUserID) + { + this->msgUserID = msgUserID; + } + + void setUseBuddyMirrorSecond() + { + this->useBuddyMirrorSecond = true; + } + +}; + diff --git a/meta/source/components/worker/LockEntryNotificationWork.cpp b/meta/source/components/worker/LockEntryNotificationWork.cpp new file mode 100644 index 0000000..3ef36ad --- /dev/null +++ b/meta/source/components/worker/LockEntryNotificationWork.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include "LockEntryNotificationWork.h" + +#include + +Mutex LockEntryNotificationWork::ackCounterMutex; +unsigned LockEntryNotificationWork::ackCounter = 0; + + +void LockEntryNotificationWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + /* note: this code is very similar to LockRangeNotificationWork, so if you change something here, + you probably want to change it there, too. */ + + const char* logContext = "LockEntryNotificationWork::process"; + App* app = Program::getApp(); + Logger* logger = Logger::getLogger(); + + Config* cfg = app->getConfig(); + AcknowledgmentStore* ackStore = app->getAckStore(); + DatagramListener* dgramLis = app->getDatagramListener(); + MetaStore* metaStore = app->getMetaStore(); + NodeStoreClients* clients = app->getClientNodes(); + NumNodeID localNodeID = app->getLocalNode().getNumID(); + + // max total time is ackWaitMS * numRetries, defaults to 333ms * 15 => 5s + int ackWaitSleepMS = cfg->getTuneLockGrantWaitMS(); + int numRetriesLeft = cfg->getTuneLockGrantNumRetries(); + + WaitAckMap waitAcks; + WaitAckMap receivedAcks; + WaitAckNotification notifier; + + bool allAcksReceived = false; + + // note: we use uint for tv_sec (not uint64) because 32 bits are enough here + // gives string like this: "time-counter-elck-" + std::string ackIDPrefix = + StringTk::uintToHexStr(TimeAbs().getTimeval()->tv_sec) + "-" + + StringTk::uintToHexStr(incAckCounter() ) + "-" "elck" "-"; + + + if (notifyList.empty()) + return; // nothing to be done + + + // create and register waitAcks + + /* note: waitAcks store pointers to notifyList items, so make sure to not remove anything from + the list while we're still using the waitAcks pointers */ + + for (LockEntryNotifyListIter iter = notifyList.begin(); iter != notifyList.end(); iter++) + { + std::string ackID = ackIDPrefix + iter->lockAckID; // (we assume lockAckID is globally unique) + + WaitAck waitAck(ackID, &(*iter) ); + + waitAcks.insert(WaitAckMapVal(ackID, waitAck) ); + } + + ackStore->registerWaitAcks(&waitAcks, &receivedAcks, ¬ifier); + + + // loop: send requests -> waitforcompletion -> resend + + while(numRetriesLeft && !app->getSelfTerminate() ) + { + // create waitAcks copy + + WaitAckMap currentWaitAcks; + { + const std::lock_guard lock (notifier.waitAcksMutex); + + currentWaitAcks = waitAcks; + } + + // send messages + + for(WaitAckMapIter iter = currentWaitAcks.begin(); iter != currentWaitAcks.end(); iter++) + { + EntryLockDetails* lockDetails = (EntryLockDetails*)iter->second.privateData; + + LockGrantedMsg msg(lockDetails->lockAckID, iter->first, localNodeID); + + std::pair serializeRes = msg.serializeMessage(bufOut, bufOutLen); + if(unlikely(!serializeRes.first) ) + { // buffer too small - should never happen + logger->log(Log_CRITICAL, logContext, "BUG(?): Buffer too small for message " + "serialization: " + StringTk::intToStr(bufOutLen) + "/" + + StringTk::intToStr(serializeRes.second) ); + continue; + } + + auto node = clients->referenceNode(lockDetails->clientNumID); + if(unlikely(!node) ) + { // node not exists + logger->log(Log_DEBUG, logContext, "Cannot grant lock to unknown client: " + + lockDetails->clientNumID.str()); + continue; + } + + dgramLis->sendBufToNode(*node, bufOut, serializeRes.second); + } + + // wait for acks + + allAcksReceived = ackStore->waitForAckCompletion(¤tWaitAcks, ¬ifier, ackWaitSleepMS); + if(allAcksReceived) + break; // all acks received + + // some waitAcks left => prepare next loop + + numRetriesLeft--; + } + + // waiting for acks is over + + ackStore->unregisterWaitAcks(&waitAcks); + + // check and handle results (waitAcks now contains all unreceived acks) + + if (waitAcks.empty()) + { + LOG_DBG(GENERAL, DEBUG, "Stats: received all acks.", receivedAcks.size(), notifyList.size()); + return; // perfect, all acks received + } + + // some acks were missing... + + logger->log(Log_DEBUG, logContext, "Some replies to lock grants missing. Received: " + + StringTk::intToStr(receivedAcks.size() ) + "/" + + StringTk::intToStr(receivedAcks.size() + waitAcks.size() ) ); + + // the inode is supposed to be be referenced already + MetaFileHandle inode = metaStore->referenceLoadedFile(this->parentEntryID, this->isBuddyMirrored, + this->entryID); + if(unlikely(!inode) ) + { // locked inode cannot be referenced + logger->log(Log_DEBUG, logContext, "FileID cannot be referenced (file unlinked?): " + + this->entryID); + return; + } + + // unlock all locks for which we didn't receive an ack + + for(WaitAckMapIter iter = waitAcks.begin(); iter != waitAcks.end(); iter++) + { + EntryLockDetails* lockDetails = (EntryLockDetails*)iter->second.privateData; + + unlockWaiter(*inode, lockDetails); + + LOG_DEBUG(logContext, Log_DEBUG, "Reply was missing from: " + lockDetails->clientNumID.str()); + } + + // cancel all remaining lock waiters if too many acks were missing + // (this is very important to avoid long timeouts if multiple clients are gone/disconnected) + + if(waitAcks.size() > 1) + { // cancel all waiters + cancelAllWaiters(*inode); + } + + // cleanup + + metaStore->releaseFile(this->parentEntryID, inode); +} + +/** + * Remove lock of a waiter from which we didn't receive an ack. + */ +void LockEntryNotificationWork::unlockWaiter(FileInode& inode, EntryLockDetails* lockDetails) +{ + lockDetails->setUnlock(); + + + if(lockType == LockEntryNotifyType_APPEND) + inode.flockAppend(*lockDetails); + else + if(lockType == LockEntryNotifyType_FLOCK) + inode.flockEntry(*lockDetails); + else + LOG(GENERAL, ERR, "Invalid lockType given.", lockType); +} + +/** + * Cancel all remaining lock waiters. + * + * Usually called because too many acks were not received and we want to avoid repeated long + * timeout stalls. + */ +void LockEntryNotificationWork::cancelAllWaiters(FileInode& inode) +{ + if(lockType == LockEntryNotifyType_APPEND) + inode.flockAppendCancelAllWaiters(); + else + if(lockType == LockEntryNotifyType_FLOCK) + inode.flockEntryCancelAllWaiters(); + else + LOG(GENERAL, ERR, "Invalid lockType given.", lockType); +} + +unsigned LockEntryNotificationWork::incAckCounter() +{ + const std::lock_guard lock(ackCounterMutex); + + return ackCounter++; +} + +Mutex* LockEntryNotificationWork::getDGramLisMutex(AbstractDatagramListener* dgramLis) +{ + return dgramLis->getSendMutex(); +} diff --git a/meta/source/components/worker/LockEntryNotificationWork.h b/meta/source/components/worker/LockEntryNotificationWork.h new file mode 100644 index 0000000..2ae1e3c --- /dev/null +++ b/meta/source/components/worker/LockEntryNotificationWork.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class FileInode; // forward declaration + + +typedef std::list LockEntryNotifyList; +typedef LockEntryNotifyList::iterator LockEntryNotifyListIter; +typedef LockEntryNotifyList::const_iterator LockEntryNotifyListCIter; + + +class LockEntryNotificationWork : public Work +{ + public: + /** + * @param notifyList will be owned and freed by this object, so do not use or free it after + * calling this. + */ + LockEntryNotificationWork(LockEntryNotifyType lockType, const std::string& parentEntryID, + const std::string& entryID, bool isBuddyMirrored, LockEntryNotifyList notifyList) : + lockType(lockType), parentEntryID(parentEntryID), entryID(entryID), + isBuddyMirrored(isBuddyMirrored), notifyList(std::move(notifyList)) + { + } + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + // static attributes & methods + + static Mutex ackCounterMutex; + static unsigned ackCounter; + + static unsigned incAckCounter(); + + // instance attributes & methods + + LockEntryNotifyType lockType; + std::string parentEntryID; + std::string entryID; + bool isBuddyMirrored; + LockEntryNotifyList notifyList; + + void unlockWaiter(FileInode& inode, EntryLockDetails* lockDetails); + void cancelAllWaiters(FileInode& inode); + + Mutex* getDGramLisMutex(AbstractDatagramListener* dgramLis); +}; + + diff --git a/meta/source/components/worker/LockRangeNotificationWork.cpp b/meta/source/components/worker/LockRangeNotificationWork.cpp new file mode 100644 index 0000000..e863ebb --- /dev/null +++ b/meta/source/components/worker/LockRangeNotificationWork.cpp @@ -0,0 +1,185 @@ +#include +#include +#include +#include +#include +#include +#include "LockRangeNotificationWork.h" + +#include + +Mutex LockRangeNotificationWork::ackCounterMutex; +unsigned LockRangeNotificationWork::ackCounter = 0; + + +void LockRangeNotificationWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + /* note: this code is very similar to LockEntryNotificationWork, so if you change something here, + you probably want to change it there, too. */ + + const char* logContext = __func__; + App* app = Program::getApp(); + Logger* logger = Logger::getLogger(); + + Config* cfg = app->getConfig(); + AcknowledgmentStore* ackStore = app->getAckStore(); + DatagramListener* dgramLis = app->getDatagramListener(); + MetaStore* metaStore = app->getMetaStore(); + NodeStoreClients* clients = app->getClientNodes(); + NumNodeID localNodeID = app->getLocalNode().getNumID(); + + // max total time is ackWaitMS * numRetries, defaults to 333ms * 15 => 5s + int ackWaitSleepMS = cfg->getTuneLockGrantWaitMS(); + int numRetriesLeft = cfg->getTuneLockGrantNumRetries(); + + WaitAckMap waitAcks; + WaitAckMap receivedAcks; + WaitAckNotification notifier; + + bool allAcksReceived = false; + + // note: we use uint for tv_sec (not uint64) because 32 bits are enough here + std::string ackIDPrefix = + StringTk::uintToHexStr(TimeAbs().getTimeval()->tv_sec) + "-" + + StringTk::uintToHexStr(incAckCounter() ) + "-" + "rlck" "-"; + + + if (notifyList.empty()) + return; // nothing to be done + + + // create and register waitAcks + + /* note: waitAcks store pointers to notifyList items, so make sure to not remove anything from + the list while we're still using the waitAcks pointers */ + + for (LockRangeNotifyListIter iter = notifyList.begin(); iter != notifyList.end(); iter++) + { + std::string ackID = ackIDPrefix + iter->lockAckID; // (we assume lockAckID is globally unique) + + WaitAck waitAck(ackID, &(*iter) ); + + waitAcks.insert(WaitAckMapVal(ackID, waitAck) ); + } + + ackStore->registerWaitAcks(&waitAcks, &receivedAcks, ¬ifier); + + + // loop: send requests -> waitforcompletion -> resend + + while(numRetriesLeft && !app->getSelfTerminate() ) + { + // create waitAcks copy + + WaitAckMap currentWaitAcks; + { + const std::lock_guard lock(notifier.waitAcksMutex); + + currentWaitAcks = waitAcks; + } + + // send messages + + for(WaitAckMapIter iter = currentWaitAcks.begin(); iter != currentWaitAcks.end(); iter++) + { + RangeLockDetails* lockDetails = (RangeLockDetails*)iter->second.privateData; + + LockGrantedMsg msg(lockDetails->lockAckID, iter->first, localNodeID); + + std::pair serializeRes = msg.serializeMessage(bufOut, bufOutLen); + if(unlikely(!serializeRes.first) ) + { // buffer too small - should never happen + logger->log(Log_CRITICAL, logContext, "BUG(?): Buffer too small for message " + "serialization: " + StringTk::intToStr(bufOutLen) + "/" + + StringTk::intToStr(serializeRes.second) ); + continue; + } + + auto node = clients->referenceNode(lockDetails->clientNumID); + if(unlikely(!node) ) + { // node not exists + logger->log(Log_DEBUG, logContext, "Cannot grant lock to unknown client: " + + lockDetails->clientNumID.str()); + continue; + } + + dgramLis->sendBufToNode(*node, bufOut, serializeRes.second); + } + + // wait for acks + + allAcksReceived = ackStore->waitForAckCompletion(¤tWaitAcks, ¬ifier, ackWaitSleepMS); + if(allAcksReceived) + break; // all acks received + + // some waitAcks left => prepare next loop + + numRetriesLeft--; + } + + // waiting for acks is over + + ackStore->unregisterWaitAcks(&waitAcks); + + // check and handle results (waitAcks now contains all unreceived acks) + + if (waitAcks.empty()) + { + LOG_DBG(GENERAL, DEBUG, "Stats: received all acks.", receivedAcks.size(), notifyList.size()); + return; // perfect, all acks received + } + + // some acks were missing... + + logger->log(Log_DEBUG, logContext, "Some replies to lock grants missing. Received: " + + StringTk::intToStr(receivedAcks.size() ) + "/" + + StringTk::intToStr(receivedAcks.size() + waitAcks.size() ) ); + + // the inode is supposed to be be referenced already + MetaFileHandle inode = metaStore->referenceLoadedFile(this->parentEntryID, this->isBuddyMirrored, + this->entryID); + if(unlikely(!inode) ) + { // locked inode cannot be referenced + logger->log(Log_DEBUG, logContext, "FileID cannot be referenced (file unlinked?): " + + this->entryID); + return; + } + + // unlock all locks for which we didn't receive an ack + + for(WaitAckMapIter iter = waitAcks.begin(); iter != waitAcks.end(); iter++) + { + RangeLockDetails* lockDetails = (RangeLockDetails*)iter->second.privateData; + lockDetails->setUnlock(); + + inode->flockRange(*lockDetails); + + LOG_DEBUG(logContext, Log_DEBUG, "Reply was missing from: " + lockDetails->clientNumID.str()); + } + + // cancel all remaining lock waiters if too many acks were missing + // (this is very important to avoid long timeouts it multiple clients are gone/disconnected) + + if(waitAcks.size() > 1) + { // cancel all waiters + inode->flockRangeCancelAllWaiters(); + } + + // cleanup + + metaStore->releaseFile(this->parentEntryID, inode); +} + +unsigned LockRangeNotificationWork::incAckCounter() +{ + const std::lock_guard lock(ackCounterMutex); + + return ackCounter++; +} + +Mutex* LockRangeNotificationWork::getDGramLisMutex(AbstractDatagramListener* dgramLis) +{ + return dgramLis->getSendMutex(); +} diff --git a/meta/source/components/worker/LockRangeNotificationWork.h b/meta/source/components/worker/LockRangeNotificationWork.h new file mode 100644 index 0000000..d248691 --- /dev/null +++ b/meta/source/components/worker/LockRangeNotificationWork.h @@ -0,0 +1,49 @@ +#pragma once + + +#include +#include +#include + +typedef std::list LockRangeNotifyList; +typedef LockRangeNotifyList::iterator LockRangeNotifyListIter; +typedef LockRangeNotifyList::const_iterator LockRangeNotifyListCIter; + + +class LockRangeNotificationWork : public Work +{ + public: + /** + * @param notifyList will be owned and freed by this object, so do not use or free it after + * calling this. + */ + LockRangeNotificationWork(const std::string& parentEntryID, const std::string& entryID, + bool isBuddyMirrored, LockRangeNotifyList notifyList): + parentEntryID(parentEntryID), entryID(entryID), isBuddyMirrored(isBuddyMirrored), + notifyList(std::move(notifyList)) + { + /* all assignments done in initializer list */ + } + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + // static attributes & methods + + static Mutex ackCounterMutex; + static unsigned ackCounter; + + static unsigned incAckCounter(); + + // instance attributes & methods + + std::string parentEntryID; + std::string entryID; + bool isBuddyMirrored; + LockRangeNotifyList notifyList; + + Mutex* getDGramLisMutex(AbstractDatagramListener* dgramLis); +}; + + diff --git a/meta/source/components/worker/SetChunkFileAttribsWork.cpp b/meta/source/components/worker/SetChunkFileAttribsWork.cpp new file mode 100644 index 0000000..c744052 --- /dev/null +++ b/meta/source/components/worker/SetChunkFileAttribsWork.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +void SetChunkFileAttribsWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + FhgfsOpsErr commRes = communicate(); + *outResult = commRes; + + counter->incCount(); +} + +FhgfsOpsErr SetChunkFileAttribsWork::communicate() +{ + const char* logContext = "Set chunk file attribs work"; + + App* app = Program::getApp(); + + SetLocalAttrMsg setAttrMsg(entryID, targetID, pathInfo, validAttribs, attribs, enableCreation); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + { + setAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_BUDDYMIRROR); + + if(useBuddyMirrorSecond) + setAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND); + } + + if(quotaChown) + setAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_USE_QUOTA); + + setAttrMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), useBuddyMirrorSecond); + + RequestResponseArgs rrArgs(NULL, &setAttrMsg, NETMSGTYPE_SetLocalAttrResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "entryID: " + entryID); + + return requestRes; + } + + // correct response type received + const auto setRespMsg = (const SetLocalAttrRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr setRespVal = setRespMsg->getResult(); + if(setRespVal != FhgfsOpsErr_SUCCESS) + { // error occurred + LogContext(logContext).log(Log_WARNING, + "Setting chunk file attributes on target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return setRespVal; + } + + // success + LOG_DEBUG(logContext, Log_DEBUG, + "Set attribs of chunk file on target. " + + std::string( (pattern->getPatternType() == StripePatternType_BuddyMirror) ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + if ((outDynamicAttribs) + && (setRespMsg->isMsgHeaderFeatureFlagSet(SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS))) + { + setRespMsg->getDynamicAttribs(outDynamicAttribs); + } + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/meta/source/components/worker/SetChunkFileAttribsWork.h b/meta/source/components/worker/SetChunkFileAttribsWork.h new file mode 100644 index 0000000..f392c20 --- /dev/null +++ b/meta/source/components/worker/SetChunkFileAttribsWork.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +class SetChunkFileAttribsWork : public Work +{ + public: + + /** + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + SetChunkFileAttribsWork(const std::string& entryID, int validAttribs, + SettableFileAttribs* attribs, bool enableCreation, StripePattern* pattern, + uint16_t targetID, PathInfo* pathInfo, DynamicFileAttribs* outDynamicAttribs, + FhgfsOpsErr* outResult, SynchronizedCounter* counter) : + entryID(entryID), validAttribs(validAttribs), attribs(attribs), + enableCreation(enableCreation), pattern(pattern), targetID(targetID), + pathInfo(pathInfo), outDynamicAttribs(outDynamicAttribs), + outResult(outResult), counter(counter), quotaChown(false), + useBuddyMirrorSecond(false), msgUserID(NETMSG_DEFAULT_USERID) + { + // all assignments done in initializer list + } + + virtual ~SetChunkFileAttribsWork() {} + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + std::string entryID; + int validAttribs; + SettableFileAttribs* attribs; + bool enableCreation; + StripePattern* pattern; + uint16_t targetID; + PathInfo* pathInfo; + DynamicFileAttribs* outDynamicAttribs; // will hold the chunks dynamic attribs as stat'ed on + // the storage server + FhgfsOpsErr* outResult; + SynchronizedCounter* counter; + + bool quotaChown; + + bool useBuddyMirrorSecond; + + unsigned msgUserID; // only used for msg header info + + FhgfsOpsErr communicate(); + + + public: + // getters & setters + void setQuotaChown(bool quotaChown) + { + this->quotaChown = quotaChown; + } + + void setMsgUserID(unsigned msgUserID) + { + this->msgUserID = msgUserID; + } + + void setUseBuddyMirrorSecond() + { + this->useBuddyMirrorSecond = true; + } + +}; + diff --git a/meta/source/components/worker/TruncChunkFileWork.cpp b/meta/source/components/worker/TruncChunkFileWork.cpp new file mode 100644 index 0000000..7c7df8e --- /dev/null +++ b/meta/source/components/worker/TruncChunkFileWork.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +void TruncChunkFileWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + FhgfsOpsErr commRes = communicate(); + *outResult = commRes; + + counter->incCount(); +} + +FhgfsOpsErr TruncChunkFileWork::communicate() +{ + const char* logContext = "Trunc chunk file work"; + + App* app = Program::getApp(); + + TruncLocalFileMsg truncMsg(filesize, entryID, targetID, pathInfo); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + { + truncMsg.addMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(useBuddyMirrorSecond) + { + truncMsg.addMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_NODYNAMICATTRIBS); + truncMsg.addMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + } + + if(useQuota) + truncMsg.setUserdataForQuota(userID, groupID); + + truncMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), useBuddyMirrorSecond); + + RequestResponseArgs rrArgs(NULL, &truncMsg, NETMSGTYPE_TruncLocalFileResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return requestRes; + } + + // correct response type received + TruncLocalFileRespMsg* truncRespMsg = (TruncLocalFileRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr truncRespVal = truncRespMsg->getResult(); + + // set current dynamic attribs (even if result not success, because then storageVersion==0) + if(outDynAttribs) + { + DynamicFileAttribs currentDynAttribs(truncRespMsg->getStorageVersion(), + truncRespMsg->getFileSize(), truncRespMsg->getAllocedBlocks(), + truncRespMsg->getModificationTimeSecs(), truncRespMsg->getLastAccessTimeSecs() ); + + *outDynAttribs = currentDynAttribs; + } + + if(unlikely(truncRespVal != FhgfsOpsErr_SUCCESS) ) + { // error: chunk file not truncated + if(truncRespVal == FhgfsOpsErr_TOOBIG) + return truncRespVal; // will be passed through to user app on client, so don't log here + + LogContext(logContext).log(Log_WARNING, + "Truncation of chunk file on target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID + "; " + "Error: " + boost::lexical_cast(truncRespVal) ); + + return truncRespVal; + } + + // success: chunk file truncated + + LOG_DEBUG(logContext, Log_DEBUG, + "Chunk file truncated on target. " + + std::string( (pattern->getPatternType() == StripePatternType_BuddyMirror) ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/components/worker/TruncChunkFileWork.h b/meta/source/components/worker/TruncChunkFileWork.h new file mode 100644 index 0000000..73120ca --- /dev/null +++ b/meta/source/components/worker/TruncChunkFileWork.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + + +/** + * Truncate file on storage servers + */ +class TruncChunkFileWork : public Work +{ + public: + /** + * @param outDynAttribs may be NULL if caller is not interested + */ + TruncChunkFileWork(const std::string& entryID, int64_t filesize, StripePattern* pattern, + uint16_t targetID, PathInfo* pathInfo, DynamicFileAttribs *outDynAttribs, + FhgfsOpsErr* outResult, SynchronizedCounter* counter) : + entryID(entryID), filesize(filesize), pattern(pattern), targetID(targetID), + pathInfo(pathInfo), outDynAttribs(outDynAttribs), outResult(outResult), counter(counter), + useQuota(false), useBuddyMirrorSecond(false), msgUserID(NETMSG_DEFAULT_USERID) + { + // all assignments done in initializer list + } + + virtual ~TruncChunkFileWork() {} + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + std::string entryID; + int64_t filesize; // already converted to storage node's local file size + StripePattern* pattern; + uint16_t targetID; + PathInfo* pathInfo; // note: not owned by this object + DynamicFileAttribs* outDynAttribs; + FhgfsOpsErr* outResult; + SynchronizedCounter* counter; + + unsigned userID; + unsigned groupID; + bool useQuota; + + bool useBuddyMirrorSecond; + + unsigned msgUserID; // only used for msg header info + + FhgfsOpsErr communicate(); + + + + public: + // getters & setters + void setUserdataForQuota(unsigned userID, unsigned groupID) + { + this->useQuota = true; + this->userID = userID; + this->groupID = groupID; + } + + void setMsgUserID(unsigned msgUserID) + { + this->msgUserID = msgUserID; + } + + void setUseBuddyMirrorSecond() + { + this->useBuddyMirrorSecond = true; + } +}; + diff --git a/meta/source/components/worker/UnlinkChunkFileWork.cpp b/meta/source/components/worker/UnlinkChunkFileWork.cpp new file mode 100644 index 0000000..880f560 --- /dev/null +++ b/meta/source/components/worker/UnlinkChunkFileWork.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include +#include +#include +#include + + +void UnlinkChunkFileWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + FhgfsOpsErr commRes = communicate(); + *outResult = commRes; + + counter->incCount(); +} + +FhgfsOpsErr UnlinkChunkFileWork::communicate() +{ + const char* logContext = "Unlink chunk file work"; + + App* app = Program::getApp(); + + UnlinkLocalFileMsg unlinkMsg(entryID, targetID, pathInfo); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + { + unlinkMsg.addMsgHeaderFeatureFlag(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR); + + if(useBuddyMirrorSecond) + unlinkMsg.addMsgHeaderFeatureFlag(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + } + + unlinkMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), useBuddyMirrorSecond); + + RequestResponseArgs rrArgs(NULL, &unlinkMsg, NETMSGTYPE_UnlinkLocalFileResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed. " + + std::string( (pattern->getPatternType() == StripePatternType_BuddyMirror) ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return requestRes; + } + + // correct response type received + UnlinkLocalFileRespMsg* unlinkRespMsg = (UnlinkLocalFileRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr unlinkResult = unlinkRespMsg->getResult(); + if(unlinkResult != FhgfsOpsErr_SUCCESS) + { // error: local file not unlinked + LogContext(logContext).log(Log_WARNING, + "Unlinking of chunk file from target failed. " + + std::string(pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return unlinkResult; + } + + // success: chunk file unlinked + LOG_DEBUG(logContext, Log_DEBUG, + "Chunk file unlinked from target. " + + std::string( (pattern->getPatternType() == StripePatternType_BuddyMirror) ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/meta/source/components/worker/UnlinkChunkFileWork.h b/meta/source/components/worker/UnlinkChunkFileWork.h new file mode 100644 index 0000000..f9d3dda --- /dev/null +++ b/meta/source/components/worker/UnlinkChunkFileWork.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + + +class UnlinkChunkFileWork : public Work +{ + public: + + /** + * @param pathInfo just a reference, so do not free it as long as you use this object! + */ + UnlinkChunkFileWork(const std::string& entryID, StripePattern* pattern, uint16_t targetID, + PathInfo* pathInfo, FhgfsOpsErr* outResult, SynchronizedCounter* counter) : + entryID(entryID), pattern(pattern), targetID(targetID), pathInfo(pathInfo), + outResult(outResult), counter(counter), useBuddyMirrorSecond(false), + msgUserID(NETMSG_DEFAULT_USERID) + { + // all assignments done in initializer list + } + + virtual ~UnlinkChunkFileWork() {} + + + virtual void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + + private: + std::string entryID; + StripePattern* pattern; + uint16_t targetID; + PathInfo* pathInfo; + FhgfsOpsErr* outResult; + SynchronizedCounter* counter; + + bool useBuddyMirrorSecond; + + unsigned msgUserID; + + FhgfsOpsErr communicate(); + + + public: + // getters & setters + void setMsgUserID(unsigned msgUserID) + { + this->msgUserID = msgUserID; + } + + void setUseBuddyMirrorSecond() + { + this->useBuddyMirrorSecond = true; + } +}; + diff --git a/meta/source/net/message/MirroredMessage.h b/meta/source/net/message/MirroredMessage.h new file mode 100644 index 0000000..435c77c --- /dev/null +++ b/meta/source/net/message/MirroredMessage.h @@ -0,0 +1,445 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +template +class MirroredMessage : public BaseT +{ + protected: + typedef MirroredMessage BaseType; + + BuddyResyncJob* resyncJob; + LockStateT lockState; + + MirroredMessage(): + resyncJob(nullptr) + {} + + virtual FhgfsOpsErr processSecondaryResponse(NetMessage& resp) = 0; + + virtual const char* mirrorLogContext() const = 0; + + virtual std::unique_ptr executeLocally( + NetMessage::ResponseContext& ctx, bool isSecondary) = 0; + + virtual bool isMirrored() = 0; + + // IMPORTANT NOTE ON LOCKING ORDER: + // * always take locks the order + // - HashDirLock + // - DirIDLock + // - ParentNameLock + // - FileIDLock + // * always take locks of each type with the order induced by: + // - HashDirLock: id + // - DirIDLock: (id, forWrite) + // - ParentNameLock: (parentID, name) + // - FileIDLock: id + // + // not doing this may result in deadlocks. + virtual LockStateT lock(EntryLockStore& store) = 0; + + virtual void forwardToSecondary(NetMessage::ResponseContext& ctx) = 0; + + virtual bool processIncoming(NetMessage::ResponseContext& ctx) + { + Session* session = nullptr; + bool isNewState = true; + + if (isMirrored() && !this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + if (Program::getApp()->getInternodeSyncer()->getResyncInProgress()) + resyncJob = Program::getApp()->getBuddyResyncer()->getResyncJob(); + + lockState = lock(*Program::getApp()->getMirroredSessions()->getEntryLockStore()); + } + + // make sure that the thread change set is *always* cleared when we leave this method. + struct _ClearChangeSet { + ~_ClearChangeSet() + { + if (BuddyResyncer::getSyncChangeset()) + { + LOG(MIRRORING, WARNING, "Abandoning sync changeset"); + BuddyResyncer::abandonSyncChangeset(); + } + } + } _clearChangeSet; + (void) _clearChangeSet; + + mirrorState.reset(); + if (isMirrored()) + { + const auto nodeID = this->getRequestorID(ctx).second; + session = Program::getApp()->getMirroredSessions()->referenceSession(nodeID, true); + } + + if (isMirrored() && this->hasFlag(NetMessageHeader::Flag_HasSequenceNumber)) + { + // special case: client has not been told where to start its sequence. in this case, + // we want to answer with only the new seqNoBase for the client, and do NO processing. + if (this->getSequenceNumber() == 0) + { + GenericResponseMsg response(GenericRespMsgCode_NEWSEQNOBASE, "New seqNoBase"); + + response.addFlag(NetMessageHeader::Flag_HasSequenceNumber); + response.setSequenceNumber(session->getSeqNoBase()); + ctx.sendResponse(response); + goto exit; + } + + // a note on locking of mirrorState. since clients process each request in only one + // thread, per client we can have only one request for a given sequence number at any + // given time. retries may reuse the same sequence number, and they may be processed in + // a different thread on the server, but no two threads process the same sequence number + // from the same client at the same time. thus, no locking for the actual structure is + // needed, but extra memory barriers to ensure propagation of results between threads + // are necessary. + __sync_synchronize(); + if (this->hasFlag(NetMessageHeader::Flag_IsSelectiveAck)) + std::tie(mirrorState, isNewState) = session->acquireMirrorStateSlotSelective( + this->getSequenceNumberDone(), + this->getSequenceNumber()); + else + std::tie(mirrorState, isNewState) = session->acquireMirrorStateSlot( + this->getSequenceNumberDone(), + this->getSequenceNumber()); + } + + if (!isNewState) + { + if (mirrorState->response) + mirrorState->response->sendResponse(ctx); + else + ctx.sendResponse( + GenericResponseMsg( + GenericRespMsgCode_TRYAGAIN, + "Request for same sequence number is currently in progress")); + } + else + { + if (resyncJob && resyncJob->isRunning()) + { + BuddyResyncer::registerSyncChangeset(); + resyncJob->registerOps(); + } + + auto responseState = executeLocally(ctx, + isMirrored() && this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)); + + // responseState may ne null if the message has called earlyComplete(). do not finish + // the operation twice in this case. + if (responseState) + finishOperation(ctx, std::move(responseState)); + } + + exit: + if (session) + Program::getApp()->getMirroredSessions()->releaseSession(session); + + return true; + } + + template + void earlyComplete(NetMessage::ResponseContext& ctx, ResponseT&& state) + { + finishOperation(ctx, boost::make_unique(std::move(state))); + + Socket* sock = ctx.getSocket(); + IncomingPreprocessedMsgWork::releaseSocket(Program::getApp(), &sock, this); + } + + void buddyResyncNotify(NetMessage::ResponseContext& ctx, bool stateChanged) + { + // pairs with the memory barrier before acquireMirrorStateSlot + __sync_synchronize(); + + if (BuddyResyncer::getSyncChangeset()) + { + if (isMirrored() && + !this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) && + stateChanged) + BuddyResyncer::commitThreadChangeSet(); + else + BuddyResyncer::abandonSyncChangeset(); + } + } + + void finishOperation(NetMessage::ResponseContext& ctx, + std::unique_ptr state) + { + auto* responsePtr = state.get(); + + if (isMirrored() && + !this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) && + state) + { + if (state->changesObservableState()) + forwardToSecondary(ctx); + else + notifySecondaryOfACK(ctx); + } + + if (mirrorState) + mirrorState->response = std::move(state); + + // pairs with the memory barrier before acquireMirrorStateSlot + __sync_synchronize(); + + if (BuddyResyncer::getSyncChangeset()) + { + resyncJob = Program::getApp()->getBuddyResyncer()->getResyncJob(); + if (isMirrored() && + !this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) && + responsePtr && + responsePtr->changesObservableState()) + BuddyResyncer::commitThreadChangeSet(); + else + BuddyResyncer::abandonSyncChangeset(); + + resyncJob->unregisterOps(); + } + + if (responsePtr) + responsePtr->sendResponse(ctx); + + lockState = {}; + } + + void notifySecondaryOfACK(NetMessage::ResponseContext& ctx) + { + AckNotifiyMsg msg; + // if the secondary does not respond with SUCCESS, it will automatically be set to + // needs-resync. eventually, resync will clear the secondary sessions entirely, which will + // also flush the sequence number store. + sendToSecondary(ctx, msg, NETMSGTYPE_AckNotifyResp); + } + + virtual void prepareMirrorRequestArgs(RequestResponseArgs& args) + { + } + + template + void sendToSecondary(NetMessage::ResponseContext& ctx, MirroredMessageBase& message, + unsigned respType, FhgfsOpsErr expectedResult = FhgfsOpsErr_SUCCESS) + { + App* app = Program::getApp(); + NodeStoreServers* metaNodes = app->getMetaNodes(); + MirrorBuddyGroupMapper* buddyGroups = app->getMetaBuddyGroupMapper(); + + DEBUG_ENV_VAR(unsigned, FORWARD_DELAY, 0, "BEEGFS_FORWARD_DELAY_SECS"); + + if (FORWARD_DELAY) + sleep(FORWARD_DELAY); + + // if a resync is currently running, abort right here, immediatly. we do not need to know + // the exact state of the buddy: a resync is running. it's bad. + if (app->getInternodeSyncer()->getResyncInProgress()) + return; + + // check whether the secondary is viable at all: if it is not online and good, + // communicating will not do any good. even online/needs-resync must be skipped, because + // the resyncer must be the only entitity that changes the secondary as long as it is not + // good yet. + { + CombinedTargetState secondaryState; + NumNodeID secondaryID(buddyGroups->getSecondaryTargetID( + buddyGroups->getLocalGroupID())); + + bool getStateRes = app->getMetaStateStore()->getState(secondaryID.val(), + secondaryState); + + // if the secondary is anything except online/good, set it to needs-resync immediately. + // whenever we pass this point, the secondary will have missed *something* of + // importance, so anything except online/good must be set to needs-resync right here. + if (!getStateRes + || secondaryState.reachabilityState != TargetReachabilityState_ONLINE + || secondaryState.consistencyState != TargetConsistencyState_GOOD) + { + auto* const resyncer = app->getBuddyResyncer(); + auto* const job = resyncer->getResyncJob(); + + // if we have no job or a running job, we must start a resync soon. if we have a + // job that has finished successfully, the management server may not have noticed + // that the secondary is completely resynced, so our buddys state may well not be + // GOOD even though we have resynced completely. we may assume that a successful + // resync implies that the buddy is good, even if the management server thinks it + // isn't. + if (!job || + (!job->isRunning() && job->getState() != BuddyResyncJobState_SUCCESS)) + { + setBuddyNeedsResync(); + return; + } + } + } + + RequestResponseArgs rrArgs(NULL, &message, respType); + RequestResponseNode rrNode(NumNodeID(buddyGroups->getLocalGroupID()), metaNodes); + + rrNode.setMirrorInfo(buddyGroups, true); + rrNode.setTargetStates(app->getMetaStateStore()); + + prepareMirrorRequestArgs(rrArgs); + + // copy sequence numbers and set original requestor info for secondary + message.setSequenceNumber(this->getSequenceNumber()); + message.setSequenceNumberDone(this->getSequenceNumberDone()); + message.setRequestorID(this->getRequestorID(ctx)); + // (almost) all messages do some sort of statistics gathering by user ID + message.setMsgHeaderUserID(this->getMsgHeaderUserID()); + // set flag here instead of at the beginning because &message == this is often used + message.addFlag(NetMessageHeader::Flag_BuddyMirrorSecond); + message.addFlag(this->getFlags() & NetMessageHeader::Flag_IsSelectiveAck); + message.addFlag(this->getFlags() & NetMessageHeader::Flag_HasSequenceNumber); + + FhgfsOpsErr commRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + message.removeFlag(NetMessageHeader::Flag_BuddyMirrorSecond); + + if (commRes != FhgfsOpsErr_SUCCESS) + { + // since we have reached this point, the secondary has indubitably not received + // important information from the primary. we now have two choices to keep the system + // in a consistent, safe state: + // + // 1) set the secondary to needs-resync + // 2) rollback the modifications we have made and let the client retry, hoping that + // some future communication with the secondary is successful + // + // 2 is not a viable option: since some operations may move data off of this metadata + // server and onto another one completely; allowing these to be undone requires a + // two-phase commit protocol, which incurs large communication overhead for a + // (hopefully) very rare error case. other operations delete local state (eg unlink, + // or close of an unlinked file), which would have to be held in limbo until either a + // commit or a rollback is issued. + // + // since we assume that communication errors are very rare, option 1 is the most + // efficient in the general case (as it does not have to keep objects alive past their + // intended lifetimes), so we set the secondary to needs-resync on any kind of + // communication error. + // other errors, e.g. out-of-memory conditions or errors caused by streamout hooks, are + // also assumed to be rare. if any of these happens, the secondary must be resynced no + // matter what actually happened. since the operations itself succeeded, we cannot send + // a notification about the communication error either - we'd have to drop the operation + // result to do that. + +#ifdef BEEGFS_DEBUG + int buddyNodeID = buddyGroups->getBuddyTargetID(app->getLocalNodeNumID().val()); + + LOG_CTX(MIRRORING, DEBUG, mirrorLogContext(), "Communication with secondary failed. " + "Resync will be required when secondary comes back", buddyNodeID, commRes); +#endif + setBuddyNeedsResync(); + + return; + } + + FhgfsOpsErr respMsgRes = processSecondaryResponse(*rrArgs.outRespMsg); + + if (respMsgRes != expectedResult) + { + // whoops; primary and secondary did different things; if secondary is not resyncing + // AND communication was good this is concerning (result must have been success on + // primary, otherwise no forwarding would have happened). + // usually, this would mean that primary and secondary do not have the same state, or + // that the secondary has some kind of system error. (if the primary had a system error, + // it would be more likely to fail than to succeed). + // in either case, the secondary should be resynced, even if the primary experienced + // a hardware fault or similar errors: at this point, we can no longer differentiate + // between good and bad state on the primary, and the secondary may be arbitrarily out + // of sync. + LOG_CTX(MIRRORING, NOTICE, mirrorLogContext(), + "Different return codes from primary and secondary buddy. " + "Setting secondary to needs-resync.", + ("Expected response", expectedResult), + ("Received response", respMsgRes)); + setBuddyNeedsResync(); + } + } + + // inodes that are changes during mirrored processing on the secondary (eg file creation or + // deletion, setxattr, etc) may have timestamps changes to a different value than the primary. + // to remedy this, the secondary must explicitly set these timestamps during processing. + bool shouldFixTimestamps() + { + return isMirrored() && Program::getApp()->getConfig()->getTuneMirrorTimestamps(); + } + + void fixInodeTimestamp(DirInode& inode, MirroredTimestamps& ts) + { + if (!isMirrored()) + return; + + BEEGFS_BUG_ON_DEBUG(!inode.getIsLoaded(), "inode not loaded"); + + StatData stat; + + inode.getStatData(stat); + + if (!this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + ts = stat.getMirroredTimestamps(); + } + else + { + stat.setMirroredTimestamps(ts); + + inode.setStatData(stat); + } + } + + void fixInodeTimestamp(FileInode& inode, MirroredTimestamps& ts, + EntryInfo* const saveEntryInfo) + { + if (!isMirrored()) + return; + + StatData stat; + + inode.getStatData(stat); + + if (!this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + ts = stat.getMirroredTimestamps(); + } + else + { + stat.setMirroredTimestamps(ts); + + inode.setStatData(stat); + if (saveEntryInfo) + inode.updateInodeOnDisk(saveEntryInfo); + } + } + + void updateNodeOp(NetMessage::ResponseContext& ctx, MetaOpCounterTypes type) + { + const auto counter = isMirrored() && this->hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) + ? MetaOpCounter_MIRROR + : type; + + Program::getApp()->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + counter, this->getMsgHeaderUserID()); + } + + private: + std::shared_ptr mirrorState; + + void setBuddyNeedsResync() + { + BuddyCommTk::setBuddyNeedsResync(Program::getApp()->getMetaPath(), true); + } +}; + diff --git a/meta/source/net/message/NetMessageFactory.cpp b/meta/source/net/message/NetMessageFactory.cpp new file mode 100644 index 0000000..7789f94 --- /dev/null +++ b/meta/source/net/message/NetMessageFactory.cpp @@ -0,0 +1,354 @@ +// control messages +#include +#include +#include +#include +#include + +// nodes messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// storage messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// session messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// mon message +#include + +// fsck messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// chunk balancing +#include +#include +#include + + +#include +#include + +#include "NetMessageFactory.h" + + +/** + * @return NetMessage that must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr NetMessageFactory::createFromMsgType(unsigned short msgType) const +{ + NetMessage* msg; + + switch(msgType) + { + // The following lines are grouped by "type of the message" and ordered alphabetically inside + // the groups. There should always be one message per line to keep a clear layout (although + // this might lead to lines that are longer than usual) + + // control messages + case NETMSGTYPE_Ack: { msg = new AckMsgEx(); } break; + case NETMSGTYPE_AuthenticateChannel: { msg = new AuthenticateChannelMsgEx(); } break; + case NETMSGTYPE_GenericResponse: { msg = new GenericResponseMsg(); } break; + case NETMSGTYPE_SetChannelDirect: { msg = new SetChannelDirectMsgEx(); } break; + case NETMSGTYPE_PeerInfo: { msg = new PeerInfoMsgEx(); } break; + + // nodes messages + case NETMSGTYPE_ChangeTargetConsistencyStatesResp: { msg = new ChangeTargetConsistencyStatesRespMsg(); } break; + case NETMSGTYPE_GenericDebug: { msg = new GenericDebugMsgEx(); } break; + case NETMSGTYPE_GetClientStats: { msg = new GetClientStatsMsgEx(); } break; + case NETMSGTYPE_GetMirrorBuddyGroupsResp: { msg = new GetMirrorBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetNodeCapacityPools: { msg = new GetNodeCapacityPoolsMsgEx(); } break; + case NETMSGTYPE_GetNodeCapacityPoolsResp: { msg = new GetNodeCapacityPoolsRespMsg(); } break; + case NETMSGTYPE_GetNodes: { msg = new GetNodesMsgEx(); } break; + case NETMSGTYPE_GetNodesResp: { msg = new GetNodesRespMsg(); } break; + case NETMSGTYPE_GetStatesAndBuddyGroupsResp: { msg = new GetStatesAndBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetStoragePoolsResp: { msg = new GetStoragePoolsRespMsg(); } break; + case NETMSGTYPE_GetTargetMappings: { msg = new GetTargetMappingsMsgEx(); } break; + case NETMSGTYPE_GetTargetMappingsResp: { msg = new GetTargetMappingsRespMsg(); } break; + case NETMSGTYPE_GetTargetStatesResp: { msg = new GetTargetStatesRespMsg(); } break; + case NETMSGTYPE_HeartbeatRequest: { msg = new HeartbeatRequestMsgEx(); } break; + case NETMSGTYPE_Heartbeat: { msg = new HeartbeatMsgEx(); } break; + case NETMSGTYPE_MapTargets: { msg = new MapTargetsMsgEx(); } break; + case NETMSGTYPE_PublishCapacities: { msg = new PublishCapacitiesMsgEx(); } break; + case NETMSGTYPE_RefreshStoragePools: { msg = new RefreshStoragePoolsMsgEx(); } break; + case NETMSGTYPE_RegisterNodeResp: { msg = new RegisterNodeRespMsg(); } break; + case NETMSGTYPE_RemoveNode: { msg = new RemoveNodeMsgEx(); } break; + case NETMSGTYPE_RemoveNodeResp: { msg = new RemoveNodeRespMsg(); } break; + case NETMSGTYPE_RefreshCapacityPools: { msg = new RefreshCapacityPoolsMsgEx(); } break; + case NETMSGTYPE_RefreshTargetStates: { msg = new RefreshTargetStatesMsgEx(); } break; + case NETMSGTYPE_SetMirrorBuddyGroup: { msg = new SetMirrorBuddyGroupMsgEx(); } break; + case NETMSGTYPE_SetTargetConsistencyStates: { msg = new SetTargetConsistencyStatesMsgEx(); } break; + case NETMSGTYPE_SetTargetConsistencyStatesResp: { msg = new SetTargetConsistencyStatesRespMsg(); } break; + + // storage messages + case NETMSGTYPE_ChunkBalance: { msg = new ChunkBalanceMsgEx(); } break; + case NETMSGTYPE_CpChunkPathsResp: { msg = new CpChunkPathsRespMsg(); } break; + case NETMSGTYPE_FindLinkOwner: { msg = new FindLinkOwnerMsgEx(); } break; + case NETMSGTYPE_FindOwner: { msg = new FindOwnerMsgEx(); } break; + case NETMSGTYPE_FindOwnerResp: { msg = new FindOwnerRespMsg(); } break; + case NETMSGTYPE_GetChunkFileAttribsResp: { msg = new GetChunkFileAttribsRespMsg(); } break; + case NETMSGTYPE_GetEntryInfo: { msg = new GetEntryInfoMsgEx(); } break; + case NETMSGTYPE_GetEntryInfoResp: { msg = new GetEntryInfoRespMsg(); } break; + case NETMSGTYPE_GetHighResStats: { msg = new GetHighResStatsMsgEx(); } break; + case NETMSGTYPE_GetMetaResyncStats: { msg = new GetMetaResyncStatsMsgEx(); } break; + case NETMSGTYPE_RequestExceededQuotaResp: {msg = new RequestExceededQuotaRespMsg(); } break; + case NETMSGTYPE_SetExceededQuota: {msg = new SetExceededQuotaMsgEx(); } break; + case NETMSGTYPE_StorageResyncStarted: { msg = new StorageResyncStartedMsgEx(); } break; + case NETMSGTYPE_StorageResyncStartedResp: { msg = new StorageResyncStartedRespMsg(); } break; + case NETMSGTYPE_GetXAttr: { msg = new GetXAttrMsgEx(); } break; + case NETMSGTYPE_GetXAttrResp: { msg = new GetXAttrRespMsg(); } break; + case NETMSGTYPE_Hardlink: { msg = new HardlinkMsgEx(); } break; + case NETMSGTYPE_HardlinkResp: { msg = new HardlinkRespMsg(); } break; + case NETMSGTYPE_ListDirFromOffset: { msg = new ListDirFromOffsetMsgEx(); } break; + case NETMSGTYPE_ListDirFromOffsetResp: { msg = new ListDirFromOffsetRespMsg(); } break; + case NETMSGTYPE_ListXAttr: { msg = new ListXAttrMsgEx(); } break; + case NETMSGTYPE_ListXAttrResp: { msg = new ListXAttrRespMsg(); } break; + case NETMSGTYPE_LookupIntent: { msg = new LookupIntentMsgEx(); } break; + case NETMSGTYPE_LookupIntentResp: { msg = new LookupIntentRespMsg(); } break; + case NETMSGTYPE_MkDir: { msg = new MkDirMsgEx(); } break; + case NETMSGTYPE_MkDirResp: { msg = new MkDirRespMsg(); } break; + case NETMSGTYPE_MkFile: { msg = new MkFileMsgEx(); } break; + case NETMSGTYPE_MkFileResp: { msg = new MkFileRespMsg(); } break; + case NETMSGTYPE_MkFileWithPattern: { msg = new MkFileWithPatternMsgEx(); } break; + case NETMSGTYPE_MkFileWithPatternResp: { msg = new MkFileWithPatternRespMsg(); } break; + case NETMSGTYPE_MkLocalDir: { msg = new MkLocalDirMsgEx(); } break; + case NETMSGTYPE_MkLocalDirResp: { msg = new MkLocalDirRespMsg(); } break; + case NETMSGTYPE_MovingDirInsert: { msg = new MovingDirInsertMsgEx(); } break; + case NETMSGTYPE_MovingDirInsertResp: { msg = new MovingDirInsertRespMsg(); } break; + case NETMSGTYPE_MovingFileInsert: { msg = new MovingFileInsertMsgEx(); } break; + case NETMSGTYPE_MovingFileInsertResp: { msg = new MovingFileInsertRespMsg(); } break; + case NETMSGTYPE_RefreshEntryInfo: { msg = new RefreshEntryInfoMsgEx(); } break; + case NETMSGTYPE_RefreshEntryInfoResp: { msg = new RefreshEntryInfoRespMsg(); } break; + case NETMSGTYPE_ResyncRawInodes: { msg = new ResyncRawInodesMsgEx(); } break; + case NETMSGTYPE_ResyncRawInodesResp: { msg = new ResyncRawInodesRespMsg(); } break; + case NETMSGTYPE_ResyncSessionStore: { msg = new ResyncSessionStoreMsgEx(); } break; + case NETMSGTYPE_ResyncSessionStoreResp: { msg = new ResyncSessionStoreRespMsg(); } break; + case NETMSGTYPE_RemoveXAttr: { msg = new RemoveXAttrMsgEx(); } break; + case NETMSGTYPE_RemoveXAttrResp: { msg = new RemoveXAttrRespMsg(); } break; + case NETMSGTYPE_Rename: { msg = new RenameV2MsgEx(); } break; + case NETMSGTYPE_RenameResp: { msg = new RenameRespMsg(); } break; + case NETMSGTYPE_RmChunkPathsResp: { msg = new RmChunkPathsRespMsg(); } break; + case NETMSGTYPE_RmDirEntry: { msg = new RmDirEntryMsgEx(); } break; + case NETMSGTYPE_RmDir: { msg = new RmDirMsgEx(); } break; + case NETMSGTYPE_RmDirResp: { msg = new RmDirRespMsg(); } break; + case NETMSGTYPE_RmLocalDir: { msg = new RmLocalDirMsgEx(); } break; + case NETMSGTYPE_RmLocalDirResp: { msg = new RmLocalDirRespMsg(); } break; + case NETMSGTYPE_SetAttr: { msg = new SetAttrMsgEx(); } break; + case NETMSGTYPE_SetAttrResp: { msg = new SetAttrRespMsg(); } break; + case NETMSGTYPE_SetDirPattern: { msg = new SetDirPatternMsgEx(); } break; + case NETMSGTYPE_SetDirPatternResp: { msg = new SetDirPatternRespMsg(); } break; + case NETMSGTYPE_SetLocalAttrResp: { msg = new SetLocalAttrRespMsg(); } break; + case NETMSGTYPE_SetMetadataMirroring: { msg = new SetMetadataMirroringMsgEx(); } break; + case NETMSGTYPE_SetStorageTargetInfoResp: { msg = new SetStorageTargetInfoRespMsg(); } break; + case NETMSGTYPE_SetXAttr: { msg = new SetXAttrMsgEx(); } break; + case NETMSGTYPE_SetXAttrResp: { msg = new SetXAttrRespMsg(); } break; + case NETMSGTYPE_Stat: { msg = new StatMsgEx(); } break; + case NETMSGTYPE_StatResp: { msg = new StatRespMsg(); } break; + case NETMSGTYPE_StatStoragePath: { msg = new StatStoragePathMsgEx(); } break; + case NETMSGTYPE_StatStoragePathResp: { msg = new StatStoragePathRespMsg(); } break; + case NETMSGTYPE_StripePatternUpdate: { msg = new StripePatternUpdateMsgEx(); } break; + case NETMSGTYPE_TruncFile: { msg = new TruncFileMsgEx(); } break; + case NETMSGTYPE_TruncFileResp: { msg = new TruncFileRespMsg(); } break; + case NETMSGTYPE_TruncLocalFileResp: { msg = new TruncLocalFileRespMsg(); } break; + case NETMSGTYPE_UnlinkFile: { msg = new UnlinkFileMsgEx(); } break; + case NETMSGTYPE_UnlinkFileResp: { msg = new UnlinkFileRespMsg(); } break; + case NETMSGTYPE_UnlinkLocalFileResp: { msg = new UnlinkLocalFileRespMsg(); } break; + case NETMSGTYPE_UpdateDirParent: { msg = new UpdateDirParentMsgEx(); } break; + case NETMSGTYPE_UpdateDirParentResp: { msg = new UpdateDirParentRespMsg(); } break; + case NETMSGTYPE_MoveFileInode: { msg = new MoveFileInodeMsgEx(); } break; + case NETMSGTYPE_MoveFileInodeResp: {msg = new MoveFileInodeRespMsg(); } break; + case NETMSGTYPE_UnlinkLocalFileInode: {msg = new UnlinkLocalFileInodeMsgEx(); } break; + case NETMSGTYPE_UnlinkLocalFileInodeResp: {msg = new UnlinkLocalFileInodeRespMsg(); } break; + case NETMSGTYPE_SetFilePattern: { msg = new SetFilePatternMsgEx(); } break; + case NETMSGTYPE_SetFilePatternResp: { msg = new SetFilePatternRespMsg(); } break; + case NETMSGTYPE_SetFileState: { msg = new SetFileStateMsgEx(); } break; + case NETMSGTYPE_SetFileStateResp: { msg = new SetFileStateRespMsg(); } break; + + // session messages + case NETMSGTYPE_BumpFileVersion: { msg = new BumpFileVersionMsgEx(); } break; + case NETMSGTYPE_BumpFileVersionResp: { msg = new BumpFileVersionRespMsg(); } break; + case NETMSGTYPE_OpenFile: { msg = new OpenFileMsgEx(); } break; + case NETMSGTYPE_OpenFileResp: { msg = new OpenFileRespMsg(); } break; + case NETMSGTYPE_CloseFile: { msg = new CloseFileMsgEx(); } break; + case NETMSGTYPE_CloseFileResp: { msg = new CloseFileRespMsg(); } break; + case NETMSGTYPE_CloseChunkFileResp: { msg = new CloseChunkFileRespMsg(); } break; + case NETMSGTYPE_WriteLocalFileResp: { msg = new WriteLocalFileRespMsg(); } break; + case NETMSGTYPE_FSyncLocalFileResp: { msg = new FSyncLocalFileRespMsg(); } break; + case NETMSGTYPE_FLockAppend: { msg = new FLockAppendMsgEx(); } break; + case NETMSGTYPE_FLockAppendResp: { msg = new FLockAppendRespMsg(); } break; + case NETMSGTYPE_FLockEntry: { msg = new FLockEntryMsgEx(); } break; + case NETMSGTYPE_FLockEntryResp: { msg = new FLockEntryRespMsg(); } break; + case NETMSGTYPE_FLockRange: { msg = new FLockRangeMsgEx(); } break; + case NETMSGTYPE_FLockRangeResp: { msg = new FLockRangeRespMsg(); } break; + case NETMSGTYPE_GetFileVersion: { msg = new GetFileVersionMsgEx(); } break; + case NETMSGTYPE_GetFileVersionResp: { msg = new GetFileVersionRespMsg(); } break; + case NETMSGTYPE_AckNotify: { msg = new AckNotifiyMsgEx(); } break; + case NETMSGTYPE_AckNotifyResp: { msg = new AckNotifiyRespMsg(); } break; + + // mon message + case NETMSGTYPE_RequestMetaData: { msg = new RequestMetaDataMsgEx(); } break; + + // fsck messages + case NETMSGTYPE_RetrieveDirEntries: { msg = new RetrieveDirEntriesMsgEx(); } break; + case NETMSGTYPE_RetrieveInodes: { msg = new RetrieveInodesMsgEx(); } break; + case NETMSGTYPE_RetrieveFsIDs: { msg = new RetrieveFsIDsMsgEx(); } break; + case NETMSGTYPE_DeleteDirEntries: { msg = new DeleteDirEntriesMsgEx(); } break; + case NETMSGTYPE_CreateDefDirInodes: { msg = new CreateDefDirInodesMsgEx(); } break; + case NETMSGTYPE_FixInodeOwners: { msg = new FixInodeOwnersMsgEx(); } break; + case NETMSGTYPE_FixInodeOwnersInDentry: { msg = new FixInodeOwnersInDentryMsgEx(); } break; + case NETMSGTYPE_LinkToLostAndFound: { msg = new LinkToLostAndFoundMsgEx(); } break; + case NETMSGTYPE_CreateEmptyContDirs: { msg = new CreateEmptyContDirsMsgEx(); } break; + case NETMSGTYPE_UpdateFileAttribs: { msg = new UpdateFileAttribsMsgEx(); } break; + case NETMSGTYPE_UpdateDirAttribs: { msg = new UpdateDirAttribsMsgEx(); } break; + case NETMSGTYPE_RemoveInodes: { msg = new RemoveInodesMsgEx(); } break; + case NETMSGTYPE_RecreateFsIDs: { msg = new RecreateFsIDsMsgEx(); } break; + case NETMSGTYPE_RecreateDentries: { msg = new RecreateDentriesMsgEx(); } break; + case NETMSGTYPE_FsckSetEventLogging: { msg = new FsckSetEventLoggingMsgEx(); } break; + case NETMSGTYPE_AdjustChunkPermissions: { msg = new AdjustChunkPermissionsMsgEx(); } break; + case NETMSGTYPE_CheckAndRepairDupInode: { msg = new CheckAndRepairDupInodeMsgEx(); } break; + + default: + { + msg = new SimpleMsg(NETMSGTYPE_Invalid); + } break; + } + + return std::unique_ptr(msg); +} + diff --git a/meta/source/net/message/NetMessageFactory.h b/meta/source/net/message/NetMessageFactory.h new file mode 100644 index 0000000..7331f6c --- /dev/null +++ b/meta/source/net/message/NetMessageFactory.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class NetMessageFactory : public AbstractNetMessageFactory +{ + public: + NetMessageFactory() {} + + protected: + virtual std::unique_ptr createFromMsgType(unsigned short msgType) const override; +} ; + diff --git a/meta/source/net/message/control/AckMsgEx.cpp b/meta/source/net/message/control/AckMsgEx.cpp new file mode 100644 index 0000000..44a5619 --- /dev/null +++ b/meta/source/net/message/control/AckMsgEx.cpp @@ -0,0 +1,24 @@ +#include +#include "AckMsgEx.h" + +bool AckMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "Ack incoming"; + #endif //BEEGFS_DEBUG + + LOG_DEBUG(logContext, 5, std::string("Value: ") + getValue() ); + + AcknowledgmentStore* ackStore = Program::getApp()->getAckStore(); + ackStore->receivedAck(getValue() ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_ACK, + getMsgHeaderUserID() ); + + // note: this message does not require a response + + return true; +} + + diff --git a/meta/source/net/message/control/AckMsgEx.h b/meta/source/net/message/control/AckMsgEx.h new file mode 100644 index 0000000..e439d1a --- /dev/null +++ b/meta/source/net/message/control/AckMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +// see class AcknowledgeableMsg (fhgfs_common) for a short description + +class AckMsgEx : public AckMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/control/SetChannelDirectMsgEx.cpp b/meta/source/net/message/control/SetChannelDirectMsgEx.cpp new file mode 100644 index 0000000..7917620 --- /dev/null +++ b/meta/source/net/message/control/SetChannelDirectMsgEx.cpp @@ -0,0 +1,21 @@ +#include +#include "SetChannelDirectMsgEx.h" + +bool SetChannelDirectMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "SetChannelDirect incoming"; + + LOG_DEBUG(logContext, 5, std::string("Value: ") + StringTk::intToStr(getValue() ) ); + #endif // BEEGFS_DEBUG + + ctx.getSocket()->setIsDirect(getValue() ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_SETCHANNELDIRECT, + getMsgHeaderUserID() ); + + return true; +} + + diff --git a/meta/source/net/message/control/SetChannelDirectMsgEx.h b/meta/source/net/message/control/SetChannelDirectMsgEx.h new file mode 100644 index 0000000..80d707a --- /dev/null +++ b/meta/source/net/message/control/SetChannelDirectMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class SetChannelDirectMsgEx : public SetChannelDirectMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.cpp b/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.cpp new file mode 100644 index 0000000..42f0b91 --- /dev/null +++ b/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.cpp @@ -0,0 +1,213 @@ +#include "AdjustChunkPermissionsMsgEx.h" + +#include +#include +#include +#include +#include + +bool AdjustChunkPermissionsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Incoming AdjustChunkPermissionsMsg"); + + MetaStore *metaStore = Program::getApp()->getMetaStore(); + + unsigned hashDirNum = this->getHashDirNum(); + unsigned maxEntries = this->getMaxEntries(); + int64_t lastHashDirOffset = this->getLastHashDirOffset(); + int64_t lastContDirOffset = this->getLastContDirOffset(); + std::string currentContDirID = this->getCurrentContDirID(); + int64_t newHashDirOffset = 0; + int64_t newContDirOffset = 0; + unsigned errorCount = 0; + + unsigned readOutEntries = 0; + + bool hasNext; + + if ( currentContDirID.empty() ) + { + hasNext = StorageTkEx::getNextContDirID(hashDirNum, getIsBuddyMirrored(), lastHashDirOffset, + ¤tContDirID, &newHashDirOffset); + if ( hasNext ) + { + lastHashDirOffset = newHashDirOffset; + } + } + else + hasNext = true; + + while ( hasNext ) + { + std::string parentID = currentContDirID; + + unsigned remainingEntries = maxEntries - readOutEntries; + StringList entryNames; + + bool parentDirInodeIsTemp = false; + + FileIDLock dirLock; + + if (getIsBuddyMirrored()) + dirLock = {Program::getApp()->getMirroredSessions()->getEntryLockStore(), parentID, false}; + + DirInode* parentDirInode = metaStore->referenceDir(parentID, getIsBuddyMirrored(), true); + + // it could be, that parentDirInode does not exist + // in fsck we create a temporary inode for this case + if ( unlikely(!parentDirInode) ) + { + log.log( + Log_NOTICE, + "Could not reference directory. EntryID: " + parentID + + " => using temporary directory inode "); + + // create temporary inode + int mode = S_IFDIR | S_IRWXU; + UInt16Vector stripeTargets; + Raid0Pattern stripePattern(0, stripeTargets, 0); + parentDirInode = new DirInode(parentID, mode, 0, 0, + Program::getApp()->getLocalNode().getNumID(), stripePattern, getIsBuddyMirrored()); + + parentDirInodeIsTemp = true; + } + + if ( parentDirInode->listIncremental(lastContDirOffset, remainingEntries, &entryNames, + &newContDirOffset) == FhgfsOpsErr_SUCCESS ) + { + lastContDirOffset = newContDirOffset; + readOutEntries += entryNames.size(); + } + else + { + log.log(Log_WARNING, "Could not list contents of directory. EntryID: " + parentID); + } + + // actually process the entries; for the dentry part we only need to know if it is a file + // with inlined inode data + for ( StringListIter namesIter = entryNames.begin(); namesIter != entryNames.end(); + namesIter++ ) + { + std::string filename = MetaStorageTk::getMetaDirEntryPath( + getIsBuddyMirrored() + ? Program::getApp()->getBuddyMirrorDentriesPath()->str() + : Program::getApp()->getDentriesPath()->str(), parentID) + "/" + *namesIter; + + EntryInfo entryInfo; + FileInodeStoreData inodeDiskData; + + auto [getEntryRes, isFileOpen] = metaStore->getEntryData(parentDirInode, *namesIter, &entryInfo, + &inodeDiskData); + inodeDiskData.setDynamicOrigParentEntryID(parentID); + + if (getEntryRes == FhgfsOpsErr_SUCCESS || + getEntryRes == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED ) + { + DirEntryType entryType = entryInfo.getEntryType(); + + // we only care if inode data is present + if ( (DirEntryType_ISFILE(entryType)) && (entryInfo.getIsInlined() ) ) + { + const std::string& inodeID = inodeDiskData.getEntryID(); + unsigned userID = inodeDiskData.getInodeStatData()->getUserID(); + unsigned groupID = inodeDiskData.getInodeStatData()->getGroupID(); + StripePattern* pattern = inodeDiskData.getStripePattern(); + + PathInfo pathInfo; + inodeDiskData.getPathInfo(&pathInfo); + + if ( !this->sendSetAttrMsg(inodeID, userID, groupID, &pathInfo, pattern) ) + errorCount++; + } + } + else + { + log.log(Log_WARNING, "Unable to create dir entry from entry with name " + *namesIter + + " in directory with ID " + parentID); + } + } + + if ( parentDirInodeIsTemp ) + SAFE_DELETE(parentDirInode); + else + metaStore->releaseDir(parentID); + + if ( entryNames.size() < remainingEntries ) + { + // directory is at the end => proceed with next + hasNext = StorageTkEx::getNextContDirID(hashDirNum, getIsBuddyMirrored(), + lastHashDirOffset, ¤tContDirID, &newHashDirOffset); + + if ( hasNext ) + { + lastHashDirOffset = newHashDirOffset; + lastContDirOffset = 0; + } + } + else + { + // there are more to come, but we need to exit the loop now, because maxCount is reached + hasNext = false; + } + } + + ctx.sendResponse( + AdjustChunkPermissionsRespMsg(readOutEntries, currentContDirID, lastHashDirOffset, + lastContDirOffset, errorCount) ); + + return true; +} + +bool AdjustChunkPermissionsMsgEx::sendSetAttrMsg(const std::string& entryID, unsigned userID, + unsigned groupID, PathInfo* pathInfo, StripePattern* pattern) +{ + const char* logContext = "AdjustChunkPermissionsMsgEx::sendSetAttrMsg"; + + MultiWorkQueue* slaveQueue = Program::getApp()->getCommSlaveQueue(); + + int validAttribs = SETATTR_CHANGE_USERID | SETATTR_CHANGE_GROUPID; // only interested in these + SettableFileAttribs attribs; + attribs.userID = userID; + attribs.groupID = groupID; + + const UInt16Vector* stripeTargets = pattern->getStripeTargetIDs(); + size_t numTargetWorks = stripeTargets->size(); + + FhgfsOpsErrVec nodeResults(numTargetWorks); + SynchronizedCounter counter; + + // generate work for storage targets... + + for(size_t i=0; i < numTargetWorks; i++) + { + SetChunkFileAttribsWork* work = new SetChunkFileAttribsWork( + entryID, validAttribs, &attribs, false, pattern, (*stripeTargets)[i], pathInfo, + NULL, &(nodeResults[i]), &counter); + + work->setQuotaChown(true); + work->setMsgUserID(getMsgHeaderUserID() ); + + slaveQueue->addDirectWork(work); + } + + // wait for work completion... + + counter.waitForCount(numTargetWorks); + + // check target results... + + for(size_t i=0; i < numTargetWorks; i++) + { + if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during setting of chunk file attribs. " + "fileID: " + entryID ); + + return false; + } + } + + + return true; +} diff --git a/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.h b/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.h new file mode 100644 index 0000000..fa2f500 --- /dev/null +++ b/meta/source/net/message/fsck/AdjustChunkPermissionsMsgEx.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include +#include + +class AdjustChunkPermissionsMsgEx : public AdjustChunkPermissionsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + bool sendSetAttrMsg(const std::string& entryID, unsigned userID, unsigned groupID, + PathInfo* pathInfo, StripePattern* pattern); +}; + diff --git a/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.cpp b/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.cpp new file mode 100644 index 0000000..283fecf --- /dev/null +++ b/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.cpp @@ -0,0 +1,38 @@ +#include +#include + +#include "CheckAndRepairDupInodeMsgEx.h" + +bool CheckAndRepairDupInodeMsgEx::processIncoming(ResponseContext& ctx) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + StringList failedIDList; + + for (const auto& inode : getDuplicateInodes()) + { + const std::string& entryID = inode.getID(); + const std::string& parentEntryID = inode.getParentDirID(); + const bool isBuddyMirrored = inode.getIsBuddyMirrored(); + + FileIDLock dirLock = {entryLockStore, parentEntryID, true}; + FileIDLock fileLock = {entryLockStore, entryID, true}; + + EntryInfo fileInfo(NumNodeID(0), parentEntryID, entryID, std::string(""), DirEntryType_REGULARFILE, 0); + fileInfo.setBuddyMirroredFlag(isBuddyMirrored); + + DirInode* parentDir = metaStore->referenceDir(parentEntryID, isBuddyMirrored, true); + FhgfsOpsErr repairRes = metaStore->checkAndRepairDupFileInode(*parentDir, &fileInfo); + + if (repairRes != FhgfsOpsErr_SUCCESS) + { + failedIDList.push_back(entryID); + } + + metaStore->releaseDir(parentDir->getID()); + } + + ctx.sendResponse(CheckAndRepairDupInodeRespMsg(std::move(failedIDList))); + return true; +} diff --git a/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.h b/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.h new file mode 100644 index 0000000..d4569d3 --- /dev/null +++ b/meta/source/net/message/fsck/CheckAndRepairDupInodeMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class CheckAndRepairDupInodeMsgEx : public CheckAndRepairDupInodeMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.cpp b/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.cpp new file mode 100644 index 0000000..b874926 --- /dev/null +++ b/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.cpp @@ -0,0 +1,60 @@ +#include "CreateDefDirInodesMsgEx.h" +#include +#include + +bool CreateDefDirInodesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("CreateDefDirInodesMsg incoming"); + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + StringList failedInodeIDs; + FsckDirInodeList createdInodes; + + for (auto it = items.begin(); it != items.end(); ++it) + { + const std::string& inodeID = std::get<0>(*it); + const bool isBuddyMirrored = std::get<1>(*it); + int mode = S_IFDIR | S_IRWXU; + unsigned userID = 0; // root + unsigned groupID = 0; // root + const NumNodeID ownerNodeID = isBuddyMirrored + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID()) + : app->getLocalNode().getNumID(); + + UInt16Vector stripeTargets; + unsigned defaultChunkSize = cfg->getTuneDefaultChunkSize(); + unsigned defaultNumStripeTargets = cfg->getTuneDefaultNumStripeTargets(); + Raid0Pattern stripePattern(defaultChunkSize, stripeTargets, defaultNumStripeTargets); + + // we try to create a new directory inode, with default values + + FileIDLock dirLock; + + if (isBuddyMirrored) + dirLock = {Program::getApp()->getMirroredSessions()->getEntryLockStore(), inodeID, true}; + + DirInode dirInode(inodeID, mode, userID, groupID, ownerNodeID, stripePattern, + isBuddyMirrored); + if ( dirInode.storeAsReplacementFile(inodeID) == FhgfsOpsErr_SUCCESS ) + { + // try to refresh the metainfo (maybe a .cont directory was already present) + dirInode.refreshMetaInfo(); + + StatData statData; + dirInode.getStatData(statData); + + FsckDirInode fsckDirInode(inodeID, "", NumNodeID(), ownerNodeID, statData.getFileSize(), + statData.getNumHardlinks(), stripeTargets, FsckStripePatternType_RAID0, + ownerNodeID, isBuddyMirrored, true, false); + createdInodes.push_back(fsckDirInode); + } + else + failedInodeIDs.push_back(inodeID); + } + + ctx.sendResponse(CreateDefDirInodesRespMsg(&failedInodeIDs, &createdInodes) ); + + return true; +} diff --git a/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.h b/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.h new file mode 100644 index 0000000..876088f --- /dev/null +++ b/meta/source/net/message/fsck/CreateDefDirInodesMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class CreateDefDirInodesMsgEx : public CreateDefDirInodesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.cpp b/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.cpp new file mode 100644 index 0000000..d853df5 --- /dev/null +++ b/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.cpp @@ -0,0 +1,77 @@ +#include "CreateEmptyContDirsMsgEx.h" +#include +#include + +bool CreateEmptyContDirsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "CreateEmptyContDirsMsg incoming"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + StringList failedIDs; + + for (auto iter = items.begin(); iter != items.end(); iter++) + { + const std::string& dirID = std::get<0>(*iter); + const bool isBuddyMirrored = std::get<1>(*iter); + + std::string contentsDirStr = MetaStorageTk::getMetaDirEntryPath( + isBuddyMirrored + ? app->getBuddyMirrorDentriesPath()->str() + : app->getDentriesPath()->str(), dirID); + + // create contents directory + int mkRes = mkdir(contentsDirStr.c_str(), 0755); + + if ( mkRes != 0 ) + { // error + LOG(GENERAL, ERR, "Unable to create contents directory.", contentsDirStr, sysErr); + failedIDs.push_back(dirID); + continue; + } + + // create the dirEntryID directory, which allows access to inlined inodes via dirID access + std::string contentsDirIDStr = MetaStorageTk::getMetaDirEntryIDPath(contentsDirStr); + + int mkDirIDRes = mkdir(contentsDirIDStr.c_str(), 0755); + + if ( mkDirIDRes != 0 ) + { // error + LOG(GENERAL, ERR, "Unable to create dirEntryID directory.", contentsDirIDStr, sysErr); + failedIDs.push_back(dirID); + continue; + } + + FileIDLock lock; + + if (isBuddyMirrored) + lock = {Program::getApp()->getMirroredSessions()->getEntryLockStore(), dirID, true}; + + // update the dir attribs + + DirInode* dirInode = metaStore->referenceDir(dirID, isBuddyMirrored, true); + + if (!dirInode) + { + LOG(GENERAL, ERR, "Unable to reference directory.", dirID); + failedIDs.push_back(dirID); + continue; + } + + FhgfsOpsErr refreshRes = dirInode->refreshMetaInfo(); + + if (refreshRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_NOTICE, "Unable to refresh contents directory metadata: " + + contentsDirStr + ". " + "SysErr: " + System::getErrString()); + } + + metaStore->releaseDir(dirID); + } + + ctx.sendResponse(CreateEmptyContDirsRespMsg(&failedIDs) ); + + return true; + +} diff --git a/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.h b/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.h new file mode 100644 index 0000000..0dd017f --- /dev/null +++ b/meta/source/net/message/fsck/CreateEmptyContDirsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class CreateEmptyContDirsMsgEx : public CreateEmptyContDirsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.cpp b/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.cpp new file mode 100644 index 0000000..b390c26 --- /dev/null +++ b/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.cpp @@ -0,0 +1,66 @@ +#include "DeleteDirEntriesMsgEx.h" + +#include +#include +#include + +#include + +bool DeleteDirEntriesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("DeleteDirEntriesMsgEx"); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + FsckDirEntryList& entries = getEntries(); + FsckDirEntryList failedEntries; + + for ( FsckDirEntryListIter iter = entries.begin(); iter != entries.end(); iter++ ) + { + const std::string& parentID = iter->getParentDirID(); + const std::string& entryName = iter->getName(); + FsckDirEntryType dirEntryType = iter->getEntryType(); + + FileIDLock dirLock; + ParentNameLock dentryLock; + + if (iter->getIsBuddyMirrored()) + { + dirLock = {entryLockStore, parentID, true}; + dentryLock = {entryLockStore, parentID, entryName}; + } + + DirInode* parentDirInode = metaStore->referenceDir(parentID, iter->getIsBuddyMirrored(), + true); + + if (!parentDirInode) + { + log.log(3,"Failed to delete directory entry; ParentID: " + parentID + "; EntryName: " + + entryName + " - ParentID does not exist"); + failedEntries.push_back(*iter); + continue; + } + + FhgfsOpsErr unlinkRes; + + if (FsckDirEntryType_ISDIR(dirEntryType)) + unlinkRes = parentDirInode->removeDir(entryName, NULL); + else + unlinkRes = parentDirInode->unlinkDirEntry(entryName, NULL, + DirEntry_UNLINK_ID_AND_FILENAME); + + metaStore->releaseDir(parentID); + + if (unlinkRes != FhgfsOpsErr_SUCCESS ) + { + log.logErr("Failed to delete directory entry; ParentID: " + parentID + "; EntryName: " + + entryName + "; Err: " + boost::lexical_cast(unlinkRes)); + failedEntries.push_back(*iter); + } + } + + ctx.sendResponse(DeleteDirEntriesRespMsg(&failedEntries) ); + + return true; +} diff --git a/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.h b/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.h new file mode 100644 index 0000000..fa85982 --- /dev/null +++ b/meta/source/net/message/fsck/DeleteDirEntriesMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class DeleteDirEntriesMsgEx : public DeleteDirEntriesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.cpp b/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.cpp new file mode 100644 index 0000000..97f8c0c --- /dev/null +++ b/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.cpp @@ -0,0 +1,77 @@ +#include "FixInodeOwnersInDentryMsgEx.h" + +#include +#include +#include +#include + +bool FixInodeOwnersInDentryMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("FixInodeOwnersInDentryMsgEx"); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + FsckDirEntryList& dentries = getDentries(); + FsckDirEntryList failedEntries; + + FsckDirEntryListIter dentryIter = dentries.begin(); + NumNodeIDListIter ownerIter = getOwners().begin(); + + while (dentryIter != dentries.end() ) + { + const std::string& parentID = dentryIter->getParentDirID(); + const std::string& entryName = dentryIter->getName(); + + ParentNameLock lock; + + if (dentryIter->getIsBuddyMirrored()) + lock = {entryLockStore, parentID, entryName}; + + bool parentDirInodeIsTemp = false; + DirInode* parentDirInode = metaStore->referenceDir(parentID, + dentryIter->getIsBuddyMirrored(), true); + + // it could be, that parentDirInode does not exist + // in fsck we create a temporary inode for this case, so that we can modify the dentry + // hopefully, the inode itself will get fixed later + if (unlikely(!parentDirInode)) + { + log.log(Log_NOTICE, + "Failed to update directory entry. Parent directory could not be " + "referenced. parentID: " + parentID + " entryName: " + entryName + + " => Using temporary inode"); + + // create temporary inode + int mode = S_IFDIR | S_IRWXU; + UInt16Vector stripeTargets; + Raid0Pattern stripePattern(0, stripeTargets, 0); + parentDirInode = new DirInode(parentID, mode, 0, 0, + Program::getApp()->getLocalNode().getNumID(), stripePattern, + dentryIter->getIsBuddyMirrored()); + + parentDirInodeIsTemp = true; + } + + FhgfsOpsErr updateRes = parentDirInode->setOwnerNodeID(entryName, *ownerIter); + + if (updateRes != FhgfsOpsErr_SUCCESS ) + { + log.log(Log_WARNING, "Failed to update directory entry. parentID: " + parentID + + " entryName: " + entryName); + failedEntries.push_back(*dentryIter); + } + + if (parentDirInodeIsTemp) + SAFE_DELETE(parentDirInode); + else + metaStore->releaseDir(parentID); + + dentryIter++; + ownerIter++; + } + + ctx.sendResponse(FixInodeOwnersInDentryRespMsg(&failedEntries) ); + + return true; +} diff --git a/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.h b/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.h new file mode 100644 index 0000000..ff1d364 --- /dev/null +++ b/meta/source/net/message/fsck/FixInodeOwnersInDentryMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class FixInodeOwnersInDentryMsgEx : public FixInodeOwnersInDentryMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/FixInodeOwnersMsgEx.cpp b/meta/source/net/message/fsck/FixInodeOwnersMsgEx.cpp new file mode 100644 index 0000000..9d270d7 --- /dev/null +++ b/meta/source/net/message/fsck/FixInodeOwnersMsgEx.cpp @@ -0,0 +1,51 @@ +#include "FixInodeOwnersMsgEx.h" + +#include +#include +#include + +bool FixInodeOwnersMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "FixInodeOwnersMsgEx incoming"; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + FsckDirInodeList& inodes = getInodes(); + FsckDirInodeList failedInodes; + + for ( FsckDirInodeListIter iter = inodes.begin(); iter != inodes.end(); iter++ ) + { + const std::string& entryID = iter->getID(); + NumNodeID ownerNodeID = iter->getOwnerNodeID(); + + FileIDLock lock; + + if (iter->getIsBuddyMirrored()) + lock = {entryLockStore, entryID, true}; + + DirInode* dirInode = metaStore->referenceDir(entryID, iter->getIsBuddyMirrored(), true); + + if (unlikely(!dirInode)) + { + LogContext(logContext).log(Log_WARNING, "Failed to update directory inode. Inode could" + " not be referenced. entryID: " + entryID); + continue; // continue to next entry + } + + bool updateRes = dirInode->setOwnerNodeID(ownerNodeID); + + metaStore->releaseDir(entryID); + + if (!updateRes) + { + LogContext(logContext).log(Log_WARNING, "Failed to update directory inode. entryID: " + + entryID); + failedInodes.push_back(*iter); + } + } + + ctx.sendResponse(FixInodeOwnersRespMsg(&failedInodes) ); + + return true; +} diff --git a/meta/source/net/message/fsck/FixInodeOwnersMsgEx.h b/meta/source/net/message/fsck/FixInodeOwnersMsgEx.h new file mode 100644 index 0000000..308d126 --- /dev/null +++ b/meta/source/net/message/fsck/FixInodeOwnersMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class FixInodeOwnersMsgEx : public FixInodeOwnersMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.cpp b/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.cpp new file mode 100644 index 0000000..5d15ada --- /dev/null +++ b/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.cpp @@ -0,0 +1,34 @@ +#include +#include +#include "FsckSetEventLoggingMsgEx.h" + +bool FsckSetEventLoggingMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("FsckSetEventLoggingMsg incoming"); + + App* app = Program::getApp(); + ModificationEventFlusher* flusher = app->getModificationEventFlusher(); + + bool result; + bool loggingEnabled; + bool missedEvents; + + bool enableLogging = this->getEnableLogging(); + + if (enableLogging) + { + loggingEnabled = flusher->enableLogging(getPortUDP(), getNicList(), getForceRestart()); + result = true; // (always true when logging is enabled) + missedEvents = true; // (value ignored when logging is enabled) + } + else + { // disable logging + result = flusher->disableLogging(); + loggingEnabled = false; // (value ignored when logging is disabled) + missedEvents = flusher->getFsckMissedEvent(); + } + + ctx.sendResponse(FsckSetEventLoggingRespMsg(result, loggingEnabled, missedEvents)); + + return true; +} diff --git a/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.h b/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.h new file mode 100644 index 0000000..eebcd70 --- /dev/null +++ b/meta/source/net/message/fsck/FsckSetEventLoggingMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class FsckSetEventLoggingMsgEx : public FsckSetEventLoggingMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.cpp b/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.cpp new file mode 100644 index 0000000..dae7219 --- /dev/null +++ b/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.cpp @@ -0,0 +1,108 @@ +#include "LinkToLostAndFoundMsgEx.h" + +#include +#include +#include +#include + +bool LinkToLostAndFoundMsgEx::processIncoming(ResponseContext& ctx) +{ + if (FsckDirEntryType_ISDIR(this->getEntryType())) + { + FsckDirEntryList createdDirEntries; + FsckDirInodeList failedInodes; + linkDirInodes(&failedInodes, &createdDirEntries); + ctx.sendResponse(LinkToLostAndFoundRespMsg(&failedInodes, &createdDirEntries) ); + } + else + { + LOG(COMMUNICATION, ERR, "LinkToLostAndFoundMsg received for non-inlined file inode.", + ("from", ctx.peerName())); + return false; + } + + return true; +} + +void LinkToLostAndFoundMsgEx::linkDirInodes(FsckDirInodeList* outFailedInodes, + FsckDirEntryList* outCreatedDirEntries) +{ + const char* logContext = "LinkToLostAndFoundMsgEx (linkDirInodes)"; + + NumNodeID localNodeNumID = Program::getApp()->getLocalNode().getNumID(); + + FsckDirInodeList& dirInodes = getDirInodes(); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + EntryInfo* lostAndFoundInfo = this->getLostAndFoundInfo(); + DirInode* lostAndFoundDir = metaStore->referenceDir(lostAndFoundInfo->getEntryID(), + lostAndFoundInfo->getIsBuddyMirrored(), true); + + if ( !lostAndFoundDir ) + { + *outFailedInodes = dirInodes; + return; + } + else + { + for ( FsckDirInodeListIter iter = dirInodes.begin(); iter != dirInodes.end(); iter++ ) + { + const std::string& entryID = iter->getID(); + NumNodeID ownerNodeID = iter->getOwnerNodeID(); + DirEntryType entryType = DirEntryType_DIRECTORY; + DirEntry newDirEntry(entryType, entryID, entryID, ownerNodeID); + + FileIDLock lock; + + if (iter->getIsBuddyMirrored()) + { + lock = {entryLockStore, entryID, true}; + newDirEntry.setBuddyMirrorFeatureFlag(); + } + + bool makeRes = lostAndFoundDir->makeDirEntry(newDirEntry); + + // stat the new file to get device and inode information + std::string filename = MetaStorageTk::getMetaDirEntryPath( + lostAndFoundInfo->getIsBuddyMirrored() + ? Program::getApp()->getBuddyMirrorDentriesPath()->str() + : Program::getApp()->getDentriesPath()->str(), + lostAndFoundInfo->getEntryID()) + "/" + entryID; + + struct stat statBuf; + + int statRes = stat(filename.c_str(), &statBuf); + + int saveDevice; + uint64_t saveInode; + if ( likely(!statRes) ) + { + saveDevice = statBuf.st_dev; + saveInode = statBuf.st_ino; + } + else + { + saveDevice = 0; + saveInode = 0; + LogContext(logContext).log(Log_CRITICAL, + "Could not stat dir entry file; entryID: " + entryID + ";filename: " + filename); + } + + if ( makeRes != FhgfsOpsErr_SUCCESS ) + outFailedInodes->push_back(*iter); + else + { + std::string parentID = lostAndFoundInfo->getEntryID(); + FsckDirEntry newFsckDirEntry(entryID, entryID, parentID, localNodeNumID, + ownerNodeID, FsckDirEntryType_DIRECTORY, false, localNodeNumID, + saveDevice, saveInode, lostAndFoundInfo->getIsBuddyMirrored()); + outCreatedDirEntries->push_back(newFsckDirEntry); + } + } + + lostAndFoundDir->refreshMetaInfo(); + metaStore->releaseDir(lostAndFoundInfo->getEntryID() ); + } +} diff --git a/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.h b/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.h new file mode 100644 index 0000000..d12560b --- /dev/null +++ b/meta/source/net/message/fsck/LinkToLostAndFoundMsgEx.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + +class LinkToLostAndFoundMsgEx : public LinkToLostAndFoundMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + void linkDirInodes(FsckDirInodeList* outFailedInodes, FsckDirEntryList* outCreatedDirEntries); + void linkFileInodes(FsckFileInodeList* outFailedInodes, + FsckDirEntryList* outCreatedDirEntries); + + FhgfsOpsErr deleteInode(std::string& entryID, uint16_t ownerNodeID); +}; + diff --git a/meta/source/net/message/fsck/RecreateDentriesMsgEx.cpp b/meta/source/net/message/fsck/RecreateDentriesMsgEx.cpp new file mode 100644 index 0000000..1d21965 --- /dev/null +++ b/meta/source/net/message/fsck/RecreateDentriesMsgEx.cpp @@ -0,0 +1,141 @@ +#include "RecreateDentriesMsgEx.h" + +#include +#include +#include +#include + +bool RecreateDentriesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("RecreateDentriesMsgEx"); + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + EntryLockStore* entryLockStore = app->getMirroredSessions()->getEntryLockStore(); + + FsckFsIDList& fsIDs = getFsIDs(); + FsckFsIDList failedCreates; + FsckDirEntryList createdDentries; + FsckFileInodeList createdInodes; + + for ( FsckFsIDListIter iter = fsIDs.begin(); iter != fsIDs.end(); iter++ ) + { + NumNodeID localNodeID = iter->getIsBuddyMirrored() + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID()) + : app->getLocalNodeNumID(); + + std::string parentPath = MetaStorageTk::getMetaDirEntryPath( + iter->getIsBuddyMirrored() + ? app->getBuddyMirrorDentriesPath()->str() + : app->getDentriesPath()->str(), iter->getParentDirID()); + + std::string dirEntryIDFilePath = MetaStorageTk::getMetaDirEntryIDPath(parentPath) + "/" + + iter->getID(); + + // the name is lost, so we take the ID as new name + std::string dirEntryNameFilePath = parentPath + "/" + iter->getID(); + + // before we link, let's see if we can open the parent dir, otherwise we should not mess + // around here + const std::string& dirID = iter->getParentDirID(); + + FileIDLock dirLock; + ParentNameLock dentryLock; + + if (iter->getIsBuddyMirrored()) + { + dirLock = {entryLockStore, dirID, true}; + dentryLock = {entryLockStore, dirID, iter->getID()}; + } + + DirInode* parentDirInode = metaStore->referenceDir(dirID, iter->getIsBuddyMirrored(), false); + + if (!parentDirInode) + { + log.logErr("Unable to reference parent directory; ID: " + iter->getParentDirID()); + failedCreates.push_back(*iter); + continue; + } + + // link the dentry-by-name file + int linkRes = link(dirEntryIDFilePath.c_str(), dirEntryNameFilePath.c_str()); + + if ( linkRes ) + { + // error occured while linking + log.logErr( + "Failed to link dentry file; ParentID: " + iter->getParentDirID() + "; ID: " + + iter->getID()); + failedCreates.push_back(*iter); + + metaStore->releaseDir(dirID); + continue; + } + + // linking was OK => gather dentry (and inode) data, so fsck can add it + + DirEntry dirEntry(iter->getID()); + bool getRes = parentDirInode->getDentry(iter->getID(), dirEntry); + + if (!getRes) + { + log.logErr( + "Could not read the created dentry file; ParentID: " + iter->getParentDirID() + "; ID: " + + iter->getID()); + failedCreates.push_back(*iter); + + metaStore->releaseDir(dirID); + continue; + } + + // create the FsckDirEntry + FsckDirEntry fsckDirEntry(dirEntry.getID(), dirEntry.getName(), iter->getParentDirID(), + localNodeID, localNodeID, + FsckTk::DirEntryTypeToFsckDirEntryType(dirEntry.getEntryType()), true, localNodeID, + iter->getSaveDevice(), iter->getSaveInode(), iter->getIsBuddyMirrored()); + createdDentries.push_back(fsckDirEntry); + + // inlined inode data should be present, because otherwise dentry-by-id file would not + // exist, and we could not get this far + FileInodeStoreData* inodeData = dirEntry.getInodeStoreData(); + + if ( inodeData ) + { + int pathInfoFlags; + + if (inodeData->getOrigFeature() == FileInodeOrigFeature_TRUE) + pathInfoFlags = PATHINFO_FEATURE_ORIG; + else + pathInfoFlags = PATHINFO_FEATURE_ORIG_UNKNOWN; + + PathInfo pathInfo(inodeData->getOrigUID(), inodeData->getOrigParentEntryID(), + pathInfoFlags); + + UInt16Vector targetIDs; + unsigned chunkSize; + FsckStripePatternType fsckStripePatternType = FsckTk::stripePatternToFsckStripePattern( + inodeData->getPattern(), &chunkSize, &targetIDs); + + FsckFileInode fsckFileInode(inodeData->getEntryID(), iter->getParentDirID(), + localNodeID, pathInfo, inodeData->getInodeStatData(), + inodeData->getInodeStatData()->getNumBlocks(), targetIDs, fsckStripePatternType, + chunkSize, localNodeID, iter->getSaveInode(), iter->getSaveDevice(), true, + iter->getIsBuddyMirrored(), true, false); + + createdInodes.push_back(fsckFileInode); + } + else + { + log.logErr( + "No inlined inode data found; parentID: " + iter->getParentDirID() + "; ID: " + + iter->getID()); + } + + metaStore->releaseDir(dirID); + } + + ctx.sendResponse(RecreateDentriesRespMsg(&failedCreates, &createdDentries, &createdInodes) ); + + return true; +} diff --git a/meta/source/net/message/fsck/RecreateDentriesMsgEx.h b/meta/source/net/message/fsck/RecreateDentriesMsgEx.h new file mode 100644 index 0000000..744254f --- /dev/null +++ b/meta/source/net/message/fsck/RecreateDentriesMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class RecreateDentriesMsgEx : public RecreateDentriesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/RecreateFsIDsMsgEx.cpp b/meta/source/net/message/fsck/RecreateFsIDsMsgEx.cpp new file mode 100644 index 0000000..7f64bf6 --- /dev/null +++ b/meta/source/net/message/fsck/RecreateFsIDsMsgEx.cpp @@ -0,0 +1,65 @@ +#include "RecreateFsIDsMsgEx.h" + +#include +#include +#include + +bool RecreateFsIDsMsgEx::processIncoming(ResponseContext& ctx) +{ + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + LogContext log("RecreateFsIDsMsgEx"); + + FsckDirEntryList& entries = getEntries(); + FsckDirEntryList failedEntries; + + for ( FsckDirEntryListIter iter = entries.begin(); iter != entries.end(); iter++ ) + { + const std::string& parentID = iter->getParentDirID(); + const std::string& entryName = iter->getName(); + const std::string& entryID = iter->getID(); + + std::string dirEntryPath = MetaStorageTk::getMetaDirEntryPath( + iter->getIsBuddyMirrored() + ? Program::getApp()->getBuddyMirrorDentriesPath()->str() + : Program::getApp()->getDentriesPath()->str(), parentID); + + std::string dirEntryIDFilePath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + + "/" + entryID; + + std::string dirEntryNameFilePath = dirEntryPath + "/" + entryName; + + FileIDLock dirLock(entryLockStore, parentID, true); + ParentNameLock dentryLock(entryLockStore, parentID, entryName); + FileIDLock fileLock(entryLockStore, entryID, true); + + // delete the old dentry-by-id file link (if one existed) + int removeRes = unlink(dirEntryIDFilePath.c_str()); + + if ( (removeRes) && (errno != ENOENT) ) + { + log.logErr( + "Failed to recreate dentry-by-id file for directory entry; ParentID: " + parentID + + "; EntryName: " + entryName + + " - Could not delete old, faulty dentry-by-id file link"); + failedEntries.push_back(*iter); + continue; + } + + // link a new one + int linkRes = link(dirEntryNameFilePath.c_str(), dirEntryIDFilePath.c_str()); + + if ( linkRes ) + { + log.logErr( + "Failed to recreate dentry-by-id file for directory entry; ParentID: " + parentID + + "; EntryName: " + entryName + " - File could not be linked"); + failedEntries.push_back(*iter); + continue; + } + } + + ctx.sendResponse(RecreateFsIDsRespMsg(&failedEntries) ); + + return true; +} diff --git a/meta/source/net/message/fsck/RecreateFsIDsMsgEx.h b/meta/source/net/message/fsck/RecreateFsIDsMsgEx.h new file mode 100644 index 0000000..ea2f446 --- /dev/null +++ b/meta/source/net/message/fsck/RecreateFsIDsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class RecreateFsIDsMsgEx : public RecreateFsIDsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/RemoveInodesMsgEx.cpp b/meta/source/net/message/fsck/RemoveInodesMsgEx.cpp new file mode 100644 index 0000000..b17ef5a --- /dev/null +++ b/meta/source/net/message/fsck/RemoveInodesMsgEx.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include + +#include "RemoveInodesMsgEx.h" + + +bool RemoveInodesMsgEx::processIncoming(ResponseContext& ctx) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + StringList failedIDList; + + for (auto it = items.begin(); it != items.end(); ++it) + { + const std::string& entryID = std::get<0>(*it); + const DirEntryType entryType = std::get<1>(*it); + const bool isBuddyMirrored = std::get<2>(*it); + FhgfsOpsErr rmRes; + + FileIDLock dirLock; + FileIDLock fileLock; + + if (entryType == DirEntryType_DIRECTORY) + { + dirLock = {entryLockStore, entryID, true}; + rmRes = metaStore->removeDirInode(entryID, isBuddyMirrored); + } + else + { + fileLock = {entryLockStore, entryID, true}; + rmRes = metaStore->fsckUnlinkFileInode(entryID, isBuddyMirrored); + } + + if (rmRes != FhgfsOpsErr_SUCCESS) + failedIDList.push_back(entryID); + } + + ctx.sendResponse(RemoveInodesRespMsg(std::move(failedIDList))); + + return true; +} diff --git a/meta/source/net/message/fsck/RemoveInodesMsgEx.h b/meta/source/net/message/fsck/RemoveInodesMsgEx.h new file mode 100644 index 0000000..7351fea --- /dev/null +++ b/meta/source/net/message/fsck/RemoveInodesMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RemoveInodesMsgEx : public RemoveInodesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.cpp b/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.cpp new file mode 100644 index 0000000..3906f76 --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.cpp @@ -0,0 +1,269 @@ +#include "RetrieveDirEntriesMsgEx.h" + +#include +#include +#include + +bool RetrieveDirEntriesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Incoming RetrieveDirEntriesMsg"); + + unsigned hashDirNum = getHashDirNum(); + std::string currentContDirID = getCurrentContDirID(); + unsigned maxOutEntries = getMaxOutEntries(); + + int64_t lastContDirOffset = getLastContDirOffset(); + int64_t lastHashDirOffset = getLastHashDirOffset(); + int64_t newHashDirOffset; + int64_t newContDirOffset; + + FsckContDirList contDirsOutgoing; + FsckDirEntryList dirEntriesOutgoing; + FsckFileInodeList inlinedFileInodesOutgoing; + + unsigned readOutEntries = 0; + + NumNodeID localNodeNumID = getIsBuddyMirrored() + ? NumNodeID(Program::getApp()->getMetaBuddyGroupMapper()->getLocalGroupID()) + : Program::getApp()->getLocalNode().getNumID(); + MetaStore* metaStore = Program::getApp()->getMetaStore(); + MirrorBuddyGroupMapper* bgm = Program::getApp()->getMetaBuddyGroupMapper(); + + if (getIsBuddyMirrored() && + (bgm->getLocalBuddyGroup().secondTargetID + == Program::getApp()->getLocalNode().getNumID().val() + || bgm->getLocalGroupID() == 0)) + { + ctx.sendResponse( + RetrieveDirEntriesRespMsg(&contDirsOutgoing, &dirEntriesOutgoing, + &inlinedFileInodesOutgoing, currentContDirID, lastHashDirOffset, lastContDirOffset)); + return true; + } + + bool hasNext; + + if ( currentContDirID.empty() ) + { + hasNext = StorageTkEx::getNextContDirID(hashDirNum, getIsBuddyMirrored(), lastHashDirOffset, + ¤tContDirID, &newHashDirOffset); + if ( hasNext ) + { + lastHashDirOffset = newHashDirOffset; + // we found a new .cont directory => send it to fsck + FsckContDir contDir(currentContDirID, localNodeNumID, getIsBuddyMirrored()); + contDirsOutgoing.push_back(contDir); + } + } + else + hasNext = true; + + while ( hasNext ) + { + std::string parentID = currentContDirID; + + unsigned remainingOutNames = maxOutEntries - readOutEntries; + StringList entryNames; + + bool parentDirInodeIsTemp = false; + DirInode* parentDirInode = metaStore->referenceDir(parentID, getIsBuddyMirrored(), true); + + // it could be, that parentDirInode does not exist + // in fsck we create a temporary inode for this case, so that we can modify the dentry + // hopefully, the inode itself will get fixed later + if ( unlikely(!parentDirInode) ) + { + log.log( + Log_NOTICE, + "Could not reference directory. EntryID: " + parentID + + " => using temporary directory inode "); + + // create temporary inode + int mode = S_IFDIR | S_IRWXU; + UInt16Vector stripeTargets; + Raid0Pattern stripePattern(0, stripeTargets, 0); + parentDirInode = new DirInode(parentID, mode, 0, 0, + Program::getApp()->getLocalNode().getNumID(), stripePattern, getIsBuddyMirrored()); + + parentDirInodeIsTemp = true; + } + + if ( parentDirInode->listIncremental(lastContDirOffset, remainingOutNames, &entryNames, + &newContDirOffset) == FhgfsOpsErr_SUCCESS ) + { + lastContDirOffset = newContDirOffset; + } + else + { + log.log(Log_WARNING, "Could not list contents of directory. EntryID: " + parentID); + } + + // actually process the entries + for ( StringListIter namesIter = entryNames.begin(); namesIter != entryNames.end(); + namesIter++ ) + { + std::string filename = MetaStorageTk::getMetaDirEntryPath( + getIsBuddyMirrored() + ? Program::getApp()->getBuddyMirrorDentriesPath()->str() + : Program::getApp()->getDentriesPath()->str(), parentID) + "/" + *namesIter; + + // create a EntryInfo and put the information into an FsckDirEntry object + EntryInfo entryInfo; + FileInodeStoreData inodeDiskData; + bool hasInlinedInode = false; + + int32_t saveDevice = 0; + uint64_t saveInode = 0; + + auto [getEntryRes, isFileOpen] = metaStore->getEntryData(parentDirInode, *namesIter, &entryInfo, + &inodeDiskData); + + if (getEntryRes == FhgfsOpsErr_SUCCESS || + getEntryRes == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED ) + { + DirEntryType entryType = entryInfo.getEntryType(); + + const std::string& dentryID = entryInfo.getEntryID(); + const std::string& dentryName = *namesIter; + NumNodeID dentryOwnerID = entryInfo.getOwnerNodeID(); + FsckDirEntryType fsckEntryType = FsckTk::DirEntryTypeToFsckDirEntryType(entryType); + + // stat the file to get device and inode information + struct stat statBuf; + + int statRes = stat(filename.c_str(), &statBuf); + + if (likely(!statRes)) + { + saveDevice = statBuf.st_dev; + saveInode = statBuf.st_ino; + } + else + { + log.log(Log_CRITICAL, "Could not stat dir entry file; entryID: " + dentryID + + ";filename: " + filename); + } + + if ( (DirEntryType_ISFILE(entryType)) && (entryInfo.getIsInlined() ) ) + { + hasInlinedInode = true; + } + + FsckDirEntry fsckDirEntry(dentryID, dentryName, parentID, localNodeNumID, + dentryOwnerID, fsckEntryType, hasInlinedInode, localNodeNumID, + saveDevice, saveInode, entryInfo.getIsBuddyMirrored()); + + dirEntriesOutgoing.push_back(fsckDirEntry); + } + else + { + log.log(Log_WARNING, "Unable to create dir entry from entry with name " + *namesIter + + " in directory with ID " + parentID); + } + + // now, if the inode data is inlined we create an fsck inode object here + if ( hasInlinedInode ) + { + std::string inodeID = inodeDiskData.getEntryID(); + + int pathInfoFlag; + if (inodeDiskData.getOrigFeature() == FileInodeOrigFeature_TRUE) + pathInfoFlag = PATHINFO_FEATURE_ORIG; + else + pathInfoFlag = PATHINFO_FEATURE_ORIG_UNKNOWN; + + unsigned origUID = inodeDiskData.getOrigUID(); + std::string origParentEntryID = inodeDiskData.getOrigParentEntryID(); + PathInfo pathInfo(origUID, origParentEntryID, pathInfoFlag); + + unsigned userID; + unsigned groupID; + + int64_t fileSize; + unsigned numHardLinks; + uint64_t numBlocks; + + StatData* statData; + StatData updatedStatData; + + if (getEntryRes == FhgfsOpsErr_SUCCESS) + statData = inodeDiskData.getInodeStatData(); + else + { + FhgfsOpsErr statRes = MsgHelperStat::stat(&entryInfo, true, getMsgHeaderUserID(), + updatedStatData); + + if (statRes == FhgfsOpsErr_SUCCESS) + statData = &updatedStatData; + else + statData = NULL; + } + + if ( statData ) + { + userID = statData->getUserID(); + groupID = statData->getGroupID(); + fileSize = statData->getFileSize(); + numHardLinks = statData->getNumHardlinks(); + numBlocks = statData->getNumBlocks(); + } + else + { + log.logErr(std::string("Unable to get stat data of inlined file inode: ") + inodeID + + ". SysErr: " + System::getErrString()); + userID = 0; + groupID = 0; + fileSize = 0; + numHardLinks = 0; + numBlocks = 0; + } + + UInt16Vector stripeTargets; + unsigned chunkSize; + FsckStripePatternType stripePatternType = FsckTk::stripePatternToFsckStripePattern( + inodeDiskData.getPattern(), &chunkSize, &stripeTargets); + + FsckFileInode fileInode(inodeID, parentID, localNodeNumID, pathInfo, userID, groupID, + fileSize, numHardLinks, numBlocks, stripeTargets, stripePatternType, chunkSize, + localNodeNumID, saveInode, saveDevice, true, entryInfo.getIsBuddyMirrored(), + true, inodeDiskData.getIsBuddyMirrored() != getIsBuddyMirrored()); + + inlinedFileInodesOutgoing.push_back(fileInode); + } + } + + if ( parentDirInodeIsTemp ) + SAFE_DELETE(parentDirInode); + else + metaStore->releaseDir(parentID); + + if ( entryNames.size() < remainingOutNames ) + { + // directory is at the end => proceed with next + hasNext = StorageTkEx::getNextContDirID(hashDirNum, getIsBuddyMirrored(), + lastHashDirOffset, ¤tContDirID, &newHashDirOffset); + + if ( hasNext ) + { + lastHashDirOffset = newHashDirOffset; + lastContDirOffset = 0; + + readOutEntries += entryNames.size(); + + // we found a new .cont directory => send it to fsck + FsckContDir contDir(currentContDirID, localNodeNumID, getIsBuddyMirrored()); + contDirsOutgoing.push_back(contDir); + } + } + else + { + // there are more to come, but we need to exit the loop now, because maxCount is reached + hasNext = false; + } + } + + ctx.sendResponse( + RetrieveDirEntriesRespMsg(&contDirsOutgoing, &dirEntriesOutgoing, + &inlinedFileInodesOutgoing, currentContDirID, lastHashDirOffset, lastContDirOffset) ); + + return true; +} diff --git a/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.h b/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.h new file mode 100644 index 0000000..7ca89c4 --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveDirEntriesMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class RetrieveDirEntriesMsgEx : public RetrieveDirEntriesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.cpp b/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.cpp new file mode 100644 index 0000000..a0c8976 --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.cpp @@ -0,0 +1,166 @@ +#include "RetrieveFsIDsMsgEx.h" + +#include +#include +#include + +bool RetrieveFsIDsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Incoming RetrieveFsIDsMsg"); + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + unsigned hashDirNum = getHashDirNum(); + bool buddyMirrored = getBuddyMirrored(); + std::string currentContDirID = getCurrentContDirID(); + unsigned maxOutIDs = getMaxOutIDs(); + + int64_t lastContDirOffset = getLastContDirOffset(); + int64_t lastHashDirOffset = getLastHashDirOffset(); + int64_t newHashDirOffset; + + FsckFsIDList fsIDsOutgoing; + + unsigned readOutIDs = 0; + + NumNodeID localNodeNumID = buddyMirrored + ? NumNodeID(Program::getApp()->getMetaBuddyGroupMapper()->getLocalGroupID()) + : Program::getApp()->getLocalNode().getNumID(); + MirrorBuddyGroupMapper* bgm = Program::getApp()->getMetaBuddyGroupMapper(); + + if (buddyMirrored && + (bgm->getLocalBuddyGroup().secondTargetID == app->getLocalNode().getNumID().val() + || bgm->getLocalGroupID() == 0)) + { + ctx.sendResponse( + RetrieveFsIDsRespMsg(&fsIDsOutgoing, currentContDirID, lastHashDirOffset, + lastContDirOffset)); + return true; + } + + bool hasNext; + + if ( currentContDirID.empty() ) + { + hasNext = StorageTkEx::getNextContDirID(hashDirNum, buddyMirrored, lastHashDirOffset, + ¤tContDirID, &newHashDirOffset); + if ( hasNext ) + lastHashDirOffset = newHashDirOffset; + } + else + hasNext = true; + + while ( hasNext ) + { + std::string parentID = currentContDirID; + std::string idPath = MetaStorageTk::getMetaDirEntryIDPath( + MetaStorageTk::getMetaDirEntryPath( + buddyMirrored + ? app->getBuddyMirrorDentriesPath()->str() + : app->getDentriesPath()->str(), + parentID)); + + bool parentDirInodeIsTemp = false; + StringList outNames; + int64_t outNewServerOffset; + ListIncExOutArgs outArgs(&outNames, NULL, NULL, NULL, &outNewServerOffset); + + FhgfsOpsErr listRes; + + unsigned remainingOutIDs = maxOutIDs - readOutIDs; + + DirInode* parentDirInode = metaStore->referenceDir(parentID, buddyMirrored, true); + + // it could be, that parentDirInode does not exist + // in fsck we create a temporary inode for this case, so that we can modify the dentry + // hopefully, the inode itself will get fixed later + if ( unlikely(!parentDirInode) ) + { + log.log( + Log_NOTICE, + "Could not reference directory. EntryID: " + parentID + + " => using temporary directory inode "); + + // create temporary inode + int mode = S_IFDIR | S_IRWXU; + UInt16Vector stripeTargets; + Raid0Pattern stripePattern(0, stripeTargets, 0); + parentDirInode = new DirInode(parentID, mode, 0, 0, + Program::getApp()->getLocalNode().getNumID(), stripePattern, buddyMirrored); + + parentDirInodeIsTemp = true; + } + + listRes = parentDirInode->listIDFilesIncremental(lastContDirOffset, 0, remainingOutIDs, + outArgs); + + lastContDirOffset = outNewServerOffset; + + if ( parentDirInodeIsTemp ) + SAFE_DELETE(parentDirInode); + else + metaStore->releaseDir(parentID); + + if (listRes != FhgfsOpsErr_SUCCESS) + { + log.logErr("Could not read dentry-by-ID files; parentID: " + parentID); + } + + // process entries + readOutIDs += outNames.size(); + + for ( StringListIter iter = outNames.begin(); iter != outNames.end(); iter++ ) + { + std::string id = *iter; + std::string filename = idPath + "/" + id; + + // stat the file to get device and inode information + struct stat statBuf; + + int statRes = stat(filename.c_str(), &statBuf); + + int saveDevice; + uint64_t saveInode; + if ( likely(!statRes) ) + { + saveDevice = statBuf.st_dev; + saveInode = statBuf.st_ino; + } + else + { + saveDevice = 0; + saveInode = 0; + log.log(Log_CRITICAL, + "Could not stat ID file; ID: " + id + ";filename: " + filename); + } + + FsckFsID fsID(id, parentID, localNodeNumID, saveDevice, saveInode, buddyMirrored); + fsIDsOutgoing.push_back(fsID); + } + + if ( readOutIDs < maxOutIDs ) + { + // directory is at the end => proceed with next + hasNext = StorageTkEx::getNextContDirID(hashDirNum, buddyMirrored, lastHashDirOffset, + ¤tContDirID, &newHashDirOffset); + + if ( hasNext ) + { + lastHashDirOffset = newHashDirOffset; + lastContDirOffset = 0; + } + } + else + { + // there are more to come, but we need to exit the loop now, because maxCount is reached + hasNext = false; + } + } + + ctx.sendResponse( + RetrieveFsIDsRespMsg(&fsIDsOutgoing, currentContDirID, lastHashDirOffset, + lastContDirOffset) ); + + return true; +} diff --git a/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.h b/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.h new file mode 100644 index 0000000..4d206a6 --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveFsIDsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class RetrieveFsIDsMsgEx : public RetrieveFsIDsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/RetrieveInodesMsgEx.cpp b/meta/source/net/message/fsck/RetrieveInodesMsgEx.cpp new file mode 100644 index 0000000..f1d24b5 --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveInodesMsgEx.cpp @@ -0,0 +1,25 @@ +#include "RetrieveInodesMsgEx.h" + +#include + +bool RetrieveInodesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Incoming RetrieveInodesMsg"); + + MetaStore *metaStore = Program::getApp()->getMetaStore(); + + unsigned hashDirNum = getHashDirNum(); + unsigned maxOutInodes = getMaxOutInodes(); + int64_t lastOffset = getLastOffset(); + int64_t newOffset; + + FsckFileInodeList fileInodesOutgoing; + FsckDirInodeList dirInodesOutgoing; + + metaStore->getAllInodesIncremental(hashDirNum, lastOffset, maxOutInodes, &dirInodesOutgoing, + &fileInodesOutgoing, &newOffset, getIsBuddyMirrored()); + + ctx.sendResponse(RetrieveInodesRespMsg(&fileInodesOutgoing, &dirInodesOutgoing, newOffset) ); + + return true; +} diff --git a/meta/source/net/message/fsck/RetrieveInodesMsgEx.h b/meta/source/net/message/fsck/RetrieveInodesMsgEx.h new file mode 100644 index 0000000..10ab54b --- /dev/null +++ b/meta/source/net/message/fsck/RetrieveInodesMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class RetrieveInodesMsgEx : public RetrieveInodesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.cpp b/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.cpp new file mode 100644 index 0000000..11769ac --- /dev/null +++ b/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.cpp @@ -0,0 +1,51 @@ +#include "UpdateDirAttribsMsgEx.h" + +#include +#include +#include + +bool UpdateDirAttribsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "UpdateDirAttribsMsg incoming"; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + FsckDirInodeList& inodes = getInodes(); + FsckDirInodeList failedInodes; + + for (FsckDirInodeListIter iter = inodes.begin(); iter != inodes.end(); iter++) + { + // call the updating method + const std::string& dirID = iter->getID(); + + FileIDLock lock; + + if (iter->getIsBuddyMirrored()) + lock = {entryLockStore, dirID, true}; + + DirInode* dirInode = metaStore->referenceDir(dirID, iter->getIsBuddyMirrored(), true); + + if (!dirInode) + { + LogContext(logContext).logErr("Unable to reference directory; ID: " + dirID); + failedInodes.push_back(*iter); + continue; + } + + FhgfsOpsErr refreshRes = dirInode->refreshMetaInfo(); + + metaStore->releaseDir(dirID); + + if (refreshRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, "Failed to update attributes of directory. " + "entryID: " + dirID); + failedInodes.push_back(*iter); + } + } + + ctx.sendResponse(UpdateDirAttribsRespMsg(&failedInodes) ); + + return true; +} diff --git a/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.h b/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.h new file mode 100644 index 0000000..86ac6b5 --- /dev/null +++ b/meta/source/net/message/fsck/UpdateDirAttribsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class UpdateDirAttribsMsgEx : public UpdateDirAttribsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.cpp b/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.cpp new file mode 100644 index 0000000..680d0c4 --- /dev/null +++ b/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.cpp @@ -0,0 +1,62 @@ +#include "UpdateFileAttribsMsgEx.h" + +#include +#include +#include + +bool UpdateFileAttribsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "UpdateFileAttribsMsg incoming"; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryLockStore* entryLockStore = Program::getApp()->getMirroredSessions()->getEntryLockStore(); + + FsckFileInodeList& inodes = getInodes(); + FsckFileInodeList failedInodes; + + for (FsckFileInodeListIter iter = inodes.begin(); iter != inodes.end(); iter++) + { + // create an EntryInfo object (NOTE: dummy fileName) + EntryInfo entryInfo(iter->getSaveNodeID(), iter->getParentDirID(), iter->getID(), "", + DirEntryType_REGULARFILE, + (iter->getIsBuddyMirrored() ? ENTRYINFO_FEATURE_BUDDYMIRRORED : 0) | + (iter->getIsInlined() ? ENTRYINFO_FEATURE_INLINED : 0)); + + FileIDLock lock; + + if (iter->getIsBuddyMirrored()) + lock = {entryLockStore, entryInfo.getEntryID(), true}; + + auto [inode, referenceRes] = metaStore->referenceFile(&entryInfo); + + if (inode) + { + inode->setNumHardlinksUnpersistent(iter->getNumHardLinks()); + inode->updateInodeOnDisk(&entryInfo); + + // call the dynamic attribs refresh method + FhgfsOpsErr refreshRes = MsgHelperStat::refreshDynAttribs(&entryInfo, true, + getMsgHeaderUserID() ); + if (refreshRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, "Failed to update dynamic attributes of file. " + "entryID: " + iter->getID()); + failedInodes.push_back(*iter); + } + + /* only release it here, as refreshDynAttribs() also takes an inode reference and can + * do the reference from in-memory data then */ + metaStore->releaseFile(entryInfo.getParentEntryID(), inode); + } + else + { + LogContext(logContext).log(Log_WARNING, "Could not reference inode to update attributes. " + "entryID: " + iter->getID()); + failedInodes.push_back(*iter); + } + } + + ctx.sendResponse(UpdateFileAttribsRespMsg(&failedInodes) ); + + return true; +} diff --git a/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.h b/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.h new file mode 100644 index 0000000..a9606f2 --- /dev/null +++ b/meta/source/net/message/fsck/UpdateFileAttribsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class UpdateFileAttribsMsgEx : public UpdateFileAttribsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/mon/RequestMetaDataMsgEx.cpp b/meta/source/net/message/mon/RequestMetaDataMsgEx.cpp new file mode 100644 index 0000000..3b8cae0 --- /dev/null +++ b/meta/source/net/message/mon/RequestMetaDataMsgEx.cpp @@ -0,0 +1,39 @@ +#include +#include +#include +#include "RequestMetaDataMsgEx.h" + +bool RequestMetaDataMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("RequestMetaDataMsg incoming"); + + App *app = Program::getApp(); + Node& node = app->getLocalNode(); + MultiWorkQueue *workQueue = app->getWorkQueue(); + + unsigned sessionCount = app->getSessions()->getSize() + app->getMirroredSessions()->getSize(); + + NicAddressList nicList(node.getNicList()); + std::string hostnameid = System::getHostname(); + + // highresStats + HighResStatsList statsHistory; + uint64_t lastStatsMS = getValue(); + + // get stats history + StatsCollector* statsCollector = app->getStatsCollector(); + statsCollector->getStatsSince(lastStatsMS, statsHistory); + + RequestMetaDataRespMsg requestMetaDataRespMsg(node.getAlias(), hostnameid, node.getNumID(), &nicList, + app->getMetaRoot().getID() == node.getNumID(), workQueue->getIndirectWorkListSize(), + workQueue->getDirectWorkListSize(), sessionCount, &statsHistory); + ctx.sendResponse(requestMetaDataRespMsg); + + LOG_DEBUG_CONTEXT(log, 5, std::string("Sent a message with type: " ) + + StringTk::uintToStr(requestMetaDataRespMsg.getMsgType() ) + std::string(" to mon") ); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_REQUESTMETADATA, + getMsgHeaderUserID() ); + + return true; +} diff --git a/meta/source/net/message/mon/RequestMetaDataMsgEx.h b/meta/source/net/message/mon/RequestMetaDataMsgEx.h new file mode 100644 index 0000000..d1cc32b --- /dev/null +++ b/meta/source/net/message/mon/RequestMetaDataMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include +#include +#include +#include + +class RequestMetaDataMsgEx : public RequestMetaDataMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/GenericDebugMsgEx.cpp b/meta/source/net/message/nodes/GenericDebugMsgEx.cpp new file mode 100644 index 0000000..ab95005 --- /dev/null +++ b/meta/source/net/message/nodes/GenericDebugMsgEx.cpp @@ -0,0 +1,900 @@ +#include +#include +#include +#include +#include +#include +#include "GenericDebugMsgEx.h" + + +#define GENDBGMSG_OP_LISTFILEAPPENDLOCKS "listfileappendlocks" +#define GENDBGMSG_OP_LISTFILEENTRYLOCKS "listfileentrylocks" +#define GENDBGMSG_OP_LISTFILERANGELOCKS "listfilerangelocks" +#define GENDBGMSG_OP_LISTOPENFILES "listopenfiles" +#define GENDBGMSG_OP_REFERENCESTATISTICS "refstats" +#define GENDBGMSG_OP_CACHESTATISTICS "cachestats" +#define GENDBGMSG_OP_VERSION "version" +#define GENDBGMSG_OP_MSGQUEUESTATS "msgqueuestats" +#define GENDBGMSG_OP_LISTPOOLS "listpools" +#define GENDBGMSG_OP_DUMPDENTRY "dumpdentry" +#define GENDBGMSG_OP_DUMPINODE "dumpinode" +#define GENDBGMSG_OP_DUMPINLINEDINODE "dumpinlinedinode" + +#ifdef BEEGFS_DEBUG + #define GENDBGMSG_OP_WRITEDIRDENTRY "writedirdentry" + #define GENDBGMSG_OP_WRITEDIRINODE "writedirinode" + #define GENDBGMSG_OP_WRITEFILEINODE "writefileinode" +#endif // BEEGFS_DEBUG + + +bool GenericDebugMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("GenericDebugMsg incoming"); + + LOG_DEBUG_CONTEXT(log, 5, std::string("Command string: ") + getCommandStr() ); + + std::string cmdRespStr = processCommand(); + + ctx.sendResponse(GenericDebugRespMsg(cmdRespStr.c_str() ) ); + + return true; +} + +/** + * @return command response string + */ +std::string GenericDebugMsgEx::processCommand() +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + std::string responseStr; + std::string operation; + + // load command string into a stream to allow us to use getline + std::istringstream commandStream(getCommandStr() ); + + // get operation type from command string + std::getline(commandStream, operation, ' '); + + if(operation == GENDBGMSG_OP_LISTFILEAPPENDLOCKS) + responseStr = processOpListFileAppendLocks(commandStream); + else + if(operation == GENDBGMSG_OP_LISTFILEENTRYLOCKS) + responseStr = processOpListFileEntryLocks(commandStream); + else + if(operation == GENDBGMSG_OP_LISTFILERANGELOCKS) + responseStr = processOpListFileRangeLocks(commandStream); + else + if(operation == GENDBGMSG_OP_LISTOPENFILES) + responseStr = processOpListOpenFiles(commandStream); + else + if(operation == GENDBGMSG_OP_REFERENCESTATISTICS) + responseStr = processOpReferenceStatistics(commandStream); + else + if(operation == GENDBGMSG_OP_CACHESTATISTICS) + responseStr = processOpCacheStatistics(commandStream); + else + if(operation == GENDBGMSG_OP_VERSION) + responseStr = processOpVersion(commandStream); + else + if(operation == GENDBGMSG_OP_MSGQUEUESTATS) + responseStr = processOpMsgQueueStats(commandStream); + else + if(operation == GENDBGMSG_OP_VARLOGMESSAGES) + responseStr = MsgHelperGenericDebug::processOpVarLogMessages(commandStream); + else + if(operation == GENDBGMSG_OP_VARLOGKERNLOG) + responseStr = MsgHelperGenericDebug::processOpVarLogKernLog(commandStream); + else + if(operation == GENDBGMSG_OP_FHGFSLOG) + responseStr = MsgHelperGenericDebug::processOpFhgfsLog(commandStream); + else + if(operation == GENDBGMSG_OP_LOADAVG) + responseStr = MsgHelperGenericDebug::processOpLoadAvg(commandStream); + else + if(operation == GENDBGMSG_OP_DROPCACHES) + responseStr = MsgHelperGenericDebug::processOpDropCaches(commandStream); + else + if(operation == GENDBGMSG_OP_GETCFG) + responseStr = MsgHelperGenericDebug::processOpCfgFile(commandStream, cfg->getCfgFile() ); + else + if(operation == GENDBGMSG_OP_GETLOGLEVEL) + responseStr = MsgHelperGenericDebug::processOpGetLogLevel(commandStream); + else + if(operation == GENDBGMSG_OP_SETLOGLEVEL) + responseStr = MsgHelperGenericDebug::processOpSetLogLevel(commandStream); + else + if(operation == GENDBGMSG_OP_NETOUT) + responseStr = MsgHelperGenericDebug::processOpNetOut(commandStream, + app->getMgmtNodes(), app->getMetaNodes(), app->getStorageNodes() ); + else + if(operation == GENDBGMSG_OP_LISTMETASTATES) + responseStr = MsgHelperGenericDebug::processOpListTargetStates(commandStream, + app->getMetaStateStore() ); + else + if(operation == GENDBGMSG_OP_LISTSTORAGESTATES) + responseStr = MsgHelperGenericDebug::processOpListTargetStates(commandStream, + app->getTargetStateStore() ); + else + if(operation == GENDBGMSG_OP_LISTPOOLS) + responseStr = processOpListPools(commandStream); + else + if(operation == GENDBGMSG_OP_DUMPDENTRY) + responseStr = processOpDumpDentry(commandStream); + else + if(operation == GENDBGMSG_OP_DUMPINODE) + responseStr = processOpDumpInode(commandStream); + else + if(operation == GENDBGMSG_OP_DUMPINLINEDINODE) + responseStr = processOpDumpInlinedInode(commandStream); + else + if(operation == GENDBGMSG_OP_QUOTAEXCEEDED) + responseStr = processOpQuotaExceeded(commandStream); + else if(operation == GENDBGMSG_OP_LISTSTORAGEPOOLS) + responseStr = MsgHelperGenericDebug::processOpListStoragePools(commandStream, + app->getStoragePoolStore()); +#ifdef BEEGFS_DEBUG + else + if(operation == GENDBGMSG_OP_WRITEDIRDENTRY) + responseStr = processOpWriteDirDentry(commandStream); + else + if(operation == GENDBGMSG_OP_WRITEDIRINODE) + responseStr = processOpWriteDirInode(commandStream); + else + if(operation == GENDBGMSG_OP_WRITEFILEINODE) + responseStr = processOpWriteInlinedFileInode(commandStream); +#endif // BEEGFS_DEBUG + else + responseStr = "Unknown/invalid operation"; + + return responseStr; +} + +/** + * Retrieve append lock stats for a certain file. + */ +std::string GenericDebugMsgEx::processOpListFileAppendLocks(std::istringstream& commandStream) +{ + // procotol: entryID as only argument + + std::string parentEntryID; + std::string entryID; + std::string responseStr; + std::string isBuddyMirroredStr; + bool isBuddyMirrored; + + // get entryID from command string + std::getline(commandStream, parentEntryID, ' '); + std::getline(commandStream, entryID, ' '); + std::getline(commandStream, isBuddyMirroredStr, ' '); + + if (parentEntryID.empty() ) + return "Invalid or missing parentEntryID"; + + if(entryID.empty() ) + return "Invalid or missing entryID"; + + if(isBuddyMirroredStr.empty()) + isBuddyMirrored = false; + else + isBuddyMirrored = StringTk::strToBool(isBuddyMirroredStr); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + MetaFileHandle inode = metaStore->referenceLoadedFile(parentEntryID, isBuddyMirrored, entryID); + if(!inode) + return "FileID not exists: " + entryID; + + responseStr = inode->flockAppendGetAllAsStr(); + + metaStore->releaseFile(parentEntryID, inode); + + return responseStr; +} + +std::string GenericDebugMsgEx::processOpListFileEntryLocks(std::istringstream& commandStream) +{ + // procotol: entryID as only argument + + std::string parentEntryID; + std::string entryID; + std::string responseStr; + std::string isBuddyMirroredStr; + bool isBuddyMirrored; + + // get entryID from command string + std::getline(commandStream, parentEntryID, ' '); + std::getline(commandStream, entryID, ' '); + std::getline(commandStream, isBuddyMirroredStr, ' '); + + if (parentEntryID.empty() ) + return "Invalid or missing parentEntryID"; + + if(entryID.empty() ) + return "Invalid or missing entryID"; + + if(isBuddyMirroredStr.empty()) + isBuddyMirrored = false; + else + isBuddyMirrored = StringTk::strToBool(isBuddyMirroredStr); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + MetaFileHandle inode = metaStore->referenceLoadedFile(parentEntryID, isBuddyMirrored, entryID); + if(!inode) + return "FileID not exists: " + entryID; + + responseStr = inode->flockEntryGetAllAsStr(); + + metaStore->releaseFile(parentEntryID, inode); + + return responseStr; +} + +std::string GenericDebugMsgEx::processOpListFileRangeLocks(std::istringstream& commandStream) +{ + // procotol: entryID as only argument + + std::string parentEntryID; + std::string entryID; + std::string isBuddyMirroredStr; + bool isBuddyMirrored; + + // get parentEntryID from command string + std::getline(commandStream, parentEntryID, ' '); + + if(parentEntryID.empty() ) + return "Invalid or missing parentEntryID"; + + // get entryID from command string + std::getline(commandStream, entryID, ' '); + + if(entryID.empty() ) + return "Invalid or missing entryID"; + + // get isBuddyMirrored from command string + std::getline(commandStream, isBuddyMirroredStr, ' '); + + if(isBuddyMirroredStr.empty()) + isBuddyMirrored = false; + else + isBuddyMirrored = StringTk::strToBool(isBuddyMirroredStr); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + MetaFileHandle file = metaStore->referenceLoadedFile(parentEntryID, isBuddyMirrored, entryID); + if(!file) + return "FileID not found: " + entryID; + + std::string responseStr = file->flockRangeGetAllAsStr(); + + metaStore->releaseFile(parentEntryID, file); + + return responseStr; +} + +std::string GenericDebugMsgEx::processOpListOpenFiles(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + SessionStore* sessions = app->getSessions(); + SessionStore* mirroredSessions = app->getMirroredSessions(); + + std::ostringstream responseStream; + size_t numFilesTotal = 0; + size_t numCheckedSessions = 0; // may defer from number of initially queried sessions + + NumNodeIDList sessionIDs = sessions->getAllSessionIDs(); + NumNodeIDList mirroredSessionIDs = mirroredSessions->getAllSessionIDs(); + + responseStream << "Found " << sessionIDs.size() << " non-mirrored sessions and " + << mirroredSessionIDs.size() << " mirrored sessions." << std::endl; + + responseStream << std::endl; + + responseStream << "Non-mirrored sessions:" << std::endl; + // walk over all sessions + for(NumNodeIDListCIter iter = sessionIDs.begin(); iter != sessionIDs.end(); iter++) + { + Session* session = sessions->referenceSession(*iter, false); + // note: sessionID might have been removed since we queried it, e.g. because client is gone + if(!session) + continue; + + numCheckedSessions++; + + SessionFileStore* sessionFiles = session->getFiles(); + size_t numFiles = sessionFiles->getSize(); + sessions->releaseSession(session); + + if(!numFiles) + continue; // only print sessions with open files + + numFilesTotal += numFiles; + + responseStream << *iter << ": " << numFiles << std::endl; + } + + responseStream << "Mirrored sessions:" << std::endl; + // ...and the mirrored sessions + for(NumNodeIDListCIter iter = mirroredSessionIDs.begin(); iter != mirroredSessionIDs.end(); + ++iter) + { + Session* session = mirroredSessions->referenceSession(*iter, false); + if (!session) + continue; + + numCheckedSessions++; + + SessionFileStore* sessionFiles = session->getFiles(); + size_t numFiles = sessionFiles->getSize(); + mirroredSessions->releaseSession(session); + + if (!numFiles) + continue; + + numFilesTotal += numFiles; + responseStream << *iter << ": " << numFiles << std::endl; + } + + responseStream << std::endl; + + responseStream << "Final results: " << numFilesTotal << " open files in " << + numCheckedSessions << " checked sessions"; + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpReferenceStatistics(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + std::ostringstream responseStream; + size_t numDirs; + size_t numFiles; + + metaStore->getReferenceStats(&numDirs, &numFiles); + + responseStream << "Dirs: " << numDirs << std::endl; + responseStream << "Files: " << numFiles; + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpCacheStatistics(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + std::ostringstream responseStream; + size_t numDirs; + + metaStore->getCacheStats(&numDirs); + + responseStream << "Dirs: " << numDirs; + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpVersion(std::istringstream& commandStream) +{ + return BEEGFS_VERSION; +} + +std::string GenericDebugMsgEx::processOpMsgQueueStats(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + MultiWorkQueue* workQ = app->getWorkQueue(); + + std::ostringstream responseStream; + std::string indirectQueueStats; + std::string directQueueStats; + std::string busyStats; + + workQ->getStatsAsStr(indirectQueueStats, directQueueStats, busyStats); + + responseStream << "general queue stats: " << std::endl << + indirectQueueStats << std::endl; + + responseStream << "direct queue stats: " << std::endl << + directQueueStats << std::endl; + + responseStream << "busy worker stats: " << std::endl << + busyStats << std::endl; + + return responseStream.str(); +} + +/** + * List internal state of meta and storage capacity pools. + */ +std::string GenericDebugMsgEx::processOpListPools(std::istringstream& commandStream) +{ + // protocol: no arguments + + const App* app = Program::getApp(); + const NodeCapacityPools* metaPools = app->getMetaCapacityPools(); + + std::ostringstream responseStream; + + responseStream << "META POOLS STATE: " << std::endl << metaPools->getStateAsStr() << std::endl; + + const StoragePoolPtrVec storagePools = app->getStoragePoolStore()->getPoolsAsVec(); + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + const TargetCapacityPools* capPool = (*iter)->getTargetCapacityPools(); + + responseStream << "STORAGE CAPACITY POOLS STATE (STORAGE POOL ID: " << (*iter)->getId() + << "): " << std::endl << capPool->getStateAsStr() << std::endl; + } + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + const NodeCapacityPools* capPool = (*iter)->getBuddyCapacityPools(); + + responseStream << "STORAGE BUDDY CAPACITY POOLS STATE (STORAGE POOL ID: " + << (*iter)->getId() << "): " << std::endl << capPool->getStateAsStr() + << std::endl; + } + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpDumpDentry(std::istringstream& commandStream) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + std::ostringstream responseStream; + + StringList parameterList; + StringTk::explode(commandStream.str(), ' ', ¶meterList); + + if ( parameterList.size() < 3 || parameterList.size() > 4 ) + return "Invalid or missing parameters; Parameter format: parentDirID entryName " + "[isBuddyMirrored]"; + + StringListIter iter = parameterList.begin(); + iter++; + std::string parentDirID = *iter; + iter++; + std::string entryName = *iter; + iter++; + bool isBuddyMirrored = false; + if (iter != parameterList.end()) + { + isBuddyMirrored = StringTk::strToBool(*iter); + } + + DirInode* parentDirInode = metaStore->referenceDir(parentDirID, isBuddyMirrored, false); + + if (!parentDirInode) + return "Unable to reference parent directory."; + + DirEntry dentry(entryName); + bool getDentryRes = parentDirInode->getDentry(entryName, dentry); + + metaStore->releaseDir(parentDirID); + + if (!getDentryRes) + return "Unable to get dentry from parent directory."; + + responseStream << "entryType: " << dentry.getEntryType() << std::endl; + responseStream << "ID: " << dentry.getID() << std::endl; + responseStream << "ownerNodeID: " << dentry.getOwnerNodeID() << std::endl; + responseStream << "featureFlags: " << dentry.getDentryFeatureFlags() << std::endl; + + return responseStream.str(); +} + +#ifdef BEEGFS_DEBUG +std::string GenericDebugMsgEx::processOpWriteDirDentry(std::istringstream& commandStream) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + std::string dentriesPath = Program::getApp()->getDentriesPath()->str(); + + std::ostringstream responseStream; + + StringList parameterList; + StringTk::explode(commandStream.str(), ' ', ¶meterList); + + if ( parameterList.size() < 4 || parameterList.size() > 5 ) + return "Invalid or missing parameters; Parameter format: parentDirID entryName ownerNodeID " + "[isBuddyMirrored]"; + + StringListIter iter = parameterList.begin(); + iter++; + std::string parentDirID = *iter; + iter++; + std::string entryName = *iter; + iter++; + NumNodeID ownerNodeID(StringTk::strToUInt(*iter) ); + + iter++; + bool isBuddyMirrored = false; + if (iter!=parameterList.end()) + { + isBuddyMirrored = StringTk::strToBool(*iter); + } + + DirInode* parentDirInode = metaStore->referenceDir(parentDirID, isBuddyMirrored, true); + + if (!parentDirInode) + return "Unable to reference parent directory."; + + DirEntry dentry(entryName); + bool getDentryRes = parentDirInode->getDentry(entryName, dentry); + + metaStore->releaseDir(parentDirID); + + if (!getDentryRes) + return "Unable to get dentry from parent directory."; + + bool setOwnerRes = dentry.setOwnerNodeID( + MetaStorageTk::getMetaDirEntryPath(dentriesPath, parentDirID), ownerNodeID); + + if (!setOwnerRes) + return "Unable to set new owner node ID in dentry."; + + return "OK"; +} +#endif // BEEGFS_DEBUG + +std::string GenericDebugMsgEx::processOpDumpInode(std::istringstream& commandStream) +{ + // commandStream: ID of inode + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + std::ostringstream responseStream; + + std::string inodeID; + std::string isBuddyMirroredStr; + bool isBuddyMirrored; + + // get inodeID from command string + std::getline(commandStream, inodeID, ' '); + + if(inodeID.empty() ) + return "Invalid or missing inode ID"; + + // get isBuddyMirrored from command string + std::getline(commandStream, isBuddyMirroredStr, ' '); + if(isBuddyMirroredStr.empty()) + isBuddyMirrored = false; + else + isBuddyMirrored = StringTk::strToBool(isBuddyMirroredStr); + + MetaFileHandle fileInode; + DirInode* dirInode = NULL; + + metaStore->referenceInode(inodeID, isBuddyMirrored, fileInode, dirInode); + + if (fileInode) + { + StatData statData; + if (fileInode->getStatData(statData) != FhgfsOpsErr_SUCCESS) + { // stat data retrieval failed + metaStore->releaseFile("", fileInode); + return "Could not get stat data for requested file inode"; + } + + DirEntryType dirEntryType = MetadataTk::posixFileTypeToDirEntryType(fileInode->getMode() ); + + std::string parentDirID = "cannotBeUsed"; + uint16_t parentNodeID = 0; + + responseStream << "entryType: " << dirEntryType << std::endl; + responseStream << "parentEntryID: " << parentDirID << std::endl; + responseStream << "parentNodeID: " << StringTk::uintToStr(parentNodeID) << std::endl; + responseStream << "mode: " << StringTk::intToStr(statData.getMode()) << std::endl; + responseStream << "uid: " << StringTk::uintToStr(statData.getUserID()) << std::endl; + responseStream << "gid: " << StringTk::uintToStr(statData.getGroupID()) << std::endl; + responseStream << "filesize: " << StringTk::int64ToStr(statData.getFileSize()) << std::endl; + responseStream << "ctime: " << StringTk::int64ToStr(statData.getCreationTimeSecs()) << std::endl; + responseStream << "atime: " << StringTk::int64ToStr(statData.getLastAccessTimeSecs()) << std::endl; + responseStream << "mtime: " << StringTk::int64ToStr(statData.getModificationTimeSecs()) << std::endl; + responseStream << "hardlinks: " << StringTk::intToStr(statData.getNumHardlinks()) << std::endl; + responseStream << "stripeTargets: " + << StringTk::uint16VecToStr(fileInode->getStripePattern()->getStripeTargetIDs()) + << std::endl; + responseStream << "chunkSize: " + << StringTk::uintToStr(fileInode->getStripePattern()->getChunkSize()) << std::endl; + responseStream << "featureFlags: " << fileInode->getFeatureFlags() << std::endl; + + metaStore->releaseFile("", fileInode); + } + else + if (dirInode) + { + StatData statData; + if (dirInode->getStatData(statData) != FhgfsOpsErr_SUCCESS) + { // stat data retrieval failed + metaStore->releaseDir(inodeID); + return "Could not get stat data for requested dir inode"; + } + + DirEntryType dirEntryType = MetadataTk::posixFileTypeToDirEntryType(dirInode->getMode() ); + + std::string parentDirID; + NumNodeID parentNodeID; + dirInode->getParentInfo(&parentDirID, &parentNodeID); + + responseStream << "entryType: " << dirEntryType << std::endl; + responseStream << "parentEntryID: " << parentDirID << std::endl; + responseStream << "parentNodeID: " << parentNodeID.str() << std::endl; + responseStream << "ownerNodeID: " << dirInode->getOwnerNodeID().str() << std::endl; + responseStream << "mode: " << StringTk::intToStr(statData.getMode()) << std::endl; + responseStream << "uid: " << StringTk::uintToStr(statData.getUserID()) << std::endl; + responseStream << "gid: " << StringTk::uintToStr(statData.getGroupID()) << std::endl; + responseStream << "size: " << StringTk::int64ToStr(statData.getFileSize()) << std::endl; + responseStream << "numLinks: " << StringTk::int64ToStr(statData.getNumHardlinks()) + << std::endl; + responseStream << "ctime: " << StringTk::int64ToStr(statData.getCreationTimeSecs()) + << std::endl; + responseStream << "atime: " << StringTk::int64ToStr(statData.getLastAccessTimeSecs()) + << std::endl; + responseStream << "mtime: " << StringTk::int64ToStr(statData.getModificationTimeSecs()) + << std::endl; + responseStream << "featureFlags: " << dirInode->getFeatureFlags() << std::endl; + + metaStore->releaseDir(inodeID); + } + else + { + return "Could not read requested inode"; + } + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpDumpInlinedInode(std::istringstream& commandStream) +{ + // commandStream: parentID, name + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + NumNodeID localNodeID = Program::getApp()->getLocalNode().getNumID(); + std::ostringstream responseStream; + + StringList parameterList; + StringTk::explode(commandStream.str(), ' ', ¶meterList); + + if ( parameterList.size() < 3 || parameterList.size() > 4 ) + return "Invalid or missing parameters; Parameter format: parentDirID entryName " + "[isBuddyMirrored]"; + + StringListIter iter = parameterList.begin(); + iter++; + std::string parentEntryID = *iter; + iter++; + std::string entryName = *iter; + iter++; + bool isBuddyMirrored = false; + if (iter != parameterList.end()) + { + isBuddyMirrored = StringTk::strToBool(*iter); + } + + EntryInfo entryInfo(localNodeID, parentEntryID, "unknown", entryName, DirEntryType_REGULARFILE, + 0); + + DirInode* parentInode = metaStore->referenceDir(parentEntryID, isBuddyMirrored, false); + + if ( !parentInode ) + return "Could not open parent directory"; + + DirEntry dirEntry(entryName); + bool getDentryRes = parentInode->getDentry(entryName, dirEntry); + + if ( !getDentryRes ) + { + metaStore->releaseDir(parentEntryID); + return "Could not open dir entry"; + } + + FileInodeStoreData* inodeData = dirEntry.getInodeStoreData(); + + if ( !inodeData ) + { + metaStore->releaseDir(parentEntryID); + return "Could not get inlined inode data"; + } + + StatData* statData = inodeData->getInodeStatData(); + if ( !statData ) + { + metaStore->releaseDir(parentEntryID); + return "Could not get stat data for requested file inode"; + } + + responseStream << "entryID: " << inodeData->getEntryID() << std::endl; + responseStream << "mode: " << StringTk::intToStr(statData->getMode()) << std::endl; + responseStream << "uid: " << StringTk::uintToStr(statData->getUserID()) << std::endl; + responseStream << "gid: " << StringTk::uintToStr(statData->getGroupID()) << std::endl; + responseStream << "filesize: " << StringTk::int64ToStr(statData->getFileSize()) << std::endl; + responseStream << "ctime: " << StringTk::int64ToStr(statData->getCreationTimeSecs()) << std::endl; + responseStream << "atime: " << StringTk::int64ToStr(statData->getLastAccessTimeSecs()) + << std::endl; + responseStream << "mtime: " << StringTk::int64ToStr(statData->getModificationTimeSecs()) + << std::endl; + responseStream << "hardlinks: " << StringTk::intToStr(statData->getNumHardlinks()) << std::endl; + responseStream << "stripeTargets: " + << StringTk::uint16VecToStr(inodeData->getPattern()->getStripeTargetIDs()) << std::endl; + responseStream << "chunkSize: " + << StringTk::uintToStr(inodeData->getPattern()->getChunkSize()) << std::endl; + responseStream << "featureFlags: " << inodeData->getInodeFeatureFlags() << std::endl; + + metaStore->releaseDir(parentEntryID); + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpQuotaExceeded(std::istringstream& commandStream) +{ + App* app = Program::getApp(); + + std::string targetIdStr; + std::getline(commandStream, targetIdStr, ' '); + uint16_t targetId = StringTk::strToUInt(targetIdStr); + + std::string returnString; + + if(!app->getConfig()->getQuotaEnableEnforcement() ) + return "No quota exceeded IDs on this storage daemon because quota enforcement is" + "disabled."; + + ExceededQuotaStorePtr exQuotaStore = app->getExceededQuotaStores()->get(targetId); + // exQuotaStore may be null;needs to be checked in MsgHelperGenericDebug::processOpQuotaExceeded + return MsgHelperGenericDebug::processOpQuotaExceeded(commandStream, exQuotaStore.get()); +} + +#ifdef BEEGFS_DEBUG +std::string GenericDebugMsgEx::processOpWriteDirInode(std::istringstream& commandStream) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + // get parameters from command string + StringVector paramVec; + StringTk::explode(commandStream.str(), ' ', ¶mVec); + + std::string entryID; + std::string parentDirID; + NumNodeID parentNodeID; + NumNodeID ownerNodeID; + int mode; + uint uid; + uint gid; + int64_t size; + unsigned numLinks; + bool isBuddyMirrored; + + try + { + unsigned i = 1; + entryID = paramVec.at(i++); + parentDirID = paramVec.at(i++); + parentNodeID = NumNodeID(StringTk::strToUInt(paramVec.at(i++))); + ownerNodeID = NumNodeID(StringTk::strToUInt(paramVec.at(i++))); + mode = StringTk::strToInt(paramVec.at(i++)); + uid = StringTk::strToUInt(paramVec.at(i++)); + gid = StringTk::strToUInt(paramVec.at(i++)); + size = StringTk::strToInt64(paramVec.at(i++)); + numLinks = StringTk::strToUInt(paramVec.at(i++)); + if (ireferenceDir(entryID, isBuddyMirrored, true); + + if ( !dirInode ) + return "Could not find directory with ID: " + entryID; + + StatData statData; + if ( dirInode->getStatData(statData) != FhgfsOpsErr_SUCCESS ) + { + metaStore->releaseDir(entryID); + return "Could not get stat data for requested dir inode"; + } + + dirInode->setParentInfoInitial(parentDirID, parentNodeID); + dirInode->setOwnerNodeID(ownerNodeID); + + statData.setMode(mode); + statData.setUserID(uid); + statData.setGroupID(gid); + statData.setFileSize(size); + statData.setNumHardLinks(numLinks); + + dirInode->setStatData(statData); + + metaStore->releaseDir(entryID); + + return "OK"; +} + + +std::string GenericDebugMsgEx::processOpWriteInlinedFileInode(std::istringstream& commandStream) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + std::string retStr = "OK"; + + // get parameters from command string + StringVector paramVec; + StringTk::explode(commandStream.str(), ' ', ¶mVec); + + std::string parentDirID; + std::string name; + std::string entryID; + int mode; + uint uid; + uint gid; + int64_t filesize; + unsigned numLinks; + UInt16Vector stripeTargets; + // UInt16Vector* origStripeTargets; + + try + { + unsigned i = 1; + parentDirID = paramVec.at(i++); + name = paramVec.at(i++); + entryID = paramVec.at(i++); + mode = StringTk::strToInt(paramVec.at(i++)); + uid = StringTk::strToUInt(paramVec.at(i++)); + gid = StringTk::strToUInt(paramVec.at(i++)); + filesize = StringTk::strToInt64(paramVec.at(i++)); + numLinks = StringTk::strToUInt(paramVec.at(i++)); + StringTk::strToUint16Vec(paramVec.at(i++), &stripeTargets); + } + catch (std::out_of_range& e) + { + std::string paramFormatStr = + "parentDirID entryName entryID mode uid gid filesize numLinks stripeTargets"; + return "Invalid or missing parameters; Parameter format: " + paramFormatStr; + } + + EntryInfo entryInfo(Program::getApp()->getLocalNodeNumID(), parentDirID, entryID, name, + DirEntryType_REGULARFILE, 0); + + auto [fileInode, referenceRes] = metaStore->referenceFile(&entryInfo); + + if (!fileInode) + return "Could not reference inode"; + + StatData statData; + fileInode->getStatData(statData); + + statData.setMode(mode); + statData.setUserID(uid); + statData.setGroupID(gid); + statData.setFileSize(filesize); + statData.setNumHardLinks(numLinks); + + fileInode->setStatData(statData); + + StripePattern* pattern = fileInode->getStripePattern(); + UInt16Vector* origTargets = pattern->getStripeTargetIDsModifyable(); + *origTargets = stripeTargets; + + fileInode->updateInodeOnDisk(&entryInfo, pattern); + + metaStore->releaseFile(parentDirID, fileInode); + + return retStr; +} +#endif // BEEGFS_DEBUG diff --git a/meta/source/net/message/nodes/GenericDebugMsgEx.h b/meta/source/net/message/nodes/GenericDebugMsgEx.h new file mode 100644 index 0000000..2b07abd --- /dev/null +++ b/meta/source/net/message/nodes/GenericDebugMsgEx.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + + +class GenericDebugMsgEx : public GenericDebugMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + std::string processCommand(); + + std::string processOpListFileAppendLocks(std::istringstream& commandStream); + std::string processOpListFileEntryLocks(std::istringstream& commandStream); + std::string processOpListFileRangeLocks(std::istringstream& commandStream); + std::string processOpListOpenFiles(std::istringstream& commandStream); + std::string processOpReferenceStatistics(std::istringstream& commandStream); + std::string processOpCacheStatistics(std::istringstream& commandStream); + std::string processOpVersion(std::istringstream& commandStream); + std::string processOpMsgQueueStats(std::istringstream& commandStream); + std::string processOpListPools(std::istringstream& commandStream); + std::string processOpDumpDentry(std::istringstream& commandStream); + std::string processOpDumpInode(std::istringstream& commandStream); + std::string processOpDumpInlinedInode(std::istringstream& commandStream); + std::string processOpQuotaExceeded(std::istringstream& commandStream); + + #ifdef BEEGFS_DEBUG + std::string processOpWriteDirDentry(std::istringstream& commandStream); + std::string processOpWriteDirInode(std::istringstream& commandStream); + std::string processOpWriteInlinedFileInode(std::istringstream& commandStream); + #endif // BEEGFS_DEBUG + +}; + diff --git a/meta/source/net/message/nodes/GetClientStatsMsgEx.cpp b/meta/source/net/message/nodes/GetClientStatsMsgEx.cpp new file mode 100644 index 0000000..a3ba0af --- /dev/null +++ b/meta/source/net/message/nodes/GetClientStatsMsgEx.cpp @@ -0,0 +1,32 @@ +#include +#include +#include +#include +#include "GetClientStatsMsgEx.h" +#include +#include + + +/** + * Server side gets a GetClientStatsMsgEx request + */ +bool GetClientStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("GetClientStatsMsgEx incoming"); + + uint64_t cookieIP = getCookieIP(); // requested is cookie+1 + + // get stats + MetaNodeOpStats* opStats = Program::getApp()->getNodeOpStats(); + + bool wantPerUserStats = isMsgHeaderFeatureFlagSet(GETCLIENTSTATSMSG_FLAG_PERUSERSTATS); + UInt64Vector opStatsVec; + + opStats->mapToUInt64Vec( + cookieIP, GETCLIENTSTATSRESP_MAX_PAYLOAD_LEN, wantPerUserStats, &opStatsVec); + + ctx.sendResponse(GetClientStatsRespMsg(&opStatsVec) ); + + return true; +} + diff --git a/meta/source/net/message/nodes/GetClientStatsMsgEx.h b/meta/source/net/message/nodes/GetClientStatsMsgEx.h new file mode 100644 index 0000000..f780d51 --- /dev/null +++ b/meta/source/net/message/nodes/GetClientStatsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + + +class GetClientStatsMsgEx : public GetClientStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.cpp b/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.cpp new file mode 100644 index 0000000..9845514 --- /dev/null +++ b/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.cpp @@ -0,0 +1,65 @@ +#include +#include +#include + +#include "GetNodeCapacityPoolsMsgEx.h" + +bool GetNodeCapacityPoolsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "GetNodeCapacityPools incoming"; + + CapacityPoolQueryType poolType = getCapacityPoolQueryType(); + + LOG_DEBUG(logContext, Log_SPAM, "PoolType: " + StringTk::intToStr(poolType) ); + + const App* app = Program::getApp(); + + GetNodeCapacityPoolsRespMsg::PoolsMap capacityPoolsMap; + + switch(poolType) + { + case CapacityPoolQuery_META: + { + const NodeCapacityPools* capPools = app->getMetaCapacityPools(); + + capacityPoolsMap[StoragePoolId(StoragePoolStore::INVALID_POOL_ID)] = + capPools->getPoolsAsLists(); + } break; + + case CapacityPoolQuery_STORAGE: + { + const StoragePoolPtrVec storagePools = app->getStoragePoolStore()->getPoolsAsVec(); + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + const TargetCapacityPools* capPools = (*iter)->getTargetCapacityPools(); + + capacityPoolsMap[(*iter)->getId()] = capPools->getPoolsAsLists(); + } + } break; + + case CapacityPoolQuery_STORAGEBUDDIES: + { + const StoragePoolPtrVec storagePools = app->getStoragePoolStore()->getPoolsAsVec(); + + for (auto iter = storagePools.begin(); iter != storagePools.end(); iter++) + { + const NodeCapacityPools* capPools = (*iter)->getBuddyCapacityPools(); + + capacityPoolsMap[(*iter)->getId()] = capPools->getPoolsAsLists(); + } + } break; + + default: + { + LogContext(logContext).logErr("Invalid pool type: " + StringTk::intToStr(poolType) ); + + return false; + } break; + } + + ctx.sendResponse(GetNodeCapacityPoolsRespMsg(&capacityPoolsMap)); + + return true; +} + diff --git a/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.h b/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.h new file mode 100644 index 0000000..a107084 --- /dev/null +++ b/meta/source/net/message/nodes/GetNodeCapacityPoolsMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class GetNodeCapacityPoolsMsgEx : public GetNodeCapacityPoolsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/GetNodesMsgEx.cpp b/meta/source/net/message/nodes/GetNodesMsgEx.cpp new file mode 100644 index 0000000..bdb58c0 --- /dev/null +++ b/meta/source/net/message/nodes/GetNodesMsgEx.cpp @@ -0,0 +1,37 @@ +#include +#include +#include "GetNodesMsgEx.h" + +#include + +bool GetNodesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("GetNodes incoming"); + + NodeType nodeType = (NodeType)getValue(); + + LOG_DEBUG_CONTEXT(log, 5, "NodeType: " + boost::lexical_cast(nodeType)); + + App* app = Program::getApp(); + + NodeStore* nodes = app->getServerStoreFromType(nodeType); + if(unlikely(!nodes) ) + { + LOG(GENERAL, ERR, "Invalid node type.", + ("Node Type", getNodeType()), + ("Sender", ctx.peerName()) + ); + + return true; + } + + auto nodeList = nodes->referenceAllNodes(); + + NumNodeID rootID = app->getMetaRoot().getID(); + bool rootIsBuddyMirrored = app->getMetaRoot().getIsMirrored(); + + ctx.sendResponse(GetNodesRespMsg(rootID, rootIsBuddyMirrored, nodeList) ); + + return true; +} + diff --git a/meta/source/net/message/nodes/GetNodesMsgEx.h b/meta/source/net/message/nodes/GetNodesMsgEx.h new file mode 100644 index 0000000..d225af5 --- /dev/null +++ b/meta/source/net/message/nodes/GetNodesMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class GetNodesMsgEx : public GetNodesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/GetTargetMappingsMsgEx.cpp b/meta/source/net/message/nodes/GetTargetMappingsMsgEx.cpp new file mode 100644 index 0000000..7748146 --- /dev/null +++ b/meta/source/net/message/nodes/GetTargetMappingsMsgEx.cpp @@ -0,0 +1,16 @@ +#include +#include +#include +#include "GetTargetMappingsMsgEx.h" + + +bool GetTargetMappingsMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + TargetMapper* targetMapper = app->getTargetMapper(); + + ctx.sendResponse(GetTargetMappingsRespMsg(targetMapper->getMapping())); + + return true; +} + diff --git a/meta/source/net/message/nodes/GetTargetMappingsMsgEx.h b/meta/source/net/message/nodes/GetTargetMappingsMsgEx.h new file mode 100644 index 0000000..1bac10e --- /dev/null +++ b/meta/source/net/message/nodes/GetTargetMappingsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class GetTargetMappingsMsgEx : public GetTargetMappingsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/nodes/HeartbeatMsgEx.cpp b/meta/source/net/message/nodes/HeartbeatMsgEx.cpp new file mode 100644 index 0000000..7888ca5 --- /dev/null +++ b/meta/source/net/message/nodes/HeartbeatMsgEx.cpp @@ -0,0 +1,90 @@ +#include +#include +#include "HeartbeatMsgEx.h" + +#include + +bool HeartbeatMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Heartbeat incoming"); + + App* app = Program::getApp(); + bool isNodeNew; + + // construct node + + NicAddressList& nicList = getNicList(); + + auto node = std::make_shared(getNodeType(), getNodeID(), getNodeNumID(), getPortUDP(), + getPortTCP(), nicList); + + // set local nic capabilities + + NicAddressList localNicList(app->getLocalNicList() ); + NicListCapabilities localNicCaps; + + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + node->getConnPool()->setLocalNicList(localNicList, localNicCaps); + + std::string nodeIDWithTypeStr = node->getNodeIDWithTypeStr(); + + log.log(Log_DEBUG, std::string("Heartbeat node: ") + nodeIDWithTypeStr); + + // add/update node in store + AbstractNodeStore* nodes = app->getAbstractNodeStoreFromType(getNodeType() ); + if(!nodes) + { + log.logErr("Invalid node type: " + StringTk::intToStr(getNodeType() ) + + "(" + boost::lexical_cast(getNodeType()) + ")"); + + goto ack_resp; + } + + isNodeNew = (nodes->addOrUpdateNode(std::move(node)) == NodeStoreResult::Added); + if( (isNodeNew) && (getNodeType() != NODETYPE_Client) ) + { // log info about new server + bool supportsRDMA = NetworkInterfaceCard::supportsRDMA(&nicList); + + std::string supports; + if (supportsRDMA) + supports = "; RDMA."; + + + log.log(Log_WARNING, std::string("New node: ") + nodeIDWithTypeStr + supports); + + log.log(Log_DEBUG, "Number of nodes: " + "Meta: " + StringTk::intToStr(app->getMetaNodes()->getSize() ) + "; " + "Storage: " + StringTk::intToStr(app->getStorageNodes()->getSize() ) ); + } + + processIncomingRoot(); + +ack_resp: + acknowledge(ctx); + + return true; +} + +/** + * Handles the contained root information. + */ +void HeartbeatMsgEx::processIncomingRoot() +{ + LogContext log("Heartbeat incoming"); + + // check whether root info is defined + if( (getNodeType() != NODETYPE_Meta) || !getRootNumID() ) + return; + + // try to apply the contained root info + if(Program::getApp()->getMetaRoot().setIfDefault(getRootNumID(), getRootIsBuddyMirrored())) + { + log.log(Log_CRITICAL, "Root (by Heartbeat): " + getRootNumID().str() ); + + Program::getApp()->getRootDir()->setOwnerNodeID(getRootNumID() ); + if (getRootIsBuddyMirrored()) + Program::getApp()->getRootDir()->setIsBuddyMirroredFlag(true); + + } +} + diff --git a/meta/source/net/message/nodes/HeartbeatMsgEx.h b/meta/source/net/message/nodes/HeartbeatMsgEx.h new file mode 100644 index 0000000..737c2d4 --- /dev/null +++ b/meta/source/net/message/nodes/HeartbeatMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class HeartbeatMsgEx : public HeartbeatMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + void processIncomingRoot(); +}; + diff --git a/meta/source/net/message/nodes/HeartbeatRequestMsgEx.cpp b/meta/source/net/message/nodes/HeartbeatRequestMsgEx.cpp new file mode 100644 index 0000000..53cc258 --- /dev/null +++ b/meta/source/net/message/nodes/HeartbeatRequestMsgEx.cpp @@ -0,0 +1,24 @@ +#include +#include +#include +#include "HeartbeatRequestMsgEx.h" + +bool HeartbeatRequestMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + Node& localNode = app->getLocalNode(); + NumNodeID localNodeNumID = localNode.getNumID(); + NumNodeID rootNodeID = app->getMetaRoot().getID(); + NicAddressList nicList(localNode.getNicList()); + + HeartbeatMsg hbMsg(localNode.getAlias(), localNodeNumID, NODETYPE_Meta, &nicList); + hbMsg.setRootNumID(rootNodeID); + hbMsg.setPorts(cfg->getConnMetaPort(), cfg->getConnMetaPort() ); + + ctx.sendResponse(hbMsg); + + return true; +} + diff --git a/meta/source/net/message/nodes/HeartbeatRequestMsgEx.h b/meta/source/net/message/nodes/HeartbeatRequestMsgEx.h new file mode 100644 index 0000000..e5a50c4 --- /dev/null +++ b/meta/source/net/message/nodes/HeartbeatRequestMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class HeartbeatRequestMsgEx : public HeartbeatRequestMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/MapTargetsMsgEx.cpp b/meta/source/net/message/nodes/MapTargetsMsgEx.cpp new file mode 100644 index 0000000..5f0d28d --- /dev/null +++ b/meta/source/net/message/nodes/MapTargetsMsgEx.cpp @@ -0,0 +1,45 @@ +#include +#include +#include +#include + +#include "MapTargetsMsgEx.h" + + +bool MapTargetsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("MapTargetsMsg incoming"); + + const App* app = Program::getApp(); + const NodeStoreServers* storageNodes = app->getStorageNodes(); + TargetMapper* targetMapper = app->getTargetMapper(); + + const NumNodeID nodeID = getNodeID(); + std::map results; + + for (const auto mapping : getTargets()) + { + const auto targetId = mapping.first; + const auto poolId = mapping.second; + + const auto mapRes = targetMapper->mapTarget(targetId, nodeID, poolId); + + results[targetId] = mapRes.first; + + if ( (mapRes.first != FhgfsOpsErr_SUCCESS) && (mapRes.second) ) + { // target could be mapped and is new + LOG_DEBUG_CONTEXT(log, Log_WARNING, "Mapping " + "target " + StringTk::uintToStr(targetId) + + " => " + + storageNodes->getNodeIDWithTypeStr(nodeID) ); + + IGNORE_UNUSED_VARIABLE(storageNodes); + } + } + + if(!acknowledge(ctx) ) + ctx.sendResponse(MapTargetsRespMsg(results)); + + return true; +} + diff --git a/meta/source/net/message/nodes/MapTargetsMsgEx.h b/meta/source/net/message/nodes/MapTargetsMsgEx.h new file mode 100644 index 0000000..ba521c3 --- /dev/null +++ b/meta/source/net/message/nodes/MapTargetsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class MapTargetsMsgEx : public MapTargetsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/nodes/PublishCapacitiesMsgEx.cpp b/meta/source/net/message/nodes/PublishCapacitiesMsgEx.cpp new file mode 100644 index 0000000..bee61e0 --- /dev/null +++ b/meta/source/net/message/nodes/PublishCapacitiesMsgEx.cpp @@ -0,0 +1,23 @@ +#include +#include + +#include "PublishCapacitiesMsgEx.h" + + +bool PublishCapacitiesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("PublishCapacitiesMsg incoming"); + + App* app = Program::getApp(); + InternodeSyncer* syncer = app->getInternodeSyncer(); + + + // force upload of capacity information + syncer->setForcePublishCapacities(); + + // send response + acknowledge(ctx); + + return true; +} + diff --git a/meta/source/net/message/nodes/PublishCapacitiesMsgEx.h b/meta/source/net/message/nodes/PublishCapacitiesMsgEx.h new file mode 100644 index 0000000..be11015 --- /dev/null +++ b/meta/source/net/message/nodes/PublishCapacitiesMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include + + +class PublishCapacitiesMsgEx : public PublishCapacitiesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.cpp b/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.cpp new file mode 100644 index 0000000..f08b831 --- /dev/null +++ b/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.cpp @@ -0,0 +1,23 @@ +#include +#include +#include "RefreshCapacityPoolsMsgEx.h" + + +bool RefreshCapacityPoolsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("RefreshCapacityPoolsMsg incoming"); + + App* app = Program::getApp(); + InternodeSyncer* syncer = app->getInternodeSyncer(); + + + // force update of capacity pools + syncer->setForcePoolsUpdate(); + + + // send response + acknowledge(ctx); + + return true; +} + diff --git a/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.h b/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.h new file mode 100644 index 0000000..b32f535 --- /dev/null +++ b/meta/source/net/message/nodes/RefreshCapacityPoolsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + + +class RefreshCapacityPoolsMsgEx : public RefreshCapacityPoolsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp b/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp new file mode 100644 index 0000000..aae4f1b --- /dev/null +++ b/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp @@ -0,0 +1,22 @@ +#include +#include + +#include "RefreshTargetStatesMsgEx.h" + + +bool RefreshTargetStatesMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("RefreshTargetStatesMsg incoming"); + + App* app = Program::getApp(); + InternodeSyncer* syncer = app->getInternodeSyncer(); + + // force update of target states + syncer->setForceTargetStatesUpdate(); + + // send response + acknowledge(ctx); + + return true; +} + diff --git a/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.h b/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.h new file mode 100644 index 0000000..e32c878 --- /dev/null +++ b/meta/source/net/message/nodes/RefreshTargetStatesMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + + +class RefreshTargetStatesMsgEx : public RefreshTargetStatesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/RemoveNodeMsgEx.cpp b/meta/source/net/message/nodes/RemoveNodeMsgEx.cpp new file mode 100644 index 0000000..0c87f4f --- /dev/null +++ b/meta/source/net/message/nodes/RemoveNodeMsgEx.cpp @@ -0,0 +1,34 @@ +#include +#include +#include +#include "RemoveNodeMsgEx.h" + + +bool RemoveNodeMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + LOG_DBG(GENERAL, SPAM, "Removing node.", getNodeNumID()); + + if (getNodeType() == NODETYPE_Meta || getNodeType() == NODETYPE_Storage) + { + NodeStoreServers* nodes = app->getServerStoreFromType(getNodeType()); + auto node = nodes->referenceNode(getNodeNumID()); + bool delRes = nodes->deleteNode(getNodeNumID()); + + // log + if (delRes) + { + LOG(GENERAL, WARNING, "Node removed.", ("node", node->getNodeIDWithTypeStr())); + LOG(GENERAL, WARNING, "Number of nodes in the system:", + ("meta", app->getMetaNodes()->getSize()), + ("storage", app->getStorageNodes()->getSize())); + } + } + + if (!acknowledge(ctx)) + ctx.sendResponse(RemoveNodeRespMsg(0)); + + return true; +} + diff --git a/meta/source/net/message/nodes/RemoveNodeMsgEx.h b/meta/source/net/message/nodes/RemoveNodeMsgEx.h new file mode 100644 index 0000000..634668f --- /dev/null +++ b/meta/source/net/message/nodes/RemoveNodeMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class RemoveNodeMsgEx : public RemoveNodeMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp b/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp new file mode 100644 index 0000000..e334f4b --- /dev/null +++ b/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp @@ -0,0 +1,48 @@ +#include +#include +#include + +#include + +#include "SetMirrorBuddyGroupMsgEx.h" + +bool SetMirrorBuddyGroupMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("SetMirrorBuddyGroupMsg incoming"); + + App* app = Program::getApp(); + + MirrorBuddyGroupMapper* buddyGroupMapper; + NodeType nodeType = getNodeType(); + + switch (nodeType) + { + case NODETYPE_Storage: + buddyGroupMapper = app->getStorageBuddyGroupMapper(); + break; + + case NODETYPE_Meta: + buddyGroupMapper = app->getMetaBuddyGroupMapper(); + break; + + default: + log.logErr("Node type mismatch"); + return false; + } + + + uint16_t buddyGroupID = this->getBuddyGroupID(); + uint16_t primaryTargetID = this->getPrimaryTargetID(); + uint16_t secondaryTargetID = this->getSecondaryTargetID(); + bool allowUpdate = this->getAllowUpdate(); + uint16_t newBuddyGroupID = 0; + + FhgfsOpsErr mapResult = buddyGroupMapper->mapMirrorBuddyGroup(buddyGroupID, primaryTargetID, + secondaryTargetID, app->getLocalNode().getNumID(), allowUpdate, &newBuddyGroupID); + + if(!acknowledge(ctx) ) + ctx.sendResponse(SetMirrorBuddyGroupRespMsg(mapResult, newBuddyGroupID) ); + + return true; +} + diff --git a/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h b/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h new file mode 100644 index 0000000..91930dd --- /dev/null +++ b/meta/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class SetMirrorBuddyGroupMsgEx : public SetMirrorBuddyGroupMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp b/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp new file mode 100644 index 0000000..d5d1a63 --- /dev/null +++ b/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include + +#include "SetTargetConsistencyStatesMsgEx.h" + +bool SetTargetConsistencyStatesMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "Set target states incoming"; + + App* app = Program::getApp(); + InternodeSyncer* internodeSyncer = app->getInternodeSyncer(); + FhgfsOpsErr result = FhgfsOpsErr_SUCCESS; + + if (getTargetIDs().size() != 1 || getStates().size() != 1) + { + LogContext(logContext).logErr("Invalid list size of target ID or state List (must be 1)."); + result = FhgfsOpsErr_INTERNAL; + goto send_response; + } + + if (getTargetIDs().front() != app->getLocalNodeNumID().val() ) + { + LogContext(logContext).logErr("ID in message does not match local node ID."); + result = FhgfsOpsErr_INTERNAL; + goto send_response; + } + + internodeSyncer->setNodeConsistencyState( + static_cast(getStates().front() ) ); + +send_response: + ctx.sendResponse(SetTargetConsistencyStatesRespMsg(result) ); + + return true; +} diff --git a/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h b/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h new file mode 100644 index 0000000..dcae86b --- /dev/null +++ b/meta/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class SetTargetConsistencyStatesMsgEx : public SetTargetConsistencyStatesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp b/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp new file mode 100644 index 0000000..546f73a --- /dev/null +++ b/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp @@ -0,0 +1,14 @@ +#include "RefreshStoragePoolsMsgEx.h" + +#include + +bool RefreshStoragePoolsMsgEx::processIncoming(ResponseContext& ctx) +{ + Program::getApp()->getInternodeSyncer()->setForceStoragePoolsUpdate(); + + // can only come as an AcknowledgableMsg from mgmtd + acknowledge(ctx); + + return true; +} + diff --git a/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h b/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h new file mode 100644 index 0000000..64490b4 --- /dev/null +++ b/meta/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RefreshStoragePoolsMsgEx : public RefreshStoragePoolsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/session/AckNotifyMsgEx.h b/meta/source/net/message/session/AckNotifyMsgEx.h new file mode 100644 index 0000000..34541b8 --- /dev/null +++ b/meta/source/net/message/session/AckNotifyMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + +class AckNotifiyMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState< + AckNotifiyRespMsg, + NETMSGTYPE_AckNotify> ResponseState; + + std::unique_ptr executeLocally(ResponseContext&, bool) override + { + // do nothing at all. MirroredMessage has taken care of everything + return boost::make_unique(FhgfsOpsErr_SUCCESS); + } + + std::tuple<> lock(EntryLockStore&) override { return {}; } + + bool isMirrored() override { return true; } + + private: + void forwardToSecondary(ResponseContext& ctx) override + { + sendToSecondary(ctx, *this, NETMSGTYPE_AckNotifyResp); + } + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "AckNotifiyMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/BumpFileVersionMsgEx.cpp b/meta/source/net/message/session/BumpFileVersionMsgEx.cpp new file mode 100644 index 0000000..8c0ab7e --- /dev/null +++ b/meta/source/net/message/session/BumpFileVersionMsgEx.cpp @@ -0,0 +1,62 @@ +#include "BumpFileVersionMsgEx.h" + +#include +#include + +bool BumpFileVersionMsgEx::processIncoming(ResponseContext& ctx) +{ + LOG_DBG(SESSIONS, DEBUG, "", getEntryInfo().getEntryID(), getEntryInfo().getIsBuddyMirrored(), + hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond), + isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_PERSISTENT), + isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_HASEVENT)); + if (isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_HASEVENT)) + LOG_DBG(SESSIONS, DEBUG, "", int(getFileEvent()->type), getFileEvent()->path); + + return BaseType::processIncoming(ctx); +} + +FileIDLock BumpFileVersionMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo().getEntryID(), true}; +} + +std::unique_ptr BumpFileVersionMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + auto* app = Program::getApp(); + auto* metaStore = app->getMetaStore(); + + auto [inode, referenceRes] = metaStore->referenceFile(&getEntryInfo()); + if (!inode) + return boost::make_unique(FhgfsOpsErr_INTERNAL); + + if (isMsgHeaderFeatureFlagSet(BUMPFILEVERSIONMSG_FLAG_PERSISTENT) && + !inode->incrementFileVersion(&getEntryInfo())) + { + metaStore->releaseFile(getEntryInfo().getParentEntryID(), inode); + return boost::make_unique(FhgfsOpsErr_SAVEERROR); + } + + if (!isSecondary && app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext( + &getEntryInfo(), + getEntryInfo().getParentEntryID(), + getMsgHeaderUserID(), + "", + inode->getNumHardlinks(), + isSecondary + ); + + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + metaStore->releaseFile(getEntryInfo().getParentEntryID(), inode); + return boost::make_unique(FhgfsOpsErr_SUCCESS); +} + +void BumpFileVersionMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + unsetMsgHeaderFeatureFlag(BUMPFILEVERSIONMSG_FLAG_HASEVENT); + sendToSecondary(ctx, *this, NETMSGTYPE_BumpFileVersionResp); +} diff --git a/meta/source/net/message/session/BumpFileVersionMsgEx.h b/meta/source/net/message/session/BumpFileVersionMsgEx.h new file mode 100644 index 0000000..b523984 --- /dev/null +++ b/meta/source/net/message/session/BumpFileVersionMsgEx.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class BumpFileVersionMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState + ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo().getIsBuddyMirrored(); } + + private: + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "BumpFileVersionMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/GetFileVersionMsgEx.cpp b/meta/source/net/message/session/GetFileVersionMsgEx.cpp new file mode 100644 index 0000000..527f9ed --- /dev/null +++ b/meta/source/net/message/session/GetFileVersionMsgEx.cpp @@ -0,0 +1,35 @@ +#include "GetFileVersionMsgEx.h" +#include + +bool GetFileVersionMsgEx::processIncoming(ResponseContext& ctx) +{ + LOG_DBG(SESSIONS, DEBUG, "", getEntryInfo().getEntryID(), getEntryInfo().getIsBuddyMirrored()); + return BaseType::processIncoming(ctx); +} + +FileIDLock GetFileVersionMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo().getEntryID(), false}; +} + +std::unique_ptr GetFileVersionMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + GetFileVersionMsgResponseState resp; + + auto& metaStore = *Program::getApp()->getMetaStore(); + auto [inode, referenceRes] = metaStore.referenceFile(&getEntryInfo()); + if (!inode) + { + // The GetFileVersionMsgResponseState constructor sets default values for 'result' and + // 'version', indicating an error condition, eliminating the need to specify them + // separately. Hence, returning a ResponseState with the moved 'resp'. + return boost::make_unique(std::move(resp)); + } + + resp.setGetFileVersionResult(FhgfsOpsErr_SUCCESS); + resp.setFileVersion(inode->getFileVersion()); + metaStore.releaseFile(getEntryInfo().getParentEntryID(), inode); + + return boost::make_unique(std::move(resp)); +} diff --git a/meta/source/net/message/session/GetFileVersionMsgEx.h b/meta/source/net/message/session/GetFileVersionMsgEx.h new file mode 100644 index 0000000..9840a16 --- /dev/null +++ b/meta/source/net/message/session/GetFileVersionMsgEx.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include +#include + +class GetFileVersionMsgResponseState : public MirroredMessageResponseState +{ + public: + GetFileVersionMsgResponseState() : result(FhgfsOpsErr_INTERNAL), version(0) + { + } + + GetFileVersionMsgResponseState(GetFileVersionMsgResponseState&& other) : + result(other.result), + version(other.version) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + GetFileVersionRespMsg resp(result, version); + ctx.sendResponse(resp); + } + + // GetFileVersionMsgEx is transformed into a mirrored message to leverage + // MirroredMessage::lock(), preventing races with operations such as unlink. + // However, forwarding this message to the secondary is unnecessary. + // Overriding the changeObservableState() function to always return false ensures + // that this message is never forwarded unnecessarily. + bool changesObservableState() const override + { + return false; + } + + void setGetFileVersionResult(FhgfsOpsErr result) { this->result = result; } + void setFileVersion(uint32_t version) { this->version = version; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_GetFileVersion; } + void serializeContents(Serializer& ser) const override {} + + private: + FhgfsOpsErr result; + uint32_t version; +}; + +class GetFileVersionMsgEx : public MirroredMessage +{ + public: + typedef GetFileVersionMsgResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + bool isMirrored() override + { + return getEntryInfo().getIsBuddyMirrored(); + } + + private: + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override {} + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + + const char* mirrorLogContext() const override { return "GetFileVersionMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/locking/FLockAppendMsgEx.cpp b/meta/source/net/message/session/locking/FLockAppendMsgEx.cpp new file mode 100644 index 0000000..f7717c8 --- /dev/null +++ b/meta/source/net/message/session/locking/FLockAppendMsgEx.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include +#include +#include "FLockAppendMsgEx.h" + + +bool FLockAppendMsgEx::processIncoming(ResponseContext& ctx) +{ + /* note: this code is very similar to FLockRangeMsgEx::processIncoming(), so if you change + something here, you probably want to change it there, too. */ + +#ifdef BEEGFS_DEBUG + const char* logContext = "FLockAppendMsg incoming"; +#endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_DEBUG, "entryID: " + this->getEntryInfo()->getEntryID()); + + clientResult = FhgfsOpsErr_INTERNAL; + return BaseType::processIncoming(ctx); +} + +FileIDLock FLockAppendMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr FLockAppendMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + const char* logContext = "FLock Append Msg"; + + unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() ); + + EntryLockDetails lockDetails(getClientNumID(), getClientFD(), getOwnerPID(), getLockAckID(), + getLockTypeFlags() ); + + LogContext(logContext).log(Log_DEBUG, lockDetails.toString()); + + EntryInfo* entryInfo = getEntryInfo(); + clientResult = MsgHelperLocking::flockAppend(entryInfo, ownerFD, lockDetails); + + LogContext(logContext).log(Log_DEBUG, "FLock Append result: " + std::to_string(clientResult)); + + Program::getApp()->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + MetaOpCounter_FLOCKAPPEND, getMsgHeaderUserID() ); + + return boost::make_unique(clientResult); +} + +void FLockAppendMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_FLockAppendResp, clientResult); +} diff --git a/meta/source/net/message/session/locking/FLockAppendMsgEx.h b/meta/source/net/message/session/locking/FLockAppendMsgEx.h new file mode 100644 index 0000000..837204b --- /dev/null +++ b/meta/source/net/message/session/locking/FLockAppendMsgEx.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include +#include + +class FLockAppendMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + FhgfsOpsErr clientResult; + + void forwardToSecondary(ResponseContext& ctx) override; + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "FLockAppendMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/locking/FLockEntryMsgEx.cpp b/meta/source/net/message/session/locking/FLockEntryMsgEx.cpp new file mode 100644 index 0000000..eee9654 --- /dev/null +++ b/meta/source/net/message/session/locking/FLockEntryMsgEx.cpp @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include +#include "FLockEntryMsgEx.h" + + +bool FLockEntryMsgEx::processIncoming(ResponseContext& ctx) +{ + /* note: this code is very similar to FLockRangeMsgEx::processIncoming(), so if you change + something here, you probably want to change it there, too. */ + + clientResult = FhgfsOpsErr_INTERNAL; + + updateNodeOp(ctx, MetaOpCounter_FLOCKENTRY); + + return BaseType::processIncoming(ctx); +} + +FileIDLock FLockEntryMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr FLockEntryMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() ); + + EntryLockDetails lockDetails(getClientNumID(), getClientFD(), getOwnerPID(), getLockAckID(), + getLockTypeFlags() ); + + LOG_DBG(GENERAL, SPAM, lockDetails.toString()); + + EntryInfo* entryInfo = getEntryInfo(); + + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? app->getMirroredSessions() + : app->getSessions(); + + // find sessionFile + Session* session = sessions->referenceSession(getClientNumID(), true); + SessionFileStore* sessionFiles = session->getFiles(); + + SessionFile* sessionFile = sessionFiles->referenceSession(ownerFD); + if(!sessionFile) + { // sessionFile not exists (mds restarted?) + + // check if this is just an UNLOCK REQUEST + + if(getLockTypeFlags() & ENTRYLOCKTYPE_UNLOCK) + { // it's an unlock => we'll just ignore it (since the locks are gone anyways) + clientResult = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // check if this is a LOCK CANCEL REQUEST + + if(getLockTypeFlags() & ENTRYLOCKTYPE_CANCEL) + { // it's a lock cancel + /* this is an important special case, because client might have succeeded in closing the + file but the conn might have been interrupted during unlock, so we definitely have to try + canceling the lock here */ + + // if the file still exists, just do the lock cancel without session recovery attempt + + auto [lockCancelFile, referenceRes] = metaStore->referenceFile(entryInfo); + if(lockCancelFile) + { + lockCancelFile->flockEntry(lockDetails); + metaStore->releaseFile(entryInfo->getParentEntryID(), lockCancelFile); + } + + clientResult = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // it's a LOCK REQUEST => try to recover session file to do the locking + + clientResult = MsgHelperLocking::trySesssionRecovery(entryInfo, getClientNumID(), + ownerFD, sessionFiles, &sessionFile); + + // (note: sessionFile==NULL now if recovery was not successful) + + } // end of session file session recovery attempt + + if(sessionFile) + { // sessionFile exists (or was successfully recovered) + auto& file = sessionFile->getInode(); + + auto lockGranted = file->flockEntry(lockDetails); + if (!lockGranted.first) + clientResult = FhgfsOpsErr_WOULDBLOCK; + else + clientResult = FhgfsOpsErr_SUCCESS; + + if ((getLockTypeFlags() & ENTRYLOCKTYPE_UNLOCK) && !file->incrementFileVersion(entryInfo)) + { + LOG(GENERAL, ERR, "Could not bump file version.", file->getEntryID()); + sessionFiles->releaseSession(sessionFile, entryInfo); + clientResult = FhgfsOpsErr_INTERNAL; + goto cleanup_session; + } + + if (!lockGranted.second.empty() && !isSecondary) + LockingNotifier::notifyWaitersEntryLock(LockEntryNotifyType_FLOCK, + file->getReferenceParentID(), file->getEntryID(), file->getIsBuddyMirrored(), + std::move(lockGranted.second)); + + // cleanup + sessionFiles->releaseSession(sessionFile, entryInfo); + } + + + // cleanup +cleanup_session: + sessions->releaseSession(session); + + LOG_DBG(GENERAL, SPAM, "", getClientNumID(), clientResult); + + return boost::make_unique(clientResult); +} + +void FLockEntryMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_FLockEntryResp, clientResult); +} diff --git a/meta/source/net/message/session/locking/FLockEntryMsgEx.h b/meta/source/net/message/session/locking/FLockEntryMsgEx.h new file mode 100644 index 0000000..1854849 --- /dev/null +++ b/meta/source/net/message/session/locking/FLockEntryMsgEx.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class FLockEntryMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + FhgfsOpsErr clientResult; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "FLockEntryMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/locking/FLockRangeMsgEx.cpp b/meta/source/net/message/session/locking/FLockRangeMsgEx.cpp new file mode 100644 index 0000000..8b76c1d --- /dev/null +++ b/meta/source/net/message/session/locking/FLockRangeMsgEx.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include +#include +#include +#include +#include "FLockRangeMsgEx.h" + + +bool FLockRangeMsgEx::processIncoming(ResponseContext& ctx) +{ + /* note: this code is very similar to FLockEntryMsgEx::processIncoming(), so if you change + something here, you probably want to change it there, too. */ + + clientResult = FhgfsOpsErr_INTERNAL; + + updateNodeOp(ctx, MetaOpCounter_FLOCKRANGE); + + return BaseType::processIncoming(ctx); +} + +FileIDLock FLockRangeMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr FLockRangeMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() ); + + RangeLockDetails lockDetails(getClientNumID(), getOwnerPID(), getLockAckID(), + getLockTypeFlags(), getStart(), getEnd() ); + + LOG_DBG(GENERAL, SPAM, lockDetails.toString()); + + EntryInfo* entryInfo = getEntryInfo(); + + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? app->getMirroredSessions() + : app->getSessions(); + + // find sessionFile + Session* session = sessions->referenceSession(getClientNumID(), true); + SessionFileStore* sessionFiles = session->getFiles(); + + SessionFile* sessionFile = sessionFiles->referenceSession(ownerFD); + if(!sessionFile) + { // sessionFile not exists (mds restarted?) + + // check if this is just an UNLOCK REQUEST + + if(getLockTypeFlags() & ENTRYLOCKTYPE_LOCKOPS_REMOVE) + { // it's an unlock => we'll just ignore it (since the locks are gone anyways) + clientResult = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // check if this is a LOCK CANCEL REQUEST + + if(getLockTypeFlags() & ENTRYLOCKTYPE_CANCEL) + { // it's a lock cancel + /* this is an important special case, because client might have succeeded in closing the + file but the conn might have been interrupted during unlock, so we definitely have to try + canceling the lock here */ + + // if the file still exists, just do the lock cancel without session recovery attempt + + auto [lockCancelFile, referenceRes] = metaStore->referenceFile(entryInfo); + if(lockCancelFile) + { + lockCancelFile->flockRange(lockDetails); + metaStore->releaseFile(entryInfo->getParentEntryID(), lockCancelFile); + } + + clientResult = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // it's a LOCK REQUEST => try to recover session file to do the locking + + clientResult = MsgHelperLocking::trySesssionRecovery(entryInfo, getClientNumID(), + ownerFD, sessionFiles, &sessionFile); + + // (note: sessionFile==NULL now if recovery was not successful) + + } // end of session file session recovery attempt + + if(sessionFile) + { // sessionFile exists (or was successfully recovered) + auto& file = sessionFile->getInode(); + + auto lockGranted = file->flockRange(lockDetails); + if (!lockGranted.first) + clientResult = FhgfsOpsErr_WOULDBLOCK; + else + clientResult = FhgfsOpsErr_SUCCESS; + + if ((getLockTypeFlags() & ENTRYLOCKTYPE_UNLOCK) && !file->incrementFileVersion(entryInfo)) + { + LOG(GENERAL, ERR, "Could not bump file version.", file->getEntryID()); + sessionFiles->releaseSession(sessionFile, entryInfo); + clientResult = FhgfsOpsErr_INTERNAL; + goto cleanup_session; + } + + if (!lockGranted.second.empty() && !isSecondary) + LockingNotifier::notifyWaitersRangeLock(file->getReferenceParentID(), file->getEntryID(), + file->getIsBuddyMirrored(), std::move(lockGranted.second)); + + // cleanup + sessionFiles->releaseSession(sessionFile, entryInfo); + } + + + // cleanup +cleanup_session: + sessions->releaseSession(session); + + LOG_DBG(GENERAL, SPAM, "", clientResult); + + return boost::make_unique(clientResult); +} + +void FLockRangeMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_FLockRangeResp, clientResult); +} diff --git a/meta/source/net/message/session/locking/FLockRangeMsgEx.h b/meta/source/net/message/session/locking/FLockRangeMsgEx.h new file mode 100644 index 0000000..e5a5bd7 --- /dev/null +++ b/meta/source/net/message/session/locking/FLockRangeMsgEx.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class FLockRangeMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + FhgfsOpsErr clientResult; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "FLockRangeMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/opening/CloseFileMsgEx.cpp b/meta/source/net/message/session/opening/CloseFileMsgEx.cpp new file mode 100644 index 0000000..26d1824 --- /dev/null +++ b/meta/source/net/message/session/opening/CloseFileMsgEx.cpp @@ -0,0 +1,246 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CloseFileMsgEx.h" + +FileIDLock CloseFileMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +bool CloseFileMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "CloseFileMsg incoming"; +#endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_DEBUG, + "BuddyMirrored: " + std::string(getEntryInfo()->getIsBuddyMirrored() ? "Yes" : "No") + + " Secondary: " + + std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No") ); + + // update operation counters (here on top because we have an early sock release in this msg) + updateNodeOp(ctx, MetaOpCounter_CLOSE); + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr CloseFileMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + if (isSecondary) + return closeFileSecondary(ctx); + else + return closeFilePrimary(ctx); +} + +std::unique_ptr CloseFileMsgEx::closeFilePrimary( + ResponseContext& ctx) +{ + FhgfsOpsErr closeRes; + bool unlinkDisposalFile = false; + bool outLastWriterClosed = false; + EntryInfo* entryInfo = getEntryInfo(); + unsigned numHardlinks; + bool closeSucceeded; + + if(isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS) ) + { // client requests cleanup of granted or pending locks for this handle + + unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() ); + EntryLockDetails lockDetails(getClientNumID(), 0, 0, "", ENTRYLOCKTYPE_CANCEL); + + MsgHelperLocking::flockAppend(entryInfo, ownerFD, lockDetails); + } + + /* two alternatives: + 1) early response before chunk file close (if client isn't interested in chunks result). + 2) normal response after chunk file close. */ + + // if we are buddy mirrored, *do not* allow early close responses. since the primary will close + // the chunk files (and update the inodes dynamic attributes), the secondary cannot do that. + // thus we need to close all chunks, get the dynamic attributes, and push them to the secondary + if (getEntryInfo()->getIsBuddyMirrored()) + unsetMsgHeaderFeatureFlag(CLOSEFILEMSG_FLAG_EARLYRESPONSE); + + if(isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_EARLYRESPONSE) ) + { // alternative 1: client requests an early response + + /* note: linux won't even return the vfs release() result to userspace apps, so there's + usually no point in waiting for the chunk file close result before sending the response */ + + unsigned accessFlags; + MetaFileHandle inode; + + closeRes = MsgHelperClose::closeSessionFile( + getClientNumID(), getFileHandleID(), entryInfo, &accessFlags, inode); + closeSucceeded = closeRes == FhgfsOpsErr_SUCCESS; + + // send response + earlyComplete(ctx, ResponseState(closeRes)); + + // if session file close succeeds but chunk file close fails we should not attempt to + // dispose. if chunk close failed for any other reason than network outages we might + // put the storage server into an even weirder state by unlinking the chunk file: + // the file itself is still open (has a session), but is gone on disk, and thus cannot + // be reopened from stored sessions when the server is restarted. + if(likely(closeRes == FhgfsOpsErr_SUCCESS) ) + closeRes = closeFileAfterEarlyResponse(std::move(inode), accessFlags, &unlinkDisposalFile, + numHardlinks, outLastWriterClosed); + } + else + { // alternative 2: normal response (after chunk file close) + + closeRes = MsgHelperClose::closeFile(getClientNumID(), getFileHandleID(), + entryInfo, getMaxUsedNodeIndex(), getMsgHeaderUserID(), &unlinkDisposalFile, + &numHardlinks, outLastWriterClosed, &dynAttribs, &inodeTimestamps); + + closeSucceeded = closeRes == FhgfsOpsErr_SUCCESS; + + if (getEntryInfo()->getIsBuddyMirrored() && getMaxUsedNodeIndex() >= 0) + addMsgHeaderFeatureFlag(CLOSEFILEMSG_FLAG_DYNATTRIBS); + + //Avoid sending early response. Let unlink of Disposal file happens. + //with Locks held. But make sure before file gets unlink we synchronise + //the operation with any on-going buddy mirror resync operation. + ResponseState responseState(closeRes); + buddyResyncNotify(ctx, responseState.changesObservableState()); + } + + if (closeSucceeded && Program::getApp()->getFileEventLogger() && getFileEvent()) + { + // Important Note: + // - If the last writer, who previously opened this file with write permissions, + // is currently closing it, + // - And it is not marked for disposal, + // - And it is not due to a symlink creation, + // Then we update the event type to LAST_WRITER_CLOSED. + bool isSymlinkEvent = getFileEvent()->type == FileEventType::SYMLINK; + if (!isSymlinkEvent && outLastWriterClosed && !unlinkDisposalFile) + { + auto fileEvent = const_cast(getFileEvent()); + fileEvent->type = FileEventType::LAST_WRITER_CLOSED; + } + + EventContext eventCtx = makeEventContext( + entryInfo, + entryInfo->getParentEntryID(), + getMsgHeaderUserID(), + "", // targetParentID + numHardlinks, + false // This is not the secondary node if closeFilePrimary() was called. + ); + + logEvent(Program::getApp()->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + // unlink if file marked as disposable + if( (closeRes == FhgfsOpsErr_SUCCESS) && unlinkDisposalFile) + { // check whether file has been unlinked (and perform the unlink operation on last close) + + /* note: we do this only if also the chunk file close succeeded, because if storage servers + are down, unlinkDisposableFile() will keep the file in the disposal store anyways */ + + // this only touches timestamps on the disposal dirinode, which is not visible to the user, + // so no need to fix up timestamps that have diverged between primary and secondary + MsgHelperClose::unlinkDisposableFile(entryInfo->getEntryID(), getMsgHeaderUserID(), + entryInfo->getIsBuddyMirrored()); + } + + //for alternative 2: forward the operation to secondary + //after unlinkDisposalFile on primary is complete. + if(!isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_EARLYRESPONSE)) + return boost::make_unique(closeRes); + + return {}; +} + +std::unique_ptr CloseFileMsgEx::closeFileSecondary( + ResponseContext& ctx) +{ + if (isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_CANCELAPPENDLOCKS)) + { + // client requests cleanup of granted or pending locks for this handle + unsigned ownerFD = SessionTk::ownerFDFromHandleID(getFileHandleID() ); + EntryLockDetails lockDetails(getClientNumID(), 0, 0, "", ENTRYLOCKTYPE_CANCEL); + + MsgHelperLocking::flockAppend(getEntryInfo(), ownerFD, lockDetails); + } + + // on secondary we only need to close the session and the meta file, because the chunk files + // will be closed by primary + + unsigned accessFlags; + MetaFileHandle inode; + + FhgfsOpsErr closeRes = MsgHelperClose::closeSessionFile( + getClientNumID(), getFileHandleID(), getEntryInfo(), &accessFlags, inode); + + // the file may not be open on the secondary, in which case inode == nullptr + if (closeRes != FhgfsOpsErr_SUCCESS) + return boost::make_unique(closeRes); + + // maybe assert? + if (isMsgHeaderFeatureFlagSet(CLOSEFILEMSG_FLAG_DYNATTRIBS)) + inode->setDynAttribs(dynAttribs); + + fixInodeTimestamp(*inode, inodeTimestamps, getEntryInfo()); + + unsigned numHardlinks; + unsigned numInodeRefs; + bool outLastWriterClosed; + + Program::getApp()->getMetaStore()->closeFile(getEntryInfo(), std::move(inode), accessFlags, + &numHardlinks, &numInodeRefs, outLastWriterClosed); + + // unlink if file marked as disposable + // this only touches timestamps on the disposal dirinode, which is not visible to the user, + // so no need to fix up timestamps that have diverged between primary and secondary + if( (closeRes == FhgfsOpsErr_SUCCESS) && (!numHardlinks) && (!numInodeRefs)) + MsgHelperClose::unlinkDisposableFile(getEntryInfo()->getEntryID(), getMsgHeaderUserID(), + getEntryInfo()->getIsBuddyMirrored()); + + return boost::make_unique(closeRes); +} + +/** + * The rest after MsgHelperClose::closeSessionFile(), i.e. MsgHelperClose::closeChunkFile() + * and metaStore->closeFile(). + * + * @param inode inode for closed file (as returned by MsgHelperClose::closeSessionFile() ) + * @param maxUsedNodeIndex zero-based index, -1 means "none" + * @param outFileWasUnlinked true if the hardlink count of the file was 0 + */ +FhgfsOpsErr CloseFileMsgEx::closeFileAfterEarlyResponse(MetaFileHandle inode, unsigned accessFlags, + bool* outUnlinkDisposalFile, unsigned& outNumHardlinks, bool& outLastWriterClosed) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + unsigned numInodeRefs; + + *outUnlinkDisposalFile = false; + + FhgfsOpsErr chunksRes = MsgHelperClose::closeChunkFile( + getClientNumID(), getFileHandleID(), getMaxUsedNodeIndex(), *inode, getEntryInfo(), + getMsgHeaderUserID(), NULL); + + metaStore->closeFile(getEntryInfo(), std::move(inode), accessFlags, &outNumHardlinks, + &numInodeRefs, outLastWriterClosed); + + if (!outNumHardlinks && !numInodeRefs) + *outUnlinkDisposalFile = true; + + return chunksRes; +} + +void CloseFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_CloseFileResp); +} diff --git a/meta/source/net/message/session/opening/CloseFileMsgEx.h b/meta/source/net/message/session/opening/CloseFileMsgEx.h new file mode 100644 index 0000000..90ec4a9 --- /dev/null +++ b/meta/source/net/message/session/opening/CloseFileMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class CloseFileMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr closeFilePrimary(ResponseContext& ctx); + std::unique_ptr closeFileSecondary(ResponseContext& ctx); + void forwardToSecondary(ResponseContext& ctx) override; + FhgfsOpsErr closeFileAfterEarlyResponse(MetaFileHandle inode, unsigned accessFlags, + bool* outUnlinkDisposalFile, unsigned& numHardlinks, bool& outLastWriterClosed); + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "CloseFileMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/session/opening/OpenFileMsgEx.cpp b/meta/source/net/message/session/opening/OpenFileMsgEx.cpp new file mode 100644 index 0000000..afda476 --- /dev/null +++ b/meta/source/net/message/session/opening/OpenFileMsgEx.cpp @@ -0,0 +1,157 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "OpenFileMsgEx.h" + +FileIDLock OpenFileMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +bool OpenFileMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "OpenFileMsg incoming"; + + EntryInfo* entryInfo = getEntryInfo(); + + LOG_DEBUG(logContext, Log_SPAM, "ParentInfo: " + entryInfo->getParentEntryID() + + " EntryID: " + entryInfo->getEntryID() + " FileName: " + entryInfo->getFileName() ); + + LOG_DEBUG(logContext, Log_DEBUG, + "BuddyMirrored: " + std::string(entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + + " Secondary: " + + std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No") ); +#endif // BEEGFS_DEBUG + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr OpenFileMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + + EntryInfo* entryInfo = this->getEntryInfo(); + bool useQuota = isMsgHeaderFeatureFlagSet(OPENFILEMSG_FLAG_USE_QUOTA); + bool bypassAccessCheck = isMsgHeaderFeatureFlagSet(OPENFILEMSG_FLAG_BYPASS_ACCESS_CHECK); + + const bool eventLoggingEnabled = !isSecondary && app->getFileEventLogger() && getFileEvent(); + MetaFileHandle inode; + + PathInfo pathInfo; + StripePattern* pattern; + unsigned sessionFileID; + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? app->getMirroredSessions() + : app->getSessions(); + + FhgfsOpsErr openRes = MsgHelperOpen::openFile( + entryInfo, getAccessFlags(), useQuota, bypassAccessCheck, getMsgHeaderUserID(), + inode, isSecondary); + + if (openRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*inode, fileTimestamps, entryInfo); + + // update operation counters + updateNodeOp(ctx, MetaOpCounter_OPEN); + + if (openRes != FhgfsOpsErr_SUCCESS) + { + // If open() fails due to file state restrictions, emit an OPEN_BLOCKED event + // (when event logging is enabled). The numHardlinks in event context will be 0 + // since inode is nullptr after access denial. + if ((openRes == FhgfsOpsErr_FILEACCESS_DENIED) && eventLoggingEnabled) + { + FileEvent* fileEvent = const_cast(getFileEvent()); + fileEvent->type = FileEventType::OPEN_BLOCKED; + + EventContext eventCtx = makeEventContext( + entryInfo, + entryInfo->getParentEntryID(), + getMsgHeaderUserID(), + "", + inode ? inode->getNumHardlinks() : 0, + isSecondary + ); + logEvent(app->getFileEventLogger(), *fileEvent, eventCtx); + } + + // error occurred + Raid0Pattern dummyPattern(1, UInt16Vector{}); + + // generate response + if(unlikely(openRes == FhgfsOpsErr_COMMUNICATION)) + { + return boost::make_unique(); + } + else + { // normal response + return boost::make_unique(openRes, std::string(), dummyPattern, + pathInfo, 0); + } + } + + if (eventLoggingEnabled) + { + EventContext eventCtx = makeEventContext( + entryInfo, + entryInfo->getParentEntryID(), + getMsgHeaderUserID(), + "", + inode->getNumHardlinks(), + isSecondary + ); + + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + // success => insert session + SessionFile* sessionFile = new SessionFile(std::move(inode), getAccessFlags(), entryInfo); + + Session* session = sessions->referenceSession(getClientNumID(), true); + + pattern = sessionFile->getInode()->getStripePattern(); + + if (!isSecondary) + { + sessionFileID = session->getFiles()->addSession(sessionFile); + fileHandleID = SessionTk::generateFileHandleID(sessionFileID, entryInfo->getEntryID() ); + + setSessionFileID(sessionFileID); + setFileHandleID(fileHandleID.c_str()); + } + else + { + fileHandleID = getFileHandleID(); + sessionFileID = getSessionFileID(); + + bool addRes = session->getFiles()->addSession(sessionFile, sessionFileID); + + if (!addRes) + { + const char* logContext = "OpenFileMsgEx (executeLocally)"; + LogContext(logContext).log(Log_NOTICE, + "Couldn't add sessionFile on secondary buddy; sessionID: " + getClientNumID().str() + + "; sessionFileID: " + StringTk::uintToStr(sessionFileID)); + } + } + + sessions->releaseSession(session); + + sessionFile->getInode()->getPathInfo(&pathInfo); + + return boost::make_unique(openRes, fileHandleID, *pattern, pathInfo, + sessionFile->getInode()->getFileVersion()); +} + +void OpenFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_OpenFileResp); +} diff --git a/meta/source/net/message/session/opening/OpenFileMsgEx.h b/meta/source/net/message/session/opening/OpenFileMsgEx.h new file mode 100644 index 0000000..9cf5602 --- /dev/null +++ b/meta/source/net/message/session/opening/OpenFileMsgEx.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class OpenFileResponseState : public MirroredMessageResponseState +{ + public: + OpenFileResponseState() + : isIndirectCommErr(true) + { + } + + explicit OpenFileResponseState(Deserializer& des) + { + serialize(this, des); + } + + OpenFileResponseState(FhgfsOpsErr result, const std::string& fileHandleID, + const StripePattern& pattern, const PathInfo& pathInfo, uint32_t fileVersion) + : isIndirectCommErr(false), result(result), fileHandleID(fileHandleID), + pattern(pattern.clone()), pathInfo(pathInfo), fileVersion(fileVersion) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + if (isIndirectCommErr) + ctx.sendResponse( + GenericResponseMsg( + GenericRespMsgCode_INDIRECTCOMMERR, + "Communication with storage targets failed")); + else + ctx.sendResponse( + OpenFileRespMsg( + result, fileHandleID, pattern.get(), &pathInfo, fileVersion)); + } + + bool changesObservableState() const override + { + return !isIndirectCommErr && result == FhgfsOpsErr_SUCCESS; + } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_OpenFile; } + + template + static void serialize(This* obj, Ctx& ctx) + { + ctx + % obj->isIndirectCommErr; + + if (!obj->isIndirectCommErr) + ctx + % serdes::as(obj->result) + % obj->fileHandleID + % obj->pattern + % obj->pathInfo; + } + + void serializeContents(Serializer& ser) const override + { + serialize(this, ser); + } + + private: + bool isIndirectCommErr; + + FhgfsOpsErr result; + std::string fileHandleID; + std::unique_ptr pattern; + PathInfo pathInfo; + uint32_t fileVersion; +}; + +class OpenFileMsgEx : public MirroredMessage +{ + public: + typedef OpenFileResponseState ResponseState; + + bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + void forwardToSecondary(ResponseContext& ctx) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "OpenFileMsgEx/forward"; } + + std::string fileHandleID; +}; + diff --git a/meta/source/net/message/storage/GetHighResStatsMsgEx.cpp b/meta/source/net/message/storage/GetHighResStatsMsgEx.cpp new file mode 100644 index 0000000..c87ebc0 --- /dev/null +++ b/meta/source/net/message/storage/GetHighResStatsMsgEx.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include "GetHighResStatsMsgEx.h" + + +bool GetHighResStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + HighResStatsList statsHistory; + uint64_t lastStatsMS = getValue(); + + // get stats history + StatsCollector* statsCollector = Program::getApp()->getStatsCollector(); + statsCollector->getStatsSince(lastStatsMS, statsHistory); + + ctx.sendResponse(GetHighResStatsRespMsg(&statsHistory) ); + + return true; +} + diff --git a/meta/source/net/message/storage/GetHighResStatsMsgEx.h b/meta/source/net/message/storage/GetHighResStatsMsgEx.h new file mode 100644 index 0000000..31ef4d7 --- /dev/null +++ b/meta/source/net/message/storage/GetHighResStatsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + + +class GetHighResStatsMsgEx : public GetHighResStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/storage/StatStoragePathMsgEx.cpp b/meta/source/net/message/storage/StatStoragePathMsgEx.cpp new file mode 100644 index 0000000..e9a77c3 --- /dev/null +++ b/meta/source/net/message/storage/StatStoragePathMsgEx.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include "StatStoragePathMsgEx.h" + + +bool StatStoragePathMsgEx::processIncoming(ResponseContext& ctx) +{ + int64_t sizeTotal = 0; + int64_t sizeFree = 0; + int64_t inodesTotal = 0; + int64_t inodesFree = 0; + + FhgfsOpsErr statRes = statStoragePath(&sizeTotal, &sizeFree, &inodesTotal, &inodesFree); + + ctx.sendResponse(StatStoragePathRespMsg(statRes, sizeTotal, sizeFree, inodesTotal, inodesFree) ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_STATFS, + getMsgHeaderUserID() ); + + return true; +} + +FhgfsOpsErr StatStoragePathMsgEx::statStoragePath(int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree) +{ + const char* logContext = "StatStoragePathMsg (stat path)"; + + std::string pathStr = Program::getApp()->getMetaPath(); + + bool statSuccess = StorageTk::statStoragePath( + pathStr, outSizeTotal, outSizeFree, outInodesTotal, outInodesFree); + + if(unlikely(!statSuccess) ) + { // error + LogContext(logContext).logErr("Unable to statfs() storage path: " + pathStr + + " (SysErr: " + System::getErrString() ); + + return FhgfsOpsErr_INTERNAL; + } + + + StorageTk::statStoragePathOverride(pathStr, outSizeFree, outInodesFree); + + return FhgfsOpsErr_SUCCESS; +} + + diff --git a/meta/source/net/message/storage/StatStoragePathMsgEx.h b/meta/source/net/message/storage/StatStoragePathMsgEx.h new file mode 100644 index 0000000..da194bf --- /dev/null +++ b/meta/source/net/message/storage/StatStoragePathMsgEx.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +// derives from config option "StoragePath" and actually is similar to statfs() + +class StatStoragePathMsgEx : public StatStoragePathMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr statStoragePath(int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree); +}; + + diff --git a/meta/source/net/message/storage/TruncFileMsgEx.cpp b/meta/source/net/message/storage/TruncFileMsgEx.cpp new file mode 100644 index 0000000..9d720be --- /dev/null +++ b/meta/source/net/message/storage/TruncFileMsgEx.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include "TruncFileMsgEx.h" +#include + +FileIDLock TruncFileMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +bool TruncFileMsgEx::processIncoming(ResponseContext& ctx) +{ + return BaseType::processIncoming(ctx); +} + +std::unique_ptr TruncFileMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + // update operation counters + updateNodeOp(ctx, MetaOpCounter_TRUNCATE); + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (!isSecondary) + { + FhgfsOpsErr truncRes = MsgHelperTrunc::truncFile(getEntryInfo(), getFilesize(), + isMsgHeaderFeatureFlagSet(TRUNCFILEMSG_FLAG_USE_QUOTA), getMsgHeaderUserID(), + dynAttribs); + if (truncRes == FhgfsOpsErr_SUCCESS && (shouldFixTimestamps() || getFileEvent())) + { + auto [inode, referenceRes] = metaStore->referenceFile(getEntryInfo()); + unsigned numHardlinks = 0; + + if (likely(inode)) + { + if (shouldFixTimestamps()) + fixInodeTimestamp(*inode, mirroredTimestamps, nullptr); + + numHardlinks = inode->getNumHardlinks(); + metaStore->releaseFile(getEntryInfo()->getParentEntryID(), inode); + } + + if (app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext( + getEntryInfo(), + getEntryInfo()->getParentEntryID(), + getMsgHeaderUserID(), + "", + numHardlinks, + isSecondary + ); + + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + } + return boost::make_unique(truncRes); + } + + auto [inode, referenceRes] = metaStore->referenceFile(getEntryInfo()); + if(!inode) + return boost::make_unique(referenceRes); + + inode->setDynAttribs(dynAttribs); + if (shouldFixTimestamps()) + fixInodeTimestamp(*inode, mirroredTimestamps, nullptr); + inode->updateInodeOnDisk(getEntryInfo()); + + metaStore->releaseFile(getEntryInfo()->getParentEntryID(), inode); + + return boost::make_unique(FhgfsOpsErr_SUCCESS); +} + +void TruncFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_TruncFileResp); +} diff --git a/meta/source/net/message/storage/TruncFileMsgEx.h b/meta/source/net/message/storage/TruncFileMsgEx.h new file mode 100644 index 0000000..c8c6ca4 --- /dev/null +++ b/meta/source/net/message/storage/TruncFileMsgEx.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include +#include +#include + + +class TruncFileMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "TruncFileMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.cpp b/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.cpp new file mode 100644 index 0000000..4cd8395 --- /dev/null +++ b/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.cpp @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include "GetEntryInfoMsgEx.h" + + +bool GetEntryInfoMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "GetEntryInfoMsg incoming"; +#endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_SPAM, + "ParentEntryID: " + this->getEntryInfo()->getParentEntryID() + "; " + "entryID: " + this->getEntryInfo()->getParentEntryID() ); + + return BaseType::processIncoming(ctx); +} + +FileIDLock GetEntryInfoMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), false}; +} + +std::unique_ptr GetEntryInfoMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + + GetEntryInfoMsgResponseState resp; + + EntryInfo* entryInfo = this->getEntryInfo(); + StripePattern* pattern = NULL; + FhgfsOpsErr getInfoRes; + PathInfo pathInfo; + RemoteStorageTarget rstInfo; + uint32_t numSessionsRead = 0; + uint32_t numSessionsWrite = 0; + uint8_t dataState = 0; + + if (entryInfo->getParentEntryID().empty()) + { + // special case: get info for root directory + // no pathInfo here, as this is currently only used for fileInodes + getInfoRes = getRootInfo(&pattern, &rstInfo); + } + else + { + getInfoRes = getInfo(entryInfo, &pattern, &pathInfo, &rstInfo, numSessionsRead, + numSessionsWrite, dataState); + } + + if (getInfoRes != FhgfsOpsErr_SUCCESS) + { // error occurred => create a dummy pattern + UInt16Vector dummyStripeNodes; + pattern = new Raid0Pattern(1, dummyStripeNodes); + } + + resp.setGetEntryInfoResult(getInfoRes); + resp.setStripePattern(pattern); + resp.setPathInfo(pathInfo); + resp.setRemoteStorageTarget(rstInfo); + resp.setNumSessionsRead(numSessionsRead); + resp.setNumSessionsWrite(numSessionsWrite); + resp.setFileDataState(dataState); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_GETENTRYINFO, + getMsgHeaderUserID() ); + + return boost::make_unique(std::move(resp)); +} + + +/** + * @param outPattern StripePattern clone (in case of success), that must be deleted by the caller + */ +FhgfsOpsErr GetEntryInfoMsgEx::getInfo(EntryInfo* entryInfo, StripePattern** outPattern, + PathInfo* outPathInfo, RemoteStorageTarget* outRstInfo, uint32_t& outNumReadSessions, + uint32_t& outNumWriteSessions, uint8_t& outDataState) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (entryInfo->getEntryType() == DirEntryType_DIRECTORY) + { // entry is a directory + DirInode* dir = metaStore->referenceDir(entryInfo->getEntryID(), + entryInfo->getIsBuddyMirrored(), true); + if(dir) + { + *outPattern = dir->getStripePatternClone(); + outRstInfo->set(dir->getRemoteStorageTargetInfo()); + metaStore->releaseDir(entryInfo->getEntryID() ); + return FhgfsOpsErr_SUCCESS; + } + } + else + { // entry is a file + auto [fileInode, referenceRes] = metaStore->referenceFile(entryInfo); + if(fileInode) + { + *outPattern = fileInode->getStripePattern()->clone(); + outRstInfo->set(fileInode->getRemoteStorageTargetInfo()); + fileInode->getPathInfo(outPathInfo); + + outNumReadSessions = fileInode->getNumSessionsRead(); + outNumWriteSessions = fileInode->getNumSessionsWrite(); + outDataState = fileInode->getFileState().getRawValue(); + + metaStore->releaseFile(entryInfo->getParentEntryID(), fileInode); + return referenceRes; + } + } + + return FhgfsOpsErr_PATHNOTEXISTS; +} + +/** + * @param outPattern StripePattern clone (in case of success), that must be deleted by the caller + */ +FhgfsOpsErr GetEntryInfoMsgEx::getRootInfo(StripePattern** outPattern, RemoteStorageTarget* outRstInfo) +{ + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + DirInode* rootDir = app->getRootDir(); + + NumNodeID localNodeID = app->getLocalNodeNumID(); + + if (rootDir->getIsBuddyMirrored()) + { + uint16_t buddyGroupID = metaBuddyGroupMapper->getBuddyGroupID(localNodeID.val()); + if (buddyGroupID != rootDir->getOwnerNodeID().val() ) + return FhgfsOpsErr_NOTOWNER; + } + else + if (localNodeID != rootDir->getOwnerNodeID() ) + return FhgfsOpsErr_NOTOWNER; + + *outPattern = rootDir->getStripePatternClone(); + outRstInfo->set(rootDir->getRemoteStorageTargetInfo()); + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.h b/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.h new file mode 100644 index 0000000..bbef260 --- /dev/null +++ b/meta/source/net/message/storage/attribs/GetEntryInfoMsgEx.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include +#include +#include + +class GetEntryInfoMsgResponseState : public MirroredMessageResponseState +{ + public: + GetEntryInfoMsgResponseState() : result(FhgfsOpsErr_INTERNAL), mirrorNodeID(0) + { + } + + GetEntryInfoMsgResponseState(GetEntryInfoMsgResponseState&& other) : + result(other.result), + mirrorNodeID(other.mirrorNodeID), + pattern(std::move(other.pattern)), + pathInfo(std::move(other.pathInfo)), + rst(std::move(other.rst)), + numSessionsRead(other.numSessionsRead), + numSessionsWrite(other.numSessionsWrite), + fileDataState(other.fileDataState) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + GetEntryInfoRespMsg resp(result, pattern.get(), mirrorNodeID, &pathInfo, &rst, + numSessionsRead, numSessionsWrite, fileDataState); + ctx.sendResponse(resp); + } + + // GetEntryInfoMsgEx is transformed into a mirrored message to utilize + // MirroredMessage::lock(), thereby preventing races with operations such + // as unlink. However, forwarding this message to the secondary is unnecessary. + // Overriding the changeObservableState() function to always return false ensures + // that this message never gets forwarded to seconadary. + bool changesObservableState() const override + { + return false; + } + + void setGetEntryInfoResult(FhgfsOpsErr result) { this->result = result; } + void setMirrorNodeID(uint16_t nodeId) { this->mirrorNodeID = nodeId; } + void setStripePattern(StripePattern* pattern) { this->pattern.reset(pattern); } + void setPathInfo(PathInfo const& pathInfo) { this->pathInfo = pathInfo; } + void setRemoteStorageTarget(RemoteStorageTarget const& rstInfo) { rst = rstInfo; } + void setNumSessionsRead(uint32_t numReaders) { numSessionsRead = numReaders; } + void setNumSessionsWrite(uint32_t numWriters) { numSessionsWrite = numWriters; } + void setFileDataState(uint8_t dataState) { fileDataState = dataState; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_GetEntryInfo; } + void serializeContents(Serializer& ser) const override {} + + private: + FhgfsOpsErr result; + uint16_t mirrorNodeID; // metadata mirror node (0 means "none") + std::unique_ptr pattern; + PathInfo pathInfo; + RemoteStorageTarget rst; + uint32_t numSessionsRead; + uint32_t numSessionsWrite; + uint8_t fileDataState; +}; + +class GetEntryInfoMsgEx : public MirroredMessage +{ + public: + typedef GetEntryInfoMsgResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override + { + return getEntryInfo()->getIsBuddyMirrored(); + } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override {} + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr getInfo(EntryInfo* entryInfo, StripePattern** outPattern, PathInfo* outPathInfo, + RemoteStorageTarget* outRstInfo, uint32_t& outNumReadSessions, uint32_t& outNumWriteSessions, + uint8_t& outDataState); + FhgfsOpsErr getRootInfo(StripePattern** outPattern, RemoteStorageTarget* outRstInfo); + + const char* mirrorLogContext() const override { return "GetEntryInfoMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/attribs/GetXAttrMsgEx.cpp b/meta/source/net/message/storage/attribs/GetXAttrMsgEx.cpp new file mode 100644 index 0000000..7ae9fd6 --- /dev/null +++ b/meta/source/net/message/storage/attribs/GetXAttrMsgEx.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include "GetXAttrMsgEx.h" + +bool GetXAttrMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "GetXAttrMsg incoming"; + LogContext(logContext).log(Log_DEBUG , "name: " + this->getName() + "; size: " + + StringTk::intToStr(this->getSize()) + ";"); + return BaseType::processIncoming(ctx); +} + +FileIDLock GetXAttrMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), false}; +} + +std::unique_ptr GetXAttrMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + const char* logContext = "Get XAttr Msg"; + GetXAttrMsgResponseState resp; + + EntryInfo* entryInfo = this->getEntryInfo(); + CharVector xAttrValue; + FhgfsOpsErr getXAttrRes; + ssize_t size = this->getSize(); + const std::string& name = this->getName(); + + App* app = Program::getApp(); + Config* config = app->getConfig(); + if (!config->getStoreClientXAttrs()) + { + LogContext(logContext).log(Log_ERR, + "Received a GetXAttrMsg, but client-side extended attributes are disabled in config."); + + getXAttrRes = FhgfsOpsErr_NOTSUPP; + goto resp; + } + + // Clamp buffer size to the maximum the NetMsg can handle, plus one byte in the (unlikely) case + // the on-disk metadata is larger than that. + if (size > MsgHelperXAttr::MAX_VALUE_SIZE) + size = MsgHelperXAttr::MAX_VALUE_SIZE + 1; + + std::tie(getXAttrRes, xAttrValue, size) = MsgHelperXAttr::getxattr(entryInfo, name, size); + + if (size >= MsgHelperXAttr::MAX_VALUE_SIZE + 1 && getXAttrRes == FhgfsOpsErr_SUCCESS) + { + // The xattr on disk is at least one byte too large. In this case, we have to return + // an internal error because it won't fit the net message. + xAttrValue.clear(); + getXAttrRes = FhgfsOpsErr_INTERNAL; + } + +resp: + resp.setGetXAttrValue(xAttrValue); + resp.setSize(size); + resp.setGetXAttrResult(getXAttrRes); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_GETXATTR, + getMsgHeaderUserID() ); + + return boost::make_unique(std::move(resp)); +} diff --git a/meta/source/net/message/storage/attribs/GetXAttrMsgEx.h b/meta/source/net/message/storage/attribs/GetXAttrMsgEx.h new file mode 100644 index 0000000..de2688e --- /dev/null +++ b/meta/source/net/message/storage/attribs/GetXAttrMsgEx.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +class GetXAttrMsgResponseState : public MirroredMessageResponseState +{ + public: + GetXAttrMsgResponseState() : size(0), returnCode(FhgfsOpsErr_INTERNAL) + { + } + + GetXAttrMsgResponseState(GetXAttrMsgResponseState&& other) : + value(std::move(other.value)), + size(other.size), + returnCode(other.returnCode) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + GetXAttrRespMsg resp(value, size, returnCode); + ctx.sendResponse(resp); + } + + // GetXAttrMsgEx is converted into a mirrored message to utilize MirroredMessage::lock(), + // thereby preventing races with operations like unlink. However, forwarding this message + // to the secondary is unnecessary. Overriding the changesObservableState() function to + // always return false ensures that this message is never forwarded unnecessarily. + bool changesObservableState() const override + { + return false; + } + + void setGetXAttrValue(const CharVector& value) { this->value = value; } + void setSize(size_t size) { this->size = size; } + void setGetXAttrResult(FhgfsOpsErr result) { this->returnCode = result; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_GetXAttr; } + void serializeContents(Serializer& ser) const override {} + + private: + CharVector value; + size_t size; + FhgfsOpsErr returnCode; +}; + +class GetXAttrMsgEx : public MirroredMessage +{ + public: + typedef GetXAttrMsgResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx); + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override + { + return getEntryInfo()->getIsBuddyMirrored(); + } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override {} + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + + const char* mirrorLogContext() const override { return "GetXAttrMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/attribs/ListXAttrMsgEx.cpp b/meta/source/net/message/storage/attribs/ListXAttrMsgEx.cpp new file mode 100644 index 0000000..cf4b606 --- /dev/null +++ b/meta/source/net/message/storage/attribs/ListXAttrMsgEx.cpp @@ -0,0 +1,72 @@ +#include +#include +#include +#include "ListXAttrMsgEx.h" + +bool ListXAttrMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "ListXAttrMsg incoming"; + #endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_DEBUG, "size: " + StringTk::intToStr(this->getSize()) + ";"); + return BaseType::processIncoming(ctx); +} + +FileIDLock ListXAttrMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), false}; +} + +std::unique_ptr ListXAttrMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + const char* logContext = "List XAttr Msg"; + ListXAttrMsgResponseState resp; + + App* app = Program::getApp(); + Config* config = app->getConfig(); + EntryInfo* entryInfo = this->getEntryInfo(); + size_t listSize = 0; + + StringVector xAttrVec; + FhgfsOpsErr listXAttrRes; + + if (!config->getStoreClientXAttrs() ) + { + LogContext(logContext).log(Log_ERR, + "Received a ListXAttrMsg, but client-side extended attributes are disabled in config."); + + listXAttrRes = FhgfsOpsErr_NOTSUPP; + goto resp; + } + + std::tie(listXAttrRes, xAttrVec) = MsgHelperXAttr::listxattr(entryInfo); + + for (StringVectorConstIter it = xAttrVec.begin(); it != xAttrVec.end(); ++it) + { + // Plus one byte to account for the '\0's separating the list elements in the buffer. + listSize += it->size() + 1; + } + + // note: MsgHelperXAttr::MAX_SIZE is a ssize_t, which is always positive, so it will always fit + // into a size_t + if ((listSize >= (size_t)MsgHelperXAttr::MAX_VALUE_SIZE + 1) + && (listXAttrRes == FhgfsOpsErr_SUCCESS)) + { + // The xattr list on disk is at least one byte too large. In this case, we have to return + // an internal error because it won't fit the net message. + xAttrVec.clear(); + listXAttrRes = FhgfsOpsErr_TOOBIG; + } + +resp: + resp.setListXAttrValue(xAttrVec); + resp.setSize(listSize); + resp.setListXAttrResult(listXAttrRes); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_LISTXATTR, + getMsgHeaderUserID() ); + + return boost::make_unique(std::move(resp)); +} diff --git a/meta/source/net/message/storage/attribs/ListXAttrMsgEx.h b/meta/source/net/message/storage/attribs/ListXAttrMsgEx.h new file mode 100644 index 0000000..ef02c2c --- /dev/null +++ b/meta/source/net/message/storage/attribs/ListXAttrMsgEx.h @@ -0,0 +1,74 @@ +#pragma once + +#include +#include +#include + +class ListXAttrMsgResponseState : public MirroredMessageResponseState +{ + public: + ListXAttrMsgResponseState() : size(0), returnCode(FhgfsOpsErr_INTERNAL) + { + } + + ListXAttrMsgResponseState(ListXAttrMsgResponseState&& other) : + value(std::move(other.value)), + size(other.size), + returnCode(other.returnCode) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + ListXAttrRespMsg resp(value, size, returnCode); + ctx.sendResponse(resp); + } + + // ListXAttrMsgEx is transformed into a mirrored message to leverage MirroredMessage::lock(), + // preventing races with operations such as unlink. However, forwarding this message to the + // secondary is unnecessary. Overriding the changesObservableState() function to always + // return false ensures that this message is never forwarded unnecessarily. + bool changesObservableState() const override + { + return false; + } + + void setListXAttrValue(const StringVector& value) { this->value = value; } + void setSize(size_t size) { this->size = size; } + void setListXAttrResult(FhgfsOpsErr result) { this->returnCode = result; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_ListXAttr; } + void serializeContents(Serializer& ser) const override {} + + private: + StringVector value; + size_t size; + FhgfsOpsErr returnCode; +}; + +class ListXAttrMsgEx : public MirroredMessage +{ + public: + typedef ListXAttrMsgResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + bool isMirrored() override + { + return getEntryInfo()->getIsBuddyMirrored(); + } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override {} + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + + const char* mirrorLogContext() const override { return "ListXAttrMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/attribs/RefreshEntryInfoMsg.cpp b/meta/source/net/message/storage/attribs/RefreshEntryInfoMsg.cpp new file mode 100644 index 0000000..ae1deee --- /dev/null +++ b/meta/source/net/message/storage/attribs/RefreshEntryInfoMsg.cpp @@ -0,0 +1,104 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "RefreshEntryInfoMsgEx.h" + + +bool RefreshEntryInfoMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("RefreshEntryInfoMsgEx incoming"); + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, "Received a RefreshEntryInfoMsg from: " + ctx.peerName() ); + + LOG_DEBUG("RefreshEntryInfoMsgEx::processIncoming", Log_SPAM, + "ParentID: " + getEntryInfo()->getParentEntryID() + " EntryID: " + + getEntryInfo()->getEntryID() + " BuddyMirrored: " + + (getEntryInfo()->getIsBuddyMirrored() ? "Yes" : "No") + " Secondary: " + + (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No")); + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_REFRESHENTRYINFO); + + return true; +} + +std::tuple RefreshEntryInfoMsgEx::lock(EntryLockStore& store) +{ + if (DirEntryType_ISDIR(getEntryInfo()->getEntryType())) + return std::make_tuple( + FileIDLock(), + FileIDLock(&store, getEntryInfo()->getEntryID(), true)); + else + return std::make_tuple( + FileIDLock(&store, getEntryInfo()->getEntryID(), true), + FileIDLock()); +} + +std::unique_ptr RefreshEntryInfoMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + if (getEntryInfo()->getParentEntryID().empty()) // special case: get info for root directory + return boost::make_unique(refreshInfoRoot()); + else + return boost::make_unique(refreshInfoRec()); +} + +/** + * @param outPattern StripePattern clone (in case of success), that must be deleted by the caller + */ +FhgfsOpsErr RefreshEntryInfoMsgEx::refreshInfoRec() +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + EntryInfo* entryInfo = getEntryInfo(); + + DirInode* dir = metaStore->referenceDir(entryInfo->getEntryID(), + entryInfo->getIsBuddyMirrored(), true); + if(dir) + { // entry is a directory + dir->refreshMetaInfo(); + metaStore->releaseDir(entryInfo->getEntryID() ); + return FhgfsOpsErr_SUCCESS; + } + + // not a dir => try file + auto refreshRes = MsgHelperStat::refreshDynAttribs(entryInfo, true, getMsgHeaderUserID()); + if (refreshRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + auto [file, referenceRes] = metaStore->referenceFile(getEntryInfo()); + if (file) + fixInodeTimestamp(*file, fileTimestamps, getEntryInfo()); + + metaStore->releaseFile(getEntryInfo()->getParentEntryID(), file); + } + + return refreshRes; +} + +FhgfsOpsErr RefreshEntryInfoMsgEx::refreshInfoRoot() +{ + App* app = Program::getApp(); + DirInode* rootDir = app->getRootDir(); + + NumNodeID expectedOwnerNode = rootDir->getIsBuddyMirrored() + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) + : app->getLocalNode().getNumID(); + + if ( expectedOwnerNode != rootDir->getOwnerNodeID() ) + return FhgfsOpsErr_NOTOWNER; + + rootDir->refreshMetaInfo(); + + return FhgfsOpsErr_SUCCESS; +} + +void RefreshEntryInfoMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_RefreshEntryInfoResp); +} diff --git a/meta/source/net/message/storage/attribs/RefreshEntryInfoMsgEx.h b/meta/source/net/message/storage/attribs/RefreshEntryInfoMsgEx.h new file mode 100644 index 0000000..5c7ffa0 --- /dev/null +++ b/meta/source/net/message/storage/attribs/RefreshEntryInfoMsgEx.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Update entry info, called by fsck or by fhgfs-ctl + +class RefreshEntryInfoMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState + ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + FhgfsOpsErr refreshInfoRec(); + FhgfsOpsErr refreshInfoRoot(); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "RefreshEntryInfoMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.cpp b/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.cpp new file mode 100644 index 0000000..546c336 --- /dev/null +++ b/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.cpp @@ -0,0 +1,86 @@ +#include +#include +#include +#include +#include "RemoveXAttrMsgEx.h" + + +bool RemoveXAttrMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "RemoveXAttrMsg incoming"; + EntryInfo* entryInfo = this->getEntryInfo(); + const std::string& name = this->getName(); + + LOG_DEBUG(logContext, Log_DEBUG, "name: " + name + ";"); + + LOG_DEBUG("RemoveXAttrMsgEx::processIncoming", Log_DEBUG, + "ParentID: " + entryInfo->getParentEntryID() + " EntryID: " + + entryInfo->getEntryID() + " BuddyMirrored: " + + (entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + " Secondary: " + + (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No")); +#endif + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_REMOVEXATTR); + + return true; +} + +std::tuple RemoveXAttrMsgEx::lock(EntryLockStore& store) +{ + if (getEntryInfo()->getEntryType() == DirEntryType_DIRECTORY) + return std::make_tuple( + FileIDLock(), + FileIDLock(&store, getEntryInfo()->getEntryID(), true)); + else + return std::make_tuple( + FileIDLock(&store, getEntryInfo()->getEntryID(), true), + FileIDLock()); +} + +std::unique_ptr RemoveXAttrMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + if (!Program::getApp()->getConfig()->getStoreClientXAttrs() ) // xattrs disabled in config + { + LOG(GENERAL, ERR, "Received a RemoveXAttrMsg, " + "but client-side extended attributes are disabled in config."); + return boost::make_unique(FhgfsOpsErr_NOTSUPP); + } + + auto rmRes = MsgHelperXAttr::removexattr(getEntryInfo(), getName()); + + if (rmRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (DirEntryType_ISDIR(getEntryInfo()->getEntryType())) + { + auto dir = metaStore->referenceDir(getEntryInfo()->getEntryID(), + getEntryInfo()->getIsBuddyMirrored(), true); + if (dir) + { + fixInodeTimestamp(*dir, inodeTimestamps); + metaStore->releaseDir(dir->getID()); + } + } + else + { + auto [file, referenceRes] = metaStore->referenceFile(getEntryInfo()); + if (file) + { + fixInodeTimestamp(*file, inodeTimestamps, getEntryInfo()); + metaStore->releaseFile(getEntryInfo()->getParentEntryID(), file); + } + } + } + + return boost::make_unique(rmRes); +} + +void RemoveXAttrMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_RemoveXAttrResp); +} diff --git a/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.h b/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.h new file mode 100644 index 0000000..24de745 --- /dev/null +++ b/meta/source/net/message/storage/attribs/RemoveXAttrMsgEx.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + +class RemoveXAttrMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "RemoveXAttrMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/attribs/SetAttrMsgEx.cpp b/meta/source/net/message/storage/attribs/SetAttrMsgEx.cpp new file mode 100644 index 0000000..3112d3a --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetAttrMsgEx.cpp @@ -0,0 +1,436 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "SetAttrMsgEx.h" + +#include + + +bool SetAttrMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "SetAttrMsg incoming"; +#endif // BEEGFS_DEBUG + + EntryInfo* entryInfo = getEntryInfo(); + + LOG_DEBUG(logContext, Log_DEBUG, + "BuddyMirrored: " + std::string(entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + + " Secondary: " + std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) + ? "Yes" : "No") ); + (void) entryInfo; + + // update operation counters (here on top because we have an early sock release in this msg) + updateNodeOp(ctx, MetaOpCounter_SETATTR); + + return BaseType::processIncoming(ctx); +} + +std::tuple SetAttrMsgEx::lock(EntryLockStore& store) +{ + if (DirEntryType_ISDIR(getEntryInfo()->getEntryType())) + return std::make_tuple( + FileIDLock(), + FileIDLock(&store, getEntryInfo()->getEntryID(), true)); + else + return std::make_tuple( + FileIDLock(&store, getEntryInfo()->getEntryID(), true), + FileIDLock()); +} + +std::unique_ptr SetAttrMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + const char* logContext = "Set file attribs"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + Config* cfg = app->getConfig(); + + EntryInfo* entryInfo = getEntryInfo(); + FhgfsOpsErr setAttrRes; + const bool forwardToStorage = !entryInfo->getIsBuddyMirrored() + || !hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond); + + bool responseSent = false; + + if (entryInfo->getParentEntryID().empty() || DirEntryType_ISDIR(entryInfo->getEntryType())) + { + // special case: setAttr for root directory + if (entryInfo->getParentEntryID().empty()) + setAttrRes = setAttrRoot(); + else + setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs()); + + if (setAttrRes != FhgfsOpsErr_SUCCESS || !(shouldFixTimestamps() || getFileEvent())) + return boost::make_unique(setAttrRes); + + if (shouldFixTimestamps()) + { + auto dir = metaStore->referenceDir(entryInfo->getEntryID(), + entryInfo->getIsBuddyMirrored(), true); + if (dir) + { + fixInodeTimestamp(*dir, inodeTimestamps); + metaStore->releaseDir(dir->getID()); + } + } + + if (!isSecondary && getFileEvent() && app->getFileEventLogger() && getFileEvent()) + { + unsigned numHardlinks = 0; + auto dir = metaStore->referenceDir(entryInfo->getEntryID(), + entryInfo->getIsBuddyMirrored(), true); + if (likely(dir)) + { + numHardlinks = dir->getNumHardlinks(); + metaStore->releaseDir(dir->getID()); + } + + EventContext eventCtx = makeEventContext(entryInfo, entryInfo->getParentEntryID(), + getMsgHeaderUserID(), "", numHardlinks, isSecondary); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + return boost::make_unique(setAttrRes); + } + + // update nlink count if requested by caller + if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) && + isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_DECR_NLINKCNT)) + { + // error: both increase and decrease of nlink count was requested + return boost::make_unique(FhgfsOpsErr_INTERNAL); + } + else if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) || + isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_DECR_NLINKCNT)) + { + int val = isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_INCR_NLINKCNT) ? 1 : -1; + setAttrRes = metaStore->incDecLinkCount(entryInfo, val); + return boost::make_unique(setAttrRes); + } + + // we need to reference the inode first, as we want to use it several times + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (!inode) + return boost::make_unique(referenceRes); + + // in the following we need to distinguish between several cases. + // 1. if times shall be updated we need to send the update to the storage servers first + // because, we need to rely on storage server's attrib version to prevent races with + // other messages that update the times (e.g. Close) + // 2. if times shall not be updated (must be chmod or chown then) and quota is enabled + // we first set the local attributes and then send the update to the storage server. + // if an early response optimization is set in this case we send the response between + // these two steps + // 3. no times update (i.e. chmod or chown) and quota is disabled => only update locally, + // as we don't have a reason to waste time with contacting the storage servers + + bool timeUpdate = + getValidAttribs() & (SETATTR_CHANGE_MODIFICATIONTIME | SETATTR_CHANGE_LASTACCESSTIME); + + // note: time update and mode/owner update can never be at the same time + if (timeUpdate && forwardToStorage) + { + // only relevant if message needs to be sent to the storage server. if not (e.g. + // because this is secondary of a buddy group, we can use the "default" case later + setAttrRes = setChunkFileAttribs(*inode, true); + + if (setAttrRes == FhgfsOpsErr_SUCCESS) + setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs()); + } + else if (isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA) && forwardToStorage) + { + const UInt16Vector* targetIdVec = inode->getStripePattern()->getStripeTargetIDs(); + ExceededQuotaStorePtr quotaExStore; + for (auto targetIter = targetIdVec->begin(); targetIter != targetIdVec->end(); targetIter++) + { + if (inode->getStripePattern()->getPatternType() == StripePatternType_BuddyMirror) + { + uint16_t primaryTargetID = app->getStorageBuddyGroupMapper()->getPrimaryTargetID(*targetIter); + quotaExStore = app->getExceededQuotaStores()->get(primaryTargetID); + } + else + { + // this is not very efficient at the moment, as we need to look at every quota exceeded + // store for every target in the stripe pattern; we need to do that because storage pools + // might change over time, i.e. the pool that is stored in the metadata doesn't always + // match the actual targets a file is stored on + quotaExStore = app->getExceededQuotaStores()->get(*targetIter); + } + + // check if exceeded quotas exists, before doing a more expensive and explicit check + if (quotaExStore && quotaExStore->someQuotaExceeded()) + { // store for this target is present AND someQuotaExceeded() was true + QuotaExceededErrorType quotaExceeded = quotaExStore->isQuotaExceeded( + getAttribs()->userID, getAttribs()->groupID); + + if (quotaExceeded != QuotaExceededErrorType_NOT_EXCEEDED) + { + LogContext(logContext).log(Log_NOTICE, + QuotaData::QuotaExceededErrorTypeToString(quotaExceeded) + " " + "UID: " + StringTk::uintToStr(getAttribs()->userID) + "; " + "GID: " + StringTk::uintToStr(getAttribs()->groupID)); + + setAttrRes = FhgfsOpsErr_DQUOT; + goto finish; + } + } + } + + // only relevant if message needs to be sent to the storage server. if not (e.g. + // because this is secondary of a buddy group, we can use the "default" case later + setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs()); + + // allowed only if early chown respnse for quota is set + if (cfg->getQuotaEarlyChownResponse()) + { + earlyComplete(ctx, ResponseState(setAttrRes)); + responseSent = true; + } + + if (setAttrRes == FhgfsOpsErr_SUCCESS) + setChunkFileAttribs(*inode, false); + } + else + { + setAttrRes = metaStore->setAttr(entryInfo, getValidAttribs(), getAttribs()); + } + +finish: + if (shouldFixTimestamps()) + fixInodeTimestamp(*inode, inodeTimestamps, entryInfo); + + if (!isSecondary && setAttrRes == FhgfsOpsErr_SUCCESS && + app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext( + entryInfo, + entryInfo->getParentEntryID(), + getMsgHeaderUserID(), + "", + inode->getNumHardlinks(), + isSecondary + ); + + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + if (!responseSent) + return boost::make_unique(setAttrRes); + else + return {}; +} + +void SetAttrMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_SetAttrResp); +} + +FhgfsOpsErr SetAttrMsgEx::setAttrRoot() +{ + App* app = Program::getApp(); + DirInode* rootDir = app->getRootDir(); + + NumNodeID expectedOwnerNode = rootDir->getIsBuddyMirrored() + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) + : app->getLocalNode().getNumID(); + + if ( expectedOwnerNode != rootDir->getOwnerNodeID() ) + return FhgfsOpsErr_NOTOWNER; + + if(!rootDir->setAttrData(getValidAttribs(), getAttribs() ) ) + return FhgfsOpsErr_INTERNAL; + + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribs(FileInode& file, bool requestDynamicAttribs) +{ + StripePattern* pattern = file.getStripePattern(); + + if( (pattern->getStripeTargetIDs()->size() > 1) || + (pattern->getPatternType() == StripePatternType_BuddyMirror) ) + return setChunkFileAttribsParallel(file, requestDynamicAttribs); + else + return setChunkFileAttribsSequential(file, requestDynamicAttribs); +} + +/** + * Note: This method does not work for mirrored files; use setChunkFileAttribsParallel() for those. + */ +FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribsSequential(FileInode& inode, + bool requestDynamicAttribs) +{ + const char* logContext = "Set chunk file attribs S"; + + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + TargetStateStore* targetStates = Program::getApp()->getTargetStateStore(); + NodeStore* nodes = Program::getApp()->getStorageNodes(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + std::string fileID(inode.getEntryID()); + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + // send request to each node and receive the response message + unsigned currentTargetIndex = 0; + for(UInt16VectorConstIter iter = targetIDs->begin(); + iter != targetIDs->end(); + iter++, currentTargetIndex++) + { + uint16_t targetID = *iter; + bool enableFileCreation = (currentTargetIndex == 0); // enable inode creation of first node + + SetLocalAttrMsg setAttrMsg(fileID, targetID, &pathInfo, getValidAttribs(), getAttribs(), + enableFileCreation); + + setAttrMsg.setMsgHeaderUserID(inode.getUserID()); + + if(isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA)) + setAttrMsg.addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_USE_QUOTA); + + RequestResponseArgs rrArgs(NULL, &setAttrMsg, NETMSGTYPE_SetLocalAttrResp); + RequestResponseTarget rrTarget(targetID, targetMapper, nodes); + + rrTarget.setTargetStates(targetStates); + + // send request to node and receive response + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID() + "; " + "Error: " + boost::lexical_cast(requestRes)); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = requestRes; + + continue; + } + + // correct response type received + const auto setRespMsg = (const SetLocalAttrRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr setRespResult = setRespMsg->getResult(); + if (setRespResult != FhgfsOpsErr_SUCCESS) + { // error: local inode attribs not set + LogContext(logContext).log(Log_WARNING, + "Target failed to set attribs of chunk file: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = setRespResult; + + continue; + } + + // success: local inode attribs set + if (setRespMsg->isMsgHeaderFeatureFlagSet(SETLOCALATTRRESPMSG_FLAG_HAS_ATTRS)) + { + DynamicFileAttribsVec dynAttribsVec(1); + setRespMsg->getDynamicAttribs(&(dynAttribsVec[0])); + inode.setDynAttribs(dynAttribsVec); + } + + LOG_DEBUG(logContext, Log_DEBUG, + "Target has set attribs of chunk file: " + StringTk::uintToStr(targetID) + "; " + + "fileID: " + inode.getEntryID()); + } + + if(unlikely(retVal != FhgfsOpsErr_SUCCESS) ) + LogContext(logContext).log(Log_WARNING, + "Problems occurred during setting of chunk file attribs. " + "fileID: " + inode.getEntryID()); + + return retVal; +} + +FhgfsOpsErr SetAttrMsgEx::setChunkFileAttribsParallel(FileInode& inode, bool requestDynamicAttribs) +{ + const char* logContext = "Set chunk file attribs"; + + App* app = Program::getApp(); + MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + size_t numTargetWorks = targetIDs->size(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + DynamicFileAttribsVec dynAttribsVec(numTargetWorks); + FhgfsOpsErrVec nodeResults(numTargetWorks); + SynchronizedCounter counter; + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + // generate work for storage targets... + + for(size_t i=0; i < numTargetWorks; i++) + { + bool enableFileCreation = (i == 0); // enable inode creation on first target + + SetChunkFileAttribsWork* work = NULL; + if (requestDynamicAttribs) // we are interested in the chunk's dynamic attributes, because we + { // modify timestamps and this operation might race with others + work = new SetChunkFileAttribsWork(inode.getEntryID(), getValidAttribs(), getAttribs(), + enableFileCreation, pattern, (*targetIDs)[i], &pathInfo, &(dynAttribsVec[i]), + &(nodeResults[i]), &counter); + } + else + { + work = new SetChunkFileAttribsWork(inode.getEntryID(), getValidAttribs(), getAttribs(), + enableFileCreation, pattern, (*targetIDs)[i], &pathInfo, NULL, &(nodeResults[i]), + &counter); + } + + work->setQuotaChown(isMsgHeaderFeatureFlagSet(SETATTRMSG_FLAG_USE_QUOTA) ); + work->setMsgUserID(getMsgHeaderUserID() ); + + slaveQ->addDirectWork(work); + } + + // wait for work completion... + counter.waitForCount(numTargetWorks); + + // we set the dynamic attribs here, no matter if the remote operation suceeded or not. If it + // did not, storageVersion will be zero and the corresponding data will be ignored + // note: if the chunk's attributes were not requested from server at all, this is also OK here, + // because the storageVersion will be 0 + inode.setDynAttribs(dynAttribsVec); + + // check target results... + for(size_t i=0; i < numTargetWorks; i++) + { + if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during setting of chunk file attribs. " + "fileID: " + inode.getEntryID()); + + retVal = nodeResults[i]; + goto error_exit; + } + } + + +error_exit: + return retVal; +} diff --git a/meta/source/net/message/storage/attribs/SetAttrMsgEx.h b/meta/source/net/message/storage/attribs/SetAttrMsgEx.h new file mode 100644 index 0000000..d3425d4 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetAttrMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include + +class SetAttrMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr setAttrRoot(); + FhgfsOpsErr setChunkFileAttribs(FileInode& file, bool requestDynamicAttribs); + FhgfsOpsErr setChunkFileAttribsSequential(FileInode& inode, bool requestDynamicAttribs); + FhgfsOpsErr setChunkFileAttribsParallel(FileInode& inode, bool requestDynamicAttribs); + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "SetAttrMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.cpp b/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.cpp new file mode 100644 index 0000000..3d1e3e2 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.cpp @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include +#include "SetDirPatternMsgEx.h" + + +bool SetDirPatternMsgEx::processIncoming(ResponseContext& ctx) +{ + EntryInfo* entryInfo = this->getEntryInfo(); + + LOG_DEBUG("SetDirPatternMsgEx::processIncoming", Log_SPAM, + "parentEntryID: " + entryInfo->getParentEntryID() + " EntryID: " + + entryInfo->getEntryID() + " BuddyMirrored: " + + (entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + " Secondary: " + + (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No")); + (void) entryInfo; + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_SETDIRPATTERN); + + return true; +} + + +std::unique_ptr SetDirPatternMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + const char* logContext = "SetDirPatternMsg (set dir pattern)"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + + EntryInfo* entryInfo = getEntryInfo(); + StripePattern* pattern = &getPattern(); + RemoteStorageTarget* rst = getRemoteStorageTarget(); + + FhgfsOpsErr retVal = FhgfsOpsErr_NOTADIR; + + uint32_t actorUID = isMsgHeaderFeatureFlagSet(Flags::HAS_UID) + ? getUID() + : 0; + + if (actorUID != 0 && !app->getConfig()->getSysAllowUserSetPattern()) + return boost::make_unique(FhgfsOpsErr_PERM); + + if (pattern->getChunkSize() < STRIPEPATTERN_MIN_CHUNKSIZE || + !MathTk::isPowerOfTwo(pattern->getChunkSize())) + { // check of stripe pattern details validity failed + LOG(GENERAL, ERR, "Received an invalid pattern chunksize", + ("chunkSize", pattern->getChunkSize())); + return boost::make_unique(FhgfsOpsErr_INTERNAL); + } + + // verify owner of root dir + if (entryInfo->getEntryID() == META_ROOTDIR_ID_STR) + { + const bool isMirrored = entryInfo->getIsBuddyMirrored(); + const NumNodeID rootOwnerID = app->getRootDir()->getOwnerNodeID(); + const NumNodeID localGroupID(app->getMetaBuddyGroupMapper()->getLocalGroupID()); + + if ((!isMirrored && rootOwnerID != app->getLocalNodeNumID()) + || (isMirrored && rootOwnerID != localGroupID)) + { + LogContext(logContext).log(Log_DEBUG, "This node does not own the root directory."); + return boost::make_unique(FhgfsOpsErr_NOTOWNER); + } + } + + DirInode* dir = metaStore->referenceDir(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored(), + true); + + if (unlikely(!dir)) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + // entry is a directory + retVal = dir->setStripePattern(*pattern, actorUID); + if (retVal != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Update of stripe pattern failed. " + "DirID: " + entryInfo->getEntryID()); + metaStore->releaseDir(entryInfo->getEntryID()); + return boost::make_unique(retVal); + } + + // Ignore if the request did not contain RST configuration: + if (!rst->hasInvalidVersion()) + { + auto const& rstIDs = rst->getRstIdVector(); + if (rstIDs.empty()) + { + // Empty RST ID list indicates a request to clear/unset RSTs for this directory. + retVal = dir->clearRemoteStorageTarget(); + if (retVal != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to clear RST info; " + "DirID: " + entryInfo->getEntryID()); + metaStore->releaseDir(entryInfo->getEntryID()); + return boost::make_unique(retVal); + } + } + else + { + retVal = dir->setRemoteStorageTarget(*rst); + if (retVal != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Storing remote storage targets failed. " + "DirID: " + entryInfo->getEntryID()); + metaStore->releaseDir(entryInfo->getEntryID()); + return boost::make_unique(retVal); + } + } + } + + metaStore->releaseDir(entryInfo->getEntryID()); + return boost::make_unique(retVal); +} + +void SetDirPatternMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_SetDirPatternResp); +} diff --git a/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.h b/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.h new file mode 100644 index 0000000..c706329 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetDirPatternMsgEx.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include + +// set stripe pattern, called by fhgfs-ctl + +class SetDirPatternMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override + { + return {&store, getEntryInfo()->getEntryID(), true}; + } + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "SetDirPatternMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.cpp b/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.cpp new file mode 100644 index 0000000..9be78ca --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.cpp @@ -0,0 +1,73 @@ +#include +#include "SetFilePatternMsgEx.h" + +bool SetFilePatternMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "SetFilePatternMsgEx incoming"; + + EntryInfo* entryInfo = this->getEntryInfo(); + LOG_DEBUG(logContext, 5, "EntryID: " + entryInfo->getEntryID() + + "; FileName: " + entryInfo->getFileName() + + "; EntryType: " + StringTk::intToStr(entryInfo->getEntryType()) + + "; isBuddyMirrored: " + StringTk::intToStr(entryInfo->getIsBuddyMirrored()) + + "; remoteStorageTarget (to be set) info: " + getRemoteStorageTarget()->toStr()); + #endif + + BaseType::processIncoming(ctx); + return true; +} + +FileIDLock SetFilePatternMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr SetFilePatternMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + const char* logContext = "Set File Pattern"; + FhgfsOpsErr res = FhgfsOpsErr_SUCCESS; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + EntryInfo* entryInfo = this->getEntryInfo(); + RemoteStorageTarget* rst = this->getRemoteStorageTarget(); + + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (unlikely(!inode)) + return boost::make_unique(referenceRes); + + // Ignore if the request did not contain RST configuration: + if (!rst->hasInvalidVersion()) + { + auto const& rstIDs = rst->getRstIdVector(); + if (rstIDs.empty()) + { + // Empty RST ID list indicates a request to clear/unset RSTs for this file. + res = inode->clearRemoteStorageTarget(entryInfo); + if (res != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to clear RST info; entryID: " + + entryInfo->getEntryID()); + } + } + else + { + res = inode->setRemoteStorageTarget(entryInfo, *rst); + if (res != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Setting RST info failed; entryID: " + + entryInfo->getEntryID()); + } + } + } + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + return boost::make_unique(res); +} + +void SetFilePatternMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_SetFilePatternResp); +} diff --git a/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.h b/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.h new file mode 100644 index 0000000..0f32e71 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetFilePatternMsgEx.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class SetFilePatternMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "SetFilePatternMsgEx/forward"; } +}; diff --git a/meta/source/net/message/storage/attribs/SetFileStateMsgEx.cpp b/meta/source/net/message/storage/attribs/SetFileStateMsgEx.cpp new file mode 100644 index 0000000..7fb6bde --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetFileStateMsgEx.cpp @@ -0,0 +1,48 @@ +#include +#include "SetFileStateMsgEx.h" + +bool SetFileStateMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "SetFileStateMsgEx incoming"; + + EntryInfo* entryInfo = this->getEntryInfo(); + LOG_DEBUG(logContext, 5, "EntryID: " + entryInfo->getEntryID() + + "; FileName: " + entryInfo->getFileName() + + "; EntryType: " + StringTk::intToStr(entryInfo->getEntryType()) + + "; isBuddyMirrored: " + StringTk::intToStr(entryInfo->getIsBuddyMirrored()) + + "; file state (accessFlags+dataState): " + StringTk::intToStr(this->getFileState())); + #endif + + BaseType::processIncoming(ctx); + return true; +} + +FileIDLock SetFileStateMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr SetFileStateMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + const char* logContext = "Set File State"; + FhgfsOpsErr res = FhgfsOpsErr_INTERNAL; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + res = metaStore->setFileState(getEntryInfo(), FileState(getFileState())); + + if (res != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_DEBUG, "Setting file state failed. EntryID: " + + getEntryInfo()->getEntryID()); + return boost::make_unique(res); + } + + return boost::make_unique(res); +} + +void SetFileStateMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_SetFileStateResp); +} \ No newline at end of file diff --git a/meta/source/net/message/storage/attribs/SetFileStateMsgEx.h b/meta/source/net/message/storage/attribs/SetFileStateMsgEx.h new file mode 100644 index 0000000..638e903 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetFileStateMsgEx.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class SetFileStateMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "SetFileStateMsgEx/forward"; } +}; \ No newline at end of file diff --git a/meta/source/net/message/storage/attribs/SetXAttrMsgEx.cpp b/meta/source/net/message/storage/attribs/SetXAttrMsgEx.cpp new file mode 100644 index 0000000..ee11eb9 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetXAttrMsgEx.cpp @@ -0,0 +1,85 @@ +#include +#include +#include +#include +#include "SetXAttrMsgEx.h" + +std::tuple SetXAttrMsgEx::lock(EntryLockStore& store) +{ + if (getEntryInfo()->getEntryType() == DirEntryType_DIRECTORY) + return std::make_tuple( + FileIDLock(), + FileIDLock(&store, getEntryInfo()->getEntryID(), true)); + else + return std::make_tuple( + FileIDLock(&store, getEntryInfo()->getEntryID(), true), + FileIDLock()); +} + +bool SetXAttrMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "SetXAttrMsg incoming"; + EntryInfo* entryInfo = this->getEntryInfo(); + const std::string& name = this->getName(); + + LOG_DEBUG(logContext, Log_DEBUG, "name: " + name + ";"); + + LOG_DEBUG("SetXAttrMsgEx::processIncoming", Log_DEBUG, + "ParentID: " + entryInfo->getParentEntryID() + " EntryID: " + + entryInfo->getEntryID() + " BuddyMirrored: " + + (entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + " Secondary: " + + (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No")); +#endif + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_SETXATTR); + + return true; +} + +std::unique_ptr SetXAttrMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + if (!Program::getApp()->getConfig()->getStoreClientXAttrs()) + { + LOG(GENERAL, ERR, + "Received a SetXAttrMsg, but client-side extended attributes are disabled in config."); + return boost::make_unique(FhgfsOpsErr_NOTSUPP); + } + + auto setXAttrRes = MsgHelperXAttr::setxattr(getEntryInfo(), getName(), getValue(), getFlags()); + + if (setXAttrRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (DirEntryType_ISDIR(getEntryInfo()->getEntryType())) + { + auto dir = metaStore->referenceDir(getEntryInfo()->getEntryID(), + getEntryInfo()->getIsBuddyMirrored(), true); + if (dir) + { + fixInodeTimestamp(*dir, inodeTimestamps); + metaStore->releaseDir(dir->getID()); + } + } + else + { + auto [file, referenceRes] = metaStore->referenceFile(getEntryInfo()); + if (file) + { + fixInodeTimestamp(*file, inodeTimestamps, getEntryInfo()); + metaStore->releaseFile(getEntryInfo()->getParentEntryID(), file); + } + } + } + + return boost::make_unique(setXAttrRes); +} + +void SetXAttrMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_SetXAttrResp); +} diff --git a/meta/source/net/message/storage/attribs/SetXAttrMsgEx.h b/meta/source/net/message/storage/attribs/SetXAttrMsgEx.h new file mode 100644 index 0000000..7b7e7e1 --- /dev/null +++ b/meta/source/net/message/storage/attribs/SetXAttrMsgEx.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +class SetXAttrMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + private: + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "SetXAttrMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/attribs/StatMsgEx.cpp b/meta/source/net/message/storage/attribs/StatMsgEx.cpp new file mode 100644 index 0000000..08bb0c9 --- /dev/null +++ b/meta/source/net/message/storage/attribs/StatMsgEx.cpp @@ -0,0 +1,87 @@ +#include +#include +#include +#include +#include "StatMsgEx.h" + + +bool StatMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "StatMsgEx incoming"; +#endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, 5, "ParentID: " + getEntryInfo()->getParentEntryID() + + "; EntryID: " + getEntryInfo()->getEntryID() + + "; EntryType: " + StringTk::intToStr(getEntryInfo()->getEntryType() ) + + "; isBuddyMirrored: " + StringTk::intToStr(getEntryInfo()->getIsBuddyMirrored())); + + return BaseType::processIncoming(ctx); +} + +FileIDLock StatMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), false}; +} + +std::unique_ptr StatMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + StatMsgResponseState resp; + + StatData statData; + FhgfsOpsErr statRes; + + NumNodeID parentNodeID; + std::string parentEntryID; + + EntryInfo* entryInfo = this->getEntryInfo(); + + if (entryInfo->getParentEntryID().empty() || (entryInfo->getEntryID() == META_ROOTDIR_ID_STR) ) + { // special case: stat for root directory + statRes = statRoot(statData); + } + else + { + statRes = MsgHelperStat::stat(entryInfo, true, getMsgHeaderUserID(), statData, &parentNodeID, + &parentEntryID); + } + + LOG_DBG(GENERAL, DEBUG, "", statRes); + + resp.setStatResult(statRes); + resp.setStatData(statData); + + if (isMsgHeaderFeatureFlagSet(STATMSG_FLAG_GET_PARENTINFO) && parentNodeID) + resp.setParentInfo(parentNodeID, parentEntryID); + + updateNodeOp(ctx, MetaOpCounter_STAT); + + return boost::make_unique(std::move(resp)); +} + +FhgfsOpsErr StatMsgEx::statRoot(StatData& outStatData) +{ + App* app = Program::getApp(); + Node& localNode = app->getLocalNode(); + DirInode* rootDir = app->getRootDir(); + NumNodeID expectedOwnerID; + + // if root is buddy mirrored compare ownership to buddy group id, otherwise to node id itself + if ( rootDir->getIsBuddyMirrored() ) + expectedOwnerID = + NumNodeID(app->getMetaBuddyGroupMapper()->getBuddyGroupID(localNode.getNumID().val() ) ); + else + expectedOwnerID = localNode.getNumID(); + + if(expectedOwnerID != rootDir->getOwnerNodeID() ) + { + return FhgfsOpsErr_NOTOWNER; + } + + rootDir->getStatData(outStatData); + + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/meta/source/net/message/storage/attribs/StatMsgEx.h b/meta/source/net/message/storage/attribs/StatMsgEx.h new file mode 100644 index 0000000..796f4d3 --- /dev/null +++ b/meta/source/net/message/storage/attribs/StatMsgEx.h @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include + +// The "getattr" operation for linux-kernel filesystems + +class StatMsgResponseState : public MirroredMessageResponseState +{ + public: + StatMsgResponseState() : result(FhgfsOpsErr_INTERNAL), hasParentInfo(false) {} + + explicit StatMsgResponseState(Deserializer& des) + { + serialize(this, des); + } + + StatMsgResponseState(StatMsgResponseState&& other) : + result(other.result), + statData(other.statData), + parentNodeID(other.parentNodeID), + parentEntryID(other.parentEntryID), + hasParentInfo(other.hasParentInfo) + {} + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + StatRespMsg resp(result, statData); + + if (this->hasParentInfo) + { + resp.addParentInfo(parentNodeID, parentEntryID); + } + + ctx.sendResponse(resp); + } + + // StatMsgEx is converted to mirrored message to leverage locking part of + // mirrored messages to prevent races with unlink, open etc. + // + // Always return false from changeObservableState() to prohibit forwarding + // to secondary. See MirroredMessage::finishOperation() for more details. + bool changesObservableState() const override + { + return false; + } + + void setParentInfo(NumNodeID nodeID, const std::string& parentEntryID) + { + this->hasParentInfo = true; + this->parentNodeID = nodeID; + this->parentEntryID = parentEntryID; + } + + void setStatData(const StatData& statData) + { + this->statData = statData; + } + + void setStatResult(FhgfsOpsErr statRes) { this->result = statRes; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_Stat; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->statData.serializeAs(StatDataFormat_NET); + + if (obj->hasParentInfo) + { + ctx + % serdes::stringAlign4(obj->parentEntryID) + % obj->parentNodeID; + } + } + + void serializeContents(Serializer& ser) const override + { + serialize(this, ser); + } + + private: + FhgfsOpsErr result; + StatData statData; + NumNodeID parentNodeID; + std::string parentEntryID; + bool hasParentInfo; +}; + +class StatMsgEx: public MirroredMessage +{ + public: + typedef StatMsgResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override + { + return getEntryInfo()->getIsBuddyMirrored(); + } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override {} + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + + const char* mirrorLogContext() const override { return "StatMsgEx/forward"; } + + FhgfsOpsErr statRoot(StatData& outStatData); +}; + diff --git a/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.cpp b/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.cpp new file mode 100644 index 0000000..b1ebe6e --- /dev/null +++ b/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include +#include +#include "UpdateDirParentMsgEx.h" + + +bool UpdateDirParentMsgEx::processIncoming(ResponseContext& ctx) +{ + EntryInfo* entryInfo = getEntryInfo(); + NumNodeID parentNodeID = getParentNodeID(); + + LOG_DEBUG("UpdateDirParentMsgEx::processIncoming", Log_DEBUG, + "ParentID: " + entryInfo->getParentEntryID() + " EntryID: " + + entryInfo->getEntryID() + " parentNodeID: " + parentNodeID.str() + " BuddyMirrored: " + + (entryInfo->getIsBuddyMirrored() ? "Yes" : "No") + " Secondary: " + + (hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? "Yes" : "No")); + (void) entryInfo; + (void) parentNodeID; + + rctx = &ctx; + + BaseType::processIncoming(ctx); + + // update operation counters + updateNodeOp(ctx, MetaOpCounter_UPDATEDIRPARENT); + + return true; +} + +FileIDLock UpdateDirParentMsgEx::lock(EntryLockStore& store) +{ + if (rctx->isLocallyGenerated()) + return {}; + + return {&store, getEntryInfo()->getEntryID(), true}; +} + +std::unique_ptr UpdateDirParentMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + auto setRes = Program::getApp()->getMetaStore()->setDirParent(getEntryInfo(), getParentNodeID()); + + if (setRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + auto dir = Program::getApp()->getMetaStore()->referenceDir(getEntryInfo()->getEntryID(), + true, true); + if (dir) + { + fixInodeTimestamp(*dir, dirTimestamps); + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + } + } + + return boost::make_unique(setRes); +} + +void UpdateDirParentMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_UpdateDirParentResp); +} diff --git a/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.h b/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.h new file mode 100644 index 0000000..77e4484 --- /dev/null +++ b/meta/source/net/message/storage/attribs/UpdateDirParentMsgEx.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include +#include +#include + +class UpdateDirParentMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState + ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "UpdateDirParentMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.cpp b/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.cpp new file mode 100644 index 0000000..3b9f272 --- /dev/null +++ b/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.cpp @@ -0,0 +1,45 @@ +#include +#include + +#include "ChunkBalanceMsgEx.h" + + +FileIDLock ChunkBalanceMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +bool ChunkBalanceMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "ChunkBalanceMsg incoming"; + #endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_SPAM, "Starting ChunkBalance job from localTargetID: " + + StringTk::uintToStr(getTargetID()) + "; to destinationTargetID: " + + StringTk::uintToStr(getDestinationID())); + return BaseType::processIncoming(ctx); +} + +std::unique_ptr ChunkBalanceMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + ChunkBalanceMsgResponseState resp; + + FhgfsOpsErr chunkBalanceMsgRes = FhgfsOpsErr_SUCCESS; + + const char* logContext = "Update Stripe Pattern"; + + LogContext(logContext).logErr("This message is not yet implemented. \n It should trigger chunk balancing components on the metadata service. "); + + resp.setResult(chunkBalanceMsgRes); + return boost::make_unique(std::move(resp)); +} + +ChunkBalancerJob* ChunkBalanceMsgEx::addChunkBalanceJob() +{ + std::lock_guard mutexLock(ChunkBalanceJobMutex); + + ChunkBalancerJob* chunkBalanceJob = nullptr; + return chunkBalanceJob; +} diff --git a/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.h b/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.h new file mode 100644 index 0000000..4166e8d --- /dev/null +++ b/meta/source/net/message/storage/chunkbalancing/ChunkBalanceMsgEx.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +#include +#include + +class ChunkBalancerJob; + +class ChunkBalanceMsgResponseState : public ErrorCodeResponseState +{ + public: + + ChunkBalanceMsgResponseState() : ErrorCodeResponseState(FhgfsOpsErr_INTERNAL) + { + } + + ChunkBalanceMsgResponseState(ChunkBalanceMsgResponseState&& other) : + ErrorCodeResponseState(other.result) + { + } + + /* + // Always return false from changeObservableState() to prohibit forwarding + // to secondary. See MirroredMessage::finishOperation() for more details. + */ + + bool changesObservableState() const override + { + return false; + } + + void setResult(FhgfsOpsErr result) { this->result = result; } + + private: + FhgfsOpsErr result; +}; + +class ChunkBalanceMsgEx : public MirroredMessage +{ + public: + typedef ChunkBalanceMsgResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + Mutex ChunkBalanceJobMutex; + ChunkBalancerJob* addChunkBalanceJob(); + + void forwardToSecondary(ResponseContext& ctx) override {}; + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return FhgfsOpsErr_SUCCESS; + } + const char* mirrorLogContext() const override { return "ChunkBalanceMsgEx/forward"; } + +}; + diff --git a/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.cpp b/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.cpp new file mode 100644 index 0000000..5096c6a --- /dev/null +++ b/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.cpp @@ -0,0 +1,48 @@ +#include +#include + +#include "StripePatternUpdateMsgEx.h" + +FileIDLock StripePatternUpdateMsgEx::lock(EntryLockStore& store) +{ + return {&store, getEntryInfo()->getEntryID(), true}; +} + +bool StripePatternUpdateMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "StripePatternUpdateMsg incoming"; + #endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_SPAM, "Attempting to change stripe pattern of file at chunkPath: " + getRelativePath() + "; from localTargetID: " + + StringTk::uintToStr(getTargetID()) + "; to destinationTargetID: " + + StringTk::uintToStr(getDestinationID())); + return BaseType::processIncoming(ctx); + +} + +std::unique_ptr StripePatternUpdateMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + + FhgfsOpsErr stripePatternMsgRes; + const char* logContext = "Update Stripe Pattern"; + + LogContext(logContext).logErr("This message is not yet implemented. \n It should change the metadata stripe pattern of the chunk that is being balanced. "); + + stripePatternMsgRes = FhgfsOpsErr_SUCCESS; + return boost::make_unique(stripePatternMsgRes); +} + +ChunkBalancerJob* StripePatternUpdateMsgEx::addChunkBalanceJob() +{ + std::lock_guard mutexLock(ChunkBalanceJobMutex); + + ChunkBalancerJob* chunkBalanceJob = nullptr; + return chunkBalanceJob; +} + +void StripePatternUpdateMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_StripePatternUpdate); +} \ No newline at end of file diff --git a/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.h b/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.h new file mode 100644 index 0000000..0a7f9f7 --- /dev/null +++ b/meta/source/net/message/storage/chunkbalancing/StripePatternUpdateMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include + + +class ChunkBalancerJob; + +class StripePatternUpdateMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FileIDLock lock(EntryLockStore& store) override; + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + Mutex ChunkBalanceJobMutex; + Mutex StripePatternUpdateMutex; + Condition StripePatternUpdateCond; + + ChunkBalancerJob* addChunkBalanceJob(); + void waitForStripePatternUpdate(); + + void forwardToSecondary(ResponseContext& ctx) override; + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(static_cast(resp).getValue()); + } + const char* mirrorLogContext() const override { return "StripePatternUpdateMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/creating/HardlinkMsgEx.cpp b/meta/source/net/message/storage/creating/HardlinkMsgEx.cpp new file mode 100644 index 0000000..af08421 --- /dev/null +++ b/meta/source/net/message/storage/creating/HardlinkMsgEx.cpp @@ -0,0 +1,303 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "HardlinkMsgEx.h" + +std::tuple HardlinkMsgEx::lock( + EntryLockStore& store) +{ + // NOTE: normally we'd need to also lock on the MDS holding the destination file, + // but we don't support hardlinks to different servers, yet + ParentNameLock fromLock; + ParentNameLock toLock; + + FileIDLock fileLock; + + FileIDLock dirLock(&store, getToDirInfo()->getEntryID(), true); + + // take care about lock ordering! see MirroredMessage::lock() + if (getFromDirInfo()->getEntryID() < getToDirInfo()->getEntryID() + || (getFromDirInfo()->getEntryID() == getToDirInfo()->getEntryID() + && getFromName() < getToName())) + { + fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; + toLock = {&store, getToDirInfo()->getEntryID(), getToName()}; + } + else if (getFromDirInfo()->getEntryID() == getToDirInfo()->getEntryID() + && getFromName() == getToName()) + { + fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; + } + else + { + toLock = {&store, getToDirInfo()->getEntryID(), getToName()}; + fromLock = {&store, getFromDirInfo()->getEntryID(), getFromName()}; + } + + // We need to lock the inode because hardlinking modifies the link count. + // If the file inode is present on the current meta node or buddy-group, + // then acquire the inodeLock. + App* app = Program::getApp(); + NumNodeID ownerNodeID = getFromInfo()->getOwnerNodeID(); + bool isLocalInode = ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || + (isMirrored() && ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())); + + if (isLocalInode) + fileLock = {&store, getFromInfo()->getEntryID(), true}; + + return std::make_tuple( + std::move(dirLock), + std::move(fromLock), + std::move(toLock), + std::move(fileLock)); +} + +bool HardlinkMsgEx::processIncoming(ResponseContext& ctx) +{ + LOG_DBG(GENERAL, DEBUG, "", + ("fromDirID", getFromDirInfo()->getEntryID()), + ("fromID", getFromInfo()->getEntryID()), + ("fromName", getFromName()), + ("toDirID", getToDirInfo()->getEntryID()), + ("toName", getToName()), + ("buddyMirrored", getToDirInfo()->getIsBuddyMirrored())); + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_HARDLINK); + + return true; +} + +std::unique_ptr HardlinkMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + const char* logContext = "create hard link"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + unsigned updatedLinkCount = 0; + + // reference directory where hardlink needs to be created + DirInode* toDir = metaStore->referenceDir(getToDirInfo()->getEntryID(), + getToDirInfo()->getIsBuddyMirrored(), true); + + // check if file dentry already exists with same name as hardlink + // return error if link already exists + if (likely(toDir)) + { + DirEntry newLink(getToName()); + if (toDir->getFileDentry(getToName(), newLink)) + { + metaStore->releaseDir(toDir->getID()); + return boost::make_unique(FhgfsOpsErr_EXISTS); + } + } + else + { + LogContext(logContext).logErr(std::string("target directory for hard-link doesn't exist," + "dirname: " + getToDirInfo()->getFileName())); + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + } + + // Prevent hardlinks across directories with different buddy mirror configurations + if (getFromDirInfo()->getIsBuddyMirrored() != getToDirInfo()->getIsBuddyMirrored()) + { + LogContext(logContext).logErr("Hardlinks not supported across directories with different " + "buddy mirror config setting. SourceDirID: " + getFromDirInfo()->getEntryID() + + "; TargetDirID: " + getToDirInfo()->getEntryID()); + metaStore->releaseDir(toDir->getID()); + return boost::make_unique(FhgfsOpsErr_NOTSUPP); + } + + // check whether local node/group owns source file's inode or not + NumNodeID ownerNodeID = getFromInfo()->getOwnerNodeID(); + bool isLocalOwner = ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || + (isMirrored() && ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())); + + if (isLocalOwner) + { + std::tie(retVal, updatedLinkCount) = metaStore->makeNewHardlink(getFromInfo()); + } + else if (!isSecondary) + { + MoveFileInodeMsg deinlineMsg(getFromInfo(), MODE_DEINLINE, true); + + RequestResponseArgs rrArgs(NULL, &deinlineMsg, NETMSGTYPE_MoveFileInodeResp); + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); + rrNode.setTargetStates(app->getMetaStateStore()); + + if (getFromInfo()->getIsBuddyMirrored()) + { + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + } + + do + { + // send request to other MDs and receive response + FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (unlikely(resp != FhgfsOpsErr_SUCCESS)) + { + // error + LogContext(logContext).logErr("Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str()); + + retVal = resp; + break; + } + + // response received + const auto moveFileInodeRespMsg = (MoveFileInodeRespMsg*) rrArgs.outRespMsg.get(); + FhgfsOpsErr res = moveFileInodeRespMsg->getResult(); + if (res != FhgfsOpsErr_SUCCESS) + { + // error: either source file not exists or deinline operation failed + LogContext(logContext).logErr("verify and move file inode failed! nodeID: " + + ownerNodeID.str() + "; entryID: " + getFromInfo()->getEntryID()); + + retVal = res; + break; + } + + // success + retVal = res; + updatedLinkCount = moveFileInodeRespMsg->getHardlinkCount(); + } while (false); + } + + // If verify/deinline inode and link count update is successful - only then create new + // dentry in "toDir" with entryID and ownerNodeID of "fromFile" which might reside + // on another meta node/buddy-group + if (retVal == FhgfsOpsErr_SUCCESS) + { + DirEntry newHardlink(DirEntryType_REGULARFILE, getToName(), + getFromInfo()->getEntryID(), getFromInfo()->getOwnerNodeID()); + + newHardlink.removeDentryFeatureFlag(DENTRY_FEATURE_INODE_INLINE); + + // buddy mirroring is inherited from parent directory + if (toDir->getIsBuddyMirrored()) + newHardlink.setBuddyMirrorFeatureFlag(); + + FhgfsOpsErr makeRes = toDir->makeDirEntry(newHardlink); + + if (makeRes != FhgfsOpsErr_SUCCESS) + { + // compensate link count if dentry create fails + // + // for a remote inode, Use SetAttrMsg to decrease link count + // since its a mirrored message so it will update link count on both primary and + // secondary hence don't send this message from secondary buddy + if (!isSecondary && !isLocalOwner) + incDecRemoteLinkCount(ownerNodeID, false); + else + metaStore->incDecLinkCount(getFromInfo(), -1); + + retVal = makeRes; + } + } + + if (retVal == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + fixInodeTimestamp(*toDir, dirTimestamps); + + if (isLocalOwner) + { + auto [file, referenceRes] = metaStore->referenceFile(getFromInfo()); + if (file) + { + fixInodeTimestamp(*file, fileTimestamps, getFromInfo()); + metaStore->releaseFile(getFromInfo()->getParentEntryID(), file); + } + } + } + + metaStore->releaseDir(toDir->getID()); + + if (!isSecondary && retVal == FhgfsOpsErr_SUCCESS && app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext(getFromInfo(), getFromInfo()->getParentEntryID(), + getMsgHeaderUserID(), "", updatedLinkCount, isSecondary); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + return boost::make_unique(retVal); +} + +FhgfsOpsErr HardlinkMsgEx::incDecRemoteLinkCount(NumNodeID const& ownerNodeID, bool increment) +{ + const char* logContext = "incDecRemoteLinkCount"; + App* app = Program::getApp(); + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + SettableFileAttribs dummyAttrib = {}; + SetAttrMsg msg(getFromInfo(), 0, &dummyAttrib); + + if (increment) + { + msg.addMsgHeaderFeatureFlag(SETATTRMSG_FLAG_INCR_NLINKCNT); + } + else + { + msg.addMsgHeaderFeatureFlag(SETATTRMSG_FLAG_DECR_NLINKCNT); + } + + RequestResponseArgs rrArgs(NULL, &msg, NETMSGTYPE_SetAttrResp); + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); + + rrNode.setTargetStates(app->getMetaStateStore()); + + if (getFromInfo()->getIsBuddyMirrored()) + { + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + } + + do + { + // send request to other MDs and receive response + FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (unlikely(resp != FhgfsOpsErr_SUCCESS)) + { + // error + LogContext(logContext).log(Log_WARNING, + "Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str()); + retVal = resp; + break; + } + + // response received + const auto incLinkCountResp = (SetAttrRespMsg*) rrArgs.outRespMsg.get(); + FhgfsOpsErr res = static_cast(incLinkCountResp->getValue()); + if (res != FhgfsOpsErr_SUCCESS) + { + // error: either source file not exists or nlink count increment failed + std::string operationType = (increment ? "increase": "decrease"); + LogContext(logContext).logErr("nLink count " + operationType + + " failed! nodeID: " + ownerNodeID.str() + "; " + + "entryID: " + getFromInfo()->getEntryID()); + + retVal = res; + break; + } + + // success + retVal = res; + } while (false); + + return retVal; +} + +void HardlinkMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_HardlinkResp); +} diff --git a/meta/source/net/message/storage/creating/HardlinkMsgEx.h b/meta/source/net/message/storage/creating/HardlinkMsgEx.h new file mode 100644 index 0000000..8767730 --- /dev/null +++ b/meta/source/net/message/storage/creating/HardlinkMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +class HardlinkMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple + lock(EntryLockStore& store) override; + + bool isMirrored() override { return getFromInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + FhgfsOpsErr incDecRemoteLinkCount(NumNodeID const& ownerNodeID, bool increment); + const char* mirrorLogContext() const override { return "HardlinkMsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/creating/MkDirMsgEx.cpp b/meta/source/net/message/storage/creating/MkDirMsgEx.cpp new file mode 100644 index 0000000..d85971c --- /dev/null +++ b/meta/source/net/message/storage/creating/MkDirMsgEx.cpp @@ -0,0 +1,411 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RmDirMsgEx.h" +#include "MkDirMsgEx.h" + + +bool MkDirMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + entryID = StorageTk::generateFileID(app->getLocalNode().getNumID()); + + BaseType::processIncoming(ctx); + + // update operation counters + updateNodeOp(ctx, MetaOpCounter_MKDIR); + + return true; +} + +std::unique_ptr MkDirMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + auto result = isSecondary + ? mkDirSecondary() + : mkDirPrimary(ctx); + + if (result && result->getResult() != FhgfsOpsErr_SUCCESS) + LOG_DBG(GENERAL, DEBUG, "Failed to create directory", + ("parentID", getParentInfo()->getEntryID()), + ("newDirName", getNewDirName()), + ("error", result->getResult())); + + return result; +} + +std::tuple MkDirMsgEx::lock(EntryLockStore& store) +{ + HashDirLock hashLock; + + // during resync we must lock the hash dir of the new inode even if the inode will be created on + // a different node because we select the target node for the inode only after we have locked our + // structures. + if (resyncJob && resyncJob->isRunning()) + hashLock = {&store, MetaStorageTk::getMetaInodeHash(entryID)}; + + FileIDLock dirLock(&store, getParentInfo()->getEntryID(), true); + ParentNameLock dentryLock(&store, getParentInfo()->getEntryID(), getNewDirName()); + + return std::make_tuple(std::move(hashLock), std::move(dirLock), std::move(dentryLock)); +} + +std::unique_ptr MkDirMsgEx::mkDirPrimary(ResponseContext& ctx) +{ + const char* logContext = "MkDirMsg (mkDirPrimary)"; + + App* app = Program::getApp(); + ModificationEventFlusher* modEventFlusher = app->getModificationEventFlusher(); + const bool modEventLoggingEnabled = modEventFlusher->isLoggingEnabled(); + MetaStore* metaStore = app->getMetaStore(); + Config* config = app->getConfig(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + NodeCapacityPools* metaCapacityPools; + NumNodeID expectedOwnerID; + + const EntryInfo* const parentInfo = getParentInfo(); + const std::string& newName = getNewDirName(); + + FhgfsOpsErr retVal; + + // not a good idea to use scoped locks here, because we don't have a well-defined scope with + // only buddy mirrored paths here; directly use entrylockstore + EntryLockStore* entryLockStore = Program::getApp()->getSessions()->getEntryLockStore(); + + // reference parent + DirInode* parentDir = metaStore->referenceDir(parentInfo->getEntryID(), + parentInfo->getIsBuddyMirrored(), true); + if(!parentDir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS, EntryInfo()); + + // check whether localNode owns this (parent) directory + NumNodeID localNodeID = app->getLocalNodeNumID(); + bool isBuddyMirrored = parentDir->getIsBuddyMirrored() + && !isMsgHeaderFeatureFlagSet(MKDIRMSG_FLAG_NOMIRROR); + + // check whether localNode owns this (parent) directory; if parentDir is buddy mirrored compare + // ownership to buddy group id, otherwise to node id itself + if (parentDir->getIsBuddyMirrored()) + expectedOwnerID = NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ); + else + expectedOwnerID = localNodeID; + + if (isBuddyMirrored) + metaCapacityPools = app->getMetaBuddyCapacityPools(); + else + metaCapacityPools = app->getMetaCapacityPools(); + + if(parentDir->getOwnerNodeID() != expectedOwnerID) + { // this node doesn't own the parent dir + LogContext(logContext).logErr(std::string("Dir-owner mismatch: \"") + + parentDir->getOwnerNodeID().str() + "\" vs. \"" + + expectedOwnerID.str() + "\""); + metaStore->releaseDir(parentInfo->getEntryID() ); + return boost::make_unique(FhgfsOpsErr_NOTOWNER, EntryInfo()); + } + + // choose new directory owner... + unsigned numDesiredTargets = 1; + unsigned minNumRequiredTargets = numDesiredTargets; + UInt16Vector newOwnerNodes; + + metaCapacityPools->chooseStorageTargets(numDesiredTargets, minNumRequiredTargets, + &getPreferredNodes(), &newOwnerNodes); + + if(unlikely(newOwnerNodes.size() < minNumRequiredTargets) ) + { // (might be caused by a bad list of preferred targets) + LogContext(logContext).logErr("No metadata servers available for new directory: " + newName); + + metaStore->releaseDir(parentInfo->getEntryID() ); + // we know that *some* metadata server must exist, since we are obviously active when we get + // here. most likely a client has received a pool update before we have, or we have been + // switched from secondary to primary and haven't been set to Good yet. + // if preferred nodes have been set (currently only done by ctl), those may also be registered + // as unavailable at the current time. + // have the client retry until things work out. + return boost::make_unique(FhgfsOpsErr_COMMUNICATION, EntryInfo()); + } + + const uint16_t ownerNodeID = newOwnerNodes[0]; + const std::string parentEntryID = parentInfo->getEntryID(); + int entryInfoFlags = isBuddyMirrored ? ENTRYINFO_FEATURE_BUDDYMIRRORED : 0; + + int mode = getMode(); + const int umask = getUmask(); + CharVector parentDefaultACLXAttr; + CharVector accessACLXAttr; + + if (config->getStoreClientACLs()) + { + // Determine the ACLs of the new directory. + PosixACL parentDefaultACL; + bool needsACL; + FhgfsOpsErr parentDefaultACLRes; + + std::tie(parentDefaultACLRes, parentDefaultACLXAttr, std::ignore) = parentDir->getXAttr( + nullptr, PosixACL::defaultACLXAttrName, XATTR_SIZE_MAX); + + if (parentDefaultACLRes == FhgfsOpsErr_SUCCESS) + { + // parent has a default ACL + if (!parentDefaultACL.deserializeXAttr(parentDefaultACLXAttr)) + { + LogContext(logContext).log(Log_ERR, + "Error deserializing directory default ACL for directory ID " + parentDir->getID()); + retVal = FhgfsOpsErr_INTERNAL; + goto clean_up; + } + + if (!parentDefaultACL.empty()) + { + // Note: This modifies the mode bits as well as the ACL itself. + FhgfsOpsErr modeRes = parentDefaultACL.modifyModeBits(mode, needsACL); + setMode(mode, 0); + + if (modeRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_ERR, "Error generating access ACL for new directory " + + newName); + retVal = FhgfsOpsErr_INTERNAL; + goto clean_up; + } + + if (needsACL) + parentDefaultACL.serializeXAttr(accessACLXAttr); + } + else + { + // On empty ACL, clear the Xattr, so it doesn't get set on the newly created dir + parentDefaultACLXAttr.clear(); + } + } + + if (parentDefaultACLRes == FhgfsOpsErr_NODATA + || (parentDefaultACLRes == FhgfsOpsErr_SUCCESS && parentDefaultACL.empty())) + { + // containing dir has no ACL, so we can continue without one. + mode &= ~umask; + setMode(mode, umask); + } + + if (parentDefaultACLRes != FhgfsOpsErr_SUCCESS && parentDefaultACLRes != FhgfsOpsErr_NODATA) + { + LogContext(logContext).log(Log_ERR, + "Error loading default ACL for directory ID " + parentDir->getID() ); + retVal = parentDefaultACLRes; + goto clean_up; + } + } + + newEntryInfo.set(NumNodeID(ownerNodeID), parentEntryID, entryID, newName, + DirEntryType_DIRECTORY, entryInfoFlags); + + // create remote dir metadata + // (we create this before the dentry to reduce the risk of dangling dentries) + retVal = mkRemoteDirInode(*parentDir, newName, &newEntryInfo, parentDefaultACLXAttr, + accessACLXAttr); + + if ( likely(retVal == FhgfsOpsErr_SUCCESS) ) + { // remote dir created => create dentry in parent dir + // note: we can't lock before this point, because mkRemoteDirInode will also send a message + // that needs to aquire a lock on the ID + FileIDLock lock; + + if (parentInfo->getIsBuddyMirrored()) + lock = {entryLockStore, entryID, true}; + + retVal = mkDirDentry(*parentDir, newName, &newEntryInfo, isBuddyMirrored); + + if ( retVal != FhgfsOpsErr_SUCCESS ) + { // error (or maybe name just existed already) => compensate metaDir creation + // note: unlock needs to happen before a possible remoteDirCompensation, because rmDir + // will also need to lock entryID + lock = {}; + mkRemoteDirCompensate(&newEntryInfo); + } + else + { + if (app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext(&newEntryInfo, newEntryInfo.getParentEntryID(), + // This is not the secondary node if mkDirPrimary() was called. + getMsgHeaderUserID(), "", INITIAL_DIR_LINK_COUNT, false); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + } + } + +clean_up: + metaStore->releaseDir(parentInfo->getEntryID() ); + + if (modEventLoggingEnabled) + modEventFlusher->add(ModificationEvent_DIRCREATED, newEntryInfo.getEntryID()); + + return boost::make_unique(retVal, std::move(newEntryInfo)); +} + +std::unique_ptr MkDirMsgEx::mkDirSecondary() +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + // only create the dentry here; forwarding of inode creation directly happens in MkLocalFileMsg + // and error handling and compensation is done by primary + FhgfsOpsErr retVal; + + // reference parent + DirInode* parentDir = metaStore->referenceDir(getParentInfo()->getEntryID(), + getParentInfo()->getIsBuddyMirrored(), true); + if(!parentDir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS, EntryInfo()); + + retVal = mkDirDentry(*parentDir, getCreatedEntryInfo()->getFileName(), getCreatedEntryInfo(), + getCreatedEntryInfo()->getIsBuddyMirrored()); + + metaStore->releaseDir(getParentInfo()->getEntryID()); + + return boost::make_unique(retVal, *getCreatedEntryInfo()); +} + +FhgfsOpsErr MkDirMsgEx::mkDirDentry(DirInode& parentDir, const std::string& name, + const EntryInfo* entryInfo, const bool isBuddyMirrored) +{ + const std::string entryID = entryInfo->getEntryID(); + const NumNodeID ownerNodeID = entryInfo->getOwnerNodeID(); + + DirEntry newDirDentry(DirEntryType_DIRECTORY, name, entryID, ownerNodeID); + + if(isBuddyMirrored) + newDirDentry.setBuddyMirrorFeatureFlag(); + + const FhgfsOpsErr mkRes = parentDir.makeDirEntry(newDirDentry); + + if (mkRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(parentDir, parentTimestamps); + + return mkRes; +} + +/** + * Create dir inode on a remote server. + * + * @param name only used for logging + * @param mirrorNodeID 0 for disabled mirroring + */ +FhgfsOpsErr MkDirMsgEx::mkRemoteDirInode(DirInode& parentDir, const std::string& name, + EntryInfo* entryInfo, const CharVector& defaultACLXAttr, const CharVector& accessACLXAttr) +{ + const char* logContext = "MkDirMsg (mk dir inode)"; + + App* app = Program::getApp(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + StripePattern* pattern = parentDir.getStripePatternClone(); + NumNodeID ownerNodeID = entryInfo->getOwnerNodeID(); + + RemoteStorageTarget rstInfo; + if (parentDir.getIsRstAvailable()) + rstInfo.set(parentDir.getRemoteStorageTargetInfo()); + + LOG_DEBUG(logContext, Log_DEBUG, + "Creating dir inode at metadata node: " + ownerNodeID.str() + "; dirname: " + name); + + // prepare request + + NumNodeID parentNodeID = app->getLocalNode().getNumID(); + MkLocalDirMsg mkMsg(entryInfo, getUserID(), getGroupID(), getMode(), pattern, &rstInfo, + parentNodeID, defaultACLXAttr, accessACLXAttr); + + RequestResponseArgs rrArgs(NULL, &mkMsg, NETMSGTYPE_MkLocalDirResp); + + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes() ); + rrNode.setTargetStates(app->getMetaStateStore() ); + + if(entryInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + do // (this loop just exists to enable the "break"-jump, so it's not really a loop) + { + // send request to other mds and receive response + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str() + "; " + + "dirname: " + name); + retVal = requestRes; + break; + } + + // correct response type received + const auto mkRespMsg = (const MkLocalDirRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr mkRemoteInodeRes = mkRespMsg->getResult(); + if(mkRemoteInodeRes != FhgfsOpsErr_SUCCESS) + { // error: remote dir inode not created + LogContext(logContext).log(Log_WARNING, + "Metadata server failed to create dir inode. " + "nodeID: " + ownerNodeID.str() + "; " + + "dirname: " + name); + + retVal = mkRemoteInodeRes; + break; + } + + // success: remote dir inode created + LOG_DEBUG(logContext, Log_DEBUG, + "Metadata server created dir inode. " + "nodeID: " + ownerNodeID.str() + "; " + "dirname: " + name); + + } while(false); + + + delete(pattern); + + return retVal; +} + +/** + * Remove dir metadata on a remote server to compensate for creation. + */ +FhgfsOpsErr MkDirMsgEx::mkRemoteDirCompensate(EntryInfo* entryInfo) +{ + LogContext log("MkDirMsg (undo dir inode [" + entryInfo->getFileName() + "])"); + + FhgfsOpsErr rmRes = RmDirMsgEx::rmRemoteDirInode(entryInfo); + + if(unlikely(rmRes != FhgfsOpsErr_SUCCESS) ) + { // error + log.log(Log_WARNING, std::string("Compensation not completely successful. ") + + "File system might contain (uncritical) inconsistencies."); + + return rmRes; + } + + log.log(Log_SPAM, "Creation of dir inode compensated"); + + return rmRes; +} + +void MkDirMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + // secondary needs to know the created entryInfo, because it needs to use the same information + setCreatedEntryInfo(&newEntryInfo); + + // not needed on secondary + clearPreferredNodes(); + + sendToSecondary(ctx, *this, NETMSGTYPE_MkDirResp); +} diff --git a/meta/source/net/message/storage/creating/MkDirMsgEx.h b/meta/source/net/message/storage/creating/MkDirMsgEx.h new file mode 100644 index 0000000..7f163f8 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkDirMsgEx.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +class MkDirMsgEx : public MirroredMessage> +{ + public: + typedef ErrorAndEntryResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + private: + // Initial hard link count for a newly created directory (self and "." entry). + // Note: ".." entry increments the parent's link count, not this directory's. + static constexpr unsigned INITIAL_DIR_LINK_COUNT = 2; + + std::string entryID; + + std::unique_ptr mkDirPrimary(ResponseContext& ctx); + std::unique_ptr mkDirSecondary(); + + FhgfsOpsErr mkDirDentry(DirInode& parentDir, const std::string& name, + const EntryInfo* entryInfo, const bool isBuddyMirrored); + + FhgfsOpsErr mkRemoteDirInode(DirInode& parentDir, const std::string& name, + EntryInfo* entryInfo, const CharVector& defaultACLXAttr, const CharVector& accessACLXAttr); + FhgfsOpsErr mkRemoteDirCompensate(EntryInfo* entryInfo); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MkDirMsgEx/forward"; } + + EntryInfo newEntryInfo; +}; + diff --git a/meta/source/net/message/storage/creating/MkFileMsgEx.cpp b/meta/source/net/message/storage/creating/MkFileMsgEx.cpp new file mode 100644 index 0000000..54212e0 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkFileMsgEx.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include +#include +#include +#include +#include "MkFileMsgEx.h" + +std::tuple MkFileMsgEx::lock(EntryLockStore& store) +{ + FileIDLock dirLock(&store, getParentInfo()->getEntryID(), true); + ParentNameLock dentryLock(&store, getParentInfo()->getEntryID(), getNewName()); + FileIDLock fileLock(&store, newEntryID, true); + + return std::make_tuple( + std::move(dirLock), + std::move(dentryLock), + std::move(fileLock)); +} + + +bool MkFileMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "MkFileMsg incoming"; +#endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_DEBUG, "parentEntryID: " + + getParentInfo()->getEntryID() + " newFileName: " + getNewName() ); + + LOG_DEBUG(logContext, Log_DEBUG, + "BuddyMirrored: " + std::string(getParentInfo()->getIsBuddyMirrored() ? "'Yes'" : "'No'") + + " Secondary: " + std::string(hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond) ? + "Yes" : "No") ); + + if (!hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + createTime = TimeAbs().getTimeval()->tv_sec; + + mkDetails = {getNewName(), getUserID(), getGroupID(), getMode(), getUmask(), createTime}; + + if (getParentInfo()->getIsBuddyMirrored()) + { + if (!hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + newEntryID = StorageTk::generateFileID(Program::getApp()->getLocalNode().getNumID()); + else + newEntryID = getNewEntryID(); + + mkDetails.setNewEntryID(newEntryID.c_str()); + } + + BaseType::processIncoming(ctx); + + updateNodeOp(ctx, MetaOpCounter_MKFILE); + + return true; +} + +std::unique_ptr MkFileMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + if (isSecondary) + return executeSecondary(); + else + return executePrimary(); +} + +std::unique_ptr MkFileMsgEx::executePrimary() +{ + DirInode* dir = Program::getApp()->getMetaStore()->referenceDir( + getParentInfo()->getEntryID(), getParentInfo()->getIsBuddyMirrored(), true); + if (!dir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS, entryInfo); + + FhgfsOpsErr mkRes; + if (isMsgHeaderFeatureFlagSet(MKFILEMSG_FLAG_STORAGEPOOLID)) + { + mkRes = MsgHelperMkFile::mkFile(*dir, &mkDetails, &getPreferredNodes(), + getNumTargets(), getChunkSize(), NULL, getRemoteStorageTarget(), + &entryInfo, &inodeData, storagePoolId); + } + else + { + mkRes = MsgHelperMkFile::mkFile(*dir, &mkDetails, &getPreferredNodes(), + getNumTargets(), getChunkSize(), NULL, getRemoteStorageTarget(), &entryInfo, &inodeData); + } + + if (mkRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*dir, dirTimestamps); + + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + + if (mkRes == FhgfsOpsErr_SUCCESS && Program::getApp()->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext(&entryInfo, entryInfo.getParentEntryID(), + // This is not the secondary node if executePrimary() was called. + getMsgHeaderUserID(), "", this->inodeData.getInodeStatData()->getNumHardlinks(), false); + + logEvent(Program::getApp()->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + return boost::make_unique(mkRes, entryInfo); +} + +std::unique_ptr MkFileMsgEx::executeSecondary() +{ + StripePattern* stripePattern = getPattern().clone(); + + FileInodeStoreData inodeData; + + DirInode* dir = Program::getApp()->getMetaStore()->referenceDir( + getParentInfo()->getEntryID(), getParentInfo()->getIsBuddyMirrored(), true); + if (!dir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS, entryInfo); + + FhgfsOpsErr mkRes = MsgHelperMkFile::mkFile(*dir, &mkDetails, &getPreferredNodes(), + getNumTargets(), getChunkSize(), stripePattern, getRemoteStorageTarget(), &entryInfo, &inodeData); + + if (mkRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*dir, dirTimestamps); + + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + + return boost::make_unique(mkRes, entryInfo); +} + +void MkFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + // secondary needs to know the created entryID and stripe pattern, because it needs to use the + // same information + setNewEntryID(newEntryID.c_str()); + setPattern(inodeData.getStripePattern()); + setRemoteStorageTarget(getRemoteStorageTarget()); + + sendToSecondary(ctx, *this, NETMSGTYPE_MkFileResp); +} diff --git a/meta/source/net/message/storage/creating/MkFileMsgEx.h b/meta/source/net/message/storage/creating/MkFileMsgEx.h new file mode 100644 index 0000000..aef2f87 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkFileMsgEx.h @@ -0,0 +1,48 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + + +class MkFileMsgEx : public MirroredMessage> +{ + public: + typedef ErrorAndEntryResponseState ResponseState; + + MkFileMsgEx() + : mkDetails({}, 0, 0, 0, 0, 0) + { + } + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executePrimary(); + std::unique_ptr executeSecondary(); + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MkFileMsgEx/forward"; } + + FileInodeStoreData inodeData; + MkFileDetails mkDetails; + std::string newEntryID; + + EntryInfo entryInfo; +}; + diff --git a/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.cpp b/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.cpp new file mode 100644 index 0000000..3a37d98 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MkFileWithPatternMsgEx.h" + +// Called from fhgfs-ctl (online_cfg) to create a file on a specific node. + +std::tuple MkFileWithPatternMsgEx::lock( + EntryLockStore& store) +{ + // no need to lock the created file as well, since + // a) no other operation can create the same file id + // b) until we finish, no part of the system except us knows the new file id + // c) if bulk resync gets the file while it is incomplete, individual resync will get it again + FileIDLock dirLock(&store, getParentInfo()->getEntryID(), true); + ParentNameLock dentryLock(&store, getParentInfo()->getEntryID(), getNewFileName()); + FileIDLock fileLock(&store, entryID, true); + + return std::make_tuple(std::move(dirLock), std::move(dentryLock), std::move(fileLock)); +} + +bool MkFileWithPatternMsgEx::processIncoming(ResponseContext& ctx) +{ + const EntryInfo* parentInfo = getParentInfo(); + std::string newFileName = getNewFileName(); + EntryInfo newEntryInfo; + + LOG_DEBUG("MkFileWithPatternMsg", Log_DEBUG, + " parentDirID: " + parentInfo->getEntryID() + " newFileName: " + newFileName + + " isBuddyMirrored: " + (parentInfo->getIsBuddyMirrored() ? "'true'" : "'false'")); + + // create ID first + if (parentInfo->getIsBuddyMirrored()) + entryID = StorageTk::generateFileID(Program::getApp()->getLocalNode().getNumID()); + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr MkFileWithPatternMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + EntryInfo newEntryInfo; + + MkFileDetails mkDetails(getNewFileName(), getUserID(), getGroupID(), getMode(), getUmask(), + TimeAbs().getTimeval()->tv_sec); + if (!entryID.empty()) + mkDetails.setNewEntryID(entryID.c_str()); + + FhgfsOpsErr mkRes = mkFile(getParentInfo(), mkDetails, &newEntryInfo, inodeDiskData); + + updateNodeOp(ctx, MetaOpCounter_MKFILE); + + return boost::make_unique(mkRes, std::move(newEntryInfo)); +} + + +/** + * @param dir current directory + * @param currentDepth 1-based path depth + */ +FhgfsOpsErr MkFileWithPatternMsgEx::mkFile(const EntryInfo* parentInfo, MkFileDetails& mkDetails, + EntryInfo* outEntryInfo, FileInodeStoreData& inodeDiskData) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr retVal; + + // reference parent + DirInode* dir = metaStore->referenceDir(parentInfo->getEntryID(), + parentInfo->getIsBuddyMirrored(), true); + if ( !dir ) + return FhgfsOpsErr_PATHNOTEXISTS; + + // create meta file + retVal = mkMetaFile(*dir, mkDetails, outEntryInfo, inodeDiskData); + + if (shouldFixTimestamps()) + fixInodeTimestamp(*dir, dirTimestamps); + + // clean-up + metaStore->releaseDir(parentInfo->getEntryID()); + + return retVal; +} + +/** + * Create an inode and directory-entry + */ +FhgfsOpsErr MkFileWithPatternMsgEx::mkMetaFile(DirInode& dir, MkFileDetails& mkDetails, + EntryInfo* outEntryInfo, FileInodeStoreData& inodeDiskData) +{ + // note: to guarantee amtomicity of file creation (from the view of a client), we have + // to create the inode first and insert the directory entry afterwards + + std::unique_ptr stripePattern(getPattern().clone()); + const UInt16Vector* stripeTargets = stripePattern->getStripeTargetIDs(); + StoragePoolId storagePoolId = stripePattern->getStoragePoolId(); + + if (stripeTargets->empty()) + { + StoragePoolPtr storagePool = + Program::getApp()->getStoragePoolStore()->getPool(storagePoolId); + + if (!storagePool) + { + LOG(GENERAL, ERR, "Given Storage Pool ID " + + StringTk::uintToStr(storagePoolId.val()) + " doesn't exist."); + return FhgfsOpsErr_INTERNAL; + } + + UInt16Vector chosenStripeTargets; + + if(stripePattern->getPatternType() == StripePatternType_BuddyMirror) + { + storagePool->getBuddyCapacityPools()->chooseStorageTargets( + stripePattern->getDefaultNumTargets(), stripePattern->getMinNumTargets(), + nullptr, &chosenStripeTargets); + } + else + { + storagePool->getTargetCapacityPools()->chooseStorageTargets( + stripePattern->getDefaultNumTargets(), stripePattern->getMinNumTargets(), + nullptr, &chosenStripeTargets); + } + stripePattern->getStripeTargetIDsModifyable()->swap(chosenStripeTargets); + } + + // check if num targets and target list match and if targets actually exist + if(stripeTargets->empty() || (stripeTargets->size() < stripePattern->getMinNumTargets() ) ) + { + LOG(GENERAL, ERR, "No (or not enough) storage targets defined.", + ("numTargets", stripeTargets->size()), + ("expectedMinNumTargets", stripePattern->getMinNumTargets())); + return FhgfsOpsErr_INTERNAL; + } + + for (auto iter = stripeTargets->begin(); iter != stripeTargets->end(); iter++) + { + if(stripePattern->getPatternType() == StripePatternType_BuddyMirror) + { + if (!Program::getApp()->getStorageBuddyGroupMapper()->getPrimaryTargetID(*iter)) + { + LOG(GENERAL, ERR, "Unknown buddy group targets defined.", + ("targetId", *iter)); + return FhgfsOpsErr_UNKNOWNTARGET; + } + } + else + { + if (!Program::getApp()->getTargetMapper()->targetExists(*iter)) + { + LOG(GENERAL, ERR, "Unknown storage targets defined.", + ("targetId", *iter)); + return FhgfsOpsErr_UNKNOWNTARGET; + } + } + } + + // check if chunk size satisfies constraints + if (!MathTk::isPowerOfTwo(stripePattern->getChunkSize())) + { + LOG(GENERAL, DEBUG, "Invalid chunk size: Must be a power of two.", + stripePattern->getChunkSize()); + return FhgfsOpsErr_INTERNAL; + } + if (stripePattern->getChunkSize() < STRIPEPATTERN_MIN_CHUNKSIZE) + { + LOG(GENERAL, DEBUG, "Invalid chunk size: Below minimum size.", + stripePattern->getChunkSize(), + ("minChunkSize", STRIPEPATTERN_MIN_CHUNKSIZE)); + return FhgfsOpsErr_INTERNAL; + } + + return Program::getApp()->getMetaStore()->mkNewMetaFile( + dir, &mkDetails, std::move(stripePattern), getRemoteStorageTarget(), outEntryInfo, + &inodeDiskData); + // (note: internally deletes stripePattern) +} + +void MkFileWithPatternMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + UInt16List preferredTargets; // can be an empty dummy + std::string newFileName = getNewFileName(); + + MkFileMsg mkFileMsg(getParentInfo(), newFileName, getUserID(), getGroupID(), getMode(), + getUmask(), &preferredTargets); + + mkFileMsg.addFlag(NetMessageHeader::Flag_BuddyMirrorSecond); + // secondary needs to know the created entryID ans stripe pattern, because it needs to use the + // same information + mkFileMsg.setNewEntryID(entryID.c_str()); + mkFileMsg.setPattern(inodeDiskData.getStripePattern()); + mkFileMsg.setDirTimestamps(dirTimestamps); + mkFileMsg.setCreateTime(inodeDiskData.getInodeStatData()->getCreationTimeSecs()); + mkFileMsg.setRemoteStorageTarget(getRemoteStorageTarget()); + + sendToSecondary(ctx, mkFileMsg, NETMSGTYPE_MkFileResp); +} diff --git a/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.h b/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.h new file mode 100644 index 0000000..a772b95 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkFileWithPatternMsgEx.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +/** + * Similar to class MsgHelperMkFile, but called with a create pattern, for example from fhgfs-ctl + * or from ioctl calls. + */ +class MkFileWithPatternMsgEx : public MirroredMessage> +{ + public: + typedef ErrorAndEntryResponseState + ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + private: + MirroredTimestamps dirTimestamps; + + FhgfsOpsErr mkFile(const EntryInfo* parentInfo, MkFileDetails& mkDetails, + EntryInfo* outEntryInfo, FileInodeStoreData& inodeDiskData); + FhgfsOpsErr mkMetaFile(DirInode& dir, MkFileDetails& mkDetails, + EntryInfo* outEntryInfo, FileInodeStoreData& inodeDiskData); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MkFileWithPatternMsgEx/forward"; } + + FileInodeStoreData inodeDiskData; + std::string entryID; +}; + diff --git a/meta/source/net/message/storage/creating/MkLocalDirMsgEx.cpp b/meta/source/net/message/storage/creating/MkLocalDirMsgEx.cpp new file mode 100644 index 0000000..90b2975 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkLocalDirMsgEx.cpp @@ -0,0 +1,76 @@ +#include +#include +#include + +#include "MkLocalDirMsgEx.h" + +HashDirLock MkLocalDirMsgEx::lock(EntryLockStore& store) +{ + // we usually need not lock anything here, because the inode ID will be completely unknown to + // anyone until we finish processing here *and* on the metadata server that sent this message. + // during resync though we need to lock the hash dir to avoid interefence between bulk resync and + // mod resync. + + // do not lock the hash dir if we are creating the inode on the same meta node as the dentry, + // MkDir will have already locked the hash dir. + if (!rctx->isLocallyGenerated() && resyncJob && resyncJob->isRunning()) + return {&store, MetaStorageTk::getMetaInodeHash(getEntryInfo()->getEntryID())}; + + return {}; +} + +bool MkLocalDirMsgEx::processIncoming(ResponseContext& ctx) +{ + EntryInfo* entryInfo = getEntryInfo(); + + LOG_DBG(GENERAL, DEBUG, "", entryInfo->getEntryID(), entryInfo->getFileName()); + (void) entryInfo; + + rctx = &ctx; + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr MkLocalDirMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + StripePattern& pattern = getPattern(); + + RemoteStorageTarget* rstInfo = getRemoteStorageTarget(); + + EntryInfo *entryInfo = getEntryInfo(); + NumNodeID parentNodeID = getParentNodeID(); + + NumNodeID ownerNodeID = entryInfo->getIsBuddyMirrored() + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) + : app->getLocalNode().getNumID(); + + DirInode newDir(entryInfo->getEntryID(), getMode(), getUserID(), + getGroupID(), ownerNodeID, pattern, entryInfo->getIsBuddyMirrored()); + + newDir.setParentInfoInitial(entryInfo->getParentEntryID(), parentNodeID); + + FhgfsOpsErr mkRes = metaStore->makeDirInode(newDir, getDefaultACLXAttr(), getAccessACLXAttr() ); + + if (!rstInfo->hasInvalidVersion() && (mkRes == FhgfsOpsErr_SUCCESS)) + { + FhgfsOpsErr setRstRes = newDir.setRemoteStorageTarget(*rstInfo); + if (setRstRes != FhgfsOpsErr_SUCCESS) + { + LogContext("MkLocalDir").log(Log_WARNING, "Failed to set remote storage targets for " + "dirID: " + newDir.getID() + ". RST might be invalid."); + } + } + + if (mkRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(newDir, dirTimestamps); + + return boost::make_unique(mkRes); +} + +void MkLocalDirMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_MkLocalDirResp); +} diff --git a/meta/source/net/message/storage/creating/MkLocalDirMsgEx.h b/meta/source/net/message/storage/creating/MkLocalDirMsgEx.h new file mode 100644 index 0000000..f664757 --- /dev/null +++ b/meta/source/net/message/storage/creating/MkLocalDirMsgEx.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class MkLocalDirMsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + HashDirLock lock(EntryLockStore& store) override; + + bool isMirrored() override { return getEntryInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "MkLocalDirMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.cpp b/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.cpp new file mode 100644 index 0000000..d74af03 --- /dev/null +++ b/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.cpp @@ -0,0 +1,67 @@ +#include +#include "MoveFileInodeMsgEx.h" + +std::tuple MoveFileInodeMsgEx::lock(EntryLockStore& store) +{ + EntryInfo* fileInfo = getFromFileEntryInfo(); + + FileIDLock dirLock(&store, fileInfo->getParentEntryID(), true); + ParentNameLock dentryLock(&store, fileInfo->getParentEntryID(), fileInfo->getFileName()); + FileIDLock inodeLock(&store, fileInfo->getEntryID(), true); + + return std::make_tuple( + std::move(dirLock), + std::move(dentryLock), + std::move(inodeLock)); +} + +bool MoveFileInodeMsgEx::processIncoming(ResponseContext& ctx) +{ + rctx = &ctx; + return BaseType::processIncoming(ctx); +} + +std::unique_ptr MoveFileInodeMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + MoveFileInodeMsgResponseState resp; + + if (getMode() == FileInodeMode::MODE_INVALID) + { + // invalid operation requested + return boost::make_unique(std::move(resp)); + } + + EntryInfo* fromFileInfo = getFromFileEntryInfo(); + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + unsigned linkCount = 0; + + if (getCreateHardlink()) + { + std::tie(retVal, linkCount) = metaStore->makeNewHardlink(fromFileInfo); + } + else + { + DirInode* parentDir = metaStore->referenceDir( + fromFileInfo->getParentEntryID(), fromFileInfo->getIsBuddyMirrored(), true); + + if (likely(parentDir)) + { + retVal = metaStore->verifyAndMoveFileInode(*parentDir, fromFileInfo, getMode()); + metaStore->releaseDir(parentDir->getID()); + } + else + retVal = FhgfsOpsErr_PATHNOTEXISTS; + } + + resp.setResult(retVal); + resp.setHardlinkCount(linkCount); + return boost::make_unique(std::move(resp)); +} + +void MoveFileInodeMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_MoveFileInodeResp); +} diff --git a/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.h b/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.h new file mode 100644 index 0000000..2ed1c05 --- /dev/null +++ b/meta/source/net/message/storage/creating/MoveFileInodeMsgEx.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class MoveFileInodeMsgResponseState : public MirroredMessageResponseState +{ + public: + MoveFileInodeMsgResponseState() : result(FhgfsOpsErr_INTERNAL), linkCount(0) {} + + explicit MoveFileInodeMsgResponseState(Deserializer& des) + { + serialize(this, des); + } + + MoveFileInodeMsgResponseState(MoveFileInodeMsgResponseState&& other) : + result(other.result), linkCount(other.linkCount) {} + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + MoveFileInodeRespMsg resp(result, linkCount); + ctx.sendResponse(resp); + } + + void setResult(FhgfsOpsErr res) { this->result = res; } + void setHardlinkCount(unsigned linkCount) { this->linkCount = linkCount; } + bool changesObservableState() const override { return this->result == FhgfsOpsErr_SUCCESS; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_MoveFileInodeResp; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->linkCount; + } + + void serializeContents(Serializer& ser) const override + { + serialize(this, ser); + } + + private: + FhgfsOpsErr result; + unsigned linkCount; +}; + +class MoveFileInodeMsgEx : public MirroredMessage> +{ + public: + typedef MoveFileInodeMsgResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + std::tuple lock(EntryLockStore& store) override; + bool isMirrored() override { return getFromFileEntryInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MoveFileInodeMsgEx/forward"; } +}; diff --git a/meta/source/net/message/storage/creating/RmDirEntryMsgEx.cpp b/meta/source/net/message/storage/creating/RmDirEntryMsgEx.cpp new file mode 100644 index 0000000..3465de8 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmDirEntryMsgEx.cpp @@ -0,0 +1,67 @@ +#include +#include +#include +#include "RmDirEntryMsgEx.h" + +bool RmDirEntryMsgEx::processIncoming(ResponseContext& ctx) +{ + EntryInfo* parentInfo = getParentInfo(); + std::string entryName = getEntryName(); + + FhgfsOpsErr rmRes = rmDirEntry(parentInfo, entryName); + + ctx.sendResponse(RmDirEntryRespMsg(rmRes) ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_RMLINK, + getMsgHeaderUserID() ); + + return true; +} + +FhgfsOpsErr RmDirEntryMsgEx::rmDirEntry(EntryInfo* parentInfo, std::string& entryName) +{ + const char* logContext = "RmDirEntryMsg (rm entry)"; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr retVal; + + + // reference parent + DirInode* parentDir = metaStore->referenceDir(parentInfo->getEntryID(), + parentInfo->getIsBuddyMirrored(), true); + if(!parentDir) + return FhgfsOpsErr_PATHNOTEXISTS; + + DirEntry removeDentry(entryName); + bool getRes = parentDir->getDentry(entryName, removeDentry); + if(!getRes) + retVal = FhgfsOpsErr_PATHNOTEXISTS; + else + { + if(DirEntryType_ISDIR(removeDentry.getEntryType() ) ) + { // remove dir link + retVal = parentDir->removeDir(entryName, NULL); + } + else + if(DirEntryType_ISFILE(removeDentry.getEntryType() ) ) + { + retVal = parentDir->unlinkDirEntry(entryName, &removeDentry, + DirEntry_UNLINK_ID_AND_FILENAME); + } + else + { // unknown entry type + LogContext(logContext).logErr(std::string("Unknown entry type: ") + + StringTk::intToStr(removeDentry.getEntryType() ) ); + + retVal = FhgfsOpsErr_INTERNAL; + } + } + + // clean-up + metaStore->releaseDir(parentInfo->getEntryID() ); + + return retVal; +} + diff --git a/meta/source/net/message/storage/creating/RmDirEntryMsgEx.h b/meta/source/net/message/storage/creating/RmDirEntryMsgEx.h new file mode 100644 index 0000000..d189061 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmDirEntryMsgEx.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include +#include + +// A repair operation, called from fhgfs-ctl + +class RmDirEntryMsgEx : public RmDirEntryMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr rmDirEntry(EntryInfo* parentInfo, std::string& entryName); +}; + diff --git a/meta/source/net/message/storage/creating/RmDirMsgEx.cpp b/meta/source/net/message/storage/creating/RmDirMsgEx.cpp new file mode 100644 index 0000000..1e45211 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmDirMsgEx.cpp @@ -0,0 +1,229 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RmDirMsgEx.h" + + +bool RmDirMsgEx::processIncoming(ResponseContext& ctx) +{ + BaseType::processIncoming(ctx); + + // update operation counters + updateNodeOp(ctx, MetaOpCounter_RMDIR); + + return true; +} + +std::tuple RmDirMsgEx::lock(EntryLockStore& store) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + EntryInfo delEntryInfo; + + DirInode* parentDir = metaStore->referenceDir( + getParentInfo()->getEntryID(), getParentInfo()->getIsBuddyMirrored(), true); + if (!parentDir) + return {}; + else + { + parentDir->getDirEntryInfo(getDelDirName(), delEntryInfo); + metaStore->releaseDir(getParentInfo()->getEntryID()); + } + + FileIDLock parentDirLock; + FileIDLock delDirLock; + HashDirLock hashLock; + + if (resyncJob && resyncJob->isRunning()) + hashLock = {&store, MetaStorageTk::getMetaInodeHash(delEntryInfo.getEntryID())}; + + // lock directories in deadlock-avoidance order, see MirroredMessage::lock() + if (delEntryInfo.getEntryID().empty()) + { + parentDirLock = {&store, getParentInfo()->getEntryID(), true}; + } + else if (delEntryInfo.getEntryID() < getParentInfo()->getEntryID()) + { + delDirLock = {&store, delEntryInfo.getEntryID(), true}; + parentDirLock = {&store, getParentInfo()->getEntryID(), true}; + } + else if (delEntryInfo.getEntryID() == getParentInfo()->getEntryID()) + { + delDirLock = {&store, delEntryInfo.getEntryID(), true}; + } + else + { + parentDirLock = {&store, getParentInfo()->getEntryID(), true}; + delDirLock = {&store, delEntryInfo.getEntryID(), true}; + } + + ParentNameLock parentNameLock(&store, getParentInfo()->getEntryID(), getDelDirName()); + + return std::make_tuple( + std::move(hashLock), + std::move(parentDirLock), + std::move(delDirLock), + std::move(parentNameLock)); +} + +std::unique_ptr RmDirMsgEx::rmDir(ResponseContext& ctx, + const bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = Program::getApp()->getMetaStore(); + ModificationEventFlusher* modEventFlusher = Program::getApp()->getModificationEventFlusher(); + bool modEventLoggingEnabled = modEventFlusher->isLoggingEnabled(); + + FhgfsOpsErr retVal; + + EntryInfo* parentInfo = this->getParentInfo(); + const std::string& delDirName = this->getDelDirName(); + + EntryInfo outDelEntryInfo; + + // reference parent + DirInode* parentDir = metaStore->referenceDir(parentInfo->getEntryID(), + parentInfo->getIsBuddyMirrored(), true); + if(!parentDir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + DirEntry removeDirEntry(delDirName); + bool getEntryRes = parentDir->getDirDentry(delDirName, removeDirEntry); + if(!getEntryRes) + retVal = FhgfsOpsErr_PATHNOTEXISTS; + else + { + int additionalFlags = 0; + + const std::string& parentEntryID = parentInfo->getEntryID(); + removeDirEntry.getEntryInfo(parentEntryID, additionalFlags, &outDelEntryInfo); + + App* app = Program::getApp(); + NumNodeID ownerNodeID = outDelEntryInfo.getOwnerNodeID(); + + // no-comms path: the dir inode is owned by us + if ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || + (isMirrored() && + ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())) + retVal = app->getMetaStore()->removeDirInode(outDelEntryInfo.getEntryID(), + outDelEntryInfo.getIsBuddyMirrored()); + else if (!isSecondary) + retVal = rmRemoteDirInode(&outDelEntryInfo); + else + retVal = FhgfsOpsErr_SUCCESS; + + if(retVal == FhgfsOpsErr_SUCCESS) + { // local removal succeeded => remove meta dir + retVal = parentDir->removeDir(delDirName, NULL); + } + } + + if (retVal == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*parentDir, dirTimestamps); + + // clean-up + metaStore->releaseDir(parentInfo->getEntryID() ); + + if (!isSecondary && retVal == FhgfsOpsErr_SUCCESS && app->getFileEventLogger() && getFileEvent()) + { + EventContext eventCtx = makeEventContext(&outDelEntryInfo, outDelEntryInfo.getParentEntryID(), + getMsgHeaderUserID(), "", 0, isSecondary); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + if (retVal == FhgfsOpsErr_SUCCESS && modEventLoggingEnabled) + { + const std::string& entryID = outDelEntryInfo.getEntryID(); + modEventFlusher->add(ModificationEvent_DIRREMOVED, entryID); + } + + return boost::make_unique(retVal); +} + +/** + * Remove the inode of this directory + */ +FhgfsOpsErr RmDirMsgEx::rmRemoteDirInode(EntryInfo* delEntryInfo) +{ + const char* logContext = "RmDirMsg (rm dir inode)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + NumNodeID ownerNodeID = delEntryInfo->getOwnerNodeID(); + + LOG_DEBUG(logContext, Log_DEBUG, + "Removing dir inode from metadata node: " + ownerNodeID.str() + "; " + "dirname: " + delEntryInfo->getFileName() + "; isBuddyMirrored: " + + StringTk::intToStr(delEntryInfo->getIsBuddyMirrored()) ); + + // prepare request + + RmLocalDirMsg rmMsg(delEntryInfo); + + RequestResponseArgs rrArgs(NULL, &rmMsg, NETMSGTYPE_RmLocalDirResp); + + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes() ); + rrNode.setTargetStates(app->getMetaStateStore() ); + + if (delEntryInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + do // (this loop just exists to enable the "break"-jump, so it's not really a loop) + { + // send request to other mds and receive response + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with metadata server failed. " + "ownerNodeID: " + ownerNodeID.str() + "; " + "dirID: " + delEntryInfo->getEntryID() + "; " + + "dirname: " + delEntryInfo->getFileName() ); + retVal = requestRes; + break; + } + + // correct response type received + const auto rmRespMsg = (const RmLocalDirRespMsg*)rrArgs.outRespMsg.get(); + + retVal = rmRespMsg->getResult(); + if(unlikely( (retVal != FhgfsOpsErr_SUCCESS) && (retVal != FhgfsOpsErr_NOTEMPTY) ) ) + { // error: dir inode not removed + std::string errString = boost::lexical_cast(retVal); + + LogContext(logContext).log(Log_DEBUG, + "Metadata server was unable to remove dir inode. " + "ownerNodeID: " + ownerNodeID.str() + "; " + "dirID: " + delEntryInfo->getEntryID() + "; " + + "dirname: " + delEntryInfo->getFileName() + "; " + + "error: " + errString); + + break; + } + + // success: dir inode removed + LOG_DEBUG(logContext, Log_DEBUG, + "Metadata server removed dir inode: " + "ownerNodeID: " + ownerNodeID.str() + "; " + "dirID: " + delEntryInfo->getEntryID() + "; " + + "dirname: " + delEntryInfo->getFileName() ); + + } while(false); + + + return retVal; +} + +void RmDirMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_RmDirResp); +} diff --git a/meta/source/net/message/storage/creating/RmDirMsgEx.h b/meta/source/net/message/storage/creating/RmDirMsgEx.h new file mode 100644 index 0000000..27defe7 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmDirMsgEx.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + + +class RmDirMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple + lock(EntryLockStore& store) override; + + static FhgfsOpsErr rmRemoteDirInode(EntryInfo* delEntryInfo); + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override + { + return rmDir(ctx, isSecondary); + } + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr rmDir(ResponseContext& ctx, const bool isSecondary); + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "RmDirMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/creating/RmLocalDirMsgEx.cpp b/meta/source/net/message/storage/creating/RmLocalDirMsgEx.cpp new file mode 100644 index 0000000..b917cc2 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmLocalDirMsgEx.cpp @@ -0,0 +1,56 @@ +#include +#include +#include + +#include "RmLocalDirMsgEx.h" + + +std::tuple RmLocalDirMsgEx::lock(EntryLockStore& store) +{ + HashDirLock hashLock; + + // if we are currently under modsync, we must lock the hash dir from which we are removing the + // inode. otherwise bulk sync may interfere with mod sync and cause the resync to fail. + // do not lock the hash dir if we are removing the inode from the same meta node as the dentry, + // RmDir will have already locked the hash dir. + if (!rctx->isLocallyGenerated() && resyncJob && resyncJob->isRunning()) + hashLock = {&store, MetaStorageTk::getMetaInodeHash(getDelEntryInfo()->getEntryID())}; + + return std::make_tuple( + std::move(hashLock), + FileIDLock(&store, getDelEntryInfo()->getEntryID(), true)); +} + +bool RmLocalDirMsgEx::processIncoming(ResponseContext& ctx) +{ + rctx = &ctx; + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr RmLocalDirMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + return rmDir(); +} + +std::unique_ptr RmLocalDirMsgEx::rmDir() +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + EntryInfo* delEntryInfo = this->getDelEntryInfo(); + + LOG_DEBUG("RmLocalDirMsgEx (rmDir)", Log_DEBUG, + "Removing local dir inode: " + delEntryInfo->getFileName() + "; isBuddyMirrored: " + + StringTk::intToStr(delEntryInfo->getIsBuddyMirrored()) ); + + FhgfsOpsErr res = metaStore->removeDirInode(delEntryInfo->getEntryID(), + delEntryInfo->getIsBuddyMirrored()); + + return boost::make_unique(res); +} + +void RmLocalDirMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_RmLocalDirResp); +} diff --git a/meta/source/net/message/storage/creating/RmLocalDirMsgEx.h b/meta/source/net/message/storage/creating/RmLocalDirMsgEx.h new file mode 100644 index 0000000..65d2590 --- /dev/null +++ b/meta/source/net/message/storage/creating/RmLocalDirMsgEx.h @@ -0,0 +1,38 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class RmLocalDirMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getDelEntryInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + std::unique_ptr rmDir(); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "RmLocalDirMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/creating/UnlinkFileMsgEx.cpp b/meta/source/net/message/storage/creating/UnlinkFileMsgEx.cpp new file mode 100644 index 0000000..8475dcb --- /dev/null +++ b/meta/source/net/message/storage/creating/UnlinkFileMsgEx.cpp @@ -0,0 +1,294 @@ +#include +#include +#include +#include +#include +#include "UnlinkFileMsgEx.h" + +std::tuple UnlinkFileMsgEx::lock(EntryLockStore& store) +{ + HashDirLock hashLock; + FileIDLock inodeLock; + + // we also have to lock the inode attached to the dentry - if we delete the inode, we must + // exclude concurrent actions on the same inode. if we cannot look up a file inode for the + // dentry, nothing bad happens. + MetaStore* metaStore = Program::getApp()->getMetaStore(); + auto dir = metaStore->referenceDir(getParentInfo()->getEntryID(), + getParentInfo()->getIsBuddyMirrored(), false); + + DirEntry dentry(getDelFileName()); + bool dentryExists = dir->getFileDentry(getDelFileName(), dentry); + + if (dentryExists) + { + dentry.getEntryInfo(getParentInfo()->getEntryID(), 0, &fileInfo); + + // lock hash dir where we are going to remove (or update) file inode + // need to take it only if it is a non-inlined inode and resynch is running + if (resyncJob && resyncJob->isRunning() && !dentry.getIsInodeInlined()) + hashLock = {&store, MetaStorageTk::getMetaInodeHash(dentry.getID())}; + } + + FileIDLock dirLock(&store, getParentInfo()->getEntryID(), true); + ParentNameLock dentryLock(&store, getParentInfo()->getEntryID(), getDelFileName()); + + if (dentryExists) + inodeLock = {&store, dentry.getID(), true}; + + metaStore->releaseDir(dir->getID()); + return std::make_tuple(std::move(hashLock), std::move(dirLock), std::move(dentryLock), std::move(inodeLock)); +} + +bool UnlinkFileMsgEx::processIncoming(ResponseContext& ctx) +{ +#ifdef BEEGFS_DEBUG + const char* logContext = "UnlinkFileMsg incoming"; + + const std::string& removeName = getDelFileName(); + EntryInfo* parentInfo = getParentInfo(); + LOG_DEBUG(logContext, Log_DEBUG, "ParentID: " + parentInfo->getEntryID() + "; " + "deleteName: " + removeName); +#endif // BEEGFS_DEBUG + + // update operation counters (here on top because we have an early sock release in this msg) + updateNodeOp(ctx, MetaOpCounter_UNLINK); + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr UnlinkFileMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + const char* logContext = "Unlink File Msg"; + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + // reference parent dir + DirInode* dir = metaStore->referenceDir(getParentInfo()->getEntryID(), + getParentInfo()->getIsBuddyMirrored(), true); + + if (!dir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + DirEntry dentryToRemove(getDelFileName()); + if (!dir->getFileDentry(getDelFileName(), dentryToRemove)) + { + metaStore->releaseDir(dir->getID()); + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + } + + // On meta-mirrored setup, ensure the dentry we just loaded i.e. 'dentryToRemove' has the same entryID as 'fileInfo'. + // This check is crucial to detect a very rare race condition where dentry gets unlinked + // because the parent directory was not locked during UnlinkFileMsgEx::lock(), + // and then reappears because the same file is being created again. If the entryIDs + // don't match, it means the dentry has changed, and the exclusive lock taken on the + // entryID before won't be effective in preventing future races of ongoing unlink + // operation with other filesystem operations (e.g. close()) which might happen on this file. + // Therefore, we release the directory from the meta store and return an error saying path + // not exists. + if (isMirrored() && (dentryToRemove.getEntryID() != this->fileInfo.getEntryID())) + { + metaStore->releaseDir(dir->getID()); + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + } + + // re-fetch entryInfo + dentryToRemove.getEntryInfo(getParentInfo()->getEntryID(), 0, &fileInfo); + + // check whether local node/group owns file's inode (dentry's owner may/maynot be same) + NumNodeID ownerNodeID = fileInfo.getOwnerNodeID(); + if ( (!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || + (isMirrored() && + ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID()) ) + { + // File inode is on the same metadata node/buddy group as the dentry. + if (isSecondary) + return executeSecondary(ctx, *dir); + else + return executePrimary(ctx, *dir); + } + else + { // Handle case where file inode is on a different metadata node/buddy group than dentry. + // Step 1: Remove file's dentry (local operation) + // Note: Dentry was already loaded and validated earlier + FhgfsOpsErr unlinkRes = FhgfsOpsErr_INTERNAL; + unlinkRes = dir->unlinkDirEntry(getDelFileName(), &dentryToRemove, DirEntry_UNLINK_FILENAME); + metaStore->releaseDir(dir->getID()); + if (unlinkRes != FhgfsOpsErr_SUCCESS) + return boost::make_unique(unlinkRes); + + // Step 2: Remove file's inode (remote operation) + if (!isSecondary) + { + unsigned preUnlinkHardlinkCount = 0; + + UnlinkLocalFileInodeMsg unlinkInodeMsg(&fileInfo); + RequestResponseArgs rrArgs(NULL, &unlinkInodeMsg, NETMSGTYPE_UnlinkLocalFileInodeResp); + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); + rrNode.setTargetStates(app->getMetaStateStore()); + if (fileInfo.getIsBuddyMirrored()) + { + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + } + + do + { + FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + if (unlikely(resp != FhgfsOpsErr_SUCCESS)) + { + LogContext(logContext).log(Log_WARNING, + "Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str() + "; " + + "entryID: " + fileInfo.getEntryID().c_str()); + break; + } + + // response received + const auto respMsg = (UnlinkLocalFileInodeRespMsg*) rrArgs.outRespMsg.get(); + FhgfsOpsErr res = respMsg->getResult(); + if (res != FhgfsOpsErr_SUCCESS) + { + // error: either inode file doesn't exists or some other error happened + LogContext(logContext).log(Log_WARNING, "unlink file inode failed! " + "nodeID: " + ownerNodeID.str() + "; " + + "entryID: " + fileInfo.getEntryID().c_str()); + break; + } + + // since dentry has been removed successfully so all good for user - no need + // to return an error if unlinking remote inode fails due to some reasons. + // we still need to remove dentry from secondary buddy so should not overwrite + // local dentry removal success with some remote error + unlinkRes = FhgfsOpsErr_SUCCESS; + preUnlinkHardlinkCount = respMsg->getPreUnlinkHardlinkCount(); + } while (false); + + if (unlinkRes == FhgfsOpsErr_SUCCESS && app->getFileEventLogger() && getFileEvent()) + { + // Determine remaining hardlinks after unlink operation + unsigned remainingLinks = (preUnlinkHardlinkCount > 0) ? preUnlinkHardlinkCount - 1 : 0; + + EventContext eventCtx = makeEventContext(&fileInfo, getParentInfo()->getEntryID(), + getMsgHeaderUserID(), "", remainingLinks, isSecondary); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + } + + return boost::make_unique(unlinkRes); + } + + // Should never reach this point but added to please the compiler + return boost::make_unique(FhgfsOpsErr_SUCCESS); +} + +std::unique_ptr UnlinkFileMsgEx::executePrimary( + ResponseContext& ctx, DirInode& dir) +{ + App* app = Program::getApp(); + unsigned preUnlinkHardlinkCount = 0; // number of hardlink(s) before unlink happens + + // Two alternatives: + // 1) early response before chunk files unlink. + // 2) normal response after chunk files unlink (incl. chunk files error). + if (app->getConfig()->getTuneEarlyUnlinkResponse() && !isMirrored()) + { + // alternative 1: response before chunk files unlink + std::unique_ptr unlinkedInode; + + FhgfsOpsErr unlinkMetaRes = MsgHelperUnlink::unlinkMetaFile(dir, + getDelFileName(), &unlinkedInode, preUnlinkHardlinkCount); + + app->getMetaStore()->releaseDir(dir.getID()); + earlyComplete(ctx, ResponseState(unlinkMetaRes)); + + /* note: if the file is still opened or if there were hardlinks then unlinkedInode will be + NULL even on FhgfsOpsErr_SUCCESS */ + if ((unlinkMetaRes == FhgfsOpsErr_SUCCESS) && unlinkedInode) + MsgHelperUnlink::unlinkChunkFiles(unlinkedInode.release(), getMsgHeaderUserID() ); + + if (unlinkMetaRes == FhgfsOpsErr_SUCCESS && app->getFileEventLogger() && getFileEvent()) + { + // Determine remaining hardlinks after unlink operation + unsigned remainingLinks = (preUnlinkHardlinkCount > 0) ? preUnlinkHardlinkCount - 1 : 0; + + EventContext eventCtx = makeEventContext(&fileInfo, getParentInfo()->getEntryID(), + // This is not the secondary node if executePrimary() was called. + getMsgHeaderUserID(), "", remainingLinks, false); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + return {}; + } + + // alternative 2: response after chunk files unlink + std::unique_ptr unlinkedInode; + FhgfsOpsErr unlinkRes = MsgHelperUnlink::unlinkMetaFile(dir, + getDelFileName(), &unlinkedInode, preUnlinkHardlinkCount); + + if ((unlinkRes == FhgfsOpsErr_SUCCESS) && shouldFixTimestamps()) + { + fixInodeTimestamp(dir, dirTimestamps); + if (!unlinkedInode) + { + auto [file, referenceRes] = app->getMetaStore()->referenceFile(&fileInfo); + if (file) + { + fixInodeTimestamp(*file, fileTimestamps, &fileInfo); + app->getMetaStore()->releaseFile(dir.getID(), file); + } + } + } + + /* note: if the file is still opened or if there are/were hardlinks then unlinkedInode will be + NULL even on FhgfsOpsErr_SUCCESS */ + if ((unlinkRes == FhgfsOpsErr_SUCCESS) && unlinkedInode) + MsgHelperUnlink::unlinkChunkFiles(unlinkedInode.release(), getMsgHeaderUserID()); + + app->getMetaStore()->releaseDir(dir.getID()); + if ((unlinkRes == FhgfsOpsErr_SUCCESS) && app->getFileEventLogger() && getFileEvent()) + { + // Determine remaining hardlinks after unlink operation + unsigned remainingLinks = (preUnlinkHardlinkCount > 0) ? preUnlinkHardlinkCount - 1 : 0; + + EventContext eventCtx = makeEventContext(&fileInfo, getParentInfo()->getEntryID(), + // This is not the secondary node if executePrimary() was called. + getMsgHeaderUserID(), "", remainingLinks, false); + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + return boost::make_unique(unlinkRes); +} + +std::unique_ptr UnlinkFileMsgEx::executeSecondary( + ResponseContext& ctx, DirInode& dir) +{ + MetaStore* const metaStore = Program::getApp()->getMetaStore(); + std::unique_ptr unlinkedInode; + unsigned preUnlinkHardlinkCount; // Not used here! + + FhgfsOpsErr unlinkMetaRes = MsgHelperUnlink::unlinkMetaFile(dir, + getDelFileName(), &unlinkedInode, preUnlinkHardlinkCount); + + if ((unlinkMetaRes == FhgfsOpsErr_SUCCESS) && shouldFixTimestamps()) + { + fixInodeTimestamp(dir, dirTimestamps); + if (!unlinkedInode) + { + auto [file, referenceRes] = metaStore->referenceFile(&fileInfo); + if (file) + { + fixInodeTimestamp(*file, fileTimestamps, &fileInfo); + metaStore->releaseFile(dir.getID(), file); + } + } + } + + metaStore->releaseDir(dir.getID()); + return boost::make_unique(unlinkMetaRes); +} + +void UnlinkFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_UnlinkFileResp); +} diff --git a/meta/source/net/message/storage/creating/UnlinkFileMsgEx.h b/meta/source/net/message/storage/creating/UnlinkFileMsgEx.h new file mode 100644 index 0000000..4f88dd9 --- /dev/null +++ b/meta/source/net/message/storage/creating/UnlinkFileMsgEx.h @@ -0,0 +1,45 @@ +#pragma once + +#include +#include +#include +#include + +class UnlinkFileMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + private: + /** + * Execute unlink operation on primary and secondary metadata nodes. + * Primary handles metadata removal, chunk cleanup, and event logging. + * Secondary handles only metadata operations. + * + * @param ctx Response context + * @param dir Parent directory reference. Functions guarantee directory reference + * release before return, including error paths. + * @return Response state containing operation result. + */ + std::unique_ptr executePrimary(ResponseContext& ctx, DirInode& dir); + std::unique_ptr executeSecondary(ResponseContext& ctx, DirInode& dir); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "UnlinkFileMsgEx/forward"; } +}; diff --git a/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.cpp b/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.cpp new file mode 100644 index 0000000..0c01e5a --- /dev/null +++ b/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include "UnlinkLocalFileInodeMsgEx.h" + +std::tuple UnlinkLocalFileInodeMsgEx::lock(EntryLockStore& store) +{ + // we must not lock hash dir and inode if it is owned by current node. if it is a locally + // generated message which is sent by unlinkmsg and renamev2msg sends then aforementioned + // locks are already held by them + if (rctx->isLocallyGenerated()) + return {}; + + HashDirLock hashLock; + + if (resyncJob && resyncJob->isRunning()) + HashDirLock hashLock = {&store, MetaStorageTk::getMetaInodeHash(getDelEntryInfo()->getEntryID())}; + + return std::make_tuple( + std::move(hashLock), + FileIDLock(&store, getDelEntryInfo()->getEntryID(), true)); +} + +bool UnlinkLocalFileInodeMsgEx::processIncoming(ResponseContext& ctx) +{ + rctx = &ctx; + return BaseType::processIncoming(ctx); +} + +std::unique_ptr UnlinkLocalFileInodeMsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + // Create a copy of the passed-in entryInfo and use it! + // Reason: + // Some fields of EntryInfo, such as parentEntryID and isInlined, can be modified + // by MetaStore::unlinkInodeLater() during unlink processing. Using a copy ensures + // that the original (and unmodified) entryInfo is forwarded to the secondary buddy + // to avoid different states on meta-buddies. + EntryInfo entryInfo; + entryInfo.set(getDelEntryInfo()); + + UnlinkLocalFileInodeResponseState resp; + unsigned outLinkCount = 0; + + std::unique_ptr unlinkedInode; + FhgfsOpsErr unlinkInodeRes = MsgHelperUnlink::unlinkFileInode(&entryInfo, &unlinkedInode, outLinkCount); + resp.setResult(unlinkInodeRes); + resp.setPreUnlinkHardlinkCount(outLinkCount); + + if ((unlinkInodeRes == FhgfsOpsErr_SUCCESS) && unlinkedInode && !isSecondary) + { + MsgHelperUnlink::unlinkChunkFiles(unlinkedInode.release(), getMsgHeaderUserID()); + } + + return boost::make_unique(std::move(resp)); +} + +void UnlinkLocalFileInodeMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_UnlinkLocalFileInodeResp); +} diff --git a/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.h b/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.h new file mode 100644 index 0000000..837ba34 --- /dev/null +++ b/meta/source/net/message/storage/creating/UnlinkLocalFileInodeMsgEx.h @@ -0,0 +1,86 @@ +#pragma once + +#include +#include +#include +#include +#include + +class UnlinkLocalFileInodeResponseState : public MirroredMessageResponseState +{ + public: + UnlinkLocalFileInodeResponseState() : result(FhgfsOpsErr_INTERNAL), preUnlinkHardlinkCount(0) {} + + UnlinkLocalFileInodeResponseState(FhgfsOpsErr result, unsigned linkCount) : + result(result), preUnlinkHardlinkCount(linkCount) {} + + explicit UnlinkLocalFileInodeResponseState(Deserializer& des) + { + serialize(this, des); + } + + UnlinkLocalFileInodeResponseState(UnlinkLocalFileInodeResponseState&& other) : + result(other.result), preUnlinkHardlinkCount(other.preUnlinkHardlinkCount) {} + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + UnlinkLocalFileInodeRespMsg resp(result, preUnlinkHardlinkCount); + ctx.sendResponse(resp); + } + + void setResult(FhgfsOpsErr res) { this->result = res; } + void setPreUnlinkHardlinkCount(unsigned linkCount) { this->preUnlinkHardlinkCount = linkCount; } + bool changesObservableState() const override { return true; } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_UnlinkLocalFileInodeResp; } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->result + % obj->preUnlinkHardlinkCount; + } + + void serializeContents(Serializer& ser) const override + { + serialize(this, ser); + } + + private: + FhgfsOpsErr result; + // The preUnlinkHardlinkCount represents the number of hardlinks that existed before + // the actual unlink happens. This is mainly needed for event logging purposes. + unsigned preUnlinkHardlinkCount; +}; + +class UnlinkLocalFileInodeMsgEx : public MirroredMessage> +{ + public: + typedef UnlinkLocalFileInodeResponseState ResponseState; + virtual bool processIncoming(ResponseContext& ctx) override; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getDelEntryInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override + { + return "UnlinkLocalFileInodeMsgEx/forward"; + } +}; diff --git a/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.cpp b/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.cpp new file mode 100644 index 0000000..b53281b --- /dev/null +++ b/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.cpp @@ -0,0 +1,71 @@ +#include +#include +#include "ListDirFromOffsetMsgEx.h" + +#include + + +bool ListDirFromOffsetMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "ListDirFromOffsetMsgEx incoming"; + #endif // BEEGFS_DEBUG + + EntryInfo* entryInfo = this->getEntryInfo(); + + LOG_DEBUG(logContext, Log_SPAM, + std::string("serverOffset: ") + StringTk::int64ToStr(getServerOffset() ) + "; " + + std::string("maxOutNames: ") + StringTk::int64ToStr(getMaxOutNames() ) + "; " + + std::string("filterDots: ") + StringTk::uintToStr(getFilterDots() ) + "; " + + std::string("parentEntryID: ") + entryInfo->getParentEntryID() + "; " + + std::string("buddyMirrored: ") + (entryInfo->getIsBuddyMirrored() ? "true" : "false") + "; " + + std::string("entryID: ") + entryInfo->getEntryID() ); + + StringList names; + UInt8List entryTypes; + StringList entryIDs; + Int64List serverOffsets; + int64_t newServerOffset = getServerOffset(); // init to something useful + + FhgfsOpsErr listRes = listDirIncremental(entryInfo, &names, &entryTypes, &entryIDs, + &serverOffsets, &newServerOffset); + + LOG_DEBUG(logContext, Log_SPAM, + std::string("newServerOffset: ") + StringTk::int64ToStr(newServerOffset) + "; " + + std::string("names.size: ") + StringTk::int64ToStr(names.size() ) + "; " + + std::string("listRes: ") + boost::lexical_cast(listRes)); + + ctx.sendResponse( + ListDirFromOffsetRespMsg( + listRes, &names, &entryTypes, &entryIDs, &serverOffsets, newServerOffset) ); + + Program::getApp()->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + MetaOpCounter_READDIR, getMsgHeaderUserID() ); + + return true; +} + +FhgfsOpsErr ListDirFromOffsetMsgEx::listDirIncremental(EntryInfo* entryInfo, StringList* outNames, + UInt8List* outEntryTypes, StringList* outEntryIDs, Int64List* outServerOffsets, + int64_t* outNewOffset) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + // reference dir + DirInode* dir = metaStore->referenceDir(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored(), + true); + if(!dir) + return FhgfsOpsErr_PATHNOTEXISTS; + + // query contents + ListIncExOutArgs outArgs(outNames, outEntryTypes, outEntryIDs, outServerOffsets, outNewOffset); + + FhgfsOpsErr listRes = dir->listIncrementalEx( + getServerOffset(), getMaxOutNames(), getFilterDots(), outArgs); + + // clean-up + metaStore->releaseDir(entryInfo->getEntryID() ); + + return listRes; +} + diff --git a/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.h b/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.h new file mode 100644 index 0000000..adc45fc --- /dev/null +++ b/meta/source/net/message/storage/listing/ListDirFromOffsetMsgEx.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include + + +class ListDirFromOffsetMsgEx : public ListDirFromOffsetMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr listDirIncremental(EntryInfo* entryInfo, StringList* outNames, + UInt8List* outEntryTypes, StringList* outEntryIDs, Int64List* outServerOffsets, + int64_t* outNewOffset); +}; + + diff --git a/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.cpp b/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.cpp new file mode 100644 index 0000000..fdb69e6 --- /dev/null +++ b/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.cpp @@ -0,0 +1,47 @@ +#include "FindLinkOwnerMsgEx.h" + +#include + +bool FindLinkOwnerMsgEx::processIncoming(ResponseContext& ctx) +{ + MetaStore *metaStore = Program::getApp()->getMetaStore(); + std::string entryID = this->getEntryID(); + + int result = 1; + std::string parentEntryID; + NumNodeID parentNodeID; + + MetaFileHandle file; + DirInode *dir = NULL; + // we don't know if the ID belongs to a file or a directory, so we try the file first and if that + // does not work, we try directory + + /* TODO: Used by fhgfs-ctl ModeReverseLookup, but this mode does not support to send the + * parentID. With the 2014.01 format this should be possible for most files/chunks, though. + */ + + // TODO: buddy mirroring => but this is not used anymore, so maybe it's better to just delete it + file = metaStore->referenceLoadedFile(parentEntryID, false, entryID); + if (file != NULL) + { + // file->getParentInfo(&parentEntryID, &parentNodeID); + result = 0; + metaStore->releaseFile(parentEntryID, file); + goto send_response; + } + // TODO: buddy mirroring => but this is not used anymore, so maybe it's better to just delete it + dir = metaStore->referenceDir(entryID, false, true); + if (dir != NULL) + { + dir->getParentInfo(&parentEntryID, &parentNodeID); + result = 0; + metaStore->releaseDir(entryID); + goto send_response; + } + +send_response: + ctx.sendResponse(FindLinkOwnerRespMsg(result, parentNodeID, parentEntryID) ); + + return true; +} + diff --git a/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.h b/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.h new file mode 100644 index 0000000..342c24b --- /dev/null +++ b/meta/source/net/message/storage/lookup/FindLinkOwnerMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class FindLinkOwnerMsgEx : public FindLinkOwnerMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/storage/lookup/FindOwnerMsgEx.cpp b/meta/source/net/message/storage/lookup/FindOwnerMsgEx.cpp new file mode 100644 index 0000000..e10c0de --- /dev/null +++ b/meta/source/net/message/storage/lookup/FindOwnerMsgEx.cpp @@ -0,0 +1,120 @@ +#include +#include +#include "FindOwnerMsgEx.h" + + +bool FindOwnerMsgEx::processIncoming(ResponseContext& ctx) +{ + #ifdef BEEGFS_DEBUG + const char* logContext = "FindOwnerMsg incoming"; + #endif // BEEGFS_DEBUG + + LOG_DEBUG(logContext, Log_SPAM, "Path: '" + getPath().str() + "'"); + + EntryInfoWithDepth ownerInfo; + FhgfsOpsErr findRes; + + if(!getSearchDepth() ) + { // looking for the root node + NumNodeID rootNodeID = Program::getApp()->getRootDir()->getOwnerNodeID(); + + if (rootNodeID) + { + ownerInfo.set(rootNodeID, "", META_ROOTDIR_ID_STR, "/", DirEntryType_DIRECTORY, 0, 0); + + if (Program::getApp()->getRootDir()->getIsBuddyMirrored()) + ownerInfo.setBuddyMirroredFlag(true); + + findRes = FhgfsOpsErr_SUCCESS; + } + else + { // we don't know the root node + findRes = FhgfsOpsErr_INTERNAL; + } + } + else + { // a normal path lookup + findRes = findOwner(&ownerInfo); + } + + ctx.sendResponse(FindOwnerRespMsg(findRes, &ownerInfo) ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), MetaOpCounter_FINDOWNER, + getMsgHeaderUserID() ); + + return true; +} + + +/** + * Note: This does not work for finding the root dir owner (because it relies on the existence + * of a parent dir). + */ +FhgfsOpsErr FindOwnerMsgEx::findOwner(EntryInfoWithDepth* outInfo) +{ + FhgfsOpsErr findRes = FhgfsOpsErr_INTERNAL; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + unsigned searchDepth = this->getSearchDepth(); + unsigned currentDepth = this->getCurrentDepth(); + EntryInfo *currentEntryInfo = this->getEntryInfo(); + std::string currentEntryID = currentEntryInfo->getEntryID(); + bool currentEntryIsBuddyMirrored = currentEntryInfo->getIsBuddyMirrored(); + + const Path path(getPath()); + + // search + while(currentDepth < searchDepth) + { + DirInode* currentDir = metaStore->referenceDir(currentEntryID, currentEntryIsBuddyMirrored, + true); + if(!currentDir) + { // someone said we own the dir with this ID, but we do not => it was deleted + // note: this might also be caused by a change of ownership, but a feature like that is + // currently not implemented + return (findRes == FhgfsOpsErr_SUCCESS) ? FhgfsOpsErr_SUCCESS : FhgfsOpsErr_PATHNOTEXISTS; + } + + EntryInfo subInfo; + auto [getEntryRes, isFileOpen] = metaStore->getEntryData(currentDir, path[currentDepth], + &subInfo, NULL); + + if (getEntryRes == FhgfsOpsErr_SUCCESS || getEntryRes == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED) + { // next entry exists and is a dir or a file + currentDepth++; + + *outInfo = subInfo; + outInfo->setEntryDepth(currentDepth); + + findRes = FhgfsOpsErr_SUCCESS; + } + else + { // entry does not exist + findRes = FhgfsOpsErr_PATHNOTEXISTS; + } + + metaStore->releaseDir(currentEntryID); + + if(findRes != FhgfsOpsErr_SUCCESS) + break; + + // is next entry owned by a different node? + if (subInfo.getIsBuddyMirrored()) + { + if(subInfo.getOwnerNodeID() + != NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) ) + break; + } + else + if (subInfo.getOwnerNodeID() != app->getLocalNode().getNumID()) + break; + + // prepare for next round + currentEntryID = outInfo->getEntryID(); + } + + return findRes; +} diff --git a/meta/source/net/message/storage/lookup/FindOwnerMsgEx.h b/meta/source/net/message/storage/lookup/FindOwnerMsgEx.h new file mode 100644 index 0000000..dfe7555 --- /dev/null +++ b/meta/source/net/message/storage/lookup/FindOwnerMsgEx.h @@ -0,0 +1,22 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// Get the meta-data server of a file + +class FindOwnerMsgEx : public FindOwnerMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr findOwner(EntryInfoWithDepth* outInfo); + +}; + + diff --git a/meta/source/net/message/storage/lookup/LookupIntentMsgEx.cpp b/meta/source/net/message/storage/lookup/LookupIntentMsgEx.cpp new file mode 100644 index 0000000..1e13b65 --- /dev/null +++ b/meta/source/net/message/storage/lookup/LookupIntentMsgEx.cpp @@ -0,0 +1,550 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "LookupIntentMsgEx.h" + + +std::tuple LookupIntentMsgEx::lock(EntryLockStore& store) +{ + ParentNameLock dentryLock; + FileIDLock entryIDLockForDir; + FileIDLock entryIDLockForFile; + enum DirEntryType entryType = getEntryInfo()->getEntryType(); + + if (getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE) + { + entryIDLockForDir = {&store, getParentInfo()->getEntryID(), true}; + dentryLock = {&store, getParentInfo()->getEntryID(), getEntryName()}; + entryIDLockForFile = {&store, entryID, true}; + } + else + { + //XXX Note: If you are addding new flag for lookupintent, make sure to + //check if shared lock for parent dir and file is sufficient. + //Otherwise add another else if condition. + // For other lookup flag, we don't have entryID yet. So rather than + // taking lock on the emptry EntryID(which can serialize lookup on all + // files even if from different parent directory), lookup the entryID + // while holding parent lock. And take the lock on the actual entryID. + entryIDLockForDir = {&store, getParentInfo()->getEntryID(), false}; + + lookupRes = lookup(getParentInfo()->getEntryID(), getEntryName(), isMirrored(), + &diskEntryInfo, &inodeData, inodeDataOutdated); + if (lookupRes == FhgfsOpsErr_SUCCESS) + { + entryID = diskEntryInfo.getEntryID(); + entryType = diskEntryInfo.getEntryType(); + } + + // for revalidate entryInfo is valid, so use the info from it + if (entryID.empty() && (getIntentFlags() & LOOKUPINTENTMSG_FLAG_REVALIDATE)) + { + entryID = getEntryInfo()->getEntryID(); + entryType = getEntryInfo()->getEntryType(); + } + + if (getIntentFlags() & LOOKUPINTENTMSG_FLAG_OPEN) + { + entryIDLockForFile = {&store, entryID, true}; + } + else + { + if (DirEntryType_ISDIR(entryType) && + (entryID < getParentInfo()->getEntryID())) + { + // Release parent lock because child lock needs to be taken first. + entryIDLockForDir = {}; + + // Take the lock in reverse order + entryIDLockForFile = {&store, entryID, false}; + entryIDLockForDir = {&store, getParentInfo()->getEntryID(), false}; + } + else + { + entryIDLockForFile = {&store, entryID, false}; + } + } + } + + return std::make_tuple(std::move(entryIDLockForDir), std::move(dentryLock), + std::move(entryIDLockForFile)); +} + +bool LookupIntentMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + const std::string& parentEntryID = getParentInfo()->getEntryID(); + const std::string& entryName = getEntryName(); + + inodeDataOutdated = false; // true if the file/inode is currently open (referenced) + + lookupRes = FhgfsOpsErr_INTERNAL; + + LOG_DBG(GENERAL, DEBUG, "", parentEntryID, entryName, getParentInfo()->getIsBuddyMirrored()); + + // sanity checks + if (unlikely(parentEntryID.empty() || entryName.empty())) + { + LOG(GENERAL, WARNING, "Sanity check failed", parentEntryID, entryName); + + ctx.sendResponse(LookupIntentRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + if (getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE) + entryID = StorageTk::generateFileID(app->getLocalNode().getNumID()); + + return BaseType::processIncoming(ctx); +} + +std::unique_ptr LookupIntentMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + LookupIntentResponseState response; + + int createFlag = getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE; + FhgfsOpsErr createRes = FhgfsOpsErr_INTERNAL; + + // Note: Actually we should first do a lookup. However, a successful create also implies a + // failed Lookup, so we can take a shortcut. + + // lookup-create + if (createFlag) + { + LOG_DBG(GENERAL, SPAM, "Create"); + + createRes = create(getParentInfo(), getEntryName(), &diskEntryInfo, &inodeData, isSecondary); + switch (createRes) + { + case FhgfsOpsErr_SUCCESS: + // Successful Create, which implies Lookup-before-create would have been failed. + response.setLookupResult(FhgfsOpsErr_PATHNOTEXISTS); + response.setEntryInfo(diskEntryInfo); + response.addResponseCreate(createRes); + break; + + case FhgfsOpsErr_EXISTS: + // NOTE: we need to do a Lookup to get required lookup data + if (getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATEEXCLUSIVE) + response.addResponseCreate(FhgfsOpsErr_EXISTS); + else + response.addResponseCreate(FhgfsOpsErr_SUCCESS); + break; + case FhgfsOpsErr_DQUOT: + response.addResponseCreate(FhgfsOpsErr_DQUOT); + break; + + default: + response.addResponseCreate(FhgfsOpsErr_INTERNAL); + } + + // note: don't quit here on error because caller might still have requested stat info + } + + // lookup + if ((!createFlag) || (createRes == FhgfsOpsErr_EXISTS) ) + { + LOG_DBG(GENERAL, SPAM, "Lookup"); + + // Lookup will happen for secondary node or create failed or lookup + // failed earlier. + if (isSecondary || (createRes == FhgfsOpsErr_EXISTS) || + lookupRes != FhgfsOpsErr_SUCCESS) + { + // lock is not taken on secondary, so we need to perform lookup() + // for secondary here. + lookupRes = lookup(getParentInfo()->getEntryID(), getEntryName(), isMirrored(), + &diskEntryInfo, &inodeData, inodeDataOutdated); + + } + // Lookup was already done in lock() function after taking lock on parent. + response.setLookupResult(lookupRes); + response.setEntryInfo(diskEntryInfo); + + if (unlikely(lookupRes != FhgfsOpsErr_SUCCESS && createFlag)) + { + // so createFlag is set, so createRes is either Success or Exists, but now lookup fails + // create/unlink race? + + StatData statData; + statData.setAllFake(); // set arbitrary stat values (receiver won't use the values) + + response.addResponseStat(lookupRes, statData); + + return boost::make_unique(std::move(response)); + } + } + + // lookup-revalidate + if (getIntentFlags() & LOOKUPINTENTMSG_FLAG_REVALIDATE) + { + LOG_DBG(GENERAL, SPAM, "Revalidate"); + + auto revalidateRes = revalidate(&diskEntryInfo, inodeData.getMetaVersion()); + response.addResponseRevalidate(revalidateRes); + + if (revalidateRes != FhgfsOpsErr_SUCCESS) + return boost::make_unique(std::move(response)); + } + + // lookup-stat + // note: we do stat before open to avoid the dyn attribs refresh if the file is not opened by + // someone else currently. + if ((getIntentFlags() & LOOKUPINTENTMSG_FLAG_STAT) && + (lookupRes == FhgfsOpsErr_SUCCESS || createRes == FhgfsOpsErr_SUCCESS)) + { + LOG_DBG(GENERAL, SPAM, "Stat"); + + // check if lookup and create failed (we don't have an entryID to stat then) + if (diskEntryInfo.getEntryID().empty()) + return boost::make_unique(std::move(response)); + + if ((diskEntryInfo.getFeatureFlags() & ENTRYINFO_FEATURE_INLINED) && !inodeDataOutdated) + { + // For inlined inodes with up-to-date inode data, use stat data fetched during lookup() + response.addResponseStat(FhgfsOpsErr_SUCCESS, *inodeData.getInodeStatData()); + } + else + { // read stat data separately + StatData statData; + FhgfsOpsErr statRes = stat(&diskEntryInfo, true, statData); + + response.addResponseStat(statRes, statData); + + if (statRes != FhgfsOpsErr_SUCCESS) + return boost::make_unique(std::move(response)); + } + } + + // lookup-open + if(getIntentFlags() & LOOKUPINTENTMSG_FLAG_OPEN) + { + LOG_DBG(GENERAL, SPAM, "Open"); + + std::string fileHandleID; + PathInfo pathInfo; + Raid0Pattern dummyPattern(1, UInt16Vector() ); + + // don't open if create failed + if ((createRes != FhgfsOpsErr_SUCCESS) && (getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE)) + return boost::make_unique(std::move(response)); + + // if it's not a regular file, we don't open that + if (!DirEntryType_ISREGULARFILE(diskEntryInfo.getEntryType())) + return boost::make_unique(std::move(response)); + + // check if lookup and/or create failed (we don't have an entryID to open then) + if (diskEntryInfo.getEntryID().empty()) + return boost::make_unique(std::move(response)); + + StripePattern* pattern = NULL; + + FhgfsOpsErr openRes = open(&diskEntryInfo, &fileHandleID, &pattern, &pathInfo, isSecondary); + + if(openRes != FhgfsOpsErr_SUCCESS) + { // open failed => use dummy pattern for response + response.addResponseOpen(openRes, std::move(fileHandleID), + std::unique_ptr(dummyPattern.clone()), pathInfo); + + return boost::make_unique(std::move(response)); + } + + response.addResponseOpen(openRes, std::move(fileHandleID), + std::unique_ptr(pattern->clone()), pathInfo); + } + + updateNodeOp(ctx, getOpCounterType()); + + return boost::make_unique(std::move(response)); +} + +FhgfsOpsErr LookupIntentMsgEx::lookup(const std::string& parentEntryID, + const std::string& entryName, bool isBuddyMirrored, EntryInfo* outEntryInfo, + FileInodeStoreData* outInodeStoreData, bool& outInodeDataOutdated) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + DirInode* parentDir = metaStore->referenceDir(parentEntryID, isBuddyMirrored, false); + if (unlikely(!parentDir)) + { // out of memory + return FhgfsOpsErr_INTERNAL; + } + + auto [lookupRes1, isFileOpen] = metaStore->getEntryData(parentDir, entryName, outEntryInfo, + outInodeStoreData); + + if (lookupRes1 == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED) + { + lookupRes1 = FhgfsOpsErr_SUCCESS; + outInodeDataOutdated = isFileOpen; + + // If the file inode is not inlined and the intent includes the creation flag, + // we need to use a different overload of getEntryData() to correctly retrieve the + // inode disk data for non-inlined inode(s) and prevent potential crashes due to + // race conditions between create and hardlink creation. + // If create flag is set, fetch full inode disk data using non-inlined inode. + int createFlag = getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE; + if (!outEntryInfo->getIsInlined() && createFlag) + { + FhgfsOpsErr getRes = metaStore->getEntryData(outEntryInfo, outInodeStoreData); + if (getRes != FhgfsOpsErr_SUCCESS) + lookupRes1 = getRes; + } + } + + metaStore->releaseDir(parentEntryID); + return lookupRes1; +} + +/** + * compare entryInfo on disk with EntryInfo send by the client + * @return FhgfsOpsErr_SUCCESS if revalidation successful, FhgfsOpsErr_PATHNOTEXISTS otherwise, FhgfsOpsErr_METAVERSIONMISMATCH in case of version change. + */ +FhgfsOpsErr LookupIntentMsgEx::revalidate(EntryInfo* diskEntryInfo, uint32_t metaVersion) + { + EntryInfo* clientEntryInfo = this->getEntryInfo(); + uint32_t clientMetaVersion = this->getMetaVersion(); + + + if ( (diskEntryInfo->getEntryID() == clientEntryInfo->getEntryID() ) && + (diskEntryInfo->getOwnerNodeID() == clientEntryInfo->getOwnerNodeID() ) ) + { + if (clientMetaVersion != metaVersion) + return FhgfsOpsErr_METAVERSIONMISMATCH; + + return FhgfsOpsErr_SUCCESS; + } + + return FhgfsOpsErr_PATHNOTEXISTS; +} + +FhgfsOpsErr LookupIntentMsgEx::create(EntryInfo* parentInfo, const std::string& entryName, + EntryInfo *outEntryInfo, FileInodeStoreData* outInodeData, bool isSecondary) +{ + MkFileDetails mkDetails(entryName, getUserID(), getGroupID(), getMode(), getUmask(), + TimeAbs().getTimeval()->tv_sec); + StripePattern* pattern = nullptr; + std::unique_ptr rstInfo; + FhgfsOpsErr res; + + DirInode* dir = Program::getApp()->getMetaStore()->referenceDir( + parentInfo->getEntryID(), parentInfo->getIsBuddyMirrored(), true); + if (!dir) + return FhgfsOpsErr_PATHNOTEXISTS; + + // create new RST object and set remote storage target info from parentDir if available + if (dir->getIsRstAvailable()) + { + rstInfo = std::make_unique(); + rstInfo->set(dir->getRemoteStorageTargetInfo()); + } + + if (isSecondary) + { + mkDetails.setNewEntryID(getNewEntryID().c_str()); + mkDetails.createTime = fileTimestamps.creationTimeSecs; + pattern = getNewStripePattern()->clone(); + } + else + { + if (!entryID.empty()) + mkDetails.setNewEntryID(entryID.c_str()); + + pattern = dir->createFileStripePattern(&getPreferredTargets(), 0, 0, StoragePoolId(0)); + } + + if (!pattern) + { + LogContext("Lookup Create").logErr( + "StripePattern is NULL. Can't proceed. Filename: " + getEntryName()); + + res = FhgfsOpsErr_INTERNAL; + goto releasedir_and_return; + } + + if (isMsgHeaderFeatureFlagSet(LOOKUPINTENTMSG_FLAG_USE_QUOTA) && + Program::getApp()->getConfig()->getQuotaEnableEnforcement()) + { + const char* logContext = "Quota Enforcement for create"; + StoragePoolPtr storagePool = + Program::getApp()->getStoragePoolStore()->getPool(pattern->getStoragePoolId()); + + if (!storagePool) + { + LOG(QUOTA, WARNING, "Requested storage pool doesn't exist on metadata server.", + ("StoragePoolId", pattern->getStoragePoolId())); + + res = FhgfsOpsErr_UNKNOWNPOOL; + goto releasedir_and_return; + } + + UInt16Set targetIds = storagePool->getTargets(); + for (auto targetId : targetIds) + { + ExceededQuotaStorePtr exceededQuotaStore = Program::getApp()->getExceededQuotaStores()->get(targetId); + if (exceededQuotaStore && exceededQuotaStore->someQuotaExceeded()) + { + QuotaExceededErrorType quotaExceeded = exceededQuotaStore->isQuotaExceeded(mkDetails.userID, mkDetails.groupID); + if (quotaExceeded != QuotaExceededErrorType_NOT_EXCEEDED) + { + LogContext(logContext).log(Log_NOTICE, + QuotaData::QuotaExceededErrorTypeToString(quotaExceeded) + " " + "UID: " + StringTk::uintToStr(mkDetails.userID) + "; " + "GID: " + StringTk::uintToStr(mkDetails.groupID)); + res = FhgfsOpsErr_DQUOT; + goto releasedir_and_return; + } + } + } + } + + res = MsgHelperMkFile::mkFile(*dir, &mkDetails, &getPreferredTargets(), 0, 0, + pattern, rstInfo.get(), outEntryInfo, outInodeData); + + if (res == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + { + fixInodeTimestamp(*dir, dirTimestamps); + + if (!isSecondary) + fileTimestamps = outInodeData->getInodeStatData()->getMirroredTimestamps(); + } + + if (!isSecondary && res == FhgfsOpsErr_SUCCESS && Program::getApp()->getFileEventLogger() + && getFileEvent()) + { + EventContext eventCtx = makeEventContext(outEntryInfo, outEntryInfo->getParentEntryID(), + getMsgHeaderUserID(), "", outInodeData->getInodeStatData()->getNumHardlinks(), isSecondary); + logEvent(Program::getApp()->getFileEventLogger(), *getFileEvent(), eventCtx); + } + +releasedir_and_return: + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + + return res; +} + +FhgfsOpsErr LookupIntentMsgEx::stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData) +{ + Node& localNode = Program::getApp()->getLocalNode(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = Program::getApp()->getMetaBuddyGroupMapper(); + + FhgfsOpsErr statRes = FhgfsOpsErr_NOTOWNER; + + // check if we can stat on this machine or if entry is owned by another server + NumNodeID expectedOwner; + if (entryInfo->getIsBuddyMirrored()) + expectedOwner = NumNodeID(metaBuddyGroupMapper->getLocalGroupID()); + else + expectedOwner = localNode.getNumID(); + + if(entryInfo->getOwnerNodeID() == expectedOwner) + statRes = MsgHelperStat::stat(entryInfo, loadFromDisk, getMsgHeaderUserID(), outStatData); + + return statRes; +} + +/** + * @param outPattern only set if success is returned; points to referenced open file, so it does + * not need to be free'd/deleted. + */ +FhgfsOpsErr LookupIntentMsgEx::open(EntryInfo* entryInfo, std::string* outFileHandleID, + StripePattern** outPattern, PathInfo* outPathInfo, bool isSecondary) +{ + App* app = Program::getApp(); + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? app->getMirroredSessions() + : app->getSessions(); + + MetaFileHandle inode; + + bool useQuota = isMsgHeaderFeatureFlagSet(LOOKUPINTENTMSG_FLAG_USE_QUOTA); + + FhgfsOpsErr openRes = MsgHelperOpen::openFile( + entryInfo, getAccessFlags(), useQuota, /* bypassFileAccessCheck */ false, + getMsgHeaderUserID(), inode, isSecondary); + + if (openRes == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*inode, fileTimestamps, entryInfo); + + if(openRes != FhgfsOpsErr_SUCCESS) + return openRes; // error occurred + + // open successful => insert session + + SessionFile* sessionFile = new SessionFile(std::move(inode), getAccessFlags(), entryInfo); + + Session* session = sessions->referenceSession(getSessionID(), true); + + *outPattern = sessionFile->getInode()->getStripePattern(); + sessionFile->getInode()->getPathInfo(outPathInfo); + + unsigned sessionFileID; + + if (!hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + sessionFileID = session->getFiles()->addSession(sessionFile); + newOwnerFD = sessionFileID; + } + else + { + sessionFileID = newOwnerFD; + bool addRes = session->getFiles()->addSession(sessionFile, sessionFileID); + + if (!addRes) + LOG(GENERAL, NOTICE, "Couldn't add sessionFile on secondary", + ("sessionID", this->getSessionID()), sessionFileID); + } + + sessions->releaseSession(session); + + *outFileHandleID = SessionTk::generateFileHandleID(sessionFileID, entryInfo->getEntryID() ); + + return openRes; +} + +/** + * Decide which type of client stats op counter we increase for this msg (based on given msg flags). + */ +MetaOpCounterTypes LookupIntentMsgEx::getOpCounterType() +{ + /* note: as introducting a speparate opCounter type for each flag would have been too much, + we assign prioritiess here as follows: create > open > revalidate > stat > simple_no_flags */ + + /* NOTE: Those if's are rather slow, maybe we should create a table that has the values? + * Problem is that the table has to be filled for flag combinations, which is also ugly + */ + + if(this->getIntentFlags() & LOOKUPINTENTMSG_FLAG_CREATE) + return MetaOpCounter_LOOKUPINTENT_CREATE; + + if(this->getIntentFlags() & LOOKUPINTENTMSG_FLAG_OPEN) + return MetaOpCounter_LOOKUPINTENT_OPEN; + + if(this->getIntentFlags() & LOOKUPINTENTMSG_FLAG_REVALIDATE) + return MetaOpCounter_LOOKUPINTENT_REVALIDATE; + + if(this->getIntentFlags() & LOOKUPINTENTMSG_FLAG_STAT) + return MetaOpCounter_LOOKUPINTENT_STAT; + + return MetaOpCounter_LOOKUPINTENT_SIMPLE; + +} + +void LookupIntentMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + addBuddyInfo(entryID, inodeData.getStripePattern()); + sendToSecondary(ctx, *this, NETMSGTYPE_LookupIntentResp); +} diff --git a/meta/source/net/message/storage/lookup/LookupIntentMsgEx.h b/meta/source/net/message/storage/lookup/LookupIntentMsgEx.h new file mode 100644 index 0000000..dd10dd1 --- /dev/null +++ b/meta/source/net/message/storage/lookup/LookupIntentMsgEx.h @@ -0,0 +1,222 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class LookupIntentResponseState : public MirroredMessageResponseState +{ + public: + LookupIntentResponseState() : + responseFlags(0), lookupResult(FhgfsOpsErr_INTERNAL), statResult(FhgfsOpsErr_INTERNAL), + revalidateResult(FhgfsOpsErr_INTERNAL), createResult(FhgfsOpsErr_INTERNAL), + openResult(FhgfsOpsErr_INTERNAL) + {} + + explicit LookupIntentResponseState(Deserializer& des) + { + serialize(this, des); + } + + LookupIntentResponseState(LookupIntentResponseState&& other) : + responseFlags(other.responseFlags), + lookupResult(other.lookupResult), + statResult(other.statResult), + statData(other.statData), + revalidateResult(other.revalidateResult), + createResult(other.createResult), + openResult(other.openResult), + fileHandleID(std::move(other.fileHandleID)), + entryInfo(std::move(other.entryInfo)), + pattern(std::move(other.pattern)), + pathInfo(std::move(other.pathInfo)) + {} + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + LookupIntentRespMsg resp(lookupResult); + + if (responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) + resp.addResponseStat(statResult, &statData); + + if (responseFlags & LOOKUPINTENTRESPMSG_FLAG_REVALIDATE) + resp.addResponseRevalidate(revalidateResult); + + if (responseFlags & LOOKUPINTENTRESPMSG_FLAG_CREATE) + resp.addResponseCreate(createResult); + + if (responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) + resp.addResponseOpen(openResult, fileHandleID, pattern.get(), &pathInfo); + + resp.setEntryInfo(&entryInfo); + + ctx.sendResponse(resp); + } + + bool changesObservableState() const override + { + if ((responseFlags & LOOKUPINTENTRESPMSG_FLAG_CREATE) + && createResult == FhgfsOpsErr_SUCCESS) + return true; + if ((responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) && openResult == FhgfsOpsErr_SUCCESS) + return true; + + return false; + } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_LookupIntent; } + + template + static void serialize(This* obj, Ctx& ctx) + { + ctx + % obj->responseFlags + % serdes::as(obj->lookupResult); + + if (obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_STAT) + ctx + % obj->statData.serializeAs(StatDataFormat_NET) + % serdes::as(obj->statResult); + + ctx + % serdes::as(obj->revalidateResult) + % serdes::as(obj->createResult); + + if (obj->responseFlags & LOOKUPINTENTRESPMSG_FLAG_OPEN) + ctx + % serdes::as(obj->openResult) + % obj->fileHandleID + % obj->entryInfo + % obj->pattern + % obj->pathInfo; + } + + void serializeContents(Serializer& ser) const override + { + serialize(this, ser); + } + + private: + int32_t responseFlags; + + FhgfsOpsErr lookupResult; + + FhgfsOpsErr statResult; + StatData statData; + + FhgfsOpsErr revalidateResult; + + FhgfsOpsErr createResult; + + FhgfsOpsErr openResult; + std::string fileHandleID; + + EntryInfo entryInfo; + std::unique_ptr pattern; + PathInfo pathInfo; + + public: + void setLookupResult(FhgfsOpsErr lookupRes) { lookupResult = lookupRes; } + + void addResponseRevalidate(FhgfsOpsErr revalidateResult) + { + responseFlags |= LOOKUPINTENTRESPMSG_FLAG_REVALIDATE; + this->revalidateResult = revalidateResult; + } + + void addResponseCreate(FhgfsOpsErr createResult) + { + responseFlags |= LOOKUPINTENTRESPMSG_FLAG_CREATE; + this->createResult = createResult; + } + + void addResponseOpen(FhgfsOpsErr openResult, std::string fileHandleID, + std::unique_ptr pattern, const PathInfo& pathInfo) + { + responseFlags |= LOOKUPINTENTRESPMSG_FLAG_OPEN; + this->openResult = openResult; + this->fileHandleID = std::move(fileHandleID); + this->pattern = std::move(pattern); + this->pathInfo = pathInfo; + } + + void addResponseStat(FhgfsOpsErr statResult, const StatData& statData) + { + responseFlags |= LOOKUPINTENTRESPMSG_FLAG_STAT; + this->statResult = statResult; + this->statData = statData; + } + + void setEntryInfo(EntryInfo value) { entryInfo = std::move(value); } +}; + +/** + * This combines the normal lookup of a directory entry with intents, i.e. options to create, + * open and stat the entry in a single message. + * + * Note: The intent options currently work only for files. + */ +class LookupIntentMsgEx : public MirroredMessage> +{ + public: + typedef LookupIntentResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override; + + bool isMirrored() override { return getParentInfo()->getIsBuddyMirrored(); } + + const char* mirrorLogContext() const override { return "LookupIntentMsgEx/forward"; } + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + auto& respMsg = static_cast(resp); + + // since we only forward for open and create, we need only check those two. + if ((respMsg.getResponseFlags() & LOOKUPINTENTRESPMSG_FLAG_CREATE) + && respMsg.getCreateResult() != FhgfsOpsErr_SUCCESS) + return respMsg.getCreateResult(); + + if ((respMsg.getResponseFlags() & LOOKUPINTENTRESPMSG_FLAG_OPEN) + && respMsg.getOpenResult() != FhgfsOpsErr_SUCCESS) + return respMsg.getOpenResult(); + + return FhgfsOpsErr_SUCCESS; + } + + private: + FhgfsOpsErr lookup(const std::string& parentEntryID, const std::string& entryName, + bool isBuddyMirrored, EntryInfo* outEntryInfo, FileInodeStoreData* outInodeStoreData, + bool& outInodeDataOutdated); + FhgfsOpsErr revalidate(EntryInfo* diskEntryInfo, uint32_t metaVersion); + FhgfsOpsErr create(EntryInfo* parentInfo, const std::string& entryName, + EntryInfo* outEntryInfo, FileInodeStoreData* outInodeData, bool isSecondary); + FhgfsOpsErr stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData); + FhgfsOpsErr open(EntryInfo* entryInfo, std::string* outFileHandleID, + StripePattern** outPattern, PathInfo* outPathInfo, bool isSecondary); + + void forwardToSecondary(ResponseContext& ctx) override; + + MetaOpCounterTypes getOpCounterType(); + + FileInodeStoreData inodeData; + std::string entryID; + FhgfsOpsErr lookupRes; + bool inodeDataOutdated; + EntryInfo diskEntryInfo; +}; + + + diff --git a/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.cpp b/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.cpp new file mode 100644 index 0000000..f1d0902 --- /dev/null +++ b/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.cpp @@ -0,0 +1,20 @@ +#include "GetMetaResyncStatsMsgEx.h" + +#include +#include +#include + +bool GetMetaResyncStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + BuddyResyncer* resyncer = Program::getApp()->getBuddyResyncer(); + + MetaBuddyResyncJobStatistics stats; + + BuddyResyncJob* job = resyncer->getResyncJob(); + if (job) + stats = job->getJobStats(); + + ctx.sendResponse(GetMetaResyncStatsRespMsg(&stats)); + + return true; +} diff --git a/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.h b/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.h new file mode 100644 index 0000000..13016af --- /dev/null +++ b/meta/source/net/message/storage/mirroring/GetMetaResyncStatsMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class GetMetaResyncStatsMsgEx : public GetMetaResyncStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.cpp b/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.cpp new file mode 100644 index 0000000..8070183 --- /dev/null +++ b/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.cpp @@ -0,0 +1,376 @@ +#include "ResyncRawInodesMsgEx.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +bool ResyncRawInodesMsgEx::processIncoming(ResponseContext& ctx) +{ + LOG_DBG(MIRRORING, DEBUG, "", basePath, hasXAttrs, wholeDirectory); + + const FhgfsOpsErr resyncRes = resyncStream(ctx); + + ctx.sendResponse(ResyncRawInodesRespMsg(resyncRes)); + return resyncRes == FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr ResyncRawInodesMsgEx::resyncStream(ResponseContext& ctx) +{ + if (hasXAttrs && !Program::getApp()->getConfig()->getStoreClientXAttrs()) + { + LOG(MIRRORING, ERR, "Primary has indicated xattr resync, but xattrs are disabled in config."); + return FhgfsOpsErr_NOTSUPP; + } + + const auto& rootInfo = Program::getApp()->getMetaRoot(); + auto* const metaBGM = Program::getApp()->getMetaBuddyGroupMapper(); + auto* const rootDir = Program::getApp()->getRootDir(); + + // if the local root is not buddyMirrored yet, set local buddy mirroring for the root inode. + if (rootInfo.getID().val() == metaBGM->getLocalGroupID() && + !rootDir->getIsBuddyMirrored()) + { + const auto setMirrorRes = SetMetadataMirroringMsgEx::setMirroring(); + if (setMirrorRes != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Failed to set meta mirroring on the root directory", setMirrorRes); + return setMirrorRes; + } + } + + // if our path is a directory, we must create it now, otherwise, the directory may not be + // created at all. for example the #fSiDs# directory in an empty content directory with no + // orphaned fsids would not be created. + if (wholeDirectory) + { + const auto mkRes = Program::getApp()->getMetaStore()->beginResyncFor( + META_BUDDYMIRROR_SUBDIR_NAME / basePath, true); + if (mkRes.first != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Failed to create metadata directory.", basePath, + ("mkRes", mkRes.first)); + return mkRes.first; + } + } + + while (true) + { + const auto resyncPartRes = resyncSingle(ctx); + if (resyncPartRes == FhgfsOpsErr_AGAIN) + continue; + else if (resyncPartRes == FhgfsOpsErr_SUCCESS) + break; + + return resyncPartRes; + } + + const FhgfsOpsErr result = wholeDirectory + ? removeUntouchedInodes() + : FhgfsOpsErr_SUCCESS; + + return result; +} + +FhgfsOpsErr ResyncRawInodesMsgEx::resyncSingle(ResponseContext& ctx) +{ + uint32_t packetLength; + + ctx.getSocket()->recvExact(&packetLength, sizeof(packetLength), 0); + packetLength = LE_TO_HOST_32(packetLength); + + if (packetLength == 0) + return FhgfsOpsErr_SUCCESS; + + std::unique_ptr packetData(new char[packetLength]); + + ctx.getSocket()->recvExact(packetData.get(), packetLength, 0); + + Deserializer des(packetData.get(), packetLength); + + MetaSyncFileType packetType; + std::string relPath; + + des + % packetType + % relPath; + if (!des.good()) + { + LOG(MIRRORING, ERR, "Received bad data from primary."); + return FhgfsOpsErr_INTERNAL; + } + + if (wholeDirectory) + inodesWritten.push_back(relPath); + + FhgfsOpsErr result; + + switch (packetType) + { + case MetaSyncFileType::Inode: + case MetaSyncFileType::Directory: + result = resyncInode(ctx, basePath / relPath, des, + packetType == MetaSyncFileType::Directory); + break; + + case MetaSyncFileType::Dentry: + result = resyncDentry(ctx, basePath / relPath, des); + break; + + default: + result = FhgfsOpsErr_INVAL; + } + + ctx.sendResponse(ResyncRawInodesRespMsg(result)); + + // if the resync has failed, we have to return the result twice - once as an ACK for the packet, + // and another time to terminate the stream. mod sync could do without the termination, but + // bulk resync can't. + if (result == FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_AGAIN; + else + return result; +} + +FhgfsOpsErr ResyncRawInodesMsgEx::resyncInode(ResponseContext& ctx, const Path& path, + Deserializer& data, const bool isDirectory, const bool recvXAttrs) +{ + std::map> content; + bool isDeletion; + + // Decide how to correctly deserialize incoming data based on 'recvXAttrs' flag: + // + // Note: After switching data structure used to encode/serialize inode data from std::vector to + // std::map to accomodate new xattr introduced that stores Remote Storage Targets (RST) info in + // inodes, we need to handle both formats (See PR#3905 for more details): + // - true (default): Represents inode data with base meta xattr (META_XATTR_NAME) plus any + // user-defined xattrs as key-value pairs in map format. + // - false: Represents standalone dentry data still using the original vector format for + // compatibility, deserialized as single value. + if (!recvXAttrs) + { + content.try_emplace(META_XATTR_NAME); + data + % content[META_XATTR_NAME] + % isDeletion; + } + else + { + data + % content + % isDeletion; + } + + if (!data.good()) + { + LOG(MIRRORING, ERR, "Received bad data from primary."); + return FhgfsOpsErr_INTERNAL; + } + + if (isDeletion) + { + const bool rmRes = isDirectory + ? StorageTk::removeDirRecursive((META_BUDDYMIRROR_SUBDIR_NAME / path).str()) + : unlink((META_BUDDYMIRROR_SUBDIR_NAME / path).str().c_str()) == 0; + + if (rmRes || errno == ENOENT) + return FhgfsOpsErr_SUCCESS; + + LOG(MIRRORING, ERR, "Failed to remove raw meta inode.", path, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + if (!isDirectory && wholeDirectory) + { + const auto unlinkRes = Program::getApp()->getMetaStore()->unlinkRawMetadata( + META_BUDDYMIRROR_SUBDIR_NAME / path); + if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS) + { + LOG(MIRRORING, ERR, "Could not unlink raw metadata", path, unlinkRes); + return FhgfsOpsErr_INTERNAL; + } + } + + auto inode = Program::getApp()->getMetaStore()->beginResyncFor( + META_BUDDYMIRROR_SUBDIR_NAME / path, isDirectory); + if (inode.first) + return inode.first; + + if (!isDirectory) + { + for (const auto& attr : content) + { + const auto setContentRes = inode.second.setContent( + attr.first.c_str(), attr.second.data(), attr.second.size()); + + if (setContentRes) + return setContentRes; + } + } + + if (!hasXAttrs || !recvXAttrs) + return FhgfsOpsErr_SUCCESS; + + const auto xattrRes = resyncInodeXAttrs(ctx, inode.second); + if (xattrRes != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Syncing XAttrs failed.", path, xattrRes); + return xattrRes; + } + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr ResyncRawInodesMsgEx::resyncDentry(ResponseContext& ctx, const Path& path, + Deserializer& data) +{ + bool linksToFsID; + + data % linksToFsID; + if (!data.good()) + { + LOG(MIRRORING, ERR, "Received bad data from primary."); + return FhgfsOpsErr_INTERNAL; + } + + // dentries with independent contents (dir dentries, dentries to non-inlined files) can be + // treated like inodes for the purpose of resync. don't sync xattrs though, because dentries + // should never have them. set recvXAttrs=false to indicate independent dentry data. + if (!linksToFsID) + return resyncInode(ctx, path, data, false, false); + + std::string targetID; + bool isDeletion; + + data + % targetID + % isDeletion; + if (!data.good()) + { + LOG(MIRRORING, ERR, "Received bad data from primary."); + return FhgfsOpsErr_INTERNAL; + } + + const FhgfsOpsErr rmRes = Program::getApp()->getMetaStore()->unlinkRawMetadata( + META_BUDDYMIRROR_SUBDIR_NAME / path); + if (rmRes != FhgfsOpsErr_SUCCESS && rmRes != FhgfsOpsErr_PATHNOTEXISTS) + { + LOG(MIRRORING, ERR, "Could not unlink old dentry.", path, rmRes); + return FhgfsOpsErr_INTERNAL; + } + + if (isDeletion) + return FhgfsOpsErr_SUCCESS; + + const Path& idPath = path.dirname() / META_DIRENTRYID_SUB_STR / targetID; + const int linkRes = ::link( + (META_BUDDYMIRROR_SUBDIR_NAME / idPath).str().c_str(), + (META_BUDDYMIRROR_SUBDIR_NAME / path).str().c_str()); + if (linkRes < 0) + { + LOG(MIRRORING, ERR, "Could not link dentry to fsid.", path, idPath, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr ResyncRawInodesMsgEx::resyncInodeXAttrs(ResponseContext& ctx, IncompleteInode& inode) +{ + std::string name; + std::vector value; + + while (true) + { + auto readRes = MsgHelperXAttr::StreamXAttrState::readNextXAttr(ctx.getSocket(), name, value); + if (readRes == FhgfsOpsErr_SUCCESS) + break; + else if (readRes != FhgfsOpsErr_AGAIN) + return readRes; + + auto setRes = inode.setXattr((XAttrTk::UserXAttrPrefix + name).c_str(), &value[0], + value.size()); + if (setRes != FhgfsOpsErr_SUCCESS) + return setRes; + } + + return inode.clearUnsetXAttrs(); +} + +FhgfsOpsErr ResyncRawInodesMsgEx::removeUntouchedInodes() +{ + std::sort(inodesWritten.begin(), inodesWritten.end()); + + const Path dirPath(META_BUDDYMIRROR_SUBDIR_NAME / basePath); + + std::unique_ptr dir(::opendir(dirPath.str().c_str())); + + if (!dir) + { + LOG(MIRRORING, ERR, "Could not open meta directory.", dirPath, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + int dirFD = ::dirfd(dir.get()); + if (dirFD < 0) + { + LOG(MIRRORING, ERR, "Could not get directory fd.", sysErr); + return FhgfsOpsErr_INTERNAL; + } + + while (true) + { + struct dirent* found; + +#if USE_READDIR_P + struct dirent entry; + int err = readdir_r(dir.get(), &entry, &found); +#else + errno = 0; + found = readdir(dir.get()); + int err = found ? 0 : errno; +#endif + if (err > 0) + { + LOG(MIRRORING, ERR, "readdir() failed.", sysErr(err)); + return FhgfsOpsErr_INTERNAL; + } + + if (!found) + break; + + if (strcmp(found->d_name, ".") == 0 || strcmp(found->d_name, "..") == 0) + continue; + + bool written = std::binary_search( + inodesWritten.begin(), inodesWritten.end(), + found->d_name); + if (written) + continue; + + const int unlinkRes = ::unlinkat(dirFD, found->d_name, 0); + if (unlinkRes == 0 || errno == ENOENT) + continue; + + if (errno != EISDIR) + { + LOG(MIRRORING, ERR, "Could not remove file", basePath, found->d_name, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + const bool rmRes = StorageTk::removeDirRecursive((dirPath / found->d_name).str()); + if (!rmRes) + { + LOG(MIRRORING, ERR, "Could not remove file", found->d_name, sysErr); + return FhgfsOpsErr_INTERNAL; + } + } + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.h b/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.h new file mode 100644 index 0000000..dc719ee --- /dev/null +++ b/meta/source/net/message/storage/mirroring/ResyncRawInodesMsgEx.h @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +class ResyncRawInodesMsgEx : public NetMessageSerdes +{ + public: + ResyncRawInodesMsgEx(Path basePath, bool hasXAttrs, bool wholeDirectory): + BaseType(NETMSGTYPE_ResyncRawInodes), + basePath(std::move(basePath)), + hasXAttrs(hasXAttrs), + wholeDirectory(wholeDirectory) + {} + + ResyncRawInodesMsgEx(): BaseType(NETMSGTYPE_ResyncRawInodes) {} + + bool processIncoming(ResponseContext& ctx) override; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->basePath + % obj->hasXAttrs + % obj->wholeDirectory; + } + + private: + Path basePath; + bool hasXAttrs; + bool wholeDirectory; + + std::vector inodesWritten; + + FhgfsOpsErr resyncStream(ResponseContext& ctx); + + FhgfsOpsErr resyncSingle(ResponseContext& ctx); + + FhgfsOpsErr resyncInode(ResponseContext& ctx, const Path& path, Deserializer& data, + const bool isDirectory, const bool recvXAttrs = true); + FhgfsOpsErr resyncDentry(ResponseContext& ctx, const Path& path, Deserializer& data); + + FhgfsOpsErr resyncInodeXAttrs(ResponseContext& ctx, IncompleteInode& inode); + + FhgfsOpsErr removeUntouchedInodes(); +}; + diff --git a/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.cpp b/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.cpp new file mode 100644 index 0000000..741b354 --- /dev/null +++ b/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.cpp @@ -0,0 +1,49 @@ +#include "ResyncSessionStoreMsgEx.h" + +#include +#include +#include +#include + +bool ResyncSessionStoreMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + Config* config = app->getConfig(); + + FhgfsOpsErr receiveRes = receiveStoreBuf(ctx.getSocket(), config->getConnMsgShortTimeout()); + + if (receiveRes == FhgfsOpsErr_OUTOFMEM) + { + LOG(MIRRORING, ERR, "Failed to allocate receive buffer for session store resync - out of memory."); + return false; + } + else if (receiveRes == FhgfsOpsErr_COMMUNICATION) + { + LOG(MIRRORING, ERR, "Failed to receive session store buffer during resync."); + return false; + } + + auto sessionStoreBuf = getSessionStoreBuf(); + + SessionStore* sessionStore = Program::getApp()->getMirroredSessions(); + + const bool clearRes = sessionStore->clear(); + if (!clearRes) + { + LOG(MIRRORING, ERR, "Failed to clear session store."); + ctx.sendResponse(ResyncSessionStoreRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + const bool deserRes = sessionStore->deserializeFromBuf(sessionStoreBuf.first, + sessionStoreBuf.second, *Program::getApp()->getMetaStore()); + if (!deserRes) + { + LOG(MIRRORING, ERR, "Failed to deserialize session store data from primary."); + ctx.sendResponse(ResyncSessionStoreRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + ctx.sendResponse(ResyncSessionStoreRespMsg(FhgfsOpsErr_SUCCESS)); + return true; +} diff --git a/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.h b/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.h new file mode 100644 index 0000000..050e63f --- /dev/null +++ b/meta/source/net/message/storage/mirroring/ResyncSessionStoreMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class ResyncSessionStoreMsgEx : public ResyncSessionStoreMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.cpp b/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.cpp new file mode 100644 index 0000000..94c5482 --- /dev/null +++ b/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include + +#include + +#include "SetMetadataMirroringMsgEx.h" + + +bool SetMetadataMirroringMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + // verify that the current node is in a group, and is the primary for its group + bool localNodeIsPrimary; + uint16_t buddyGroupID = metaBuddyGroupMapper->getBuddyGroupID(app->getLocalNodeNumID().val(), + &localNodeIsPrimary); + + if (buddyGroupID == 0) + { + LogContext(__func__).logErr("This node is not part of a buddy group."); + ctx.sendResponse(SetMetadataMirroringRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + if (!localNodeIsPrimary) + { + LogContext(__func__).logErr("This node is not the primary root node."); + ctx.sendResponse(SetMetadataMirroringRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + // verify owner of root dir + if (app->getLocalNodeNumID() != app->getRootDir()->getOwnerNodeID()) + { + LogContext(__func__).logErr("This node does not own the root directory."); + ctx.sendResponse(SetMetadataMirroringRespMsg(FhgfsOpsErr_NOTOWNER)); + return true; + } + + FhgfsOpsErr setRes = setMirroring(); + + ctx.sendResponse(SetMetadataMirroringRespMsg(setRes) ); + + return true; +} + +FhgfsOpsErr SetMetadataMirroringMsgEx::setMirroring() +{ + // no two threads must be allowed to run this code at the same time. this could happen during + // bulk resync. + static Mutex setMirrorMtx; + std::unique_lock setMirrorMtxLock(setMirrorMtx); + + // more than one thread may have called this method. if so, report success to the ones who waited + if (Program::getApp()->getRootDir()->getIsBuddyMirrored()) + return FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + NumNodeID localNodeNumID = app->getLocalNodeNumID(); + + // get buddy group for this node + bool localNodeIsPrimary; + uint16_t buddyGroupID = metaBuddyGroupMapper->getBuddyGroupID(localNodeNumID.val(), + &localNodeIsPrimary); + + // move inode of root directory to mirrored dir + FhgfsOpsErr mvInodeRes; + FhgfsOpsErr mvDirRes; + + mvInodeRes = moveRootInode(false); + + if (mvInodeRes != FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_INTERNAL; + + // move root directory to mirrored dir + mvDirRes = moveRootDirectory(false); + if (mvDirRes != FhgfsOpsErr_SUCCESS) + { + // get inode back + moveRootInode(true); + + return FhgfsOpsErr_INTERNAL; + } + + // update buddy mirror info and write to disk + // NOTE: this must happen after the data has been moved, because buddy mirror flag changes save + // path inside of DirInode object + DirInode* dir = app->getRootDir(); + + const FhgfsOpsErr setMirrorRes = dir->setAndStoreIsBuddyMirrored(true); + if (setMirrorRes != FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, ERR, "Could not set mirror state on root inode", setMirrorRes); + const FhgfsOpsErr revertSetRes = dir->setAndStoreIsBuddyMirrored(false); + if (revertSetRes != FhgfsOpsErr_SUCCESS) + LOG(MIRRORING, ERR, "Could not revert mirror setting either, your filesystem is now corrupt", + revertSetRes); + + return FhgfsOpsErr_SAVEERROR; + } + + bool setOwnerRes = dir->setOwnerNodeID(NumNodeID(buddyGroupID) ); + + if (!setOwnerRes) + { + // get inode back + moveRootInode(true); + + // get dir back + moveRootDirectory(true); + + return FhgfsOpsErr_INTERNAL; + } + + // update root Node in meta store + app->getMetaRoot().set(NumNodeID(buddyGroupID), true); + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr SetMetadataMirroringMsgEx::moveRootInode(bool revertMove) +{ + App* app = Program::getApp(); + + Path oldPath( app->getMetaPath() + "/" + + MetaStorageTk::getMetaInodePath(app->getInodesPath()->str(), + META_ROOTDIR_ID_STR)); + + Path newPath( app->getMetaPath() + "/" + + MetaStorageTk::getMetaInodePath(app->getBuddyMirrorInodesPath()->str(), + META_ROOTDIR_ID_STR)); + + int renameRes; + if ( !revertMove ) + { + StorageTk::createPathOnDisk(newPath, true); + renameRes = rename(oldPath.str().c_str(), newPath.str().c_str()); + } + else + renameRes = rename(newPath.str().c_str(), oldPath.str().c_str()); + + if ( renameRes ) + { + LogContext(__func__).logErr( + "Unable to move root inode; error: " + System::getErrString()); + + return FhgfsOpsErr_INTERNAL; + } + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr SetMetadataMirroringMsgEx::moveRootDirectory(bool revertMove) +{ + App* app = Program::getApp(); + + Path oldPath(app->getMetaPath() + "/" + + MetaStorageTk::getMetaDirEntryPath(app->getDentriesPath()->str(), + META_ROOTDIR_ID_STR)); + Path newPath(app->getMetaPath() + "/" + + MetaStorageTk::getMetaDirEntryPath(app->getBuddyMirrorDentriesPath()->str(), + META_ROOTDIR_ID_STR)); + + int renameRes; + if ( !revertMove ) + { + StorageTk::createPathOnDisk(newPath, true); + renameRes = rename(oldPath.str().c_str(), newPath.str().c_str()); + } + else + renameRes = rename(newPath.str().c_str(), oldPath.str().c_str()); + + if (renameRes) + { + LogContext(__func__).logErr( + "Unable to move root directory; error: " + System::getErrString()); + + return FhgfsOpsErr_INTERNAL; + } + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.h b/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.h new file mode 100644 index 0000000..8453c10 --- /dev/null +++ b/meta/source/net/message/storage/mirroring/SetMetadataMirroringMsgEx.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + + +class SetMetadataMirroringMsgEx : public SetMetadataMirroringMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + static FhgfsOpsErr setMirroring(); + + private: + static FhgfsOpsErr moveRootInode(bool revertMove); + static FhgfsOpsErr moveRootDirectory(bool revertMove); +}; + diff --git a/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp b/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp new file mode 100644 index 0000000..69e1cdb --- /dev/null +++ b/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "StorageResyncStartedMsgEx.h" + +bool StorageResyncStartedMsgEx::processIncoming(ResponseContext& ctx) +{ + NumNodeID nodeID = Program::getApp()->getLocalNodeNumID(); + + uint16_t targetID = getValue(); + if (targetID != nodeID.val()) + return false; + + // Make sure all workers have processed all messages that were received before this one. + // This ensures that no mirrored messages are in flight while resync starts. + pauseWorkers(); + + // we may not have received a heartbeat from the mgmtd yet that could have told us that the + // root inode is mirrored. since un-mirroring the root inode is currently not possible, we may + // assume that the root inode is very much supposed to be mirrored. + // we only need this info if we are currently the secondary of the root buddy group, but we + // can safely set it on all nodes until we add an option to disable meta mirroring for the + // root inode again. + Program::getApp()->getRootDir()->setIsBuddyMirroredFlag(true); + + // clear session store here to get rid of all inodes that will go away during resync. + Program::getApp()->getMirroredSessions()->clear(); + + // we can now be sure that no mirrored dir inode is still referenced by an operation: + // * the session store is cleared, so no references from there + // * we have received this message, so must be in NeedsResync state + // -> no mirrored operations will be addressed to this node + // * we hold no references ourselves + // + // since resync changes mirrored dir inodes, we must invalidate all inodes that are currently + // loaded (which will be at least root and mdisposal, plus any cached dir inodes) to ensure that + // future operations will use the correct resynced data + Program::getApp()->getMetaStore()->invalidateMirroredDirInodes(); + + ctx.sendResponse(StorageResyncStartedRespMsg()); + + return true; +} + +void StorageResyncStartedMsgEx::pauseWorkers() +{ + App* app = Program::getApp(); + WorkerList* workers = app->getWorkers(); + MultiWorkQueue* workQueue = app->getWorkQueue(); + pthread_t threadID = PThread::getCurrentThreadID(); + + // Stop all worker threads except our own + Barrier workerBarrier(workers->size()); + for (WorkerListIter workerIt = workers->begin(); workerIt != workers->end(); ++workerIt) + { + // don't enqueue it in the worker that processes this message (this would deadlock) + if (!PThread::threadIDEquals((*workerIt)->getID(), threadID)) + { + PersonalWorkQueue* personalQ = (*workerIt)->getPersonalWorkQueue(); + workQueue->addPersonalWork(new BarrierWork(&workerBarrier), personalQ); + } + } + + // Stall our own worker until all the other threads are blocked. + workerBarrier.wait(); + + // Continue all workers + workerBarrier.wait(); +} diff --git a/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h b/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h new file mode 100644 index 0000000..76a94aa --- /dev/null +++ b/meta/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class StorageResyncStartedMsgEx : public StorageResyncStartedMsg +{ + public: + StorageResyncStartedMsgEx() : StorageResyncStartedMsg() + { } + + virtual bool processIncoming(ResponseContext& ctx); + + private: + void pauseWorkers(); +}; + diff --git a/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.cpp b/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.cpp new file mode 100644 index 0000000..98dd9ee --- /dev/null +++ b/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.cpp @@ -0,0 +1,71 @@ +#include +#include +#include +#include +#include +#include "MovingDirInsertMsgEx.h" + + +bool MovingDirInsertMsgEx::processIncoming(ResponseContext& ctx) +{ + rctx = &ctx; + + return BaseType::processIncoming(ctx); +} + + +std::unique_ptr MovingDirInsertMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr retVal; + + EntryInfo* toDirInfo = this->getToDirInfo(); + + // reference parent + DirInode* parentDir = metaStore->referenceDir(toDirInfo->getEntryID(), + toDirInfo->getIsBuddyMirrored(), true); + if(!parentDir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + /* create dir-entry and add information about its inode from the given buffer */ + + const std::string& newName = this->getNewName(); + const char* buf = this->getSerialBuf(); + Deserializer des(buf, getSerialBufLen()); + DirEntry newDirEntry(newName); + + newDirEntry.deserializeDentry(des); + if (!des.good()) + { + LogContext("File rename").logErr("Bug: Deserialization of remote buffer failed. Are all " + "meta servers running the same version?" ); + + retVal = FhgfsOpsErr_INTERNAL; + goto out; + } + + if (newDirEntry.getEntryID() == parentDir->getID() ) + { // attempt to rename a dir into itself + retVal = FhgfsOpsErr_INVAL; + goto out; + } + + retVal = parentDir->makeDirEntry(newDirEntry); + + if (retVal == FhgfsOpsErr_SUCCESS && shouldFixTimestamps()) + fixInodeTimestamp(*parentDir, dirTimestamps); + + if (retVal != FhgfsOpsErr_SUCCESS && retVal != FhgfsOpsErr_EXISTS) + retVal = FhgfsOpsErr_INTERNAL; + +out: + metaStore->releaseDir(toDirInfo->getEntryID()); + return boost::make_unique(retVal); +} + +void MovingDirInsertMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_MovingDirInsertResp); +} diff --git a/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.h b/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.h new file mode 100644 index 0000000..e0a99ab --- /dev/null +++ b/meta/source/net/message/storage/moving/MovingDirInsertMsgEx.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include +#include +#include + +// Move directory to another meta-data server + +class MovingDirInsertMsgEx : public MirroredMessage> +{ + public: + typedef ErrorCodeResponseState + ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple lock(EntryLockStore& store) override + { + // we must not lock the directory if it is owned by the current node. if it is, the + // current message was also sent by the local node, specifically by a RmDirMsgEx, which + // also locks the directory for write + if (rctx->isLocallyGenerated()) + return {}; + + FileIDLock dirLock(&store, getToDirInfo()->getEntryID(), true); + ParentNameLock nameLock(&store, getToDirInfo()->getEntryID(), getNewName()); + + return std::make_tuple(std::move(dirLock), std::move(nameLock)); + } + + bool isMirrored() override { return getToDirInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MovingDirInsertMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.cpp b/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.cpp new file mode 100644 index 0000000..38d1fb1 --- /dev/null +++ b/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.cpp @@ -0,0 +1,173 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "MovingFileInsertMsgEx.h" + +std::tuple MovingFileInsertMsgEx::lock( + EntryLockStore& store) +{ + // we must not lock the directory if it is owned by the current node. if it is, the + // current message was also sent by the local node, specifically by a rename msg, which + // also locks the directory for write + if (rctx->isLocallyGenerated()) + return {}; + + FileIDLock dirLock(&store, getToDirInfo()->getEntryID(), true); + + ParentNameLock nameLock(&store, getToDirInfo()->getEntryID(), getNewName()); + + FileIDLock newLock; + FileIDLock unlinkedLock; + + auto dir = Program::getApp()->getMetaStore()->referenceDir(getToDirInfo()->getEntryID(), + getToDirInfo()->getIsBuddyMirrored(), true); + if (dir) + { + FileInode newInode; + Deserializer des(getSerialBuf(), getSerialBufLen()); + newInode.deserializeMetaData(des); + if (des.good()) + { + std::string unlinkedID = newInode.getEntryID(); + + EntryInfo unlinkedInfo; + dir->getFileEntryInfo(getNewName(), unlinkedInfo); + if (DirEntryType_ISFILE(unlinkedInfo.getEntryType())) + unlinkedID = unlinkedInfo.getEntryID(); + + if (newInode.getEntryID() < unlinkedID) + { + newLock = {&store, newInode.getEntryID(), true}; + unlinkedLock = {&store, unlinkedID, true}; + } + else if (newInode.getEntryID() == unlinkedID) + { + newLock = {&store, newInode.getEntryID(), true}; + } + else + { + unlinkedLock = {&store, unlinkedID, true}; + newLock = {&store, newInode.getEntryID(), true}; + } + } + + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + } + + return std::make_tuple( + std::move(newLock), + std::move(unlinkedLock), + std::move(dirLock), + std::move(nameLock)); +} + +bool MovingFileInsertMsgEx::processIncoming(ResponseContext& ctx) +{ + rctx = &ctx; + + return BaseType::processIncoming(ctx); +} + + +std::unique_ptr MovingFileInsertMsgEx::executeLocally( + ResponseContext& ctx, bool isSecondary) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + EntryInfo* fromFileInfo = this->getFromFileInfo(); + EntryInfo* toDirInfo = this->getToDirInfo(); + std::string newName = this->getNewName(); + + EntryInfo overWrittenEntryInfo; + std::unique_ptr unlinkInode; + unsigned inodeBufLen; + std::unique_ptr inodeBuf; + + DirInode* toDir = metaStore->referenceDir(toDirInfo->getEntryID(), + toDirInfo->getIsBuddyMirrored(), true); + if (!toDir) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + FhgfsOpsErr moveRes = metaStore->moveRemoteFileInsert( + fromFileInfo, *toDir, newName, getSerialBuf(), getSerialBufLen(), &unlinkInode, + &overWrittenEntryInfo, newFileInfo); + if (moveRes != FhgfsOpsErr_SUCCESS) + { + metaStore->releaseDir(toDir->getID()); + return boost::make_unique(moveRes); + } + + std::string xattrName; + CharVector xattrValue; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + while (isMsgHeaderFeatureFlagSet(MOVINGFILEINSERTMSG_FLAG_HAS_XATTRS)) + { + retVal = MsgHelperXAttr::StreamXAttrState::readNextXAttr(ctx.getSocket(), xattrName, + xattrValue); + if (retVal == FhgfsOpsErr_SUCCESS) + break; + else if (retVal != FhgfsOpsErr_AGAIN) + goto xattr_error; + + retVal = MsgHelperXAttr::setxattr(&newFileInfo, xattrName, xattrValue, 0); + if (retVal != FhgfsOpsErr_SUCCESS) + goto xattr_error; + + xattrNames.push_back(xattrName); + } + + if(unlinkInode) + { + inodeBuf.reset(new (std::nothrow) char[META_SERBUF_SIZE]); + if (unlikely(!inodeBuf) ) + { // out of memory, we are going to leak an inode and chunks + inodeBufLen = 0; + LOG(GENERAL, ERR, "Malloc failed, leaking chunks", ("inodeID", unlinkInode->getEntryID())); + } + else + { + Serializer ser(inodeBuf.get(), META_SERBUF_SIZE); + unlinkInode->serializeMetaData(ser); + inodeBufLen = ser.size(); + } + } + else + { // no file overwritten + inodeBufLen = 0; + } + + if (shouldFixTimestamps()) + { + fixInodeTimestamp(*toDir, dirTimestamps); + auto [newFile, referenceRes] = metaStore->referenceFile(&newFileInfo); + if (newFile) + { + fixInodeTimestamp(*newFile, fileTimestamps, &newFileInfo); + metaStore->releaseFile(toDir->getID(), newFile); + } + } + + metaStore->releaseDir(toDir->getID()); + + return boost::make_unique(FhgfsOpsErr_SUCCESS, inodeBufLen, + std::move(inodeBuf), overWrittenEntryInfo); + +xattr_error: + unsigned outNumHardlinks; // Not used here! + MsgHelperUnlink::unlinkMetaFile(*toDir, newName, NULL, outNumHardlinks); + metaStore->releaseDir(toDir->getID()); + + return boost::make_unique(retVal); +} + +void MovingFileInsertMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_MovingFileInsertResp); +} diff --git a/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.h b/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.h new file mode 100644 index 0000000..338034d --- /dev/null +++ b/meta/source/net/message/storage/moving/MovingFileInsertMsgEx.h @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// this class is used on the server where the file is moved to + +class MovingFileInsertResponseState : public MirroredMessageResponseState +{ + public: + explicit MovingFileInsertResponseState(FhgfsOpsErr result) + : result(result), inodeBufLen(0) + { + } + + explicit MovingFileInsertResponseState(Deserializer& des) + { + des + % serdes::as(result) + % inodeBufLen; + + if (inodeBufLen > META_SERBUF_SIZE) + des.setBad(); + else + { + inodeBuf.reset(new char[inodeBufLen]); + des.getBlock(inodeBuf.get(), inodeBufLen); + } + + des % overWrittenEntryInfo; + } + + MovingFileInsertResponseState(FhgfsOpsErr result, unsigned inodeBufLen, + std::unique_ptr inodeBuf, EntryInfo entryInfo) + : result(result), inodeBufLen(inodeBufLen), inodeBuf(std::move(inodeBuf)), + overWrittenEntryInfo(entryInfo) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + ctx.sendResponse(MovingFileInsertRespMsg(result, inodeBufLen, inodeBuf.get(), overWrittenEntryInfo)); + } + + bool changesObservableState() const override + { + return result == FhgfsOpsErr_SUCCESS; + } + + protected: + uint32_t serializerTag() const override { return NETMSGTYPE_MovingFileInsert; } + + void serializeContents(Serializer& ser) const override + { + ser + % serdes::as(result) + % inodeBufLen; + + ser.putBlock(inodeBuf.get(), inodeBufLen); + } + + private: + FhgfsOpsErr result; + unsigned inodeBufLen; + std::unique_ptr inodeBuf; + EntryInfo overWrittenEntryInfo; +}; + +class MovingFileInsertMsgEx : public MirroredMessage> +{ + public: + typedef MovingFileInsertResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + std::tuple + lock(EntryLockStore& store) override; + + bool isMirrored() override { return getToDirInfo()->getIsBuddyMirrored(); } + + private: + ResponseContext* rctx; + + StringVector xattrNames; + EntryInfo newFileInfo; + MsgHelperXAttr::StreamXAttrState streamState; + + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + void forwardToSecondary(ResponseContext& ctx) override; + + void prepareMirrorRequestArgs(RequestResponseArgs& args) override + { + if (isMsgHeaderFeatureFlagSet(MOVINGFILEINSERTMSG_FLAG_HAS_XATTRS)) + { + streamState = {newFileInfo, xattrNames}; + registerStreamoutHook(args, streamState); + } + } + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return static_cast(resp).getResult(); + } + + const char* mirrorLogContext() const override { return "MovingFileInsertMsgEx/forward"; } +}; + diff --git a/meta/source/net/message/storage/moving/RenameV2MsgEx.cpp b/meta/source/net/message/storage/moving/RenameV2MsgEx.cpp new file mode 100644 index 0000000..02a170a --- /dev/null +++ b/meta/source/net/message/storage/moving/RenameV2MsgEx.cpp @@ -0,0 +1,896 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "RenameV2MsgEx.h" + +#include + +namespace { +struct DirHandle { + MetaStore* metaStore; + const EntryInfo* ei; + + DirHandle(MetaStore* metaStore, const EntryInfo* ei): metaStore(metaStore), ei(ei) {} + + DirHandle(const DirHandle&) = delete; + DirHandle(DirHandle&&) = delete; + + DirHandle& operator=(const DirHandle&) = delete; + DirHandle& operator=(DirHandle&&) = delete; + + ~DirHandle() { + metaStore->releaseDir(ei->getEntryID()); + } +}; +} + +RenameV2Locks RenameV2MsgEx::lock(EntryLockStore& store) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + // if the directory could not be referenced it does not exist on the current node. this will + // cause the operation to fail lateron during executeLocally() when we reference the same + // directory again. since we cannot do anything without having access to the source directory, + // and since no directory with the same id as the source directory can appear after the source + // directory has been removed, we can safely unlock everything right here and continue without + // blocking other workers on the (probably live) target directory. + DirInode* fromDir = metaStore->referenceDir(getFromDirInfo()->getEntryID(), + getFromDirInfo()->getIsBuddyMirrored(), true); + if (!fromDir) + return {}; + + const DirHandle _from(metaStore, getFromDirInfo()); + + DirInode* toDir = metaStore->referenceDir(getToDirInfo()->getEntryID(), + getToDirInfo()->getIsBuddyMirrored(), true); + + if (!toDir) + return {}; + + const DirHandle _to(metaStore, getToDirInfo()); + + for (;;) { + RenameV2Locks result; + + EntryInfo fromFileInfo; + EntryInfo toFileInfo; + + fromDir->getFileEntryInfo(getOldName(), fromFileInfo); + bool toFileExists = toDir->getFileEntryInfo(getNewName(), toFileInfo); + + if (toFileExists && DirEntryType_ISFILE(toFileInfo.getEntryType())) + { + // lock hash dir of tofile inode only if: + // 1) its a file and + // 2) its a non-inlined inode and + // 3) resynch job is running + if (resyncJob && resyncJob->isRunning() && !toFileInfo.getIsInlined()) + result.toFileHashLock = {&store, MetaStorageTk::getMetaInodeHash(toFileInfo.getEntryID())}; + } + + { + std::map lockOrder; + + lockOrder.insert(std::make_pair(getFromDirInfo()->getEntryID(), &result.fromDirLock)); + lockOrder.insert(std::make_pair(getToDirInfo()->getEntryID(), &result.toDirLock)); + if (DirEntryType_ISDIR(fromFileInfo.getEntryType())) + lockOrder.insert(std::make_pair(fromFileInfo.getEntryID(), &result.fromFileLockD)); + + for (auto it = lockOrder.begin(); it != lockOrder.end(); ++it) + *it->second = {&store, it->first, true}; + } + + // we might have locked fromFileLockD before fromDirLock due to ordering. resolve the source + // once more and check that we still refer to the same id, otherwise retry until we have the + // correct inode. + // if the name went away we don't have to retry (it can't be created while the dir is locked), + // but retrying is simpler to do. + EntryInfo fromFileInfoCheck; + fromDir->getFileEntryInfo(getOldName(), fromFileInfoCheck); + if (fromFileInfo.getEntryID() != fromFileInfoCheck.getEntryID()) + continue; + + // take care about lock ordering! see MirroredMessage::lock() + // since directories are locked for read, and by the same id as the (parent,name) tuples,the + // same ordering applies. + if (getFromDirInfo()->getEntryID() < getToDirInfo()->getEntryID()) + { + result.fromNameLock = {&store, getFromDirInfo()->getEntryID(), getOldName()}; + result.toNameLock = {&store, getToDirInfo()->getEntryID(), getNewName()}; + } + else if (getFromDirInfo()->getEntryID() == getToDirInfo()->getEntryID()) + { + if (getOldName() < getNewName()) + { + result.fromNameLock = {&store, getFromDirInfo()->getEntryID(), getOldName()}; + result.toNameLock = {&store, getToDirInfo()->getEntryID(), getNewName()}; + } + else if (getOldName() == getNewName()) + { + result.fromNameLock = {&store, getFromDirInfo()->getEntryID(), getOldName()}; + } + else + { + result.toNameLock = {&store, getToDirInfo()->getEntryID(), getNewName()}; + result.fromNameLock = {&store, getFromDirInfo()->getEntryID(), getOldName()}; + } + } + else + { + result.toNameLock = {&store, getToDirInfo()->getEntryID(), getNewName()}; + result.fromNameLock = {&store, getFromDirInfo()->getEntryID(), getOldName()}; + } + + if (DirEntryType_ISFILE(fromFileInfo.getEntryType()) && fromFileInfo.getIsInlined()) + { + if (DirEntryType_ISFILE(toFileInfo.getEntryType()) && toFileInfo.getIsInlined()) + { + if (fromFileInfo.getEntryID() < toFileInfo.getEntryID()) + { + result.fromFileLockF = {&store, fromFileInfo.getEntryID(), true}; + result.unlinkedFileLock = {&store, toFileInfo.getEntryID(), true}; + } + else if (fromFileInfo.getEntryID() == toFileInfo.getEntryID()) + { + result.fromFileLockF = {&store, fromFileInfo.getEntryID(), true}; + } + else + { + result.unlinkedFileLock = {&store, toFileInfo.getEntryID(), true}; + result.fromFileLockF = {&store, fromFileInfo.getEntryID(), true}; + } + } + else + { + result.fromFileLockF = {&store, fromFileInfo.getEntryID(), true}; + } + } + + return result; + } +} + +bool RenameV2MsgEx::processIncoming(ResponseContext& ctx) +{ + LOG_DEBUG(__func__, Log_DEBUG, "FromDirID: " + getFromDirInfo()->getEntryID() + "; " + "oldName: '" + getOldName() + "'; " + "ToDirID: " + getToDirInfo()->getEntryID() + "; " + "newName: '" + getNewName() + "'"); + + BaseType::processIncoming(ctx); + + // update operation counters + updateNodeOp(ctx, MetaOpCounter_RENAME); + + return true; +} + +/** + * Checks existence of the from-part and calls movingPerform(). + */ +std::unique_ptr RenameV2MsgEx::executeLocally(ResponseContext& ctx, + bool isSecondary) +{ + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + // reference fromParent + DirInode* fromParent = metaStore->referenceDir(getFromDirInfo()->getEntryID(), + getFromDirInfo()->getIsBuddyMirrored(), true); + + if (unlikely(!fromParent)) + return boost::make_unique(FhgfsOpsErr_PATHNOTEXISTS); + + std::string unlinkedEntryID; // ID of potential overwritten destination file for cleanup + EntryInfo srcEntryInfo; // EntryInfo of source file/directory being renamed + unsigned srcEntryLinkCount = 0; // Hardlink count needed for event logging + + ModificationEventFlusher* modEventFlusher = app->getModificationEventFlusher(); + const bool modEventLoggingEnabled = modEventFlusher->isLoggingEnabled(); + + const bool fileEventLogEnabled = !isSecondary && getFileEvent() && app->getFileEventLogger(); + + if (modEventLoggingEnabled || fileEventLogEnabled) + { + fromParent->getEntryInfo(getOldName(), srcEntryInfo); + + // Fetch link count early before rename occurs: + // - Inlined inode data might move to a different metadata node post-rename + // - Early fetch avoids a StatMsg round trip to retrieve link count from the new node + FhgfsOpsErr res; + std::tie(res, srcEntryLinkCount) = getLinkCountForMovedEntry(&srcEntryInfo); + if (res != FhgfsOpsErr_SUCCESS) + { + LogContext("RenameV2MsgEx::executeLocally").logErr( + "Failed to get link count for entry: " + srcEntryInfo.getEntryID()); + // don't return an error to client + } + } + + FhgfsOpsErr renameRes = movingPerform(*fromParent, getOldName(), + getEntryType(), getToDirInfo(), getNewName(), unlinkedEntryID); + + if ((renameRes == FhgfsOpsErr_SUCCESS) && shouldFixTimestamps()) + fixInodeTimestamp(*fromParent, fromDirTimestamps); + + metaStore->releaseDir(getFromDirInfo()->getEntryID()); + + if ((renameRes == FhgfsOpsErr_SUCCESS) && fileEventLogEnabled) + { + EventContext eventCtx = makeEventContext( + &srcEntryInfo, + getFromDirInfo()->getEntryID(), + getMsgHeaderUserID(), + getToDirInfo()->getEntryID(), + srcEntryLinkCount, + isSecondary + ); + + logEvent(app->getFileEventLogger(), *getFileEvent(), eventCtx); + } + + // clean-up + if ((renameRes == FhgfsOpsErr_SUCCESS) && modEventLoggingEnabled) + { + if (DirEntryType_ISDIR(getEntryType())) + modEventFlusher->add(ModificationEvent_DIRMOVED, srcEntryInfo.getEntryID()); + else + { + modEventFlusher->add(ModificationEvent_FILEMOVED, srcEntryInfo.getEntryID()); + if (!unlinkedEntryID.empty()) + modEventFlusher->add(ModificationEvent_FILEREMOVED, unlinkedEntryID); + } + } + + return boost::make_unique(renameRes); +} + +FhgfsOpsErr RenameV2MsgEx::movingPerform(DirInode& fromParent, const std::string& oldName, + DirEntryType entryType, EntryInfo* toDirInfo, const std::string& newName, std::string& unlinkedEntryID) +{ + const char* logContext = "RenameV2MsgEx::movingPerform"; + IGNORE_UNUSED_VARIABLE(logContext); + App* app = Program::getApp(); + + // is this node the owner of the fromParent dir? + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + NumNodeID expectedOwnerID = fromParent.getIsBuddyMirrored() ? + NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ) : app->getLocalNode().getNumID(); + + if (fromParent.getOwnerNodeID() != expectedOwnerID) + return FhgfsOpsErr_NOTOWNER; + + if (unlikely(entryType == DirEntryType_INVALID) ) + { + LOG_DEBUG(logContext, Log_SPAM, "Received an invalid entry type!"); + return FhgfsOpsErr_INTERNAL; + } + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + if (fromParent.getID() == toDirInfo->getEntryID()) + { // simple rename (<= not a move && everything local) + LOG_DEBUG(logContext, Log_SPAM, "Method: rename in same dir"); // debug in + retVal = renameInSameDir(fromParent, oldName, newName, unlinkedEntryID); + } + else + if (entryType == DirEntryType_DIRECTORY) + retVal = renameDir(fromParent, oldName, toDirInfo, newName); + else + retVal = renameFile(fromParent, oldName, toDirInfo, newName, unlinkedEntryID); + + return retVal; +} + +void RenameV2MsgEx::forwardToSecondary(ResponseContext& ctx) +{ + sendToSecondary(ctx, *this, NETMSGTYPE_RenameResp); +} + +/** + * Rename a directory + */ +FhgfsOpsErr RenameV2MsgEx::renameDir(DirInode& fromParent, const std::string& oldName, + EntryInfo* toDirInfo, const std::string& newName) +{ + const char* logContext = "RenameV2MsgEx::renameDir"; + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + DirEntry fromDirEntry(oldName); + bool dirEntryCopyRes = fromParent.getDirDentry(oldName, fromDirEntry); + if (!dirEntryCopyRes) + { + LOG_DEBUG("RenameV2MsgEx::movingPerform", Log_SPAM, "getDirEntryCopy() failed"); + return FhgfsOpsErr_NOTADIR; + } + + // when we we verified the 'oldName' is really a directory + LOG_DEBUG(logContext, Log_SPAM, "Method: remote dir move."); // debug in + + if (fromParent.getIsBuddyMirrored() && hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + retVal = FhgfsOpsErr_SUCCESS; + } + else + { + // prepare local part of the move operation + boost::scoped_array serialBuf(new char[META_SERBUF_SIZE]); + Serializer ser(serialBuf.get(), META_SERBUF_SIZE); + + /* Put all meta data of this dentry into the given buffer. The buffer then will be + * used on the remote side to fill the new dentry */ + fromDirEntry.serializeDentry(ser); + + if (!ser.good()) + LogContext(logContext).logErr("dentry too large: " + oldName); + else + retVal = remoteDirInsert(toDirInfo, newName, serialBuf.get(), ser.size()); + } + + // finish local part of the move operation + if (retVal == FhgfsOpsErr_SUCCESS) + { + DirEntry* rmDirEntry; + + retVal = fromParent.removeDir(oldName, &rmDirEntry); + + if (retVal == FhgfsOpsErr_SUCCESS) + { + std::string parentID = fromParent.getID(); + EntryInfo removedInfo; + + rmDirEntry->getEntryInfo(parentID, 0, &removedInfo); + + if (!hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + updateRenamedDirInode(&removedInfo, toDirInfo); + } + else + { + LogContext(logContext).log(Log_CRITICAL, + std::string("Failed to remove fromDir: ") + oldName + + " Error: " + boost::lexical_cast(retVal)); + } + + SAFE_DELETE(rmDirEntry); + } + + return retVal; +} + +/** + * Rename a file + */ +FhgfsOpsErr RenameV2MsgEx::renameFile(DirInode& fromParent, const std::string& oldName, + EntryInfo* toDirInfo, const std::string& newName, std::string& unlinkedEntryID) +{ + const char* logContext = "RenameV2MsgEx::renameFile"; + MetaStore* metaStore = Program::getApp()->getMetaStore(); + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + EntryInfo fromFileInfo; + bool getRes = fromParent.getFileEntryInfo(oldName, fromFileInfo); + if (!getRes) + { + LOG_DEBUG(logContext, Log_SPAM, "Error: fromDir does not exist."); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + // when we are here we verified the file to be renamed is really a file + + if (fromParent.getIsBuddyMirrored() && hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + retVal = FhgfsOpsErr_SUCCESS; + } + else + { + // the buffer is used to transfer all data of the dir-entry + boost::scoped_array serialBuf(new char[META_SERBUF_SIZE]); + size_t usedSerialBufLen; + + retVal = metaStore->moveRemoteFileBegin( + fromParent, &fromFileInfo, serialBuf.get(), META_SERBUF_SIZE, &usedSerialBufLen); + if (retVal != FhgfsOpsErr_SUCCESS) + return retVal; + + LOG_DEBUG(logContext, Log_SPAM, "Method: remote file move."); // debug in + + StringVector xattrNames; + if (Program::getApp()->getConfig()->getStoreClientXAttrs() && fromFileInfo.getIsInlined()) + { + FhgfsOpsErr listXAttrRes; + + std::tie(listXAttrRes, xattrNames) = MsgHelperXAttr::listxattr(&fromFileInfo); + if (listXAttrRes != FhgfsOpsErr_SUCCESS) + { + metaStore->moveRemoteFileComplete(fromParent, fromFileInfo.getEntryID()); + return FhgfsOpsErr_TOOBIG; + } + } + + // Do the remote operation (insert and possible unlink of an existing toFile) + retVal = remoteFileInsertAndUnlink(&fromFileInfo, toDirInfo, newName, serialBuf.get(), + usedSerialBufLen, std::move(xattrNames), unlinkedEntryID); + } + + // finish local part of the owned file move operation (+ remove local file-link) + if (retVal == FhgfsOpsErr_SUCCESS) + { + FhgfsOpsErr unlinkRes; + + if (fromFileInfo.getIsInlined()) + { + // We are not interested in the inode here , as we are not going to delete storage chunks. + EntryInfo entryInfo; + unsigned outNumHardlinks; // Not used here! + unlinkRes = metaStore->unlinkFile(fromParent, oldName, &entryInfo, NULL, outNumHardlinks); + } + else + { + // only unlink dentry-by-filename for nonInlined inode(s) + DirEntry oldDentry(oldName); + if (fromParent.getDentry(oldName, oldDentry)) + unlinkRes = fromParent.unlinkDirEntry(oldName, &oldDentry, DirEntry_UNLINK_FILENAME); + else + unlinkRes = FhgfsOpsErr_PATHNOTEXISTS; + } + + if (unlikely (unlinkRes) ) + { + LogContext(logContext).logErr(std::string("Error: Failed to unlink fromFile: ") + + "DirID: " + fromFileInfo.getParentEntryID() + " " + "entryName: " + oldName + ". " + + "Remote toFile was successfully created."); + } + } + + if (!fromParent.getIsBuddyMirrored() || !hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + metaStore->moveRemoteFileComplete(fromParent, fromFileInfo.getEntryID() ); + + return retVal; +} + +/** + * Renames a directory or a file (no moving between different directories involved). + */ +FhgfsOpsErr RenameV2MsgEx::renameInSameDir(DirInode& fromParent, const std::string& oldName, + const std::string& toName, std::string& unlinkedEntryID) +{ + const char* logContext = "RenameV2MsgEx::renameInSameDir"; + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + /* we are passing here the very same fromParent pointer also a toParent pointer, which is + * essential in order not to dead-lock */ + + std::unique_ptr unlinkInode; // inode belong to a possibly existing toName file + DirEntry* overWrittenEntry = NULL; + bool wasInlined; // to determine if overwritten inode was inlined or not + + FhgfsOpsErr renameRes = metaStore->renameInSameDir(fromParent, oldName, toName, + &unlinkInode, overWrittenEntry, wasInlined); + + if (renameRes == FhgfsOpsErr_SUCCESS) + { + if (shouldFixTimestamps()) + { + DirEntry dentry(toName); + if (fromParent.getDentry(toName, dentry)) + { + EntryInfo info; + dentry.getEntryInfo(fromParent.getID(), 0, &info); + + if (DirEntryType_ISDIR(info.getEntryType())) + { + auto dir = metaStore->referenceDir(info.getEntryID(), info.getIsBuddyMirrored(), + true); + if (dir) + { + fixInodeTimestamp(*dir, renamedInodeTimestamps); + metaStore->releaseDir(dir->getID()); + } + } + else + { + auto [file, referenceRes] = metaStore->referenceFile(&info); + if (file) + { + fixInodeTimestamp(*file, renamedInodeTimestamps, &info); + metaStore->releaseFile(fromParent.getID(), file); + } + } + } + } + + if (overWrittenEntry && !overWrittenEntry->getIsInodeInlined()) + { + EntryInfo overWrittenEntryInfo; + overWrittenEntry->getEntryInfo(fromParent.getID(), 0, &overWrittenEntryInfo); + + if (wasInlined) + { + // if overwritten file previously had an inlined inode which got de-inlined because + // it was IN_USE during rename operation - in this case, its inode should already be + // linked to disposal directory and will be removed upon close() + return renameRes; + } + else if (!hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + // if overwritten file had a non-inlined inode - update hardlink count and remove + // inode and chunk files if link count becomes zero + unlinkRemoteFileInode(&overWrittenEntryInfo); + } + } + + // handle unlinkInode for inlined inode(s) + if (unlinkInode && !hasFlag(NetMessageHeader::Flag_BuddyMirrorSecond)) + { + unlinkedEntryID = unlinkInode->getEntryID(); + + FhgfsOpsErr chunkUnlinkRes = MsgHelperUnlink::unlinkChunkFiles( + unlinkInode.release(), getMsgHeaderUserID() ); + + if (chunkUnlinkRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr(std::string("Rename succeeded, but unlinking storage ") + + "chunk files of the overwritten targetFileName (" + toName + ") failed. " + + "Entry-ID: " + unlinkedEntryID); + + // we can't do anything about it, so we won't even inform the user + } + } + } + + return renameRes; +} + +/** + * Note: This method not only sends the insertion message, but also unlinks an overwritten local + * file if it is contained in the response. + * + * @param serialBuf the inode values serialized into this buffer. + */ +FhgfsOpsErr RenameV2MsgEx::remoteFileInsertAndUnlink(EntryInfo* fromFileInfo, EntryInfo* toDirInfo, + std::string newName, char* serialBuf, size_t serialBufLen, StringVector xattrs, + std::string& unlinkedEntryID) +{ + LogContext log("RenameV2MsgEx::remoteFileInsert"); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + NumNodeID toNodeID = toDirInfo->getOwnerNodeID(); + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, + "Inserting remote parentID: " + toDirInfo->getEntryID() + "; " + "newName: '" + newName + "'; " + "entryID: '" + fromFileInfo->getEntryID() + "'; " + "node: " + toNodeID.str() ); + + // prepare request + + MovingFileInsertMsg insertMsg(fromFileInfo, toDirInfo, newName, serialBuf, serialBufLen); + + RequestResponseArgs rrArgs(NULL, &insertMsg, NETMSGTYPE_MovingFileInsertResp); + + RequestResponseNode rrNode(toNodeID, app->getMetaNodes() ); + rrNode.setTargetStates(app->getMetaStateStore() ); + if (toDirInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + // send request and receive response + + MsgHelperXAttr::StreamXAttrState streamState(*fromFileInfo, std::move(xattrs)); + insertMsg.registerStreamoutHook(rrArgs, streamState); + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + log.log(Log_WARNING, + "Communication with metadata sever failed. nodeID: " + toNodeID.str() ); + return requestRes; + } + + // correct response type received + const auto insertRespMsg = (MovingFileInsertRespMsg*)rrArgs.outRespMsg.get(); + retVal = insertRespMsg->getResult(); + + // handle unlink of chunk files for inlined inode(s) + // handle unlink of inode for non-inlined inode(s) + EntryInfo* overWrittenInfo = insertRespMsg->getOverWrittenEntryInfo(); + if (overWrittenInfo->getEntryType() != DirEntryType_INVALID) + { + if (overWrittenInfo->getIsInlined()) + { + unsigned unlinkedInodeBufLen = insertRespMsg->getInodeBufLen(); + if (unlinkedInodeBufLen) + { + const char* unlinkedInodeBuf = insertRespMsg->getInodeBuf(); + FileInode* toUnlinkInode = new FileInode(); + + Deserializer des(unlinkedInodeBuf, unlinkedInodeBufLen); + toUnlinkInode->deserializeMetaData(des); + if(unlikely(!des.good())) + { // deserialization of received inode failed (should never happen) + log.logErr("Failed to deserialize unlinked file inode. nodeID: " + toNodeID.str() ); + delete(toUnlinkInode); + } + else if (toUnlinkInode->getIsInlined()) + { + MsgHelperUnlink::unlinkChunkFiles( + toUnlinkInode, getMsgHeaderUserID() ); // destructs toUnlinkInode + } + } + } + else + { + unlinkRemoteFileInode(overWrittenInfo); + } + } + + if (retVal != FhgfsOpsErr_SUCCESS) + { + // error: remote file not inserted + LOG_DEBUG_CONTEXT(log, Log_NOTICE, + "Metadata server was unable to insert file. " + "nodeID: " + toNodeID.str() + "; " + "Error: " + boost::lexical_cast(retVal)); + } + else + { + // success: remote file inserted + LOG_DEBUG_CONTEXT(log, Log_DEBUG, "Metadata server inserted file. " + "nodeID: " + toNodeID.str() ); + } + + return retVal; +} + +/** + * Insert a remote directory dir-entry + */ +FhgfsOpsErr RenameV2MsgEx::remoteDirInsert(EntryInfo* toDirInfo, const std::string& newName, + char* serialBuf, size_t serialBufLen) +{ + LogContext log("RenameV2MsgEx::remoteDirInsert"); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + NumNodeID toNodeID = toDirInfo->getOwnerNodeID(); + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, + "Inserting remote parentID: " + toDirInfo->getEntryID() + "; " + "newName: '" + newName + "'; " + "node: " + toNodeID.str() ); + + // prepare request + + MovingDirInsertMsg insertMsg(toDirInfo, newName, serialBuf, serialBufLen); + + RequestResponseArgs rrArgs(NULL, &insertMsg, NETMSGTYPE_MovingDirInsertResp); + + RequestResponseNode rrNode(toNodeID, app->getMetaNodes() ); + rrNode.setTargetStates(app->getMetaStateStore() ); + if (toDirInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + + // send request and receive response + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + log.log(Log_WARNING, "Communication with metadata server failed. nodeID: " + toNodeID.str() ); + return requestRes; + } + + // correct response type received + const auto insertRespMsg = (const MovingDirInsertRespMsg*)rrArgs.outRespMsg.get(); + + retVal = insertRespMsg->getResult(); + if(retVal != FhgfsOpsErr_SUCCESS) + { // error: remote dir not inserted + LOG_DEBUG_CONTEXT(log, Log_NOTICE, + "Metdata server was unable to insert directory. " + "nodeID: " + toNodeID.str() + "; " + "Error: " + boost::lexical_cast(retVal)); + } + else + { + // success: remote dir inserted + LOG_DEBUG_CONTEXT(log, Log_DEBUG, + "Metadata server inserted directory. " + "nodeID: " + toNodeID.str() ); + } + + return retVal; +} + +/** + * Set the new parent information to the inode of renamedDirEntryInfo + */ +FhgfsOpsErr RenameV2MsgEx::updateRenamedDirInode(EntryInfo* renamedDirEntryInfo, + EntryInfo* toDirInfo) +{ + LogContext log("RenameV2MsgEx::updateRenamedDirInode"); + + const std::string& parentEntryID = toDirInfo->getEntryID(); + renamedDirEntryInfo->setParentEntryID(parentEntryID); + + App* app = Program::getApp(); + NumNodeID toNodeID = renamedDirEntryInfo->getOwnerNodeID(); + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, + "Update remote inode: " + renamedDirEntryInfo->getEntryID() + "; " + "node: " + toNodeID.str() ); + + // prepare request + + UpdateDirParentMsg updateMsg(renamedDirEntryInfo, toDirInfo->getOwnerNodeID() ); + + RequestResponseArgs rrArgs(NULL, &updateMsg, NETMSGTYPE_UpdateDirParentResp); + + RequestResponseNode rrNode(toNodeID, app->getMetaNodes() ); + rrNode.setTargetStates(app->getMetaStateStore() ); + if (renamedDirEntryInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + // send request and receive response + + FhgfsOpsErr requestRes = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + log.log(Log_WARNING, + "Communication with metadata server failed. nodeID: " + toNodeID.str() ); + return requestRes; + } + + // correct response type received + const auto updateRespMsg = (const UpdateDirParentRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr retVal = (FhgfsOpsErr)updateRespMsg->getValue(); + if(retVal != FhgfsOpsErr_SUCCESS) + { // error + LOG_DEBUG_CONTEXT(log, Log_NOTICE, + "Failed to update ParentEntryID: " + renamedDirEntryInfo->getEntryID() + "; " + "nodeID: " + toNodeID.str() + "; " + "Error: " + boost::lexical_cast(retVal)); + } + + return retVal; +} + +/* + * Handle unlink for a non-inlined inode + * Decrement hardLink count, if linkCount becomes zero then remove inode and chunk files + */ +FhgfsOpsErr RenameV2MsgEx::unlinkRemoteFileInode(EntryInfo* entryInfo) +{ + const char* logContext = "Unlink remote file inode"; + App* app = Program::getApp(); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + NumNodeID ownerNodeID = entryInfo->getOwnerNodeID(); + + UnlinkLocalFileInodeMsg unlinkInodeMsg(entryInfo); + RequestResponseArgs rrArgs(NULL, &unlinkInodeMsg, NETMSGTYPE_UnlinkLocalFileInodeResp); + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); + + if (entryInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(Program::getApp()->getMetaBuddyGroupMapper(), false); + + do + { + retVal = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + + if (unlikely(retVal != FhgfsOpsErr_SUCCESS)) + { + LogContext(logContext).log(Log_WARNING, + "Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str() + "; " + + "entryID: " + entryInfo->getEntryID().c_str()); + break; + } + + // response received + const auto unlinkFileInodeRespMsg = (UnlinkLocalFileInodeRespMsg*) rrArgs.outRespMsg.get(); + retVal = unlinkFileInodeRespMsg->getResult(); + if (retVal != FhgfsOpsErr_SUCCESS) + { + // error: either inode file doesn't exists or some other error happened + LogContext(logContext).logErr("unlink of inode failed! " + "nodeID: " + ownerNodeID.str() + "; " + + "entryID: " + entryInfo->getEntryID().c_str()); + break; + } + } while (false); + + return retVal; +} + +std::pair RenameV2MsgEx::getLinkCountForMovedEntry(EntryInfo* entryInfo) +{ + const char* logContext = "RenameV2Msg (Get Link Count)"; + + App* app = Program::getApp(); + FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS; + unsigned linkCount = 0; + + NumNodeID ownerNodeID = entryInfo->getOwnerNodeID(); + bool isLocalOwner = ((!isMirrored() && ownerNodeID == app->getLocalNode().getNumID()) || + (isMirrored() && ownerNodeID.val() == app->getMetaBuddyGroupMapper()->getLocalGroupID())); + + if (isLocalOwner) + { + StatData statData; + statRes = MsgHelperStat::stat(entryInfo, true, getMsgHeaderUserID(), statData); + + if (statRes == FhgfsOpsErr_SUCCESS) + linkCount = statData.getNumHardlinks(); + else + LogContext(logContext).logErr("Stat Failed!. entryID: " + entryInfo->getEntryID()); + } + else + { + // send StatMsg to remote meta node/buddygroup + StatMsg statMsg(entryInfo); + RequestResponseArgs rrArgs(NULL, &statMsg, NETMSGTYPE_StatResp); + RequestResponseNode rrNode(ownerNodeID, app->getMetaNodes()); + rrNode.setTargetStates(app->getMetaStateStore()); + + if (entryInfo->getIsBuddyMirrored()) + rrNode.setMirrorInfo(app->getMetaBuddyGroupMapper(), false); + + do + { + FhgfsOpsErr resp = MessagingTk::requestResponseNode(&rrNode, &rrArgs); + if (unlikely(resp != FhgfsOpsErr_SUCCESS)) + { + LogContext(logContext).logErr("Communication with metadata server failed. " + "nodeID: " + ownerNodeID.str()); + statRes = resp; + break; + } + + // response received + const auto statRespMsg = (StatRespMsg*) rrArgs.outRespMsg.get(); + statRes = (FhgfsOpsErr) statRespMsg->getResult(); + if (statRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Stat Failed!. nodeID: " + ownerNodeID.str() + + "; entryID: " + entryInfo->getEntryID()); + break; + } + + // success + linkCount = statRespMsg->getStatData()->getNumHardlinks(); + } while (false); + } + + return {statRes, linkCount}; +} diff --git a/meta/source/net/message/storage/moving/RenameV2MsgEx.h b/meta/source/net/message/storage/moving/RenameV2MsgEx.h new file mode 100644 index 0000000..4d75381 --- /dev/null +++ b/meta/source/net/message/storage/moving/RenameV2MsgEx.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +struct RenameV2Locks +{ + HashDirLock toFileHashLock; + + ParentNameLock fromNameLock; + ParentNameLock toNameLock; + FileIDLock fromDirLock; + FileIDLock toDirLock; + // source file must be locked because concurrent modifications of file attributes may + // race with the moving operation between two servers. + FileIDLock fromFileLockF; + FileIDLock fromFileLockD; + // if target exists, the target file must be unlocked to exclude concurrent operations on + // target (eg close, setxattr, ...) + FileIDLock unlinkedFileLock; + + RenameV2Locks() = default; + + RenameV2Locks(const RenameV2Locks&) = delete; + RenameV2Locks& operator=(const RenameV2Locks&) = delete; + + RenameV2Locks(RenameV2Locks&& other) + { + swap(other); + } + + RenameV2Locks& operator=(RenameV2Locks&& other) + { + RenameV2Locks(std::move(other)).swap(*this); + return *this; + } + + void swap(RenameV2Locks& other) + { + std::swap(toFileHashLock, other.toFileHashLock); + std::swap(fromNameLock, other.fromNameLock); + std::swap(toNameLock, other.toNameLock); + std::swap(fromDirLock, other.fromDirLock); + std::swap(toDirLock, other.toDirLock); + std::swap(fromFileLockF, other.fromFileLockF); + std::swap(fromFileLockD, other.fromFileLockD); + std::swap(unlinkedFileLock, other.unlinkedFileLock); + } +}; + +class RenameV2MsgEx : public MirroredMessage +{ + public: + typedef ErrorCodeResponseState ResponseState; + + virtual bool processIncoming(ResponseContext& ctx) override; + + RenameV2Locks lock(EntryLockStore& store) override; + + bool isMirrored() override { return getFromDirInfo()->getIsBuddyMirrored(); } + + private: + std::unique_ptr executeLocally(ResponseContext& ctx, + bool isSecondary) override; + + FhgfsOpsErr movingPerform(DirInode& fromParent, const std::string& oldName, + DirEntryType entryType, EntryInfo* toDirInfo, const std::string& newName, + std::string& unlinkedEntryID); + + FhgfsOpsErr renameInSameDir(DirInode& fromParent, const std::string& oldName, + const std::string& toName, std::string& unlinkedEntryID); + FhgfsOpsErr renameDir(DirInode& fromParent, const std::string& oldName, + EntryInfo* toDirInfo, const std::string& newName); + FhgfsOpsErr renameFile(DirInode& fromParent, const std::string& oldName, EntryInfo* toDirInfo, + const std::string& newName, std::string& unlinkedEntryID); + + FhgfsOpsErr remoteFileInsertAndUnlink(EntryInfo* fromFileInfo, EntryInfo* toDirInfo, + const std::string newName, char* serialBuf, size_t serialBufLen, + StringVector xattrs, std::string& unlinkedEntryID); + FhgfsOpsErr remoteDirInsert(EntryInfo* toDirInfo, const std::string& newName, + char* serialBuf, size_t serialBufLen); + FhgfsOpsErr updateRenamedDirInode(EntryInfo* renamedDirEntryInfo, EntryInfo* toDirInfo); + FhgfsOpsErr unlinkRemoteFileInode(EntryInfo* entryInfo); + std::pair getLinkCountForMovedEntry(EntryInfo* entryInfo); + + void forwardToSecondary(ResponseContext& ctx) override; + + FhgfsOpsErr processSecondaryResponse(NetMessage& resp) override + { + return (FhgfsOpsErr) static_cast(resp).getValue(); + } + + const char* mirrorLogContext() const override { return "RenameV2MsgEx/forward"; } +}; + + diff --git a/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp b/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp new file mode 100644 index 0000000..9be8599 --- /dev/null +++ b/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp @@ -0,0 +1,65 @@ +#include +#include +#include +#include + +#include "SetExceededQuotaMsgEx.h" + +bool SetExceededQuotaMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("SetExceededQuotaMsgEx incoming"); + + bool retVal = true; + FhgfsOpsErr errorCode = FhgfsOpsErr_SUCCESS; + + if(Program::getApp()->getConfig()->getQuotaEnableEnforcement() ) + { + // get the storage pool for which quota is exceeded + StoragePoolPtr storagePool = + Program::getApp()->getStoragePoolStore()->getPool(getStoragePoolId()); + + if (!storagePool) + { + LOG(QUOTA, WARNING, "Couldn't set exceeded quota, " + "because requested storage pool doesn't exist on metadata server.", + ("storagePoolId", getStoragePoolId())); + + errorCode = FhgfsOpsErr_UNKNOWNPOOL; + + goto send_response; + } + + // set exceeded quota info for all of its targets + UInt16Set targetIds = storagePool->getTargets(); + + for (auto targetId : targetIds) + { + // update exceeded quota + ExceededQuotaStorePtr exQuotaStore = + Program::getApp()->getExceededQuotaStores()->get(targetId); + if (!exQuotaStore) + { + LOG(QUOTA, ERR, "Could not access exceeded quota store.", targetId); + errorCode = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + exQuotaStore->updateExceededQuota(getExceededQuotaIDs(), getQuotaDataType(), + getExceededType()); + } + } + else + { + log.log(Log_ERR, "Unable to set exceeded quota IDs. Configuration problem detected. " + "The management daemon on " + ctx.peerName() + " has quota enforcement enabled, " + "but not this storage daemon. Fix this configuration problem or quota enforcement will " + "not work correctly. If quota enforcement settings have changed recently in the " + "mgmtd configuration, please restart all BeeGFS services."); + + errorCode = FhgfsOpsErr_INTERNAL; + } + +send_response: + ctx.sendResponse(SetExceededQuotaRespMsg(errorCode) ); + + return retVal; +} diff --git a/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.h b/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.h new file mode 100644 index 0000000..5b28600 --- /dev/null +++ b/meta/source/net/message/storage/quota/SetExceededQuotaMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + + +#include +#include + + +class SetExceededQuotaMsgEx : public SetExceededQuotaMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/meta/source/net/msghelpers/MsgHelperClose.cpp b/meta/source/net/msghelpers/MsgHelperClose.cpp new file mode 100644 index 0000000..1bc55f9 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperClose.cpp @@ -0,0 +1,361 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MsgHelperClose.h" + +#include + +/** + * The wrapper for closeSessionFile() and closeChunkFile(). + * + * @param maxUsedNodeIndex zero-based index, -1 means "none" + * @param msgUserID only used for msg header info. + * @param outUnlinkDisposalFile true if the hardlink count of the file was 0 + */ +FhgfsOpsErr MsgHelperClose::closeFile(const NumNodeID sessionID, const std::string& fileHandleID, + EntryInfo* entryInfo, int maxUsedNodeIndex, unsigned msgUserID, bool* outUnlinkDisposalFile, + unsigned* outNumHardlinks, bool& outLastWriterClosed, DynamicFileAttribsVec* dynAttribs, + MirroredTimestamps* timestamps) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + unsigned accessFlags; + unsigned numInodeRefs; + + MetaFileHandle inode; + *outUnlinkDisposalFile = false; + + FhgfsOpsErr sessionRes = closeSessionFile(sessionID, fileHandleID, entryInfo, + &accessFlags, inode); + if(unlikely(sessionRes != FhgfsOpsErr_SUCCESS) ) + return sessionRes; + + FhgfsOpsErr chunksRes = closeChunkFile( + sessionID, fileHandleID, maxUsedNodeIndex, *inode, entryInfo, msgUserID, dynAttribs); + + if (timestamps) + { + StatData sd; + + inode->getStatData(sd); + *timestamps = sd.getMirroredTimestamps(); + } + + metaStore->closeFile(entryInfo, std::move(inode), accessFlags, outNumHardlinks, &numInodeRefs, + outLastWriterClosed); + + if (!*outNumHardlinks && !numInodeRefs) + *outUnlinkDisposalFile = true; + + return chunksRes; +} + +/** + * Close session in SessionStore. + * + * @param outCloseFile caller is responsible for calling MetaStore::closeFile() later if we + * returned success + */ +FhgfsOpsErr MsgHelperClose::closeSessionFile(const NumNodeID sessionID, + const std::string& fileHandleID, EntryInfo* entryInfo, unsigned* outAccessFlags, + MetaFileHandle& outCloseInode) +{ + const char* logContext = "Close Helper (close session file)"; + + FhgfsOpsErr closeRes = FhgfsOpsErr_INTERNAL; + unsigned ownerFD = SessionTk::ownerFDFromHandleID(fileHandleID); + + outCloseInode = {}; + + // find sessionFile + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? Program::getApp()->getMirroredSessions() + : Program::getApp()->getSessions(); + Session* session = sessions->referenceSession(sessionID, true); + SessionFileStore* sessionFiles = session->getFiles(); + SessionFile* sessionFile = sessionFiles->referenceSession(ownerFD); + + if(!sessionFile) + { // sessionFile not exists + // note: nevertheless, we try to forward the close to the storage servers, + // because the meta-server just might have been restarted (for whatever reason). + // so we open the file here (if possible) and let the caller go on as if nothing was wrong... + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + LogContext(logContext).log(Log_DEBUG, std::string("File not open ") + + "(session: " + sessionID.str() + "; " + "handle: " + StringTk::uintToStr(ownerFD) + "; " + "parentID: " + entryInfo->getParentEntryID() + "; " + "ID: " + entryInfo->getEntryID() + ")" ); + + *outAccessFlags = OPENFILE_ACCESS_READWRITE; + + bool bypassAccessCheck = false; // Enforce regular file access restrictions + closeRes = metaStore->openFile(entryInfo, *outAccessFlags, bypassAccessCheck, outCloseInode); + } + else + { // sessionFile exists + + // save access flags and file for later + outCloseInode = sessionFile->releaseInode(); + *outAccessFlags = sessionFile->getAccessFlags(); + + sessionFiles->releaseSession(sessionFile, entryInfo); + + if(!sessionFiles->removeSession(ownerFD) ) + { // removal failed + LogContext(logContext).log(Log_WARNING, "Unable to remove file session " + "(still in use, marked for async cleanup now). " + "SessionID: " + sessionID.str() + "; " + "FileHandle: " + std::string(fileHandleID) ); + } + else + { // file session removed => caller can close file + closeRes = FhgfsOpsErr_SUCCESS; + } + + } + + sessions->releaseSession(session); + + return closeRes; +} + +/** + * Close chunk files on storage servers. + * + * Note: This method is also called by the hbMgr during client sync. + * + * @param msgUserID only for msg header info. + */ +FhgfsOpsErr MsgHelperClose::closeChunkFile(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo *entryInfo, + unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) +{ + if(maxUsedNodeIndex == -1) + return FhgfsOpsErr_SUCCESS; // file contents were not accessed => nothing to do + else + if( (maxUsedNodeIndex > 0) || + (inode.getStripePattern()->getPatternType() == StripePatternType_BuddyMirror) ) + return closeChunkFileParallel( + sessionID, fileHandleID, maxUsedNodeIndex, inode, entryInfo, msgUserID, dynAttribs); + else + return closeChunkFileSequential( + sessionID, fileHandleID, maxUsedNodeIndex, inode, entryInfo, msgUserID, dynAttribs); +} + +/** + * Note: This method does not work for mirrored files; use closeChunkFileParallel() for those. + * + * @param maxUsedNodeIndex (zero-based position in nodeID vector) + * @param msgUserID only for msg header info. + */ +FhgfsOpsErr MsgHelperClose::closeChunkFileSequential(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo *entryInfo, + unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) +{ + const char* logContext = "Close Helper (close chunk files S)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + TargetStateStore* targetStates = Program::getApp()->getTargetStateStore(); + NodeStore* nodes = Program::getApp()->getStorageNodes(); + StripePattern* pattern = inode.getStripePattern(); + PathInfo pathInfo; + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); + + inode.getPathInfo(&pathInfo); + + // send request to each node and receive the response message + int currentTargetIndex = 0; + for(UInt16VectorConstIter iter = targetIDs->begin(); + (currentTargetIndex <= maxUsedNodeIndex) && (iter != targetIDs->end() ); + iter++, currentTargetIndex++) + { + uint16_t targetID = *iter; + + CloseChunkFileMsg closeMsg(sessionID, fileHandleID, targetID, &pathInfo); + + closeMsg.setMsgHeaderUserID(msgUserID); + + RequestResponseArgs rrArgs(NULL, &closeMsg, NETMSGTYPE_CloseChunkFileResp); + RequestResponseTarget rrTarget(targetID, targetMapper, nodes); + + rrTarget.setTargetStates(targetStates); + + // send request to node and receive response + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; " + "FileHandle: " + fileHandleID + "; " + "Error: " + boost::lexical_cast(requestRes)); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = requestRes; + + continue; + } + + // correct response type received + CloseChunkFileRespMsg* closeRespMsg = (CloseChunkFileRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr closeRemoteRes = closeRespMsg->getResult(); + + // set current dynamic attribs (even if result not success, because then storageVersion==0) + DynamicFileAttribs currentDynAttribs(closeRespMsg->getStorageVersion(), + closeRespMsg->getFileSize(), closeRespMsg->getAllocedBlocks(), + closeRespMsg->getModificationTimeSecs(), closeRespMsg->getLastAccessTimeSecs() ); + + dynAttribsVec[currentTargetIndex] = currentDynAttribs; + + if(unlikely(closeRemoteRes != FhgfsOpsErr_SUCCESS) ) + { // error: chunk file close problem + int logLevel = Log_WARNING; + + if(closeRemoteRes == FhgfsOpsErr_INUSE) + logLevel = Log_DEBUG; // happens on ctrl+c, so don't irritate user with these log msgs + + LogContext(logContext).log(logLevel, + "Storage target was unable to close chunk file: " + StringTk::uintToStr(targetID) + "; " + "Error: " + boost::lexical_cast(closeRemoteRes) + "; " + "Session: " + sessionID.str() + "; " + "FileHandle: " + fileHandleID); + + if(closeRemoteRes == FhgfsOpsErr_INUSE) + continue; // don't escalate this error to client (happens on ctrl+c) + + retVal = closeRemoteRes; + continue; + } + + // success: chunk file closed + LOG_DEBUG(logContext, Log_DEBUG, + "Storage target closed chunk file: " + StringTk::uintToStr(targetID) + "; " + "FileHandle: " + fileHandleID); + } + + inode.setDynAttribs(dynAttribsVec); // the actual update + if (dynAttribs) + dynAttribs->swap(dynAttribsVec); + + if(unlikely(retVal != FhgfsOpsErr_SUCCESS) ) + LogContext(logContext).log(Log_WARNING, + "Problems occurred during close of chunk files. " + "FileHandle: " + fileHandleID); + + return retVal; +} + + +/** + * @param maxUsedNodeIndex (zero-based position in nodeID vector) + * @param msgUserID only for msg header info. + */ +FhgfsOpsErr MsgHelperClose::closeChunkFileParallel(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, EntryInfo* entryInfo, + unsigned msgUserID, DynamicFileAttribsVec* dynAttribs) +{ + const char* logContext = "Close Helper (close chunk files)"; + + App* app = Program::getApp(); + MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + PathInfo pathInfo; + + size_t numTargetWorksHint = (maxUsedNodeIndex < 0) ? 0 : (maxUsedNodeIndex+1); + size_t numTargetWorks = BEEGFS_MIN(numTargetWorksHint, targetIDs->size() ); + + DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + FhgfsOpsErrVec nodeResults(numTargetWorks); + SynchronizedCounter counter; + + inode.getPathInfo(&pathInfo); + + // generate work for storage targets... + for(size_t i=0; i < numTargetWorks; i++) + { + CloseChunkFileWork* work = new CloseChunkFileWork(sessionID, fileHandleID, pattern, + (*targetIDs)[i], &pathInfo, &(dynAttribsVec[i]), &(nodeResults[i]), &counter); + + work->setMsgUserID(msgUserID); + + slaveQ->addDirectWork(work); + } + + // wait for work completion... + + counter.waitForCount(numTargetWorks); + + // check target results... + + for(size_t i=0; i < numTargetWorks; i++) + { + if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) ) + { + if(nodeResults[i] == FhgfsOpsErr_INUSE) + continue; // don't escalate this error to client (happens on ctrl+c) + + LogContext(logContext).log(Log_WARNING, + "Problems occurred during release of storage server file handles. " + "FileHandle: " + std::string(fileHandleID) ); + + retVal = nodeResults[i]; + goto apply_dyn_attribs; + } + } + + +apply_dyn_attribs: + + inode.setDynAttribs(dynAttribsVec); // the actual update + if (dynAttribs) + dynAttribs->swap(dynAttribsVec); + + return retVal; +} + + +/** + * Unlink file in META_DISPOSALDIR_ID_STR/ + * + * @param msgUserID only for msg header info. + */ +FhgfsOpsErr MsgHelperClose::unlinkDisposableFile(const std::string& fileID, unsigned msgUserID, + bool isBuddyMirrored) +{ + if (isBuddyMirrored && 0 < Program::getApp()->getConfig()->getTuneDisposalGCPeriod()) + return FhgfsOpsErr_SUCCESS; + + // Note: This attempt to unlink directly is inefficient if the file is marked as disposable + // and is still busy (but we assume that this rarely happens) + + DirInode* dir = Program::getApp()->getMetaStore()->referenceDir( + isBuddyMirrored ? META_MIRRORDISPOSALDIR_ID_STR : META_DISPOSALDIR_ID_STR, + isBuddyMirrored, true); + if (!dir) + return FhgfsOpsErr_INTERNAL; + + FhgfsOpsErr disposeRes = MsgHelperUnlink::unlinkFile(*dir, fileID, msgUserID); + if (disposeRes == FhgfsOpsErr_PATHNOTEXISTS) + disposeRes = FhgfsOpsErr_SUCCESS; // file not marked for disposal => not an error + + Program::getApp()->getMetaStore()->releaseDir(dir->getID()); + + return disposeRes; +} diff --git a/meta/source/net/msghelpers/MsgHelperClose.h b/meta/source/net/msghelpers/MsgHelperClose.h new file mode 100644 index 0000000..87d6ed1 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperClose.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +class MsgHelperClose +{ + public: + static FhgfsOpsErr closeFile(const NumNodeID sessionID, const std::string& fileHandleID, + EntryInfo* entryInfo, int maxUsedNodeIndex, unsigned msgUserID, + bool* outUnlinkDisposalFile, unsigned* outNumHardlinks, bool& outLastWriterClosed, + DynamicFileAttribsVec* dynAttribs = NULL, MirroredTimestamps* timestamps = NULL); + static FhgfsOpsErr closeSessionFile(const NumNodeID sessionID, const std::string& fileHandleID, + EntryInfo* entryInfo, unsigned* outAccessFlags, MetaFileHandle& outCloseInode); + static FhgfsOpsErr closeChunkFile(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, + EntryInfo *entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs = NULL); + static FhgfsOpsErr unlinkDisposableFile(const std::string& fileID, unsigned msgUserID, + bool isBuddyMirrored); + + private: + MsgHelperClose() + { + } + + static FhgfsOpsErr closeChunkFileSequential(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, + EntryInfo *entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs); + static FhgfsOpsErr closeChunkFileParallel(const NumNodeID sessionID, + const std::string& fileHandleID, int maxUsedNodeIndex, FileInode& inode, + EntryInfo* entryInfo, unsigned msgUserID, DynamicFileAttribsVec* dynAttribs); + + public: + // inliners +}; + diff --git a/meta/source/net/msghelpers/MsgHelperLocking.cpp b/meta/source/net/msghelpers/MsgHelperLocking.cpp new file mode 100644 index 0000000..6f885cc --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperLocking.cpp @@ -0,0 +1,168 @@ +#include +#include "MsgHelperLocking.h" + +#include + + +/** + * Try to recover a file session that got lost (e.g. due to a mds restart) and was implicitly + * reported to exist by a client, e.g. during a flock request. The session will be inserted into + * the store. + * + * Note: We re-open the file in r+w mode here, because we don't know the orig mode. + * + * @param outSessionFile will be set to referenced (recovered) session if success is returned, + * NULL otherwise + */ +FhgfsOpsErr MsgHelperLocking::trySesssionRecovery(EntryInfo* entryInfo, NumNodeID clientID, + unsigned ownerFD, SessionFileStore* sessionFiles, SessionFile** outSessionFile) +{ + const char* logContext = "MsgHelperLocking (try session recovery)"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + LogContext(logContext).log(Log_WARNING, std::string("Attempting recovery of file session ") + + "(session: " + clientID.str() + "; " + "handle: " + StringTk::uintToStr(ownerFD) + "; " + "parentID: " + entryInfo->getParentEntryID() + "; " + "entryID: " + entryInfo->getEntryID() + ")" ); + + *outSessionFile = NULL; + + MetaFileHandle recoveryFile; + unsigned recoveryAccessFlags = OPENFILE_ACCESS_READWRITE; /* (r+w is our only option, since we + don't know the original flags) */ + + bool bypassAccessCheck = false; // Enforce regular file access restrictions + FhgfsOpsErr openRes = metaStore->openFile(entryInfo, recoveryAccessFlags, + bypassAccessCheck, recoveryFile); + if(openRes != FhgfsOpsErr_SUCCESS) + { // file could not be opened => there's nothing we can do in this case + LogContext(logContext).log(Log_WARNING, std::string("Recovery of file session failed: ") + + boost::lexical_cast(openRes)); + + return openRes; + } + else + { // file opened => try to insert the recovery file session + SessionFile* recoverySessionFile = new SessionFile(std::move(recoveryFile), + recoveryAccessFlags, entryInfo); + recoverySessionFile->setSessionID(ownerFD); + + *outSessionFile = sessionFiles->addAndReferenceRecoverySession(recoverySessionFile); + + if(!*outSessionFile) + { // bad, our recovery session ID was used by someone in the meantime => cleanup + unsigned numHardlinks; // ignored here + unsigned numInodeRefs; // ignored here + bool lastWriterClosed; // ignored here + + LogContext(logContext).log(Log_WARNING, + "Recovery of file session failed: SessionID is in use by another file now."); + + metaStore->closeFile(entryInfo, recoverySessionFile->releaseInode(), recoveryAccessFlags, + &numHardlinks, &numInodeRefs, lastWriterClosed); + + delete(recoverySessionFile); + + return FhgfsOpsErr_INTERNAL; + } + else + { // recovery succeeded + LogContext(logContext).log(Log_NOTICE, "File session recovered."); + } + } + + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Note: This method also tries session recovery for lock requests. + */ +FhgfsOpsErr MsgHelperLocking::flockAppend(EntryInfo* entryInfo, unsigned ownerFD, + EntryLockDetails& lockDetails) +{ + App* app = Program::getApp(); + SessionStore* sessions = entryInfo->getIsBuddyMirrored() + ? app->getMirroredSessions() + : app->getSessions(); + MetaStore* metaStore = app->getMetaStore(); + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + + // find sessionFile + Session* session = sessions->referenceSession(lockDetails.getClientNumID(), true); + SessionFileStore* sessionFiles = session->getFiles(); + + SessionFile* sessionFile = sessionFiles->referenceSession(ownerFD); + if(!sessionFile) + { // sessionFile not exists (mds restarted?) + + // check if this is just an UNLOCK REQUEST + + if(lockDetails.isUnlock() ) + { // it's an unlock => we'll just ignore it (since the locks are gone anyways) + retVal = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // check if this is a LOCK CANCEL REQUEST + + if(lockDetails.isCancel() ) + { // it's a lock cancel + /* this is an important special case, because client might have succeeded in closing the + file but the conn might have been interrupted during unlock, so we definitely have to try + canceling the lock here */ + + // if the file still exists, just do the lock cancel without session recovery attempt + + auto [lockCancelFile, referenceRes] = metaStore->referenceFile(entryInfo); + if(lockCancelFile) + { + lockCancelFile->flockAppend(lockDetails); + metaStore->releaseFile(entryInfo->getParentEntryID(), lockCancelFile); + } + + retVal = FhgfsOpsErr_SUCCESS; + + goto cleanup_session; + } + + // it's a LOCK REQUEST => try to recover session file to do the locking + + retVal = MsgHelperLocking::trySesssionRecovery(entryInfo, lockDetails.getClientNumID(), + ownerFD, sessionFiles, &sessionFile); + + // (note: sessionFile==NULL now if recovery was not successful) + + } // end of session file session recovery attempt + + if(sessionFile) + { // sessionFile exists (or was successfully recovered) + auto& file = sessionFile->getInode(); + auto lockGranted = file->flockAppend(lockDetails); + if (!lockGranted.first) + retVal = FhgfsOpsErr_WOULDBLOCK; + else + retVal = FhgfsOpsErr_SUCCESS; + + if (!lockGranted.second.empty()) + LockingNotifier::notifyWaitersEntryLock(LockEntryNotifyType_APPEND, + file->getReferenceParentID(), file->getEntryID(), file->getIsBuddyMirrored(), + std::move(lockGranted.second)); + + // cleanup + sessionFiles->releaseSession(sessionFile, entryInfo); + } + + + // cleanup +cleanup_session: + sessions->releaseSession(session); + + return retVal; +} diff --git a/meta/source/net/msghelpers/MsgHelperLocking.h b/meta/source/net/msghelpers/MsgHelperLocking.h new file mode 100644 index 0000000..bb18ba0 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperLocking.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + + +/** + * Common helpers for locking related messages. + */ +class MsgHelperLocking +{ + public: + static FhgfsOpsErr trySesssionRecovery(EntryInfo* entryInfo, NumNodeID clientID, + unsigned ownerFD, SessionFileStore* sessionFiles, SessionFile** outSessionFile); + + static FhgfsOpsErr flockAppend(EntryInfo* entryInfo, unsigned ownerFD, + EntryLockDetails& lockDetails); + + + private: + MsgHelperLocking() {} + +}; + + diff --git a/meta/source/net/msghelpers/MsgHelperMkFile.cpp b/meta/source/net/msghelpers/MsgHelperMkFile.cpp new file mode 100644 index 0000000..e9994aa --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperMkFile.cpp @@ -0,0 +1,53 @@ +#include +#include +#include +#include "MsgHelperMkFile.h" + +/* + * @param stripePattern can be NULL, in which case a new pattern gets created; should only be set + * if this is the secondary buddy of a mirror group + */ +FhgfsOpsErr MsgHelperMkFile::mkFile(DirInode& parentDir, MkFileDetails* mkDetails, + const UInt16List* preferredTargets, const unsigned numtargets, const unsigned chunksize, + StripePattern* stripePattern, RemoteStorageTarget* rstInfo, EntryInfo* outEntryInfo, + FileInodeStoreData* outInodeData, StoragePoolId storagePoolId) +{ + const char* logContext = "MsgHelperMkFile (create file)"; + + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + ModificationEventFlusher* modEventFlusher = app->getModificationEventFlusher(); + bool modEventLoggingEnabled = modEventFlusher->isLoggingEnabled(); + + FhgfsOpsErr retVal; + + // create new stripe pattern + if ( !stripePattern ) + stripePattern = parentDir.createFileStripePattern(preferredTargets, numtargets, chunksize, + storagePoolId); + + // check availability of stripe targets + if(unlikely(!stripePattern || stripePattern->getStripeTargetIDs()->empty() ) ) + { + LogContext(logContext).logErr( + "Unable to create stripe pattern. No storage targets available? " + "File: " + mkDetails->newName); + + SAFE_DELETE(stripePattern); + return FhgfsOpsErr_INTERNAL; + } + + // create meta file + retVal = metaStore->mkNewMetaFile(parentDir, mkDetails, + std::unique_ptr(stripePattern), rstInfo, outEntryInfo, outInodeData); + + if ( (modEventLoggingEnabled ) && ( outEntryInfo ) ) + { + std::string entryID = outEntryInfo->getEntryID(); + modEventFlusher->add(ModificationEvent_FILECREATED, entryID); + } + + return retVal; +} + + diff --git a/meta/source/net/msghelpers/MsgHelperMkFile.h b/meta/source/net/msghelpers/MsgHelperMkFile.h new file mode 100644 index 0000000..3b38e19 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperMkFile.h @@ -0,0 +1,28 @@ +#pragma once + + +#include +#include +#include +#include + + +struct MkFileDetails; // forward declaration + +/** + * Default class to create meta-data files (including inodes and directories). + */ +class MsgHelperMkFile +{ + public: + static FhgfsOpsErr mkFile(DirInode& parentDir, MkFileDetails* mkDetails, + const UInt16List* preferredTargets, const unsigned numtargets, const unsigned chunksize, + StripePattern* stripePattern, RemoteStorageTarget* rstInfo, EntryInfo* outEntryInfo, + FileInodeStoreData* outInodeData, StoragePoolId storagePoolId = StoragePoolStore::INVALID_POOL_ID); + + private: + MsgHelperMkFile() {} + +}; + + diff --git a/meta/source/net/msghelpers/MsgHelperOpen.cpp b/meta/source/net/msghelpers/MsgHelperOpen.cpp new file mode 100644 index 0000000..c0d8116 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperOpen.cpp @@ -0,0 +1,97 @@ +#include +#include +#include +#include +#include +#include +#include +#include "MsgHelperOpen.h" + +/** + * Note: This only does the basic open; you probably still want to create the session for this + * opened file afterwards. + * Note: Also performs truncation based on accessFlags if necessary. + * + * @param msgUserID only used for msg header info. + * @param outOpenFile only set if return indicates success. + */ +FhgfsOpsErr MsgHelperOpen::openFile(EntryInfo* entryInfo, unsigned accessFlags, + bool useQuota, bool bypassAccessCheck, unsigned msgUserID, MetaFileHandle& outFileInode, + bool isSecondary) +{ + const char* logContext = "Open File Helper"; + IGNORE_UNUSED_VARIABLE(logContext); + + bool truncLocalRequired = false; + + if(accessFlags & OPENFILE_ACCESS_TRUNC) + truncLocalRequired = MsgHelperTrunc::isTruncChunkRequired(entryInfo, 0); + + FhgfsOpsErr openRes = openMetaFile(entryInfo, accessFlags, bypassAccessCheck, outFileInode); + + + if(openRes != FhgfsOpsErr_SUCCESS) + return openRes; + + /* check chunkSize for compatibility. + this check was introduced in 2011.05-r6, when we switched from arbitrary chunk sizes to the + new chunk size constraints (min size and power of two). the check can be removed in a future + release, when we are sure that there are no old installations with arbitrary chunk sizes + left. */ + unsigned chunkSize = outFileInode->getStripePattern()->getChunkSize(); + if(unlikely( (chunkSize < STRIPEPATTERN_MIN_CHUNKSIZE) || + !MathTk::isPowerOfTwo(chunkSize) ) ) + { // validity check failed => don't open this file (we would risk corrupting it otherwise) + LogContext(logContext).logErr("This version of BeeGFS is not compatible with this " + "chunk size: " + StringTk::uintToStr(chunkSize) + ". " + "Refusing to open file. " + "parentInfo: " + entryInfo->getParentEntryID() + " " + "entryInfo: " + entryInfo->getEntryID() ); + + openMetaFileCompensate(entryInfo, std::move(outFileInode), accessFlags); + return FhgfsOpsErr_INTERNAL; + } + + if(truncLocalRequired && !isSecondary) + { // trunc was specified and is needed => do it + LOG_DEBUG(logContext, Log_DEBUG, std::string("Opening with trunc local") ); + + DynamicFileAttribsVec dynAttribs; + FhgfsOpsErr truncRes = MsgHelperTrunc::truncChunkFile( + *outFileInode, entryInfo, 0, useQuota, msgUserID, dynAttribs); + + if(unlikely(truncRes != FhgfsOpsErr_SUCCESS) ) + { // error => undo open() + openMetaFileCompensate(entryInfo, std::move(outFileInode), accessFlags); + openRes = truncRes; + } + } + + return openRes; +} + +FhgfsOpsErr MsgHelperOpen::openMetaFile(EntryInfo* entryInfo, unsigned accessFlags, + bool bypassAccessCheck, MetaFileHandle& outOpenInode) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr openRes = metaStore->openFile(entryInfo, accessFlags, bypassAccessCheck, outOpenInode); + + return openRes; +} + +/** + * Undo an open. + * (Typically called when an error occurred after a successful open). + */ +void MsgHelperOpen::openMetaFileCompensate(EntryInfo* entryInfo, + MetaFileHandle inode, unsigned accessFlags) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + unsigned numHardlinks; // ignored here + unsigned numInodeRefs; // ignored here + bool lastWriterClosed; // ignored here + + metaStore->closeFile(entryInfo, std::move(inode), accessFlags, &numHardlinks, &numInodeRefs, + lastWriterClosed); +} diff --git a/meta/source/net/msghelpers/MsgHelperOpen.h b/meta/source/net/msghelpers/MsgHelperOpen.h new file mode 100644 index 0000000..97b33dc --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperOpen.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + + +class MsgHelperOpen +{ + public: + static FhgfsOpsErr openFile(EntryInfo* entryInfo, unsigned accessFlags, + bool useQuota, bool bypassAccessCheck, unsigned msgUserID, MetaFileHandle& outFileInode, + bool isSecondary); + + + private: + MsgHelperOpen() {} + + static FhgfsOpsErr openMetaFile(EntryInfo* entryInfo, + unsigned accessFlags, bool bypassAccessCheck, MetaFileHandle& outOpenInode); + static void openMetaFileCompensate(EntryInfo* entryInfo, + MetaFileHandle inode, unsigned accessFlags); + +}; + diff --git a/meta/source/net/msghelpers/MsgHelperStat.cpp b/meta/source/net/msghelpers/MsgHelperStat.cpp new file mode 100644 index 0000000..dcc8d03 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperStat.cpp @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include +#include +#include "MsgHelperStat.h" + + +/** + * Note: This will automatically refresh dynamic attribs if they are outdated. + * + * @param loadFromDisk do we need to load the data from disk or do we want to have data from + * an already opened inode only + * @param msgUserID will only be used in msg header info. + * @param outParentNodeID may be NULL (default) if the caller is not interested + * @param outParentEntryID may NULL (if outParentNodeID is NULL) + */ +FhgfsOpsErr MsgHelperStat::stat(EntryInfo* entryInfo, bool loadFromDisk, unsigned msgUserID, + StatData& outStatData, NumNodeID* outParentNodeID, std::string* outParentEntryID) +{ + const char* logContext = "Stat Helper (stat entry)"; + + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr retVal; + + retVal = metaStore->stat(entryInfo, loadFromDisk, outStatData, outParentNodeID, + outParentEntryID); + + if(retVal == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED) + { // dynamic attribs outdated => get fresh dynamic attribs from storage servers and stat again + MsgHelperStat::refreshDynAttribs(entryInfo, false, msgUserID); + + //note: if we are here it is regular file and we don't need to request parentData + + retVal = metaStore->stat(entryInfo, loadFromDisk, outStatData); + + // this time we ignore outdated dynamic attribs because refreshing them again would + // be useless (or we could keep on doing it forever) + if(retVal == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED) + retVal = FhgfsOpsErr_SUCCESS; + } + else + if(retVal == FhgfsOpsErr_PATHNOTEXISTS && loadFromDisk) + { /* metadata not found: it is hard to tell whether this is an error (e.g. metadata was never + created) or just a normal case (e.g. someone removed a file during an "ls -l") */ + + LogContext(logContext).log(Log_DEBUG, "Missing metadata for entryID: " + + entryInfo->getEntryID() + ". " + "(Possibly a valid race of two processes or a cached entry that is now being " + "checked by a client revalidate() method.)"); + } + + return retVal; +} + + +/** + * Refresh current file size and other dynamic attribs from storage servers. + * + * @makePersistent whether or not this method should also update persistent metadata. + * @param msgUserID only used for msg header info. + */ +FhgfsOpsErr MsgHelperStat::refreshDynAttribs(EntryInfo* entryInfo, bool makePersistent, + unsigned msgUserID) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + FhgfsOpsErr retVal; + + std::string parentEntryID = entryInfo->getParentEntryID(); + std::string entryID = entryInfo->getEntryID(); + + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if(!inode) + { + std::string logContext("Stat Helper (refresh filesize: parentID: " + parentEntryID + + " entryID: " + entryID + ")"); + LogContext(logContext).log(Log_DEBUG, std::string("File could not be referenced") ); + + return referenceRes; + } + + if(inode->getStripePattern()->getAssignedNumTargets() == 1) + retVal = refreshDynAttribsSequential(*inode, entryID, msgUserID); + else + retVal = refreshDynAttribsParallel(*inode, entryID, msgUserID); + + if( (retVal == FhgfsOpsErr_SUCCESS) && makePersistent) + { + bool persistenceRes = inode->updateInodeOnDisk(entryInfo); + if(!persistenceRes) + retVal = FhgfsOpsErr_INTERNAL; + } + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + return retVal; +} + +/** + * Note: This method does support getting attrs from buddymirrors, but only from group's primary. + * + * @param msgUserID only used for msg header info. + */ +FhgfsOpsErr MsgHelperStat::refreshDynAttribsSequential(FileInode& inode, const std::string& entryID, + unsigned msgUserID) +{ + const char* logContext = "Stat Helper (refresh chunk files S)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; // will be set to node error, if any + + App* app = Program::getApp(); + + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + // send request to each node and receive the response message + unsigned currentStripeNodeIndex = 0; + for(UInt16VectorConstIter iter = targetIDs->begin(); + iter != targetIDs->end(); + iter++, currentStripeNodeIndex++) + { + uint16_t targetID = *iter; + + // prepare request message + + GetChunkFileAttribsMsg getSizeMsg(entryID, *iter, &pathInfo); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + getSizeMsg.addMsgHeaderFeatureFlag(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR); + + getSizeMsg.setMsgHeaderUserID(msgUserID); + + // prepare communication + + RequestResponseTarget rrTarget(targetID, app->getTargetMapper(), app->getStorageNodes() ); + + rrTarget.setTargetStates(app->getTargetStateStore() ); + + if(pattern->getPatternType() == StripePatternType_BuddyMirror) + rrTarget.setMirrorInfo(app->getStorageBuddyGroupMapper(), false); + + RequestResponseArgs rrArgs(NULL, &getSizeMsg, NETMSGTYPE_GetChunkFileAttribsResp); + + // communicate + + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(unlikely(requestRes != FhgfsOpsErr_SUCCESS) ) + { // communication error + LogContext(logContext).log(Log_WARNING, + std::string("Communication with storage target failed. ") + + (pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + retVal = requestRes; + continue; + } + + // correct response type received + auto* getSizeRespMsg = (GetChunkFileAttribsRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr getSizeResult = getSizeRespMsg->getResult(); + if(getSizeResult != FhgfsOpsErr_SUCCESS) + { // error: got no fresh attributes + LogContext(logContext).log(Log_WARNING, + std::string("Getting fresh chunk file attributes from target failed. ") + + (pattern->getPatternType() == StripePatternType_BuddyMirror ? "Mirror " : "") + + "TargetID: " + StringTk::uintToStr(targetID) + "; " + "EntryID: " + entryID); + + retVal = getSizeResult; + continue; + } + + // success: got fresh chunk file attributes + //log.log(3, std::string("Got fresh filesize from node: ") + nodeID); + + DynamicFileAttribs currentDynAttribs(getSizeRespMsg->getStorageVersion(), + getSizeRespMsg->getSize(), getSizeRespMsg->getAllocedBlocks(), + getSizeRespMsg->getModificationTimeSecs(), getSizeRespMsg->getLastAccessTimeSecs() ); + + dynAttribsVec[currentStripeNodeIndex] = currentDynAttribs; + } + + + inode.setDynAttribs(dynAttribsVec); // the actual update + + if(retVal != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during chunk file attributes refresh. " + "EntryID: " + entryID); + } + + return retVal; +} + +/** + * Note: For buddymirrored files, only group's primary is used. + */ +FhgfsOpsErr MsgHelperStat::refreshDynAttribsParallel(FileInode& inode, const std::string& entryID, + unsigned msgUserID) +{ + const char* logContext = "Stat Helper (refresh chunk files)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; // will be set to node error, if any + + App* app = Program::getApp(); + MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + DynamicFileAttribsVec dynAttribsVec(targetIDs->size() ); + + size_t numWorks = targetIDs->size(); + + FhgfsOpsErrVec nodeResults(numWorks); + SynchronizedCounter counter; + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + for(size_t i=0; i < numWorks; i++) + { + GetChunkFileAttribsWork* work = new GetChunkFileAttribsWork(entryID, pattern, (*targetIDs)[i], + &pathInfo, &(dynAttribsVec[i]), &(nodeResults[i]), &counter); + + work->setMsgUserID(msgUserID); + + slaveQ->addDirectWork(work); + } + + counter.waitForCount(numWorks); + + for(size_t i=0; i < numWorks; i++) + { + if(nodeResults[i] != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during file attribs refresh. entryID: " + entryID); + + retVal = nodeResults[i]; + break; + } + } + + inode.setDynAttribs(dynAttribsVec); // the actual update + + return retVal; +} diff --git a/meta/source/net/msghelpers/MsgHelperStat.h b/meta/source/net/msghelpers/MsgHelperStat.h new file mode 100644 index 0000000..a3b7717 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperStat.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + + +class MsgHelperStat +{ + public: + static FhgfsOpsErr stat(EntryInfo* entryInfo, bool loadFromDisk, unsigned msgUserID, + StatData& outStatData, NumNodeID* outParentNodeID = NULL, + std::string* outParentEntryID = NULL); + static FhgfsOpsErr refreshDynAttribs(EntryInfo* entryInfo, bool makePersistent, + unsigned msgUserID); + + + private: + MsgHelperStat() {} + + static FhgfsOpsErr refreshDynAttribsSequential(FileInode& inode, const std::string& entryID, + unsigned msgUserID); + static FhgfsOpsErr refreshDynAttribsParallel(FileInode& inode, const std::string& entryID, + unsigned msgUserID); + + + public: + // inliners +}; + diff --git a/meta/source/net/msghelpers/MsgHelperTrunc.cpp b/meta/source/net/msghelpers/MsgHelperTrunc.cpp new file mode 100644 index 0000000..c9ed1ad --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperTrunc.cpp @@ -0,0 +1,279 @@ +#include +#include +#include +#include +#include "MsgHelperTrunc.h" + +#include + + +/** + * Note: Will also update persistent metadata on disk. + * + * @param msgUserID will only be used in msg header info. + */ +FhgfsOpsErr MsgHelperTrunc::truncFile(EntryInfo* entryInfo, int64_t filesize, bool useQuota, + unsigned msgUserID, DynamicFileAttribsVec& dynAttribs) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if(!inode) + return referenceRes; + + FhgfsOpsErr localRes = truncChunkFile(*inode, entryInfo, filesize, useQuota, msgUserID, + dynAttribs); + + inode->updateInodeOnDisk(entryInfo); + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + return localRes; +} + +/** + * Note: This also updates dynamic file attribs. + * Note: Call this directly (instead of truncFile() ) if you already got the file handle. + * Note: Will NOT update persistent metadata on disk. + * + * @param msgUserID only used for msg header info. + */ +FhgfsOpsErr MsgHelperTrunc::truncChunkFile(FileInode& inode, EntryInfo* entryInfo, + int64_t filesize, bool useQuota, unsigned userIDHint, DynamicFileAttribsVec& dynAttribs) +{ + StripePattern* pattern = inode.getStripePattern(); + + if( (pattern->getStripeTargetIDs()->size() > 1) || + (pattern->getPatternType() == StripePatternType_BuddyMirror) ) + return truncChunkFileParallel(inode, entryInfo, filesize, useQuota, userIDHint, dynAttribs); + else + return truncChunkFileSequential(inode, entryInfo, filesize, useQuota, userIDHint, dynAttribs); +} + +/** + * Note: This method does not work for mirrored files; use truncChunkFileParallel() for those. + * Note: This also updates dynamic file attribs. + * + * @param msgUserID only used for msg header info. + */ +FhgfsOpsErr MsgHelperTrunc::truncChunkFileSequential(FileInode& inode, EntryInfo* entryInfo, + int64_t filesize, bool useQuota, unsigned msgUserID, DynamicFileAttribsVec& dynAttribs) +{ + const char* logContext = "Trunc chunk file helper S"; + + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + TargetStateStore* targetStates = Program::getApp()->getTargetStateStore(); + NodeStore* nodes = Program::getApp()->getStorageNodes(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + dynAttribs.resize(targetIDs->size()); + + // send request to each node and receive the response message + unsigned currentNodeIndex = 0; + for(UInt16VectorConstIter iter = targetIDs->begin(); + iter != targetIDs->end(); + iter++, currentNodeIndex++) + { + uint16_t targetID = *iter; + + std::string entryID = entryInfo->getEntryID(); + int64_t truncPos = getNodeLocalTruncPos(filesize, *pattern, currentNodeIndex); + TruncLocalFileMsg truncMsg(truncPos, entryID, targetID, &pathInfo); + + if (useQuota) + truncMsg.setUserdataForQuota(inode.getUserID(), inode.getGroupID()); + + truncMsg.setMsgHeaderUserID(msgUserID); + + RequestResponseArgs rrArgs(NULL, &truncMsg, NETMSGTYPE_TruncLocalFileResp); + RequestResponseTarget rrTarget(targetID, targetMapper, nodes); + + rrTarget.setTargetStates(targetStates); + + // send request to node and receive response + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication error + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = requestRes; + + continue; + } + + // correct response type received + TruncLocalFileRespMsg* truncRespMsg = (TruncLocalFileRespMsg*)rrArgs.outRespMsg.get(); + + // set current dynamic attribs (even if result not success, because then storageVersion==0) + DynamicFileAttribs currentDynAttribs(truncRespMsg->getStorageVersion(), + truncRespMsg->getFileSize(), truncRespMsg->getAllocedBlocks(), + truncRespMsg->getModificationTimeSecs(), truncRespMsg->getLastAccessTimeSecs() ); + + dynAttribs[currentNodeIndex] = currentDynAttribs; + + FhgfsOpsErr chunkTruncRes = truncRespMsg->getResult(); + if(chunkTruncRes != FhgfsOpsErr_SUCCESS) + { // error: chunk file not truncated + LogContext(logContext).log(Log_WARNING, + "Storage target failed to truncate chunk file: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID() + "; " + "Error: " + boost::lexical_cast(chunkTruncRes)); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = truncRespMsg->getResult(); + + continue; + } + + // success: local inode unlinked + LOG_DEBUG(logContext, Log_DEBUG, + "Storage target truncated chunk file: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + } + + + inode.setDynAttribs(dynAttribs); // the actual update + + if(unlikely( (retVal != FhgfsOpsErr_SUCCESS) && (retVal != FhgfsOpsErr_TOOBIG) ) ) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during truncation of chunk files. " + "fileID: " + inode.getEntryID()); + } + + return retVal; +} + +/** + * Note: This also updates dynamic file attribs. + * + * @param msgUserID only used for msg header info. + */ +FhgfsOpsErr MsgHelperTrunc::truncChunkFileParallel(FileInode& inode, EntryInfo* entryInfo, + int64_t filesize, bool useQuota, unsigned msgUserID, DynamicFileAttribsVec& dynAttribs) +{ + const char* logContext = "Trunc chunk file helper"; + + App* app = Program::getApp(); + MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + size_t numTargetWorks = targetIDs->size(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + FhgfsOpsErrVec nodeResults(numTargetWorks); + SynchronizedCounter counter; + + // generate work for storage targets... + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + dynAttribs.resize(numTargetWorks); + + for(size_t i=0; i < numTargetWorks; i++) + { + int64_t localTruncPos = getNodeLocalTruncPos(filesize, *pattern, i); + + TruncChunkFileWork* work = new TruncChunkFileWork(inode.getEntryID(), localTruncPos, + pattern, (*targetIDs)[i], &pathInfo, &dynAttribs[i], &(nodeResults[i]), &counter); + + if (useQuota) + work->setUserdataForQuota(inode.getUserID(), inode.getGroupID()); + + work->setMsgUserID(msgUserID); + + slaveQ->addDirectWork(work); + } + + // wait for work completion... + + counter.waitForCount(numTargetWorks); + + // check target results... + + for(size_t i=0; i < numTargetWorks; i++) + { + FhgfsOpsErr nodeResult = nodeResults[i]; + if(unlikely(nodeResult != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_WARNING, + "Problems occurred during truncation of storage server chunk files. " + "File: " + inode.getEntryID()); + + retVal = nodeResult; + break; + } + } + + inode.setDynAttribs(dynAttribs); // the actual update + + return retVal; +} + +/** + * Note: Makes only sense to call this before opening the file (because afterwards the filesize + * will be unknown). + * + * @param filesize desired trunc filesize + * @return true if current filesize is unknown or not equal to given filesize + */ +bool MsgHelperTrunc::isTruncChunkRequired(EntryInfo* entryInfo, int64_t filesize) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + StatData statData; + + FhgfsOpsErr statRes = metaStore->stat(entryInfo, true, statData); + + if( (statRes == FhgfsOpsErr_PATHNOTEXISTS) && (!filesize) ) + return false; // does not exist => no zero-trunc necessary + + if( (statRes == FhgfsOpsErr_SUCCESS) && (filesize == statData.getFileSize() ) ) + return false; // exists and already has desired size + + return true; +} + + +int64_t MsgHelperTrunc::getNodeLocalOffset(int64_t pos, int64_t chunkSize, size_t numNodes, + size_t stripeNodeIndex) +{ + // (note: we can use "& ... -1" here instead if "%", because chunkSize is a po wer of two + int64_t posModChunkSize = pos & (chunkSize - 1); + int64_t chunkStart = pos - posModChunkSize - (stripeNodeIndex*chunkSize); + + return ( (chunkStart / numNodes) + posModChunkSize); +} + +int64_t MsgHelperTrunc::getNodeLocalTruncPos(int64_t pos, StripePattern& pattern, + size_t stripeNodeIndex) +{ + int64_t truncPos; + size_t numTargets = pattern.getStripeTargetIDs()->size(); + + size_t mainNodeIndex = pattern.getStripeTargetIndex(pos); + int64_t mainNodeLocalOffset = getNodeLocalOffset( + pos, pattern.getChunkSize(), numTargets, mainNodeIndex); + + if(stripeNodeIndex < mainNodeIndex) + truncPos = pattern.getNextChunkStart(mainNodeLocalOffset); + else + if(stripeNodeIndex == mainNodeIndex) + truncPos = mainNodeLocalOffset; + else + truncPos = pattern.getChunkStart(mainNodeLocalOffset); + + truncPos = BEEGFS_MAX(truncPos, 0); + + return truncPos; +} diff --git a/meta/source/net/msghelpers/MsgHelperTrunc.h b/meta/source/net/msghelpers/MsgHelperTrunc.h new file mode 100644 index 0000000..e56438c --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperTrunc.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + + +class MsgHelperTrunc +{ + public: + static FhgfsOpsErr truncFile(EntryInfo* entryInfo, int64_t filesize, + bool useQuota, unsigned msgUserID, DynamicFileAttribsVec& dynAttribs); + static FhgfsOpsErr truncChunkFile(FileInode& inode, EntryInfo* entryInfo, int64_t filesize, + bool useQuota, unsigned userIDHint, DynamicFileAttribsVec& dynAttribs); + + static bool isTruncChunkRequired(EntryInfo* entryInfo, int64_t filesize); + + private: + MsgHelperTrunc() {} + + static FhgfsOpsErr truncChunkFileSequential(FileInode& inode, EntryInfo* entryInfo, + int64_t filesize, bool useQuota, unsigned msgUserID, DynamicFileAttribsVec& dynAttribs); + static FhgfsOpsErr truncChunkFileParallel(FileInode& inode, EntryInfo* entryInfo, + int64_t filesize, bool useQuota, unsigned msgUserID, DynamicFileAttribsVec& dynAttribs); + static int64_t getNodeLocalOffset(int64_t pos, int64_t chunkSize, size_t numNodes, + size_t stripeNodeIndex); + static int64_t getNodeLocalTruncPos(int64_t pos, StripePattern& pattern, + size_t stripeNodeIndex); + + + public: + // inliners +}; + diff --git a/meta/source/net/msghelpers/MsgHelperUnlink.cpp b/meta/source/net/msghelpers/MsgHelperUnlink.cpp new file mode 100644 index 0000000..d49276d --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperUnlink.cpp @@ -0,0 +1,287 @@ +#include +#include +#include +#include +#include +#include +#include +#include "MsgHelperUnlink.h" + + +/** + * Wrapper for unlinkMetaFile() and unlinkChunkFiles(). + * + * @param msgUserID only used in msg header info. + */ +FhgfsOpsErr MsgHelperUnlink::unlinkFile(DirInode& parentDir, const std::string& removeName, + unsigned msgUserID) +{ + std::unique_ptr unlinkedInode; + unsigned numHardlinks; // Not used here! + + FhgfsOpsErr unlinkMetaRes = unlinkMetaFile(parentDir, removeName, &unlinkedInode, numHardlinks); + + /* note: if the file is still opened or if there are/were hardlinks then unlinkedInode will be + NULL even on FhgfsOpsErr_SUCCESS */ + if (unlinkMetaRes == FhgfsOpsErr_SUCCESS && unlinkedInode) + unlinkMetaRes = unlinkChunkFiles(unlinkedInode.release(), msgUserID); + + return unlinkMetaRes; +} + +/** + * Unlink file in metadata store. + * + * @param outInitialHardlinkCount will be set to the initial hardlink count of the file + * inode before unlinking. + * + * @return if this returns success and outUnlinkedFile is set, then the caller also needs to unlink + * the chunk files via unlinkChunkFiles(). + */ +FhgfsOpsErr MsgHelperUnlink::unlinkMetaFile(DirInode& parentDir, + const std::string& removeName, std::unique_ptr* outUnlinkedFile, + unsigned& outInitialHardlinkCount) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + ModificationEventFlusher* modEventFlusher = Program::getApp()->getModificationEventFlusher(); + bool modEventLoggingEnabled = modEventFlusher->isLoggingEnabled(); + + EntryInfo entryInfo; + + FhgfsOpsErr unlinkMetaRes = metaStore->unlinkFile(parentDir, removeName, + &entryInfo, outUnlinkedFile, outInitialHardlinkCount); + + if (modEventLoggingEnabled) + { + std::string entryID = entryInfo.getEntryID(); + modEventFlusher->add(ModificationEvent_FILEREMOVED, entryID); + } + + return unlinkMetaRes; +} + +/** + * Decrement hardlink count and + * Unlink file's inode if hardlink count reaches zero + * + * @return Success if hardlink count decrement is successful + * and if it became zero then fileinode removal is also sucessful (outUnlinkInode will be + * set in this case which will be later used to remove chunk files). If file is in use and + * its last entry getting removed then inode will be linked with disposal directory for later + * removal + * + */ +FhgfsOpsErr MsgHelperUnlink::unlinkFileInode(EntryInfo* delFileInfo, + std::unique_ptr* outUnlinkedFile, unsigned& outInitialHardlinkCount) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + FhgfsOpsErr unlinkRes = metaStore->unlinkFileInode(delFileInfo, outUnlinkedFile, + outInitialHardlinkCount); + return unlinkRes; +} + +/** + * Unlink (storage) chunk files. + * + * Note: If chunk files unlink fails, this method will create a disposal entry. + * + * @param unlinkedInode will be deleted inside this method or owned by another object, so caller + * may no longer access it after calling this. + * @param msgUserID only used in msg header info. + */ +FhgfsOpsErr MsgHelperUnlink::unlinkChunkFiles(FileInode* unlinkedInode, unsigned msgUserID) +{ + const char* logContext = "Delete chunk files"; + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + FhgfsOpsErr retVal; + + retVal = unlinkChunkFilesInternal(*unlinkedInode, msgUserID); + if(retVal != FhgfsOpsErr_SUCCESS) + { /* Failed to unlink storage chunk files => add file to the disposable store to try + * again later. */ + + LogContext(logContext).logErr("Failed to delete all chunk files of ID: " + + unlinkedInode->getEntryID() + ". Added disposal entry."); + + retVal = metaStore->insertDisposableFile(unlinkedInode); // destructs unlinkedInode + } + else + { // success (local files unlinked) + delete(unlinkedInode); + } + + return retVal; +} + +/** + * Wrapper to decide parallel or sequential chunks unlink. + * + * @param msgUserID only used in msg header info. + */ +FhgfsOpsErr MsgHelperUnlink::unlinkChunkFilesInternal(FileInode& file, unsigned msgUserID) +{ + StripePattern* pattern = file.getStripePattern(); + + if( (pattern->getStripeTargetIDs()->size() > 1) || + (pattern->getPatternType() == StripePatternType_BuddyMirror) ) + return unlinkChunkFileParallel(file, msgUserID); + else + return unlinkChunkFileSequential(file, msgUserID); +} + +/** + * Note: This method does not work for mirrored files; use unlinkChunkFileParallel() for those. + * + * @param msgUserID only used in msg header info. + */ +FhgfsOpsErr MsgHelperUnlink::unlinkChunkFileSequential(FileInode& inode, unsigned msgUserID) +{ + std::string logContext("Unlink Helper (unlink chunk file S [" + inode.getEntryID() + "])"); + + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + TargetStateStore* targetStates = Program::getApp()->getTargetStateStore(); + NodeStore* nodes = Program::getApp()->getStorageNodes(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + std::string fileID(inode.getEntryID()); + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + // send request to each node and receive the response message + for(UInt16VectorConstIter iter = targetIDs->begin(); + iter != targetIDs->end(); + iter++) + { + uint16_t targetID = *iter; + + UnlinkLocalFileMsg unlinkMsg(fileID, targetID, &pathInfo); + + unlinkMsg.setMsgHeaderUserID(msgUserID); + + RequestResponseArgs rrArgs(NULL, &unlinkMsg, NETMSGTYPE_UnlinkLocalFileResp); + RequestResponseTarget rrTarget(targetID, targetMapper, nodes); + + rrTarget.setTargetStates(targetStates); + + // send request to node and receive response + FhgfsOpsErr requestRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + if(requestRes != FhgfsOpsErr_SUCCESS) + { // communication failed + + if( (requestRes == FhgfsOpsErr_UNKNOWNNODE) || + (requestRes == FhgfsOpsErr_UNKNOWNTARGET) ) + { /* special case: for unlink, we don't treat this as error to allow easy deletion of + files after intentional target removal. */ + LogContext(logContext).log(Log_WARNING, + "Unable to resolve storage node targetID: " + StringTk::uintToStr(targetID) ); + continue; + } + + LogContext(logContext).log(Log_WARNING, + "Communication with storage target failed: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = requestRes; + + continue; + } + + // correct response type received + UnlinkLocalFileRespMsg* unlinkRespMsg = (UnlinkLocalFileRespMsg*)rrArgs.outRespMsg.get(); + + FhgfsOpsErr unlinkResult = unlinkRespMsg->getResult(); + if(unlinkResult != FhgfsOpsErr_SUCCESS) + { // error: local inode not unlinked + LogContext(logContext).log(Log_WARNING, + "Storage target failed to unlink chunk file: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + + if(retVal == FhgfsOpsErr_SUCCESS) + retVal = unlinkResult; + + continue; + } + + // success: local inode unlinked + LOG_DEBUG(logContext, Log_DEBUG, + "Storage targed unlinked chunk file: " + StringTk::uintToStr(targetID) + "; " + "fileID: " + inode.getEntryID()); + } + + if(unlikely(retVal != FhgfsOpsErr_SUCCESS) ) + LogContext(logContext).log(Log_WARNING, + "Problems occurred during unlinking of the chunk files. " + "fileID: " + inode.getEntryID()); + + + return retVal; +} + +/** + * @param msgUserID only used in msg header info. + */ +FhgfsOpsErr MsgHelperUnlink::unlinkChunkFileParallel(FileInode& inode, unsigned msgUserID) +{ + std::string logContext("Unlink Helper (unlink chunk file [" + inode.getEntryID() + "])"); + + App* app = Program::getApp(); + MultiWorkQueue* slaveQ = app->getCommSlaveQueue(); + StripePattern* pattern = inode.getStripePattern(); + const UInt16Vector* targetIDs = pattern->getStripeTargetIDs(); + + size_t numTargetWorks = targetIDs->size(); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + FhgfsOpsErrVec nodeResults(numTargetWorks); + SynchronizedCounter counter; + + PathInfo pathInfo; + inode.getPathInfo(&pathInfo); + + // generate work for storage targets... + + for(size_t i=0; i < numTargetWorks; i++) + { + UnlinkChunkFileWork* work = new UnlinkChunkFileWork(inode.getEntryID(), pattern, + (*targetIDs)[i], &pathInfo, &(nodeResults[i]), &counter); + + work->setMsgUserID(msgUserID); + + slaveQ->addDirectWork(work); + } + + // wait for work completion... + + counter.waitForCount(numTargetWorks); + + // check target results... + + for(size_t i=0; i < numTargetWorks; i++) + { + if(unlikely(nodeResults[i] != FhgfsOpsErr_SUCCESS) ) + { + if( (nodeResults[i] == FhgfsOpsErr_UNKNOWNNODE) || + (nodeResults[i] == FhgfsOpsErr_UNKNOWNTARGET) ) + { /* we don't return this as an error to the user, because the node/target was probably + removed intentionally (and either way the rest of this inode is lost now) */ + continue; + } + + LogContext(logContext).log(Log_WARNING, + "Problems occurred during unlinking of chunk files."); + + retVal = nodeResults[i]; + goto error_exit; + } + } + + +error_exit: + return retVal; +} diff --git a/meta/source/net/msghelpers/MsgHelperUnlink.h b/meta/source/net/msghelpers/MsgHelperUnlink.h new file mode 100644 index 0000000..434716d --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperUnlink.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + + +class MsgHelperUnlink +{ + public: + static FhgfsOpsErr unlinkFile(DirInode& parentDir, const std::string& removeName, + unsigned msgUserID); + static FhgfsOpsErr unlinkMetaFile(DirInode& parentDir, const std::string& removeName, + std::unique_ptr* outUnlinkedFile, unsigned& outInitialHardlinkCount); + static FhgfsOpsErr unlinkFileInode(EntryInfo* delFileInfo, + std::unique_ptr* outUnlinkedFile, unsigned& outInitialHardlinkCount); + static FhgfsOpsErr unlinkChunkFiles(FileInode* unlinkedInode, unsigned msgUserID); + + private: + MsgHelperUnlink() {} + + static FhgfsOpsErr unlinkChunkFileSequential(FileInode& inode, unsigned msgUserID); + static FhgfsOpsErr unlinkChunkFileParallel(FileInode& inode, unsigned msgUserID); + static FhgfsOpsErr insertDisposableFile(FileInode& file); + + + public: + // inliners + + static FhgfsOpsErr unlinkChunkFilesInternal(FileInode& file, unsigned msgUserID); +}; + diff --git a/meta/source/net/msghelpers/MsgHelperXAttr.cpp b/meta/source/net/msghelpers/MsgHelperXAttr.cpp new file mode 100644 index 0000000..2a835e0 --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperXAttr.cpp @@ -0,0 +1,259 @@ +#include +#include +#include +#include +#include "MsgHelperXAttr.h" + +#include + +const std::string MsgHelperXAttr::CURRENT_DIR_FILENAME = std::string("."); +const ssize_t MsgHelperXAttr::MAX_VALUE_SIZE = 60*1024; + +std::pair MsgHelperXAttr::listxattr(EntryInfo* entryInfo) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (entryInfo->getEntryType() == DirEntryType_REGULARFILE && !entryInfo->getIsInlined()) + { + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (!inode) + return {referenceRes, {}}; + + auto result = inode->listXAttr(); + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + return result; + } + + DirInode* dir = metaStore->referenceDir( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? entryInfo->getEntryID() + : entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), + true); + + if (!dir) + return {FhgfsOpsErr_INTERNAL, {}}; + + auto result = dir->listXAttr( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? nullptr + : entryInfo); + + metaStore->releaseDir(dir->getID()); + + return result; +} + + +std::tuple, ssize_t> MsgHelperXAttr::getxattr(EntryInfo* entryInfo, + const std::string& name, size_t maxSize) +{ + std::tuple, ssize_t> result; + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (entryInfo->getEntryType() == DirEntryType_REGULARFILE && !entryInfo->getIsInlined()) + { + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (!inode) + return std::make_tuple(referenceRes, std::vector(), ssize_t(0)); + + result = inode->getXAttr(name, maxSize); + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + } + else + { + DirInode* dir = metaStore->referenceDir( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? entryInfo->getEntryID() + : entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), + true); + + if (!dir) + return std::make_tuple(FhgfsOpsErr_INTERNAL, std::vector(), ssize_t(0)); + + result = dir->getXAttr( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? nullptr + : entryInfo, + name, + maxSize); + + metaStore->releaseDir(dir->getID()); + } + + // Attribute might be too large for NetMessage. + if (std::get<1>(result).size() > size_t(MsgHelperXAttr::MAX_VALUE_SIZE)) + { + // Note: This can happen if it was set with an older version of the client which did not + // include the size check. + return std::make_tuple(FhgfsOpsErr_INTERNAL, std::vector(), ssize_t(0)); + } + + return result; +} + +FhgfsOpsErr MsgHelperXAttr::removexattr(EntryInfo* entryInfo, const std::string& name) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (entryInfo->getEntryType() == DirEntryType_REGULARFILE && !entryInfo->getIsInlined()) + { + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (!inode) + return referenceRes; + + auto result = inode->removeXAttr(entryInfo, name); + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + return result; + } + + DirInode* dir = metaStore->referenceDir( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? entryInfo->getEntryID() + : entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), + true); + + if (!dir) + return FhgfsOpsErr_INTERNAL; + + auto result = dir->removeXAttr( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? nullptr + : entryInfo, + name); + + metaStore->releaseDir(dir->getID()); + + return result; +} + +FhgfsOpsErr MsgHelperXAttr::setxattr(EntryInfo* entryInfo, const std::string& name, + const CharVector& value, int flags) +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + if (entryInfo->getEntryType() == DirEntryType_REGULARFILE && !entryInfo->getIsInlined()) + { + auto [inode, referenceRes] = metaStore->referenceFile(entryInfo); + if (!inode) + return referenceRes; + + auto result = inode->setXAttr(entryInfo, name, value, flags); + + metaStore->releaseFile(entryInfo->getParentEntryID(), inode); + + return result; + } + + DirInode* dir = metaStore->referenceDir( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? entryInfo->getEntryID() + : entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), + true); + + if (!dir) + return FhgfsOpsErr_INTERNAL; + + auto result = dir->setXAttr( + DirEntryType_ISDIR(entryInfo->getEntryType()) + ? nullptr + : entryInfo, + name, + value, + flags); + + metaStore->releaseDir(dir->getID()); + + return result; +} + +FhgfsOpsErr MsgHelperXAttr::StreamXAttrState::streamXattrFn(Socket* socket, void* context) +{ + StreamXAttrState* state = static_cast(context); + + return state->streamXattr(socket); +} + +FhgfsOpsErr MsgHelperXAttr::StreamXAttrState::streamXattr(Socket* socket) const +{ + for (auto xattr = names.cbegin(); xattr != names.cend(); ++xattr) + { + const auto& name = *xattr; + + CharVector value; + FhgfsOpsErr getRes; + + if (entryInfo) + std::tie(getRes, value, std::ignore) = getxattr(entryInfo, name, XATTR_SIZE_MAX); + else + std::tie(getRes, value, std::ignore) = XAttrTk::getUserXAttr(path, name, XATTR_SIZE_MAX); + + if (getRes != FhgfsOpsErr_SUCCESS) + { + uint32_t endMark = HOST_TO_LE_32(-1); + socket->send(&endMark, sizeof(endMark), 0); + return getRes; + } + + uint32_t nameLen = HOST_TO_LE_32(name.size()); + socket->send(&nameLen, sizeof(nameLen), 0); + socket->send(&name[0], nameLen, 0); + + uint64_t valueLen = HOST_TO_LE_64(value.size()); + socket->send(&valueLen, sizeof(valueLen), 0); + socket->send(&value[0], value.size(), 0); + } + + uint32_t endMark = HOST_TO_LE_32(0); + socket->send(&endMark, sizeof(endMark), 0); + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr MsgHelperXAttr::StreamXAttrState::readNextXAttr(Socket* socket, std::string& name, + CharVector& value) +{ + uint32_t nameLen; + Config* cfg = Program::getApp()->getConfig(); + + ssize_t nameLenRes = socket->recvExactT(&nameLen, sizeof(nameLen), 0, cfg->getConnMsgShortTimeout()); + if (nameLenRes < 0 || size_t(nameLenRes) < sizeof(nameLen)) + return FhgfsOpsErr_COMMUNICATION; + + nameLen = LE_TO_HOST_32(nameLen); + if (nameLen == 0) + return FhgfsOpsErr_SUCCESS; + else if (nameLen == uint32_t(-1)) + return FhgfsOpsErr_COMMUNICATION; + + if (nameLen > XATTR_NAME_MAX) + return FhgfsOpsErr_RANGE; + + name.resize(nameLen); + if (socket->recvExactT(&name[0], nameLen, 0, cfg->getConnMsgShortTimeout()) != (ssize_t) name.size()) + return FhgfsOpsErr_COMMUNICATION; + + uint64_t valueLen; + ssize_t valueLenRes = socket->recvExactT(&valueLen, sizeof(valueLen), 0, + cfg->getConnMsgShortTimeout()); + if (valueLenRes < 0 || size_t(valueLenRes) != sizeof(valueLen)) + return FhgfsOpsErr_COMMUNICATION; + + valueLen = LE_TO_HOST_64(valueLen); + if (valueLen > XATTR_SIZE_MAX) + return FhgfsOpsErr_RANGE; + + value.resize(valueLen); + if (socket->recvExactT(&value[0], valueLen, 0, cfg->getConnMsgShortTimeout()) != ssize_t(valueLen)) + return FhgfsOpsErr_COMMUNICATION; + + return FhgfsOpsErr_AGAIN; +} diff --git a/meta/source/net/msghelpers/MsgHelperXAttr.h b/meta/source/net/msghelpers/MsgHelperXAttr.h new file mode 100644 index 0000000..a4341ae --- /dev/null +++ b/meta/source/net/msghelpers/MsgHelperXAttr.h @@ -0,0 +1,48 @@ +#pragma once + +#include + +class EntryInfo; // forward declaration +class Socket; + +class MsgHelperXAttr +{ + public: + static std::pair listxattr(EntryInfo* entryInfo); + static std::tuple, ssize_t> getxattr(EntryInfo* entryInfo, + const std::string& name, size_t maxSize); + static FhgfsOpsErr removexattr(EntryInfo* entryInfo, const std::string& name); + static FhgfsOpsErr setxattr(EntryInfo* entryInfo, const std::string& name, + const CharVector& value, int flags); + + static const std::string CURRENT_DIR_FILENAME; + static const ssize_t MAX_VALUE_SIZE; + + class StreamXAttrState + { + public: + StreamXAttrState(): + entryInfo(nullptr) + {} + + StreamXAttrState(EntryInfo& entryInfo, StringVector names): + entryInfo(&entryInfo), names(std::move(names)) + {} + + StreamXAttrState(std::string path, StringVector names): + entryInfo(nullptr), path(std::move(path)), names(std::move(names)) + {} + + static FhgfsOpsErr streamXattrFn(Socket* socket, void* context); + + static FhgfsOpsErr readNextXAttr(Socket* socket, std::string& name, CharVector& value); + + private: + EntryInfo* entryInfo; + std::string path; + StringVector names; + + FhgfsOpsErr streamXattr(Socket* socket) const; + }; +}; + diff --git a/meta/source/nodes/MetaNodeOpStats.h b/meta/source/nodes/MetaNodeOpStats.h new file mode 100644 index 0000000..55e204b --- /dev/null +++ b/meta/source/nodes/MetaNodeOpStats.h @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +/** + * Count filesystem metadata operations + */ +class MetaNodeOpStats : public NodeOpStats +{ + public: + + /** + * Update operation counter for the given nodeIP and userID. + * + * @param nodeIP IP of the node + * @param opType the filesystem operation to count + */ + void updateNodeOp(unsigned nodeIP, MetaOpCounterTypes opType, unsigned userID) + { + SafeRWLock safeLock(&lock, SafeRWLock_READ); + + NodeOpCounterMapIter nodeIter = clientCounterMap.find(nodeIP); + NodeOpCounterMapIter userIter = userCounterMap.find(userID); + + if( (nodeIter == clientCounterMap.end() ) || + (userIter == userCounterMap.end() ) ) + { // nodeIP or userID NOT found in map yet, we need a write lock + + safeLock.unlock(); // possible race, but insert below is a NOOP if key already exists + safeLock.lock(SafeRWLock_WRITE); + + if(nodeIter == clientCounterMap.end() ) + { + nodeIter = clientCounterMap.insert( + NodeOpCounterMapVal(nodeIP, MetaOpCounter() ) ).first; + } + + if(userIter == userCounterMap.end() ) + { + userIter = userCounterMap.insert( + NodeOpCounterMapVal(userID, MetaOpCounter() ) ).first; + } + } + + nodeIter->second.increaseOpCounter(opType); + userIter->second.increaseOpCounter(opType); + + safeLock.unlock(); + } +}; + + diff --git a/meta/source/pmq/pmq.cpp b/meta/source/pmq/pmq.cpp new file mode 100644 index 0000000..7650f55 --- /dev/null +++ b/meta/source/pmq/pmq.cpp @@ -0,0 +1,2500 @@ +#include "pmq_common.hpp" +#include "pmq.hpp" + +static constexpr uint64_t PMQ_SLOT_SIZE = 128; +static constexpr uint64_t PMQ_SLOT_HEADER_SIZE = 16; +static constexpr uint64_t PMQ_SLOT_SPACE = PMQ_SLOT_SIZE - PMQ_SLOT_HEADER_SIZE; + +// A bit somewhere in the slot header that indicates that a given slot is the +// first slot of a sequence of slots that hold a message. +static constexpr uint64_t PMQ_SLOT_LEADER_MASK = 1; + +static constexpr uint64_t PMQ_CHUNK_SHIFT = 16; +static constexpr uint64_t PMQ_CHUNK_SIZE = (uint64_t) 1 << PMQ_CHUNK_SHIFT; + + +class CSN_Tag{}; +class SSN_Tag{}; +class MSN_Tag{}; + +using CSN = SN; +using SSN = SN; +using MSN = SN; + +struct PMQ_Chunk_Hdr +{ + // msn: msn of first message stored in this chunk. + // msgoffsets_off: offset in the chunk to an array of (msgcount + 1) offsets. + MSN msn; + uint16_t msgcount; + uint16_t msgoffsets_off; + uint32_t pad; // pad to 16 bytes for now +}; + +struct PMQ_Slot +{ + uint32_t flags; + uint32_t msgsize; + uint32_t pad0; + uint32_t pad1; + char payload[PMQ_SLOT_SPACE]; + + __pmq_artificial_method + Untyped_Slice payload_untyped_slice() + { + return Untyped_Slice(payload, sizeof payload); + } +}; + +/* In-memory enqueue buffer. This is where incoming message get written first. + * It consists of a ringbuffer of fixed-size slots. + * A slot has a header and a payload. The size of each slot is PMQ_SLOT_SIZE, + * and the payload can be up to up to PMQ_SLOT_SPACE bytes. + * In the future, we might support even concurrent writes. + * This structure needs no locking; its contents are static except when + * initialization and destroying. + * Accessing the cursors though needs a mutex lock. + */ +struct In_Queue +{ + uint64_t slot_count = 0; + uint64_t size_bytes = 0; // slot_count * PMQ_SLOT_SIZE + + // When reaching this fill level (in slots) we persist unconditionally. + uint64_t slots_persist_watermark = 0; + + // A shared memory file store that survives application restarts (but not + // OS restarts). + Posix_FD shm_fd; + + // Memory mapping of @shm_fd. + MMap_Region mapping; + + Ringbuffer slots; // the buffer from the mapping. +}; + +/* Cursors published by the enqueuer. The ssn_* members index into the + * In_Queue. They are consumed by the persister and by reader threads. + * They get written by enqueuer threads only. + * + * NOTE: The *_disk cursors are treated as "tail" cursors i.e. mark the end of + * the valid region of the queue. They are a copy from the persister's cursors. + * They get copied only from time to time as necessary if the In_Queue ran out + * of space to store new messages: We have ssn_disk <= ssn_mem and msn_disk <= + * msn. The cursors indicate how far the persister has come in persisting + * chunks by compacting messages (stored in slots) from the In_Queue. + */ +struct In_Queue_Cursors +{ + MSN msn; + SSN ssn_mem; + MSN msn_disk; + SSN ssn_disk; +}; + +/* An in-memory representation for a chunk page that is about to be written to + * disk. + * Contains an untyped buffer and minimal book-keeping information. + */ +struct Chunk_Buffer +{ + void *data; + + // Tracking msn and ssn of first message so we can know how many pages + // to write() or fsync(). + MSN msn; + SSN ssn; + + // last_msn and last_ssn: These fields indicate the one-past-last msn and + // ssn. They get set only when the chunk buffer gets finalized. + // The purpose is to allow the persister thread to update the persister + // cursors after persisting the chunk buffer. + // Because msn's and ssn's are continuous (i.e. last_msn is equal to the + // next buffer's msn field), these data fields may seem redundant -- the + // persister could use the msn and ssn from the following chunk buffer + // instead. + // However, that approach has in the past caused a bug where the persister + // used the cursors from the following buffer before that buffer was even + // initialized -- effectively decreasing the cursors to an earlier state + // instead of advancing them. + // After finding the bug, it was clear that we should introduce a little bit + // of redundancy in order to keep things simple: the persister will access + // only finalized buffers, and those will always have last_msn and last_ssn + // fields set correctly. + + MSN last_msn; + SSN last_ssn; + + __pmq_artificial_method + Untyped_Slice untyped_slice() const + { + return Untyped_Slice(data, PMQ_CHUNK_SIZE); + } + + __pmq_artificial_method + PMQ_Chunk_Hdr *get_chunk_header() const + { + return (PMQ_Chunk_Hdr *) data; + } +}; + +/* A queue of in-memory chunks. + * We might write() only one chunk at a time, but we need to hold each chunk in + * memory until it is fsynced, so we need a bunch of chunk buffers. + * */ +struct Chunk_Queue +{ + // The only purpose of this is to be a "holder" for the data buffers + // contained in the "chunks" Chunk_Buffer's. + // It's currently a single contiguous mmap allocation, i.e. (PMQ_CHUNK_SIZE + // * chunks.slot_count()) + MMap_Region chunk_buffer_mapping; + + // The only purpose of this is to be a "holder" for the Chunk_Buffer + // structures (the "chunks" Ringbuffer is non-owning). + Alloc_Slice chunks_alloc_slice; + + Ringbuffer chunks; + + /* CSN, MSN, SSN of the "current" next message that will be compacted. */ + MSN cq_msn; + CSN cq_csn; + SSN cq_ssn; + + // Construction data for currently built chunk + + // buffer of current chunk (current chunk is identified Persist_Cursors::cq_csn) + Chunk_Buffer *ck_buffer; + + uint64_t msg_count; // number of messages compacted in current chunk + + /* Array of message offsets. msg_count + 1 elements are always valid. The + * next message will be appended (if it fits) at an offset of + * offsets[msg_count] bytes in the current chunks page. When the chunk is + * finalized, the position of the array is set. + */ + Alloc_Slice offsets; +}; + +/* Persistent chunk store -- storing chunks on the file system. + */ +struct Chunk_Store +{ + Posix_FD chunk_fd; + + uint64_t capacity_bytes = 0; + + // After persisting to the chunk store, when at least high_watermark many + // chunks are filled, we may discard some to lower the chunk fill count to + // low_watermark. + // Discarding may also be required in the middle of persisting when all + // chunks are full. But normally this shouldn't happen because the + // In_Queue's capacity should be smaller than (chunk_count - + // high_watermark) chunks. + // Since discarding chunks involves an fsync() (really a write barrrier + // would be enough but in practice we only have fsync() currently), + // we discard many chunks at once to hide the overhead coming from disk + // latency. + uint64_t chunks_high_watermark = 0; + uint64_t chunks_low_watermark = 0; + + __pmq_artificial_method + uint64_t chunk_count() const + { + return capacity_bytes >> PMQ_CHUNK_SHIFT; + } +}; + +/* Cursors published by the persister. They index into the chunk store. They + * are consumed by message readers. Sometimes enqueuer threads read these + * cursors as well, to be able to skip persisting slots from the In_Queue. + */ +struct Persist_Cursors +{ + // The wal_ssn member indicates the tentative next slot to be written to the + // In_Queue's persist file. + // Most slots never end up going to that persist file but are compacted + // directly to the chunk store. At each sync to disk, only the slots that + // can't form a complete chunk buffer go to the In_Queue's persist file. In + // that file, only the slots from cks_ssn to wal_ssn (exclusive) are valid. + SSN wal_ssn; + + // we also store the MSN corresponding to the wal_ssn. + MSN wal_msn; + + // We might want to also introduce a cursor to indicate the oldest valid + // chunk buffer in the (in-memory) queue. But currently only the flusher is + // reading from the queue -- always at position cks_csn. + + // The next chunk that will be written (and fsync'ed) to disk. + CSN cks_csn; + + // The msn of the first message that is stored in the chunk indicated by + // cks_csn. That msn is also stored in that chunk's header. + MSN cks_msn; + + // The ssn of the leader slot where the first message of the chunk indicated + // by cks_csn was stored. Note, this ssn is _not_ stored in the chunk's + // header -- it only has value coordinating with the In_Queue. + SSN cks_ssn; + + // The next chunk that will be discarded from the chunk store. + CSN cks_discard_csn; +}; + +/* This structure is persisted in a state file. + */ +struct Commit_Record +{ + // Number of slots of the in-queue + // must be a power of 2 currently. + uint64_t inqueue_slotcount; + + uint64_t slotsfile_size_bytes; + + // next ssn that will hit the slots-persist file + SSN wal_ssn; + MSN wal_msn; + + uint64_t chunkfile_size_bytes; + + // Csn, msn, and ssn of the next chunk that will be persisted to the chunk + // store. + CSN cks_csn; + MSN cks_msn; + SSN cks_ssn; + + // The next chunk that will be discarded from the chunk store + CSN cks_discard_csn; +}; + + +// Data owned by the enqueuer functionality. There can only be 1 enqueuer +// thread at a time. +struct Enqueuer +{ + PMQ_Enqueuer_Stats enqueuer_stats; + In_Queue_Cursors in_queue_cursors; +}; + +// Data owner by the persister functionality. There can only be 1 persister +// thread at a time +struct Persister +{ + PMQ_Persister_Stats stats; + Persist_Cursors persist_cursors; +}; + + +struct PMQ +{ + PMQ_Owned_String basedir_path; + + Posix_FD basedir_fd; + + Posix_FD slotsfile_fd; + uint64_t slotsfile_size_bytes = 0; + + In_Queue in_queue; + + // Chunks that get compacted from the in_queue. They will by persisted + // to the chunk_store. + Chunk_Queue chunk_queue; + + Chunk_Store chunk_store; + + // Cursors published by enqueuer threads, consumable by persister and reader + // threads. + In_Queue_Cursors pub_in_queue_cursors; + + PMQ_PROFILED_MUTEX(pub_in_queue_mutex); + PMQ_PROFILED_CONDVAR(pub_in_queue_cond); + + // Cursors published by persister threads, consumable by enqueuer and reader + // threads. + Mutex_Protected pub_persist_cursors; + Mutex_Protected pub_persister_stats; + + + // must be held to guarantee only 1 enqueuer at a time (protects only + // In_Queue currently). + PMQ_PROFILED_MUTEX(enqueue_mutex); + + Enqueuer enqueuer; + + + // Must be held to to guarantee only 1 persister at a time. Flushing may + // happen by a dedicated persister thread that checks regularly, or by an + // enqueuer thread when the In_Queue is full. + + PMQ_PROFILED_MUTEX(persist_mutex); + + Persister persister; + + Posix_FD statefile_fd; +}; + +void pmq_get_stats(PMQ *q, PMQ_Stats *out_stats) +{ + PMQ_Stats stats = {}; + stats.persister = q->pub_persister_stats.load(); + { + PMQ_PROFILED_LOCK(lock_, q->enqueue_mutex); + stats.enqueuer = q->enqueuer.enqueuer_stats; + } + + *out_stats = stats; +} + +PMQ_Persist_Info pmq_get_persist_info(PMQ *q) +{ + Persist_Cursors persist_cursors = q->pub_persist_cursors.load(); + PMQ_Persist_Info out; + out.wal_msn = persist_cursors.wal_msn.value(); + out.cks_msn = persist_cursors.cks_msn.value(); + out.cks_discard_csn = persist_cursors.cks_discard_csn.value(); + return out; +} + +static bool pmq_persist_finished_chunk_buffers(PMQ *q); + +// Called only on initialization and then subsequently by pmq_switch_to_next_chunk_buffer() +// Note: must be called from a persister context (with persist_mutex locked) +static bool pmq_begin_current_chunk_buffer(PMQ *q) +{ + PMQ_PROFILED_FUNCTION; + + Persist_Cursors *pc = &q->persister.persist_cursors; + Chunk_Queue *cq = &q->chunk_queue; + + uint64_t num_chunks = cq->chunks_alloc_slice.capacity(); + + if (cq->cq_csn - pc->cks_csn == num_chunks) + { + if (! pmq_persist_finished_chunk_buffers(q)) + return false; + } + + Chunk_Buffer *ck_buffer = cq->chunks.get_slot_for(cq->cq_csn); + ck_buffer->msn = cq->cq_msn; + ck_buffer->ssn = cq->cq_ssn; + ck_buffer->last_msn = MSN(0); // only set when chunk buffer gets finalized + ck_buffer->last_ssn = SSN(0); // only set when chunk buffer gets finalized + + cq->ck_buffer = ck_buffer; + cq->msg_count = 0; + cq->offsets[0] = 16; // XXX ??? + + return true; +} + +static void pmq_finalize_current_chunk_buffer(PMQ *q) +{ + Chunk_Queue *cq = &q->chunk_queue; + + Chunk_Buffer *cb = cq->ck_buffer; + cb->last_msn = cq->cq_msn; + cb->last_ssn = cq->cq_ssn; + pmq_assert(cq->cq_msn == cb->msn + cq->msg_count); + + pmq_debug_f("Finalize chunk %" PRIu64 ": MSN %" PRIu64 " - %" PRIu64 + ", %" PRIu64 " messages. Last msg ends at %" PRIu64, + cq->cq_csn.value(), + cb->msn.value(), + cb->last_msn.value(), + cq->msg_count, (uint64_t) cq->offsets[cq->msg_count]); + + // msn: msn of first message stored in this chunk. + // msgoffsets_off: offset in the chunk to an array of (msgcount + 1) offsets. + + Untyped_Slice chunk_slice = cq->ck_buffer->untyped_slice(); + Slice offsets_slice = cq->offsets.slice().sub_slice(0, cq->msg_count + 1); + + PMQ_Chunk_Hdr *hdr = (PMQ_Chunk_Hdr *) chunk_slice.data(); + hdr->msn = cq->ck_buffer->msn; + hdr->msgcount = cq->msg_count; + hdr->msgoffsets_off = PMQ_CHUNK_SIZE - offsets_slice.size_in_bytes(); + + pmq_debug_f("Place msgoffsets array in chunk at bytes offset %" PRIu64, + (uint64_t) hdr->msgoffsets_off); + + // zero out gap between last message and message-offsets-array + zero_out_slice(chunk_slice + .limit_size_bytes(hdr->msgoffsets_off) + .offset_bytes(offsets_slice.at(cq->msg_count))); + + Untyped_Slice chunk_offsets_slice = chunk_slice.offset_bytes(hdr->msgoffsets_off); + copy_slice(chunk_offsets_slice, offsets_slice.untyped()); +} + +// Finalize the current chunk buffer and start the next one. +static bool pmq_switch_to_next_chunk_buffer(PMQ *q) +{ + Chunk_Queue *cq = &q->chunk_queue; + + pmq_finalize_current_chunk_buffer(q); + + // "ship" + cq->cq_csn += 1; + + if (! pmq_begin_current_chunk_buffer(q)) + return false; + + pmq_assert(cq->ck_buffer->msn.value() == cq->cq_msn.value()); + + return true; +} + +static bool pmq_msg_fits_current_chunk_buffer(PMQ *q, uint64_t msgsize) +{ + Chunk_Queue *cq = &q->chunk_queue; + + // Compute start of offsets-array given the number of messages in the chunk. + // Why (msg_count + 2)? Here is why: (msg_count + 2) = (next_count + 1). + // next_count = the count, after appending current message. + // Add 1 to that because the offsets array holds one more slot to include + // the final size. + uint64_t offsets_off = PMQ_CHUNK_SIZE - (cq->msg_count + 2) * (uint64_t) sizeof cq->offsets[0]; + + uint64_t msgs_end = cq->offsets[cq->msg_count] + msgsize; + + return msgs_end <= offsets_off; +} + +// Helper function for pmq_persist() +// NOTE: persist_mutex must be locked +static bool pmq_compact(PMQ *q, SSN compact_ssn, SSN max_ssn) +{ + if (false) // NOLINT + { + pmq_debug_f("pmq_persist(ssn=%" PRIu64 ", max_ssn=%" PRIu64 ")", + compact_ssn.value(), max_ssn.value()); + } + + PMQ_PROFILED_FUNCTION; + + Chunk_Queue *cq = &q->chunk_queue; + + for (;;) + { + if (sn64_ge(cq->cq_ssn, max_ssn)) + { + return true; + } + + // Extract message size from slot header. + uint64_t msgsize; + { + SSN ssn = cq->cq_ssn; + const PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + if ((slot->flags & PMQ_SLOT_LEADER_MASK) == 0) + { + // Earlier there was an assert() here instead of an integrity check, + // assuming that RAM should never be corrupted. However, the RAM might + // be filled from disk, and we currently don't validate the data after + // loading. Thus we now consider slot memory just as corruptible as + // disk data. + pmq_perr_f("slot %" PRIu64 " is not a leader slot.", ssn.value()); + return false; + } + msgsize = slot->msgsize; + } + + // check if there is enough room for the message in current chunk buffer. + // If necessary, start a new chunk buffer. This in turn may require + // flushing another chunk buffer to disk (we may flush multiple + // considering throughput vs latency). + if (! pmq_msg_fits_current_chunk_buffer(q, msgsize)) + { + if (! pmq_switch_to_next_chunk_buffer(q)) + { + return false; + } + + // Actually let's always try to compact up to max_ssn + // sice we should avoid writing small batches. + // So disabling this early return. + if(false) // NOLINT + if (sn64_ge(cq->cq_ssn, compact_ssn)) + { + return true; + } + } + + // compute number of slots that we need to read, and copy to out-message + uint64_t nslots_req = (msgsize + PMQ_SLOT_SPACE - 1) / PMQ_SLOT_SPACE; + + uint64_t nslots_avail = max_ssn - cq->cq_ssn; + if (nslots_req > nslots_avail) + { + pmq_perr_f("Internal error: Invalid msgsize field in the slots file!"); + pmq_perr_f("Internal error: msgsize is %" PRIu64 ", needs %" PRIu64 + " slots but I believe only %" PRIu64 " are available!", + msgsize, nslots_req, nslots_avail); + return false; // can't we do a little more to handle the issue? + } + + // copy one message + uint64_t remain = msgsize; + uint64_t dst_offset = cq->offsets[cq->msg_count]; + + for (uint64_t i = 0; i < nslots_req; i++) + { + // copy slots to chunks + SSN ssn = cq->cq_ssn + i; + const PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + + void *dst = (char *) cq->ck_buffer->data + dst_offset; + const void *src = __pmq_assume_aligned<16>(slot->payload); + uint64_t n = remain < PMQ_SLOT_SPACE ? remain : PMQ_SLOT_SPACE; + memcpy(dst, src, n); + dst_offset += n; + } + + cq->cq_ssn += nslots_req; + cq->cq_msn += 1; + + cq->offsets[cq->msg_count + 1] = cq->offsets[cq->msg_count] + msgsize; + cq->msg_count += 1; + } + + return true; +} + +static bool pmq_commit(PMQ *q); // forward decl. We should get rid of this and fix the order. + +// Helper function for pmq_persist() +// NOTE: persister lock must be taken! +// Persists all chunk buffers from disk_csn up to (but excluding) cq_csn. +static bool pmq_persist_finished_chunk_buffers(PMQ *q) +{ + Chunk_Queue *cq = &q->chunk_queue; + Persist_Cursors *pc = &q->persister.persist_cursors; + + pmq_assert(cq->cq_csn - pc->cks_csn <= q->chunk_queue.chunks.slot_count()); + + CSN cq_csn = cq->cq_csn; + + uint64_t chunk_slot_mask = pmq_mask_power_of_2(q->chunk_store.chunk_count()); + + for (CSN csn = pc->cks_csn; + csn != cq_csn;) + { + pmq_assert(csn - pc->cks_discard_csn <= q->chunk_store.chunk_count()); + if (csn - pc->cks_discard_csn == q->chunk_store.chunk_count()) + { + // All chunks are filled. This should rarely happen since chunks are + // normally discarded when the high-watermark chunk fill count is + // reached. Typically, the chunk store is much larger than the + // In_Queue, and we should not get here. + // We discard some chunks manually by calling pmq_commit(). + if (! pmq_commit(q)) + return false; + } + pmq_assert(csn - pc->cks_discard_csn < q->chunk_store.chunk_count()); + + Chunk_Buffer *cb = q->chunk_queue.chunks.get_slot_for(csn); + + Pointer hdr = cb->get_chunk_header(); + + pmq_debug_f("Persist chunk buffer csn=%" PRIu64 ", pointer is %p, msn is %" PRIu64, + csn.value(), cb, hdr->msn.value()); + + // write chunk buffer to disk. + Untyped_Slice slice = cb->untyped_slice(); + pmq_assert(slice.size() == PMQ_CHUNK_SIZE); + + int fd = q->chunk_store.chunk_fd.get(); + uint64_t chunk_slot_index = csn.value() & chunk_slot_mask; + off_t offset_bytes = chunk_slot_index << PMQ_CHUNK_SHIFT; + + if (! pmq_pwrite_all(fd, slice, offset_bytes, "chunk buffer")) + { + // should we publish the new cursors anyway? (see exit code below) + return false; + } + + csn += 1; + + // Advance chunk store pointers + pc->cks_csn = csn; + pc->cks_ssn = cb->last_ssn; + pc->cks_msn = cb->last_msn; + + pmq_debug_f("persisted. pc->cks_msn=%" PRIu64 ", pc->cks_ssn=%" PRIu64, + pc->cks_msn.value(), pc->cks_ssn.value()); + } + + return true; +} + +// Helper function for pmq_persist_unpersisted_slots() +// Persist a contiguous sub-range of all slots. +// The *slots* argument must correspond to the slots ringbuffer. +// start_index + count may not exceed the slots size. +static bool pmq_persist_slots_slice(PMQ *q, uint64_t start_index, uint64_t count) +{ + Slice slots = q->in_queue.slots.as_slice(); + + uint64_t offset_bytes = start_index * sizeof (PMQ_Slot); + + Untyped_Slice slice = slots.sub_slice(start_index, count).untyped(); + + pmq_debug_f("Persist %" PRIu64 " slots (%zu bytes) starting from %" PRIu64, + count, slice.size(), start_index); + + bool ret = pmq_pwrite_all(q->slotsfile_fd.get(), slice, offset_bytes, "slots-file"); + + if (ret) + { + q->persister.stats.wal_flushes += 1; + q->persister.stats.wal_flush_bytes += slice.size(); + } + + return ret; +} + +// Helper function for pmq_persist() +// NOTE: persister lock must be taken! +// This function writes all unpersisted slots to the slots-file. The point of +// not writing them as a chunk is that we only want to write complete chunk +// buffers. The reason is that each chunk gets written once only (append-only), +// and chunks are fixed-size (PMQ_CHUNK_SIZE). Syncing a complete chunk would +// mean spending at least a whole chunk regardless of the number of messages in +// it. +static bool pmq_persist_unpersisted_slots(PMQ *q, SSN persist_ssn) +{ + PMQ_PROFILED_FUNCTION; + + Persist_Cursors *pc = &q->persister.persist_cursors; + + if (sn64_lt(pc->wal_ssn, pc->cks_ssn)) + { + // The chunk store contains more recent data than the slots file. + // Effectively clear slotsfile, by setting the valid range from cks_ssn to cks_ssn + pc->wal_ssn = pc->cks_ssn; + pc->wal_msn = pc->cks_msn; + } + + if (sn64_ge(pc->wal_ssn, persist_ssn)) + { + return true; + } + + pmq_debug_f("Persist unpersisted slots from %" PRIu64 " to %" PRIu64, pc->wal_ssn.value(), persist_ssn.value()); + + SSN ssn_lo = pc->wal_ssn; + SSN ssn_hi = persist_ssn; + + uint64_t count = q->in_queue.slots.slot_count(); + uint64_t mask = pmq_mask_power_of_2(count); + uint64_t i_lo = ssn_lo.value() & mask; + uint64_t i_hi = ssn_hi.value() & mask; + + bool ret; + if (i_lo <= i_hi) + { + ret = pmq_persist_slots_slice(q, i_lo, i_hi - i_lo); + } + else + { + ret = pmq_persist_slots_slice(q, i_lo, count - i_lo); + if (ret) + ret = pmq_persist_slots_slice(q, 0, i_hi); + } + + if (! ret) + { + pmq_perr_f("Failed to persist to slots-file!"); + return false; + } + + q->persister.stats.fsync_calls += 1; + if (fsync(q->slotsfile_fd.get()) < 0) + { + pmq_perr_ef(errno, "fsync() of slots file failed"); + return false; + } + + // Only now we update the cursor. + pc->wal_ssn = ssn_hi; + + // We also need to update the MSN accordingly. To find the MSN, because the + // MSN is currently not stored in the slots, instead we count the number of + // slot leaders. + // This assumes that all the follower slots have been atomically enqueued + // with each leader. + + for (SSN ssn = ssn_lo; ssn != ssn_hi; ssn ++) + { + PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + if ((slot->flags & PMQ_SLOT_LEADER_MASK)) + { + pc->wal_msn ++; + } + } + + return true; +} + +// Helper function, only used by pmq_commit() +// Compute the next value of cks_discard_csn. +// NOTE: persister lock must be taken +static CSN pmq_compute_next_discard_csn(PMQ *q) +{ + Persist_Cursors *pc = &q->persister.persist_cursors; + + uint64_t chunks_count = pc->cks_csn - pc->cks_discard_csn; + uint64_t low_mark = q->chunk_store.chunks_low_watermark; + uint64_t high_mark = q->chunk_store.chunks_high_watermark; + + if (chunks_count < high_mark) + return pc->cks_discard_csn; + + CSN old_discard_csn = pc->cks_discard_csn; + CSN new_discard_csn = pc->cks_csn - low_mark; + pmq_debug_f("Discarding chunks from %" PRIu64 " to %" PRIu64, + old_discard_csn.value(), new_discard_csn.value()); + + return new_discard_csn; +} + +// persister lock must be taken +bool __pmq_profiled pmq_commit(PMQ *q) +{ + PMQ_PROFILED_FUNCTION; + + { + q->persister.stats.fsync_calls += 1; + if (fsync(q->chunk_store.chunk_fd.get()) < 0) + { + pmq_perr_ef(errno, "fsync() of chunks file failed"); + return false; + } + } + + Persist_Cursors *pc = &q->persister.persist_cursors; + + Commit_Record commit_record; + commit_record.inqueue_slotcount = q->in_queue.slot_count; + commit_record.slotsfile_size_bytes = q->slotsfile_size_bytes; + commit_record.wal_ssn = pc->wal_ssn; + commit_record.wal_msn = pc->wal_msn; + commit_record.chunkfile_size_bytes = q->chunk_store.capacity_bytes; + commit_record.cks_csn = pc->cks_csn; + commit_record.cks_msn = pc->cks_msn; + commit_record.cks_ssn = pc->cks_ssn; + commit_record.cks_discard_csn = pmq_compute_next_discard_csn(q); + + { + Untyped_Slice slice = Untyped_Slice(&commit_record, sizeof commit_record); + + if (! pmq_pwrite_all(q->statefile_fd.get(), slice, 0, "state.dat file")) + { + return false; + } + } + + { + if (fsync(q->statefile_fd.get()) < 0) + { + pmq_perr_ef(errno, "fsync() of statefile failed"); + return false; + } + q->persister.stats.fsync_calls += 1; + } + + // Successfully committed the next discard cursor, now we can recycle the + // released chunks internally. + pc->cks_discard_csn = commit_record.cks_discard_csn; + + q->pub_persister_stats.store(q->persister.stats); + q->pub_persist_cursors.store(q->persister.persist_cursors); + + return true; +} + +// Persist messages from the In_Queue to the Chunk_Queue, at least until reaching ssn. +// The given max_ssn is the hard stop, a good choice here is the In_Queue's ssn_mem. +// The function isn't able to determine max_ssn as ssn_mem on its own, since it +// may or may not be used from within the enqueuer context. +// NOTE: This function tries to fill the current Chunk_Buffer once it reaches compact_ssn. +static bool pmq_persist(PMQ *q, SSN ssn, SSN max_ssn) +{ + if (! pmq_compact(q, ssn, max_ssn)) + goto error; + + if (! pmq_persist_finished_chunk_buffers(q)) + goto error; + + if (! pmq_persist_unpersisted_slots(q, ssn)) + goto error; + + if (! pmq_commit(q)) + goto error; + + return true; + +error: + pmq_perr_f("Failed to persist slots"); + return false; +} + +// Only meant to be called by pmq_sync() +static bool _pmq_sync(PMQ *q) +{ + In_Queue_Cursors ic; + PMQ_PROFILED_LOCK(lock_, q->persist_mutex); + + { + PMQ_PROFILED_SCOPE("wait-fill"); + PMQ_PROFILED_UNIQUE_LOCK(lock_, q->pub_in_queue_mutex); + for (;;) + { + ic = q->pub_in_queue_cursors; + pmq_assert(sn64_le(ic.ssn_disk, ic.ssn_mem)); + uint64_t slots_fill = ic.ssn_mem - ic.ssn_disk; + if (slots_fill >= q->in_queue.slots_persist_watermark) + break; + auto max_wait_time = std::chrono::milliseconds(50); + auto wait_result = q->pub_in_queue_cond.wait_for(lock_, max_wait_time); + if (wait_result == std::cv_status::timeout) + break; + q->persister.stats.wakeups += 1; + } + } + + if (false) // NOLINT + { + pmq_debug_f("ic.ssn_mem is now %" PRIu64, ic.ssn_mem.value()); + uint64_t slots_fill = ic.ssn_mem - ic.ssn_disk; + pmq_debug_f("slots_fill is now %" PRIu64, slots_fill); + pmq_debug_f("slots_persist_watermark is %" PRIu64, q->in_queue.slots_persist_watermark); + } + + if (! pmq_persist(q, ic.ssn_mem, ic.ssn_mem)) + return false; + + q->persister.stats.num_async_flushes += 1; + return true; +} + +// Entry point to persisted all messages that have been successfully enqueued +// so far. Concurrent operations (e.g. pmq_enqueue_msg()) are possible, but may +// not be persisted this time. +bool pmq_sync(PMQ *q) +{ + PMQ_PROFILED_FUNCTION; + + bool ret = _pmq_sync(q); + + if (! ret) + pmq_perr_f("Failed to pmq_sync()!"); + + return ret; +} + +// Helper function for pmq_enqueue_msg +// Attempts to make enough room in the In_Queue +// enqueue_mutex must be locked. + +static bool __pmq_profiled pmq_prepare_input_slots(PMQ *q, uint64_t nslots_req) +{ + PMQ_PROFILED_FUNCTION; + + In_Queue_Cursors *ic = &q->enqueuer.in_queue_cursors; + + uint64_t slot_count = q->in_queue.slot_count; + SSN next_ssn_mem = ic->ssn_mem + nslots_req; + + pmq_assert(ic->ssn_mem - ic->ssn_disk <= slot_count); + + if (next_ssn_mem - ic->ssn_disk <= slot_count) + return true; + + // Update the ssn_disk cursor from the pub_persist_cursors. Those hold the + // same values as q->persister.persist_cursors, just ever so slightly + // outdated. This information lets us detect if we can jump out early, + // without requiring to lock the persister context, which can take a lot of + // time. + { + Persist_Cursors pc = q->pub_persist_cursors.load(); + ic->msn_disk = pc.cks_msn; + ic->ssn_disk = pc.cks_ssn; + } + + if (next_ssn_mem - ic->ssn_disk <= slot_count) + return true; + + // Still not enough room, need to switch to persister context (lock + // it) and flush some more messages. + + q->enqueuer.enqueuer_stats.buffer_full_count += 1; + + PMQ_PROFILED_LOCK(lock_, q->persist_mutex); + + if (! pmq_persist(q, next_ssn_mem - slot_count, ic->ssn_mem)) + { + return false; + } + + if (false) // NOLINT + { + Chunk_Queue *cq = &q->chunk_queue; + Persist_Cursors *pc = &q->persister.persist_cursors; + pmq_assert(sn64_ge(cq->cq_ssn, ic->ssn_mem)); + pmq_debug_f("ic->ssn_mem: %" PRIu64 ", cq_ssn - cks_ssn: %" PRIu64, + ic->ssn_mem.value(), cq->cq_ssn.value() - pc->cks_ssn.value()); + } + + if (false) // NOLINT + { + SSN old_ssn_disk = ic->ssn_disk; + SSN new_ssn_disk = q->persister.persist_cursors.cks_ssn; + + pmq_debug_f("Flushed %" PRIu64 " ssns", ic->ssn_disk - old_ssn_disk); + + if (sn64_le(new_ssn_disk, old_ssn_disk)) + { + pmq_perr_f("Something is wrong: %" PRIu64 ", %" PRIu64, + old_ssn_disk.value(), ic->ssn_disk.value()); + } + pmq_assert(slot_count >= (ic->ssn_mem - ic->ssn_disk)); + } + + // Update the ssn_disk cursor from the (locked) persister context. + { + ic->msn_disk = q->persister.persist_cursors.cks_msn; + ic->ssn_disk = q->persister.persist_cursors.cks_ssn; + } + + pmq_assert(next_ssn_mem - ic->ssn_disk <= slot_count); + return true; +} + +// Helper function for pmq_enqueue_msg(). +// Serialize message to In_Queue's memory buffer. +// Expects enqueue_mutex to be taken. +// Expects that there is enough room to serialize the message (pmq_prepare_input_slots()) +static void pmq_serialize_msg(PMQ *q, const void *data, size_t size) +{ + PMQ_PROFILED_FUNCTION; + + In_Queue_Cursors *ic = &q->enqueuer.in_queue_cursors; + + SSN ssn_mem = ic->ssn_mem; + SSN old_ssn_mem = ic->ssn_mem; + + uint64_t slot_count = q->in_queue.slot_count; + pmq_assert(pmq_is_power_of_2(slot_count)); + uint32_t slot_flags = PMQ_SLOT_LEADER_MASK; + + // write full slots + size_t i = 0; + while (i + PMQ_SLOT_SPACE <= size) + { + PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn_mem); + slot->flags = slot_flags; + slot->msgsize = size - i; + + memcpy(__pmq_assume_aligned<16>(slot->payload), (const char *) data + i, PMQ_SLOT_SPACE); + + ssn_mem += 1; + i += PMQ_SLOT_SPACE; + slot_flags &= ~PMQ_SLOT_LEADER_MASK; + } + + // write last slot + if (i < size) + { + PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn_mem); + slot->flags = slot_flags; + slot->msgsize = size - i; + memcpy(__pmq_assume_aligned<16>(slot->payload), (const char *) data + i, size - i); + + ssn_mem += 1; + } + + // can bump ssn_mem cursor, publish new cursors field, and release lock now + + ic->ssn_mem = ssn_mem; + ic->msn += 1; + + q->enqueuer.enqueuer_stats.total_messages_enqueued += 1; + q->enqueuer.enqueuer_stats.total_bytes_enqueued += size; + + { + uint64_t new_slot_count = ic->ssn_mem - ic->ssn_disk; + uint64_t old_slot_count = old_ssn_mem - ic->ssn_disk; + + bool notify = + old_slot_count < q->in_queue.slots_persist_watermark && + new_slot_count >= q->in_queue.slots_persist_watermark; + + { + PMQ_PROFILED_UNIQUE_LOCK(lock_, q->pub_in_queue_mutex); + q->pub_in_queue_cursors = *ic; + if (notify) + q->pub_in_queue_cond.notify_one(); + } + } + + pmq_assert(ic->ssn_mem - ic->ssn_disk <= slot_count); +} + +bool pmq_enqueue_msg(PMQ *q, const void *data, size_t size) +{ + PMQ_PROFILED_FUNCTION; + + pmq_assert(size > 0); + uint64_t nslots_req = (size + PMQ_SLOT_SPACE - 1) / PMQ_SLOT_SPACE; + + PMQ_PROFILED_LOCK(lock_, q->enqueue_mutex); + if (! pmq_prepare_input_slots(q, nslots_req)) + return false; + pmq_serialize_msg(q, data, size); + return true; +} + + + +static void pmq_init_chunk_store_size(Chunk_Store *cks, uint64_t capacity_bytes) +{ + pmq_assert(pmq_is_power_of_2(capacity_bytes)); + pmq_assert(capacity_bytes >= PMQ_Megabytes(64)); + + cks->capacity_bytes = capacity_bytes; + + uint64_t chunks_count = cks->capacity_bytes >> PMQ_CHUNK_SHIFT; + + // What is a reasonable watermark at which we should start discarding chunks? + // Note that while discarding a chunk is logically only advancing a CSN cursor, + // it's very expensive because we have to fsync() that updated cursor to disk. + // For now, I'm deciding to set them to chunks_count minus 256 resp. 512. + // On each discard we'll be discarding between (hi_mark - low_mark) and + // (chunks_count - low_mark) chunks, i.e. between 16 and 32 MiB of data. + // These values should be fair when targetting a reasonable throughput of + // 2GB/sec and an fsync() latency of ~5ms. + cks->chunks_low_watermark = chunks_count - 512; + cks->chunks_high_watermark = chunks_count - 256; + + pmq_assert(cks->chunks_low_watermark < chunks_count); + pmq_assert(cks->chunks_high_watermark < chunks_count); + pmq_assert(cks->chunks_low_watermark < cks->chunks_high_watermark); +} + +static bool pmq_init_createnew(PMQ *q, const PMQ_Init_Params *params) +{ + const char *basedir_path = q->basedir_path.get().buffer; + + if (mkdir(basedir_path, 0750) == -1) + { + pmq_perr_ef(errno, "Failed to create queue directory %s", basedir_path); + return false; + } + + q->basedir_fd = pmq_open_dir(basedir_path); + + if (! q->basedir_fd.valid()) + { + pmq_perr_ef(errno, + "Failed to open the directory we created: %s", basedir_path); + return false; + } + + // Initialize In_Queue_Cursors to all 0. + { + q->enqueuer.in_queue_cursors = In_Queue_Cursors {}; + } + + // Initialize persister cursors to all 0. + { + q->persister.persist_cursors = Persist_Cursors {}; + } + + // Create slots-file. + // The slots-file is called "wal.dat" but it's not really a WAL -- only a + // buffer to store the rest slots that didn't make a complete chunk page. + { + q->slotsfile_fd = pmq_openat_regular_create(q->basedir_fd.get(), + "wal.dat", O_RDWR, 0644); + + if (! q->slotsfile_fd.valid()) + { + pmq_perr_ef(errno, "Failed to create slots file (wal.dat)"); + return false; + } + + //TODO: currently this must be the same size as the in-memory slots buffer. Fix this, we only need a tiny file on disk to persist the remaining slots that + //didn't fill a complete chunk page. + q->slotsfile_size_bytes = q->in_queue.size_bytes; + + if (fallocate(q->slotsfile_fd.get(), FALLOC_FL_ZERO_RANGE, + 0, q->slotsfile_size_bytes) == -1) + { + pmq_perr_ef(errno, "Failed to fallocate() slots file"); + return false; + } + } + + // Create chunk store + { + Chunk_Store *cks = &q->chunk_store; + + uint64_t create_size = params->create_size; + + if (create_size == 0) + create_size = PMQ_Gigabytes(1); // default to 1 GiB + + if (create_size < PMQ_Megabytes(64)) + { + pmq_perr_f("PMQ_Init_Params::create_size is invalid: " + "Must be at least 64 MiB. Requested: %" PRIu64, create_size); + return false; + } + + if (! pmq_is_power_of_2(create_size)) + { + pmq_warn_f("PMQ_Init_Params::create_size is not a power of 2: %" PRIu64, create_size); + create_size *= 2; + while (! pmq_is_power_of_2(create_size)) + create_size = create_size & (create_size - 1); + pmq_warn_f("PMQ_Init_Params::create_size is not a power of 2: rounded up to %" PRIu64, create_size); + } + + pmq_init_chunk_store_size(cks, create_size); + + cks->chunk_fd = pmq_openat_regular_create(q->basedir_fd.get(), + "chunks.dat", O_RDWR, 0644); + + if (! cks->chunk_fd.valid()) + { + pmq_perr_ef(errno, "Failed to create chunks file"); + return false; + } + + if (fallocate(cks->chunk_fd.get(), FALLOC_FL_ZERO_RANGE, + 0, cks->capacity_bytes) == -1) + { + pmq_perr_ef(errno, "Failed to fallocate() chunks file" + " to size %" PRIu64, cks->capacity_bytes); + return false; + } + } + + // Create state.dat file + { + q->statefile_fd = pmq_openat_regular_create(q->basedir_fd.get(), + "state.dat", O_RDWR, 0644); + + if (! q->statefile_fd.valid()) + { + pmq_perr_ef(errno, "Failed to open state.dat file"); + return false; + } + + // Is it ok to try and reuse the pmq_commit() function to initialize the file? + if (! pmq_commit(q)) + return false; + } + + // Sync basedir to make sure the new files are persisted. + { + if (fsync(q->basedir_fd.get()) == -1) + { + pmq_perr_ef(errno, "Error from fsync() on base directory"); + return false; + } + } + + return true; +} + +static bool __pmq_validate_commit_record_weak_ordering( + uint64_t sn_lo, uint64_t sn_hi, const char *name_lo, const char *name_hi) +{ + if (! _sn64_le(sn_lo, sn_hi)) + { + pmq_perr_f("Integrity error in state.dat file: We expected %s <= %s" + " but their values are %" PRIu64 " > %" PRIu64, + name_lo, name_hi, sn_lo, sn_hi); + return false; + } + return true; +} + +template +static bool _pmq_validate_commit_record_weak_ordering( + T sn_lo, T sn_hi, const char *name_lo, const char *name_hi) +{ + return __pmq_validate_commit_record_weak_ordering( + sn_lo.value(), sn_hi.value(), name_lo, name_hi); +} + +#define pmq_validate_commit_record_weak_ordering(cr, lo, hi) \ + _pmq_validate_commit_record_weak_ordering((cr).lo, (cr).hi, #lo, #hi) + + +static bool pmq_inithelper_check_file_size( + int fd, uint64_t expected_file_size, const char *what_file) +{ + pmq_assert(fd >= 0); + + struct stat st; + + if (fstat(fd, &st) == -1) + { + pmq_perr_ef(errno, "Failed to fstat() %s", what_file); + return false; + } + + if (! S_ISREG(st.st_mode)) + { + pmq_perr_f("Internal error: Expected regular file"); + return false; + } + + uint64_t actual_file_size = (uint64_t) st.st_size; + + if (actual_file_size != expected_file_size) + { + pmq_perr_f("%s has wrong size. Expected: %" PRIu64 ", got: %" PRIu64, + what_file, expected_file_size, actual_file_size); + return false; + } + + return true; +} + +static bool pmq_init_loadexisting(PMQ *q) +{ + // Open State File + { + q->statefile_fd = pmq_openat_regular_existing(q->basedir_fd.get(), + "state.dat", O_RDWR); + + if (! q->statefile_fd.valid()) + { + pmq_perr_ef(errno, "Failed to open state.dat file"); + return false; + } + } + + Commit_Record commit_record; + + // Load commit record and store in commit_record + { + if (! pmq_pread_all(q->statefile_fd.get(), + Untyped_Slice(&commit_record, sizeof commit_record), + 0, "state.dat")) + { + return false; + } + + if (! pmq_validate_commit_record_weak_ordering(commit_record, cks_discard_csn, cks_csn)) + return false; + + if (! pmq_validate_commit_record_weak_ordering(commit_record, cks_ssn, wal_ssn)) + return false; + + if (! pmq_validate_commit_record_weak_ordering(commit_record, cks_msn, wal_msn)) + return false; + + { + uint64_t file_size = commit_record.chunkfile_size_bytes; + + if ((file_size % PMQ_CHUNK_SIZE) != 0) + { + pmq_perr_f( + "state.dat file contains invalid chunkfile size: " + "%" PRIu64 " which is not a multiple of the chunk size " + "(%" PRIu64 ")", file_size, PMQ_CHUNK_SIZE); + return false; + } + + uint64_t chunks_count = file_size / PMQ_CHUNK_SIZE; + CSN csn_lo = commit_record.cks_discard_csn; + CSN csn_hi = commit_record.cks_csn; + + if (csn_hi - csn_lo > chunks_count) + { + pmq_perr_f("state.dat cks_discard_csn=%" PRIu64 ", cks_csn=%" PRIu64, + csn_lo.value(), csn_hi.value()); + pmq_perr_f( + "state.dat file contains invalid chunk cursor positions: " + " Their distance exceeds the size of the chunks-file " + "(%" PRIu64 " > %" PRIu64 ".", csn_hi - csn_lo, chunks_count); + return false; + } + } + + { + uint64_t file_size = commit_record.slotsfile_size_bytes; + + if ((file_size % PMQ_SLOT_SIZE) != 0) + { + pmq_perr_f( + "state.dat file contains invalid slots-file size: " + "%" PRIu64 " which is not a multiple of the slot size " + "(%" PRIu64 ")", file_size, PMQ_SLOT_SIZE); + return false; + } + + uint64_t slots_count = file_size / PMQ_SLOT_SIZE; + SSN ssn_lo = commit_record.cks_ssn; + SSN ssn_hi = commit_record.wal_ssn; + + if (ssn_hi - ssn_lo > slots_count) + { + pmq_perr_f( + "state.dat file contains invalid slot cursor positions: " + " Their distance exceeds the size of the slots-file."); + return false; + } + } + } + + // TODO: Currently the slots-file and the in-memory slots-ringbuffer are the same size + // Later, make the slots-file smaller (just because it doesn't need to be very big) + // and be very careful how to load to memory. + { + q->slotsfile_size_bytes = commit_record.slotsfile_size_bytes; + q->slotsfile_fd = pmq_openat_regular_existing(q->basedir_fd.get(), + "wal.dat", O_RDWR); + + if (! q->slotsfile_fd.valid()) + { + pmq_perr_ef(errno, "Failed to open slots file (wal.dat)"); + return false; + } + + if (! pmq_inithelper_check_file_size(q->slotsfile_fd.get(), + q->slotsfile_size_bytes, "state-file (state.dat)")) + { + return false; + } + + if (! pmq_pread_all( + q->slotsfile_fd.get(), + q->in_queue.slots.as_slice().untyped(), + 0, "slots-file (wal.dat)")) + { + pmq_perr_f("Failed to read from slots file to in-memory slots ringbuffer"); + return false; + } + } + + // Load chunk store + { + Chunk_Store *cks = &q->chunk_store; + + pmq_init_chunk_store_size(cks, commit_record.chunkfile_size_bytes); + + cks->chunk_fd = pmq_openat_regular_existing(q->basedir_fd.get(), + "chunks.dat", O_RDWR); + + if (! cks->chunk_fd.valid()) + { + pmq_perr_ef(errno, "Failed to open chunks.dat file"); + return false; + } + + if (! pmq_inithelper_check_file_size(cks->chunk_fd.get(), + cks->capacity_bytes, "chunk file (chunks.dat)")) + { + return false; + } + } + + // Initialize In_Queue_Cursors + { + In_Queue_Cursors ic; + ic.msn = commit_record.wal_msn; + ic.ssn_mem = commit_record.wal_ssn; + ic.msn_disk = commit_record.cks_msn; + ic.ssn_disk = commit_record.cks_ssn; + + q->pub_in_queue_cursors = ic; + } + + // Initialize persister cursors + { + Persist_Cursors pc; + pc.wal_ssn = commit_record.wal_ssn; + pc.wal_msn = commit_record.wal_msn; + pc.cks_csn = commit_record.cks_csn; + pc.cks_msn = commit_record.cks_msn; + pc.cks_ssn = commit_record.cks_ssn; + pc.cks_discard_csn = commit_record.cks_discard_csn; + + q->pub_persist_cursors.store(pc); + } + + return true; +} + +static bool pmq_init(PMQ *q, const PMQ_Init_Params *params) +{ + q->basedir_path.set(params->basedir_path); + + const char *basedir_path = q->basedir_path.get().buffer; + + // Set up In_Queue + // This is currently independent of any database state, so we can do it first. + { + // TODO how to find proper size (slot count) for the In_Queue buffer? + // For most use cases, we don't need extremely high bandwidth, but we + // should think about making it tunable and come up with recommendations. + // Or even allow it to be sized dynamically. + + q->in_queue.slot_count = 512 * 1024; // each slot is 128 bytes + q->in_queue.size_bytes = q->in_queue.slot_count * PMQ_SLOT_SIZE; + q->in_queue.slots_persist_watermark = q->in_queue.slot_count / 2; + + pmq_debug_f("in-queue size: %" PRIu64 " (%" PRIu64 " slots)", + q->in_queue.size_bytes, q->in_queue.slot_count); + + // We could consider making an SHM file here to back the In_Queue memory, + // making the In_Queue persist across application restarts. + // This would allow to recover any message that was successfully enqueued + // to the In_Queue (unless the machine was also restarted or crashed + // before recovery). On the other hand, it would require elaborate + // recovery code. + + if (! q->in_queue.mapping.create(NULL, q->in_queue.size_bytes, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) + { + pmq_perr_ef(errno, "Failed to mmap() queue memory"); + return false; + } + + PMQ_Slot *slots = (PMQ_Slot *) q->in_queue.mapping.get(); + pmq_assert(slots); + __pmq_assert_aligned(slots, 16); + + q->in_queue.slots.reset(Slice(slots, q->in_queue.slot_count)); + } + + + // Create or load the on-disk database + + q->basedir_fd = pmq_open_dir(basedir_path); + + if (! q->basedir_fd.valid()) + { + if (! (errno == ENOTDIR || errno == ENOENT)) + { + pmq_perr_ef(errno, "Failed to open queue directory at %s", + basedir_path); + return false; + } + + pmq_msg_f("No queue directory present at %s", basedir_path); + pmq_msg_f("Creating new queue directory at %s", basedir_path); + + if (! pmq_init_createnew(q, params)) + { + pmq_perr_f("Failed to create queue directory at %s", basedir_path); + return false; + } + } + else + { + pmq_msg_f("Loading existing queue from %s", basedir_path); + + if (! pmq_init_loadexisting(q)) + { + pmq_perr_f("Failed to load queue directory at %s", basedir_path); + return false; + } + + if (params->create_size != 0 && + params->create_size != q->chunk_store.capacity_bytes) + { + pmq_warn_f("NOTE: Configured chunk store size is %" PRIu64 + " bytes, which is different from the size of the existing" + " chunk store: %" PRIu64 " bytes." + " The chunk store size configuration is currently only" + " considered when creating a new chunk store.", + params->create_size, + q->chunk_store.capacity_bytes); + } + } + + // Set up cursors + q->enqueuer.in_queue_cursors = q->pub_in_queue_cursors; + q->persister.persist_cursors = q->pub_persist_cursors.load(); + + // Initialize Chunk_Queue + { + Chunk_Queue *cq = &q->chunk_queue; + cq->cq_csn = q->persister.persist_cursors.cks_csn; + cq->cq_ssn = q->persister.persist_cursors.cks_ssn; + cq->cq_msn = q->persister.persist_cursors.cks_msn; + + cq->chunks_alloc_slice.allocate(2); // only 2 chunk buffers + + { + uint64_t map_size_bytes = cq->chunks_alloc_slice.capacity() * PMQ_CHUNK_SIZE; + + if (! cq->chunk_buffer_mapping.create(NULL, map_size_bytes, + PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)) + { + pmq_perr_ef(errno, + "Failed to mmap() %" PRIu64 " bytes for chunk buffer", + map_size_bytes); + return false; + } + } + + cq->chunks.reset(cq->chunks_alloc_slice.slice()); + for (uint64_t i = 0; i < cq->chunks.slot_count(); i++) + { + Chunk_Buffer *cb = cq->chunks.get_slot_for(CSN(i)); + cb->data = (char *) cq->chunk_buffer_mapping.get() + (i * PMQ_CHUNK_SIZE); + // initialized later, anyway... + cb->msn = MSN(0); + cb->ssn = SSN(0); + } + + // current chunk page buffer starts out empty. + cq->msg_count = 0; + + // Since each message is at least 1 byte large, and requires a 2 byte + // offset stored as well, we can have no more than this number of + // messages (and thus offsets) in each chunk. + cq->offsets.allocate(PMQ_CHUNK_SIZE / 3); + + // Set up for submitting messages to first chunk buffer in the Chunk_Queue + // NOTE I think this will use the Chunk_Buffer identified by the value of cks_csn + // after the queue was loaded. + if (! pmq_begin_current_chunk_buffer(q)) + return false; + } + + { + In_Queue_Cursors ic = q->enqueuer.in_queue_cursors; + pmq_debug_f("in_queue_cursors.msn: %" PRIu64, ic.msn.value()); + pmq_debug_f("in_queue_cursors.ssn_mem: %" PRIu64, ic.ssn_mem.value()); + pmq_debug_f("in_queue_cursors.msn_disk: %" PRIu64, ic.msn_disk.value()); + pmq_debug_f("in_queue_cursors.ssn_disk: %" PRIu64, ic.ssn_disk.value()); + } + + { + Chunk_Queue *cq = &q->chunk_queue; + pmq_debug_f("chunk_queue.cq_csn: %" PRIu64, cq->cq_csn.value()); + pmq_debug_f("chunk_queue.cq_msn: %" PRIu64, cq->cq_msn.value()); + pmq_debug_f("chunk_queue.cq_ssn: %" PRIu64, cq->cq_ssn.value()); + } + + { + Persist_Cursors pc = q->persister.persist_cursors; + pmq_debug_f("persister.wal_ssn: %" PRIu64, pc.wal_ssn.value()); + pmq_debug_f("persister.wal_msn: %" PRIu64, pc.wal_msn.value()); + pmq_debug_f("persister.cks_csn: %" PRIu64, pc.cks_csn.value()); + pmq_debug_f("persister.cks_msn: %" PRIu64, pc.cks_msn.value()); + pmq_debug_f("persister.cks_ssn: %" PRIu64, pc.cks_ssn.value()); + pmq_debug_f("persister.cks_discard_csn: %" PRIu64, pc.cks_discard_csn.value()); + } + + return true; +} + +PMQ *pmq_create(const PMQ_Init_Params *params) +{ + PMQ *q = new PMQ(); + if (! q) + return nullptr; + if (! pmq_init(q, params)) + { + delete q; + return nullptr; + } + return q; +} + +// This function makes sure that all messages currently written are synced to disk. +// When calling this function, all concurrent access (e.g. pmq_enqueue_msg()) +// must have returned and no new ones may be done. +void pmq_destroy(PMQ *q) +{ + if (! pmq_sync(q)) + { + pmq_warn_f("Failed to sync the queue before shutting it down"); + } + delete q; +} + + + + +/* PMQ_Reader */ + +// To be able to read the newest messages, which may not be persisted to the +// chunk store but only to the slot-file / in_queue, we need multiple read modes. +// We keep track of where we're reading explicitly because we need to reset the +// appropriate cursors whenever we're switching between the modes. +enum PMQ_Read_Mode +{ + PMQ_Read_Mode_Chunkstore, + PMQ_Read_Mode_Slotsfile, +}; + +// read state for reading from the slots file +struct PMQ_Slots_Readstate +{ + SSN ssn; +}; + +// read state for reading from the chunk store +struct PMQ_Chunks_Readstate +{ + // tracks the current csn + CSN cnk_csn; + + // tracks whether the chunk indicated by cnk_csn is loaded. + bool cnk_loaded; + + // This data is extracted from chunk_page (only valid if cnk_loaded) + MSN cnk_msn; + uint64_t cnk_msgcount; + Alloc_Slice cnk_buffer; + Slice cnk_msgoffsets; // msg-offsets (a subrange inside the cnk_buffer) +}; + +struct PMQ_Reader +{ + PMQ *q; + + // to prevent races, the reader has its own copy of the Persist_Cursors. + // They get updated only before reading a message. + Persist_Cursors persist_cursors; + + // the MSN of the next message we're going to read. + // Gets incremented each time pmq_read_msg() is called. + MSN msn; + + PMQ_Read_Mode read_mode; + + // Place to store an error. This will prevent reading after an error. + // Subsequent seeking (if successful) will clear the error. + PMQ_Read_Result last_result; + + PMQ_Slots_Readstate slots_readstate; + PMQ_Chunks_Readstate chunks_readstate; +}; + +struct PMQ_Msg_Output +{ + void *data; + // size of the data buffer + size_t data_size; + // where the caller wants the size of the message to be written. + size_t *size_out; + + PMQ_Msg_Output(void *data, size_t data_size, size_t *size_out) + : data(data), data_size(data_size), size_out(size_out) + {} +}; + +static void pmq_reader_update_persist_cursors(PMQ_Reader *reader) +{ + reader->persist_cursors = reader->q->pub_persist_cursors.load(); +} + +static bool pmq_reader_validate_chunk_hdr(PMQ_Chunks_Readstate *ckread, Pointer hdr) +{ + if (hdr->msgcount == 0) + { + pmq_perr_f("Read invalid chunk %" PRIu64 ": msgcount is 0.", + ckread->cnk_csn.value()); + return false; + } + + uint64_t off_end = (uint64_t) hdr->msgoffsets_off + (hdr->msgcount + 1) * sizeof (uint16_t); + if (off_end > PMQ_CHUNK_SIZE) + { + pmq_perr_f("Read invalid chunk %" PRIu64 ": msg-offsets array exceeds chunk size." + " msgcount: %" PRIu64 ", msgoffsets_off: %" PRIu64, + ckread->cnk_csn.value(), (uint64_t) hdr->msgcount, (uint64_t) hdr->msgoffsets_off); + return false; + } + + return true; +} + +static PMQ_Read_Result pmq_load_chunk(PMQ_Reader *reader) +{ + pmq_assert(reader->read_mode == PMQ_Read_Mode_Chunkstore); + + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + + pmq_assert(!ckread->cnk_loaded); + + PMQ *q = reader->q; + + if (sn64_le(reader->persist_cursors.cks_csn, ckread->cnk_csn)) + { + return PMQ_Read_Result_EOF; + } + + Chunk_Store *cks = &q->chunk_store; + + // load chunk + uint64_t mask = pmq_mask_power_of_2(q->chunk_store.chunk_count()); + uint64_t index = ckread->cnk_csn.value() & mask; + uint64_t offset = index << PMQ_CHUNK_SHIFT; + + Untyped_Slice buffer_slice = ckread->cnk_buffer.untyped_slice(); + pmq_assert(buffer_slice.size() == PMQ_CHUNK_SIZE); + + if (! pmq_pread_all( + cks->chunk_fd.get(), buffer_slice, offset, "chunk in chunk file")) + { + return PMQ_Read_Result_IO_Error; + } + + Pointer hdr = (PMQ_Chunk_Hdr *) buffer_slice.data(); + + // first check if the chunk is still supposed to be there -- it might have + // been overwritten by the next chunk + { + pmq_reader_update_persist_cursors(reader); + if (sn64_lt(ckread->cnk_csn, reader->persist_cursors.cks_discard_csn)) + { + pmq_debug_f("LOST SYNC: ckread->cnk_csn=%" PRIu64 ", reader->persist_cursors.cks_discard_csn=%" PRIu64, + ckread->cnk_csn.value(), reader->persist_cursors.cks_discard_csn.value()); + return PMQ_Read_Result_Out_Of_Bounds; + } + } + + if (! pmq_reader_validate_chunk_hdr(ckread, hdr)) + { + return PMQ_Read_Result_Integrity_Error; + } + + // Initial validation of the loaded chunk completed. + // Set variables and return success. + + ckread->cnk_msn = hdr->msn; + ckread->cnk_msgcount = hdr->msgcount; + ckread->cnk_msgoffsets = Slice( + (uint16_t *) ((char *) ckread->cnk_buffer.data() + hdr->msgoffsets_off), + hdr->msgcount + 1); + ckread->cnk_loaded = true; + + // Set the MSN to this chunk's msn too. + reader->msn = hdr->msn; + + return PMQ_Read_Result_Success; +} + +static void pmq_reset_to_specific_chunk(PMQ_Reader *reader, CSN csn) +{ + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + ckread->cnk_loaded = false; + ckread->cnk_csn = csn; +} + +static PMQ_Read_Result pmq_reset_to_specific_chunk_and_load( + PMQ_Reader *reader, CSN csn) +{ + pmq_reset_to_specific_chunk(reader, csn); + return pmq_load_chunk(reader); +} + +static void pmq_reader_copy_chunk_header(PMQ_Chunks_Readstate *ckread, PMQ_Chunk_Hdr *out) +{ + *out = *(PMQ_Chunk_Hdr *) ckread->cnk_buffer.data(); +} + +static bool pmq_check_chunk_msns(CSN csn_lo, CSN csn_hi, + const PMQ_Chunk_Hdr *hdr_lo, const PMQ_Chunk_Hdr *hdr_hi) +{ + if (sn64_lt(csn_hi, csn_lo)) + return pmq_check_chunk_msns(csn_hi, csn_lo, hdr_hi, hdr_lo); + + MSN cnk_lo_last_msn = hdr_lo->msn + hdr_lo->msgcount; + MSN cnk_hi_first_msn = hdr_hi->msn; + + if (csn_lo + 1 == csn_hi) + { + if (cnk_lo_last_msn != cnk_hi_first_msn) + { + pmq_perr_f("Integrity error while reading chunks: MSN %" PRIu64 + " was expected in the chunk following chunk %" PRIu64 + " but found %" PRIu64, + cnk_lo_last_msn.value(), + csn_lo.value(), + cnk_hi_first_msn.value()); + return false; + } + } + else + { + // maybe we should check sn64_lt() instead of sn64_le(), because + // chunks must contain at least 1 message, at least currently. + if (! sn64_le(cnk_lo_last_msn, cnk_hi_first_msn)) + { + pmq_perr_f("Integrity error while reading chunks: Chunk SN %" PRIu64 + " < %" PRIu64 " but these chunks have low / high MSNs " + "%" PRIu64 " >= %" PRIu64, + csn_lo.value(), + csn_hi.value(), + cnk_lo_last_msn.value(), + cnk_hi_first_msn.value()); + return false; + } + } + return true; +} + +static PMQ_Read_Result pmq_bsearch_msg(PMQ_Reader *reader, MSN msn, CSN csn_lo, CSN csn_hi) +{ + if (reader->read_mode != PMQ_Read_Mode_Chunkstore) + { + reader->read_mode = PMQ_Read_Mode_Chunkstore; + reader->chunks_readstate.cnk_loaded = false; + } + + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + + bool hdr_valid = false; + PMQ_Chunk_Hdr hdr; + CSN hdr_csn; + + for (;;) + { + CSN csn = csn_lo + (csn_hi - csn_lo) / 2; + + PMQ_Read_Result readres = pmq_reset_to_specific_chunk_and_load(reader, csn); + + if (readres == PMQ_Read_Result_Out_Of_Bounds) + { + // Assuming that csn_lo was valid when we were called, + // we now have a situation where the chunk was concurrently discarded. + if (csn == csn_lo) + { + // Already at the final recursion (csn_lo + 1 == csn_hi). Search + // space is now empty. + return PMQ_Read_Result_Out_Of_Bounds; + } + // shrink the search space, adapt lower boundary to account for the concurrently discarded data. + csn_lo = csn + 1; + } + else if (readres == PMQ_Read_Result_EOF) + { + // Could this happen? I believe not. We're assuming that at the start, + // csn_lo == csn_hi or csn_hi - 1 was valid. + assert(0); + } + else if (readres != PMQ_Read_Result_Success) + { + return readres; + } + else + { + if (hdr_valid) + { + PMQ_Chunk_Hdr old_hdr = hdr; + CSN old_csn = hdr_csn; + + pmq_reader_copy_chunk_header(ckread, &hdr); + hdr_csn = csn; + + if (! pmq_check_chunk_msns(csn, old_csn, &hdr, &old_hdr)) + { + return PMQ_Read_Result_Integrity_Error; + } + } + else + { + pmq_reader_copy_chunk_header(ckread, &hdr); + hdr_csn = csn; + hdr_valid = true; + } + + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + + if (sn64_lt(msn, ckread->cnk_msn)) + { + if (csn == csn_lo) + // already final iteration + return PMQ_Read_Result_Out_Of_Bounds; + csn_hi = csn; + } + else if (sn64_ge(msn, ckread->cnk_msn + ckread->cnk_msgcount)) + { + if (csn == csn_lo) + // already final iteration + return PMQ_Read_Result_Out_Of_Bounds; + csn_lo = csn + 1; + } + else + { + // message inside this block. + return PMQ_Read_Result_Success; + } + } + } +} + +static PMQ_Read_Result pmq_reader_seek_to_msg_chunkstore(PMQ_Reader *reader, MSN msn) +{ + Persist_Cursors pc = reader->persist_cursors; + + pmq_assert(sn64_le(pc.cks_discard_csn, pc.cks_csn)); + if (pc.cks_discard_csn == pc.cks_csn) + { + // The store is empty. + // Since we already detected that msn is older than pc.cks_msn, we return + // Out_Of_Bounds, not EOF. + return PMQ_Read_Result_Out_Of_Bounds; + } + + CSN csn_lo = pc.cks_discard_csn; + CSN csn_hi = pc.cks_csn - 1; + + PMQ_Read_Result result = pmq_bsearch_msg(reader, msn, csn_lo, csn_hi); + + if (result != PMQ_Read_Result_Success) + return result; + + // Currently setting the msn only after the appropriate chunk was found and + // loaded successfully. We might want to change this later. + reader->msn = msn; + return PMQ_Read_Result_Success; +} + +struct PMQ_Slot_Header_Read_Result +{ + bool is_leader_slot; + uint16_t msgsize; + uint16_t nslots_req; +}; + +// Helper for function that read slots. +// NOTE: Enqueuer lock must be held! +static PMQ_Read_Result pmq_read_slot_header(PMQ *q, SSN ssn, PMQ_Slot_Header_Read_Result *out) +{ + //XXX this code is copied and adapted from pmq_compact() + + const PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + + // Extract message size from slot header. + out->is_leader_slot = (slot->flags & PMQ_SLOT_LEADER_MASK) != 0; + out->msgsize = slot->msgsize; + out->nslots_req = (slot->msgsize + PMQ_SLOT_SPACE - 1) / PMQ_SLOT_SPACE; + + // TODO validate msgsize field, does it make sense? + + return PMQ_Read_Result_Success; +} + +// Seek message in the slots file. +// Currently this requires locking the enqueuer and a linear scan. +// We should look for ways to improve. +static PMQ_Read_Result pmq_reader_seek_to_msg_slotsfile(PMQ_Reader *reader, MSN msn) +{ + Persist_Cursors pc = reader->persist_cursors; + assert(sn64_inrange(msn, pc.cks_msn, pc.wal_msn)); // checked in caller + + std::lock_guard lock(reader->q->enqueue_mutex); + + // To prevent races, we need to check again using the enqueuer's cursors + // that the MSN that we're looking for is still in the In_Queue. + + In_Queue_Cursors *ic = &reader->q->enqueuer.in_queue_cursors; + if (sn64_inrange(msn, ic->msn_disk, ic->msn)) + { + if (sn64_inrange(pc.wal_msn, msn, ic->msn)) + // this is almost guaranteed but there is a race that should be + // impossible in practice (requires ic->msn to wrap around between + // msn and pc.wal_msn). + { + MSN msn_cur = ic->msn_disk; + SSN ssn_cur = ic->ssn_disk; + + while (msn_cur != msn) + { + if (sn64_ge(ssn_cur, pc.wal_ssn)) + { + pmq_perr_f("Integrity Error: Reached end of persisted region in slotsfile " + "but did not encounter msn=%" PRIu64, msn.value()); + return PMQ_Read_Result_Integrity_Error; + } + + PMQ_Slot_Header_Read_Result slot_read_result; + + if (PMQ_Read_Result readres = pmq_read_slot_header(reader->q, ssn_cur, &slot_read_result); + readres != PMQ_Read_Result_Success) + { + return readres; + } + + if (! slot_read_result.is_leader_slot) + { + // Earlier there was an assert() here instead of an integrity check, + // assuming that RAM should never be corrupted. However, the RAM might + // be filled from disk, and we currently don't validate the data after + // loading. Thus we now consider slot memory just as corruptible as + // disk data. + pmq_perr_f("Integrity Error: slot %" PRIu64 " is not a leader slot.", ssn_cur.value()); + return PMQ_Read_Result_Integrity_Error; + } + + if (pc.wal_ssn - ssn_cur < slot_read_result.nslots_req) + { + pmq_perr_f("Integrity Error: forwarding %d slots through the slots file" + " would skip over persisted region", (int) slot_read_result.nslots_req); + pmq_perr_f("current msn=%" PRIu64 ", ssn=%" PRIu64 ", last valid slot is %" PRIu64, + msn_cur.value(), ssn_cur.value(), pc.wal_msn.value()); + return PMQ_Read_Result_Integrity_Error; + } + + ssn_cur += slot_read_result.nslots_req; + msn_cur += 1; + } + + reader->read_mode = PMQ_Read_Mode_Slotsfile; + reader->slots_readstate.ssn = ssn_cur; + return PMQ_Read_Result_Success; + } + } + + // if we missed the window (race condition) we can expect to find the message in the chunk store. + return pmq_reader_seek_to_msg_chunkstore(reader, msn); +} + +static PMQ_Read_Result pmq_reader_seek_to_msg_impl_real(PMQ_Reader *reader, MSN msn) +{ + pmq_reader_update_persist_cursors(reader); + + Persist_Cursors pc = reader->persist_cursors; + + if (sn64_ge(msn, pc.cks_msn)) + { + if (sn64_gt(msn, pc.wal_msn)) + { + return PMQ_Read_Result_Out_Of_Bounds; + } + return pmq_reader_seek_to_msg_slotsfile(reader, msn); + } + + return pmq_reader_seek_to_msg_chunkstore(reader, msn); +} + +static PMQ_Read_Result pmq_reader_seek_to_msg_impl(PMQ_Reader *reader, MSN msn) +{ + PMQ_Read_Result result = pmq_reader_seek_to_msg_impl_real(reader, msn); + reader->last_result = result; + + if (result == PMQ_Read_Result_Success) + { + reader->msn = msn; + } + else + { + pmq_assert(result != PMQ_Read_Result_EOF); // seeking shouldn't return EOF + } + return result; +} + +PMQ_Read_Result pmq_reader_seek_to_msg(PMQ_Reader *reader, uint64_t msn_value) +{ + MSN msn = MSN(msn_value); + return pmq_reader_seek_to_msg_impl(reader, msn); +} + +PMQ_Read_Result pmq_reader_seek_to_current(PMQ_Reader *reader) +{ + pmq_reader_update_persist_cursors(reader); + + MSN msn = reader->persist_cursors.wal_msn; + pmq_debug_f("Try seeking to MSN %" PRIu64, msn.value()); + + return pmq_reader_seek_to_msg_impl(reader, msn); +} + +PMQ_Read_Result pmq_reader_seek_to_csn_impl(PMQ_Reader *reader, CSN csn) +{ + Persist_Cursors *pc = &reader->persist_cursors; + + if (uint64_t chunks_in_store = pc->cks_csn - pc->cks_discard_csn; + csn - pc->cks_discard_csn >= chunks_in_store) + { + if (csn == pc->cks_csn) + { + // While we cannot know the msn from a chunk (there are no chunks) we + // can take the cks_msn instead. + // This should return EOF but the reader should positioned correctly. + return pmq_reader_seek_to_msg_impl(reader, pc->cks_msn); + } + return PMQ_Read_Result_Out_Of_Bounds; + } + + // Otherwise, let's load a chunk and read the oldest msn from there. + // The reader state management should be cleaned up. It's not very clear + // what all the members mean and how they need to be mutated. + + reader->read_mode = PMQ_Read_Mode_Chunkstore; + + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + + PMQ_Read_Result result = pmq_reset_to_specific_chunk_and_load(reader, csn); + + if (result != PMQ_Read_Result_Success) + { + // EOF should not happen because of our prior checks. + // I would like to use an assert but at least in theory there is the + // chance of a wraparound happening concurrently. + if (result == PMQ_Read_Result_EOF) + { + // EOF would be misleading since we are not "positioned". Not sure what to do currently. + result = PMQ_Read_Result_Out_Of_Bounds; + } + return result; + } + + reader->msn = ckread->cnk_msn; + return PMQ_Read_Result_Success; +} + +PMQ_Read_Result pmq_reader_seek_to_oldest(PMQ_Reader *reader) +{ + pmq_reader_update_persist_cursors(reader); + + CSN csn = reader->persist_cursors.cks_discard_csn; + pmq_debug_f("Try seeking to CSN %" PRIu64, csn.value()); + + reader->last_result = pmq_reader_seek_to_csn_impl(reader, csn); + if (reader->last_result == PMQ_Read_Result_Success) + { + pmq_debug_f("Succeeded in seeking to CSN %" PRIu64 ". MSN is %" PRIu64, + csn.value(), reader->msn.value()); + } + else + { + pmq_debug_f("Seeking to CSN failed"); + } + return reader->last_result; +} + +static PMQ_Read_Result pmq_read_msg_slotsfile(PMQ_Reader *reader, PMQ_Msg_Output output); + +// Attempt to read the message given by reader->msn from the chunk store. +// We may have to switch to reading from the slotsfile if we detect an EOF. +static PMQ_Read_Result pmq_read_msg_chunkstore(PMQ_Reader *reader, PMQ_Msg_Output output) +{ + pmq_assert(reader->read_mode == PMQ_Read_Mode_Chunkstore); + + PMQ_Chunks_Readstate *ckread = &reader->chunks_readstate; + + if (! ckread->cnk_loaded) + { + PMQ_Read_Result readres = pmq_load_chunk(reader); + + if (readres == PMQ_Read_Result_EOF) + { + pmq_debug_f("Reader switches to slots file"); + // Switch to slot-file read mode + reader->read_mode = PMQ_Read_Mode_Slotsfile; + reader->slots_readstate.ssn = reader->persist_cursors.cks_ssn; + return pmq_read_msg_slotsfile(reader, output); + } + + if (readres != PMQ_Read_Result_Success) + { + return readres; + } + + pmq_assert(ckread->cnk_loaded); + } + else if (reader->msn - ckread->cnk_msn == ckread->cnk_msgcount) + { + // Load next chunk + CSN csn_old = ckread->cnk_csn; + PMQ_Chunk_Hdr hdr_old; + pmq_reader_copy_chunk_header(ckread, &hdr_old); + + PMQ_Read_Result readres = + pmq_reset_to_specific_chunk_and_load(reader, ckread->cnk_csn + 1); + + if (readres != PMQ_Read_Result_Success) + return readres; + + CSN csn_new = ckread->cnk_csn; + PMQ_Chunk_Hdr hdr_new; + pmq_reader_copy_chunk_header(ckread, &hdr_new); + + if (! pmq_check_chunk_msns(csn_old, csn_new, &hdr_old, &hdr_new)) + { + return PMQ_Read_Result_Integrity_Error; + } + } + + // Chunk is present + pmq_assert(sn64_le(ckread->cnk_msn, reader->msn)); + pmq_assert(sn64_lt(reader->msn, ckread->cnk_msn + ckread->cnk_msgcount)); + uint64_t msgindex = reader->msn - ckread->cnk_msn; + uint64_t msgoff = ckread->cnk_msgoffsets.at(msgindex); + uint64_t nextoff = ckread->cnk_msgoffsets.at(msgindex + 1); + uint64_t msgsize = nextoff - msgoff; + + if (msgoff >= nextoff) + { + pmq_perr_f("Invalid offsets in chunk %" PRIu64 + ": Offset #%u and #%u are %u > %u", + ckread->cnk_csn.value(), + (unsigned) msgindex, (unsigned) msgindex + 1, + (unsigned) msgoff, (unsigned) nextoff); + return PMQ_Read_Result_Integrity_Error; + } + + if (nextoff > PMQ_CHUNK_SIZE) + { + pmq_perr_f("Invalid offset in chunk %" PRIu64 ": " + "Offset #%u = %u exceed chunk size", + ckread->cnk_csn.value(), + (unsigned) msgindex + 1, (unsigned) nextoff); + return PMQ_Read_Result_Integrity_Error; + } + + *output.size_out = msgsize; + + if (msgsize <= output.data_size) + { + Untyped_Slice slice = ckread->cnk_buffer.untyped_slice().offset_bytes(msgoff); + copy_from_slice(output.data, slice, msgsize); + } + + reader->msn += 1; + + return PMQ_Read_Result_Success; +} + +// Attempt to read the message given by reader->msn from the chunk store. +// We may have to switch to reading from the chunk store if we detect that +// we've lost sync -- this may happen if the message we want to read has +// already disappeared (was overwritten) from the slotsfile. +static PMQ_Read_Result pmq_read_msg_slotsfile(PMQ_Reader *reader, PMQ_Msg_Output output) +{ + pmq_assert(reader->read_mode == PMQ_Read_Mode_Slotsfile); + + PMQ *q = reader->q; + PMQ_Slots_Readstate *slread = &reader->slots_readstate; + SSN ssn = slread->ssn; + + if (ssn == reader->persist_cursors.wal_ssn) + { + return PMQ_Read_Result_EOF; + } + + if (sn64_lt(reader->persist_cursors.wal_ssn, ssn)) + { + pmq_debug_f("sn64_lt(reader->persist_cursors.wal_ssn, ssn): wal_ssn=%" PRIu64 ", ssn=%" PRIu64, + reader->persist_cursors.wal_ssn.value(), ssn.value()); + // Should we even allow this to happen? + return PMQ_Read_Result_Out_Of_Bounds; + } + + // NOTE: We need to be careful to avoid that the ringbuffer slots that we + // read get overwritten concurrently because of new messages being enqueued. + // For now we will simply lock the in_queue. We may try to optimize this later. + // One possible approach could be to check that the slots that we read from + // are valid -- check it both before and after we read the slots. + + // !!! IDEA !!! instead of locking the in-queue, we could lock the persister. + // The reason why this should work is that data from the in-queue only gets + // overwritten after having been persisted. + // On the other hand, locking the persister might block for an unreasonable + // amount of time. + + std::lock_guard lock(q->enqueue_mutex); + + // check that the message we're looking for is still there. + if (sn64_gt(q->enqueuer.in_queue_cursors.ssn_disk, ssn)) + { + // The slot was already overwritten before we took the lock. + // pmq_reader_seek_to_msg() should find the message in the chunk store. + PMQ_Read_Result readres = pmq_reader_seek_to_msg_impl(reader, reader->msn); + + if (readres != PMQ_Read_Result_Success) + return readres; + + return pmq_read_msg_chunkstore(reader, output); + } + + PMQ_Slot_Header_Read_Result slot_read_result; + + { + PMQ_Read_Result readres = pmq_read_slot_header(q, ssn, &slot_read_result); + if (readres != PMQ_Read_Result_Success) + return readres; + } + + if (! slot_read_result.is_leader_slot) + { + // Earlier there was an assert() here instead of an integrity check, + // assuming that RAM should never be corrupted. However, the RAM might + // be filled from disk, and we currently don't validate the data after + // loading. Thus we now consider slot memory just as corruptible as + // disk data. + pmq_perr_f("slot %" PRIu64 " is not a leader slot.", ssn.value()); + return PMQ_Read_Result_Integrity_Error; + } + + *output.size_out = slot_read_result.msgsize; + + if (slot_read_result.msgsize > output.data_size) + return PMQ_Read_Result_Buffer_Too_Small; + + if (reader->persist_cursors.wal_ssn - ssn < slot_read_result.nslots_req) + { + pmq_perr_f("Integrity error: Read inconsistent msgsize from slot"); + return PMQ_Read_Result_Integrity_Error; + } + + // copy one message + { + char *dst = (char *) output.data; + uint64_t remain = slot_read_result.msgsize; + for (; remain >= PMQ_SLOT_SPACE;) + { + const PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + const char *src = __pmq_assume_aligned<16>(slot->payload); + memcpy(dst, src, PMQ_SLOT_SPACE); + ++ ssn; + dst += PMQ_SLOT_SPACE; + remain -= PMQ_SLOT_SPACE; + } + if (remain) + { + const PMQ_Slot *slot = q->in_queue.slots.get_slot_for(ssn); + const char *src = __pmq_assume_aligned<16>(slot->payload); + memcpy(dst, src, remain); + ++ ssn; + } + } + + slread->ssn = ssn; + reader->msn += 1; + + return PMQ_Read_Result_Success; +} + +PMQ_Read_Result pmq_read_msg(PMQ_Reader *reader, + void *data, size_t size, size_t *out_size) +{ + if (reader->last_result != PMQ_Read_Result_Success + && reader->last_result != PMQ_Read_Result_EOF) + { + return reader->last_result; // need to seek to clear the error! + } + + PMQ_Msg_Output output(data, size, out_size); + + pmq_reader_update_persist_cursors(reader); + + if (sn64_ge(reader->msn, reader->persist_cursors.wal_msn)) + { + if (reader->msn == reader->persist_cursors.wal_msn) + { + //pmq_debug_f("Reader reaches EOF at msn=%" PRIu64, reader->msn.value()); + return PMQ_Read_Result_EOF; + } + return PMQ_Read_Result_Out_Of_Bounds; + } + + switch (reader->read_mode) + { + case PMQ_Read_Mode_Chunkstore: + pmq_debug_f("Read message %" PRIu64 " from chunk store.", reader->msn.value()); + return pmq_read_msg_chunkstore(reader, output); + case PMQ_Read_Mode_Slotsfile: + pmq_debug_f("Read message %" PRIu64 " from slots file.", reader->msn.value()); + return pmq_read_msg_slotsfile(reader, output); + default: + // shouldn't happen. + pmq_assert(0); + abort(); + } +} + +PMQ_Reader *pmq_reader_create(PMQ *q) +{ + PMQ_Reader *reader = new PMQ_Reader; + + if (! reader) + { + pmq_perr_f("Failed to allocate reader!"); + return nullptr; + } + + reader->q = q; + reader->msn = MSN(0); + reader->read_mode = PMQ_Read_Mode_Chunkstore; + reader->last_result = PMQ_Read_Result_Success; + reader->chunks_readstate.cnk_csn = CSN(0); // for now + reader->chunks_readstate.cnk_buffer.allocate(PMQ_CHUNK_SIZE); + reader->chunks_readstate.cnk_loaded = false; + reader->chunks_readstate.cnk_msn = MSN(0); + reader->chunks_readstate.cnk_msgcount = 0; + reader->slots_readstate.ssn = SSN(0); + return reader; +} + +void pmq_reader_destroy(PMQ_Reader *reader) +{ + // TODO? + delete reader; +} + +PMQ *pmq_reader_get_pmq(PMQ_Reader *reader) +{ + return reader->q; +} + +uint64_t pmq_reader_get_current_msn(PMQ_Reader *reader) +{ + return reader->msn.value(); +} + +uint64_t pmq_reader_find_old_msn(PMQ_Reader *reader) +{ + for (uint64_t distance = 1; ; distance = (distance ? 2 * distance : 1)) + { + pmq_reader_update_persist_cursors(reader); + Persist_Cursors persist_cursors = reader->persist_cursors; + CSN csn = persist_cursors.cks_discard_csn + distance; + if (sn64_ge(csn, persist_cursors.cks_csn)) + { + return persist_cursors.cks_msn.value(); + } + // possible optimization: don't load the whole chunk but only the header + PMQ_Read_Result readres = pmq_reset_to_specific_chunk_and_load(reader, csn); + if (readres == PMQ_Read_Result_Success) + { + return reader->msn.value(); + } + } +} + +PMQ_Persist_Info pmq_reader_get_persist_info(PMQ_Reader *reader) +{ + return pmq_get_persist_info(reader->q); +} + +bool pmq_reader_eof(PMQ_Reader *reader) +{ + // this is a bit wacky -- we read the pub_persist_cursors, which requires a mutex lock, + // because we do not know from the current context if we could just access the Persister State's prive persist_cursors. + + // NOTE: We expect that wal_msn is always kept "in front" of cks_msn (the chunk-store MSN). + // Even when the wal does not have any additional slots -- in this case, we expect wal_msn == cks_msn. + + pmq_reader_update_persist_cursors(reader); + + MSN wal_msn = reader->persist_cursors.wal_msn; + return sn64_ge(reader->msn, wal_msn); +} diff --git a/meta/source/pmq/pmq.hpp b/meta/source/pmq/pmq.hpp new file mode 100644 index 0000000..84e23e4 --- /dev/null +++ b/meta/source/pmq/pmq.hpp @@ -0,0 +1,243 @@ +#pragma once + +#include // uint64_t etc. +#include // size_t + +struct PMQ_Enqueuer_Stats +{ + // how many times was the buffer filled up (the flusher couldn't keep up)? + uint64_t buffer_full_count; + uint64_t total_messages_enqueued; + uint64_t total_bytes_enqueued; +}; + +struct PMQ_Persister_Stats +{ + uint64_t num_async_flushes; // calls to pmq_sync() + uint64_t wakeups; + uint64_t fsync_calls; + uint64_t wal_flushes; + uint64_t wal_flush_bytes; +}; + +struct PMQ_Stats +{ + PMQ_Enqueuer_Stats enqueuer; + PMQ_Persister_Stats persister; +}; + +struct PMQ; + +/* Parmeters for creating a new new queue object (see pmq_create()). + * If basedir_path exists, try to load existing queue data structures from disk. + * Otherwise, create the directory and initialize a new queue there. + * A queue use approximately the number of bytes that were specified in + * create_size at the time of creation. (Something like 2 GiB is not + * unreasonable). + */ +struct PMQ_Init_Params +{ + const char *basedir_path; + uint64_t create_size; +}; + +PMQ *pmq_create(const PMQ_Init_Params *params); + +/* Destroy queue object. This will first flush the remaining buffered messages to disk. + */ +void pmq_destroy(PMQ *q); + +bool pmq_enqueue_msg(PMQ *q, const void *data, size_t size); +bool pmq_sync(PMQ *q); + +void pmq_get_stats(PMQ *q, PMQ_Stats *stats); + +/* Information about persisted data */ +struct PMQ_Persist_Info +{ + uint64_t cks_discard_csn; // oldest CSN in the chunk store (next chunk to be discarded) + uint64_t cks_msn; // next MSN to hit the chunk store + uint64_t wal_msn; // next MSN to hit the WAL +}; + +PMQ_Persist_Info pmq_get_persist_info(PMQ *q); + +/* + * Get an updated value of the byte range of the underlying data store. + * The returned range will be chunk-aligned, but what size chunks are is + * currently not exposed in this API. + */ + + +/* */ +enum PMQ_Read_Result +{ + // The message was successfully read back. + PMQ_Read_Result_Success, + + // The provided buffer has insufficient size. (The size gets returned back nevertheless) + PMQ_Read_Result_Buffer_Too_Small, + + // The requested data is at the current end of the storage area/window. It + // is the next data the will be written. Try again later. + // TODO: We might want to introduce mechanisms to block until new data arrives at + // every level. Currently this has to be implemented in the integration + // code. + PMQ_Read_Result_EOF, + + // The requested data is not present. Maybe the requested data was discarded + // concurrently? It is safe to re-position he cursor and retry. + PMQ_Read_Result_Out_Of_Bounds, + + // An error was detected by the storage layer + PMQ_Read_Result_IO_Error, + + // A problem with the data read back from the storage layer was detected. + PMQ_Read_Result_Integrity_Error, +}; + +static inline const char *pmq_read_result_string(PMQ_Read_Result readres) +{ + switch (readres) + { + case PMQ_Read_Result_Success: return "Success"; + case PMQ_Read_Result_Buffer_Too_Small: return "Buffer_Too_Small"; + case PMQ_Read_Result_EOF: return "EOF"; + case PMQ_Read_Result_Out_Of_Bounds: return "Out_Of_Bounds"; + case PMQ_Read_Result_IO_Error: return "IO_Error"; + case PMQ_Read_Result_Integrity_Error: return "Integrity_Error"; + default: return "(invalid value)"; + } +} + + +struct PMQ_Reader; + +PMQ_Reader *pmq_reader_create(PMQ *q); +void pmq_reader_destroy(PMQ_Reader *reader); + +PMQ *pmq_reader_get_pmq(PMQ_Reader *reader); + +/* Position cursor at the next incoming message -- or, in other words, at the + * current write end of the queue. */ +PMQ_Read_Result pmq_reader_seek_to_current(PMQ_Reader *reader); + +/* Position cursor at the oldest message (the first message in the chunk + * cks_discard). Note that this is rarely a good idea since this message is + * likely to be discarded concurrently, so it runs risk of losing sync + * immediately or shortly. */ +PMQ_Read_Result pmq_reader_seek_to_oldest(PMQ_Reader *reader); + +/* Position cursor to given msn. MSNs cannot be directly adressed. The + * implementation will have to load multiple chunks to find it. + * This also means that the call can fail -- I/O errors etc. can be returned. + */ +PMQ_Read_Result pmq_reader_seek_to_msg(PMQ_Reader *reader, uint64_t msn); + +/* Read the current message and advance. On success, returns the size of the + * message that was read in @out_size and advances to the next message + * internally. + */ +PMQ_Read_Result pmq_read_msg(PMQ_Reader *reader, + void *data, size_t size, size_t *out_size); + +uint64_t pmq_reader_get_current_msn(PMQ_Reader *reader); + +/* Attempt to find the MSN of the oldest persisted message. + * + * Note that the MSN that ends up being returned might already be discarded + * once the caller tries to read that message. So calling this function might + * not be a good idea. + * + * Another difficulty, at the implementation level, is that the implementationn + * needs to read the oldest chunk to know the oldest MSN in that chunk. But the + * oldest chunk may be discarded concurrently, so reading it might fail. In + * case of a concurrent discard, the implementation will update its + * oldest-chunk information and then skip ahead some chunks, trying to read a + * slightly newer chunk. This makes the operation more likely to succeed next + * time. This continues until either a chunk was read successfully, or we run + * out of persisted chunks. In the latter case, the implementation returns the + * current "next" MSN. The PMQ always keeps track of this information, so we + * can know it without reading a chunk from disk. + */ +uint64_t pmq_reader_find_old_msn(PMQ_Reader *reader); + +/* Equivalent to pmq_get_persist_info(pmq_reader_get_pmq(reader)); */ +PMQ_Persist_Info pmq_reader_get_persist_info(PMQ_Reader *reader); + +/* pmq_reader_eof() -- Inexpensive check if there are messages available + * currently. + * This allows a concurrent reader procedure synchronize with writers without + * having to actually read a message while holding a lock -- which could block + * writers for a long time if we have to do actual I/O. + */ +bool pmq_reader_eof(PMQ_Reader *reader); + + + +// C++ RAII wrappers + +// unique_ptr is maybe not precisely what we're looking for. So we're using some boilerplate instead. +//#include +//using PMQ_Handle = std::unique_ptr; +//using PMQ_Reader_Handle = std::unique_ptr; + + +template +class PMQ_Handle_Wrapper +{ + T *m_ptr = nullptr; + +public: + + T *get() const + { + return m_ptr; + } + + void drop() + { + if (m_ptr) + { + Deleter(m_ptr); + m_ptr = nullptr; + } + } + + operator T *() const // automatic implicit cast to T * + { + return m_ptr; + } + + explicit operator bool() const + { + return m_ptr != nullptr; + } + + void operator=(PMQ_Handle_Wrapper&& other) + { + drop(); + std::swap(m_ptr, other.m_ptr); + } + + void operator=(T *ptr) + { + drop(); + m_ptr = ptr; + } + + void operator=(PMQ_Handle_Wrapper const& other) = delete; + + explicit PMQ_Handle_Wrapper(T *ptr = nullptr) + : m_ptr(ptr) + { + } + + ~PMQ_Handle_Wrapper() + { + drop(); + } +}; + +using PMQ_Handle = PMQ_Handle_Wrapper; +using PMQ_Reader_Handle = PMQ_Handle_Wrapper; diff --git a/meta/source/pmq/pmq_base.hpp b/meta/source/pmq/pmq_base.hpp new file mode 100644 index 0000000..76d6099 --- /dev/null +++ b/meta/source/pmq/pmq_base.hpp @@ -0,0 +1,350 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// macro to align variables to cache line size +// There is C++ standardized value of std::hardware_destructive_interference_size. +// However that currently produces a warning, probably because of concerns about ABI stability. +// So instead I just hardcode a cache line size of 64 bytes for now. +// The worst that could happen would be bad performance. +//#define __pmq_cache_aligned alignas(std::hardware_destructive_interference_size) +#define __pmq_cache_aligned alignas(64) + +// These #define's work for GCC and possibly other compilers. To guarantee +// that these definitions are active wherever they could potentially work, I +// will define them unconditionally for now, instead of guarding them with +// #ifdef __GNUC__. +// TODO: try on more compilers and improve compatibility logic! + +#if PMQ_WITH_PROFILING +#define __pmq_profiled __attribute__((noinline)) // could consider attribute "noipa" instead of "noinline" +#else +#define __pmq_profiled +#endif + +// "artificial" is used for small inlined wrapper methods, such as operator[]. +// In theory (and to some extent in practice) the effect should be that the +// code that gets inlined to a call site gets attributed to the _call site_ +// instead of to the definition site of the inlined function -- reducing the +// effect of jumping around like wild files when debugging. + +#define __pmq_artificial_method inline __attribute__((always_inline, artificial)) +#define __pmq_artificial_func static inline __attribute__((always_inline, artificial)) + +// Attribute used for logging functions and other printf-style functions. If +// these functions are properly annotated, the compiler can check matching +// arguments in usage places. + +#define __pmq_formatter(fmt_index, first_arg_index) \ + __attribute__((format(printf, (fmt_index), (first_arg_index)))) + +// treat format warnings as errors for the PMQ +// This could be a build system flag but for now I want the change just for +// this module in the larger system +#pragma GCC diagnostic error "-Wformat" + + +#ifdef NDEBUG +#define pmq_assert(expr) +#else +static inline void __pmq_assert_fail(const char *expr, const char *file, int line, const char *func) +{ + // this hopefully gives the logger a chance to save the logs. + // If there was time, we should probably implement the logger in a separate component, + // communicating using a shared memory mapping. + + sleep(3); + __assert_fail(expr, file, line, func); +} +#define pmq_assert(expr) do { if (! (expr)) { __pmq_assert_fail(#expr, __FILE__, __LINE__, __func__); } } while (0) +#endif + + + +__pmq_artificial_func +void __pmq_assert_aligned(const void *ptr, size_t size) +{ + assert((uintptr_t) (ptr) % size == 0); +} + +template +__pmq_artificial_func +T __attribute__((aligned(size))) *__pmq_assume_aligned(const T *ptr) +{ + __pmq_assert_aligned(ptr, size); + return (T *) __builtin_assume_aligned(ptr, size); +} + + +static inline bool pmq_is_power_of_2(uint64_t value) +{ + assert(value != 0); + return (value & (value - 1)) == 0; +} + +static inline uint64_t pmq_mask_power_of_2(uint64_t value) +{ + assert(value != 0); + assert((value & (value - 1)) == 0); + return value - 1; +} + + +static inline constexpr uint64_t PMQ_Kilobytes(uint64_t count) { return count << 10; } +static inline constexpr uint64_t PMQ_Megabytes(uint64_t count) { return count << 20; } +static inline constexpr uint64_t PMQ_Gigabytes(uint64_t count) { return count << 30; } +static inline constexpr uint64_t PMQ_Terabytes(uint64_t count) { return count << 40; } +static inline constexpr uint64_t PMQ_Petabytes(uint64_t count) { return count << 50; } + + +/* Untyped slice class. This is mainly used for slice-copy operations, both for + * memory and disk I/O. It saves some boilerplate and is a little bit safer to use. + * + * Note, we should check if we can replace this with a standard C++ type maybe. + * But I personally don't consider this code a liability, and add + * __pmq_artificial_method method improves the debugging experience. + */ +class Untyped_Slice +{ + void *m_data; + size_t m_size; + +public: + __pmq_artificial_method void *data() const { return m_data; } + __pmq_artificial_method size_t size() const { return m_size; } + + __pmq_artificial_method + Untyped_Slice offset_bytes(size_t offset) const + { + assert(offset <= m_size); + return Untyped_Slice((char *) m_data + offset, m_size - offset); + } + + __pmq_artificial_method + Untyped_Slice limit_size_bytes(size_t size) const + { + assert(size <= m_size); + return Untyped_Slice(m_data, size); + } + + __pmq_artificial_method + Untyped_Slice sub_slice_bytes(size_t offset, size_t size) const + { + return offset_bytes(offset).limit_size_bytes(size); + } + + __pmq_artificial_method + Untyped_Slice() + { + m_data = nullptr; + m_size = 0; + } + + __pmq_artificial_method + Untyped_Slice(void *data, size_t size) + { + m_data = data; + m_size = size; + } +}; + +__pmq_artificial_func +void zero_out_slice(Untyped_Slice dst) +{ + memset(dst.data(), 0, dst.size()); +} + +__pmq_artificial_func +void copy_slice(Untyped_Slice dst, Untyped_Slice src) +{ + assert(dst.size() == src.size()); + memcpy(dst.data(), src.data(), dst.size()); +} + +__pmq_artificial_func +void copy_slice_bytes(Untyped_Slice dst, Untyped_Slice src, size_t size_bytes) +{ + assert(size_bytes <= dst.size()); + assert(size_bytes <= src.size()); + memcpy(dst.data(), src.data(), size_bytes); +} + +__pmq_artificial_func +void copy_to_slice(Untyped_Slice slice, const void *data, size_t size) +{ + assert(slice.size() >= size); + memcpy(slice.data(), data, size); +} + +__pmq_artificial_func +void copy_from_slice(void *data, Untyped_Slice slice, size_t size) +{ + assert(slice.size() >= size); + memcpy(data, slice.data(), size); +} + + +/* + * Typed slice type. + * + * Note, we should check if we can replace this using std::span (C++20). + */ +template +class Slice +{ + T *m_data; + size_t m_count; + +public: + + __pmq_artificial_method + T *data() const + { + return m_data; + } + + __pmq_artificial_method + size_t count() const + { + return m_count; + } + + __pmq_artificial_method + size_t size_in_bytes() const + { + return m_count * sizeof (T); + } + + __pmq_artificial_method + T get(size_t index) const + { + assert(index < m_count); + return m_data[index]; + } + + __pmq_artificial_method + T& at(size_t index) + { + assert(index < m_count); + return m_data[index]; + } + + __pmq_artificial_method + T const& at(size_t index) const + { + assert(index < m_count); + return m_data[index]; + } + + __pmq_artificial_method + Untyped_Slice untyped() const + { + return Untyped_Slice(m_data, m_count * sizeof (T)); + } + + __pmq_artificial_method + Slice slice_from(size_t start_index) + { + assert(start_index <= m_count); + return Slice(m_data + start_index, m_count - start_index); + } + + __pmq_artificial_method + Slice slice_to(size_t count) + { + assert(count <= m_count); + return Slice(m_data, count); + } + + __pmq_artificial_method + Slice sub_slice(size_t start_index, size_t count) + { + return slice_from(start_index).slice_to(count); + } + + __pmq_artificial_method + Slice() + { + m_data = nullptr; + m_count = 0; + } + + __pmq_artificial_method + Slice(T *data, size_t count) + { + m_data = data; + m_count = count; + } +}; + +template +__pmq_artificial_func +void copy_to_slice(Slice slice, const void *data, size_t size) +{ + assert(slice.size_in_bytes() >= size); + memcpy(slice.data(), data, size); +} + +template +__pmq_artificial_func +void copy_from_slice(void *data, Slice slice, size_t size) +{ + assert(slice.size_in_bytes() >= size); + memcpy(data, slice.data(), size); +} + + + +// A reference type, which wraps a bare pointer. The semantics are the same as +// pointer but we don't allow indexing. In other words, the point of this class +// is to make clear that it doesn't point to an array but only to a single +// (potentially null) object. +// In contrast to C++ reference types (T& value), no surprises given value +// syntax but pointer semantics. + +template +class Pointer +{ + T *m_ptr; + +public: + + __pmq_artificial_method + T *ptr() const + { + return m_ptr; + } + + __pmq_artificial_method + const T *const_ptr() const + { + return m_ptr; + } + + __pmq_artificial_method + Pointer as_const() const + { + return Pointer(m_ptr); + } + + __pmq_artificial_method + T *operator->() + { + return m_ptr; + } + + __pmq_artificial_method + Pointer(T *ptr) + { + assert(ptr); + m_ptr = ptr; + } +}; diff --git a/meta/source/pmq/pmq_common.hpp b/meta/source/pmq/pmq_common.hpp new file mode 100644 index 0000000..75c4b66 --- /dev/null +++ b/meta/source/pmq/pmq_common.hpp @@ -0,0 +1,851 @@ +#pragma once + +#include // std::bad_alloc + +#include "pmq_base.hpp" +#include "pmq_logging.hpp" +#include "pmq_posix_io.hpp" +#include "pmq_profiling.hpp" + +#include +#include +#include +#include +#include + +// +// Simple allocating slice class with delayed allocation. +// +// Why don't I just use std::vector or similar? After all, std::vector is a +// well known standard solution that allocates a contiguous buffer of memory. +// +// I understand this concern, but I've written several simple classes anyway. +// Let me try and defend this case of "NIH". (It may or may not convince the +// reader). +// +// This code is much more straightforward and simple compared to STL headers. +// It is basically "new" and "delete" wrapped in a simple package together with +// operator[] and a way to get a slice to the memory without attached lifetime +// semantics. +// +// Most of the STL classes try to be very generic solutions applicable in a +// wide variety of use cases. While using with standardized solutions has the +// advantages of familiarity, this flexibility and wide applicability comes +// with a complexity cost that brings a disadvantage to anyone working with the +// codebase. +// +// Beyond having a fill level separate from allocation size (size() vs +// capacity()), std::vector has all sorts of methods and functionality to +// support pushing, popping, emplacing, iterators, constructors, destructors, +// and so on. It is highly flexible, which shows whenever an actual +// instanciated vector type is printed on the terminal, including template +// allocator parameter amongst to other things. +// +// All this is ill-fitting for our simple use case. For a queue we just need a +// few preallocated buffers. Just for convenience and to get a little safety, +// the Alloc_Slice wrapper class was created -- so we can do bounds checking +// and get to automatically deallocate the buffers in the destructor. +// +// The size() field and associated semantics that come with std::vector are +// baggage that we can't make use of (we have multiple cursors that wrap around +// our buffers in circular fashion). These semantics are not just available, +// but are understood by programmers as how std::vector gets used. +// +// From a mere functionality standpoint this shouldn't be an issue -- We could +// make sure that we call .resize(N) only once in the beginning and never call +// e.g. push_back(), emplace_back(), reserve(), or similar. This way we'd +// essentially be considering the size() as a constant i.e. ignore it. +// +// However, again, this usage of the type is not guaranteed. The sight of a +// std::vector normally suggest pushing (maybe popping), resizing and +// reserving, buffer reallocation, pointer/iterator invalidation, and runtime +// exceptions. +// +// With the Alloc_Slice class on the other hand, there is no reallocation and +// consequently no iterator invalidation. Exceptions might or might not happen +// depending on compile settings -- but only at construction time, i.e. program +// startup. Because no reallocations are possible, no pointer invalidation / +// iterator invalidation is possible. +// +// Compared to std::vector and other STL headers, significantly less header +// code gets included, so the code compiles quicker. How much quicker? In a +// simple test with a single file, adding any of vector, string, map etc. +// added around 100ms of compilation time (each). I believe I've seen much worse, +// but just multiply 100-400ms by the number of files in a large project and +// there may be a good argument for avoiding to include STL headers based on +// build time. (TODO: refer to example program). +// +// In fairness, this problem may be partially solved with precompiled headers, +// but those come with some issues too. (build setup, pollution, still have to +// compile on each rebuild or precompiled header change). +// +// With the Alloc_Slice class, methods like operator[] have been marked as +// "artificial", meaning it's easier to debug code without jumping all over the +// place. With std::vector and similar classes, I believe there is no way, or +// no standardized way, to build such that we don't jump around files like wild +// when debugging. +// +// If these arguments haven't been convincing, I'll end it now anyway -- the +// text is already much bigger than the actual code. + +template +class Alloc_Slice +{ + T *m_ptr = nullptr; + size_t m_capacity = 0; + +public: + + __pmq_artificial_method + T& operator[](size_t i) const + { + return m_ptr[i]; + } + + __pmq_artificial_method + T *data() const + { + return m_ptr; + } + + __pmq_artificial_method + size_t capacity() const + { + return m_capacity; + } + + __pmq_artificial_method + Slice slice() const + { + return Slice(m_ptr, m_capacity); + } + + __pmq_artificial_method + Untyped_Slice untyped_slice() const + { + return slice().untyped(); + } + + void allocate(size_t capacity) + { + assert(! m_ptr); + m_ptr = new T[capacity]; + m_capacity = capacity; + } + + ~Alloc_Slice() + { + delete[] m_ptr; + } +}; + + +// Posix_FD +// +// Simple file-descriptor holder. The only important purpose is automatically +// closing the fd in the destructor. Setting the fd can happen in the +// constructor or be delayed. A new fd can be set after closing the old one. +// The fd can be retrieved using the .get() method. There are no other methods +// defined, the point here is not to make an abstraction over FDs but just to +// auto-close it. +// +// There is not much more to say. A concern was brought up was that it would be +// better to use an existing class. Again, it's important to note that we're +// not trying to add some (probably ill-defined) abstraction. The fact that +// this class stores fds is not hidden and there isn't any I/O functionality +// contained. +// +// Given this, I wasn't sure what existing class to use that does the same +// thing. This Posix_FD class was quick and easy to write and I hope it is easy +// to read too. +// +// Another concern was that we shouldn't use close() directly here, but instead +// use an abstraction (from an existing library) that papers over platform +// differences such that the code can work on e.g. Windows too. (Windows has a +// Poxix FS layer as well but the code probably wouldn't work without extra +// work and handling of subtle differences). +// +// I can understand this concern, however BeeGFS can not be easily ported to +// e.g. Windows anyway, and this has never been a declared goal of the project. +// BeeGFS currently can't build on Windows and probably never will. +// +// The usage code currently makes non-trivial use of advanced POSIX and Linux +// functions, such as openat(), fsync(), mmap(), pread(), pwrite(). sendfile() +// was used earlier, and might come back. We rely on Posix file permissions +// too, and on certain semantics like for example O_CREAT | O_EXCL during file +// creation. +// +// I'm not aware of a better API that is more portable while providing the same +// functionality. +// +// Also, papering over platform differences may be harder than it initially +// sounds as soon as good performance and thus good control and good error +// handling is a requirement. To be portable, special handling of platform +// idiosyncracies might be required, and the architecture would have to change +// anyway: away from synchronous function calls which would make the +// abstraction leak into the core code, and towards a more asynchronous model +// that is better decoupled from the core code. +// +// It was proposed that std::ifstream / std::ofstream (or similar standardized +// class) could be used instead. std::ifstream in particular would be a bad fit +// since it is a very generic class that comes with buffering and formatting by +// default. I can't easily see how to replace the calls I listed above using +// std::ifstream. Event if it's possible, the result may be more complicated / +// require use of the underlying Posix FD anyway / be less clear / be more code +// / require to give up some control over syscalls etc. ifstream uses +// exceptions and has facilities such as formatting that aren't needed, but the +// presence of this attached functionality would make the purpose less clear +// IMO. +// + +class Posix_FD +{ + int m_fd = -1; + +public: + + __pmq_artificial_method + int get() + { + return m_fd; + } + + __pmq_artificial_method + bool valid() + { + return m_fd != -1; + } + + int close_fd() + { + int ret = 0; + if (m_fd != -1) + { + ret = close(m_fd); + m_fd = -1; + } + return ret; + } + + __pmq_artificial_method + void set(int fd) + { + assert(m_fd == -1); + m_fd = fd; + } + + __pmq_artificial_method + void operator=(int fd) + { + set(fd); + } + + __pmq_artificial_method + Posix_FD() + { + } + + __pmq_artificial_method + Posix_FD(int fd) + { + set(fd); + } + + __pmq_artificial_method + ~Posix_FD() + { + close_fd(); + } +}; + + +// +// Libc_DIR +// +// Similar to Posix_FD, but for libc DIR * handles. Same rationale for why I've +// written this applies as for Posix_FD. +// +// This class is currently not used so could be removed. +// + +class Libc_DIR +{ + DIR *m_dir = nullptr; + + public: + + __pmq_artificial_method + bool valid() + { + return m_dir != nullptr; + } + + __pmq_artificial_method + DIR *get() + { + return m_dir; + } + + __pmq_artificial_method + void set(DIR *dir) + { + assert(m_dir == nullptr); + m_dir = dir; + } + + void close_dir() + { + if (m_dir) + { + closedir(m_dir); + m_dir = nullptr; + } + } + + __pmq_artificial_method + void operator=(DIR *dir) + { + set(dir); + } + + __pmq_artificial_method + Libc_DIR() + { + } + + __pmq_artificial_method + Libc_DIR(DIR *dir) + { + m_dir = dir; + } + + __pmq_artificial_method + ~Libc_DIR() + { + close_dir(); + } +}; + +// +// Mmap_Region +// +// Similar to Posix_FD, but for memory mappings. +// +// On destruction, unmaps the mapped region using munmap(). +// + +class MMap_Region +{ + void *m_ptr = MAP_FAILED; + size_t m_length = 0; + +public: + + __pmq_artificial_method + void *get() const + { + return m_ptr; + } + + __pmq_artificial_method + Untyped_Slice untyped_slice() const + { + return Untyped_Slice(m_ptr, m_length); + } + + __pmq_artificial_method + bool valid() + { + return m_ptr != MAP_FAILED; + } + + void close_mapping() + { + if (m_ptr != MAP_FAILED) + { + if (munmap(m_ptr, m_length) == -1) + { + // should not happen. Simply printing the error for now + pmq_perr_ef(errno, "WARNING: munmap() failed"); + } + m_ptr = MAP_FAILED; + m_length = 0; + } + } + + // like mmap but returns whether successful + bool create(void *addr, size_t newlength, int prot, int flags, + int fd, off_t offset) + { + assert(m_ptr == MAP_FAILED); + void *newptr = mmap(addr, newlength, prot, flags, fd, offset); + if (newptr == MAP_FAILED) + return false; + m_ptr = newptr; + m_length = newlength; + return true; + } + + __pmq_artificial_method + ~MMap_Region() + { + close_mapping(); + } +}; + + +// Mutex_Protected +// +// Simple wrapper class that protects a data item with a mutex. +// The load() and store() mutex implement thread-synchronized read and write +// access to the data item by locking the resource with a mutex during the +// operation. +// +// A class like Folly::Synchronized might replace this. But again, this was +// very easy to write and is extremely small. Pulling in a large dependency +// just for that might not be justified. Also, having our own class allows +// choosing the mutex type. For example, if we want to profile mutexes using +// the Tracy frame profiler, we need to use Tracy's mutex wrappers (here, +// hidden in the PMQ_PROFILED_MUTEX wrapper). While Folly::Synchronized supports +// custom mutexes, one would need to understand and impleemnt "the extended +// protocol implemented in folly/synchronized/Lock.h". +// +// Upon quick browsing of the 1000 lines in Lock.h, it isn't immediately clear +// what that protocol entails and how much work it would be (if any) to wrap +// our own mutex type (which is potentially a wrap of std::mutex already) to +// conform to that protocol. +// +// Maybe there is something in the C++ standard that is suited as a +// replacement? +// +// Maybe there is, but I consider it much easier to just write 2 methods +// totalling 4 straightforward lines of code... +// + +template +class Mutex_Protected +{ + PMQ_PROFILED_MUTEX(m_mutex); + T m_value; + +public: + + void store(T value) + { + PMQ_PROFILED_LOCK(lock_, m_mutex); + m_value = value; + } + + T load() + { + PMQ_PROFILED_LOCK(lock_, m_mutex); + return m_value; + } +}; + + +/* + * String "slice" that can be passed around. No lifetime semantics or + * unexpected copying etc. + * + * We could use std::string_view instead, but that is a templated type. The + * idea of PMQ_String is to wrap just a char-pointer with a size, and nothing + * more, to have a package that one can ship around. We mostly use strings for + * printf-style formatting and to open files, and we don't need or want any + * more complicated semantics than that. + */ +struct PMQ_String +{ + const char *buffer; + size_t size; +}; + +/* + * Simple string "holder" class that allocates and frees its buffer. The + * contained string is immutable once constructed. But a new one can be + * "swapped" in by dropping the old string and creating a new one. + * + * Is this a case of NIH when there is std::string? Maybe, but basically the + * same arguments as for Alloc_Slice and the other classes above apply. + * + * std::string + * + * - is somewhat slow to compile + * - Unexpected allocations / copies (and thus exceptions as well) can happen + * very easily, without anyone noticing -- For example, it's as easy as + * writing "auto x = y" instead of "auto& x = y". + * - Apart from exceptions and copies / resizes, appending, there is more + * complexity that we don't need and don't want and that would actually be a + * misfit for our project. Ugly error messages with huge types (... + * std::basic_char ... etc.) is only a small symptom of this. + */ +class PMQ_Owned_String +{ + PMQ_String m_string = {}; + +public: + + bool valid() const + { + return m_string.buffer != nullptr; + } + + __pmq_artificial_method + PMQ_String get() const + { + return m_string; + } + + void drop() + { + // Checking only for clarity. free() and the rest of the code would work + // with a null buffer too. + if (m_string.buffer != nullptr) + { + free((void *) m_string.buffer); + m_string.buffer = nullptr; + m_string.size = 0; + } + } + + void set(const char *buffer) + { + assert(! m_string.buffer); + char *copy = strdup(buffer); + if (copy == nullptr) + { + // is an exception what we want / need? + throw std::bad_alloc(); + } + m_string.buffer = copy; + m_string.size = strlen(buffer); + } + + __pmq_artificial_method + PMQ_Owned_String() + { + m_string.buffer = nullptr; + m_string.size = 0; + } + + ~PMQ_Owned_String() + { + drop(); + } +}; + + + +/* + * SNs (sequence numbers) + * + * Sequence numbers, and the ringbuffers that build on them, are a core concept + * of how the PMQ works. + * + * I believe they are pretty much what is elsewhere known as "LMAX Disruptor" + * (google it). + * + * Sequence numbers are 64-bit unsigned integers that can wraparound (but this + * is only theoretical -- wraparound is probably completely untested since + * 64-bit numbers don't overflow easily in practice). + * + * Ringbuffers have a number of slots that is 2^N for some N. SN's are mapped + * to slots with wrap-around in the ringbuffer's 2^N slots by using the lowest + * N bits of the SN to index into the slots array. + * + * The SN templated class provides some type safety -- the Tag type is a + * "phantom tag" (can be implemented by making a new "empty" class) that + * prevents indexing into a ringbuffer using a mismatching sequence number. For + * example, we have a ringbuffer of input-slots that should be indexed by *slot + * sequence numbers* (SSNs). And we have a ringbuffer of chunks that should be + * indexed by *chunk sequence numbers (CSNs). The on-disk chunk store is + * another kind of ringbuffer that works with the same principle of wrapping + * around automatically. + * + * We also track *message sequence numbers* (MSNs) but we don't use them for + * indexing, only for binary search. + * + * Mathematically, SNs form an affine space. This is like a vector space but + * without a designated origin (pls forgive me if what I write here is slightly + * incorrect as far as mathematics is concerned. Only the idea matters). There + * is a 0 value, but it is not meaningfully different compared to any other + * value. + * + * One can subtract two sequence numbers to get a distance (represented as bare + * uint64_t), and one can add a distance to a sequence number to get a new + * sequence number. However, unlike a vector space with designated 0, one can + * not add two sequence numbers meaningfully (SN has operator+(uint64_t d) + * but no operator+(SN& other). + */ + +template +class SN +{ + uint64_t m_value; + +public: + + explicit SN(uint64_t value) + { + m_value = value; + } + + // Some C++ trivia following. In most cases you can ignore this and just use + // the class similar to primitive integers. + // + // Here we specify an *explicitly-defaulted default-constructor*. This will + // allow us to initialize the object with undefined (garbage) value if we + // want so. + // + // Explanation: Since we have explicitly specified the constructor with 1 + // argument already, there wouldn't be an implicit default constructor (a + // constructor with no arguments). To get a default constructor, we need to + // explicitly specify one. We need a default constructor (no constructor + // arguments) if we want to write + // + // SN sn; + // + // For simple data types (like SN), we typically want the above line to + // leave the object's members uninitialized (garbage values). While this is + // in some ways dangerous, it can be simpler especially for objects where + // zero-initialization isn't very convenient or meaningful. Leaving values + // uninitialized in the default constructor also allows the compiler to + // catch bugs in some situations when the user unintentionally forgot to + // specify an explicit value. + // + // Note a gotcha: There is a difference between an empty default constructor + // + // SN() {} + // + // and an (explicitly or implicitly) defaulted default constructor: + // + // SN() = default; + // + // If we use the class like this: + // + // SN x {}; + // SN y = SN(); // or like this + // SN z = {}; // or like this... + // + // then x will contain garbarge with the empty default constructor, but will + // be zero-initialized with the (explicitly-) defaulted default constructor. + // We'd typically want zero initialization with this syntax. + + SN() = default; + + __pmq_artificial_method + uint64_t value() const + { + return m_value; + } + + __pmq_artificial_method + void operator++() + { + m_value++; + } + + __pmq_artificial_method + void operator++(int) + { + m_value++; + } + + __pmq_artificial_method + SN operator+=(uint64_t d) + { + m_value += d; + return *this; + } + + __pmq_artificial_method + SN& operator-=(uint64_t d) + { + m_value -= d; + return *this; + } + + __pmq_artificial_method + SN operator+(uint64_t d) const + { + return SN(m_value + d); + } + + __pmq_artificial_method + SN operator-(uint64_t d) const + { + return SN(m_value - d); + } + + __pmq_artificial_method + uint64_t operator-(SN other) const + { + return m_value - other.m_value; + } + + __pmq_artificial_method + bool operator==(SN other) const + { + return m_value == other.m_value; + } + + __pmq_artificial_method + bool operator!=(SN other) const + { + return m_value != other.m_value; + } +}; + +/* + * COMPARING SEQUENCE NUMBERS + * ========================== + * + * Since sequence numbers wrap around (in theory, when 64 bits overflow) they + * have no natural ordering. + * + * However, in practice, sequence numbers are used to index in much smaller + * buffer, and at any given time there is only a small window of sequence + * numbers. It's a sliding window, but a window still. + * + * So, admitting that the sequence numbers in a given window may wraparound, + * back to 0, we can still assume that they never "overtake" each other. + * We can subtract two numbers using unsigned arithmetic and determine their + * relative ordering from the result. Centering our worldview at a number x, we + * divide the space of uint64_t numbers into those that are less than x (x - + * 2^63 to x) and those that are greater than x (x to 2^63). + * + * Note that this relation is not transitive (x <= y && y <= z does not imply x + * <= z), and not antisymmetric -- (x + 2^63) is both greater and less than x. + * So it's not a true ordering relation, but in practice we can use it to + * reliably compare items by "age". + * + * The value 1 should be considered greater than UINT64_MAX, since 1 - + * UINT64_MAX == 2. Conversely, UINT64_MAX is less than 1 since UINT64_MAX - 1 + * equals (UINT64_MAX - 1), which is a. + * + */ + + +// Comparing bare uint64_t sequence values. + +__pmq_artificial_func +bool _sn64_lt(uint64_t a, uint64_t b) +{ + return b - (a + 1) <= UINT64_MAX / 2; +} + +__pmq_artificial_func +bool _sn64_le(uint64_t a, uint64_t b) +{ + return b - a <= UINT64_MAX / 2; +} + +__pmq_artificial_func +bool _sn64_ge(uint64_t a, uint64_t b) +{ + return a - b <= UINT64_MAX / 2; +} + +__pmq_artificial_func +bool _sn64_gt(uint64_t a, uint64_t b) +{ + return a - (b + 1) <= UINT64_MAX / 2; +} + + + +// Comparing type-safe "tagged" SN values + +template +__pmq_artificial_func +bool sn64_lt(SN a, SN b) +{ + return b - (a + 1) <= UINT64_MAX / 2; +} + +template +__pmq_artificial_func +bool sn64_le(SN a, SN b) +{ + return b - a <= UINT64_MAX / 2; +} + +template +__pmq_artificial_func +bool sn64_ge(SN a, SN b) +{ + return a - b <= UINT64_MAX / 2; +} + +template +__pmq_artificial_func +bool sn64_gt(SN a, SN b) +{ + return a - (b + 1) <= UINT64_MAX / 2; +} + +template +__pmq_artificial_func +bool sn64_inrange(SN sn, SN lo, SN hi) +{ + return sn - lo <= hi - lo; +} + + + +// Ringbuffer containing a buffer (power-of-2 size) of element of type V. It +// can be "indexed" using SN's of matching type. + +template +class Ringbuffer +{ + using K = SN; + + V *m_ptr = nullptr; + size_t m_count = 0; + +public: + + __pmq_artificial_method + uint64_t slot_count() const + { + return m_count; + } + + __pmq_artificial_method + void reset(Slice slice) + { + assert(pmq_is_power_of_2(slice.count())); + m_ptr = slice.data(); + m_count = slice.count(); + } + + __pmq_artificial_method + Slice as_slice() const + { + return Slice(m_ptr, m_count); + } + + __pmq_artificial_method + const V *get_slot_for(K k) const + { + return &m_ptr[k.value() & (m_count - 1)]; + } + + __pmq_artificial_method + V *get_slot_for(K k) + { + return &m_ptr[k.value() & (m_count - 1)]; + } + + __pmq_artificial_method + Ringbuffer() + { + } + + __pmq_artificial_method + Ringbuffer(V *ptr, uint64_t size) + { + reset(ptr, size); + } +}; diff --git a/meta/source/pmq/pmq_logging.cpp b/meta/source/pmq/pmq_logging.cpp new file mode 100644 index 0000000..4121a33 --- /dev/null +++ b/meta/source/pmq/pmq_logging.cpp @@ -0,0 +1,241 @@ +#include "pmq_logging.hpp" +#include "pmq_common.hpp" + +#include +#include + + +// The logging module can either print to stderr or use the BeeGFS metadata +// server's logging backend. + +#ifdef PMQ_TEST +# ifndef PMQ_LOG_LEVEL +# error PMQ_LOG_LEVEL must be defined when compiling test case +# endif +#define INTEGRATE_WITH_METADATA_SERVER 0 +#else +#define INTEGRATE_WITH_METADATA_SERVER 1 +#endif + + +#if INTEGRATE_WITH_METADATA_SERVER +// Integrate into metadata server +#include +#endif + +struct Log_Buffer +{ + PMQ_PROFILED_MUTEX(mutex); + PMQ_PROFILED_CONDVAR(writeable); // reader => writer + PMQ_PROFILED_CONDVAR(readable); // writer => reader. Corresponding mutex is in Log_Message + Alloc_Slice msgs; + size_t capacity = 0; + size_t writepos = 0; + size_t readpos = 0; + + Log_Buffer() + { + // TODO: this costs a lot of memory. However, a previous setting of 64 + // wasn't enough for high-frequency logging. We need more dynamic and + // judicious memory allocation. + capacity = 1024; + msgs.allocate(capacity); + } +}; + +static void log_buffer_write(Log_Buffer *logbuf, Log_Message const *input) +{ + PMQ_PROFILED_UNIQUE_LOCK(lock, logbuf->mutex); + while (logbuf->writepos - logbuf->readpos == logbuf->capacity) + logbuf->writeable.wait(lock); + size_t mask = logbuf->capacity - 1; + size_t pos = logbuf->writepos; + Log_Message *msg = &logbuf->msgs[pos & mask]; + msg->size = input->size; + memcpy(msg->data, input->data, input->size); + ++ logbuf->writepos; + // hoping that this is cheap: otherwise we should track the number of + // readers and check it before calling notify_one() + logbuf->readable.notify_one(); +} + +static void _log_buffer_read(Log_Buffer *logbuf, Log_Message *output) +{ + size_t mask = logbuf->capacity - 1; + size_t pos = logbuf->readpos; + Log_Message *msg = &logbuf->msgs[pos & mask]; + output->size = msg->size; + memcpy(output->data, msg->data, msg->size); + ++ logbuf->readpos; + // hoping that this is cheap: otherwise we should track the number of + // writers and check it before calling notify_one() + logbuf->writeable.notify_one(); +} + +static void log_buffer_read(Log_Buffer *logbuf, Log_Message *output) +{ + PMQ_PROFILED_UNIQUE_LOCK(lock, logbuf->mutex); + while (logbuf->writepos == logbuf->readpos) + logbuf->readable.wait(lock); + _log_buffer_read(logbuf, output); +} + +static bool log_buffer_try_read(Log_Buffer *logbuf, Log_Message *output) +{ + PMQ_PROFILED_LOCK(lock, logbuf->mutex); + if (logbuf->writepos == logbuf->readpos) + return false; + _log_buffer_read(logbuf, output); + return true; +} + +static bool log_buffer_try_read_timeout_millis(Log_Buffer *logbuf, Log_Message *output, int millis) +{ + auto time_point = std::chrono::steady_clock::now() + std::chrono::milliseconds(millis); + PMQ_PROFILED_UNIQUE_LOCK(lock, logbuf->mutex); + while (logbuf->writepos == logbuf->readpos) + { + if (logbuf->readable.wait_until(lock, time_point) == std::cv_status::timeout) + return false; + } + _log_buffer_read(logbuf, output); + return true; +} + + + +static Log_Buffer global_log_buffer; + +void pmq_write_log_message(Log_Message const *input) +{ + log_buffer_write(&global_log_buffer, input); +} + +void pmq_read_log_message(Log_Message *output) +{ + log_buffer_read(&global_log_buffer, output); +} + +bool pmq_try_read_log_message(Log_Message *output) +{ + return log_buffer_try_read(&global_log_buffer, output); +} + +bool pmq_try_read_log_message_timeout_millis(Log_Message *output, int millis) +{ + return log_buffer_try_read_timeout_millis(&global_log_buffer, output, millis); +} + + +void log_msg_printfv(Log_Message *msg, const char *fmt, va_list ap) +{ + int ret = vsnprintf(msg->data + msg->size, sizeof msg->data - 1 - msg->size, fmt, ap); + assert(ret >= 0); + msg->size += (size_t) ret; + if (msg->size > sizeof msg->data - 1) + msg->size = sizeof msg->data - 1; + msg->data[msg->size] = 0; +} + +// Note: this is a method (implicit this pointer) so we use +// __pmq_formatter(2, 3) instead of __pmq_formatter(1, 2). +void __pmq_formatter(2, 3) log_msg_printf(Log_Message *msg, const char *fmt, ...) // NOLINT this is safe because of __pmq_formatter() annotation +{ + va_list ap; + va_start(ap, fmt); + log_msg_printfv(msg, fmt, ap); + va_end(ap); +} + +void pmq_msg_ofv(const PMQ_Msg_Options& opt, const char *fmt, va_list ap) +{ + bool print_errno = (bool) (opt.flags & PMQ_MSG_OPT_ERRNO); + uint32_t priority = opt.flags & PMQ_MSG_OPT_LVL_MASK; + + Log_Message log_msg; + log_msg.size = 0; + +#if INTEGRATE_WITH_METADATA_SERVER + + int metadata_priority = 0; + + switch (priority) + { + case PMQ_MSG_OPT_LVL_DEBUG: metadata_priority = Log_DEBUG; break; + case PMQ_MSG_OPT_LVL_INFO: metadata_priority = Log_NOTICE; break; + case PMQ_MSG_OPT_LVL_WARN: metadata_priority = Log_WARNING; break; + case PMQ_MSG_OPT_LVL_ERR: metadata_priority = Log_ERR; break; + default: assert(0); // can't happen at least currently where log mask has 2 bits. + } + +#else + + // Early return, avoiding most of the work if the message has less priority + // than the log level. + // TODO: we should have something like this for metadata server integration + // too. + if (PMQ_LOG_LEVEL > priority) + return; + + switch (priority) + { + case PMQ_MSG_OPT_LVL_DEBUG: log_msg_printf(&log_msg, "DEBUG: "); break; + case PMQ_MSG_OPT_LVL_INFO: log_msg_printf(&log_msg, "INFO: "); break; + case PMQ_MSG_OPT_LVL_WARN: log_msg_printf(&log_msg, "WARNING: "); break; + case PMQ_MSG_OPT_LVL_ERR: log_msg_printf(&log_msg, "ERROR: "); break; + default: assert(0); // can't happen at least currently where log mask has 2 bits. + } + +#endif + + log_msg_printfv(&log_msg, fmt, ap); + + if (print_errno) + { + char errbuf[64]; + const char *errstr; + +#if (_POSIX_C_SOURCE >= 200112L) && ! _GNU_SOURCE + { + // XSI compliant strerror_r() + int ret = strerror_r(opt.errnum, errbuf, sizeof errbuf); + if (ret == 0) + errstr = errbuf; + } +#else + { + // GNU version of strerror_r() + errstr = strerror_r(opt.errnum, errbuf, sizeof errbuf); + } +#endif + if (! errstr) + { + snprintf(errbuf, sizeof errbuf, "(errno=%d)", opt.errnum); + errstr = errbuf; + } + + log_msg_printf(&log_msg, ": %s", errstr); + } + +#if INTEGRATE_WITH_METADATA_SERVER + // Integration into metadata server + Logger *logger = Logger::getLogger(); + logger->log(LogTopic_EVENTLOGGER, metadata_priority, opt.loc.file, opt.loc.line, log_msg.data); +#else + + log_msg_printf(&log_msg, "\n"); + + //fwrite(log_msg.data, log_msg.size, 1, stderr); + + pmq_write_log_message(&log_msg); + +#endif +} + +void __pmq_formatter(2, 3) pmq_msg_of(const PMQ_Msg_Options& opt, const char *fmt, ...) // NOLINT this is safe because of use of __pmq_formatter() annotation +{ + va_list ap; + va_start(ap, fmt); + pmq_msg_ofv(opt, fmt, ap); + va_end(ap); +} diff --git a/meta/source/pmq/pmq_logging.hpp b/meta/source/pmq/pmq_logging.hpp new file mode 100644 index 0000000..73657e7 --- /dev/null +++ b/meta/source/pmq/pmq_logging.hpp @@ -0,0 +1,112 @@ +#pragma once + +#include "pmq_base.hpp" + +enum +{ + PMQ_MSG_OPT_DEFAULT = 0, + PMQ_MSG_OPT_ERRNO = (1 << 0), + PMQ_MSG_HAS_SOURCE_LOC = (1 << 1), + + PMQ_MSG_OPT_LVL_MASK = (3 << 2), // bits 3 and 4 for debug level. + PMQ_MSG_OPT_LVL_DEBUG = (0 << 2), + PMQ_MSG_OPT_LVL_INFO = (1 << 2), + PMQ_MSG_OPT_LVL_WARN = (2 << 2), + PMQ_MSG_OPT_LVL_ERR = (3 << 2), +}; + +struct PMQ_Source_Loc +{ + const char *file; + uint32_t line; +}; + +struct PMQ_Msg_Options +{ + PMQ_Source_Loc loc; + uint32_t flags; // PMQ_MSG_OPT_ + int errnum; +}; + + +// Logging functions / macros. +// +// The following functions are typically used in the client code. +// +// pmq_msg_f(fmt, ...): Submit a log message with default log level and format string + var-args +// pmq_perr_f(fmt, ...): Submit a log message with error log level and format string + var-args +// pmq_perr_ef(errno, fmt, ...): like pmq_perr_f() but also add text for given system error code ("errno") +// +// To explain all functions available here: they are made according to a pattern +// +// pmq_{LVL}_{MNEMNONICS} +// +// LVL: logging level, possible options +// - msg: Default level or use specified level ('l' mnemnonic) +// - debug: Debug level +// - warn: Warning level +// - perr: Error level ("print-error") +// +// MNEMNONICS: combination of 1-letter chars +// - l: means a logging level is specified (only available with 'msg' category) +// - e: add a text for specified system error code ("errno") +// - f: "format", like in the stdio function printf(). +// - v: in combination with f (so 'fv'), means the arguments come as a va_list, like in stdio function vfprintf(). +// - o: Use a single options struct holding level, errno explictly, as well as source code location info. + + +void pmq_msg_ofv(const PMQ_Msg_Options& opt, const char *fmt, va_list ap); + +void __pmq_formatter(2, 3) pmq_msg_of(const PMQ_Msg_Options& opt, const char *fmt, ...); + +#define PMQ_SOURCE_LOC ((PMQ_Source_Loc) { __FILE__, __LINE__ }) + +#define PMQ_MSG_OPTIONS(...) (PMQ_Msg_Options { PMQ_SOURCE_LOC, ##__VA_ARGS__ }) + +#define pmq_msg_lf(lvl, fmt, ...) \ + pmq_msg_of(PMQ_MSG_OPTIONS((lvl), 0), fmt, ##__VA_ARGS__) + +#define pmq_msg_lef(lvl, e, fmt, ...) \ + pmq_msg_of(PMQ_MSG_OPTIONS(PMQ_MSG_OPT_ERRNO | (lvl), e), fmt, ##__VA_ARGS__) + +#define pmq_msg_f(fmt, ...) \ + pmq_msg_lf(PMQ_MSG_OPT_LVL_INFO, fmt, ##__VA_ARGS__) + +#define pmq_msg_ef(e, fmt, ...) \ + pmq_msg_lef(PMQ_MSG_OPT_LVL_INFO, (e), fmt, ##__VA_ARGS__) + +#define pmq_debug_f(fmt, ...) \ + pmq_msg_lf(PMQ_MSG_OPT_LVL_DEBUG, fmt, ##__VA_ARGS__) + +#define pmq_debug_ef(e, fmt, ...) \ + pmq_msg_lef(PMQ_MSG_OPT_LVL_DEBUG, (e), fmt, ##__VA_ARGS__) + +#define pmq_warn_f(fmt, ...) \ + pmq_msg_lf(PMQ_MSG_OPT_LVL_WARN, fmt, ##__VA_ARGS__) + +#define pmq_warn_ef(e, fmt, ...) \ + pmq_msg_lef(PMQ_MSG_OPT_LVL_WARN, (e), fmt, ##__VA_ARGS__) + +#define pmq_perr_f(fmt, ...) \ + pmq_msg_lf(PMQ_MSG_OPT_LVL_ERR, fmt, ##__VA_ARGS__) + +#define pmq_perr_ef(e, fmt, ...) \ + pmq_msg_lef(PMQ_MSG_OPT_LVL_ERR, (e), fmt, ##__VA_ARGS__) + + + + + + +// Low-level Logging I/O interface + +struct Log_Message +{ + size_t size; + char data[256 - sizeof (size_t)]; // for simplicity +}; + +void pmq_write_log_message(Log_Message const *input); +void pmq_read_log_message(Log_Message *output); +bool pmq_try_read_log_message_timeout_millis(Log_Message *output, int millis); +bool pmq_try_read_log_message(Log_Message *output); diff --git a/meta/source/pmq/pmq_posix_io.hpp b/meta/source/pmq/pmq_posix_io.hpp new file mode 100644 index 0000000..9c2d591 --- /dev/null +++ b/meta/source/pmq/pmq_posix_io.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "pmq_logging.hpp" + +/* Wrapper around open() to open directories to put this awkward code in a + * central place. It is counter-intuitive but this is apparently how you're + * supposed to open directories on Unix, both the reading and "writing" (i.e. + * create, unlink, rename). + * The O_DIRECTORY flag is optional but the O_RDONLY is not; opening with + * O_RDWR | O_DIRECTORY fails with "is a directory" (weird!). + * + * Returns: fd to open directory or -1, in which case the errno variable must + * be handled as usual. + */ +static inline int pmq_open_dir(const char *path) +{ + return open(path, O_RDONLY | O_DIRECTORY); +} + +static inline int pmq_check_regular_file(int fd, const char *what_file) +{ + struct stat st; + + if (fstat(fd, &st) == -1) + { + pmq_perr_ef(errno, "Failed to fstat() the fd we opened"); + return false; + } + + if (! S_ISREG(st.st_mode)) + { + pmq_perr_f("We opened the file '%s' expecting a regular file but it's not", + what_file); + return false; + } + + return true; +} + +// Note: returns an fd >= 0 if successful. +// On failure, -1 is returned and +// - if the file failed to open, errno indicates why the file failed to open. +// - if the file was opened successfuly but then closed again because it was not +// a regular file, errno is set to 0. +static inline int pmq_openat_regular_existing( + int basedir_fd, const char *relpath, int flags) +{ + // only access mode may be specified -- no other flags + // In particular, O_CREAT would break the logic. + assert(flags == O_RDWR || flags == O_RDONLY || flags == O_WRONLY); + + int fd = openat(basedir_fd, relpath, flags, 0); + + if (fd == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to openat() existing file='%s', flags=%x", + relpath, flags); + errno = e; + return fd; + } + + /* The case where fd refers to something other than a regular file _may_ + * have been caught by the kernel already above. For example, opening a + * directory using O_RDWR will fail. On the other hand, opening a directory + * using O_RDONLY will succeed. + * In any case, doing an explicit check here. + */ + if (! pmq_check_regular_file(fd, relpath)) + { + close(fd); + errno = 0; + return -1; + } + + return fd; +} + +static inline int pmq_openat_regular_create( + int basedir_fd, const char *relpath, int flags, mode_t mode) +{ + // only access mode may be specified -- no other flags + assert(flags == O_RDWR || flags == O_RDONLY || flags == O_WRONLY); + + // But this func makes sure that the creation-flags are specified + flags |= O_CREAT | O_EXCL; + + int fd = openat(basedir_fd, relpath, flags, mode); + + if (fd == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to openat() file='%s', flags=%x, mode=%o", + relpath, flags, (unsigned) mode); + errno = e; + return fd; + } + + // all necessarily error handling should be done by the OS. (note O_EXCL) + + return fd; +} + +__pmq_artificial_func +void assert_sane_size(size_t size) +{ + // check that size is representable as a ssize_t too. + // It is implementation defined how syscalls like write() handle write I/O sizes larger than SSIZE_T. + // So better don't even try to. + + assert((size_t) (ssize_t) size == size); +} + +static inline bool pmq_write_all(int fd, Untyped_Slice slice, const char *what) +{ + assert_sane_size(slice.size()); + + while (slice.size()) + { + ssize_t nw = write(fd, slice.data(), slice.size()); + + if (nw == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to write %zu bytes to %s", + slice.size(), what); + errno = e; + return false; + } + + slice = slice.offset_bytes((size_t) nw); + } + + return true; +} + +static inline bool pmq_pwrite_all(int fd, Untyped_Slice slice, off_t offset, const char *what) +{ + assert_sane_size(slice.size()); + + while (slice.size()) + { + ssize_t nw = pwrite(fd, slice.data(), slice.size(), offset); + + if (nw == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to pwrite() %zu bytes at offset %jd to %s", + slice.size(), (intmax_t) offset, what); + errno = e; + return false; + } + + slice = slice.offset_bytes((size_t) nw); + offset += (size_t) nw; + } + + return true; +} + +static inline bool pmq_read_all(int fd, Untyped_Slice slice, const char *what) +{ + assert_sane_size(slice.size()); + + while (slice.size()) + { + ssize_t nw = read(fd, slice.data(), slice.size()); + + if (nw == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to read %zu bytes from %s", + slice.size(), what); + errno = e; + return false; + } + + slice = slice.offset_bytes((size_t) nw); + } + + return true; +} + +static inline bool pmq_pread_all(int fd, Untyped_Slice slice, off_t offset, const char *what) +{ + assert_sane_size(slice.size()); + + while (slice.size()) + { + ssize_t nw = pread(fd, slice.data(), slice.size(), offset); + + if (nw == -1) + { + int e = errno; + pmq_perr_ef(errno, "Failed to pread() %zu bytes at offset %jd to %s", + slice.size(), (intmax_t) offset, what); + errno = e; + return false; + } + + slice = slice.offset_bytes((size_t) nw); + offset += (size_t) nw; + } + + return true; +} diff --git a/meta/source/pmq/pmq_profiling.hpp b/meta/source/pmq/pmq_profiling.hpp new file mode 100644 index 0000000..ad5997f --- /dev/null +++ b/meta/source/pmq/pmq_profiling.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include + +#include +#include + +#if PMQ_WITH_PROFILING + +// NOTE: this requires building with matching headers in the include-path for +// tracy. Tracy is a "frame profiler": https://github.com/wolfpld/tracy +// If PMQ is built as part of a bigger project (e.g. BeeGFS metadata server) +// buildin with the proper settings may not be supported (yet). +// A build setup that supports tracy currently exists as part of the flex-docs +// repository (ask the BeeGFS team). + +# include + +# define PMQ_PROFILING_CTX FrameMark +# define PMQ_PROFILED_SCOPE(name) ZoneScopedN(name) +# define PMQ_PROFILED_FUNCTION ZoneScoped +# define PMQ_PROFILED_MUTEX(name) TracyLockable(std::mutex, name) +# define PMQ_PROFILED_CONDVAR(name) std::condition_variable_any name +# define PMQ_PROFILED_LOCK(name, themutex) \ + auto& __ref__##name(themutex); \ + std::lock_guard name(__ref__##name); \ + LockMark(__ref__##name) +# define PMQ_PROFILED_UNIQUE_LOCK(name, themutex) \ + auto& __ref__##name(themutex); \ + std::unique_lock name(__ref__##name); \ + LockMark(__ref__##name) \ + +#else + +# define PMQ_PROFILING_CTX +# define PMQ_PROFILED_SCOPE(name) +# define PMQ_PROFILED_FUNCTION +# define PMQ_PROFILED_MUTEX(name) std::mutex name +# define PMQ_PROFILED_CONDVAR(name) std::condition_variable name +# define PMQ_PROFILED_LOCK(name, themutex) \ + std::lock_guard name(themutex) +# define PMQ_PROFILED_UNIQUE_LOCK(name, themutex) \ + std::unique_lock name(themutex) + +#endif diff --git a/meta/source/program/Main.cpp b/meta/source/program/Main.cpp new file mode 100644 index 0000000..e989ceb --- /dev/null +++ b/meta/source/program/Main.cpp @@ -0,0 +1,7 @@ +#include "Program.h" + +int main(int argc, char** argv) +{ + return Program::main(argc, argv); +} + diff --git a/meta/source/program/Program.cpp b/meta/source/program/Program.cpp new file mode 100644 index 0000000..4794b5f --- /dev/null +++ b/meta/source/program/Program.cpp @@ -0,0 +1,23 @@ +#include +#include "Program.h" + +App* Program::app = NULL; + +int Program::main(int argc, char** argv) +{ + BuildTypeTk::checkDebugBuildTypes(); + + AbstractApp::runTimeInitsAndChecks(); // must be called before creating a new App + + app = new App(argc, argv); + + app->startInCurrentThread(); + + int appRes = app->getAppResult(); + + delete app; + + return appRes; +} + + diff --git a/meta/source/program/Program.h b/meta/source/program/Program.h new file mode 100644 index 0000000..ebfa0c1 --- /dev/null +++ b/meta/source/program/Program.h @@ -0,0 +1,30 @@ +#pragma once + +#include + + +/** + * Represents the static program. It creates an App object to represent the running instance of + * the program and provides a getter for the App object. + */ +class Program +{ + public: + static int main(int argc, char** argv); + + + private: + Program() {} + + static App* app; + + + public: + // getters & setters + static App* getApp() + { + return app; + } + +}; + diff --git a/meta/source/session/EntryLock.h b/meta/source/session/EntryLock.h new file mode 100644 index 0000000..b14dd21 --- /dev/null +++ b/meta/source/session/EntryLock.h @@ -0,0 +1,127 @@ +#pragma once + +#include "EntryLockStore.h" + +template +class UniqueEntryLockBase +{ + public: + ~UniqueEntryLockBase() + { + if (lockData) + entryLockStore->unlock(lockData); + } + + UniqueEntryLockBase(const UniqueEntryLockBase&) = delete; + UniqueEntryLockBase& operator=(const UniqueEntryLockBase&) = delete; + + UniqueEntryLockBase(UniqueEntryLockBase&& src) + : entryLockStore(NULL), lockData(NULL) + { + swap(src); + } + + UniqueEntryLockBase& operator=(UniqueEntryLockBase&& src) + { + UniqueEntryLockBase(std::move(src)).swap(*this); + return *this; + } + + void swap(UniqueEntryLockBase& other) + { + std::swap(entryLockStore, other.entryLockStore); + std::swap(lockData, other.lockData); + } + + protected: + typedef UniqueEntryLockBase BaseType; + + template + UniqueEntryLockBase(EntryLockStore* entryLockStore, const ArgsT&... args) + : entryLockStore(entryLockStore) + { + lockData = entryLockStore->lock(args...); + } + + UniqueEntryLockBase() + : entryLockStore(NULL), lockData(NULL) + { + } + + private: + EntryLockStore* entryLockStore; + LockDataT* lockData; +}; + +template +inline void swap(UniqueEntryLockBase& a, UniqueEntryLockBase& b) +{ + a.swap(b); +} + + + +class FileIDLock : UniqueEntryLockBase +{ + public: + FileIDLock() = default; + + FileIDLock(const FileIDLock&) = delete; + FileIDLock& operator=(const FileIDLock&) = delete; + + FileIDLock(FileIDLock&& src) : BaseType(std::move(src)) {} + FileIDLock& operator=(FileIDLock&& src) + { + BaseType::operator=(std::move(src)); + return *this; + } + + FileIDLock(EntryLockStore* entryLockStore, const std::string& fileID, const bool writeLock) + : UniqueEntryLockBase(entryLockStore, fileID, writeLock) + { + } +}; + +class ParentNameLock : UniqueEntryLockBase +{ + public: + ParentNameLock() = default; + + ParentNameLock(const ParentNameLock&) = delete; + ParentNameLock& operator=(const ParentNameLock&) = delete; + + ParentNameLock(ParentNameLock&& src) : BaseType(std::move(src)) {} + ParentNameLock& operator=(ParentNameLock&& src) + { + BaseType::operator=(std::move(src)); + return *this; + } + + ParentNameLock(EntryLockStore* entryLockStore, const std::string& parentID, + const std::string& name) + : UniqueEntryLockBase(entryLockStore, parentID, name) + { + } +}; + +class HashDirLock : UniqueEntryLockBase +{ + public: + HashDirLock() = default; + + HashDirLock(const HashDirLock&) = delete; + HashDirLock& operator=(const HashDirLock&) = delete; + + HashDirLock(HashDirLock&& src) : BaseType(std::move(src)) {} + HashDirLock& operator=(HashDirLock&& src) + { + BaseType::operator=(std::move(src)); + return *this; + } + + HashDirLock(EntryLockStore* entryLockStore, std::pair hashDir) + : UniqueEntryLockBase(entryLockStore, hashDir) + { + } +}; + diff --git a/meta/source/session/EntryLockStore.cpp b/meta/source/session/EntryLockStore.cpp new file mode 100644 index 0000000..90cc65b --- /dev/null +++ b/meta/source/session/EntryLockStore.cpp @@ -0,0 +1,45 @@ +#include "EntryLockStore.h" + +ParentNameLockData* EntryLockStore::lock(const std::string& parentID, const std::string& name) +{ + ParentNameLockData& lock = parentNameLocks.getLockFor( + std::pair(parentID, name) ); + lock.getLock().lock(); + return &lock; +} + +FileIDLockData* EntryLockStore::lock(const std::string& fileID, const bool writeLock) +{ + FileIDLockData& lock = fileLocks.getLockFor(fileID); + if(writeLock) + lock.getLock().writeLock(); + else + lock.getLock().readLock(); + + return &lock; +} + +HashDirLockData* EntryLockStore::lock(std::pair hashDir) +{ + HashDirLockData& lock = hashDirLocks.getLockFor(hashDir); + lock.getLock().lock(); + return &lock; +} + +void EntryLockStore::unlock(ParentNameLockData* parentNameLockData) +{ + parentNameLockData->getLock().unlock(); + parentNameLocks.putLock(*parentNameLockData); +} + +void EntryLockStore::unlock(FileIDLockData* fileIDLockData) +{ + fileIDLockData->getLock().unlock(); + fileLocks.putLock(*fileIDLockData); +} + +void EntryLockStore::unlock(HashDirLockData* hashDirLockData) +{ + hashDirLockData->getLock().unlock(); + hashDirLocks.putLock(*hashDirLockData); +} diff --git a/meta/source/session/EntryLockStore.h b/meta/source/session/EntryLockStore.h new file mode 100644 index 0000000..dad490a --- /dev/null +++ b/meta/source/session/EntryLockStore.h @@ -0,0 +1,231 @@ +#pragma once + +#include +#include +#include +#include + +template +struct ValueLockHash; + + + +// This class implements a hashmap Value -> Lock of size HashSize. To avoid memory allocations +// and deallocations, each bucket may keep up to BufferSize lock objects that are not currently +// used. +// +// Hashes for values are computed by ValueLockHash, so an appropriate specialization of this +// template must exists in order to use this class. +// +// Currently the Lock argument is not restricted in any way, but it is intended to only ever be set +// to types of locking primitives. +// +// This implementation does not use boost::unordered_map because we want a fixed-size table and +// control over the per-bucket value lists, since these lists required memory allocations and +// insert and deallocations on erase. The allocation overhead per list entry could also be +// optimized away by using intrusive lists for bucket contents, but currently the lock buffers +// are sufficient to amortize almost all allocator traffic. +template +class ValueLockStore +{ + private: + struct LockBucket; + + public: + class ValueLock + { + friend class ValueLockStore; + + public: + Lock& getLock() + { + return lock; + } + + private: + Lock lock; + Value value; + LockBucket* bucket; + typename std::list::iterator iter; + unsigned references; + + ValueLock(const Value& value, LockBucket& bucket, + typename std::list::iterator iter) + : value(value), bucket(&bucket), iter(iter), references(0) + {} + + ValueLock(const ValueLock&); + ValueLock& operator=(const ValueLock&); + }; + + private: + struct LockBucket + { + Mutex mtx; + + std::list locks; + + std::list lockBuffer; + unsigned lockBufferSize; + + LockBucket() + : lockBufferSize(0) + {} + }; + + public: + // Acquires a lock descriptor for `value`. The descriptor is only acquired, not locked; + // locking and unlocking is left to the user. Increments the reference count of the returned + // descriptor. The descriptor must be released with `putLock` when done. + ValueLock& getLockFor(const Value& value) + { + const uint32_t valueHash = ValueLockHash()(value); + LockBucket& bucket = buckets[valueHash % HashSize]; + + ValueLock* lock = NULL; + + bucket.mtx.lock(); + { + for(typename std::list::iterator it = bucket.locks.begin(), + end = bucket.locks.end(); it != end; ++it) + { + if(value == (*it)->value) + { + lock = *it; + break; + } + } + + if(!lock) + { + if(bucket.lockBufferSize == 0) + { + bucket.locks.push_front(NULL); + lock = new ValueLock(value, bucket, bucket.locks.begin() ); + bucket.locks.front() = lock; + } + else + { + bucket.locks.splice( + bucket.locks.begin(), + bucket.lockBuffer, + bucket.lockBuffer.begin() ); + bucket.lockBufferSize--; + + lock = bucket.locks.front(); + lock->value = value; + } + } + + lock->references++; + } + bucket.mtx.unlock(); + + return *lock; + } + + // Releases a lock descriptor acquired by `getLockFor` and decreases its reference counter. + // When the reference count reaches 0, `lock` is invalidated. + void putLock(ValueLock& lock) + { + LockBucket& bucket = *lock.bucket; + + bucket.mtx.lock(); + do { + lock.references--; + + if(lock.references > 0) + break; + + if(bucket.lockBufferSize < BufferSize) + { + bucket.lockBuffer.splice( + bucket.lockBuffer.begin(), + bucket.locks, + lock.iter); + bucket.lockBufferSize++; + } + else + { + bucket.locks.erase(lock.iter); + delete &lock; + } + } while (0); + bucket.mtx.unlock(); + } + + private: + LockBucket buckets[HashSize]; +}; + + + +template<> +struct ValueLockHash +{ + // this is the jenkins hash function (http://www.burtleburtle.net/bob/hash/doobs.html). + // it was chosen for this use case because it is fast for small inputs, has no alignment + // requirements and exhibits good mixing. + uint32_t operator()(const std::string& str) const + { + uint32_t hash, i; + for(hash = i = 0; i < str.size(); ++i) + { + hash += str[i]; + hash += (hash << 10); + hash ^= (hash >> 6); + } + hash += (hash << 3); + hash ^= (hash >> 11); + hash += (hash << 15); + return hash; + } +}; + +template<> +struct ValueLockHash > +{ + uint32_t operator()(std::pair pair) const + { + ValueLockHash hash; + return hash(pair.first) ^ hash(pair.second); + } +}; + +template<> +struct ValueLockHash> +{ + uint32_t operator()(std::pair pair) const + { + // optimized for the inode/dentries hash dir structure of 2 fragments of 7 bits each + return std::hash()((pair.first << 8) | pair.second); + } +}; + +typedef ValueLockStore, Mutex, 1024> ParentNameLockStore; +typedef ParentNameLockStore::ValueLock ParentNameLockData; + +typedef ValueLockStore FileIDLockStore; +typedef FileIDLockStore::ValueLock FileIDLockData; + +typedef ValueLockStore, Mutex, 1024> HashDirLockStore; +typedef HashDirLockStore::ValueLock HashDirLockData; + +class EntryLockStore +{ + public: + ParentNameLockData* lock(const std::string& parentID, const std::string& name); + //FileIDLock is used for both files and directories + FileIDLockData* lock(const std::string& fileID, const bool writeLock); + HashDirLockData* lock(std::pair hashDir); + + void unlock(ParentNameLockData* parentNameLockData); + void unlock(FileIDLockData* fileIDLockData); + void unlock(HashDirLockData* hashDirLockData); + + private: + ParentNameLockStore parentNameLocks; + FileIDLockStore fileLocks; + HashDirLockStore hashDirLocks; +}; + diff --git a/meta/source/session/LockingNotifier.cpp b/meta/source/session/LockingNotifier.cpp new file mode 100644 index 0000000..35a494c --- /dev/null +++ b/meta/source/session/LockingNotifier.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include "LockingNotifier.h" + + +/** + * @param notifyList will be owned and freed by the LockingNotifier, so do not use or free it after + * calling this. + */ +void LockingNotifier::notifyWaitersEntryLock(LockEntryNotifyType lockType, + const std::string& parentEntryID, const std::string& entryID, bool isBuddyMirrored, + LockEntryNotifyList notifyList) +{ + // just delegate to comm slaves + + MultiWorkQueue* slaveQ = Program::getApp()->getCommSlaveQueue(); + + Work* work = new LockEntryNotificationWork(lockType, parentEntryID, entryID, isBuddyMirrored, + std::move(notifyList)); + + slaveQ->addDirectWork(work); +} + +/** + * @param notifyList will be owned and freed by the LockingNotifier, so do not use or free it after + * calling this. + */ +void LockingNotifier::notifyWaitersRangeLock(const std::string& parentEntryID, + const std::string& entryID, bool isBuddyMirrored, LockRangeNotifyList notifyList) +{ + // just delegate to comm slaves + + MultiWorkQueue* slaveQ = Program::getApp()->getCommSlaveQueue(); + + Work* work = new LockRangeNotificationWork(parentEntryID, entryID, isBuddyMirrored, + std::move(notifyList)); + + slaveQ->addDirectWork(work); +} diff --git a/meta/source/session/LockingNotifier.h b/meta/source/session/LockingNotifier.h new file mode 100644 index 0000000..551d6ce --- /dev/null +++ b/meta/source/session/LockingNotifier.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include +#include + + +/** + * Creates work packages to notify waiting clients/processes about file lock grants. + */ +class LockingNotifier +{ + public: + static void notifyWaitersEntryLock(LockEntryNotifyType lockType, + const std::string& parentEntryID, const std::string& entryID, bool isBuddyMirrored, + LockEntryNotifyList notifyList); + static void notifyWaitersRangeLock(const std::string& parentEntryID, + const std::string& entryID, bool isBuddyMirrored, LockRangeNotifyList notifyList); + + + private: + LockingNotifier() {} + +}; + diff --git a/meta/source/session/MirrorMessageResponseState.cpp b/meta/source/session/MirrorMessageResponseState.cpp new file mode 100644 index 0000000..3d29793 --- /dev/null +++ b/meta/source/session/MirrorMessageResponseState.cpp @@ -0,0 +1,82 @@ +#include "MirrorMessageResponseState.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void MirroredMessageResponseState::serialize(Serializer& ser) const +{ + ser % serializerTag(); + serializeContents(ser); +} + +std::unique_ptr MirroredMessageResponseState::deserialize( + Deserializer& des) +{ + uint32_t tag; + + des % tag; + if (!des.good()) + return {}; + +#define HANDLE_TAG(value, type) \ + case value: return boost::make_unique(des); + + switch (tag) + { + HANDLE_TAG(NETMSGTYPE_OpenFile, OpenFileMsgEx) + HANDLE_TAG(NETMSGTYPE_CloseFile, CloseFileMsgEx) + HANDLE_TAG(NETMSGTYPE_TruncFile, TruncFileMsgEx) + HANDLE_TAG(NETMSGTYPE_MkFileWithPattern, MkFileWithPatternMsgEx) + HANDLE_TAG(NETMSGTYPE_Hardlink, HardlinkMsgEx) + HANDLE_TAG(NETMSGTYPE_MkLocalDir, MkLocalDirMsgEx) + HANDLE_TAG(NETMSGTYPE_UnlinkFile, UnlinkFileMsgEx) + HANDLE_TAG(NETMSGTYPE_RmLocalDir, RmLocalDirMsgEx) + HANDLE_TAG(NETMSGTYPE_MkDir, MkDirMsgEx) + HANDLE_TAG(NETMSGTYPE_MkFile, MkFileMsgEx) + HANDLE_TAG(NETMSGTYPE_RmDir, RmDirMsgEx) + HANDLE_TAG(NETMSGTYPE_UpdateDirParent, UpdateDirParentMsgEx) + HANDLE_TAG(NETMSGTYPE_RemoveXAttr, RemoveXAttrMsgEx) + HANDLE_TAG(NETMSGTYPE_SetXAttr, SetXAttrMsgEx) + HANDLE_TAG(NETMSGTYPE_SetDirPattern, SetDirPatternMsgEx) + HANDLE_TAG(NETMSGTYPE_SetAttr, SetAttrMsgEx) + HANDLE_TAG(NETMSGTYPE_RefreshEntryInfo, RefreshEntryInfoMsgEx) + HANDLE_TAG(NETMSGTYPE_MovingFileInsert, MovingFileInsertMsgEx) + HANDLE_TAG(NETMSGTYPE_MovingDirInsert, MovingDirInsertMsgEx) + HANDLE_TAG(NETMSGTYPE_Rename, RenameV2MsgEx) + HANDLE_TAG(NETMSGTYPE_LookupIntent, LookupIntentMsgEx) + HANDLE_TAG(NETMSGTYPE_AckNotify, AckNotifiyMsgEx) + HANDLE_TAG(NETMSGTYPE_FLockEntry, FLockEntryMsgEx) + HANDLE_TAG(NETMSGTYPE_FLockRange, FLockRangeMsgEx) + HANDLE_TAG(NETMSGTYPE_BumpFileVersion, BumpFileVersionMsgEx) + + default: + LOG(MIRRORING, ERR, "bad mirror response state tag.", tag); + des.setBad(); + break; + } + + return {}; +} diff --git a/meta/source/session/MirrorMessageResponseState.h b/meta/source/session/MirrorMessageResponseState.h new file mode 100644 index 0000000..78fae58 --- /dev/null +++ b/meta/source/session/MirrorMessageResponseState.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include + +class MirroredMessageResponseState +{ + public: + virtual ~MirroredMessageResponseState() + { + } + + virtual void sendResponse(NetMessage::ResponseContext& ctx) = 0; + + virtual bool changesObservableState() const = 0; + + void serialize(Serializer& ser) const; + + static std::unique_ptr deserialize(Deserializer& des); + + protected: + virtual uint32_t serializerTag() const = 0; + virtual void serializeContents(Serializer& ser) const = 0; +}; + +template +class ErrorCodeResponseState : public MirroredMessageResponseState +{ + public: + explicit ErrorCodeResponseState(Deserializer& des) + { + des % serdes::as(result); + } + + explicit ErrorCodeResponseState(FhgfsOpsErr result) : result(result) {} + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + if (result == FhgfsOpsErr_COMMUNICATION) + ctx.sendResponse( + GenericResponseMsg( + GenericRespMsgCode_INDIRECTCOMMERR, + "Communication with storage targets failed")); + else + ctx.sendResponse(RespMsgT(result)); + } + + bool changesObservableState() const override + { + return result == FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr getResult() const { return result; } + + protected: + uint32_t serializerTag() const override { return SerializerTag; } + + void serializeContents(Serializer& ser) const override + { + ser % serdes::as(result); + } + + private: + FhgfsOpsErr result; +}; + +template +class ErrorAndEntryResponseState : public MirroredMessageResponseState +{ + public: + explicit ErrorAndEntryResponseState(Deserializer& des) + { + des + % serdes::as(result) + % info; + } + + ErrorAndEntryResponseState(FhgfsOpsErr result, EntryInfo info) + : result(result), info(std::move(info)) + { + } + + void sendResponse(NetMessage::ResponseContext& ctx) override + { + if (result == FhgfsOpsErr_COMMUNICATION) + ctx.sendResponse( + GenericResponseMsg( + GenericRespMsgCode_INDIRECTCOMMERR, + "Communication with storage targets failed")); + else + ctx.sendResponse(RespMsgT(result, &info)); + } + + bool changesObservableState() const override + { + return result == FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr getResult() const { return result; } + + protected: + uint32_t serializerTag() const override { return SerializerTag; } + + void serializeContents(Serializer& ser) const override + { + ser + % serdes::as(result) + % info; + } + + private: + FhgfsOpsErr result; + EntryInfo info; +}; + diff --git a/meta/source/session/Session.cpp b/meta/source/session/Session.cpp new file mode 100644 index 0000000..9f1a305 --- /dev/null +++ b/meta/source/session/Session.cpp @@ -0,0 +1,16 @@ +#include "Session.h" + +/* Merges the SessionFiles of the given session into this session, only not existing + * SessionFiles will be added to the existing session + * @param session the session which will be merged with this session + */ +void Session::mergeSessionFiles(Session* session) +{ + this->getFiles()->mergeSessionFiles(session->getFiles() ); +} + +bool Session::operator==(const Session& other) const +{ + return sessionID == other.sessionID + && files == other.files; +} diff --git a/meta/source/session/Session.h b/meta/source/session/Session.h new file mode 100644 index 0000000..ad6eaed --- /dev/null +++ b/meta/source/session/Session.h @@ -0,0 +1,179 @@ +#pragma once + +#include +#include +#include +#include "SessionFileStore.h" + +#include + +struct MirrorStateSlot +{ + std::unique_ptr response; + + friend Serializer& operator%(Serializer& ser, const std::shared_ptr& obj) + { + ser % bool(obj->response); + if (obj->response) + obj->response->serialize(ser); + + return ser; + } + + friend Deserializer& operator%(Deserializer& des, std::shared_ptr& obj) + { + bool hasContent; + + obj.reset(new MirrorStateSlot); + + des % hasContent; + if (des.good() && hasContent) + obj->response = MirroredMessageResponseState::deserialize(des); + + return des; + } +}; + +/* + * A session always belongs to a client ID, therefore the session ID is always the nodeID of the + * corresponding client + */ +class Session +{ + public: + Session(NumNodeID sessionID) : sessionID(sessionID) {} + + /* + * For deserialization only + */ + Session() {}; + + void mergeSessionFiles(Session* session); + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->sessionID + % obj->files + % obj->mirrorProcessState; + + obj->dropEmptyStateSlots(ctx); + } + + bool operator==(const Session& other) const; + + bool operator!=(const Session& other) const { return !(*this == other); } + + private: + NumNodeID sessionID; + + SessionFileStore files; + + Mutex mirrorProcessStateLock; + // mirror state slots are shared_ptrs for two reasons: ordering of memory operations, and + // misbehaving clients. + // * memory ordering turns into a very easy problem if every user of an object holds a + // reference that will keep the object alive, even if it is removed from this map for + // some reason. + // * clients may reuse sequences numbers in violation of the protocol, or clear out sequence + // numbers that are still processing on the server. in the first case, we will want an + // ephemeral response state that will immediatly go away, in the second we want to keep + // the response state alive as long as some thread is still operating on it. + std::map> mirrorProcessState; + + void dropEmptyStateSlots(Serializer&) const + { + } + + // when the session state for any given client is loaded, the server must discard empty state + // slots. if empty slots are not discarded, requests for any such sequence number would be + // answered with "try again later" - forever, because the slot will never be filled. + // this is not an issue for resync, because during session resync, all slots are filled. + // empty slots can only occur if a server was shut down while messages were still being + // processed. + void dropEmptyStateSlots(Deserializer&) + { + auto it = mirrorProcessState.begin(); + auto end = mirrorProcessState.end(); + + while (it != end) + { + if (it->second->response) + { + ++it; + continue; + } + + auto slot = it; + + ++it; + mirrorProcessState.erase(slot); + } + } + + public: + // getters & setters + NumNodeID getSessionID() + { + return sessionID; + } + + SessionFileStore* getFiles() + { + return &files; + } + + bool relinkInodes(MetaStore& store) + { + return files.relinkInodes(store); + } + + void freeMirrorStateSlot(uint64_t seqNo) + { + std::lock_guard lock(mirrorProcessStateLock); + + mirrorProcessState.erase(seqNo); + } + + std::pair, bool> acquireMirrorStateSlot(uint64_t endSeqno, + uint64_t thisSeqno) + { + std::lock_guard lock(mirrorProcessStateLock); + + // the client sets endSeqno to the largest sequence number it knows to be fully processed, + // with no sequence numbers below it still being active somewhere. we can remove all states + // with smaller sequence numbers and endSeqno itself + mirrorProcessState.erase( + mirrorProcessState.begin(), + mirrorProcessState.lower_bound(endSeqno + 1)); + + auto inserted = mirrorProcessState.insert( + {thisSeqno, std::make_shared()}); + return {inserted.first->second, inserted.second}; + } + + std::pair, bool> acquireMirrorStateSlotSelective( + uint64_t finishedSeqno, uint64_t thisSeqno) + { + std::lock_guard lock(mirrorProcessStateLock); + + mirrorProcessState.erase(finishedSeqno); + + auto inserted = mirrorProcessState.insert( + {thisSeqno, std::make_shared()}); + return {inserted.first->second, inserted.second}; + } + + uint64_t getSeqNoBase() + { + std::lock_guard lock(mirrorProcessStateLock); + + if (!mirrorProcessState.empty()) + return mirrorProcessState.rbegin()->first + 1; + else + return 1; + } +}; + + diff --git a/meta/source/session/SessionFile.cpp b/meta/source/session/SessionFile.cpp new file mode 100644 index 0000000..be7884a --- /dev/null +++ b/meta/source/session/SessionFile.cpp @@ -0,0 +1,27 @@ +#include +#include + +#include "SessionFile.h" + +bool SessionFile::operator==(const SessionFile& other) const +{ + return accessFlags == other.accessFlags + && sessionID == other.sessionID + && entryInfo == other.entryInfo + && useAsyncCleanup == other.useAsyncCleanup; +} + +bool SessionFile::relinkInode(MetaStore& store) +{ + // Bypass access checks during session recovery to ensure locked files in disposal directory + // can be properly recovered after system crashes or unclean shutdowns. This preserves session + // continuity regardless of file state locks. We may revisit this approach if anything changes. + auto openRes = store.openFile(&entryInfo, accessFlags, /* bypassAccessCheck */ true, inode, true); + + if (openRes == FhgfsOpsErr_SUCCESS) + return true; + + LOG(SESSIONS, ERR, "Could not relink session for inode.", + ("inode", entryInfo.getParentEntryID() + "/" + entryInfo.getEntryID())); + return false; +} diff --git a/meta/source/session/SessionFile.h b/meta/source/session/SessionFile.h new file mode 100644 index 0000000..58b85d9 --- /dev/null +++ b/meta/source/session/SessionFile.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include +#include + +class MetaStore; + +class SessionFile +{ + public: + /** + * @param accessFlags OPENFILE_ACCESS_... flags + */ + SessionFile(MetaFileHandle openInode, unsigned accessFlags, EntryInfo* entryInfo): + inode(std::move(openInode)), accessFlags(accessFlags), sessionID(0), + useAsyncCleanup(false) + { + this->entryInfo.set(entryInfo); + } + + /** + * For dezerialisation only + */ + SessionFile(): + accessFlags(0), sessionID(0), useAsyncCleanup(false) + { + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->accessFlags + % obj->sessionID + % obj->entryInfo + % obj->useAsyncCleanup; + } + + bool relinkInode(MetaStore& store); + + bool operator==(const SessionFile& other) const; + + bool operator!=(const SessionFile& other) const { return !(*this == other); } + + private: + MetaFileHandle inode; + uint32_t accessFlags; // OPENFILE_ACCESS_... flags + uint32_t sessionID; + bool useAsyncCleanup; // if session was busy (=> referenced) while client sent close + + EntryInfo entryInfo; + + // getters & setters + + /** + * Returns lock of internal FileInode object. + */ + RWLock* getInodeLock() + { + return &inode->rwlock; + } + + public: + + // getters & setters + unsigned getAccessFlags() + { + return accessFlags; + } + + unsigned getSessionID() + { + return sessionID; + } + + void setSessionID(unsigned sessionID) + { + this->sessionID = sessionID; + } + + const MetaFileHandle& getInode() const + { + return inode; + } + + MetaFileHandle releaseInode() + { + return std::move(inode); + } + + bool getUseAsyncCleanup() + { + // note: unsynced, because it can only become true and stays true then + return this->useAsyncCleanup; + } + + void setUseAsyncCleanup() + { + // note: unsynced, because it can only become true and stays true then + this->useAsyncCleanup = true; + } + + EntryInfo* getEntryInfo(void) + { + return &this->entryInfo; + } + + /** + * Update the parentEntryID + */ + void setParentEntryID(const std::string& newParentID) + { + this->entryInfo.setParentEntryID(newParentID); + } +}; + + + diff --git a/meta/source/session/SessionFileStore.cpp b/meta/source/session/SessionFileStore.cpp new file mode 100644 index 0000000..a988060 --- /dev/null +++ b/meta/source/session/SessionFileStore.cpp @@ -0,0 +1,369 @@ +#include +#include "SessionFileStore.h" + +#include + +/** + * @param session belongs to the store after calling this method - so do not free it and don't + * use it any more afterwards (re-get it from this store if you need it) + * @return assigned sessionID + */ +unsigned SessionFileStore::addSession(SessionFile* session) +{ + const std::lock_guard lock(mutex); + + unsigned sessionID = generateNewSessionID(); + session->setSessionID(sessionID); + + sessions.insert(SessionFileMapVal(sessionID, new SessionFileReferencer(session) ) ); + + return sessionID; +} + +/** + * @param session belongs to the store after calling this method - so do not free it and don't + * use it any more afterwards (re-get it from this store if you need it) + * @param sessionFileID use this ID to store session file + * @return true if successfully added, false otherwise (most likely ID conflict) + */ +bool SessionFileStore::addSession(SessionFile* session, unsigned sessionFileID) +{ + const std::lock_guard lock(mutex); + + session->setSessionID(sessionFileID); + + std::pair insertRes = + sessions.insert(SessionFileMapVal(sessionFileID, new SessionFileReferencer(session) ) ); + + return insertRes.second; +} + +/** + * Insert a session with a pre-defined sessionID and reference it. Will fail if given ID existed + * already. + * + * Note: This is a special method intended for recovery purposes only! + * Note: remember to call releaseSession() if the new session was actually inserted. + * + * @param session belongs to the store if it was inserted - so do not free it and don't + * use it any more afterwards (reference it if you still need it) + * @return NULL if sessionID existed already (and new session not inserted or touched in any way), + * pointer to referenced session file otherwise. + */ +SessionFile* SessionFileStore::addAndReferenceRecoverySession(SessionFile* session) +{ + const std::lock_guard lock(mutex); + + SessionFile* retVal = NULL; + unsigned sessionID = session->getSessionID(); + + SessionFileMapIter iter = sessions.find(sessionID); + if(iter == sessions.end() ) + { // session with this ID didn't exist (this is the normal case) => insert and reference it + + /* note: we do the sessions.find() first to avoid allocating SessionRefer if it's not + necessary */ + + SessionFileReferencer* sessionFileRefer = new SessionFileReferencer(session); + + sessions.insert(SessionFileMapVal(sessionID, sessionFileRefer) ); + + retVal = sessionFileRefer->reference(); + } + + return retVal; +} + +/** + * Note: remember to call releaseSession() + * + * @return NULL if no such session exists + */ +SessionFile* SessionFileStore::referenceSession(unsigned sessionID) +{ + const std::lock_guard lock(mutex); + + SessionFileMapIter iter = sessions.find(sessionID); + if(iter == sessions.end() ) + { // not found + return NULL; + } + else + { + SessionFileReferencer* sessionRefer = iter->second; + return sessionRefer->reference(); + } +} + +void SessionFileStore::releaseSession(SessionFile* session, EntryInfo* entryInfo) +{ + unsigned sessionID = session->getSessionID(); + bool asyncCleanup = false; + unsigned asyncCleanupAccessFlags = 0; // only for asyncCleanup + MetaFileHandle asyncCleanupFile; // only for asyncCleanup + + { + const std::lock_guard lock(mutex); + + SessionFileMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { // session exists => decrease refCount + SessionFileReferencer* sessionRefer = iter->second; + SessionFile* sessionNonRef = sessionRefer->getReferencedObject(); + + if(unlikely(sessionNonRef->getUseAsyncCleanup() ) ) + { // marked for async cleanup => check whether we're dropping the last reference + if(sessionRefer->getRefCount() == 1) + { // we're dropping the last reference => save async cleanup data and trigger cleanup + + asyncCleanup = true; + + asyncCleanupFile = sessionNonRef->releaseInode(); + asyncCleanupAccessFlags = sessionNonRef->getAccessFlags(); + + sessionRefer->release(); + + sessions.erase(iter); + delete(sessionRefer); + } + } + else + { // the normal case: just release this reference + sessionRefer->release(); + } + } + + } + + if(unlikely(asyncCleanup) ) + performAsyncCleanup(entryInfo, std::move(asyncCleanupFile), asyncCleanupAccessFlags); +} + +/** + * @return false if session could not be removed, because it was still in use; it will be marked + * for async cleanup when the last reference is dropped + */ +bool SessionFileStore::removeSession(unsigned sessionID) +{ + bool delErr = true; + + const std::lock_guard lock(mutex); + + SessionFileMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { + SessionFileReferencer* sessionRefer = iter->second; + + if(sessionRefer->getRefCount() ) + { // session still in use => mark for async cleanup (on last reference drop) + SessionFile* fileNonRef = sessionRefer->getReferencedObject(); + + fileNonRef->setUseAsyncCleanup(); + + delErr = true; + } + else + { // no references => delete + sessions.erase(iter); + delete(sessionRefer); + delErr = false; + } + } + + return !delErr; +} + +/** + * @return might be NULL if the session is in use + */ +SessionFile* SessionFileStore::removeAndGetSession(unsigned sessionID) +{ + // note: this method is currently unused + + SessionFile* session = NULL; + + const std::lock_guard lock(mutex); + + SessionFileMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { + SessionFileReferencer* sessionRefer = iter->second; + + if(!sessionRefer->getRefCount() ) + { // no references => allow deletion + sessions.erase(iter); + sessionRefer->setOwnReferencedObject(false); + session = sessionRefer->getReferencedObject(); + delete(sessionRefer); + } + } + + return session; +} + +/** + * Removes all sessions and additionally adds those that had a reference count to the StringList. + * + * @outRemovedSessions caller is responsible for clean up of contained objects + */ +void SessionFileStore::removeAllSessions(SessionFileList* outRemovedSessions, + UIntList* outReferencedSessions) +{ + const std::lock_guard lock(mutex); + + for(SessionFileMapIter iter = sessions.begin(); iter != sessions.end(); iter++) + { + SessionFileReferencer* sessionRefer = iter->second; + SessionFile* session = sessionRefer->getReferencedObject(); + + outRemovedSessions->push_back(session); + + if(unlikely(sessionRefer->getRefCount() ) ) + outReferencedSessions->push_back(iter->first); + + sessionRefer->setOwnReferencedObject(false); + delete(sessionRefer); + } + + sessions.clear(); +} + +/* + * Intended to be used for cleanup if deserialization failed, no locking is used + */ +void SessionFileStore::deleteAllSessions() +{ + for(SessionFileMapIter iter = sessions.begin(); iter != sessions.end(); iter++) + { + SessionFileReferencer* sessionRefer = iter->second; + + delete(sessionRefer); + } + + sessions.clear(); +} + +size_t SessionFileStore::getSize() +{ + const std::lock_guard lock(mutex); + + return sessions.size(); +} + + +unsigned SessionFileStore::generateNewSessionID() +{ + SessionFileMapIter iter; + + // note: we assume here that there always is at least one free sessionID. + + do + { + // generate new ID + lastSessionID++; + + // check whether this ID is being used already + iter = sessions.find(lastSessionID); + + } while(iter != sessions.end() ); + + // we found an available sessionID => return it + + return lastSessionID; +} + +/** + * Performs local cleanup tasks for file sessions, which are marked for async cleanup. + */ +void SessionFileStore::performAsyncCleanup(EntryInfo* entryInfo, + MetaFileHandle inode, unsigned accessFlags) +{ + const char* logContext = __func__; + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + + unsigned numHardlinks; // ignored here + unsigned numInodeRefs; // ignored here + bool lastWriterClosed; // ignored here + + LOG_DEBUG(logContext, Log_NOTICE, "Performing async cleanup of file session"); + IGNORE_UNUSED_VARIABLE(logContext); + + metaStore->closeFile(entryInfo, std::move(inode), accessFlags, &numHardlinks, &numInodeRefs, + lastWriterClosed); + + /* note: we ignore closing storage server files here (e.g. because we don't have the sessionID + and fileHandleID at hand) and unlinking of disposable files (disposal can still be triggered + by fhgfs_online_cfg). + this is something that we should change in the future (but maybe rather indirectly by syncing + open files between clients and servers at regular intervals). */ +} + +SessionFileMap* SessionFileStore::getSessionMap() +{ + return &this->sessions; +} + +/* Merges the SessionFiles of the given SessionFileStore into this SessionFileStore. + * Only not existing SessionFiles will be added to the existing SessionFileStore + * + * @param sessionFileStore the sessionFileStore which will be merged with this sessionFileStore + */ +void SessionFileStore::mergeSessionFiles(SessionFileStore* sessionFileStore) +{ + Logger* log = Logger::getLogger(); + + SessionFileMapIter sessionIter = sessionFileStore->getSessionMap()->begin(); + + while(sessionIter != sessionFileStore->getSessionMap()->end() ) + { + bool sessionFound = false; + SessionFileMapIter destSessionIter = this->sessions.find(sessionIter->first); + + if (destSessionIter != this->sessions.end()) + { + sessionFound = true; + log->log(Log_WARNING, "SessionFileStore merge", "found SessionFile with same " + "ID: " + StringTk::uintToStr(sessionIter->first) + + " , merge not possible, may be a bug?"); + } + + if (!sessionFound) + { + bool success = this->sessions.insert(SessionFileMapVal(sessionIter->first, + sessionIter->second)).second; + + if (!success) + { + log->log(Log_WARNING, "SessionFileStore merge", "could not merge: " + + StringTk::uintToStr(sessionIter->first) ); + + delete(sessionIter->second); + } + } + else + { + delete(sessionIter->second); + } + + sessionIter++; + } +} + +bool SessionFileStore::operator==(const SessionFileStore& other) const +{ + struct ops { + static bool cmp(const SessionFileMapVal& lhs, const SessionFileMapVal& rhs) + { + return lhs.first == rhs.first + && *lhs.second->getReferencedObject() == *rhs.second->getReferencedObject(); + } + }; + + return lastSessionID == other.lastSessionID + && sessions.size() == other.sessions.size() + && std::equal( + sessions.begin(), sessions.end(), + other.sessions.begin(), + ops::cmp); +} diff --git a/meta/source/session/SessionFileStore.h b/meta/source/session/SessionFileStore.h new file mode 100644 index 0000000..09fe3fe --- /dev/null +++ b/meta/source/session/SessionFileStore.h @@ -0,0 +1,132 @@ +#pragma once + +#include +#include +#include +#include +#include "SessionFile.h" + +typedef ObjectReferencer SessionFileReferencer; +typedef std::map SessionFileMap; +typedef SessionFileMap::iterator SessionFileMapIter; +typedef SessionFileMap::const_iterator SessionFileMapCIter; +typedef SessionFileMap::value_type SessionFileMapVal; + +typedef std::list SessionFileList; +typedef SessionFileList::iterator SessionFileListIter; + +class SessionStore; + +class SessionFileStore +{ + friend class SessionStore; + + public: + SessionFileStore() + { + // note: randomize to make it more unlikely that after a meta server shutdown the same + // client instance can open a file and gets a colliding (with the old meta server + // instance) sessionID + this->lastSessionID = Random().getNextInt(); + } + + unsigned addSession(SessionFile* session); + bool addSession(SessionFile* session, unsigned sessionFileID); + SessionFile* addAndReferenceRecoverySession(SessionFile* session); + SessionFile* referenceSession(unsigned sessionID); + void releaseSession(SessionFile* session, EntryInfo* entryInfo); + bool removeSession(unsigned sessionID); + void removeAllSessions(SessionFileList* outRemovedSessions, + UIntList* outReferencedSessions); + void deleteAllSessions(); + void mergeSessionFiles(SessionFileStore* sessionFileStore); + + size_t getSize(); + + bool relinkInodes(MetaStore& store) + { + bool result = true; + + for (auto it = sessions.begin(); it != sessions.end(); ) + { + const bool relinkRes = it->second->getReferencedObject()->relinkInode(store); + + if (!relinkRes) + sessions.erase(it++); + else + ++it; + + result &= relinkRes; + } + + return result; + } + + static void serialize(const SessionFileStore* obj, Serializer& ser) + { + ser + % obj->lastSessionID + % uint32_t(obj->sessions.size()); + + for (SessionFileMapCIter it = obj->sessions.begin(); it != obj->sessions.end(); ++it) + { + ser + % it->first + % *it->second->getReferencedObject(); + } + + LOG_DEBUG("SessionFileStore serialize", Log_DEBUG, "count of serialized " + "SessionFiles: " + StringTk::uintToStr(obj->sessions.size()) ); + } + + static void serialize(SessionFileStore* obj, Deserializer& des) + { + uint32_t elemCount; + + des + % obj->lastSessionID + % elemCount; + + for(unsigned i = 0; i < elemCount; i++) + { + uint32_t key; + + des % key; + if (!des.good()) + return; + + SessionFile* sessionFile = new SessionFile(); + des % *sessionFile; + if (!des.good()) + { + delete(sessionFile); + return; + } + + obj->sessions.insert(SessionFileMapVal(key, new SessionFileReferencer(sessionFile))); + } + + LOG_DEBUG("SessionFileStore deserialize", Log_DEBUG, "count of deserialized " + "SessionFiles: " + StringTk::uintToStr(elemCount)); + } + + bool operator==(const SessionFileStore& other) const; + + bool operator!=(const SessionFileStore& other) const { return !(*this == other); } + + protected: + SessionFileMap* getSessionMap(); + + private: + SessionFileMap sessions; + uint32_t lastSessionID; + + Mutex mutex; + + SessionFile* removeAndGetSession(unsigned sessionID); // actually not needed now + + unsigned generateNewSessionID(); + + void performAsyncCleanup(EntryInfo* entryInfo, MetaFileHandle inode, unsigned accessFlags); +}; + diff --git a/meta/source/session/SessionStore.cpp b/meta/source/session/SessionStore.cpp new file mode 100644 index 0000000..a13e9ca --- /dev/null +++ b/meta/source/session/SessionStore.cpp @@ -0,0 +1,625 @@ +#include +#include "SessionStore.h" + +#include +#include + +// these two u32 values are the first eight bytes of a session dump file. +// in 2015.03, the first four bytes were the number of sessions in the dump, so here we use +// 0xffffffff to indicate that we are not using the 2015.03 format - it couldn't store that many +// sessions anyway. +#define SESSION_FORMAT_HEADER uint32_t(0xffffffffUL) +#define SESSION_FORMAT_VERSION uint32_t(1) + +/** + * @param session belongs to the store after calling this method - so do not free it and don't + * use it any more afterwards (re-get it from this store if you need it) + */ +void SessionStore::addSession(Session* session) +{ + NumNodeID sessionID = session->getSessionID(); + + std::lock_guard mutexLock(mutex); + + // is session in the store already? + + SessionMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { + delete(session); + } + else + { // session not in the store yet + sessions.insert(SessionMapVal(sessionID, new SessionReferencer(session) ) ); + } +} + +/** + * Note: remember to call releaseSession() + * + * @return NULL if no such session exists + */ +Session* SessionStore::referenceSession(NumNodeID sessionID, bool addIfNotExists) +{ + Session* session; + + std::lock_guard mutexLock(mutex); + + SessionMapIter iter = sessions.find(sessionID); + if(iter == sessions.end() ) + { // not found + if(!addIfNotExists) + session = NULL; + else + { // add as new session and reference it + LogContext log("SessionStore (ref)"); + log.log(Log_DEBUG, std::string("Creating a new session. SessionID: ") + sessionID.str()); + + Session* newSession = new Session(sessionID); + SessionReferencer* sessionRefer = new SessionReferencer(newSession); + sessions.insert(SessionMapVal(sessionID, sessionRefer) ); + session = sessionRefer->reference(); + } + } + else + { + SessionReferencer* sessionRefer = iter->second; + session = sessionRefer->reference(); + } + + return session; +} + +void SessionStore::releaseSession(Session* session) +{ + NumNodeID sessionID = session->getSessionID(); + + std::lock_guard mutexLock(mutex); + + SessionMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { // session exists => decrease refCount + SessionReferencer* sessionRefer = iter->second; + sessionRefer->release(); + } +} + +bool SessionStore::removeSession(NumNodeID sessionID) +{ + bool delErr = true; + + std::lock_guard mutexLock(mutex); + + SessionMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { + SessionReferencer* sessionRefer = iter->second; + + if(sessionRefer->getRefCount() ) + delErr = true; + else + { // no references => delete + sessions.erase(sessionID); + delete(sessionRefer); + delErr = false; + } + } + + return !delErr; +} + +/** + * @return NULL if session is referenced, otherwise the sesion must be cleaned up by the caller + */ +Session* SessionStore::removeSessionUnlocked(NumNodeID sessionID) +{ + Session* retVal = NULL; + + SessionMapIter iter = sessions.find(sessionID); + if(iter != sessions.end() ) + { + SessionReferencer* sessionRefer = iter->second; + + if(!sessionRefer->getRefCount() ) + { // no references => return session to caller + retVal = sessionRefer->getReferencedObject(); + + sessionRefer->setOwnReferencedObject(false); + delete(sessionRefer); + sessions.erase(sessionID); + } + } + + return retVal; +} + +/** + * Removes all sessions from the session store which are not contained in the masterList. + * @param masterList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @param outRemovedSessions contained sessions must be cleaned up by the caller + * note: list will NOT be cleared, new elements will be appended + * @param outUnremovableSesssions contains sessions that would have been removed but are currently + * referenced + * note: list will NOT be cleared, new elements will be appended + */ +void SessionStore::syncSessions(const std::vector& masterList, + SessionList& outRemovedSessions, NumNodeIDList& outUnremovableSessions) +{ + std::lock_guard mutexLock(mutex); + + SessionMapIter sessionIter = sessions.begin(); + auto masterIter = masterList.begin(); + + while( (sessionIter != sessions.end() ) && (masterIter != masterList.end() ) ) + { + NumNodeID currentSession = sessionIter->first; + NumNodeID currentMaster = (*masterIter)->getNumID() ; + + if(currentMaster < currentSession) + { // session is added + // note: we add sessions only on demand, so no operation here + masterIter++; + } + else + if(currentSession < currentMaster) + { // session is removed + sessionIter++; // (removal invalidates iterator) + + Session* session = removeSessionUnlocked(currentSession); + if(session) + outRemovedSessions.push_back(session); + else + outUnremovableSessions.push_back(currentSession); // session was referenced + } + else + { // session unchanged + masterIter++; + sessionIter++; + } + } + + // remaining sessions are removed + while(sessionIter != sessions.end() ) + { + NumNodeID currentSession = sessionIter->first; + sessionIter++; // (removal invalidates iterator) + + Session* session = removeSessionUnlocked(currentSession); + if(session) + outRemovedSessions.push_back(session); + else + outUnremovableSessions.push_back(currentSession); // session was referenced + } +} + +/** + * @return number of sessions + */ +NumNodeIDList SessionStore::getAllSessionIDs() +{ + std::lock_guard mutexLock(mutex); + NumNodeIDList res; + + for(SessionMapIter iter = sessions.begin(); iter != sessions.end(); iter++) + res.push_back(iter->first); + + return res; +} + +size_t SessionStore::getSize() +{ + std::lock_guard mutexLock(mutex); + return sessions.size(); +} + +void SessionStore::serialize(Serializer& ser) const +{ + ser + % SESSION_FORMAT_HEADER + % SESSION_FORMAT_VERSION + % uint32_t(sessions.size()); + + for (SessionMap::const_iterator it = sessions.begin(); it != sessions.end(); ++it) + { + ser + % it->first + % *it->second->getReferencedObject(); + } + + { + Serializer lenPos = ser.mark(); + + ser % uint32_t(0); + + std::set inodesSeen; + + for (auto sessionIter = sessions.begin(); sessionIter != sessions.end(); ++sessionIter) + { + Session& session = *sessionIter->second->getReferencedObject(); + const auto& fileMap = session.getFiles()->sessions; + + for (auto fileMapIter = fileMap.begin(); fileMapIter != fileMap.end(); ++fileMapIter) + { + SessionFile& file = *fileMapIter->second->getReferencedObject(); + const auto& inode = file.getInode(); + + if (!inodesSeen.insert(inode.get()).second) + continue; + + ser + % *file.getEntryInfo() + % inode->lockState(); + } + } + + lenPos % uint32_t(inodesSeen.size()); + } + + LOG_DEBUG("SessionStore serialize", Log_DEBUG, "Serialized session store. Session count: " + + StringTk::uintToStr(sessions.size())); +} + +void SessionStore::deserialize(Deserializer& des) +{ + uint32_t sessionFormatHeader; + uint32_t sessionFormatVersion; + uint32_t elemCount; + + des + % sessionFormatHeader + % sessionFormatVersion; + if (sessionFormatHeader != SESSION_FORMAT_HEADER || + sessionFormatVersion != SESSION_FORMAT_VERSION) + { + des.setBad(); + return; + } + + des % elemCount; + + // sessions + for(unsigned i = 0; i < elemCount; i++) + { + NumNodeID key; + + des % key; + if (!des.good()) + return; + + Session* session = new Session(); + des % *session; + + if (!des.good()) + { + session->getFiles()->deleteAllSessions(); + delete(session); + return; + } + + SessionMapIter searchResult = this->sessions.find(key); + if (searchResult == this->sessions.end() ) + { + this->sessions.insert(SessionMapVal(key, new SessionReferencer(session))); + } + else + { // exist so local files will merged + searchResult->second->getReferencedObject()->mergeSessionFiles(session); + delete(session); + } + } + + LOG_DEBUG("SessionStore deserialize", Log_DEBUG, "Deserialized session store. Session count: " + + StringTk::uintToStr(elemCount)); +} + +void SessionStore::deserializeLockStates(Deserializer& des, MetaStore& metaStore) +{ + uint32_t elemCount; + + des % elemCount; + + // file inode lock states + while (elemCount > 0) + { + elemCount--; + + EntryInfo info; + + des % info; + + if (!des.good()) + break; + + auto [inode, referenceRes] = metaStore.referenceFile(&info); + + des % inode->lockState(); + + metaStore.releaseFile(info.getParentEntryID(), inode); + + if (!des.good()) + break; + } + + if (!des.good()) + { + LOG(SESSIONS, ERR, "Could not restore file inode lock states."); + return; + } +} + +/** + * Note: Caller must either hold a lock on the SessionStore, or otherwise ensure that it is not + * accessed from anywhere. + */ +bool SessionStore::deserializeFromBuf(const char* buf, size_t bufLen, MetaStore& metaStore) +{ + Deserializer des(buf, bufLen); + deserialize(des); + + if (!des.good()) + LOG(SESSIONS, ERR, "Unable to deserialize session store from buffer."); + + const bool relinkRes = relinkInodes(metaStore); + if (!relinkRes) + LOG(SESSIONS, ERR, "Unable to relink inodes."); + + if (!des.good() || !relinkRes) + return false; + + deserializeLockStates(des, metaStore); + if (!des.good()) + { + LOG(SESSIONS, ERR, "Unable to load lock states."); + return false; + } + + return true; +} + +bool SessionStore::loadFromFile(const std::string& filePath, MetaStore& metaStore) +{ + LogContext log("SessionStore (load)"); + log.log(Log_DEBUG,"load sessions from file: " + filePath); + + bool retVal = false; + boost::scoped_array buf; + int readRes; + + struct stat statBuf; + int retValStat; + + if(filePath.empty()) + return false; + + std::lock_guard mutexLock(mutex); + + int fd = open(filePath.c_str(), O_RDONLY, 0); + if(fd == -1) + { // open failed + log.log(Log_DEBUG, "Unable to open session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + retValStat = fstat(fd, &statBuf); + if(retValStat) + { // stat failed + log.log(Log_WARNING, "Unable to stat session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_stat; + } + + if (statBuf.st_size == 0) + { + LOG(SESSIONS, WARNING, "Session file exists, but is empty.", filePath); + return false; + } + + buf.reset(new (std::nothrow) char[statBuf.st_size]); + if (!buf) + { + LOG(SESSIONS, ERR, "Could not allocate memory to read session file", filePath); + goto err_stat; + } + + readRes = read(fd, buf.get(), statBuf.st_size); + if(readRes <= 0) + { // reading failed + log.log(Log_WARNING, "Unable to read session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + } + else + { // parse contents + retVal = deserializeFromBuf(buf.get(), readRes, metaStore); + } + + if (!retVal) + log.logErr("Could not deserialize SessionStore from file: " + filePath); + +err_stat: + close(fd); + + return retVal; +} + +/* + * @returns pair of buffer and buffer size, { nullptr, 0 } on error. + */ +std::pair, size_t> SessionStore::serializeToBuf() +{ + size_t bufLen; + std::unique_ptr buf; + + { + Serializer ser; + serialize(ser); + + bufLen = ser.size(); + buf.reset(new (std::nothrow)char[bufLen]); + + if (!buf) + { + LOG(SESSIONS, ERR, "Could not allocate memory to serialize sessions."); + return { std::unique_ptr(), 0 }; + } + } + + { + Serializer ser(buf.get(), bufLen); + serialize(ser); + if (!ser.good()) + { + LOG(SESSIONS, ERR, "Unable to serialize sessions."); + return { std::unique_ptr(), 0 }; + } + } + + return { std::move(buf), bufLen }; +} + +/** + * Note: setStorePath must be called before using this. + */ +bool SessionStore::saveToFile(const std::string& filePath) +{ + LogContext log("SessionStore (save)"); + log.log(Log_DEBUG,"save sessions to file: " + filePath); + + bool retVal = false; + + if(!filePath.length() ) + return false; + + std::lock_guard mutexLock(mutex); + + // create/trunc file + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(filePath.c_str(), openFlags, 0666); + if(fd == -1) + { // error + log.logErr("Unable to create session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + // file created => store data + { + ssize_t writeRes; + + auto buf = serializeToBuf(); + if (!buf.first) + { + LOG(SESSIONS, ERR, "Unable to save session file", filePath); + goto err_closefile; + } + + writeRes = write(fd, buf.first.get(), buf.second); + + if(writeRes != (ssize_t)buf.second) + { + log.logErr("Unable to store session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_closefile; + } + } + + retVal = true; + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, "Session file stored: " + filePath); + + // error compensation +err_closefile: + close(fd); + + return retVal; +} + +/** + * Clears out all sessions from session store. + * Caller must hold lock on session store or otherwise ensure no access to the session store takes + * place. + * Intended for using prior to a session store resync on the secondary of a mirror buddy group. + * This method *does not* unlink disposed files or remove their chunk files on storage servers, + * since this may invalidate results of previous resync activity or conflict with the role of the + * current server as secondary in a mirror group. + * @returns true if all sessions could be removed, false if there are sessions still referenced and + * can't be removed. + */ +bool SessionStore::clear() +{ + MetaStore* metaStore = Program::getApp()->getMetaStore(); + + for (SessionMapIter sessionIt = sessions.begin(); sessionIt != sessions.end(); /* inc in body */) + { + // Iter is incremented here because removeSessionUnlocked invalidates it. + Session* session = removeSessionUnlocked((sessionIt++)->first); + if (session) + { + // session was removed - clean it up + SessionFileStore* sessionFiles = session->getFiles(); + + SessionFileList removedSessionFiles; + UIntList referencedSessionFiles; + + sessionFiles->removeAllSessions(&removedSessionFiles, &referencedSessionFiles); + + // referencedSessionFiles should always be empty, because otherwise, the session itself + // would also still be referenced somewhere, so we wouldn't end up here. Nevertheless, + // check + log. + if (!referencedSessionFiles.empty()) + { + LOG(SESSIONS, ERR, "Session file still references in unreferenced session."); + continue; + } + + for (SessionFileListIter fileIt = removedSessionFiles.begin(); + fileIt != removedSessionFiles.end(); ++fileIt) + { + SessionFile* sessionFile = *fileIt; + unsigned accessFlags = sessionFile->getAccessFlags(); + unsigned numHardlinks; // ignored here + unsigned numInodeRefs; // ignored here + bool lastWriterClosed; // ignored here + + MetaFileHandle inode = sessionFile->releaseInode(); + + EntryInfo* entryInfo = sessionFile->getEntryInfo(); + metaStore->closeFile(entryInfo, std::move(inode), accessFlags, &numHardlinks, + &numInodeRefs, lastWriterClosed); + + delete sessionFile; + } + + delete session; + } + else + { + LOG(SESSIONS, ERR, "Session still referenced", ("ID", sessionIt->first)); + } + } + + return true; +} + +bool SessionStore::operator==(const SessionStore& other) const +{ + struct ops { + static bool cmp(const SessionMapVal& lhs, const SessionMapVal& rhs) + { + return lhs.first == rhs.first + && *lhs.second->getReferencedObject() == *rhs.second->getReferencedObject(); + } + }; + + return sessions.size() == other.sessions.size() + && std::equal( + sessions.begin(), sessions.end(), + other.sessions.begin(), + ops::cmp); +} diff --git a/meta/source/session/SessionStore.h b/meta/source/session/SessionStore.h new file mode 100644 index 0000000..1aba07f --- /dev/null +++ b/meta/source/session/SessionStore.h @@ -0,0 +1,97 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include "Session.h" + + +typedef ObjectReferencer SessionReferencer; +typedef std::map SessionMap; +typedef SessionMap::iterator SessionMapIter; +typedef SessionMap::value_type SessionMapVal; + +typedef std::list SessionList; +typedef SessionList::iterator SessionListIter; + +/* + * A session always belongs to a client ID, therefore the session ID is always the nodeID of the + * corresponding client + */ +class SessionStore +{ + friend class TestSerialization; // for testing + + public: + SessionStore() {} + + Session* referenceSession(NumNodeID sessionID, bool addIfNotExists=true); + void releaseSession(Session* session); + void syncSessions(const std::vector& masterList, SessionList& outRemovedSessions, + NumNodeIDList& outUnremovableSessions); + + NumNodeIDList getAllSessionIDs(); + size_t getSize(); + + void serialize(Serializer& ser) const; + void deserialize(Deserializer& des); + void deserializeLockStates(Deserializer& des, MetaStore& metaStore); + + std::pair, size_t> serializeToBuf(); + bool deserializeFromBuf(const char* buf, size_t bufLen, MetaStore& metaStore); + bool loadFromFile(const std::string& filePath, MetaStore& metaStore); + bool saveToFile(const std::string& filePath); + + bool clear(); + + EntryLockStore* getEntryLockStore() + { + return &this->entryLockStore; + } + + bool operator==(const SessionStore& other) const; + + bool operator!=(const SessionStore& other) const { return !(*this == other); } + + private: + SessionMap sessions; + + Mutex mutex; + + EntryLockStore entryLockStore; // Locks on entryIDs to synchronize with mirror buddy. + + void addSession(Session* session); // actually not needed (maybe later one day...) + bool removeSession(NumNodeID sessionID); // actually not needed (maybe later one day...) + Session* removeSessionUnlocked(NumNodeID sessionID); + + bool relinkInodes(MetaStore& store) + { + bool result = true; + + for (auto it = sessions.begin(); it != sessions.end(); ) + { + const bool relinkRes = it->second->getReferencedObject()->relinkInodes(store); + + if (!relinkRes) + { + // Relinking failed on at least one SessionFile in this Session -> remove if empty + if (it->second->getReferencedObject()->getFiles()->getSize() == 0) + sessions.erase(it++); + else + ++it; + } + else + { + ++it; + } + + result &= relinkRes; + } + + return result; + } +}; + diff --git a/meta/source/storage/DentryStoreData.h b/meta/source/storage/DentryStoreData.h new file mode 100644 index 0000000..0ca7b97 --- /dev/null +++ b/meta/source/storage/DentryStoreData.h @@ -0,0 +1,103 @@ +/* + * Dentry information stored on disk + */ + +#pragma once + + +#include + +/* Note: Don't forget to update DiskMetaData::getSupportedDentryFeatureFlags() if you add new + * flags here. */ + +// Feature flags, 16 bit +#define DENTRY_FEATURE_INODE_INLINE 1 // inode inlined into a dentry +#define DENTRY_FEATURE_IS_FILEINODE 2 // file-inode +#define DENTRY_FEATURE_MIRRORED 4 // feature flag for mirrored dentries (deprecated) +#define DENTRY_FEATURE_BUDDYMIRRORED 8 // feature flag to indicate buddy mirrored dentries +#define DENTRY_FEATURE_32BITIDS 16 // dentry uses 32bit node IDs on disk + +class DentryStoreData +{ + friend class DirEntry; + friend class DiskMetaData; + friend class FileInode; + + public: + + DentryStoreData() + : entryType(DirEntryType_INVALID), + ownerNodeID(0), + dentryFeatureFlags(0) + { } + + DentryStoreData(const std::string& entryID, DirEntryType entryType, NumNodeID ownerNodeID, + unsigned dentryFeatureFlags) + : entryID(entryID), + entryType(entryType), + ownerNodeID(ownerNodeID), + dentryFeatureFlags( (uint16_t)dentryFeatureFlags) + { } + + std::string entryID; // a filesystem-wide identifier for this dir + DirEntryType entryType; + NumNodeID ownerNodeID; // 0 means undefined + uint16_t dentryFeatureFlags; + + protected: + + // getters / setters + + void setEntryID(const std::string& entryID) + { + this->entryID = entryID; + } + + const std::string& getEntryID() const + { + return this->entryID; + } + + void setDirEntryType(DirEntryType entryType) + { + this->entryType = entryType; + } + + DirEntryType getDirEntryType() const + { + return this->entryType; + } + + void setOwnerNodeID(NumNodeID ownerNodeID) + { + this->ownerNodeID = ownerNodeID; + } + + NumNodeID getOwnerNodeID() const + { + return this->ownerNodeID; + } + + void setDentryFeatureFlags(unsigned dentryFeatureFlags) + { + this->dentryFeatureFlags = dentryFeatureFlags; + } + + void addDentryFeatureFlag(unsigned dentryFeatureFlag) + { + this->dentryFeatureFlags |= dentryFeatureFlag; + } + + void removeDentryFeatureFlag(unsigned flag) + { + this->dentryFeatureFlags &= ~flag; + } + + public: + unsigned getDentryFeatureFlags() const + { + return this->dentryFeatureFlags; + } +}; + + diff --git a/meta/source/storage/DirEntry.cpp b/meta/source/storage/DirEntry.cpp new file mode 100644 index 0000000..1d89031 --- /dev/null +++ b/meta/source/storage/DirEntry.cpp @@ -0,0 +1,869 @@ +#include +#include +#include +#include "MetadataEx.h" +#include "DirEntry.h" + +#include + +/* + * Store the dirEntryID file. This is a normal dirEntry (with inlined inode), + * but the file name is the entryID. + * + * @param logContext + * @param idPath - path to the idFile, including the file name + */ +FhgfsOpsErr DirEntry::storeInitialDirEntryID(const char* logContext, const std::string& idPath) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + char buf[DIRENTRY_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + // create file + + /* note: if we ever think about switching to a rename-based version here, we must keep very + long user file names in mind, which might lead to problems if we add an extension to the + temporary file name. */ + + int openFlags = O_CREAT|O_EXCL|O_WRONLY; + + int fd = open(idPath.c_str(), openFlags, 0644); + if (unlikely (fd == -1) ) // this is our ID file, failing to create it is very unlikely + { // error + LogContext(logContext).logErr("Unable to create dentry file: " + idPath + ". " + + "SysErr: " + System::getErrString() ); + + if (errno == EMFILE) + { /* Creating the file succeeded, but there are already too many open file descriptors to + * open the file. We don't want to leak an entry-by-id file, so delete it. + * We only want to delete the file for specific errors, as for example EEXIST would mean + * we would delete an existing (probably) working entry. */ + int unlinkRes = unlink(idPath.c_str() ); + if (unlinkRes && errno != ENOENT) + LogContext(logContext).logErr("Failed to unlink failed dentry: " + idPath + ". " + + "SysErr: " + System::getErrString() ); + } + + if (errno == EEXIST) + { + /* EEXIST never should happen, as our ID is supposed to be unique, but there rare cases + * as for the upgrade tool */ + retVal = FhgfsOpsErr_EXISTS; + #ifdef BEEGFS_DEBUG + LogContext(logContext).logBacktrace(); + #endif + } + else + { + retVal = FhgfsOpsErr_INTERNAL; + } + + return retVal; + } + + // serialize (to new buf) + serializeDentry(ser); + if (!ser.good()) + { + LogContext(logContext).logErr("Dentry too large: " + idPath + "."); + retVal = FhgfsOpsErr_INTERNAL; + } + + // write buf to file + + if(useXAttrs) + { // extended attribute + int setRes = fsetxattr(fd, META_XATTR_NAME, buf, ser.size(), 0); + + if(unlikely(setRes == -1) ) + { // error + LogContext(logContext).logErr("Unable to store dentry xattr metadata: " + idPath + ". " + + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + goto error_closefile; + } + } + else + { // normal file content + ssize_t writeRes = write(fd, buf, ser.size()); + + if(unlikely(writeRes != (ssize_t)ser.size())) + { // error + LogContext(logContext).logErr("Unable to store dentry metadata: " + idPath + ". " + + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + goto error_closefile; + } + } + + close(fd); + + return retVal; + + // error compensation +error_closefile: + close(fd); + + int unlinkRes = unlink(idPath.c_str() ); + if (unlikely(unlinkRes && errno != ENOENT) ) + { + LogContext(logContext).logErr("Creating the dentry-by-name file failed and" + "now also deleting the dentry-by-id file fails: " + idPath); + } + + return retVal; +} + +/** + * Store the dirEntry as file name + */ +FhgfsOpsErr DirEntry::storeInitialDirEntryName(const char* logContext, const std::string& idPath, + const std::string& namePath, bool isNonInlinedInode) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + int linkRes = link(idPath.c_str(), namePath.c_str() ); + if (linkRes) + { /* Creating the dirEntry-by-name failed, most likely this is EEXIST. + * In principle it also might be possible there is an invalid dentry-by-name file, + * however, we already want to delete those during lookup calls now. So invalid + * entries are supposed to be very very unlikely and so no self-healing code is + * implemented here. */ + + if (likely(errno == EEXIST) ) + retVal = FhgfsOpsErr_EXISTS; + else + { + LogContext(logContext).logErr("Creating the dentry-by-name file failed: Path: " + + namePath + " SysErr: " + System::getErrString() ); + + retVal = FhgfsOpsErr_INTERNAL; + } + + int unlinkRes = unlink(idPath.c_str() ); + if (unlikely(unlinkRes) ) + { + LogContext(logContext).logErr("Creating the dentry-by-name file failed and" + "now also deleting the dentry-by-id file fails: " + idPath); + } + + return retVal; + } + + if (isNonInlinedInode) + { + // unlink the dentry-by-id file - we don't need it for dirs (or non-inlined inodes in general) + int unlinkRes = unlink(idPath.c_str() ); + if (unlikely(unlinkRes) ) + { + LogContext(logContext).logErr("Failed to unlink the (dir) dentry-by-id file "+ idPath + + " SysErr: " + System::getErrString() ); + } + } + + LOG_DEBUG(logContext, 4, "Initial dirEntry stored: " + namePath); + + return retVal; +} + +/** + * Note: Wrapper/chooser for storeUpdatedDirEntryBufAsXAttr/Contents. + * + * @param buf the serialized object state that is to be stored + */ +bool DirEntry::storeUpdatedDirEntryBuf(const std::string& idStorePath, char* buf, unsigned bufLen) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + bool result = useXAttrs + ? storeUpdatedDirEntryBufAsXAttr(idStorePath, buf, bufLen) + : storeUpdatedDirEntryBufAsContents(idStorePath, buf, bufLen); + + return result; +} + +/** + * Note: Don't call this directly, use the wrapper storeUpdatedDirEntryBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool DirEntry::storeUpdatedDirEntryBufAsXAttr(const std::string& idStorePath, + char* buf, unsigned bufLen) +{ + const char* logContext = DIRENTRY_LOG_CONTEXT "(store updated xattr metadata)"; + + // write data to file + + int setRes = setxattr(idStorePath.c_str(), META_XATTR_NAME, buf, bufLen, 0); + + if(unlikely(setRes == -1) ) + { // error + LogContext(logContext).logErr("Unable to write dentry update: " + + idStorePath + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + + LOG_DEBUG(logContext, 4, "Dentry update stored: " + idStorePath); + + return true; +} + +/** + * Stores the update directly to the current metadata file (instead of creating a separate file + * first and renaming it). + * + * Note: Don't call this directly, use the wrapper storeUpdatedDirEntryBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool DirEntry::storeUpdatedDirEntryBufAsContents(const std::string& idStorePath, char* buf, + unsigned bufLen) +{ + const char* logContext = DIRENTRY_LOG_CONTEXT "(store updated metadata in-place)"; + + int fallocRes; + ssize_t writeRes; + int truncRes; + + // open file (create it, but not O_EXCL because a former update could have failed) + + int openFlags = O_CREAT|O_WRONLY; + + int fd = open(idStorePath.c_str(), openFlags, 0644); + if(fd == -1) + { // error + LogContext(logContext).logErr("Unable to create dentry metadata update file: " + + idStorePath + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + // make sure we have enough room to write our update + fallocRes = posix_fallocate(fd, 0, bufLen); // (note: posix_fallocate does not set errno) + if(fallocRes == EBADF) + { // special case for XFS bug + struct stat statBuf; + int statRes = fstat(fd, &statBuf); + + if (statRes == -1) + { + LogContext(logContext).log(Log_WARNING, "Unexpected error: fstat() failed with SysErr: " + + System::getErrString(errno) ); + goto error_closefile; + } + + if (statBuf.st_size < bufLen) + { + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for metadata update failed: " + idStorePath + ". " + + "SysErr: " + System::getErrString(fallocRes) + " " + "statRes: " + StringTk::intToStr(statRes) + " " + "oldSize: " + StringTk::intToStr(statBuf.st_size) ); + goto error_closefile; + } + else + { // // XFS bug! We only return an error if statBuf.st_size < bufLen. Ingore fallocRes then + LOG_DEBUG(logContext, Log_SPAM, "Ignoring kernel file system bug: " + "posix_fallocate() failed for len < filesize"); + } + } + else + if (fallocRes != 0) + { // default error handling if posix_fallocate() failed + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for metadata update failed: " + idStorePath + ". " + + "SysErr: " + System::getErrString(fallocRes) ); + goto error_closefile; + + } + + // write data to file + + writeRes = write(fd, buf, bufLen); + + if(unlikely(writeRes != (ssize_t)bufLen) ) + { // error + LogContext(logContext).logErr("Unable to write dentry metadata update: " + + idStorePath + ". " + "SysErr: " + System::getErrString() ); + + goto error_closefile; + } + + // truncate in case the update lead to a smaller file size + truncRes = ftruncate(fd, bufLen); + if(truncRes == -1) + { // ignore trunc errors + LogContext(logContext).log(Log_WARNING, "Unable to truncate metadata file (strange, but " + "proceeding anyways): " + idStorePath + ". " + "SysErr: " + System::getErrString() ); + } + + close(fd); + + LOG_DEBUG(logContext, 4, "Dentry metadata update stored: " + idStorePath); + + return true; + + + // error compensation +error_closefile: + close(fd); + + return false; +} + +/** + * Store an update DirEntry. + * + * Note: Never write to a dentry using the entryNamePath. We might simply write to the wrong + * path. E.g. after a rename() and overwriting an opened file. Closing the overwritten + * file will result in an inode update. If then some data structures are not correct + * yet, writing to dentry-by-name instead of dentry-by-id might update the wrong file, + * instead of simply returning an error message. + * However, an exception is fsck, which needs to modify the dentry-by-name directly to update + * a dentry-owner. + */ +bool DirEntry::storeUpdatedDirEntry(const std::string& dirEntryPath) +{ + char buf[DIRENTRY_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + + serializeDentry(ser); + + std::string idStorePath = dirEntryPath + "/" + name; + + bool result = storeUpdatedDirEntryBuf(idStorePath, buf, ser.size()); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(idStorePath, MetaSyncFileType::Dentry); + + return result; +} + +/** + * Store the inlined inode from a dir-entry. + * */ +FhgfsOpsErr DirEntry::storeUpdatedInode(const std::string& dirEntryPath) +{ + if (!this->getIsInodeInlined() ) + return FhgfsOpsErr_INODENOTINLINED; + + char buf[DIRENTRY_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + + serializeDentry(ser); + + std::string idStorePath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + getEntryID(); + + bool storeRes = storeUpdatedDirEntryBuf(idStorePath, buf, ser.size()); + if (!storeRes) + return FhgfsOpsErr_SAVEERROR; + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(idStorePath, MetaSyncFileType::Inode); + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr DirEntry::removeDirEntryFile(const std::string& filePath) +{ + int unlinkRes = unlink(filePath.c_str() ); + if (unlinkRes == 0) + return FhgfsOpsErr_SUCCESS; + + if (errno == ENOENT) + return FhgfsOpsErr_PATHNOTEXISTS; + + LOG(GENERAL, ERR, "Unable to delete dentry file", filePath, sysErr); + return FhgfsOpsErr_INTERNAL; +} + +/** + * Remove the given filePath. This method is used for dirEntries-by-entryID and dirEntries-by-name. + */ +FhgfsOpsErr DirEntry::removeDirEntryName(const char* logContext, const std::string& filePath, + bool isBuddyMirrored) +{ + FhgfsOpsErr retVal = removeDirEntryFile(filePath); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(filePath, MetaSyncFileType::Dentry); + + return retVal; +} + +/** + * Remove the dirEntrID file. + */ +FhgfsOpsErr DirEntry::removeDirEntryID(const std::string& dirEntryPath, + const std::string& entryID, bool isBuddyMirrored) +{ + std::string idPath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + entryID; + + FhgfsOpsErr idUnlinkRes = removeDirEntryFile(idPath); + + if (likely(idUnlinkRes == FhgfsOpsErr_SUCCESS)) + LOG_DBG(GENERAL, DEBUG, "Dir-Entry ID metadata deleted", idPath); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(idPath, MetaSyncFileType::Inode); + + return idUnlinkRes; +} + + +/** + * Remove a dir-entry with an inlined inode. We cannot remove the inode, though and so + * will rename the dir-entry into the inode-hash directories to create a non-inlined + * inode (with dentry format, though). + * + * @param unlinkFileName Unlink only the ID file or also the entryName. If false entryName might + * be empty. + */ +FhgfsOpsErr DirEntry::removeBusyFile(const std::string& dirEntryBasePath, + const std::string& entryID, const std::string& entryName, unsigned unlinkTypeFlags) +{ + const char* logContext = "Unlinking dirEnty with busy inlined inode"; + + App* app = Program::getApp(); + + std::string dentryPath = dirEntryBasePath + '/' + entryName; + std::string idPath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryBasePath ) + entryID; + + std::string inodePath = MetaStorageTk::getMetaInodePath( + getIsBuddyMirrored() + ? app->getBuddyMirrorInodesPath()->str() + : app->getInodesPath()->str(), + entryID); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + if (unlinkTypeFlags & DirEntry_UNLINK_FILENAME) + { + // Delete the dentry-by-name + + int unlinkNameRes = unlink(dentryPath.c_str() ); + if (unlinkNameRes) + { + if (errno != ENOENT) + { + LogContext(logContext).logErr("Failed to unlink the dirEntry file: " + dentryPath + + " SysErr: " + System::getErrString() ); + + retVal = FhgfsOpsErr_INTERNAL; + } + else + retVal = FhgfsOpsErr_PATHNOTEXISTS; + + goto out; + } + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(dentryPath, MetaSyncFileType::Dentry); + + retVal = FhgfsOpsErr_SUCCESS; + } + + if (unlinkTypeFlags & DirEntry_UNLINK_ID) + { + // Rename the ID to the inode directory + + int renameRes = rename(idPath.c_str(), inodePath.c_str() ); + if (!renameRes) + { + /* Posix rename() has a very weird feature - it does nothing if fromPath and toPath + * are a hard-link pointing to each other. And it even does not give an error message. + * Seems they wanted to have rename(samePath, samePath). + * Once we do support hard links, we are going to hard-link dentryPath and inodePath + * to each other. An unlink() then should be done using the seperate dentry/inode path, + * but if there should be race between link() and unlink(), we might leave a dentry in + * place, which was supposed to be unlinked. So without an error message from rename() + * if it going to do nothing, we always need to try to unlink the file now. We can only + * hope the kernel still has a negative dentry in place, which will immediately tell + * that the file already does not exist anymore. */ + unlink(idPath.c_str() ); + + // Now link to the disposal dir + DirInode* disposalDir = getIsBuddyMirrored() + ? app->getBuddyMirrorDisposalDir() + : app->getDisposalDir(); + disposalDir->linkFileInodeToDir(inodePath, entryID); // entryID will be the new fileName + // Note: we ignore a possible error here, as don't know what to do with it. + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + resync->addDeletion(idPath, MetaSyncFileType::Inode); + resync->addModification(inodePath, MetaSyncFileType::Inode); + } + + retVal = FhgfsOpsErr_SUCCESS; + } + else + { + int errCode = errno; + + LogContext(logContext).logErr("Failed to move dirEntry:" + " From: " + idPath + + " To: " + inodePath + + " SysErr: " + System::getErrString() ); + + if (unlinkTypeFlags & DirEntry_UNLINK_FILENAME) + { + // take the error code (SUCCESS) from file name unlink + } + else + { + if (errCode == ENOENT) + retVal = FhgfsOpsErr_PATHNOTEXISTS; + else + retVal = FhgfsOpsErr_INTERNAL; + } + } + + } + +out: + return retVal; +} + + + +/** + * Note: Wrapper/chooser for loadFromFileXAttr/Contents. + * To be used for all Dentry (dir-entry) operations. + */ +bool DirEntry::loadFromFileName(const std::string& dirEntryPath, const std::string& entryName) +{ + std::string entryNamePath = dirEntryPath + "/" + entryName; + + return loadFromFile(entryNamePath); +} + +/** + * To be used for all operations regarding the inlined inode. + */ +bool DirEntry::loadFromID(const std::string& dirEntryPath, const std::string& entryID) +{ + std::string idStorePath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + entryID; + + return loadFromFile(idStorePath); +} + +/** + * Note: Wrapper/chooser for loadFromFileXAttr/Contents. + * Note: Do not call directly, but use loadFromFileName() or loadFromID() + * Retrieve the dir-entry either from xattrs or from real file data - configuration option + */ +bool DirEntry::loadFromFile(const std::string& path) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + if(useXAttrs) + return loadFromFileXAttr(path); + + return loadFromFileContents(path); +} + +/** + * Note: Don't call this directly, use the wrapper loadFromFileName(). + */ +bool DirEntry::loadFromFileXAttr(const std::string& path) +{ + const char* logContext = DIRENTRY_LOG_CONTEXT "(load from xattr file)"; + Config* cfg = Program::getApp()->getConfig(); + + bool retVal = false; + + char buf[DIRENTRY_SERBUF_SIZE]; + + ssize_t getRes = getxattr(path.c_str(), META_XATTR_NAME, buf, DIRENTRY_SERBUF_SIZE); + if(getRes > 0) + { // we got something => deserialize it + Deserializer des(buf, getRes); + deserializeDentry(des); + if(unlikely(!des.good())) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize dir-entry file: " + path); + goto error_exit; + } + + retVal = true; + } + else + if( (getRes == -1) && (errno == ENOENT) ) + { // file not exists + LOG_DEBUG_CONTEXT(LogContext(logContext), Log_DEBUG, "dir-entry file not exists: " + + path + ". " + "SysErr: " + System::getErrString() ); + } + else + if( ( (getRes == 0) || ( (getRes == -1) && (errno == ENODATA) ) ) && + (cfg->getStoreSelfHealEmptyFiles() ) ) + { // empty link file probably due to server crash => self-heal through removal + if (likely(this->name != META_DIRENTRYID_SUB_STR) ) + { + LogContext(logContext).logErr("Found an empty dir-entry file. " + "(Self-healing through file removal): " + path); + + int unlinkRes = unlink(path.c_str() ); + if(unlinkRes == -1) + { + LogContext(logContext).logErr("File removal for self-healing failed: " + path + ". " + "SysErr: " + System::getErrString() ); + } + } + } + else + { // unhandled error + LogContext(logContext).logErr("Unable to open/read dir-entry file: " + path + ". " + + "SysErr: " + System::getErrString() ); + } + + +error_exit: + + return retVal; +} + +/** + * Note: Don't call this directly, use the wrapper loadFromFile(). + */ +bool DirEntry::loadFromFileContents(const std::string& path) +{ + const char* logContext = DIRENTRY_LOG_CONTEXT "(load from file)"; + Config* cfg = Program::getApp()->getConfig(); + + bool retVal = false; + char buf[DIRENTRY_SERBUF_SIZE]; + int readRes; + + int openFlags = O_NOATIME | O_RDONLY; + + int fd = open(path.c_str(), openFlags); + if(fd == -1) + { // open failed + if(likely(errno == ENOENT) ) + { // file not exists + LOG_DEBUG_CONTEXT(LogContext(logContext), Log_DEBUG, "Unable to open dentry file: " + + path + ". " + "SysErr: " + System::getErrString() ); + } + else + { + LogContext(logContext).logErr("Unable to open link file: " + path + ". " + + "SysErr: " + System::getErrString() ); + } + + goto error_donothing; + } + + readRes = read(fd, buf, DIRENTRY_SERBUF_SIZE); + if(likely(readRes > 0) ) + { // we got something => deserialize it + Deserializer des(buf, readRes); + deserializeDentry(des); + if(unlikely(!des.good())) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize dentry file: " + path); + goto error_close; + } + + retVal = true; + } + else + if( (readRes == 0) && cfg->getStoreSelfHealEmptyFiles() ) + { // empty link file probably due to server crash => self-heal through removal + LogContext(logContext).logErr("Found an empty link file. " + "(Self-healing through file removal): " + path); + + int unlinkRes = unlink(path.c_str() ); + if(unlinkRes == -1) + { + LogContext(logContext).logErr("File removal for self-healing failed: " + path + ". " + "SysErr: " + System::getErrString() ); + } + } + else + { // read error + LogContext(logContext).logErr("Unable to read denty file: " + path + ". " + + "SysErr: " + System::getErrString() ); + } + + +error_close: + close(fd); + +error_donothing: + + return retVal; +} + +DirEntryType DirEntry::loadEntryTypeFromFile(const std::string& path, const std::string& entryName) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + if(useXAttrs) + return loadEntryTypeFromFileXAttr(path, entryName); + + return loadEntryTypeFromFileContents(path, entryName); + +} + +/** + * Note: We don't do any serious error checking here, we just want to find out the type or will + * return DirEntryType_INVALID otherwise (eg if file not found) + */ +DirEntryType DirEntry::loadEntryTypeFromFileXAttr(const std::string& path, + const std::string& entryName) +{ + DirEntryType retVal = DirEntryType_INVALID; + + std::string storePath(path + "/" + entryName); + + char buf[DIRENTRY_SERBUF_SIZE]; + + int getRes = getxattr(storePath.c_str(), META_XATTR_NAME, buf, DIRENTRY_SERBUF_SIZE); + if(getRes <= 0) + { // getting failed + goto out; + } + + retVal = (DirEntryType)buf[DIRENTRY_TYPE_BUF_POS]; + +out: + + return retVal; +} + +/** + * Note: We don't do any serious error checking here, we just want to find out the type or will + * return DirEntryType_INVALID otherwise (eg if file not found) + */ +DirEntryType DirEntry::loadEntryTypeFromFileContents(const std::string& path, + const std::string& entryName) +{ + DirEntryType retVal = DirEntryType_INVALID; + + std::string storePath(path + "/" + entryName); + + int openFlags = O_NOATIME | O_RDONLY; + + int fd = open(storePath.c_str(), openFlags); + if(fd == -1) + { // open failed + return DirEntryType_INVALID; + } + + char buf; + int readRes = pread(fd, &buf, 1, DIRENTRY_TYPE_BUF_POS); + if(likely(readRes > 0) ) + { // we got something + retVal = (DirEntryType)buf; + } + + close(fd); + + return retVal; +} + + +DirEntry* DirEntry::createFromFile(const std::string& path, const std::string& entryName) +{ + std::string filepath = path + "/" + entryName; + + DirEntry* newDir = new DirEntry(entryName); + + bool loadRes = newDir->loadFromFileName(path, entryName); + if(!loadRes) + { + delete(newDir); + return NULL; + } + + return newDir; +} + +/** + * Return the inlined inode from a dir-entry. + */ +FileInode* DirEntry::createInodeByID(const std::string& dirEntryPath, EntryInfo* entryInfo) +{ + bool loadRes = loadFromID(dirEntryPath, entryInfo->getEntryID() ); + if (!loadRes) + return NULL; + + if (!this->getIsInodeInlined() ) + return NULL; + + unsigned dentryFeatureFlags = getDentryFeatureFlags(); + + unsigned inodeFeatureFlags = this->inodeData.getInodeFeatureFlags(); + + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_PARENTID) + { + // i.e. file was renamed between directories, disk data already have origParentEntryID + } + else + this->inodeData.setDynamicOrigParentEntryID(entryInfo->getParentEntryID() ); + + /* NOTE: origParentUID also might not be on disk, but the deserializer then set it from + * statData->uid */ + + FileInode* inode = new (std::nothrow) FileInode(this->getID(), &this->inodeData, + this->getEntryType(), dentryFeatureFlags); + if (unlikely(!inode) ) + { + LogContext(__func__).logErr("Out of memory, failed to allocate inode."); + + return NULL; // out of memory + } + + return inode; +} + + +/** + * Note: Must be called before any of the disk modifying methods + * (otherwise they will fail) + * + * @param path does not include the filename + */ +FhgfsOpsErr DirEntry::storeInitialDirEntry(const std::string& dirEntryPath) +{ + const char* logContext = DIRENTRY_LOG_CONTEXT "(store initial dirEntry)"; + + LOG_DEBUG(logContext, 4, "Storing initial dentry metadata for ID: '" + getEntryID() + "'"); + + std::string idPath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + getEntryID(); + + // first create the dirEntry-by-ID + FhgfsOpsErr entryIdRes = this->storeInitialDirEntryID(logContext, idPath); + + if (entryIdRes != FhgfsOpsErr_SUCCESS) + return entryIdRes; + + bool nonInlined = DirEntryType_ISDIR(getEntryType()) || !this->getIsInodeInlined(); + + // eventually the dirEntry-by-name + std::string namePath = dirEntryPath + '/' + this->name; + FhgfsOpsErr result = this->storeInitialDirEntryName(logContext, idPath, namePath, nonInlined); + + if (result == FhgfsOpsErr_SUCCESS && getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + if (!nonInlined) + resync->addModification(idPath, MetaSyncFileType::Inode); + + resync->addModification(namePath, MetaSyncFileType::Dentry); + } + + return result; +} diff --git a/meta/source/storage/DirEntry.h b/meta/source/storage/DirEntry.h new file mode 100644 index 0000000..87e22e4 --- /dev/null +++ b/meta/source/storage/DirEntry.h @@ -0,0 +1,332 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DentryStoreData.h" +#include "DiskMetaData.h" +#include "MetadataEx.h" +#include "FileInodeStoreData.h" + + +#define DIRENTRY_LOG_CONTEXT "DirEntry " + +#define DirEntry_UNLINK_ID 1 +#define DirEntry_UNLINK_FILENAME 2 +#define DirEntry_UNLINK_ID_AND_FILENAME (DirEntry_UNLINK_ID | DirEntry_UNLINK_FILENAME) + +/* + * Class for directory entries (aka "dentries", formerly also referred to as "links"), which + * contains the filename and information about where to find the inode (e.g. for remote dir + * inodes). + * + * Note on locking: In contrast to files/dirs, dentries are not referenced. Every caller/thread + * gets its own copy to work with, so dentry instances are not even shared. That's why we don't + * have a mutex here. + */ +class DirEntry +{ + friend class MetaStore; + friend class DirEntryStore; + friend class DirInode; + friend class FileInode; + friend class GenericDebugMsgEx; + friend class RecreateDentriesMsgEx; + + public: + + DirEntry(DirEntryType entryType, const std::string& name, const std::string& entryID, + NumNodeID ownerNodeID) : dentryDiskData(entryID, entryType, ownerNodeID, 0), name(name) + { + } + + /** + * Note: This constructor does not perform initialization, so use it for + * metadata loading only. + */ + DirEntry(const std::string& entryName) : name(entryName) + { + // this->name = entryName; // set in initializer list + } + + static DirEntry* createFromFile(const std::string& path, const std::string& entryName); + static DirEntryType loadEntryTypeFromFile(const std::string& path, + const std::string& entryName); + + protected: + + bool loadFromID(const std::string& dirEntryPath, const std::string& entryID); + + private: + + DentryStoreData dentryDiskData; // data stored on disk + + FileInodeStoreData inodeData; + + std::string name; // the user-friendly name, note: not set on reading entries anymore + + FhgfsOpsErr storeInitialDirEntryID(const char* logContext, const std::string& idPath); + static FhgfsOpsErr storeInitialDirEntryName(const char* logContext, const std::string& idPath, + const std::string& namePath, bool isNonInlinedInode); + bool storeUpdatedDirEntryBuf(const std::string& idStorePath, char* buf, unsigned bufLen); + bool storeUpdatedDirEntryBufAsXAttr(const std::string& idStorePath, char* buf, + unsigned bufLen); + bool storeUpdatedDirEntryBufAsContents(const std::string& idStorePath, char* buf, + unsigned bufLen); + bool storeUpdatedDirEntry(const std::string& dirEntryPath); + FhgfsOpsErr storeUpdatedInode(const std::string& dirEntryPath); + + static FhgfsOpsErr removeDirEntryName(const char* logContext, const std::string& filePath, + bool isBuddyMirrored); + FhgfsOpsErr removeBusyFile(const std::string& dirEntryBasePath, const std::string& entryID, + const std::string& entryName, unsigned unlinkTypeFlags); + + FileInode* createInodeByID(const std::string& dirEntryPath, EntryInfo* entryInfo); + + bool loadFromFileName(const std::string& dirEntryPath, const std::string& entryName); + bool loadFromFile(const std::string& path); + bool loadFromFileXAttr(const std::string& path); + bool loadFromFileContents(const std::string& path); + + static DirEntryType loadEntryTypeFromFileXAttr(const std::string& path, + const std::string& entryName); + static DirEntryType loadEntryTypeFromFileContents(const std::string& path, + const std::string& entryName); + + FhgfsOpsErr storeInitialDirEntry(const std::string& dirEntryPath); + + static FhgfsOpsErr removeDirEntryFile(const std::string& filePath); + static FhgfsOpsErr removeDirEntryID(const std::string& dirEntryPath, + const std::string& entryID, bool isBuddyMirrored); + + public: + + // inliners + + /** + * Remove file dentries. + * + * If the argument is a directory the caller already must have checked if the directory + * is empty. + */ + static FhgfsOpsErr removeFileDentry(const std::string& dirEntryPath, + const std::string& entryID, const std::string& entryName, unsigned unlinkTypeFlags, + bool isBuddyMirrored) + { + const char* logContext = DIRENTRY_LOG_CONTEXT "(remove stored file dentry)"; + FhgfsOpsErr retVal; + + // first we delete entry-by-name and use this retVal as return code + if (unlinkTypeFlags & DirEntry_UNLINK_FILENAME) + { + std::string namePath = dirEntryPath + '/' + entryName; + + retVal = removeDirEntryName(logContext, namePath, isBuddyMirrored); + + if (retVal == FhgfsOpsErr_SUCCESS && (unlinkTypeFlags & DirEntry_UNLINK_ID) ) + { + // once the dirEntry-by-name was successfully unlinked, unlink dirEntry-by-ID + removeDirEntryID(dirEntryPath, entryID, isBuddyMirrored); // error code is ignored + } + else + { + /* We must not try to delete the ID file on FhgfsOpsErr_NOTEXISTS, as during a race + * (possible locking issue) the file may have been renamed and so the ID might be + * still valid. + */ + } + + } + else + if (unlinkTypeFlags & DirEntry_UNLINK_ID) + retVal = removeDirEntryID(dirEntryPath, entryID, isBuddyMirrored); + else + { + /* It might happen that the code was supposed to unlink an ID only, but if the inode + * has a link count > 1, even the ID is not supposed to be unlinked. So unlink + * is a no-op then. */ + retVal = FhgfsOpsErr_SUCCESS; + } + + return retVal; + } + + /** + * Remove directory dentries. + * + * If the argument is a directory the caller already must have checked if the directory + * is empty. + */ + static FhgfsOpsErr removeDirDentry(const std::string& dirEntryPath, + const std::string& entryName, bool isBuddyMirrored) + { + const char* logContext = DIRENTRY_LOG_CONTEXT "(remove stored directory dentry)"; + + std::string namePath = dirEntryPath + '/' + entryName; + + FhgfsOpsErr retVal = removeDirEntryName(logContext, namePath, isBuddyMirrored); + + return retVal; + } + + + // getters & setters + + + /** + * Set a new ownerNodeID, used by fsck or generic debug message. + */ + bool setOwnerNodeID(const std::string& dirEntryPath, NumNodeID newOwner) + { + bool success = true; + + // only dentries without inlined inodes have an ownerNode field, trying to set the owner + // node on a dentry with an inlined inode is thus impossible. + if (getIsInodeInlined()) + return false; + + NumNodeID oldOwner = this->getOwnerNodeID(); + this->dentryDiskData.setOwnerNodeID(newOwner); + + if (!storeUpdatedDirEntry(dirEntryPath)) + { // failed to update metadata => restore old value + this->dentryDiskData.setOwnerNodeID(oldOwner); + + success = false; + } + + return success; + } + + void setFileInodeData(FileInodeStoreData& inodeData) + { + this->inodeData = inodeData; + + unsigned updatedFlags = getDentryFeatureFlags() | + (DENTRY_FEATURE_INODE_INLINE | DENTRY_FEATURE_IS_FILEINODE); + + setDentryFeatureFlags(updatedFlags); + } + + void setBuddyMirrorFeatureFlag() + { + addDentryFeatureFlag(DENTRY_FEATURE_BUDDYMIRRORED); + } + + bool getIsBuddyMirrored() + { + return (getDentryFeatureFlags() & DENTRY_FEATURE_BUDDYMIRRORED); + } + + unsigned getDentryFeatureFlags() + { + return this->dentryDiskData.getDentryFeatureFlags(); + } + + void setDentryFeatureFlags(unsigned featureFlags) + { + this->dentryDiskData.setDentryFeatureFlags(featureFlags); + } + + void addDentryFeatureFlag(unsigned featureFlag) + { + this->dentryDiskData.addDentryFeatureFlag(featureFlag); + } + + void removeDentryFeatureFlag(unsigned featureFlag) + { + this->dentryDiskData.removeDentryFeatureFlag(featureFlag); + } + + // getters + + const std::string& getEntryID() + { + return this->dentryDiskData.getEntryID(); + + } + + const std::string& getID() + { + return this->dentryDiskData.getEntryID(); + } + + /** + * Note: Should not be changed after object init => not synchronized! + */ + DirEntryType getEntryType() + { + return this->dentryDiskData.getDirEntryType(); + } + + const std::string& getName() + { + return this->name; + } + + NumNodeID getOwnerNodeID() + { + return this->dentryDiskData.getOwnerNodeID(); + } + + void getEntryInfo(const std::string& parentEntryID, int flags, EntryInfo* outEntryInfo) + { + if (getIsInodeInlined() ) + flags |= ENTRYINFO_FEATURE_INLINED; + + if (getIsBuddyMirrored()) + flags |= ENTRYINFO_FEATURE_BUDDYMIRRORED; + + outEntryInfo->set(getOwnerNodeID(), parentEntryID, getID(), name, + getEntryType(), flags); + } + + + /** + * Unset the DENTRY_FEATURE_INODE_INLINE flag + */ + void unsetInodeInlined() + { + uint16_t dentryFeatureFlags = this->dentryDiskData.getDentryFeatureFlags(); + + dentryFeatureFlags &= ~(DENTRY_FEATURE_INODE_INLINE); + + this->dentryDiskData.setDentryFeatureFlags(dentryFeatureFlags); + } + /** + * Check if the inode is inlined and no flag is set to indicate the same object + * (file-as-hard-link) is also in the inode-hash directories. + */ + bool getIsInodeInlined() + { + if (this->dentryDiskData.getDentryFeatureFlags() & DENTRY_FEATURE_INODE_INLINE) + return true; + + return false; + } + + void serializeDentry(Serializer& ser) + { + DiskMetaData diskMetaData(&this->dentryDiskData, &this->inodeData); + diskMetaData.serializeDentry(ser); + } + + void deserializeDentry(Deserializer& des) + { + DiskMetaData diskMetaData(&this->dentryDiskData, &this->inodeData); + diskMetaData.deserializeDentry(des); + } + + protected: + + FileInodeStoreData* getInodeStoreData(void) + { + return &this->inodeData; + } + + +}; + + diff --git a/meta/source/storage/DirEntryStore.cpp b/meta/source/storage/DirEntryStore.cpp new file mode 100644 index 0000000..125ecd8 --- /dev/null +++ b/meta/source/storage/DirEntryStore.cpp @@ -0,0 +1,777 @@ +#include +#include +#include "DirEntryStore.h" + +#include +#include +#include +#include + +/** + * Get a dir-entry-path + * + * Note: Not supposed to be used outside of this file and shall be inlined + */ +static inline std::string getDirEntryStoreDynamicEntryPath(const std::string& parentID, + bool isBuddyMirrored) +{ + App* app = Program::getApp(); + + if ( isBuddyMirrored ) + return MetaStorageTk::getMetaDirEntryPath( + app->getBuddyMirrorDentriesPath()->str(), parentID); + else + return MetaStorageTk::getMetaDirEntryPath(app->getDentriesPath()->str(), + parentID); +} + + +/** + * Note: Sets the parentID to an invalid value, so do not forget to set the parentID before + * adding any elements. + */ +DirEntryStore::DirEntryStore() : + parentID(""), isBuddyMirrored(false) +{ +} + +/** + * @param parentID ID of the directory to which this store belongs + * @param isBuddyMirrored true if the directory to which this store belongs is buddy mirrored + */ +DirEntryStore::DirEntryStore(const std::string& parentID, bool isBuddyMirrored) : + parentID(parentID), dirEntryPath(getDirEntryStoreDynamicEntryPath(parentID, isBuddyMirrored) ), + isBuddyMirrored(isBuddyMirrored) +{ +} + +/* + * Create the new directory for dentries (dir-entries). This directory will contain directory + * entries of files and sub-directories of the directory given by dirID. + */ +FhgfsOpsErr DirEntryStore::mkDentryStoreDir(const std::string& dirID, bool isBuddyMirrored) +{ + const char* logContext = "Directory (store initial metadata dir)"; + + App* app = Program::getApp(); + + const Path* dentriesPath = + isBuddyMirrored ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string contentsDirStr = MetaStorageTk::getMetaDirEntryPath( + dentriesPath->str(), dirID); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + // create (contents) directory, which will hold directory entries of subdirs and subfiles + + int mkDirRes = mkdir(contentsDirStr.c_str(), 0755); + if(mkDirRes) + { // error + if(errno == EEXIST) + retVal = FhgfsOpsErr_EXISTS; + else + { + LogContext(logContext).logErr("Unable to create contents directory: " + contentsDirStr + + ". " + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + } + + return retVal; + } + + LOG_DEBUG(logContext, 4, "Metadata dir created: " + contentsDirStr); + + // create the dirEntryID directory, which allows access to inlined inodes via dirID access + + std::string contentsDirIDStr = MetaStorageTk::getMetaDirEntryIDPath(contentsDirStr); + + int mkDirIDRes = mkdir(contentsDirIDStr.c_str(), 0755); + if(mkDirIDRes) + { // So contentsDirStr worked, but creating the sub directory contentsDirIDStr failed + + LogContext(logContext).logErr("Unable to create dirEntryID directory: " + + contentsDirIDStr + ". " + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + + int unlinkRes = unlink(contentsDirStr.c_str() ); + if (unlinkRes) + { + // we can only write a log message here, but can't do anything about it + LogContext(logContext).logErr("Failed to remove: " + contentsDirStr + + ". " + "SysErr: " + System::getErrString() ); + } + + return retVal; + } + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + resync->addModification(contentsDirStr, MetaSyncFileType::Directory); + resync->addModification(contentsDirIDStr, MetaSyncFileType::Directory); + } + + return retVal; +} + +/** + * Remove directory for dir-entries (dentries) + * + * Note: Assumes that the caller already verified that the directory is empty + */ +bool DirEntryStore::rmDirEntryStoreDir(const std::string& id, bool isBuddyMirrored) +{ + const char* logContext = "Directory (remove contents dir)"; + + App* app = Program::getApp(); + + const Path* dentriesPath = + isBuddyMirrored ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string contentsDirStr = MetaStorageTk::getMetaDirEntryPath( + dentriesPath->str(), id); + + std::string contentsDirIDStr = MetaStorageTk::getMetaDirEntryIDPath(contentsDirStr); + + LOG_DEBUG(logContext, Log_DEBUG, + "Removing content directory: " + contentsDirStr + "; " "id: " + id + "; isBuddyMirrored: " + + StringTk::intToStr(isBuddyMirrored)); + + // remove the dirEntryID directory + int rmdirIdRes = rmdir(contentsDirIDStr.c_str() ); + if(rmdirIdRes) + { // error + LogContext(logContext).logErr("Unable to delete dirEntryID directory: " + contentsDirIDStr + + ". " + "SysErr: " + System::getErrString() ); + + if (errno != ENOENT) + return false; + } + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(contentsDirIDStr, MetaSyncFileType::Directory); + + // remove contents directory + + int rmdirRes = rmdir(contentsDirStr.c_str() ); + if(rmdirRes) + { // error + LogContext(logContext).logErr("Unable to delete contents directory: " + contentsDirStr + + ". " + "SysErr: " + System::getErrString() ); + + if (errno != ENOENT) + return false; + } + + LOG_DEBUG(logContext, 4, "Contents directory deleted: " + contentsDirStr); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(contentsDirStr, MetaSyncFileType::Directory); + + return true; +} + + +/** + * @param file belongs to the store after calling this method - so do not free it and don't + * use it any more afterwards (re-get it from this store if you need it) + */ +FhgfsOpsErr DirEntryStore::makeEntry(DirEntry* entry) +{ + FhgfsOpsErr mkRes; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + mkRes = makeEntryUnlocked(entry); + + safeLock.unlock(); + + return mkRes; +} + +/** + * @param file belongs to the store after calling this method - so do not free it and don't + * use it any more afterwards (re-get it from this store if you need it) + */ +FhgfsOpsErr DirEntryStore::makeEntryUnlocked(DirEntry* entry) +{ + const std::string& dirEntryPath = getDirEntryPathUnlocked(); + const char* logContext = "make meta dir-entry"; + + FhgfsOpsErr mkRes = entry->storeInitialDirEntry(dirEntryPath); + + if (unlikely(mkRes != FhgfsOpsErr_SUCCESS) && mkRes != FhgfsOpsErr_EXISTS) + LogContext(logContext).logErr(std::string("Failed to create: name: ") + entry->getName() + + std::string(" entryID: ") + entry->getID() + " in path: " + dirEntryPath); + + return mkRes; +} + +/** + * Create a new dir-entry based on an inode, which is stored in dir-entry format + * (with inlined inode) + */ +FhgfsOpsErr DirEntryStore::linkInodeToDir(const std::string& inodePath, const std::string &fileName) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr retVal = linkInodeToDirUnlocked(inodePath, fileName); + + safeLock.unlock(); + + return retVal; +} + + +FhgfsOpsErr DirEntryStore::linkInodeToDirUnlocked(const std::string& inodePath, + const std::string& fileName) +{ + const char* logContext = "make meta dir-entry"; + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + std::string dirEntryPath = getDirEntryPathUnlocked() + '/' + fileName; + + int linkRes = link(inodePath.c_str(), dirEntryPath.c_str() ); + if (linkRes) + { + LogContext(logContext).logErr(std::string("Failed to create link from: ") + inodePath + + " To: " + dirEntryPath + " SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + } + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(dirEntryPath, MetaSyncFileType::Inode); + + return retVal; +} + +/** + * note: only for dir dentries + * + * @param outDirEntry is object of the removed dirEntry, maybe NULL of the caller does not need it + */ +FhgfsOpsErr DirEntryStore::removeDir(const std::string& entryName, DirEntry** outDirEntry) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr delErr = removeDirUnlocked(entryName, outDirEntry); + + safeLock.unlock(); + + return delErr; +} + +/** + * note: only for file dentries + * + * @param unlinkEntryName If false do not try to unlink the dentry-name entry, entryName even + * might not be set. + * @param outEntry will be set to the unlinked file and the object must then be deleted by the + * caller (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr DirEntryStore::unlinkDirEntry(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr delErr = unlinkDirEntryUnlocked(entryName, entry, unlinkTypeFlags); + + safeLock.unlock(); + + return delErr; +} + +/** + * Note: only for dir dentries + * Note: This does not lock the mutex, so it must already be locked when calling this. + * + * @param outDirEntry is object of the removed dirEntry, maybe NULL of the caller does not need it + */ +FhgfsOpsErr DirEntryStore::removeDirUnlocked(const std::string& entryName, DirEntry** outDirEntry) +{ + SAFE_ASSIGN(outDirEntry, NULL); + + DirEntry* entry = DirEntry::createFromFile(getDirEntryPathUnlocked(), entryName); + if(!entry) + return FhgfsOpsErr_PATHNOTEXISTS; + + if(!DirEntryType_ISDIR(entry->getEntryType() ) ) + { + delete(entry); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + + FhgfsOpsErr retVal = DirEntry::removeDirDentry(getDirEntryPathUnlocked(), entryName, + entry->getIsBuddyMirrored()); + + if (outDirEntry) + *outDirEntry = entry; + else + delete(entry); + + return retVal; +} + +/** + * Note: only for file dentries + * Note: This does not lock the mutex, so it must already be locked when calling this. + * + * @param inEntry might be NULL and the entry then needs to be loaded from disk. + * @param unlinkEntryName If false do not try to unlink the dentry-name entry, entryName even + * might not be set. So !unlinkEntryName and !inEntry together are a logical conflict. + * @param outEntry will be set to the unlinked file and the object must then be deleted by the + * caller (can be NULL if the caller is not interested in the file). Also will not be set if + * inEntry is not NULL, as the caller already knows the DirEntry then. + * @param outEntryID for callers that need the entryID and want to avoid the overhead of using + * outEntry (can be NULL if the caller is not interested); only set on success + */ +FhgfsOpsErr DirEntryStore::unlinkDirEntryUnlocked(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags) +{ + FhgfsOpsErr delErr = DirEntry::removeFileDentry(getDirEntryPathUnlocked(), entry->getID(), + entryName, unlinkTypeFlags, entry->getIsBuddyMirrored()); + + return delErr; +} + +/** + * Create a hardlink from 'fromEntryName' to 'toEntryName'. + * + * NOTE: Only do this for the entries in the same directory with inlined inodes. In order to avoid + * a wrong link count on a possible crash/reboot, the link count already must have been + * increased by 1 (If the link count is too high after a crash we might end up with leaked + * chunk files on unlink, but not with missing chunk files if done the other way around). + */ +FhgfsOpsErr DirEntryStore::linkEntryInDir(const std::string& fromEntryName, + const std::string& toEntryName) +{ + const char *logContext = "DirEntryStore renameEntry"; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + std::string fromPath = getDirEntryPathUnlocked() + '/' + fromEntryName; + std::string toPath = getDirEntryPathUnlocked() + '/' + toEntryName; + + int linkRes = link(fromPath.c_str(), toPath.c_str() ); + if (linkRes) + { + if (errno == EEXIST) + retVal = FhgfsOpsErr_EXISTS; + else + { + LogContext(logContext).logErr(std::string("Failed to link file in dir: ") + + getDirEntryPathUnlocked() + " from: " + fromEntryName + " to: " + toEntryName + + ". SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + } + } + + safeLock.unlock(); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(toPath, MetaSyncFileType::Dentry); + + return retVal; +} + +/** + * In constrast to the moving...()-methods, this method performs a simple rename of an entry, + * where no moving is involved. + * + * @param outRemovedEntry accoring to the rules, this can only be an overwritten file, not a dir + * + * NOTE: The caller already must have done rename-sanity checks. + */ +FhgfsOpsErr DirEntryStore::renameEntry(const std::string& fromEntryName, + const std::string& toEntryName) +{ + const char *logContext = "DirEntryStore renameEntry"; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + std::string fromPath = getDirEntryPathUnlocked() + '/' + fromEntryName; + std::string toPath = getDirEntryPathUnlocked() + '/' + toEntryName; + + int renameRes = rename(fromPath.c_str(), toPath.c_str() ); + + if (renameRes) + { + LogContext(logContext).logErr(std::string("Failed to rename file in dir: ") + + getDirEntryPathUnlocked() + " from: " + fromEntryName + " to: " + toEntryName + + ". SysErr: " + System::getErrString() ); + + retVal = FhgfsOpsErr_INTERNAL; + } + + safeLock.unlock(); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + resync->addDeletion(fromPath, MetaSyncFileType::Dentry); + resync->addModification(toPath, MetaSyncFileType::Dentry); + } + + return retVal; +} + +/** + * Note: serverOffset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as offset (similar to posix telldir/seekdir). + * + * Note: You have reached the end of the directory when success is returned and + * "outNames.size() != maxOutNames". + * + * @param serverOffset zero-based offset; represents the native local fs offset (as in telldir() ). + * @param filterDots true if "." and ".." should not be returned. + * @param outArgs outNewOffset is only valid if return value indicates success, + * outEntryTypes and outEntryIDs may be NULL, the rest is required. + */ +FhgfsOpsErr DirEntryStore::listIncrementalEx(int64_t serverOffset, + unsigned maxOutNames, bool filterDots, ListIncExOutArgs& outArgs) +{ + // note: we need offsets here that are stable after unlink, because apps like bonnie++ use + // readdir(), then unlink() the returned files and expect readdir() to continue after that. + // this won't work if we use our own offset number and skip the given number of initial + // entries each time. that's why we use the native local file system offset and seek here. + + const char* logContext = "DirEntryStore (list inc)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + uint64_t numEntries = 0; + struct dirent* dirEntry = NULL; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + DIR* dirHandle = opendir(getDirEntryPathUnlocked().c_str() ); + if(!dirHandle) + { + LogContext(logContext).logErr(std::string("Unable to open dentry directory: ") + + getDirEntryPathUnlocked() + ". SysErr: " + System::getErrString() ); + + goto err_unlock; + } + + + // seek to offset (if provided) + if(serverOffset) + { + seekdir(dirHandle, serverOffset); // (seekdir has no return value) + } + + + // loop over the actual directory entries + for( ; + (numEntries < maxOutNames) && + (dirEntry = StorageTk::readdirFilteredEx(dirHandle, filterDots, true) ); + numEntries++) + { + outArgs.outNames->push_back(dirEntry->d_name); + + if(outArgs.outServerOffsets) + outArgs.outServerOffsets->push_back(dirEntry->d_off); + + SAFE_ASSIGN(outArgs.outNewServerOffset, dirEntry->d_off); + + if(outArgs.outEntryTypes || outArgs.outEntryIDs) + { + DirEntryType entryType; + std::string entryID; + + if(!filterDots && !strcmp(dirEntry->d_name, ".") ) + { + entryType = DirEntryType_DIRECTORY; + entryID = "<.>"; + } + else + if(!filterDots && !strcmp(dirEntry->d_name, "..") ) + { + entryType = DirEntryType_DIRECTORY; + entryID = "<..>"; + } + else + { // load dentry metadata + DirEntry entry(dirEntry->d_name); + + bool loadSuccess = entry.loadFromFileName(getDirEntryPathUnlocked(), dirEntry->d_name); + if (likely(loadSuccess) ) + { + entryType = entry.getEntryType(); + entryID = entry.getEntryID(); + } + else + { // loading failed + entryType = DirEntryType_INVALID; + entryID = ""; + + errno = 0; + } + } + + if(outArgs.outEntryTypes) + outArgs.outEntryTypes->push_back( (int)entryType); + + if (outArgs.outEntryIDs) + outArgs.outEntryIDs->push_back(entryID); + } + + } + + if(!dirEntry && errno) + { + LogContext(logContext).logErr(std::string("Unable to fetch links directory entry from: ") + + getDirEntryPathUnlocked() + ". SysErr: " + System::getErrString() ); + } + else + { // all entries read + retVal = FhgfsOpsErr_SUCCESS; + } + + + closedir(dirHandle); + +err_unlock: + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Note: serverOffset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as outNewOffset. + * Note: You have reached the end of the directory when "outNames.size() != maxOutNames". + * Note: This function was written for fsck + * + * @param serverOffset zero-based offset; represents the native local fs offset; preferred over + * incrementalOffset; use -1 here if you want to seek to e.g. the n-th element and use the slow + * incrementalOffset for that case. + * @param incrementalOffset zero-based offset; only used if serverOffset is -1; skips the given + * number of entries. + * @param outArgs outNewOffset is only valid if return value indicates success, outEntryTypes is + * not used (NULL), outNames is required. + */ +FhgfsOpsErr DirEntryStore::listIDFilesIncremental(int64_t serverOffset, + uint64_t incrementalOffset, unsigned maxOutNames, ListIncExOutArgs& outArgs) +{ + // note: we need offsets here that are stable after unlink, because apps like bonnie++ use + // readdir(), then unlink() the returned files and expect readdir() to continue after that. + // this won't work if we use our own offset number and skip the given number of initial + // entries each time. that's why we use the native local file system offset and seek here. + // (incrementalOffset is only provided as fallback for client directory seeks and should + // be avoided when possible.) + + const char* logContext = "DirEntryStore (list ID files inc)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + uint64_t numEntries = 0; + struct dirent* dirEntry = NULL; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + std::string path = MetaStorageTk::getMetaDirEntryIDPath(getDirEntryPathUnlocked()); + + DIR* dirHandle = opendir(path.c_str() ); + if(!dirHandle) + { + LogContext(logContext).logErr(std::string("Unable to open dentry-by-ID directory: ") + + path + ". SysErr: " + System::getErrString() ); + + goto err_unlock; + } + + + errno = 0; // recommended by posix (readdir(3p) ) + + // seek to offset + if(serverOffset != -1) + { // caller provided direct offset + seekdir(dirHandle, serverOffset); // (seekdir has no return value) + } + else + { // slow path: incremental seek to current offset + for(uint64_t currentOffset = 0; + (currentOffset < incrementalOffset) && (dirEntry=StorageTk::readdirFiltered(dirHandle) ); + currentOffset++) + { + // (actual seek work done in loop header) + *outArgs.outNewServerOffset = dirEntry->d_off; + } + } + + // the actual entry reading + for( ; (numEntries < maxOutNames) && (dirEntry = StorageTk::readdirFiltered(dirHandle) ); + numEntries++) + { + outArgs.outNames->push_back(dirEntry->d_name); + *outArgs.outNewServerOffset = dirEntry->d_off; + } + + if(!dirEntry && errno) + { + LogContext(logContext).logErr(std::string("Unable to fetch dentry-by-ID entry from: ") + + path + ". SysErr: " + System::getErrString() ); + } + else + { // all entries read + retVal = FhgfsOpsErr_SUCCESS; + } + + + closedir(dirHandle); + +err_unlock: + safeLock.unlock(); // U N L O C K + + return retVal; +} + +bool DirEntryStore::exists(const std::string& entryName) +{ + bool existsRes = false; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + existsRes = existsUnlocked(entryName); + + safeLock.unlock(); // U N L O C K + + return existsRes; +} + +bool DirEntryStore::existsUnlocked(const std::string& entryName) +{ + bool existsRes = false; + std::string filepath = getDirEntryPathUnlocked() + "/" + entryName; + struct stat statBuf; + + int statRes = stat(filepath.c_str(), &statBuf); + if(!statRes) + existsRes = true; + + return existsRes; +} + + +/** + * @param outInodeMetaData might be NULL + * @return DirEntryType_INVALID if no such dir exists + * + */ +FhgfsOpsErr DirEntryStore::getEntryData(const std::string& entryName, EntryInfo* outInfo, + FileInodeStoreData* outInodeMetaData) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_PATHNOTEXISTS; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + DirEntry entry(entryName); + + bool loadRes = entry.loadFromFileName(getDirEntryPathUnlocked(), entryName); + if(loadRes) + { + /* copy FileInodeStoreData from entry to outInodeMetaData. We also do not want to allocate + * a new stripe pattern, so we simply copy the pointer and set + * entry.dentryInodeMeta.stripePattern = NULL. So on destruction of the object, it will not + * be deleted. */ + FileInodeStoreData* inodeDiskData = entry.getInodeStoreData(); + + if (outInodeMetaData) + { + *outInodeMetaData = *inodeDiskData; + inodeDiskData->setPattern(NULL); + } + + int flags = entry.getIsInodeInlined() ? ENTRYINFO_FEATURE_INLINED : 0; + + if (entry.getIsBuddyMirrored()) + flags |= ENTRYINFO_FEATURE_BUDDYMIRRORED; + + NumNodeID ownerNodeID = entry.getOwnerNodeID(); + std::string entryID = entry.getID(); + + outInfo->set(ownerNodeID, this->parentID, entryID, entryName, + entry.getEntryType(), flags); + + retVal = FhgfsOpsErr_SUCCESS; + } + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Load and return the dir-entry of the given entry-/fileName + */ +DirEntry* DirEntryStore::dirEntryCreateFromFile(const std::string& entryName) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + DirEntry* dirEntry= DirEntry::createFromFile(this->getDirEntryPathUnlocked(), entryName); + + safeLock.unlock(); // U N L O C K + + return dirEntry; +} + +/** + * Note: Unlocked, so make sure you hold the lock when calling this. + */ +const std::string& DirEntryStore::getDirEntryPathUnlocked() const +{ + return dirEntryPath; +} + +/** + * @param entryName + * @param ownerNode + * + * @return FhgfsOpsErr based on the result of the operation + */ +FhgfsOpsErr DirEntryStore::setOwnerNodeID(const std::string& entryName, NumNodeID ownerNode) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + DirEntry entry(entryName); + bool loadRes; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + loadRes = entry.loadFromFileName(getDirEntryPathUnlocked(), entryName); + if(!loadRes) + { // no such entry + retVal = FhgfsOpsErr_PATHNOTEXISTS; + } + else + { + if ( entry.setOwnerNodeID(getDirEntryPathUnlocked(), ownerNode) ) + { + retVal = FhgfsOpsErr_SUCCESS; + } + } + safeLock.unlock(); + + return retVal; +} + +/** + * Note: Unlocked. Use this only during init. + */ +void DirEntryStore::setParentID(const std::string& parentID, bool parentIsBuddyMirrored) +{ + this->parentID = parentID; + this->dirEntryPath = getDirEntryStoreDynamicEntryPath(parentID, parentIsBuddyMirrored); + this->isBuddyMirrored = parentIsBuddyMirrored; +} diff --git a/meta/source/storage/DirEntryStore.h b/meta/source/storage/DirEntryStore.h new file mode 100644 index 0000000..ad47261 --- /dev/null +++ b/meta/source/storage/DirEntryStore.h @@ -0,0 +1,201 @@ +#pragma once + +#include +#include +#include +#include +#include +#include "DirEntry.h" + + +struct ListIncExOutArgs +{ + ListIncExOutArgs(StringList* outNames, UInt8List* outEntryTypes, StringList* outEntryIDs, + Int64List* outServerOffsets, int64_t* outNewServerOffset) : + outNames(outNames), outEntryTypes(outEntryTypes), outEntryIDs(outEntryIDs), + outServerOffsets(outServerOffsets), outNewServerOffset(outNewServerOffset) + { + // see initializer list + } + + + StringList* outNames; /* required */ + UInt8List* outEntryTypes; /* optional (can be NULL if caller is not interested; contains + DirEntryType stored as int to ease message serialization) */ + StringList* outEntryIDs; /* optional (may be NULL if caller is not interested) */ + Int64List* outServerOffsets; /* optional (may be NULL if caller is not interested) */ + int64_t* outNewServerOffset; /* optional (may be NULL), equals last value from + outServerOffsets */ +}; + + +class DirEntryStore +{ + friend class DirInode; + friend class MetaStore; + + public: + DirEntryStore(); + DirEntryStore(const std::string& parentID, bool isBuddyMirrored); + + FhgfsOpsErr makeEntry(DirEntry* entry); + + FhgfsOpsErr linkEntryInDir(const std::string& fromEntryName, const std::string& toEntryName); + FhgfsOpsErr linkInodeToDir(const std::string& inodePath, const std::string &fileName); + + FhgfsOpsErr removeDir(const std::string& entryName, DirEntry** outDirEntry); + FhgfsOpsErr unlinkDirEntry(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags); + + FhgfsOpsErr renameEntry(const std::string& fromEntryName, const std::string& toEntryName); + + FhgfsOpsErr listIncrementalEx(int64_t serverOffset, unsigned maxOutNames, bool filterDots, + ListIncExOutArgs& outArgs); + FhgfsOpsErr listIDFilesIncremental(int64_t serverOffset, uint64_t incrementalOffset, + unsigned maxOutNames, ListIncExOutArgs& outArgs); + + bool exists(const std::string& entryName); + + FhgfsOpsErr getEntryData(const std::string& entryName, EntryInfo* outInfo, + FileInodeStoreData* outInodeMetaData); + FhgfsOpsErr setOwnerNodeID(const std::string& entryName, NumNodeID ownerNode); + + DirEntry* dirEntryCreateFromFile(const std::string& entryName); + + static FhgfsOpsErr mkDentryStoreDir(const std::string& dirID, bool isBuddyMirrored); + static bool rmDirEntryStoreDir(const std::string& id, bool isBuddyMirrored); + + private: + std::string parentID; // ID of the directory to which this store belongs + std::string dirEntryPath; /* path to dirEntry, without the last element (fileName) + * depends on parentID, so changes when parentID is set */ + + RWLock rwlock; + bool isBuddyMirrored; + + FhgfsOpsErr makeEntryUnlocked(DirEntry* entry); + FhgfsOpsErr linkInodeToDirUnlocked(const std::string& inodePath, const std::string &fileName); + + + FhgfsOpsErr removeDirUnlocked(const std::string& entryName, DirEntry** outDirEntry); + FhgfsOpsErr unlinkDirEntryUnlocked(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags); + + bool existsUnlocked(const std::string& entryName); + + const std::string& getDirEntryPathUnlocked() const; + + + public: + // inliners + + /** + * @return false if no link with this name exists + */ + bool getDentry(const std::string& entryName, DirEntry& outEntry) + { + // note: the difference to getDirDentry/getFileDentry is that this works independent + // of the link type + + bool exists = false; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + exists = outEntry.loadFromFileName(getDirEntryPathUnlocked(), entryName); + + safeLock.unlock(); + + return exists; + } + + const std::string& getParentEntryID() const + { + return this->parentID; + } + + /** + * @return false if no dir dentry (dir-entry) with this name exists or its type is not dir + */ + bool getDirDentry(const std::string& entryName, DirEntry& outEntry) + { + bool getRes = getDentry(entryName, outEntry); + return getRes && DirEntryType_ISDIR(outEntry.getEntryType() ); + } + + /** + * + * @return false if no dentry with this name exists or its type is not file + */ + bool getFileDentry(const std::string& entryName, DirEntry& outEntry) + { + bool getRes = getDentry(entryName, outEntry); + return getRes && DirEntryType_ISFILE(outEntry.getEntryType() ); + } + + bool getEntryInfo(const std::string& entryName, EntryInfo& outEntryInfo) + { + DirEntry entry(entryName); + std::string parentEntryID = this->getParentEntryID(); + int additionalFlags = 0; // unknown + + bool getRes = getDentry(entryName, entry); + if (getRes == true) + entry.getEntryInfo(parentEntryID, additionalFlags, &outEntryInfo); + + return getRes; + } + + bool getFileEntryInfo(const std::string& entryName, EntryInfo& outEntryInfo) + { + bool getRes = getEntryInfo(entryName, outEntryInfo); + return getRes && DirEntryType_ISFILE(outEntryInfo.getEntryType() ); + } + + bool getDirEntryInfo(const std::string& entryName, EntryInfo& outEntryInfo) + { + bool getRes = getEntryInfo(entryName, outEntryInfo); + return getRes && DirEntryType_ISDIR(outEntryInfo.getEntryType() ); + } + + std::string getDirEntryPath() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + std::string dirEntryPath = this->dirEntryPath; + + safeLock.unlock(); + + return dirEntryPath; + } + + /* + * Note: No locking here, isBuddyMirrored should only be set on initialization + */ + bool getIsBuddyMirrored() const + { + return this->isBuddyMirrored; + } + + // getters & setters + void setParentID(const std::string& parentID, bool parentIsBuddyMirrored); + + private: + + /** + * Handle the unlink of a file, for which we need to keep the inode. + */ + FhgfsOpsErr removeBusyFile(const std::string& entryName, DirEntry* dentry, + unsigned unlinkTypeFlags) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // Lock + + FhgfsOpsErr retVal = dentry->removeBusyFile(getDirEntryPathUnlocked(), dentry->getID(), + entryName, unlinkTypeFlags); + + safeLock.unlock(); + + return retVal; + } +}; + + diff --git a/meta/source/storage/DirInode.cpp b/meta/source/storage/DirInode.cpp new file mode 100644 index 0000000..1f23065 --- /dev/null +++ b/meta/source/storage/DirInode.cpp @@ -0,0 +1,2095 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "DiskMetaData.h" +#include "DirEntry.h" +#include "DirInode.h" + +#include + +/** + * Constructur used to create new directories. + */ +DirInode::DirInode(const std::string& id, int mode, unsigned userID, unsigned groupID, + NumNodeID ownerNodeID, const StripePattern& stripePattern, bool isBuddyMirrored) + : id(id), + ownerNodeID(ownerNodeID), + parentNodeID(0), // 0 means undefined + featureFlags( DIRINODE_FEATURE_EARLY_SUBDIRS | DIRINODE_FEATURE_STATFLAGS | + (isBuddyMirrored ? DIRINODE_FEATURE_BUDDYMIRRORED : 0) ), + exclusive(false), + numSubdirs(0), + numFiles(0), + entries(id, isBuddyMirrored), + isLoaded(true) +{ + this->stripePattern = stripePattern.clone(); + + int64_t currentTimeSecs = TimeAbs().getTimeval()->tv_sec; + SettableFileAttribs settableAttribs = {mode, userID, groupID, currentTimeSecs, currentTimeSecs}; + this->statData.setInitNewDirInode(&settableAttribs, currentTimeSecs); +} + +/** + * Create a stripe pattern based on the current pattern config and add targets to it. + * + * @param preferredTargets may be NULL + * @param numtargets 0 to use setting from directory (-1/~0 for all available targets). + * @param chunksize 0 to use setting from directory, must be 2^n and >=64Ki otherwise. + * @param storagePoolId if this is set only targets from this pool are considered and settings of + * parent directory are ignored + * @return NULL on error, new object on success. + */ +StripePattern* DirInode::createFileStripePattern(const UInt16List* preferredTargets, + unsigned numtargets, unsigned chunksize, StoragePoolId storagePoolId) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + StripePattern* filePattern = createFileStripePatternUnlocked(preferredTargets, numtargets, + chunksize, storagePoolId); + + safeLock.unlock(); // U N L O C K + + return filePattern; +} + +/** + * @param preferredTargets may be NULL + * @param numtargets 0 to use setting from directory (-1/~0 for all available targets). + * @param chunksize 0 to use setting from directory, must be 2^n and >=64Ki otherwise. + * @param storagePoolId if this is set only targets from this pool are considered and settings of + * parent directory are ignored + * @return NULL on error, new object on success. + */ +StripePattern* DirInode::createFileStripePatternUnlocked(const UInt16List* preferredTargets, + unsigned numtargets, unsigned chunksize, StoragePoolId storagePoolId) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + TargetChooserType chooserType = cfg->getTuneTargetChooserNum(); + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return NULL; + + if(!chunksize) + chunksize = stripePattern->getChunkSize(); // use default dir chunksize + else + if(unlikely( (chunksize < STRIPEPATTERN_MIN_CHUNKSIZE) || + !MathTk::isPowerOfTwo(chunksize) ) ) + return NULL; // invalid chunksize given + + StripePattern* filePattern; + + // choose some available storage targets + + unsigned desiredNumTargets = numtargets ? numtargets : stripePattern->getDefaultNumTargets(); + unsigned minNumRequiredTargets = stripePattern->getMinNumTargets(); + + UInt16Vector stripeTargets; + + if (desiredNumTargets < minNumRequiredTargets) + desiredNumTargets = minNumRequiredTargets; + + // limit maximum number of targets per file to a safe value (such that serialized stripe patterns + // are bounded in size) + if(desiredNumTargets > DIRINODE_MAX_STRIPE_TARGETS) + desiredNumTargets = DIRINODE_MAX_STRIPE_TARGETS; + + // get the storage pool, which is set to later restrict targets to those contained in the pool + // note: storage pool is used from the parent dir, if not set explicitely + if (storagePoolId == StoragePoolStore::INVALID_POOL_ID) + storagePoolId = stripePattern->getStoragePoolId(); + + if(stripePattern->getPatternType() == StripePatternType_BuddyMirror) + { // buddy mirror is special case, because no randmoninternode support and NodeCapacityPool + + // get the storage pool, which is set and restrict targets to those contained in the pool + StoragePoolPtr storagePool = app->getStoragePoolStore()->getPool(storagePoolId); + if (!storagePool) + { + // means there won't be allowed targets => file will not be created + LOG(GENERAL, WARNING, "Stripe pattern has storage pool ID set, which doesn't exist.", + ("dirID", id), ("poolID", stripePattern->getStoragePoolId())); + return NULL; + } + + NodeCapacityPools* capacityPools = storagePool->getBuddyCapacityPools(); + + /* (note: chooserType random{inter,intra}node is not supported by buddy mirrors, because we + can't do the internal node-based grouping for buddy mirrors, so we fallback to random in + in this case) */ + if( (chooserType == TargetChooserType_RANDOMIZED) || + (chooserType == TargetChooserType_RANDOMINTERNODE) || + // (chooserType == TargetChooserType_RANDOMINTRANODE) || + (preferredTargets && !preferredTargets->empty() ) ) + { // randomized chooser, the only chooser that currently supports preferredTargets + capacityPools->chooseStorageTargets(desiredNumTargets, minNumRequiredTargets, + preferredTargets, &stripeTargets); + } + else + { // round robin or randomized round robin chooser + capacityPools->chooseStorageTargetsRoundRobin(desiredNumTargets, &stripeTargets); + + if(chooserType == TargetChooserType_RANDOMROBIN) + random_shuffle(stripeTargets.begin(), stripeTargets.end() ); // randomize result vector + } + } + else + { + // get the storage pool, which is set and restrict targets to those contained in the pool + StoragePoolPtr storagePool = app->getStoragePoolStore()->getPool(storagePoolId); + if (!storagePool) + { + // means there won't be allowed targets => file will not be created + LOG(GENERAL, WARNING, "Stripe pattern has storage pool ID set, which doesn't exist.", + ("dirID", id), ("poolID", stripePattern->getStoragePoolId())); + return NULL; + } + + TargetCapacityPools* capacityPools = storagePool->getTargetCapacityPools(); + + if(chooserType == TargetChooserType_RANDOMIZED || + (preferredTargets && !preferredTargets->empty() ) ) + { // randomized chooser, the only chooser that currently supports preferredTargets + capacityPools->chooseStorageTargets(desiredNumTargets, minNumRequiredTargets, + preferredTargets, &stripeTargets); + } + else + if(chooserType == TargetChooserType_RANDOMINTERNODE) + { // select targets from different nodeIDs + capacityPools->chooseTargetsInterdomain(desiredNumTargets, minNumRequiredTargets, + &stripeTargets); + } + else + if(chooserType == TargetChooserType_RANDOMINTRANODE) + { // select targets from different nodeIDs + capacityPools->chooseTargetsIntradomain(desiredNumTargets, minNumRequiredTargets, + &stripeTargets); + } + else + { // round robin or randomized round robin chooser + capacityPools->chooseStorageTargetsRoundRobin(desiredNumTargets, &stripeTargets); + + if(chooserType == TargetChooserType_RANDOMROBIN) + random_shuffle(stripeTargets.begin(), stripeTargets.end() ); // randomize result vector + } + } + + // check whether we got enough targets... + if(unlikely(stripeTargets.size() < minNumRequiredTargets) ) + return NULL; + + // clone the dir pattern with new stripeTargets... + + filePattern = stripePattern->clone(); + *filePattern->getStripeTargetIDsModifyable() = stripeTargets; + + if(cfg->getTuneRotateMirrorTargets() && + (stripePattern->getPatternType() == StripePatternType_Raid10) ) + { + // rotate the selected target by half their number and use the rotated vector as mirror + // targets. The advantage here is that we can use an odd number of stripe targets. + // Example: stripeTargets=1,2,3,4,5; mirrorTargets=3,4,5,1,2 + std::vector mirrorTargets; + + mirrorTargets.insert(mirrorTargets.end(), + stripeTargets.begin() + (stripeTargets.size() / 2), stripeTargets.end()); + mirrorTargets.insert(mirrorTargets.end(), + stripeTargets.begin(), stripeTargets.begin() + (stripeTargets.size() / 2)); + + *((Raid10Pattern*) filePattern)->getMirrorTargetIDsModifyable() = mirrorTargets; + } + + filePattern->setDefaultNumTargets(desiredNumTargets); + filePattern->setChunkSize(chunksize); + + return filePattern; +} + +StripePattern* DirInode::getStripePatternClone() +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + StripePattern* pattern = stripePattern->clone(); + + safeLock.unlock(); + + return pattern; +} + +/** + * @param newPattern will be cloned + */ +FhgfsOpsErr DirInode::setStripePattern(const StripePattern& newPattern, uint32_t actorUID) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_INTERNAL; + + if (actorUID != 0 && statData.getUserID() != actorUID) + return FhgfsOpsErr_PERM; + + StripePattern* oldPattern; + + if (actorUID != 0) + { + // Note there is no way to distinguish if the pool ID or pattern type in the pattern was + // actually updated by the user or if CTL just set it to the previous value. Only if either + // changes return a permissions error. This does mean non-root users that try to set these to + // their current values will not see a permissions error. + if (stripePattern->getStoragePoolId() != newPattern.getStoragePoolId()) { + return FhgfsOpsErr_PERM; + } + if (stripePattern->getPatternType() != newPattern.getPatternType()) { + return FhgfsOpsErr_PERM; + } + oldPattern = stripePattern->clone(); + stripePattern->setDefaultNumTargets(newPattern.getDefaultNumTargets()); + stripePattern->setChunkSize(newPattern.getChunkSize()); + } + else + { + oldPattern = this->stripePattern; + this->stripePattern = newPattern.clone(); + } + + if(!storeUpdatedMetaDataUnlocked() ) + { // failed to update metadata => restore old value + delete(this->stripePattern); // delete newPattern-clone + this->stripePattern = oldPattern; + return FhgfsOpsErr_INTERNAL; + } + else + { // sucess + delete(oldPattern); + } + + lock.unlock(); + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return FhgfsOpsErr_SUCCESS; +} + +/* + * set remote storage target(s) for DirInode + */ +FhgfsOpsErr DirInode::setRemoteStorageTarget(const RemoteStorageTarget& rst) +{ + const char* logContext = "Set Remote Storage Target (DirInode)"; + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_INTERNAL; + + auto [isValid, details] = rst.validateWithDetails(); + if (!isValid) + { + LogContext(logContext).log(Log_WARNING, "Invalid RST data: " + details); + return FhgfsOpsErr_INTERNAL; + } + + // copy-assignment + this->rstInfo = rst; + + if (storeRemoteStorageTargetInfoUnlocked()) + { + if (!getIsRstAvailable()) + { + this->addFeatureFlag(DIRINODE_FEATURE_HAS_RST); + if (!storeUpdatedMetaDataUnlocked()) + return FhgfsOpsErr_INTERNAL; + } + } + else + return FhgfsOpsErr_INTERNAL; + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr DirInode::clearRemoteStorageTarget() +{ + const char* logContext = "Clear Remote Storage Target (DirInode)"; + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + + if (!this->getIsRstAvailable()) + return FhgfsOpsErr_SUCCESS; + + // Clear RST feature flag and store updated metadata + removeFeatureFlag(DIRINODE_FEATURE_HAS_RST); + if (!storeUpdatedMetaDataUnlocked()) + { + LogContext(logContext).log(Log_WARNING, + "Failed to update metadata after clearing RST feature flag; dirID: " + getID()); + return FhgfsOpsErr_INTERNAL; + } + + // Clear in-memory RST info + this->rstInfo.reset(); + + // Remove RST xattr from meta file + App* app = Program::getApp(); + const Path* inodePath = getIsBuddyMirrored() ? + app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + int res = removexattr(metaFilename.c_str(), RST_XATTR_NAME); + if (unlikely(res == -1)) + { + if (errno == ENODATA) + { + LogContext(logContext).log(Log_WARNING, "RST xattr not found; dirID: " + getID()); + } + else + { + LogContext(logContext).log(Log_WARNING, "Failed to remove RST xattr; dirID: " + + getID() + "; error: " + System::getErrString()); + } + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Note: See listIncrementalEx for comments; this is just a wrapper for it that doen't retrieve the + * direntry types. + */ +FhgfsOpsErr DirInode::listIncremental(int64_t serverOffset, + unsigned maxOutNames, StringList* outNames, int64_t* outNewServerOffset) +{ + ListIncExOutArgs outArgs(outNames, NULL, NULL, NULL, outNewServerOffset); + + return listIncrementalEx(serverOffset, maxOutNames, true, outArgs); +} + +/** + * Note: Returns only a limited number of entries. + * + * Note: serverOffset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as outNewOffset (similar to telldir/seekdir). + * + * Note: You have reached the end of the directory when success is returned and + * "outNames.size() != maxOutNames". + * + * @param serverOffset zero-based offset + * @param maxOutNames the maximum number of entries that will be added to the outNames + * @param filterDots true to not return "." and ".." entries. + * @param outArgs outNewOffset is only valid if return value indicates success. + */ +FhgfsOpsErr DirInode::listIncrementalEx(int64_t serverOffset, + unsigned maxOutNames, bool filterDots, ListIncExOutArgs& outArgs) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + FhgfsOpsErr listRes = entries.listIncrementalEx(serverOffset, maxOutNames, filterDots, outArgs); + + safeLock.unlock(); // U N L O C K + + return listRes; +} + +/** + * Note: Returns only a limited number of dentry-by-ID file names + * + * Note: serverOffset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as outNewOffset. + * + * Note: this function was written for fsck + * + * @param serverOffset zero-based offset + * @param incrementalOffset zero-based offset; only used if serverOffset is -1 + * @param maxOutNames the maximum number of entries that will be added to the outNames + * @param outArgs outNewOffset is only valid if return value indicates success, outEntryTypes is + * not used (NULL), outNames is required. + */ +FhgfsOpsErr DirInode::listIDFilesIncremental(int64_t serverOffset, uint64_t incrementalOffset, + unsigned maxOutNames, ListIncExOutArgs& outArgs) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + FhgfsOpsErr listRes = entries.listIDFilesIncremental(serverOffset, incrementalOffset, + maxOutNames, outArgs); + + safeLock.unlock(); // U N L O C K + + return listRes; +} + +/** + * Check whether an entry with the given name exists in this directory. + */ +bool DirInode::exists(const std::string& entryName) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + bool retVal = entries.exists(entryName); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * @param outInodeMetaData might be NULL + * @return DirEntryType_INVALID if entry does not exist + */ +FhgfsOpsErr DirInode::getEntryData(const std::string& entryName, EntryInfo* outInfo, + FileInodeStoreData* outInodeMetaData) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + FhgfsOpsErr retVal = entries.getEntryData(entryName, outInfo, outInodeMetaData); + + if (retVal == FhgfsOpsErr_SUCCESS && DirEntryType_ISREGULARFILE(outInfo->getEntryType() ) ) + { + bool inStore = fileStore.isInStore(outInfo->getEntryID() ); + if (inStore) + { // hint for the caller not to rely on outInodeMetaData + retVal = FhgfsOpsErr_DYNAMICATTRIBSOUTDATED; + } + } + + safeLock.unlock(); // U N L O C K + + return retVal; +} + + +/** + * Create new directory entry in the directory given by DirInode + */ +FhgfsOpsErr DirInode::makeDirEntry(DirEntry& entry) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + // we always delete the entry from this method + FhgfsOpsErr mkRes = makeDirEntryUnlocked(&entry); + + safeLock.unlock(); // U N L O C K + + return mkRes; +} + +FhgfsOpsErr DirInode::makeDirEntryUnlocked(DirEntry* entry) +{ + FhgfsOpsErr mkRes = FhgfsOpsErr_INTERNAL; + + DirEntryType entryType = entry->getEntryType(); + if (unlikely( (!DirEntryType_ISFILE(entryType) && (!DirEntryType_ISDIR(entryType) ) ) ) ) + goto out; + + // load DirInode on demand if required, we need it now + if (!loadIfNotLoadedUnlocked()) + { + mkRes = FhgfsOpsErr_PATHNOTEXISTS; + goto out; + } + + mkRes = this->entries.makeEntry(entry); + if(mkRes == FhgfsOpsErr_SUCCESS) + { // entry successfully created + + if (DirEntryType_ISDIR(entryType) ) + { + // update our own dirInode + increaseNumSubDirsAndStoreOnDisk(); + } + else + { + // update our own dirInode + increaseNumFilesAndStoreOnDisk(); + } + + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + +out: + return mkRes; +} + +FhgfsOpsErr DirInode::linkFilesInDirUnlocked(const std::string& fromName, FileInode& fromInode, + const std::string& toName) +{ + FhgfsOpsErr linkRes = entries.linkEntryInDir(fromName, toName); + + if(linkRes == FhgfsOpsErr_SUCCESS) + { + // ignore the return code, time stamps are not that important + increaseNumFilesAndStoreOnDisk(); + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return linkRes; +} + + +/** + * Link an existing inode (stored in dir-entry format) to our directory. So add a new dirEntry. + * + * Used for example for linking an (unlinked/deleted) file-inode to the disposal dir. + */ +FhgfsOpsErr DirInode::linkFileInodeToDir(const std::string& inodePath, const std::string& fileName) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + // we always delete the entry from this method + FhgfsOpsErr retVal = linkFileInodeToDirUnlocked(inodePath, fileName); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +FhgfsOpsErr DirInode::linkFileInodeToDirUnlocked(const std::string& inodePath, + const std::string& fileName) +{ + bool loadRes = loadIfNotLoadedUnlocked(); + if (!loadRes) + return FhgfsOpsErr_PATHNOTEXISTS; + + FhgfsOpsErr retVal = this->entries.linkInodeToDir(inodePath, fileName); + + if (retVal == FhgfsOpsErr_SUCCESS) + increaseNumFilesAndStoreOnDisk(); + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return retVal; +} + +FhgfsOpsErr DirInode::removeDir(const std::string& entryName, DirEntry** outDirEntry) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + FhgfsOpsErr rmRes = removeDirUnlocked(entryName, outDirEntry); + + safeLock.unlock(); // U N L O C K + + return rmRes; +} + +/** + * @param outDirEntry is object of the removed dirEntry, maybe NULL of the caller does not need it + */ +FhgfsOpsErr DirInode::removeDirUnlocked(const std::string& entryName, DirEntry** outDirEntry) +{ + // load DirInode on demand if required, we need it now + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + + FhgfsOpsErr rmRes = entries.removeDir(entryName, outDirEntry); + + if(rmRes == FhgfsOpsErr_SUCCESS) + { + if (unlikely (!decreaseNumSubDirsAndStoreOnDisk() ) ) + rmRes = FhgfsOpsErr_INTERNAL; + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return rmRes; +} + +/** + * + * @param unlinkEntryName If true do not try to unlink the dentry-name entry, entryName even + * might not be set. + * @param outFile will be set to the unlinked file and the object must then be deleted by the caller + * (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr DirInode::renameDirEntry(const std::string& fromName, const std::string& toName, + DirEntry* overWriteEntry) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + FhgfsOpsErr renameRes = renameDirEntryUnlocked(fromName, toName, overWriteEntry); + + safeLock.unlock(); // U N L O C K + + return renameRes; +} + + +/** + * Rename an entry in the same directory. + */ +FhgfsOpsErr DirInode::renameDirEntryUnlocked(const std::string& fromName, const std::string& toName, + DirEntry* overWriteEntry) +{ + const char* logContext = "DirInode rename entry"; + + FhgfsOpsErr renameRes = entries.renameEntry(fromName, toName); + + if(renameRes == FhgfsOpsErr_SUCCESS) + { + if (overWriteEntry) + { + if (DirEntryType_ISDIR(overWriteEntry->getEntryType() ) ) + this->numSubdirs--; + else + this->numFiles--; + } + + // ignore the return code, time stamps are not that important + updateTimeStampsAndStoreToDisk(logContext); + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return renameRes; +} + +/** + * @param inEntry should be provided if available for performance, but may be NULL if + * unlinkTypeFlags == DirEntry_UNLINK_FILENAME_ONLY or unlinkTypeFlags == DirEntry_UNLINK_ID_AND_FILENAME + * @param unlinkEntryName If false do not try to unlink the dentry-name entry, entryName even + * might not be set (but inEntry must be set in this case). + * @param outFile will be set to the unlinked file and the object must then be deleted by the caller + * (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr DirInode::unlinkDirEntry(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + FhgfsOpsErr unlinkRes = unlinkDirEntryUnlocked(entryName, entry, unlinkTypeFlags); + + safeLock.unlock(); // U N L O C K + + return unlinkRes; +} + +/** + * @inEntry should be provided if available for performance reasons, but may be NULL (in which case + * unlinkTypeFlags must be != DirEntry_UNLINK_ID_ONLY) + */ +FhgfsOpsErr DirInode::unlinkDirEntryUnlocked(const std::string& entryName, DirEntry* inEntry, + unsigned unlinkTypeFlags) +{ + const char* logContext = "DirInode unlink entry"; + + // load DirInode on demand if required, we need it now + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + + DirEntry* entry; + DirEntry loadedEntry(entryName); + + if (!inEntry) + { + if (unlikely(!(unlinkTypeFlags & DirEntry_UNLINK_FILENAME) ) ) + { + LogContext(logContext).logErr("Bug: inEntry missing!"); + LogContext(logContext).logBacktrace(); + return FhgfsOpsErr_INTERNAL; + } + + bool getRes = getDentryUnlocked(entryName, loadedEntry); + if (!getRes) + return FhgfsOpsErr_PATHNOTEXISTS; + + entry = &loadedEntry; + } + else + entry = inEntry; + + FhgfsOpsErr unlinkRes = this->entries.unlinkDirEntry(entryName, entry, unlinkTypeFlags); + + if (unlinkRes != FhgfsOpsErr_SUCCESS) + goto out; + + if (unlinkTypeFlags & DirEntry_UNLINK_FILENAME) + { + bool decRes; + + if (DirEntryType_ISDIR(entry->getEntryType() ) ) + decRes = decreaseNumSubDirsAndStoreOnDisk(); + else + decRes = decreaseNumFilesAndStoreOnDisk(); + + if (unlikely(!decRes) ) + { + LogContext(logContext).logErr("Unlink succeeded, but failed to store updated DirInode" + " DirID: " + this->getID() ); + + // except of this warning we ignore this error, as the unlink otherwise succeeded + } + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + +out: + return unlinkRes; +} + +FhgfsOpsErr DirInode::refreshMetaInfo() +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + FhgfsOpsErr retVal = refreshMetaInfoUnlocked(); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Re-computes number of subentries. + * + * Note: Intended to be called only when you have reason to believe that the stored metadata is + * not correct (eg after an upgrade that introduced new metadata information). + */ +FhgfsOpsErr DirInode::refreshMetaInfoUnlocked() +{ + + // load DirInode on demand if required, we need it now + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + + FhgfsOpsErr retVal = refreshSubentryCountUnlocked(); + if(retVal == FhgfsOpsErr_SUCCESS) + { + if(!storeUpdatedMetaDataUnlocked() ) + retVal = FhgfsOpsErr_INTERNAL; + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return retVal; +} + +/** + * Reads all stored entry links (via entries::listInc() ) to re-compute number of subentries. + * + * Note: Intended to be called only when you have reason to believe that the stored metadata is + * not correct (eg after an upgrade that introduced new metadata information). + * Note: Does not update persistent metadata. + * Note: Unlocked, so hold the write lock when calling this. + */ +FhgfsOpsErr DirInode::refreshSubentryCountUnlocked() +{ + const char* logContext = "Directory (refresh entry count)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + unsigned currentNumSubdirs = 0; + unsigned currentNumFiles = 0; + + unsigned maxOutNames = 128; + StringList names; + UInt8List entryTypes; + int64_t serverOffset = 0; + size_t numOutEntries; // to avoid inefficient calls to list::size. + + // query contents + ListIncExOutArgs outArgs(&names, &entryTypes, NULL, NULL, &serverOffset); + + do + { + names.clear(); + entryTypes.clear(); + + FhgfsOpsErr listRes = entries.listIncrementalEx(serverOffset, maxOutNames, true, outArgs); + if(listRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr( + std::string("Unable to fetch dir contents for dirID: ") + id); + retVal = listRes; + break; + } + + // walk over returned entries + + StringListConstIter namesIter = names.begin(); + UInt8ListConstIter typesIter = entryTypes.begin(); + numOutEntries = 0; + + for( /* using namesIter, typesIter, numOutEntries */; + (namesIter != names.end() ) && (typesIter != entryTypes.end() ); + namesIter++, typesIter++, numOutEntries++) + { + if(DirEntryType_ISDIR(*typesIter) ) + currentNumSubdirs++; + else + if(DirEntryType_ISFILE(*typesIter) ) + currentNumFiles++; + else + { // invalid entry => log error, but continue + LogContext(logContext).logErr(std::string("Unable to read entry type for name: '") + + *namesIter + "' " "(dirID: " + id + ")"); + } + } + + } while(numOutEntries == maxOutNames); + + if(retVal == FhgfsOpsErr_SUCCESS) + { // update in-memory counters (but we leave persistent updates to the caller) + this->numSubdirs = currentNumSubdirs; + this->numFiles = currentNumFiles; + } + + return retVal; +} + + +/* + * Note: Current object state is used for the serialization. + */ +FhgfsOpsErr DirInode::storeInitialMetaData() +{ + FhgfsOpsErr dirRes = DirEntryStore::mkDentryStoreDir(this->id, this->getIsBuddyMirrored()); + if(dirRes != FhgfsOpsErr_SUCCESS) + return dirRes; + + FhgfsOpsErr fileRes = storeInitialMetaDataInode(); + if(unlikely(fileRes != FhgfsOpsErr_SUCCESS) ) + { + if (unlikely(fileRes == FhgfsOpsErr_EXISTS) ) + { + // there must have been some kind of race as dirRes was successful + fileRes = FhgfsOpsErr_SUCCESS; + } + else + DirEntryStore::rmDirEntryStoreDir(this->id, this->getIsBuddyMirrored()); // remove dir + } + + return fileRes; +} + +FhgfsOpsErr DirInode::storeInitialMetaData(const CharVector& defaultACLXAttr, + const CharVector& accessACLXAttr) +{ + FhgfsOpsErr res = storeInitialMetaData(); + + if (res != FhgfsOpsErr_SUCCESS) + return res; + + // xattr updates done here are also resynced if storeInitialMetaData enqueued the inode + if (!defaultACLXAttr.empty()) + res = setXAttr(nullptr, PosixACL::defaultACLXAttrName, defaultACLXAttr, 0); + + if (res != FhgfsOpsErr_SUCCESS) + return res; + + if (!accessACLXAttr.empty()) + res = setXAttr(nullptr, PosixACL::accessACLXAttrName, accessACLXAttr, 0); + + return res; +} + +/* + * Creates the initial metadata inode for this directory. + * + * Note: Current object state is used for the serialization. + */ +FhgfsOpsErr DirInode::storeInitialMetaDataInode() +{ + const char* logContext = "Directory (store initial metadata file)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + bool useXAttrs = app->getConfig()->getStoreUseExtendedAttribs(); + + char buf[META_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + + // create file + + int openFlags = O_CREAT|O_EXCL|O_WRONLY; + mode_t openMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + int fd = open(metaFilename.c_str(), openFlags, openMode); + if(fd == -1) + { // error + if(errno == EEXIST) + retVal = FhgfsOpsErr_EXISTS; + else + { + LogContext(logContext).logErr("Unable to create dir metadata inode " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + } + + goto error_donothing; + } + + // alloc buf and serialize + + DiskMetaData::serializeDirInode(ser, *this); + if (!ser.good()) + { + LOG(GENERAL, ERR, "Serialized metadata is larger than serialization buffer size.", + id, parentDirID, metaFilename, + ("Data size", ser.size()), + ("Buffer size", META_SERBUF_SIZE)); + + retVal = FhgfsOpsErr_INTERNAL; + goto error_closefile; + } + + // write data to file + + if(useXAttrs) + { // extended attribute + int setRes = fsetxattr(fd, META_XATTR_NAME, buf, ser.size(), 0); + + if(unlikely(setRes == -1) ) + { // error + if(errno == ENOTSUP) + LogContext(logContext).logErr("Unable to store directory xattr metadata: " + + metaFilename + ". " + + "Did you enable extended attributes (user_xattr) on the underlying file system?"); + else + LogContext(logContext).logErr("Unable to store directory xattr metadata: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + + retVal = FhgfsOpsErr_INTERNAL; + + goto error_closefile; + } + } + else + { // normal file content + ssize_t writeRes = write(fd, buf, ser.size()); + + if(writeRes != (ssize_t)ser.size()) + { + LogContext(logContext).logErr("Unable to store directory metadata: " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + retVal = FhgfsOpsErr_INTERNAL; + + goto error_closefile; + } + } + + close(fd); + + LOG_DEBUG(logContext, Log_DEBUG, "Directory metadata inode stored: " + metaFilename); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return retVal; + + + // error compensation +error_closefile: + close(fd); + unlink(metaFilename.c_str() ); + +error_donothing: + + return retVal; +} + +/** + * Note: Wrapper/chooser for storeUpdatedMetaDataBufAsXAttr/Contents. + * + * @param buf the serialized object state that is to be stored + */ +bool DirInode::storeUpdatedMetaDataBuf(char* buf, unsigned bufLen) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + if(useXAttrs) + return storeUpdatedMetaDataBufAsXAttr(buf, bufLen); + + return storeUpdatedMetaDataBufAsContents(buf, bufLen); +} + +/** + * Note: Don't call this directly, use the wrapper storeUpdatedMetaDataBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool DirInode::storeUpdatedMetaDataBufAsXAttr(char* buf, unsigned bufLen) +{ + const char* logContext = "Directory (store updated xattr metadata)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + // write data to file + + int setRes = setxattr(metaFilename.c_str(), META_XATTR_NAME, buf, bufLen, 0); + + if(unlikely(setRes == -1) ) + { // error + LogContext(logContext).logErr("Unable to write directory metadata update: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + LOG_DEBUG(logContext, 4, "Directory metadata update stored: " + id); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return true; +} + +/** + * Stores the update to a sparate file first and then renames it. + * + * Note: Don't call this directly, use the wrapper storeUpdatedMetaDataBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool DirInode::storeUpdatedMetaDataBufAsContents(char* buf, unsigned bufLen) +{ + const char* logContext = "Directory (store updated metadata)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + std::string metaUpdateFilename(metaFilename + META_UPDATE_EXT_STR); + + ssize_t writeRes; + int renameRes; + + // open file (create it, but not O_EXCL because a former update could have failed) + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(metaUpdateFilename.c_str(), openFlags, 0644); + if(fd == -1) + { // error + if(errno == ENOSPC) + { // no free space => try again with update in-place + LogContext(logContext).log(Log_DEBUG, "No space left to create update file. Trying update " + "in-place: " + metaUpdateFilename + ". " + "SysErr: " + System::getErrString() ); + + return storeUpdatedMetaDataBufAsContentsInPlace(buf, bufLen); + } + + LogContext(logContext).logErr("Unable to create directory metadata update file: " + + metaUpdateFilename + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + // metafile created => store meta data + writeRes = write(fd, buf, bufLen); + if(writeRes != (ssize_t)bufLen) + { + if( (writeRes >= 0) || (errno == ENOSPC) ) + { // no free space => try again with update in-place + LogContext(logContext).log(Log_DEBUG, "No space left to write update file. Trying update " + "in-place: " + metaUpdateFilename + ". " + "SysErr: " + System::getErrString() ); + + close(fd); + unlink(metaUpdateFilename.c_str() ); + + return storeUpdatedMetaDataBufAsContentsInPlace(buf, bufLen); + } + + LogContext(logContext).logErr("Unable to store directory metadata update: " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + + goto error_closefile; + } + + close(fd); + + renameRes = rename(metaUpdateFilename.c_str(), metaFilename.c_str() ); + if(renameRes == -1) + { + LogContext(logContext).logErr("Unable to replace old directory metadata file: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + + goto error_unlink; + } + + LOG_DEBUG(logContext, 4, "Directory metadata update stored: " + id); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return true; + + + // error compensation +error_closefile: + close(fd); + +error_unlink: + unlink(metaUpdateFilename.c_str() ); + + return false; +} + +/** + * Stores the update directly to the current metadata file (instead of creating a separate file + * first and renaming it). + * + * Note: Don't call this directly, it is automatically called by storeUpdatedMetaDataBufAsContents() + * when necessary. + * + * @param buf the serialized object state that is to be stored + */ +bool DirInode::storeUpdatedMetaDataBufAsContentsInPlace(char* buf, unsigned bufLen) +{ + const char* logContext = "Directory (store updated metadata in-place)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + int fallocRes; + ssize_t writeRes; + int truncRes; + + // open file (create it, but not O_EXCL because a former update could have failed) + int openFlags = O_CREAT|O_WRONLY; + + int fd = open(metaFilename.c_str(), openFlags, 0644); + if(fd == -1) + { // error + LogContext(logContext).logErr("Unable to open directory metadata file: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + // make sure we have enough room to write our update + fallocRes = posix_fallocate(fd, 0, bufLen); // (note: posix_fallocate does not set errno) + if(fallocRes == EBADF) + { // special case for XFS bug + struct stat statBuf; + int statRes = fstat(fd, &statBuf); + + if (statRes == -1) + { + LogContext(logContext).log(Log_WARNING, "Unexpected error: fstat() failed with SysErr: " + + System::getErrString(errno)); + goto error_closefile; + } + + if (statBuf.st_size < bufLen) + { + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for metadata update failed: " + metaFilename + ". " + + "SysErr: " + System::getErrString(fallocRes) + " " + "statRes: " + StringTk::intToStr(statRes) + " " + "oldSize: " + StringTk::intToStr(statBuf.st_size)); + goto error_closefile; + } + else + { // // XFS bug! We only return an error if statBuf.st_size < bufLen. Ingore fallocRes then + LOG_DEBUG(logContext, Log_SPAM, "Ignoring kernel file system bug: " + "posix_fallocate() failed for len < filesize"); + } + } + else + if (fallocRes != 0) + { // default error handling if posix_fallocate() failed + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for metadata update failed: " + metaFilename + ". " + + "SysErr: " + System::getErrString(fallocRes)); + goto error_closefile; + } + + // metafile created => store meta data + writeRes = write(fd, buf, bufLen); + if(writeRes != (ssize_t)bufLen) + { + LogContext(logContext).logErr("Unable to store directory metadata update: " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + + goto error_closefile; + } + + // truncate in case the update lead to a smaller file size + truncRes = ftruncate(fd, bufLen); + if(truncRes == -1) + { // ignore trunc errors + LogContext(logContext).log(Log_WARNING, "Unable to truncate metadata file (strange, but " + "proceeding anyways): " + metaFilename + ". " + "SysErr: " + System::getErrString() ); + } + + + close(fd); + + LOG_DEBUG(logContext, 4, "Directory metadata in-place update stored: " + id); + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return true; + + + // error compensation +error_closefile: + close(fd); + + return false; +} + + +bool DirInode::storeUpdatedMetaDataUnlocked() +{ + std::string logContext = "Write Meta Inode"; + + if (unlikely(!this->isLoaded) ) + { + LogContext(logContext).logErr("Bug: Inode data not loaded yet."); + LogContext(logContext).logBacktrace(); + + return false; + } + + #ifdef DEBUG_MUTEX_LOCKING + if (unlikely(!this->rwlock.isRWLocked() ) ) + { + LogContext(logContext).logErr("Bug: Inode not rw-locked."); + LogContext(logContext).logBacktrace(); + + return false; + } + #endif + + char buf[META_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + + DiskMetaData::serializeDirInode(ser, *this); + if (!ser.good()) + { + LOG(GENERAL, ERR, "Serialized metadata is larger than serialization buffer size.", + id, parentDirID, + ("Data size", ser.size()), + ("Buffer size", META_SERBUF_SIZE)); + LogContext(logContext).logBacktrace(); + return false; + } + + return storeUpdatedMetaDataBuf(buf, ser.size()); +} + +bool DirInode::storeRemoteStorageTargetInfoUnlocked() +{ + char buf[META_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + ser % rstInfo; + + if (!ser.good()) + return false; + + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + if (useXAttrs) + return storeRemoteStorageTargetDataBufAsXAttr(buf, ser.size()); + else + { + LOG(GENERAL, WARNING, "Storing RST info as file contents is unsupported. " + "Please check the 'storeUseExtendedAttribs' setting in the BeeGFS meta config"); + return false; + } +} + +bool DirInode::storeRemoteStorageTargetDataBufAsXAttr(char* buf, unsigned bufLen) +{ + const char* logContext = "DirInode (store remote storage targets as xattr)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + int setRes = setxattr(metaFilename.c_str(), RST_XATTR_NAME, buf, bufLen, 0); + + if (unlikely(setRes == -1)) + { + // error + LogContext(logContext).logErr("Unable to write remote storage target info to disk: " + + metaFilename + ". SysErr: " + System::getErrString()); + + return false; + } + + return true; +} + +/** + * Note: Assumes that the caller already verified that the directory is empty + */ +bool DirInode::removeStoredMetaDataFile(const std::string& id, bool isBuddyMirrored) +{ + const char* logContext = "Directory (remove stored metadata)"; + + App* app = Program::getApp(); + const Path* inodePath = + isBuddyMirrored ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + // delete metadata file + + int unlinkRes = unlink(metaFilename.c_str() ); + if(unlinkRes == -1) + { // error + LogContext(logContext).logErr("Unable to delete directory metadata file: " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + + if (errno != ENOENT) + return false; + } + + LOG_DEBUG(logContext, 4, "Directory metadata deleted: " + metaFilename); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(metaFilename, MetaSyncFileType::Inode); + + return true; +} + +bool DirInode::loadIfNotLoaded() +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + bool loadRes = loadIfNotLoadedUnlocked(); + + safeLock.unlock(); + + return loadRes; +} + +void DirInode::invalidate() +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + isLoaded = false; +} + + +/** + * Load the DirInode from disk if it was notalready loaded before. + * + * @return true if loading not required or loading successfully, false if loading from disk failed. + */ +bool DirInode::loadIfNotLoadedUnlocked() +{ + if (!this->isLoaded) + { // So the object was already created before without loading the inode from disk, do that now. + bool loadSuccess = loadFromFile(); + if (!loadSuccess) + { + const char* logContext = "Loading DirInode on demand"; + std::string msg = "Loading DirInode failed dir-ID: "; + LOG_DEBUG_CONTEXT(LogContext(logContext), Log_DEBUG, msg + this->id); + IGNORE_UNUSED_VARIABLE(logContext); + + return false; + } + + if (this->getIsRstAvailable()) + loadRstFromFileXAttr(); + } + + return true; +} + +/** + * Note: Wrapper/chooser for loadFromFileXAttr/Contents. + */ +bool DirInode::loadFromFile() +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + bool loadRes; + if(useXAttrs) + loadRes = loadFromFileXAttr(); + else + loadRes = loadFromFileContents(); + + if (loadRes) + this->isLoaded = true; + + return loadRes; +} + + +/** + * Note: Don't call this directly, use the wrapper loadFromFile(). + */ +bool DirInode::loadFromFileXAttr() +{ + const char* logContext = "Directory (load from xattr file)"; + + App* app = Program::getApp(); + + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + bool retVal = false; + + char buf[META_SERBUF_SIZE]; + + ssize_t getRes = getxattr(inodeFilename.c_str(), META_XATTR_NAME, buf, META_SERBUF_SIZE); + if(getRes > 0) + { // we got something => deserialize it + Deserializer des(buf, getRes); + DiskMetaData::deserializeDirInode(des, *this); + if(unlikely(!des.good())) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize metadata in file: " + inodeFilename); + goto error_exit; + } + + retVal = true; + } + else + if( (getRes == -1) && (errno == ENOENT) ) + { // file not exists + LOG_DEBUG(logContext, Log_DEBUG, "Metadata file not exists: " + + inodeFilename + ". " + "SysErr: " + System::getErrString() ); + } + else + { // unhandled error + LogContext(logContext).logErr("Unable to open/read xattr metadata file: " + + inodeFilename + ". " + "SysErr: " + System::getErrString() ); + } + + +error_exit: + + return retVal; +} + +/** + * Note: Don't call this directly, use the wrapper loadFromFile(). + */ +bool DirInode::loadFromFileContents() +{ + const char* logContext = "Directory (load from file)"; + + App* app = Program::getApp(); + const Path* inodePath = + getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + bool retVal = false; + + int openFlags = O_NOATIME | O_RDONLY; + + int fd = open(inodeFilename.c_str(), openFlags, 0); + if(fd == -1) + { // open failed + if(errno != ENOENT) + LogContext(logContext).logErr("Unable to open metadata file: " + inodeFilename + + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + char buf[META_SERBUF_SIZE]; + int readRes = read(fd, buf, META_SERBUF_SIZE); + if(readRes <= 0) + { // reading failed + LogContext(logContext).logErr("Unable to read metadata file: " + inodeFilename + ". " + + "SysErr: " + System::getErrString() ); + } + else + { + Deserializer des(buf, readRes); + DiskMetaData::deserializeDirInode(des, *this); + if (!des.good()) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize metadata in file: " + inodeFilename); + } + else + { // success + retVal = true; + } + } + + close(fd); + + return retVal; +} + +bool DirInode::loadRstFromFileXAttr() +{ + const char* logContext = "Remote storage target (load from xattr)"; + App* app = Program::getApp(); + + const Path* inodePath = getIsBuddyMirrored() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + bool retVal = false; + char buf[META_SERBUF_SIZE]; + ssize_t getRes = getxattr(inodeFilename.c_str(), RST_XATTR_NAME, buf, META_SERBUF_SIZE); + + if (getRes > 0) + { + Deserializer des(buf, getRes); + des % rstInfo; + + if (unlikely(!des.good())) + { + LogContext(logContext).logErr("Unable to deserialize metadata in file: " + inodeFilename); + } + + retVal = true; + } + else if ((getRes == -1) && (errno == ENOENT)) + { + LOG_DEBUG(logContext, Log_DEBUG, "Metadata file not exists: " + + inodeFilename + ". " + "SysErr: " + System::getErrString() ); + } + else + { + LogContext(logContext).logErr("Unable to open/read xattr metadata file: " + + inodeFilename + ". " + "SysErr: " + System::getErrString() ); + } + + return retVal; +} + +DirInode* DirInode::createFromFile(const std::string& id, bool isBuddyMirrored) +{ + DirInode* newDir = new DirInode(id, isBuddyMirrored); + + bool loadRes = newDir->loadFromFile(); + if(!loadRes) + { + delete(newDir); + return NULL; + } + + newDir->entries.setParentID(id, isBuddyMirrored); + + return newDir; +} + +/** + * Get directory stat information + * + * @param outParentNodeID may NULL if the caller is not interested + */ +FhgfsOpsErr DirInode::getStatData(StatData& outStatData, + NumNodeID* outParentNodeID, std::string* outParentEntryID) +{ + // note: keep in mind that correct nlink count (2+numSubdirs) is absolutely required to not break + // the "find" command, which uses optimizations based on nlink (see "find -noleaf" for + // details). The other choice would be to set nlink=1, which find would reduce to -1 + // as is substracts 2 for "." and "..". -1 = max(unsigned) and makes find to check all + // possible subdirs. But as we have the correct nlink anyway, we can provide it to find + // and allow it to use non-posix optimizations. + + const char* logContext = "DirInode::getStatData"; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + this->statData.setNumHardLinks(2 + numSubdirs); // +2 by definition (for "." and "") + + this->statData.setFileSize(numSubdirs + numFiles); // just because we got those values anyway + this->statData.setMetaVersionStat(0); //metaVersion not planned for use with directories, so it is set to zero. + + outStatData = this->statData; + + if (outParentNodeID) + { + *outParentNodeID = this->parentNodeID; + + #ifdef BEEGFS_DEBUG + if (!outParentEntryID) + { + LogContext(logContext).logErr("Bug: outParentEntryID is unexpectedly NULL"); + LogContext(logContext).logBacktrace(); + } + #endif + IGNORE_UNUSED_VARIABLE(logContext); + + *outParentEntryID = this->parentDirID; + } + + safeLock.unlock(); + + return FhgfsOpsErr_SUCCESS; +} + +/* + * only used for testing at the moment + */ +void DirInode::setStatData(StatData& statData) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + this->statData = statData; + + this->numSubdirs = statData.getNumHardlinks() - 2; + this->numFiles = statData.getFileSize() - this->numSubdirs; + + storeUpdatedMetaDataUnlocked(); + + safeLock.unlock(); // U N L O C K + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } +} + +/** + * @param validAttribs SETATTR_CHANGE_...-Flags, might be 0 if only attribChangeTimeSecs + * shall be updated + * @attribs new attributes, but might be NULL if validAttribs == 0 + */ +bool DirInode::setAttrData(int validAttribs, SettableFileAttribs* attribs) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + bool retVal = setAttrDataUnlocked(validAttribs, attribs); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * See setAttrData() for documentation. + */ +bool DirInode::setAttrDataUnlocked(int validAttribs, SettableFileAttribs* attribs) +{ + bool success = true; + + + // load DirInode on demand if required, we need it now + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return false; + + // save old attribs + SettableFileAttribs oldAttribs; + oldAttribs = *(this->statData.getSettableFileAttribs() ); + + this->statData.setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + + if(validAttribs) + { // apply new attribs wrt flags... + + if(validAttribs & SETATTR_CHANGE_MODE) + this->statData.setMode(attribs->mode); + + if(validAttribs & SETATTR_CHANGE_MODIFICATIONTIME) + { + /* only static value update required for storeUpdatedMetaDataUnlocked() */ + this->statData.setModificationTimeSecs(attribs->modificationTimeSecs); + } + + if(validAttribs & SETATTR_CHANGE_LASTACCESSTIME) + { + /* only static value update required for storeUpdatedMetaDataUnlocked() */ + this->statData.setLastAccessTimeSecs(attribs->lastAccessTimeSecs); + } + + if(validAttribs & SETATTR_CHANGE_USERID) + this->statData.setUserID(attribs->userID); + + if(validAttribs & SETATTR_CHANGE_GROUPID) + this->statData.setGroupID(attribs->groupID); + } + + bool storeRes = storeUpdatedMetaDataUnlocked(); + if(!storeRes) + { // failed to update metadata on disk => restore old values + this->statData.setSettableFileAttribs(oldAttribs); + + success = false; + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return success; +} + +/** + * Set/Update parent information (from the given entryInfo) + */ +FhgfsOpsErr DirInode::setDirParentAndChangeTime(EntryInfo* entryInfo, NumNodeID parentNodeID) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + FhgfsOpsErr retVal = setDirParentAndChangeTimeUnlocked(entryInfo, parentNodeID); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * See setParentEntryID() for documentation. + */ +FhgfsOpsErr DirInode::setDirParentAndChangeTimeUnlocked(EntryInfo* entryInfo, + NumNodeID parentNodeID) +{ + const char* logContext = "DirInode::setDirParentAndChangeTimeUnlocked"; + + // load DirInode on demand if required, we need it now + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + +#ifdef BEEGFS_DEBUG + LogContext(logContext).log(Log_DEBUG, "DirInode" + this->getID() + " " + "newParentDirID: " + entryInfo->getParentEntryID() + " " + "newParentNodeID: " + parentNodeID.str() + "."); +#endif + IGNORE_UNUSED_VARIABLE(logContext); + + this->parentDirID = entryInfo->getParentEntryID(); + this->parentNodeID = parentNodeID; + + // updates change time stamps and saves to disk + bool attrRes = setAttrDataUnlocked(0, NULL); + + if (unlikely(!attrRes) ) + return FhgfsOpsErr_INTERNAL; + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return FhgfsOpsErr_SUCCESS; +} + +std::pair DirInode::listXAttr(const EntryInfo* file) +{ + return file + ? XAttrTk::listUserXAttrs(MetaStorageTk::getMetaDirEntryIDPath(entries.getDirEntryPath()) + + "/" + file->getEntryID()) + : XAttrTk::listUserXAttrs(entries.getDirEntryPath()); +} + +/** + * Get an extended attribute by name. + * @returns first: FhgfsOpsErr_RANGE if size is smaller than xattr, FhgfsOpsErr_NODATA if there is + * no xattr by that name, FhgfsOpsErr_INTERNAL on any other errors, FhfgfsOpsErr_SUCCESS + * on success. second: xattr data, if first is FhgfsOpsErr_SUCCESS + */ +std::tuple, ssize_t> DirInode::getXAttr(const EntryInfo* file, + const std::string& xAttrName, size_t maxSize) +{ + return file + ? XAttrTk::getUserXAttr(MetaStorageTk::getMetaDirEntryIDPath(entries.getDirEntryPath()) + + "/" + file->getEntryID(), xAttrName, maxSize) + : XAttrTk::getUserXAttr(entries.getDirEntryPath(), xAttrName, maxSize); +} + +FhgfsOpsErr DirInode::removeXAttr(EntryInfo* file, const std::string& xAttrName) +{ + const std::string& fileName = file + ? MetaStorageTk::getMetaDirEntryIDPath(entries.getDirEntryPath()) + + "/" + file->getEntryID() + : entries.getDirEntryPath(); + + FhgfsOpsErr result = XAttrTk::removeUserXAttr(fileName, xAttrName); + + if (result == FhgfsOpsErr_SUCCESS) + { + if (file) + { + auto [fileHandle, referenceRes] = Program::getApp()->getMetaStore()->referenceFile(file); + if (fileHandle) + { + fileHandle->updateInodeChangeTime(file); + Program::getApp()->getMetaStore()->releaseFile(getID(), fileHandle); + } + } + else + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + updateTimeStampsAndStoreToDisk(__func__); + } + } + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(fileName, + file + ? MetaSyncFileType::Inode + : MetaSyncFileType::Directory); + + return result; +} + +FhgfsOpsErr DirInode::setXAttr(EntryInfo* file, const std::string& xAttrName, + const CharVector& xAttrValue, int flags, bool updateTimestamps) +{ + const std::string& fileName = file + ? MetaStorageTk::getMetaDirEntryIDPath(entries.getDirEntryPath()) + + "/" + file->getEntryID() + : entries.getDirEntryPath(); + + FhgfsOpsErr result = XAttrTk::setUserXAttr(fileName, xAttrName, &xAttrValue[0], + xAttrValue.size(), flags); + + if (result == FhgfsOpsErr_SUCCESS && updateTimestamps) + { + if (file) + { + auto [fileHandle, referenceRes] = Program::getApp()->getMetaStore()->referenceFile(file); + if (fileHandle) + { + fileHandle->updateInodeChangeTime(file); + Program::getApp()->getMetaStore()->releaseFile(getID(), fileHandle); + } + } + else + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + updateTimeStampsAndStoreToDisk(__func__); + } + } + + if (getIsBuddyMirrored()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(fileName, + file + ? MetaSyncFileType::Inode + : MetaSyncFileType::Directory); + + return result; +} + + +FhgfsOpsErr DirInode::setOwnerNodeID(const std::string& entryName, NumNodeID ownerNode) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + { + safeLock.unlock(); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + FhgfsOpsErr retVal = entries.setOwnerNodeID(entryName, ownerNode); + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +/** + * Move a dentry that has in inlined inode to the inode-hash directories + * (probably an unlink() of an opened file). + * + * @param unlinkFileName Also unlink the fileName or only the ID file. + */ +bool DirInode::unlinkBusyFileUnlocked(const std::string& fileName, DirEntry* dentry, + unsigned unlinkTypeFlags) +{ + const char* logContext = "unlink busy file"; + + FhgfsOpsErr unlinkRes = this->entries.removeBusyFile(fileName, dentry, unlinkTypeFlags); + if (unlinkRes == FhgfsOpsErr_SUCCESS) + { + if( (unlinkTypeFlags & DirEntry_UNLINK_FILENAME) && + unlikely(!decreaseNumFilesAndStoreOnDisk() ) ) + unlinkRes = FhgfsOpsErr_INTERNAL; + } + else + { + std::string msg = "Failed to remove file dentry. " + "DirInode: " + this->id + " " + "entryName: " + fileName + " " + "entryID: " + dentry->getID() + " " + "Error: " + boost::lexical_cast(unlinkRes); + + LogContext(logContext).logErr(msg); + } + + if (getIsBuddyMirrored()) + { + if (auto* resync = BuddyResyncer::getSyncChangeset()) + { + const Path* inodePath = Program::getApp()->getBuddyMirrorInodesPath(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + resync->addModification(inodeFilename, MetaSyncFileType::Inode); + } + } + + return unlinkRes; +} + +/** + * Set the buddyMirrored flag on the inode and all dentries/inlined file inode contained within. + * If this method fails, the contained dentries and inodes will NOT be reset to their former value, + * instead, a mix of mirrored and unmirrored items will be left behind. + */ +FhgfsOpsErr DirInode::setIsBuddyMirrored(const bool isBuddyMirrored) +{ + if (isBuddyMirrored) + addFeatureFlag(DIRINODE_FEATURE_BUDDYMIRRORED); + else + removeFeatureFlag(DIRINODE_FEATURE_BUDDYMIRRORED); + + entries.setParentID(entries.getParentEntryID(), isBuddyMirrored); + + { + int64_t offset = 0; + + while (true) + { + StringList dentries; + ListIncExOutArgs args(&dentries, nullptr, nullptr, nullptr, &offset); + + const auto listRes = entries.listIncrementalEx(offset, 1, true, args); + if (listRes != FhgfsOpsErr_SUCCESS) + return listRes; + + if (dentries.empty()) + break; + + DirEntry dentry(dentries.front()); + + if (!entries.getDentry(dentries.front(), dentry)) + return FhgfsOpsErr_PATHNOTEXISTS; + + if (!dentry.getIsInodeInlined()) + continue; + + if (isBuddyMirrored) + dentry.addDentryFeatureFlag(DENTRY_FEATURE_BUDDYMIRRORED); + else + dentry.setDentryFeatureFlags( + dentry.getDentryFeatureFlags() & ~DENTRY_FEATURE_BUDDYMIRRORED); + + if (!dentry.storeUpdatedDirEntry(entries.getDirEntryPath())) + return FhgfsOpsErr_INTERNAL; + + EntryInfo ei; + + dentry.getEntryInfo(getID(), 0, &ei); + + std::unique_ptr inode(FileInode::createFromEntryInfo(&ei)); + if (!inode) + return FhgfsOpsErr_PATHNOTEXISTS; + + inode->setIsBuddyMirrored(isBuddyMirrored); + + if (!inode->updateInodeOnDisk(&ei)) + return FhgfsOpsErr_INTERNAL; + } + } + + return FhgfsOpsErr_SUCCESS; +} diff --git a/meta/source/storage/DirInode.h b/meta/source/storage/DirInode.h new file mode 100644 index 0000000..12c7721 --- /dev/null +++ b/meta/source/storage/DirInode.h @@ -0,0 +1,653 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "DirEntryStore.h" +#include "MetadataEx.h" +#include "InodeFileStore.h" + + +/* Note: Don't forget to update DiskMetaData::getSupportedDirInodeFeatureFlags() if you add new + * flags here. */ + +#define DIRINODE_FEATURE_EARLY_SUBDIRS 2 // indicate proper alignment for statData +#define DIRINODE_FEATURE_MIRRORED 4 // indicate old mirrored directory (for compatibility) +#define DIRINODE_FEATURE_STATFLAGS 8 // StatData have a flags field +#define DIRINODE_FEATURE_BUDDYMIRRORED 16 // indicate buddy mirrored directory +#define DIRINODE_FEATURE_HAS_RST 32 // indicates remote target availability + +// limit number of stripes per file to a high but safe number. too many stripe targets will cause +// the serialized stripe pattern to be too large to store reliably, so choose a value well below +// that limit (but still high enough to not bother anyone). +// the number of stripe targets is limited by: +// * stripe patterns in inodes: for each target, 32 bits of target ID are stored +// * chunk size infos: for each target, 64 bits of #used_blocks may be stored +// at 256 targets, those two structures may use up to 3072 bytes, leaving ample room for other +// inode data. +#define DIRINODE_MAX_STRIPE_TARGETS 256 + +/** + * Our inode object, but for directories only. Files are in class FileInode. + */ +class DirInode +{ + friend class MetaStore; + friend class InodeDirStore; + friend class DiskMetaData; + + public: + DirInode(const std::string& id, int mode, unsigned userID, unsigned groupID, + NumNodeID ownerNodeID, const StripePattern& stripePattern, bool isBuddyMirrored); + + /** + * Constructur used to load inodes from disk + * Note: Not all values are set, as we load those from disk. + */ + DirInode(const std::string& id, bool isBuddyMirrored) + : id(id), + stripePattern(NULL), + featureFlags(isBuddyMirrored ? DIRINODE_FEATURE_BUDDYMIRRORED : 0), + exclusive(false), + entries(id, isBuddyMirrored), + isLoaded(false) + { } + + ~DirInode() + { + LOG_DEBUG("Delete DirInode", Log_SPAM, std::string("Deleting inode: ") + this->id); + + SAFE_DELETE_NOSET(stripePattern); + } + + + static DirInode* createFromFile(const std::string& id, bool isBuddyMirrored); + + StripePattern* createFileStripePattern(const UInt16List* preferredTargets, + unsigned numtargets, unsigned chunksize, StoragePoolId storagePoolId); + + FhgfsOpsErr listIncremental(int64_t serverOffset, + unsigned maxOutNames, StringList* outNames, int64_t* outNewServerOffset); + FhgfsOpsErr listIncrementalEx(int64_t serverOffset, + unsigned maxOutNames, bool filterDots, ListIncExOutArgs& outArgs); + FhgfsOpsErr listIDFilesIncremental(int64_t serverOffset, uint64_t incrementalOffset, + unsigned maxOutNames, ListIncExOutArgs& outArgs); + + bool exists(const std::string& entryName); + + FhgfsOpsErr makeDirEntry(DirEntry& entry); + FhgfsOpsErr linkFileInodeToDir(const std::string& inodePath, const std::string& fileName); + + FhgfsOpsErr removeDir(const std::string& entryName, DirEntry** outDirEntry); + FhgfsOpsErr unlinkDirEntry(const std::string& entryName, DirEntry* entry, + unsigned unlinkTypeFlags); + + bool loadIfNotLoaded(void); + void invalidate(); + + FhgfsOpsErr refreshMetaInfo(); + + // non-inlined getters & setters + FhgfsOpsErr setOwnerNodeID(const std::string& entryName, NumNodeID ownerNode); + + StripePattern* getStripePatternClone(); + FhgfsOpsErr setStripePattern(const StripePattern& newPattern, uint32_t actorUID = 0); + FhgfsOpsErr setRemoteStorageTarget(const RemoteStorageTarget& rst); + FhgfsOpsErr clearRemoteStorageTarget(); + + FhgfsOpsErr getStatData(StatData& outStatData, + NumNodeID* outParentNodeID = NULL, std::string* outParentEntryID = NULL); + void setStatData(StatData& statData); + + bool setAttrData(int validAttribs, SettableFileAttribs* attribs); + FhgfsOpsErr setDirParentAndChangeTime(EntryInfo* entryInfo, NumNodeID parentNodeID); + + std::pair listXAttr(const EntryInfo* file); + std::tuple, ssize_t> getXAttr(const EntryInfo* file, + const std::string& xAttrName, size_t maxSize); + FhgfsOpsErr removeXAttr(EntryInfo* file, const std::string& xAttrName); + FhgfsOpsErr setXAttr(EntryInfo* file, const std::string& xAttrName, + const CharVector& xAttrValue, int flags, bool updateTimestamps = true); + + private: + std::string id; // filesystem-wide unique string + NumNodeID ownerNodeID; // 0 means undefined + StripePattern* stripePattern; // is the default for new files and subdirs + RemoteStorageTarget rstInfo; // remote storage target information + + std::string parentDirID; // must be reliable for NFS + NumNodeID parentNodeID; // must be reliable for NFS + + uint16_t featureFlags; + + bool exclusive; // if set, we do not allow other references + + // StatData + StatData statData; + uint32_t numSubdirs; // indirectly updated by subdir creation/removal + uint32_t numFiles; // indirectly updated by subfile creation/removal + + RWLock rwlock; + + DirEntryStore entries; + + /* if not set we have an object that has not read data from disk yet, the dir might not + even exist on disk */ + bool isLoaded; + Mutex loadLock; // protects the disk load + + InodeFileStore fileStore; /* We must not delete the DirInode as long as this + * InodeFileStore still has entries. Therefore a dir reference + * has to be taken for entry in this InodeFileStore */ + + StripePattern* createFileStripePatternUnlocked(const UInt16List* preferredTargets, + unsigned numtargets, unsigned chunksize, StoragePoolId storagePoolId); + + FhgfsOpsErr storeInitialMetaData(); + FhgfsOpsErr storeInitialMetaData(const CharVector& defaultACLXAttr, + const CharVector& accessACLXAttr); + FhgfsOpsErr storeInitialMetaDataInode(); + bool storeUpdatedMetaDataBuf(char* buf, unsigned bufLen); + bool storeUpdatedMetaDataBufAsXAttr(char* buf, unsigned bufLen); + bool storeUpdatedMetaDataBufAsContents(char* buf, unsigned bufLen); + bool storeUpdatedMetaDataBufAsContentsInPlace(char* buf, unsigned bufLen); + bool storeUpdatedMetaDataUnlocked(); + + bool storeRemoteStorageTargetInfoUnlocked(); + bool storeRemoteStorageTargetDataBufAsXAttr(char* buf, unsigned bufLen); + + FhgfsOpsErr renameDirEntry(const std::string& fromName, const std::string& toName, + DirEntry* overWriteEntry); + FhgfsOpsErr renameDirEntryUnlocked(const std::string& fromName, const std::string& toName, + DirEntry* overWriteEntry); + + static bool removeStoredMetaData(const std::string& id); + static bool removeStoredMetaDataDir(const std::string& id); + static bool removeStoredMetaDataFile(const std::string& id, bool isBuddyMirrored); + + FhgfsOpsErr refreshMetaInfoUnlocked(); + + + FhgfsOpsErr makeDirEntryUnlocked(DirEntry* entry); + FhgfsOpsErr linkFileInodeToDirUnlocked(const std::string& inodePath, + const std::string &fileName); + + FhgfsOpsErr removeDirUnlocked(const std::string& entryName, DirEntry** outDirEntry); + FhgfsOpsErr unlinkDirEntryUnlocked(const std::string& entryName, DirEntry* inEntry, + unsigned unlinkTypeFlags); + + FhgfsOpsErr refreshSubentryCountUnlocked(); + + bool loadFromFile(); + bool loadFromFileXAttr(); + bool loadFromFileContents(); + bool loadIfNotLoadedUnlocked(); + + bool loadRstFromFileXAttr(); + + FhgfsOpsErr getEntryData(const std::string& entryName, EntryInfo* outInfo, + FileInodeStoreData* outInodeMetaData); + + bool setAttrDataUnlocked(int validAttribs, SettableFileAttribs* attribs); + FhgfsOpsErr setDirParentAndChangeTimeUnlocked(EntryInfo* entryInfo, NumNodeID parentNodeID); + + bool unlinkBusyFileUnlocked(const std::string& fileName, DirEntry* dentry, + unsigned unlinkTypeFlags); + + protected: + FhgfsOpsErr linkFilesInDirUnlocked(const std::string& fromName, FileInode& fromInode, + const std::string& toName); + + FhgfsOpsErr setIsBuddyMirrored(const bool isBuddyMirrored); + + + public: + // inliners + + + /** + * Note: Must be called before any of the mutator methods (otherwise they will fail) + */ + FhgfsOpsErr storePersistentMetaData() + { + return storeInitialMetaData(); + } + + FhgfsOpsErr storePersistentMetaData(const CharVector& defaultACLXAttr, + const CharVector& accessACLXAttr) + { + return storeInitialMetaData(defaultACLXAttr, accessACLXAttr); + } + + /** + * Unlink the dir-inode file on disk. + * Note: Assumes that the caller already verified that the directory is empty + */ + static bool unlinkStoredInode(const std::string& id, bool isBuddyMirrored) + { + bool dirRes = DirEntryStore::rmDirEntryStoreDir(id, isBuddyMirrored); + if(!dirRes) + return dirRes; + + return removeStoredMetaDataFile(id, isBuddyMirrored); + } + + /** + * Note: Intended to be used by fsck only. + */ + FhgfsOpsErr storeAsReplacementFile(const std::string& id) + { + // note: creates new dir metadata file for non-existing or invalid one => no locking needed + removeStoredMetaDataFile(id, this->getIsBuddyMirrored()); + return storeInitialMetaDataInode(); + } + + /** + * Return create a DirEntry for the given file name + * + * note: this->rwlock already needs to be locked + */ + DirEntry* dirEntryCreateFromFileUnlocked(const std::string& entryName) + { + return this->entries.dirEntryCreateFromFile(entryName); + } + + + /** + * Get a dentry + * note: the difference to getDirEntryInfo/getFileEntryInfo is that this works independent + * of the entry-type + */ + bool getDentry(const std::string& entryName, DirEntry& outEntry) + { + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = getDentryUnlocked(entryName, outEntry); + + safeLock.unlock(); + + return retVal; + } + + /** + * Get a dentry + * note: the difference to getDirEntryInfo/getFileEntryInfo is that this works independent + * of the entry-type + */ + bool getDentryUnlocked(const std::string& entryName, DirEntry& outEntry) + { + return this->entries.getDentry(entryName, outEntry); + } + + /** + * Get the dentry (dir-entry) of a directory + */ + bool getDirDentry(const std::string& dirName, DirEntry& outEntry) + { + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = entries.getDirDentry(dirName, outEntry); + + safeLock.unlock(); + + return retVal; + } + + /* + * Get the dentry (dir-entry) of a file + */ + bool getFileDentry(const std::string& fileName, DirEntry& outEntry) + { + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = entries.getFileDentry(fileName, outEntry); + + safeLock.unlock(); + + return retVal; + } + + /** + * Get the EntryInfo + * note: the difference to getDirEntryInfo/getFileEntryInfo is that this works independent + * of the entry-type + */ + bool getEntryInfo(const std::string& entryName, EntryInfo& outEntryInfo) + { + + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = this->entries.getEntryInfo(entryName, outEntryInfo); + + safeLock.unlock(); + + return retVal; + } + + /** + * Get the EntryInfo of a directory + */ + bool getDirEntryInfo(const std::string& dirName, EntryInfo& outEntryInfo) + { + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = entries.getDirEntryInfo(dirName, outEntryInfo); + + safeLock.unlock(); + + return retVal; + } + + /* + * Get the dir-entry of a file + */ + bool getFileEntryInfo(const std::string& fileName, EntryInfo& outEntryInfo) + { + bool retVal; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + retVal = entries.getFileEntryInfo(fileName, outEntryInfo); + + safeLock.unlock(); + + return retVal; + } + + const std::string& getID() const + { + return id; + } + + NumNodeID getOwnerNodeID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + NumNodeID owner = ownerNodeID; + + safeLock.unlock(); + + return owner; + } + + bool setOwnerNodeID(NumNodeID newOwner) + { + bool success = true; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + bool loadSuccess = loadIfNotLoadedUnlocked(); + if (!loadSuccess) + { + safeLock.unlock(); + return false; + } + + NumNodeID oldOwner = this->ownerNodeID; + this->ownerNodeID = newOwner; + + if(!storeUpdatedMetaDataUnlocked() ) + { // failed to update metadata => restore old value + this->ownerNodeID = oldOwner; + + success = false; + } + + safeLock.unlock(); + + return success; + } + + void getParentInfo(std::string* outParentDirID, NumNodeID* outParentNodeID) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + *outParentDirID = this->parentDirID; + *outParentNodeID = this->parentNodeID; + + safeLock.unlock(); + } + + /** + * Note: Initial means for newly created objects (=> unlocked, unpersistent) + */ + void setParentInfoInitial(const std::string& parentDirID, NumNodeID parentNodeID) + { + this->parentDirID = parentDirID; + this->parentNodeID = parentNodeID; + } + + void setFeatureFlags(unsigned flags) + { + this->featureFlags = flags; + } + + void addFeatureFlag(unsigned flag) + { + this->featureFlags |= flag; + } + + void removeFeatureFlag(unsigned flag) + { + this->featureFlags &= ~flag; + } + + unsigned getFeatureFlags() const + { + return this->featureFlags; + } + + void setIsBuddyMirroredFlag(const bool isBuddyMirrored) + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + if (isBuddyMirrored) + featureFlags |= DIRINODE_FEATURE_BUDDYMIRRORED; + else + featureFlags &= ~DIRINODE_FEATURE_BUDDYMIRRORED; + + entries.setParentID(entries.getParentEntryID(), isBuddyMirrored); + } + + FhgfsOpsErr setAndStoreIsBuddyMirrored(bool isBuddyMirrored) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + const FhgfsOpsErr result = setIsBuddyMirrored(isBuddyMirrored); + + if (result == FhgfsOpsErr_SUCCESS) + storeUpdatedMetaDataUnlocked(); + + safeLock.unlock(); + + return result; + } + + bool getIsBuddyMirrored() const + { + return (getFeatureFlags() & DIRINODE_FEATURE_BUDDYMIRRORED); + } + + bool getIsRstAvailable() const + { + return (getFeatureFlags() & DIRINODE_FEATURE_HAS_RST); + } + + bool getExclusive() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + bool retVal = this->exclusive; + + safeLock.unlock(); + + return retVal; + } + + void setExclusive(bool exclusive) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->exclusive = exclusive; + + safeLock.unlock(); + } + + unsigned getUserID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned retVal = this->statData.getUserID(); + + safeLock.unlock(); + + return retVal; + } + + unsigned getUserIDUnlocked() + { + return this->statData.getUserID(); + } + + unsigned getGroupID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned retVal = this->statData.getGroupID(); + + safeLock.unlock(); + + return retVal; + } + + int getMode() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + int retVal = this->statData.getMode(); + + safeLock.unlock(); + + return retVal; + } + + unsigned getNumHardlinks() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + unsigned retVal = this->statData.getNumHardlinks(); + safeLock.unlock(); + return retVal; + } + + size_t getNumSubEntries() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + size_t retVal = numSubdirs + numFiles; + + safeLock.unlock(); + + return retVal; + } + + bool getIsLoaded() + { + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return isLoaded; + } + + + static FhgfsOpsErr getStatData(const std::string& dirID, bool isBuddyMirrored, + StatData& outStatData, NumNodeID* outParentNodeID, std::string* outParentEntryID) + { + DirInode dir(dirID, isBuddyMirrored); + if(!dir.loadFromFile() ) + return FhgfsOpsErr_PATHNOTEXISTS; + + return dir.getStatData(outStatData, outParentNodeID, outParentEntryID); + } + + StripePattern* getStripePattern() const + { + return stripePattern; + } + + RemoteStorageTarget* getRemoteStorageTargetInfo() + { + return &this->rstInfo; + } + + private: + + bool updateTimeStampsAndStoreToDisk(const char* logContext) + { + int64_t nowSecs = TimeAbs().getTimeval()->tv_sec;; + this->statData.setAttribChangeTimeSecs(nowSecs); + this->statData.setModificationTimeSecs(nowSecs); + + if(unlikely(!storeUpdatedMetaDataUnlocked() ) ) + { + LogContext(logContext).logErr(std::string("Failed to update dir-info on disk: " + "Dir-ID: ") + this->getID() + std::string(". SysErr: ") + System::getErrString() ); + + return false; + } + + return true; + } + + + bool increaseNumSubDirsAndStoreOnDisk(void) + { + const char* logContext = "DirInfo update: increase number of SubDirs"; + + numSubdirs++; + + return updateTimeStampsAndStoreToDisk(logContext); + } + + bool increaseNumFilesAndStoreOnDisk(void) + { + const char* logContext = "DirInfo update: increase number of Files"; + + numFiles++; + + return updateTimeStampsAndStoreToDisk(logContext); + } + + bool decreaseNumFilesAndStoreOnDisk(void) + { + const char* logContext = "DirInfo update: decrease number of Files"; + + if (numFiles) // make sure it does not get sub-zero + numFiles--; + + return updateTimeStampsAndStoreToDisk(logContext); + } + + bool decreaseNumSubDirsAndStoreOnDisk(void) + { + const char* logContext = "DirInfo update: decrease number of SubDirs"; + + if (numSubdirs) // make sure it does not get sub-zero + numSubdirs--; + + return updateTimeStampsAndStoreToDisk(logContext); + } +}; + diff --git a/meta/source/storage/DiskMetaData.cpp b/meta/source/storage/DiskMetaData.cpp new file mode 100644 index 0000000..e2b67ec --- /dev/null +++ b/meta/source/storage/DiskMetaData.cpp @@ -0,0 +1,851 @@ +/* + * Dentry and inode serialization/deserialization. + * + * Note: Currently inodes and dentries are stored in exactly the same format, even if + * inodes are not inlined into a dentry. + * If we should add another inode-only format, all code linking inodes to dentries + * e.g. (MetaStore::unlinkInodeLaterUnlocked() calling dirInode->linkInodeToDir() must + * be updated. + */ + +#include +#include +#include "DiskMetaData.h" +#include "DirInode.h" +#include "FileInode.h" +#include "DirEntry.h" + + +#define DISKMETADATA_LOG_CONTEXT "DiskMetadata" + + +// 8-bit +#define DIRENTRY_STORAGE_FORMAT_VER2 2 // version beginning with release 2011.04 +#define DIRENTRY_STORAGE_FORMAT_VER3 3 // version, same as V2, but removes the file name + // from the dentry (for dir.dentries) +#define DIRENTRY_STORAGE_FORMAT_VER4 4 // version, which includes inlined inodes, deprecated +#define DIRENTRY_STORAGE_FORMAT_VER5 5 /* version, which includes inlined inodes + * and chunk-path V3, StatData have a flags field */ +#define DIRENTRY_STORAGE_FORMAT_VER6 6 /* VER5 + additional storage pool in pattern */ + +// 8-bit +#define DIRECTORY_STORAGE_FORMAT_VER1 1 // 16 bit node IDs +#define DIRECTORY_STORAGE_FORMAT_VER2 2 // 32 bit node IDs +#define DIRECTORY_STORAGE_FORMAT_VER3 3 // 32 bit node IDs + storage pool in pattern + +void DiskMetaData::serializeFileInode(Serializer& ser) +{ + // inodeData set in constructor + + if (!DirEntryType_ISVALID(this->dentryDiskData->getDirEntryType() ) ) + { + StatData* statData = this->inodeData->getInodeStatData(); + unsigned mode = statData->getMode(); + DirEntryType entryType = MetadataTk::posixFileTypeToDirEntryType(mode); + this->dentryDiskData->setDirEntryType(entryType); + +#ifdef BEEGFS_DEBUG + const char* logContext = "Serialize FileInode"; + LogContext(logContext).logErr("Bug: entryType not set!"); + LogContext(logContext).logBacktrace(); +#endif + } + + /* We use this method to clone inodes which might be inlined into a dentry, so the real + * meta type depends on if the inode is inlined or not. */ + DiskMetaDataType metaDataType; + if (this->dentryDiskData->dentryFeatureFlags & DENTRY_FEATURE_INODE_INLINE) + metaDataType = DiskMetaDataType_FILEDENTRY; + else + metaDataType = DiskMetaDataType_FILEINODE; + + serializeInDentryFormat(ser, metaDataType); +} + +void DiskMetaData::serializeDentry(Serializer& ser) +{ + DiskMetaDataType metaDataType; + + if (DirEntryType_ISDIR(this->dentryDiskData->getDirEntryType() ) ) + metaDataType = DiskMetaDataType_DIRDENTRY; + else + metaDataType = DiskMetaDataType_FILEDENTRY; + + serializeInDentryFormat(ser, metaDataType); +} + +/* + * Note: Current object state is used for the serialization + */ +void DiskMetaData::serializeInDentryFormat(Serializer& ser, DiskMetaDataType metaDataType) +{ + // note: the total amount of serialized data may not be larger than DIRENTRY_SERBUF_SIZE + int dentryFormatVersion; + + // set the type into the entry (1 byte) + ser % uint8_t(metaDataType); + + // storage-format-version (1 byte) + if (DirEntryType_ISDIR(this->dentryDiskData->getDirEntryType())) + { + // metadata format version-3 for directories + ser % uint8_t(DIRENTRY_STORAGE_FORMAT_VER3); + + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER3; + } + else + { + if (metaDataType == DiskMetaDataType_FILEINODE) + { + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER6; + } + else if ((!(this->dentryDiskData->getDentryFeatureFlags() & + DENTRY_FEATURE_INODE_INLINE))) + { + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER3; + } + else if (this->inodeData->getOrigFeature() == FileInodeOrigFeature_TRUE) + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER6; + else + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER4; + + // metadata format version-4 for files (inlined inodes) + ser % uint8_t(dentryFormatVersion); + } + + // dentry feature flags (2 bytes) + // note: for newly written/serialized dentries we always use the long nodeIDs + this->dentryDiskData->addDentryFeatureFlag(DENTRY_FEATURE_32BITIDS); + + ser % uint16_t(this->dentryDiskData->getDentryFeatureFlags()); + + // entryType (1 byte) + // (note: we have a fixed position for the entryType byte: DIRENTRY_TYPE_BUF_POS) + ser % uint8_t(this->dentryDiskData->getDirEntryType()); + + // 3 bytes padding for 8 byte alignment + ser.skip(3); + + // end of 8 byte header + + switch (dentryFormatVersion) + { + case DIRENTRY_STORAGE_FORMAT_VER3: + serializeDentryV3(ser); // V3, currently for dirs only + break; + + case DIRENTRY_STORAGE_FORMAT_VER4: + serializeDentryV4(ser); // V4 for files with inlined inodes + break; + + case DIRENTRY_STORAGE_FORMAT_VER5: // gets automatically upgraded to v6 + dentryFormatVersion = DIRENTRY_STORAGE_FORMAT_VER6; // inlined inodes + chunk-path-V3 + inodeData->getStripePattern()->setStoragePoolId(StoragePoolStore::DEFAULT_POOL_ID); + + BEEGFS_FALLTHROUGH; + + case DIRENTRY_STORAGE_FORMAT_VER6: + serializeDentryV6(ser); // inlined inodes + chunk-path-V3 + storage pools in pattern + break; + } +} + +/* + * Deserialize a dentry buffer. Here we only deserialize basic values and will continue with + * version specific dentry sub functions. + * + * Note: Applies deserialized data directly to the current object + */ +void DiskMetaData::deserializeDentry(Deserializer& des) +{ + const char* logContext = DISKMETADATA_LOG_CONTEXT " (Dentry Deserialization)"; + // note: assumes that the total amount of serialized data may not be larger than + // DIRENTRY_SERBUF_SIZE + + uint8_t formatVersion; // which dentry format version + + { + uint8_t metaDataType; + des % metaDataType; + } + + des % formatVersion; + + { // dentry feature flags + uint16_t dentryFeatureFlags; + + des % dentryFeatureFlags; + if (!des.good()) + { + std::string serialType = "Feature flags"; + LogContext(logContext).logErr("Deserialization failed: " + serialType); + + return; + } + + bool compatCheckRes = checkFeatureFlagsCompat( + dentryFeatureFlags, getSupportedDentryFeatureFlags() ); + if(unlikely(!compatCheckRes) ) + { + des.setBad(); + LOG(GENERAL, ERR, "Incompatible DirEntry feature flags found.", hex(dentryFeatureFlags), + hex(getSupportedDentryFeatureFlags())); + + return; + } + + this->dentryDiskData->setDentryFeatureFlags(dentryFeatureFlags); + } + + { + // (note: we have a fixed position for the entryType byte: DIRENTRY_TYPE_BUF_POS) + uint8_t type; + + des % type; + this->dentryDiskData->setDirEntryType((DirEntryType)type ); + } + + // mirrorNodeID (depends on feature flag) + padding + if(this->dentryDiskData->getDentryFeatureFlags() & DENTRY_FEATURE_MIRRORED) + { // mirrorNodeID + padding + // Note: we have an old-style mirrored file here; what we do is just throw away the mirror + // information here, because we don't need it; when the file gets written back to disk it + // will be written as unmirrored file! + + // first of all strip the feature flag, so we do not write it to disk again + this->dentryDiskData->removeDentryFeatureFlag(DENTRY_FEATURE_MIRRORED); + + uint16_t mirrorNodeID; // will be thrown away + + des % mirrorNodeID; + + // 1 byte padding for 8 byte aligned header + des.skip(1); + } + else + { // 3 bytes padding for 8 byte aligned header + des.skip(3); + } + + // end of 8-byte header + + switch (formatVersion) + { + case DIRENTRY_STORAGE_FORMAT_VER3: + deserializeDentryV3(des); + return; + + case DIRENTRY_STORAGE_FORMAT_VER4: + { + // data inlined, so this node must be the owner + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + deserializeDentryV4(des); + if (!des.good()) + return; + + // setting the owner node ID is a manual action, as it is not saved on disk + // depending on whether the file is mirrored or not, we set nodeID oder buddyGroupID here + NumNodeID ownerNodeID = this->inodeData->getIsBuddyMirrored() ? + NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ) : app->getLocalNode().getNumID(); + + this->dentryDiskData->setOwnerNodeID(ownerNodeID); + + this->inodeData->setOrigFeature(FileInodeOrigFeature_FALSE); // V4 does not have it + } break; + + case DIRENTRY_STORAGE_FORMAT_VER5: + { + // data inlined, so this node must be the owner + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + deserializeDentryV5(des); + if (!des.good()) + return; + + // setting the owner node ID is a manual action, as it is not saved on disk + // depending on whether the file is mirrored or not, we set nodeID oder buddyGroupID here + NumNodeID ownerNodeID = this->inodeData->getIsBuddyMirrored() ? + NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ) : app->getLocalNode().getNumID(); + + this->dentryDiskData->setOwnerNodeID(ownerNodeID); + + this->inodeData->setOrigFeature(FileInodeOrigFeature_TRUE); // V5 has the origFeature + + // for upgrade to V6 format, immediately add pool ID + inodeData->getStripePattern()->setStoragePoolId(StoragePoolStore::DEFAULT_POOL_ID); + } break; + + case DIRENTRY_STORAGE_FORMAT_VER6: + { + // data inlined, so this node must be the owner + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + deserializeDentryV6(des); + if (!des.good()) + return; + + // setting the owner node ID is a manual action, as it is not saved on disk + // depending on whether the file is mirrored or not, we set nodeID oder buddyGroupID here + NumNodeID ownerNodeID = this->inodeData->getIsBuddyMirrored() ? + NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ) : app->getLocalNode().getNumID(); + + this->dentryDiskData->setOwnerNodeID(ownerNodeID); + + this->inodeData->setOrigFeature(FileInodeOrigFeature_TRUE); // V5 has the origFeature + } break; + + default: + LogContext(logContext).logErr("Invalid Storage Format: " + + StringTk::uintToStr((unsigned) formatVersion) ); + des.setBad(); + } +} + +/* + * Version 3 format, now only used for directories and for example for disposal files + */ +void DiskMetaData::serializeDentryV3(Serializer& ser) +{ + ser + % serdes::stringAlign4(this->dentryDiskData->getEntryID()) + % this->dentryDiskData->getOwnerNodeID(); +} + +/** + * Deserialize dentries, which have the V3 format. + */ +void DiskMetaData::deserializeDentryV3(Deserializer& des) +{ + { + std::string entryID; + + des % serdes::stringAlign4(entryID); + this->dentryDiskData->setEntryID(entryID); + this->inodeData->setEntryID(entryID); + } + + if (this->dentryDiskData->getDentryFeatureFlags() & DENTRY_FEATURE_32BITIDS) + { + // dentry uses 32 bit nodeIDs, so we can just use our regular NumNodeIDs + NumNodeID ownerNodeID; + + des % ownerNodeID; + this->dentryDiskData->setOwnerNodeID(ownerNodeID); + } + else + { + // dentry uses old-style 16 bit nodeIDs + uint16_t ownerNodeID; + + des % ownerNodeID; + this->dentryDiskData->setOwnerNodeID(NumNodeID(ownerNodeID)); + } +} + +/* + * Version 4 format, for files with inlined inodes + */ +void DiskMetaData::serializeDentryV4(Serializer& ser) +{ + StatData* statData = this->inodeData->getInodeStatData(); + StripePattern* stripePattern = this->inodeData->getPattern(); + + ser % inodeData->getInodeFeatureFlags(); + ser.skip(4); // unused, was the chunkHash + ser + % statData->serializeAs(StatDataFormat_DENTRYV4) + % serdes::stringAlign4(this->dentryDiskData->getEntryID()) + % stripePattern; + + if (inodeData->getInodeFeatureFlags() & FILEINODE_FEATURE_HAS_VERSIONS) + { + ser % inodeData->getFileVersion(); + ser % inodeData->getMetaVersion(); + } +} + +/** + * Deserialize dentries, which have the V4 format, which includes inlined inodes and have the old + * chunk path (V1, by directly in hash dirs) + */ +void DiskMetaData::deserializeDentryV4(Deserializer& des) +{ + uint32_t inodeFeatureFlags; + + { + des % inodeFeatureFlags; + if (!des.good()) + return; + + bool compatCheckRes = checkFeatureFlagsCompat( + inodeFeatureFlags, getSupportedDentryV4FileInodeFeatureFlags() ); + if(unlikely(!compatCheckRes) ) + { + des.setBad(); + LOG(GENERAL, ERR, "Incompatible FileInode feature flags found.", hex(inodeFeatureFlags), + hex(getSupportedDentryV4FileInodeFeatureFlags())); + + return; + } + + this->inodeData->setInodeFeatureFlags(inodeFeatureFlags); + } + + // unused, was the chunkHash + des.skip(4); + + des % this->inodeData->getInodeStatData()->serializeAs(StatDataFormat_DENTRYV4); + + // note: up to here only fixed integers length, below follow variable string lengths + + { + std::string entryID; + + des % serdes::stringAlign4(entryID); + this->dentryDiskData->setEntryID(entryID); + this->inodeData->setEntryID(entryID); + } + + // mirrorNodeID (depends on feature flag) + if(inodeFeatureFlags & FILEINODE_FEATURE_MIRRORED) + { + // Note: we have an old-style mirrored file here; what we do is just throw away the mirror + // information here, because we don't need it; when the file gets written back to disk it + // will be written as unmirrored file! + + // first of all strip the feature flag, so we do not write it to disk again + this->inodeData->removeInodeFeatureFlag(FILEINODE_FEATURE_MIRRORED); + uint16_t mirrorNodeID; // will be thrown away + + des % mirrorNodeID; + } + + { + StripePattern* pattern = StripePattern::deserialize(des, false); + this->inodeData->setPattern(pattern); + } + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_VERSIONS) + { + des % inodeData->fileVersion; + des % inodeData->metaVersion; + } + inodeData->addInodeFeatureFlag(FILEINODE_FEATURE_HAS_VERSIONS); + + // sanity checks + +#ifdef BEEGFS_DEBUG + const char* logContext = DISKMETADATA_LOG_CONTEXT " (Dentry Deserialization V4)"; + if (unlikely(!(this->dentryDiskData->getDentryFeatureFlags() & DENTRY_FEATURE_IS_FILEINODE))) + { + LogContext(logContext).logErr("Bug: inode data successfully deserialized, but " + "the file-inode flag is not set. "); + return; + } +#endif +} + +/* + * Version 6 format, for files with inlined inodes and orig-parentID + orig-UID + storage pool + * + * Note: Serialization in V5 is not supported any longer => auto upgrade to v6 + */ +void DiskMetaData::serializeDentryV6(Serializer& ser) +{ + StatData* statData = this->inodeData->getInodeStatData(); + StripePattern* stripePattern = this->inodeData->getPattern(); + uint32_t inodeFeatureFlags = inodeData->getInodeFeatureFlags(); + + ser % inodeFeatureFlags; + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_STATE_FLAGS) + { + ser % inodeData->getFileState(); + ser.skip(3); // unused (3 bytes) for 8 byte alignment + } + else + { + // unused, for alignment + ser.skip(4); + } + + ser % statData->serializeAs(StatDataFormat_FILEINODE); + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_UID) + ser % this->inodeData->getOrigUID(); + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_PARENTID) + ser % serdes::stringAlign4(this->inodeData->getOrigParentEntryID()); + + ser + % serdes::stringAlign4(this->dentryDiskData->getEntryID()) + % stripePattern; + + if (inodeData->getInodeFeatureFlags() & FILEINODE_FEATURE_HAS_VERSIONS) + { + ser % inodeData->getFileVersion(); + ser % inodeData->getMetaVersion(); + } +} + + +/** + * Deserialize dentries, which have the V5 or V6 format. Both include inlined inodes and have the + * new chunk path (V2, which has UID and parentID); Additionally, V6 has a storage pool in stripe + * pattern + */ +void DiskMetaData::deserializeDentryV5V6(Deserializer& des, bool hasStoragePool) +{ + unsigned inodeFeatureFlags; + + { + des % inodeFeatureFlags; + if (!des.good()) + return; + + bool compatCheckRes = checkFeatureFlagsCompat( + inodeFeatureFlags, getSupportedDentryV5FileInodeFeatureFlags() ); + if(unlikely(!compatCheckRes) ) + { + des.setBad(); + LOG(GENERAL, ERR, "Incompatible FileInode feature flags found.", hex(inodeFeatureFlags), + hex(getSupportedDentryV5FileInodeFeatureFlags())); + + return; + } + + this->inodeData->setInodeFeatureFlags(inodeFeatureFlags); + } + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_STATE_FLAGS) + { + uint8_t state; + + des % state; + this->inodeData->setFileState(state); + + // unused, for alignment + des.skip(3); + } + else + { + // unused, for alignment + des.skip(4); + } + + StatData* statData = this->inodeData->getInodeStatData(); + + des % statData->serializeAs(StatDataFormat_FILEINODE); + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_UID) + { + unsigned origParentUID; + + des % origParentUID; + this->inodeData->setOrigUID(origParentUID); + } + else + { // no separate field, orig-UID and UID are identical + unsigned origParentUID = statData->getUserID(); + this->inodeData->setOrigUID(origParentUID); + } + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_PARENTID) + { + std::string origParentEntryID; + + des % serdes::stringAlign4(origParentEntryID); + this->inodeData->setDiskOrigParentEntryID(origParentEntryID); + } + + // note: up to here only fixed integers length, below follow variable string lengths + + { + std::string entryID; + + des % serdes::stringAlign4(entryID); + this->dentryDiskData->setEntryID(entryID); + this->inodeData->setEntryID(entryID); + } + + if(inodeFeatureFlags & FILEINODE_FEATURE_MIRRORED) + { + // Note: we have an old-style mirrored file here; what we do is just throw away the mirror + // information here, because we don't need it; when the file gets written back to disk it + // will be written as unmirrored file! + + // first of all strip the feature flag, so we do not write it to disk again + this->inodeData->removeInodeFeatureFlag(FILEINODE_FEATURE_MIRRORED); + uint16_t mirrorNodeID; // will be thrown away + + des % mirrorNodeID; + } + + { + StripePattern* pattern = StripePattern::deserialize(des, hasStoragePool); + this->inodeData->setPattern(pattern); + } + + if (inodeFeatureFlags & FILEINODE_FEATURE_HAS_VERSIONS) + { + des % inodeData->fileVersion; + des % inodeData->metaVersion; + } + inodeData->addInodeFeatureFlag(FILEINODE_FEATURE_HAS_VERSIONS); +} + +void DiskMetaData::deserializeDentryV5(Deserializer& des) +{ + // sanity checks + deserializeDentryV5V6(des, false); + +#ifdef BEEGFS_DEBUG + const char* logContext = DISKMETADATA_LOG_CONTEXT " (Dentry Deserialization V5)"; + if (unlikely(!(this->dentryDiskData->getDentryFeatureFlags() & DENTRY_FEATURE_IS_FILEINODE))) + { + LogContext(logContext).logErr("Bug: inode data successfully deserialized, but " + "the file-inode flag is not set. "); + return; + } +#endif +} + +void DiskMetaData::deserializeDentryV6(Deserializer& des) +{ + // sanity checks + deserializeDentryV5V6(des, true); + +#ifdef BEEGFS_DEBUG + const char* logContext = DISKMETADATA_LOG_CONTEXT " (Dentry Deserialization V6)"; + if (unlikely(!(this->dentryDiskData->getDentryFeatureFlags() & DENTRY_FEATURE_IS_FILEINODE))) + { + LogContext(logContext).logErr("Bug: inode data successfully deserialized, but " + "the file-inode flag is not set. "); + return; + } +#endif +} + +/** + * This method is for file-inodes located in the inode-hash directories. + */ +void DiskMetaData::deserializeFileInode(Deserializer& des) +{ + // right now file inodes are stored in the dentry format only, that will probably change + // later on. + return deserializeDentry(des); +} + + +template +void DiskMetaData::serializeDirInodeCommonData(Inode& inode, Ctx& ctx) +{ + if (likely(inode.featureFlags & DIRINODE_FEATURE_EARLY_SUBDIRS)) + ctx % inode.numSubdirs; + + ctx % inode.statData.serializeAs( + inode.featureFlags & DIRINODE_FEATURE_STATFLAGS + ? StatDataFormat_DIRINODE + : StatDataFormat_DIRINODE_NOFLAGS); + + if (unlikely(!(inode.featureFlags & DIRINODE_FEATURE_EARLY_SUBDIRS))) + ctx % inode.numSubdirs; + + ctx + % inode.numFiles + % serdes::stringAlign4(inode.id) + % serdes::stringAlign4(inode.parentDirID); +} + +/* + * Note: Current object state is used for the serialization + */ +void DiskMetaData::serializeDirInode(Serializer& ser, DirInode& inode) +{ + // note: the total amount of serialized data may not be larger than META_SERBUF_SIZE + + inode.featureFlags |= (DIRINODE_FEATURE_EARLY_SUBDIRS | DIRINODE_FEATURE_STATFLAGS); + + ser + % uint8_t(DiskMetaDataType_DIRINODE) + % uint8_t(DIRECTORY_STORAGE_FORMAT_VER3) + % inode.featureFlags; + + serializeDirInodeCommonData(inode, ser); + + ser + % inode.ownerNodeID + % inode.parentNodeID + % inode.stripePattern; +} + +/* + * Deserialize a DirInode + * + * Note: Applies deserialized data directly to the current object + * + */ +void DiskMetaData::deserializeDirInode(Deserializer& des, DirInode& outInode) +{ + const char* logContext = DISKMETADATA_LOG_CONTEXT " (DirInode Deserialization)"; + // note: assumes that the total amount of serialized data may not be larger than META_SERBUF_SIZE + + uint8_t formatVersion; + + { + uint8_t metaDataType; + + des % metaDataType; + if (unlikely(des.good() && metaDataType != DiskMetaDataType_DIRINODE)) + { + LogContext(logContext).logErr( + std::string("Deserialization failed: expected DirInode, but got (numeric type): ") + + StringTk::uintToStr((unsigned) metaDataType) ); + des.setBad(); + return; + } + } + + des % formatVersion; + + { + des % outInode.featureFlags; + if (!des.good()) + return; + + bool compatCheckRes = checkFeatureFlagsCompat( + outInode.featureFlags, getSupportedDirInodeFeatureFlags() ); + if(unlikely(!compatCheckRes) ) + { + LogContext(logContext).logErr("Incompatible DirInode feature flags found. " + "Used flags (hex): " + StringTk::uintToHexStr(outInode.featureFlags) + "; " + "Supported (hex): " + StringTk::uintToHexStr(getSupportedDirInodeFeatureFlags() ) ); + des.setBad(); + return; + } + } + + serializeDirInodeCommonData(outInode, des); + + bool hasStoragePool; + + switch (formatVersion) + { + case DIRECTORY_STORAGE_FORMAT_VER1: + { + uint16_t ownerNode; + uint16_t parentNode; + + des + % ownerNode + % parentNode; + + outInode.ownerNodeID = NumNodeID(ownerNode); + outInode.parentNodeID = NumNodeID(parentNode); + hasStoragePool = false; + break; + } + + case DIRECTORY_STORAGE_FORMAT_VER2: + { + des + % outInode.ownerNodeID + % outInode.parentNodeID; + + hasStoragePool = false; + break; + } + + case DIRECTORY_STORAGE_FORMAT_VER3: + { + des + % outInode.ownerNodeID + % outInode.parentNodeID; + + hasStoragePool = true; + break; + } + + default: + { + LogContext(logContext).logErr("Incompatible DirInode version found. " + "Version:" + StringTk::uintToStr(formatVersion)); + des.setBad(); + return; + } + } + + // mirrorNodeID (depends on feature flag) + if(outInode.featureFlags & DIRINODE_FEATURE_MIRRORED) + { + // Note: we have an old-style mirrored file here; what we do is just throw away the mirror + // information here, because we don't need it; when the file gets written back to disk it + // will be written as unmirrored file! + + // first of all strip the feature flag, so we do not write it to disk again + outInode.removeFeatureFlag(DIRINODE_FEATURE_MIRRORED); + uint16_t mirrorNodeID; // will be thrown away + + des % mirrorNodeID; + } + + outInode.stripePattern = StripePattern::deserialize(des, hasStoragePool); + if (!hasStoragePool) + outInode.getStripePattern()->setStoragePoolId(StoragePoolStore::DEFAULT_POOL_ID); +} + +/** + * @return mask of supported dentry feature flags + */ +unsigned DiskMetaData::getSupportedDentryFeatureFlags() +{ + return DENTRY_FEATURE_INODE_INLINE | DENTRY_FEATURE_IS_FILEINODE | DENTRY_FEATURE_MIRRORED + | DENTRY_FEATURE_BUDDYMIRRORED | DENTRY_FEATURE_32BITIDS; +} + + +/** + * @return mask of supported file inode feature flags, inlined into V4 Dentries + */ +unsigned DiskMetaData::getSupportedDentryV4FileInodeFeatureFlags() +{ + return FILEINODE_FEATURE_MIRRORED | FILEINODE_FEATURE_BUDDYMIRRORED | + FILEINODE_FEATURE_HAS_VERSIONS; +} + +/** + * @return mask of supported file inode feature flags, inlined into V5 Dentries + */ +unsigned DiskMetaData::getSupportedDentryV5FileInodeFeatureFlags() +{ + return FILEINODE_FEATURE_MIRRORED | FILEINODE_FEATURE_BUDDYMIRRORED | + FILEINODE_FEATURE_HAS_ORIG_PARENTID | FILEINODE_FEATURE_HAS_ORIG_UID | + FILEINODE_FEATURE_HAS_VERSIONS | FILEINODE_FEATURE_HAS_RST | FILEINODE_FEATURE_HAS_STATE_FLAGS; +} + +/** + * @return mask of supported dir inode feature flags + */ +unsigned DiskMetaData::getSupportedDirInodeFeatureFlags() +{ + return DIRINODE_FEATURE_EARLY_SUBDIRS | DIRINODE_FEATURE_MIRRORED | DIRINODE_FEATURE_STATFLAGS | + DIRINODE_FEATURE_BUDDYMIRRORED | DIRINODE_FEATURE_HAS_RST; +} + +/** + * Compare usedFeatureFlags with supportedFeatureFlags to find out whether an unsupported + * feature flag is used. + * + * @return false if an unsupported feature flag was set in usedFeatureFlags + */ +bool DiskMetaData::checkFeatureFlagsCompat(unsigned usedFeatureFlags, + unsigned supportedFeatureFlags) +{ + unsigned unsupportedFlags = ~supportedFeatureFlags; + + return !(usedFeatureFlags & unsupportedFlags); +} diff --git a/meta/source/storage/DiskMetaData.h b/meta/source/storage/DiskMetaData.h new file mode 100644 index 0000000..c8660e4 --- /dev/null +++ b/meta/source/storage/DiskMetaData.h @@ -0,0 +1,78 @@ +#pragma once + +#include "FileInodeStoreData.h" + +#define DIRENTRY_SERBUF_SIZE (1024 * 4) /* make sure that this is always smaller or equal to + * META_SERBUF_SIZE */ + +#define DISKMETADATA_TYPE_BUF_POS 0 +#define DIRENTRY_TYPE_BUF_POS 4 + +// 8-bit +enum DiskMetaDataType +{ + DiskMetaDataType_FILEDENTRY = 1, // may have inlined inodes + DiskMetaDataType_DIRDENTRY = 2, + DiskMetaDataType_FILEINODE = 3, // currently in dentry-format + DiskMetaDataType_DIRINODE = 4, +}; + + +// forward declarations; +class DirInode; +class FileInode; +class DirEntry; +class DentryStoreData; +class FileInodeStoreData; + +/** + * Generic class for on-disk storage. + * + * Note: The class is inherited from DirEntry::. But is an object in FileInode:: methods. + */ +class DiskMetaData +{ + public: + + // used for DirEntry derived class + DiskMetaData(DentryStoreData* dentryData, FileInodeStoreData* inodeData) + { + this->dentryDiskData = dentryData; + this->inodeData = inodeData; + }; + + + void serializeFileInode(Serializer& ser); + void serializeDentry(Serializer& ser); + void deserializeFileInode(Deserializer& des); + void deserializeDentry(Deserializer& des); + static void serializeDirInode(Serializer& ser, DirInode& inode); + static void deserializeDirInode(Deserializer& des, DirInode& outInode); + + protected: + DentryStoreData* dentryDiskData; // Not owned by this object! + FileInodeStoreData* inodeData; // Not owned by this object! + + private: + void serializeInDentryFormat(Serializer& ser, DiskMetaDataType metaDataType); + void serializeDentryV3(Serializer& ser); + void serializeDentryV4(Serializer& ser); + void serializeDentryV6(Serializer& ser); + void deserializeDentryV3(Deserializer& des); + void deserializeDentryV4(Deserializer& des); + void deserializeDentryV5(Deserializer& des); + void deserializeDentryV6(Deserializer& des); + void deserializeDentryV5V6(Deserializer& des, bool hasStoragePool); + + static unsigned getSupportedDentryFeatureFlags(); + static unsigned getSupportedDentryV4FileInodeFeatureFlags(); + static unsigned getSupportedDentryV5FileInodeFeatureFlags(); + static unsigned getSupportedDirInodeFeatureFlags(); + + static bool checkFeatureFlagsCompat(unsigned usedFeatureFlags, + unsigned supportedFeatureFlags); + + template + static void serializeDirInodeCommonData(Inode& inode, Ctx& ctx); +}; + diff --git a/meta/source/storage/FileInode.cpp b/meta/source/storage/FileInode.cpp new file mode 100644 index 0000000..ee4bbcf --- /dev/null +++ b/meta/source/storage/FileInode.cpp @@ -0,0 +1,3039 @@ +#include +#include +#include +#include +#include +#include +#include "FileInode.h" +#include "Locking.h" + +#include + +#include + + +// shorthand for the long init line of AppendLockQueuesContainer to create on stack +#define FILEINODE_APPEND_LOCK_QUEUES_CONTAINER(varName) \ + AppendLockQueuesContainer varName( \ + &exclAppendLock, &waitersExclAppendLock, &waitersLockIDsAppendLock) + +// shorthand for the long init line of EntryLockQueuesContainer to create on stack +#define FILEINODE_ENTRY_LOCK_QUEUES_CONTAINER(varName) \ + EntryLockQueuesContainer varName( \ + &exclFLock, &sharedFLocks, &waitersExclFLock, &waitersSharedFLock, &waitersLockIDsFLock) + + + +/** + * Inode initialization. The preferred initializer. Used for loading an inode from disk + */ +FileInode::FileInode(std::string entryID, FileInodeStoreData* inodeDiskData, + DirEntryType entryType, unsigned dentryFeatureFlags) : inodeDiskData(entryID, inodeDiskData) +{ + this->exclusiveTID = 0; + this->numSessionsRead = 0; + this->numSessionsWrite = 0; + + initFileInfoVec(); + + this->dentryCompatData.entryType = entryType; + this->dentryCompatData.featureFlags = dentryFeatureFlags; +} + +/** + * Note: This constructor does not perform the full initialization, so use it for + * metadata loading (or similar deserialization) only. + * + * Note: Don't forget to call initFileInfoVec() when using this (loadFromInodeFile() includes it). + */ +FileInode::FileInode() +{ + this->exclusiveTID = 0; + this->numSessionsRead = 0; + this->numSessionsWrite = 0; + + this->dentryCompatData.entryType = DirEntryType_INVALID; + this->dentryCompatData.featureFlags = 0; +} + +/** + * Requires: init'ed stripe pattern, modification and last access time secs + */ +void FileInode::initFileInfoVec() +{ + // create a fileInfo in the vector for each stripe node + + StripePattern* pattern = inodeDiskData.getStripePattern(); + size_t numTargets = pattern->getStripeTargetIDs()->size(); + unsigned chunkSize = pattern->getChunkSize(); + unsigned chunkSizeLog2 = MathTk::log2Int32(chunkSize); + + uint64_t stripeSetSize = chunkSize * numTargets; + + int64_t lastStripeSetSize; // =fileLength%stripeSetSize (remainder after stripeSetStart) + int64_t stripeSetStart; // =fileLength-stripeSetSize + int64_t fullLengthPerTarget; // =stripeSetStart/numTargets (without last stripe set remainder) + + StatData* statData = this->inodeDiskData.getInodeStatData(); + int64_t fileSize = statData->getFileSize(); + + /* compute stripeset start to get number of complete chunks on all nodes and stripeset remainder + to compute each target's remainder in the last stripe set. */ + + /* note: chunkSize is definitely power of two. if numTargets is also power of two, then + stripeSetSize is also power of two */ + + if(MathTk::isPowerOfTwo(numTargets) ) + { // quick path => optimized without division/modulo + lastStripeSetSize = fileSize & (stripeSetSize-1); + stripeSetStart = fileSize - lastStripeSetSize; + fullLengthPerTarget = stripeSetStart >> MathTk::log2Int32(numTargets); + } + else + { // slow path => requires division/modulo + lastStripeSetSize = fileSize % stripeSetSize; + stripeSetStart = fileSize - lastStripeSetSize; + fullLengthPerTarget = stripeSetStart / numTargets; + } + + // walk over all targets: compute their chunk file sizes and init timestamps + + fileInfoVec.reserve(numTargets); + + // to subtract last stripe set length of pevious targets in for-loop below + int64_t remainingLastSetSize = lastStripeSetSize; + + for(unsigned target=0; target < numTargets; target++) // iterate over all chunks / targets + { + int64_t targetFileLength = fullLengthPerTarget; + + if(remainingLastSetSize > 0) + targetFileLength += BEEGFS_MIN(remainingLastSetSize, chunkSize); + + int64_t modificationTimeSecs = statData->getModificationTimeSecs(); + int64_t lastAccessTimeSecs = statData->getLastAccessTimeSecs(); + + uint64_t usedBlocks; + if (statData->getIsSparseFile() ) + usedBlocks = statData->getTargetChunkBlocks(target); + else + { // estimate the number of blocks by the file size + usedBlocks = targetFileLength >> StatData::BLOCK_SHIFT; + } + + DynamicFileAttribs dynAttribs(0, targetFileLength, usedBlocks, modificationTimeSecs, + lastAccessTimeSecs); + ChunkFileInfo fileInfo(chunkSize, chunkSizeLog2, dynAttribs); + + fileInfoVec.push_back(fileInfo); + + remainingLastSetSize -= chunkSize; + } +} + +/* + * set remote targets for FileInode + */ +FhgfsOpsErr FileInode::setRemoteStorageTarget(EntryInfo* entryInfo, const RemoteStorageTarget& rst) +{ + const char* logContext = "Set Remote Storage Target (FileInode)"; + + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + auto [isValid, details] = rst.validateWithDetails(); + if (!isValid) + { + LogContext(logContext).log(Log_WARNING, "Invalid RST data: " + details); + retVal = FhgfsOpsErr_INTERNAL; + } + else + { + // set file's rst now + this->rstInfo.set(rst); + + if (this->storeRemoteStorageTargetUnlocked(entryInfo)) + { + if (!this->getIsRstAvailableUnlocked()) + { + addFeatureFlagUnlocked(FILEINODE_FEATURE_HAS_RST); + if (!this->storeUpdatedInodeUnlocked(entryInfo)) + retVal = FhgfsOpsErr_INTERNAL; + } + } + else + retVal = FhgfsOpsErr_INTERNAL; + } + + safeLock.unlock(); + return retVal; +} + +FhgfsOpsErr FileInode::clearRemoteStorageTarget(EntryInfo* entryInfo) +{ + const char* logContext = "Clear Remote Storage Target (FileInode)"; + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + if (!this->getIsRstAvailableUnlocked()) + return FhgfsOpsErr_SUCCESS; + + // Clear inode feature flag and store updated inode + unsigned flags = this->inodeDiskData.getInodeFeatureFlags(); + flags &= ~FILEINODE_FEATURE_HAS_RST; + this->inodeDiskData.setInodeFeatureFlags(flags); + if (!this->storeUpdatedInodeUnlocked(entryInfo)) + return FhgfsOpsErr_INTERNAL; + + // Clear in-memory RST info + this->rstInfo.reset(); + + // Remove RST xattr from meta file + std::string metafile = this->getMetaFilePath(entryInfo); + int res = removexattr(metafile.c_str(), RST_XATTR_NAME); + if (unlikely(res == -1)) + { + // Not reporting as error to caller because: + // 1. Feature flag is already cleared in metadata + // 2. In-memory state is reset + // 3. Future operations will ignore xattr due to cleared flag + // Just log warning msgs for any unexpected errors or missing xattr. + if (errno == ENODATA) + { + LogContext(logContext).log(Log_WARNING, "RST xattr not found. Path: " + metafile); + } + else + { + LogContext(logContext).log(Log_WARNING, "Failed to remove RST xattr; entryID: " + + entryInfo->getEntryID() + "; error: " + System::getErrString()); + } + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Decrease number of sessions for read or write (=> file close) and update persistent + * metadata. + * Note: This currently includes persistent metadata update for efficiency reasons (because + * we already hold the mutex lock here). + * + * @param accessFlags OPENFILE_ACCESS_... flags + */ +void FileInode::decNumSessionsAndStore(EntryInfo* entryInfo, unsigned accessFlags) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + if(accessFlags & OPENFILE_ACCESS_READ) + { + if(unlikely(!numSessionsRead) ) + { + LogContext log("File::decNumSessionsRead"); + log.logErr( + std::string("Warning: numSessionsRead is already zero. " + + std::string("File: ") + getEntryIDUnlocked() ) ); + } + else + this->numSessionsRead--; + } + else + { // (includes read+write) + if(unlikely(!numSessionsWrite) ) + { + LogContext log("File::decNumSessionsWrite"); + log.logErr( + std::string("Warning: numSessionsWrite is already zero. " + + std::string("File: ") + getEntryIDUnlocked() ) ); + } + else + this->numSessionsWrite--; + } + + // dyn attribs have been updated during close, so we save them here + storeUpdatedInodeUnlocked(entryInfo); + + safeLock.unlock(); +} + + +/** + * Note: This version is compatible with sparse files. + */ +void FileInode::updateDynamicAttribs() +{ + this->inodeDiskData.inodeStatData.updateDynamicFileAttribs(this->fileInfoVec, + this->inodeDiskData.getPattern() ); +} + +/* + * Note: Current object state is used for the serialization + */ +void FileInode::serializeMetaData(Serializer& ser) +{ + // note: the total amount of serialized data may not be larger than META_SERBUF_SIZE + + // get latest dyn attrib values + updateDynamicAttribs(); + + NumNodeID ownerNodeID ; /* irrelevant here. The serialize will set it to ourselves for inlined + * inodes */ + DentryStoreData dentryDiskData(this->inodeDiskData.getEntryID(), + this->dentryCompatData.entryType, ownerNodeID, this->dentryCompatData.featureFlags); + + DiskMetaData diskMetaData(&dentryDiskData, &this->inodeDiskData); + + diskMetaData.serializeFileInode(ser); +} + +/* + * Note: Applies deserialized data directly to the current object + */ +void FileInode::deserializeMetaData(Deserializer& des) +{ + DentryStoreData dentryDiskData; + DiskMetaData diskMetaData(&dentryDiskData, &this->inodeDiskData); + + diskMetaData.deserializeFileInode(des); + if (!des.good()) + return; + + { // dentry compat data + // entryType + this->dentryCompatData.entryType = dentryDiskData.getDirEntryType(); + + // (dentry) feature flags + this->dentryCompatData.featureFlags = dentryDiskData.getDentryFeatureFlags(); + } +} + + +/** + * Note: Wrapper/chooser for storeUpdatedMetaDataBufAsXAttr/Contents. + * Note: Unlocked, caller must hold write lock. + * + * @param buf the serialized object state that is to be stored + */ +bool FileInode::storeUpdatedMetaDataBuf(char* buf, unsigned bufLen) +{ + App* app = Program::getApp(); + + bool useXAttrs = app->getConfig()->getStoreUseExtendedAttribs(); + + const Path* inodesPath = + getIsBuddyMirroredUnlocked() ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodesPath->str(), + inodeDiskData.getEntryID()); + + bool result = useXAttrs + ? storeUpdatedMetaDataBufAsXAttr(buf, bufLen, metaFilename) + : storeUpdatedMetaDataBufAsContents(buf, bufLen, metaFilename); + + if (getIsBuddyMirroredUnlocked()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return result; +} + +/** + * Note: Don't call this directly, use the wrapper storeUpdatedMetaDataBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool FileInode::storeUpdatedMetaDataBufAsXAttr(char* buf, unsigned bufLen, std::string metaFilename) +{ + const char* logContext = "File (store updated xattr metadata)"; + + // open file (create file if not already present) + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + int fd = open(metaFilename.c_str(), openFlags, 0644); + + if (unlikely(fd == -1)) + { + LogContext(logContext).logErr("Unable to open/create inode metafile: " + metaFilename + + ". " + "SysErr: " + System::getErrString()); + return false; + } + + // write data to file + + int setRes = fsetxattr(fd, META_XATTR_NAME, buf, bufLen, 0); + + if(unlikely(setRes == -1) ) + { // error + LogContext(logContext).logErr("Unable to write FileInode metadata update: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + + close(fd); + return false; + } + + LOG_DEBUG(logContext, 4, "File inode update stored: " + this->inodeDiskData.getEntryID() ); + + close(fd); + return true; +} + +/** + * Stores the update to a sparate file first and then renames it. + * + * Note: Don't call this directly, use the wrapper storeUpdatedMetaDataBuf(). + * + * @param buf the serialized object state that is to be stored + */ +bool FileInode::storeUpdatedMetaDataBufAsContents(char* buf, unsigned bufLen, + std::string metaFilename) +{ + const char* logContext = "File (store updated inode)"; + + std::string metaUpdateFilename(metaFilename + META_UPDATE_EXT_STR); + + ssize_t writeRes; + int renameRes; + + // open file (create it, but not O_EXCL because a former update could have failed) + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(metaUpdateFilename.c_str(), openFlags, 0644); + if(fd == -1) + { // error + if(errno == ENOSPC) + { // no free space => try again with update in-place + LogContext(logContext).log(Log_DEBUG, "No space left to create update file. Trying update " + "in-place: " + metaUpdateFilename + ". " + "SysErr: " + System::getErrString() ); + + return storeUpdatedMetaDataBufAsContentsInPlace(buf, bufLen, metaFilename); + } + + LogContext(logContext).logErr("Unable to create inode update file: " + metaUpdateFilename + + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + // metafile created => store meta data + writeRes = write(fd, buf, bufLen); + if(writeRes != (ssize_t)bufLen) + { + if( (writeRes >= 0) || (errno == ENOSPC) ) + { // no free space => try again with update in-place + LogContext(logContext).log(Log_DEBUG, "No space left to write update inode. Trying update " + "in-place: " + metaUpdateFilename + ". " + "SysErr: " + System::getErrString() ); + + close(fd); + unlink(metaUpdateFilename.c_str() ); + + return storeUpdatedMetaDataBufAsContentsInPlace(buf, bufLen, metaFilename); + } + + LogContext(logContext).logErr("Unable to write inode update: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + + goto error_closefile; + } + + close(fd); + + renameRes = rename(metaUpdateFilename.c_str(), metaFilename.c_str() ); + if(renameRes == -1) + { + LogContext(logContext).logErr("Unable to replace old inode file: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + + goto error_unlink; + } + + LOG_DEBUG(logContext, 4, "Inode update stored: " + this->inodeDiskData.getEntryID() ); + + return true; + + + // error compensation +error_closefile: + close(fd); + +error_unlink: + unlink(metaUpdateFilename.c_str() ); + + return false; +} + +/** + * Stores the update directly to the current metadata file (instead of creating a separate file + * first and renaming it). + * + * Note: Don't call this directly, it is automatically called by storeUpdatedMetaDataBufAsContents() + * when necessary. + * + * @param buf the serialized object state that is to be stored + */ +bool FileInode::storeUpdatedMetaDataBufAsContentsInPlace(char* buf, unsigned bufLen, + std::string metaFilename) +{ + const char* logContext = "File (store updated inode in-place)"; + + int fallocRes; + ssize_t writeRes; + int truncRes; + + // open file (create it, but not O_EXCL because a former update could have failed) + int openFlags = O_CREAT|O_WRONLY; + + int fd = open(metaFilename.c_str(), openFlags, 0644); + if(fd == -1) + { // error + LogContext(logContext).logErr("Unable to open inode file: " + metaFilename + + ". " + "SysErr: " + System::getErrString() ); + + return false; + } + + // make sure we have enough room to write our update + fallocRes = posix_fallocate(fd, 0, bufLen); // (note: posix_fallocate does not set errno) + if(fallocRes == EBADF) + { // special case for XFS bug + struct stat statBuf; + int statRes = fstat(fd, &statBuf); + + if (statRes == -1) + { + LogContext(logContext).log(Log_WARNING, "Unexpected error: fstat() failed with SysErr: " + + System::getErrString(errno)); + goto error_closefile; + } + + if (statBuf.st_size < bufLen) + { + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for inode update failed: " + metaFilename + ". " + + "SysErr: " + System::getErrString(fallocRes) + " " + "statRes: " + StringTk::intToStr(statRes) + " " + "oldSize: " + StringTk::intToStr(statBuf.st_size)); + goto error_closefile; + } + else + { // // XFS bug! We only return an error if statBuf.st_size < bufLen. Ingore fallocRes then + LOG_DEBUG(logContext, Log_SPAM, "Ignoring kernel file system bug: " + "posix_fallocate() failed for len < filesize"); + } + } + else + if (fallocRes != 0) + { // default error handling if posix_fallocate() failed + LogContext(logContext).log(Log_WARNING, "File space allocation (" + + StringTk::intToStr(bufLen) + ") for inode update failed: " + metaFilename + ". " + + "SysErr: " + System::getErrString(fallocRes) ); + goto error_closefile; + } + + // metafile created => store meta data + writeRes = write(fd, buf, bufLen); + if(writeRes != (ssize_t)bufLen) + { + LogContext(logContext).logErr("Unable to write inode update: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + + goto error_closefile; + } + + close(fd); + + // truncate in case the update lead to a smaller file size + truncRes = ftruncate(fd, bufLen); + if(truncRes == -1) + { // ignore trunc errors + LogContext(logContext).log(Log_WARNING, "Unable to truncate inode file (strange, but " + "proceeding anyways): " + metaFilename + ". " + "SysErr: " + System::getErrString() ); + } + + LOG_DEBUG(logContext, 4, "File inode update stored: " + this->inodeDiskData.getEntryID() ); + + return true; + + + // error compensation +error_closefile: + close(fd); + + return false; +} + + +/** + * Update the inode on disk + * + * Note: We already need to have a FileInode (WRITE) rwlock here + */ +bool FileInode::storeUpdatedInodeUnlocked(EntryInfo* entryInfo, StripePattern* updatedStripePattern) +{ + const char* logContext = "FileInode (store updated Inode)"; + bool saveRes; + + bool isInLined = this->isInlined; + + if (isInLined) + { + FhgfsOpsErr dentrySaveRes = storeUpdatedInlinedInodeUnlocked(entryInfo, updatedStripePattern); + if (dentrySaveRes == FhgfsOpsErr_SUCCESS) + return true; + + // dentrySaveRes != FhgfsOpsErr_SUCCESS + std::string parentID = entryInfo->getParentEntryID(); + std::string entryID = entryInfo->getEntryID(); + std::string fileName = entryInfo->getFileName(); + + if (dentrySaveRes == FhgfsOpsErr_INODENOTINLINED) + { + /* dentrySaveRes == FhgfsOpsErr_INODENOTINLINED. Our internal inode information says the + * inode is inlined, but on writing it we figure out it is not. As we we are holding a + * write lock here, that never should have happened. So probably a locking bug, but not + * critical here and we retry using the non-inlined way. + */ + + LogContext(logContext).log(Log_WARNING, std::string("Inode unexpectedly not inlined: ") + + "parentID: "+ parentID + " entryID: " + entryID + " fileName: " + fileName ); + this->isInlined = false; + + } + else + { + LogContext(logContext).log(Log_WARNING, std::string("Failed to write inlined inode: ") + + "parentID: "+ parentID + " entryID: " + entryID + " fileName: " + fileName + + " Error: " + boost::lexical_cast(dentrySaveRes)); + #ifdef BEEGFS_DEBUG + LogContext(logContext).logBacktrace(); + #endif + + } + + + // it now falls through to the not-inlined handling, hopefully this is goint to work + } + + // inode not inlined + + // change the stripe pattern here before serializing; + + if (unlikely(updatedStripePattern)) + { + StripePattern* pattern = this->inodeDiskData.getPattern(); + if (!pattern->updateStripeTargetIDs(updatedStripePattern)) + LogContext(logContext).log(Log_WARNING, "Could not set requested new stripe pattern"); + } + + char buf[META_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + + serializeMetaData(ser); + + if (ser.good()) + saveRes = storeUpdatedMetaDataBuf(buf, ser.size()); + else + saveRes = false; + + if (!saveRes && isInlined) + { + LogContext(logContext).log(Log_WARNING, std::string("Trying to write as non-inlined inode " + "also failed.") ); + + } + + return saveRes; +} + +/** + * Update an inode, which is inlined into a dentry + */ +FhgfsOpsErr FileInode::storeUpdatedInlinedInodeUnlocked(EntryInfo* entryInfo, + StripePattern* updatedStripePattern) +{ + const char* logContext = "DirEntry (storeUpdatedInode)"; + App* app = Program::getApp(); + + // get latest dyn attrib vals... + updateDynamicAttribs(); + + std::string parentEntryID = entryInfo->getParentEntryID(); + + const Path* dentriesPath = + entryInfo->getIsBuddyMirrored() ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string dirEntryPath = MetaStorageTk::getMetaDirEntryPath(dentriesPath->str(), + parentEntryID); + + FileInodeStoreData* inodeDiskData = this->getInodeDiskData(); + + if (unlikely(updatedStripePattern)) + { + // note: We do not set the complete stripe pattern here, but only the stripe target IDs + if (! inodeDiskData->getPattern()->updateStripeTargetIDs(updatedStripePattern)) + LogContext(logContext).log(Log_WARNING, "Could not set new stripe target IDs."); + } + + DirEntry dirEntry(entryInfo->getEntryType(), entryInfo->getFileName(), + entryInfo->getEntryID(), entryInfo->getOwnerNodeID() ); + + /* Note: As we are called from FileInode most data of this DirEntry are unknown and we need to + * load it from disk. */ + bool loadRes = dirEntry.loadFromID(dirEntryPath, entryInfo->getEntryID() ); + if (!loadRes) + return FhgfsOpsErr_INTERNAL; + + FileInodeStoreData* entryInodeDiskData = dirEntry.getInodeStoreData(); + entryInodeDiskData->setFileInodeStoreData(inodeDiskData); + + FhgfsOpsErr retVal = dirEntry.storeUpdatedInode(dirEntryPath); + + return retVal; +} + +std::string FileInode::getMetaFilePath(EntryInfo* entryInfo) +{ + App* app = Program::getApp(); + if (isInlined) + { + const Path* dentriesPath = getIsBuddyMirroredUnlocked() + ? app->getBuddyMirrorDentriesPath() + : app->getDentriesPath(); + + std::string dirEntryPath = MetaStorageTk::getMetaDirEntryPath( + dentriesPath->str(), entryInfo->getParentEntryID()); + return MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + entryInfo->getEntryID(); + } + + const Path* inodesPath = getIsBuddyMirroredUnlocked() + ? app->getBuddyMirrorInodesPath() + : app->getInodesPath(); + + return MetaStorageTk::getMetaInodePath(inodesPath->str(), entryInfo->getEntryID()); +} + +bool FileInode::storeRemoteStorageTargetUnlocked(EntryInfo* entryInfo) +{ + std::string metafile = getMetaFilePath(entryInfo); + + char buf[META_SERBUF_SIZE]; + Serializer ser(buf, sizeof(buf)); + ser % rstInfo; + + if (!ser.good()) + return false; + + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + if (useXAttrs) + return storeRemoteStorageTargetBufAsXAttr(buf, ser.size(), metafile); + else + { + LOG(GENERAL, WARNING, "Storing RST info as file contents is unsupported. " + "Please check the 'storeUseExtendedAttribs' setting in the BeeGFS meta config"); + return false; + } +} + +bool FileInode::storeRemoteStorageTargetBufAsXAttr(char* buf, unsigned bufLen, const std::string& metafilename) +{ + const char* logContext = "FileInode (store remote storage target as xattr)"; + + int setRes = setxattr(metafilename.c_str(), RST_XATTR_NAME, buf, bufLen, 0); + + if (unlikely(setRes == -1)) + { + // error + LogContext(logContext).logErr("Unable to write remote storage target info to disk: " + + metafilename + ". SysErr: " + System::getErrString()); + + return false; + } + + return true; +} + +bool FileInode::removeStoredMetaData(const std::string& id, bool isBuddyMirrored) +{ + const char* logContext = "FileInode (remove stored metadata)"; + + App* app = Program::getApp(); + std::string inodeFilename = MetaStorageTk::getMetaInodePath( + isBuddyMirrored + ? app->getBuddyMirrorInodesPath()->str() + : app->getInodesPath()->str(), + id); + + // delete metadata file + int unlinkRes = unlink(inodeFilename.c_str() ); + + /* ignore errno == ENOENT as the file does not exist anymore for whatever reasons. Although + * unlink() failed, we do not have to care, as our goal is still reached. This is also about + * inode removal, if the dir-entry also does not exist, the application still will get + * the right error code */ + if(unlinkRes == -1 && errno != ENOENT) + { // error + LogContext(logContext).logErr("Unable to delete inode file: " + inodeFilename + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + LOG_DEBUG(logContext, 4, "Inode file deleted: " + inodeFilename); + + if (isBuddyMirrored) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addDeletion(inodeFilename, MetaSyncFileType::Inode); + + return true; +} + + +/** + * Note: Wrapper/chooser for loadFromFileXAttr/Contents. + * Note: This also (indirectly) calls initFileInfoVec() + */ +bool FileInode::loadFromInodeFile(EntryInfo* entryInfo) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + if(useXAttrs) + return loadFromFileXAttr(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored() ); + + return loadFromFileContents(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored() ); +} + +/** + * Note: Don't call this directly, use the wrapper loadFromInodeFile(). + * Note: This also calls initFileInfoVec() + */ +bool FileInode::loadFromFileXAttr(const std::string& id, bool isBuddyMirrored) +{ + const char* logContext = "File inode (load from xattr file)"; + App* app = Program::getApp(); + + const Path* inodePath = isBuddyMirrored ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + + bool retVal = false; + + char buf[META_SERBUF_SIZE]; + + ssize_t getRes = getxattr(metaFilename.c_str(), META_XATTR_NAME, buf, META_SERBUF_SIZE); + if(getRes > 0) + { // we got something => deserialize it + Deserializer des(buf, getRes); + deserializeMetaData(des); + + if(unlikely(!des.good())) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize metadata in file: " + metaFilename); + goto error_exit; + } + + // deserialization successful => init dyn attribs + + initFileInfoVec(); /* note: this can only be done after the stripePattern + has been initialized, that's why we do it here at this "unusual" place. */ + + retVal = true; + } + else + if( (getRes == -1) && (errno == ENOENT) ) + { // file not exists + LOG_DEBUG_CONTEXT(LogContext(logContext), Log_DEBUG, "Inode file not exists: " + + metaFilename + ". " + "SysErr: " + System::getErrString() ); + } + else + { // unhandled error + LogContext(logContext).logErr("Unable to open/read inode file: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + } + + +error_exit: + + return retVal; +} + +/** + * Note: Don't call this directly, use the wrapper loadFromInodeFile(). + * Note: This also calls initFileInfoVec() + */ +bool FileInode::loadFromFileContents(const std::string& id, bool isBuddyMirrored) +{ + const char* logContext = "File inode (load from file)"; + App* app = Program::getApp(); + + const Path* inodePath = isBuddyMirrored ? app->getBuddyMirrorInodesPath() : app->getInodesPath(); + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodePath->str(), id); + bool retVal = false; + + int openFlags = O_NOATIME | O_RDONLY; + + int fd = open(metaFilename.c_str(), openFlags, 0); + if(fd == -1) + { // open failed + if(errno != ENOENT) + LogContext(logContext).logErr("Unable to open inode file: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + + return false; + } + + char buf[META_SERBUF_SIZE]; + int readRes = read(fd, buf, META_SERBUF_SIZE); + if(readRes <= 0) + { // reading failed + LogContext(logContext).logErr("Unable to read inode file: " + metaFilename + ". " + + "SysErr: " + System::getErrString() ); + } + else + { + Deserializer des(buf, readRes); + deserializeMetaData(des); + if(!des.good()) + { // deserialization failed + LogContext(logContext).logErr("Unable to deserialize inode in file: " + metaFilename); + } + else + { // deserialization successful => init dyn attribs + initFileInfoVec(); // note: this can only be done after the stripePattern + // has been initialized, that's why we do it here at this "unusual" place + + retVal = true; + } + } + + close(fd); + + return retVal; +} + +bool FileInode::loadRstFromInodeFile(EntryInfo* entryInfo) +{ + bool useXAttrs = Program::getApp()->getConfig()->getStoreUseExtendedAttribs(); + + if (useXAttrs) + return loadRstFromFileXAttr(entryInfo); + + return false; +} + +bool FileInode::loadRstFromFileXAttr(EntryInfo* entryInfo) +{ + const char* logContext = "File inode RST (load from xattr file)"; + App* app = Program::getApp(); + std::string metafile; + + if (isInlined) + { + const Path* dentriesPath = + getIsBuddyMirroredUnlocked() ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string dirEntryPath = + MetaStorageTk::getMetaDirEntryPath(dentriesPath->str(), entryInfo->getParentEntryID()); + + metafile = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + entryInfo->getEntryID(); + } + else + { + const Path* inodesPath = + getIsBuddyMirroredUnlocked() ? app->getBuddyMirrorInodesPath(): app->getInodesPath(); + + metafile = MetaStorageTk::getMetaInodePath(inodesPath->str(), entryInfo->getEntryID()); + } + + char buf[META_SERBUF_SIZE]; + ssize_t getRes = getxattr(metafile.c_str(), RST_XATTR_NAME, buf, META_SERBUF_SIZE); + + if (getRes > 0) + { + // we got something => deserialize it + Deserializer des(buf, getRes); + des % this->rstInfo; + + if (unlikely(!des.good())) + { + // deserialization failed + LogContext(logContext).logErr("Unable to deserialize remote storage targets" + ", file: " + metafile); + return false; + } + + return true; + } + else + if( (getRes == -1) && (errno == ENOENT) ) + { // file not exists + LOG_DEBUG_CONTEXT(LogContext(logContext), Log_DEBUG, "Inode file not exists: " + + metafile + ". " + "SysErr: " + System::getErrString() ); + } + else + { // unhandled error + LogContext(logContext).logErr("Unable to open/read inode file: " + metafile + ". " + + "SysErr: " + System::getErrString() ); + } + + return false; +} + +/** + * Create an inode from an entryInfo. + * + * Note: The entryInfo indicates if the inode is inlined or not. However, this information + * might be outdated and so we need to try inlined and file-inode access, if creating + * the inode failed. + * We here rely on kernel lookup calls, to update client side entryInfo data. + */ +FileInode* FileInode::createFromEntryInfo(EntryInfo* entryInfo) +{ + FileInode* inode; + + if (entryInfo->getIsInlined() ) + { + /* entryInfo indicates the inode is inlined. So first try to get the inode by + * dir-entry inlined inode and if that failes try again with an inode-file. */ + inode = createFromInlinedInode(entryInfo); + + if (!inode) + inode = createFromInodeFile(entryInfo); + } + else + { + /* entryInfo indicates the inode is not inlined, but a separate inode-file. So first + * try to get the inode by inode-file and only if that fails try again with the dir-entry, + * maybe the inode was re-inlined. */ + inode = createFromInodeFile(entryInfo); + + if (!inode) + inode = createFromInlinedInode(entryInfo); + } + + if (likely(inode) && inode->getIsRstAvailableUnlocked()) + inode->loadRstFromInodeFile(entryInfo); + + return inode; +} + +/** + * Inode from inode file (inode is not inlined) + * + * Note: Do not call directly, but use FileInode::createFromEntryInfo() + */ +FileInode* FileInode::createFromInodeFile(EntryInfo* entryInfo) +{ + FileInode* newInode = new FileInode(); + + bool loadRes = newInode->loadFromInodeFile(entryInfo); + if(!loadRes) + { + delete(newInode); + + return NULL; + } + + newInode->setIsInlinedUnlocked(false); + + return newInode; +} + + +/** + * Inode from dir-entry with inlined inode. + * + * Note: Do not call directly, but use FileInode::createFromEntryInfo() + */ +FileInode* FileInode::createFromInlinedInode(EntryInfo* entryInfo) +{ + App* app = Program::getApp(); + + std::string parentEntryID = entryInfo->getParentEntryID(); + + const Path* dentryPath = + entryInfo->getIsBuddyMirrored() ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string dirEntryPath = MetaStorageTk::getMetaDirEntryPath(dentryPath->str(), + parentEntryID); + + DirEntry dirEntry(entryInfo->getEntryType(), entryInfo->getFileName(), + entryInfo->getEntryID(), entryInfo->getOwnerNodeID() ); + + FileInode* newInode = dirEntry.createInodeByID(dirEntryPath, entryInfo); + + if (newInode) + newInode->setIsInlinedUnlocked(true); + + return newInode; +} + + +/** + * Update entry attributes like chmod() etc. do it. + * + * Note: modificationTimeSecs and lastAccessTimeSecs are dynamic attribs, so they require + a special handling by the caller (but we also set the static attribs here). + + * @param validAttribs SETATTR_CHANGE_...-Flags, but maybe 0, if we only want to update + * AttribChangeTimeSecs. + * @param attribs new attributes, but may be NULL if validAttribs == 0 + */ +bool FileInode::setAttrData(EntryInfo * entryInfo, int validAttribs, SettableFileAttribs* attribs) +{ + bool success = true; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + /* note: modificationTimeSecs and lastAccessTimeSecs are dynamic attribs, so they require + a special handling by the caller (i.e. to also update chunk files) */ + // save old attribs + StatData* statData = this->inodeDiskData.getInodeStatData(); + SettableFileAttribs oldAttribs = *(statData->getSettableFileAttribs() ); + + statData->setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + + if(validAttribs) + { + // apply new attribs wrt flags... + + if(validAttribs & SETATTR_CHANGE_MODE) + statData->setMode(attribs->mode); + + if(validAttribs & SETATTR_CHANGE_MODIFICATIONTIME) + { + /* only static value update required for storeUpdatedInodeUnlocked() */ + statData->setModificationTimeSecs(attribs->modificationTimeSecs); + } + + if(validAttribs & SETATTR_CHANGE_LASTACCESSTIME) + { + /* only static value update required for storeUpdatedInodeUnlocked() */ + statData->setLastAccessTimeSecs(attribs->lastAccessTimeSecs); + } + + if(validAttribs & SETATTR_CHANGE_USERID) + { + statData->setUserID(attribs->userID); + + if ((attribs->userID != this->inodeDiskData.getOrigUID() ) && + (this->inodeDiskData.getOrigFeature() == FileInodeOrigFeature_TRUE) ) + addFeatureFlagUnlocked(FILEINODE_FEATURE_HAS_ORIG_UID); + } + + if(validAttribs & SETATTR_CHANGE_GROUPID) + statData->setGroupID(attribs->groupID); + } + + bool storeRes = storeUpdatedInodeUnlocked(entryInfo); // store on disk + if(!storeRes) + { // failed to update metadata on disk => restore old values + statData->setSettableFileAttribs(oldAttribs); + + success = false; + goto err_unlock; + } + + // persistent update succeeded + + // update attribs vec (wasn't done earlier because of backup overhead for restore on error) + + if(validAttribs & SETATTR_CHANGE_MODIFICATIONTIME) + { + for(size_t i=0; i < fileInfoVec.size(); i++) + fileInfoVec[i].getRawDynAttribs()->modificationTimeSecs = attribs->modificationTimeSecs; + } + + if(validAttribs & SETATTR_CHANGE_LASTACCESSTIME) + { + for(size_t i=0; i < fileInfoVec.size(); i++) + fileInfoVec[i].getRawDynAttribs()->lastAccessTimeSecs = attribs->lastAccessTimeSecs; + } + +err_unlock: + safeLock.unlock(); // U N L O C K + + return success; +} + +/** + * General wrapper for append lock and unlock operations. + * + * Append supports exclusive locking only, no shared locks. + * + * Note: Unlocks are always immediately granted (=> they always return "true"). + * + * @return true if operation succeeded immediately; false if registered for waiting (or failed in + * case of NOWAIT-flag) + */ +std::pair FileInode::flockAppend(EntryLockDetails& lockDetails) +{ + FILEINODE_APPEND_LOCK_QUEUES_CONTAINER(lockQs); + + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + return flockEntryUnlocked(lockDetails, &lockQs); +} + +/** + * General wrapper for flock lock and unlock operations. + * + * Note: Unlocks are always immediately granted (=> they always return "true"). + * + * @return true if operation succeeded immediately; false if registered for waiting (or failed in + * case of NOWAIT-flag) + */ +std::pair FileInode::flockEntry(EntryLockDetails& lockDetails) +{ + FILEINODE_ENTRY_LOCK_QUEUES_CONTAINER(lockQs); + + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + return flockEntryUnlocked(lockDetails, &lockQs); +} + +/** + * General wrapper for flock lock and unlock operations. + * + * Note: Unlocks are always immediately granted (=> they always return "true"). + * Note: Unlocked version => caller must hold write lock. + * + * @return true if operation succeeded immediately; false if registered for waiting (or failed in + * case of NOWAIT-flag) + */ +std::pair FileInode::flockEntryUnlocked(EntryLockDetails& lockDetails, + EntryLockQueuesContainer* lockQs) +{ + bool tryNextWaiters = false; + bool immediatelyGranted = false; // return value + + if(lockDetails.isCancel() ) + { + // C A N C E L request + + /* note: this is typically used when a client closes a file, so we remove all granted and + pending locks for the given handle here */ + + if(flockEntryCancelByHandle(lockDetails, lockQs) ) + tryNextWaiters = true; + + immediatelyGranted = true; + } + else + if(lockDetails.isUnlock() ) + { + // U N L O C K request + + tryNextWaiters = flockEntryUnlock(lockDetails, lockQs); + immediatelyGranted = true; + } + else + { + // L O C K request + + // check waiters to filter duplicate requests + + StringSetIter iterWaiters = lockQs->waitersLockIDs->find(lockDetails.lockAckID); + if(iterWaiters != lockQs->waitersLockIDs->end() ) + return {false, {}}; // re-request from waiter, but still in the queue => keep on waiting + + // not in waiters queue => is it granted already? + + bool isGrantedAlready = flockEntryIsGranted(lockDetails, lockQs); + if(isGrantedAlready) + return {true, {}}; // request was granted already + + // not waiting, not granted => we have a new request + + bool hasConflicts = flockEntryCheckConflicts(lockDetails, lockQs, NULL); + + if(!hasConflicts || lockDetails.allowsWaiting() ) + tryNextWaiters = flockEntryUnlock(lockDetails, lockQs); // unlock (for lock up-/downgrades) + + if(lockDetails.isShared() ) + { + // S H A R E D lock request + + if(!hasConflicts) + { // no confictors for this lock => can be immediately granted + flockEntryShared(lockDetails, lockQs); + immediatelyGranted = true; + } + else + if(lockDetails.allowsWaiting() ) + { // we have conflictors and locker wants to wait + lockQs->waitersSharedLock->push_back(lockDetails); + lockQs->waitersLockIDs->insert(lockDetails.lockAckID); + } + } + else + { + // E X C L U S I V E lock request + + if(!hasConflicts) + { // no confictors for this lock => can be immediately granted + flockEntryExclusive(lockDetails, lockQs); + immediatelyGranted = true; + } + else + if(lockDetails.allowsWaiting() ) + { // we have conflictors and locker wants to wait + lockQs->waitersExclLock->push_back(lockDetails); + lockQs->waitersLockIDs->insert(lockDetails.lockAckID); + } + } + } + + if (tryNextWaiters) + return {immediatelyGranted, flockEntryTryNextWaiters(lockQs)}; + + return {immediatelyGranted, {}}; +} + +/** + * Remove all waiters from the queues. + */ +void FileInode::flockAppendCancelAllWaiters() +{ + FILEINODE_APPEND_LOCK_QUEUES_CONTAINER(lockQs); + + flockEntryGenericCancelAllWaiters(&lockQs); +} + +/** + * Remove all waiters from the queues. + */ +void FileInode::flockEntryCancelAllWaiters() +{ + FILEINODE_ENTRY_LOCK_QUEUES_CONTAINER(lockQs); + + flockEntryGenericCancelAllWaiters(&lockQs); +} + +/** + * Remove all waiters from the queues. + * + * Generic version shared by append and flock locking. + */ +void FileInode::flockEntryGenericCancelAllWaiters(EntryLockQueuesContainer* lockQs) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + lockQs->waitersLockIDs->clear(); + lockQs->waitersExclLock->clear(); + lockQs->waitersSharedLock->clear(); +} + + +/** + * Unlock all locks and wait entries of the given clientID. + */ +LockEntryNotifyList FileInode::flockAppendCancelByClientID(NumNodeID clientID) +{ + FILEINODE_APPEND_LOCK_QUEUES_CONTAINER(lockQs); + + return flockEntryGenericCancelByClientID(clientID, &lockQs); +} + +/** + * Unlock all locks and wait entries of the given clientID. + */ +LockEntryNotifyList FileInode::flockEntryCancelByClientID(NumNodeID clientID) +{ + FILEINODE_ENTRY_LOCK_QUEUES_CONTAINER(lockQs); + + return flockEntryGenericCancelByClientID(clientID, &lockQs); +} + +/** + * Unlock all locks and wait entries of the given clientID. + * + * Generic version shared by append and flock locking. + */ +LockEntryNotifyList FileInode::flockEntryGenericCancelByClientID(NumNodeID clientNumID, + EntryLockQueuesContainer* lockQs) +{ + /* note: this code is in many aspects similar to flockEntryCancelByHandle(), so if you change + * something here, you probably want to change it there, too. */ + + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + bool tryNextWaiters = false; + + // exclusive lock + + if(lockQs->exclLock->isSet() && (lockQs->exclLock->clientNumID == clientNumID) ) + { + *lockQs->exclLock = {}; + tryNextWaiters = true; + } + + // shared locks + + for(EntryLockDetailsSetIter iter = lockQs->sharedLocks->begin(); + iter != lockQs->sharedLocks->end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + EntryLockDetailsSetIter iterNext = iter; + iterNext++; + + lockQs->sharedLocks->erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters exlusive + + for(EntryLockDetailsListIter iter = lockQs->waitersExclLock->begin(); + iter != lockQs->waitersExclLock->end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + lockQs->waitersLockIDs->erase(iter->lockAckID); + iter = lockQs->waitersExclLock->erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters shared + + for(EntryLockDetailsListIter iter = lockQs->waitersSharedLock->begin(); + iter != lockQs->waitersSharedLock->end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + lockQs->waitersLockIDs->erase(iter->lockAckID); + iter = lockQs->waitersSharedLock->erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + if (tryNextWaiters) + return flockEntryTryNextWaiters(lockQs); + + return {}; +} + +/** + * Remove all granted and pending locks that match the given handle. + * (This is typically called by clients during file close.) + * + * Note: unlocked, so hold the mutex when calling this. + * + * @return true if locks were removed and next waiters should be tried. + */ +bool FileInode::flockEntryCancelByHandle(EntryLockDetails& lockDetails, + EntryLockQueuesContainer* lockQs) +{ + /* note: this code is in many aspects similar to flockEntryCancelByClientID(), so if you change + * something here, you probably want to change it there, too. */ + + + bool tryNextWaiters = false; + + // exclusive lock + + if(lockQs->exclLock->isSet() && lockDetails.equalsHandle(*lockQs->exclLock) ) + { + *lockQs->exclLock = {}; + tryNextWaiters = true; + } + + // shared locks + + for(EntryLockDetailsSetIter iter = lockQs->sharedLocks->begin(); + iter != lockQs->sharedLocks->end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + EntryLockDetailsSetIter iterNext = iter; + iterNext++; + + lockQs->sharedLocks->erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters exlusive + + for(EntryLockDetailsListIter iter = lockQs->waitersExclLock->begin(); + iter != lockQs->waitersExclLock->end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + lockQs->waitersLockIDs->erase(iter->lockAckID); + iter = lockQs->waitersExclLock->erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters shared + + for(EntryLockDetailsListIter iter = lockQs->waitersSharedLock->begin(); + iter != lockQs->waitersSharedLock->end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + lockQs->waitersLockIDs->erase(iter->lockAckID); + iter = lockQs->waitersSharedLock->erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + return tryNextWaiters; +} + +/** + * Note: Automatically ignores self-conflicts (locks that could be up- or downgraded). + * Note: Make sure to remove lock duplicates before calling this. + * Note: unlocked, so hold the mutex when calling this. + * + * @param outConflictor first identified conflicting lock (only set if true is returned; can be + * NULL if caller is not interested) + * @return true if there is a conflict with a lock that is not owned by the current lock requestor, + * false if the request can defintely be granted immediately without waiting + */ +bool FileInode::flockEntryCheckConflicts(EntryLockDetails& lockDetails, + EntryLockQueuesContainer* lockQs, EntryLockDetails* outConflictor) +{ + // note: we also check waiting writers here, because we have writer preference and so we don't + // want to grant access for a new reader if we have a waiting writer + + + // check conflicting exclusive lock (for shared & eclusive requests) + + if(lockQs->exclLock->isSet() && !lockQs->exclLock->equalsHandle(lockDetails) ) + { + SAFE_ASSIGN(outConflictor, *lockQs->exclLock); + return true; + } + + // no exclusive lock exists + + if(lockDetails.isExclusive() ) + { // exclusive lock request: check conflicting shared lock + + for(EntryLockDetailsSetCIter iterShared = lockQs->sharedLocks->begin(); + iterShared != lockQs->sharedLocks->end(); + iterShared++) + { + if(!iterShared->equalsHandle(lockDetails) ) + { // found a conflicting lock + SAFE_ASSIGN(outConflictor, *iterShared); + return true; + } + } + } + else + { // non-exclusive lock: check for waiting writers to enforce writer preference + + if(!lockQs->waitersExclLock->empty() ) + { + SAFE_ASSIGN(outConflictor, *lockQs->waitersExclLock->begin() ); + return true; + } + } + + return false; +} + +/** + * Find out whether a given lock is currently being held by the given owner. + * + * Note: unlocked, hold the read lock when calling this. + * + * @return true if the given lock is being held by the given owner. + */ +bool FileInode::flockEntryIsGranted(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs) +{ + if(lockDetails.isExclusive() ) + { + if(lockQs->exclLock->equalsHandle(lockDetails) ) + { // was an exclusive lock + return true; + } + } + else + if(lockDetails.isShared() ) + { + EntryLockDetailsSetIter iterShared = lockQs->sharedLocks->find(lockDetails); + if(iterShared != lockQs->sharedLocks->end() ) + { // was a shared lock + return true; + } + } + + return false; +} + + +/** + * Note: unlocked, so hold the write lock when calling this. + * + * @return true if an existing lock was released + */ +bool FileInode::flockEntryUnlock(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs) +{ + if(lockQs->exclLock->equalsHandle(lockDetails) ) + { // was an exclusive lock + *lockQs->exclLock = {}; + return true; + } + + EntryLockDetailsSetIter iterShared = lockQs->sharedLocks->find(lockDetails); + if(iterShared != lockQs->sharedLocks->end() ) + { // was a shared lock + lockQs->sharedLocks->erase(iterShared); + + return true; + } + + return false; +} + + +/** + * Note: We assume that unlock() has been called before, so we don't check for up-/downgrades or + * duplicates. + * Note: unlocked, so hold the mutex when calling this + */ +void FileInode::flockEntryShared(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs) +{ + lockQs->sharedLocks->insert(lockDetails); +} + +/** + * Note: We assume that unlock() has been called before, so we don't check for up-/downgrades or + * duplicates. + * Note: unlocked, so hold the mutex when calling this + */ +void FileInode::flockEntryExclusive(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs) +{ + *lockQs->exclLock = lockDetails; +} + + +/** + * Remove next requests from waiters queue and try to grant it - until we reach an entry that + * cannot be granted immediately. + * + * Note: We assume that duplicate waiters and duplicate granted locks (up-/downgrades) have been + * removed before a lock request is enqueued, so we don't check for that. + * + * Note: FileInode must be already write-locked by the caller! + */ +LockEntryNotifyList FileInode::flockEntryTryNextWaiters(EntryLockQueuesContainer* lockQs) +{ + /* note: we have writer preference, so we don't grant any new readers while we have waiting + writers */ + + if(lockQs->exclLock->isSet() ) + return {}; // eclusive lock => there's nothing we can do right now + + // no exclusive lock set + + if(!lockQs->waitersSharedLock->empty() && lockQs->waitersExclLock->empty() ) + { // shared locks waiting and no exclusive locks waiting => grant all + + LockEntryNotifyList notifyList; + + while(!lockQs->waitersSharedLock->empty() ) + { + flockEntryShared(*lockQs->waitersSharedLock->begin(), lockQs); + + notifyList.push_back(*lockQs->waitersSharedLock->begin() ); + + lockQs->waitersLockIDs->erase(lockQs->waitersSharedLock->begin()->lockAckID); + lockQs->waitersSharedLock->pop_front(); + } + + return notifyList; + } + + // no exclusive and no shared locks set => we can grant an exclusive lock + + if(!lockQs->waitersExclLock->empty() ) + { // exclusive locks waiting => grant first one of them + flockEntryExclusive(*lockQs->waitersExclLock->begin(), lockQs); + + LockEntryNotifyList notifyList; + notifyList.push_back(*lockQs->waitersExclLock->begin() ); + + lockQs->waitersLockIDs->erase(lockQs->waitersExclLock->begin()->lockAckID); + lockQs->waitersExclLock->pop_front(); + + return notifyList; + } + + return {}; +} + +/** + * Generate a complete locking status overview (all granted and waiters) as human-readable string. + */ +std::string FileInode::flockAppendGetAllAsStr() +{ + FILEINODE_APPEND_LOCK_QUEUES_CONTAINER(lockQs); + + return flockEntryGenericGetAllAsStr(&lockQs); +} + +/** + * Generate a complete locking status overview (all granted and waiters) as human-readable string. + */ +std::string FileInode::flockEntryGetAllAsStr() +{ + FILEINODE_ENTRY_LOCK_QUEUES_CONTAINER(lockQs); + + return flockEntryGenericGetAllAsStr(&lockQs); +} + +/** + * Generate a complete locking status overview (all granted and waiters) as human-readable string. + * + * Generic version shared by append and flock locking. + */ +std::string FileInode::flockEntryGenericGetAllAsStr(EntryLockQueuesContainer* lockQs) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + std::ostringstream outStream; + + outStream << "Exclusive" << std::endl; + outStream << "=========" << std::endl; + if(lockQs->exclLock->isSet() ) + outStream << lockQs->exclLock->toString() << std::endl; + + outStream << std::endl; + + outStream << "Shared" << std::endl; + outStream << "=========" << std::endl; + for(EntryLockDetailsSetCIter iter = lockQs->sharedLocks->begin(); + iter != lockQs->sharedLocks->end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Exclusive Waiters" << std::endl; + outStream << "=========" << std::endl; + for(EntryLockDetailsListCIter iter = lockQs->waitersExclLock->begin(); + iter != lockQs->waitersExclLock->end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Shared Waiters" << std::endl; + outStream << "=========" << std::endl; + for(EntryLockDetailsListCIter iter = lockQs->waitersSharedLock->begin(); + iter != lockQs->waitersSharedLock->end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Waiters lockIDs" << std::endl; + outStream << "=========" << std::endl; + for(StringSetCIter iter = lockQs->waitersLockIDs->begin(); + iter != lockQs->waitersLockIDs->end(); + iter++) + { + outStream << *iter << std::endl; + } + + outStream << std::endl; + + return outStream.str(); +} + +/** + * General wrapper for flock lock and unlock operations. + * + * @return true if operation succeeded immediately; false if registered for waiting (or failed in + * case of NOWAIT-flag) + */ +std::pair FileInode::flockRange(RangeLockDetails& lockDetails) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + return flockRangeUnlocked(lockDetails); +} + +/** + * General wrapper for flock lock and unlock operations. + * + * Note: Unlocked, so caller must hold the write lock. + * + * @return true if operation succeeded immediately; false if registered for waiting (or failed in + * case of NOWAIT-flag) + */ +std::pair FileInode::flockRangeUnlocked(RangeLockDetails& lockDetails) +{ + bool tryNextWaiters = false; + bool immediatelyGranted = false; // return value + + if(lockDetails.isCancel() ) + { + // C A N C E L request + + /* note: this is typically used when a client closes a file, so we remove all granted and + pending locks for the given handle here */ + + if(flockRangeCancelByHandle(lockDetails) ) + tryNextWaiters = true; + + immediatelyGranted = true; + } + else + if(lockDetails.isUnlock() ) + { + // U N L O C K request + + tryNextWaiters = flockRangeUnlock(lockDetails); + immediatelyGranted = true; + } + else + { + // L O C K request + + // check waiters to filter duplicate requests + + StringSetIter iterWaiters = waitersLockIDsRangeFLock.find(lockDetails.lockAckID); + if(iterWaiters != waitersLockIDsRangeFLock.end() ) + return {false, {}}; // re-request from waiter, but still in the queue => keep on waiting + + // not in waiters queue => is it granted already? + + bool isGrantedAlready = flockRangeIsGranted(lockDetails); + if(isGrantedAlready) + return {true, {}}; // request was granted already + + // not waiting, not granted => we have a new request + + bool hasConflicts = flockRangeCheckConflicts(lockDetails, NULL); + + if(!hasConflicts || lockDetails.allowsWaiting() ) + tryNextWaiters = flockRangeUnlock(lockDetails); // unlock range (for lock up-/downgrades) + + if(lockDetails.isShared() ) + { + // S H A R E D lock request + + if(!hasConflicts) + { // no confictors for this lock => can be immediately granted + flockRangeShared(lockDetails); + immediatelyGranted = true; + } + else + if(lockDetails.allowsWaiting() ) + { // we have conflictors and locker wants to wait + waitersSharedRangeFLock.push_back(lockDetails); + waitersLockIDsRangeFLock.insert(lockDetails.lockAckID); + } + } + else + { + // E X C L U S I V E lock request + + if(!hasConflicts) + { // no confictors for this lock => can be immediately granted + flockRangeExclusive(lockDetails); + immediatelyGranted = true; + } + else + if(lockDetails.allowsWaiting() ) + { // we have conflictors and locker wants to wait + waitersExclRangeFLock.push_back(lockDetails); + waitersLockIDsRangeFLock.insert(lockDetails.lockAckID); + } + } + } + + if (tryNextWaiters) + return {immediatelyGranted, flockRangeTryNextWaiters()}; + + return {immediatelyGranted, {}}; +} + +/** + * Remove all waiters from the queues. + */ +void FileInode::flockRangeCancelAllWaiters() +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + waitersLockIDsRangeFLock.clear(); + waitersExclRangeFLock.clear(); + waitersSharedRangeFLock.clear(); +} + +/** + * Unlock all locks and wait entries of the given clientID. + */ +LockRangeNotifyList FileInode::flockRangeCancelByClientID(NumNodeID clientNumID) +{ + /* note: this code is in many aspects similar to flockRangeCancelByHandle(), so if you change + * something here, you probably want to change it there, too. */ + + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + bool tryNextWaiters = false; + + // exclusive locks + + for(RangeLockExclSetIter iter = exclRangeFLocks.begin(); + iter != exclRangeFLocks.end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + RangeLockExclSetIter iterNext = iter; + iterNext++; + + exclRangeFLocks.erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // shared locks + + for(RangeLockSharedSetIter iter = sharedRangeFLocks.begin(); + iter != sharedRangeFLocks.end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + RangeLockSharedSetIter iterNext = iter; + iterNext++; + + sharedRangeFLocks.erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters exlusive + + for(RangeLockDetailsListIter iter = waitersExclRangeFLock.begin(); + iter != waitersExclRangeFLock.end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersExclRangeFLock.erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters shared + + for(RangeLockDetailsListIter iter = waitersSharedRangeFLock.begin(); + iter != waitersSharedRangeFLock.end(); + /* iter inc'ed inside loop */ ) + { + if(iter->clientNumID == clientNumID) + { + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersSharedRangeFLock.erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + if(tryNextWaiters) + return flockRangeTryNextWaiters(); + + return {}; +} + +/** + * Remove all granted and pending locks that match the given handle. + * (This is typically called by clients during file close.) + * + * Note: unlocked, so hold the mutex when calling this. + * + * @return true if locks were removed and next waiters should be tried. + */ +bool FileInode::flockRangeCancelByHandle(RangeLockDetails& lockDetails) +{ + /* note: this code is in many aspects similar to flockRangeCancelByClientID(), so if you change + * something here, you probably want to change it there, too. */ + + + bool tryNextWaiters = false; + + // exclusive locks + + for(RangeLockExclSetIter iter = exclRangeFLocks.begin(); + iter != exclRangeFLocks.end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + RangeLockExclSetIter iterNext = iter; + iterNext++; + + exclRangeFLocks.erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // shared locks + + for(RangeLockSharedSetIter iter = sharedRangeFLocks.begin(); + iter != sharedRangeFLocks.end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + RangeLockSharedSetIter iterNext = iter; + iterNext++; + + sharedRangeFLocks.erase(iter); + + iter = iterNext; + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters exlusive + + for(RangeLockDetailsListIter iter = waitersExclRangeFLock.begin(); + iter != waitersExclRangeFLock.end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersExclRangeFLock.erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + // waiters shared + + for(RangeLockDetailsListIter iter = waitersSharedRangeFLock.begin(); + iter != waitersSharedRangeFLock.end(); + /* iter inc'ed inside loop */ ) + { + if(lockDetails.equalsHandle(*iter) ) + { + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersSharedRangeFLock.erase(iter); + + tryNextWaiters = true; + continue; + } + + iter++; + } + + + return tryNextWaiters; +} + + +/** + * Checks if there is a conflict for the given lock (but does not actually place lock). + * + * @param outConflictor the conflicting lock (or of of them) in case we return true. + * @return true if there is a conflict for the given lock request. + */ +bool FileInode::flockRangeGetConflictor(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + return flockRangeCheckConflicts(lockDetails, outConflictor); +} + +/** + * Note: see flockRangeCheckConflictsEx() for comments (this is just the simple version which + * checks the whole excl waiters queue and hence is inappropriate for tryNextWaiters() ). + */ +bool FileInode::flockRangeCheckConflicts(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor) +{ + return flockRangeCheckConflictsEx(lockDetails, -1, outConflictor); +} + + +/** + * Note: Automatically ignores self-conflicts (locks that could be up- or downgraded) + * Note: unlocked, so hold the mutex when calling this + * + * @param outConflictor first identified conflicting lock (only set if true is returned; can be + * NULL if caller is not interested) + * @param maxExclWaitersCheckNum only required by tryNextWaiters to find out how many pending excls + * in the queue before the checked element should be tested for conflicts (ie for the 5th queue + * element you will pass 4 here); -1 will check the whole queue, which is what all other callers + * probably want to do. + * @return true if there is a conflict with a lock that is not owned by the current lock requestor + */ +bool FileInode::flockRangeCheckConflictsEx(RangeLockDetails& lockDetails, int maxExclWaitersCheckNum, + RangeLockDetails* outConflictor) +{ + // note: we also check waiting writers here, because we have writer preference and so we don't + // want to grant access for a new reader if we have a waiting writer + // ...and we also don't want to starve writers by other writers, so we also check for + // overlapping waiting writer requests before granting a write lock + + + // check conflicting exclusive locks (for shared & exclusive requests) + + for(RangeLockExclSetCIter iterExcl = exclRangeFLocks.begin(); + (iterExcl != exclRangeFLocks.end() ) && (iterExcl->start <= lockDetails.end); + iterExcl++) + { + if(lockDetails.overlaps(*iterExcl) && + !lockDetails.equalsHandle(*iterExcl) ) + { + SAFE_ASSIGN(outConflictor, *iterExcl); + return true; + } + } + + // no conflicting exclusive lock exists + + if(lockDetails.isExclusive() ) + { // exclusive lock request: check conflicting shared locks + + // check granted shared locks + + for(RangeLockSharedSetCIter iterShared = sharedRangeFLocks.begin(); + iterShared != sharedRangeFLocks.end(); + iterShared++) + { + if(lockDetails.overlaps(*iterShared) && + !lockDetails.equalsHandle(*iterShared) ) + { + SAFE_ASSIGN(outConflictor, *iterShared); + return true; + } + } + } + + // no conflicting shared lock exists + + // check waiting writers (for shared reqs to prefer writers and for excl reqs to avoid + // writer starvation of partially overlapping waiting writers) + + // (note: keep in mind that maxExclWaitersCheckNum can also be -1 for infinite checks) + + for(RangeLockDetailsListCIter iter = waitersExclRangeFLock.begin(); + (iter != waitersExclRangeFLock.end() ) && (maxExclWaitersCheckNum != 0); + iter++, maxExclWaitersCheckNum--) + { + if(lockDetails.overlaps(*iter) && + !lockDetails.equalsHandle(*iter) ) + { + SAFE_ASSIGN(outConflictor, *iter); + return true; + } + } + + + + return false; +} + + +/** + * Note: We assume that unlock() has been called before, so we don't check for up-/downgrades or + * duplicates. + * Note: unlocked, so hold the mutex when calling this + */ +void FileInode::flockRangeShared(RangeLockDetails& lockDetails) +{ + // insert shared lock request... + // (avoid duplicates and side-by-side locks for same file handles by merging) + + for(RangeLockSharedSetIter iterShared = sharedRangeFLocks.begin(); + iterShared != sharedRangeFLocks.end(); + /* conditional iter increment inside loop */ ) + { + bool incIterAtEnd = true; + + if(lockDetails.equalsHandle(*iterShared) && lockDetails.isMergeable(*iterShared) ) + { // same handle => merge with existing lock + + // note: all overlaps will be merged into lockDetails, so every other overlapping entry + // can be removed here + + lockDetails.merge(*iterShared); + + RangeLockExclSetIter iterSharedNext(iterShared); + iterSharedNext++; + + sharedRangeFLocks.erase(iterShared); + + iterShared = iterSharedNext; + incIterAtEnd = false; + } + + if(incIterAtEnd) + iterShared++; + } + + // actually insert the new lock + sharedRangeFLocks.insert(lockDetails); +} + +/** + * Note: We assume that unlock() has been called before, so we don't check for up-/downgrades or + * duplicates. + * Note: unlocked, so hold the mutex when calling this + */ +void FileInode::flockRangeExclusive(RangeLockDetails& lockDetails) +{ + // insert excl lock request... + // (avoid duplicates and side-by-side locks for same file handles by merging) + + // (note: lockDetails.end+1: because we're also looking for extensions, not only overlaps) + for(RangeLockExclSetIter iterExcl = exclRangeFLocks.begin(); + (iterExcl != exclRangeFLocks.end() ) && (iterExcl->start <= (lockDetails.end+1) ); + /* conditional iter increment inside loop */ ) + { + bool incIterAtEnd = true; + + if(lockDetails.equalsHandle(*iterExcl) && lockDetails.isMergeable(*iterExcl) ) + { // same handle => merge with existing lock + + // note: all overlaps will be merged into lockDetails, so every other overlapping entry + // can be removed here + + lockDetails.merge(*iterExcl); + + RangeLockExclSetIter iterExclNext(iterExcl); + iterExclNext++; + + exclRangeFLocks.erase(iterExcl); + + iterExcl = iterExclNext; + incIterAtEnd = false; + } + + if(incIterAtEnd) + iterExcl++; + } + + // actually insert the new lock + exclRangeFLocks.insert(lockDetails); +} + +/** + * Find out whether a given range lock is currently being held by the given owner. + * + * Note: unlocked, hold the read lock when calling this. + * + * @return true if the range is locked by the given owner + */ +bool FileInode::flockRangeIsGranted(RangeLockDetails& lockDetails) +{ + if(lockDetails.isExclusive() ) + { + for(RangeLockExclSetIter iterExcl = exclRangeFLocks.begin(); + (iterExcl != exclRangeFLocks.end() ) && (iterExcl->start <= lockDetails.end); + /* conditional iter increment at end of loop */ ) + { + if(!lockDetails.equalsHandle(*iterExcl) ) + { // lock owned by another client/process + iterExcl++; + continue; + } + + // found a lock that is owned by the same client/process => check overlap with given lock + + bool incIterAtEnd = true; + + RangeOverlapType overlap = lockDetails.overlapsEx(*iterExcl); + + switch(overlap) + { + case RangeOverlapType_EQUALS: + { // found an exact match => don't need to look any further + return true; + } break; + + case RangeOverlapType_ISCONTAINED: + { /* given range is fully contained in a greater locked area => don't need to look any + further */ + return true; + } break; + + case RangeOverlapType_CONTAINS: + { /* found a range which is part of the given lock => given owner cannot currently hold + the lock for the whole given range, otherwise we wouldn't find a partial match + because of our merging => don't need to look any further */ + + return false; + } break; + + case RangeOverlapType_STARTOVERLAP: + case RangeOverlapType_ENDOVERLAP: + { /* found a range which is part of the given lock => given owner cannot currently hold + the lock for the whole given range, otherwise we wouldn't find a partial match + because of our merging => don't need to look any further */ + + return false; + } break; + + default: break; // no overlap + + } // end of switch(overlap) + + if(incIterAtEnd) + iterExcl++; + } + } // end of exclusive locks check + else + if(lockDetails.isShared() ) + { + for(RangeLockSharedSetIter iterShared = sharedRangeFLocks.begin(); + iterShared != sharedRangeFLocks.end(); + /* conditional iter increment at end of loop */ ) + { + if(!lockDetails.equalsHandle(*iterShared) ) + { // lock owned by another client/process + iterShared++; + continue; + } + + // found a lock that is owned by the same client/process => check overlap with given lock + + bool incIterAtEnd = true; + + RangeOverlapType overlap = lockDetails.overlapsEx(*iterShared); + + switch(overlap) + { + case RangeOverlapType_EQUALS: + { // found an exact match => don't need to look any further + + return true; + } break; + + case RangeOverlapType_ISCONTAINED: + { /* given lock is fully contained in a greater locked area => don't need to look any + further */ + + return true; + } break; + + case RangeOverlapType_CONTAINS: + { /* found a range which is part of the given lock => given owner cannot currently hold + the lock for the whole given range, otherwise we wouldn't find a partial match + because of our merging => don't need to look any further */ + + return false; + } break; + + case RangeOverlapType_STARTOVERLAP: + case RangeOverlapType_ENDOVERLAP: + { /* found a range which is part of the given lock => given owner cannot currently hold + the lock for the whole given range, otherwise we wouldn't find a partial match + because of our merging => don't need to look any further */ + + return false; + } break; + + default: break; // no overlap + + } // end of switch(overlap) + + if(incIterAtEnd) + iterShared++; + } + } // end of shared locks check + + + return false; +} + + +/** + * Note: unlocked, so hold the mutex when calling this. + * + * @return true if an existing lock has been removed + */ +bool FileInode::flockRangeUnlock(RangeLockDetails& lockDetails) +{ + bool lockRemoved = false; // return value + + // check exclusive locks... + // (quick path: if the whole unlock is entirely covered by an exclusive range, then we don't need + // to look any further) + + for(RangeLockExclSetIter iterExcl = exclRangeFLocks.begin(); + (iterExcl != exclRangeFLocks.end() ) && (iterExcl->start <= lockDetails.end); + /* conditional iter increment at end of loop */ ) + { + if(!lockDetails.equalsHandle(*iterExcl) ) + { // lock owned by another client/process + iterExcl++; + continue; + } + + // found a lock that is owned by the same client/process => check overlap with unlock request + + bool incIterAtEnd = true; + + RangeOverlapType overlap = lockDetails.overlapsEx(*iterExcl); + + switch(overlap) + { + case RangeOverlapType_EQUALS: + { // found an exact match => don't need to look any further + exclRangeFLocks.erase(iterExcl); + + return true; + } break; + + case RangeOverlapType_ISCONTAINED: + { // unlock is fully contained in a greater locked area => don't need to look any further + + // check if 1 or 2 locked areas remain (=> shrink or split) + + if( (lockDetails.start == iterExcl->start) || + (lockDetails.end == iterExcl->end) ) + { // only one locked area remains + RangeLockDetails oldExcl(*iterExcl); + oldExcl.trim(lockDetails); + + exclRangeFLocks.erase(iterExcl); + exclRangeFLocks.insert(oldExcl); + } + else + { // two locked areas remain + RangeLockDetails oldExcl(*iterExcl); + RangeLockDetails newExcl; + + oldExcl.split(lockDetails, newExcl); + + exclRangeFLocks.erase(iterExcl); + exclRangeFLocks.insert(oldExcl); + exclRangeFLocks.insert(newExcl); + } + + return true; + } break; + + case RangeOverlapType_CONTAINS: + { // full removal of this lock, but there may still be some others that need to be removed + RangeLockExclSetIter iterExclNext(iterExcl); + iterExclNext++; + + exclRangeFLocks.erase(iterExcl); + + lockRemoved = true; + + iterExcl = iterExclNext; + incIterAtEnd = false; + } break; + + case RangeOverlapType_STARTOVERLAP: + case RangeOverlapType_ENDOVERLAP: + { // partial removal of this lock and there may still be others that need to be removed + // note: might change start and consequently map position => re-insert excl lock + RangeLockExclSetIter iterExclNext(iterExcl); + iterExclNext++; + + RangeLockDetails oldExcl(*iterExcl); + oldExcl.trim(lockDetails); + + exclRangeFLocks.erase(iterExcl); + exclRangeFLocks.insert(oldExcl); + + lockRemoved = true; + + iterExcl = iterExclNext; + incIterAtEnd = false; + } break; + + default: break; // no overlap + + } // end of switch(overlap) + + if(incIterAtEnd) + iterExcl++; + } + + // check shared locks... + // (similar to exclusive locks, we can stop here if unlock is entirely covered by one of our + // owned shared ranges, because there cannot be another overlapping range which we also own) + + for(RangeLockSharedSetIter iterShared = sharedRangeFLocks.begin(); + iterShared != sharedRangeFLocks.end(); + /* conditional iter increment at end of loop */ ) + { + if(!lockDetails.equalsHandle(*iterShared) ) + { // lock owned by another client/process + iterShared++; + continue; + } + + // found a lock that is owned by the same client/process => check overlap with unlock request + + bool incIterAtEnd = true; + + RangeOverlapType overlap = lockDetails.overlapsEx(*iterShared); + + switch(overlap) + { + case RangeOverlapType_EQUALS: + { // found an exact match => don't need to look any further + sharedRangeFLocks.erase(iterShared); + + return true; + } break; + + case RangeOverlapType_ISCONTAINED: + { // unlock is fully contained in a greater locked area => don't need to look any further + + // check if 1 or 2 locked areas remain... + + if( (lockDetails.start == iterShared->start) || + (lockDetails.end == iterShared->end) ) + { // only one locked area remains + RangeLockDetails oldShared(*iterShared); + oldShared.trim(lockDetails); + + sharedRangeFLocks.erase(iterShared); + sharedRangeFLocks.insert(oldShared); + } + else + { // two locked areas remain + RangeLockDetails oldShared(*iterShared); + RangeLockDetails newShared; + + oldShared.split(lockDetails, newShared); + + sharedRangeFLocks.erase(iterShared); + sharedRangeFLocks.insert(oldShared); + sharedRangeFLocks.insert(newShared); + } + + return true; + } break; + + case RangeOverlapType_CONTAINS: + { // full removal of this lock, but there may still be some others that need to be removed + RangeLockExclSetIter iterExclNext(iterShared); + iterExclNext++; + + sharedRangeFLocks.erase(iterShared); + + lockRemoved = true; + + iterShared = iterExclNext; + incIterAtEnd = false; + } break; + + case RangeOverlapType_STARTOVERLAP: + case RangeOverlapType_ENDOVERLAP: + { // partial removal of this lock and there may still be others that need to be removed + // note: might change start and consequently map position => re-insert excl lock + RangeLockExclSetIter iterSharedNext(iterShared); + iterSharedNext++; + + RangeLockDetails oldShared(*iterShared); + oldShared.trim(lockDetails); + + sharedRangeFLocks.erase(iterShared); + sharedRangeFLocks.insert(oldShared); + + lockRemoved = true; + + iterShared = iterSharedNext; + incIterAtEnd = false; + } break; + + default: break; // no overlap + + } // end of switch(overlap) + + if(incIterAtEnd) + iterShared++; + } + + + return lockRemoved; +} + +/** + * Remove next requests from waiters queue and try to grant it - until we reach an entry that + * cannot be granted immediately. + * + * Note: unlocked, so hold the mutex when calling this. + */ +LockRangeNotifyList FileInode::flockRangeTryNextWaiters() +{ + int numWaitersBefore = 0; // number of waiters in the queue before the current checked element + + LockRangeNotifyList notifyList; // quick stack version to speed up the no waiter granted path + + + for(RangeLockDetailsListIter iter = waitersExclRangeFLock.begin(); + iter != waitersExclRangeFLock.end(); + /* conditional iter inc inside loop */) + { + bool hasConflict = flockRangeCheckConflictsEx(*iter, numWaitersBefore, NULL); + if(hasConflict) + { + iter++; + numWaitersBefore++; + continue; + } + + // no conflict => grant lock + + flockRangeExclusive(*iter); + + notifyList.push_back(*iter); + + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersExclRangeFLock.erase(iter); + } + + for(RangeLockDetailsListIter iter = waitersSharedRangeFLock.begin(); + iter != waitersSharedRangeFLock.end(); + /* conditional iter inc inside loop */) + { + bool hasConflict = flockRangeCheckConflicts(*iter, NULL); + if(hasConflict) + { + iter++; + continue; + } + + // no conflict => grant lock + + flockRangeShared(*iter); + + notifyList.push_back(*iter); + + waitersLockIDsRangeFLock.erase(iter->lockAckID); + iter = waitersSharedRangeFLock.erase(iter); + } + + return notifyList; +} + + +/** + * Generate a complete locking status overview (all granted and waiters) as human-readable string. + */ +std::string FileInode::flockRangeGetAllAsStr() +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + std::ostringstream outStream; + + outStream << "Exclusive" << std::endl; + outStream << "=========" << std::endl; + for(RangeLockExclSetCIter iter = exclRangeFLocks.begin(); + iter != exclRangeFLocks.end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Shared" << std::endl; + outStream << "=========" << std::endl; + for(RangeLockSharedSetCIter iter = sharedRangeFLocks.begin(); + iter != sharedRangeFLocks.end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Exclusive Waiters" << std::endl; + outStream << "=========" << std::endl; + for(RangeLockDetailsListCIter iter = waitersExclRangeFLock.begin(); + iter != waitersExclRangeFLock.end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Shared Waiters" << std::endl; + outStream << "=========" << std::endl; + for(RangeLockDetailsListCIter iter = waitersSharedRangeFLock.begin(); + iter != waitersSharedRangeFLock.end(); + iter++) + { + outStream << iter->toString() << std::endl; + } + + outStream << std::endl; + + outStream << "Waiters lockIDs" << std::endl; + outStream << "=========" << std::endl; + for(StringSetCIter iter = waitersLockIDsRangeFLock.begin(); + iter != waitersLockIDsRangeFLock.end(); + iter++) + { + outStream << *iter << std::endl; + } + + outStream << std::endl; + + return outStream.str(); +} + +/** + * Increase/decreas the link count of this inode + */ +bool FileInode::incDecNumHardLinks(EntryInfo* entryInfo, int value) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + incDecNumHardlinksUnpersistentUnlocked(value); + + // update ctime + StatData* statData = this->inodeDiskData.getInodeStatData(); + statData->setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + + bool retVal = storeUpdatedInodeUnlocked(entryInfo); // store on disk + if(!retVal) + { // failed to update metadata on disk => restore old values + incDecNumHardlinksUnpersistentUnlocked(-value); + } + + safeLock.unlock(); // U N L O C K + + return retVal; +} + +bool FileInode::operator==(const FileInode& other) const +{ + return inodeDiskData == other.inodeDiskData + && fileInfoVec == other.fileInfoVec + && exclusiveTID == other.exclusiveTID + && numSessionsRead == other.numSessionsRead + && numSessionsWrite == other.numSessionsWrite + && exclAppendLock == other.exclAppendLock + && waitersExclAppendLock == other.waitersExclAppendLock + && waitersLockIDsAppendLock == other.waitersLockIDsAppendLock + && exclFLock == other.exclFLock + && sharedFLocks == other.sharedFLocks + && waitersExclFLock == other.waitersExclFLock + && waitersSharedFLock == other.waitersSharedFLock + && waitersLockIDsFLock == other.waitersLockIDsFLock + && exclRangeFLocks == other.exclRangeFLocks + && sharedRangeFLocks == other.sharedRangeFLocks + && waitersExclRangeFLock == other.waitersExclRangeFLock + && waitersSharedRangeFLock == other.waitersSharedRangeFLock + && waitersLockIDsRangeFLock == other.waitersLockIDsRangeFLock + && dentryCompatData == other.dentryCompatData + && numParentRefs.read() == other.numParentRefs.read() + && referenceParentID == other.referenceParentID + && isInlined == other.isInlined; +} + +std::pair FileInode::listXAttr() +{ + BEEGFS_BUG_ON_DEBUG(isInlined, "inlined file inode cannot access its own xattrs"); + + const Path* inodesPath = getIsBuddyMirroredUnlocked() + ? Program::getApp()->getBuddyMirrorInodesPath() + : Program::getApp()->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodesPath->str(), + inodeDiskData.getEntryID()); + + return XAttrTk::listUserXAttrs(metaFilename); +} + +std::tuple, ssize_t> FileInode::getXAttr( + const std::string& xAttrName, size_t maxSize) +{ + BEEGFS_BUG_ON_DEBUG(isInlined, "inlined file inode cannot access its own xattrs"); + + const Path* inodesPath = getIsBuddyMirroredUnlocked() + ? Program::getApp()->getBuddyMirrorInodesPath() + : Program::getApp()->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodesPath->str(), + inodeDiskData.getEntryID()); + + return XAttrTk::getUserXAttr(metaFilename, xAttrName, maxSize); +} + +FhgfsOpsErr FileInode::removeXAttr(EntryInfo* entryInfo, const std::string& xAttrName) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + BEEGFS_BUG_ON_DEBUG(isInlined, "inlined file inode cannot access its own xattrs"); + + const Path* inodesPath = getIsBuddyMirroredUnlocked() + ? Program::getApp()->getBuddyMirrorInodesPath() + : Program::getApp()->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodesPath->str(), + inodeDiskData.getEntryID()); + + FhgfsOpsErr result = XAttrTk::removeUserXAttr(metaFilename, xAttrName); + + if (result == FhgfsOpsErr_SUCCESS) + { + inodeDiskData.inodeStatData.setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + storeUpdatedInodeUnlocked(entryInfo, nullptr); + } + + // FIXME: should resync only this xattr ON THE INODE + if (getIsBuddyMirroredUnlocked()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return result; +} + +FhgfsOpsErr FileInode::setXAttr(EntryInfo* entryInfo, const std::string& xAttrName, + const CharVector& xAttrValue, int flags) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + BEEGFS_BUG_ON_DEBUG(isInlined, "inlined file inode cannot access its own xattrs"); + + const Path* inodesPath = getIsBuddyMirroredUnlocked() + ? Program::getApp()->getBuddyMirrorInodesPath() + : Program::getApp()->getInodesPath(); + + std::string metaFilename = MetaStorageTk::getMetaInodePath(inodesPath->str(), + inodeDiskData.getEntryID()); + + FhgfsOpsErr result = XAttrTk::setUserXAttr(metaFilename, xAttrName, &xAttrValue[0], + xAttrValue.size(), flags); + + if (result == FhgfsOpsErr_SUCCESS) + { + inodeDiskData.inodeStatData.setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + storeUpdatedInodeUnlocked(entryInfo, nullptr); + } + + // FIXME: should resync only this xattr ON THE INODE + if (getIsBuddyMirroredUnlocked()) + if (auto* resync = BuddyResyncer::getSyncChangeset()) + resync->addModification(metaFilename, MetaSyncFileType::Inode); + + return result; +} + +void FileInode::initLocksRandomForSerializationTests() +{ + Random rand; + + this->exclusiveTID = rand.getNextInt(); + this->numSessionsRead = rand.getNextInt(); + this->numSessionsWrite = rand.getNextInt(); + + + this->exclAppendLock.initRandomForSerializationTests(); + + int max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + EntryLockDetails lock; + lock.initRandomForSerializationTests(); + this->waitersExclAppendLock.push_back(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + std::string id; + StringTk::genRandomAlphaNumericString(id, rand.getNextInRange(2, 30) ); + this->waitersLockIDsAppendLock.insert(id); + } + + + this->exclFLock.initRandomForSerializationTests(); + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + EntryLockDetails lock; + lock.initRandomForSerializationTests(); + this->sharedFLocks.insert(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + EntryLockDetails lock; + lock.initRandomForSerializationTests(); + this->waitersExclFLock.push_back(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + EntryLockDetails lock; + lock.initRandomForSerializationTests(); + this->waitersSharedFLock.push_back(lock); + } + + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + std::string id; + StringTk::genRandomAlphaNumericString(id, rand.getNextInRange(2, 30) ); + this->waitersLockIDsFLock.insert(id); + } + + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + RangeLockDetails lock; + lock.initRandomForSerializationTests(); + this->exclRangeFLocks.insert(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + RangeLockDetails lock; + lock.initRandomForSerializationTests(); + this->sharedRangeFLocks.insert(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + RangeLockDetails lock; + lock.initRandomForSerializationTests(); + this->waitersExclRangeFLock.push_back(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + RangeLockDetails lock; + lock.initRandomForSerializationTests(); + this->waitersSharedRangeFLock.push_back(lock); + } + + max = rand.getNextInRange(0, 1024); + for(int i = 0; i < max; i++) + { + std::string id; + StringTk::genRandomAlphaNumericString(id, rand.getNextInRange(2, 30) ); + this->waitersLockIDsFLock.insert(id); + } + + + StringTk::genRandomAlphaNumericString(this->referenceParentID, rand.getNextInRange(2, 30) ); + this->numParentRefs.set(rand.getNextInt() ); +} + +/** + * Checks whether current file state allows the requested access and increments appropriate + * session counter if permitted. The entire operation occurs under a single write lock + * to prevent races between open() operation and state validate-and-update operations. + * + * @param accessFlags OPENFILE_ACCESS_... flags + * @param bypassAccessCheck if true, skip all file state-based access checks + * @return FhgfsOpsErr_SUCCESS if file opened successfully + * FhgfsOpsErr_FILEACCESS_DENIED if file state restricts the requested access + */ +FhgfsOpsErr FileInode::checkAccessAndOpen(unsigned accessFlags, bool bypassAccessCheck) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + if (!bypassAccessCheck) + { + FileState state(getFileStateUnlocked()); + + // Fast path: Check if file is unlocked (common case) + if (unlikely(!state.isUnlocked())) + { + // File has active state restrictions - determine what access types are being requested + bool readRequested = accessFlags & (OPENFILE_ACCESS_READ | OPENFILE_ACCESS_READWRITE); + bool writeRequested = accessFlags & (OPENFILE_ACCESS_WRITE | OPENFILE_ACCESS_READWRITE | + OPENFILE_ACCESS_TRUNC); + + // Access not allowed if: state implies fully locked, or + // read requested when read-locked, or write requested when write-locked + bool blockOpenRequest = state.isFullyLocked() || + (state.isReadLocked() && readRequested) || + (state.isWriteLocked() && writeRequested); + + if (blockOpenRequest) + return FhgfsOpsErr_FILEACCESS_DENIED; + } + } + + // Access allowed - increment session counter + incNumSessionsUnlocked(accessFlags); + return FhgfsOpsErr_SUCCESS; +} \ No newline at end of file diff --git a/meta/source/storage/FileInode.h b/meta/source/storage/FileInode.h new file mode 100644 index 0000000..1545988 --- /dev/null +++ b/meta/source/storage/FileInode.h @@ -0,0 +1,1009 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Locking.h" +#include "MetadataEx.h" +#include "DiskMetaData.h" +#include "DentryStoreData.h" +#include "FileInodeStoreData.h" + + +typedef std::vector DynamicFileAttribsVec; +typedef DynamicFileAttribsVec::iterator DynamicFileAttribsVecIter; +typedef DynamicFileAttribsVec::const_iterator DynamicFileAttribsVecCIter; + +typedef std::vector ChunkFileInfoVec; +typedef ChunkFileInfoVec::iterator ChunkFileInfoVecIter; +typedef ChunkFileInfoVec::const_iterator ChunkFileInfoVecCIter; + + +/** + * Data are not really belonging to the inode, but we just need it to write inodes in the + * dentry-format. We need to read those from disk first or obtain it from the dentry. + */ +struct DentryCompatData +{ + DentryCompatData() + : entryType(DirEntryType_INVALID), featureFlags(0) + { + } + + DirEntryType entryType; + uint32_t featureFlags; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % serdes::as(obj->entryType) + % obj->featureFlags; + } + + bool operator==(const DentryCompatData& other) const + { + return entryType == other.entryType + && featureFlags == other.featureFlags; + } + + bool operator!=(const DentryCompatData& other) const { return !(*this == other); } +}; + + +/** + * Our inode object, but for files only (so all file types except of directories). + * Directories are in class DirInode. + */ +class FileInode +{ + friend class MetaStore; + friend class InodeFileStore; + friend class SessionFile; /* (needed for entry/range locking) */ + + + public: + + /** + * Should be rarely used and if required, only with an immediate following + * inode->deserialize(buf) + */ + FileInode(); + + /** + * The preferred inode initializer. + */ + FileInode(std::string entryID, FileInodeStoreData* inodeDiskData, + DirEntryType entryType, unsigned dentryFeatureFlags); + + + ~FileInode() + { + LOG_DEBUG("Delete FileInode", Log_SPAM, std::string("Deleting inode: ") + getEntryID()); + } + + FhgfsOpsErr setRemoteStorageTarget(EntryInfo* entryInfo, const RemoteStorageTarget& rst); + FhgfsOpsErr clearRemoteStorageTarget(EntryInfo* entryInfo); + + void decNumSessionsAndStore(EntryInfo* entryInfo, unsigned accessFlags); + static FileInode* createFromEntryInfo(EntryInfo* entryInfo); + + void serializeMetaData(Serializer& ser); + + std::pair flockAppend(EntryLockDetails& lockDetails); + void flockAppendCancelAllWaiters(); + LockEntryNotifyList flockAppendCancelByClientID(NumNodeID clientID); + std::string flockAppendGetAllAsStr(); + std::pair flockEntry(EntryLockDetails& lockDetails); + void flockEntryCancelAllWaiters(); + LockEntryNotifyList flockEntryCancelByClientID(NumNodeID clientID); + std::string flockEntryGetAllAsStr(); + std::pair flockRange(RangeLockDetails& lockDetails); + void flockRangeCancelAllWaiters(); + LockRangeNotifyList flockRangeCancelByClientID(NumNodeID clientNumID); + bool flockRangeGetConflictor(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor); + std::string flockRangeGetAllAsStr(); + + void deserializeMetaData(Deserializer& des); + + std::pair listXAttr(); + std::tuple, ssize_t> getXAttr(const std::string& xAttrName, + size_t maxSize); + FhgfsOpsErr removeXAttr(EntryInfo* entryInfo, const std::string& xAttrName); + FhgfsOpsErr setXAttr(EntryInfo* entryInfo, const std::string& xAttrName, + const CharVector& xAttrValue, int flags); + + bool operator==(const FileInode& other) const; + + bool operator!=(const FileInode& other) const { return !(*this == other); } + + /** + * only for unit tests + */ + void initLocksRandomForSerializationTests(); + + FhgfsOpsErr checkAccessAndOpen(unsigned openAccessFlags, bool bypassAccessCheck); + + public: + template + class LockState + { + friend class FileInode; + + public: + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->source->exclAppendLock + % obj->source->exclFLock + % obj->source->sharedFLocks + % obj->source->exclRangeFLocks + % obj->source->sharedRangeFLocks; + } + + friend Deserializer& operator%(Deserializer& des, const LockState& state) + { + serialize(&state, des); + return des; + } + + private: + LockState(InodeT* source) : source(source) {} + + InodeT* source; + }; + template + friend class LockState; + + LockState lockState() { return {this}; } + LockState lockState() const { return {this}; } + + private: + FileInodeStoreData inodeDiskData; + RemoteStorageTarget rstInfo; + + ChunkFileInfoVec fileInfoVec; // stores individual data for each node (e.g. file length), + // esp. the DYNAMIC ATTRIBS, that live only in memory and are not stored to disk (compared + // to static attribs) + + pthread_t exclusiveTID; // ID of the thread, which has exclusiveTID access to the inode + + uint32_t numSessionsRead; // open read-only + uint32_t numSessionsWrite; // open for writing or read-write + + + bool isInlined; // boolean if the inode inlined into the Dentry or a separate file + + // append lock queues (exclusive only, entire file locking) + EntryLockDetails exclAppendLock; // current exclusiveTID lock + EntryLockDetailsList waitersExclAppendLock; // queue (append new to end, pop from top) + StringSet waitersLockIDsAppendLock; // currently enqueued lockIDs (for fast duplicate check) + + // flock() queues (entire file locking, i.e. no ranges) + EntryLockDetails exclFLock; // current exclusiveTID lock + EntryLockDetailsSet sharedFLocks; // current shared locks (key is lock, value is dummy) + EntryLockDetailsList waitersExclFLock; // queue (append new to end, pop from top) + EntryLockDetailsList waitersSharedFLock; // queue (append new to end, pop from top) + StringSet waitersLockIDsFLock; // currently enqueued lockIDs (for fast duplicate check) + + // fcntl() flock queues (range-based) + RangeLockExclSet exclRangeFLocks; // current exclusiveTID locks + RangeLockSharedSet sharedRangeFLocks; // current shared locks (key is lock, value is dummy) + RangeLockDetailsList waitersExclRangeFLock; // queue (append new to end, pop from top) + RangeLockDetailsList waitersSharedRangeFLock; // queue (append new to end, pop from top) + StringSet waitersLockIDsRangeFLock; // currently enqueued lockIDs (for fast duplicate check) + + RWLock rwlock; // default inode lock + + DentryCompatData dentryCompatData; + + RWLock setGetReferenceParentLock; // just to set/get the referenceParentID + std::string referenceParentID; + AtomicSSizeT numParentRefs; /* counter how often parentDir is referenced, 0 if we + * are part of MetaStores FileStore */ + + + static FileInode* createFromInlinedInode(EntryInfo* entryInfo); + static FileInode* createFromInodeFile(EntryInfo* entryInfo); + + void initFileInfoVec(); + void updateDynamicAttribs(void); + + bool setAttrData(EntryInfo* entryInfo, int validAttribs, SettableFileAttribs* attribs); + bool incDecNumHardLinks(EntryInfo * entryInfo, int value); + + bool storeUpdatedMetaDataBuf(char* buf, unsigned bufLen); + bool storeUpdatedMetaDataBufAsXAttr(char* buf, unsigned bufLen, std::string metaFilename); + bool storeUpdatedMetaDataBufAsContents(char* buf, unsigned bufLen, std::string metaFilename); + bool storeUpdatedMetaDataBufAsContentsInPlace(char* buf, unsigned bufLen, + std::string metaFilename); + bool storeUpdatedInodeUnlocked(EntryInfo* entryInfo, + StripePattern* updatedStripePattern = NULL); + FhgfsOpsErr storeUpdatedInlinedInodeUnlocked(EntryInfo* entryInfo, + StripePattern* updatedStripePattern = NULL); + + std::string getMetaFilePath(EntryInfo* entryInfo); + bool storeRemoteStorageTargetUnlocked(EntryInfo* entryInfo); + bool storeRemoteStorageTargetBufAsXAttr(char* buf, unsigned bufLen, const std::string& metafilename); + + static bool removeStoredMetaData(const std::string& id, bool isBuddyMirrored); + + bool loadFromInodeFile(EntryInfo* entryInfo); + bool loadFromFileXAttr(const std::string& id, bool isBuddyMirrored); + bool loadFromFileContents(const std::string& id, bool isBuddyMirrored); + + bool loadRstFromInodeFile(EntryInfo* entryInfo); + bool loadRstFromFileXAttr(EntryInfo* entryInfo); + + std::pair flockEntryUnlocked(EntryLockDetails& lockDetails, + EntryLockQueuesContainer* lockQs); + bool flockEntryCancelByHandle(EntryLockDetails& lockDetails, + EntryLockQueuesContainer* lockQs); + void flockEntryGenericCancelAllWaiters(EntryLockQueuesContainer* lockQs); + LockEntryNotifyList flockEntryGenericCancelByClientID(NumNodeID clientNumID, + EntryLockQueuesContainer* lockQs); + std::string flockEntryGenericGetAllAsStr(EntryLockQueuesContainer* lockQs); + + bool flockEntryCheckConflicts(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs, + EntryLockDetails* outConflictor); + bool flockEntryIsGranted(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs); + bool flockEntryUnlock(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs); + void flockEntryShared(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs); + void flockEntryExclusive(EntryLockDetails& lockDetails, EntryLockQueuesContainer* lockQs); + LockEntryNotifyList flockEntryTryNextWaiters(EntryLockQueuesContainer* lockQs); + + std::pair flockRangeUnlocked(RangeLockDetails& lockDetails); + bool flockRangeCancelByHandle(RangeLockDetails& lockDetails); + + bool flockRangeCheckConflicts(RangeLockDetails& lockDetails, RangeLockDetails* outConflictor); + bool flockRangeCheckConflictsEx(RangeLockDetails& lockDetails, int maxExclWaitersCheckNum, + RangeLockDetails* outConflictor); + bool flockRangeIsGranted(RangeLockDetails& lockDetails); + bool flockRangeUnlock(RangeLockDetails& lockDetails); + void flockRangeShared(RangeLockDetails& lockDetails); + void flockRangeExclusive(RangeLockDetails& lockDetails); + LockRangeNotifyList flockRangeTryNextWaiters(); + + + public: + // inliners + + void setRemoteStorageTargetUnpersistent(const RemoteStorageTarget& rst) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + this->rstInfo.set(rst); + safeLock.unlock(); + } + + /** + * Save meta-data to disk + */ + bool updateInodeOnDisk(EntryInfo* entryInfo, StripePattern* updatedStripePattern = NULL) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + bool retVal = storeUpdatedInodeUnlocked(entryInfo, updatedStripePattern); + + safeLock.unlock(); // U N L O C K + + return retVal; + } + + bool updateInodeChangeTime(EntryInfo* entryInfo) + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + inodeDiskData.inodeStatData.setAttribChangeTimeSecs(TimeAbs().getTimeval()->tv_sec); + + return storeUpdatedInodeUnlocked(entryInfo, nullptr); + } + + /** + * Unlink the stored file-inode file on disk. + */ + static bool unlinkStoredInodeUnlocked(const std::string& id, bool isBuddyMirrored) + { + return removeStoredMetaData(id, isBuddyMirrored); + } + + + // getters & setters + std::string getEntryID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + std::string entryID = getEntryIDUnlocked(); + + safeLock.unlock(); + + return entryID; + } + + std::string getEntryIDUnlocked() + { + return this->inodeDiskData.getEntryID(); + } + + + /** + * Note: Does not (immediately) update the persistent metadata. + * Note: Be careful with this! + */ + void setIDUnpersistent(std::string newID) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->inodeDiskData.setEntryID(newID); + + safeLock.unlock(); + } + + RemoteStorageTarget* getRemoteStorageTargetInfo() + { + return &this->rstInfo; + } + + StripePattern* getStripePatternUnlocked() + { + return this->inodeDiskData.getStripePattern(); + } + + StripePattern* getStripePattern() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + StripePattern* pattern = this->getStripePatternUnlocked(); + + safeLock.unlock(); + + return pattern; + } + + /** + * Return the stripe pattern and set our value to NULL. + * + * Note: Usually this inode should not be further used anymore. + */ + StripePattern* getStripePatternAndSetToNull() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + StripePattern* pattern = this->inodeDiskData.getStripePatternAndSetToNull(); + + safeLock.unlock(); + + return pattern; + } + + void setFeatureFlags(unsigned flags) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->inodeDiskData.setInodeFeatureFlags(flags); + + safeLock.unlock(); + } + + void addFeatureFlag(unsigned flag) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + addFeatureFlagUnlocked(flag); + + safeLock.unlock(); + } + + unsigned getFeatureFlags() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned flags = this->inodeDiskData.getInodeFeatureFlags(); + + safeLock.unlock(); + + return flags; + } + + unsigned getUserID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned retVal = this->inodeDiskData.getInodeStatData()->getUserID(); + + safeLock.unlock(); + + return retVal; + } + + unsigned getGroupID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned retVal = this->inodeDiskData.getInodeStatData()->getGroupID(); + + safeLock.unlock(); + + return retVal; + } + + int getMode() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + int retVal = this->inodeDiskData.getInodeStatData()->getMode(); + + safeLock.unlock(); + + return retVal; + } + + /* uint16_t getMirrorNodeID() + { + return this->inodeDiskData.getMirrorNodeID(); + } */ + + + /** + * Note: We do not set our class values here yet, because this method is called frequently and + * we do not need our class values here yet. So class values are only set on demand. + * this->updateDynamicAttribs() will set/update our class values· + * + * @param erroneousVec if errors occurred during vector retrieval from storage nodes + * (this parameter was used to set dynAttribsUptodate to false, but we do not have + * this value anymore since the switch to (post-)DB metadata format) + * @return false if error during save occurred (but we don't save dynAttribs currently) + * + */ + bool setDynAttribs(DynamicFileAttribsVec& newAttribsVec) + { + /* note: we cannot afford to make a disk write each time this is updated, so + we only update metadata on close currrently */ + + bool retVal = true; + bool anyAttribUpdated = false; + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + if(unlikely(newAttribsVec.size() != fileInfoVec.size() ) ) + { // should never happen + LOG(GENERAL, ERR, "Apply dynamic file attribs - Vector sizes do not match."); + + retVal = false; + goto unlock_and_exit; + } + + + for(size_t i=0; i < newAttribsVec.size(); i++) + { + if(fileInfoVec[i].updateDynAttribs(newAttribsVec[i] ) ) + anyAttribUpdated = true; + } + + IGNORE_UNUSED_VARIABLE(anyAttribUpdated); + + unlock_and_exit: + safeLock.unlock(); + + return retVal; + } + + /** + * Note: Use this only if the file is not loaded already (because otherwise the dyn attribs + * will be outdated) + */ + static FhgfsOpsErr getStatData(EntryInfo* entryInfo, StatData& outStatData) + { + FileInode* inode = createFromEntryInfo(entryInfo); + + if(!inode) + return FhgfsOpsErr_PATHNOTEXISTS; + + FhgfsOpsErr retVal = inode->getStatData(outStatData); + + delete inode; + + return retVal; + } + + /** + * Unlocked method to get statdata + * + * Note: Needs write-lock + */ + FhgfsOpsErr getStatDataUnlocked(StatData& outStatData) + { + FhgfsOpsErr statRes = FhgfsOpsErr_SUCCESS; + + this->updateDynamicAttribs(); + + outStatData = *(this->inodeDiskData.getInodeStatData() ); + + /* note: we don't check numSessionsRead below (for dyn attribs outated statRes), because + that would be too much overhead; side-effect is that we don't update atime for read-only + files while they are open. */ + + if(numSessionsWrite) + statRes = FhgfsOpsErr_DYNAMICATTRIBSOUTDATED; + + return statRes; + } + + + /** + * Return statData + * + * Note: Also updates dynamic attribs, so needs a write lock + */ + FhgfsOpsErr getStatData(StatData& outStatData) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr statRes = getStatDataUnlocked(outStatData); + + safeLock.unlock(); + + return statRes; + } + + void setStatData(StatData& statData) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->inodeDiskData.setInodeStatData(statData); + + safeLock.unlock(); + } + + /** + * Note: Does not take a lock and is a very special use case. For example on moving a file + * to a remote server, when an inode object is created from a buffer and we want to + * have values from this inode object. + */ + StatData* getNotUpdatedStatDataUseCarefully() + { + return this->inodeDiskData.getInodeStatData(); + } + + void setNumHardlinksUnpersistent(unsigned numHardlinks) + { + /* note: this is the version for the quick on-close unlink check, so file is referenced by + * client sessions and we don't need to update persistent metadata + */ + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->inodeDiskData.setNumHardlinks(numHardlinks); + + safeLock.unlock(); + } + + unsigned getNumHardlinks() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned retVal = this->inodeDiskData.getNumHardlinks(); + + safeLock.unlock(); + + return retVal; + } + + /** + * Get the thread ID of the thread allow to work with this inode, so the ID, which has + * set the exclusiveTID value. + */ + pthread_t getExclusiveThreadID() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + pthread_t retVal = this->exclusiveTID; + + safeLock.unlock(); + + return retVal; + } + + void setExclusiveTID(pthread_t posixTID) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->exclusiveTID = posixTID; + + safeLock.unlock(); + } + + /** + * @return num read-sessions + num write-sessions + */ + unsigned getNumSessionsAll() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned num = this->numSessionsRead + this->numSessionsWrite; + + safeLock.unlock(); + + return num; + } + + unsigned getNumSessionsRead() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned num = this->numSessionsRead; + + safeLock.unlock(); + + return num; + } + + unsigned getNumSessionsWrite() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + unsigned num = this->numSessionsWrite; + + safeLock.unlock(); + + return num; + } + + /** + * Increase number of sessions for read or write (=> file open). + * + * @param accessFlags OPENFILE_ACCESS_... flags + */ + void incNumSessions(unsigned accessFlags) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + incNumSessionsUnlocked(accessFlags); + safeLock.unlock(); + } + + void incNumSessionsUnlocked(unsigned accessFlags) + { + if(accessFlags & OPENFILE_ACCESS_READ) + this->numSessionsRead++; + else + this->numSessionsWrite++; // (includes read+write) + } + + FileInode* clone() + { + Serializer ser; + serializeMetaData(ser); + + boost::scoped_array buf(new (std::nothrow) char[ser.size()]); + if (!buf) + return NULL; + + ser = Serializer(buf.get(), ser.size()); + serializeMetaData(ser); + + FileInode* clone = new FileInode(); + Deserializer des(buf.get(), ser.size()); + clone->deserializeMetaData(des); + + if (unlikely(!des.good()) ) + { + delete clone; + clone = NULL; + } + else + { + /* Ihe inode serializer might will not set origParentEntryID and origParentUID if the file + * was not moved or the inode was not de-inlined, but the cloned inode needs these + * values */ + clone->inodeDiskData.origParentEntryID = this->inodeDiskData.origParentEntryID; + clone->inodeDiskData.origParentUID = this->inodeDiskData.origParentUID; + } + + return clone; + } + + void incParentRef(const std::string& referenceParentID) + { + this->numParentRefs.increase(); + + SafeRWLock rwLock(&this->setGetReferenceParentLock, SafeRWLock_WRITE); + this->referenceParentID = referenceParentID; + rwLock.unlock(); + } + + void decParentRef() + { + this->numParentRefs.decrease(); + } + + ssize_t getNumParentRefs() + { + return this->numParentRefs.read(); + } + + std::string getReferenceParentID() + { + SafeRWLock rwLock(&this->setGetReferenceParentLock, SafeRWLock_READ); + + std::string id = this->referenceParentID; + + rwLock.unlock(); + + return id; + } + + /** + * Update inline information. + * + * @param value If set to false pathInfo flags and dentryCompat data will be updated as + * well. + */ + void setIsInlined(bool value) + { + SafeRWLock safeLock(&this->rwlock, SafeRWLock_WRITE); + + setIsInlinedUnlocked(value); + + safeLock.unlock(); + } + + /** + * See setIsInlined(). + */ + void setIsInlinedUnlocked(bool value) + { + this->isInlined = value; + + if (value == false) + { + if (this->inodeDiskData.getOrigFeature() == FileInodeOrigFeature_TRUE) + { + /* If the inode gets de-inlined origParentEntryID cannot be set from parentDir + * anymore. */ + addFeatureFlagUnlocked(FILEINODE_FEATURE_HAS_ORIG_PARENTID); + } + + // dentryCompatData also need the update + this->dentryCompatData.featureFlags &= ~(DENTRY_FEATURE_INODE_INLINE); + + + } + else + this->dentryCompatData.featureFlags |= DENTRY_FEATURE_INODE_INLINE; + } + + bool getIsInlined() + { + SafeRWLock rwLock(&this->rwlock, SafeRWLock_READ); + + bool retVal = this->isInlined; + + rwLock.unlock(); + + return retVal; + } + + void getPathInfo(PathInfo* outPathInfo) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + this->inodeDiskData.getPathInfo(outPathInfo); + + safeLock.unlock(); + } + + void setIsBuddyMirrored(bool mirrored = true) + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); + + this->inodeDiskData.setBuddyMirrorFeatureFlag(mirrored); + + safeLock.unlock(); + } + + bool getIsBuddyMirrored() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + + bool retVal = getIsBuddyMirroredUnlocked(); + + safeLock.unlock(); + + return retVal; + } + + bool getIsRstAvailable() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); + bool retVal = this->getIsRstAvailableUnlocked(); + safeLock.unlock(); + return retVal; + } + + /** + * Sets the file state using a raw state value and persists it to disk. + * Only updates file state if the access flags transition is allowed based + * on active read/write sessions. + * + * @param entryInfo EntryInfo of the file to update + * @param value Raw byte value representing the new file state + * @return FhgfsOpsErr_SUCCESS if state was successfully updated to disk + * FhgfsOpsErr_INUSE if file has active client sessions + * FhgfsOpsErr_INTERNAL if state update failed to persist to disk + * @note This method is called by the FileState-based overload below. + */ + FhgfsOpsErr setFileState(EntryInfo* entryInfo, uint8_t value) + { + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + FileState oldState(this->getFileStateUnlocked()); + FileState newState(value); + + // Prevent file state changes if file has active sessions + if (!checkAccessFlagTransition(oldState, newState)) + return FhgfsOpsErr_INUSE; + + this->inodeDiskData.setFileState(value); + if (this->storeUpdatedInodeUnlocked(entryInfo)) + return FhgfsOpsErr_SUCCESS; + else + return FhgfsOpsErr_INTERNAL; + } + + FhgfsOpsErr setFileState(EntryInfo* entryInfo, const FileState& state) + { + return setFileState(entryInfo, state.getRawValue()); + } + + /** + * Validates whether a file access flag transition is permitted + * based on the current active read/write sessions. + * Transition rules: + * - Acquiring stricter locks (adding flags): not allowed if conflicting sessions exist + * - Relaxing locks (removing flags): not allowed if dependent sessions exist + * (This protects HSM/special clients using bypass access privileges) + * - Full unlock (no flags): only allowed if there are no active sessions + * + * Special client handling using bypass access: + * When a file is already locked (e.g., READ_LOCK), only special clients with + * bypass permissions can have active read sessions. These sessions must finish + * before removing the lock to ensure consistency of HSM operations. + * + * @param oldState The current file state + * @param newState The requested new file state + * @return true if the transition is allowed, false otherwise + */ + bool checkAccessFlagTransition(const FileState& oldState, const FileState& newState) const + { + const uint8_t oldFlags = oldState.getAccessFlags(); + const uint8_t newFlags = newState.getAccessFlags(); + + // no change in access flags + // (e.g., READ_LOCK -> READ_LOCK) + if (oldFlags == newFlags) + return true; + + // --- Acquiring / Relaxing locks --- + const uint8_t addedFlags = newFlags & ~oldFlags; + const uint8_t removedFlags = oldFlags & ~newFlags; + + // --- Acquiring stricter locks --- + // Prevent upgrading to a stricter lock if there are conflicting sessions + if ((addedFlags & AccessFlags::READ_LOCK) && numSessionsRead > 0) + return false; + if ((addedFlags & AccessFlags::WRITE_LOCK) && numSessionsWrite > 0) + return false; + + // --- Relaxing locks --- + // Prevent loosening restrictions while dependent sessions are active + // These checks primarily protect special client operations using bypass access + if ((removedFlags & AccessFlags::READ_LOCK) && numSessionsRead > 0) + return false; + if ((removedFlags & AccessFlags::WRITE_LOCK) && numSessionsWrite > 0) + return false; + + return true; + } + + FileState getFileState() + { + RWLockGuard lock(rwlock, SafeRWLock_READ); + return FileState(this->getFileStateUnlocked()); + } + + bool incrementFileVersion(EntryInfo* entryInfo) + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + inodeDiskData.setFileVersion(inodeDiskData.getFileVersion() + 1); + return storeUpdatedInodeUnlocked(entryInfo, nullptr); + } + + uint32_t getFileVersion() + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + return inodeDiskData.getFileVersion(); + } + + bool incrementMetaVersion(EntryInfo* entryInfo) + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + inodeDiskData.setMetaVersion(inodeDiskData.getMetaVersion() + 1); + return storeUpdatedInodeUnlocked(entryInfo, nullptr); + } + + uint32_t getMetaVersion() + { + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + return inodeDiskData.getMetaVersion(); + } + + + + protected: + + void setOrigParentID(std::string origParentEntryId) + { + this->inodeDiskData.setDynamicOrigParentEntryID(origParentEntryId); + } + + void setPersistentOrigParentID(std::string origParentEntryId) + { + this->inodeDiskData.setPersistentOrigParentEntryID(origParentEntryId); + } + + private: + + /** + * Increase link counter by one + */ + void incDecNumHardlinksUnpersistentUnlocked(int value) + { + this->inodeDiskData.incDecNumHardlinks(value); + } + + FileInodeStoreData* getInodeDiskData() + { + return &this->inodeDiskData; + } + + void addFeatureFlagUnlocked(unsigned flag) + { + this->inodeDiskData.addInodeFeatureFlag(flag); + } + + /** + * Unlocked version of getIsBuddyMirrored(). + * Note: Caller must at least hold read lock. + */ + bool getIsBuddyMirroredUnlocked() + { + return this->inodeDiskData.getIsBuddyMirrored(); + } + + bool getIsRstAvailableUnlocked() + { + return this->inodeDiskData.getIsRstAvailable(); + } + + uint8_t getFileStateUnlocked() const + { + return this->inodeDiskData.getFileState(); + } +}; + diff --git a/meta/source/storage/FileInodeStoreData.cpp b/meta/source/storage/FileInodeStoreData.cpp new file mode 100644 index 0000000..e8b9103 --- /dev/null +++ b/meta/source/storage/FileInodeStoreData.cpp @@ -0,0 +1,45 @@ +#include "FileInodeStoreData.h" + +void FileInodeStoreData::getPathInfo(PathInfo* outPathInfo) +{ + const char* logContext = "FileInode getPathInfo"; + unsigned flags; + + FileInodeOrigFeature origFeature = getOrigFeature(); + switch (origFeature) + { + case FileInodeOrigFeature_TRUE: + { + flags = PATHINFO_FEATURE_ORIG; + } break; + + case FileInodeOrigFeature_FALSE: + { + flags = 0; + } break; + + default: + case FileInodeOrigFeature_UNSET: + { + flags = PATHINFO_FEATURE_ORIG_UNKNOWN; + LogContext(logContext).logErr("Bug: Unknown PathInfo status."); + } break; + + } + + unsigned origParentUID = getOrigUID(); + const std::string& origParentEntryID = getOrigParentEntryID(); + + outPathInfo->set(origParentUID, origParentEntryID, flags); +} + +bool FileInodeStoreData::operator==(const FileInodeStoreData& second) const +{ + return inodeFeatureFlags == second.inodeFeatureFlags + && inodeStatData == second.inodeStatData + && entryID == second.entryID + && stripePattern->stripePatternEquals(second.stripePattern) + && origFeature == second.origFeature + && origParentUID == second.origParentUID + && origParentEntryID == second.origParentEntryID; +} diff --git a/meta/source/storage/FileInodeStoreData.h b/meta/source/storage/FileInodeStoreData.h new file mode 100644 index 0000000..e4f9501 --- /dev/null +++ b/meta/source/storage/FileInodeStoreData.h @@ -0,0 +1,437 @@ +/* + * Data of a FileInode stored on disk. + */ + +#pragma once + +#include +#include +#include +#include + +/* Note: Don't forget to update DiskMetaData::getSupportedFileInodeFeatureFlags() if you add new + * flags here. */ + +#define FILEINODE_FEATURE_MIRRORED 1 // indicate mirrored inodes +#define FILEINODE_FEATURE_BUDDYMIRRORED 8 // indicate mirrored inodes + +// note: original parent-id and uid are required for the chunk-path calculation +#define FILEINODE_FEATURE_HAS_ORIG_PARENTID 16 // parent-id was updated +#define FILEINODE_FEATURE_HAS_ORIG_UID 32 // uid was updated +#define FILEINODE_FEATURE_HAS_STATFLAGS 64 // stat-data have their own flags +#define FILEINODE_FEATURE_HAS_VERSIONS 128 // file has a cto version counter +#define FILEINODE_FEATURE_HAS_RST 256 // file has remote targets +#define FILEINODE_FEATURE_HAS_STATE_FLAGS 512 // file has state flags (access state + data state) + +enum FileInodeOrigFeature +{ + FileInodeOrigFeature_UNSET = -1, + FileInodeOrigFeature_TRUE, + FileInodeOrigFeature_FALSE +}; + +// Access control flags (lower 5 bits) +// NOTE: The naming of access flags might seem counter-intuitive. +// +// - UNLOCKED means no access restrictions are in place +// - READ_LOCK means READ operations are BLOCKED, allowing only write operations. +// The file is effectively "write-only" +// - WRITE_LOCK means WRITE operations are BLOCKED, allowing only read operations. +// The file is effectively "read-only" +// - When both READ_LOCK and WRITE_LOCK are set, all access is blocked +namespace AccessFlags { + constexpr uint8_t UNLOCKED = 0x00; // No flags set (no restrictions) + constexpr uint8_t READ_LOCK = 0x01; // Bit 0 (1 << 0) - Block reads + constexpr uint8_t WRITE_LOCK = 0x02; // Bit 1 (1 << 1) - Block writes + constexpr uint8_t RESERVED3 = 0x04; // Bit 2 (1 << 2) - Reserved for future use + constexpr uint8_t RESERVED4 = 0x08; // Bit 3 (1 << 3) - Reserved for future use + constexpr uint8_t RESERVED5 = 0x10; // Bit 4 (1 << 4) - Reserved for future use +} + +// Represents data state (HSM application defined) (0-7) +using DataState = uint8_t; + +class FileState { + public: + static constexpr uint8_t ACCESS_FLAGS_MASK = 0x1F; // 0001 1111 (5 bits) + static constexpr uint8_t DATA_STATE_MASK = 0xE0; // 1110 0000 (3 bits) + static constexpr uint8_t DATA_STATE_SHIFT = 5; // Number of bits to shift + + // Constructor taking a raw byte value + explicit FileState(uint8_t value = 0) : raw(value) {} + + uint8_t getAccessFlags() const + { + return raw & ACCESS_FLAGS_MASK; + } + + DataState getDataState() const + { + return (raw & DATA_STATE_MASK) >> DATA_STATE_SHIFT; + } + + bool isReadLocked() const + { + return (raw & AccessFlags::READ_LOCK) != 0; + } + + bool isWriteLocked() const + { + return (raw & AccessFlags::WRITE_LOCK) != 0; + } + + bool isUnlocked() const + { + return (getAccessFlags() == 0); + } + + bool isFullyLocked() const + { + return (isReadLocked() && isWriteLocked()); + } + + uint8_t getRawValue() const { return raw; } + +private: + uint8_t raw; // Raw byte representing access flags + data state of a file +}; + +/* inode data inlined into a direntry, such as in DIRENTRY_STORAGE_FORMAT_VER3 */ +class FileInodeStoreData +{ + friend class FileInode; + friend class DirEntry; + friend class DirEntryStore; + friend class GenericDebugMsgEx; + friend class LookupIntentMsgEx; // just to avoid to copy two time statData + friend class RecreateDentriesMsgEx; + friend class RetrieveDirEntriesMsgEx; + friend class MetaStore; + friend class DiskMetaData; + + friend class AdjustChunkPermissionsMsgEx; + + friend class TestSerialization; // for testing + + public: + + FileInodeStoreData() + : inodeFeatureFlags(FILEINODE_FEATURE_HAS_VERSIONS), + stripePattern(NULL), + origFeature(FileInodeOrigFeature_UNSET), + fileVersion(0), + metaVersion(0), + rawFileState(0) + { } + + FileInodeStoreData(const std::string& entryID, StatData* statData, + StripePattern* stripePattern, unsigned featureFlags, unsigned origParentUID, + const std::string& origParentEntryID, FileInodeOrigFeature origFeature) + : inodeFeatureFlags(featureFlags), + inodeStatData(*statData), + entryID(entryID), + origFeature(origFeature), + origParentUID(origParentUID), + origParentEntryID(origParentEntryID), + fileVersion(0), + metaVersion(0), rawFileState(0) + { + this->stripePattern = stripePattern->clone(); + + if ((statData->getUserID() != origParentUID) && + (origFeature == FileInodeOrigFeature_TRUE) ) + this->inodeFeatureFlags |= FILEINODE_FEATURE_HAS_ORIG_UID; + } + + bool operator==(const FileInodeStoreData& second) const; + + bool operator!=(const FileInodeStoreData& other) const { return !(*this == other); } + + /** + * Used to set the values from those read from disk + */ + FileInodeStoreData(std::string entryID, FileInodeStoreData* diskData) : + entryID(entryID) + { + // this->entryID = entryID; // set in initializer + this->stripePattern = NULL; + + setFileInodeStoreData(diskData); + } + + ~FileInodeStoreData() + { + SAFE_DELETE_NOSET(this->stripePattern); + } + + private: + uint32_t inodeFeatureFlags; // feature flags for the inode itself, e.g. for mirroring + + StatData inodeStatData; + std::string entryID; // filesystem-wide unique string + StripePattern* stripePattern; + + FileInodeOrigFeature origFeature; // indirectly determined via dentry-version + uint32_t origParentUID; + std::string origParentEntryID; + + // version number for CTO cache consistency optimizations + uint32_t fileVersion; + uint32_t metaVersion; + + // raw byte value representing access flags + data state + uint8_t rawFileState; + + void getPathInfo(PathInfo* outPathInfo); + + + public: + StatData* getInodeStatData() + { + return &this->inodeStatData; + } + + std::string getEntryID() + { + return this->entryID; + } + + StripePattern* getStripePattern() + { + return this->stripePattern; + } + + uint32_t getFileVersion() const { return fileVersion; } + uint32_t getMetaVersion() const { return metaVersion; } + + void setFileVersion(uint32_t fileVersion) + { + this->fileVersion = fileVersion; + } + + void setMetaVersion(uint32_t metaVersion) + { + this->metaVersion = metaVersion; + setMetaVersionStat(metaVersion); //update metadata version in StatData + } + + protected: + + /** + * Set all fileInodeStoreData + * + * Note: Might update existing values and if these are allocated, such as stripePattern, + * these need to be freed first. + */ + void setFileInodeStoreData(FileInodeStoreData* diskData) + { + SAFE_DELETE_NOSET(this->stripePattern); + this->stripePattern = diskData->getStripePattern()->clone(); + + this->inodeStatData = *(diskData->getInodeStatData() ); + this->inodeFeatureFlags = diskData->getInodeFeatureFlags(); + this->origFeature = diskData->origFeature; + this->origParentUID = diskData->origParentUID; + this->origParentEntryID = diskData->origParentEntryID; + this->fileVersion = diskData->fileVersion; + setMetaVersion(diskData->metaVersion); + this->rawFileState = diskData->rawFileState; + } + + void setInodeFeatureFlags(unsigned flags) + { + this->inodeFeatureFlags = flags; + } + + void addInodeFeatureFlag(unsigned flag) + { + this->inodeFeatureFlags |= flag; + } + + void removeInodeFeatureFlag(unsigned flag) + { + this->inodeFeatureFlags &= ~flag; + } + + void setBuddyMirrorFeatureFlag(bool mirrored) + { + if (mirrored) + addInodeFeatureFlag(FILEINODE_FEATURE_BUDDYMIRRORED); + else + removeInodeFeatureFlag(FILEINODE_FEATURE_BUDDYMIRRORED); + } + + bool getIsBuddyMirrored() const + { + return (getInodeFeatureFlags() & FILEINODE_FEATURE_BUDDYMIRRORED); + } + + void setIsRstAvailable(bool available) + { + if (available) + addInodeFeatureFlag(FILEINODE_FEATURE_HAS_RST); + else + removeInodeFeatureFlag(FILEINODE_FEATURE_HAS_RST); + } + + bool getIsRstAvailable() const + { + return (getInodeFeatureFlags() & FILEINODE_FEATURE_HAS_RST); + } + + void setFileState(uint8_t value) + { + this->rawFileState = value; + if (this->rawFileState == 0) + removeInodeFeatureFlag(FILEINODE_FEATURE_HAS_STATE_FLAGS); + else + addInodeFeatureFlag(FILEINODE_FEATURE_HAS_STATE_FLAGS); + } + + uint8_t getFileState() const + { + // If FILEINODE_FEATURE_HAS_STATE_FLAGS is not set, + // return the default "unlocked + zero data state" + if (!(inodeFeatureFlags & FILEINODE_FEATURE_HAS_STATE_FLAGS)) + { + constexpr uint8_t defaultAccessFlags = AccessFlags::UNLOCKED; + constexpr uint8_t defaultDataState = 0; + + // Format: [dataState (upper 3 bits) | accessFlags (lower 5 bits)] + return (defaultAccessFlags & FileState::ACCESS_FLAGS_MASK) | + ((defaultDataState << FileState::DATA_STATE_SHIFT) & FileState::DATA_STATE_MASK); + } + + // Return the explicitly set state if feature is supported + return rawFileState; + } + + void setInodeStatData(StatData& statData) + { + this->inodeStatData = statData; + } + + void setEntryID(const std::string& entryID) + { + this->entryID = entryID; + } + + void setPattern(StripePattern* pattern) + { + this->stripePattern = pattern; + } + + void setOrigUID(unsigned origParentUID) + { + this->origParentUID = origParentUID; + } + + /** + * Set the origParentEntryID based on the parentDir. Feature flag will not be updated. + * This is for inodes which are not de-inlined and not renamed between dirs. + */ + void setDynamicOrigParentEntryID(const std::string& origParentEntryID) + { + /* Never overwrite existing data! Callers do not check if they need to set it only we do + * that here. */ + if (!(this->inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_PARENTID) ) + this->origParentEntryID = origParentEntryID; + } + + /** + * Set the origParentEntryID from disk, no feature flag test and + * feature flag will not be updated. + * Note: Use this for disk-deserialization. + */ + void setDiskOrigParentEntryID(const std::string& origParentEntryID) + { + this->origParentEntryID = origParentEntryID; + } + + /** + * Set the origParentEntryID. Feature flag will be updated, origInformation will be stored + * to disk. + * Note: Use this on file renames between dirs and de-inlining. + */ + void setPersistentOrigParentEntryID(const std::string& origParentEntryID) + { + /* Never overwrite existing data! Callers do not check if they need to set it only we do + * that here. */ + if ( (!(this->inodeFeatureFlags & FILEINODE_FEATURE_HAS_ORIG_PARENTID) ) && + (this->getOrigFeature() == FileInodeOrigFeature_TRUE) ) + { + this->origParentEntryID = origParentEntryID; + addInodeFeatureFlag(FILEINODE_FEATURE_HAS_ORIG_PARENTID); + } + } + + uint32_t getInodeFeatureFlags() const + { + return this->inodeFeatureFlags; + } + + StripePattern* getPattern() + { + return this->stripePattern; + } + + void setOrigFeature(FileInodeOrigFeature value) + { + this->origFeature = value; + } + + FileInodeOrigFeature getOrigFeature() const + { + return this->origFeature; + } + + uint32_t getOrigUID() const + { + return this->origParentUID; + } + + const std::string& getOrigParentEntryID() const + { + return this->origParentEntryID; + } + + void incDecNumHardlinks(int value) + { + this->inodeStatData.incDecNumHardLinks(value); + } + + void setNumHardlinks(unsigned numHardlinks) + { + this->inodeStatData.setNumHardLinks(numHardlinks); + } + + void setMetaVersionStat(unsigned metaVersion) + { + this->inodeStatData.setMetaVersionStat(metaVersion); + } + + unsigned getNumHardlinks() const + { + return this->inodeStatData.getNumHardlinks(); + } + + /** + * Return the pattern and set the internal pattern to NULL to make sure it does not get + * deleted on object destruction. + */ + StripePattern* getStripePatternAndSetToNull() + { + StripePattern* pattern = this->stripePattern; + this->stripePattern = NULL; + + return pattern; + } + + void setAttribChangeTimeSecs(int64_t attribChangeTimeSecs) + { + this->inodeStatData.setAttribChangeTimeSecs(attribChangeTimeSecs); + } + +}; + + diff --git a/meta/source/storage/GlobalInodeLockStore.cpp b/meta/source/storage/GlobalInodeLockStore.cpp new file mode 100644 index 0000000..5ab4d7f --- /dev/null +++ b/meta/source/storage/GlobalInodeLockStore.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include "GlobalInodeLockStore.h" + + +/** + * Note: remember to call releaseFileInode() + * + * @return false of file is already in store or inode creation failed, true if file was inserted + */ +bool GlobalInodeLockStore::insertFileInode(EntryInfo* entryInfo) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + std::string entryID = entryInfo->getEntryID(); + GlobalInodeLockMapIter iter = this->inodes.find(entryID); + + if(iter == this->inodes.end()) + { // not in map yet => try to insert it in map + LOG_DBG(GENERAL, SPAM, "Insert file inode in GlobalInodeLockStore.", ("FileInodeID", iter->first)); + FileInode* inode = FileInode::createFromEntryInfo(entryInfo); + if(!inode) + return false; + this->inodes.insert(GlobalInodeLockMap::value_type(entryID, inode)); + this->inodeTimes.insert(GlobalInodeTimestepMap::value_type(entryID, 0.0)); + return true; + } + return false; +} + +/** + * @return false if file was not in file or time store, true if we found the file in the file and time store + */ +bool GlobalInodeLockStore::releaseFileInode(const std::string& entryID) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + GlobalInodeLockMapIter iter = this->inodes.find(entryID); + + if(iter != this->inodes.end() ) + { // inode is in the map, release it + LOG_DBG(GENERAL, SPAM, "Release file inode in GlobalInodeLockStore.", ("FileInodeID", iter->first)); + delete(iter->second); + this->inodes.erase(iter); + return this->releaseInodeTime(entryID); + } + return false; +} + +/** + * Note: SafeRWLock_WRITE lock should be held here + * + * @return false if file was not in time store at all, true if we found the file in the time store + */ +bool GlobalInodeLockStore::releaseInodeTime(const std::string& entryID) +{ + GlobalInodeTimestepMap::iterator iterTime = this->inodeTimes.find(entryID); + if(iterTime != this->inodeTimes.end() ) + { + this->inodeTimes.erase(iterTime); + return true; + } + return false; +} + +/** + * @return true if file is in the store, false if it is not + */ +bool GlobalInodeLockStore::lookupFileInode(EntryInfo* entryInfo) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + auto iter = this->inodes.find(entryInfo->getEntryID()); + return (iter != this->inodes.end()); +} + +FileInode* GlobalInodeLockStore::getFileInode(EntryInfo* entryInfo) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + std::string entryID = entryInfo->getEntryID(); + GlobalInodeLockMapIter iter = this->inodes.find(entryID); + + if(iter != this->inodes.end() ) + { + FileInode* inode = iter->second; + return inode; + } + return nullptr; +} + +void GlobalInodeLockStore::clearLockStore() +{ + LOG_DBG(GENERAL, DEBUG, "GlobalInodeLockStore::clearLockStore", + ("# of loaded entries to be cleared", inodes.size())); + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + for(GlobalInodeLockMapIter iter = this->inodes.begin(); iter != this->inodes.end();) + { + delete(iter->second); + iter = this->inodes.erase(iter); // return next iterator after erase and assign to `iter` variable + } + //clear Timestep store + this->clearTimeStoreUnlocked(); +} + +void GlobalInodeLockStore::clearTimeStoreUnlocked() +{ + for(GlobalInodeTimestepMap::iterator iter = this->inodeTimes.begin(); iter != this->inodeTimes.end();) + { + iter = this->inodeTimes.erase(iter); // return next iterator after erase and assign to `iter` variable + } +} + + diff --git a/meta/source/storage/GlobalInodeLockStore.h b/meta/source/storage/GlobalInodeLockStore.h new file mode 100644 index 0000000..8b553b8 --- /dev/null +++ b/meta/source/storage/GlobalInodeLockStore.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +typedef std::map GlobalInodeLockMap; +typedef GlobalInodeLockMap::iterator GlobalInodeLockMapIter; + +typedef std::map GlobalInodeTimestepMap; + +/** + * Global store for file inodes which are locked for referencing. + * Used for chunk balancing operations. + * This object initalizes the GlobalInodeLockMap + * It is used for taking and releasing locks on the inodes of file chunks. + * * "locking" here means we successfully insert an element into a map. + */ +class GlobalInodeLockStore +{ + friend class InodeFileStore; + friend class MetaStore; + + public: + ~GlobalInodeLockStore() + { + this->clearLockStore(); + } + + bool insertFileInode(EntryInfo* entryInfo); + bool releaseFileInode(const std::string& entryID); + bool lookupFileInode(EntryInfo* entryInfo); + private: + GlobalInodeLockMap inodes; + GlobalInodeTimestepMap inodeTimes; + FileInode* getFileInode(EntryInfo* entryInfo); + bool releaseInodeTime(const std::string& entryID); + + RWLock rwlock; + void clearLockStore(); + void clearTimeStoreUnlocked(); +}; + + diff --git a/meta/source/storage/IncompleteInode.cpp b/meta/source/storage/IncompleteInode.cpp new file mode 100644 index 0000000..0947703 --- /dev/null +++ b/meta/source/storage/IncompleteInode.cpp @@ -0,0 +1,117 @@ +#include "IncompleteInode.h" + +#include +#include + +#include +#include +#include + +IncompleteInode::~IncompleteInode() +{ + if (fd >= 0 && close(fd) < 0) + LOG(GENERAL, ERR, "Failed to close file", fd, sysErr); +} + +FhgfsOpsErr IncompleteInode::setXattr(const char* name, const void* value, size_t size) +{ + int setRes = ::fsetxattr(fd, name, value, size, 0); + if (setRes != 0) + return FhgfsOpsErrTk::fromSysErr(errno); + + xattrsSet.insert(name); + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr IncompleteInode::setContent(const char* attrName, const void* value, size_t size) +{ + if (Program::getApp()->getConfig()->getStoreUseExtendedAttribs()) + return setXattr(attrName, value, size); + + if (strcmp(attrName, META_XATTR_NAME) != 0) + { + LOG(GENERAL, ERR, "Setting attribute data as file contents is not supported.", attrName); + return FhgfsOpsErr_INVAL; + } + + if (hasContent) + { + int truncRes = ::ftruncate(fd, size); + if (truncRes) + return FhgfsOpsErrTk::fromSysErr(errno); + } + + ssize_t writeRes = ::write(fd, value, size); + if (writeRes < 0 || size_t(writeRes) < size) + return FhgfsOpsErrTk::fromSysErr(errno); + + hasContent = true; + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr IncompleteInode::clearUnsetXAttrs() +{ + std::set xattrsOnInode; + char xattrNameBuf[XATTR_LIST_MAX]; + + const ssize_t listRes = ::flistxattr(fd, xattrNameBuf, sizeof(xattrNameBuf)); + if (listRes < 0) + { + LOG(GENERAL, ERR, "could not list xattrs", fileName(), sysErr); + return FhgfsOpsErr_INTERNAL; + } + + for (ssize_t offset = 0; offset < listRes; ) + { + std::string currentName = xattrNameBuf + offset; + + offset += currentName.size() + 1; + xattrsOnInode.insert(std::move(currentName)); + } + + for (auto it = xattrsOnInode.begin(); it != xattrsOnInode.end(); it++) + { + if (xattrsSet.count(*it)) + continue; + // do not sync the system namespaces. + // * security. cannot be synced at all, since userspace can't write to it + // * trusted. is unused by us and thus not interesting + // * system. contains only acls, which we don't use to represent metadata + if (it->compare(0, 5, "user.") != 0) + continue; + + int removeRes = ::fremovexattr(fd, it->c_str()); + if (removeRes != 0) + { + LOG(GENERAL, ERR, "could not remove superfluous xattr", + fileName(), ("name", *it), sysErr); + return FhgfsOpsErr_INTERNAL; + } + } + + return FhgfsOpsErr_SUCCESS; +} + +std::string IncompleteInode::fileName() const +{ + static const char* FD_FORMAT = "/proc/self/fd/%i"; + + // reserve enough space for the entire format string, the trailing \0, and the fd value + // in octal. in decimal, that will be more than enough. + char buffer[strlen(FD_FORMAT) + 3 * sizeof(fd) + 1]; + ::sprintf(buffer, FD_FORMAT, fd); + + std::string result; + result.resize(PATH_MAX + 1); + + ssize_t readRes = ::readlink(buffer, &result[0], PATH_MAX); + if (readRes < 0) + { + LOG(GENERAL, ERR, "Failed to resolve file for fd", fd, sysErr); + return ""; + } + + result.resize(readRes); + return result; +} diff --git a/meta/source/storage/IncompleteInode.h b/meta/source/storage/IncompleteInode.h new file mode 100644 index 0000000..af24378 --- /dev/null +++ b/meta/source/storage/IncompleteInode.h @@ -0,0 +1,54 @@ +#pragma once + +#include + +class IncompleteInode +{ + public: + IncompleteInode() : fd(-1), hasContent(false) {} + + explicit IncompleteInode(int fd) : fd(fd), hasContent(false) {} + + ~IncompleteInode(); + + IncompleteInode(const IncompleteInode&) = delete; + IncompleteInode& operator=(const IncompleteInode&) = delete; + + IncompleteInode(IncompleteInode&& other) + : fd(-1), hasContent(false) + { + swap(other); + } + + IncompleteInode& operator=(IncompleteInode&& other) + { + IncompleteInode(std::move(other)).swap(*this); + return *this; + } + + void swap(IncompleteInode& other) + { + std::swap(fd, other.fd); + std::swap(hasContent, other.hasContent); + std::swap(xattrsSet, other.xattrsSet); + } + + friend void swap(IncompleteInode& a, IncompleteInode& b) + { + a.swap(b); + } + + FhgfsOpsErr setXattr(const char* name, const void* value, size_t size); + + FhgfsOpsErr setContent(const char* name, const void* value, size_t size); + + FhgfsOpsErr clearUnsetXAttrs(); + + private: + int fd; + bool hasContent; + std::set xattrsSet; + + std::string fileName() const; +}; + diff --git a/meta/source/storage/InodeDirStore.cpp b/meta/source/storage/InodeDirStore.cpp new file mode 100644 index 0000000..401662e --- /dev/null +++ b/meta/source/storage/InodeDirStore.cpp @@ -0,0 +1,498 @@ +#include +#include +#include +#include +#include "DirInode.h" +#include "InodeDirStore.h" + + +#define DIRSTORE_REFCACHE_REMOVE_SKIP_SYNC (4) /* n-th elements to be removed on sync sweep */ +#define DIRSTORE_REFCACHE_REMOVE_SKIP_ASYNC (3) /* n-th elements to be removed on async sweep */ + +/** + * not inlined as we need to include + */ +InodeDirStore::InodeDirStore() +{ + Config* cfg = Program::getApp()->getConfig(); + + this->refCacheSyncLimit = cfg->getTuneDirMetadataCacheLimit(); + this->refCacheAsyncLimit = refCacheSyncLimit - (refCacheSyncLimit/2); +} + +bool InodeDirStore::dirInodeInStoreUnlocked(const std::string& dirID) +{ + DirectoryMapIter iter = this->dirs.find(dirID); + return iter != this->dirs.end(); +} + + +/** + * Note: remember to call releaseDir() + * + * @param forceLoad Force to load the DirInode from disk. Defaults to false. + * @return NULL if no such dir exists (or if the dir is being moved), but cannot be NULL if + * forceLoad is false + */ +DirInode* InodeDirStore::referenceDirInode(const std::string& dirID, bool isBuddyMirrored, + bool forceLoad) +{ + DirInode* dir = NULL; + bool wasReferenced = true; /* Only try to add to cache if not in memory yet. + * Any attempt to add it to the cache causes a cache sweep, which is + * rather expensive. + * Note: when set to false we also need a write-lock! */ + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + DirectoryMapIter iter; + + iter = dirs.find(dirID); + if (iter == dirs.end()) + { + lock.unlock(); + lock.lock(SafeRWLock_WRITE); + iter = dirs.find(dirID); + } + + if(iter == this->dirs.end() ) + { // Not in map yet => try to load it. We must be write-locked here! + iter = insertDirInodeUnlocked(dirID, isBuddyMirrored, forceLoad); + wasReferenced = false; + } + + if(iter != this->dirs.end() ) + { // exists in map + DirectoryReferencer* dirRefer = iter->second; + DirInode* dirNonRef = dirRefer->getReferencedObject(); + + if(!dirNonRef->getExclusive() ) // check moving + { + dir = dirRefer->reference(); + LOG_DBG(GENERAL, SPAM, "referenceDirInode", dir->getID(), dirRefer->getRefCount()); + + if (!wasReferenced) + cacheAddUnlocked(dirID, dirRefer); + } + + // no "else". + // note: we don't need to check unload here, because exclusive means there already is a + // reference so we definitely didn't load here + } + + lock.unlock(); + + /* Only try to load the DirInode after giving up the lock. DirInodes are usually referenced + * without being loaded at all from the kernel client, so we can afford the extra lock if loading + * the DirInode fails. */ + if (forceLoad && dir && (!dir->loadIfNotLoaded()) ) + { // loading from disk failed, release the dir again + releaseDir(dirID); + dir = NULL; + } + + return dir; +} + +/** + * Release reduce the refcounter of an DirInode here + */ +void InodeDirStore::releaseDir(const std::string& dirID) +{ + RWLockGuard lock(this->rwlock, SafeRWLock_WRITE); + releaseDirUnlocked(dirID); +} + +void InodeDirStore::releaseDirUnlocked(const std::string& dirID) +{ + App* app = Program::getApp(); + + DirectoryMapIter iter = this->dirs.find(dirID); + if(likely(iter != this->dirs.end() ) ) + { // dir exists => decrease refCount + DirectoryReferencer* dirRefer = iter->second; + + if (likely(dirRefer->getRefCount() ) ) + { + dirRefer->release(); + DirInode* dirNonRef = dirRefer->getReferencedObject(); + + LOG_DBG(GENERAL, SPAM, "releaseDirInode", dirID, dirRefer->getRefCount()); + + if(!dirRefer->getRefCount() ) + { // dropped last reference => unload dir + + if (unlikely( dirNonRef->fileStore.getSize() && !app->getSelfTerminate() ) ) + { + LOG(GENERAL, ERR, "Bug: Refusing to release the directory, " + "its fileStore still has references!", dirID); + + dirRefer->reference(); + } + else + { // as expected, fileStore is empty + delete(dirRefer); + this->dirs.erase(iter); + } + } + } + else + { // attempt to release a Dir without a refCount + LOG(GENERAL, ERR, "Bug: Refusing to release dir with a zero refCount", dirID); + this->dirs.erase(iter); + } + } + else + { + LOG(GENERAL, ERR, "Bug: releaseDir requested, but dir not referenced!", dirID); + LogContext(__func__).logBacktrace(); + } +} + +FhgfsOpsErr InodeDirStore::removeDirInode(const std::string& dirID, bool isBuddyMirrored) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + cacheRemoveUnlocked(dirID); /* we should move this after isRemovable()-check as soon as we can + remove referenced dirs */ + + FhgfsOpsErr removableRes = isRemovableUnlocked(dirID, isBuddyMirrored); + if(removableRes != FhgfsOpsErr_SUCCESS) + return removableRes; + + bool persistenceOK = DirInode::unlinkStoredInode(dirID, isBuddyMirrored); + if(!persistenceOK) + return FhgfsOpsErr_INTERNAL; + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Check whether the dir is removable (i.e. not in use). + * + * Note: Caller must make sure that there is no cache reference that would lead to a false in-use + * result. + * + * @param outMirrorNodeID if this returns success and the dir is mirrored, this param will be set to + * the mirror of this dir, i.e. !=0; (note: mirroring has actually nothing to do with removable + * check, but we have it here because this method already loads the dir inode, so that we can avoid + * another inode load in removeDirInodeUnlocked() for mirror checking.) + */ +FhgfsOpsErr InodeDirStore::isRemovableUnlocked(const std::string& dirID, bool isBuddyMirrored) +{ + const char* logContext = "InodeDirStore check if dir is removable"; + DirectoryMapCIter iter = dirs.find(dirID); + + if(iter != dirs.end() ) + { // dir currently loaded, refuse to let it rmdir'ed + DirectoryReferencer* dirRefer = iter->second; + DirInode* dir = dirRefer->getReferencedObject(); + + if (unlikely(!dirRefer->getRefCount() ) ) + { + LogContext(logContext).logErr("Bug: unreferenced dir found! EntryID: " + dirID); + + return FhgfsOpsErr_INTERNAL; + } + + dir->loadIfNotLoaded(); + + if(dir->getNumSubEntries() ) + return FhgfsOpsErr_NOTEMPTY; + + if(dir->getExclusive() ) // Someone else is already trying to delete it. + return FhgfsOpsErr_INUSE; + + // Dir is still referenced (e.g. because someone is just trying to create a file in it.) + return FhgfsOpsErr_INUSE; + } + else + { // not loaded => static checking + DirInode dirInode(dirID, isBuddyMirrored); + + if (!dirInode.loadFromFile() ) + return FhgfsOpsErr_PATHNOTEXISTS; + + if(dirInode.getNumSubEntries() ) + return FhgfsOpsErr_NOTEMPTY; + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Note: This does not load any entries, so it will only return the number of already loaded + * entries. (Only useful for debugging and statistics probably.) + */ +size_t InodeDirStore::getSize() +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + return dirs.size(); +} + +/** + * @param outParentNodeID may be NULL + * @param outParentEntryID may be NULL (if outParentNodeID is NULL) + */ +FhgfsOpsErr InodeDirStore::stat(const std::string& dirID, bool isBuddyMirrored, + StatData& outStatData, NumNodeID* outParentNodeID, std::string* outParentEntryID) +{ + App* app = Program::getApp(); + + FhgfsOpsErr statRes = FhgfsOpsErr_PATHNOTEXISTS; + + NumNodeID expectedOwnerID = isBuddyMirrored + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) + : app->getLocalNode().getNumID(); + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + DirectoryMapIter iter = dirs.find(dirID); + if(iter != dirs.end() ) + { // dir loaded + DirectoryReferencer* dirRefer = iter->second; + DirInode* dir = dirRefer->getReferencedObject(); + + bool loadRes = dir->loadIfNotLoaded(); // might not be loaded at all... + if (!loadRes) + { + return statRes; /* Loading from disk failed, the dir is only referenced, but does not exist + * Might be due to our dir-reference optimization or a corruption problem */ + } + + if(expectedOwnerID != dir->getOwnerNodeID() ) + { + /* check for owner node ID is especially important for root dir + * NOTE: this check is only performed, if directory is already loaded, because all MDSs + * need to be able to reference the root directory even if owner is not set (it is not set + * on first startup). */ + LOG(GENERAL, WARNING, "Owner verification failed.", dirID); + statRes = FhgfsOpsErr_NOTOWNER; + } + else + { + statRes = dir->getStatData(outStatData, outParentNodeID, outParentEntryID); + } + + lock.unlock(); + } + else + { + lock.unlock(); /* Unlock first, no need to hold a lock to just read the file from disk. + * If xattr or data are not writte yet we just raced and return + * FhgfsOpsErr_PATHNOTEXISTS */ + + // read data on disk + statRes = DirInode::getStatData(dirID, isBuddyMirrored, outStatData, outParentNodeID, + outParentEntryID); + } + + return statRes; +} + +/** + * @param validAttribs SETATTR_CHANGE_...-Flags + */ +FhgfsOpsErr InodeDirStore::setAttr(const std::string& dirID, bool isBuddyMirrored, int validAttribs, + SettableFileAttribs* attribs) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + DirectoryMapIter iter = dirs.find(dirID); + if(iter == dirs.end() ) + { // not loaded => load, apply, destroy + DirInode dir(dirID, isBuddyMirrored); + + if(dir.loadFromFile() ) + { // loaded + bool setRes = dir.setAttrData(validAttribs, attribs); + + return setRes ? FhgfsOpsErr_SUCCESS : FhgfsOpsErr_INTERNAL; + } + } + else + { // dir loaded + DirectoryReferencer* dirRefer = iter->second; + DirInode* dir = dirRefer->getReferencedObject(); + + if(!dir->getExclusive() ) + { + bool setRes = dir->setAttrData(validAttribs, attribs); + + return setRes ? FhgfsOpsErr_SUCCESS : FhgfsOpsErr_INTERNAL; + } + } + + return FhgfsOpsErr_PATHNOTEXISTS; +} + +void InodeDirStore::invalidateMirroredDirInodes() +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + for (auto it = dirs.begin(); it != dirs.end(); ++it) + { + DirInode& dir = *it->second->getReferencedObject(); + + if (dir.getIsBuddyMirrored()) + dir.invalidate(); + } +} + +/** + * Creates and empty DirInode and inserts it into the map. + * + * Note: We only need to hold a read-lock here, as we check if inserting an entry into the map + * succeeded. + */ +DirectoryMapIter InodeDirStore::insertDirInodeUnlocked(const std::string& dirID, + bool isBuddyMirrored, bool forceLoad) +{ + std::unique_ptr inode(new (std::nothrow) DirInode(dirID, isBuddyMirrored)); + if (unlikely (!inode) ) + return dirs.end(); // out of memory + + if (forceLoad) + { // load from disk requested + if (!inode->loadIfNotLoaded() ) + return dirs.end(); + } + + std::pair pairRes = + this->dirs.insert(DirectoryMapVal(dirID, new DirectoryReferencer(inode.release()))); + + return pairRes.first; +} + +void InodeDirStore::clearStoreUnlocked() +{ + LOG_DBG(GENERAL, DEBUG, "clearStoreUnlocked", dirs.size()); + + cacheRemoveAllUnlocked(); + + for(DirectoryMapIter iter = dirs.begin(); iter != dirs.end(); iter++) + { + DirectoryReferencer* dirRef = iter->second; + + // will also call destructor for dirInode and sub-objects as dirInode->fileStore + delete(dirRef); + } + + dirs.clear(); +} + +/** + * Note: Make sure to call this only after the new reference has been taken by the caller + * (otherwise it might happen that the new element is deleted during sweep if it was cached + * before and appears to be unneeded now). + */ +void InodeDirStore::cacheAddUnlocked(const std::string& dirID, DirectoryReferencer* dirRefer) +{ + Config* cfg = Program::getApp()->getConfig(); + + unsigned cacheLimit = cfg->getTuneDirMetadataCacheLimit(); + + if (unlikely(cacheLimit == 0) ) + return; // cache disabled by user config + + // (we do cache sweeping before insertion to make sure we don't sweep the new entry) + cacheSweepUnlocked(true); + + if(refCache.insert(DirCacheMapVal(dirID, dirRefer->getReferencedObject()) ).second) + { // new insert => inc refcount + dirRefer->reference(); + + LOG_DBG(GENERAL, SPAM, "InodeDirStore cache add DirInode.", dirID, dirRefer->getRefCount()); + } +} + +void InodeDirStore::cacheRemoveUnlocked(const std::string& dirID) +{ + DirCacheMapIter iter = refCache.find(dirID); + if(iter == refCache.end() ) + return; + + releaseDirUnlocked(dirID); + refCache.erase(iter); +} + +void InodeDirStore::cacheRemoveAllUnlocked() +{ + for(DirCacheMapIter iter = refCache.begin(); iter != refCache.end(); /* iter inc inside loop */) + { + releaseDirUnlocked(iter->first); + refCache.erase(iter++); + } +} + +/** + * @param isSyncSweep true if this is a synchronous sweep (e.g. we need to free a few elements to + * allow quick insertion of a new element), false is this is an asynchronous sweep (that might take + * a bit longer). + * @return true if a cache flush was triggered, false otherwise + */ +bool InodeDirStore::cacheSweepUnlocked(bool isSyncSweep) +{ + // sweeping means we remove every n-th element from the cache, starting with a random element + // in the range 0 to n + size_t cacheLimit; + size_t removeSkipNum; + + // check type of sweep and set removal parameters accordingly + + if(isSyncSweep) + { // sync sweep settings + cacheLimit = refCacheSyncLimit; + removeSkipNum = DIRSTORE_REFCACHE_REMOVE_SKIP_SYNC; + } + else + { // async sweep settings + cacheLimit = refCacheAsyncLimit; + removeSkipNum = DIRSTORE_REFCACHE_REMOVE_SKIP_ASYNC; + } + + if(refCache.size() <= cacheLimit) + return false; + + + // pick a random start element (note: the element will be removed in first loop pass below) + + unsigned randStart = randGen.getNextInRange(0, removeSkipNum - 1); + DirCacheMapIter iter = refCache.begin(); + + while(randStart--) + iter++; + + // walk over all cached elements and remove every n-th element + DirCacheMapSizeT i = 0; // counts elements + while (iter != refCache.end() ) + { + if ( (++i % removeSkipNum) == 0) + { + releaseDirUnlocked(iter->first); + refCache.erase(iter++); + } + else + iter++; + } + + return true; +} + +/** + * @return true if a cache flush was triggered, false otherwise + */ +bool InodeDirStore::cacheSweepAsync() +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + return cacheSweepUnlocked(false); +} + +/** + * @return current number of cached entries + */ +size_t InodeDirStore::getCacheSize() +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + return refCache.size(); +} diff --git a/meta/source/storage/InodeDirStore.h b/meta/source/storage/InodeDirStore.h new file mode 100644 index 0000000..4571f1f --- /dev/null +++ b/meta/source/storage/InodeDirStore.h @@ -0,0 +1,94 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +class DirInode; + +typedef AtomicObjectReferencer DirectoryReferencer; +typedef std::map DirectoryMap; +typedef DirectoryMap::iterator DirectoryMapIter; +typedef DirectoryMap::const_iterator DirectoryMapCIter; +typedef DirectoryMap::value_type DirectoryMapVal; + +typedef std::map DirCacheMap; // keys are dirIDs (same as DirMap) +typedef DirCacheMap::iterator DirCacheMapIter; +typedef DirCacheMap::const_iterator DirCacheMapCIter; +typedef DirCacheMap::value_type DirCacheMapVal; +typedef DirCacheMap::size_type DirCacheMapSizeT; + +/** + * Layer in between our inodes and the data on the underlying file system. So we read/write from/to + * underlying files and this class is to do this corresponding data access. + * This object is used for for _directories_ only. + */ +class InodeDirStore +{ + friend class DirInode; + friend class MetaStore; + + public: + InodeDirStore(); + + ~InodeDirStore() + { + this->clearStoreUnlocked(); + }; + + InodeDirStore(const InodeDirStore&) = delete; + InodeDirStore(InodeDirStore&&) = delete; + + InodeDirStore& operator=(const InodeDirStore&) = delete; + InodeDirStore& operator=(InodeDirStore&&) = delete; + + bool dirInodeInStoreUnlocked(const std::string& dirID); + DirInode* referenceDirInode(const std::string& dirID, bool isBuddyMirrored, bool forceLoad); + void releaseDir(const std::string& dirID); + FhgfsOpsErr removeDirInode(const std::string& dirID, bool isBuddyMirrored); + + size_t getSize(); + size_t getCacheSize(); + + FhgfsOpsErr stat(const std::string& dirID, bool isBuddyMirrored, StatData& outStatData, + NumNodeID* outParentNodeID, std::string* outParentEntryID); + FhgfsOpsErr setAttr(const std::string& dirID, bool isBuddyMirrored, int validAttribs, + SettableFileAttribs* attribs); + + void invalidateMirroredDirInodes(); + + bool cacheSweepAsync(); + + + private: + DirectoryMap dirs; + + size_t refCacheSyncLimit; // synchronous access limit (=> async limit plus some grace size) + size_t refCacheAsyncLimit; // asynchronous cleanup limit (this is what the user configures) + Random randGen; // for random cache removal + DirCacheMap refCache; + + RWLock rwlock; + + void releaseDirUnlocked(const std::string& dirID); + + FhgfsOpsErr isRemovableUnlocked(const std::string& dirID, bool isBuddyMirrored); + + DirectoryMapIter insertDirInodeUnlocked(const std::string& dirID, bool isBuddyMirrored, + bool forceLoad); + + FhgfsOpsErr setDirParent(EntryInfo* entryInfo, uint16_t parentNodeID); + + void clearStoreUnlocked(); + + void cacheAddUnlocked(const std::string& dirID, DirectoryReferencer* dirRefer); + void cacheRemoveUnlocked(const std::string& dirID); + void cacheRemoveAllUnlocked(); + bool cacheSweepUnlocked(bool isSyncSweep); +}; + diff --git a/meta/source/storage/InodeFileStore.cpp b/meta/source/storage/InodeFileStore.cpp new file mode 100644 index 0000000..cce6888 --- /dev/null +++ b/meta/source/storage/InodeFileStore.cpp @@ -0,0 +1,660 @@ +#include +#include +#include +#include "InodeFileStore.h" + + +/** + * check if the given ID is in the store + * + */ +bool InodeFileStore::isInStore(const std::string& fileID) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + return inodes.count(fileID) > 0; +} + +/** + * Get the referencer and delete this ID from the map. Mainly used to move the referencer between + * Stores. + */ +FileInodeReferencer* InodeFileStore::getReferencerAndDeleteFromMap(const std::string& fileID) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + InodeMapIter iter = this->inodes.find(fileID); + + if(iter != inodes.end() ) + { // exists in map + auto fileRefer = iter->second; + this->inodes.erase(iter); + return fileRefer; + } + + return nullptr; + +} + +/** + * References a file to be known to already referenced. + * Also could be called "referenceReferencedFile" + * + * Note: remember to call releaseFileInode() + * + * @param loadFromDisk - true for the per-directory InodeFileStore, false for references + * from MetaStore (global map) + * @return NULL if no such file exists + */ +FileInode* InodeFileStore::referenceLoadedFile(const std::string& entryID) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + InodeMapIter iter = this->inodes.find(entryID); + + if(iter != this->inodes.end() ) + return referenceFileInodeMapIterUnlocked(iter); + + return nullptr; +} + + +/** + * Note: remember to call releaseFileInode() + * + * @param loadFromDisk - true for the per-directory InodeFileStore, false for references + * from MetaStore (global map) + * @param checkLockStore - true in most cases, triggers check if file is locked due + * to internal meta operations + * @return FileInode* and FhgfsOpsErr. FileInode* NULL if no such file exists. + */ +FileInodeRes InodeFileStore::referenceFileInode(EntryInfo* entryInfo, bool loadFromDisk, bool checkLockStore) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + if (checkLockStore) // check bool for internal lock store + return referenceFileInodeUnlocked(entryInfo, loadFromDisk); + else + return referenceFileInodeUnlockedIgnoreLocking(entryInfo, loadFromDisk); +} + +/** + * Note: this->rwlock needs to be write locked + * Note: We do not add a reference if isRename == true, but we set an exclusive flag and just + * return an unreferenced inode, which can be deleted anytime. + */ +FileInodeRes InodeFileStore::referenceFileInodeUnlocked(EntryInfo* entryInfo, bool loadFromDisk) +{ + FileInode* inode = NULL; + FhgfsOpsErr retVal = FhgfsOpsErr_PATHNOTEXISTS; + + InodeMapIter iter = this->inodes.find(entryInfo->getEntryID() ); + if(iter == this->inodes.end() && loadFromDisk) + { // not in map yet => check if in globalInodeLockStore + App* app = Program::getApp(); + MetaStore* metaStore = app->getMetaStore(); + GlobalInodeLockStore* inodeLockStore = metaStore->getInodeLockStore(); + if (!inodeLockStore->lookupFileInode(entryInfo)) + //not in globalInodeLockStore, try to load + { + loadAndInsertFileInodeUnlocked(entryInfo, iter); + } + else + { //inode is in GlobalInodeLockStore + retVal = FhgfsOpsErr_INODELOCKED; + } + } + if(iter != this->inodes.end() ) + { // outInode exists + inode = referenceFileInodeMapIterUnlocked(iter); + retVal = FhgfsOpsErr_SUCCESS; + return {inode, retVal}; + } + + return {inode, retVal}; +} + +/** + * Note: this->rwlock needs to be write locked + * Note: We do not add a reference if isRename == true, but we set an exclusive flag and just + * return an unreferenced inode, which can be deleted anytime. + * Note: Variation of referenceFileInodeUnlocked(). + * Bypasses the check for locked files due to internal meta operations. + */ +FileInodeRes InodeFileStore::referenceFileInodeUnlockedIgnoreLocking(EntryInfo* entryInfo, bool loadFromDisk) +{ + FileInode* inode = NULL; + FhgfsOpsErr retVal = FhgfsOpsErr_PATHNOTEXISTS; + FileInodeRes FileInodeResPair = { inode, retVal}; + InodeMapIter iter = this->inodes.find(entryInfo->getEntryID() ); + + if(iter == this->inodes.end() && loadFromDisk) + { // not in map yet => try to load it + loadAndInsertFileInodeUnlocked(entryInfo, iter); + } + + if(iter != this->inodes.end() ) + { // outInode exists + FileInodeResPair.first = referenceFileInodeMapIterUnlocked(iter); + FileInodeResPair.second = FhgfsOpsErr_SUCCESS; + return FileInodeResPair; + } + + return FileInodeResPair; +} + +/** + * Return an unreferenced inode object. The inode is also not exclusively locked. + */ +FhgfsOpsErr InodeFileStore::getUnreferencedInodeUnlocked(EntryInfo* entryInfo, FileInode*& outInode) +{ + FileInode* inode = NULL; + FhgfsOpsErr retVal = FhgfsOpsErr_PATHNOTEXISTS; + + InodeMapIter iter = this->inodes.find(entryInfo->getEntryID() ); + + if(iter == this->inodes.end() ) + { // not in map yet => try to load it. + loadAndInsertFileInodeUnlocked(entryInfo, iter); + } + + if(iter != this->inodes.end() ) + { // outInode exists => check whether no references etc. exist + FileInodeReferencer* inodeRefer = iter->second; + inode = inodeRefer->getReferencedObject(); + + if(inodeRefer->getRefCount() || + (inode->getExclusiveThreadID() && inode->getExclusiveThreadID() != System::getPosixTID() ) ) + { + retVal = FhgfsOpsErr_INUSE; + inode = NULL; + + /* note: We do not unload the outInode here (if we loaded it in this method), + * because freshly loaded inodes can't be referenced or exclusive and hence + * they cannot be "in use". */ + } + else + retVal = FhgfsOpsErr_SUCCESS; + } + + if (inode && inode->getNumHardlinks() > 1) + { /* So the inode is not referenced and we set our exclusive lock. However, there are several + * hardlinks for this file. Currently only rename with a linkCount == 1 is supported! */ + deleteUnreferencedInodeUnlocked(entryInfo->getEntryID() ); + inode = NULL; + retVal = FhgfsOpsErr_INUSE; + } + + outInode = inode; + return retVal; +} + +/** + * referece an a file from InodeMapIter + * NOTE: iter should have been checked by the caller: iter != this->inodes.end() + */ +FileInode* InodeFileStore::referenceFileInodeMapIterUnlocked(InodeMapIter& iter) +{ + if (unlikely(iter == inodes.end() ) ) + return nullptr; + + FileInodeReferencer* inodeRefer = iter->second; + FileInode* inodeNonRef = inodeRefer->getReferencedObject(); + + if(!inodeNonRef->getExclusiveThreadID() || + inodeNonRef->getExclusiveThreadID() == System::getPosixTID() ) + { + FileInode* inode = inodeRefer->reference(); + // note: we don't need to check unload here, because exclusive means there already is a + // reference so we definitely didn't load here + + LOG_DBG(GENERAL, SPAM, "Reference file inode.", ("FileInodeID", iter->first), + ("Refcount", inodeRefer->getRefCount())); + return inode; + } + + return nullptr; +} + +/** + * Decrease the inode reference counter using the given iter. + * + * Note: InodeFileStore needs to be write-locked. + * + * @return number of inode references after release() + */ +unsigned InodeFileStore::decreaseInodeRefCountUnlocked(InodeMapIter& iter) +{ + // decrease refount + FileInodeReferencer* inodeRefer = iter->second; + + unsigned refCount = (unsigned) inodeRefer->release(); + + LOG_DBG(GENERAL, SPAM, "Release file inode.", ("FileInodeID", iter->first), + ("Refcount", inodeRefer->getRefCount())); + + if(!refCount) + { // dropped last reference => unload outInode + delete(inodeRefer); + this->inodes.erase(iter); + } + + + return refCount; +} + +/** + * Close the given file. Also updates the InodeFile on disk. + */ +bool InodeFileStore::closeFile(EntryInfo* entryInfo, FileInode* inode, unsigned accessFlags, + unsigned* outNumHardlinks, unsigned* outNumRefs, bool& outLastWriterClosed) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + *outNumHardlinks = 1; // (we're careful here about inodes that are not currently open) + outLastWriterClosed = false; + + InodeMapIter iter = this->inodes.find(inode->getEntryID() ); + if (iter != this->inodes.end() ) + { // outInode exists + + *outNumHardlinks = inode->getNumHardlinks(); + + // Store inode information on disk, they have been set with inode->setDynAttribs() before + entryInfo->setInodeInlinedFlag(inode->getIsInlined() ); + inode->decNumSessionsAndStore(entryInfo, accessFlags); + + // Here, we monitor the final closure of files opened for writing, including append mode. + // To identify when the last writer closes the file, we consider the following conditions: + // 1. Whether the file was originally opened with write, append, or read-write permissions. + // 2. Whether the count of write sessions associated with the file has dropped to zero. + if (!(accessFlags & OPENFILE_ACCESS_READ) && !inode->getNumSessionsWrite()) + outLastWriterClosed = true; + + *outNumRefs = decreaseInodeRefCountUnlocked(iter); + + return true; + } + + return false; +} + +/** + * @param outNumHardlinks for quick on-close unlink check (may not be NULL!) + * @return false if file was not in store at all, true if we found the file in the store + */ +bool InodeFileStore::releaseFileInode(FileInode* inode) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + InodeMapIter iter = this->inodes.find(inode->getEntryID() ); + if(iter != this->inodes.end() ) + { // outInode exists => decrease refCount + decreaseInodeRefCountUnlocked(iter); + return true; + } + + return false; +} + +/** + * Check whether the file is unlinkable (not in use). + * Can either be used with a reference or a fileID. + * + * @return FhgfsOpsErr_SUCCESS when not in use, FhgfsOpsErr_INUSE when the inode is referenced and + * FhgfsOpsErr_PATHNOTEXISTS when it is exclusively locked. + */ +FhgfsOpsErr InodeFileStore::isUnlinkableUnlocked(EntryInfo* entryInfo) +{ + std::string entryID = entryInfo->getEntryID(); + + InodeMapCIter iter = inodes.find(entryID); + if(iter != inodes.end() ) + { + FileInodeReferencer* fileRefer = iter->second; + FileInode* inode = fileRefer->getReferencedObject(); + + if(fileRefer->getRefCount() ) + return FhgfsOpsErr_INUSE; + else + if(inode->getExclusiveThreadID() && inode->getExclusiveThreadID() != System::getPosixTID() ) + return FhgfsOpsErr_PATHNOTEXISTS; /* TODO: Not correct (if rename() fails), but will be + * FIXED using the per-file checkMap holds. */ + } + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr InodeFileStore::isUnlinkable(EntryInfo* entryInfo) +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + return this->isUnlinkableUnlocked(entryInfo); +} + +/** + * @param outInode will be set to the unlinked file and the object must then be deleted by the + * caller (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr InodeFileStore::unlinkFileInodeUnlocked(EntryInfo* entryInfo, + std::unique_ptr* outInode) +{ + if(outInode) + outInode->reset(); + + std::string entryID = entryInfo->getEntryID(); + + FhgfsOpsErr unlinkableRes = isUnlinkableUnlocked(entryInfo); + if(unlinkableRes != FhgfsOpsErr_SUCCESS) + return unlinkableRes; + + if (outInode) + { + outInode->reset(createUnreferencedInodeUnlocked(entryInfo)); + if (!*outInode) + return FhgfsOpsErr_PATHNOTEXISTS; + } + + bool unlinkRes = FileInode::unlinkStoredInodeUnlocked(entryID, entryInfo->getIsBuddyMirrored()); + if(!unlinkRes) + { + if(outInode) + outInode->reset(); + + return FhgfsOpsErr_INTERNAL; + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * @param outFile will be set to the unlinked file and the object must then be deleted by the caller + * (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr InodeFileStore::unlinkFileInode(EntryInfo* entryInfo, + std::unique_ptr* outInode) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + return unlinkFileInodeUnlocked(entryInfo, outInode); +} + +/** + * Note: This works by serializing the original and marking the object unreferencable (exclusive), + * so remember to call movingCancel() or movingComplete() + * + * @param buf target buffer for serialization (only valid if success is returned) + * @param bufLen must be at least META_SERBUF_SIZE + * @param outUsedBufLen the used bufLen + */ +FhgfsOpsErr InodeFileStore::moveRemoteBegin(EntryInfo* entryInfo, char* buf, size_t bufLen, + size_t* outUsedBufLen) +{ + const char* logContext = "Serialize Inode"; + + if(bufLen < META_SERBUF_SIZE) + { + LogContext(logContext).log(Log_ERR, "Error: Buffer too small!"); + return FhgfsOpsErr_INTERNAL; + } + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + FileInode* inode; + FhgfsOpsErr retVal = getUnreferencedInodeUnlocked(entryInfo, inode); // does not set refCount + if (retVal == FhgfsOpsErr_SUCCESS) + { + /* We got an inode, which is in the map, but is unreferenced. Now we are going to exclusively + * lock it. If another thread should try to reference it, it will fail due to this lock. + */ + inode->setExclusiveTID(System::getPosixTID() ); + + /* Set the origParentEntryID before serializing and moving the inode / file. It is not set + * by default to avoid additional diskData for most files. + */ + inode->setPersistentOrigParentID(entryInfo->getParentEntryID() ); + + Serializer ser(buf, bufLen); + inode->serializeMetaData(ser); + + // Serialize RST info (if present) + if (inode->getIsRstAvailable()) + ser % inode->rstInfo; + + *outUsedBufLen = ser.size(); + } + + return retVal; +} + + +/** + * Finish the rename/move operation by deleting the inode object. + */ +void InodeFileStore::moveRemoteComplete(const std::string& entryID) +{ + // moving succeeded => delete original + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + deleteUnreferencedInodeUnlocked(entryID); +} + +/** + * Finish the rename/move operation by deleting the inode object. + */ +void InodeFileStore::deleteUnreferencedInodeUnlocked(const std::string& entryID) +{ + InodeMapIter iter = inodes.find(entryID); + if(iter != inodes.end() ) + { // file exists + FileInodeReferencer* fileRefer = iter->second; + + delete fileRefer; + inodes.erase(entryID); + } +} + +/** + * Note: This does not load any entries, so it will only return the number of already loaded + * entries. (Only useful for debugging and statistics probably.) + */ +size_t InodeFileStore::getSize() +{ + RWLockGuard lock(rwlock, SafeRWLock_READ); + + return inodes.size(); +} + +/** + * Opens a file and increments the session count if the file is in an accessible state. + * + * This function references the file inode, checks if the file's state allows the requested + * access type, and increments the appropriate session counter if access is permitted. + * If the file is in a locked state, the function returns an error. + * + * Note: Open inodes are always also implicitly referenced. + * + * @param entryInfo entry info of the file to be opened + * @param accessFlags OPENFILE_ACCESS_... flags + * @param outInode Output parameter that will hold the referenced inode on success + * @param loadFromDisk - true for access from DirInode, false for access from MetaStore + */ +FhgfsOpsErr InodeFileStore::openFile(EntryInfo* entryInfo, unsigned accessFlags, + FileInode*& outInode, bool loadFromDisk, bool bypassAccessCheck) +{ + FhgfsOpsErr referenceRes; + FileInodeRes FileInodeResPair = referenceFileInode(entryInfo, loadFromDisk, true); + outInode = FileInodeResPair.first; + referenceRes = FileInodeResPair.second; + + if (!outInode) + return referenceRes; + + FhgfsOpsErr openRes = outInode->checkAccessAndOpen(accessFlags, bypassAccessCheck); + if (openRes != FhgfsOpsErr_SUCCESS) + { + releaseFileInode(outInode); + outInode = nullptr; + return openRes; + } + + return referenceRes; +} + +/** + * get the statData of an inode + * + * @param loadFromDisk if false and the inode is not referenced yet + * we are not going to try to get data from disk. + */ +FhgfsOpsErr InodeFileStore::stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData) +{ + std::string entryID = entryInfo->getEntryID(); + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + InodeMapIter iter = inodes.find(entryID); + if(iter != inodes.end() ) + { // inode loaded + FileInodeReferencer* fileRefer = iter->second; + FileInode* inode = fileRefer->getReferencedObject(); + + return inode->getStatData(outStatData); + } + + lock.unlock(); // no need to keep the lock anymore + + if (loadFromDisk) + { // not loaded => static stat + return FileInode::getStatData(entryInfo, outStatData); + } + + return FhgfsOpsErr_PATHNOTEXISTS; +} + +/** + * @param validAttribs SETATTR_CHANGE_...-Flags. + */ +FhgfsOpsErr InodeFileStore::setAttr(EntryInfo* entryInfo, int validAttribs, + SettableFileAttribs* attribs) +{ + std::string entryID = entryInfo->getEntryID(); + + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + InodeMapIter iter = inodes.find(entryID); + if(iter == inodes.end() ) + { // not loaded => load, apply, destroy + + // Note: A very uncommon code path, as SetAttrMsgEx::setAttr() references the inode first. + + std::unique_ptr inode(FileInode::createFromEntryInfo(entryInfo)); + + if(inode) + { // loaded + bool setRes = inode->setAttrData(entryInfo, validAttribs, attribs); + + if(likely(setRes) ) + { // attr update succeeded + return FhgfsOpsErr_SUCCESS; + } + else + return FhgfsOpsErr_INTERNAL; + } + } + else + { // inode loaded + FileInodeReferencer* inodeRefer = iter->second; + FileInode* inode = inodeRefer->getReferencedObject(); + + if(!inode->getExclusiveThreadID() || inode->getExclusiveThreadID() == System::getPosixTID() ) + { + bool setRes = inode->setAttrData(entryInfo, validAttribs, attribs); + + if(likely(setRes) ) + { // attr update succeeded + return FhgfsOpsErr_SUCCESS; + } + else + return FhgfsOpsErr_INTERNAL; + } + } + + return FhgfsOpsErr_PATHNOTEXISTS; +} + +/** + * Loads a file and inserts it into the map. + * + * Note: Caller must make sure that the element wasn't in the map before. + * + * @return newElemIter only valid if true is returned, untouched otherwise + */ +bool InodeFileStore::loadAndInsertFileInodeUnlocked(EntryInfo* entryInfo, InodeMapIter& newElemIter) +{ + FileInode* inode = FileInode::createFromEntryInfo(entryInfo); + if(!inode) + return false; + + std::string entryID = entryInfo->getEntryID(); + newElemIter = inodes.insert(InodeMapVal(entryID, new FileInodeReferencer(inode) ) ).first; + + return true; +} + +/** + * Insert the existing FileInodeReferencer into the map. + * + * This is mainly required to move references between stores (per-directory to metaStore) + */ +bool InodeFileStore::insertReferencer(std::string entryID, FileInodeReferencer* fileRefer) +{ + RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + return this->inodes.insert(InodeMapVal(entryID, fileRefer) ).second; +} + + +void InodeFileStore::clearStoreUnlocked() +{ + App* app = Program::getApp(); + + LOG_DBG(GENERAL, DEBUG, "InodeFileStore::clearStoreUnlocked", + ("# of loaded entries to be cleared", inodes.size())); + + for(InodeMapIter iter = inodes.begin(); iter != inodes.end(); iter++) + { + FileInode* file = iter->second->getReferencedObject(); + + if(unlikely(file->getNumSessionsAll() ) ) + { // check whether file was still open + LOG_DBG(GENERAL, DEBUG, "File was still open during shutdown.", file->getEntryID(), + file->getNumSessionsAll()); + + if (!app->getSelfTerminate() ) + LogContext(__func__).logBacktrace(); + } + + delete(iter->second); + } + + inodes.clear(); +} + +/** + * Increase or decrease the link count of an inode by the given value + */ +FhgfsOpsErr InodeFileStore::incDecLinkCount(FileInode& inode, EntryInfo* entryInfo, int value) +{ + std::string entryID = entryInfo->getEntryID(); + + bool setRes = inode.incDecNumHardLinks(entryInfo, value); + if(likely(setRes) ) + { // update succeeded + return FhgfsOpsErr_SUCCESS; + } + else + return FhgfsOpsErr_INTERNAL; +} + diff --git a/meta/source/storage/InodeFileStore.h b/meta/source/storage/InodeFileStore.h new file mode 100644 index 0000000..9bbd495 --- /dev/null +++ b/meta/source/storage/InodeFileStore.h @@ -0,0 +1,115 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "FileInode.h" + + +typedef ObjectReferencer FileInodeReferencer; +typedef std::map InodeMap; +typedef InodeMap::iterator InodeMapIter; +typedef InodeMap::const_iterator InodeMapCIter; +typedef InodeMap::value_type InodeMapVal; +typedef std::pair FileInodeRes; + +/** + * Layer in between our inodes and the data on the underlying file system. So we read/write from/to + * underlying inodes and this class is to do this corresponding data access. + * This object is used for all file types, for example regular files, but NOT directories. + */ +class InodeFileStore +{ + friend class DirInode; + friend class MetaStore; + + public: + InodeFileStore() {} + ~InodeFileStore() + { + this->clearStoreUnlocked(); + } + + bool isInStore(const std::string& fileID); + FileInodeRes referenceFileInode(EntryInfo* entryInfo, bool loadFromDisk, bool checkLockStore); + FileInode* referenceLoadedFile(const std::string& entryID); + bool releaseFileInode(FileInode* inode); + FhgfsOpsErr unlinkFileInode(EntryInfo* entryInfo, std::unique_ptr* outInode); + void unlinkAllFiles(); + + FhgfsOpsErr moveRemoteBegin(EntryInfo* entryInfo, char* buf, size_t bufLen, + size_t* outUsedBufLen); + void moveRemoteComplete(const std::string& entryID); + + size_t getSize(); + + bool closeFile(EntryInfo* entryInfo, FileInode* inode, unsigned accessFlags, + unsigned* outNumHardlinks, unsigned* outNumRefs, bool& outLastWriterClosed); + FhgfsOpsErr openFile(EntryInfo* entryInfo, unsigned accessFlags, + FileInode*& outInode, bool loadFromDisk, bool bypassAccessCheck); + + FhgfsOpsErr stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData); + FhgfsOpsErr setAttr(EntryInfo* entryInfo, int validAttribs, SettableFileAttribs* attribs); + + FhgfsOpsErr isUnlinkable(EntryInfo* entryInfo); + + private: + InodeMap inodes; + + RWLock rwlock; + + unsigned decreaseInodeRefCountUnlocked(InodeMapIter& iter); + FileInodeRes referenceFileInodeUnlocked(EntryInfo* entryInfo, bool loadFromDisk); + FileInodeRes referenceFileInodeUnlockedIgnoreLocking(EntryInfo* entryInfo, bool loadFromDisk); + FhgfsOpsErr getUnreferencedInodeUnlocked(EntryInfo* entryInfo, FileInode*& outInode); + void deleteUnreferencedInodeUnlocked(const std::string& entryID); + + FhgfsOpsErr isUnlinkableUnlocked(EntryInfo* entryInfo); + + FhgfsOpsErr unlinkFileInodeUnlocked(EntryInfo* entryInfo, + std::unique_ptr* outInode); + + bool loadAndInsertFileInodeUnlocked(EntryInfo* entryInfo, InodeMapIter& newElemIter); + bool insertReferencer(std::string entryID, FileInodeReferencer* fileRefer); + + FileInodeReferencer* getReferencerAndDeleteFromMap(const std::string& fileID); + + void clearStoreUnlocked(); + + FileInode* referenceFileInodeMapIterUnlocked(InodeMapIter& iter); + + FhgfsOpsErr incDecLinkCount(FileInode& inode, EntryInfo* entryInfo, int value); + + public: + + // inliners + + FhgfsOpsErr incLinkCount(FileInode& inode, EntryInfo* entryInfo) + { + return incDecLinkCount(inode, entryInfo, 1); + } + + FhgfsOpsErr decLinkCount(FileInode& inode, EntryInfo* entryInfo) + { + return incDecLinkCount(inode, entryInfo, -1); + } + + + private: + + // inliners + + /** + * Create an unreferenced file inode from an existing inode on disk disk. + */ + FileInode* createUnreferencedInodeUnlocked(EntryInfo* entryInfo) + { + return FileInode::createFromEntryInfo(entryInfo); + } + +}; + diff --git a/meta/source/storage/Locking.cpp b/meta/source/storage/Locking.cpp new file mode 100644 index 0000000..89818e5 --- /dev/null +++ b/meta/source/storage/Locking.cpp @@ -0,0 +1,50 @@ +#include +#include +#include +#include "Locking.h" + + + +bool EntryLockDetails::operator==(const EntryLockDetails& other) const +{ + return clientNumID == other.clientNumID + && clientFD == other.clientFD + && ownerPID == other.ownerPID + && lockAckID == other.lockAckID + && lockTypeFlags == other.lockTypeFlags; +} + +bool RangeLockDetails::operator==(const RangeLockDetails& other) const +{ + return clientNumID == other.clientNumID + && ownerPID == other.ownerPID + && lockAckID == other.lockAckID + && lockTypeFlags == other.lockTypeFlags + && start == other.start + && end == other.end; +} + + + +void EntryLockDetails::initRandomForSerializationTests() +{ + Random rand; + + this->clientFD = rand.getNextInt(); + this->clientNumID = NumNodeID(rand.getNextInt() ); + StringTk::genRandomAlphaNumericString(this->lockAckID, rand.getNextInRange(0, 30) ); + this->lockTypeFlags = rand.getNextInt(); + this->ownerPID = rand.getNextInt(); +} + +void RangeLockDetails::initRandomForSerializationTests() +{ + Random rand; + + this->clientNumID = NumNodeID(rand.getNextInt() ); + this->end = rand.getNextInt(); + StringTk::genRandomAlphaNumericString(this->lockAckID, rand.getNextInRange(0, 30) ); + this->lockTypeFlags = rand.getNextInt(); + this->ownerPID = rand.getNextInt(); + this->start = rand.getNextInt(); +} diff --git a/meta/source/storage/Locking.h b/meta/source/storage/Locking.h new file mode 100644 index 0000000..89b2f02 --- /dev/null +++ b/meta/source/storage/Locking.h @@ -0,0 +1,490 @@ +#pragma once + +#include +#include +#include + + +/** + * The file lock type, e.g. append lock or entry lock (=>flock) for LockEntryNotificationWork. + */ +enum LockEntryNotifyType +{ + LockEntryNotifyType_APPEND = 0, + LockEntryNotifyType_FLOCK, +}; + +enum RangeOverlapType +{ + RangeOverlapType_NONE=0, + RangeOverlapType_EQUALS=1, // ranges are equal + RangeOverlapType_CONTAINS=2, // first range wraps around second range + RangeOverlapType_ISCONTAINED=3, // first range is contained within second range + RangeOverlapType_STARTOVERLAP=4, // second range overlaps start of first range + RangeOverlapType_ENDOVERLAP=5 // second range overlaps end of first range +}; + + +/** + * Entry-locks are treated per FD, e.g. a single process will block itself when it holds an + * exclusive lock and tries to get another exclusive lock via another file descriptor. That's why + * we compare clientID and clientFD. (This is different for range-locks.) + */ +struct EntryLockDetails +{ + /** + * @param lockTypeFlags ENTRYLOCKTYPE_... + */ + EntryLockDetails(NumNodeID clientNumID, int64_t clientFD, int ownerPID, + const std::string& lockAckID, int lockTypeFlags): + clientNumID(clientNumID), clientFD(clientFD), ownerPID(ownerPID), lockAckID(lockAckID), + lockTypeFlags(lockTypeFlags) + { } + + /** + * Constructor for unset lock (empty clientID; other fields not init'ed). + */ + EntryLockDetails() {} + + + NumNodeID clientNumID; + int64_t clientFD; // unique handle on the corresponding client + int32_t ownerPID; // pid on client (just informative, because shared on fork() ) + std::string lockAckID; /* ID for ack message when log is granted (and to identify duplicate + requests, so it must be a globally unique ID) */ + int32_t lockTypeFlags; // ENTRYLOCKTYPE_... + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->clientFD + % obj->ownerPID + % serdes::stringAlign4(obj->lockAckID) + % obj->lockTypeFlags; + } + + bool operator==(const EntryLockDetails& other) const; + + bool operator!=(const EntryLockDetails& other) const { return !(*this == other); } + + void initRandomForSerializationTests(); + + bool isSet() const + { + return bool(clientNumID); + } + + bool allowsWaiting() const + { + return !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT); + } + + bool isUnlock() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_UNLOCK); + } + + void setUnlock() + { + lockTypeFlags |= ENTRYLOCKTYPE_UNLOCK; + lockTypeFlags &= ~(ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED | ENTRYLOCKTYPE_CANCEL); + } + + bool isExclusive() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_EXCLUSIVE); + } + + bool isShared() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_SHARED); + } + + bool isCancel() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_CANCEL); + } + + NumNodeID getClientNumID() + { + return clientNumID; + } + + /** + * Compares clientID and clientFD. + */ + bool equalsHandle(const EntryLockDetails& other) const + { + // note: if you make changes here, you (probably) also need to change the MapComparator + + return (clientFD == other.clientFD) && (clientNumID == other.clientNumID); + } + + std::string toString() const + { + std::ostringstream outStream; + outStream << + "clientNumID: " << clientNumID << "; " << + "clientFD: " << clientFD << "; " << + "ownerPID: " << ownerPID << "; " << + "lockAckID: " << lockAckID << "; " << + "lockTypeFlags: "; + + // append lockTypeFlags + if(isUnlock() ) + outStream << "u"; + if(isShared() ) + outStream << "s"; + if(isExclusive() ) + outStream << "x"; + if(isCancel() ) + outStream << "c"; + if(allowsWaiting() ) + outStream << "w"; + + return outStream.str(); + } + + struct MapComparator + { + /** + * Order by file handle. + * + * @return true if a is smaller than b + */ + bool operator() (const EntryLockDetails& a, const EntryLockDetails& b) const + { + return (a.clientFD < b.clientFD) || + ( (a.clientFD == b.clientFD) && (a.clientNumID < b.clientNumID) ); + } + }; +}; + + +typedef std::set EntryLockDetailsSet; +typedef EntryLockDetailsSet::iterator EntryLockDetailsSetIter; +typedef EntryLockDetailsSet::const_iterator EntryLockDetailsSetCIter; + +typedef std::list EntryLockDetailsList; +typedef EntryLockDetailsList::iterator EntryLockDetailsListIter; +typedef EntryLockDetailsList::const_iterator EntryLockDetailsListCIter; + + +/** + * A simple container for flock/append entry lock queue pointers. + * + * We have this to easily pass the different flock and append queues to the corresponding generic + * lock management methods. + */ +class EntryLockQueuesContainer +{ + public: + EntryLockQueuesContainer(EntryLockDetails* exclLock, EntryLockDetailsSet* sharedLocks, + EntryLockDetailsList* waitersExclLock, EntryLockDetailsList* waitersSharedLock, + StringSet* waitersLockIDs, LockEntryNotifyType lockType = LockEntryNotifyType_FLOCK) : + exclLock(exclLock), sharedLocks(sharedLocks), waitersExclLock(waitersExclLock), + waitersSharedLock(waitersSharedLock), waitersLockIDs(waitersLockIDs), lockType(lockType) + { + // (all inits done in initializer list) + } + + EntryLockDetails* exclLock; // current exclusiveTID lock + EntryLockDetailsSet* sharedLocks; // current shared locks (key is lock, value is dummy) + EntryLockDetailsList* waitersExclLock; // queue (append new to end, pop from top) + EntryLockDetailsList* waitersSharedLock; // queue (append new to end, pop from top) + StringSet* waitersLockIDs; // currently enqueued lockIDs (for fast duplicate check) + + LockEntryNotifyType lockType; // to be passed to LockingNotifier +}; + + +/** + * A simple container for append entry lock queue pointers + * + * This contains dummy queues for shared locks, because append does not use shared locks, but the + * generic lock management methods expect them to exist (which could be optimized out if necessary). + */ +class AppendLockQueuesContainer : public EntryLockQueuesContainer +{ + public: + AppendLockQueuesContainer(EntryLockDetails* exclLock, EntryLockDetailsList* waitersExclLock, + StringSet* waitersLockIDsFLock) : + EntryLockQueuesContainer(exclLock, &sharedLocksDummy, waitersExclLock, + &waitersSharedLockDummy, waitersLockIDsFLock, LockEntryNotifyType_APPEND) + { + // (all inits done in initializer list) + } + + private: + EntryLockDetailsSet sharedLocksDummy; // dummy, because append uses exclusive only + EntryLockDetailsList waitersSharedLockDummy; // dummy, because append uses exclusive only +}; + + +/** + * Range-locks are treated per-process, e.g. independent of different file descriptors or threads, + * a process cannot block itself with two exclusive locks. That's why we only compare clientID and + * ownerPID here. (This is different for entry-locks.) + */ +struct RangeLockDetails +{ + RangeLockDetails(NumNodeID clientNumID, int ownerPID, const std::string& lockAckID, + int lockTypeFlags, uint64_t start, uint64_t end) : + clientNumID(clientNumID), ownerPID(ownerPID), lockAckID(lockAckID), + lockTypeFlags(lockTypeFlags), start(start), end(end) + { } + + /** + * Constructor for unset lock (empty clientID; other fields not init'ed). + */ + RangeLockDetails() {} + + NumNodeID clientNumID; + int32_t ownerPID; // pid on client + std::string lockAckID; /* ID for ack message when log is granted (and to identify duplicate + requests, so it must be globally unique) */ + int32_t lockTypeFlags; // ENTRYLOCKTYPE_... + + uint64_t start; + uint64_t end; // inclusive end + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->clientNumID + % obj->ownerPID + % serdes::stringAlign4(obj->lockAckID) + % obj->lockTypeFlags + % obj->start + % obj->end; + } + + bool operator==(const RangeLockDetails& other) const; + + bool operator!=(const RangeLockDetails& other) const { return !(*this == other); } + + void initRandomForSerializationTests(); + + bool isSet() const + { + return bool(clientNumID); + } + + bool allowsWaiting() const + { + return !(lockTypeFlags & ENTRYLOCKTYPE_NOWAIT); + } + + bool isUnlock() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_UNLOCK); + } + + void setUnlock() + { + lockTypeFlags |= ENTRYLOCKTYPE_UNLOCK; + lockTypeFlags &= ~(ENTRYLOCKTYPE_EXCLUSIVE | ENTRYLOCKTYPE_SHARED | ENTRYLOCKTYPE_CANCEL); + } + + bool isExclusive() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_EXCLUSIVE); + } + + bool isShared() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_SHARED); + } + + bool isCancel() const + { + return (lockTypeFlags & ENTRYLOCKTYPE_CANCEL); + } + + /** + * Compares clientID and ownerPID. + */ + bool equalsHandle(const RangeLockDetails& other) const + { + // note: if you make changes here, you (probably) also need to change the MapComparators + + return (ownerPID == other.ownerPID) && (clientNumID == other.clientNumID); + } + + /** + * Check if ranges overlap or directly extend each other. + * Note: this just checks ranges, not owner (which is good, because we rely on that at some + * points in the code) + */ + bool isMergeable(const RangeLockDetails& other) const + { + // check if other region ends before this one or starts after this one + // (+1 is because we are not only looking for overlaps, but also for extensions) + if( ( (other.end+1) < start) || (other.start > (end+1) ) ) + return false; + + // other range overlaps or is directly extending this range + return true; + } + + /** + * Check if ranges have common values. + */ + bool overlaps(const RangeLockDetails& other) const + { + // check if other region ends before this one or starts after this one + if( (other.end < start) || (other.start > end) ) + return false; + + // other range not before or after this one => overlap + return true; + } + + RangeOverlapType overlapsEx(const RangeLockDetails& other) const + { + if( (other.end < start) || (other.start > end) ) + return RangeOverlapType_NONE; // other region ends before this one or starts after this one + + if( (other.start == start) && (other.end == end) ) + return RangeOverlapType_EQUALS; + + if( (start <= other.start) && (end >= other.end) ) + return RangeOverlapType_CONTAINS; // this range contains other range + + if( (start >= other.start) && (end <= other.end) ) + return RangeOverlapType_ISCONTAINED; // this range is contained in other range + + if(start < other.start) + return RangeOverlapType_STARTOVERLAP; // other range overlaps at start of this range + + return RangeOverlapType_ENDOVERLAP; // other range overlaps at end of this range + } + + /** + * Trim the part of this lock that overlaps with trimmer. + * The caller must make sure that the resulting region has "length>0" and that this is a + * real one-sided overlap (none of the regions contains the other except if start or end match). + */ + void trim(const RangeLockDetails& trimmer) + { + if(trimmer.end < end) + start = trimmer.end+1; // trim on start-side + else + end = trimmer.start-1; // trim on end-side + } + + /** + * Split this region by the splitter region. + * The result for this will be the remaining left side of splitter, outEndRegion will be set to + * the remaining right side of splitter. + * The caller must make sure that both resulting regions have "length>0", so splitter must be + * completely contained in this and start/end of this/splitter may not match. + */ + void split(const RangeLockDetails& splitter, RangeLockDetails& outEndRegion) + { + // right side remainder + outEndRegion = *this; + outEndRegion.start = splitter.end+1; + + // trim left side remainder + end = splitter.start-1; + } + + /** + * Extends this region by the dimension of other region. + * Caller must make sure that the regions actually do overlap. + */ + void merge(const RangeLockDetails& other) + { + start = BEEGFS_MIN(start, other.start); + end = BEEGFS_MAX(end, other.end); + } + + std::string toString() const + { + std::ostringstream outStream; + outStream << + "clientNumID: " << clientNumID << "; " << + "PID: " << ownerPID << "; " << + "lockAckID: " << lockAckID << "; " << + "range: " << start << " - " << end << "; " << + "lockTypeFlags: "; + + // append lockTypeFlags + if(isUnlock() ) + outStream << "u"; + if(isShared() ) + outStream << "s"; + if(isExclusive() ) + outStream << "x"; + if(isCancel() ) + outStream << "c"; + if(allowsWaiting() ) + outStream << "w"; + + return outStream.str(); + } + + struct MapComparatorShared + { + /** + * Order by file handle, then range start (to make overlap detection for same handle easy). + * + * @return true if a is smaller than b + */ + bool operator() (const RangeLockDetails& a, const RangeLockDetails& b) const + { + if(a.ownerPID < b.ownerPID) + return true; + if(a.ownerPID > b.ownerPID) + return false; + + // equal ownerPID + + if(a.clientNumID < b.clientNumID) + return true; + if(a.clientNumID > b.clientNumID) + return false; + + // equal clientID + + if(a.start < b.start) + return true; + + return false; + } + }; + + struct MapComparatorExcl + { + /** + * Order by range start (to make general overlap detection easy). + * + * @return true if a is smaller than b + */ + bool operator() (const RangeLockDetails& a, const RangeLockDetails& b) const + { + // note: this only works because we cannot have range overlaps for exclusive locks + return (a.start < b.start); + } + }; + +}; + + +typedef std::set RangeLockSharedSet; +typedef RangeLockSharedSet::iterator RangeLockSharedSetIter; +typedef RangeLockSharedSet::const_iterator RangeLockSharedSetCIter; + +typedef std::set RangeLockExclSet; +typedef RangeLockExclSet::iterator RangeLockExclSetIter; +typedef RangeLockExclSet::const_iterator RangeLockExclSetCIter; + +typedef std::list RangeLockDetailsList; +typedef RangeLockDetailsList::iterator RangeLockDetailsListIter; +typedef RangeLockDetailsList::const_iterator RangeLockDetailsListCIter; + + diff --git a/meta/source/storage/MetaFileHandle.h b/meta/source/storage/MetaFileHandle.h new file mode 100644 index 0000000..c69674a --- /dev/null +++ b/meta/source/storage/MetaFileHandle.h @@ -0,0 +1,65 @@ +#pragma once + +#include +#include + +class MetaStore; + +// this class is used to transport referenced file inode out of and back into the meta store. +// since file inodes must keep their parent directory alive, a handle to an inode also requires a +// handle to a directory to function properly. +class MetaFileHandle +{ + friend class MetaStore; + + public: + typedef void (MetaFileHandle::*bool_type)(); + + MetaFileHandle(): + inode(nullptr), parent(nullptr) + {} + + MetaFileHandle(FileInode* inode, DirInode* parent): + inode(inode), parent(parent) + {} + + MetaFileHandle(const MetaFileHandle&) = delete; + MetaFileHandle& operator=(const MetaFileHandle&) = delete; + + MetaFileHandle(MetaFileHandle&& other): + inode(nullptr), parent(nullptr) + { + swap(other); + } + + MetaFileHandle& operator=(MetaFileHandle&& other) + { + MetaFileHandle(std::move(other)).swap(*this); + return *this; + } + + FileInode* operator->() const { return inode; } + FileInode& operator*() const { return *inode; } + + FileInode* get() const { return inode; } + + operator bool_type() const { return inode ? &MetaFileHandle::bool_value : 0; } + + void swap(MetaFileHandle& other) + { + std::swap(inode, other.inode); + std::swap(parent, other.parent); + } + + friend void swap(MetaFileHandle& a, MetaFileHandle& b) + { + a.swap(b); + } + + private: + FileInode* inode; + DirInode* parent; + + void bool_value() {} +}; + diff --git a/meta/source/storage/MetaStore.cpp b/meta/source/storage/MetaStore.cpp new file mode 100644 index 0000000..65ea9ef --- /dev/null +++ b/meta/source/storage/MetaStore.cpp @@ -0,0 +1,2660 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "MetaStore.h" + +#include +#include +#include + +#include + +#define MAX_DEBUG_LOCK_TRY_TIME 30 // max lock wait time in seconds + +/** + * Reference the given directory. + * + * @param dirID the ID of the directory to reference + * @param forceLoad not all operations require the directory to be loaded from disk (i.e. stat() + * of a sub-entry) and the DirInode is only used for directory locking. Other + * operations need the directory to locked and those shall set forceLoad = true. + * Should be set to true if we are going to write to the DirInode anyway or + * if DirInode information are required. + * @return cannot be NULL if forceLoad=false, even if the directory with the given id + * does not exist. But it can be NULL of forceLoad=true, as we only then + * check if the directory exists on disk at all. + */ +DirInode* MetaStore::referenceDir(const std::string& dirID, const bool isBuddyMirrored, + const bool forceLoad) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return referenceDirUnlocked(dirID, isBuddyMirrored, forceLoad); +} + +DirInode* MetaStore::referenceDirUnlocked(const std::string& dirID, bool isBuddyMirrored, + bool forceLoad) +{ + return dirStore.referenceDirInode(dirID, isBuddyMirrored, forceLoad); +} + +void MetaStore::releaseDir(const std::string& dirID) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + releaseDirUnlocked(dirID); +} + +void MetaStore::releaseDirUnlocked(const std::string& dirID) +{ + dirStore.releaseDir(dirID); +} + +/** + * Reference a file. It is unknown if this file is already referenced in memory or needs to be + * loaded. Therefore a complete entryInfo is required. + */ +MetaFileHandleRes MetaStore::referenceFile(EntryInfo* entryInfo, bool checkLockStore) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + auto [inode, referenceRes] = referenceFileUnlocked(entryInfo, checkLockStore); + if (unlikely(!inode)) + return {MetaFileHandle(), referenceRes}; + + // Return MetaFileHandle from here if: + // 1. The inode is inlined, or + // 2. The inode is loaded into the global store, indicating that + // it was previously deinlined hence inlined=false was received from client, or + // 3. Inode is already present in the global store (numParentRefs remains zero if inode + // is referenced from the global store) + if (inode->getIsInlined() || (inode->getNumParentRefs() == 0)) + return {std::move(inode), referenceRes}; + + // If we reach at this point, then following holds true: + // 1. The file inode is non-inlined (validated from on-disk metadata) + // 2. The entryInfo received from client has stale value of inlined flag (i.e. true) + // 3. The file inode is referenced in dir-specific store + // + // According to existing design, non-inlined file inodes should always be referenced + // in the global inode store. Therefore, we need to perform following additional steps + // to maintain consistency with aformentioned design: + // 1. Take parent directory reference and release inode from dir-specific store + // 2. Release the meta read-lock and call tryReferenceFileWriteLocked() to put inode + // reference into the global store + + auto dir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), false); + if (!dir) + return {MetaFileHandle(), FhgfsOpsErr_INTERNAL}; + + UniqueRWLock subDirLock(dir->rwlock, SafeRWLock_READ); + + inode->decParentRef(); + dir->fileStore.releaseFileInode(inode.get()); + + subDirLock.unlock(); + releaseDirUnlocked(dir->getID()); + + releaseDirUnlocked(dir->getID()); + + lock.unlock(); // U N L O C K + return tryReferenceFileWriteLocked(entryInfo, checkLockStore); +} + +/** + * See referenceFileInode() for details. We already have the lock here. + */ +MetaFileHandleRes MetaStore::referenceFileUnlocked(EntryInfo* entryInfo, bool checkLockStore) +{ + // load inode into global store from disk if it is nonInlined + bool loadFromDisk = !entryInfo->getIsInlined(); + auto [inode, referenceRes] = this->fileStore.referenceFileInode(entryInfo, loadFromDisk, checkLockStore); + if (inode) + return {MetaFileHandle(inode, nullptr), referenceRes}; + + // not in global map, now per directory and also try to load from disk + + const std::string& parentEntryID = entryInfo->getParentEntryID(); + bool isBuddyMirrored = entryInfo->getIsBuddyMirrored(); + DirInode* subDir = referenceDirUnlocked(parentEntryID, isBuddyMirrored, false); + + if (!subDir) + return {MetaFileHandle(), FhgfsOpsErr_PATHNOTEXISTS}; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + std::tie(inode, referenceRes) = subDir->fileStore.referenceFileInode(entryInfo, true, checkLockStore); + if (!inode) + { + subDirLock.unlock(); + releaseDirUnlocked(entryInfo->getParentEntryID()); + return {MetaFileHandle(), referenceRes}; + } + + // we are not going to release the directory here, as we are using its FileStore + inode->incParentRef(parentEntryID); + + return {MetaFileHandle(inode, nullptr), referenceRes};; +} + +/** + * See referenceFileInode() for details. DirInode is already known here and will not be futher + * referenced due to the write-lock hold by the caller. Getting further references might cause + * dead locks due to locking order problems (DirStore needs to be locked while holding the DirInode + * lock). + * + * Locking: + * MetaStore read locked + * DirInode write locked. + * + * Note: Callers must release FileInode before releasing DirInode! Use this method with care! + * + */ +MetaFileHandleRes MetaStore::referenceFileUnlocked(DirInode& subDir, EntryInfo* entryInfo, bool checkLockStore) +{ + // load inode into global store from disk if it is nonInlined + bool loadFromDisk = !entryInfo->getIsInlined(); + auto [inode, referenceRes] = this->fileStore.referenceFileInode(entryInfo, loadFromDisk, checkLockStore); + if (inode) + return {MetaFileHandle(inode, nullptr), referenceRes}; + + // not in global map, now per directory and also try to load from disk + std::tie(inode, referenceRes) = subDir.fileStore.referenceFileInode(entryInfo, true, checkLockStore); + + return {MetaFileHandle(inode, nullptr), referenceRes}; +} + +/** + * Reference a file known to be already referenced. So disk-access is not required and we don't + * need the complete EntryInfo, but parentEntryID and entryID are sufficient. + * Another name for this function also could be "referenceReferencedFiles". + */ +MetaFileHandle MetaStore::referenceLoadedFile(const std::string& parentEntryID, + bool parentIsBuddyMirrored, const std::string& entryID) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return referenceLoadedFileUnlocked(parentEntryID, parentIsBuddyMirrored, entryID); +} + +/** + * See referenceLoadedFile() for details. We already have the lock here. + */ +MetaFileHandle MetaStore::referenceLoadedFileUnlocked(const std::string& parentEntryID, + bool isBuddyMirrored, const std::string& entryID) +{ + FileInode* inode = this->fileStore.referenceLoadedFile(entryID); + if (inode) + return {inode, nullptr}; + + // not in global map, now per directory and also try to load from disk + DirInode* subDir = referenceDirUnlocked(parentEntryID, isBuddyMirrored, false); + if (!subDir) + return {}; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + inode = subDir->fileStore.referenceLoadedFile(entryID); + + if (!inode) + { + subDirLock.unlock(); + releaseDirUnlocked(parentEntryID); + return {}; + } + + // we are not going to release the directory here, as we are using its FileStore + inode->incParentRef(parentEntryID); + + return {inode, subDir}; +} + +/** + * See referenceFileInode() for details. DirInode is already known here and will not be futher + * referenced due to the write-lock hold by the caller. Getting further references might cause + * dead locks due to locking order problems (DirStore needs to be locked while holding the DirInode + * lock). + * + * Locking: + * MetaStore read locked + * DirInode write locked. + * + * Note: Callers must release FileInode before releasing DirInode! Use this method with care! + * + */ +MetaFileHandle MetaStore::referenceLoadedFileUnlocked(DirInode& subDir, const std::string& entryID) +{ + FileInode* inode = this->fileStore.referenceLoadedFile(entryID); + if (inode) + return {inode, nullptr}; + + // not in global map, now per directory and also try to load from disk + inode = subDir.fileStore.referenceLoadedFile(entryID); + return {inode, nullptr}; +} + +/** + * Tries to reference the file inode with MetaStore's write lock held. + * Moves the reference to the global store if not already present. + * + * @param entryInfo The entry information of the file. + * @return A handle to the file inode if successful, otherwise an empty handle. + */ +MetaFileHandleRes MetaStore::tryReferenceFileWriteLocked(EntryInfo* entryInfo, bool checkLockStore) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + if (!this->fileStore.isInStore(entryInfo->getEntryID())) + { + this->moveReferenceToMetaFileStoreUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), entryInfo->getEntryID()); + } + + auto [inode, referenceRes] = this->fileStore.referenceFileInode(entryInfo, true, checkLockStore); + + if (inode) + return {MetaFileHandle(inode, nullptr), referenceRes}; + + return {MetaFileHandle(), referenceRes}; +} + +/** + * Tries to open the file with MetaStore's write lock held. + * Moves the reference to the global store if not present. + * + * @param entryInfo The entry information of the file. + * @param accessFlags The access flags for opening the file. + * @param bypassAccessCheck Whether to bypass access checks. + * @param outInode Output parameter for the file inode handle. + * @return Error code indicating the result of the operation. + */ +FhgfsOpsErr MetaStore::tryOpenFileWriteLocked(EntryInfo* entryInfo, unsigned accessFlags, + bool bypassAccessCheck, MetaFileHandle& outInode) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + if (!this->fileStore.isInStore(entryInfo->getEntryID())) + { + this->moveReferenceToMetaFileStoreUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), entryInfo->getEntryID()); + } + + FileInode* inode; + auto openRes = this->fileStore.openFile(entryInfo, accessFlags, inode, + /* loadFromDisk */ true, bypassAccessCheck); + outInode = {inode, nullptr}; + return openRes; +} + +bool MetaStore::releaseFile(const std::string& parentEntryID, MetaFileHandle& inode) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return releaseFileUnlocked(parentEntryID, inode); +} + +/** + * Release a file inode. + * + * Note: If the inode belongs to the per-directory file store also this directory will be + * released. + */ +bool MetaStore::releaseFileUnlocked(const std::string& parentEntryID, MetaFileHandle& inode) +{ + bool releaseRes = fileStore.releaseFileInode(inode.get()); + if (releaseRes) + return true; + + // Not in global map, now per directory and also try to load from disk. + + // NOTE: we assume here, that if the file inode is buddy mirrored, the parent is buddy + // mirrored, to + DirInode* subDir = referenceDirUnlocked(parentEntryID, inode->getIsBuddyMirrored(), false); + if (!subDir) + return false; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + inode->decParentRef(); + releaseRes = subDir->fileStore.releaseFileInode(inode.get()); + + // this is the current lock and reference + subDirLock.unlock(); + releaseDirUnlocked(parentEntryID); + + // when we referenced the inode we did not release the directory yet, so do that here + releaseDirUnlocked(parentEntryID); + + return releaseRes; +} + +/** + * Release a file inode. DirInode is known by the caller. Only call this after using + * referenceFileUnlocked(DirInode* subDir, EntryInfo* entryInfo) + * or + * referenceLoadedFileUnlocked(DirInode* subDir, std::string entryID) + * to reference a FileInode. + * + * Locking: + * MetaStore is locked + * DirInode is locked + * + * Note: No extra DirInode releases here, as the above mentioned referenceFileUnlocked() method also + * does not get additional references. + */ +bool MetaStore::releaseFileUnlocked(DirInode& subDir, MetaFileHandle& inode) +{ + bool releaseRes = fileStore.releaseFileInode(inode.get()); + if (releaseRes) + return true; + + // Not in global map, now per directory. + return subDir.fileStore.releaseFileInode(inode.get()); +} + + +/* + * Can be used to reference an inode, if it is unknown whether it is a file or directory. While + * one of the out.... pointers will hold a reference to the corresponidng inode (which must be + * released), the other pointer will be set to NULL. + * + * Note: This funtion is mainly used by fsck + * Note: dirInodes will always be loaded from disk with this function + * Note: This will NOT work with inlined file inodes, as we only pass the ID + * Note: This is not very efficient, but OK for now. Can definitely be optimized. + * + * @param entryID + * @param outDirInode + * @param outFilenode + * + * @return false if entryID could neither be loaded as dir nor as file inode + */ +bool MetaStore::referenceInode(const std::string& entryID, bool isBuddyMirrored, + MetaFileHandle& outFileInode, DirInode*& outDirInode) +{ + outFileInode = {}; + outDirInode = NULL; + FhgfsOpsErr referenceRes; + + // trying dir first, because we assume there are more non-inlined dir inodes than file inodes + outDirInode = referenceDir(entryID, isBuddyMirrored, true); + + if (outDirInode) + return true; + + // opening as dir failed => try as file + + /* we do not set full entryInfo (we do not have most of the info), but only entryID. That's why + it does not work with inlined inodes */ + EntryInfo entryInfo(NumNodeID(), "", entryID, "", DirEntryType_REGULARFILE,0); + if (isBuddyMirrored) + entryInfo.setBuddyMirroredFlag(true); + + std::tie(outFileInode, referenceRes) = referenceFile(&entryInfo); + + return outFileInode; +} + + +FhgfsOpsErr MetaStore::isFileUnlinkable(DirInode& subDir, EntryInfo *entryInfo) +{ + FhgfsOpsErr isUnlinkable = this->fileStore.isUnlinkable(entryInfo); + if (isUnlinkable == FhgfsOpsErr_SUCCESS) + isUnlinkable = subDir.fileStore.isUnlinkable(entryInfo); + + return isUnlinkable; +} + + +/** + * Move a fileInode reference from subDir->fileStore to (MetaStore) this->fileStore + * + * @return true if an existing reference was moved + * + * Note: MetaStore needs to be write locked! + */ +bool MetaStore::moveReferenceToMetaFileStoreUnlocked(const std::string& parentEntryID, + bool parentIsBuddyMirrored, const std::string& entryID) +{ + bool retVal = false; + const char* logContext = "MetaStore (Move reference from per-Dir fileStore to global Store)"; + + DirInode* subDir = referenceDirUnlocked(parentEntryID, parentIsBuddyMirrored, false); + if (unlikely(!subDir) ) + return false; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_WRITE); + + FileInodeReferencer* inodeRefer = subDir->fileStore.getReferencerAndDeleteFromMap(entryID); + if (likely(inodeRefer) ) + { + // The inode is referenced in the per-directory fileStore. Move it to the global store. + + FileInode* inode = inodeRefer->reference(); + ssize_t numParentRefs = inode->getNumParentRefs(); + + retVal = this->fileStore.insertReferencer(entryID, inodeRefer); + if (unlikely(retVal == false) ) + { + std::string msg = "Bug: Failed to move to MetaStore FileStore - already exists in map" + " ParentID: " + parentEntryID + " EntryID: " + entryID; + LogContext(logContext).logErr(msg); + /* NOTE: We are leaking memory here, but as there is a general bug, this is better + * than trying to free an object possibly in-use. */ + } + else + { + while (numParentRefs > 0) + { + releaseDirUnlocked(parentEntryID); /* inode references also always keep a dir reference, + * so we need to release the dir as well */ + numParentRefs--; + inode->decParentRef(); + } + + inodeRefer->release(); + } + } + else + { + retVal = true; /* it is probably a valid race (), so do not return an error, + * unlinkInodeLaterUnlocked() also can handle it as it does find this inode + * in the global FileStore then */ + } + + subDirLock.unlock(); + releaseDirUnlocked(parentEntryID); + + return retVal; +} + +/** + * @param accessFlags OPENFILE_ACCESS_... flags + */ +FhgfsOpsErr MetaStore::openFile(EntryInfo* entryInfo, unsigned accessFlags, bool bypassAccessCheck, + MetaFileHandle& outInode, bool checkDisposalFirst) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + // session restore must load disposed files with disposal as parent entry id - if the file is + // disposed any other parent id will also work, but will make that parent id unremovable for + // as long as the file is opened. + if (unlikely(checkDisposalFirst)) + { + auto eiDisposal = *entryInfo; + eiDisposal.setParentEntryID(META_DISPOSALDIR_ID_STR); + + FileInode* inode; + auto openRes = this->fileStore.openFile(&eiDisposal, accessFlags, inode, + /* loadFromDisk */ true, bypassAccessCheck); + if (inode) + { + outInode = {inode, nullptr}; + return openRes; + } + } + + /* check the MetaStore fileStore map here, but most likely the file will not be in this map, + * but is either in the per-directory-map or has to be loaded from disk */ + if (this->fileStore.isInStore(entryInfo->getEntryID())) + { + FileInode* inode; + auto openRes = this->fileStore.openFile(entryInfo, accessFlags, inode, + /* loadFromDisk */ false, bypassAccessCheck); + outInode = {inode, nullptr}; + return openRes; + } + + // onwards v7.4.0 non-inlined inode might be present due to hardlinks. Non inlined file inodes + // should be referenced from and stored into global file store (and not in per-directory-store) + // if inode is non-inlined then code block below will load inode from disk and puts it into + // global store if not already present there + // + // to forceLoad inode into global file store we need to pass "loadFromDisk = true" in call to + // InodeFileStore::openFile(...) below + if (!entryInfo->getIsInlined()) + { + FileInode* inode; + auto openRes = this->fileStore.openFile(entryInfo, accessFlags, inode, + /* loadFromDisk */ true, bypassAccessCheck); + outInode = {inode, nullptr}; + return openRes; + } + + // not in global map, now per directory and also try to load from disk + std::string parentEntryID = entryInfo->getParentEntryID(); + // Note: We assume, that if the file is buddy mirrored, the parent is mirrored, too + bool isBuddyMirrored = entryInfo->getIsBuddyMirrored(); + + DirInode* subDir = referenceDirUnlocked(parentEntryID, isBuddyMirrored, false); + if (!subDir) + return FhgfsOpsErr_INTERNAL; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + FileInode* inode; + FhgfsOpsErr retVal = subDir->fileStore.openFile(entryInfo, accessFlags, inode, + /* loadFromDisk */ true, bypassAccessCheck); + outInode = {inode, subDir}; + + if (!outInode) + { + subDirLock.unlock(); + releaseDirUnlocked(parentEntryID); + return retVal; + } + + if (outInode->getIsInlined()) + { + outInode->incParentRef(parentEntryID); + // Do not release dir here, we are returning the inode referenced in subDirs fileStore! + + return retVal; + } + + // If execution reaches this point then following holds true: + // 1. The file inode is non-inlined (validated from on-disk metadata). + // 2. The entryInfo received from client has stale value of inlined flag (i.e. true). + // 3. The file inode reference exists in dir-specific store. + // + // According to existing design, non-inlined file inodes should always be referenced + // in the global inode store. Therefore, the following additional steps are performed + // to maintain consistency with the aforementioned design: + // 1. Close the file in dir specific store and release parent directory reference + // 2. Release meta read-lock and call tryOpenFileWriteLocked() to open the + // file in global store. + + unsigned numHardlinks; // ignored here! + unsigned numInodeRefs; // ignored here! + bool lastWriterClosed; // ignored here! + + subDir->fileStore.closeFile(entryInfo, outInode.get(), accessFlags, &numHardlinks, + &numInodeRefs, lastWriterClosed); + + subDirLock.unlock(); + releaseDirUnlocked(parentEntryID); + + lock.unlock(); // U N L O C K + return tryOpenFileWriteLocked(entryInfo, accessFlags, bypassAccessCheck, outInode); +} + +/** + * @param accessFlags OPENFILE_ACCESS_... flags + * @param outNumHardlinks for quick on-close unlink check + * @param outNumRef also for on-close unlink check + * @param outLastWriterClosed set to true when last writer closes file + */ +void MetaStore::closeFile(EntryInfo* entryInfo, MetaFileHandle inode, unsigned accessFlags, + unsigned* outNumHardlinks, unsigned* outNumRefs, bool& outLastWriterClosed) +{ + const char* logContext = "Close file"; + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + // now release (and possible free (delete) the inode if not further referenced) + + // first try global metaStore map + bool closeRes = this->fileStore.closeFile(entryInfo, inode.get(), accessFlags, + outNumHardlinks, outNumRefs, outLastWriterClosed); + if (closeRes) + return; + + // not in global /store/map, now per directory + + // Note: We assume, that if the file is buddy mirrored, the parent is mirrored, too + DirInode* subDir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), false); + if (!subDir) + return; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + inode->decParentRef(); /* Already decrease it here, as the inode might get destroyed + * in closeFile(). The counter is very important if there is + * another open reference on this file and if the file is unlinked + * while being open */ + + closeRes = subDir->fileStore.closeFile(entryInfo, inode.get(), accessFlags, + outNumHardlinks, outNumRefs, outLastWriterClosed); + if (!closeRes) + { + LOG_DEBUG(logContext, Log_SPAM, "File not open: " + entryInfo->getEntryID() ); + IGNORE_UNUSED_VARIABLE(logContext); + } + + subDirLock.unlock(); + releaseDirUnlocked(entryInfo->getParentEntryID()); + + // we kept another dir reference in openFile(), so release it here + releaseDirUnlocked(entryInfo->getParentEntryID()); +} + +/** + * get statData of a DirInode or FileInode. + * + * @param outParentNodeID maybe NULL (default). Its value for FileInodes is always 0, as + * the value is undefined for files due to possible hard links. + * @param outParentEntryID, as with outParentNodeID it is undefined for files + */ +FhgfsOpsErr MetaStore::stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData, + NumNodeID* outParentNodeID, std::string* outParentEntryID) +{ + FhgfsOpsErr statRes = FhgfsOpsErr_PATHNOTEXISTS; + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + if (entryInfo->getEntryType() == DirEntryType_DIRECTORY) + { // entry is a dir + return dirStore.stat(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored(), outStatData, + outParentNodeID, outParentEntryID); + } + + // entry is any type, but a directory, e.g. a regular file + if (outParentNodeID) + { // no need to set these for a regular file + *outParentNodeID = NumNodeID(); + } + + // first check if the inode is referenced in the global store + // if inode is non-inlined then forceLoad it from disk if it is not + // already present in global fileStore (similar to MetaStore::openFile()) + statRes = fileStore.stat(entryInfo, !entryInfo->getIsInlined(), outStatData); + + if (statRes != FhgfsOpsErr_PATHNOTEXISTS) + return statRes; + + // not in global /store/map, now per directory + + // Note: We assume, that if the file is buddy mirrored, the parent is mirrored, too + DirInode* subDir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), false); + if (likely(subDir)) + { + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + statRes = subDir->fileStore.stat(entryInfo, loadFromDisk, outStatData); + + subDirLock.unlock(); + releaseDirUnlocked(entryInfo->getParentEntryID()); + } + + return statRes; +} + +FhgfsOpsErr MetaStore::setAttr(EntryInfo* entryInfo, int validAttribs, SettableFileAttribs* attribs) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return setAttrUnlocked(entryInfo, validAttribs, attribs); +} + +/** + * @param validAttribs SETATTR_CHANGE_...-Flags or no flags to only update attribChangeTimeSecs + * @param attribs new attribs, may be NULL if validAttribs==0 + */ +FhgfsOpsErr MetaStore::setAttrUnlocked(EntryInfo* entryInfo, int validAttribs, + SettableFileAttribs* attribs) +{ + FhgfsOpsErr setAttrRes = FhgfsOpsErr_PATHNOTEXISTS; + + if (DirEntryType_ISDIR(entryInfo->getEntryType())) + return dirStore.setAttr(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored(), + validAttribs, attribs); + + if (this->fileStore.isInStore(entryInfo->getEntryID() ) ) + return this->fileStore.setAttr(entryInfo, validAttribs, attribs); + + // not in global /store/map, now per directory + // NOTE: we assume that if the file is mirrored, the parent dir is mirrored too + DirInode* subDir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), true); + if (likely(subDir) ) + { + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + setAttrRes = subDir->fileStore.setAttr(entryInfo, validAttribs, attribs); + + subDirLock.unlock(); + releaseDirUnlocked(entryInfo->getParentEntryID()); + } + + return setAttrRes; +} + +FhgfsOpsErr MetaStore::incDecLinkCount(EntryInfo* entryInfo, int value) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + return incDecLinkCountUnlocked(entryInfo, value); +} + +/** + * Update link count value in file inode + */ +FhgfsOpsErr MetaStore::incDecLinkCountUnlocked(EntryInfo* entryInfo, int value) +{ + auto [inode, retVal] = referenceFileUnlocked(entryInfo); + if (unlikely(!inode)) + return retVal; + + if (!inode->incDecNumHardLinks(entryInfo, value)) + retVal = FhgfsOpsErr_INTERNAL; + else + retVal = FhgfsOpsErr_SUCCESS; + + releaseFileUnlocked(entryInfo->getParentEntryID(), inode); + return retVal; +} + +/** + * Set / update the parent information of a dir inode + */ +FhgfsOpsErr MetaStore::setDirParent(EntryInfo* entryInfo, NumNodeID parentNodeID) +{ + if ( unlikely(!DirEntryType_ISDIR(entryInfo->getEntryType() )) ) + return FhgfsOpsErr_INTERNAL; + + const std::string& dirID = entryInfo->getEntryID(); + const bool isBuddyMirrored = entryInfo->getIsBuddyMirrored(); + + DirInode* dir = referenceDir(dirID, isBuddyMirrored, true); + if ( !dir ) + return FhgfsOpsErr_PATHNOTEXISTS; + + // also update the time stamps + FhgfsOpsErr setRes = dir->setDirParentAndChangeTime(entryInfo, parentNodeID); + + releaseDir(dirID); + + return setRes; +} + +/** + * Create a File (dentry + inlined-inode) from an existing inlined inode + * Create a dentry (in Ver-3 format) from existing dentry (if inode was deinlined) + * + * @param dir Already needs to be locked by the caller. + * @param inode Take values from this inode to create the new file. Object will be destroyed + * here. + */ +FhgfsOpsErr MetaStore::mkMetaFileUnlocked(DirInode& dir, const std::string& entryName, + EntryInfo* entryInfo, FileInode* inode) +{ + App* app = Program::getApp(); + Node& localNode = app->getLocalNode(); + DirEntryType entryType = entryInfo->getEntryType(); + const std::string& entryID = inode->getEntryID(); + + NumNodeID ownerNodeID; + if (entryInfo->getIsInlined()) + { + ownerNodeID = inode->getIsBuddyMirrored() ? + NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) : localNode.getNumID(); + } + else + { + // owner node may not be same as local node for files having deinlined inode + ownerNodeID = entryInfo->getOwnerNodeID(); + } + + DirEntry newDentry (entryType, entryName, entryID, ownerNodeID); + + if (inode->getIsBuddyMirrored()) + newDentry.setBuddyMirrorFeatureFlag(); + + if (entryInfo->getIsInlined()) + { + // only set inode data if we are dealing with inlined inode(s) + FileInodeStoreData inodeDiskData(entryID, inode->getInodeDiskData() ); + inodeDiskData.setInodeFeatureFlags(inode->getFeatureFlags() ); + newDentry.setFileInodeData(inodeDiskData); + inodeDiskData.setPattern(NULL); /* pattern now owned by newDentry, so make sure it won't be + * deleted on inodeMetadata object destruction */ + } + + // create a dir-entry + FhgfsOpsErr makeRes = dir.makeDirEntryUnlocked(&newDentry); + + // save RSTs to disk + if ((makeRes == FhgfsOpsErr_SUCCESS) && inode->getIsRstAvailable()) + { + // Update isInlined flag and parentDirID in inode to ensure correct metafile path computation: + // - isInlined: Usually set during inode data load from disk but here it needs to be updated + // now for deriving correct metafile path + // - parentDirID: Reflects the new parent directory after rename + // Both of above must be updated before storing RSTs + if (entryInfo->getIsInlined()) + inode->setIsInlined(true); + + entryInfo->setParentEntryID(dir.getID()); + + inode->storeRemoteStorageTargetUnlocked(entryInfo); + } + + delete inode; + + return makeRes; +} + +/** + * Create a new File (directory-entry with inlined inode) + * + * @param rstInfo must be provided by caller (can be invalid though) + * @param stripePattern must be provided; will be assigned to given outInodeData. + * @param outEntryInfo will not be set if NULL (caller not interested) + * @param outInodeData will not be set if NULL (caller not interested) + */ +FhgfsOpsErr MetaStore::mkNewMetaFile(DirInode& dir, MkFileDetails* mkDetails, + std::unique_ptr stripePattern, RemoteStorageTarget* rstInfo, + EntryInfo* outEntryInfo, FileInodeStoreData* outInodeData) +{ + UniqueRWLock metaLock(rwlock, SafeRWLock_READ); + UniqueRWLock dirLock(dir.rwlock, SafeRWLock_WRITE); + + const char* logContext = "Make New Meta File"; + App* app = Program::getApp(); + Config* config = app->getConfig(); + Node& localNode = app->getLocalNode(); + MirrorBuddyGroupMapper* metaBuddyGroupMapper = app->getMetaBuddyGroupMapper(); + + const std::string& newEntryID = mkDetails->newEntryID.empty() ? + StorageTk::generateFileID(localNode.getNumID() ) : mkDetails->newEntryID; + + const std::string& parentEntryID = dir.getID(); + + NumNodeID ownerNodeID = dir.getIsBuddyMirrored() ? + NumNodeID(metaBuddyGroupMapper->getLocalGroupID() ) : localNode.getNumID(); + + // refuse to create a directory before we even touch the parent. a client could send a request + // to create an S_IFDIR inode via mkfile. + if (S_ISDIR(mkDetails->mode)) + return FhgfsOpsErr_INVAL; + + // load DirInode on demand if required, we need it now + if (!dir.loadIfNotLoadedUnlocked()) + return FhgfsOpsErr_PATHNOTEXISTS; + + CharVector aclXAttr; + bool needsACL; + if (config->getStoreClientACLs()) + { + // Find out if parent dir has an ACL. + FhgfsOpsErr aclXAttrRes; + + std::tie(aclXAttrRes, aclXAttr, std::ignore) = dir.getXAttr(nullptr, + PosixACL::defaultACLXAttrName, XATTR_SIZE_MAX); + + if (aclXAttrRes == FhgfsOpsErr_SUCCESS) + { + // dir has a default acl. + PosixACL defaultACL; + if (!defaultACL.deserializeXAttr(aclXAttr)) + { + LogContext(logContext).log(Log_ERR, "Error deserializing directory default ACL."); + return FhgfsOpsErr_INTERNAL; + } + else + { + if (!defaultACL.empty()) + { + // Note: This modifies mkDetails->mode as well as the ACL. + FhgfsOpsErr modeRes = defaultACL.modifyModeBits(mkDetails->mode, needsACL); + + if (modeRes != FhgfsOpsErr_SUCCESS) + return modeRes; + + if (needsACL) + defaultACL.serializeXAttr(aclXAttr); + } + else + { + mkDetails->mode &= ~mkDetails->umask; + needsACL = false; + } + } + } + else if (aclXAttrRes == FhgfsOpsErr_NODATA) + { + // Directory does not have a default ACL - subtract umask from mode bits. + mkDetails->mode &= ~mkDetails->umask; + needsACL = false; + } + else + { + LogContext(logContext).log(Log_ERR, "Error loading directory default ACL."); + return FhgfsOpsErr_INTERNAL; + } + } + else + { + needsACL = false; + } + + DirEntryType entryType = MetadataTk::posixFileTypeToDirEntryType(mkDetails->mode); + StatData statData(mkDetails->mode, mkDetails->userID, mkDetails->groupID, + stripePattern->getAssignedNumTargets(), mkDetails->createTime); + + unsigned origParentUID = dir.getUserIDUnlocked(); // new file, we use the parent UID + + unsigned fileInodeFlags; + if (dir.getIsBuddyMirrored()) + fileInodeFlags = FILEINODE_FEATURE_BUDDYMIRRORED; + else + fileInodeFlags = 0; + + // (note: inodeMetaData constructor clones stripePattern) + FileInodeStoreData inodeMetaData(newEntryID, &statData, stripePattern.get(), fileInodeFlags, + origParentUID, parentEntryID, FileInodeOrigFeature_TRUE); + + DirEntry newDentry(entryType, mkDetails->newName, newEntryID, ownerNodeID); + + if (dir.getIsBuddyMirrored()) + newDentry.setBuddyMirrorFeatureFlag(); // buddy mirroring is inherited + + newDentry.setFileInodeData(inodeMetaData); + inodeMetaData.setPattern(NULL); /* cloned pattern now belongs to newDentry, so make sure it won't + be deleted in inodeMetaData destructor */ + + // create a dir-entry with inlined inodes + FhgfsOpsErr makeRes = dir.makeDirEntryUnlocked(&newDentry); + + if(makeRes == FhgfsOpsErr_SUCCESS) + { // new entry successfully created + if (outInodeData) + { // set caller's outInodeData + outInodeData->setInodeStatData(statData); + outInodeData->setPattern(stripePattern.release()); // (will be deleted with outInodeData) + outInodeData->setEntryID(newEntryID); + } + + } + + unsigned entryInfoFlags = ENTRYINFO_FEATURE_INLINED; + if (dir.getIsBuddyMirrored()) + entryInfoFlags |= ENTRYINFO_FEATURE_BUDDYMIRRORED; + + EntryInfo newEntryInfo(ownerNodeID, parentEntryID, newEntryID, mkDetails->newName, entryType, + entryInfoFlags); + + // apply access ACL calculated from default ACL + if (needsACL) + { + FhgfsOpsErr setXAttrRes = dir.setXAttr(&newEntryInfo, PosixACL::accessACLXAttrName, aclXAttr, + 0, false); + + if (setXAttrRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_ERR, "Error setting file ACL."); + makeRes = FhgfsOpsErr_INTERNAL; + } + } + + // only proceed with RST operations if rstInfo has valid version + if (rstInfo && !rstInfo->hasInvalidVersion() && (makeRes == FhgfsOpsErr_SUCCESS)) + { + // reference newly created file + auto [fileInode, referenceRes] = referenceFileUnlocked(dir, &newEntryInfo); + if (likely(fileInode)) + { + FhgfsOpsErr setRstRes = fileInode->setRemoteStorageTarget(&newEntryInfo, *rstInfo); + if (setRstRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, "Failed to set remote storage targets for " + "entryID: " + newEntryInfo.getEntryID() + ". RST might be invalid."); + } + releaseFileUnlocked(dir, fileInode); + } + else + { + // critical error: we cannot proceed with RSTs as inode can't be referenced + LogContext(logContext).logErr("Unable to reference inode. entryID: " + + newEntryInfo.getEntryID() + ". RST operation aborted."); + + // consider file creation failed if we can't reference the inode + makeRes = FhgfsOpsErr_INTERNAL; + } + } + + if (outEntryInfo) + *outEntryInfo = newEntryInfo; + + return makeRes; +} + + +FhgfsOpsErr MetaStore::makeDirInode(DirInode& inode) +{ + return inode.storePersistentMetaData(); +} + +FhgfsOpsErr MetaStore::makeDirInode(DirInode& inode, const CharVector& defaultACLXAttr, + const CharVector& accessACLXAttr) +{ + return inode.storePersistentMetaData(defaultACLXAttr, accessACLXAttr); +} + + +FhgfsOpsErr MetaStore::removeDirInode(const std::string& entryID, bool isBuddyMirrored) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return dirStore.removeDirInode(entryID, isBuddyMirrored); +} + +/** + * Unlink a non-inlined file inode + * + * Note: Specialy case without an entryInfo, for fsck only! + */ +FhgfsOpsErr MetaStore::fsckUnlinkFileInode(const std::string& entryID, bool isBuddyMirrored) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + // generic code needs an entryInfo, but most values can be empty for non-inlined inodes + NumNodeID ownerNodeID; + std::string parentEntryID; + std::string fileName; + DirEntryType entryType = DirEntryType_REGULARFILE; + int flags = 0; + + EntryInfo entryInfo(ownerNodeID, parentEntryID, entryID, fileName, entryType, flags); + + if (isBuddyMirrored) + entryInfo.setBuddyMirroredFlag(true); + + return this->fileStore.unlinkFileInode(&entryInfo, NULL); +} + +/** + * @param subDir may be NULL and then needs to be referenced + */ +FhgfsOpsErr MetaStore::unlinkInodeUnlocked(EntryInfo* entryInfo, DirInode* subDir, + std::unique_ptr* outInode) +{ + if (this->fileStore.isInStore(entryInfo->getEntryID())) + return fileStore.unlinkFileInode(entryInfo, outInode); + + if (subDir) + return subDir->fileStore.unlinkFileInode(entryInfo, outInode); + + // not in global /store/map, now per directory + + // Note: We assume, that if the file is buddy mirrored, the parent is mirrored, too + subDir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), false); + if (!subDir) + return FhgfsOpsErr_PATHNOTEXISTS; + + UniqueRWLock subDirLock(subDir->rwlock, SafeRWLock_READ); + + FhgfsOpsErr unlinkRes = subDir->fileStore.unlinkFileInode(entryInfo, outInode); + + // we can release the DirInode here, as the FileInode is not supposed to be in the + // DirInodes FileStore anymore + subDirLock.unlock(); + releaseDirUnlocked(entryInfo->getParentEntryID()); + + return unlinkRes; +} + + +/** + * @param outFile will be set to the unlinked file and the object must then be deleted by the caller + * (can be NULL if the caller is not interested in the file) + */ +FhgfsOpsErr MetaStore::unlinkInode(EntryInfo* entryInfo, std::unique_ptr* outInode) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return unlinkInodeUnlocked(entryInfo, NULL, outInode); +} + +/** + * need the following locks: + * this->rwlock: SafeRWLock_WRITE + * subDir: reference + * subdir->rwlock: SafeRWLock_WRITE + * + * note: caller needs to delete storage chunk files. E.g. via MsgHelperUnlink::unlinkLocalFile() + */ +FhgfsOpsErr MetaStore::unlinkFileUnlocked(DirInode& subdir, const std::string& fileName, + std::unique_ptr* outInode, EntryInfo* outEntryInfo, bool& outWasInlined, + unsigned& outNumHardlinks) +{ + FhgfsOpsErr retVal; + + std::unique_ptr dirEntry(subdir.dirEntryCreateFromFileUnlocked(fileName)); + if (!dirEntry) + return FhgfsOpsErr_PATHNOTEXISTS; + + // dirEntry exists time to make sure we have loaded subdir + bool loadRes = subdir.loadIfNotLoadedUnlocked(); + if (unlikely(!loadRes) ) + return FhgfsOpsErr_INTERNAL; // if dirEntry exists, subDir also has to exist! + + // set the outEntryInfo + int additionalFlags = 0; + std::string parentEntryID = subdir.getID(); + dirEntry->getEntryInfo(parentEntryID, additionalFlags, outEntryInfo); + + if (dirEntry->getIsInodeInlined() ) + { // inode is inlined into the dir-entry + retVal = unlinkDirEntryWithInlinedInodeUnlocked(fileName, subdir, dirEntry.get(), + DirEntry_UNLINK_ID_AND_FILENAME, outInode, outNumHardlinks); + + outWasInlined = true; + } + else + { // inode and dir-entry are separated fileStore + retVal = unlinkDentryAndInodeUnlocked(fileName, subdir, dirEntry.get(), + DirEntry_UNLINK_ID_AND_FILENAME, outInode, outNumHardlinks); + + outWasInlined = false; + } + + return retVal; +} + +/** + * Unlinks the entire file, so dir-entry and inode. + * + * @param fileName friendly name + * @param outEntryInfo contains the entryInfo of the unlinked file + * @param outInode will be set to the unlinked (owned) file and the object and + * storage server fileStore must then be deleted by the caller; even if success is returned, + * this might be NULL (e.g. because the file is in use and was added to the disposal directory); + * can be NULL if the caller is not interested in the file + * @param outNumHardlinks will be set to the number of hardlinks the file had before the unlink + * operation. This is mainly needed for event logging purposes. + * @return normal fhgfs error code, normally succeeds even if a file was open; special case is + * when this is called to unlink a file with the disposalDir dirID, then an open file will + * result in a inuse-error (required for online_cfg mode=dispose) + * + * note: caller needs to delete storage chunk files. E.g. via MsgHelperUnlink::unlinkLocalFile() + */ +FhgfsOpsErr MetaStore::unlinkFile(DirInode& dir, const std::string& fileName, + EntryInfo* outEntryInfo, std::unique_ptr* outInode, unsigned& outNumHardlinks) +{ + const char* logContext = "Unlink File"; + FhgfsOpsErr retVal = FhgfsOpsErr_PATHNOTEXISTS; + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + UniqueRWLock subDirLock(dir.rwlock, SafeRWLock_WRITE); + + bool wasInlined; + retVal = unlinkFileUnlocked(dir, fileName, outInode, outEntryInfo, wasInlined, outNumHardlinks); + + subDirLock.unlock(); + + /* Give up the read-lock here, unlinkInodeLater() will aquire a write lock. We already did + * most of the work, just possible back linking of the inode to the disposal dir is missing. + * As our important work is done, we can also risk to give up the read lock. */ + lock.unlock(); // U N L O C K + + if (retVal != FhgfsOpsErr_INUSE) + return retVal; + + if (dir.getID() != META_DISPOSALDIR_ID_STR && dir.getID() != META_MIRRORDISPOSALDIR_ID_STR) + { // we already successfully deleted the dentry, so all fine for the user + retVal = FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr laterRes = unlinkInodeLater(outEntryInfo, wasInlined); + if (laterRes == FhgfsOpsErr_AGAIN) + { + /* So the inode was not referenced in memory anymore and probably close() already deleted + * it. Just make sure here it really does not exist anymore */ + FhgfsOpsErr inodeUnlinkRes = unlinkInode(outEntryInfo, outInode); + + if (unlikely((inodeUnlinkRes != FhgfsOpsErr_PATHNOTEXISTS) && + (inodeUnlinkRes != FhgfsOpsErr_SUCCESS) ) ) + { + LogContext(logContext).logErr(std::string("Failed to unlink inode. Error: ") + + boost::lexical_cast(inodeUnlinkRes)); + + retVal = inodeUnlinkRes; + } + } + + return retVal; +} + +/** + * + * Decrement nlink count and remove file inode if link count reaches zero + */ +FhgfsOpsErr MetaStore::unlinkFileInode(EntryInfo* delFileInfo, std::unique_ptr* outInode, + unsigned& outNumHardlinks) +{ + const char* logContext = "Unlink File Inode"; + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr retVal; + FhgfsOpsErr isUnlinkable = this->fileStore.isUnlinkable(delFileInfo); + + if (isUnlinkable != FhgfsOpsErr_INUSE && isUnlinkable != FhgfsOpsErr_SUCCESS) + return isUnlinkable; + + if (isUnlinkable == FhgfsOpsErr_INUSE) + { + FileInode* inode = this->fileStore.referenceLoadedFile(delFileInfo->getEntryID()); + if (unlikely(!inode)) + { + LogContext(logContext).logErr("Busy/Inuse file inode found but failed to reference it." + " EntryID: " + delFileInfo->getEntryID()); + return FhgfsOpsErr_INTERNAL; + } + + outNumHardlinks = inode->getNumHardlinks(); + + if (outNumHardlinks < 2) + { + retVal = FhgfsOpsErr_INUSE; + } + else + { + retVal = FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr decRes = this->fileStore.decLinkCount(*inode, delFileInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count. entryID: " + + delFileInfo->getEntryID()); + } + + this->fileStore.releaseFileInode(inode); + } + else + { + // File is not IN_USE so decrement link count and if link count reach zero then + // delete inode file from disk + auto [inode, referenceRes] = referenceFileUnlocked(delFileInfo); + if (unlikely(!inode)) + return referenceRes; + + outNumHardlinks = inode->getNumHardlinks(); + if (outNumHardlinks > 1) + { + FhgfsOpsErr decRes = this->fileStore.decLinkCount(*inode, delFileInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count. entryID: " + + delFileInfo->getEntryID()); + } + + releaseFileUnlocked(delFileInfo->getParentEntryID(), inode); + retVal = FhgfsOpsErr_SUCCESS; + } + else + { + // release inode reference before calling unlinkFileInode() + // because it checks for IN_USE situation + releaseFileUnlocked(delFileInfo->getParentEntryID(), inode); + retVal = this->fileStore.unlinkFileInode(delFileInfo, outInode); + } + } + + if (retVal != FhgfsOpsErr_INUSE) + return retVal; + + lock.unlock(); // U N L O C K + + const std::string& parentEntryID = delFileInfo->getParentEntryID(); + if (parentEntryID != META_DISPOSALDIR_ID_STR && parentEntryID != META_MIRRORDISPOSALDIR_ID_STR) + { + retVal = FhgfsOpsErr_SUCCESS; + } + + FhgfsOpsErr laterRes = unlinkInodeLater(delFileInfo, false); + if (laterRes == FhgfsOpsErr_AGAIN) + { + // so the inode was not referenced in memory anymore and probably close() already + // deleted it. Just make sure here that it really doesn't exists anymore. + FhgfsOpsErr inodeUnlinkRes = unlinkInode(delFileInfo, outInode); + + if (unlikely((inodeUnlinkRes != FhgfsOpsErr_PATHNOTEXISTS) && + (inodeUnlinkRes != FhgfsOpsErr_SUCCESS) ) ) + { + LogContext(logContext).logErr(std::string("Failed to unlink inode. Error: ") + + boost::lexical_cast(inodeUnlinkRes)); + + retVal = inodeUnlinkRes; + } + } + + return retVal; +} + +/** + * Unlink a dirEntry with an inlined inode + */ +FhgfsOpsErr MetaStore::unlinkDirEntryWithInlinedInodeUnlocked(const std::string& entryName, + DirInode& subDir, DirEntry* dirEntry, unsigned unlinkTypeFlags, + std::unique_ptr* outInode, unsigned& outNumHardlinks) +{ + const char* logContext = "Unlink DirEntry with inlined inode"; + + if (outInode) + outInode->reset(); + + std::string parentEntryID = subDir.getID(); + + // when we are here, we no the inode is inlined into the dirEntry + int flags = ENTRYINFO_FEATURE_INLINED; + + EntryInfo entryInfo; + dirEntry->getEntryInfo(parentEntryID, flags, &entryInfo); + + FhgfsOpsErr isUnlinkable = isFileUnlinkable(subDir, &entryInfo); + + // note: FhgfsOpsErr_PATHNOTEXISTS cannot happen with isUnlinkable(id, false) + + if (isUnlinkable != FhgfsOpsErr_INUSE && isUnlinkable != FhgfsOpsErr_SUCCESS) + return isUnlinkable; + + if (isUnlinkable == FhgfsOpsErr_INUSE) + { + FhgfsOpsErr retVal; + // for some reasons we cannot unlink the inode, probably the file is opened. De-inline it. + + // *outInode stays NULL - the caller must not do anything with this inode + + MetaFileHandle inode = referenceLoadedFileUnlocked(subDir, dirEntry->getEntryID() ); + if (!inode) + { + LogContext(logContext).logErr("Bug: Busy inode found, but failed to reference it." + "FileName: " + entryName + ", EntryID: " + dirEntry->getEntryID()); + return FhgfsOpsErr_INTERNAL; + } + + outNumHardlinks = inode->getNumHardlinks(); + + if (outNumHardlinks > 1) + unlinkTypeFlags &= ~DirEntry_UNLINK_ID; + + bool unlinkError = subDir.unlinkBusyFileUnlocked(entryName, dirEntry, unlinkTypeFlags); + if (unlinkError) + retVal = FhgfsOpsErr_INTERNAL; + else + { // unlink success + if (outNumHardlinks < 2) + { + retVal = FhgfsOpsErr_INUSE; + + // The inode is not inlined anymore, update the in-memory objects + + dirEntry->unsetInodeInlined(); + inode->setIsInlined(false); + } + else + { // hard link exists, the inode is still inlined + retVal = FhgfsOpsErr_SUCCESS; + } + + /* Decrease the link count, but as dentry and inode are still hard linked objects, + * it also unsets the DENTRY_FEATURE_INODE_INLINE on disk */ + FhgfsOpsErr decRes = subDir.fileStore.decLinkCount(*inode, &entryInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count!" + " parentID: " + entryInfo.getParentEntryID() + + " entryID: " + entryInfo.getEntryID() + + " fileName: " + entryName); + } + } + + releaseFileUnlocked(subDir, inode); + + return retVal; + + /* We are done here, but the file still might be referenced in the per-dir file store. + * However, we cannot move it, as we do not have a MetaStore write-lock, but only a + * read-lock. Therefore that has to be done later on, once we have given up the read-lock. */ + } + + // here, unlinkRes == SUCCESS. + // dir-entry and inode are inlined. The file is also not opened anymore, so delete it. + + if (!(unlinkTypeFlags & DirEntry_UNLINK_ID)) + { + // only the dentry-by-name is left, so no need to care about the inode + return subDir.unlinkDirEntryUnlocked(entryName, dirEntry, unlinkTypeFlags); + } + + auto [inode, referenceRes] = referenceFileUnlocked(subDir, &entryInfo); + if (!inode) + return referenceRes; + + outNumHardlinks = inode->getNumHardlinks(); + + if (outNumHardlinks > 1) + unlinkTypeFlags &= ~DirEntry_UNLINK_ID; + + FhgfsOpsErr retVal = subDir.unlinkDirEntryUnlocked(entryName, dirEntry, + unlinkTypeFlags); + + if (retVal == FhgfsOpsErr_SUCCESS && outNumHardlinks > 1) + { + FhgfsOpsErr decRes = subDir.fileStore.decLinkCount(*inode, &entryInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count!" + " parentID: " + entryInfo.getParentEntryID() + + " entryID: " + entryInfo.getEntryID() + + " entryNameName: " + entryName); + } + } + + if (outInode && outNumHardlinks < 2) + { + inode->setIsInlined(false); // last dirEntry gone, so not inlined anymore + outInode->reset(inode->clone()); + } + + releaseFileUnlocked(subDir, inode); + + return retVal; +} + +/** + * Unlink seperated dirEntry and Inode + */ +FhgfsOpsErr MetaStore::unlinkDentryAndInodeUnlocked(const std::string& fileName, + DirInode& subdir, DirEntry* dirEntry, unsigned unlinkTypeFlags, + std::unique_ptr* outInode, unsigned& outNumHardlinks) +{ + const char* logContext = "Unlink DirEntry with non-inlined inode"; + + // unlink dirEntry first + FhgfsOpsErr retVal = subdir.unlinkDirEntryUnlocked(fileName, dirEntry, unlinkTypeFlags); + + if (retVal != FhgfsOpsErr_SUCCESS) + return retVal; + + // directory-entry was removed => unlink inode + + // if we are here we know the dir-entry does not inline the inode + + int addionalEntryInfoFlags = 0; + std::string parentEntryID = subdir.getID(); + EntryInfo entryInfo; + + dirEntry->getEntryInfo(parentEntryID, addionalEntryInfoFlags, &entryInfo); + + FhgfsOpsErr isUnlinkable = isFileUnlinkable(subdir, &entryInfo); + if (isUnlinkable != FhgfsOpsErr_INUSE && isUnlinkable != FhgfsOpsErr_SUCCESS) + return isUnlinkable; + + if (isUnlinkable == FhgfsOpsErr_INUSE) + { + MetaFileHandle inode = referenceLoadedFileUnlocked(subdir, dirEntry->getEntryID()); + if (unlikely(!inode)) + { + LogContext(logContext).logErr("Busy/Inuse file inode found but failed to reference it." + "FileName: " + fileName + ", EntryID: " + dirEntry->getEntryID()); + return FhgfsOpsErr_INTERNAL; + } + + outNumHardlinks = inode->getNumHardlinks(); + if (outNumHardlinks > 1) + { + retVal = FhgfsOpsErr_SUCCESS; + } + else + { + retVal = FhgfsOpsErr_INUSE; + } + + FhgfsOpsErr decRes = subdir.fileStore.decLinkCount(*inode, &entryInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count." + " parentEntryID: " + parentEntryID + + ", entryID: " + entryInfo.getEntryID() + + ", entryName: " + fileName); + } + + releaseFileUnlocked(subdir, inode); + return retVal; + } + else + { + auto [inode, referenceRes] = referenceFileUnlocked(subdir, &entryInfo); + if (unlikely(!inode)) + return referenceRes; + + outNumHardlinks = inode->getNumHardlinks(); + if (outNumHardlinks > 1) + { + FhgfsOpsErr decRes = subdir.fileStore.decLinkCount(*inode, &entryInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr("Failed to decrease the link count." + " parentEntryID: " + parentEntryID + + ", entryID: " + entryInfo.getEntryID() + + ", entryName: " + fileName); + } + + releaseFileUnlocked(subdir, inode); + return FhgfsOpsErr_SUCCESS; + } + else + { + releaseFileUnlocked(subdir, inode); + return unlinkInodeUnlocked(&entryInfo, &subdir, outInode); + } + } +} + +/** + * Adds the entry to the disposal directory for later (asynchronous) disposal. + */ +FhgfsOpsErr MetaStore::unlinkInodeLater(EntryInfo* entryInfo, bool wasInlined) +{ + UniqueRWLock lock(rwlock, SafeRWLock_WRITE); + return unlinkInodeLaterUnlocked(entryInfo, wasInlined); +} + +/** + * Adds the inode (with a new dirEntry) to the disposal directory for later + * (on-close or asynchronous) disposal. + * + * Note: We are going to set outInode if we determine that the file was closed in the mean time + * and all references to this inode shall be deleted. + */ +FhgfsOpsErr MetaStore::unlinkInodeLaterUnlocked(EntryInfo* entryInfo, bool wasInlined) +{ + // Note: We must not try to unlink the inode here immediately, because then the local versions + // of the data-object (on the storage nodes) would never be deleted. + + App* app = Program::getApp(); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + const std::string& parentEntryID = entryInfo->getParentEntryID(); + const std::string& entryID = entryInfo->getEntryID(); + bool isBuddyMirrored = entryInfo->getIsBuddyMirrored(); + + DirInode* disposalDir = isBuddyMirrored + ? app->getBuddyMirrorDisposalDir() + : app->getDisposalDir(); + const std::string& disposalID = disposalDir->getID(); + + /* This requires a MetaStore write lock, therefore only can be done here and not in common + * unlink code, as the common unlink code only has a MetaStore read-lock. */ + if (!this->fileStore.isInStore(entryID) ) + { + // Note: we assume that if the inode is mirrored, the parent is mirrored, too + bool moveRes = moveReferenceToMetaFileStoreUnlocked(parentEntryID, isBuddyMirrored, entryID); + if (!moveRes) + return FhgfsOpsErr_INTERNAL; /* a critical error happened, better don't do + * anything with this inode anymore */ + } + + entryInfo->setParentEntryID(disposalID); // update the dirID to disposalDir + + // set inode nlink count to 0. + // we assume the inode is typically already referenced by someone (otherwise we wouldn't need to + // unlink later) and this allows for faster checking on-close than the disposal dir check. + + // do not load the inode from disk first. + FileInode* inode = this->fileStore.referenceLoadedFile(entryID); + if (!inode) + { + /* If we cannot reference the inode from memory, we raced with close(). + * This is possible as we gave up all locks in unlinkFile() and it means the inode/file shall + * be deleted entirely on disk now. */ + + entryInfo->setInodeInlinedFlag(false); + return FhgfsOpsErr_AGAIN; + } + + int linkCount = inode->getNumHardlinks();; + + this->fileStore.releaseFileInode(inode); + + /* Now link to the disposal-dir if required. If the inode was inlined into the dentry, + * the dentry/inode unlink code already links to the disposal dir and we do not need to do + * this work. */ + if (!wasInlined && linkCount == 0) + { + const std::string& inodePath = MetaStorageTk::getMetaInodePath( + isBuddyMirrored + ? app->getBuddyMirrorInodesPath()->str() + : app->getInodesPath()->str(), + entryID); + + /* NOTE: If we are going to have another inode-format, than the current + * inode-inlined into the dentry, we need to add code for that here. */ + + disposalDir->linkFileInodeToDir(inodePath, entryID); // use entryID as file name + // ignore the return code here, as we cannot do anything about it anyway. + } + + return retVal; +} + +/** + * Reads all inodes from the given inodes storage hash subdir. + * + * Note: This is intended for use by Fsck only. + * + * Note: Offset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as outNewOffset. + * Note: You have reached the end of the directory when + * "outDirInodes->size() + outFileInodes->size() != maxOutInodes". + * + * + * @param hashDirNum number of a hash dir in the "entries" storage subdir + * @param lastOffset zero-based offset; represents the native local fs offset; + * @param outDirInodes the read directory inodes in the format fsck saves them + * @param outFileInodes the read file inodes in the format fsck saves them + * @param outNewOffset is only valid if return value indicates success. + */ +FhgfsOpsErr MetaStore::getAllInodesIncremental(unsigned hashDirNum, int64_t lastOffset, + unsigned maxOutInodes, FsckDirInodeList* outDirInodes, FsckFileInodeList* outFileInodes, + int64_t* outNewOffset, bool isBuddyMirrored) +{ + const char* logContext = "MetaStore (get all inodes inc)"; + + App* app = Program::getApp(); + MirrorBuddyGroupMapper* bgm = app->getMetaBuddyGroupMapper(); + + if (isBuddyMirrored && + (bgm->getLocalBuddyGroup().secondTargetID == app->getLocalNode().getNumID().val() + || bgm->getLocalGroupID() == 0)) + return FhgfsOpsErr_SUCCESS; + + NumNodeID rootNodeNumID = app->getMetaRoot().getID(); + NumNodeID localNodeNumID = isBuddyMirrored + ? NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID()) + : app->getLocalNode().getNumID(); + + StringList entryIDs; + unsigned firstLevelHashDir; + unsigned secondLevelHashDir; + StorageTk::splitHashDirs(hashDirNum, &firstLevelHashDir, &secondLevelHashDir); + + FhgfsOpsErr readRes = getAllEntryIDFilesIncremental(firstLevelHashDir, secondLevelHashDir, + lastOffset, maxOutInodes, &entryIDs, outNewOffset, isBuddyMirrored); + + if (readRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).logErr( + "Failed to read inodes from hash dirs; " + "HashDir Level 1: " + StringTk::uintToStr(firstLevelHashDir) + "; " + "HashDir Level 2: " + StringTk::uintToStr(firstLevelHashDir) ); + return readRes; + } + + // the actual entry processing + for ( StringListIter entryIDIter = entryIDs.begin(); entryIDIter != entryIDs.end(); + entryIDIter++ ) + { + const std::string& entryID = *entryIDIter; + + // now try to reference the file and see what we got + MetaFileHandle fileInode; + DirInode* dirInode = NULL; + + referenceInode(entryID, isBuddyMirrored, fileInode, dirInode); + + if (dirInode) + { + // entry is a directory + std::string parentDirID; + NumNodeID parentNodeID; + dirInode->getParentInfo(&parentDirID, &parentNodeID); + + NumNodeID ownerNodeID = dirInode->getOwnerNodeID(); + + // in the unlikely case, that this is the root directory and this MDS is not the owner of + // root ignore the entry + if (unlikely( (entryID.compare(META_ROOTDIR_ID_STR) == 0) + && rootNodeNumID != localNodeNumID) ) + continue; + + // not root => get stat data and create a FsckDirInode with data + StatData statData; + dirInode->getStatData(statData); + + UInt16Vector stripeTargets; + FsckStripePatternType stripePatternType = FsckTk::stripePatternToFsckStripePattern( + dirInode->getStripePattern(), NULL, &stripeTargets); + + FsckDirInode fsckDirInode(entryID, parentDirID, parentNodeID, ownerNodeID, + statData.getFileSize(), statData.getNumHardlinks(), stripeTargets, + stripePatternType, localNodeNumID, isBuddyMirrored, true, + dirInode->getIsBuddyMirrored() != isBuddyMirrored); + + outDirInodes->push_back(fsckDirInode); + + this->releaseDir(entryID); + } + else + if (fileInode) + { + // directory not successful => must be a file-like object + + // create a FsckFileInode with data + std::string parentDirID; + NumNodeID parentNodeID; + UInt16Vector stripeTargets; + + PathInfo pathInfo; + + unsigned userID; + unsigned groupID; + + int64_t fileSize; + unsigned numHardLinks; + uint64_t numBlocks; + + StatData statData; + + fileInode->getPathInfo(&pathInfo); + + FhgfsOpsErr statRes = fileInode->getStatData(statData); + + if ( statRes == FhgfsOpsErr_SUCCESS ) + { + userID = statData.getUserID(); + groupID = statData.getGroupID(); + fileSize = statData.getFileSize(); + numHardLinks = statData.getNumHardlinks(); + numBlocks = statData.getNumBlocks(); + } + else + { // couldn't get the stat data + LogContext(logContext).logErr(std::string("Unable to stat file inode: ") + + entryID + std::string(". SysErr: ") + boost::lexical_cast(statRes)); + + userID = 0; + groupID = 0; + fileSize = 0; + numHardLinks = 0; + numBlocks = 0; + } + + StripePattern* stripePattern = fileInode->getStripePattern(); + unsigned chunkSize; + FsckStripePatternType stripePatternType = FsckTk::stripePatternToFsckStripePattern( + stripePattern, &chunkSize, &stripeTargets); + + FsckFileInode fsckFileInode(entryID, parentDirID, parentNodeID, pathInfo, userID, groupID, + fileSize, numHardLinks, numBlocks, stripeTargets, stripePatternType, chunkSize, + localNodeNumID, 0, 0, false, isBuddyMirrored, true, + fileInode->getIsBuddyMirrored() != isBuddyMirrored); + + outFileInodes->push_back(fsckFileInode); + + // parentID is absolutely irrelevant here, because we know that this inode is not inlined + this->releaseFile("", fileInode); + } + else + { // something went wrong with inode loading + + // create a dir inode as dummy + const UInt16Vector stripeTargets; + const FsckDirInode fsckDirInode(entryID, "", NumNodeID(), NumNodeID(), 0, 0, stripeTargets, + FsckStripePatternType_INVALID, localNodeNumID, isBuddyMirrored, false, false); + + outDirInodes->push_back(fsckDirInode); + } + + } // end of for loop + + return FhgfsOpsErr_SUCCESS; +} + + +/** + * Reads all raw entryID filenames from the given "inodes" storage hash subdirs. + * + * Note: This is intended for use by Fsck. + * + * Note: Offset is an internal value and should not be assumed to be just 0, 1, 2, 3, ...; + * so make sure you use either 0 (at the beginning) or something that has been returned by this + * method as outNewOffset. + * + * @param hashDirNum number of a hash dir in the "entries" storage subdir + * @param lastOffset zero-based offset; represents the native local fs offset; + * @param outEntryIDFiles the raw filenames of the entries in the given hash dir (so you will need + * to remove filename suffixes to use these as entryIDs). + * @param outNewOffset is only valid if return value indicates success. + * + */ +FhgfsOpsErr MetaStore::getAllEntryIDFilesIncremental(unsigned firstLevelhashDirNum, + unsigned secondLevelhashDirNum, int64_t lastOffset, unsigned maxOutEntries, + StringList* outEntryIDFiles, int64_t* outNewOffset, bool buddyMirrored) +{ + const char* logContext = "Inode (get entry files inc)"; + App* app = Program::getApp(); + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + uint64_t numEntries = 0; + struct dirent* dirEntry = NULL; + + const std::string inodesPath = + buddyMirrored ? app->getBuddyMirrorInodesPath()->str() + : app->getInodesPath()->str(); + + const std::string path = StorageTkEx::getMetaInodeHashDir( + inodesPath, firstLevelhashDirNum, secondLevelhashDirNum); + + + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + DIR* dirHandle = opendir(path.c_str() ); + if(!dirHandle) + { + LogContext(logContext).logErr(std::string("Unable to open entries directory: ") + + path + ". SysErr: " + System::getErrString() ); + + return FhgfsOpsErr_INTERNAL; + } + + + errno = 0; // recommended by posix (readdir(3p) ) + + // seek to offset + if(lastOffset) + seekdir(dirHandle, lastOffset); // (seekdir has no return value) + + // the actual entry reading + for( ; (numEntries < maxOutEntries) && (dirEntry = StorageTk::readdirFiltered(dirHandle) ); + numEntries++) + { + outEntryIDFiles->push_back(dirEntry->d_name); + *outNewOffset = dirEntry->d_off; + } + + if(!dirEntry && errno) + { + LogContext(logContext).logErr(std::string("Unable to fetch entries directory entry from: ") + + path + ". SysErr: " + System::getErrString() ); + } + else + { // all entries read + retVal = FhgfsOpsErr_SUCCESS; + } + + + closedir(dirHandle); + + return retVal; +} + +void MetaStore::getReferenceStats(size_t* numReferencedDirs, size_t* numReferencedFiles) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + + *numReferencedDirs = dirStore.getSize(); + *numReferencedFiles = fileStore.getSize(); +} + +void MetaStore::getCacheStats(size_t* numCachedDirs) +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + *numCachedDirs = dirStore.getCacheSize(); +} + +/** + * Asynchronous cache sweep. + * + * @return true if a cache flush was triggered, false otherwise + */ +bool MetaStore::cacheSweepAsync() +{ + UniqueRWLock lock(rwlock, SafeRWLock_READ); + return dirStore.cacheSweepAsync(); +} + +/** + * So we failed to delete chunk files and need to create a new disposal file for later cleanup. + * + * @param inode will be deleted (or owned by another object) no matter whether this succeeds or not + * + * Note: No MetaStore lock required, as the disposal dir cannot be removed. + */ +FhgfsOpsErr MetaStore::insertDisposableFile(FileInode* inode) +{ + LogContext log("MetaStore (insert disposable file)"); + App* app = Program::getApp(); + + DirInode* disposalDir = inode->getIsBuddyMirrored() + ? Program::getApp()->getBuddyMirrorDisposalDir() + : Program::getApp()->getDisposalDir(); + + UniqueRWLock metaLock(this->rwlock, SafeRWLock_READ); + UniqueRWLock dirLock(disposalDir->rwlock, SafeRWLock_WRITE); + + const std::string& fileName = inode->getEntryID(); // ID is also the file name + DirEntryType entryType = MetadataTk::posixFileTypeToDirEntryType(inode->getMode() ); + + EntryInfo entryInfo; + NumNodeID ownerNodeID = inode->getIsBuddyMirrored() ? + NumNodeID(app->getMetaBuddyGroupMapper()->getLocalGroupID() ) : app->getLocalNode().getNumID(); + entryInfo.set(ownerNodeID, "", inode->getEntryID(), "", entryType, 0); + + FhgfsOpsErr retVal = mkMetaFileUnlocked(*disposalDir, fileName, &entryInfo, inode); + if (retVal != FhgfsOpsErr_SUCCESS) + { + log.log(Log_WARNING, + std::string("Failed to create disposal file for id: ") + fileName + "; " + "Storage chunks will not be entirely deleted!"); + } + + return retVal; +} + +/** + * Retrieves entry data for a given file or directory. + * + * @param dirInode Pointer to the parent directory inode. + * @param entryName Name of the entry to retrieve data for. + * @param outInfo Pointer to store the retrieved EntryInfo. + * @param outInodeMetaData Pointer to store inode metadata (may be NULL). + * + * @return A pair of: + * - FhgfsOpsErr + * SUCCESS if the entry was found and is not referenced + * DYNAMICATTRIBSOUTDATED if the entry was found but might have outdated attributes + * PATHNOTEXISTS if the entry does not exist + * - bool: true if entry is a regular file and is currently open, false otherwise + * + * Locking: No lock must be taken already. + */ +std::pair MetaStore::getEntryData(DirInode *dirInode, const std::string& entryName, + EntryInfo* outInfo, FileInodeStoreData* outInodeMetaData) +{ + FhgfsOpsErr retVal = dirInode->getEntryData(entryName, outInfo, outInodeMetaData); + + if (retVal == FhgfsOpsErr_SUCCESS && DirEntryType_ISREGULARFILE(outInfo->getEntryType())) + { + /* Hint for the caller not to rely on outInodeMetaData, properly handling close-races + * is too difficult and in the end probably slower than to just re-get the EAs from the + * inode (fsIDs/entryID). */ + return {FhgfsOpsErr_DYNAMICATTRIBSOUTDATED, false}; + } + + if (retVal == FhgfsOpsErr_DYNAMICATTRIBSOUTDATED) + return {retVal, true}; // entry is a regular file and currently open + + return {retVal, false}; +} + +/** + * Get inode disk data for non-inlined inode + * + * @param outInodeMetaData might be NULL + * @return FhgfsOpsErr_SUCCESS if inode exists + * FhgfsOpsErr_PATHNOTEXISTS if the inode does not exist + * + * Locking: No lock must be taken already. + */ +FhgfsOpsErr MetaStore::getEntryData(EntryInfo* inEntryInfo, FileInodeStoreData* outInodeMetaData) +{ + std::unique_ptr inode(FileInode::createFromEntryInfo(inEntryInfo)); + if (unlikely(!inode)) + return FhgfsOpsErr_PATHNOTEXISTS; + + *outInodeMetaData = *(inode->getInodeDiskData()); + inode->getInodeDiskData()->setPattern(NULL); + return FhgfsOpsErr_SUCCESS; +} + +/** + * Create a hard-link within a directory. + */ +FhgfsOpsErr MetaStore::linkInSameDir(DirInode& parentDir, EntryInfo* fromFileInfo, + const std::string& fromName, const std::string& toName) +{ + const char* logContext = "link in same dir"; + + UniqueRWLock metaLock(rwlock, SafeRWLock_READ); + UniqueRWLock parentDirLock(parentDir.rwlock, SafeRWLock_WRITE); + + auto [fromFileInode, retVal] = referenceFileUnlocked(parentDir, fromFileInfo); + if (!fromFileInode) + { + goto outUnlock; + } + + if (!fromFileInode->getIsInlined() ) + { // not supported + retVal = FhgfsOpsErr_INTERNAL; + goto outReleaseInode; + } + + if (this->fileStore.isInStore(fromFileInfo->getEntryID() ) ) + { // not supported + retVal = FhgfsOpsErr_INTERNAL; + goto outReleaseInode; + } + else + { + // not in global /store/map, now per directory + + FhgfsOpsErr incRes = parentDir.fileStore.incLinkCount(*fromFileInode, fromFileInfo); + if (incRes != FhgfsOpsErr_SUCCESS) + { + retVal = FhgfsOpsErr_INTERNAL; + goto outReleaseInode; + } + + retVal = parentDir.linkFilesInDirUnlocked(fromName, *fromFileInode, toName); + if (retVal != FhgfsOpsErr_SUCCESS) + { + FhgfsOpsErr decRes = parentDir.fileStore.decLinkCount(*fromFileInode, fromFileInfo); + if (decRes != FhgfsOpsErr_SUCCESS) + LogContext(logContext).logErr("Warning: Creating the link failed and decreasing " + "the inode link count again now also failed!" + " parentDir : " + parentDir.getID() + + " entryID: " + fromFileInfo->getEntryID() + + " fileName: " + fromName); + } + } + +outReleaseInode: + releaseFileUnlocked(parentDir, fromFileInode); + +outUnlock: + parentDirLock.unlock(); + + return retVal; +} + +/** + * Create a new hardlink. + * + * 1. De-inline inode if it's inlined. + * 2. Increment link count. + * + * @param fromFileInfo Pointer to EntryInfo of the target file. + * @return Pair of: + * - FhgfsOpsErr: Operation result (SUCCESS or error). + * - unsigned: Updated hardlink count on success, 0 on failure. + */ +std::pair MetaStore::makeNewHardlink(EntryInfo* fromFileInfo) +{ + UniqueRWLock metaLock(rwlock, SafeRWLock_WRITE); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + unsigned updatedLinkCount = 0; + + // try to load an unreferenced inode from disk + std::unique_ptr fInode(FileInode::createFromEntryInfo(fromFileInfo)); + if (unlikely(!fInode)) + return {FhgfsOpsErr_PATHNOTEXISTS, 0}; + + // get inlined flag from on-disk metadata and perform inode de-inline if required + bool isInlined = fInode->getIsInlined(); + if (isInlined) + { + DirInode* dir = referenceDirUnlocked(fromFileInfo->getParentEntryID(), + fromFileInfo->getIsBuddyMirrored(), true); + + if (unlikely(!dir)) + return {FhgfsOpsErr_PATHNOTEXISTS, 0}; + + retVal = verifyAndMoveFileInodeUnlocked(*dir, fromFileInfo, MODE_DEINLINE); + releaseDirUnlocked(dir->getID()); + } + + if (retVal == FhgfsOpsErr_SUCCESS) + { + // move existing inode references from dir specific store to global store + // to make sure we always use global store for non-inlined file inode(s) + if (!this->fileStore.isInStore(fromFileInfo->getEntryID())) + { + this->moveReferenceToMetaFileStoreUnlocked(fromFileInfo->getParentEntryID(), + fromFileInfo->getIsBuddyMirrored(), fromFileInfo->getEntryID()); + } + + auto [inode, referenceRes] = referenceFileUnlocked(fromFileInfo); + if (unlikely(!inode)) + return {referenceRes, 0}; + + // ensure inode is not marked for disposal (link count must be >= 1) + if (inode->getNumHardlinks() >= 1) + { + if (!inode->incDecNumHardLinks(fromFileInfo, 1)) + retVal = FhgfsOpsErr_INTERNAL; + else + { + retVal = FhgfsOpsErr_SUCCESS; + updatedLinkCount = inode->getNumHardlinks(); + } + } + else + { + // can't create a hardlink for disposal inode (link count = 0) + retVal = FhgfsOpsErr_PATHNOTEXISTS; + } + + releaseFileUnlocked(fromFileInfo->getParentEntryID(), inode); + } + + // return operation result and updated hardlink count + return {retVal, updatedLinkCount}; +} + +/** + * Deinline or reinline file's Inode on current meta-data server. It first verifies Inode + * location (inlined or deinlined) and then performes requested inode movement if needed + * + * @returns FhgfsOpsErr_SUCCESS on success. FhgfsOpsErr_PATHNOTEXISTS if file does not exists + * FhgfsOpsErr_INTERNAL on any other error + * + * @param parentDir parent directory's inode object + * @param fileInfo entryInfo of file for which inode movement is requested + * @param moveMode requested fileInode mode (i.e. deinline or reinline) + * + */ +FhgfsOpsErr MetaStore::verifyAndMoveFileInode(DirInode& parentDir, EntryInfo* fileInfo, FileInodeMode moveMode) +{ + UniqueRWLock metaLock(rwlock, SafeRWLock_WRITE); + return verifyAndMoveFileInodeUnlocked(parentDir, fileInfo, moveMode); +} + +FhgfsOpsErr MetaStore::verifyAndMoveFileInodeUnlocked(DirInode& parentDir, EntryInfo* fileInfo, + FileInodeMode moveMode) +{ + App* app = Program::getApp(); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + if (!parentDir.loadIfNotLoadedUnlocked()) + { + return FhgfsOpsErr_PATHNOTEXISTS; + } + + DirEntry fileDentry(fileInfo->getFileName()); + if (!parentDir.getDentry(fileInfo->getFileName(), fileDentry)) + { + return FhgfsOpsErr_PATHNOTEXISTS; + } + + if (moveMode == MODE_INVALID) + return FhgfsOpsErr_INTERNAL; + + bool isInodeMoveRequired = ((moveMode == MODE_DEINLINE) && fileDentry.getIsInodeInlined()) || + ((moveMode == MODE_REINLINE) && !fileDentry.getIsInodeInlined()); + + if (isInodeMoveRequired) + { + // prepare dentry path + const Path* dentriesPath = + fileInfo->getIsBuddyMirrored() ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + std::string dirEntryPath = MetaStorageTk::getMetaDirEntryPath(dentriesPath->str(), parentDir.getID()); + + switch (moveMode) + { + case MODE_DEINLINE: + { + retVal = deinlineFileInode(parentDir, fileInfo, fileDentry, dirEntryPath); + break; + } + + case MODE_REINLINE: + { + retVal = reinlineFileInode(parentDir, fileInfo, fileDentry, dirEntryPath); + break; + } + + case MODE_INVALID: + break; // added just to please compiler (already handled above) + } + } + else + { + // due to unexpected failures (like node crash) there may some inconsistencies present in + // filesystem like duplicate inode(s) or dentry-by-entryID file missing for an inlined inode + // following code takes care to recover from such situations if user re-runs previous failed + // operation again (i.e. run beegfs-ctl command again in case of error) + switch (moveMode) + { + case MODE_DEINLINE: + { + // remove dentry-by-entryID file + parentDir.unlinkDirEntry(fileInfo->getFileName(), &fileDentry, DirEntry_UNLINK_ID); + break; + } + + case MODE_REINLINE: + { + // check if duplicate inode exists in inode Tree and remove associated meta file + EntryInfo fileInfoCopy(*fileInfo); + fileInfoCopy.setInodeInlinedFlag(false); + std::unique_ptr inode(FileInode::createFromEntryInfo(&fileInfoCopy)); + if (likely(inode)) + { + FileInode::unlinkStoredInodeUnlocked(fileInfo->getEntryID(), fileInfo->getIsBuddyMirrored()); + } + break; + } + + case MODE_INVALID: + break; // nothing to do + } + } + + return retVal; +} + +/** + * Helper function of verifyAndMoveFileInode() + * + * Updates the inode metadata to transform it into a non-inlined inode, copies remote storage + * targets and all user-defined extended attributes (XAttrs) from the inlined to the non-inlined + * inode, and at last updates the file dentry to reflect the non-inlined state of file inode. + * In case of an error, it attempts to roll-back partial changes (i.e. unlink non-inlined inode) + * made so far to avoid any filesystem inconsistencies (e.g., duplicate inodes). + */ +FhgfsOpsErr MetaStore::deinlineFileInode(DirInode& parentDir, EntryInfo* entryInfo, DirEntry& dentry, std::string const& dirEntryPath) +{ + UniqueRWLock dirLock(parentDir.rwlock, SafeRWLock_WRITE); + + auto [fileInode, referenceRes] = referenceFileUnlocked(parentDir, entryInfo); + if (!fileInode) + return referenceRes; + + // 1. Set the inode as non-inlined and save this change to disk + fileInode->setIsInlined(false); + if (!fileInode->updateInodeOnDisk(entryInfo)) + { + releaseFileUnlocked(parentDir, fileInode); + return FhgfsOpsErr_INTERNAL; + } + + // Lambda function to perform cleanup in case of errors: + // a) Unlink the non-inlined inode to maintain filesystem consistency + // b) Release the file inode reference + auto cleanupOnError = [this, &fileInode = fileInode, &entryInfo = entryInfo, &parentDir= parentDir](FhgfsOpsErr errCode) { + FileInode::unlinkStoredInodeUnlocked(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored()); + releaseFileUnlocked(parentDir, fileInode); + return errCode; + }; + + // 2. Copy remote storage targets (if present) to non-inlined inode + if (fileInode->getIsRstAvailable()) + { + FhgfsOpsErr retVal = fileInode->setRemoteStorageTarget(entryInfo, + *(fileInode->getRemoteStorageTargetInfo())); + + if (retVal != FhgfsOpsErr_SUCCESS) + return cleanupOnError(retVal); + } + + // 3. Copy all user-defined extended attributes (if present) to non-inlined inode + StringVector xAttrNames; + FhgfsOpsErr listXAttrRes; + std::tie(listXAttrRes, xAttrNames) = parentDir.listXAttr(entryInfo); + + if (listXAttrRes == FhgfsOpsErr_SUCCESS) + { + for (const auto& xAttrName : xAttrNames) + { + CharVector xAttrValue; + FhgfsOpsErr getXAttrRes; + + // Retrieve the value of the current xattr from the inlined inode + std::tie(getXAttrRes, xAttrValue, std::ignore) = parentDir.getXAttr(entryInfo, + xAttrName, XATTR_SIZE_MAX); + + if (getXAttrRes == FhgfsOpsErr_SUCCESS) + { + // Set the retrieved xattr value on the non-inlined inode + FhgfsOpsErr setRes = fileInode->setXAttr(entryInfo, xAttrName, xAttrValue, 0); + if (setRes != FhgfsOpsErr_SUCCESS) + return cleanupOnError(setRes); + } + else + return cleanupOnError(getXAttrRes); + } + } + + // 4. Update the file dentry + // + // Modify/update dentry feature flags to indicate that the inode is no + // longer inlined into dentry. Save the updated dentry data to disk, + // ensuring it gets written in VER-3 format + dentry.unsetInodeInlined(); + unsigned flags = dentry.getDentryFeatureFlags(); + flags &= ~(DENTRY_FEATURE_IS_FILEINODE); + dentry.setDentryFeatureFlags(flags); + dentry.getInodeStoreData()->setOrigFeature(FileInodeOrigFeature_UNSET); + + // Save updated dentry object to disk and unlink corresponding + // dentry-by-entryID file from '#fSiDs#' directory if successful + bool saveRes = dentry.storeUpdatedDirEntry(dirEntryPath); + if (saveRes) + parentDir.unlinkDirEntryUnlocked(entryInfo->getFileName(), &dentry, DirEntry_UNLINK_ID); + else + return cleanupOnError(FhgfsOpsErr_INTERNAL); + + releaseFileUnlocked(parentDir, fileInode); + return FhgfsOpsErr_SUCCESS; +} + +/** + * Helper function of verifyAndMoveFileInode() + * Makes a file Inode inlined. Caller should check if inode is already not inlined + */ +FhgfsOpsErr MetaStore::reinlineFileInode(DirInode& parentDir, EntryInfo* entryInfo, DirEntry& dentry, std::string const& dirEntryPath) +{ + const char* logContext = "make fileInode Inlined"; + auto [fileInode, referenceRes] = referenceFileUnlocked(entryInfo); + if (!fileInode) + return referenceRes; + + UniqueRWLock dirLock(parentDir.rwlock, SafeRWLock_WRITE); + + // 1. set inode specific data in dentry object after updating inode feature flags + fileInode->setIsInlined(true); + dentry.setFileInodeData(*(fileInode->getInodeDiskData())); + fileInode->getInodeDiskData()->setPattern(NULL); + + // 2. create link in '#fSiDs#' directory for dentry-by-entryID file + // needed because an inlined inode always have this link present + std::string idPath = MetaStorageTk::getMetaDirEntryIDPath(dirEntryPath) + entryInfo->getEntryID(); + std::string namePath = dirEntryPath + "/" + entryInfo->getFileName(); + int linkRes = link(namePath.c_str(), idPath.c_str()); + + if (linkRes) + { + if (errno != EEXIST) + { + LogContext(logContext).logErr("Creating dentry-by-entryid file failed: Path: " + + idPath + " SysErr: " + System::getErrString()); + + releaseFileUnlocked(parentDir, fileInode); + return FhgfsOpsErr_INTERNAL; + } + } + + // 3. now save in-memory dentry data (having inlined inode) onto disk + bool saveRes = dentry.storeUpdatedDirEntry(dirEntryPath); + + // 4. delete non-inlined inode meta file from disk + if (saveRes) + { + if (!FileInode::unlinkStoredInodeUnlocked(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored())) + { + releaseFileUnlocked(parentDir, fileInode); + return FhgfsOpsErr_INTERNAL; + } + } + + releaseFileUnlocked(parentDir, fileInode); + return FhgfsOpsErr_SUCCESS; +} + +/** + * Check if duplicate inodes exists or not (i.e. both inlined + nonInlined inode) + * If yes, then remove nonInlined inode to fix duplicacy + * + * @param parentDir parant directory of file for which duplicacy of inode needs to be checked + * @param entryInfo entry info of file + */ +FhgfsOpsErr MetaStore::checkAndRepairDupFileInode(DirInode& parentDir, EntryInfo* entryInfo) +{ + UniqueRWLock metaLock(rwlock, SafeRWLock_WRITE); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + if (!parentDir.loadIfNotLoadedUnlocked()) + { + return FhgfsOpsErr_PATHNOTEXISTS; + } + + // first try to load inlined inode + entryInfo->setInodeInlinedFlag(true); + auto [inlinedInode, referenceRes] = referenceFileUnlocked(entryInfo); + if (!(inlinedInode && inlinedInode->getIsInlined())) + { + return referenceRes; + } + + UniqueRWLock dirLock(parentDir.rwlock, SafeRWLock_WRITE); + + // now try to load nonInlined inode + entryInfo->setInodeInlinedFlag(false); + FileInode* nonInlinedInode = FileInode::createFromEntryInfo(entryInfo); + if (!nonInlinedInode) + return retVal; + + // if we are here then we know that both inlined + nonInlined inodes exists and + // we can safely remove nonInlined inode to fix duplicacy + if (!FileInode::unlinkStoredInodeUnlocked(entryInfo->getEntryID(), entryInfo->getIsBuddyMirrored())) + { + retVal = FhgfsOpsErr_INTERNAL; + } + + releaseFileUnlocked(parentDir, inlinedInode); + SAFE_DELETE(nonInlinedInode); + return retVal; +} + +/** + * Get the raw contents of the metadata file specified by path. + * Note: This is intended to be used by the buddy resyncer only. + * + * Behavior: + * - If extended attributes are enabled (via config): + * - Reads the specified attribute using getxattr. + * - Supports reading any attribute name. + * - If extended attributes are disabled: + * - Only allows reading the default attribute (META_XATTR_NAME). + * - Reads from file contents for the default attribute. + * - Returns an error for any other attribute name. + * + * @param path The path to the metadata file. + * @param contents The attribute contents will be stored in this vector. The vector will be assigned + * the new contents; any old contents will be lost. + * @param attrName The name of the attribute to read. Must be META_XATTR_NAME if extended attributes + * are disabled. + * + * @returns FhgfsOpsErr_SUCCESS on success. FhgfsOpsErr_PATHNOTEXISTS if file does not exist. + * FhgfsOpsErr_INTERNAL on any other error. + */ +FhgfsOpsErr MetaStore::getRawMetadata(const Path& path, const char* attrName, CharVector& contents) +{ + App* app = Program::getApp(); + const bool useXAttrs = app->getConfig()->getStoreUseExtendedAttribs(); + const std::string metaPath = app->getMetaPath(); + + char buf[META_SERBUF_SIZE]; + ssize_t readRes; + + if (useXAttrs) + { + // Load from Xattr + readRes = ::getxattr(path.str().c_str(), attrName, buf, META_SERBUF_SIZE); + if (readRes <= 0) + { + if (readRes == -1 && errno == ENOENT) + { + LOG(GENERAL, WARNING, "Metadata file does not exist", path); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + LOG(GENERAL, WARNING, "Unable to read metadata file", path, sysErr); + return FhgfsOpsErr_INTERNAL; + } + } + else + { + // If xattrs are disabled via config, only allow reading the default xattr (META_XATTR_NAME) + if (strcmp(attrName, META_XATTR_NAME) != 0) + { + LOG(GENERAL, ERR, "Reading a non-default attribute as file contents is not supported.", + path, attrName); + return FhgfsOpsErr_INVAL; + } + + // Load from file contents. + + int fd = open(path.str().c_str(), O_NOATIME | O_RDONLY, 0); + if (fd == -1) + { + if (errno != ENOENT) + { + LOG(GENERAL, WARNING, "Unable to read metadata file", path, sysErr); + return FhgfsOpsErr_INTERNAL; + } + + LOG(GENERAL, WARNING, "Metadata file does not exist", path); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + readRes = ::read(fd, buf, META_SERBUF_SIZE); + if (readRes <= 0) + { + LOG(GENERAL, ERR, "Unable to read metadata file", path, sysErr); + close(fd); + return FhgfsOpsErr_INTERNAL; + } + + close(fd); + } + + contents.assign(buf, buf + readRes); + return FhgfsOpsErr_SUCCESS; +} + + +/** + * Create a file or directory for metadata resync. The caller is responsible for filling the + * resulting object with metadata content or xattrs. + * Note: This is intended to be used by the buddy resynver only. + * + * A metatada inode which does not exist yet is created. + */ +std::pair MetaStore::beginResyncFor(const Path& path, + bool isDirectory) +{ + // first try to create the path directly, and if that fails with ENOENT (ie a directory in the + // path does not exist), create all parent directories and try again. + for (int round = 0; round < 2; round++) + { + int mkRes; + + if (isDirectory) + { + mkRes = ::mkdir(path.str().c_str(), 0755); + if (mkRes == 0 || errno == EEXIST) + mkRes = ::open(path.str().c_str(), O_DIRECTORY); + } + else + mkRes = ::open(path.str().c_str(), O_CREAT | O_TRUNC | O_RDWR, 0644); + + if (mkRes < 0 && errno == ENOENT && round == 0) + { + if (!StorageTk::createPathOnDisk(path, true)) + { + LOG(GENERAL, ERR, "Could not create metadata path", path, isDirectory); + return {FhgfsOpsErr_INTERNAL, IncompleteInode{}}; + } + + continue; + } + + if (mkRes < 0) + break; + + return {FhgfsOpsErr_SUCCESS, IncompleteInode(mkRes)}; + } + + LOG(GENERAL, ERR, "Could not create metadata file/directory", path, isDirectory, sysErr); + return {FhgfsOpsErrTk::fromSysErr(errno), IncompleteInode{}}; +} + +/** + * Deletes a raw metadata file specified by path. + * Note: This is intended to be used by the buddy resyncer only. + */ +FhgfsOpsErr MetaStore::unlinkRawMetadata(const Path& path) +{ + App* app = Program::getApp(); + const std::string metaPath = app->getMetaPath(); + + int unlinkRes = ::unlink(path.str().c_str()); + + if (!unlinkRes) + return FhgfsOpsErr_SUCCESS; + + if (errno == ENOENT) + { + LOG(GENERAL, DEBUG, "Metadata file does not exist", path); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + LOG(GENERAL, DEBUG, "Error unlinking metadata file", path, sysErr); + return FhgfsOpsErr_INTERNAL; +} + +/** + * Sets the state of a file and persists it to disk. + * This method locks inode in GlobalInodeLockStore to prevent concurrent operations, + * verifies the state transition is allowed based on active sessions, and updates its state. + * + * @param entryInfo The entry information of the file to modify + * @param state The new state to set for the file + * @return FhgfsOpsErr_SUCCESS if state was successfully updated to disk + * FhgfsOpsErr_INODELOCKED if file is locked in global lock store + * FhgfsOpsErr_INUSE if the requested state transition would affect active sessions + * FhgfsOpsErr_PATHNOTEXISTS if the file or parent directory doesn't exist + * FhgfsOpsErr_INTERNAL for other errors + */ +FhgfsOpsErr MetaStore::setFileState(EntryInfo* entryInfo, const FileState& state) +{ + const char* logContext = "MetaStore (set file state)"; + UniqueRWLock metaLock(rwlock, SafeRWLock_READ); + + // Add inode to global lock store for exclusive access + GlobalInodeLockStore* lockStore = this->getInodeLockStore(); + if (!lockStore->insertFileInode(entryInfo)) + { + LogContext(logContext).log(Log_DEBUG, "Inode is locked in global lock store; " + "state update rejected. EntryID: " + entryInfo->getEntryID()); + return FhgfsOpsErr_INODELOCKED; + } + + DirInode* parentDir = referenceDirUnlocked(entryInfo->getParentEntryID(), + entryInfo->getIsBuddyMirrored(), false); + if (unlikely(!parentDir)) + { + lockStore->releaseFileInode(entryInfo->getEntryID()); + return FhgfsOpsErr_PATHNOTEXISTS; + } + + // Bypass lock store checks since GlobalInodeLockStore ensures exclusivity. + // A narrow race window exists where another thread may reference the inode + // between our call to insertFileInode() and referenceFileUnlocked() + // (via concurrent operations like open(), stat(), etc.). Such cases are now + // handled directly by FileInode::checkAccessFlagTransition() logic, which + // verifies if the state change is allowed based on active sessions. + // Read-only operations may transiently reference the inode but don't increment + // session counters and therefore don't block state transitions. + auto [inode, refRes] = referenceFileUnlocked(*parentDir, entryInfo, /* checkLockStore */ false); + if (unlikely(!inode)) + { + releaseDirUnlocked(parentDir->getID()); + lockStore->releaseFileInode(entryInfo->getEntryID()); + return refRes; + } + + FhgfsOpsErr setRes = inode->setFileState(entryInfo, state); + if (setRes != FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_DEBUG, "Failed to set file state. EntryID: " + + entryInfo->getEntryID() + ", FileName: " + entryInfo->getFileName()); + } + + // Clean up resources in reverse order of acquisition + releaseFileUnlocked(*parentDir, inode); + releaseDirUnlocked(parentDir->getID()); + lockStore->releaseFileInode(entryInfo->getEntryID()); + return setRes; +} + +void MetaStore::invalidateMirroredDirInodes() +{ + dirStore.invalidateMirroredDirInodes(); +} diff --git a/meta/source/storage/MetaStore.h b/meta/source/storage/MetaStore.h new file mode 100644 index 0000000..48e03cb --- /dev/null +++ b/meta/source/storage/MetaStore.h @@ -0,0 +1,202 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DirEntry.h" +#include "InodeDirStore.h" +#include "InodeFileStore.h" +#include "MetadataEx.h" +#include "MetaFileHandle.h" + +typedef std::pair MetaFileHandleRes; + +/* + * This is the main class for all client side posix io operations regarding the meta server. + * So client side net message will do io via this class. + */ +class MetaStore +{ + public: + DirInode* referenceDir(const std::string& dirID, const bool isBuddyMirrored, + const bool forceLoad); + void releaseDir(const std::string& dirID); + MetaFileHandleRes referenceFile(EntryInfo* entryInfo, bool checkLockStore = true); + MetaFileHandle referenceLoadedFile(const std::string& parentEntryID, + bool parentIsBuddyMirrored, const std::string& entryID); + bool releaseFile(const std::string& parentEntryID, MetaFileHandle& inode); + bool referenceInode(const std::string& entryID, bool isBuddyMirrored, + MetaFileHandle& outFileInode, DirInode*& outDirInode); + + FhgfsOpsErr openFile(EntryInfo* entryInfo, unsigned accessFlags, bool bypassAccessCheck, + MetaFileHandle& outInode, bool checkDisposalFirst = false); + void closeFile(EntryInfo* entryInfo, MetaFileHandle inode, unsigned accessFlags, + unsigned* outNumHardlinks, unsigned* outNumRefs, bool& outLastWriterClosed); + + FhgfsOpsErr stat(EntryInfo* entryInfo, bool loadFromDisk, StatData& outStatData, + NumNodeID* outParentNodeID = NULL, std::string* outParentEntryID = NULL); + + FhgfsOpsErr setAttr(EntryInfo* entryInfo, int validAttribs, SettableFileAttribs* attribs); + FhgfsOpsErr incDecLinkCount(EntryInfo* entryInfo, int value); + FhgfsOpsErr setDirParent(EntryInfo* entryInfo, NumNodeID parentNodeID); + + FhgfsOpsErr mkNewMetaFile(DirInode& dir, MkFileDetails* mkDetails, + std::unique_ptr stripePattern, RemoteStorageTarget* rstInfo, + EntryInfo* outEntryInfo, FileInodeStoreData* outInodeData); + + FhgfsOpsErr makeDirInode(DirInode& inode); + FhgfsOpsErr makeDirInode(DirInode& inode, const CharVector& defaultACLXAttr, + const CharVector& accessACLXAttr); + FhgfsOpsErr removeDirInode(const std::string& entryID, bool isBuddyMirrored); + FhgfsOpsErr unlinkInode(EntryInfo* entryInfo, std::unique_ptr* outInode); + FhgfsOpsErr fsckUnlinkFileInode(const std::string& entryID, bool isBuddyMirrored); + FhgfsOpsErr unlinkFile(DirInode& dir, const std::string& fileName, + EntryInfo* outEntryInfo, std::unique_ptr* outInode, unsigned& outNumHardlinks); + FhgfsOpsErr unlinkFileInode(EntryInfo* delFileInfo, std::unique_ptr* outInode, + unsigned& outNumHardlinks); + + FhgfsOpsErr unlinkInodeLater(EntryInfo* entryInfo, bool wasInlined); + + FhgfsOpsErr renameInSameDir(DirInode& parentDir, const std::string& fromName, + const std::string& toName, std::unique_ptr* outUnlinkInode, + DirEntry*& outOverWrittenEntry, bool& outUnlinkedWasInlined); + + FhgfsOpsErr moveRemoteFileInsert(EntryInfo* fromFileInfo, DirInode& toParent, + const std::string& newEntryName, const char* buf, uint32_t bufLen, + std::unique_ptr* outUnlinkedInode, EntryInfo* overWriteInfo, EntryInfo& newFileInfo); + + FhgfsOpsErr moveRemoteFileBegin(DirInode& dir, EntryInfo* entryInfo, char* buf, size_t bufLen, + size_t* outUsedBufLen); + void moveRemoteFileComplete(DirInode& dir, const std::string& entryID); + + FhgfsOpsErr getAllInodesIncremental(unsigned hashDirNum, int64_t lastOffset, + unsigned maxOutInodes, FsckDirInodeList* outDirInodes, FsckFileInodeList* outFileInodes, + int64_t* outNewOffset, bool isBuddyMirrored); + + FhgfsOpsErr getAllEntryIDFilesIncremental(unsigned firstLevelhashDirNum, + unsigned secondLevelhashDirNum, int64_t lastOffset, unsigned maxOutEntries, + StringList* outEntryIDFiles, int64_t* outNewOffset, bool buddyMirrored); + + void getReferenceStats(size_t* numReferencedDirs, size_t* numReferencedFiles); + void getCacheStats(size_t* numCachedDirs); + + bool cacheSweepAsync(); + + FhgfsOpsErr insertDisposableFile(FileInode* inode); + + std::pair getEntryData(DirInode *dirInode, const std::string& entryName, + EntryInfo* outInfo, FileInodeStoreData* outInodeMetaData); + FhgfsOpsErr getEntryData(EntryInfo* inEntryInfo, FileInodeStoreData* outInodeMetaData); + + FhgfsOpsErr linkInSameDir(DirInode& parentDir, EntryInfo* fromFileInfo, + const std::string& fromName, const std::string& toName); + + std::pair makeNewHardlink(EntryInfo* fromFileInfo); + FhgfsOpsErr verifyAndMoveFileInode(DirInode& parentDir, EntryInfo* fileInfo, + FileInodeMode moveMode); + FhgfsOpsErr checkAndRepairDupFileInode(DirInode& parentDir, EntryInfo* entryInfo); + + FhgfsOpsErr getRawMetadata(const Path& path, const char* attrName, CharVector& contents); + std::pair beginResyncFor(const Path& path, bool isDirectory); + FhgfsOpsErr unlinkRawMetadata(const Path& path); + + FhgfsOpsErr setFileState(EntryInfo* entryInfo, const FileState& state); + + void invalidateMirroredDirInodes(); + + private: + InodeDirStore dirStore; + + /* We need to avoid to use that one, as it is a global store, with possible lots of entries. + * So access to the map is slow and inserting entries blocks the entire MetaStore */ + InodeFileStore fileStore; + + GlobalInodeLockStore inodeLockStore; + + RWLock rwlock; /* note: this is mostly not used as a read/write-lock but rather a shared/excl + lock (because we're not really modifying anyting directly) - especially relevant for the + mutliple dirStore locking dual-move methods */ + + FhgfsOpsErr isFileUnlinkable(DirInode& subDir, EntryInfo* entryInfo); + + FhgfsOpsErr mkMetaFileUnlocked(DirInode& dir, const std::string& entryName, + EntryInfo* entryInfo, FileInode* inode); + + FhgfsOpsErr unlinkInodeUnlocked(EntryInfo* entryInfo, DirInode* subDir, + std::unique_ptr* outInode); + FhgfsOpsErr unlinkInodeLaterUnlocked(EntryInfo* entryInfo, bool wasInlined); + + FhgfsOpsErr unlinkFileUnlocked(DirInode& subdir, const std::string& fileName, + std::unique_ptr* outInode, EntryInfo* outEntryInfo, bool& outWasInlined, + unsigned& outNumHardlinks); + + FhgfsOpsErr unlinkDirEntryWithInlinedInodeUnlocked(const std::string& entryName, + DirInode& subDir, DirEntry* dirEntry, unsigned unlinkTypeFlags, + std::unique_ptr* outInode, unsigned& outNumHardlinks); + FhgfsOpsErr unlinkDentryAndInodeUnlocked(const std::string& fileName, DirInode& subdir, + DirEntry* dirEntry, unsigned unlinkTypeFlags, std::unique_ptr* outInode, + unsigned& outNumHardlinks); + + FhgfsOpsErr unlinkOverwrittenEntry(DirInode& parentDir, DirEntry* overWrittenEntry, + std::unique_ptr* outInode); + FhgfsOpsErr unlinkOverwrittenEntryUnlocked(DirInode& parentDir, DirEntry* overWrittenEntry, + std::unique_ptr* outInode); + + + DirInode* referenceDirUnlocked(const std::string& dirID, bool isBuddyMirrored, + bool forceLoad); + void releaseDirUnlocked(const std::string& dirID); + MetaFileHandleRes referenceFileUnlocked(EntryInfo* entryInfo, bool checkLockStore = true); + MetaFileHandleRes referenceFileUnlocked(DirInode& subDir, EntryInfo* entryInfo, + bool checkLockStore = true); + MetaFileHandle referenceLoadedFileUnlocked(const std::string& parentEntryID, + bool isBuddyMirrored, const std::string& entryID); + MetaFileHandle referenceLoadedFileUnlocked(DirInode& subDir, const std::string& entryID); + bool releaseFileUnlocked(const std::string& parentEntryID, MetaFileHandle& inode); + bool releaseFileUnlocked(DirInode& subDir, MetaFileHandle& inode); + + MetaFileHandleRes tryReferenceFileWriteLocked(EntryInfo* entryInfo, bool checkLockStore = true); + FhgfsOpsErr tryOpenFileWriteLocked(EntryInfo* entryInfo, unsigned accessFlags, bool bypassAccessCheck, + MetaFileHandle& outInode); + + bool moveReferenceToMetaFileStoreUnlocked(const std::string& parentEntryID, + bool parentIsBuddyMirrored, const std::string& entryID); + + FhgfsOpsErr performRenameEntryInSameDir(DirInode& dir, const std::string& fromName, + const std::string& toName, DirEntry** outOverwrittenEntry); + FhgfsOpsErr checkRenameOverwrite(EntryInfo* fromEntry, EntryInfo* overWriteEntry, + bool& outIsSameInode); + + FhgfsOpsErr setAttrUnlocked(EntryInfo* entryInfo, int validAttribs, + SettableFileAttribs* attribs); + FhgfsOpsErr incDecLinkCountUnlocked(EntryInfo* entryInfo, int value); + + FhgfsOpsErr verifyAndMoveFileInodeUnlocked(DirInode& parentDir, EntryInfo* fileInfo, + FileInodeMode moveMode); + FhgfsOpsErr deinlineFileInode(DirInode& parentDir, EntryInfo* entryInfo, + DirEntry& dentry, const std::string& dirEntryPath); + FhgfsOpsErr reinlineFileInode(DirInode& parentDir, EntryInfo* entryInfo, + DirEntry& dentry, const std::string& dirEntryPath); + public: + // getters & setters + GlobalInodeLockStore* getInodeLockStore() + { + return &inodeLockStore; + } + // inliners + +}; + + diff --git a/meta/source/storage/MetaStoreRename.cpp b/meta/source/storage/MetaStoreRename.cpp new file mode 100644 index 0000000..74e64db --- /dev/null +++ b/meta/source/storage/MetaStoreRename.cpp @@ -0,0 +1,527 @@ +/* + * MetaStoreRenameHelper.cpp + * + * These methods belong to class MetaStore, but are all related to rename() + */ + +#include +#include +#include +#include +#include +#include "MetaStore.h" + +#include +#include +#include "MetaStore.h" + +#include + +/** + * Simple rename on the same server in the same directory. + * + * @param outUnlinkInode is the inode of a dirEntry being possibly overwritten (toName already + * existed). + */ +FhgfsOpsErr MetaStore::renameInSameDir(DirInode& parentDir, const std::string& fromName, + const std::string& toName, std::unique_ptr* outUnlinkInode, + DirEntry*& outOverWrittenEntry, bool& outUnlinkedWasInlined) +{ + const char* logContext = "Rename in dir"; + + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + SafeRWLock fromMutexLock(&parentDir.rwlock, SafeRWLock_WRITE); // L O C K ( F R O M ) + + FhgfsOpsErr retVal; + FhgfsOpsErr unlinkRes = FhgfsOpsErr_SUCCESS; // initialize just to please compiler + + outOverWrittenEntry = NULL; + + retVal = performRenameEntryInSameDir(parentDir, fromName, toName, &outOverWrittenEntry); + + if (retVal != FhgfsOpsErr_SUCCESS) + { + fromMutexLock.unlock(); + safeLock.unlock(); + + SAFE_DELETE(outOverWrittenEntry); + + return retVal; + } + + EntryInfo unlinkEntryInfo; + + // unlink for non-inlined inode will be handled later + if (outOverWrittenEntry) + { + const std::string& parentDirID = parentDir.getID(); + outOverWrittenEntry->getEntryInfo(parentDirID, 0, &unlinkEntryInfo); + outUnlinkedWasInlined = outOverWrittenEntry->getIsInodeInlined(); + + if (outOverWrittenEntry->getIsInodeInlined()) + { + unlinkRes = unlinkOverwrittenEntryUnlocked(parentDir, outOverWrittenEntry, outUnlinkInode); + } + else + { + outUnlinkInode->reset(); + unlinkRes = FhgfsOpsErr_SUCCESS; + } + } + + /* Now update the ctime (attribChangeTime) of the renamed entry. + * Only do that for Directory dentry after giving up the DirInodes (fromMutex) lock + * as dirStore.setAttr() will aquire the InodeDirStore:: lock + * and the lock order is InodeDirStore:: and then DirInode:: (risk of deadlock) */ + + DirEntry* entry = parentDir.dirEntryCreateFromFileUnlocked(toName); + if (likely(entry) ) // entry was just renamed to, so very likely it exists + { + EntryInfo entryInfo; + const std::string& parentID = parentDir.getID(); + entry->getEntryInfo(parentID, 0, &entryInfo); + + fromMutexLock.unlock(); + setAttrUnlocked(&entryInfo, 0, NULL); /* This will fail if the DirInode is on another + * meta server, but as updating the ctime is not + * a real posix requirement (but filesystems usually + * do it) we simply ignore this issue for now. */ + + SAFE_DELETE(entry); + } + else + fromMutexLock.unlock(); + + safeLock.unlock(); + + // unlink later must be called after releasing all locks + + if (outOverWrittenEntry) + { + if (unlinkRes == FhgfsOpsErr_INUSE) + { + unlinkRes = unlinkInodeLater(&unlinkEntryInfo, outUnlinkedWasInlined ); + if (unlinkRes == FhgfsOpsErr_AGAIN) + { + unlinkRes = unlinkOverwrittenEntry(parentDir, outOverWrittenEntry, outUnlinkInode); + } + } + + if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS) + { + LogContext(logContext).logErr("Failed to unlink overwritten entry:" + " FileName: " + toName + + " ParentEntryID: " + parentDir.getID() + + " entryID: " + outOverWrittenEntry->getEntryID() + + " Error: " + boost::lexical_cast(unlinkRes)); + + + // TODO: Restore the dentry + } + } + + return retVal; +} + +/** + * Unlink an overwritten dentry. From this dentry either the #fsid# entry or its inode is left. + * + * Locking: + * We lock everything ourself + */ +FhgfsOpsErr MetaStore::unlinkOverwrittenEntry(DirInode& parentDir, + DirEntry* overWrittenEntry, std::unique_ptr* outInode) +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + SafeRWLock parentLock(&parentDir.rwlock, SafeRWLock_WRITE); + + FhgfsOpsErr unlinkRes = unlinkOverwrittenEntryUnlocked(parentDir, overWrittenEntry, outInode); + + parentLock.unlock(); + safeLock.unlock(); + + return unlinkRes; +} + +/** + * See unlinkOverwrittenEntry() for details + * + * Locking: + * MetaStore rwlock: Read-lock + * parentDir : Write-lock + */ +FhgfsOpsErr MetaStore::unlinkOverwrittenEntryUnlocked(DirInode& parentDir, + DirEntry* overWrittenEntry, std::unique_ptr* outInode) +{ + FhgfsOpsErr unlinkRes; + unsigned outNumHardlinks; // Not used here! + + if (overWrittenEntry->getIsInodeInlined() ) + { + /* We advise the calling code not to try to delete the entryName dentry, + * as renameEntryUnlocked() already did that */ + unlinkRes = unlinkDirEntryWithInlinedInodeUnlocked("", parentDir, overWrittenEntry, + DirEntry_UNLINK_ID, outInode, outNumHardlinks); + } + else + { + // And also do not try to delete the dir-entry-by-name here. + unlinkRes = unlinkDentryAndInodeUnlocked("", parentDir, overWrittenEntry, + DirEntry_UNLINK_ID, outInode, outNumHardlinks); + } + + return unlinkRes; +} + +/** + * Perform the rename action here. + * + * In constrast to the moving...()-methods, this method performs a simple rename of an entry, + * where no moving is involved. + * + * Rules: Files can overwrite existing files, but not existing dirs. Dirs cannot overwrite any + * existing entry. + * + * @param dir needs to write-locked already + * @param outOverwrittenEntry the caller is responsible for the deletion of the local file; + * accoring to the rules, this can only be an overwritten file, not a dir; may not be NULL. + * Also, we only overwrite the entryName dentry, but not the ID dentry. + * + * Note: MetaStore is ReadLocked, dir is WriteLocked + */ +FhgfsOpsErr MetaStore::performRenameEntryInSameDir(DirInode& dir, const std::string& fromName, + const std::string& toName, DirEntry** outOverwrittenEntry) +{ + *outOverwrittenEntry = NULL; + + FhgfsOpsErr retVal; + + // load DirInode on demand if required, we need it now + bool loadSuccess = dir.loadIfNotLoadedUnlocked(); + if (!loadSuccess) + return FhgfsOpsErr_PATHNOTEXISTS; + + // of the file being renamed + DirEntry* fromEntry = dir.dirEntryCreateFromFileUnlocked(fromName); + if (!fromEntry) + { + return FhgfsOpsErr_PATHNOTEXISTS; + } + + EntryInfo fromEntryInfo; + const std::string& parentEntryID = dir.getID(); + fromEntry->getEntryInfo(parentEntryID, 0, &fromEntryInfo); + + // reference the inode + MetaFileHandle fromFileInode; + // DirInode* fromDirInode = NULL; + if (DirEntryType_ISDIR(fromEntryInfo.getEntryType() ) ) + { + // TODO, exclusive lock + } + else + { + // for nonInlined inode(s) - inode may not be present on local meta server + // only try to referece file inode for inlined inode(s) + if (fromEntry->getIsInodeInlined()) + { + FhgfsOpsErr referenceRes; + std::tie(fromFileInode, referenceRes) = referenceFileUnlocked(dir, &fromEntryInfo); + if (!fromFileInode) + { + /* Note: The inode might be exclusively locked and a remote rename op might be in progress. + * If that fails we should actually continue with our rename. That will be solved + * in the future by using hardlinks for remote renaming. */ + return referenceRes; + } + } + } + + DirEntry* overWriteEntry = dir.dirEntryCreateFromFileUnlocked(toName); + if (overWriteEntry) + { + // sanity checks if we really shall overwrite the entry + + const std::string& parentID = dir.getID(); + + EntryInfo fromEntryInfo; + fromEntry->getEntryInfo(parentID , 0, &fromEntryInfo); + + EntryInfo overWriteEntryInfo; + overWriteEntry->getEntryInfo(parentID, 0, &overWriteEntryInfo); + + bool isSameInode; + retVal = checkRenameOverwrite(&fromEntryInfo, &overWriteEntryInfo, isSameInode); + + if (isSameInode) + { + delete(overWriteEntry); + overWriteEntry = NULL; + goto out; // nothing to do then, rename request will be silently ignored + } + + if (retVal != FhgfsOpsErr_SUCCESS) + goto out; // not allowed for some reasons, return it to the user + } + + // eventually rename here + retVal = dir.renameDirEntryUnlocked(fromName, toName, overWriteEntry); + + /* Note: If rename faild and and an existing toName was to be overwritten, we don't need to care + * about it, the underlying file system has to handle it. */ + + /* Note2: Do not decrease directory link count here, even if we overwrote an entry. We will do + * that later on in common unlink code, when we going to unlink the entry from + * the #fsIDs# dir. + */ + + if (fromFileInode) + releaseFileUnlocked(dir, fromFileInode); + else + { + // TODO dir + } + + +out: + + if (retVal == FhgfsOpsErr_SUCCESS) + *outOverwrittenEntry = overWriteEntry; + else + SAFE_DELETE(overWriteEntry); + + SAFE_DELETE(fromEntry); // always exists when we are here + + return retVal; +} + +/** + * Check if overwriting an entry on rename is allowed. + */ +FhgfsOpsErr MetaStore::checkRenameOverwrite(EntryInfo* fromEntry, EntryInfo* overWriteEntry, + bool& outIsSameInode) +{ + outIsSameInode = false; + + // check if we are going to rename to a dentry with the same inode + if (fromEntry->getEntryID() == overWriteEntry->getEntryID() ) + { // According to posix we must not do anything and return success. + + outIsSameInode = true; + return FhgfsOpsErr_SUCCESS; + } + + if (overWriteEntry->getEntryType() == DirEntryType_DIRECTORY) + { + return FhgfsOpsErr_EXISTS; + } + + /* TODO: We should allow this if overWriteEntry->getEntryType() == DirEntryType_DIRECTORY + * and overWriteEntry is empty. + */ + if (fromEntry->getEntryType() == DirEntryType_DIRECTORY) + { + return FhgfsOpsErr_EXISTS; + } + + return FhgfsOpsErr_SUCCESS; +} + +/** + * Create a new file on this (remote) meta-server. This is the 'toFile' on a rename() client call. + * + * Note: Replaces existing entry. + * + * @param buf serialized inode object + * @param outUnlinkedInode the unlinked (owned) file (in case a file was overwritten + * @param overWriteInfo entryInfo of overwritten (and possibly unlinked inode if it was inlined) file + * by the move operation); the caller is responsible for the deletion of the local file and the + * corresponding object; may not be NULL + */ +FhgfsOpsErr MetaStore::moveRemoteFileInsert(EntryInfo* fromFileInfo, DirInode& toParent, + const std::string& newEntryName, const char* buf, uint32_t bufLen, + std::unique_ptr* outUnlinkedInode, EntryInfo* overWriteInfo, EntryInfo& newFileInfo) +{ + // note: we do not allow newEntry to be a file if the old entry was a directory (and vice versa) + const char* logContext = "rename(): Insert remote entry"; + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + outUnlinkedInode->reset(); + + SafeRWLock safeMetaStoreLock(&rwlock, SafeRWLock_READ); // L O C K + SafeRWLock toParentMutexLock(&toParent.rwlock, SafeRWLock_WRITE); // L O C K ( T O ) + + std::unique_ptr overWrittenEntry(toParent.dirEntryCreateFromFileUnlocked(newEntryName)); + if (overWrittenEntry) + { + const std::string& parentID = overWrittenEntry->getID(); + overWrittenEntry->getEntryInfo(parentID, 0, overWriteInfo); + bool isSameInode; + + FhgfsOpsErr checkRes = checkRenameOverwrite(fromFileInfo, overWriteInfo, isSameInode); + if ((checkRes != FhgfsOpsErr_SUCCESS) || ((checkRes == FhgfsOpsErr_SUCCESS) && isSameInode) ) + { + retVal = checkRes; + goto outUnlock; + } + + // only unlink the dir-entry-name here, so we can still restore it from dir-entry-id + FhgfsOpsErr unlinkRes = toParent.unlinkDirEntryUnlocked(newEntryName, overWrittenEntry.get(), + DirEntry_UNLINK_FILENAME); + if (unlikely(unlinkRes != FhgfsOpsErr_SUCCESS) ) + { + if (unlikely (unlinkRes == FhgfsOpsErr_PATHNOTEXISTS) ) + LogContext(logContext).log(Log_WARNING, "Unexpectedly failed to unlink file: " + + toParent.entries.getDirEntryPathUnlocked() + newEntryName + ". "); + else + { + LogContext(logContext).logErr("Failed to unlink existing file. Aborting rename()."); + retVal = unlinkRes; + goto outUnlock; + } + } + } + + { // create new dirEntry with inlined inode + FileInode* inode = new FileInode(); // the deserialized inode + Deserializer des(buf, bufLen); + inode->deserializeMetaData(des); + if (!des.good()) + { + LogContext("File rename").logErr("Bug: Deserialization of remote buffer failed. Are all " + "meta servers running with the same version?" ); + retVal = FhgfsOpsErr_INTERNAL; + + delete inode; + goto outUnlock; + } + + // ensure that the buddyMirrored flag of the created inode is set correctly. the source could + // check this as well, but since we already have the destination dir inode, we are in a better + // position to do this. + if (toParent.getIsBuddyMirrored()) + inode->setIsBuddyMirrored(); + else + inode->setIsBuddyMirrored(false); + + // deserialize RSTs and set in inode object + if (inode->getIsRstAvailable()) + { + RemoteStorageTarget rstInfo; + des % rstInfo; + inode->setRemoteStorageTargetUnpersistent(rstInfo); + } + + // destructs inode + retVal = mkMetaFileUnlocked(toParent, newEntryName, fromFileInfo, inode); + } + + if (retVal == FhgfsOpsErr_SUCCESS) + { + if (!toParent.entries.getFileEntryInfo(newEntryName, newFileInfo)) + retVal = FhgfsOpsErr_INTERNAL; + } + + if (overWrittenEntry && overWrittenEntry->getIsInodeInlined() && (retVal == FhgfsOpsErr_SUCCESS)) + { + // unlink overwritten entry if it had an inlined inode (non-inlined inodes will be unlinked later) + bool unlinkedWasInlined = overWrittenEntry->getIsInodeInlined(); + + FhgfsOpsErr unlinkRes = unlinkOverwrittenEntryUnlocked(toParent, overWrittenEntry.get(), + outUnlinkedInode); + + EntryInfo unlinkEntryInfo; + overWrittenEntry->getEntryInfo(toParent.getID(), 0, &unlinkEntryInfo); + + // unlock everything here, but do not release toParent yet. + toParentMutexLock.unlock(); // U N L O C K ( T O ) + safeMetaStoreLock.unlock(); + + // unlinkInodeLater() requires that everything was unlocked! + if (unlinkRes == FhgfsOpsErr_INUSE) + { + unlinkRes = unlinkInodeLater(&unlinkEntryInfo, unlinkedWasInlined); + if (unlinkRes == FhgfsOpsErr_AGAIN) + unlinkRes = unlinkOverwrittenEntry(toParent, overWrittenEntry.get(), outUnlinkedInode); + + if (unlinkRes != FhgfsOpsErr_SUCCESS && unlinkRes != FhgfsOpsErr_PATHNOTEXISTS) + LogContext(logContext).logErr("Failed to unlink overwritten entry:" + " FileName: " + newEntryName + + " ParentEntryID: " + toParent.getID() + + " entryID: " + overWrittenEntry->getEntryID() + + " Error: " + boost::lexical_cast(unlinkRes)); + } + + return retVal; + } + else if (overWrittenEntry) + { + // TODO: Restore the overwritten entry + } + +outUnlock: + toParentMutexLock.unlock(); // U N L O C K ( T O ) + safeMetaStoreLock.unlock(); + return retVal; +} + +/** + * Copies (serializes) the original file object to a buffer. + * + * Note: This works by inserting a temporary placeholder and returning the original, so remember to + * call movingComplete() + * + * @param buf target buffer for serialization + * @param bufLen must be at least META_SERBUF_SIZE + */ +FhgfsOpsErr MetaStore::moveRemoteFileBegin(DirInode& dir, EntryInfo* entryInfo, + char* buf, size_t bufLen, size_t* outUsedBufLen) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + SafeRWLock safeLock(&this->rwlock, SafeRWLock_READ); // L O C K + + // lock the dir to make sure no renameInSameDir is going on + SafeRWLock safeDirLock(&dir.rwlock, SafeRWLock_READ); + + if (entryInfo->getIsInlined()) + { + if (this->fileStore.isInStore(entryInfo->getEntryID())) + retVal = this->fileStore.moveRemoteBegin(entryInfo, buf, bufLen, outUsedBufLen); + else + retVal = dir.fileStore.moveRemoteBegin(entryInfo, buf, bufLen, outUsedBufLen); + } + else + { + // handle dentry for a non-inlined inode + DirEntry fileDentry(entryInfo->getFileName()); + if (!dir.getDentryUnlocked(entryInfo->getFileName(), fileDentry)) + return FhgfsOpsErr_PATHNOTEXISTS; + else + retVal = FhgfsOpsErr_SUCCESS; + + Serializer ser(buf, bufLen); + fileDentry.serializeDentry(ser); + *outUsedBufLen = ser.size(); + } + + safeDirLock.unlock(); + safeLock.unlock(); // U N L O C K + + return retVal; +} + +void MetaStore::moveRemoteFileComplete(DirInode& dir, const std::string& entryID) +{ + SafeRWLock safeLock(&this->rwlock, SafeRWLock_WRITE); // L O C K + + if (this->fileStore.isInStore(entryID) ) + this->fileStore.moveRemoteComplete(entryID); + else + { + SafeRWLock safeDirLock(&dir.rwlock, SafeRWLock_READ); + dir.fileStore.moveRemoteComplete(entryID); + safeDirLock.unlock(); + } + + safeLock.unlock(); // U N L O C K +} diff --git a/meta/source/storage/MetadataEx.h b/meta/source/storage/MetadataEx.h new file mode 100644 index 0000000..934b169 --- /dev/null +++ b/meta/source/storage/MetadataEx.h @@ -0,0 +1,24 @@ +#pragma once + +#include +#include + +#define META_UPDATE_EXT_STR ".new-fhgfs" +#define META_XATTR_NAME "user.fhgfs" // attribute name for dir-entries, file and dir metadata +#define RST_XATTR_NAME "user.beermt" // attribute name for storing remote storage target info + +// !!!IMPORTANT NOTICE TO MAINTAINER!!! +// Any new extended attribute (NON user-defined) that will be added +// in the future MUST be included in the list below. +// +// FAILURE TO DO SO WILL CAUSE: +// Inconsistencies between primary and secondary meta mirrors due to incomplete buddy +// resyncing, as the missing attribute's data will not be resynced to secondary meta. +const std::array METADATA_XATTR_NAME_LIST = {META_XATTR_NAME, RST_XATTR_NAME}; + +// The size must be sufficient to hold the entire dentry data. In order to simplify various +// operations, meta data or stored into a buffer and for example for a remote directory rename +// operation, this buffer is then transferred over net to the other meta node there used to fill +// the remote dentry, without any knowledge of the actual content. +#define META_SERBUF_SIZE (1024*8) + diff --git a/meta/source/storage/MkFileDetails.h b/meta/source/storage/MkFileDetails.h new file mode 100644 index 0000000..acbfe2e --- /dev/null +++ b/meta/source/storage/MkFileDetails.h @@ -0,0 +1,30 @@ +/* + * File creation information + * + */ + +#pragma once + +struct MkFileDetails +{ + MkFileDetails(const std::string& newName, const unsigned userID, const unsigned groupID, + const int mode, const int umask, int64_t createTime) : + newName(newName), userID(userID), groupID(groupID), mode(mode), umask(umask), + createTime(createTime) + { } + + void setNewEntryID(const char* newEntryID) + { + this->newEntryID = newEntryID; + } + + std::string newName; + std::string newEntryID; // only used for mirroring on secondary + unsigned userID; + unsigned groupID; + int mode; + int umask; + int64_t createTime; +}; + + diff --git a/meta/source/storage/NodeOfflineWait.h b/meta/source/storage/NodeOfflineWait.h new file mode 100644 index 0000000..d60d5ef --- /dev/null +++ b/meta/source/storage/NodeOfflineWait.h @@ -0,0 +1,78 @@ +#pragma once + +#include +#include +#include +#include + +/** + * Handles timeout during which the node may not send a state report to the mgmtd, so that the mgmtd + * will eventually set the node to (P)OFFLINE - e.g. when a primary needs a resync right after meta + * server startup + */ +class NodeOfflineWait +{ + public: + NodeOfflineWait(Config* cfg) + : waitTimeoutMS(OfflineWaitTimeoutTk::calculate(cfg) ), + active(false) + { } + + + private: + RWLock rwlock; + const unsigned waitTimeoutMS; + + Time timer; + bool active; + + public: + + /** + * Starts the timer. + */ + void startTimer() + { + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + active = true; + timer.setToNow(); + + safeLock.unlock(); // U N L O C K + } + + /** + * Checks if the timer is running and has not run out yet. + */ + bool hasTimeout() + { + bool res = true; + { + SafeRWLock lock(&rwlock, SafeRWLock_READ); // L O C K + res = active; + lock.unlock(); // U N L O C K + } + + if (res) // If active flag is set, timer was still running the last time we checked. + { // Check it again and clear active flag if it has run out in the mean time. + SafeRWLock lock(&rwlock, SafeRWLock_WRITE); // L O C K + + // else: Active flag is set - check if timer has run out yet. + unsigned elapsedMS = timer.elapsedMS(); + if (elapsedMS >= waitTimeoutMS) + res = active = false; + + lock.unlock(); // U N L O C K + + if (res) + LogContext("Node Timeout").log(Log_WARNING, + "This node was a primary node of a mirror group and needs a resync. " + "Waiting until it is marked offline on all clients. (" + + StringTk::uintToStr( (this->waitTimeoutMS - elapsedMS) / 1000) + + " seconds left)"); + } + + return res; + } +}; + diff --git a/meta/source/storage/PosixACL.cpp b/meta/source/storage/PosixACL.cpp new file mode 100644 index 0000000..2104e57 --- /dev/null +++ b/meta/source/storage/PosixACL.cpp @@ -0,0 +1,159 @@ +#include "PosixACL.h" + +const std::string PosixACL::defaultACLXAttrName = "system.posix_acl_default"; +const std::string PosixACL::accessACLXAttrName = "system.posix_acl_access"; + +/** + * @param xattr serialized form of the posix ACL to fill the PosixACL from. + */ +bool PosixACL::deserializeXAttr(const CharVector& xattr) +{ + Deserializer des(&xattr[0], xattr.size()); + + { + int32_t version; + + des % version; + if (!des.good() || version != ACLEntry::POSIX_ACL_XATTR_VERSION) + return false; + } + + entries.clear(); + while (des.good() && des.size() < xattr.size()) + { + ACLEntry newEntry; + + des % newEntry; + entries.push_back(newEntry); + } + + return des.good(); +} + +/** + * Serialize the ACL in posix_acl_xattr form. + */ +void PosixACL::serializeXAttr(CharVector& xattr) const +{ + xattr.clear(); + + // run twice: once with empty buffer, to calculate the size, then with proper buffer + for (int i = 0; i < 2; i++) + { + Serializer ser(&xattr[0], xattr.size()); + + ser % ACLEntry::POSIX_ACL_XATTR_VERSION; + + // entries + for (ACLEntryVecCIter entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) + ser % *entryIt; + + xattr.resize(ser.size()); + } +} + +/** + * Modify the ACL based on the given mode bits. Also modifies the mode bits according to the + * permissions granted in the ACL. + * This effectively turns a directory default ACL into a file access ACL. + * + * @param mode Mode bits of the new file. + * @returns whether an ACL is necessary for the newly created file. + */ +FhgfsOpsErr PosixACL::modifyModeBits(int& outMode, bool& outNeedsACL) +{ + outNeedsACL = false; + int newMode = outMode; + + // Pointers to the group/mask mode of the ACL. In case we find group/mask entries, we have to + // take them into account last. + unsigned short* groupPerm = NULL; + unsigned short* maskPerm = NULL; + + for (ACLEntryVecIter entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) + { + ACLEntry& entry = *entryIt; + + switch (entry.tag) + { + case ACLEntry::ACL_USER_OBJ: + { + // Apply 'user' permission of the mode to the ACL's 'owner' entry. + entry.perm &= (newMode >> 6) | ~0007; + + // Apply 'owner' entry of the ACL to the owner's permission in the mode flags. + newMode &= (entry.perm << 6) | ~0700; + } + break; + + case ACLEntry::ACL_USER: + case ACLEntry::ACL_GROUP: + { + // If the ACL has named user/group entries, it can't be represented using only + // mode bits. + outNeedsACL = true; + } + break; + + case ACLEntry::ACL_GROUP_OBJ: + { + groupPerm = &entry.perm; + } + break; + + case ACLEntry::ACL_OTHER: + { + // Apply 'other' permission from the mode to the ACL's 'other' entry. + entry.perm &= newMode | ~0007; + + // Apply 'other' entry of the ACL to the 'other' permission in the mode flags. + newMode &= entry.perm | ~0007; + } + break; + + case ACLEntry::ACL_MASK: + { + maskPerm = &entry.perm; + } + break; + + default: + return FhgfsOpsErr_INTERNAL; + } + } + + if (maskPerm) + { + // The 'mask' entry of the ACL influences the 'group' access bits and vice-versa. + *maskPerm &= (newMode >> 3) | ~0007; + newMode &= (*maskPerm << 3) | ~0070; + } + else + { + // If there's no mask, the group mode bits are determined using the group acl entry. + if (!groupPerm) + return FhgfsOpsErr_INTERNAL; + + *groupPerm &= (newMode >> 3) | ~0007; + newMode &= (*groupPerm << 3) | ~0070; + } + + // Apply changes to the last nine mode bits. + outMode = (outMode & ~0777) | newMode; + + return FhgfsOpsErr_SUCCESS; +} + +std::string PosixACL::toString() +{ + std::ostringstream ostr; + + ostr << "ACL Size: " << entries.size() << std::endl; + + for (ACLEntryVecCIter it = entries.begin(); it != entries.end(); ++it) + ostr << "Entry[ " + << "tag: " << it->tag << " perm: " << it->perm << " id: " << it->id + << " ]" << std::endl; + + return ostr.str(); +} diff --git a/meta/source/storage/PosixACL.h b/meta/source/storage/PosixACL.h new file mode 100644 index 0000000..48dca66 --- /dev/null +++ b/meta/source/storage/PosixACL.h @@ -0,0 +1,57 @@ +#pragma once + +#include +#include +#include + +#include +#include + +struct ACLEntry +{ + // Definitions to match linux/posix_acl.h and linux/posix_acl_xattr.h + static const int32_t POSIX_ACL_XATTR_VERSION = 0x0002; + enum Tag { + ACL_USER_OBJ = 0x01, + ACL_USER = 0x02, + ACL_GROUP_OBJ = 0x04, + ACL_GROUP = 0x08, + ACL_MASK = 0x10, + ACL_OTHER = 0x20 + }; + + int16_t tag; + uint16_t perm; + uint32_t id; // uid or gid depending on the tag + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->tag + % obj->perm + % obj->id; + } +}; + +typedef std::vector ACLEntryVec; +typedef ACLEntryVec::const_iterator ACLEntryVecCIter; +typedef ACLEntryVec::iterator ACLEntryVecIter; + +class PosixACL +{ + public: + bool deserializeXAttr(const CharVector& xattr); + void serializeXAttr(CharVector& xattr) const; + + std::string toString(); + FhgfsOpsErr modifyModeBits(int& outMode, bool& outNeedsACL); + bool empty() { return entries.empty(); } + + static const std::string defaultACLXAttrName; + static const std::string accessACLXAttrName; + + private: + ACLEntryVec entries; +}; + diff --git a/meta/source/storage/SyncedDiskAccessPath.h b/meta/source/storage/SyncedDiskAccessPath.h new file mode 100644 index 0000000..c577545 --- /dev/null +++ b/meta/source/storage/SyncedDiskAccessPath.h @@ -0,0 +1,133 @@ +#pragma once + +#include +#include +#include + + +class SyncedDiskAccessPath : public Path +{ + public: + SyncedDiskAccessPath() : Path() + { + initStorageVersion(); + } + + SyncedDiskAccessPath(const std::string& pathStr) : Path(pathStr) + { + initStorageVersion(); + } + + + private: + uint64_t storageVersion; // zero is the invalid version! + uint64_t highVersion; // high part of storageVersion (high 42 bits) + unsigned int lowVersion; // low part of storageVersion (low 22 bits) + + Mutex diskMutex; + + // inliners + void initStorageVersion() + { + const uint64_t currentSecs = System::getCurrentTimeSecs(); + const unsigned int minorVersion = 0; + + setStorageVersion(currentSecs, minorVersion); + } + + /** + * @param high currentTimeSecs (high 42 bits) + * @param low minorVersion (low 22 bits) + */ + void setStorageVersion(uint64_t highVersion, unsigned int lowVersion) + { + this->highVersion = highVersion; + this->lowVersion = lowVersion; + + this->storageVersion = (highVersion << 22); + this->storageVersion |= ( (lowVersion << 10) >> 10); + } + + public: + // inliners + + /** + * note: remember to call storageUpdateEnd() + * @return storageVersion of the current update + */ + void storageUpdateBegin() + { + diskMutex.lock(); + } + + void storageUpdateEnd() + { + diskMutex.unlock(); + } + + + /** + * Requires a call to storageUpdateBegin before! (and ...End() afterwards) + */ + uint64_t incStorageVersion() + { + // note: this relies on the fact that the currentTimeSecs never ever decrease! + + uint64_t nextHighVersion; + unsigned int nextLowVersion; + + // we try to avoid the getTime sys-call for some minor versions + if(lowVersion < 1024) + { + nextHighVersion = this->highVersion; // remains unchanged + nextLowVersion = this->lowVersion+1; + } + else + { + // in this case, we have to check that the timeSecs really have increased + // before we reset the minor version + nextHighVersion = System::getCurrentTimeSecs(); + + if(nextHighVersion == this->highVersion) + { // no timeSecs increase yet => use the minor version + nextLowVersion = this->lowVersion+1; + } + else + { // high version increased => we can reset the minor version + nextLowVersion = 0; + } + } + + setStorageVersion(nextHighVersion, nextLowVersion); + + return storageVersion; + } + + bool createSubPathOnDisk(Path& subpath, bool excludeLastElement) + { + storageUpdateBegin(); + + Path completePath = *this / subpath; + + bool createRes = StorageTk::createPathOnDisk(completePath, excludeLastElement); + + storageUpdateEnd(); + + return createRes; + } + + bool removeSubPathDirsFromDisk(Path& subpath, unsigned subKeepDepth) + { + storageUpdateBegin(); + + Path completePath = *this / subpath; + + bool removeRes = StorageTk::removePathDirsFromDisk( + completePath, subKeepDepth + this->size() ); + + storageUpdateEnd(); + + return removeRes; + } +}; + diff --git a/meta/source/toolkit/BuddyCommTk.cpp b/meta/source/toolkit/BuddyCommTk.cpp new file mode 100644 index 0000000..63e65be --- /dev/null +++ b/meta/source/toolkit/BuddyCommTk.cpp @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include + +#include "BuddyCommTk.h" + +// TODO: file handling here is duplicated from storage::StorageTarget, unify the two at some point + +#define BUDDY_NEEDS_RESYNC_FILENAME ".buddyneedsresync" + +namespace { + +enum { + BUDDY_RESYNC_UNACKED_FLAG = 1, + BUDDY_RESYNC_REQUIRED_FLAG = 2, + + BUDDY_RESYNC_NOT_REQUIRED = 0, + BUDDY_RESYNC_NOT_REQUIRED_UNACKED = BUDDY_RESYNC_UNACKED_FLAG, + BUDDY_RESYNC_REQUIRED = BUDDY_RESYNC_REQUIRED_FLAG, + BUDDY_RESYNC_REQUIRED_UNACKED = BUDDY_RESYNC_REQUIRED_FLAG | BUDDY_RESYNC_UNACKED_FLAG, +}; + +RWLock buddyNeedsResyncLock; +std::unique_ptr> buddyNeedsResyncFile; +boost::optional setBuddyNeedsResyncEntry; + +bool setBuddyNeedsResyncComm(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, TimerQueue& timerQ, + NumNodeID localNodeID); + +void retrySetBuddyNeedsResyncComm(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, + TimerQueue& timerQ, const NumNodeID localNodeID) +{ + const RWLockGuard lock(buddyNeedsResyncLock, SafeRWLock_WRITE); + setBuddyNeedsResyncComm(mgmtNode, bgm, timerQ, localNodeID); +} + +void setBuddyNeedsResync(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, TimerQueue& timerQ, + const NumNodeID localNodeID, const bool needsResync) +{ + const RWLockGuard lock(buddyNeedsResyncLock, SafeRWLock_WRITE); + + const auto oldState = buddyNeedsResyncFile->read().get_value_or(BUDDY_RESYNC_NOT_REQUIRED); + const auto newState = needsResync + ? BUDDY_RESYNC_REQUIRED_UNACKED + : BUDDY_RESYNC_NOT_REQUIRED_UNACKED; + + // if the change has already been requested by some other thread, we should not request it + // again - even if the change is unacked, as retrying immediately after a failed communication + // attempt is not likely to be successful, we must handle externally started resyncs however, + // which *do not* change the buddyneedsresync file contents but *do* use this mechanism to + // communicate that a resync has finished and the buddy is good again - these only use us to + // set the buddy to "needs no resync" though, so we can still skip setting needs-resync when that + // is already pending. + if (needsResync + && (oldState & BUDDY_RESYNC_REQUIRED_FLAG) == (newState & BUDDY_RESYNC_REQUIRED_FLAG)) + return; + + // cancel any pending retries, we will send a message to mgmt anyway. + if (setBuddyNeedsResyncEntry) + setBuddyNeedsResyncEntry->cancel(); + + buddyNeedsResyncFile->write(newState); + + if (!setBuddyNeedsResyncComm(mgmtNode, bgm, timerQ, localNodeID)) + LOG(GENERAL, CRITICAL, "Could not reach mgmt for state update, will retry.", + ("buddyNeedsResync", needsResync)); +} + +bool getBuddyNeedsResync() +{ + const RWLockGuard lock(buddyNeedsResyncLock, SafeRWLock_READ); + + const auto state = buddyNeedsResyncFile->read().get_value_or(BUDDY_RESYNC_NOT_REQUIRED); + return state & BUDDY_RESYNC_REQUIRED_FLAG; +} + +bool setBuddyNeedsResyncComm(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, TimerQueue& timerQ, + const NumNodeID localNodeID) +{ + // this is a timer callback. as such we must be prepared to deal with the fact that we were + // cancelled *after* we were dequeued and started executing, but were blocked on the lock in + // retrySetBuddyNeedsResyncComm. always reading the current state and sending that fixes this: + // if the state is not unacked we can return without doing anything, and if it is nobody can + // change it while we are using it. + const auto state = buddyNeedsResyncFile->read().get_value_or(BUDDY_RESYNC_NOT_REQUIRED); + const bool needsResync = state & BUDDY_RESYNC_REQUIRED_FLAG; + + if (!(state & BUDDY_RESYNC_UNACKED_FLAG)) + return true; + + const TargetConsistencyState stateToSet = needsResync + ? TargetConsistencyState_NEEDS_RESYNC + : TargetConsistencyState_GOOD; + + bool currentIsPrimary; + const uint16_t buddyTargetID = bgm.getBuddyTargetID(localNodeID.val(), ¤tIsPrimary); + + // until mgmt handles resync decision, refuse to set a primary to needs-resync locally. + if (!currentIsPrimary) + { + buddyNeedsResyncFile->write(BUDDY_RESYNC_NOT_REQUIRED); + return true; + } + + UInt16List targetIDList(1, buddyTargetID); + UInt8List stateList(1, stateToSet); + + SetTargetConsistencyStatesMsg msg(NODETYPE_Meta, &targetIDList, &stateList, false); + + const auto respMsg = MessagingTk::requestResponse(mgmtNode, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + + if (!respMsg) + { + setBuddyNeedsResyncEntry = timerQ.enqueue(std::chrono::seconds(5), [&, localNodeID] { + retrySetBuddyNeedsResyncComm(mgmtNode, bgm, timerQ, localNodeID); + }); + return false; + } + + auto* respMsgCast = (SetTargetConsistencyStatesRespMsg*)respMsg.get(); + + if (respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + LOG(GENERAL, CRITICAL, "Management node did not accept target states.", buddyTargetID, + needsResync); + buddyNeedsResyncFile->write(BUDDY_RESYNC_NOT_REQUIRED); + return true; + } + + buddyNeedsResyncFile->write(state & ~BUDDY_RESYNC_UNACKED_FLAG); + if (state & BUDDY_RESYNC_REQUIRED_FLAG) + LOG(GENERAL, CRITICAL, "Marked secondary buddy for needed resync.", + ("primary node", localNodeID.val())); + return true; +} + +} + +namespace BuddyCommTk +{ + + void prepareBuddyNeedsResyncState(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, + TimerQueue& timerQ, const NumNodeID localNodeID) + { + buddyNeedsResyncFile = std::make_unique>( + BUDDY_NEEDS_RESYNC_FILENAME, S_IRUSR | S_IWUSR); + + if (buddyNeedsResyncFile->read().get_value_or(0) & BUDDY_RESYNC_UNACKED_FLAG) + { + setBuddyNeedsResyncEntry = timerQ.enqueue(std::chrono::seconds(0), [&, localNodeID] { + retrySetBuddyNeedsResyncComm(mgmtNode, bgm, timerQ, localNodeID); + }); + } + } + + void checkBuddyNeedsResync() + { + App* app = Program::getApp(); + MirrorBuddyGroupMapper* metaBuddyGroups = app->getMetaBuddyGroupMapper(); + TargetStateStore* metaNodeStates = app->getMetaStateStore(); + InternodeSyncer* internodeSyncer = app->getInternodeSyncer(); + BuddyResyncer* buddyResyncer = app->getBuddyResyncer(); + + NumNodeID localID = app->getLocalNodeNumID(); + bool isPrimary; + NumNodeID buddyID = NumNodeID(metaBuddyGroups->getBuddyTargetID(localID.val(), &isPrimary) ); + + if (isPrimary) // Only do the check if we are the primary. + { + // check if the secondary is set to needs-resync by the mgmtd. + TargetConsistencyState consistencyState = internodeSyncer->getNodeConsistencyState(); + + // If our own state is not good, don't start resync (wait until InternodeSyncer sets us + // good again). + if (consistencyState != TargetConsistencyState_GOOD) + { + LOG_DEBUG(__func__, Log_DEBUG, + "Local node state is not good, won't check buddy state."); + return; + } + + CombinedTargetState buddyState; + if (!metaNodeStates->getState(buddyID.val(), buddyState) ) + { + LOG_DEBUG(__func__, Log_DEBUG, "Buddy state is invalid for node ID " + + buddyID.str() + "."); + return; + } + + if (buddyState == CombinedTargetState(TargetReachabilityState_ONLINE, + TargetConsistencyState_NEEDS_RESYNC) ) + { + FhgfsOpsErr resyncRes = buddyResyncer->startResync(); + + if (resyncRes == FhgfsOpsErr_SUCCESS) + { + LOG(MIRRORING, WARNING, + "Starting buddy resync job.", ("Buddy node ID", buddyID.val())); + } + else if (resyncRes == FhgfsOpsErr_INUSE) + { + LOG(MIRRORING, WARNING, + "Resync job currently running.", ("Buddy node ID", buddyID.val())); + } + else + { + LOG(MIRRORING, WARNING, + "Starting buddy resync job failed.", ("Buddy node ID", buddyID.val())); + } + } + } + } + + void setBuddyNeedsResync(const std::string& path, bool needsResync) + { + auto* const app = Program::getApp(); + + ::setBuddyNeedsResync(*app->getMgmtNodes()->referenceFirstNode(), + *app->getMetaBuddyGroupMapper(), *app->getTimerQueue(), + app->getLocalNode().getNumID(), needsResync); + } + + bool getBuddyNeedsResync() + { + return ::getBuddyNeedsResync(); + } +}; + diff --git a/meta/source/toolkit/BuddyCommTk.h b/meta/source/toolkit/BuddyCommTk.h new file mode 100644 index 0000000..625de78 --- /dev/null +++ b/meta/source/toolkit/BuddyCommTk.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#include +#include + +class MirrorBuddyGroupMapper; +class TimerQueue; + +/** + * This contains all the functions regarding the communication with the mirror buddy. + * In the storage server, these mostly live in the StorageTargets class, but because the metadata + * server only has a single metadata target at the moment, we group them here. + */ +namespace BuddyCommTk +{ + void prepareBuddyNeedsResyncState(Node& mgmtNode, const MirrorBuddyGroupMapper& bgm, + TimerQueue& timerQ, NumNodeID localNodeID); + + void checkBuddyNeedsResync(); + void setBuddyNeedsResync(const std::string& path, bool needsResync); + bool getBuddyNeedsResync(); +}; + diff --git a/meta/source/toolkit/StorageTkEx.cpp b/meta/source/toolkit/StorageTkEx.cpp new file mode 100644 index 0000000..4df74dc --- /dev/null +++ b/meta/source/toolkit/StorageTkEx.cpp @@ -0,0 +1,152 @@ +#include +#include "StorageTkEx.h" + +#include + +#define STORAGETK_FORMAT_XATTR "xattr" + + +/** + * Note: Creates the file only if it does not exist yet. + * + * @return true if format file was created or existed already + */ +bool StorageTkEx::createStorageFormatFile(const std::string pathStr) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + StringMap formatProperties; + + formatProperties[STORAGETK_FORMAT_XATTR] = cfg->getStoreUseExtendedAttribs() ? "true" : "false"; + + return StorageTk::createStorageFormatFile(pathStr, STORAGETK_FORMAT_CURRENT_VERSION, + &formatProperties); +} + +/** + * Compatibility and validity check of storage format file contents. + * + * @param pathStr path to the main storage working directory (not including a filename) + * @throws exception if format file was not valid (eg didn't exist or contained wrong version). + */ +void StorageTkEx::checkStorageFormatFile(const std::string pathStr) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + StringMap formatProperties; + StringMapIter formatIter; + + formatProperties = StorageTk::loadAndUpdateStorageFormatFile(pathStr, + STORAGETK_FORMAT_MIN_VERSION, STORAGETK_FORMAT_CURRENT_VERSION); + + formatIter = formatProperties.find(STORAGETK_FORMAT_XATTR); + if(formatIter == formatProperties.end() ) + { + throw InvalidConfigException(std::string("Property missing from storage format file: ") + + STORAGETK_FORMAT_XATTR " (dir: " + pathStr + ")"); + } + + if(cfg->getStoreUseExtendedAttribs() != StringTk::strToBool(formatIter->second) ) + { + throw InvalidConfigException("Mismatch of extended attributes settings in storage format file" + " and daemon config file."); + } +} + +/** + * Note: intended to be used with fsck only. + * No locks at all at the moment + */ +FhgfsOpsErr StorageTkEx::getContDirIDsIncremental(unsigned hashDirNum, bool buddyMirrored, + int64_t lastOffset, unsigned maxOutEntries, StringList* outContDirIDs, int64_t* outNewOffset) +{ + const char* logContext = "StorageTkEx (get cont dir ids inc)"; + App* app = Program::getApp(); + + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + unsigned numEntries = 0; + struct dirent* dirEntry = NULL; + + unsigned firstLevelHashDir; + unsigned secondLevelHashDir; + StorageTk::splitHashDirs(hashDirNum, &firstLevelHashDir, &secondLevelHashDir); + + const Path* dentriesPath = + buddyMirrored ? app->getBuddyMirrorDentriesPath() : app->getDentriesPath(); + + std::string path = StorageTkEx::getMetaDentriesHashDir(dentriesPath->str(), + firstLevelHashDir, secondLevelHashDir); + + DIR* dirHandle = opendir(path.c_str() ); + if(!dirHandle) + { + LogContext(logContext).logErr(std::string("Unable to open dentries directory: ") + + path + ". SysErr: " + System::getErrString() ); + + goto return_res; + } + + + errno = 0; // recommended by posix (readdir(3p) ) + + // seek to offset + seekdir(dirHandle, lastOffset); // (seekdir has no return value) + + // the actual entry reading + while ( (numEntries < maxOutEntries) && (dirEntry = StorageTk::readdirFiltered(dirHandle)) ) + { + std::string dirName = dirEntry->d_name; + std::string dirID = dirName.substr(0, dirName.length() ); + + *outNewOffset = dirEntry->d_off; + + // skip root dir if this is not the root MDS + NumNodeID rootNodeNumID = Program::getApp()->getMetaRoot().getID(); + NumNodeID localNodeNumID = buddyMirrored + ? NumNodeID(Program::getApp()->getMetaBuddyGroupMapper()->getLocalGroupID()) + : Program::getApp()->getLocalNode().getNumID(); + if (unlikely ( (dirID.compare(META_ROOTDIR_ID_STR) == 0) && + (localNodeNumID != rootNodeNumID) )) + continue; + + outContDirIDs->push_back(dirID); + numEntries++; + } + + if(!dirEntry && errno) + { + LogContext(logContext).logErr(std::string("Unable to fetch dentry from: ") + + path + ". SysErr: " + System::getErrString() ); + } + else + { // all entries read + retVal = FhgfsOpsErr_SUCCESS; + } + + + closedir(dirHandle); + +return_res: + return retVal; +} + +/** + * Note: intended to be used with fsck only + */ +bool StorageTkEx::getNextContDirID(unsigned hashDirNum, bool buddyMirrored, int64_t lastOffset, + std::string* outID, int64_t* outNewOffset) +{ + *outID = ""; + StringList outIDs; + FhgfsOpsErr retVal = getContDirIDsIncremental(hashDirNum, buddyMirrored, lastOffset, 1, &outIDs, + outNewOffset); + if ( ( outIDs.empty() ) || ( retVal != FhgfsOpsErr_SUCCESS ) ) + return false; + else + { + *outID = outIDs.front(); + return true; + } +} diff --git a/meta/source/toolkit/StorageTkEx.h b/meta/source/toolkit/StorageTkEx.h new file mode 100644 index 0000000..75cf1ef --- /dev/null +++ b/meta/source/toolkit/StorageTkEx.h @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include + + +/* + * Note: Some inliners are in Commons MetaStoreTk:: + */ + +#define STORAGETK_FORMAT_MIN_VERSION 3 +#define STORAGETK_FORMAT_CURRENT_VERSION 4 + +// forward declarations +class DirInode; +class FileInode; + +class StorageTkEx +{ + public: + static bool createStorageFormatFile(const std::string pathStr); + static void checkStorageFormatFile(const std::string pathStr); + + static FhgfsOpsErr getContDirIDsIncremental(unsigned hashDirNum, bool buddyMirrored, + int64_t lastOffset, unsigned maxOutEntries, StringList* outContDirIDs, + int64_t* outNewOffset); + static bool getNextContDirID(unsigned hashDirNum, bool buddyMirrored, int64_t lastOffset, + std::string* outID, int64_t* outNewOffset); + + private: + StorageTkEx() + { + } + + + public: + + // inliners + + static std::string getMetaInodeHashDir(const std::string entriesPath, + unsigned firstLevelhashDirNum, unsigned secondLevelhashDirNum) + { + return entriesPath + "/" + StringTk::uintToHexStr(firstLevelhashDirNum) + "/" + + StringTk::uintToHexStr(secondLevelhashDirNum); + } + + static std::string getMetaDentriesHashDir(const std::string structurePath, + unsigned firstLevelhashDirNum, unsigned secondLevelhashDirNum) + { + return structurePath + "/" + StringTk::uintToHexStr(firstLevelhashDirNum) + "/" + + StringTk::uintToHexStr(secondLevelhashDirNum); + } +}; + diff --git a/meta/source/toolkit/XAttrTk.cpp b/meta/source/toolkit/XAttrTk.cpp new file mode 100644 index 0000000..1fbe891 --- /dev/null +++ b/meta/source/toolkit/XAttrTk.cpp @@ -0,0 +1,208 @@ +#include "XAttrTk.h" + +#include +#include +#include + +#include +#include + +#include +#include + +namespace XAttrTk +{ + +const std::string UserXAttrPrefix("user.bgXA."); + + +std::pair> listXAttrs(const std::string& path) +{ + ssize_t size = ::listxattr(path.c_str(), NULL, 0); // get size of raw list + + if(size < 0) + { + LOG(GENERAL, ERR, "listxattr failed", path, sysErr); + return {FhgfsOpsErr_INTERNAL, {}}; + } + + std::unique_ptr xAttrRawList(new (std::nothrow) char[size]); + + if (!xAttrRawList) + return {FhgfsOpsErr_INTERNAL, {}}; + + size = ::listxattr(path.c_str(), xAttrRawList.get(), size); // get actual raw list + + if (size >= 0) + { + std::vector names; + + StringTk::explode(std::string(xAttrRawList.get(), size), '\0', &names); + return {FhgfsOpsErr_SUCCESS, std::move(names)}; + } + + int err = errno; + + switch (err) + { + case ERANGE: + case E2BIG: + return {FhgfsOpsErr_RANGE, {}}; + + case ENOENT: + return {FhgfsOpsErr_PATHNOTEXISTS, {}}; + + default: // don't forward other errors to the client, but log them on the server + LOG(GENERAL, ERR, "listxattr failed", path, sysErr); + return {FhgfsOpsErr_INTERNAL, {}}; + } +} + +std::tuple, ssize_t> getXAttr(const std::string& path, + const std::string& name, size_t maxSize) +{ + std::vector result(maxSize, 0); + + ssize_t size = getxattr(path.c_str(), name.c_str(), &result.front(), result.size()); + + if (size >= 0) + { + result.resize(std::min(size, maxSize)); + return std::make_tuple(FhgfsOpsErr_SUCCESS, std::move(result), size); + } + + switch (errno) + { + case ENODATA: + return std::make_tuple(FhgfsOpsErr_NODATA, std::vector(), ssize_t(0)); + + case ERANGE: + case E2BIG: + return std::make_tuple(FhgfsOpsErr_RANGE, std::vector(), ssize_t(0)); + + case ENOENT: + return std::make_tuple(FhgfsOpsErr_PATHNOTEXISTS, std::vector(), ssize_t(0)); + + default: // don't forward other errors to the client, but log them on the server + LOG(GENERAL, ERR, "getxattr failed", path, name, sysErr); + return std::make_tuple(FhgfsOpsErr_INTERNAL, std::vector(), ssize_t(0)); + } +} + +void sanitizeForUser(std::vector& names) +{ + removeMetadataAttrs(names); + + for (auto it = names.begin(); it != names.end(); ++it) + it->erase(0, UserXAttrPrefix.size()); +} + +static bool isMetaAttr(const std::string& attrName) +{ + return attrName.compare(0, UserXAttrPrefix.size(), UserXAttrPrefix) != 0; +} + +void removeMetadataAttrs(std::vector& names) +{ + names.erase( + std::remove_if(names.begin(), names.end(), isMetaAttr), + names.end()); +} + +FhgfsOpsErr setUserXAttr(const std::string& path, const std::string& name, const void* value, + size_t size, int flags) +{ + const bool limitXAttrListLength = Program::getApp()->getConfig()->getLimitXAttrListLength(); + + int res = limitXAttrListLength + ? setxattr(path.c_str(), (UserXAttrPrefix + name).c_str(), value, size, flags | XATTR_REPLACE) + : setxattr(path.c_str(), (UserXAttrPrefix + name).c_str(), value, size, flags); + + // if xattr list length is limited and we were not able to replace the attribute, we have to + // create a new one. (the user may specify XATTR_REPLACE as well, so that has to be checked for) + // if we have to create a new attribute, we must ensure that the xattr list length after the + // creation of the attribute does not exceed XATTR_LIST_MAX. + // use a large hash of mutexes to exclude concurrent setxattr operations on the same path. + // ideally we would want to use one mutex per worker thread, and ensure that each path has its + // own mutex, but that's not possible. using 1024 mutexes (more than one per worker, in the + // default configuration) and hashed pathnames is pretty much the best we can do here. + if (res < 0 && limitXAttrListLength && errno == ENODATA && !(flags & XATTR_REPLACE)) + { + static std::array mutexHash; + + Mutex& mtx = mutexHash[std::hash()(path) % mutexHash.size()]; + + std::lock_guard lock(mtx); + + ssize_t listRes = ::listxattr(path.c_str(), NULL, 0); + if (listRes < 0) + res = listRes; + else if (listRes + UserXAttrPrefix.size() + name.size() + 1 > XATTR_LIST_MAX) + return FhgfsOpsErr_NOSPACE; + + res = setxattr(path.c_str(), (UserXAttrPrefix + name).c_str(), value, size, flags); + } + + if (res == 0) + return FhgfsOpsErr_SUCCESS; + + switch (errno) + { + case EEXIST: + return FhgfsOpsErr_EXISTS; + + case ENODATA: + return FhgfsOpsErr_NODATA; + + case ERANGE: + case E2BIG: + return FhgfsOpsErr_RANGE; + + case ENOENT: + return FhgfsOpsErr_PATHNOTEXISTS; + + case ENOSPC: + return FhgfsOpsErr_NOSPACE; + + default: // don't forward other errors to the client, but log them on the server + LOG(GENERAL, ERR, "failed to set xattr", path, name, sysErr); + return FhgfsOpsErr_INTERNAL; + } +} + +FhgfsOpsErr removeUserXAttr(const std::string& path, const std::string& name) +{ + int res = removexattr(path.c_str(), (UserXAttrPrefix + name).c_str()); + + if (res == 0) + return FhgfsOpsErr_SUCCESS; + + switch (errno) + { + case ENODATA: + return FhgfsOpsErr_NODATA; + + case ERANGE: + case E2BIG: + return FhgfsOpsErr_RANGE; + + case ENOENT: + return FhgfsOpsErr_PATHNOTEXISTS; + + default: // don't forward other errors to the client, but log them on the server + LOG(GENERAL, ERR, "failed to remove xattr", path, name, sysErr); + return FhgfsOpsErr_INTERNAL; + } +} + +std::pair> listUserXAttrs(const std::string& path) +{ + auto listRes = listXAttrs(path); + if (listRes.first == FhgfsOpsErr_SUCCESS) + sanitizeForUser(listRes.second); + + return listRes; +} + + +} diff --git a/meta/source/toolkit/XAttrTk.h b/meta/source/toolkit/XAttrTk.h new file mode 100644 index 0000000..4885ce0 --- /dev/null +++ b/meta/source/toolkit/XAttrTk.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include + +namespace XAttrTk +{ + extern const std::string UserXAttrPrefix; + + std::pair> listXAttrs(const std::string& path); + + std::tuple, ssize_t> getXAttr(const std::string& path, + const std::string& name, size_t maxSize); + + void sanitizeForUser(std::vector& names); + + void removeMetadataAttrs(std::vector& names); + + FhgfsOpsErr setUserXAttr(const std::string& path, const std::string& name, const void* value, + size_t size, int flags); + + FhgfsOpsErr removeUserXAttr(const std::string& path, const std::string& name); + + inline std::tuple, ssize_t> getUserXAttr(const std::string& path, + const std::string& name, size_t maxSize) + { + return getXAttr(path, UserXAttrPrefix + name, maxSize); + } + + std::pair> listUserXAttrs(const std::string& path); +} + diff --git a/meta/tests/TestBuddyMirroring.cpp b/meta/tests/TestBuddyMirroring.cpp new file mode 100644 index 0000000..6192701 --- /dev/null +++ b/meta/tests/TestBuddyMirroring.cpp @@ -0,0 +1,235 @@ +#include +#include +#include + +#include + +class ParentNameLockTestThread : public PThread +{ + public: + ParentNameLockTestThread(const unsigned id, EntryLockStore& entryLockStore, + const std::string& parentID, const std::string& name): + PThread("ParentNameLockTestThread" + StringTk::uintToStr(id)), + entryLockStore(entryLockStore), parentID(parentID), name(name) + { + } + + void run() override + { + const unsigned maxSleepSeconds = 1; + + const std::string threadName = getCurrentThreadName(); + const char* logContext(threadName.c_str()); + + Random r; + const int sleepSec = r.getNextInRange(0, maxSleepSeconds); + + LogContext(logContext).log(Log_DEBUG, "Trying to lock '" + parentID + "/" + name + "'"); + + ParentNameLockData* lock = entryLockStore.lock(parentID, name); + + LogContext(logContext).log(Log_DEBUG, "Locked '" + parentID + "/" + name + "'"); + LogContext(logContext).log(Log_DEBUG, "Sleeping for " + StringTk::intToStr(sleepSec) + + " seconds"); + + sleep(sleepSec); + + LogContext(logContext).log(Log_DEBUG, "Trying to unlock '" + parentID + "/" + name + "'"); + + entryLockStore.unlock(lock); + + LogContext(logContext).log(Log_DEBUG, "Unlocked '" + parentID + "/" + name + "'"); + } + + private: + EntryLockStore& entryLockStore; + const std::string parentID; + const std::string name; +}; + +class FileIDLockTestThread : public PThread +{ + public: + FileIDLockTestThread(const unsigned id, EntryLockStore& entryLockStore, + const std::string& fileID) : + PThread("FileIDLockTestThread" + StringTk::uintToStr(id)), entryLockStore(entryLockStore), + fileID(fileID) + { + } + + void run() override + { + const unsigned maxSleepSeconds = 1; + + const std::string threadName = getCurrentThreadName(); + const char* logContext(threadName.c_str()); + + Random r; + const int sleepSec = r.getNextInRange(0, maxSleepSeconds); + + LogContext(logContext).log(Log_DEBUG, "Trying to lock '" + fileID + "'"); + + FileIDLockData* lock = entryLockStore.lock(fileID, true); + + LogContext(logContext).log(Log_DEBUG, "Locked '" + fileID + "'"); + LogContext(logContext).log(Log_DEBUG, "Sleeping for " + StringTk::intToStr(sleepSec) + + " seconds"); + + sleep(sleepSec); + + LogContext(logContext).log(Log_DEBUG, "Trying to unlock '" + fileID + "'"); + + entryLockStore.unlock(lock); + + LogContext(logContext).log(Log_DEBUG, "Unlocked '" + fileID + "'"); + } + + private: + EntryLockStore& entryLockStore; + const std::string fileID; +}; + +class DirIDLockTestThread : public PThread +{ + public: + DirIDLockTestThread(const unsigned id, EntryLockStore& entryLockStore, + const std::string& dirID, const bool writeLock) : + PThread("DirIDLockTestThread" + StringTk::uintToStr(id)), entryLockStore(entryLockStore), + dirID(dirID), writeLock(writeLock) + { + } + + void run() override + { + const unsigned maxSleepSeconds = 1; + const std::string logInfo(writeLock ? dirID + "/write" : dirID + "/read"); + const std::string threadName = getCurrentThreadName(); + const char* logContext(threadName.c_str()); + + Random r; + const int sleepSec = r.getNextInRange(0, maxSleepSeconds); + + LogContext(logContext).log(Log_DEBUG, "Trying to lock '" + logInfo + "'"); + + FileIDLockData* lock = entryLockStore.lock(dirID, writeLock); + + LogContext(logContext).log(Log_DEBUG, "Locked '" + logInfo + "'"); + LogContext(logContext).log(Log_DEBUG, "Sleeping for " + StringTk::intToStr(sleepSec) + + " seconds"); + + sleep(sleepSec); + + LogContext(logContext).log(Log_DEBUG, "Trying to unlock '" + logInfo + "'"); + + entryLockStore.unlock(lock); + + LogContext(logContext).log(Log_DEBUG, "Unlocked '" + logInfo + "'"); + } + + private: + EntryLockStore& entryLockStore; + const std::string dirID; + const bool writeLock; +}; + +TEST(BuddyMirroring, simpleEntryLocks) +{ + EntryLockStore entryLockStore; + + const unsigned numThreadsEach = 15; + + std::list threadList; + + // prepare ParentNameLocks + + std::string parentIDA = "PIDA"; + std::string nameA = "NAMEA"; + + std::string parentIDB = "PIDB"; + std::string nameB = "NAMEB"; + + unsigned i=0; + for ( ; i::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + (*iter)->start(); + + for ( std::list::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + (*iter)->join(); + + for ( std::list::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + SAFE_DELETE(*iter); +} + +TEST(BuddyMirroring, rwEntryLocks) +{ + EntryLockStore entryLockStore; + + const unsigned numThreadsRead = 15; + const unsigned numThreadsWrite = 5; + + std::list threadList; + + // prepare FileIDLocks for Directories + std::string dirID = "dirID"; + for (unsigned i=0 ; i::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + (*iter)->start(); + + for ( std::list::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + (*iter)->join(); + + for ( std::list::iterator iter = threadList.begin(); + iter != threadList.end(); iter++ ) + SAFE_DELETE(*iter); +} diff --git a/meta/tests/TestConfig.cpp b/meta/tests/TestConfig.cpp new file mode 100644 index 0000000..979090e --- /dev/null +++ b/meta/tests/TestConfig.cpp @@ -0,0 +1,108 @@ +#include "TestConfig.h" + +TestConfig::TestConfig() + : log("TestConfig") +{ +} + +TestConfig::~TestConfig() +{ +} + +void TestConfig::SetUp() +{ + emptyConfigFile = DUMMY_EMPTY_CONFIG_FILE; + this->dummyConfigFile = DUMMY_NOEXIST_CONFIG_FILE; +} + +void TestConfig::TearDown() +{ + // delete generated config file + if (StorageTk::pathExists(this->emptyConfigFile)) + { + /* TODO : return value of remove is ignored now; + * maybe we should notify the user here (but that + * would break test output) + */ + remove(this->emptyConfigFile.c_str()); + } +} + +TEST_F(TestConfig, missingConfigFile) +{ + log.log(Log_DEBUG, "testMissingConfigFile started"); + + // generate a bogus name for a config file + /* normally the default file should be nonexistant, but to be absolutely sure, + we check that and, in case, append a number */ + int appendix = 0; + while (StorageTk::pathExists(this->dummyConfigFile)) + { + appendix++; + this->dummyConfigFile = DUMMY_NOEXIST_CONFIG_FILE + StringTk::intToStr(appendix); + } + + int argc = 2; + char* argv[2]; + + std::string appNameStr = APP_NAME; + appNameStr += '\0'; + + std::string cfgLineStr = "cfgFile=" + this->dummyConfigFile; + cfgLineStr += '\0'; + + argv[0] = &appNameStr[0]; + argv[1] = &cfgLineStr[0]; + + // should throw InvalidConfigException now + ASSERT_THROW(Config config(argc, argv), InvalidConfigException); + + log.log(Log_DEBUG, "testMissingConfigFile finished"); +} + +TEST_F(TestConfig, defaultConfigFile) +{ + log.log(Log_DEBUG, "testDefaultConfigFile started"); + + // get the path where the binary resides + int BUFSIZE = 255; + char exePathBuf[BUFSIZE]; + // read only BUFSIZE-1, as we need to terminate the string manually later + ssize_t len = readlink("/proc/self/exe", exePathBuf, BUFSIZE-1); + + /* In case of an error, failure will indicate the error */ + if (len < 0) + FAIL() << "Internal error"; + + /* in case of insufficient buffer size, failure will indicate the error */ + if (len >= BUFSIZE) + FAIL() << "Internal error"; + + // readlink does NOT null terminate the string, so we do it here to be safe + exePathBuf[len] = '\0'; + + // construct the path to the default config file + std::string defaultFileName = std::string(dirname(exePathBuf)) + + "/" + DEFAULT_CONFIG_FILE_RELATIVE; + + // create config with the default file and see what happens while parsing + int argc = 2; + char* argv[2]; + + std::string appNameStr = APP_NAME; + appNameStr += '\0'; + + std::string cfgLineStr = "cfgFile=" + defaultFileName; + cfgLineStr += '\0'; + + argv[0] = &appNameStr[0]; + argv[1] = &cfgLineStr[0]; + + try { + Config config(argc, argv); + } catch (ConnAuthFileException& e) { + return; + } + + log.log(Log_DEBUG, "testDefaultConfigFile finished"); +} diff --git a/meta/tests/TestConfig.h b/meta/tests/TestConfig.h new file mode 100644 index 0000000..b073338 --- /dev/null +++ b/meta/tests/TestConfig.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#define DUMMY_NOEXIST_CONFIG_FILE "/tmp/nonExistantConfigFile.conf.meta" +#define DUMMY_EMPTY_CONFIG_FILE "/tmp/emptyConfigFile.conf.meta" +#define DEFAULT_CONFIG_FILE_RELATIVE "dist/etc/beegfs-meta.conf" +#define APP_NAME "beegfs-meta"; + +class TestConfig: public ::testing::Test +{ + public: + TestConfig(); + virtual ~TestConfig(); + + void SetUp() override; + void TearDown() override; + + protected: + LogContext log; + + // we have these filenames as member variables because + // we might need to delete them in tearDown function + std::string dummyConfigFile; + std::string emptyConfigFile; +}; + diff --git a/meta/tests/TestSerialization.cpp b/meta/tests/TestSerialization.cpp new file mode 100644 index 0000000..d42f48b --- /dev/null +++ b/meta/tests/TestSerialization.cpp @@ -0,0 +1,388 @@ +#include "TestSerialization.h" + +#include +#include +#include +#include +#include +#include + +TEST_F(TestSerialization, sessionSerialization) +{ + SessionStore sessionStore; + SessionStore sessionStoreClone; + + initSessionStoreForTests(sessionStore); + + size_t bufLen; + { + Serializer ser; + sessionStore.serialize(ser); + bufLen = ser.size(); + } + + boost::scoped_array buf(new char[bufLen]); + + Serializer ser(buf.get(), bufLen); + sessionStore.serialize(ser); + if (!ser.good() || ser.size() != bufLen) + FAIL() << "Serialization failed!"; + + Deserializer des(buf.get(), bufLen); + sessionStoreClone.deserialize(des); + if (!des.good()) + FAIL() << "Deserialization failed!"; + + if(sessionStore != sessionStoreClone) + FAIL() << "Sessions is not equal after serialization and deserialization!"; +} + +void TestSerialization::initSessionStoreForTests(SessionStore& testSessionStore) +{ + SettableFileAttribs* settableFileAttribs10 = new SettableFileAttribs(); + settableFileAttribs10->groupID = UINT_MAX; + settableFileAttribs10->lastAccessTimeSecs = std::numeric_limits::max(); + settableFileAttribs10->mode = INT_MAX; + settableFileAttribs10->modificationTimeSecs = std::numeric_limits::max(); + settableFileAttribs10->userID = UINT_MAX; + size_t numTargets10 = 25; + StatData* statData10 = new StatData(std::numeric_limits::min(), settableFileAttribs10, + std::numeric_limits::min(), std::numeric_limits::min(), 0, 0); + statData10->setSparseFlag(); + UInt16Vector* targets10 = new UInt16Vector(numTargets10); + TestSerialization::addRandomValuesToUInt16Vector(targets10, numTargets10); + UInt16Vector* mirrorTargets10 = new UInt16Vector(numTargets10); + TestSerialization::addRandomValuesToUInt16Vector(mirrorTargets10, numTargets10); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData10, numTargets10); + Raid10Pattern* stripePattern10 = new Raid10Pattern(UINT_MAX, *targets10, *mirrorTargets10, + numTargets10); + std::string* entryID10 = new std::string("0-00ADF231-7"); + std::string* origParentEntryID10 = new std::string("2-AFD734AF-6"); + FileInodeStoreData* fileInodeStoreData10 = new FileInodeStoreData(*entryID10, statData10, + stripePattern10, FILEINODE_FEATURE_HAS_ORIG_UID, 1345, *origParentEntryID10, + FileInodeOrigFeature_TRUE); + FileInode* inode10 = new FileInode(*entryID10, fileInodeStoreData10, DirEntryType_REGULARFILE, + UINT_MAX); + inode10->setIsInlinedUnlocked(false); + inode10->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo10 = new EntryInfo(NumNodeID(345), *origParentEntryID10, *entryID10, + "testfile10", DirEntryType_REGULARFILE, 0); + + SettableFileAttribs* settableFileAttribs11 = new SettableFileAttribs(); + settableFileAttribs11->groupID = 0; + settableFileAttribs11->lastAccessTimeSecs = std::numeric_limits::min(); + settableFileAttribs11->mode = INT_MIN; + settableFileAttribs11->modificationTimeSecs = std::numeric_limits::min(); + settableFileAttribs11->userID = 0; + size_t numTargets11 = 2487; + StatData* statData11 = new StatData(std::numeric_limits::max(), settableFileAttribs11, + std::numeric_limits::max(), std::numeric_limits::max(), + UINT_MAX, UINT_MAX); + statData11->setSparseFlag(); + UInt16Vector* targets11 = new UInt16Vector(numTargets11); + TestSerialization::addRandomValuesToUInt16Vector(targets11, numTargets11); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData11, numTargets11); + Raid0Pattern* stripePattern11 = new Raid0Pattern(1024, *targets11, numTargets11); + std::string* entryID11 = new std::string("1-345234AF-4"); + std::string* origParentEntryID11 = new std::string("1-892CD123-9"); + FileInodeStoreData* fileInodeStoreData11 = new FileInodeStoreData(*entryID11, statData11, + stripePattern11, FILEINODE_FEATURE_HAS_ORIG_UID, 145, *origParentEntryID11, + FileInodeOrigFeature_TRUE); + FileInode* inode11 = new FileInode(*entryID11, fileInodeStoreData11, DirEntryType_REGULARFILE, + 0); + inode11->setIsInlinedUnlocked(true); + inode11->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo11 = new EntryInfo(NumNodeID(45), *origParentEntryID11, *entryID11, + "testfile11", DirEntryType_FIFO, ENTRYINFO_FEATURE_INLINED); + + SettableFileAttribs* settableFileAttribs12 = new SettableFileAttribs(); + settableFileAttribs12->groupID = 457; + settableFileAttribs12->lastAccessTimeSecs = 7985603; + settableFileAttribs12->mode = 785; + settableFileAttribs12->modificationTimeSecs = 54113; + settableFileAttribs12->userID = 784; + size_t numTargets12 = 22; + StatData* statData12 = new StatData(55568, settableFileAttribs12, 785133, 92334, 45, 78); + statData12->setSparseFlag(); + UInt16Vector* targets12 = new UInt16Vector(numTargets12); + TestSerialization::addRandomValuesToUInt16Vector(targets12, numTargets12); + BuddyMirrorPattern* stripePattern12 = new BuddyMirrorPattern(4096, *targets12, numTargets12); + std::string* entryID12 = new std::string("8-DC12AF12-3"); + std::string* origParentEntryID12 = new std::string("9-FE998021-3"); + FileInodeStoreData* fileInodeStoreData12 = new FileInodeStoreData(*entryID12, statData12, + stripePattern12, FILEINODE_FEATURE_HAS_ORIG_UID, 1789, *origParentEntryID12, + FileInodeOrigFeature_TRUE); + FileInode* inode12 = new FileInode(*entryID12, fileInodeStoreData12, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode12->setIsInlinedUnlocked(true); + inode12->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo12 = new EntryInfo(NumNodeID(789), *origParentEntryID12, *entryID12, + "testfile12", DirEntryType_DIRECTORY, 0); + + SettableFileAttribs* settableFileAttribs13 = new SettableFileAttribs(); + settableFileAttribs13->groupID = 556; + settableFileAttribs13->lastAccessTimeSecs =798336; + settableFileAttribs13->mode = 456; + settableFileAttribs13->modificationTimeSecs = 12383635; + settableFileAttribs13->userID = 886; + size_t numTargets13 = 55; + StatData* statData13 = new StatData(775616, settableFileAttribs13, 56633, 565663, 7893, 1315); + UInt16Vector* targets13 = new UInt16Vector(numTargets13); + TestSerialization::addRandomValuesToUInt16Vector(targets13, numTargets13); + BuddyMirrorPattern* stripePattern13 = new BuddyMirrorPattern(10240, *targets13, numTargets13); + std::string* entryID13 = new std::string("3-CDF1239F-9"); + std::string* origParentEntryID13 = new std::string("4-DFA23A31-2"); + FileInodeStoreData* fileInodeStoreData13 = new FileInodeStoreData(*entryID13, statData13, + stripePattern13, FILEINODE_FEATURE_HAS_ORIG_UID, 1892, *origParentEntryID13, + FileInodeOrigFeature_TRUE); + FileInode* inode13 = new FileInode(*entryID13, fileInodeStoreData13, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode13->setIsInlinedUnlocked(false); + inode13->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo13 = new EntryInfo(NumNodeID(892), *origParentEntryID13, *entryID13, + "testfile13", DirEntryType_REGULARFILE, ENTRYINFO_FEATURE_INLINED); + + SettableFileAttribs* settableFileAttribs14 = new SettableFileAttribs(); + settableFileAttribs14->groupID = 8963; + settableFileAttribs14->lastAccessTimeSecs = 3468869; + settableFileAttribs14->mode = 6858; + settableFileAttribs14->modificationTimeSecs = 167859; + settableFileAttribs14->userID = 23; + size_t numTargets14 = 2; + StatData* statData14 = new StatData(5435585, settableFileAttribs14, 12855, 78, 965, 55); + statData14->setSparseFlag(); + UInt16Vector* targets14 = new UInt16Vector(numTargets14); + TestSerialization::addRandomValuesToUInt16Vector(targets14, numTargets14); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData14, numTargets14); + Raid0Pattern* stripePattern14 = new Raid0Pattern (512, *targets14, numTargets14); + std::string* entryID14 = new std::string("4-345234AF-5"); + std::string* origParentEntryID14 = new std::string("8-AB782DDF-6"); + FileInodeStoreData* fileInodeStoreData14 = new FileInodeStoreData(*entryID14, statData14, + stripePattern14, FILEINODE_FEATURE_HAS_ORIG_UID, 11021, *origParentEntryID14, + FileInodeOrigFeature_TRUE); + FileInode* inode14 = new FileInode(*entryID14, fileInodeStoreData14, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode14->setIsInlinedUnlocked(false); + inode14->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo14 = new EntryInfo(NumNodeID(1021), *origParentEntryID14, *entryID14, + "testfile14", DirEntryType_SYMLINK, ENTRYINFO_FEATURE_INLINED); + + + SettableFileAttribs* settableFileAttribs20 = new SettableFileAttribs(); + settableFileAttribs20->groupID = 6644; + settableFileAttribs20->lastAccessTimeSecs = 546463; + settableFileAttribs20->mode = 882; + settableFileAttribs20->modificationTimeSecs = 45446; + settableFileAttribs20->userID = 556; + size_t numTargets20 = 3; + StatData* statData20 = new StatData(95483, settableFileAttribs20, 35465, 4556, 78, 62); + statData20->setSparseFlag(); + UInt16Vector* targets20 = new UInt16Vector(numTargets20); + TestSerialization::addRandomValuesToUInt16Vector(targets20, 1); + targets20->at(1) = std::numeric_limits::max(); + targets20->at(2) = 0; + TestSerialization::addRandomTargetChunkBlocksToStatData(statData20, numTargets20); + Raid0Pattern* stripePattern20 = new Raid0Pattern(2048, *targets20, numTargets20); + std::string* entryID20 = new std::string("9-AF891DC1-1"); + std::string* origParentEntryID20 = new std::string("4-DFA4512F-7"); + FileInodeStoreData* fileInodeStoreData20 = new FileInodeStoreData(*entryID20, statData20, + stripePattern20, 0, 2331, *origParentEntryID20, FileInodeOrigFeature_TRUE); + FileInode* inode20 = new FileInode(*entryID20, fileInodeStoreData20, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode20->setIsInlinedUnlocked(true); + inode20->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo20 = new EntryInfo(NumNodeID(331), *origParentEntryID20, *entryID20, + "testfile20", DirEntryType_REGULARFILE, INT_MIN); + + SettableFileAttribs* settableFileAttribs21 = new SettableFileAttribs(); + settableFileAttribs21->groupID = 45; + settableFileAttribs21->lastAccessTimeSecs = 5416131; + settableFileAttribs21->mode = 533; + settableFileAttribs21->modificationTimeSecs = 5441363; + settableFileAttribs21->userID = 5823; + size_t numTargets21 = 6; + StatData* statData21 = new StatData(854721, settableFileAttribs21, 62685, 756416, 7864, 225); + statData21->setSparseFlag(); + UInt16Vector* targets21 = new UInt16Vector(numTargets21); + TestSerialization::addRandomValuesToUInt16Vector(targets21, numTargets21); + UInt16Vector* mirrorTargets21 = new UInt16Vector(numTargets21); + TestSerialization::addRandomValuesToUInt16Vector(mirrorTargets21, numTargets21); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData21, numTargets21); + Raid10Pattern* stripePattern21 = new Raid10Pattern (512, *targets21, *mirrorTargets21, + numTargets21); + std::string* entryID21 = new std::string("0-34A23FAB-6"); + std::string* origParentEntryID21 = new std::string("2-AFAF3444-6"); + FileInodeStoreData* fileInodeStoreData21 = new FileInodeStoreData(*entryID21, statData21, + stripePattern21, UINT_MAX, 2890, *origParentEntryID21, FileInodeOrigFeature_TRUE); + FileInode* inode21 = new FileInode(*entryID21, fileInodeStoreData21, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode21->setIsInlinedUnlocked(false); + inode21->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo21 = new EntryInfo(NumNodeID(890), *origParentEntryID21, *entryID21, + "testfile21", DirEntryType_DIRECTORY, ENTRYINFO_FEATURE_INLINED); + + SettableFileAttribs* settableFileAttribs22 = new SettableFileAttribs(); + settableFileAttribs22->groupID = 7889; + settableFileAttribs22->lastAccessTimeSecs = 1316313; + settableFileAttribs22->mode = 5263; + settableFileAttribs22->modificationTimeSecs = 1313416; + settableFileAttribs22->userID = 5555; + size_t numTargets22 = 42; + StatData* statData22 = new StatData(9613463, settableFileAttribs22, 7646416, 6116546, 788, 4565); + statData22->setSparseFlag(); + UInt16Vector* targets22 = new UInt16Vector(numTargets22); + TestSerialization::addRandomValuesToUInt16Vector(targets22, numTargets22); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData22, numTargets22); + BuddyMirrorPattern* stripePattern22 = new BuddyMirrorPattern (1024, *targets22, numTargets22); + std::string* entryID22 = new std::string("1-34F23DF1-5"); + std::string* origParentEntryID22 = new std::string("8-734AF123-1"); + FileInodeStoreData* fileInodeStoreData22 = new FileInodeStoreData(*entryID22, statData22, + stripePattern22, FILEINODE_FEATURE_HAS_ORIG_UID, UINT_MAX, *origParentEntryID22, + FileInodeOrigFeature_TRUE); + FileInode* inode22 = new FileInode(*entryID22, fileInodeStoreData22, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode22->setIsInlinedUnlocked(true); + inode22->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo22 = new EntryInfo(NumNodeID(453), *origParentEntryID22, *entryID22, + "testfile22", DirEntryType_SYMLINK, ENTRYINFO_FEATURE_INLINED); + + + SettableFileAttribs* settableFileAttribs30 = new SettableFileAttribs(); + settableFileAttribs30->groupID = 5646; + settableFileAttribs30->lastAccessTimeSecs = 11566300; + settableFileAttribs30->mode = 556; + settableFileAttribs30->modificationTimeSecs = 564633; + settableFileAttribs30->userID = 44; + size_t numTargets30 = 111; + StatData* statData30 = new StatData(73663, settableFileAttribs30, 555, 874496, 346, 798); + statData30->setSparseFlag(); + UInt16Vector* targets30 = new UInt16Vector(numTargets30); + TestSerialization::addRandomValuesToUInt16Vector(targets30, numTargets30); + TestSerialization::addRandomTargetChunkBlocksToStatData(statData30, numTargets30); + Raid0Pattern* stripePattern30 = new Raid0Pattern (4096, *targets30, numTargets30); + std::string* entryID30 = new std::string("2-893ADF21-9"); + std::string* origParentEntryID30 = new std::string("5-DA474213-8"); + FileInodeStoreData* fileInodeStoreData30 = new FileInodeStoreData(*entryID30, statData30, + stripePattern30, FILEINODE_FEATURE_HAS_ORIG_UID, 3121, *origParentEntryID30, + FileInodeOrigFeature_TRUE); + FileInode* inode30 = new FileInode(*entryID30, fileInodeStoreData30, DirEntryType_REGULARFILE, + DENTRY_FEATURE_IS_FILEINODE); + inode30->setIsInlinedUnlocked(true); + inode30->initLocksRandomForSerializationTests(); + EntryInfo* entryInfo30 = new EntryInfo(NumNodeID(std::numeric_limits::max() ), + *origParentEntryID30, *entryID30, "testfile30", DirEntryType_REGULARFILE, INT_MAX); + + + + SessionFile* sessionFile10 = new SessionFile({inode10, 0}, OPENFILE_ACCESS_WRITE | + OPENFILE_ACCESS_SYNC, entryInfo10); + sessionFile10->setSessionID(2344); + sessionFile10->setUseAsyncCleanup(); + + SessionFile* sessionFile11 = new SessionFile({inode11, 0}, OPENFILE_ACCESS_READWRITE, + entryInfo11); + sessionFile11->setSessionID(465); + + SessionFile* sessionFile12 = new SessionFile({inode12, 0}, OPENFILE_ACCESS_READ | + OPENFILE_ACCESS_TRUNC, entryInfo12); + sessionFile12->setSessionID(3599); + + SessionFile* sessionFile13 = new SessionFile({inode13, 0}, OPENFILE_ACCESS_WRITE | + OPENFILE_ACCESS_APPEND, entryInfo13); + sessionFile13->setSessionID(8836); + sessionFile13->setUseAsyncCleanup(); + + SessionFile* sessionFile14 = new SessionFile({inode14, 0}, OPENFILE_ACCESS_WRITE | + OPENFILE_ACCESS_DIRECT, entryInfo14); + sessionFile14->setSessionID(110); + + + SessionFile* sessionFile20 = new SessionFile({inode20, 0}, OPENFILE_ACCESS_WRITE | + OPENFILE_ACCESS_APPEND, entryInfo20); + sessionFile20->setSessionID(0); + + SessionFile* sessionFile21 = new SessionFile({inode21, 0}, OPENFILE_ACCESS_READ, entryInfo21); + sessionFile21->setSessionID(11); + sessionFile21->setUseAsyncCleanup(); + + SessionFile* sessionFile22 = new SessionFile({inode22, 0}, OPENFILE_ACCESS_READWRITE, + entryInfo22); + sessionFile22->setSessionID(7846); + + SessionFile* sessionFile30 = new SessionFile({inode30, 0}, OPENFILE_ACCESS_WRITE | + OPENFILE_ACCESS_SYNC, entryInfo30); + sessionFile30->setSessionID(UINT_MAX); + + + + Session* testSession1 = new Session(NumNodeID(1) ); + testSession1->getFiles()->addSession(sessionFile10); + testSession1->getFiles()->addSession(sessionFile11); + testSession1->getFiles()->addSession(sessionFile12); + testSession1->getFiles()->addSession(sessionFile13); + testSession1->getFiles()->addSession(sessionFile14); + + Session* testSession2 = new Session(NumNodeID(100) ); + testSession2->getFiles()->addSession(sessionFile20); + testSession2->getFiles()->addSession(sessionFile21); + testSession2->getFiles()->addSession(sessionFile22); + + Session* testSession3 = new Session(NumNodeID(100) ); + testSession3->getFiles()->addSession(sessionFile30); + + + testSessionStore.addSession(testSession1); + testSessionStore.addSession(testSession2); + testSessionStore.addSession(testSession3); +} + +TEST_F(TestSerialization, dynamicFileAttribs) +{ + DynamicFileAttribs attribs(1, 2, 3, 4, 5); + + testObjectRoundTrip(attribs); +} + +TEST_F(TestSerialization, chunkFileInfo) +{ + DynamicFileAttribs attribs(1, 2, 3, 4, 5); + ChunkFileInfo info(1, 0, attribs); + + testObjectRoundTrip(info); +} + +TEST_F(TestSerialization, entryLockDetails) +{ + EntryLockDetails eld(NumNodeID(1), 2, 3, "12345", 6); + + testObjectRoundTrip(eld); +} + +TEST_F(TestSerialization, rangeLockDetails) +{ + RangeLockDetails rld(NumNodeID(1), 2, "12345", 6, 7, 8); + + testObjectRoundTrip(rld); +} + +void TestSerialization::addRandomValuesToUInt16Vector(UInt16Vector* vector, size_t count) +{ + Random rand; + + for(size_t i = 0; i < count; i++) + { + uint16_t value = rand.getNextInRange(0, std::numeric_limits::max() ); + vector->at(i) = value; + } +} + +void TestSerialization::addRandomTargetChunkBlocksToStatData(StatData* statData, size_t count) +{ + Random rand; + + for(size_t i = 0; i < count; i++) + { + uint64_t value = rand.getNextInt(); + statData->setTargetChunkBlocks(i, value, count); + } +} diff --git a/meta/tests/TestSerialization.h b/meta/tests/TestSerialization.h new file mode 100644 index 0000000..ec5eaea --- /dev/null +++ b/meta/tests/TestSerialization.h @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +/* + * intended to test all kind of serialization/deserialization of objects (not for messages, only + * for the single objects itself + */ +class TestSerialization: public ::testing::Test +{ + protected: + void initSessionStoreForTests(SessionStore& testSessionStore); + static void addRandomValuesToUInt16Vector(UInt16Vector* vector, size_t count); + static void addRandomTargetChunkBlocksToStatData(StatData* statData, size_t count); + + template + static void testObjectRoundTrip(Obj& data) + { + Serializer ser; + ser % data; + + std::vector buffer(ser.size()); + + ser = Serializer(&buffer[0], buffer.size()); + ser % data; + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), buffer.size()); + + Deserializer des(&buffer[0], buffer.size()); + + Obj read; + + des % read; + ASSERT_TRUE(des.good()); + ASSERT_EQ(des.size(), buffer.size()); + + ser = Serializer(); + ser % read; + ASSERT_EQ(ser.size(), buffer.size()); + + std::vector rtBuffer(buffer.size()); + + ser = Serializer(&rtBuffer[0], rtBuffer.size()); + ser % read; + ASSERT_TRUE(ser.good()); + ASSERT_EQ(ser.size(), rtBuffer.size()); + ASSERT_EQ(buffer, rtBuffer); + } +}; + diff --git a/mon/CMakeLists.txt b/mon/CMakeLists.txt new file mode 100644 index 0000000..87ccaca --- /dev/null +++ b/mon/CMakeLists.txt @@ -0,0 +1,122 @@ +include_directories( + source +) + +add_library( + mon STATIC + ./source/exception/CurlException.h + ./source/exception/DatabaseException.h + ./source/net/message/NetMessageFactory.h + ./source/net/message/NetMessageFactory.cpp + ./source/net/message/nodes/HeartbeatMsgEx.h + ./source/components/NodeListRequestor.cpp + ./source/components/StatsCollector.h + ./source/components/StatsCollector.cpp + ./source/components/NodeListRequestor.h + ./source/components/worker/GetNodesWork.cpp + ./source/components/worker/RequestMetaDataWork.cpp + ./source/components/worker/RequestStorageDataWork.cpp + ./source/components/worker/RequestStorageDataWork.h + ./source/components/worker/RequestMetaDataWork.h + ./source/components/worker/GetNodesWork.h + ./source/components/CleanUp.cpp + ./source/components/CleanUp.h + ./source/app/Config.h + ./source/app/App.h + ./source/app/Config.cpp + ./source/app/App.cpp + ./source/app/SignalHandler.cpp + ./source/app/SignalHandler.h + ./source/app/Main.cpp + ./source/misc/CurlWrapper.cpp + ./source/misc/InfluxDB.cpp + ./source/misc/CurlWrapper.h + ./source/misc/Cassandra.h + ./source/misc/InfluxDB.h + ./source/misc/Cassandra.cpp + ./source/misc/TSDatabase.h + ./source/nodes/NodeStoreMgmtEx.cpp + ./source/nodes/NodeStoreStorageEx.cpp + ./source/nodes/NodeStoreMetaEx.h + ./source/nodes/StorageNodeEx.h + ./source/nodes/NodeStoreMetaEx.cpp + ./source/nodes/MetaNodeEx.cpp + ./source/nodes/MgmtNodeEx.cpp + ./source/nodes/NodeStoreStorageEx.h + ./source/nodes/StorageNodeEx.cpp + ./source/nodes/MetaNodeEx.h + ./source/nodes/MgmtNodeEx.h + ./source/nodes/NodeStoreMgmtEx.h +) + +target_include_directories( + mon PRIVATE + ../thirdparty/source/datastax +) + +target_link_libraries( + mon + beegfs-common + pthread + dl + curl +) + +add_executable( + beegfs-mon + source/app/Main.cpp +) + +target_link_libraries( + beegfs-mon + mon +) + +# if(NOT BEEGFS_SKIP_TESTS) +# add_executable( +# test-meta +# # no tests yet +# ) +# +# target_link_libraries( +# test-mon +# mon +# gtest +# ) +# +# # required for a test +# file( +# COPY ${CMAKE_CURRENT_SOURCE_DIR}/build/dist/etc/beegfs-mon.conf +# DESTINATION dist/etc/ +# ) +# +# add_test( +# NAME test-mon +# COMMAND test-mon --compiler +# ) +# endif() + +install( + TARGETS beegfs-mon + DESTINATION "usr/sbin" + COMPONENT "mon" +) + +install( + FILES "build/dist/usr/lib/systemd/system/beegfs-mon.service" "build/dist/usr/lib/systemd/system/beegfs-mon@.service" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/system" + COMPONENT "mon" +) + +install( + FILES "build/dist/etc/beegfs-mon.conf" + DESTINATION "etc/beegfs" + COMPONENT "mon" +) + +install( + FILES "build/dist/etc/beegfs-mon.auth" + DESTINATION "etc/beegfs" + COMPONENT "mon" +) + diff --git a/mon/README.txt b/mon/README.txt new file mode 100644 index 0000000..789b971 --- /dev/null +++ b/mon/README.txt @@ -0,0 +1,104 @@ +BeeGFS monitoring service README +================================ + + +Introduction +------------ + +The BeeGFS monitoring service (beegfs-mon) collects statistical data from the +various BeeGFS nodes and stores it into a time series database (at the moment InfluxDB and Apache +Cassandra are supported). + + +Prerequisites and dependencies +------------------------------ + +We highly recommend to use InfluxDB as backend unless you already have a Cassandra Cluster in use +that you want to utilize for mon. The next sections only refer to InfluxDB, if you want to use +Cassandra, please refer to the last paragraph. + +InfluxDB and Grafana are NOT included within this package for several reasons: + +* The user might want to run the InfluxDB server on another machine and/or wants + to integrate the beegfs-mon into an already existing setup. +* The user might want to use his own or other thirdparty tools to evaluate the + collected data +* They can be updated independently by the user whenever he wants to. + +So, to use beegfs-mon, a working and reachable InfluxDB setup is required. Installing +InfluxDB should be simple in most cases since there are prebuilt packages available +for all of the distributions that are supported by BeeGFS. +The installation instructions can be found at + +https://docs.influxdata.com/influxdb/v1.3/introduction/installation/ . + + +Grafana, on the other hand, is completely optional. It's completely up to the user what +he wants to do with the data stored in the database. However, for the sake of simplicity, +we provide some prebuilt Grafana dashboards that can be easily imported into the +Grafana setup and used for monitoring. The installation instructions can be +found at + +http://docs.grafana.org/installation/ . + + + +Installation +------------ + +### Meet the prerequisites + +If there isn't an already running InfluxDB service that you want to use, install and start +InfluxDB first (look above for the link to the installation documentation). +If the service runs on another host, make sure it is reachable via HTTP. + +### Grafana Dashboards + +If you want an out of the box solution, you should use the provided Grafana panels +for visualization. So, install Grafana (again, look above for +the installation instructions) and make sure it can reach the InfluxDB service via network. + + +#### Default installation + +You can then use the provided installation script which can be found under +scripts/import-dashboards. For the out-of-the-box setup with InfluxDB and Grafana being +on the same host, just use + +import-dashboards default + + +#### Custom installation + +In any other case, either provide the script with the URLs to InfluxDB and Grafana +(call the script without arguments for usage instruction) or install them manually. +The latter can be done from within Grafanas web interface: + +First, the datasource must be defined. In the main menu, click on "Data Sources" and +then "Add Data Source". Enter a name, hostname and port where your InfluxDB is running. Save. + +To add the dashboards, select "Dashboards/Import" in the main menu. Navigate to [...] and select +one of the dashboard .json files. Select the datasource you created before in the dropdown and +click "Import". Repeat for the rest of the panels. + +You can now click on "Dashboards" in the main menu and then on the Button to the right of it. +A list of the installed dashboards should pop up, in which you can select the one you want to watch. +If your BeeGFS setup, the beegfs-mon daemon and InfluxDB are already running and are configured +properly, you should already see some data being collected. + + +For more documentation and help in using Grafana, please visit the official website +http://docs.grafana.org. + + +Apache Cassandra +---------------- + +If you want to use Cassandra, please be aware that currently there are no Grafana panels for it +available. + +To configure beegfs-mon to use Cassandra, you need to install the datastax cassandra client library +on your system which you can find here: https://github.com/datastax/cpp-driver. +It has to be the version 2.9. beegfs-mon loads the library dynamically, so no recompilation is +required. The beegfs-mon config file needs to be edited to use the cassandra plugin. The available +options are explained over there. diff --git a/mon/build/Makefile b/mon/build/Makefile new file mode 100644 index 0000000..159dcd3 --- /dev/null +++ b/mon/build/Makefile @@ -0,0 +1,27 @@ +include ../../build/Makefile + +main := ../source/app/Main.cpp +sources := $(filter-out $(main), $(shell find ../source -iname '*.cpp')) + +$(call build-static-library,\ + Mon,\ + $(sources),\ + common dl curl cassandra nl3-route,\ + ../source) + +$(call define-dep-lib,\ + Mon,\ + -I ../source,\ + $(build_dir)/libMon.a) + +$(call build-executable,\ + beegfs-mon,\ + $(main),\ + Mon common dl curl cassandra nl3-route) + +$(call build-test,\ + test-runner,\ + $(shell find ../tests -name '*.cpp'),\ + Mon common dl curl cassandra nl3-route,\ + ../tests) + diff --git a/mon/build/dist/etc/beegfs-mon.auth b/mon/build/dist/etc/beegfs-mon.auth new file mode 100644 index 0000000..6cdcca6 --- /dev/null +++ b/mon/build/dist/etc/beegfs-mon.auth @@ -0,0 +1,9 @@ +# This file configures the credentials needed to connect to your monitoring database instance. +# This currently only works with InfluxDB. + +username = +password = + +# used by influxdb V2 only +organization = +token = \ No newline at end of file diff --git a/mon/build/dist/etc/beegfs-mon.conf b/mon/build/dist/etc/beegfs-mon.conf new file mode 100644 index 0000000..048d02d --- /dev/null +++ b/mon/build/dist/etc/beegfs-mon.conf @@ -0,0 +1,345 @@ +# This is a config file for the BeeGFS Mon daemon. +# http://www.beegfs.com + + +# --- [Table of Contents] --- +# +# 1) Settings +# 2) Command Line Arguments +# 3) Basic Settings Documentation +# 4) Advanced Settings Documentation + + +# +# --- Section 1.1: [Basic Settings] --- +# + +sysMgmtdHost = + +# +# --- Section 1.2: [Advanced Settings] --- +# + +dbType = influxdb +dbHostName = localhost +dbHostPort = 8086 +dbAuthFile = + +# used by influxdb only +dbDatabase = beegfs_mon +dbMaxPointsPerRequest = 5000 +dbSetRetentionPolicy = true +dbRetentionDuration = 1d + +# used by influxdb V2 only +dbBucket = + +# used by cassandra only +cassandraMaxInsertsPerBatch = 25 +cassandraTTLSecs = 86400 + + +collectClientOpsByNode = true +collectClientOpsByUser = true +statsRequestIntervalSecs = 5 +httpTimeoutMSecs = 1000 + +nodelistRequestIntervalSecs = 30 + +curlCheckSSLCertificates = true + +connMgmtdPort = 8008 +connPortShift = 0 + +connAuthFile = /etc/beegfs/conn.auth +connDisableAuthentication = false +connFallbackExpirationSecs = 900 +connMaxInternodeNum = 3 +connInterfacesFile = +connNetFilterFile = +connTcpOnlyFilterFile = + +logType = syslog +logLevel = 3 +logNoDate = false +logNumLines = 50000 +logNumRotatedFiles = 2 +logStdFile = /var/log/beegfs-mon.log + +runDaemonized = true + +tuneNumWorkers = 4 + + +# +# --- Section 2: [Command Line Arguments] --- +# + +# Use the command line argument "cfgFile=/etc/anotherconfig.conf" to +# specify a different config file for beegfs_mon. +# All other options in this file can also be used as command line +# arguments, overriding the corresponding config file values. + + +# +# --- Section 3: [Basic Settings Documentation] --- +# + +# [sysMgmtdHost] +# Hostname (or IP) of the host running the management service. +# (See also "connMgmtdPort") +# Default: + + +# +# --- Section 4: [Advanced Settings Documentation] --- +# + +# +# --- Section 4.1: [Mon] --- +# + +# [dbType] +# The time series database engine to use. Currently, influxdb and cassandra are supported. +# For most use cases, using InfluxDB is recommended because it is easier to use and more +# lightweight. + +# [dbHostName] +# The hostname where the database backend runs. Can also be given as an URL including +# protocol. The protocol can be HTTP (default), or, if an SSL encrypted connection +# is required, HTTPS. Example: https://localhost. +# Default: localhost + +# [dbPort] +# The port on which the database backend listens for connections. +# Default: 8086 (which is the default port used by InfluxDB) + +# [dbDatabase] +# The database/keyspace name in which the data is stored. +# Default: beegfs_mon + +# [dbAuthFile] +# Defines a file where the authentication credentials for the database are stored. +# This file should be set to be readable by root only. When mon was installed via +# package, the file was already created and placed at /etc/beegfs/beegfs-mon.auth +# Default: + +# [dbMaxPointsPerRequest] +# Sets the max number of points that will be cached until the whole +# set is sent via HTTP to the database backend. After a whole set of statistics has been +# collected, the cached points will be sent in any case. Small values lead to +# many sent requests, and thus, packages, too big ones can exceed certain limits and may +# cause failure. A few thousands is a sensible value here. Only used for InfluxDB. +# Default: 5000 + +# [dbSetRetentionPolicy] +# Determines whether the service shall automatically apply a retention policy +# to the database at startup. This should only be set to false if the user wants +# to configure the database by himself and wants to have a more sophisticated +# retention policy. Only used for InfluxDB. +# Default: true + +# [dbRetentionDuration] +# Defines how long the data points shall be stored until dropped by InfluxDB. +# This is only relevant if dbSetRetentionPolicy is set to true. +# Valid values are in the form ^[0-9]+[smhdw]$, while the suffixes mean +# seconds, minutes, hours, days, weeks. 2d, for example, means two days. +# Only used for InfluxDB. For more details please consult the InfluxDB documentation. +# Default: 1d (one day) + +# # [dbBucket] +# The bucket name in which the data is stored. + +# [cassandraSetMaxInsertsPerBatch] +# Sets the max number of INSERT statements that will be batched together for execution +# using the thirdparty client library for cassandra. It only accepts a maximum of a few +# thousand bytes by default, so a sensible order of magnitude is around 20 to 30. If +# you get warnings in the log like "Batch for [beegfs_mon.meta, +# beegfs_mon.highresmeta] is of size X, exceeding specified threshold of 5120 by Y.", +# you can try to reduce this number. Only used for Cassandra. +# Default: 25 + +# [cassandraTTLSecs] +# Defines the number of seconds the data rows shall be stored until marked for removal +# by the database engine. Only used for Cassandra. +# Default: 86400 (one day) + + +# [collectClientOpsByNode] +# Sets wether mon collects the client ops stats from the nodes, grouped by the client node IP. +# Default: true + +# [collectClientOpsByUser] +# Sets wether mon collects the client ops stats from the nodes, grouped by the clients user ID. +# Default: true + +# [statsRequestIntervalSecs] +# Sets the waiting interval in seconds between the stats query operation in seconds. +# This does not affect the the high resolution stats (which is always measured in +# one second intervals). +# Default: 5 + +# [httpTimeoutMSecs] +# Defines the timeout for the http requests that are sent to the InfluxDB daemon +# in milliseconds. +# Default: 1000 + +# [nodelistRequestIntervalSecs] +# Sets the waiting interval in seconds between the nodelist requests operation +# in seconds. This defines how often the service pulls the newest node lists from +# the management daemon. +# Default: 30 + + +# [curlCheckSSLCertificates] +# Decides whether the servers certificate and hostname shall be checked to be valid when using +# an SSL encrypted connection to an InfluxDB host. +# Disable when using self signed certificates without proper CA certificates. +# Default: true + +# +# --- Section 4.2: [Connections & Communication] --- +# + +# [connMgmtdPort] +# The UDP and TCP port of the management node. +# Default: 8008 + +# [connPortShift] +# Shifts all following UDP and TCP ports according to the specified value. +# Intended to make port configuration easier in case you do not want to +# configure each port individually. +# Default: 0 + +# [connAuthFile] +# The path to a file that contains a shared secret for connection based +# authentication. Only peers that use the same shared secret will be able to +# connect. +# Default: + +# [connDisableAuthentication] +# If set to true, explicitly disables connection authentication and allow the +# service to run without a connAuthFile. Running BeeGFS without connection +# authentication is considered insecure and is not recommended. +# Default: false + +# [connFallbackExpirationSecs] +# The time in seconds after which a connection to a fallback interface expires. +# When a fallback connection expires, the system will try to establish a new +# connection to the other hosts primary interface (falling back to another +# interface again if necessary). +# Note: The priority of node interfaces can be configured using the +# "connInterfacesFile" parameter. +# Default: 900 + +# [connMaxInternodeNum] +# The maximum number of simultaneous connections to the same node. +# Default: 3 + +# [connInterfacesFile] +# The path to a text file that specifies the names of the interfaces which +# may be used for communication by other nodes. One interface per line. The +# line number also defines the priority of an interface. +# Example: "ib0" in the first line, "eth0" in the second line. +# Values: This setting is optional. If unspecified, all available interfaces +# will be published and priorities will be assigned automatically. +# Note: This information is sent to other hosts to inform them about possible +# communication paths. See connRestrictOutboundInterfaces for this +# configuration's potential effect on outbound connections. +# Default: + +# [connRestrictOutboundInterfaces] +# The default behavior of BeeGFS is to use any available network interface +# to establish an outbound connection to a node, according to the TCP/IP +# configuration of the operating system. When connRestrictOutboundInterfaces +# is set to true, the network interfaces used for outbound connections are +# limited to the values specified by connInterfacesFile or connInterfacesList. +# The operating system routing tables are consulted to determine which +# interface to use for a particular node's IP address. If there is no +# route from the configured interfaces that is suitable for a node's IP +# address then the connection will fail to be established. +# Default: false + +# [connNoDefaultRoute] +# When connRestrictOutboundInterfaces is true, the routing logic will use +# the default route for a Node's IP address when no specific route for that +# address is found in the routing tables. This can be problematic during a +# failure situation, as the default route is not appropriate to use for a +# subnet that is accessible from an interface that has failed. +# connNoDefaultRoute is a comma-separated list of CIDRs that should never +# be accessed via the default route. +# Default: 0.0.0.0/0. This prevents the default route from ever being used. + +# [connNetFilterFile] +# The path to a text file that specifies allowed IP subnets, which may be used +# for outgoing communication. One subnet per line in classless notation (IP +# address and number of significant bits). +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. If unspecified, all addresses are allowed +# for outgoing communication. +# Default: + +# [connTcpOnlyFilterFile] +# The path to a text file that specifies IP address ranges to which no RDMA connection should be +# established. This is useful e.g. for environments where all hosts support RDMA, but some hosts +# cannot connect via RDMA to some other hosts. +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. +# Default: + +# +# --- Section 4.3: [Logging] --- +# + +# [logType] +# Defines the logger type. This can either be "syslog" to send log messages to +# the general system logger or "logfile". If set to logfile, logs will be written +# to logStdFile. +# Default: logfile + +# [logLevel] +# Defines the amount of output messages. The higher this level, the more +# detailed the log messages will be. +# Note: Levels above 2 might decrease performance. +# Default: 2 (Max: 5) + +# [logNoDate] +# Defines whether "date & time" (=false) or the current "time only" (=true) +# should be logged. +# Default: false + +# [logNumLines] +# The maximum number of lines per log file. +# Default: 50000 + +# [logNumRotatedFiles] +# The number of old files to keep when "logNumLines" is reached and the log file +# is rewritten. (Log rotation) +# Default: 2 + +# [logStdFile] +# The path and filename of the log file for standard log messages. If no name +# is specified, the messages will be written to the console. +# Default: /var/log/beegfs-mon.log + + +# +# --- Section 4.4: [Startup] --- +# + +# [runDaemonized] +# Detach the process from its parent (and from stdin/-out/-err). +# Default: true + + +# +# --- Section 4.5: [Tuning] --- +# + +# [tuneNumWorkers] +# The number of worker threads. Should be at least 3. A value of up to twice the +# number of CPU cores of your machine is the recommended choice. +# Default: 4 + diff --git a/mon/build/dist/etc/default/beegfs-mon b/mon/build/dist/etc/default/beegfs-mon new file mode 100644 index 0000000..5dfee68 --- /dev/null +++ b/mon/build/dist/etc/default/beegfs-mon @@ -0,0 +1,29 @@ +# BeeGFS mon service configuration. + +# Note: This file is only used together with sysV init scripts. +# If your system uses systemd, this file is ignored. +# In this case: +# +# - use `systemctl enable / disable` to activate / decativate a service +# +# - systemd service templates are used for multimode +# (See https://www.beegfs.io/wiki/MultiMode) +# +# +# Set to "NO" to disable start of the BeeGFS mon daemon via the init +# script. +START_SERVICE="YES" + +# Set to "YES" if you want to start multiple mon daemons with different +# configuration files on this machine. +# +# Create a subdirectory with the ending ".d" in "/etc/beegfs/" for every config +# file. The subdirectory name will be used to identify a particular server +# instance for init script start/stop. +# +# Note: The original config file in /etc/beegfs will not be used when multi-mode +# is enabled. +# +# Example: /etc/beegfs/scratch.d/beegfs-mon.conf +# $ /etc/init.d/beegfs-mon start scratch +MULTI_MODE="NO" diff --git a/mon/build/dist/etc/init.d/beegfs-mon.init b/mon/build/dist/etc/init.d/beegfs-mon.init new file mode 100755 index 0000000..80cf610 --- /dev/null +++ b/mon/build/dist/etc/init.d/beegfs-mon.init @@ -0,0 +1,22 @@ +#!/bin/bash +# +### BEGIN INIT INFO +# Provides: beegfs-mon +# Required-Start: +# Should-Start: $network +# Required-Stop: +# Should-Stop: $networkm +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# chkconfig: 35 95 9 +# Short-Description: BeeGFS Mon +# Description: Start BeeGFS Mon +### END INIT INFO + +APP_NAME="BeeGFS Mon" +SERVICE_NAME=beegfs-mon + +# source function library +. /etc/beegfs/lib/start-stop-functions +. /etc/beegfs/lib/init-multi-mode + diff --git a/mon/build/dist/usr/lib/systemd/system/beegfs-mon.service b/mon/build/dist/usr/lib/systemd/system/beegfs-mon.service new file mode 100644 index 0000000..194ca07 --- /dev/null +++ b/mon/build/dist/usr/lib/systemd/system/beegfs-mon.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Mon Server +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-mon cfgFile=/etc/beegfs/beegfs-mon.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/mon/build/dist/usr/lib/systemd/system/beegfs-mon@.service b/mon/build/dist/usr/lib/systemd/system/beegfs-mon@.service new file mode 100644 index 0000000..c855634 --- /dev/null +++ b/mon/build/dist/usr/lib/systemd/system/beegfs-mon@.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Mon Server (multimode) +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service beegfs-storage.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-mon cfgFile=/etc/beegfs/%I.d/beegfs-mon.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/mon/scripts/grafana/alerts/CPU-alert-v1.json b/mon/scripts/grafana/alerts/CPU-alert-v1.json new file mode 100644 index 0000000..5b338d3 --- /dev/null +++ b/mon/scripts/grafana/alerts/CPU-alert-v1.json @@ -0,0 +1,155 @@ +{ + "id": 2, + "uid": "cf53330f-49cf-4b1e-bb59-e4580d32e707", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "CPU Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "host::tag" + ], + "type": "tag" + } + ], + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT mean(\"usage_system\") FROM \"auto\".\"cpu\" WHERE $timeFilter GROUP BY \"host\"::tag", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_system" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B > 80", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-17T18:28:40+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "30m", + "annotations": { + "summary": "CPU usage is above thershold set", + "description": "Please check host \"{{ $labels.host }}\" its cpu usage is above thershold" + }, + "labels": { + "cpu-severity": "{{if gt $values.B.Value 90.0}}critical{{else if gt $values.B.Value 80.0}}warning{{else}}info{{end}}" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/CPU-alert-v2.json b/mon/scripts/grafana/alerts/CPU-alert-v2.json new file mode 100644 index 0000000..836b123 --- /dev/null +++ b/mon/scripts/grafana/alerts/CPU-alert-v2.json @@ -0,0 +1,125 @@ +{ + "id": 2, + "uid": "c1ec4ef2-dae2-4c85-b478-8119bb4326e6", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "CPU Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"cpu\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"usage_system\")\r\n|> filter(fn: (r) => r[\"cpu\"] == \"cpu-total\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n|> yield(name: \"mean\")", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B > 80", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-17T12:42:56Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "30m", + "annotations": { + "description": "Please check host \"{{ $labels.host }}\" its cpu usage is above thershold", + "summary": "CPU usage is above thershold set" + }, + "labels": { + "cpu-severity": "{{ if gt $values.B.Value 90.0 }}critical{{ else if gt $values.B.Value 80.0 }}warning{{ else }}info{{ end }}" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Disk-alert-v1.json b/mon/scripts/grafana/alerts/Disk-alert-v1.json new file mode 100644 index 0000000..2776813 --- /dev/null +++ b/mon/scripts/grafana/alerts/Disk-alert-v1.json @@ -0,0 +1,156 @@ +{ + "id": 3, + "uid": "af36a69e-fd32-4ebc-94cd-474ea6c9edb2", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Disk Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "storageTargetID::tag" + ], + "type": "tag" + } + ], + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT (diskSpaceFree / diskSpaceTotal) * 100 FROM \"auto\".\"storageTargets\" WHERE $timeFilter GROUP BY \"storageTargetID\"::tag", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [ + " / " + ], + "type": "math" + } + ] + ], + "tags": [] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B < 30", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-10T16:06:31+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "10m", + "annotations": { + "description": "Please check, as the Disk Space for Storage Target ID '{{ $labels.storageTargetID }}' is only {{ humanize $values.B.Value }}%.", + "summary": "BeeGFS Storage Target disk space is low" + }, + "labels": { + "disk-severity": "{{if lt $values.B.Value 20.0}}critical{{else if lt $values.B.Value 30.0}}warning{{else}}info{{end}}" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Disk-alert-v2.json b/mon/scripts/grafana/alerts/Disk-alert-v2.json new file mode 100644 index 0000000..eba8396 --- /dev/null +++ b/mon/scripts/grafana/alerts/Disk-alert-v2.json @@ -0,0 +1,125 @@ +{ + "id": 3, + "uid": "c0008edf-2473-47be-b0ff-ab50bad831c5", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Disk Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"storageTargets\")\r\n |> filter(fn: (r) => r._field == \"diskSpaceTotal\" or r._field == \"diskSpaceFree\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({ r with _value:(r.diskSpaceFree/ r.diskSpaceTotal) * 100.0 }))\r\n |> rename(columns: {_value: \"DiskFreePercent\"})\r\n |> drop(columns:[\"_start\",\"_stop\",\"_measurement\",\"diskSpaceTotal\",\"diskSpaceFree\"])\r\n", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B < 30", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-16T18:16:45Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "10m", + "annotations": { + "description": "Please check, as the Disk Space for Storage Target ID '{{ $labels.storageTargetID }}' is only {{ humanize $values.B.Value }}%.", + "summary": "BeeGFS Storage Target disk space is low" + }, + "labels": { + "disk-severity": "{{if lt $values.B.Value 20.0}}critical{{else if lt $values.B.Value 30.0}}warning{{else}}info{{end}}" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Inodes-alert-v1.json b/mon/scripts/grafana/alerts/Inodes-alert-v1.json new file mode 100644 index 0000000..dfb6cbf --- /dev/null +++ b/mon/scripts/grafana/alerts/Inodes-alert-v1.json @@ -0,0 +1,156 @@ +{ + "id": 4, + "uid": "e2ad5c16-110f-43df-a784-829561fe3317", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Inodes Alert ", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "storageTargetID::tag" + ], + "type": "tag" + } + ], + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT (inodesFree / inodesTotal) * 100 FROM \"auto\".\"storageTargets\" WHERE $timeFilter GROUP BY \"storageTargetID\"::tag", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [ + " / " + ], + "type": "math" + } + ] + ], + "tags": [] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B < 20", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-10T16:06:31+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "10m", + "annotations": { + "description": "Please check, as the free inodes for Storage Target ID '{{ $labels.storageTargetID }}' are only at {{ humanize $values.B.Value }}%.", + "summary": "BeeGFS Storage Target Inodes are below the threshold." + }, + "labels": { + "inodes": "free" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Inodes-alert-v2.json b/mon/scripts/grafana/alerts/Inodes-alert-v2.json new file mode 100644 index 0000000..7f0ae3d --- /dev/null +++ b/mon/scripts/grafana/alerts/Inodes-alert-v2.json @@ -0,0 +1,125 @@ +{ + "id": 4, + "uid": "be096d59-9dc4-4821-9530-8447e7261d9c", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Inodes Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"storageTargets\")\r\n |> filter(fn: (r) => r._field == \"inodesTotal\" or r._field == \"inodesFree\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({ r with _value:(r.inodesFree/ r.inodesTotal) * 100.0 }))\r\n |> rename(columns: {_value: \"InodesFreePercent\"})\r\n |> drop(columns:[\"_start\",\"_stop\",\"_measurement\",\"inodesFree\",\"inodesTotal\"])", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "$B < 20", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "math" + } + } + ], + "updated": "2023-10-16T18:16:45Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "10m", + "annotations": { + "description": "Please check, as the free inodes for Storage Target ID '{{ $labels.storageTargetID }}' are only at {{ humanize $values.B.Value }}%.", + "summary": "BeeGFS Storage Target Inodes are below the threshold." + }, + "labels": { + "inodes": "free" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v1.json b/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v1.json new file mode 100644 index 0000000..cf83203 --- /dev/null +++ b/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v1.json @@ -0,0 +1,151 @@ +{ + "id": 5, + "uid": "bc49ff76-3db9-4f8b-b88a-947c7717fc18", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Meta Queued Request Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "nodeID::tag" + ], + "type": "tag" + } + ], + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT max(\"queuedRequests\") FROM \"auto\".\"highResMeta\" WHERE $timeFilter GROUP BY \"nodeID\"::tag", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-17T18:57:50+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "2m", + "annotations": { + "description": "Queued requests of BeeGFS meta server with nodeID - \"{{ $labels.nodeID }}\" is {{ $values.B }}", + "summary": "Meta server queued requests is above threshold" + }, + "labels": { + "queued": "request" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v2.json b/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v2.json new file mode 100644 index 0000000..e6d2a1d --- /dev/null +++ b/mon/scripts/grafana/alerts/MetaQueuedrequest-alert-v2.json @@ -0,0 +1,126 @@ +{ + "id": 5, + "uid": "a5a9072e-a8c2-46c1-b3a0-88608956e83e", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Meta Queued Request Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r._measurement == \"highResMeta\" and r._field == \"queuedRequests\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) \r\n|> yield(name: \"max\")", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-16T18:16:45Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "2m", + "annotations": { + "description": "Queued requests of BeeGFS meta server with nodeID - \"{{ $labels.nodeID }}\" is {{ $values.B }}", + "summary": "Meta server queued requests is above threshold" + }, + "labels": { + "queued": "request" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Services-alert-v1.json b/mon/scripts/grafana/alerts/Services-alert-v1.json new file mode 100644 index 0000000..857c5c7 --- /dev/null +++ b/mon/scripts/grafana/alerts/Services-alert-v1.json @@ -0,0 +1,158 @@ +{ + "id": 1, + "uid": "d9a3e5ba-b5bc-4ede-989b-c605547eb2d", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Services Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "host" + ], + "type": "tag" + }, + { + "params": [ + "systemd_unit" + ], + "type": "tag" + } + ], + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "procstat_lookup", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT last(\"running\") FROM \"auto\".\"procstat_lookup\" WHERE $timeFilter GROUP BY \"host\", \"systemd_unit\"", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "running" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 0, + 0 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 1, + 0 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "datasource": { + "name": "Expression", + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-17T09:19:39+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "1m", + "annotations": { + "description": "BeeGFS Service \"{{ $labels.systemd_unit }}\" is Down , Please check host \"{{ $labels.host }}\"", + "summary": "BeeGFS Service \"{{ $labels.systemd_unit }}\" is Down" + }, + "labels": { + "service_status": "down" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/Services-alert-v2.json b/mon/scripts/grafana/alerts/Services-alert-v2.json new file mode 100644 index 0000000..81687a2 --- /dev/null +++ b/mon/scripts/grafana/alerts/Services-alert-v2.json @@ -0,0 +1,126 @@ +{ + "id": 1, + "uid": "a96d9b2e-2a6b-4ab3-9858-200da324672f", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Service Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"procstat_lookup\")\r\n |> filter(fn: (r) => r._field == \"running\")\r\n |> group(columns: [\"host\", \"systemd_unit\"], mode: \"by\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> keep(columns: [\"_time\", \"_value\", \"host\", \"systemd_unit\"])\r\n |> sort(columns: [\"_time\"])", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 1 + ], + "type": "lt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-17T11:33:42Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "1m", + "annotations": { + "description": "BeeGFS Service \"{{ $labels.systemd_unit }}\" is Down , Please check host \"{{ $labels.host }}\"", + "summary": "BeeGFS Service \"{{ $labels.systemd_unit }}\" is Down" + }, + "labels": { + "service_status": "down" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v1.json b/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v1.json new file mode 100644 index 0000000..1127a6a --- /dev/null +++ b/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v1.json @@ -0,0 +1,151 @@ +{ + "id": 6, + "uid": "c81b9c61-d553-4240-aff1-e92627a40a11", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Storage Queued Request Alert ", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "groupBy": [ + { + "params": [ + "nodeID::tag" + ], + "type": "tag" + } + ], + "intervalMs": 1000, + "maxDataPoints": 43200, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "auto", + "query": "SELECT max(\"queuedRequests\") FROM \"auto\".\"highResStorage\" WHERE $timeFilter GROUP BY \"nodeID\"::tag", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [] + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-17T19:13:11+05:30", + "noDataState": "OK", + "execErrState": "Error", + "for": "2m", + "annotations": { + "description": "Queued requests of BeeGFS Storage Server with nodeID - \"{{ $labels.nodeID }}\" is {{ $values.B }}", + "summary": "Storage server queued requests is above threshold" + }, + "labels": { + "queued": "request" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v2.json b/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v2.json new file mode 100644 index 0000000..9beecc4 --- /dev/null +++ b/mon/scripts/grafana/alerts/StorageQueuedrequest-alert-v2.json @@ -0,0 +1,126 @@ +{ + "id": 6, + "uid": "e0a4e911-6602-4adc-993b-d65672e7f431", + "orgID": 1, + "folderUID": "beegfsalertfolder", + "ruleGroup": "evaluate", + "title": "Storage Queued Request Alert", + "condition": "C", + "data": [ + { + "refId": "A", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "${DS_UID}", + "model": { + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "query": "from(bucket: \"${BUCKET}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"queuedRequests\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) \r\n|> yield(name: \"max\")", + "refId": "A" + } + }, + { + "refId": "B", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "B" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "A", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "reducer": "last", + "refId": "B", + "type": "reduce" + } + }, + { + "refId": "C", + "queryType": "", + "relativeTimeRange": { + "from": 600, + "to": 0 + }, + "datasourceUid": "__expr__", + "model": { + "conditions": [ + { + "evaluator": { + "params": [ + 50 + ], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": [ + "C" + ] + }, + "reducer": { + "params": [], + "type": "last" + }, + "type": "query" + } + ], + "datasource": { + "type": "__expr__", + "uid": "__expr__" + }, + "expression": "B", + "hide": false, + "intervalMs": 1000, + "maxDataPoints": 43200, + "refId": "C", + "type": "threshold" + } + } + ], + "updated": "2023-10-16T18:16:45Z", + "noDataState": "OK", + "execErrState": "Error", + "for": "2m", + "annotations": { + "description": "Queued requests of BeeGFS storage server with nodeID - \"{{ $labels.nodeID }}\" is {{ $values.B }}", + "summary": "Storage server queued requests is above threshold" + }, + "labels": { + "queued": "request" + }, + "isPaused": true +} diff --git a/mon/scripts/grafana/alerts/alert-dashboard.json b/mon/scripts/grafana/alerts/alert-dashboard.json new file mode 100644 index 0000000..f6f1a6b --- /dev/null +++ b/mon/scripts/grafana/alerts/alert-dashboard.json @@ -0,0 +1,121 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "alertlist", + "name": "Alert list", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "10.1.4" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "gridPos": { + "h": 22, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 1, + "options": { + "alertInstanceLabelFilter": "", + "alertName": "", + "dashboardAlerts": false, + "folder": { + "title": "BeeGFS-Alert", + "uid": "beegfsalertfolder" + }, + "groupBy": [], + "groupMode": "default", + "maxItems": 20, + "sortOrder": 1, + "stateFilter": { + "error": true, + "firing": true, + "noData": false, + "normal": true, + "pending": true + }, + "viewMode": "list" + }, + "pluginVersion": "10.1.4", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "refId": "A" + } + ], + "title": "Alert List", + "type": "alertlist" + } + ], + "refresh": "", + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Alerts List", + "uid": "c4a31d8f-4dc6-4023-bc7a-1b06167a6f74", + "version": 1, + "weekStart": "" +} diff --git a/mon/scripts/grafana/alerts/contact-point.json b/mon/scripts/grafana/alerts/contact-point.json new file mode 100644 index 0000000..4cd8539 --- /dev/null +++ b/mon/scripts/grafana/alerts/contact-point.json @@ -0,0 +1,12 @@ +{ + "uid": "d5c51f44-07d047ca-a580-5a66f643e", + "name": "beegfs-email", + "type": "email", + "settings": { + "addresses": "beegfsalert@example.com", + "message": "{{ template \"beegfs.message\" . }}", + "singleEmail": false, + "subject": "{{ template \"beegfs.title\" . }}" + }, + "disableResolveMessage": false +} diff --git a/mon/scripts/grafana/alerts/email-template.json b/mon/scripts/grafana/alerts/email-template.json new file mode 100644 index 0000000..cea8497 --- /dev/null +++ b/mon/scripts/grafana/alerts/email-template.json @@ -0,0 +1,4 @@ +{ + "name": "BeeGFS-Email-Template", + "template": "{{ define \"alert_severity_prefix_emoji\" -}}\n\t{{- if eq .Status \"firing\" -}}\n\t\t🔥\n\t{{- else -}}\n\t\t✅\n\t{{- end -}}\n{{- end -}}\n\n{{ define \"beegfs_subject\" }}\n{{ template \"alert_severity_prefix_emoji\" . }}\n[{{ .Status | toUpper }}{{ if eq .Status \"firing\" }}:{{ .Alerts.Firing | len }}{{ if gt (.Alerts.Resolved | len) 0 }}, RESOLVED:{{ .Alerts.Resolved | len }}{{ end }}{{ end }} | {{ .CommonLabels.alertname -}}] \n{{ end }}\n\n{{ define \"beegfs_text_alert_list\" }}{{ range . }}\nSummary:\n\t{{ .Annotations.summary }}\n\t\nDescription:\n\t{{ .Annotations.description }}\n\nLabels:\n {{ range .Labels.SortedPairs -}}\n {{ .Name }} = {{ .Value }}\n\t{{ end }}\n{{ end }}\n{{ range . }}\n{{ if gt (len .SilenceURL) 0 }}Silence: {{ .SilenceURL }}\n{{ end }}\n{{ end }}\n{{ end }}\n\n\n{{ define \"beegfs.title\" }}{{ template \"beegfs_subject\" . }}{{ end }}\n\n{{ define \"beegfs.message\" }}{{ if gt (len .Alerts.Firing) 0 }}*Firing 🔥*\n{{ template \"beegfs_text_alert_list\" .Alerts.Firing }}{{ if gt (len .Alerts.Resolved) 0 }}\n\n{{ end }}{{ end }}\n\n{{ if gt (len .Alerts.Resolved) 0 }}*Resolved ✅*\n\nBelow alert is resolved:\n{{ template \"beegfs_text_alert_list\" .Alerts.Resolved }}\n{{ end }}\n{{ end }}" +} diff --git a/mon/scripts/grafana/alerts/policies-telegraf.json b/mon/scripts/grafana/alerts/policies-telegraf.json new file mode 100644 index 0000000..5fb25fd --- /dev/null +++ b/mon/scripts/grafana/alerts/policies-telegraf.json @@ -0,0 +1,97 @@ + +{ + "receiver": "grafana-default-email", + "routes": [ + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "disk-severity", + "=", + "warning" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "disk-severity", + "=", + "critical" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "service_status", + "=", + "down" + ] + ], + "group_wait": "30s", + "group_interval": "3m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "inodes", + "=", + "free" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "cpu-severity", + "=", + "critical" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "cpu-severity", + "=", + "warning" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "queued", + "=", + "request" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + } + ] +} diff --git a/mon/scripts/grafana/alerts/policies.json b/mon/scripts/grafana/alerts/policies.json new file mode 100644 index 0000000..4edce9e --- /dev/null +++ b/mon/scripts/grafana/alerts/policies.json @@ -0,0 +1,58 @@ + +{ + "receiver": "grafana-default-email", + "routes": [ + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "disk-severity", + "=", + "warning" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "disk-severity", + "=", + "critical" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "inodes", + "=", + "free" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + }, + { + "receiver": "beegfs-email", + "object_matchers": [ + [ + "queued", + "=", + "request" + ] + ], + "group_wait": "30s", + "group_interval": "5m", + "repeat_interval": "6h" + } + ] +} diff --git a/mon/scripts/grafana/beegfs_overview_influxdbv1.json b/mon/scripts/grafana/beegfs_overview_influxdbv1.json new file mode 100644 index 0000000..ba92acb --- /dev/null +++ b/mon/scripts/grafana/beegfs_overview_influxdbv1.json @@ -0,0 +1,1642 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 91, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
", + "mode": "html" + }, + "pluginVersion": "9.3.0", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Total Storage of Cluster", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#1c1cd51c", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 14, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Total Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 34, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "max" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Available Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7f37a4", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 95, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Throughput", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"netSendBytes\") FROM \"highResStorage\" WHERE $timeFilter GROUP BY time($__interval), \"nodeID\"", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Throughput", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "include": [], + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Data Write" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 99, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Data Write" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(\"B-wr\") FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s GROUP BY \"user\" ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Total Write By User", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum": "Data Write", + "user": "User ID" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Recv/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 83, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Recv (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Send (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Network Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "bars", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Read/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 80, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "unit", + "value": "bool" + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 58, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Service Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeNumID" + ], + "type": "tag" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Service Status" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "hostnameid" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Host" + ], + "type": "alias" + } + ] + ], + "tags": [] + } + ], + "title": "Meta Status", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": { + "Host": 2, + "Service Status": 3, + "Time": 0, + "nodeNumID": 1 + }, + "renameByName": { + "nodeNumID": "nodeNumID" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0 + }, + { + "color": "#EAB839", + "value": 1 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "color": "#f2495c", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 8, + "y": 18 + }, + "id": 70, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Node Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeNumID" + ], + "type": "tag" + } + ], + "hide": false, + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"hostnameid\") AS \"Host\", last(\"diskSpaceTotal\") AS \"Total Space\", last(\"diskSpaceFree\") AS \"Free Space\", last(\"isResponding\") AS \"Service status\" FROM \"storage\" WHERE (\"nodeNumID\" =~ /^$storageID$/) AND $timeFilter GROUP BY \"nodeNumID\"", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "hostnameid" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Host" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Total Space" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free Space" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Service Status" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space " + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 76, + "links": [], + "maxPerRow": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "repeat": "storageID", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT \"targetConsistencyState\" as \"Consistency State\", \"diskSpaceFree\" / (1024*1024*1024) as \"GiB Free\", \"inodesFree\" as \"Inodes Free\" FROM \"storageTargets\" WHERE \"nodeID\" =~ /^$storageID$/ AND time > now() - 30s GROUP BY \"storageTargetID\" ORDER BY time DESC LIMIT 1", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Node ID - $storageID", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Time": "", + "freespace": "Free Space", + "last": "Free", + "state": "State", + "storageTargetID": "Storage Target ID", + "totalspace": "Total Space " + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key = \"nodeNumID\" ", + "hide": 2, + "includeAll": true, + "multi": false, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key = \"nodeNumID\" ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Overview", + "uid": "OYTQTb74k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/beegfs_overview_influxdbv2.json b/mon/scripts/grafana/beegfs_overview_influxdbv2.json new file mode 100644 index 0000000..3ab9fec --- /dev/null +++ b/mon/scripts/grafana/beegfs_overview_influxdbv2.json @@ -0,0 +1,1736 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
", + "mode": "html" + }, + "pluginVersion": "9.3.0", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Total Storage of Cluster", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#1c1cd51c", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceTotal\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Total Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceFree\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Available Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7f37a4", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 8, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Total$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Throughput", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"highResStorage\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"netRecvBytes\" or r[\"_field\"] == \"netSendBytes\" or r[\"_field\"] == \"nodeID\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> drop(fn: (column) => column =~ /^_(start|stop)/)\r\n |> map(fn: (r) => ({ r with Throughput: r.netSendBytes + r.netRecvBytes}))\r\n |> drop(columns: [\"netRecvBytes\",\"netSendBytes\"])", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Throughput", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Select Aggregation Period ", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Total Write" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "displayName", + "value": "User ID" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 10, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Data Write" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\" and r._field == \"B-wr\") \r\n|> sum() |> group() \r\n|> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Total Write By User", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Recv" + }, + "properties": [ + { + "id": "displayName", + "value": "Recv (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "displayName", + "value": "Send (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Recv" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID Recv", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"netRecvBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Recv\"}) ", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "$tag_nodeID Send", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"netSendBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Network Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Read/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Read" + }, + "properties": [ + { + "id": "displayName", + "value": "Read (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "displayName", + "value": "Write (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_nodeID Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"diskReadBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Read\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "$tag_nodeID Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"diskWriteBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Write\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Host" + }, + "properties": [ + { + "id": "unit", + "value": "string" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 16, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"meta\" and r._field == \"isResponding\") |> group(columns: [\"nodeNumID\"]) |> last() |> group() |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"meta\" and r._field == \"hostnameid\") |> group(columns: [\"nodeNumID\"]) |> last() |> group() |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "B" + } + ], + "title": "Meta Status", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "nodeNumID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "_value 1": 2, + "_value 2": 1, + "nodeNumID": 0 + }, + "renameByName": { + "_value 1": "Service Status", + "_value 2": "Host" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "color": "#f2495c", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 8, + "y": 18 + }, + "id": 18, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Node Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"hostnameid\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "A" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceTotal\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceFree\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "C" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"isResponding\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "D" + } + ], + "title": "Storage", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "nodeNumID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "_value 1": "Host", + "_value 2": "Total Space", + "_value 3": "Free Space", + "_value 4": "Service Status" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 20, + "links": [], + "maxPerRow": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "repeat": "storageID", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceTotal\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Total Space\"})", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceFree\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Free Space\"})", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "hide": false, + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"targetConsistencyState\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"State\"})", + "rawQuery": true, + "refId": "C", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Node ID - $storageID", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "storageTargetID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "storageTargetID": "StorageTargetID" + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 2, + "includeAll": true, + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Overview", + "uid": "Gppl0jpZh4z", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv1.json b/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv1.json new file mode 100644 index 0000000..092d508 --- /dev/null +++ b/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv1.json @@ -0,0 +1,1643 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 91, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
", + "mode": "html" + }, + "pluginVersion": "9.3.0", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Total Storage of Cluster", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#1c1cd51c", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 6 + }, + "id": 14, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Total Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 34, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "max" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Available Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7f37a4", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 95, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Throughput", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"netSendBytes\") + last(\"netRecvBytes\") FROM \"highResStorage\" WHERE $timeFilter GROUP BY time($__interval), \"nodeID\"", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Throughput", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "include": [], + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Data Write" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 99, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Data Write" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(\"B-wr\") FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s GROUP BY \"user\" ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Total Write By User", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum": "Data Write", + "user": "User ID" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Recv/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 83, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Recv (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Send (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Network Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Read/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 11 + }, + "id": 80, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write (nodeNumID = $tag_nodeNumID)", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeNumID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "unit", + "value": "bool" + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 19 + }, + "id": 58, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Service Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeNumID" + ], + "type": "tag" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Service Status" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "hostnameid" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Host" + ], + "type": "alias" + } + ] + ], + "tags": [] + } + ], + "title": "Meta Status", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": { + "Host": 2, + "Service Status": 3, + "Time": 0, + "nodeNumID": 1 + }, + "renameByName": { + "nodeNumID": "" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 0 + }, + { + "color": "#EAB839", + "value": 1 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ] + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 8, + "y": 19 + }, + "id": 70, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Node Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeNumID" + ], + "type": "tag" + } + ], + "hide": false, + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "hostnameid" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Host" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Total Space" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free Space" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Service Status" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "nodeNumID": "" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space " + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 76, + "links": [], + "maxPerRow": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "repeat": "storageID", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT \"targetConsistencyState\" as \"Consistency State\", \"diskSpaceFree\" / (1024*1024*1024) as \"GiB Free\", \"inodesFree\" as \"Inodes Free\" FROM \"storageTargets\" WHERE \"nodeID\" =~ /^$storageID$/ AND time > now() - 30s GROUP BY \"storageTargetID\" ORDER BY time DESC LIMIT 1", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Node ID - $storageID", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "Time": "", + "freespace": "Free Space", + "last": "Free", + "state": "State", + "storageTargetID": "Storage Target ID", + "totalspace": "Total Space " + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key = \"nodeNumID\" ", + "hide": 2, + "includeAll": true, + "multi": true, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key = \"nodeNumID\" ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Overview", + "uid": "OYTQTb74k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv2.json b/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv2.json new file mode 100644 index 0000000..3ab9fec --- /dev/null +++ b/mon/scripts/grafana/beegfs_overview_telegraf_influxdbv2.json @@ -0,0 +1,1736 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "options": { + "code": { + "language": "plaintext", + "showLineNumbers": false, + "showMiniMap": false + }, + "content": "
", + "mode": "html" + }, + "pluginVersion": "9.3.0", + "transparent": true, + "type": "text" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Total Storage of Cluster", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#1c1cd51c", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 0, + "y": 5 + }, + "id": 2, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "/^Total$/", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceTotal\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Total Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "noValue": "NA", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 6, + "y": 5 + }, + "id": 4, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "center", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceFree\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Available Storage", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7f37a4", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 12, + "y": 5 + }, + "id": 8, + "options": { + "colorMode": "background", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Total$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Throughput", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"highResStorage\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"netRecvBytes\" or r[\"_field\"] == \"netSendBytes\" or r[\"_field\"] == \"nodeID\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> drop(fn: (column) => column =~ /^_(start|stop)/)\r\n |> map(fn: (r) => ({ r with Throughput: r.netSendBytes + r.netRecvBytes}))\r\n |> drop(columns: [\"netRecvBytes\",\"netSendBytes\"])", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [] + } + ], + "title": "Throughput", + "transformations": [ + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "sum" + }, + "replaceFields": true + } + } + ], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "Select Aggregation Period ", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Total Write" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + }, + { + "id": "displayName", + "value": "User ID" + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 6, + "x": 18, + "y": 5 + }, + "id": 10, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Data Write" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\" and r._field == \"B-wr\") \r\n|> sum() |> group() \r\n|> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Total Write By User", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "Bps" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Recv" + }, + "properties": [ + { + "id": "displayName", + "value": "Recv (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "displayName", + "value": "Send (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Recv" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 12, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID Recv", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"netRecvBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Recv\"}) ", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "$tag_nodeID Send", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"netSendBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Network Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "left", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "/Read/" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Read" + }, + "properties": [ + { + "id": "displayName", + "value": "Read (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "displayName", + "value": "Write (nodeNumID = ${__field.labels.nodeNumID})" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 14, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_nodeID Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"diskReadBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Read\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "$tag_nodeID Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"highResStorage\" and r._field == \"diskWriteBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Write\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Host" + }, + "properties": [ + { + "id": "unit", + "value": "string" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 8, + "x": 0, + "y": 18 + }, + "id": 16, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 1, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "$tag_nodeID", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "nodeID" + ], + "type": "tag" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"meta\" and r._field == \"isResponding\") |> group(columns: [\"nodeNumID\"]) |> last() |> group() |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r._measurement == \"meta\" and r._field == \"hostnameid\") |> group(columns: [\"nodeNumID\"]) |> last() |> group() |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "B" + } + ], + "title": "Meta Status", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "nodeNumID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "_value 1": 2, + "_value 2": 1, + "nodeNumID": 0 + }, + "renameByName": { + "_value 1": "Service Status", + "_value 2": "Host" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Service Status" + }, + "properties": [ + { + "id": "custom.displayMode", + "value": "color-background" + }, + { + "id": "mappings", + "value": [ + { + "options": { + "false": { + "color": "#f2495c", + "index": 1, + "text": "STOPPED" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "RUNNING" + } + }, + "type": "value" + } + ] + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "mbytes" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 16, + "x": 8, + "y": 18 + }, + "id": 18, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "Node Status" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"hostnameid\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "A" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceTotal\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"diskSpaceFree\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "C" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n |> filter(fn: (r) => r._measurement == \"storage\" and r._field == \"isResponding\") \r\n |> group(columns: [\"nodeNumID\"]) \r\n |> last() \r\n |> group() \r\n |> keep(columns: [\"nodeNumID\", \"_value\"])", + "refId": "D" + } + ], + "title": "Storage", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "nodeNumID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "_value 1": "Host", + "_value 2": "Total Space", + "_value 3": "Free Space", + "_value 4": "Service Status" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 25 + }, + "id": 20, + "links": [], + "maxPerRow": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "repeat": "storageID", + "repeatDirection": "h", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceTotal\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Total Space\"})", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceFree\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Free Space\"})", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "hide": false, + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"targetConsistencyState\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"State\"})", + "rawQuery": true, + "refId": "C", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Node ID - $storageID", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "storageTargetID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "storageTargetID": "StorageTargetID" + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 2, + "includeAll": true, + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Overview", + "uid": "Gppl0jpZh4z", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_node_influxdbv1.json b/mon/scripts/grafana/client_ops_node_influxdbv1.json new file mode 100644 index 0000000..98c0ee7 --- /dev/null +++ b/mon/scripts/grafana/client_ops_node_influxdbv1.json @@ -0,0 +1,386 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(*) FROM \"metaClientOpsByNode\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"node\" =~ /^$nodeID$/ GROUP BY node", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": { + "Time": 0, + "node": 1, + "sum_close": 2, + "sum_createLI": 3, + "sum_getXA": 16, + "sum_hardlnk": 18, + "sum_listXA": 19, + "sum_mdsInf": 4, + "sum_mkdir": 5, + "sum_open": 6, + "sum_rddir": 7, + "sum_ren": 8, + "sum_revalLI": 9, + "sum_rmdir": 10, + "sum_sAttr": 11, + "sum_sChDrct": 12, + "sum_stat": 13, + "sum_statLI": 14, + "sum_sum": 15, + "sum_trunc": 17, + "sum_unlnk": 20 + }, + "renameByName": { + "node": "", + "sum_close": "close", + "sum_create": "create", + "sum_createLI": "createLI", + "sum_dirparent": "dirparent", + "sum_entInf": "entInf", + "sum_flckAp": "flckAp", + "sum_flckEn": "flckzEn", + "sum_flckRg": "flckRg", + "sum_fndOwn": "fndOwn", + "sum_getXA": "getXA", + "sum_hardlnk": "hardlnk", + "sum_listXA": "listXA", + "sum_lookLI": "lookLI", + "sum_mdsInf": "mdsInf", + "sum_mirror": "mirror", + "sum_mkdir": "mkdir", + "sum_mvDirIns": "mvDirIns", + "sum_mvFiIns": "mvFilns", + "sum_open": "open", + "sum_openLI": "openLI", + "sum_rddir": "rddir", + "sum_refrEnt": "refrEnt", + "sum_ren": "ren", + "sum_revalLI": "revalLI", + "sum_rmLnk": "rmLnk", + "sum_rmXA": "rmXA", + "sum_rmdir": "rmdir", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_sDirPat": "sDirPat", + "sum_setXA": "setXA", + "sum_stat": "stat", + "sum_statLI": "statLI", + "sum_statfs": "statfs", + "sum_sum": "sum", + "sum_symlnk": "symlnk", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "sum_B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "node" + ], + "type": "tag" + } + ], + "measurement": "metaClientOpsByNode", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(*) FROM \"storageClientOpsByNode\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"node\" =~ /^$nodeID$/ GROUP BY node", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "*" + ], + "type": "field" + } + ] + ], + "tags": [] + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_B-rd": "B-rd", + "sum_B-wr": "B-wr", + "sum_close": "close", + "sum_getFSize": "getFSize", + "sum_ops-rd": "ops-rd", + "sum_ops-wr": "ops-wr", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_statfs": "statfs", + "sum_storInf": "storInf", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk" + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from metaClientOpsByNode with key = \"node\"", + "hide": 0, + "includeAll": true, + "label": "nodeID", + "multi": true, + "name": "nodeID", + "options": [], + "query": "show tag values from metaClientOpsByNode with key = \"node\"", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by Node)", + "uid": "HHI9d8UO", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_node_influxdbv2.json b/mon/scripts/grafana/client_ops_node_influxdbv2.json new file mode 100644 index 0000000..db7fd37 --- /dev/null +++ b/mon/scripts/grafana/client_ops_node_influxdbv2.json @@ -0,0 +1,273 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByNode\")\r\n |> filter(fn: (r) => r.node =~ /${nodeID:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"node\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n \r\n", + "refId": "A" + } + ], + "title": "Meta Operation List", + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByNode\")\r\n |> filter(fn: (r) => r.node =~ /${nodeID:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"node\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n \r\n", + "refId": "A" + } + ], + "title": "Storage Operation List", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"node\",\r\n measurement: \"metaClientOpsByNode\"\r\n)", + "hide": 0, + "includeAll": true, + "label": "nodeID", + "multi": true, + "name": "nodeID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"node\",\r\n measurement: \"metaClientOpsByNode\"\r\n)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by Node)", + "uid": "V5Me2Vk", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_node_telegraf_influxdbv1.json b/mon/scripts/grafana/client_ops_node_telegraf_influxdbv1.json new file mode 100644 index 0000000..05b2502 --- /dev/null +++ b/mon/scripts/grafana/client_ops_node_telegraf_influxdbv1.json @@ -0,0 +1,386 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(*) FROM \"metaClientOpsByNode\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"node\" =~ /^$nodeID$/ GROUP BY \"node\"", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": { + "Time": 0, + "node": 1, + "sum_close": 2, + "sum_createLI": 3, + "sum_getXA": 16, + "sum_hardlnk": 18, + "sum_listXA": 19, + "sum_mdsInf": 4, + "sum_mkdir": 5, + "sum_open": 6, + "sum_rddir": 7, + "sum_ren": 8, + "sum_revalLI": 9, + "sum_rmdir": 10, + "sum_sAttr": 11, + "sum_sChDrct": 12, + "sum_stat": 13, + "sum_statLI": 14, + "sum_sum": 15, + "sum_trunc": 17, + "sum_unlnk": 20 + }, + "renameByName": { + "node": "", + "sum_close": "close", + "sum_create": "create", + "sum_createLI": "createLI", + "sum_dirparent": "dirparent", + "sum_entInf": "entInf", + "sum_flckAp": "flckAp", + "sum_flckEn": "flckzEn", + "sum_flckRg": "flckRg", + "sum_fndOwn": "fndOwn", + "sum_getXA": "getXA", + "sum_hardlnk": "hardlnk", + "sum_listXA": "listXA", + "sum_lookLI": "lookLI", + "sum_mdsInf": "mdsInf", + "sum_mirror": "mirror", + "sum_mkdir": "mkdir", + "sum_mvDirIns": "mvDirIns", + "sum_mvFiIns": "mvFilns", + "sum_open": "open", + "sum_openLI": "openLI", + "sum_rddir": "rddir", + "sum_refrEnt": "refrEnt", + "sum_ren": "ren", + "sum_revalLI": "revalLI", + "sum_rmLnk": "rmLnk", + "sum_rmXA": "rmXA", + "sum_rmdir": "rmdir", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_sDirPat": "sDirPat", + "sum_setXA": "setXA", + "sum_stat": "stat", + "sum_statLI": "statLI", + "sum_statfs": "statfs", + "sum_sum": "sum", + "sum_symlnk": "symlnk", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "sum_B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "node" + ], + "type": "tag" + } + ], + "measurement": "metaClientOpsByNode", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(*) FROM \"storageClientOpsByNode\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"node\" =~ /^$nodeID$/ GROUP BY node", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "*" + ], + "type": "field" + } + ] + ], + "tags": [] + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_B-rd": "B-rd", + "sum_B-wr": "B-wr", + "sum_close": "close", + "sum_getFSize": "getFSize", + "sum_ops-rd": "ops-rd", + "sum_ops-wr": "ops-wr", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_statfs": "statfs", + "sum_storInf": "storInf", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk" + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from metaClientOpsByNode with key = \"node\"", + "hide": 0, + "includeAll": true, + "label": "Node ID", + "multi": true, + "name": "nodeID", + "options": [], + "query": "show tag values from metaClientOpsByNode with key = \"node\"", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by Node)", + "uid": "HHI9dJV4k", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_node_telegraf_influxdbv2.json b/mon/scripts/grafana/client_ops_node_telegraf_influxdbv2.json new file mode 100644 index 0000000..8b2d8bf --- /dev/null +++ b/mon/scripts/grafana/client_ops_node_telegraf_influxdbv2.json @@ -0,0 +1,273 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByNode\")\r\n |> filter(fn: (r) => r.node =~ /${nodeID:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"node\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n \r\n", + "refId": "A" + } + ], + "title": "Meta Operation List", + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 9 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByNode\")\r\n |> filter(fn: (r) => r.node =~ /${nodeID:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"node\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n \r\n", + "refId": "A" + } + ], + "title": "Storage Operation List", + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"node\",\r\n measurement: \"metaClientOpsByNode\"\r\n)", + "hide": 0, + "includeAll": true, + "label": "nodeID", + "multi": true, + "name": "nodeID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"node\",\r\n measurement: \"metaClientOpsByNode\"\r\n)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-2d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by Node)", + "uid": "V5Me2Vk", + "version": 3, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_user_influxdbv1.json b/mon/scripts/grafana/client_ops_user_influxdbv1.json new file mode 100644 index 0000000..59a227f --- /dev/null +++ b/mon/scripts/grafana/client_ops_user_influxdbv1.json @@ -0,0 +1,635 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "panels": [], + "title": "Operation List", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "sum_mdsInf" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(*) FROM \"metaClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"user\" =~ /^$userid$/ GROUP BY \"user\"\n", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_close": "close", + "sum_create": "create", + "sum_createLI": "createLI", + "sum_mdsInf": "mdsInf", + "sum_mkdir": "mkdir", + "sum_open": "open", + "sum_rddir": "rddir", + "sum_ren": "ren", + "sum_revalLI": "revalLI", + "sum_rmdir": "rmdir", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_stat": "stat", + "sum_statLI": "statLI", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk", + "user": "user" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(*) FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"user\" =~ /^$userid$/ GROUP BY \"user\"\n", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_B-rd": "B-rd", + "sum_B-wr": "B-wr", + "sum_close": "close", + "sum_getFSize": "getFSize", + "sum_ops-rd": "ops-rd", + "sum_ops-wr": "ops-wr", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_statfs": "statfs", + "sum_storInf": "storinf", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk", + "user": "user" + } + } + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 17, + "panels": [], + "title": "Meta Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 20 + }, + "id": 24, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(\"close\") AS \"close\", sum(\"getXA\") AS \"getXA\", sum(\"hardlnk\") AS \"hardlnk\", sum(\"listXA\") AS \"listXA\", sum(\"mkdir\") AS \"mkdir\", sum(\"open\") AS \"open\", sum(\"rddir\") AS \"rddir\", sum(\"ren\") AS \"ren\", sum(\"rmXA\") AS \"rmXA\", sum(\"rmdir\") AS \"rmdir\", sum(\"setXA\") AS \"setXA\", sum(\"stat\") AS \"stat\", sum(\"statfs\") AS \"statfs\", sum(\"symlnk\") AS \"symlnk\", sum(\"trunc\") AS \"trunc\", sum(\"unlnk\") AS \"unlnk\", sum(\"ack\") AS \"ack\", sum(\"create\") AS \"create\", sum(\"createLI\") AS \"createLI\", sum(\"dirparent\") AS \"dirparent\", sum(\"entInf\") AS \"entInf\", sum(\"flckAp\") AS \"flckAp\",sum(\"flckEn\") AS \"flckEn\", sum(\"flckRg\") AS \"flckRg\", sum(\"fndOwn\") AS \"fndOwn\", sum(\"lookLI\") AS \"lookLI\", sum(\"mdsInf\") AS \"mdsInf\", sum(\"mirror\") AS \"mirror\", sum(\"mvDirIns\") AS \"mvDirIns\", sum(\"mvFiIns\") AS \"mvFiIns\", sum(\"openLI\") AS \"openLI\", sum(\"refrEnt\") AS \"refrEnt\", sum(\"revalLI\") AS \"revalLI\", sum(\"rmLnk\") AS \"rmLnk\", sum(\"sAttr\") AS \"sAttr\", sum(\"sChDrct\") AS \"sChDrct\", sum(\"sDirPat\") AS \"sDirPat\", sum(\"statLI\") AS \"statLI\" FROM \"metaClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND (\"user\" =~ /^$userid$/) ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 28 + }, + "id": 8, + "panels": [], + "title": "Storage Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 29 + }, + "id": 10, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.8", + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "user" + ], + "type": "tag" + } + ], + "measurement": "storageClientOpsByUser", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"ack\") AS \"ack\", sum(\"close\") AS \"close\", sum(\"fsync\") AS \"fsync\", sum(\"gendbg\") AS \"gendbg\", sum(\"getFSize\") AS \"getFSize\", sum(\"hrtbeat\") AS \"hrtbeat\", sum(\"ops-rd\") AS \"ops-rd\", sum(\"ops-wr\") AS \"ops-wr\", sum(\"remNode\") AS \"remNode\", sum(\"sAttr\") AS \"sAttr\", sum(\"sChDrct\") AS \"sChDrct\", sum(\"statfs\") AS \"statfs\", sum(\"storInf\") AS \"storInf\", sum(\"trunc\") AS \"trunc\", sum(\"unlnk\") AS \"unlnk\" FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND (\"user\" =~ /^$userid$/) ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "B-wr" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + }, + { + "params": [ + "write" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "B-rd" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + }, + { + "params": [ + "read" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "user", + "operator": "=~", + "value": "/^$userid$/" + } + ] + } + ], + "title": "User ID $userid", + "transparent": true, + "type": "piechart" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storageClientOpsByUser with key = \"user\"", + "hide": 0, + "includeAll": true, + "multi": true, + "name": "userid", + "options": [], + "query": "show tag values from storageClientOpsByUser with key = \"user\"", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by User)", + "uid": "RYuIR1V4k", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_user_influxdbv2.json b/mon/scripts/grafana/client_ops_user_influxdbv2.json new file mode 100644 index 0000000..13c081f --- /dev/null +++ b/mon/scripts/grafana/client_ops_user_influxdbv2.json @@ -0,0 +1,582 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 13, + "panels": [], + "title": "Operation List", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByUser\")\r\n |> filter(fn: (r) => r.user =~ /${userid:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n", + "refId": "A" + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": true + }, + "indexByName": {}, + "renameByName": { + "mdsInf {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "mdsInf", + "sChDrct {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "sChDrct", + "stat {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "stat", + "sum {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "", + "user {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "User" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "_value" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\")\r\n |> filter(fn: (r) => r.user =~ /${userid:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")", + "refId": "A" + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": {} + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 18, + "panels": [], + "title": "Meta Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 4, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByUser\")\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> filter(fn: (r) => r.user =~ /$userid$/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum": true, + "user": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 23, + "panels": [], + "title": "Storage Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 33, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\")\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> filter(fn: (r) => r.user =~ /$userid$/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum": true, + "user": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "piechart" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"user\", measurement: \"storageClientOpsByUser\")", + "hide": 0, + "includeAll": true, + "label": "User ID", + "multi": true, + "name": "userid", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"user\", measurement: \"storageClientOpsByUser\")", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by User)", + "uid": "RBCm2Vk", + "version": 2, + "weekStart": "" +} diff --git a/mon/scripts/grafana/client_ops_user_telegraf_influxdbv1.json b/mon/scripts/grafana/client_ops_user_telegraf_influxdbv1.json new file mode 100644 index 0000000..8d3df2a --- /dev/null +++ b/mon/scripts/grafana/client_ops_user_telegraf_influxdbv1.json @@ -0,0 +1,641 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "cellOptions": { + "type": "auto" + }, + "filterable": false, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "sum_mdsInf" + } + ] + }, + "pluginVersion": "9.5.0", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(*) FROM \"metaClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"user\" =~ /^$userid$/ GROUP BY \"user\"\n", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_close": "close", + "sum_create": "create", + "sum_createLI": "createLI", + "sum_mdsInf": "mdsInf", + "sum_mkdir": "mkdir", + "sum_open": "open", + "sum_rddir": "rddir", + "sum_ren": "ren", + "sum_revalLI": "revalLI", + "sum_rmdir": "rmdir", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_stat": "stat", + "sum_statLI": "statLI", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk", + "user": "user" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "cellOptions": { + "type": "auto" + }, + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sum_B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 10 + }, + "id": 4, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.5.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(*) FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND \"user\" =~ /^$userid$/ GROUP BY \"user\"\n", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "sum_B-rd": "B-rd", + "sum_B-wr": "B-wr", + "sum_close": "close", + "sum_getFSize": "getFSize", + "sum_ops-rd": "ops-rd", + "sum_ops-wr": "ops-wr", + "sum_sAttr": "sAttr", + "sum_sChDrct": "sChDrct", + "sum_statfs": "statfs", + "sum_storInf": "storinf", + "sum_sum": "sum", + "sum_trunc": "trunc", + "sum_unlnk": "unlnk", + "user": "user" + } + } + } + ], + "type": "table" + } + ], + "title": "Operation List", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 17, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 1 + }, + "id": 24, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT sum(\"close\") AS \"close\", sum(\"getXA\") AS \"getXA\", sum(\"hardlnk\") AS \"hardlnk\", sum(\"listXA\") AS \"listXA\", sum(\"mkdir\") AS \"mkdir\", sum(\"open\") AS \"open\", sum(\"rddir\") AS \"rddir\", sum(\"ren\") AS \"ren\", sum(\"rmXA\") AS \"rmXA\", sum(\"rmdir\") AS \"rmdir\", sum(\"setXA\") AS \"setXA\", sum(\"stat\") AS \"stat\", sum(\"statfs\") AS \"statfs\", sum(\"symlnk\") AS \"symlnk\", sum(\"trunc\") AS \"trunc\", sum(\"unlnk\") AS \"unlnk\", sum(\"ack\") AS \"ack\", sum(\"create\") AS \"create\", sum(\"createLI\") AS \"createLI\", sum(\"dirparent\") AS \"dirparent\", sum(\"entInf\") AS \"entInf\", sum(\"flckAp\") AS \"flckAp\",sum(\"flckEn\") AS \"flckEn\", sum(\"flckRg\") AS \"flckRg\", sum(\"fndOwn\") AS \"fndOwn\", sum(\"lookLI\") AS \"lookLI\", sum(\"mdsInf\") AS \"mdsInf\", sum(\"mirror\") AS \"mirror\", sum(\"mvDirIns\") AS \"mvDirIns\", sum(\"mvFiIns\") AS \"mvFiIns\", sum(\"openLI\") AS \"openLI\", sum(\"refrEnt\") AS \"refrEnt\", sum(\"revalLI\") AS \"revalLI\", sum(\"rmLnk\") AS \"rmLnk\", sum(\"sAttr\") AS \"sAttr\", sum(\"sChDrct\") AS \"sChDrct\", sum(\"sDirPat\") AS \"sDirPat\", sum(\"statLI\") AS \"statLI\" FROM \"metaClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND (\"user\" =~ /^$userid$/) ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "type": "piechart" + } + ], + "title": "Meta Operation Per User", + "type": "row" + }, + { + "collapsed": true, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 2 + }, + "id": 8, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [] + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 6, + "x": 0, + "y": 18 + }, + "id": 10, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "name", + "percent" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.0.8", + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "user" + ], + "type": "tag" + } + ], + "measurement": "storageClientOpsByUser", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"ack\") AS \"ack\", sum(\"close\") AS \"close\", sum(\"fsync\") AS \"fsync\", sum(\"gendbg\") AS \"gendbg\", sum(\"getFSize\") AS \"getFSize\", sum(\"hrtbeat\") AS \"hrtbeat\", sum(\"ops-rd\") AS \"ops-rd\", sum(\"ops-wr\") AS \"ops-wr\", sum(\"remNode\") AS \"remNode\", sum(\"sAttr\") AS \"sAttr\", sum(\"sChDrct\") AS \"sChDrct\", sum(\"statfs\") AS \"statfs\", sum(\"storInf\") AS \"storInf\", sum(\"trunc\") AS \"trunc\", sum(\"unlnk\") AS \"unlnk\" FROM \"storageClientOpsByUser\" WHERE time > ${__from:date:seconds}s AND time < ${__to:date:seconds}s AND (\"user\" =~ /^$userid$/) ", + "rawQuery": true, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "B-wr" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + }, + { + "params": [ + "write" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "B-rd" + ], + "type": "field" + }, + { + "params": [], + "type": "sum" + }, + { + "params": [ + "read" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "user", + "operator": "=~", + "value": "/^$userid$/" + } + ] + } + ], + "title": "User ID $userid", + "transparent": true, + "type": "piechart" + } + ], + "title": "Storage Operation Per User", + "type": "row" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storageClientOpsByUser with key = \"user\"", + "hide": 0, + "includeAll": true, + "label": "User ID", + "multi": true, + "name": "userid", + "options": [], + "query": "show tag values from storageClientOpsByUser with key = \"user\"", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by User)", + "uid": "RYuIR1V4k", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/client_ops_user_telegraf_influxdbv2.json b/mon/scripts/grafana/client_ops_user_telegraf_influxdbv2.json new file mode 100644 index 0000000..5f762b3 --- /dev/null +++ b/mon/scripts/grafana/client_ops_user_telegraf_influxdbv2.json @@ -0,0 +1,582 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 13, + "panels": [], + "title": "Operation List", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 8, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByUser\")\r\n |> filter(fn: (r) => r.user =~ /${userid:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n", + "refId": "A" + } + ], + "title": "Meta Operation List", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": true + }, + "indexByName": {}, + "renameByName": { + "mdsInf {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "mdsInf", + "sChDrct {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "sChDrct", + "stat {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "stat", + "sum {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "", + "user {_start=\"2023-01-11 23:29:59.44 +0000 UTC\", _stop=\"2023-01-12 05:30:59.646295406 +0000 UTC\"}": "User" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "user" + }, + "properties": [ + { + "id": "unit", + "value": "none" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 8 + }, + "id": 2, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true, + "sortBy": [ + { + "desc": false, + "displayName": "_value" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\")\r\n |> filter(fn: (r) => r.user =~ /${userid:regex}/)\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")", + "refId": "A" + } + ], + "title": "Storage Operation List", + "transformations": [ + { + "id": "organize", + "options": {} + } + ], + "type": "table" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 15 + }, + "id": 18, + "panels": [], + "title": "Meta Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 16 + }, + "id": 4, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"metaClientOpsByUser\")\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> filter(fn: (r) => r.user =~ /$userid$/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum": true, + "user": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "piechart" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 24 + }, + "id": 23, + "panels": [], + "title": "Storage Operation Per User", + "type": "row" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + } + }, + "mappings": [], + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byRegexp", + "options": "^B-wr" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byRegexp", + "options": "^B-rd" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 33, + "maxPerRow": 4, + "options": { + "displayLabels": [ + "percent", + "name" + ], + "legend": { + "displayMode": "table", + "placement": "right", + "showLegend": true, + "values": [ + "value" + ] + }, + "pieType": "donut", + "reduceOptions": { + "calcs": [ + "sum" + ], + "fields": "", + "values": false + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "repeat": "userid", + "repeatDirection": "h", + "targets": [ + { + "alias": "", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r._measurement == \"storageClientOpsByUser\")\r\n |> sum()\r\n |> group()\r\n |> pivot(rowKey:[\"user\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> filter(fn: (r) => r.user =~ /$userid$/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "table" + } + ], + "title": "User ID $userid", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "sum": true, + "user": true + }, + "indexByName": {}, + "renameByName": {} + } + } + ], + "type": "piechart" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"user\", measurement: \"storageClientOpsByUser\")", + "hide": 0, + "includeAll": true, + "label": "User ID", + "multi": true, + "name": "userid", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"user\", measurement: \"storageClientOpsByUser\")", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Client Operations (by User)", + "uid": "RBCm2Vk", + "version": 3, + "weekStart": "" +} diff --git a/mon/scripts/grafana/import-alerts b/mon/scripts/grafana/import-alerts new file mode 100755 index 0000000..3f25e3e --- /dev/null +++ b/mon/scripts/grafana/import-alerts @@ -0,0 +1,163 @@ +#!/bin/bash + +function addAlert() { + alert_json=$(cat "$1") + modified_json=$(echo "$alert_json" | sed -e "s/\${DS_UID}/$DATASOURCE_UID/g") + curl -s -X POST "$HOST/api/v1/provisioning/alert-rules" \ + --header "Content-type: application/json" \ + --header "X-Disable-Provenance;" \ + --data "$modified_json" +} + +function addAlertV2() { + alert_json=$(cat "$1") + modified_json=$(echo "$alert_json" | sed -e "s/\${DS_UID}/$DATASOURCE_UID/g; s/\${BUCKET}/$BUCKET_NAME/g") + curl -s -X POST "$HOST/api/v1/provisioning/alert-rules" \ + --header "Content-type: application/json" \ + --header "X-Disable-Provenance;" \ + --data "$modified_json" +} + +function addDashboard() { + echo -e "{\"dashboard\": $(cat $1), \"folderUid\": \"beegfsalertfolder\"}" | \ + sed -e "s,\${DS_BEEGFS_MON_INFLUXDB},$DATASOURCE_NAME,g" | \ + curl -s -X POST "$HOST/api/dashboards/db" \ + --header "Content-type: application/json" \ + --data @- +} + +function addFolder() { + curl -s -X POST "$HOST/api/folders" \ + --header "Content-type: application/json" \ + --data '{"uid": "beegfsalertfolder", "title": "BeeGFS-Alert"}' +} + +function addTemplate() { + curl -s -X PUT "$HOST/api/v1/provisioning/templates/BeeGFS-Email-Template" \ + --header "X-Disable-Provenance;" \ + --header "Content-Type: application/json" \ + --data "@$alert_path/email-template.json" +} + +function addContactPoint() { + curl -s -X POST "$HOST/api/v1/provisioning/contact-points" \ + --header "X-Disable-Provenance;" \ + --header "Content-Type: application/json" \ + --data "@$alert_path/contact-point.json" +} + +function addPolicies() { + update_policies=$(cat $1) + curl -s -X PUT "$HOST/api/v1/provisioning/policies" \ + --header "X-Disable-Provenance;" \ + --header "Content-Type: application/json" \ + --data "$update_policies" +} + +HOST="http://admin:admin@localhost:3000" + +if [[ $1 != "default" ]] && [[ ! $# -eq 1 ]]; then + echo "This script imports the default beegfs-mon Alerts into Grafana using its HTTP API." + echo "" + echo "Usage: " + echo "Default installation to localhost: $(basename "$0") default" + echo "Custom installation: $(basename "$0") " + echo "" + echo "Default:" + echo "$(basename "$0") $HOST" + exit 0 +fi + +command -v curl > /dev/null 2>&1 || \ +{ + echo "This script requires curl, but it doesn't seem to be installed. Aborting." + exit 1 +} + +if [[ $1 != "default" ]]; then + HOST="$1" +fi + +echo "Select an option:" +echo "1. Using BeeGFS Monitoring with Telegraf" +echo "2. Using BeeGFS Monitoring without Telegraf" + +read -p "Enter your Option: " option + +if [[ "$option" == "1" ]]; then + monType="wtelegraf" +elif [[ "$option" == "2" ]]; then + monType="wotelegraf" +else + echo "*** Please select correct option ***" + exit 1 +fi + +echo "Select an option:" +echo "Please select influxdb version:" +echo "1) Influxdb 1.x" +echo "2) Influxdb 2.x" + +read -p "Enter your influxdb Verion: " influxdb_version + +DATASOURCE_UID=$(curl -s "$HOST/api/datasources/name/beegfs_mon_influxdb" | grep -o '"uid": *"[^"]*"' | cut -d'"' -f4) +DATASOURCE_NAME=$(curl -s "$HOST/api/datasources/name/beegfs_mon_influxdb" | grep -o '"name": *"[^"]*"' | cut -d'"' -f4) + +if [[ "$influxdb_version" == "2" ]]; then + BUCKET_NAME=$(curl -s "$HOST/api/datasources/name/beegfs_mon_influxdb" | grep -o '"defaultBucket": *"[^"]*"' | cut -d'"' -f4) +fi + +ALERT_DIR=$(dirname "$0") +alert_path="$ALERT_DIR/alerts" +addFolder + + +if [[ "$influxdb_version" == "1" ]] && [[ "$monType" == "wtelegraf" ]]; then + + for alert_file in "$alert_path"/*-v1.json; do + if [ -f "$alert_file" ]; then + addAlert "$alert_file" + fi + done + +elif [[ "$influxdb_version" == "2" ]] && [[ "$monType" == "wtelegraf" ]] ; then + + for alert_file in "$alert_path"/*-v2.json; do + if [ -f "$alert_file" ]; then + addAlertV2 "$alert_file" + fi + done + +elif [[ "$influxdb_version" == "1" ]] && [[ "$monType" == "wotelegraf" ]] ; then + + addAlert $alert_path/Disk-alert-v1.json + addAlert $alert_path/Inodes-alert-v1.json + addAlert $alert_path/MetaQueuedrequest-alert-v1.json + addAlert $alert_path/StorageQueuedrequest-alert-v1.json + +elif [[ "$influxdb_version" == "2" ]] && [[ "$monType" == "wotelegraf" ]] ; then + + addAlertV2 $alert_path/Disk-alert-v2.json + addAlertV2 $alert_path/Inodes-alert-v2.json + addAlertV2 $alert_path/MetaQueuedrequest-alert-v2.json + addAlertV2 $alert_path/StorageQueuedrequest-alert-v2.json + +else + echo "*** Please select correct version of InfluxDB ***" + exit 1 + +fi + +addDashboard "$alert_path/alert-dashboard.json" +addTemplate +addContactPoint + +if [[ "$monType" == "wotelegraf" ]]; then + addPolicies "$alert_path/policies.json" +elif [[ "$monType" == "wtelegraf" ]] ; then + addPolicies "$alert_path/policies-telegraf.json" +else + echo "*** Please notification policies ***" +fi + +echo -e "\n\n\n######### Alert is configured. Next step: update email address in contact point of beegfs-email. #########" diff --git a/mon/scripts/grafana/import-dashboards b/mon/scripts/grafana/import-dashboards new file mode 100755 index 0000000..bd98ba0 --- /dev/null +++ b/mon/scripts/grafana/import-dashboards @@ -0,0 +1,146 @@ +#!/bin/bash + +function addDashboard() { + echo -e "{\"dashboard\": $(cat $1) }" | \ + sed -e "s,\${DS_BEEGFS_MON_INFLUXDB},$DATASOURCE_NAME,g" | \ + curl -s -X POST "$HOST/api/dashboards/db" \ + --header "Content-type: application/json" \ + --data @- +} + +function addDatasource() { + sed -e "s,%DATABASE_NAME%,$DATABASE_NAME,g" \ + -e "s,%DATABASE_USER%,$DATABASE_USER,g" \ + -e "s,%DATASOURCE_URL%,$DATASOURCE_URL,g" \ + -e "s,%DATASOURCE_NAME%,$DATASOURCE_NAME,g" \ + -e "s,%PASSWORD%,$PASSWORD,g" \ + "$1" | \ + curl -s -X POST "$HOST/api/datasources" \ + --header "Content-type: application/json" \ + --data @- +} + + +function addDatasourceV2() { + sed -e "s,%BUCKET_NAME%,$BUCKET_NAME,g" \ + -e "s,%ORG_NAME%,$ORG_NAME,g" \ + -e "s,%DATASOURCE_URL%,$DATASOURCE_URL,g" \ + -e "s,%DATASOURCE_NAME%,$DATASOURCE_NAME,g" \ + -e "s,%TOKEN%,$TOKEN,g" \ + "$1" | \ + curl -s -X POST "$HOST/api/datasources" \ + --header "Content-type: application/json" \ + --data @- +} + + +DATASOURCE_NAME="beegfs_mon_influxdb" +HOST="http://admin:admin@localhost:3000" +DATASOURCE_URL="http://localhost:8086" + +if [[ $1 != "default" ]] && [[ ! $# -eq 2 ]]; then + echo "This script imports the default beegfs-mon Dashboards into Grafana using its HTTP API." + echo "Curl is required." + echo "" + echo "Usage: " + echo "Default installation to localhost: $(basename "$0") default" + echo "Custom installation: $(basename "$0") " + echo "" + echo "Default:" + echo "$(basename "$0") $HOST $DATASOURCE_URL $DATABASE_NAME" + exit 0 +fi + +command -v curl > /dev/null 2>&1 || \ +{ + echo "This script requires curl, but it doesn't seem to be installed. Aborting." + exit 1 +} + +echo "Select an option:" +echo "1. Using BeeGFS Monitoring with Telegraf" +echo "2. Using BeeGFS Monitoring without Telegraf" + +read -p "Enter your Option: " option + +if [[ "$option" == "1" ]]; then + monType="wtelegraf" +elif [[ "$option" == "2" ]]; then + monType="wotelegraf" +else + echo "*** Please select correct option ***" + exit 1 +fi + +echo "Please select influxdb version:" +echo "1) Influxdb 1.x" +echo "2) Influxdb 2.x" + + +if [[ $1 != "default" ]]; then + HOST="$1" + DATASOURCE_URL="$2" +fi + +GRAFANA_DIR=$(dirname "$0") + +read -p "Enter your influxdb Verion: " influxdb_version + +if [[ "$influxdb_version" == "1" ]] ; then + + read -p "Enter Database Name: " DATABASE_NAME + read -p "Enter Database User: " DATABASE_USER + read -s -p "Enter Database Password: " PASSWORD + +elif [[ "$influxdb_version" == "2" ]] ; then + + read -p "Enter Bucket Name:" BUCKET_NAME + read -p "Enter Organizations: " ORG_NAME + read -s -p "Enter Token: " TOKEN +fi + +if [[ "$influxdb_version" == "1" ]] && [[ "$monType" == "wtelegraf" ]] ; then + + addDatasource "$GRAFANA_DIR/influxdb.json" + addDashboard "$GRAFANA_DIR/beegfs_overview_telegraf_influxdbv1.json" + addDashboard "$GRAFANA_DIR/meta_telegraf_influxdbv1.json" + addDashboard "$GRAFANA_DIR/storage_telegraf_influxdbv1.json" + addDashboard "$GRAFANA_DIR/storage_targets_telegraf_influxdbv1.json" + addDashboard "$GRAFANA_DIR/client_ops_node_telegraf_influxdbv1.json" + addDashboard "$GRAFANA_DIR/client_ops_user_telegraf_influxdbv1.json" + +elif [[ "$influxdb_version" == "2" ]] && [[ "$monType" == "wtelegraf" ]] ; then + + addDatasourceV2 "$GRAFANA_DIR/influxdbV2.json" + addDashboard "$GRAFANA_DIR/beegfs_overview_telegraf_influxdbv2.json" + addDashboard "$GRAFANA_DIR/meta_telegraf_influxdbv2.json" + addDashboard "$GRAFANA_DIR/storage_telegraf_influxdbv2.json" + addDashboard "$GRAFANA_DIR/storage_targets_telegraf_influxdbv2.json" + addDashboard "$GRAFANA_DIR/client_ops_node_telegraf_influxdbv2.json" + addDashboard "$GRAFANA_DIR/client_ops_user_telegraf_influxdbv2.json" + +elif [[ "$influxdb_version" == "1" ]] && [[ "$monType" == "wotelegraf" ]] ; then + + addDatasource "$GRAFANA_DIR/influxdb.json" + addDashboard "$GRAFANA_DIR/beegfs_overview_influxdbv1.json" + addDashboard "$GRAFANA_DIR/meta_influxdbv1.json" + addDashboard "$GRAFANA_DIR/storage_influxdbv1.json" + addDashboard "$GRAFANA_DIR/storage_targets_influxdbv1.json" + addDashboard "$GRAFANA_DIR/client_ops_node_influxdbv1.json" + addDashboard "$GRAFANA_DIR/client_ops_user_influxdbv1.json" + +elif [[ "$influxdb_version" == "2" ]] && [[ "$monType" == "wotelegraf" ]] ; then + + addDatasourceV2 "$GRAFANA_DIR/influxdbV2.json" + addDashboard "$GRAFANA_DIR/beegfs_overview_influxdbv2.json" + addDashboard "$GRAFANA_DIR/meta_influxdbv2.json" + addDashboard "$GRAFANA_DIR/storage_influxdbv2.json" + addDashboard "$GRAFANA_DIR/storage_targets_influxdbv2.json" + addDashboard "$GRAFANA_DIR/client_ops_node_influxdbv2.json" + addDashboard "$GRAFANA_DIR/client_ops_user_influxdbv2.json" + +else + echo "*** Please select correct version of InfluxDB ***" + exit 1 + +fi diff --git a/mon/scripts/grafana/influxdb.json b/mon/scripts/grafana/influxdb.json new file mode 100644 index 0000000..7f86e7a --- /dev/null +++ b/mon/scripts/grafana/influxdb.json @@ -0,0 +1,10 @@ +{ + "name":"%DATASOURCE_NAME%", + "type":"influxdb", + "url":"%DATASOURCE_URL%", + "access":"proxy", + "user":"%DATABASE_USER%", + "database":"%DATABASE_NAME%", + "secureJsonData":{ + "password":"%PASSWORD%"} +} diff --git a/mon/scripts/grafana/influxdbV2.json b/mon/scripts/grafana/influxdbV2.json new file mode 100644 index 0000000..0e0ab5e --- /dev/null +++ b/mon/scripts/grafana/influxdbV2.json @@ -0,0 +1,12 @@ +{ + "name":"%DATASOURCE_NAME%", + "type":"influxdb", + "url":"%DATASOURCE_URL%", + "access":"proxy", + "jsonData":{ + "organization":"%ORG_NAME%", + "defaultBucket":"%BUCKET_NAME%", + "version":"Flux"}, + "secureJsonData":{ + "token":"%TOKEN%"} +} diff --git a/mon/scripts/grafana/meta_influxdbv1.json b/mon/scripts/grafana/meta_influxdbv1.json new file mode 100644 index 0000000..e512e8a --- /dev/null +++ b/mon/scripts/grafana/meta_influxdbv1.json @@ -0,0 +1,876 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 60, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#3274d9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Sent" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + }, + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#ffb357", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 46, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Responding", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "1m" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT sum(\"isResponding\") FROM \"meta\" WHERE \"nodeID\" =~ /^$metaID$/ AND $timeFilter GROUP BY time($__interval) fill(previous)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Availability", + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Direct" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Indirect" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 34, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Direct", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "directWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Indirect", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "indirectWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Worklist Size", + "type": "timeseries" + } + ], + "refresh": "5s", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from meta with key = \"nodeNumID\" ", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "metaID", + "options": [], + "query": "show tag values from meta with key = \"nodeNumID\" ", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "BeeGFS Meta Server", + "uid": "OUJBUPQW", + "version": 1, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/meta_influxdbv2.json b/mon/scripts/grafana/meta_influxdbv2.json new file mode 100644 index 0000000..dfe6c28 --- /dev/null +++ b/mon/scripts/grafana/meta_influxdbv2.json @@ -0,0 +1,903 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 60, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "displayName", + "value": "Received" + }, + { + "id": "color", + "value": { + "fixedColor": "#3274d9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "displayName", + "value": "Send" + }, + { + "id": "color", + "value": { + "fixedColor": "#96d98d", + "mode": "fixed" + } + }, + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"highResMeta\" and r._field == \"netRecvBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Received\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"highResMeta\" and r._field == \"netSendBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "displayName", + "value": "Processed" + }, + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "displayName", + "value": "Queued" + }, + { + "id": "color", + "value": { + "fixedColor": "#ffb357", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"highResMeta\" and r._field == \"workRequests\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Processed\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"highResMeta\" and r._field == \"queuedRequests\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Queued\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "isResponding" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 8, + "links": [], + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "frameIndex": 0, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"isResponding\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> keep(columns: [\"_time\", \"_value\"]) |> aggregateWindow(every: 1m, fn: last, createEmpty: false) |> yield(name: \"last\") ", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Availability", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Direct" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-yellow", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Direct" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Indirect" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Indirect" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Direct", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"directWorkListSize\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Direct\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "directWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Indirect", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"indirectWorkListSize\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Indirect\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "indirectWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Worklist Size", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"meta\")", + "hide": 0, + "includeAll": false, + "label": "metaID", + "multi": false, + "name": "metaID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"meta\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Meta Server", + "uid": "OTSb6z", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/meta_telegraf_influxdbv1.json b/mon/scripts/grafana/meta_telegraf_influxdbv1.json new file mode 100644 index 0000000..20d11f2 --- /dev/null +++ b/mon/scripts/grafana/meta_telegraf_influxdbv1.json @@ -0,0 +1,3195 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "datasource", + "uid": "grafana" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#4ac09b75", + "value": null + } + ] + }, + "unit": "dtdhms" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "uptime" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#97b8e2", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 4, + "y": 0 + }, + "id": 12, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT mean(\"n_cpus\") FROM \"system\" WHERE \"host\" =~ /^$hostmeta$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "CPU's", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#c8a4d0b0", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 6, + "y": 0 + }, + "id": 14, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT mean(\"total\") FROM \"mem\" WHERE \"host\" =~ /^$hostmeta$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 8, + "y": 0 + }, + "id": 46, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^last$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"n_users\") FROM \"system\" WHERE \"host\" =~ /^$metaID$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Service Status", + "transformations": [], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#818421", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 10, + "y": 0 + }, + "id": 48, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^mean$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "total_threads" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Total Threads", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7c0773", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 12, + "y": 0 + }, + "id": 20, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT last(\"total\") FROM \"processes\" WHERE \"host\" =~ /^$hostmeta$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Processes", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 14, + "y": 0 + }, + "id": 50, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load1" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load1" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Load 1m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load1", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 16, + "y": 0 + }, + "id": 52, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load5" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load5" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Load 5m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load5", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 18, + "y": 0 + }, + "id": 54, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load15" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load15" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Load 15m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load15", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 50 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 56, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT 100-last(available_percent) FROM \"mem\" WHERE \"host\" =~ /^$hostmeta$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory Used %", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 60, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Sent" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + }, + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 5 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#ffb357", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 5 + }, + "id": 38, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Direct" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Indirect" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 40, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Direct", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "directWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Indirect", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "indirectWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Worklist Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_host: $col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT mean(load1) as load1,mean(load5) as load5,mean(load15) as load15 FROM \"system\" WHERE host =~ /$hostmeta$/ AND $timeFilter GROUP BY time($interval), * ORDER BY asc", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series" + } + ], + "title": "CPU Load", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 34, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "User", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_user" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "System", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_system" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "IoWait", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_iowait" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "CPU (%)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 42, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Running", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "running" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Blocked", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "blocked" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Sleeping", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "sleeping" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Stopped", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "D", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "stopped" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Zombies", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "E", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "zombies" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": "Processes", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 44, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_cpu", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "cpu" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT 100 - mean(\"usage_idle\") FROM \"cpu\" WHERE (\"cpu\" =~ /cpu[0-9].*/ AND \"host\" =~ /^$hostmeta$/) AND $timeFilter GROUP BY time($__interval), \"cpu\" fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_idle" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "condition": "AND", + "key": "cpu", + "operator": "=~", + "value": "/^$cpu$/" + }, + { + "condition": "AND", + "key": "host", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "CPU usage per core", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 36 + }, + "id": 36, + "links": [], + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.1.3", + "targets": [ + { + "alias": "Available", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"available\") FROM \"mem\" WHERE (\"host\" =~ /^$metaID$/) AND $timeFilter GROUP BY time($__interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "available" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Buffered", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "buffered" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Cached", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cached" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Free", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "D", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "free" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Used", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "E", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "used" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Total", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "F", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "total" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + }, + { + "alias": "Mapped", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "G", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "mapped" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hostmeta$/" + } + ] + } + ], + "title": " RAM", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from meta with key = \"nodeNumID\" ", + "hide": 0, + "includeAll": false, + "label": "Meta ID", + "multi": false, + "name": "metaID", + "options": [], + "query": "show tag values from meta with key = \"nodeNumID\" ", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "select last(\"hostnameid\") from meta where nodeNumID =~ /^$metaID$/", + "hide": 0, + "includeAll": false, + "label": "Meta Host", + "multi": false, + "name": "hostmeta", + "options": [], + "query": "select last(\"hostnameid\") from meta where nodeNumID =~ /^$metaID$/", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "BeeGFS Meta Server", + "uid": "tPWQX8lVk", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/meta_telegraf_influxdbv2.json b/mon/scripts/grafana/meta_telegraf_influxdbv2.json new file mode 100644 index 0000000..37750e6 --- /dev/null +++ b/mon/scripts/grafana/meta_telegraf_influxdbv2.json @@ -0,0 +1,2774 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "dtdhms" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 2, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"uptime\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hostmeta}\")\r\n |> last()", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "uptime_format" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#97b8e2", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 4, + "y": 0 + }, + "id": 4, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.host == \"${hostmeta}\" and r._measurement == \"system\" and r._field == \"n_cpus\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "CPUS", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#c8a4d0b0", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 6, + "y": 0 + }, + "id": 6, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.host == \"${hostmeta}\" and r._measurement == \"mem\" and r._field == \"total\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) \r\n|> yield(name: \"mean\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 8, + "y": 0 + }, + "id": 8, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Value$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"isResponding\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) \r\n|> yield(name: \"last\") \r\n|> keep(columns: [\"_time\",\"_value\"])", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Service Status", + "transformations": [], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#818421", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 10, + "y": 0 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"processes\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"total_threads\")\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> yield(name: \"last\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Threads", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7c0773", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 12, + "y": 0 + }, + "id": 12, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"processes\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"total\")\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> last()\r\n |> drop(columns: [\"_measurement\", \"_field\", \"_start\", \"_stop\", \"time\", \"host\"])", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Processes", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 14, + "y": 0 + }, + "id": 14, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load1\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hostmeta}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load1_percore: r.load1 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load1\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 1m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 16, + "y": 0 + }, + "id": 16, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load5\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hostmeta}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load5_percore: r.load5 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load5\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 5m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 18, + "y": 0 + }, + "id": 18, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load15\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hostmeta}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load15_percore: r.load15 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load15\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 15m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 50 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 20, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"mem\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"available_percent\")\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> drop(fn: (column) => column =~ /^_(start|stop)/)\r\n |> map(fn: (r) => ({r with usedmemory: 100.0 - r.available_percent}))\r\n |> drop(fn: (column) => column =~ /available_percent/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory Used %", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 60, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "log": 10, + "type": "log" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "displayName", + "value": "Received" + }, + { + "id": "color", + "value": { + "fixedColor": "#3274d9", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "displayName", + "value": "Send" + }, + { + "id": "custom.transform", + "value": "negative-Y" + }, + { + "id": "color", + "value": { + "fixedColor": "#96d98d", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 5 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "asc" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"highResMeta\" and\r\n r._field == \"netRecvBytes\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n |> aggregateWindow(every: 10s, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Received\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "10s" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"highResMeta\" and\r\n r._field == \"netSendBytes\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n |> aggregateWindow(every: 10s, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "displayName", + "value": "Processed" + }, + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "displayName", + "value": "Queued" + }, + { + "id": "color", + "value": { + "fixedColor": "#ffb357", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 5 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"highResMeta\" and\r\n r._field == \"workRequests\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Processed\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResMeta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"highResMeta\" and\r\n r._field == \"queuedRequests\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Queued\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Direct" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-yellow", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Direct" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Indirect" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "super-light-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Indirect" + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Direct", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"meta\" and\r\n r._field == \"directWorkListSize\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Direct\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "directWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Indirect", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop:v.timeRangeStop)\r\n |> filter(fn: (r) =>\r\n r.nodeNumID == \"${metaID}\" and \r\n r._measurement == \"meta\" and\r\n r._field == \"indirectWorkListSize\")\r\n |> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false)\r\n |> rename(columns: {_value: \"Indirect\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "indirectWorkListSize" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Worklist Size", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 19 + }, + "id": 28, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_host: $col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load1\" or r[\"_field\"] == \"load5\" or r[\"_field\"] == \"load15\")\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> yield(name: \"mean\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series" + } + ], + "title": "CPU Load", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 19 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "User", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"cpu\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"usage_user\" or r[\"_field\"] == \"usage_system\" or r[\"_field\"] == \"usage_iowait\")\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> yield(name: \"mean\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_user" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "CPU (%)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "blocked" + }, + "properties": [ + { + "id": "displayName", + "value": "Blocked" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "running" + }, + "properties": [ + { + "id": "displayName", + "value": "Running" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sleeping" + }, + "properties": [ + { + "id": "displayName", + "value": "Sleeping" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "stopped" + }, + "properties": [ + { + "id": "displayName", + "value": "Stopped" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "zombies" + }, + "properties": [ + { + "id": "displayName", + "value": "Zombies" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 37, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Running", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"processes\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"running\" or r[\"_field\"] == \"blocked\" or r[\"_field\"] == \"sleeping\" or r[\"_field\"] == \"stopped\" or r[\"_field\"] == \"zombies\") \r\n|> filter(fn: (r) => r.host == \"${hostmeta}\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n|> yield(name: \"mean\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "running" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Processes", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 36, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"cpu\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"usage_idle\")\r\n |> filter(fn: (r) => r[\"cpu\"] =~ /cpu[0-9].*/)\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> drop(fn: (column) => column =~ /^_(start|stop)/)\r\n |> map(fn: (r) => ({r with usage_idle: 100.0 - r.usage_idle}))", + "refId": "A" + } + ], + "title": "CPU usage per core", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "available" + }, + "properties": [ + { + "id": "displayName", + "value": "Available" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "buffered" + }, + "properties": [ + { + "id": "displayName", + "value": "Buffered" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "cached" + }, + "properties": [ + { + "id": "displayName", + "value": "Cached" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "free" + }, + "properties": [ + { + "id": "displayName", + "value": "Free" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "mapped" + }, + "properties": [ + { + "id": "displayName", + "value": "Mapped" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "total" + }, + "properties": [ + { + "id": "displayName", + "value": "Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "used" + }, + "properties": [ + { + "id": "displayName", + "value": "Used" + } + ] + } + ] + }, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 35 + }, + "id": 34, + "links": [], + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.1.3", + "targets": [ + { + "alias": "Available", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart)\r\n |> filter(fn: (r) => r.host == \"${hostmeta}\")\r\n |> filter(fn: (r) => r._measurement == \"mem\")\r\n |> filter(fn: (r) => r._field == \"available\" or r._field == \"buffered\" or r._field == \"free\" or r._field == \"cached\" or r._field == \"used\" or r._field == \"total\" or r._field == \"mapped\" )\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> yield(name: \"mean\")", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "available" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Buffered", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "buffered" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Cached", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cached" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Free", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "D", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "free" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Used", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "E", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "used" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Total", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "F", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "total" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + }, + { + "alias": "Mapped", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "G", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "mapped" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": " RAM", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"nodeNumID\",\r\n measurement: \"meta\"\r\n)", + "hide": 0, + "includeAll": false, + "label": "Meta ID", + "multi": false, + "name": "metaID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"\r\n\r\nschema.measurementTagValues(\r\n bucket: \"${bucket}\",\r\n tag: \"nodeNumID\",\r\n measurement: \"meta\"\r\n)", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"\r\nschema.tagValues(bucket: \"${bucket}\", tag: \"cpu\")", + "hide": 2, + "includeAll": true, + "multi": false, + "name": "cpu", + "options": [], + "query": "import \"influxdata/influxdb/schema\"\r\nschema.tagValues(bucket: \"${bucket}\", tag: \"cpu\")", + "refresh": 1, + "regex": "^cpu[0-9].*", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"hostnameid\") |> last()", + "hide": 0, + "includeAll": false, + "label": "Meta Host", + "multi": false, + "name": "hostmeta", + "options": [], + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${metaID}\" and r._measurement == \"meta\" and r._field == \"hostnameid\") |> last()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Meta Server", + "uid": "O7Sb654z", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_influxdbv1.json b/mon/scripts/grafana/storage_influxdbv1.json new file mode 100644 index 0000000..71e3024 --- /dev/null +++ b/mon/scripts/grafana/storage_influxdbv1.json @@ -0,0 +1,1311 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "light-purple", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storage.Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storage.Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Usage", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Used", + "binary": { + "left": "storage.Total", + "operator": "-", + "reducer": "sum", + "right": "storage.Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Sent" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 24, + "links": [], + "options": { + "footer": { + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "alias": "Responding", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "1m" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Availability", + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + }, + { + "id": "custom.width" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space " + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 14, + "links": [], + "options": { + "footer": { + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Time" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT \"targetConsistencyState\" as \"Consistency State\", \"diskSpaceFree\" / (1024*1024*1024) as \"GiB Free\", \"inodesFree\" as \"Inodes Free\" FROM \"storageTargets\" WHERE \"nodeID\" =~ /^$storageID$/ AND time > now() - 30s GROUP BY \"storageTargetID\" ORDER BY time DESC LIMIT 1", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Targets (last 30 seconds)", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "Time": "", + "freespace": "Free Space", + "last": "Free", + "state": "State", + "storageTargetID": "Storage Target ID", + "totalspace": "Total Space " + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key = \"nodeNumID\" ", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key = \"nodeNumID\" ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Server", + "uid": "U5xV0ddz", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_influxdbv2.json b/mon/scripts/grafana/storage_influxdbv2.json new file mode 100644 index 0000000..c3139f1 --- /dev/null +++ b/mon/scripts/grafana/storage_influxdbv2.json @@ -0,0 +1,1352 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Write" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Read" + }, + "properties": [ + { + "id": "displayName", + "value": "Read" + }, + { + "id": "color", + "value": { + "fixedColor": "super-light-yellow", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"diskReadBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Read\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"diskWriteBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Write\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "light-purple", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "diskSpaceFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Total" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceUsed" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Used" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "DiskSpaceFree", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\")\r\n|> filter(fn: (r) => r._measurement == \"storage\")\r\n|> filter(fn: (r) => r._field == \"diskSpaceTotal\" or r._field == \"diskSpaceFree\") \r\n|> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.diskSpaceTotal - r.diskSpaceFree }))\r\n|> rename(columns: {_value: \"diskSpaceUsed\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Send" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "displayName", + "value": "Received" + }, + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 8 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"netRecvBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Received\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"netSendBytes\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Processed" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Queued" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 8 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"workRequests\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Processed\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"queuedRequests\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Queued\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Value" + }, + "properties": [ + { + "id": "displayName", + "value": "isResponding" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 16 + }, + "id": 8, + "links": [], + "maxDataPoints": 100, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storage\" and r._field == \"isResponding\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> keep(columns: [\"_time\", \"_value\"]) |> aggregateWindow(every: 1m, fn: last, createEmpty: false) |> yield(name: \"last\")", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Availability", + "transformations": [], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 16 + }, + "id": 22, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceTotal\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Total Space\"})", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceFree\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Free Space\"})", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"targetConsistencyState\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"State\"})", + "refId": "C" + } + ], + "title": "Storage Targets ", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "storageTargetID", + "mode": "outer" + } + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "storageTargetID": "StorageTargetID" + } + } + } + ], + "type": "table" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 0, + "includeAll": false, + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Server", + "uid": "q3Q7dN04k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_targets_influxdbv1.json b/mon/scripts/grafana/storage_targets_influxdbv1.json new file mode 100644 index 0000000..51a8c63 --- /dev/null +++ b/mon/scripts/grafana/storage_targets_influxdbv1.json @@ -0,0 +1,633 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storageTargets.Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Disk Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Disk Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storageTargets.Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [], + "maxPerRow": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.3.2", + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT max(\"diskSpaceTotal\"), last(\"diskSpaceFree\"), difference(\"diskSpaceTotal\"), difference(\"diskSpaceTotal\") FROM \"storageTargets\" WHERE (\"storageTargetID\" =~ /^$storageTargetID$/) AND $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "storageTargetID", + "operator": "=~", + "value": "/^$storageTargetID$/" + } + ] + } + ], + "title": "Disk Space ($storageTargetID)", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Used", + "binary": { + "left": "storageTargets.Total", + "operator": "-", + "reducer": "sum", + "right": "storageTargets.Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storageTargets.Inodes Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#73ffe4", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Inodes Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Inodes Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storageTargets.Inodes Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 5, + "maxPerRow": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "inodesTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Inodes Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "inodesFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Inodes Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "storageTargetID", + "operator": "=~", + "value": "/^$storageTargetID$/" + } + ] + } + ], + "title": "Inodes ($storageTargetID)", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Inodes Used", + "binary": { + "left": "storageTargets.Inodes Total", + "operator": "-", + "reducer": "sum", + "right": "storageTargets.Inodes Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key IN ( \"nodeNumID\") ", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key IN ( \"nodeNumID\") ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storageTargets with key = \"storageTargetID\" where nodeNumID =~ /^$storageID$/", + "hide": 0, + "includeAll": true, + "label": "Storage TargetID", + "multi": true, + "name": "storageTargetID", + "options": [], + "query": "show tag values from storageTargets with key = \"storageTargetID\" where nodeNumID =~ /^$storageID$/", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "BeeGFS Storage Targets", + "uid": "NyuGiE04k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_targets_influxdbv2.json b/mon/scripts/grafana/storage_targets_influxdbv2.json new file mode 100644 index 0000000..696b1cb --- /dev/null +++ b/mon/scripts/grafana/storage_targets_influxdbv2.json @@ -0,0 +1,445 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "diskSpaceTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Space Total" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceUsed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Space Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.2", + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.storageTargetID == \"${storageTargetID}\") \r\n|> filter(fn: (r) => r._measurement == \"storageTargets\")\r\n|> filter(fn: (r) => r._field == \"diskSpaceTotal\" or r._field == \"diskSpaceFree\")\r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.diskSpaceTotal - r.diskSpaceFree }))\r\n|> rename(columns: {_value: \"diskSpaceUsed\"})", + "refId": "A" + } + ], + "title": "Disk Usage ($storageTargetID)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "inodesTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Inodes Total" + }, + { + "id": "color", + "value": { + "fixedColor": "#73ffe4", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "inodesUsed" + }, + "properties": [ + { + "id": "displayName", + "value": "Inodes Used" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "inodesFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.storageTargetID == \"${storageTargetID}\") \r\n|> filter(fn: (r) => r._measurement == \"storageTargets\")\r\n|> filter(fn: (r) => r._field == \"inodesTotal\" or r._field == \"inodesFree\" )\r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.inodesTotal - r.inodesFree }))\r\n|> rename(columns: {_value: \"inodesUsed\"})", + "refId": "A" + } + ], + "title": "Inodes ($storageTargetID)", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.tagValues( bucket: \"${bucket}\", tag: \"storageTargetID\", predicate: (r) => r._measurement == \"storageTargets\" and r.nodeNumID == \"${storageID:\"\"}\")", + "hide": 0, + "includeAll": true, + "label": "Storage TargetID", + "multi": true, + "name": "storageTargetID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.tagValues( bucket: \"${bucket}\", tag: \"storageTargetID\", predicate: (r) => r._measurement == \"storageTargets\" and r.nodeNumID == \"${storageID:\"\"}\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Targets", + "uid": "CtdY1AVzy", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_targets_telegraf_influxdbv1.json b/mon/scripts/grafana/storage_targets_telegraf_influxdbv1.json new file mode 100644 index 0000000..51a8c63 --- /dev/null +++ b/mon/scripts/grafana/storage_targets_telegraf_influxdbv1.json @@ -0,0 +1,633 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "axisSoftMin": 0, + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storageTargets.Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Disk Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "unit", + "value": "bytes" + }, + { + "id": "displayName", + "value": "Disk Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storageTargets.Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 1, + "links": [], + "maxPerRow": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.3.2", + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT max(\"diskSpaceTotal\"), last(\"diskSpaceFree\"), difference(\"diskSpaceTotal\"), difference(\"diskSpaceTotal\") FROM \"storageTargets\" WHERE (\"storageTargetID\" =~ /^$storageTargetID$/) AND $timeFilter GROUP BY time($__interval) fill(none)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "storageTargetID", + "operator": "=~", + "value": "/^$storageTargetID$/" + } + ] + } + ], + "title": "Disk Space ($storageTargetID)", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Used", + "binary": { + "left": "storageTargets.Total", + "operator": "-", + "reducer": "sum", + "right": "storageTargets.Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storageTargets.Inodes Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#73ffe4", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Inodes Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Inodes Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storageTargets.Inodes Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 5, + "maxPerRow": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "inodesTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Inodes Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "inodesFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Inodes Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "storageTargetID", + "operator": "=~", + "value": "/^$storageTargetID$/" + } + ] + } + ], + "title": "Inodes ($storageTargetID)", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Inodes Used", + "binary": { + "left": "storageTargets.Inodes Total", + "operator": "-", + "reducer": "sum", + "right": "storageTargets.Inodes Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key IN ( \"nodeNumID\") ", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key IN ( \"nodeNumID\") ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storageTargets with key = \"storageTargetID\" where nodeNumID =~ /^$storageID$/", + "hide": 0, + "includeAll": true, + "label": "Storage TargetID", + "multi": true, + "name": "storageTargetID", + "options": [], + "query": "show tag values from storageTargets with key = \"storageTargetID\" where nodeNumID =~ /^$storageID$/", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-30m", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ], + "time_options": [ + "5m", + "15m", + "1h", + "6h", + "12h", + "24h", + "2d", + "7d", + "30d" + ] + }, + "timezone": "", + "title": "BeeGFS Storage Targets", + "uid": "NyuGiE04k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_targets_telegraf_influxdbv2.json b/mon/scripts/grafana/storage_targets_telegraf_influxdbv2.json new file mode 100644 index 0000000..696b1cb --- /dev/null +++ b/mon/scripts/grafana/storage_targets_telegraf_influxdbv2.json @@ -0,0 +1,445 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "diskSpaceTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Space Total" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceUsed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Space Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 0, + "y": 0 + }, + "id": 2, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "pluginVersion": "9.3.2", + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.storageTargetID == \"${storageTargetID}\") \r\n|> filter(fn: (r) => r._measurement == \"storageTargets\")\r\n|> filter(fn: (r) => r._field == \"diskSpaceTotal\" or r._field == \"diskSpaceFree\")\r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.diskSpaceTotal - r.diskSpaceFree }))\r\n|> rename(columns: {_value: \"diskSpaceUsed\"})", + "refId": "A" + } + ], + "title": "Disk Usage ($storageTargetID)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "inodesTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Inodes Total" + }, + { + "id": "color", + "value": { + "fixedColor": "#73ffe4", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "inodesUsed" + }, + "properties": [ + { + "id": "displayName", + "value": "Inodes Used" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-yellow", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "inodesFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 12, + "x": 12, + "y": 0 + }, + "id": 4, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "repeat": "storageTargetID", + "repeatDirection": "v", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.storageTargetID == \"${storageTargetID}\") \r\n|> filter(fn: (r) => r._measurement == \"storageTargets\")\r\n|> filter(fn: (r) => r._field == \"inodesTotal\" or r._field == \"inodesFree\" )\r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.inodesTotal - r.inodesFree }))\r\n|> rename(columns: {_value: \"inodesUsed\"})", + "refId": "A" + } + ], + "title": "Inodes ($storageTargetID)", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.tagValues( bucket: \"${bucket}\", tag: \"storageTargetID\", predicate: (r) => r._measurement == \"storageTargets\" and r.nodeNumID == \"${storageID:\"\"}\")", + "hide": 0, + "includeAll": true, + "label": "Storage TargetID", + "multi": true, + "name": "storageTargetID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.tagValues( bucket: \"${bucket}\", tag: \"storageTargetID\", predicate: (r) => r._measurement == \"storageTargets\" and r.nodeNumID == \"${storageID:\"\"}\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Targets", + "uid": "CtdY1AVzy", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_telegraf_influxdbv1.json b/mon/scripts/grafana/storage_telegraf_influxdbv1.json new file mode 100644 index 0000000..ec175f4 --- /dev/null +++ b/mon/scripts/grafana/storage_telegraf_influxdbv1.json @@ -0,0 +1,4068 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "#4ac09b75", + "value": null + } + ] + }, + "unit": "dtdhms" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 4, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT last(\"uptime_format\") FROM \"default\".\"system\" WHERE (\"host\" =~ /^$storageID$/) AND $timeFilter GROUP BY time($__interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "uptime" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#97b8e2", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 4, + "y": 0 + }, + "id": 6, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT mean(\"n_cpus\") FROM \"system\" WHERE \"host\" =~ /^$hoststorage$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "CPU's", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#c8a4d0b0", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 6, + "y": 0 + }, + "id": 8, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"available\") FROM \"mem\" WHERE (\"host\" =~ /^$storageID$/) AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "total" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Memory", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 8, + "y": 0 + }, + "id": 38, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^last$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"n_users\") FROM \"system\" WHERE \"host\" =~ /^$metaID$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Service Status", + "transformations": [], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#818421", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 10, + "y": 0 + }, + "id": 40, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^mean$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "total_threads" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Total Threads", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7c0773", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 12, + "y": 0 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT last(\"total\") FROM \"processes\" WHERE \"host\" =~ /^$hoststorage$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Processes", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 14, + "y": 0 + }, + "id": 42, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load1" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load1" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Load 1m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load1", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 16, + "y": 0 + }, + "id": 44, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load5" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load5" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Load 5m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load5", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 18, + "y": 0 + }, + "id": 46, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "horizontal", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^load_per_core$/", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "load15" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "load15" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "n_cpus" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "cpu" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Load 15m", + "transformations": [ + { + "id": "calculateField", + "options": { + "alias": "load_per_core", + "binary": { + "left": "load15", + "operator": "/", + "reducer": "sum", + "right": "cpu" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + }, + "replaceFields": false + } + } + ], + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#eab839", + "value": 50 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 12, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "mean" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "SELECT 100-last(available_percent) FROM \"mem\" WHERE \"host\" =~ /^$hoststorage$/ AND $timeFilter GROUP BY time($interval) fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory Used %", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "time" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space " + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 14, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true, + "sortBy": [ + { + "desc": true, + "displayName": "Storage Target ID" + } + ] + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT \"targetConsistencyState\" as \"Consistency State\", \"diskSpaceFree\" / (1024*1024*1024) as \"GiB Free\", \"inodesFree\" as \"Inodes Free\" FROM \"storageTargets\" WHERE \"nodeID\" =~ /^$storageID$/ AND time > now() - 30s GROUP BY \"storageTargetID\" ORDER BY time DESC LIMIT 1", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Storage Targets ", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": { + "Time": false + }, + "indexByName": {}, + "renameByName": { + "Time": "", + "freespace": "Free Space", + "last": "Free", + "state": "State", + "storageTargetID": "Storage Target ID", + "totalspace": "Total Space " + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Sent" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + }, + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 16, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#FFB357", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 18, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 20, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "light-purple", + "mode": "thresholds" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "storage.Total" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Disk Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "storage.Free" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 22, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + }, + { + "params": [ + "Total" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "Free" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeNumID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Usage", + "transformations": [ + { + "id": "joinByField", + "options": {} + }, + { + "id": "calculateField", + "options": { + "alias": "Used", + "binary": { + "left": "storage.Total", + "operator": "-", + "reducer": "sum", + "right": "storage.Free" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "text", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "device" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#23c8ad75", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Device" + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Path" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#E0B400", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used Percent" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4278c8", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 24, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "path" + ], + "type": "tag" + }, + { + "params": [ + "device" + ], + "type": "tag" + } + ], + "measurement": "disk", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "used_percent" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Path Used Percent", + "transformations": [ + { + "id": "seriesToRows", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "indexByName": {}, + "renameByName": { + "last": "Used Percent", + "path": "Path" + } + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_name: $col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "name" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "diskio", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "write_time" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + }, + { + "params": [ + "10s" + ], + "type": "non_negative_derivative" + }, + { + "params": [ + "write" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "$tag_name: $col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "name" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "diskio", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "read_time" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + }, + { + "params": [ + "10s" + ], + "type": "non_negative_derivative" + }, + { + "params": [ + "read" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Disk I/O time", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 28, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "SELECT mean(load1) as load1,mean(load5) as load5,mean(load15) as load15 FROM \"system\" WHERE host =~ /$hoststorage$/ AND $timeFilter GROUP BY time($interval), * ORDER BY asc", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series" + } + ], + "title": "CPU Load", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "User", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_user" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "System", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_system" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "IoWait", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_iowait" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "CPU (%)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 44 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Running", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "running" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Blocked", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "blocked" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Sleeping", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "sleeping" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Stopped", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "D", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "stopped" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Zombies", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "refId": "E", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "zombies" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": "Processes", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 10, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "smooth", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "normal" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 44 + }, + "id": 34, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_cpu", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "cpu" + ], + "type": "tag" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT 100 - mean(\"usage_idle\") FROM \"cpu\" WHERE (\"cpu\" =~ /cpu[0-9].*/ AND \"host\" =~ /^$hoststorage$/) AND $timeFilter GROUP BY time($__interval), \"cpu\" fill(null)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_idle" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "condition": "AND", + "key": "cpu", + "operator": "=~", + "value": "/^$cpu$/" + }, + { + "condition": "AND", + "key": "host", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "CPU usage per core", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 36, + "links": [], + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.1.3", + "targets": [ + { + "alias": "Available", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"available\") FROM \"mem\" WHERE (\"host\" =~ /^$storageID$/) AND $timeFilter GROUP BY time($__interval) fill(null)", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "available" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Buffered", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "buffered" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Cached", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "C", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "cached" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Free", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "D", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "free" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Used", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "E", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "used" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Total", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": "SELECT mean(\"total\") FROM \"mem\" WHERE (\"host\" =~ /^$storageID$/) AND $timeFilter GROUP BY time($__interval) fill(null)", + "rawQuery": false, + "refId": "G", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "total" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + }, + { + "alias": "Mapped", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "refId": "F", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "mapped" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$hoststorage$/" + } + ] + } + ], + "title": " RAM", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "show tag values from storage with key = \"nodeNumID\" ", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "show tag values from storage with key = \"nodeNumID\" ", + "refresh": 2, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "select last(\"hostnameid\") from storage where nodeNumID =~ /^$storageID$/", + "hide": 0, + "includeAll": false, + "label": "Storage Host", + "multi": false, + "name": "hoststorage", + "options": [], + "query": "select last(\"hostnameid\") from storage where nodeNumID =~ /^$storageID$/", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Server", + "uid": "jKLc0Ul4k", + "version": 2, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/scripts/grafana/storage_telegraf_influxdbv2.json b/mon/scripts/grafana/storage_telegraf_influxdbv2.json new file mode 100644 index 0000000..6b04477 --- /dev/null +++ b/mon/scripts/grafana/storage_telegraf_influxdbv2.json @@ -0,0 +1,3177 @@ +{ + "__inputs": [ + { + "name": "DS_BEEGFS_MON_INFLUXDB", + "label": "beegfs_mon_influxdb", + "description": "", + "type": "datasource", + "pluginId": "influxdb", + "pluginName": "InfluxDB" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.3.0" + }, + { + "type": "datasource", + "id": "influxdb", + "name": "InfluxDB", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 2, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "dtdhms" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 0, + "y": 0 + }, + "id": 2, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "system", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"uptime\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\")\r\n |> last()", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "uptime_format" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Uptime", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#97b8e2", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 4, + "y": 0 + }, + "id": 4, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\" and r._measurement == \"system\" and r._field == \"n_cpus\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) \r\n|> yield(name: \"last\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "CPUS", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#c8a4d0b0", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 6, + "y": 0 + }, + "id": 6, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\" and r._measurement == \"mem\" and r._field == \"total\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) \r\n|> yield(name: \"mean\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#9340cc8f", + "mode": "fixed" + }, + "mappings": [ + { + "options": { + "false": { + "color": "red", + "index": 1, + "text": "DOWN" + }, + "true": { + "color": "#4ac09b75", + "index": 0, + "text": "UP" + } + }, + "type": "value" + } + ], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "semi-dark-purple", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 8, + "y": 0 + }, + "id": 8, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "/^Value$/", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [], + "measurement": "meta", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storage\" and r._field == \"isResponding\") |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) |> yield(name: \"last\") |> keep(columns: [\"_time\",\"_value\"])", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "isResponding" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Service Status", + "transformations": [], + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#818421", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 10, + "y": 0 + }, + "id": 10, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"processes\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"total_threads\") \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false) \r\n|> yield(name: \"last\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Threads", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "#7c0773", + "mode": "fixed" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 12, + "y": 0 + }, + "id": 12, + "links": [], + "maxDataPoints": 100, + "options": { + "colorMode": "background", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "textMode": "auto" + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"processes\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"total\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\")\r\n |> last()\r\n |> drop(columns: [\"_measurement\", \"_field\", \"_start\", \"_stop\", \"time\", \"host\"])", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Total Processes", + "type": "stat" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 14, + "y": 0 + }, + "id": 14, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load1\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load1_percore: r.load1 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load1\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 1m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 16, + "y": 0 + }, + "id": 16, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load5\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load5_percore: r.load5 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load5\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 5m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 3, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 1 + }, + { + "color": "red", + "value": 2 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 2, + "x": 18, + "y": 0 + }, + "id": 18, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "last" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"system\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"load15\" or r[\"_field\"] == \"n_cpus\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\")\r\n |> last()\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> map(fn: (r) => ({r with n_cpus: float(v: r.n_cpus)}))\r\n |> map(fn: (r) => ({ r with load15_percore: r.load15 / r.n_cpus}))\r\n |> drop(columns: [\"_measurement\", \"load15\", \"n_cpus\", \"host\", \"_start\", \"_stop\"])", + "refId": "A" + } + ], + "title": "Load 15m", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "mappings": [], + "max": 100, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "orange", + "value": 50 + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 4, + "x": 20, + "y": 0 + }, + "id": 20, + "links": [], + "maxDataPoints": 100, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": [ + "lastNotNull" + ], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "$interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"mem\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"available_percent\")\r\n |> filter(fn: (r) => r.host == \"${hoststorage}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n |> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n |> drop(fn: (column) => column =~ /^_(start|stop)/)\r\n |> map(fn: (r) => ({r with usedmemory: 100.0 - r.available_percent}))\r\n |> drop(fn: (column) => column =~ /available_percent/)", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [] + } + ], + "title": "Memory Used %", + "type": "gauge" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "auto", + "displayMode": "auto", + "inspect": false + }, + "mappings": [], + "noValue": "Node Down", + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byType", + "options": "number" + }, + "properties": [ + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byType", + "options": "string" + }, + "properties": [ + { + "id": "custom.width" + }, + { + "id": "custom.align", + "value": "center" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Total Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Free Space" + }, + "properties": [ + { + "id": "unit", + "value": "bytes" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "State" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4ac09b75", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 7, + "w": 24, + "x": 0, + "y": 5 + }, + "id": 22, + "links": [], + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "dsType": "influxdb", + "groupBy": [ + { + "params": [ + "storageTargetID" + ], + "type": "tag" + } + ], + "limit": "", + "measurement": "storageTargets", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceTotal\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Total Space\"})", + "rawQuery": false, + "refId": "A", + "resultFormat": "table", + "select": [ + [ + { + "params": [ + "diskSpaceTotal" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "totalspace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "freespace" + ], + "type": "alias" + } + ], + [ + { + "params": [ + "targetConsistencyState" + ], + "type": "field" + }, + { + "params": [], + "type": "last" + }, + { + "params": [ + "state" + ], + "type": "alias" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"diskSpaceFree\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"Free Space\"})", + "refId": "B" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": " from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop: v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storageTargets\" and r._field == \"targetConsistencyState\") |> group(columns: [\"storageTargetID\"]) |> last() |> group() |> keep(columns: [\"_value\", \"storageTargetID\"]) |> rename(columns: {_value: \"State\"})", + "refId": "C" + } + ], + "title": "Storage Targets ", + "transformations": [ + { + "id": "joinByField", + "options": { + "byField": "storageTargetID", + "mode": "outer" + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineStyle": { + "fill": "solid" + }, + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "custom.transform", + "value": "negative-Y" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Received" + }, + "properties": [ + { + "id": "displayName", + "value": "Received" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Send" + }, + "properties": [ + { + "id": "displayName", + "value": "Send" + }, + { + "id": "color", + "value": { + "fixedColor": "light-green", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 12 + }, + "id": 24, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Received", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"netRecvBytes\")\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) \r\n|> rename(columns: {_value: \"Received\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netRecvBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Sent", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"netSendBytes\")\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) \r\n|> rename(columns: {_value: \"Send\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "netSendBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Network Traffic", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": -1, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "none" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Processed" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#36bdbc", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Processed" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Queued" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "light-orange", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Queued" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 12 + }, + "id": 26, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Processed", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"workRequests\")\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) \r\n|> rename(columns: {_value: \"Processed\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "workRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Queued", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"queuedRequests\") |> group(columns: [\"nodeNumID\"], mode: \"by\") |> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Queued\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "queuedRequests" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [] + } + ], + "title": "Work Requests", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-YlBl", + "seriesBy": "max" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 0, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "decbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Write" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "blue", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Write" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Read" + }, + "properties": [ + { + "id": "displayName", + "value": "Read" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 20 + }, + "id": 30, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Read", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "nodeID" + ], + "type": "tag" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"diskReadBytes\")\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Read\"})", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskReadBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + }, + { + "alias": "Write", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "highResStorage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"highResStorage\" and r._field == \"diskWriteBytes\")\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\")\r\n|> aggregateWindow(every: v.windowPeriod, fn: max, createEmpty: false) |> rename(columns: {_value: \"Write\"})", + "refId": "B", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskWriteBytes" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Throughput", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "fixedColor": "light-purple", + "mode": "fixed" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "mbytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "diskSpaceFree" + }, + "properties": [ + { + "id": "custom.hideFrom", + "value": { + "legend": true, + "tooltip": true, + "viz": true + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceTotal" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Total" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-purple", + "mode": "fixed" + } + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "diskSpaceUsed" + }, + "properties": [ + { + "id": "displayName", + "value": "Disk Used" + }, + { + "id": "color", + "value": { + "fixedColor": "semi-dark-blue", + "mode": "fixed" + } + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 20 + }, + "id": 32, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "DiskSpaceFree", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "none" + ], + "type": "fill" + } + ], + "measurement": "storage", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop:v.timeRangeStop) \r\n|> filter(fn: (r) => r.nodeNumID == \"${storageID}\")\r\n|> filter(fn: (r) => r._measurement == \"storage\")\r\n|> filter(fn: (r) => r._field == \"diskSpaceTotal\" or r._field == \"diskSpaceFree\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: last, createEmpty: false)\r\n|> group(columns: [\"nodeNumID\"], mode: \"by\") \r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\")\r\n|> map(fn: (r) => ({ r with _value: r.diskSpaceTotal - r.diskSpaceFree }))\r\n|> rename(columns: {_value: \"diskSpaceUsed\"})\r\n", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "diskSpaceFree" + ], + "type": "field" + }, + { + "params": [], + "type": "max" + } + ] + ], + "tags": [ + { + "key": "nodeID", + "operator": "=~", + "value": "/^$storageID$/" + } + ] + } + ], + "title": "Disk Usage", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "inspect": false + }, + "decimals": 2, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Device" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#23c8ad75", + "mode": "fixed" + } + }, + { + "id": "displayName", + "value": "Device" + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Used Percent" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#4278c8", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "Path" + }, + "properties": [ + { + "id": "color", + "value": { + "fixedColor": "#e0b400", + "mode": "fixed" + } + }, + { + "id": "custom.displayMode", + "value": "color-background" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 28 + }, + "id": 44, + "options": { + "cellHeight": "sm", + "footer": { + "countRows": false, + "enablePagination": false, + "fields": "", + "reducer": [ + "sum" + ], + "show": false + }, + "showHeader": true + }, + "pluginVersion": "9.3.0", + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": " from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"disk\")\r\n |> filter(fn: (r) => r.host == \"${hoststorage}\" )\r\n |> filter(fn: (r) => r[\"_field\"] == \"used_percent\")\r\n |> last()\r\n |> group()\r\n |> keep(columns: [\"device\", \"path\", \"_value\"])\r\n |> rename(columns: {\"device\": \"Device\", \"path\": \"Path\", \"_value\": \"Used Percent\"})", + "refId": "A" + } + ], + "title": "Path Used Percent", + "transformations": [ + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": { + "Device": 0, + "Path": 1, + "Used Percent": 2 + }, + "renameByName": {} + } + } + ], + "type": "table" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "ms" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 28 + }, + "id": 45, + "options": { + "legend": { + "calcs": [ + "max", + "lastNotNull", + "min" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"diskio\")\r\n |> filter(fn: (r) => r[\"host\"] == \"${hoststorage}\" and r.name =~ /$disk$/)\r\n |> filter(fn: (r) => r[\"_field\"] == \"read_bytes\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> derivative(unit: 10s, nonNegative: true)\r\n |> yield(name: \"mean\")", + "refId": "A" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "hide": false, + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"diskio\")\r\n |> filter(fn: (r) => r.host == \"${hoststorage}\" and r.name =~ /$disk$/)\r\n |> filter(fn: (r) => r[\"_field\"] == \"write_time\")\r\n |> derivative(unit: 10s, nonNegative: true)\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> yield(name: \"mean\")", + "refId": "B" + } + ], + "title": "Disk I/O Time", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 36 + }, + "id": 34, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "$tag_host: $col", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"system\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"load1\" or r[\"_field\"] == \"load5\" or r[\"_field\"] == \"load15\") \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n|> yield(name: \"mean\")", + "rawQuery": true, + "refId": "A", + "resultFormat": "time_series" + } + ], + "title": "CPU Load", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 36 + }, + "id": 36, + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "targets": [ + { + "alias": "User", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "cpu", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\")\r\n |> range(start: v.timeRangeStart, stop: v.timeRangeStop)\r\n |> filter(fn: (r) => r[\"_measurement\"] == \"cpu\")\r\n |> filter(fn: (r) => r[\"_field\"] == \"usage_user\" or r[\"_field\"] == \"usage_system\" or r[\"_field\"] == \"usage_iowait\")\r\n |> filter(fn: (r) => r.host == \"${hoststorage}\")\r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false)\r\n |> yield(name: \"mean\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "usage_user" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "CPU (%)", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "hue", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "blocked" + }, + "properties": [ + { + "id": "displayName", + "value": "Blocked" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "running" + }, + "properties": [ + { + "id": "displayName", + "value": "Running" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "sleeping" + }, + "properties": [ + { + "id": "displayName", + "value": "Sleeping" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "stopped" + }, + "properties": [ + { + "id": "displayName", + "value": "Stopped" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "zombies" + }, + "properties": [ + { + "id": "displayName", + "value": "Zombies" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 44 + }, + "id": 38, + "options": { + "legend": { + "calcs": [ + "mean", + "max", + "lastNotNull" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "alias": "Running", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "measurement": "processes", + "orderByTime": "ASC", + "policy": "default", + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"processes\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"running\" or r[\"_field\"] == \"blocked\" or r[\"_field\"] == \"sleeping\" or r[\"_field\"] == \"stopped\" or r[\"_field\"] == \"zombies\") \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n|> yield(name: \"mean\")", + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "running" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": "Processes", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 15, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "percent" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 44 + }, + "id": 40, + "options": { + "legend": { + "calcs": [ + "mean", + "lastNotNull", + "max" + ], + "displayMode": "table", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "query": "from(bucket: \"${bucket}\") \r\n|> range(start: v.timeRangeStart, stop: v.timeRangeStop) \r\n|> filter(fn: (r) => r[\"_measurement\"] == \"cpu\") \r\n|> filter(fn: (r) => r[\"_field\"] == \"usage_idle\") \r\n|> filter(fn: (r) => r[\"cpu\"] =~ /cpu[0-9].*/) \r\n|> filter(fn: (r) => r.host == \"${hoststorage}\") \r\n|> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n|> pivot(rowKey: [\"_time\"], columnKey: [\"_field\"], valueColumn: \"_value\") \r\n|> drop(fn: (column) => column =~ /^_(start|stop)/) \r\n|> map(fn: (r) => ({r with usage_idle: 100.0 - r.usage_idle}))", + "refId": "A" + } + ], + "title": "CPU usage per core", + "type": "timeseries" + }, + { + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 30, + "gradientMode": "opacity", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "never", + "spanNulls": true, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "decimals": 2, + "links": [], + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green" + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "bytes" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "available" + }, + "properties": [ + { + "id": "displayName", + "value": "Available" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "buffered" + }, + "properties": [ + { + "id": "displayName", + "value": "Buffered" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "cached" + }, + "properties": [ + { + "id": "displayName", + "value": "Cached" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "mapped" + }, + "properties": [ + { + "id": "displayName", + "value": "Mapped" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "total" + }, + "properties": [ + { + "id": "displayName", + "value": "Total" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "used" + }, + "properties": [ + { + "id": "displayName", + "value": "Used" + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "free" + }, + "properties": [ + { + "id": "displayName", + "value": "Free" + } + ] + } + ] + }, + "gridPos": { + "h": 8, + "w": 24, + "x": 0, + "y": 52 + }, + "id": 42, + "links": [], + "options": { + "legend": { + "calcs": [ + "lastNotNull" + ], + "displayMode": "table", + "placement": "right", + "showLegend": true + }, + "tooltip": { + "mode": "multi", + "sort": "none" + } + }, + "pluginVersion": "9.1.3", + "targets": [ + { + "alias": "Available", + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "groupBy": [ + { + "params": [ + "$__interval" + ], + "type": "time" + }, + { + "params": [ + "null" + ], + "type": "fill" + } + ], + "hide": false, + "measurement": "mem", + "orderByTime": "ASC", + "policy": "default", + "query": " from(bucket: \"${bucket}\") \r\n |> range(start: v.timeRangeStart) \r\n |> filter(fn: (r) => r.host == \"${hoststorage}\") \r\n |> filter(fn: (r) => r._measurement == \"mem\") \r\n |> filter(fn: (r) => r._field == \"available\" or r._field == \"buffered\" or r._field == \"free\" or r._field == \"cached\" or r._field == \"used\" or r._field == \"total\" or r._field == \"mapped\" ) \r\n |> aggregateWindow(every: v.windowPeriod, fn: mean, createEmpty: false) \r\n |> yield(name: \"mean\")", + "rawQuery": false, + "refId": "A", + "resultFormat": "time_series", + "select": [ + [ + { + "params": [ + "available" + ], + "type": "field" + }, + { + "params": [], + "type": "mean" + } + ] + ], + "tags": [ + { + "key": "host", + "operator": "=~", + "value": "/^$metaID$/" + } + ] + } + ], + "title": " RAM", + "type": "timeseries" + } + ], + "refresh": "", + "schemaVersion": 37, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "hide": 0, + "includeAll": false, + "label": "Storage ID", + "multi": false, + "name": "storageID", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.measurementTagValues( bucket: \"${bucket}\", tag: \"nodeNumID\", measurement: \"storage\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "buckets()", + "hide": 0, + "includeAll": false, + "label": "Bucket", + "multi": false, + "name": "bucket", + "options": [], + "query": "buckets()", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "allValue": ".*", + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "import \"influxdata/influxdb/schema\"schema.tagValues(bucket: \"${bucket}\", tag: \"device\")", + "hide": 2, + "includeAll": true, + "multi": false, + "name": "disk", + "options": [], + "query": "import \"influxdata/influxdb/schema\"schema.tagValues(bucket: \"${bucket}\", tag: \"device\")", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + }, + { + "current": {}, + "datasource": { + "type": "influxdb", + "uid": "${DS_BEEGFS_MON_INFLUXDB}" + }, + "definition": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storage\" and r._field == \"hostnameid\") |> last() ", + "hide": 0, + "includeAll": false, + "label": "Storage Host", + "multi": false, + "name": "hoststorage", + "options": [], + "query": "from(bucket: \"${bucket}\") |> range(start: v.timeRangeStart, stop:v.timeRangeStop) |> filter(fn: (r) => r.nodeNumID == \"${storageID}\" and r._measurement == \"storage\" and r._field == \"hostnameid\") |> last() ", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "type": "query" + } + ] + }, + "time": { + "from": "now-5m", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "BeeGFS Storage Server", + "uid": "q3Q7M04k", + "version": 4, + "weekStart": "" +} \ No newline at end of file diff --git a/mon/source/app/App.cpp b/mon/source/app/App.cpp new file mode 100644 index 0000000..ffa6d6a --- /dev/null +++ b/mon/source/app/App.cpp @@ -0,0 +1,324 @@ +#include "App.h" + +#include +#include +#include +#include +#include + + +App::App(int argc, char** argv) : + argc(argc), argv(argv) +{} + +void App::run() +{ + try + { + cfg = boost::make_unique(argc,argv); + runNormal(); + appResult = AppCode::NO_ERROR; + } + catch (const InvalidConfigException& e) + { + std::ostringstream err; + err << "Config error: " << e.what() << std::endl + << "[BeeGFS Mon Version: " << BEEGFS_VERSION << std::endl + << "Refer to the default config file (/etc/beegfs/beegfs-mon.conf)" << std::endl + << "or visit http://www.beegfs.com to find out about configuration options.]"; + printOrLogError(err.str()); + appResult = AppCode::INVALID_CONFIG; + } + catch (const ComponentInitException& e) + { + printOrLogError("Component initialization error: " + std::string(e.what())); + appResult = AppCode::INITIALIZATION_ERROR; + } + catch (const std::runtime_error& e) + { + printOrLogError("Runtime error: " + std::string(e.what())); + appResult = AppCode::RUNTIME_ERROR; + } + catch (const std::exception& e) + { + printOrLogError("Generic error: " + std::string(e.what())); + appResult = AppCode::RUNTIME_ERROR; + } +} + +void App::printOrLogError(const std::string& text) const +{ + if (Logger::isInitialized()) + LOG(GENERAL, ERR, text); + else + std::cerr << std::endl << text << std::endl << std::endl; +} + +void App::runNormal() +{ + Logger::createLogger(cfg->getLogLevel(), cfg->getLogType(), cfg->getLogNoDate(), + cfg->getLogStdFile(), cfg->getLogNumLines(), cfg->getLogNumRotatedFiles()); + + pidFileLockFD = createAndLockPIDFile(cfg->getPIDFile()); + initDataObjects(); + SignalHandler::registerSignalHandler(this); + initLocalNodeInfo(); + initWorkers(); + initComponents(); + + RDMASocket::rdmaForkInitOnce(); + + + if (cfg->getRunDaemonized()) + daemonize(); + + logInfos(); + + // make sure components don't receive SIGINT/SIGTERM (blocked signals are inherited) + PThread::blockInterruptSignals(); + startWorkers(); + startComponents(); + PThread::unblockInterruptSignals(); + + joinComponents(); + joinWorkers(); +} + +void App::initLocalNodeInfo() +{ + bool useRDMA = cfg->getConnUseRDMA(); + unsigned portUDP = cfg->getConnMonPort(); + + StringList allowedInterfaces; + std::string interfacesFilename = cfg->getConnInterfacesFile(); + if (interfacesFilename.length() ) + cfg->loadStringListFile(interfacesFilename.c_str(), allowedInterfaces); + + NetworkInterfaceCard::findAll(&allowedInterfaces, useRDMA, &localNicList); + + if (localNicList.empty() ) + throw InvalidConfigException("Couldn't find any usable NIC"); + + localNicList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + + noDefaultRouteNets = std::make_shared(); + if(!initNoDefaultRouteList(noDefaultRouteNets.get())) + throw InvalidConfigException("Failed to parse connNoDefaultRoute"); + + initRoutingTable(); + updateRoutingTable(); + + std::string nodeID = System::getHostname(); + + // TODO add a Mon nodetype at some point + localNode = std::make_shared(NODETYPE_Client, nodeID, NumNodeID(1), portUDP, 0, localNicList); +} + +void App::initDataObjects() +{ + netFilter = boost::make_unique(cfg->getConnNetFilterFile()); + tcpOnlyFilter = boost::make_unique(cfg->getConnTcpOnlyFilterFile()); + netMessageFactory = boost::make_unique(); + workQueue = boost::make_unique(); + + targetMapper = boost::make_unique(); + + metaNodes = boost::make_unique(); + storageNodes = boost::make_unique(); + mgmtNodes = boost::make_unique(); + + metaBuddyGroupMapper = boost::make_unique(); + storageBuddyGroupMapper = boost::make_unique(); + + + if (cfg->getDbType() == Config::DbTypes::CASSANDRA) + { + Cassandra::Config cassandraConfig; + cassandraConfig.host = cfg->getDbHostName(); + cassandraConfig.port = cfg->getDbHostPort(); + cassandraConfig.database = cfg->getDbDatabase(); + cassandraConfig.maxInsertsPerBatch = cfg->getCassandraMaxInsertsPerBatch(); + cassandraConfig.TTLSecs = cfg->getCassandraTTLSecs(); + + tsdb = boost::make_unique(std::move(cassandraConfig)); + } + else // Config::DbTypes::INFLUXDB OR Config::DbTypes::INFLUXDB2 + { + InfluxDB::Config influxdbConfig; + influxdbConfig.host = cfg->getDbHostName(); + influxdbConfig.port = cfg->getDbHostPort(); + influxdbConfig.maxPointsPerRequest = cfg->getInfluxdbMaxPointsPerRequest(); + influxdbConfig.httpTimeout = cfg->getHttpTimeout(); + influxdbConfig.curlCheckSSLCertificates = cfg->getCurlCheckSSLCertificates(); + if (cfg->getDbType() == Config::DbTypes::INFLUXDB2) + { + influxdbConfig.bucket = cfg->getDbBucket(); + influxdbConfig.organization = cfg->getDbAuthOrg(); + influxdbConfig.token = cfg->getDbAuthToken(); + influxdbConfig.dbVersion = INFLUXDB2; + } + else + { + influxdbConfig.database = cfg->getDbDatabase(); + influxdbConfig.setRetentionPolicy = cfg->getInfluxDbSetRetentionPolicy(); + influxdbConfig.retentionDuration = cfg->getInfluxDbRetentionDuration(); + influxdbConfig.username = cfg->getDbAuthUsername(); + influxdbConfig.password = cfg->getDbAuthPassword(); + influxdbConfig.dbVersion = INFLUXDB; + } + tsdb = boost::make_unique(std::move(influxdbConfig)); + } +} + +void App::initComponents() +{ + nodeListRequestor = boost::make_unique(this); + statsCollector = boost::make_unique(this); + cleanUp = boost::make_unique(this); +} + +void App::startComponents() +{ + LOG(GENERAL, DEBUG, "Starting components..."); + nodeListRequestor->start(); + statsCollector->start(); + cleanUp->start(); + LOG(GENERAL, DEBUG, "Components running."); +} + +void App::stopComponents() +{ + if (nodeListRequestor) + nodeListRequestor->selfTerminate(); + if (statsCollector) + statsCollector->selfTerminate(); + if (cleanUp) + cleanUp->selfTerminate(); + + stopWorkers(); + selfTerminate(); +} + +void App::joinComponents() +{ + LOG(GENERAL, DEBUG, "Joining Component threads..."); + nodeListRequestor->join(); + statsCollector->join(); + cleanUp->join(); + LOG(GENERAL, CRITICAL, "All components stopped. Exiting now."); +} + +void App::initWorkers() +{ + const unsigned numDirectWorkers = 1; + const unsigned workersBufSize = 1024*1024; + + unsigned numWorkers = cfg->getTuneNumWorkers(); + + for (unsigned i=0; i < numWorkers; i++) + { + auto worker = boost::make_unique("Worker" + StringTk::intToStr(i+1), + workQueue.get(), QueueWorkType_INDIRECT); + + worker->setBufLens(workersBufSize, workersBufSize); + workerList.push_back(std::move(worker)); + } + + for (unsigned i=0; i < numDirectWorkers; i++) + { + auto worker = boost::make_unique("DirectWorker" + StringTk::intToStr(i+1), + workQueue.get(), QueueWorkType_DIRECT); + + worker->setBufLens(workersBufSize, workersBufSize); + workerList.push_back(std::move(worker)); + } +} + +void App::startWorkers() +{ + for (auto worker = workerList.begin(); worker != workerList.end(); worker++) + { + (*worker)->start(); + } +} + +void App::stopWorkers() +{ + // need two loops because we don't know if the worker that handles the work will be the same that + // received the self-terminate-request + for (auto worker = workerList.begin(); worker != workerList.end(); worker++) + { + (*worker)->selfTerminate(); + + // add dummy work to wake up the worker immediately for faster self termination + PersonalWorkQueue* personalQ = (*worker)->getPersonalWorkQueue(); + workQueue->addPersonalWork(new DummyWork(), personalQ); + } +} + +void App::joinWorkers() +{ + + for (auto worker = workerList.begin(); worker != workerList.end(); worker++) + { + waitForComponentTermination((*worker).get()); + } +} + +void App::logInfos() +{ + LOG(GENERAL, CRITICAL, std::string("Version: ") + BEEGFS_VERSION); +#ifdef BEEGFS_DEBUG + LOG(GENERAL, DEBUG, "--DEBUG VERSION--"); +#endif + + // list usable network interfaces + NicAddressList nicList = getLocalNicList(); + logUsableNICs(NULL, nicList); + + // print net filters + if (netFilter->getNumFilterEntries() ) + { + LOG(GENERAL, WARNING, std::string("Net filters: ") + + StringTk::uintToStr(netFilter->getNumFilterEntries() ) ); + } + + if (tcpOnlyFilter->getNumFilterEntries() ) + { + LOG(GENERAL, WARNING, std::string("TCP-only filters: ") + + StringTk::uintToStr(tcpOnlyFilter->getNumFilterEntries() ) ); + } +} + +void App::daemonize() +{ + int nochdir = 1; // 1 to keep working directory + int noclose = 0; // 1 to keep stdin/-out/-err open + + LOG(GENERAL, CRITICAL, "Detaching process..."); + + int detachRes = daemon(nochdir, noclose); + if (detachRes == -1) + throw std::runtime_error(std::string("Unable to detach process: ") + + System::getErrString()); + + updateLockedPIDFile(pidFileLockFD); // ignored if pidFileFD is -1 +} + +void App::handleComponentException(std::exception& e) +{ + LOG(GENERAL, CRITICAL, "This component encountered an unrecoverable error.", sysErr, + ("Exception", e.what())); + + LOG(GENERAL, WARNING, "Shutting down..."); + stopComponents(); +} + +void App::handleNetworkInterfaceFailure(const std::string& devname) +{ + // Nothing to do. This App has no internodeSyncer that would rescan the + // netdevs. + LOG(GENERAL, ERR, "Network interface failure.", + ("Device", devname)); +} diff --git a/mon/source/app/App.h b/mon/source/app/App.h new file mode 100644 index 0000000..48acc31 --- /dev/null +++ b/mon/source/app/App.h @@ -0,0 +1,184 @@ +#ifndef APP_H_ +#define APP_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class App : public AbstractApp +{ + public: + enum AppCode + { + NO_ERROR = 0, + INVALID_CONFIG = 1, + INITIALIZATION_ERROR = 2, + RUNTIME_ERROR = 3 + }; + + + App(int argc, char** argv); + + virtual void run() override; + virtual void stopComponents() override; + virtual void handleComponentException(std::exception& e) override; + virtual void handleNetworkInterfaceFailure(const std::string& devname) override; + + + private: + int appResult; + int argc; + char** argv; + LockFD pidFileLockFD; + + std::unique_ptr targetMapper; + + std::unique_ptr cfg; + std::unique_ptr netFilter; + std::unique_ptr tcpOnlyFilter; + std::unique_ptr netMessageFactory; + NicListCapabilities localNicCaps; + std::shared_ptr localNode; + std::unique_ptr tsdb; + std::unique_ptr workQueue; + + std::unique_ptr mgmtNodes; + std::unique_ptr metaNodes; + std::unique_ptr storageNodes; + std::unique_ptr metaBuddyGroupMapper; + std::unique_ptr storageBuddyGroupMapper; + + std::unique_ptr nodeListRequestor; + std::unique_ptr statsCollector; + std::unique_ptr cleanUp; + + std::list> workerList; + + void printOrLogError(const std::string& text) const; + + void runNormal(); + void initDataObjects(); + void initComponents(); + void startComponents(); + void joinComponents(); + void initWorkers(); + void startWorkers(); + void stopWorkers(); + void joinWorkers(); + void initLocalNodeInfo(); + void logInfos(); + void daemonize(); + + public: + NodeStoreServers* getServerStoreFromType(NodeType nodeType) + { + switch (nodeType) + { + case NODETYPE_Meta: + return metaNodes.get(); + + case NODETYPE_Storage: + return storageNodes.get(); + + case NODETYPE_Mgmt: + return mgmtNodes.get(); + + default: + return nullptr; + } + } + + virtual ICommonConfig* getCommonConfig() const override + { + return cfg.get(); + } + + virtual NetFilter* getNetFilter() const override + { + return netFilter.get(); + } + + virtual NetFilter* getTcpOnlyFilter() const override + { + return tcpOnlyFilter.get(); + } + + virtual AbstractNetMessageFactory* getNetMessageFactory() const override + { + return netMessageFactory.get(); + } + + std::shared_ptr getLocalNode() + { + return localNode; + } + + Config* getConfig() + { + return cfg.get(); + } + + MultiWorkQueue *getWorkQueue() + { + return workQueue.get(); + } + + NodeStoreMetaEx *getMetaNodes() + { + return metaNodes.get(); + } + + NodeStoreStorageEx *getStorageNodes() + { + return storageNodes.get(); + } + + NodeStoreMgmtEx *getMgmtNodes() + { + return mgmtNodes.get(); + } + + TSDatabase *getTSDB() + { + return tsdb.get(); + } + + TargetMapper* getTargetMapper() + { + return targetMapper.get(); + } + + MirrorBuddyGroupMapper* getMetaBuddyGroupMapper() + { + return metaBuddyGroupMapper.get(); + } + + MirrorBuddyGroupMapper* getStorageBuddyGroupMapper() + { + return storageBuddyGroupMapper.get(); + } + + int getAppResult() + { + return appResult; + } +}; + + +#endif /*APP_H_*/ diff --git a/mon/source/app/Config.cpp b/mon/source/app/Config.cpp new file mode 100644 index 0000000..66ca867 --- /dev/null +++ b/mon/source/app/Config.cpp @@ -0,0 +1,210 @@ +#include +#include "Config.h" + +#include + +#define CONFIG_DEFAULT_CFGFILENAME "/etc/beegfs/beegfs-mon.conf" + +Config::Config(int argc, char** argv): AbstractConfig(argc, argv) +{ + initConfig(argc, argv, true); + + // check mandatory value + if(getSysMgmtdHost().empty()) + throw InvalidConfigException("Management host undefined."); + + // Load auth config file + if (!dbAuthFile.empty()) + { + std::ifstream authConfig(dbAuthFile); + + if (!authConfig.good()) + throw InvalidConfigException("Could not open InfluxDB authentication file"); + + StringMap authMap; + MapTk::loadStringMapFromFile(dbAuthFile.c_str(), &authMap); + + for (const auto& e : authMap) { + if (e.first == "password") { + dbAuthPassword = e.second; + } else if (e.first == "username") { + dbAuthUsername = e.second; + } else if (e.first == "organization") { + dbAuthOrg = e.second; + } else if (e.first == "token") { + dbAuthToken = e.second; + } else { + throw InvalidConfigException("The InfluxDB authentication file may only contain " + "the options username and password for influxdb version 1.x " + "organization and token for influxdb version 2.x" ); + } + } + } +} + +void Config::loadDefaults(bool addDashes) +{ + AbstractConfig::loadDefaults(); + + // re-definitions + configMapRedefine("cfgFile", ""); + configMapRedefine("connUseRDMA", "false"); + + // own definitions + configMapRedefine("connInterfacesFile", ""); + configMapRedefine("tuneNumWorkers", "4"); + configMapRedefine("runDaemonized", "false"); + configMapRedefine("pidFile", ""); + + configMapRedefine("dbType", "influxdb"); + configMapRedefine("dbHostName", "localhost"); + configMapRedefine("dbHostPort", "8086"); + configMapRedefine("dbDatabase", "beegfs_mon"); + configMapRedefine("dbAuthFile", ""); + + // those are used by influxdb only but are kept like this for compatibility + configMapRedefine("dbMaxPointsPerRequest", "5000"); + configMapRedefine("dbSetRetentionPolicy", "true"); + configMapRedefine("dbRetentionDuration", "1d"); + + configMapRedefine("dbBucket", ""); + + configMapRedefine("cassandraMaxInsertsPerBatch","25"); + configMapRedefine("cassandraTTLSecs", "86400"); + + configMapRedefine("collectClientOpsByNode", "true"); + configMapRedefine("collectClientOpsByUser", "true"); + + configMapRedefine("httpTimeoutMSecs", "1000"); + configMapRedefine("statsRequestIntervalSecs", "5"); + configMapRedefine("nodelistRequestIntervalSecs","30"); + + configMapRedefine("curlCheckSSLCertificates", "true"); + +} + +void Config::applyConfigMap(bool enableException, bool addDashes) +{ + AbstractConfig::applyConfigMap(false); + + for (StringMapIter iter = configMap.begin(); iter != configMap.end(); ) + { + bool unknownElement = false; + + if (iter->first == std::string("logType")) + { + if (iter->second == "syslog") + { + logType = LogType_SYSLOG; + } + else if (iter->second == "logfile") + { + logType = LogType_LOGFILE; + } + else + { + throw InvalidConfigException("The value of config argument logType is invalid:" + " Must be syslog or logfile."); + } + } + else if (iter->first == std::string("connInterfacesFile")) + connInterfacesFile = iter->second; + else + if (iter->first == std::string("tuneNumWorkers")) + tuneNumWorkers = StringTk::strToUInt(iter->second); + else + if (iter->first == std::string("runDaemonized")) + runDaemonized = StringTk::strToBool(iter->second); + else + if (iter->first == std::string("pidFile")) + pidFile = iter->second; + else + if (iter->first == std::string("dbType")) + { + if (iter->second == "influxdb") + dbType = DbTypes::INFLUXDB; + else if (iter->second == "influxdb2") + dbType = DbTypes::INFLUXDB2; + else if (iter->second == "cassandra") + dbType = DbTypes::CASSANDRA; + else + throw InvalidConfigException("The value of config argument dbType is invalid:" + " Must be influxdb or cassandra."); + } + else + if (iter->first == std::string("dbHostName")) + dbHostName = iter->second; + else + if (iter->first == std::string("dbHostPort")) + dbHostPort = StringTk::strToUInt(iter->second); + else + if (iter->first == std::string("dbDatabase")) + dbDatabase = iter->second; + else + if (iter->first == std::string("dbAuthFile")) + dbAuthFile = iter->second; + else + // those are used by influxdb only but are kept like this for compatibility + if (iter->first == std::string("dbMaxPointsPerRequest")) + influxdbMaxPointsPerRequest = StringTk::strToUInt(iter->second); + else + if (iter->first == std::string("dbSetRetentionPolicy")) + influxdbSetRetentionPolicy = StringTk::strToBool(iter->second); + else + if (iter->first == std::string("dbRetentionDuration")) + influxdbRetentionDuration = iter->second; + else + // those are used by influxdb2 + if (iter->first == std::string("dbBucket")) + dbBucket = iter->second; + else + + if (iter->first == std::string("cassandraMaxInsertsPerBatch")) + cassandraMaxInsertsPerBatch = StringTk::strToUInt(iter->second); + else + if (iter->first == std::string("cassandraTTLSecs")) + cassandraTTLSecs = StringTk::strToUInt(iter->second); + else + if (iter->first == std::string("collectClientOpsByNode")) + collectClientOpsByNode = StringTk::strToBool(iter->second); + else + if (iter->first == std::string("collectClientOpsByUser")) + collectClientOpsByUser = StringTk::strToBool(iter->second); + else + if (iter->first == std::string("httpTimeoutMSecs")) + httpTimeout = std::chrono::milliseconds(StringTk::strToUInt(iter->second)); + else + if (iter->first == std::string("statsRequestIntervalSecs")) + statsRequestInterval = std::chrono::seconds(StringTk::strToUInt(iter->second)); + else + if (iter->first == std::string("nodelistRequestIntervalSecs")) + nodelistRequestInterval = std::chrono::seconds(StringTk::strToUInt(iter->second)); + else + if (iter->first == std::string("curlCheckSSLCertificates")) + curlCheckSSLCertificates = StringTk::strToBool(iter->second); + else + { + unknownElement = true; + + if (enableException) + { + throw InvalidConfigException(std::string("The config argument '") + + iter->first + std::string("' is invalid.") ); + } + } + + if (unknownElement) + { + iter++; + } + else + { + iter = eraseFromConfigMap(iter); + } + } +} + +void Config::initImplicitVals() +{ + AbstractConfig::initConnAuthHash(connAuthFile, &connAuthHash); +} diff --git a/mon/source/app/Config.h b/mon/source/app/Config.h new file mode 100644 index 0000000..11ecc0a --- /dev/null +++ b/mon/source/app/Config.h @@ -0,0 +1,179 @@ +#ifndef CONFIG_H_ +#define CONFIG_H_ + +#include + + +class Config : public AbstractConfig +{ + public: + Config(int argc, char** argv); + + enum DbTypes + { + INFLUXDB, + INFLUXDB2, + CASSANDRA + }; + + private: + // configurables + std::string connInterfacesFile; + unsigned tuneNumWorkers; + bool runDaemonized; + std::string pidFile; + + // mon-specific configurables + DbTypes dbType; + std::string dbHostName; + unsigned dbHostPort; + std::string dbDatabase; + std::string dbBucket; + std::string dbAuthFile; + unsigned influxdbMaxPointsPerRequest; + bool influxdbSetRetentionPolicy; + std::string influxdbRetentionDuration; + unsigned cassandraMaxInsertsPerBatch; + unsigned cassandraTTLSecs; + bool collectClientOpsByNode; + bool collectClientOpsByUser; + std::chrono::milliseconds httpTimeout; + std::chrono::seconds statsRequestInterval; + std::chrono::seconds nodelistRequestInterval; + bool curlCheckSSLCertificates; + + std::string dbAuthUsername; + std::string dbAuthPassword; + std::string dbAuthOrg; + std::string dbAuthToken; + + + virtual void loadDefaults(bool addDashes) override; + virtual void applyConfigMap(bool enableException, bool addDashes) override; + virtual void initImplicitVals() override; + + public: + // getters & setters + + const std::string& getConnInterfacesFile() const + { + return connInterfacesFile; + } + + unsigned getTuneNumWorkers() const + { + return tuneNumWorkers; + } + + bool getRunDaemonized() const + { + return runDaemonized; + } + + const std::string& getPIDFile() const + { + return pidFile; + } + + DbTypes getDbType() const + { + return dbType; + } + + const std::string& getDbHostName() const + { + return dbHostName; + } + + unsigned getDbHostPort() const + { + return dbHostPort; + } + + const std::string& getDbDatabase() const + { + return dbDatabase; + } + + const std::string& getDbBucket() const + { + return dbBucket; + } + + unsigned getInfluxdbMaxPointsPerRequest() const + { + return influxdbMaxPointsPerRequest; + } + + bool getInfluxDbSetRetentionPolicy() const + { + return influxdbSetRetentionPolicy; + } + + const std::string& getInfluxDbRetentionDuration() const + { + return influxdbRetentionDuration; + } + + unsigned getCassandraMaxInsertsPerBatch() const + { + return cassandraMaxInsertsPerBatch; + } + + unsigned getCassandraTTLSecs() const + { + return cassandraTTLSecs; + } + + bool getCollectClientOpsByNode() const + { + return collectClientOpsByNode; + } + + bool getCollectClientOpsByUser() const + { + return collectClientOpsByUser; + } + + const std::chrono::milliseconds& getHttpTimeout() const + { + return httpTimeout; + } + + const std::chrono::seconds& getStatsRequestInterval() const + { + return statsRequestInterval; + } + + const std::chrono::seconds& getNodelistRequestInterval() const + { + return nodelistRequestInterval; + } + + const std::string& getDbAuthUsername() const + { + return dbAuthUsername; + } + + const std::string& getDbAuthPassword() const + { + return dbAuthPassword; + } + + const std::string& getDbAuthOrg() const + { + return dbAuthOrg; + } + + const std::string& getDbAuthToken() const + { + return dbAuthToken; + } + + bool getCurlCheckSSLCertificates() const + { + return curlCheckSSLCertificates; + } +}; + +#endif /*CONFIG_H_*/ diff --git a/mon/source/app/Main.cpp b/mon/source/app/Main.cpp new file mode 100644 index 0000000..d867cdf --- /dev/null +++ b/mon/source/app/Main.cpp @@ -0,0 +1,14 @@ +#include +#include +#include + +int main(int argc, char** argv) +{ + BuildTypeTk::checkDebugBuildTypes(); + AbstractApp::runTimeInitsAndChecks(); + + App app(argc, argv); + app.startInCurrentThread(); + + return app.getAppResult(); +} \ No newline at end of file diff --git a/mon/source/app/SignalHandler.cpp b/mon/source/app/SignalHandler.cpp new file mode 100644 index 0000000..2b84770 --- /dev/null +++ b/mon/source/app/SignalHandler.cpp @@ -0,0 +1,49 @@ +#include "SignalHandler.h" + +#include +#include + +#include + +App* SignalHandler::app = nullptr; + +void SignalHandler::registerSignalHandler(App* app) +{ + SignalHandler::app = app; + signal(SIGINT, SignalHandler::handle); + signal(SIGTERM, SignalHandler::handle); +} + + +void SignalHandler::handle(int sig) +{ + // reset signal handling to default + signal(sig, SIG_DFL); + + if (Logger::isInitialized()) + { + switch(sig) + { + case SIGINT: + { + LOG(GENERAL, CRITICAL, "Received a SIGINT. Shutting down..."); + } break; + + case SIGTERM: + { + LOG(GENERAL, CRITICAL, "Received a SIGTERM. Shutting down..."); + } break; + + default: + { + // shouldn't happen + LOG(GENERAL, CRITICAL, "Received an unknown signal. Shutting down..."); + } break; + } + } + + if (app != nullptr) + { + app->stopComponents(); + } +} \ No newline at end of file diff --git a/mon/source/app/SignalHandler.h b/mon/source/app/SignalHandler.h new file mode 100644 index 0000000..ef52202 --- /dev/null +++ b/mon/source/app/SignalHandler.h @@ -0,0 +1,16 @@ +#ifndef SIGNAL_HANDLER_H_ +#define SIGNAL_HANDLER_H_ + +class App; + +class SignalHandler +{ + public: + static void registerSignalHandler(App* app); + static void handle(int sig); + + private: + static App* app; +}; + +#endif \ No newline at end of file diff --git a/mon/source/components/CleanUp.cpp b/mon/source/components/CleanUp.cpp new file mode 100644 index 0000000..68736fb --- /dev/null +++ b/mon/source/components/CleanUp.cpp @@ -0,0 +1,67 @@ +#include "CleanUp.h" + +#include + +CleanUp::CleanUp(App* app) : + PThread("CleanUp"), app(app) +{} + +void CleanUp::run() +{ + try + { + LOG(GENERAL, DEBUG, "Component started."); + registerSignalHandler(); + loop(); + LOG(GENERAL, DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + app->handleComponentException(e); + } +} + +void CleanUp::loop() +{ + const std::chrono::minutes idleDisconnectInterval(30); + + while (!waitForSelfTerminateOrder(std::chrono::milliseconds(idleDisconnectInterval).count())) + { + dropIdleConns(); + } +} + +void CleanUp::dropIdleConns() +{ + unsigned numDroppedConns = 0; + + numDroppedConns += dropIdleConnsByStore(app->getMgmtNodes()); + numDroppedConns += dropIdleConnsByStore(app->getMetaNodes()); + numDroppedConns += dropIdleConnsByStore(app->getStorageNodes()); + + if (numDroppedConns) + { + LOG(GENERAL, DEBUG, "Idle connections dropped", numDroppedConns); + } +} + +unsigned CleanUp::dropIdleConnsByStore(NodeStoreServers* nodes) +{ + unsigned numDroppedConns = 0; + + const auto referencedNodes = nodes->referenceAllNodes(); + for (auto node = referencedNodes.begin(); node != referencedNodes.end(); + node++) + { + // don't do any idle disconnect stuff with local node + // (the LocalNodeConnPool doesn't support and doesn't need this kind of treatment) + if (*node != app->getLocalNode()) + { + auto connPool = (*node)->getConnPool(); + + numDroppedConns += connPool->disconnectAndResetIdleStreams(); + } + } + + return numDroppedConns; +} diff --git a/mon/source/components/CleanUp.h b/mon/source/components/CleanUp.h new file mode 100644 index 0000000..7eaf34c --- /dev/null +++ b/mon/source/components/CleanUp.h @@ -0,0 +1,24 @@ +#ifndef CLEANUP_H_ +#define CLEANUP_H_ + +#include +#include + +class App; + +class CleanUp : public PThread +{ + public: + CleanUp(App* app); + + private: + App* const app; + virtual void run() override; + void loop(); + void dropIdleConns(); + unsigned dropIdleConnsByStore(NodeStoreServers* nodes); + +}; + + +#endif /* CLEANUP_H_ */ diff --git a/mon/source/components/NodeListRequestor.cpp b/mon/source/components/NodeListRequestor.cpp new file mode 100644 index 0000000..9a0a900 --- /dev/null +++ b/mon/source/components/NodeListRequestor.cpp @@ -0,0 +1,91 @@ +#include "NodeListRequestor.h" + +#include +#include + +#include + +static const unsigned MGMT_NUM_TRIES = 3; +static const std::chrono::milliseconds MGMT_TIMEOUT{1000}; + +NodeListRequestor::NodeListRequestor(App* app) : + PThread("NodeListReq"), app(app) +{} + +void NodeListRequestor::run() +{ + try + { + LOG(GENERAL, DEBUG, "Component started."); + registerSignalHandler(); + + requestLoop(); + + LOG(GENERAL, DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + app->handleComponentException(e); + } +} + +void NodeListRequestor::requestLoop() +{ + do + { + // Get management node. Do this every time before updating node lists to check if + // management is online to prevent log spam from NodesTk::downloadNodes when it is + // not reachable + if (!getMgmtNodeInfo()) + { + LOG(GENERAL, NOTICE, "Did not receive a response from management node!"); + continue; + } + + // try to reference first mgmt node (which is at the moment the only one) + std::shared_ptr mgmtNode = app->getMgmtNodes()->referenceFirstNode(); + + if (mgmtNode) + { + LOG(GENERAL, DEBUG, "Requesting node lists..."); + + app->getWorkQueue()->addIndirectWork(new GetNodesWork(mgmtNode, app->getMetaNodes(), + NODETYPE_Meta, app->getMetaBuddyGroupMapper(), app->getLocalNode())); + app->getWorkQueue()->addIndirectWork(new GetNodesWork(mgmtNode, + app->getStorageNodes(), NODETYPE_Storage, app->getStorageBuddyGroupMapper(), + app->getLocalNode())); + } + else + { + LOG(GENERAL, DEBUG, "Unable to reference management node for node list request."); + } + } + while (!waitForSelfTerminateOrder(std::chrono::milliseconds( + app->getConfig()->getNodelistRequestInterval()).count())); +} + +bool NodeListRequestor::getMgmtNodeInfo() +{ + for (unsigned i = 0; i < MGMT_NUM_TRIES; i++) + { + LOG(GENERAL, DEBUG, "Waiting for management node..."); + + // get mgmtd node using NodesTk + auto mgmtNode = NodesTk::downloadNodeInfo(app->getConfig()->getSysMgmtdHost(), + app->getConfig()->getConnMgmtdPort(), app->getConfig()->getConnAuthHash(), + app->getNetMessageFactory(), + NODETYPE_Mgmt, MGMT_TIMEOUT.count()); + + if(mgmtNode) + { + app->getMgmtNodes()->addOrUpdateNodeEx(std::move(mgmtNode), nullptr); + return true; + } + + if (PThread::waitForSelfTerminateOrder(std::chrono::milliseconds(MGMT_TIMEOUT).count())) + break; + } + + return false; +} + diff --git a/mon/source/components/NodeListRequestor.h b/mon/source/components/NodeListRequestor.h new file mode 100644 index 0000000..4227449 --- /dev/null +++ b/mon/source/components/NodeListRequestor.h @@ -0,0 +1,20 @@ +#ifndef NODELISTREQUESTOR_H_ +#define NODELISTREQUESTOR_H_ + +#include + +class App; + +class NodeListRequestor : public PThread +{ + public: + NodeListRequestor(App* app); + + private: + App* const app; + virtual void run() override; + void requestLoop(); + bool getMgmtNodeInfo(); +}; + +#endif /*NODELISTREQUESTOR_H_*/ diff --git a/mon/source/components/StatsCollector.cpp b/mon/source/components/StatsCollector.cpp new file mode 100644 index 0000000..086d591 --- /dev/null +++ b/mon/source/components/StatsCollector.cpp @@ -0,0 +1,206 @@ +#include "StatsCollector.h" + +#include +#include + +#include + + +StatsCollector::StatsCollector(App* app) : + PThread("StatsCollector"), app(app) +{} + +void StatsCollector::run() +{ + try + { + LOG(GENERAL, DEBUG, "Component started."); + registerSignalHandler(); + requestLoop(); + LOG(GENERAL, DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + app->handleComponentException(e); + } +} + +void StatsCollector::requestLoop() +{ + bool collectClientOpsByNode = app->getConfig()->getCollectClientOpsByNode(); + bool collectClientOpsByUser = app->getConfig()->getCollectClientOpsByUser(); + + // intially wait one query interval before requesting stats to give NodeListRequestor the time + // to retrieve the node lists + while (!waitForSelfTerminateOrder(std::chrono::milliseconds( + app->getConfig()->getStatsRequestInterval()).count())) + { + { + LOG(GENERAL, DEBUG, "Requesting Stats..."); + + std::unique_lock lock(mutex); + + workItemCounter = 0; + metaResults.clear(); + storageResults.clear(); + + // collect data + + const auto& metaNodes = app->getMetaNodes()->referenceAllNodes(); + + for (auto node = metaNodes.begin(); node != metaNodes.end(); node++) + { + workItemCounter++; + app->getWorkQueue()->addIndirectWork( + new RequestMetaDataWork(std::static_pointer_cast(*node), + this, collectClientOpsByNode, collectClientOpsByUser)); + } + + const auto& storageNodes = app->getStorageNodes()->referenceAllNodes(); + + for (auto node = storageNodes.begin(); node != storageNodes.end(); node++) + { + workItemCounter++; + app->getWorkQueue()->addIndirectWork( + new RequestStorageDataWork(std::static_pointer_cast(*node), + this, collectClientOpsByNode, collectClientOpsByUser)); + } + + while (workItemCounter > 0) + condVar.wait(lock); + + // write data + + for (auto iter = metaResults.begin(); iter != metaResults.end(); iter++) + { + app->getTSDB()->insertMetaNodeData(iter->node, iter->data); + + for (auto listIter = iter->highResStatsList.begin(); + listIter != iter->highResStatsList.end(); listIter++) + { + app->getTSDB()->insertHighResMetaNodeData(iter->node, *listIter); + } + + if (collectClientOpsByNode) + { + for (auto mapIter = iter->ipOpsUnorderedMap.begin(); + mapIter != iter->ipOpsUnorderedMap.end(); mapIter++) + { + ipMetaClientOps.addOpsList(mapIter->first, mapIter->second); + } + } + + if (collectClientOpsByUser) + { + for (auto mapIter = iter->userOpsUnorderedMap.begin(); + mapIter != iter->userOpsUnorderedMap.end(); mapIter++) + { + userMetaClientOps.addOpsList(mapIter->first, mapIter->second); + } + } + } + + for (auto iter = storageResults.begin(); iter != storageResults.end(); iter++) + { + app->getTSDB()->insertStorageNodeData(iter->node, iter->data); + + for (auto listIter = iter->highResStatsList.begin(); + listIter != iter->highResStatsList.end(); listIter++) + { + app->getTSDB()->insertHighResStorageNodeData(iter->node, *listIter); + } + + for (auto listIter = iter->storageTargetList.begin(); + listIter != iter->storageTargetList.end(); + listIter++) + { + app->getTSDB()->insertStorageTargetsData(iter->node, *listIter); + } + + if (collectClientOpsByNode) + { + for (auto mapIter = iter->ipOpsUnorderedMap.begin(); + mapIter != iter->ipOpsUnorderedMap.end(); mapIter++) + { + ipStorageClientOps.addOpsList(mapIter->first, mapIter->second); + } + } + + if (collectClientOpsByUser) + { + for (auto mapIter = iter->userOpsUnorderedMap.begin(); + mapIter != iter->userOpsUnorderedMap.end(); mapIter++) + { + userStorageClientOps.addOpsList(mapIter->first, mapIter->second); + } + } + } + + if (collectClientOpsByNode) + { + processClientOps(ipMetaClientOps, NODETYPE_Meta, false); + processClientOps(ipStorageClientOps, NODETYPE_Storage, false); + } + + if (collectClientOpsByUser) + { + processClientOps(userMetaClientOps, NODETYPE_Meta, true); + processClientOps(userStorageClientOps, NODETYPE_Storage, true); + } + + app->getTSDB()->write(); + } + } +} + +void StatsCollector::processClientOps(ClientOps& clientOps, NodeType nodeType, bool perUser) +{ + ClientOps::IdOpsMap diffOpsMap; + ClientOps::OpsList sumOpsList; + + diffOpsMap = clientOps.getDiffOpsMap(); + sumOpsList = clientOps.getDiffSumOpsList(); + + if (!diffOpsMap.empty()) + { + for (auto opsMapIter = diffOpsMap.begin(); + opsMapIter != diffOpsMap.end(); + opsMapIter++) + { + std::string id; + + if (perUser) + { + if (opsMapIter->first == ~0U) + id = "undefined"; + else + id = StringTk::uintToStr(opsMapIter->first); + } + else + { + struct in_addr inAddr = { (in_addr_t)opsMapIter->first }; + id = Socket::ipaddrToStr(inAddr); + } + + std::map stringOpMap; + unsigned opCounter = 0; + for (auto opsListIter = opsMapIter->second.begin(); + opsListIter != opsMapIter->second.end(); + opsListIter++) + { + std::string opName; + if (nodeType == NODETYPE_Meta) + opName = OpToStringMapping::mapMetaOpNum(opCounter); + else if (nodeType == NODETYPE_Storage) + opName = OpToStringMapping::mapStorageOpNum(opCounter); + + stringOpMap[opName] = *opsListIter; + opCounter++; + } + + app->getTSDB()->insertClientNodeData(id, nodeType, stringOpMap, perUser); + } + } + + clientOps.clear(); +} diff --git a/mon/source/components/StatsCollector.h b/mon/source/components/StatsCollector.h new file mode 100644 index 0000000..f81de58 --- /dev/null +++ b/mon/source/components/StatsCollector.h @@ -0,0 +1,56 @@ +#ifndef STATSCOLLECTOR_H_ +#define STATSCOLLECTOR_H_ + +#include +#include +#include +#include + +#include +#include + +class App; + +class StatsCollector : public PThread +{ + friend class RequestMetaDataWork; + friend class RequestStorageDataWork; + + public: + StatsCollector(App* app); + + private: + App* const app; + ClientOps ipMetaClientOps; + ClientOps ipStorageClientOps; + ClientOps userMetaClientOps; + ClientOps userStorageClientOps; + + mutable std::mutex mutex; + int workItemCounter; + std::list metaResults; + std::list storageResults; + std::condition_variable condVar; + + virtual void run() override; + void requestLoop(); + void processClientOps(ClientOps& clientOps, NodeType nodeType, bool perUser); + + void insertMetaData(RequestMetaDataWork::Result result) + { + const std::unique_lock lock(mutex); + metaResults.push_back(std::move(result)); + workItemCounter--; + condVar.notify_one(); + } + + void insertStorageData(RequestStorageDataWork::Result result) + { + const std::unique_lock lock(mutex); + storageResults.push_back(std::move(result)); + workItemCounter--; + condVar.notify_one(); + } +}; + +#endif /*STATSCOLLECTOR_H_*/ diff --git a/mon/source/components/worker/GetNodesWork.cpp b/mon/source/components/worker/GetNodesWork.cpp new file mode 100644 index 0000000..278371e --- /dev/null +++ b/mon/source/components/worker/GetNodesWork.cpp @@ -0,0 +1,40 @@ +#include "GetNodesWork.h" + +#include + +void GetNodesWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + std::vector> nodesList; + std::list addedNodes; + std::list removedNodes; + + + + if (NodesTk::downloadNodes(*mgmtdNode, nodeType, nodesList, false)) + { + // sync the downloaded list with the node store + nodes->syncNodes(nodesList, &addedNodes, &removedNodes, localNode.get()); + + if (!addedNodes.empty()) + LOG(GENERAL, WARNING, "Nodes added.", ("addedNodes", addedNodes.size()), nodeType); + + if (!removedNodes.empty()) + LOG(GENERAL, WARNING, "Nodes removed.", ("removedNodes", removedNodes.size()), nodeType); + } + else + { + LOG(GENERAL, ERR, "Couldn't download server list from management daemon.", nodeType); + } + + std::list buddyGroupIDList; + std::list primaryTargetIDList; + std::list secondaryTargetIDList; + + // update the storage buddy groups + if (NodesTk::downloadMirrorBuddyGroups(*mgmtdNode, nodeType, &buddyGroupIDList, + &primaryTargetIDList, &secondaryTargetIDList, false) ) + { + buddyGroupMapper->syncGroupsFromLists(buddyGroupIDList, primaryTargetIDList, + secondaryTargetIDList, NumNodeID()); + } +} diff --git a/mon/source/components/worker/GetNodesWork.h b/mon/source/components/worker/GetNodesWork.h new file mode 100644 index 0000000..723aa4b --- /dev/null +++ b/mon/source/components/worker/GetNodesWork.h @@ -0,0 +1,32 @@ +#ifndef GETNODESWORK_H_ +#define GETNODESWORK_H_ + +#include +#include +#include +#include + +class GetNodesWork : public Work +{ + public: + GetNodesWork(std::shared_ptr mgmtdNode, NodeStoreServers *nodes, NodeType nodeType, + MirrorBuddyGroupMapper* buddyGroupMapper, std::shared_ptr localNode) + : mgmtdNode(std::move(mgmtdNode)), + nodes(nodes), + nodeType(nodeType), + buddyGroupMapper(buddyGroupMapper), + localNode(localNode) + {} + + virtual void process(char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen) override; + + private: + std::shared_ptr mgmtdNode; + NodeStoreServers* nodes; + NodeType nodeType; + MirrorBuddyGroupMapper* buddyGroupMapper; + std::shared_ptr localNode; +}; + +#endif /*GETNODESWORK_H_*/ diff --git a/mon/source/components/worker/RequestMetaDataWork.cpp b/mon/source/components/worker/RequestMetaDataWork.cpp new file mode 100644 index 0000000..04ba4c3 --- /dev/null +++ b/mon/source/components/worker/RequestMetaDataWork.cpp @@ -0,0 +1,69 @@ +#include "RequestMetaDataWork.h" + +#include +#include +#include +#include +#include + +void RequestMetaDataWork::process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen) +{ + + if (!node->getIsResponding()) + { + HeartbeatRequestMsg heartbeatRequestMsg; + if(MessagingTk::requestResponse(*node, heartbeatRequestMsg, + NETMSGTYPE_Heartbeat)) + { + LOG(GENERAL, DEBUG, "Node is responding again.", + ("NodeID", node->getNodeIDWithTypeStr())); + node->setIsResponding(true); + } + } + + Result result = {}; + result.data.isResponding = false; + + if (node->getIsResponding()) + { + // generate the RequestDataMsg with the lastStatsTime + RequestMetaDataMsg requestDataMsg(node->getLastStatRequestTime().count()); + auto respMsg = MessagingTk::requestResponse(*node, requestDataMsg, + NETMSGTYPE_RequestMetaDataResp); + + if (!respMsg) + { + LOG(GENERAL, DEBUG, "Node is not responding.", ("NodeID", node->getNodeIDWithTypeStr())); + node->setIsResponding(false); + } + else + { + // get response and process it + auto metaRspMsg = static_cast(respMsg.get()); + result.highResStatsList = std::move(metaRspMsg->getStatsList()); + + result.data.isResponding = true; + result.data.indirectWorkListSize = metaRspMsg->getIndirectWorkListSize(); + result.data.directWorkListSize = metaRspMsg->getDirectWorkListSize(); + result.data.sessionCount = metaRspMsg->getSessionCount(); + result.data.hostnameid = metaRspMsg->gethostnameid(); + + if (!result.highResStatsList.empty()) + { + auto lastStatsRequestTime = std::chrono::milliseconds( + result.highResStatsList.front().rawVals.statsTimeMS); + node->setLastStatRequestTime(lastStatsRequestTime); + } + + if (collectClientOpsByNode) + result.ipOpsUnorderedMap = ClientOpsRequestor::request(*node, false); + + if (collectClientOpsByUser) + result.userOpsUnorderedMap = ClientOpsRequestor::request(*node, true); + } + } + + result.node = std::move(node); + + statsCollector->insertMetaData(std::move(result)); +} diff --git a/mon/source/components/worker/RequestMetaDataWork.h b/mon/source/components/worker/RequestMetaDataWork.h new file mode 100644 index 0000000..31f2d95 --- /dev/null +++ b/mon/source/components/worker/RequestMetaDataWork.h @@ -0,0 +1,42 @@ +#ifndef REQUESTMETADATAWORK_H_ +#define REQUESTMETADATAWORK_H_ + +#include +#include +#include +#include + +class StatsCollector; + +class RequestMetaDataWork : public Work +{ + public: + struct Result + { + std::shared_ptr node; + MetaNodeDataContent data; + HighResStatsList highResStatsList; + ClientOpsRequestor::IdOpsUnorderedMap ipOpsUnorderedMap; + ClientOpsRequestor::IdOpsUnorderedMap userOpsUnorderedMap; + }; + + RequestMetaDataWork(std::shared_ptr node, + StatsCollector* statsCollector, + bool collectClientOpsByNode, bool collectClientOpsByUser) : + node(std::move(node)), + statsCollector(statsCollector), + collectClientOpsByNode(collectClientOpsByNode), + collectClientOpsByUser(collectClientOpsByUser) + {} + + virtual void process(char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen) override; + + private: + std::shared_ptr node; + StatsCollector* statsCollector; + bool collectClientOpsByNode; + bool collectClientOpsByUser; +}; + +#endif /*REQUESTMETADATAWORK_H_*/ diff --git a/mon/source/components/worker/RequestStorageDataWork.cpp b/mon/source/components/worker/RequestStorageDataWork.cpp new file mode 100644 index 0000000..256d505 --- /dev/null +++ b/mon/source/components/worker/RequestStorageDataWork.cpp @@ -0,0 +1,74 @@ +#include "RequestStorageDataWork.h" + +#include +#include +#include +#include +#include + +void RequestStorageDataWork::process(char* bufIn, unsigned bufInLen, + char* bufOut, unsigned bufOutLen) +{ + + if (!node->getIsResponding()) + { + HeartbeatRequestMsg heartbeatRequestMsg; + + if(MessagingTk::requestResponse(*node, heartbeatRequestMsg, + NETMSGTYPE_Heartbeat)) + { + LOG(GENERAL, DEBUG, "Node is responding again.", + ("NodeID", node->getNodeIDWithTypeStr())); + node->setIsResponding(true); + } + } + + Result result = {}; + result.data.isResponding = false; + + if (node->getIsResponding()) + { + // generate the RequestStorageDataMsg with the lastStatsTime + RequestStorageDataMsg requestDataMsg(node->getLastStatRequestTime().count()); + auto respMsg = MessagingTk::requestResponse(*node, requestDataMsg, + NETMSGTYPE_RequestStorageDataResp); + + if (!respMsg) + { + LOG(GENERAL, DEBUG, "Node is not responding.", ("NodeID", node->getNodeIDWithTypeStr())); + node->setIsResponding(false); + } + else + { + // get response and process it + auto storageRspMsg = static_cast(respMsg.get()); + result.highResStatsList = std::move(storageRspMsg->getStatsList()); + result.storageTargetList = std::move(storageRspMsg->getStorageTargets()); + + result.data.isResponding = true; + result.data.indirectWorkListSize = storageRspMsg->getIndirectWorkListSize(); + result.data.directWorkListSize = storageRspMsg->getDirectWorkListSize(); + result.data.diskSpaceTotal = storageRspMsg->getDiskSpaceTotalMiB(); + result.data.diskSpaceFree = storageRspMsg->getDiskSpaceFreeMiB(); + result.data.sessionCount = storageRspMsg->getSessionCount(); + result.data.hostnameid = storageRspMsg->gethostnameid(); + + if (!result.highResStatsList.empty()) + { + auto lastStatsRequestTime = std::chrono::milliseconds( + result.highResStatsList.front().rawVals.statsTimeMS); + node->setLastStatRequestTime(lastStatsRequestTime); + } + + if (collectClientOpsByNode) + result.ipOpsUnorderedMap = ClientOpsRequestor::request(*node, false); + + if (collectClientOpsByUser) + result.userOpsUnorderedMap = ClientOpsRequestor::request(*node, true); + } + } + + result.node = std::move(node); + + statsCollector->insertStorageData(std::move(result)); +} diff --git a/mon/source/components/worker/RequestStorageDataWork.h b/mon/source/components/worker/RequestStorageDataWork.h new file mode 100644 index 0000000..ce4a68e --- /dev/null +++ b/mon/source/components/worker/RequestStorageDataWork.h @@ -0,0 +1,44 @@ +#ifndef REQUESTSTORAGEDATAWORK_H_ +#define REQUESTSTORAGEDATAWORK_H_ + +#include +#include +#include +#include +#include + +class StatsCollector; + +class RequestStorageDataWork : public Work +{ + public: + struct Result + { + std::shared_ptr node; + StorageNodeDataContent data; + HighResStatsList highResStatsList; + StorageTargetInfoList storageTargetList; + ClientOpsRequestor::IdOpsUnorderedMap ipOpsUnorderedMap; + ClientOpsRequestor::IdOpsUnorderedMap userOpsUnorderedMap; + }; + + RequestStorageDataWork(std::shared_ptr node, + StatsCollector* statsCollector, bool collectClientOpsByNode, + bool collectClientOpsByUser) : + node(std::move(node)), + statsCollector(statsCollector), + collectClientOpsByNode(collectClientOpsByNode), + collectClientOpsByUser(collectClientOpsByUser) + {} + + void process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen); + + private: + std::shared_ptr node; + StatsCollector* statsCollector; + bool collectClientOpsByNode; + bool collectClientOpsByUser; +}; + +#endif /*REQUESTSTORAGEDATAWORK_H_*/ diff --git a/mon/source/exception/CurlException.h b/mon/source/exception/CurlException.h new file mode 100644 index 0000000..5c8a8d0 --- /dev/null +++ b/mon/source/exception/CurlException.h @@ -0,0 +1,8 @@ +#ifndef CURLEXCEPTION_H_ +#define CURLEXCEPTION_H_ + +#include + +DECLARE_NAMEDEXCEPTION(CurlException, "CurlException") + +#endif /*CURLEXCEPTION_H_*/ \ No newline at end of file diff --git a/mon/source/exception/DatabaseException.h b/mon/source/exception/DatabaseException.h new file mode 100644 index 0000000..0589431 --- /dev/null +++ b/mon/source/exception/DatabaseException.h @@ -0,0 +1,8 @@ +#ifndef DATABASEEXCEPTION_H_ +#define DATABASEEXCEPTION_H_ + +#include + +DECLARE_NAMEDEXCEPTION(DatabaseException, "DatabaseException") + +#endif /*DATABASEEXCEPTION_H_*/ diff --git a/mon/source/misc/Cassandra.cpp b/mon/source/misc/Cassandra.cpp new file mode 100644 index 0000000..5d7f04e --- /dev/null +++ b/mon/source/misc/Cassandra.cpp @@ -0,0 +1,348 @@ +#include "Cassandra.h" + +#include +#include +#include + +#include +#include + +static const std::string libVersion = "2.9"; + +template +std::function loadSymbol(void* libHandle, const char* name) +{ + dlerror(); + auto f = dlsym(libHandle, name); + const char* error = dlerror(); + if (error != NULL) + throw std::runtime_error("Couldn't load symbol: " + std::string(error) + + "\nThe cassandra plugin requires the datastax client library version " + libVersion + + "."); + return reinterpret_cast(f); +} + +Cassandra::Cassandra(Config config) : + cluster(nullptr, [this](CassCluster* c){cluster_free(c);}), + session(nullptr, [this](CassSession* s){session_free(s);}), + batch(nullptr, [this](CassBatch* b){batch_free(b);}), + config(std::move(config)), + libHandle(nullptr, dlclose), + numQueries(0) +{ + // Load datastax cassandra library + dlerror(); + libHandle.reset(dlopen("libcassandra.so", RTLD_NOW)); + const char* error = dlerror(); + if (libHandle == NULL || error != NULL) + { + throw std::runtime_error("Couldn't load cassandra client library (libcassandra.so): " + + std::string(error) + "\nThe cassandra plugin requires the datastax client library" + + " version " + libVersion + "."); + } + + // load used symbols + cluster_new = loadSymbol( + libHandle.get(), "cass_cluster_new"); + cluster_free = loadSymbol( + libHandle.get(), "cass_cluster_free"); + session_new = loadSymbol( + libHandle.get(), "cass_session_new"); + session_free = loadSymbol( + libHandle.get(), "cass_session_free"); + batch_new = loadSymbol( + libHandle.get(), "cass_batch_new"); + batch_free = loadSymbol( + libHandle.get(), "cass_batch_free"); + batch_add_statement = loadSymbol( + libHandle.get(), "cass_batch_add_statement"); + cluster_set_contact_points = loadSymbol( + libHandle.get(), "cass_cluster_set_contact_points"); + cluster_set_port = loadSymbol( + libHandle.get(), "cass_cluster_set_port"); + session_connect = loadSymbol( + libHandle.get(), "cass_session_connect"); + session_execute = loadSymbol( + libHandle.get(), "cass_session_execute"); + session_execute_batch = loadSymbol( + libHandle.get(), "cass_session_execute_batch"); + future_error_code = loadSymbol( + libHandle.get(), "cass_future_error_code"); + future_error_message = loadSymbol( + libHandle.get(), "cass_future_error_message"); + future_free = loadSymbol( + libHandle.get(), "cass_future_free"); + statement_new = loadSymbol( + libHandle.get(), "cass_statement_new"); + statement_free = loadSymbol( + libHandle.get(), "cass_statement_free"); + + cluster.reset(cluster_new()); + session.reset(session_new()); + batch.reset(batch_new(CASS_BATCH_TYPE_LOGGED)); + + cluster_set_contact_points(cluster.get(), this->config.host.c_str()); + cluster_set_port(cluster.get(), this->config.port); + + unsigned tries = 0; + while (true) + { + auto connectFuture = std::unique_ptr( + session_connect(session.get(), cluster.get()), future_free); + + CassError err = future_error_code(connectFuture.get()); + if (err == CASS_OK) + break; + + const char* message; + size_t length; + future_error_message(connectFuture.get(), &message, &length); + + LOG(DATABASE, ERR, "Couldn't connect to cassandra database: " + std::string(message)); + tries++; + if (tries >= connectionRetries) + throw DatabaseException("Connection to cassandra database failed."); + else + LOG(DATABASE, WARNING, "Retrying in 10 seconds."); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + // Create and switch to keyspace + query("CREATE KEYSPACE IF NOT EXISTS " + this->config.database + " WITH " + + "replication = {'class': 'SimpleStrategy', 'replication_factor' : 3};"); + query("USE " + this->config.database + ";"); + + // Create tables + query("CREATE TABLE IF NOT EXISTS meta (" + "time timestamp, nodeNumID int, nodeID varchar, isResponding boolean, " + "indirectWorkListSize int, directWorkListSize int, PRIMARY KEY(time, nodeNumID));"); + + query("CREATE TABLE IF NOT EXISTS highResMeta (" + "time timestamp, nodeNumID int, nodeID varchar, workRequests int, " + "queuedRequests int, netSendBytes int, netRecvBytes int, PRIMARY KEY(time, nodeNumID));"); + + query("CREATE TABLE IF NOT EXISTS storage (" + "time timestamp, nodeNumID int, nodeID varchar, isResponding boolean, " + "indirectWorkListSize int, directWorkListSize int, " + "diskSpaceTotal bigint, diskSpaceFree bigint, PRIMARY KEY(time, nodeNumID));"); + + query("CREATE TABLE IF NOT EXISTS highResStorage (" + "time timestamp, nodeNumID int, nodeID varchar, workRequests int, " + "queuedRequests int, diskWriteBytes int, diskReadBytes int, " + "netSendBytes int, netRecvBytes int, PRIMARY KEY(time, nodeNumID));"); + + query("CREATE TABLE IF NOT EXISTS storageTargetData (" + "time timestamp, nodeNumID int, nodeID varchar, storageTargetID int, " + "diskSpaceTotal bigint, diskSpaceFree bigint, inodesTotal int, inodesFree int, " + "PRIMARY KEY(time, nodeNumID));"); + + query("CREATE TABLE IF NOT EXISTS metaClientOpsByNode (" + "time timestamp, node varchar, ops map ," + "PRIMARY KEY(time, node));"); + query("CREATE TABLE IF NOT EXISTS storageClientOpsByNode (" + "time timestamp, node varchar, ops map ," + "PRIMARY KEY(time, node));"); + query("CREATE TABLE IF NOT EXISTS metaClientOpsByUser (" + "time timestamp, user varchar, ops map ," + "PRIMARY KEY(time, user));"); + query("CREATE TABLE IF NOT EXISTS storageClientOpsByUser (" + "time timestamp, user varchar, ops map ," + "PRIMARY KEY(time, user));"); +} + +void Cassandra::query(const std::string& query, bool waitForResult) +{ + CassStatement* statement = statement_new(query.c_str(), 0); + auto queryFuture = std::unique_ptr( + session_execute(session.get(), statement), future_free); + statement_free(statement); + + if (waitForResult) + { + CassError result = future_error_code(queryFuture.get()); + + if (result != CASS_OK) + { + const char* message; + size_t length; + future_error_message(queryFuture.get(), &message, &length); + throw DatabaseException("Query '" + query + "' failed: " + std::string(message)); + } + } +} + +void Cassandra::insertMetaNodeData(std::shared_ptr node, const MetaNodeDataContent& data) +{ + std::ostringstream statement; + statement << "INSERT INTO meta "; + statement << "(time, nodeNumID, nodeID, isResponding"; + if (data.isResponding) + statement << ", indirectWorkListSize, directWorkListSize) "; + else + statement << ") "; + statement << "VALUES ("; + statement << "TOTIMESTAMP(NOW()), " << node->getNumID() << ", '" << node->getAlias() << "', "; + statement << std::boolalpha << data.isResponding; + if (data.isResponding) + statement << ", " << data.indirectWorkListSize << ", " << data.directWorkListSize << ") "; + else + statement << ") "; + statement << "USING TTL " << config.TTLSecs << ";"; + + appendQuery(statement.str()); +} + +void Cassandra::insertStorageNodeData(std::shared_ptr node, + const StorageNodeDataContent& data) +{ + std::ostringstream statement; + statement << "INSERT INTO storage "; + statement << "(time, nodeNumID, nodeID, isResponding"; + if (data.isResponding) + statement << ", indirectWorkListSize, directWorkListSize, diskSpaceTotal, diskSpaceFree) "; + else + statement << ") "; + statement << "VALUES ("; + statement << "TOTIMESTAMP(NOW()), " << node->getNumID() << ", '" << node->getAlias() << "', "; + statement << std::boolalpha << data.isResponding; + if (data.isResponding) + statement << ", " << data.indirectWorkListSize << ", " << data.directWorkListSize << ", " + << data.diskSpaceTotal << ", " << data.diskSpaceFree << ") "; + else + statement << ") "; + statement << "USING TTL " << config.TTLSecs << ";"; + + appendQuery(statement.str()); + +} + +void Cassandra::insertHighResMetaNodeData(std::shared_ptr node, + const HighResolutionStats& data) +{ + std::ostringstream statement; + statement << "INSERT INTO highResMeta "; + statement << "(time, nodeNumID, nodeID, workRequests, "; + statement << "queuedRequests, netSendBytes, netRecvBytes) VALUES ("; + statement << data.rawVals.statsTimeMS << ", " << node->getNumID() << ", '" << node->getAlias() << "', "; + statement << data.incVals.workRequests << ", " << data.rawVals.queuedRequests << ", "; + statement << data.incVals.netSendBytes << ", " << data.incVals.netRecvBytes << ") "; + statement << "USING TTL " << config.TTLSecs << ";"; + + appendQuery(statement.str()); +} + +void Cassandra::insertHighResStorageNodeData(std::shared_ptr node, + const HighResolutionStats& data) +{ + std::ostringstream statement; + statement << "INSERT INTO highResStorage "; + statement << "(time, nodeNumID, nodeID, workRequests, "; + statement << "queuedRequests, diskWriteBytes, diskReadBytes, netSendBytes, netRecvBytes) VALUES ("; + statement << data.rawVals.statsTimeMS << ", " << node->getNumID() << ", '" << node->getAlias() << "', "; + statement << data.incVals.workRequests << ", " << data.rawVals.queuedRequests << ", "; + statement << data.incVals.diskWriteBytes << ", " << data.incVals.diskReadBytes << ", "; + statement << data.incVals.netSendBytes << ", " << data.incVals.netRecvBytes << ") "; + statement << "USING TTL " << config.TTLSecs << ";"; + + appendQuery(statement.str()); +} + +void Cassandra::insertStorageTargetsData(std::shared_ptr node, + const StorageTargetInfo& data) +{ + std::ostringstream statement; + statement << "INSERT INTO storageTargetData "; + statement << "(time, nodeNumID, nodeID, storageTargetID, "; + statement << "diskSpaceTotal, diskSpaceFree, inodesTotal, inodesFree) VALUES ("; + statement << "TOTIMESTAMP(NOW()), " << node->getNumID() << ", '" << node->getAlias() << "', "; + statement << data.getTargetID() << ", "; + statement << data.getDiskSpaceTotal() << ", " << data.getDiskSpaceFree() << ", "; + statement << data.getInodesTotal() << ", " << data.getInodesFree() << ") "; + statement << "USING TTL " << config.TTLSecs << ";"; + + appendQuery(statement.str()); +} + +void Cassandra::insertClientNodeData(const std::string& id, const NodeType nodeType, + const std::map& opMap, bool perUser) +{ + std::ostringstream statement; + statement << "INSERT INTO "; + if (perUser) + { + if (nodeType == NODETYPE_Meta) + statement << "metaClientOpsByUser"; + else if (nodeType == NODETYPE_Storage) + statement << "storageClientOpsByUser"; + else + throw DatabaseException("Invalid Nodetype given."); + + statement << " (time, user, ops) VALUES ("; + } + else + { + if (nodeType == NODETYPE_Meta) + statement << "metaClientOpsByNode"; + else if (nodeType == NODETYPE_Storage) + statement << "storageClientOpsByNode"; + else + throw DatabaseException("Invalid Nodetype given."); + + statement << " (time, node, ops) VALUES ("; + } + + statement << "TOTIMESTAMP(NOW()), '" << id << "', {"; + + bool first = true; + + for (auto iter = opMap.begin(); iter != opMap.end(); iter++) + { + if (iter->second == 0) + continue; + + statement << (first ? "" : ",") << "'" << iter->first << "':" << iter->second; + first = false; + } + + statement << "}) USING TTL " << config.TTLSecs << ";"; + + // if no fields are != 0, dont write anything + if (!first) + appendQuery(statement.str()); +} + +void Cassandra::appendQuery(const std::string& query) +{ + const std::lock_guard lock(queryMutex); + + CassStatement* statement = statement_new(query.c_str(), 0); + batch_add_statement(batch.get(), statement); + statement_free(statement); + + numQueries++; + + if (numQueries >= config.maxInsertsPerBatch) + { + writeUnlocked(); + } +} + +void Cassandra::write() +{ + const std::lock_guard lock(queryMutex); + + if(numQueries) + writeUnlocked(); +} + +void Cassandra::writeUnlocked() +{ + CassFuture* batchFuture = session_execute_batch(session.get(), batch.get()); + batch.reset(batch_new(CASS_BATCH_TYPE_LOGGED)); + future_free(batchFuture); + + LOG(DATABASE, DEBUG, "Sent queries to Cassandra.", numQueries); + numQueries = 0; +} + diff --git a/mon/source/misc/Cassandra.h b/mon/source/misc/Cassandra.h new file mode 100644 index 0000000..f9cee10 --- /dev/null +++ b/mon/source/misc/Cassandra.h @@ -0,0 +1,80 @@ +#ifndef CASSANDRA_H_ +#define CASSANDRA_H_ + +#include +#include +#include +#include +#include + +#include +#include + +class Cassandra : public TSDatabase +{ + public: + + struct Config + { + std::string host; + int port; + std::string database; + unsigned maxInsertsPerBatch; + unsigned TTLSecs; + }; + + Cassandra(Config config); + virtual ~Cassandra() {}; + + virtual void insertMetaNodeData( + std::shared_ptr node, const MetaNodeDataContent& data) override; + virtual void insertStorageNodeData( + std::shared_ptr node, const StorageNodeDataContent& data) override; + virtual void insertHighResMetaNodeData( + std::shared_ptr node, const HighResolutionStats& data) override; + virtual void insertHighResStorageNodeData( + std::shared_ptr node, const HighResolutionStats& data) override; + virtual void insertStorageTargetsData( + std::shared_ptr node, const StorageTargetInfo& data) override; + virtual void insertClientNodeData( + const std::string& id, const NodeType nodeType, + const std::map& opMap, bool perUser) override; + virtual void write() override; + + private: + std::function cluster_new; + std::function cluster_free; + std::function session_new; + std::function session_free; + std::function batch_new; + std::function batch_free; + std::function batch_add_statement; + std::function cluster_set_contact_points; + std::function cluster_set_port; + std::function session_connect; + std::function session_execute; + std::function session_execute_batch; + std::function future_error_code; + std::function future_error_message; + std::function future_free; + std::function statement_new; + std::function statement_free; + + std::unique_ptr cluster; + std::unique_ptr session; + std::unique_ptr batch; + + const Config config; + std::unique_ptr libHandle; + + std::string queryBuffer; + unsigned numQueries; + + mutable Mutex queryMutex; + + void appendQuery(const std::string& query); + void query(const std::string& query, bool waitForResult = true); + void writeUnlocked(); +}; + +#endif diff --git a/mon/source/misc/CurlWrapper.cpp b/mon/source/misc/CurlWrapper.cpp new file mode 100644 index 0000000..0c19b44 --- /dev/null +++ b/mon/source/misc/CurlWrapper.cpp @@ -0,0 +1,153 @@ +#include "CurlWrapper.h" + +#include + +CurlWrapper::CurlWrapper(std::chrono::milliseconds timeout, bool checkSSLCertificates) : + curlHandle(curl_easy_init(), &curl_easy_cleanup) +{ + if (curlHandle.get() == NULL) + throw CurlException("Curl init failed."); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_ERRORBUFFER, &errorBuffer) != CURLE_OK) + throw CurlException("Setting Curl error buffer failed."); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_NOSIGNAL, 1L) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_TIMEOUT_MS, + std::chrono::milliseconds(timeout).count()) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_WRITEFUNCTION, writeCallback) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_WRITEDATA, static_cast(this)) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_CONNECTTIMEOUT_MS, + timeout.count()) != CURLE_OK) + throw CurlException(errorBuffer); + + if (!checkSSLCertificates) + { + if (curl_easy_setopt(curlHandle.get(), CURLOPT_SSL_VERIFYPEER, 0) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_SSL_VERIFYHOST, 0) != CURLE_OK) + throw CurlException(errorBuffer); + } +} + +void CurlWrapper::enableHttpAuth(const std::string& user, const std::string& password) +{ + if (curl_easy_setopt(curlHandle.get(), CURLOPT_HTTPAUTH, CURLAUTH_ANY)) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_USERNAME, user.c_str())) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_PASSWORD, password.c_str())) + throw CurlException(errorBuffer); +} + + +unsigned short CurlWrapper::sendGetRequest(const std::string& url, const ParameterMap& parameters) +{ + std::string parameterStr = makeParameterStr(parameters); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_URL, (url + parameterStr).c_str()) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_HTTPGET, 1L) != CURLE_OK) + throw CurlException(errorBuffer); + + // replace with curl_multi_perform? + if (curl_easy_perform(curlHandle.get()) != CURLE_OK) + throw CurlException(errorBuffer); + + long responseCode; + if (curl_easy_getinfo(curlHandle.get(), CURLINFO_RESPONSE_CODE, &responseCode) != CURLE_OK) + throw CurlException(errorBuffer); + + return responseCode; +} + +unsigned short CurlWrapper::sendPostRequest(const std::string& url, const char* data, + const ParameterMap& parameters, const std::vector& headers) +{ + std::string parameterStr = makeParameterStr(parameters); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_URL, (url + parameterStr).c_str()) != CURLE_OK) + throw CurlException(errorBuffer); + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_POSTFIELDS, data) != CURLE_OK) + throw CurlException(errorBuffer); + + struct curl_slist* headerList = nullptr; + for (const auto& header : headers) { + headerList = curl_slist_append(headerList, header.c_str()); + } + + if (curl_easy_setopt(curlHandle.get(), CURLOPT_HTTPHEADER, headerList) != CURLE_OK) + throw CurlException(errorBuffer); + + // replace with curl_multi_perform? + if (curl_easy_perform(curlHandle.get()) != CURLE_OK) + throw CurlException(errorBuffer); + + long responseCode; + if (curl_easy_getinfo(curlHandle.get(), CURLINFO_RESPONSE_CODE, &responseCode) != CURLE_OK) + throw CurlException(errorBuffer); + + return responseCode; +} + +std::string CurlWrapper::makeParameterStr(const ParameterMap& parameters) const +{ + if (!parameters.empty()) + { + std::string parameterStr = "?"; + + for (auto iter = parameters.begin(); iter != parameters.end(); iter++) + { + { + auto escaped = std::unique_ptr ( + curl_easy_escape(curlHandle.get(), (iter->first).c_str(),0), + &curl_free); + + if (!escaped) + throw CurlException(errorBuffer); + + parameterStr += escaped.get(); + } + + { + auto escaped = std::unique_ptr ( + curl_easy_escape(curlHandle.get(), (iter->second).c_str(),0), + &curl_free); + + if (!escaped) + throw CurlException(errorBuffer); + + parameterStr += "="; + parameterStr += escaped.get(); + parameterStr += "&"; + } + } + + parameterStr.resize(parameterStr.size() - 1); + + return parameterStr; + } + + return {}; +} + +size_t CurlWrapper::writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + auto instance = static_cast(userdata); + instance->setResponse(std::string(ptr, size*nmemb)); + + // Always signal success + return size*nmemb; +} diff --git a/mon/source/misc/CurlWrapper.h b/mon/source/misc/CurlWrapper.h new file mode 100644 index 0000000..597f2a0 --- /dev/null +++ b/mon/source/misc/CurlWrapper.h @@ -0,0 +1,57 @@ +#ifndef CURL_WRAPPER_H_ +#define CURL_WRAPPER_H_ + +#include + +#include + +#include +#include +#include + + +class CurlWrapper +{ + public: + CurlWrapper(std::chrono::milliseconds timeout, bool checkSSLCertificates); + + CurlWrapper(const CurlWrapper&) = delete; + CurlWrapper& operator=(const CurlWrapper&) = delete; + CurlWrapper(CurlWrapper&&) = delete; + CurlWrapper& operator=(CurlWrapper&&) = delete; + + ~CurlWrapper() = default; + + void enableHttpAuth(const std::string& user, const std::string& password); + + typedef std::unordered_map ParameterMap; + + unsigned short sendGetRequest(const std::string& url, + const ParameterMap& parameters); + unsigned short sendPostRequest(const std::string& url, const char* data, + const ParameterMap& parameters, const std::vector& headers); + + static size_t writeCallback(char *ptr, size_t size, size_t nmemb, void *userdata); + + protected: + std::unique_ptr curlHandle; + std::string response; + + char errorBuffer[CURL_ERROR_SIZE]; + + std::string makeParameterStr(const ParameterMap& parameters) const; + + void setResponse(const std::string& response) + { + this->response = response; + } + + public: + const std::string& getResponse() const + { + return response; + } + +}; + +#endif diff --git a/mon/source/misc/InfluxDB.cpp b/mon/source/misc/InfluxDB.cpp new file mode 100644 index 0000000..a747c8f --- /dev/null +++ b/mon/source/misc/InfluxDB.cpp @@ -0,0 +1,344 @@ +#include "InfluxDB.h" + +#include +#include +#include +#include + +#include +#include +#include + +static const std::string retentionPolicyName = "auto"; + +InfluxDB::InfluxDB(Config cfg) : + config(std::move(cfg)) +{ + curlWrapper = boost::make_unique(config.httpTimeout, config.curlCheckSSLCertificates); + if (config.dbVersion == INFLUXDB) + { + if (!config.username.empty()) + curlWrapper->enableHttpAuth(config.username, config.password); + + setupDatabase(); + } +} + +void InfluxDB::setupDatabase() const +{ + // Wait for InfluxDB service being available + unsigned tries = 0; + while(!sendPing()) + { + tries++; + LOG(DATABASE, ERR, "Coudn't reach InfluxDB service."); + if (tries >= connectionRetries) + throw DatabaseException("Connection to InfluxDB failed."); + else + LOG(DATABASE, WARNING, "Retrying in 10 seconds."); + + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + // these are called every time the service starts but is being ignored by influxdb if + // the db and rp already exist + sendQuery("create database " + config.database); + if (config.setRetentionPolicy) + { + sendQuery("create retention policy " + retentionPolicyName + " on " + config.database + + " duration " + config.retentionDuration + + " replication 1 default"); + } +} + +void InfluxDB::insertMetaNodeData(std::shared_ptr node, const MetaNodeDataContent& data) +{ + std::ostringstream point; + point << "meta"; + point << ",nodeID=" << escapeStringForWrite(node->getAlias()); + point << ",nodeNumID=" << node->getNumID(); + + if(data.isResponding) + { + point << " isResponding=" << std::boolalpha << true; + point << ",indirectWorkListSize=" << data.indirectWorkListSize; + point << ",directWorkListSize=" << data.directWorkListSize; + point << ",hostnameid=\"" << data.hostnameid << "\""; + + } + else + { + point << " isResponding=" << std::boolalpha << false; + } + + appendPoint(point.str()); +} + +void InfluxDB::insertStorageNodeData(std::shared_ptr node, + const StorageNodeDataContent& data) +{ + std::ostringstream point; + point << "storage"; + point << ",nodeID=" << escapeStringForWrite(node->getAlias()); + point << ",nodeNumID=" << node->getNumID(); + + if(data.isResponding) + { + point << " isResponding=" << std::boolalpha << true; + point << ",indirectWorkListSize=" << data.indirectWorkListSize; + point << ",directWorkListSize=" << data.directWorkListSize; + point << ",diskSpaceTotal=" << data.diskSpaceTotal; + point << ",diskSpaceFree=" << data.diskSpaceFree; + point << ",hostnameid=\"" << data.hostnameid << "\""; + + } + else + { + point << " isResponding=" << std::boolalpha << false; + } + + appendPoint(point.str()); +} + +void InfluxDB::insertHighResMetaNodeData(std::shared_ptr node, + const HighResolutionStats& data) +{ + std::ostringstream point; + point << "highResMeta"; + point << ",nodeID=" << escapeStringForWrite(node->getAlias()); + point << ",nodeNumID=" << node->getNumID(); + + point << " workRequests=" << data.incVals.workRequests; + point << ",queuedRequests=" << data.rawVals.queuedRequests; + point << ",netSendBytes=" << data.incVals.netSendBytes; + point << ",netRecvBytes=" << data.incVals.netRecvBytes; + + // timestamp in ns + point << " " << std::chrono::nanoseconds( + std::chrono::milliseconds(data.rawVals.statsTimeMS)).count(); + + appendPoint(point.str()); +} + +void InfluxDB::insertHighResStorageNodeData(std::shared_ptr node, + const HighResolutionStats& data) +{ + std::ostringstream point; + point << "highResStorage"; + point << ",nodeID=" << escapeStringForWrite(node->getAlias()); + point << ",nodeNumID=" << node->getNumID(); + + point << " workRequests=" << data.incVals.workRequests; + point << ",queuedRequests=" << data.rawVals.queuedRequests; + point << ",diskWriteBytes=" << data.incVals.diskWriteBytes; + point << ",diskReadBytes=" << data.incVals.diskReadBytes; + point << ",netSendBytes=" << data.incVals.netSendBytes; + point << ",netRecvBytes=" << data.incVals.netRecvBytes; + + // timestamp in ns + point << " " << std::chrono::nanoseconds( + std::chrono::milliseconds(data.rawVals.statsTimeMS)).count(); + + appendPoint(point.str()); +} + +void InfluxDB::insertStorageTargetsData(std::shared_ptr node, + const StorageTargetInfo& data) +{ + std::ostringstream point; + point << "storageTargets"; + point << ",nodeID=" << escapeStringForWrite(node->getAlias()); + point << ",nodeNumID=" << node->getNumID(); + point << ",storageTargetID=" << data.getTargetID(); + + point << " diskSpaceTotal=" << data.getDiskSpaceTotal(); + point << ",diskSpaceFree=" << data.getDiskSpaceFree(); + point << ",inodesTotal=" << data.getInodesTotal(); + point << ",inodesFree=" << data.getInodesFree(); + + std::string t; + if (data.getState() == TargetConsistencyState::TargetConsistencyState_GOOD) + t = "GOOD"; + else if (data.getState() == TargetConsistencyState::TargetConsistencyState_NEEDS_RESYNC) + t = "NEEDS_RESYNC"; + else + t = "BAD"; + + point << ",targetConsistencyState=\"" << t << "\""; + + appendPoint(point.str()); +} + +void InfluxDB::insertClientNodeData(const std::string& id, const NodeType nodeType, + const std::map& opMap, bool perUser) +{ + std::ostringstream point; + if (perUser) + { + if (nodeType == NODETYPE_Meta) + point << "metaClientOpsByUser"; + else if (nodeType == NODETYPE_Storage) + point << "storageClientOpsByUser"; + else + throw DatabaseException("Invalid Nodetype given."); + } + else + { + if (nodeType == NODETYPE_Meta) + point << "metaClientOpsByNode"; + else if (nodeType == NODETYPE_Storage) + point << "storageClientOpsByNode"; + else + throw DatabaseException("Invalid Nodetype given."); + } + + point << (perUser ? ",user=" : ",node=") << id; + + bool first = true; + + for (auto iter = opMap.begin(); iter != opMap.end(); iter++) + { + if (iter->second == 0) + continue; + + point << (first ? " " : ",") << iter->first << "=" << iter->second; + first = false; + } + + // if no fields are != 0, dont write anything + if (!first) + appendPoint(point.str()); +} + + +void InfluxDB::appendPoint(const std::string& point) +{ + const std::lock_guard mutexLock(pointsMutex); + + points += point + "\n"; + numPoints++; + + // test also for size? make it an option? + if (numPoints >= config.maxPointsPerRequest) + { + writePointsUnlocked(); + } +} +void InfluxDB::write() +{ + const std::lock_guard mutexLock(pointsMutex); + writePointsUnlocked(); +} + +void InfluxDB::writePointsUnlocked() +{ + sendWrite(points); + points.clear(); + LOG(DATABASE, DEBUG, "Sent data to InfluxDB.", numPoints); + numPoints = 0; +} + +void InfluxDB::sendWrite(const std::string& data) const +{ + unsigned short responseCode = 0; + CurlWrapper::ParameterMap params; + std::string url; + std::vector headers; + if (config.dbVersion == INFLUXDB) + { + params["db"] = config.database; + url = config.host + ":" + StringTk::intToStr(config.port) + "/write"; + } + else + { + params["org"] = config.organization; + params["bucket"] = config.bucket; + url = config.host + ":" + StringTk::intToStr(config.port) + "/api/v2/write"; + headers.push_back("Authorization: Token " + config.token); + } + + const std::lock_guard mutexLock(curlMutex); + + try + { + responseCode = curlWrapper->sendPostRequest(url, data.c_str(), params, headers); + } + catch (const CurlException& e) + { + LOG(DATABASE, ERR, "Writing to InfluxDB failed due to Curl error.", ("Error", e.what())); + return; + } + + if (responseCode < 200 || responseCode >= 300) + { + LOG(DATABASE, ERR, "Writing to InfluxDB failed.", responseCode, + ("responseMessage", curlWrapper->getResponse())); + } +} + +void InfluxDB::sendQuery(const std::string& data) const +{ + unsigned short responseCode = 0; + CurlWrapper::ParameterMap params; + params["db"] = config.database; + params["q"] = data; + + const std::lock_guard mutexLock(curlMutex); + + try + { + responseCode = curlWrapper->sendPostRequest(config.host + ":" + + StringTk::intToStr(config.port) + + "/query", "", params, {}); + } + catch (const CurlException& e) + { + LOG(DATABASE, ERR, "Querying InfluxDB failed due to Curl error.", ("Error", e.what())); + return; + } + + if (responseCode < 200 || responseCode >= 300) + { + LOG(DATABASE, ERR, "Querying InfluxDB failed.", responseCode, + ("responseMessage", curlWrapper->getResponse())); + } +} + +bool InfluxDB::sendPing() const +{ + unsigned short responseCode = 0; + + const std::lock_guard mutexLock(curlMutex); + + try + { + responseCode = curlWrapper->sendGetRequest(config.host + ":" + + StringTk::intToStr(config.port) + "/ping", CurlWrapper::ParameterMap()); + } + catch (const CurlException& e) + { + LOG(DATABASE, ERR, "Pinging InfluxDB failed due to Curl error.", ("Error", e.what())); + return false; + } + + if (responseCode < 200 || responseCode >= 300) + { + LOG(DATABASE, ERR, "Pinging InfluxDB failed.", responseCode, + ("responseMessage", curlWrapper->getResponse())); + return false; + } + + return true; +} + +/* + * According to InfluxDB documentation, spaces, "=" and "," need to be escaped for write. + */ +std::string InfluxDB::escapeStringForWrite(const std::string& str) +{ + std::string result = str; + boost::replace_all(result, " ", "\\ "); + boost::replace_all(result, "=", "\\="); + boost::replace_all(result, ",", "\\,"); + return result; +} diff --git a/mon/source/misc/InfluxDB.h b/mon/source/misc/InfluxDB.h new file mode 100644 index 0000000..ec69a36 --- /dev/null +++ b/mon/source/misc/InfluxDB.h @@ -0,0 +1,84 @@ +#ifndef INFLUXDB_H_ +#define INFLUXDB_H_ + +#include +#include +#include +#include +#include +#include +#include + +enum InfluxDBVersion +{ + INFLUXDB, + INFLUXDB2, +}; + +class App; + +class InfluxDB : public TSDatabase +{ + public: + + struct Config + { + std::string host; + int port; + std::string database; + std::chrono::milliseconds httpTimeout; + unsigned maxPointsPerRequest; + bool setRetentionPolicy; + std::string retentionDuration; + bool curlCheckSSLCertificates; + std::string username; + std::string password; + std::string bucket; + std::string organization; + std::string token; + InfluxDBVersion dbVersion; + + }; + + InfluxDB(Config cfg); + virtual ~InfluxDB() {}; + + virtual void insertMetaNodeData( + std::shared_ptr node, const MetaNodeDataContent& data) override; + virtual void insertStorageNodeData( + std::shared_ptr node, const StorageNodeDataContent& data) override; + virtual void insertHighResMetaNodeData( + std::shared_ptr node, const HighResolutionStats& data) override; + virtual void insertHighResStorageNodeData( + std::shared_ptr node, const HighResolutionStats& data) override; + virtual void insertStorageTargetsData( + std::shared_ptr node, const StorageTargetInfo& data) override; + virtual void insertClientNodeData( + const std::string& id, const NodeType nodeType, + const std::map& opMap, bool perUser) override; + virtual void write() override; + + static std::string escapeStringForWrite(const std::string& str); + + private: + const Config config; + + std::unique_ptr curlWrapper; + + std::string points; + unsigned numPoints = 0; + + mutable Mutex pointsMutex; + mutable Mutex curlMutex; + + void setupDatabase() const; + void appendPoint(const std::string& point); + void writePointsUnlocked(); + void sendWrite(const std::string& data) const; + void sendQuery(const std::string& data) const; + bool sendPing() const; + + +}; + +#endif diff --git a/mon/source/misc/TSDatabase.h b/mon/source/misc/TSDatabase.h new file mode 100644 index 0000000..dc22fe3 --- /dev/null +++ b/mon/source/misc/TSDatabase.h @@ -0,0 +1,34 @@ +#ifndef TS_DATABASE_H_ +#define TS_DATABASE_H_ + +#include +#include +#include +#include + +class TSDatabase +{ + public: + static const unsigned connectionRetries = 3; + + TSDatabase() {}; + virtual ~TSDatabase() {}; + + virtual void insertMetaNodeData( + std::shared_ptr node, const MetaNodeDataContent& data) = 0; + virtual void insertStorageNodeData( + std::shared_ptr node, const StorageNodeDataContent& data) = 0; + virtual void insertHighResMetaNodeData( + std::shared_ptr node, const HighResolutionStats& data) = 0; + virtual void insertHighResStorageNodeData( + std::shared_ptr node, const HighResolutionStats& data) = 0; + virtual void insertStorageTargetsData( + std::shared_ptr node, const StorageTargetInfo& data) = 0; + virtual void insertClientNodeData( + const std::string& id, const NodeType nodeType, + const std::map& opMap, bool perUser) = 0; + + virtual void write() = 0; +}; + +#endif diff --git a/mon/source/net/message/NetMessageFactory.cpp b/mon/source/net/message/NetMessageFactory.cpp new file mode 100644 index 0000000..8e51881 --- /dev/null +++ b/mon/source/net/message/NetMessageFactory.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "NetMessageFactory.h" + +/** + * @return NetMessage that must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr NetMessageFactory::createFromMsgType(unsigned short msgType) const +{ + NetMessage* msg; + + switch(msgType) + { + // The following lines shoudle be grouped by "type of the message" and ordered alphabetically + // inside the groups. There should always be one message per line to keep a clear layout + // (although this might lead to lines that are longer than usual) + + case NETMSGTYPE_FindOwnerResp: { msg = new FindOwnerRespMsg(); } break; + case NETMSGTYPE_GenericResponse: { msg = new GenericResponseMsg(); } break; + case NETMSGTYPE_GetClientStatsResp: { msg = new GetClientStatsRespMsg(); } break; + case NETMSGTYPE_GetMirrorBuddyGroupsResp: { msg = new GetMirrorBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetNodesResp: { msg = new GetNodesRespMsg(); } break; + case NETMSGTYPE_GetTargetMappingsResp: { msg = new GetTargetMappingsRespMsg(); } break; + case NETMSGTYPE_Heartbeat: { msg = new HeartbeatMsgEx(); } break; + case NETMSGTYPE_RequestMetaDataResp: { msg = new RequestMetaDataRespMsg(); } break; + case NETMSGTYPE_RequestStorageDataResp: { msg = new RequestStorageDataRespMsg(); } break; + + default: + { + msg = new SimpleMsg(NETMSGTYPE_Invalid); + } break; + } + + return std::unique_ptr(msg); +} + diff --git a/mon/source/net/message/NetMessageFactory.h b/mon/source/net/message/NetMessageFactory.h new file mode 100644 index 0000000..a7ef706 --- /dev/null +++ b/mon/source/net/message/NetMessageFactory.h @@ -0,0 +1,13 @@ +#ifndef NETMESSAGEFACTORY_H_ +#define NETMESSAGEFACTORY_H_ + +#include +#include + +class NetMessageFactory : public AbstractNetMessageFactory +{ + protected: + virtual std::unique_ptr createFromMsgType(unsigned short msgType) const override; +} ; + +#endif /*NETMESSAGEFACTORY_H_*/ diff --git a/mon/source/net/message/nodes/HeartbeatMsgEx.h b/mon/source/net/message/nodes/HeartbeatMsgEx.h new file mode 100644 index 0000000..956d0e2 --- /dev/null +++ b/mon/source/net/message/nodes/HeartbeatMsgEx.h @@ -0,0 +1,11 @@ +#ifndef HEARTBEATMSGEX_H_ +#define HEARTBEATMSGEX_H_ + +#include + +// This is only a dummy so the mgmt download doesn't fail + +class HeartbeatMsgEx : public HeartbeatMsg +{}; + +#endif /*HEARTBEATMSGEX_H_*/ diff --git a/mon/source/nodes/MetaNodeEx.cpp b/mon/source/nodes/MetaNodeEx.cpp new file mode 100644 index 0000000..a64e68c --- /dev/null +++ b/mon/source/nodes/MetaNodeEx.cpp @@ -0,0 +1,17 @@ +#include "MetaNodeEx.h" + +MetaNodeEx::MetaNodeEx(std::shared_ptr receivedNode) : + Node(NODETYPE_Meta, receivedNode->getAlias(), receivedNode->getNumID(), + receivedNode->getPortUDP(), receivedNode->getPortTCP(), + receivedNode->getConnPool()->getNicList()), + isResponding(true) +{} + +MetaNodeEx::MetaNodeEx(std::shared_ptr receivedNode, std::shared_ptr oldNode) : + Node(NODETYPE_Meta, receivedNode->getAlias(), receivedNode->getNumID(), + receivedNode->getPortUDP(), receivedNode->getPortTCP(), + receivedNode->getConnPool()->getNicList()) +{ + setLastStatRequestTime(oldNode->getLastStatRequestTime()); + setIsResponding(oldNode->getIsResponding()); +} diff --git a/mon/source/nodes/MetaNodeEx.h b/mon/source/nodes/MetaNodeEx.h new file mode 100644 index 0000000..8ee3e9d --- /dev/null +++ b/mon/source/nodes/MetaNodeEx.h @@ -0,0 +1,55 @@ +#ifndef METANODEEX_H_ +#define METANODEEX_H_ + +#include +#include +#include + +struct MetaNodeDataContent +{ + bool isResponding; + unsigned indirectWorkListSize; + unsigned directWorkListSize; + unsigned sessionCount; + std::string hostnameid; +}; + +class MetaNodeEx: public Node +{ + public: + MetaNodeEx(std::shared_ptr receivedNode); + MetaNodeEx(std::shared_ptr receivedNode, std::shared_ptr oldNode); + + private: + mutable RWLock lock; + bool isResponding; + std::chrono::milliseconds lastStatRequestTime{0}; + + public: + std::chrono::milliseconds getLastStatRequestTime() const + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + return lastStatRequestTime; + } + + void setLastStatRequestTime(const std::chrono::milliseconds& time) + { + RWLockGuard safeLock(lock, SafeRWLock_WRITE); + lastStatRequestTime = time; + } + + bool getIsResponding() const + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + return isResponding; + } + + void setIsResponding(bool isResponding) + { + RWLockGuard safeLock(lock, SafeRWLock_WRITE); + this->isResponding = isResponding; + } + +}; + +#endif /*METANODEEX_H_*/ diff --git a/mon/source/nodes/MgmtNodeEx.cpp b/mon/source/nodes/MgmtNodeEx.cpp new file mode 100644 index 0000000..d713118 --- /dev/null +++ b/mon/source/nodes/MgmtNodeEx.cpp @@ -0,0 +1,6 @@ +#include "MgmtNodeEx.h" + +MgmtNodeEx::MgmtNodeEx(std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP, + unsigned short portTCP, NicAddressList& nicList) : + Node(NODETYPE_Mgmt, nodeID, nodeNumID, portUDP, portTCP, nicList) +{} diff --git a/mon/source/nodes/MgmtNodeEx.h b/mon/source/nodes/MgmtNodeEx.h new file mode 100644 index 0000000..3fa7410 --- /dev/null +++ b/mon/source/nodes/MgmtNodeEx.h @@ -0,0 +1,37 @@ +#ifndef MGMTNODEEX_H_ +#define MGMTNODEEX_H_ + +#include +#include + +#include + +struct MgmtdNodeDataContent +{ + bool isResponding; +}; + +class MgmtNodeEx : public Node +{ + public: + MgmtNodeEx(std::string nodeID, NumNodeID nodeNumID, unsigned short portUDP, + unsigned short portTCP, NicAddressList& nicList); + + private: + MgmtdNodeDataContent data; + + public: + MgmtdNodeDataContent getContent() + { + const std::lock_guard lock(mutex); + return this->data; + } + + void setContent(MgmtdNodeDataContent content) + { + const std::lock_guard lock(mutex); + this->data = content; + } +}; + +#endif /*MGMTNODEEX_H_*/ diff --git a/mon/source/nodes/NodeStoreMetaEx.cpp b/mon/source/nodes/NodeStoreMetaEx.cpp new file mode 100644 index 0000000..16b46e5 --- /dev/null +++ b/mon/source/nodes/NodeStoreMetaEx.cpp @@ -0,0 +1,38 @@ +#include "NodeStoreMetaEx.h" + +#include +#include + +NodeStoreMetaEx::NodeStoreMetaEx() : + NodeStoreServers(NODETYPE_Meta, false) +{} + +NodeStoreResult NodeStoreMetaEx::addOrUpdateNodeEx(std::shared_ptr receivedNode, + NumNodeID* outNodeNumID) +{ + // sanity check: don't allow nodeNumID==0 (only mgmtd allows this) + if (!receivedNode->getNumID()) + return NodeStoreResult::Error; + + std::shared_ptr newNode; + auto storedNode = + std::static_pointer_cast(referenceNode(receivedNode->getNumID())); + if (!storedNode) + { + // new node, create StorageNodeEx object with the parameters of the received node info + newNode = std::make_shared(receivedNode); + LOG(GENERAL, DEBUG, "Received new meta node.", + ("nodeNumID", receivedNode->getNumID().val())); + } + else + { + // already stored node, create StorageNodeEx object with the parameters of the + // received node info and keep the internal data + newNode = std::make_shared(receivedNode, storedNode); + LOG(GENERAL, DEBUG, "Received update for meta node.", + ("nodeNumID", receivedNode->getNumID().val())); + } + + const std::lock_guard lock(mutex); + return addOrUpdateNodeUnlocked(std::move(newNode), nullptr); +} diff --git a/mon/source/nodes/NodeStoreMetaEx.h b/mon/source/nodes/NodeStoreMetaEx.h new file mode 100644 index 0000000..f2647d6 --- /dev/null +++ b/mon/source/nodes/NodeStoreMetaEx.h @@ -0,0 +1,16 @@ +#ifndef NODESTOREMETAEX_H_ +#define NODESTOREMETAEX_H_ + +#include + +class NodeStoreMetaEx : public NodeStoreServers +{ + public: + NodeStoreMetaEx(); + + virtual NodeStoreResult addOrUpdateNodeEx(std::shared_ptr receivedNode, + NumNodeID* outNodeNumID) override; + +}; + +#endif /*NODESTOREMETAEX_H_*/ diff --git a/mon/source/nodes/NodeStoreMgmtEx.cpp b/mon/source/nodes/NodeStoreMgmtEx.cpp new file mode 100644 index 0000000..1848de7 --- /dev/null +++ b/mon/source/nodes/NodeStoreMgmtEx.cpp @@ -0,0 +1,29 @@ +#include "NodeStoreMgmtEx.h" + +NodeStoreMgmtEx::NodeStoreMgmtEx() : + NodeStoreServers(NODETYPE_Mgmt, false) +{} + +NodeStoreResult NodeStoreMgmtEx::addOrUpdateNodeEx(std::shared_ptr node, NumNodeID* outNodeNumID) +{ + std::string nodeID(node->getAlias()); + NumNodeID nodeNumID = node->getNumID(); + + // sanity check: don't allow nodeNumID==0 (only mgmtd allows this) + if (!node->getNumID()) + return NodeStoreResult::Error; + + const std::lock_guard lock(mutex); + + // check if this is a new node + auto iter = activeNodes.find(nodeNumID); + if (iter == activeNodes.end() ) + { + NicAddressList nicList = node->getNicList(); + + node = boost::make_unique(nodeID, nodeNumID, node->getPortUDP(), + node->getPortTCP(), nicList); + } + + return addOrUpdateNodeUnlocked(std::move(node), outNodeNumID); +} diff --git a/mon/source/nodes/NodeStoreMgmtEx.h b/mon/source/nodes/NodeStoreMgmtEx.h new file mode 100644 index 0000000..0017357 --- /dev/null +++ b/mon/source/nodes/NodeStoreMgmtEx.h @@ -0,0 +1,15 @@ +#ifndef NODESTOREMGMTDEX_H_ +#define NODESTOREMGMTDEX_H_ + +#include +#include + +class NodeStoreMgmtEx : public NodeStoreServers +{ + public: + NodeStoreMgmtEx(); + + virtual NodeStoreResult addOrUpdateNodeEx(std::shared_ptr node, NumNodeID* outNodeNumID) override; +}; + +#endif /*NODESTOREMGMTDEX_H_*/ diff --git a/mon/source/nodes/NodeStoreStorageEx.cpp b/mon/source/nodes/NodeStoreStorageEx.cpp new file mode 100644 index 0000000..ff0d439 --- /dev/null +++ b/mon/source/nodes/NodeStoreStorageEx.cpp @@ -0,0 +1,38 @@ +#include "NodeStoreStorageEx.h" + +#include +#include + +NodeStoreStorageEx::NodeStoreStorageEx() : + NodeStoreServers(NODETYPE_Storage, false) +{} + +NodeStoreResult NodeStoreStorageEx::addOrUpdateNodeEx(std::shared_ptr receivedNode, + NumNodeID* outNodeNumID) +{ + // sanity check: don't allow nodeNumID==0 (only mgmtd allows this) + if (!receivedNode->getNumID()) + return NodeStoreResult::Error; + + std::shared_ptr newNode; + auto storedNode = + std::static_pointer_cast(referenceNode(receivedNode->getNumID())); + if (!storedNode) + { + // new node, create StorageNodeEx object with the parameters of the received node info + newNode = std::make_shared(receivedNode); + LOG(GENERAL, DEBUG, "Received new storage node.", + ("nodeNumID", receivedNode->getNumID().val())); + } + else + { + // already stored node, create StorageNodeEx object with the parameters of the + // received node info and keep the internal data + newNode = std::make_shared(receivedNode, storedNode); + LOG(GENERAL, DEBUG, "Received update for storage node.", + ("nodeNumID", receivedNode->getNumID().val())); + } + + const std::lock_guard lock(mutex); + return addOrUpdateNodeUnlocked(std::move(newNode), outNodeNumID); +} diff --git a/mon/source/nodes/NodeStoreStorageEx.h b/mon/source/nodes/NodeStoreStorageEx.h new file mode 100644 index 0000000..748793a --- /dev/null +++ b/mon/source/nodes/NodeStoreStorageEx.h @@ -0,0 +1,15 @@ +#ifndef NODESTORESTORAGEEX_H_ +#define NODESTORESTORAGEEX_H_ + +#include + +class NodeStoreStorageEx : public NodeStoreServers +{ + public: + NodeStoreStorageEx(); + + virtual NodeStoreResult addOrUpdateNodeEx(std::shared_ptr receivedNode, + NumNodeID* outNodeNumID) override; +}; + +#endif /*NODESTORESTORAGEEX_H_*/ diff --git a/mon/source/nodes/StorageNodeEx.cpp b/mon/source/nodes/StorageNodeEx.cpp new file mode 100644 index 0000000..2503fa6 --- /dev/null +++ b/mon/source/nodes/StorageNodeEx.cpp @@ -0,0 +1,18 @@ +#include "StorageNodeEx.h" + +StorageNodeEx::StorageNodeEx(std::shared_ptr receivedNode) : + Node(NODETYPE_Storage, receivedNode->getAlias(), receivedNode->getNumID(), + receivedNode->getPortUDP(), receivedNode->getPortTCP(), + receivedNode->getConnPool()->getNicList()), + isResponding(true) +{} + +StorageNodeEx::StorageNodeEx(std::shared_ptr receivedNode, + std::shared_ptr oldNode) : + Node(NODETYPE_Storage, receivedNode->getAlias(), receivedNode->getNumID(), + receivedNode->getPortUDP(), receivedNode->getPortTCP(), + receivedNode->getConnPool()->getNicList()) +{ + setLastStatRequestTime(oldNode->getLastStatRequestTime()); + setIsResponding(oldNode->getIsResponding()); +} diff --git a/mon/source/nodes/StorageNodeEx.h b/mon/source/nodes/StorageNodeEx.h new file mode 100644 index 0000000..bba7ad6 --- /dev/null +++ b/mon/source/nodes/StorageNodeEx.h @@ -0,0 +1,61 @@ +#ifndef STORAGENODEEX_H_ +#define STORAGENODEEX_H_ + +#include +#include +#include + +struct StorageNodeDataContent +{ + bool isResponding; + + unsigned indirectWorkListSize; + unsigned directWorkListSize; + + int64_t diskSpaceTotal; + int64_t diskSpaceFree; + int64_t diskRead; + int64_t diskWrite; + + unsigned sessionCount; + std::string hostnameid; +}; + +class StorageNodeEx : public Node +{ + public: + StorageNodeEx(std::shared_ptr receivedNode); + StorageNodeEx(std::shared_ptr receivedNode, std::shared_ptr oldNode); + + private: + mutable RWLock lock; + bool isResponding; + std::chrono::milliseconds lastStatRequestTime{0}; + + public: + std::chrono::milliseconds getLastStatRequestTime() const + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + return lastStatRequestTime; + } + + void setLastStatRequestTime(const std::chrono::milliseconds& time) + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + lastStatRequestTime = time; + } + + bool getIsResponding() const + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + return isResponding; + } + + void setIsResponding(bool isResponding) + { + RWLockGuard safeLock(lock, SafeRWLock_READ); + this->isResponding = isResponding; + } +}; + +#endif /*STORAGENODEEX_H_*/ diff --git a/storage/CMakeLists.txt b/storage/CMakeLists.txt new file mode 100644 index 0000000..346206d --- /dev/null +++ b/storage/CMakeLists.txt @@ -0,0 +1,219 @@ +include_directories( + source +) + +add_library( + storage STATIC + ./source/toolkit/QuotaTk.h + ./source/toolkit/StorageTkEx.h + ./source/toolkit/QuotaTk.cpp + ./source/net/message/mon/RequestStorageDataMsgEx.cpp + ./source/net/message/mon/RequestStorageDataMsgEx.h + ./source/net/message/control/AckMsgEx.h + ./source/net/message/control/SetChannelDirectMsgEx.cpp + ./source/net/message/control/AckMsgEx.cpp + ./source/net/message/control/SetChannelDirectMsgEx.h + ./source/net/message/NetMessageFactory.h + ./source/net/message/session/opening/CloseChunkFileMsgEx.h + ./source/net/message/session/opening/CloseChunkFileMsgEx.cpp + ./source/net/message/session/rw/WriteLocalFileMsgEx.h + ./source/net/message/session/rw/ReadLocalFileV2MsgEx.h + ./source/net/message/session/rw/ReadLocalFileV2MsgEx.cpp + ./source/net/message/session/rw/WriteLocalFileMsgEx.cpp + ./source/net/message/session/FSyncLocalFileMsgEx.h + ./source/net/message/session/FSyncLocalFileMsgEx.cpp + ./source/net/message/NetMessageFactory.cpp + ./source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp + ./source/net/message/nodes/GetClientStatsMsgEx.h + ./source/net/message/nodes/MapTargetsMsgEx.cpp + ./source/net/message/nodes/RefreshTargetStatesMsgEx.cpp + ./source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h + ./source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp + ./source/net/message/nodes/HeartbeatMsgEx.cpp + ./source/net/message/nodes/RemoveBuddyGroupMsgEx.h + ./source/net/message/nodes/HeartbeatRequestMsgEx.h + ./source/net/message/nodes/GetClientStatsMsgEx.cpp + ./source/net/message/nodes/PublishCapacitiesMsgEx.h + ./source/net/message/nodes/GenericDebugMsgEx.cpp + ./source/net/message/nodes/GetTargetConsistencyStatesMsgEx.h + ./source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h + ./source/net/message/nodes/PublishCapacitiesMsgEx.cpp + ./source/net/message/nodes/RemoveNodeMsgEx.cpp + ./source/net/message/nodes/StorageBenchControlMsgEx.cpp + ./source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h + ./source/net/message/nodes/RemoveBuddyGroupMsgEx.cpp + ./source/net/message/nodes/StorageBenchControlMsgEx.h + ./source/net/message/nodes/HeartbeatRequestMsgEx.cpp + ./source/net/message/nodes/MapTargetsMsgEx.h + ./source/net/message/nodes/HeartbeatMsgEx.h + ./source/net/message/nodes/GenericDebugMsgEx.h + ./source/net/message/nodes/GetTargetConsistencyStatesMsgEx.cpp + ./source/net/message/nodes/RemoveNodeMsgEx.h + ./source/net/message/nodes/RefreshTargetStatesMsgEx.h + ./source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp + ./source/net/message/storage/GetHighResStatsMsgEx.h + ./source/net/message/storage/creating/RmChunkPathsMsgEx.cpp + ./source/net/message/storage/creating/UnlinkLocalFileMsgEx.h + ./source/net/message/storage/creating/RmChunkPathsMsgEx.h + ./source/net/message/storage/creating/UnlinkLocalFileMsgEx.cpp + ./source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.cpp + ./source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp + ./source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h + ./source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.cpp + ./source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.h + ./source/net/message/storage/mirroring/ResyncLocalFileMsgEx.h + ./source/net/message/storage/mirroring/ResyncLocalFileMsgEx.cpp + ./source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.cpp + ./source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.h + ./source/net/message/storage/attribs/SetLocalAttrMsgEx.cpp + ./source/net/message/storage/attribs/SetLocalAttrMsgEx.h + ./source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.h + ./source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.cpp + ./source/net/message/storage/GetHighResStatsMsgEx.cpp + ./source/net/message/storage/TruncLocalFileMsgEx.cpp + ./source/net/message/storage/TruncLocalFileMsgEx.h + ./source/net/message/storage/StatStoragePathMsgEx.h + ./source/net/message/storage/StatStoragePathMsgEx.cpp + ./source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.cpp + ./source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.h + ./source/net/message/storage/quota/GetQuotaInfoMsgEx.h + ./source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp + ./source/net/message/storage/quota/SetExceededQuotaMsgEx.h + ./source/net/message/storage/quota/GetQuotaInfoMsgEx.cpp + ./source/net/message/fsck/FetchFsckChunkListMsgEx.cpp + ./source/net/message/fsck/FetchFsckChunkListMsgEx.h + ./source/net/message/fsck/DeleteChunksMsgEx.cpp + ./source/net/message/fsck/MoveChunkFileMsgEx.h + ./source/net/message/fsck/MoveChunkFileMsgEx.cpp + ./source/net/message/fsck/DeleteChunksMsgEx.h + ./source/net/msghelpers/MsgHelperIO.h + ./source/components/chunkfetcher/ChunkFetcher.cpp + ./source/components/chunkfetcher/ChunkFetcherSlave.cpp + ./source/components/chunkfetcher/ChunkFetcher.h + ./source/components/chunkfetcher/ChunkFetcherSlave.h + ./source/components/DatagramListener.h + ./source/components/InternodeSyncer.h + ./source/components/StorageStatsCollector.h + ./source/components/InternodeSyncer.cpp + ./source/components/DatagramListener.cpp + ./source/components/worker/StorageBenchWork.cpp + ./source/components/worker/StorageBenchWork.h + ./source/components/benchmarker/StorageBenchOperator.cpp + ./source/components/benchmarker/StorageBenchOperator.h + ./source/components/benchmarker/StorageBenchSlave.cpp + ./source/components/benchmarker/StorageBenchSlave.h + ./source/components/streamlistenerv2/StorageStreamListenerV2.h + ./source/components/buddyresyncer/BuddyResyncer.cpp + ./source/components/buddyresyncer/BuddyResyncJob.h + ./source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp + ./source/components/buddyresyncer/BuddyResyncerDirSyncSlave.cpp + ./source/components/buddyresyncer/SyncCandidate.h + ./source/components/buddyresyncer/BuddyResyncerGatherSlave.h + ./source/components/buddyresyncer/BuddyResyncerDirSyncSlave.h + ./source/components/buddyresyncer/BuddyResyncJob.cpp + ./source/components/buddyresyncer/BuddyResyncerFileSyncSlave.cpp + ./source/components/buddyresyncer/BuddyResyncerFileSyncSlave.h + ./source/components/buddyresyncer/BuddyResyncer.h + ./source/components/StorageStatsCollector.cpp + ./source/session/ZfsSession.h + ./source/session/Session.h + ./source/session/SessionLocalFileStore.h + ./source/session/SessionLocalFile.h + ./source/session/SessionStore.cpp + ./source/session/ZfsSession.cpp + ./source/session/SessionLocalFile.cpp + ./source/session/Session.cpp + ./source/session/SessionStore.h + ./source/session/SessionLocalFileStore.cpp + ./source/program/Program.h + ./source/program/Program.cpp + ./source/program/Main.cpp + ./source/app/App.h + ./source/app/App.cpp + ./source/app/config/Config.h + ./source/app/config/Config.cpp + ./source/nodes/StorageNodeOpStats.h + ./source/storage/ChunkDir.h + ./source/storage/SyncedStoragePaths.h + ./source/storage/QuotaBlockDevice.cpp + ./source/storage/ChunkLockStore.h + ./source/storage/ChunkStore.h + ./source/storage/StorageTargets.cpp + ./source/storage/ChunkStore.cpp + ./source/storage/QuotaBlockDevice.h + ./source/storage/StorageTargets.h +) + +target_link_libraries( + storage + beegfs-common + dl + pthread + blkid +) + +add_executable( + beegfs-storage + source/program/Main.cpp +) + +target_link_libraries( + beegfs-storage + storage +) + +if(NOT BEEGFS_SKIP_TESTS) + add_executable( + test-storage + ./tests/TestConfig.h + ./tests/TestConfig.cpp + ) + + target_link_libraries( + test-storage + storage + gtest_main + ) + + # required for a test + file( + COPY ${CMAKE_CURRENT_SOURCE_DIR}/build/dist/etc/beegfs-storage.conf + DESTINATION dist/etc/ + ) + + add_test( + NAME test-storage + COMMAND test-storage --compiler + ) +endif() + +install( + TARGETS beegfs-storage + DESTINATION "usr/sbin" + COMPONENT "storage" +) + +install( + FILES "build/dist/usr/lib/systemd/system/beegfs-storage.service" "build/dist/usr/lib/systemd/system/beegfs-storage@.service" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/systemd/system" + COMPONENT "storage" +) + +install( + PROGRAMS "build/dist/sbin/beegfs-setup-storage" + DESTINATION "usr/sbin" + COMPONENT "storage" +) + +install( + FILES "build/dist/etc/beegfs-storage.conf" + DESTINATION "etc/beegfs" + COMPONENT "storage" +) + +install( + PROGRAMS "build/beegfs-storage.sh" + RENAME "beegfs-storage" + DESTINATION "opt/beegfs/sbin" + COMPONENT "storage" +) diff --git a/storage/build/Makefile b/storage/build/Makefile new file mode 100644 index 0000000..f03acd9 --- /dev/null +++ b/storage/build/Makefile @@ -0,0 +1,26 @@ +include ../../build/Makefile + +main := ../source/program/Main.cpp +sources := $(filter-out $(main), $(shell find ../source -iname '*.cpp')) + +$(call build-static-library,\ + Storage,\ + $(sources),\ + common dl blkid uuid nl3-route,\ + ../source) + +$(call define-dep-lib,\ + Storage,\ + -I ../source,\ + $(build_dir)/libStorage.a) + +$(call build-executable,\ + beegfs-storage,\ + $(main),\ + Storage common dl blkid uuid nl3-route) + +$(call build-test,\ + test-runner,\ + $(shell find ../tests -name '*.cpp'),\ + Storage common dl blkid uuid nl3-route,\ + ../tests) diff --git a/storage/build/dist/etc/beegfs-storage.conf b/storage/build/dist/etc/beegfs-storage.conf new file mode 100644 index 0000000..f11f93b --- /dev/null +++ b/storage/build/dist/etc/beegfs-storage.conf @@ -0,0 +1,543 @@ +# This is a config file for BeeGFS storage nodes. +# http://www.beegfs.com + + +# --- [Table of Contents] --- +# +# 1) Settings +# 2) Command Line Arguments +# 3) Basic Settings Documentation +# 4) Advanced Settings Documentation + + +# +# --- Section 1.1: [Basic Settings] --- +# + +sysMgmtdHost = + +storeStorageDirectory = +storeAllowFirstRunInit = true +storeFsUUID = + + +# +# --- Section 1.2: [Advanced Settings] --- +# + +connAuthFile = /etc/beegfs/conn.auth +connDisableAuthentication = false +connBacklogTCP = 128 +connInterfacesFile = +connMaxInternodeNum = 12 + +connMgmtdPort = 8008 +connStoragePort = 8003 +connPortShift = 0 + +connNetFilterFile = + +connUseRDMA = true +connRDMATypeOfService = 0 +connTcpOnlyFilterFile = + +logType = syslog +logLevel = 3 +logNoDate = false +logNumLines = 50000 +logNumRotatedFiles = 5 +logStdFile = /var/log/beegfs-storage.log + +runDaemonized = true + +sysResyncSafetyThresholdMins = 10 +sysTargetOfflineTimeoutSecs = 180 + +tuneBindToNumaZone = +tuneFileReadAheadSize = 0m +tuneFileReadAheadTriggerSize = 4m +tuneFileReadSize = 128k +tuneFileWriteSize = 128k +tuneFileWriteSyncSize = 0m + +tuneNumResyncGatherSlaves = 6 +tuneNumResyncSlaves = 12 +tuneNumStreamListeners = 1 +tuneNumWorkers = 12 +tuneUseAggressiveStreamPoll = false +tuneUsePerTargetWorkers = true +tuneUsePerUserMsgQueues = false +tuneWorkerBufSize = 4m + + +# +# --- Section 2: [Command Line Arguments] --- +# + +# Use the command line argument "cfgFile=/etc/anotherconfig.conf" to +# specify a different config file for beegfs_storage. +# +# All other options in this file can also be used as command line +# arguments, overriding the corresponding config file values. + + +# +# --- Section 3: [Basic Settings Documentation] --- +# + +# [sysMgmtdHost] +# Hostname (or IP) of the host running the management service. +# (See also "connMgmtdPort") +# Default: + +# [storeStorageDirectory] +# The absoute path to a storage target. A storage target is a directory where +# the file system can store raw user file contents. Multiple targets can be +# specified as a comma-separated list. +# Example: /mnt/beegfs_storage1,/mnt/beegfs_storage2 +# Default: + +# [storeAllowFirstRunInit] +# Enables or disables daemon startup with an uninitialized storage directory. +# This can be used to make sure that the daemon does not run when the storage +# partition is not mounted (e.g. because it needs repair after a power outage). +# Note: This setting must be enabled during first startup of the daemon, but +# may be disabled afterwards. +# Default: true + +# [storeFsUUID] +# Requires the underlying file systems of the storage targets to have the same +# UUID as set here. This prevents the storage node from accidentaly starting targets +# from a wrong device, e.g. when it is not properly mounted. To find the UUID to +# put here, you can, for example, use blkid: +# +# blkid -s UUID +# +# This will output all devices on the host with their file systems UUID (if there +# is one). Choose the correct ones and list them here. This command needs to be run +# as root. +# +# The UUIDs need to be listed the same way and order as the storage targets paths +# provided with the storeStorageDirectory setting above as they are checked against +# the paths in that order. +# +# If left empty, the check is skipped. It is highly recommended to enable this check +# after installation to prevent data corruption. +# Default: +# + +# +# --- Section 4: [Advanced Settings Documentation] --- +# + +# +# --- Section 4.1: [Connections & Communication] --- +# + +# [connAuthFile] +# The path to a file that contains a shared secret for connection based +# authentication. Only peers that use the same shared secret will be able to +# connect. +# Default: + +# [connDisableAuthentication] +# If set to true, explicitly disables connection authentication and allow the +# service to run without a connAuthFile. Running BeeGFS without connection +# authentication is considered insecure and is not recommended. +# Default: false + +# [connBacklogTCP] +# The TCP listen backlog. +# Default: 128 + +# [connInterfacesFile] +# The path to a text file that specifies the names of the interfaces which +# may be used for communication. One interface per line. The line number also +# defines the priority of the interface. +# Example: "ib0" in the first line, "eth0" in the second line. +# Values: This setting is optional. If unspecified, all available interfaces +# will be used and priorities will be assigned automatically. +# Note: This information is sent to other hosts to inform them about possible +# communication paths. See connRestrictOutboundInterfaces for this +# configuration's potential effect on outbound connections. +# Default: + +# [connInterfacesList] +# Comma-separated list of interface names. Performs the same function as +# connInterfacesFile. +# Default: + +# [connRestrictOutboundInterfaces] +# The default behavior of BeeGFS is to use any available network interface +# to establish an outbound connection to a node, according to the TCP/IP +# configuration of the operating system. When connRestrictOutboundInterfaces +# is set to true, the network interfaces used for outbound connections are +# limited to the values specified by connInterfacesFile or connInterfacesList. +# The operating system routing tables are consulted to determine which +# interface to use for a particular node's IP address. If there is no +# route from the configured interfaces that is suitable for a node's IP +# addresses then the connection will fail to be established. +# Default: false + +# [connNoDefaultRoute] +# When connRestrictOutboundInterfaces is true, the routing logic would use +# the default route for a Node's IP address when no specific route for that +# address is found in the routing tables. This can be problematic during a +# failure situation, as the default route is not appropriate to use for a +# subnet that is accessible from an interface that has failed. +# connNoDefaultRoute is a comma-separated list of CIDRs that should never +# be accessed via the default route. +# Default: 0.0.0.0/0. This prevents the default route from ever being used. + +# [connMaxInternodeNum] +# The maximum number of simultaneous connections to the same node. +# Default: 12 + +# [connMgmtdPort] +# The UDP and TCP port of the management node. +# Default: 8008 + +# [connStoragePort] +# The UDP and TCP port of the storage node. +# Default: 8003 + +# [connPortShift] +# Shifts all following UDP and TCP ports according to the specified value. +# Intended to make port configuration easier in case you do not want to +# configure each port individually. +# Default: 0 + +# [connNetFilterFile] +# The path to a text file that specifies allowed IP subnets, which may be used +# for outgoing communication. One subnet per line in classless notation (IP +# address and number of significant bits). +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. If unspecified, all addresses are allowed +# for outgoing communication. +# Default: + +# [connTCPRcvBufSize], [connUDPRcvBufSize] +# Sets the size for TCP and UDP socket receive buffers (SO_RCVBUF). The maximum +# allowed value is determined by sysctl net.core.rmem_max. This value is +# ignored if it is less than the default value determined by +# net.core.rmem_default. +# For legacy reasons, the default value 0 indicates that the buffer size is set +# to connRDMABufNum * connRDMABufSize. +# -1 indicates that the buffer size should be left at the system default. +# Default: 0 + +# [connUseRDMA] +# Enables the use of Remote Direct Memory Access (RDMA) for Infiniband. +# This setting only has effect if libbeegfs-ib is installed. +# Default: true + +# [connRDMABufNum], [connRDMABufSize] +# Infiniband RDMA buffer settings. +# connRDMABufSize is the maximum size of a buffer (in bytes) that will be sent +# over the network; connRDMABufNum is the number of available buffers that can +# be in flight for a single connection. These client settings are also applied +# on the server side for each connection. +# Note: RAM usage per connection is connRDMABufSize x connRDMABufNum x 2. Keep +# resulting RAM usage (x connMaxInternodeNum x number_of_clients) on the +# server in mind when increasing these values. +# Note: The client needs to allocate physically contiguous pages for +# connRDMABufSize, so this setting shouldn't be higher than a few kbytes. +# Default: 8192, 70 + +# [connRDMATypeOfService] +# Infiniband provides the option to set a type of service for an application. +# This type of service can be used by your subnet manager to provide Quality of +# Service functionality (e.g. setting different service levels). +# In openSM the service type will be mapped to the parameter qos-class, which +# can be handled in your QoS configuration. +# See +# www.openfabrics.org/downloads/OFED/ofed-1.4/OFED-1.4-docs/ +# QoS_management_in_OpenSM.txt +# for more information on how to configure openSM for QoS. +# This parameter sets the type of service for all outgoing connections of this +# daemon. +# Default: 0 (Max: 255) + +# [connTcpOnlyFilterFile] +# The path to a text file that specifies IP address ranges to which no RDMA +# connection should be established. This is useful e.g. for environments where +# all hosts support RDMA, but some hosts cannot connect via RDMA to some other +# hosts. +# Example: "192.168.10.0/24" in the first line, "192.168.20.0/24" in the second +# line. +# Values: This setting is optional. +# Default: + +# [connMessagingTimeouts] +# These constants are used to set some of the connection timeouts for sending +# and receiving data between services in the cluster. They used to be hard-coded +# (CONN_LONG_TIMEOUT, CONN_MEDIUM_TIMEOUT and CONN_SHORT_TIMEOUT) but are now +# made configurable for experimentation purposes. +# This option takes three integer values of milliseconds, separated by a comma +# in the order long, medium, short. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 600000,90000,30000 + +# [connRDMATimeouts] +# These constants are used to set some of the timeouts for sending and receiving +# data between services in the cluster via RDMA. They used to be +# hard-coded IBVSOCKET_CONN_TIMEOUT_MS, IBVSOCKET_FLOWCONTROL_ONSEND_TIMEOUT_MS +# and a 10000 literal for poll timeout but are now made configurable for +# experimentation purposes. +# This option takes three integer values of milliseconds, separated by a comma +# in the order connectMS, flowSendMS and pollMS. +# WARNING: This is an EXPERIMENTAL configuration option that should not be +# changed in production environments unless properly tested and validated. +# Some configurations can lead to service lockups and other subtle issues. +# Please make sure that you know exactly what you are doing and properly +# test any changes you make. +# Default: 3000,180000,7500 + +# [connFallbackExpirationSecs] +# The time in seconds after which a connection to a fallback interface expires. +# When a fallback connection expires, the system will try to establish a new +# connection to the other hosts primary interface (falling back to another +# interface again if necessary). +# Note: The priority of node interfaces can be configured using the +# "connInterfacesFile" parameter. +# Default: 900 + + +# +# --- Section 4.2: [Logging] --- +# + +# [logType] +# Defines the logger type. This can either be "syslog" to send log messages to +# the general system logger or "logfile". If set to logfile logs will be written +# to logStdFile. +# Default: logfile + +# [logLevel] +# Defines the amount of output messages. The higher this level, the more +# detailed the log messages will be. +# Note: Levels above 3 might decrease performance. +# Default: 3 (Max: 5) + +# [logNoDate] +# Defines whether "date & time" (=false) or the current "time only" (=true) +# should be logged. +# Default: false + +# [logNumLines] +# The maximum number of lines per log file. +# Default: 50000 + +# [logNumRotatedFiles] +# The number of old files to keep when "logNumLines" is reached and the log file +# is rewritten (log rotation). +# Default: 5 + +# [logStdFile] +# The path and filename of the log file for standard log messages. +# The parameter will be considered only if logType value is not equal to syslog. +# If no name is specified, the messages will be written to the console. +# Default: /var/log/beegfs_storage.log + + +# +# --- Section 4.4: [Startup] --- +# + +# [runDaemonized] +# Detach the process from its parent (and from stdin/-out/-err). +# Default: true + + +# +# --- Section 4.5: [System Settings] --- +# + +# [sysResyncSafetyThresholdMins] +# Automatic mirror resyncs use the last successful communication time between +# two mirror buddies to skip verification of files that were not recently +# modified before a server went offline. As BeeGFS uses server-side write +# caching (where the cache is flushed to disk every minute by the Linux kernel), +# it is possible that a server looses its write cache in case of a crash, which +# contained data before the last successful communication. This value adds an +# extra amount of time to the last successful communication timestamp to include +# the time window of a potential cache loss. +# The value may be 0 (which doesn't mean there is no threshold) to completely +# disable the use of the last successful communication timestamp, +# i.e. that a full resync will be done. +# Values: time in minutes +# Default: 10 + +# [sysTargetOfflineTimeoutSecs] +# Timeout until targets on a storage server are considered offline by the +# management node when no target state updates can be fetched from that server. +# Note: This must be the same value as in the /etc/beegfs/beegfs-mgmtd.conf on +# the management node. +# Values: time in seconds +# Default: 180 + + +# +# --- Section 4.6: [Tuning] --- +# + +# [tuneBindToNumaZone] +# Defines the zero-based NUMA zone number to which all threads of this process +# should be bound. If unset, all available CPU cores may be used. +# Zone binding is especially useful if the corresponding devices (e.g. storage +# controller and network card) are also attached to the same zone. +# Note: The Linux kernel shows NUMA zones at /sys/devices/system/node/nodeXY +# Default: + +# [tuneFileReadAheadSize], [tuneFileReadAheadTriggerSize] +# tuneFileReadAheadSize is the byte range submitted to the kernel for read-head +# after at least tuneFileReadAheadTriggerSize file bytes were read sequentially +# from a target. +# Values: A typical setting is tuneFileReadAheadSize=2m. The optimal setting +# depends on your storage system configuration (e.g. your RAID layout). +# Default: tuneFileReadAheadSize=0, tuneFileReadAheadTriggerSize=4m + +# [tuneFileReadSize], [tuneFileWriteSize] +# The maximum amount of data that the server should write to (or read from) +# the underlying local file system in a single operation. +# Note: Setting these values higher than the file chunk size or +# tuneWorkerBufSize has no effect. +# Default: tuneFileReadSize=128k, tuneFileWriteSize=128k + +# [tuneFileWriteSyncSize] +# The number of sequentially written bytes (per file) after which the kernel +# will be advised to commit the written data to the underlying storage device. +# This is intended to avoid delays until the kernel notices that it is time to +# commit written data, which would reduce streaming write throughput. +# Note: When this setting is enabled, it is important to use the deadline +# scheduler (/sys/block/<...>/scheduler) to avoid reader starvation. It is +# also important to use a large request queue (/sys/block/<...>/nr_requests), +# as writes can only be asynchronous while there are free slots in the queue. +# Values: "0" disables this mechanism. Use "32m" (or a close even multiple of +# your RAID stripe set size) to test the effects of this. +# Default: 0 + +# [tuneNumResyncGatherSlaves] +# The number of threads (per target) used to gather file system information for +# a buddy mirror resync. +# Default: 6 + +# [tuneNumResyncSlaves] +# The number of threads (per target) used to perform the actual file and +# directory synchronizations for a buddy mirror resync. +# Default: 12 + +# [tuneNumStreamListeners] +# The number of threads waiting for incoming data events. Connections with +# incoming data will be handed over to the worker threads for actual message +# processing. +# Default: 1 + +# [tuneNumWorkers] +# The number of worker threads. Higher number of workers allows the server to +# handle more client requests in parallel, which also results in more +# concurrent access to the underlying storage device. +# Note: See also tuneUsePerTargetWorkers. +# Default: 12 + +# [tuneUseAggressiveStreamPoll] +# If set to true, the StreamListener component, which waits for incoming +# requests, will keep actively polling for events instead of sleeping until +# an event occurs. Active polling will reduce latency for processing of +# incoming requests at the cost of higher CPU usage. +# Default: false + +# [tuneUsePerTargetWorkers] +# If set to true, a separate set of worker threads is created and exclusively +# assigned to each attached storage target. If set to false, a global set of +# worker threads is used and each worker thread can handle requests for all +# targets. +# Separate worker threads are intended to improve balance of I/O workload +# across targets under high load (i.e. when the number of concurrently incoming +# requests is higher than the number of worker threads). +# Note: If set to true, the actual number of created worker threads is +# tuneNumWorkers x number_of_attached_targets. +# Default: true + +# [tuneUsePerUserMsgQueues] +# If set to true, per-user queues will be used to decide which of the pending +# requests is handled by the next available worker thread. If set to false, a +# single queue will be used and incoming requests will be processed in +# first-come, first-served order. +# Per-user queues are intended to improve fairness in multi-user environments. +# Default: false + +# [tuneWorkerBufSize] +# The buffer size, which is allocated twice by each worker thread for IO and +# network data buffering. +# Note: For optimal performance, this value must be at least 1MB higher than +# tuneFileReadSize and tuneFileWriteSize. +# Default: 4m + + +# +# --- Section 4.7: [Quota settings] --- +# + +# [quotaEnableEnforcement] +# Enables enforcement of user and group quota limits by periodically checking +# if the limits are exceeded. +# Note: This uses quota information provided by the underlying local file +# systems of the storage targets. +# Note: Set quota limits with "beegfs-ctl --setquota". +# Note: If this option is true, performance might be slightly decreased due to +# extra information tracking. +# Note: Must be set to the same value in meta servers and mgmtd to be +# effective. +# Default: false + + +# +# --- Section 5: [Expert options] --- +# + +# [tuneProcessFDLimit] +# Sets the maximum number of files the server can open. If the process rlimit +# is already larger than this number the limit will not be decreased. +# Default: 50000 + +# [tuneWorkerNumaAffinity] +# Distributes worker threads equally among NUMA nodes on the system when set. +# Default: false + +# [tuneListenerNumaAffinity] +# Distributes listener threads equally among NUMA nodes on the system when set. +# Default: false + +# [tuneListenerPrioShift] +# Applies a niceness offset to listener threads. Negative values will decrease +# niceness (increse priority), positive values will increase niceness (decrease +# priority). +# Default: -1 + +# [tuneDirCacheLimit] +# Number of recently used chunk directory structures to keep in memory. +# Increasing this value may reduce memory allocations. +# Default: 1024 + +# [tuneEarlyStat] +# Compute file size and storage block usage of a chunk before close() is set. +# Some filesystems may report block usage greater than required to hold all +# file data if stat() is called before close(). +# Default: false + +# [quotaDisableZfsSupport] +# Disable quota support for ZFS if quota is enabled. +# ZFS quota requires the libzfs library. Errors while loading the library are +# reported if quota is enabled and the library is not installed (or the wrong +# version is installed) +# Default: false + +# [pidFile] +# Creates a PID file for the daemon when set. Set by init scripts. +# Default: diff --git a/storage/build/dist/etc/default/beegfs-storage b/storage/build/dist/etc/default/beegfs-storage new file mode 100644 index 0000000..b222264 --- /dev/null +++ b/storage/build/dist/etc/default/beegfs-storage @@ -0,0 +1,28 @@ +# BeeGFS storage service configuration. + +# Note: This file is only used together with sysV init scripts. +# If your system uses systemd, this file is ignored. +# In this case: +# +# - use `systemctl enable / disable` to activate / decativate a service +# +# - systemd service templates are used for multimode +# (See https://www.beegfs.io/wiki/MultiMode) +# +# +# Set to "NO" to disable start of the BeeGFS storage daemon via the init script. +START_SERVICE="YES" + +# Set to "YES" if you want to start multiple storage daemons with different +# configuration files on this machine. +# +# Create a subdirectory with the ending ".d" in "/etc/beegfs/" for every config +# file. The subdirectory name will be used to identify a particular server +# instance for init script start/stop. +# +# Note: The original config file in /etc/beegfs will not be used when multi-mode +# is enabled. +# +# Example: /etc/beegfs/scratch.d/beegfs-storage.conf +# $ /etc/init.d/beegfs-storage start scratch +MULTI_MODE="NO" diff --git a/storage/build/dist/etc/init.d/beegfs-storage.init b/storage/build/dist/etc/init.d/beegfs-storage.init new file mode 100755 index 0000000..3e4693c --- /dev/null +++ b/storage/build/dist/etc/init.d/beegfs-storage.init @@ -0,0 +1,22 @@ +#!/bin/bash +# +### BEGIN INIT INFO +# Provides: beegfs-storage +# Required-Start: +# Should-Start: $network beegfs-mgmtd openibd openib rdma opensmd opensm +# Required-Stop: +# Should-Stop: $network beegfs-mgmtd openibd openib rdma opensmd opensm +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# chkconfig: 35 96 8 +# Short-Description: BeeGFS Storage +# Description: Start BeeGFS Storage Server +### END INIT INFO + +APP_NAME="BeeGFS Storage Server" +SERVICE_NAME=beegfs-storage + +# source function library +. /etc/beegfs/lib/start-stop-functions +. /etc/beegfs/lib/init-multi-mode + diff --git a/storage/build/dist/sbin/beegfs-setup-storage b/storage/build/dist/sbin/beegfs-setup-storage new file mode 100755 index 0000000..c4dd8cb --- /dev/null +++ b/storage/build/dist/sbin/beegfs-setup-storage @@ -0,0 +1,322 @@ +#!/bin/bash + +# License: BeeGFS EULA + +# constant definitions +# (for configurables see below) + +DEFAULT_CFG_PATH="/etc/beegfs/beegfs-storage.conf" +STORAGE_PATH_CFG_KEY="storeStorageDirectory" +MGMTD_CFG_KEY="sysMgmtdHost" +ALLOW_INIT_CFG_KEY="storeAllowFirstRunInit" +FS_UUID_CFG_KEY="storeFsUUID" +TARGET_NUMID_FILE="targetNumID" +TARGET_STRID_FILE="targetID" +SERVER_NUMID_FILE="nodeNumID" +STORAGEPOOL_ID_FILE="storagePoolID" +FORMAT_FILENAME="format.conf" +FORMAT_FILE_VERSION="3" +CHUNKS_DIRNAME="chunks" +BUDDYMIRROR_DIRNAME="buddymir" + +print_usage() +{ + echo + echo "DESCRIPTION: Initialize storage target directory for beegfs-storage server daemon" + echo "and update the beegfs-storage config file." + echo + echo "USAGE: `basename $0` -p [options]" + echo + echo " Mandatory Options:" + echo + echo " -p - Path to storage target directory that is to be initialized." + echo " (Path will also be added to config file.)" + echo + echo " Recommended Options:" + echo + echo " -i - Assign the given numeric ID to storage target (range 1..65535)." + echo " (Default: Randomly select a free ID.)" + echo + echo " -s - Assign the given numeric ID to the server of this storage target" + echo " (range 1..65535). (Default: Randomly select a free ID.)" + echo + echo " -m - Hostname (or IP address) of management server." + echo " (Will be stored in server config file.)" + echo + echo " Other Options:" + echo + echo " -C - Do not update server config file." + echo + echo " -c - Path to server config file." + echo " (Default: ${DEFAULT_CFG_PATH})" + echo + echo " -f - Force actions, ignore warnings." + echo + echo " -h - Print this help." + echo + echo " -P - Automatically add the configured target to storage pool with the" + echo " given ID on first start." + echo + echo " -r - Replace current list of target directories with the given path." + echo " (Default: Append directory to current target list in server" + echo " config file.)" + echo + echo " -u - Do not disable usage of uninitialized target directories in" + echo " config file and do not store the UUID of the underlying fs." + echo + echo "NOTES:" + echo " * All given IDs must be unique in their service class for the whole file system" + echo " instance, i.e. there can only be one beegfs-meta service with ID 2, but there" + echo " can also be a beegfs-storage service with ID 2 in the file system." + echo + echo " * BeeGFS servers can also run without pre-initializing storage directories, if" + echo " storeAllowFirstRunInit=true is set in the server config files (which is" + echo " usually not recommended)." + echo + echo "EXAMPLES:" + echo " * Numeric IDs can generally be chosen arbitrarily. However, in the examples" + echo " below they were chosen in a way that reflects the assignment of targets to" + echo " their corresponding servers." + echo + echo " * Example 1) Initialize first target directory \"/mnt/myraid1\" of first" + echo " storage server and set \"storage01\" as management daemon host in server" + echo " config file:" + echo " $ `basename $0` -p /mnt/myraid1/beegfs-storage -s 1 -i 101 -m storage01" + echo + echo " * Example 2) Initialize second target directory \"/mnt/myraid2\" of fourth" + echo " storage server:" + echo " $ `basename $0` -p /mnt/myraid2/beegfs-storage -s 4 -i 402" + echo +} + +# initialize storage directory (if enabled) +init_storage_dir() +{ + # check if storage path is defined + + if [ -z "${STORAGE_PATH}" ]; then + return 0 + fi + + # create storage path + + echo "Preparing storage target directory: ${STORAGE_PATH}" + mkdir -p "${STORAGE_PATH}" + + # make sure target dir is empty + + if [ -z "${FORCE_ACTIONS}" ] && [ "$(ls -AI lost+found ${STORAGE_PATH} )" ]; then + echo " * ERROR: Storage target directory is not empty. Initialization of non-empty" \ + "directories can lead to data loss or orphaned data. ('-f' disables this check.)" + exit 1 + fi + + # create format file + + echo " * Creating ${FORMAT_FILENAME} file..." + + FORMAT_FILE_PATH="${STORAGE_PATH}/${FORMAT_FILENAME}" + echo "# This file was auto-generated. Do not modify it!" >> ${FORMAT_FILE_PATH} + echo "version=${FORMAT_FILE_VERSION}" >> ${FORMAT_FILE_PATH} + + # create chunks dir + + echo " * Creating ${CHUNKS_DIRNAME} directory..." + + CHUNKS_DIR_PATH="${STORAGE_PATH}/${CHUNKS_DIRNAME}" + mkdir -p ${CHUNKS_DIR_PATH} + + # create mirror dir + + echo " * Creating ${BUDDYMIRROR_DIRNAME} directory..." + + BUDDYMIRROR_DIR_PATH="${STORAGE_PATH}/${BUDDYMIRROR_DIRNAME}" + mkdir -p ${BUDDYMIRROR_DIR_PATH} + + + # create ID files + + if [ -n "${TARGET_NUMID}" ]; then + echo " * Creating target numeric ID file: ${STORAGE_PATH}/${TARGET_NUMID_FILE}" + echo "${TARGET_NUMID}" > "${STORAGE_PATH}/${TARGET_NUMID_FILE}" + fi + + if [ -n "${TARGET_STRID}" ]; then + echo " * Creating target string ID file: ${STORAGE_PATH}/${TARGET_STRID_FILE}" + echo "${TARGET_STRID}" > "${STORAGE_PATH}/${TARGET_STRID_FILE}" + fi + + if [ -n "${SERVER_NUMID}" ]; then + echo " * Creating server numeric ID file: ${STORAGE_PATH}/${SERVER_NUMID_FILE}" + echo "${SERVER_NUMID}" > "${STORAGE_PATH}/${SERVER_NUMID_FILE}" + fi + + if [ -n "${STORAGEPOOL_ID}" ]; then + echo " * Creating storage pool ID file: ${STORAGE_PATH}/${STORAGEPOOL_ID_FILE}" + echo "${STORAGEPOOL_ID}" > "${STORAGE_PATH}/${STORAGEPOOL_ID_FILE}" + fi +} + +# update config file (if enabled) +update_config_file() +{ + # check if config file is defined + + if [ -z "${CFG_PATH}" ]; then + return 0 + fi + + echo "Updating config file: ${CFG_PATH}" + + if [ ! -f "${CFG_PATH}" ]; then + echo " * ERROR: Config file not found: ${CFG_PATH}" + exit 1 + fi + + if [ -n "${MGMTD_HOST}" ]; then + echo " * Setting management host: ${MGMTD_HOST}" + sed -i "s/\(^${MGMTD_CFG_KEY}.*=\).*/\1 ${MGMTD_HOST}/" ${CFG_PATH} + fi + + if [ -n "${STORAGE_PATH}" ]; then + if [ -n "${REPLACE_TARGETS}" ]; then + echo " * Replacing target directory list in config file..." + sed -i "s|\(^${STORAGE_PATH_CFG_KEY}.*=\).*$|\1 ${STORAGE_PATH}|" ${CFG_PATH} + else + echo " * Appending to target directory list in config file..." + + set +e + + grep "^${STORAGE_PATH_CFG_KEY}.*=.*${STORAGE_PATH}\([, ]\|$\)" "${CFG_PATH}" >/dev/null + + GREP_RES=$? + + set -e + + if [ "$GREP_RES" -eq 0 ] && [ -z "${FORCE_ACTIONS}" ]; then + echo " * WARNING: Skipping append. Target directory seems to be included in" \ + "current targets list already. ('-f' disables this check.)" + APPEND_SKIPPED=1 + else + sed -i "s|\(^${STORAGE_PATH_CFG_KEY}.*=.*$\)|\0 ,${STORAGE_PATH}|" ${CFG_PATH} + fi + + + fi + fi + + if [ -n "${DISABLE_UNINITED_TARGETS}" ] && [ -n "${STORAGE_PATH}" ]; then + echo " * Disabling usage of uninitialized storage targets in config file..." + sed -i "s/\(^${ALLOW_INIT_CFG_KEY}.*=\).*/\1 false/" ${CFG_PATH} + + if [ -z "${APPEND_SKIPPED}" ]; then + echo " * Fetching the underlying device..." + DEVICE=$(df "${STORAGE_PATH}" | tail -n1 | cut -d\ -f1) + echo "Underlying device detected: ${DEVICE}" + echo "Fetching UUID of the file system on that device..." + UUID=$(blkid -s UUID ${DEVICE} | cut -d= -f2 | sed "s/\"//g") + echo "Found UUID ${UUID}" + echo "Appending UUID to config file..." + sed -i "s|\(^${FS_UUID_CFG_KEY}.*=.*$\)|\0 ,${UUID}|" ${CFG_PATH} + fi + fi +} + +################## end of function definitions ############## + + +# configurable values and their defaults +# (for constants see above) + +CFG_PATH="$DEFAULT_CFG_PATH" # empty path means "don't update cfg file" +FORCE_ACTIONS="" +TARGET_NUMID="" +TARGET_STRID="" +MGMTD_HOST="" +REPLACE_TARGETS="" +SERVER_NUMID="" +STORAGE_PATH="" +STORAGEPOOL_ID="" +DISABLE_UNINITED_TARGETS="1" + +# parse command line arguments +# (see print_usage() for description of parameters) + +while getopts "Cc:fhI:i:m:p:P:rS:s:u" opt; do + case $opt in + C) + CFG_PATH="" + ;; + c) + CFG_PATH="$OPTARG" + ;; + f) + FORCE_ACTIONS="1" + ;; + h) + print_usage + exit 0 + ;; + I) + TARGET_STRID="$OPTARG" + ;; + i) + TARGET_NUMID="$OPTARG" + ;; + m) + MGMTD_HOST="$OPTARG" + ;; + p) + STORAGE_PATH="$OPTARG" + ;; + P) + STORAGEPOOL_ID="$OPTARG" + ;; + r) + REPLACE_TARGETS="1" + ;; + S) + echo "WARNING: The -S flag previously used to specify a string ID been deprecated and now has no effect. Starting in BeeGFS string IDs were replaced with aliases configured using BeeGFS CTL." + ;; + s) + SERVER_NUMID="$OPTARG" + ;; + u) + DISABLE_UNINITED_TARGETS="" + ;; + *) + echo "ERROR: Invalid argument" >&2 + print_usage + exit 1 + ;; + esac +done + +set -e + +# don't do anything if no arguments are provided + +if [ $# -eq 0 ]; then + print_usage + exit 1 +fi + +# make sure target dir is defined + +if [ -z "${STORAGE_PATH}" ]; then + echo "ERROR: Storage target directory is undefined." >&2 + echo + print_usage + exit 1 +fi + +# initialize storage directory + +init_storage_dir + +# update config file + +update_config_file + + +echo "All done." diff --git a/storage/build/dist/usr/lib/systemd/system/beegfs-storage.service b/storage/build/dist/usr/lib/systemd/system/beegfs-storage.service new file mode 100644 index 0000000..8b82732 --- /dev/null +++ b/storage/build/dist/usr/lib/systemd/system/beegfs-storage.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Storage Server +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-storage cfgFile=/etc/beegfs/beegfs-storage.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/storage/build/dist/usr/lib/systemd/system/beegfs-storage@.service b/storage/build/dist/usr/lib/systemd/system/beegfs-storage@.service new file mode 100644 index 0000000..a969d05 --- /dev/null +++ b/storage/build/dist/usr/lib/systemd/system/beegfs-storage@.service @@ -0,0 +1,14 @@ +[Unit] +Description=BeeGFS Storage Server (multimode) +Documentation=http://www.beegfs.com/content/documentation/ +Requires=network-online.target +# We disable the wants service, because it spams the log files +#Wants=beegfs-mgmtd.service openibd.service openib.service rdma.service opensmd.service opensm.service +After=network-online.target beegfs-mgmtd.service openibd.service openib.service rdma.service opensmd.service opensm.service zfs.target + +[Service] +ExecStart=/opt/beegfs/sbin/beegfs-storage cfgFile=/etc/beegfs/%I.d/beegfs-storage.conf runDaemonized=false +Type=simple + +[Install] +WantedBy=multi-user.target diff --git a/storage/source/app/App.cpp b/storage/source/app/App.cpp new file mode 100644 index 0000000..735b340 --- /dev/null +++ b/storage/source/app/App.cpp @@ -0,0 +1,1530 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "App.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + + + +#define APP_WORKERS_DIRECT_NUM 1 +#define APP_SYSLOG_IDENTIFIER "beegfs-storage" + +#define APP_STORAGE_UMASK (0) // allow any creat() / mkdir() mode without masking anything + +#define APP_LIB_ZFS_NAME "libzfs.so" + +App::App(int argc, char** argv) +{ + this->argc = argc; + this->argv = argv; + + this->appResult = APPCODE_NO_ERROR; + + this->cfg = NULL; + this->storageTargets = NULL; + this->netFilter = NULL; + this->tcpOnlyFilter = NULL; + this->log = NULL; + this->mgmtNodes = NULL; + this->metaNodes = NULL; + this->storageNodes = NULL; + this->targetMapper = NULL; + this->mirrorBuddyGroupMapper = NULL; + this->targetStateStore = NULL; + this->ackStore = NULL; + this->sessions = NULL; + this->chunkDirStore = NULL; + this->nodeOperationStats = NULL; + this->netMessageFactory = NULL; + this->syncedStoragePaths = NULL; + this->storageBenchOperator = NULL; + + this->chunkFetcher = NULL; + + this->dgramListener = NULL; + this->connAcceptor = NULL; + this->statsCollector = NULL; + this->internodeSyncer = NULL; + this->timerQueue = new TimerQueue(); + + this->nextNumaBindTarget = 0; + + this->workersRunning = false; + + this->buddyResyncer = NULL; + this->chunkLockStore = NULL; + + this->dlOpenHandleLibZfs = NULL; + this->libZfsErrorReported = false; +} + +App::~App() +{ + // Note: Logging of the common lib classes is not working here, because this is called + // from class Program (so the thread-specific app-pointer isn't set in this context). + + workersDelete(); + + SAFE_DELETE(this->internodeSyncer); + SAFE_DELETE(this->statsCollector); + SAFE_DELETE(this->connAcceptor); + + streamListenersDelete(); + + SAFE_DELETE(this->buddyResyncer); + SAFE_DELETE(this->chunkFetcher); + SAFE_DELETE(this->dgramListener); + SAFE_DELETE(this->chunkDirStore); + SAFE_DELETE(this->syncedStoragePaths); + SAFE_DELETE(this->netMessageFactory); + SAFE_DELETE(this->nodeOperationStats); + SAFE_DELETE(this->sessions); + SAFE_DELETE(this->ackStore); + + for(MultiWorkQueueMapIter iter = workQueueMap.begin(); iter != workQueueMap.end(); iter++) + delete(iter->second); + + SAFE_DELETE(this->storageNodes); + SAFE_DELETE(this->metaNodes); + SAFE_DELETE(this->mgmtNodes); + this->localNode.reset(); + SAFE_DELETE(this->mirrorBuddyGroupMapper); + SAFE_DELETE(this->targetMapper); + SAFE_DELETE(this->targetStateStore); + SAFE_DELETE(this->log); + SAFE_DELETE(this->tcpOnlyFilter); + SAFE_DELETE(this->netFilter); + SAFE_DELETE(this->storageTargets); + SAFE_DELETE(this->storageBenchOperator); + SAFE_DELETE(this->chunkLockStore); + + SAFE_DELETE(this->cfg); + + delete timerQueue; + + Logger::destroyLogger(); + closelog(); +} + +void App::run() +{ + try + { + openlog(APP_SYSLOG_IDENTIFIER, LOG_NDELAY | LOG_PID | LOG_CONS, LOG_DAEMON); + + this->cfg = new Config(argc, argv); + + runNormal(); + } + catch (InvalidConfigException& e) + { + std::cerr << std::endl; + std::cerr << "Error: " << e.what() << std::endl; + std::cerr << std::endl; + std::cerr << "[BeeGFS Storage Node Version: " << BEEGFS_VERSION << std::endl; + std::cerr << "Refer to the default config file (/etc/beegfs/beegfs-storage.conf)" << std::endl; + std::cerr << "or visit http://www.beegfs.com to find out about configuration options.]" + << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + appResult = APPCODE_INVALID_CONFIG; + return; + } + catch (std::exception& e) + { + std::cerr << std::endl; + std::cerr << "Unrecoverable error: " << e.what() << std::endl; + std::cerr << std::endl; + + if(this->log) + log->logErr(e.what() ); + + appResult = APPCODE_RUNTIME_ERROR; + return; + } +} + +/** + * @throw InvalidConfigException + */ +void App::runNormal() +{ + // numa binding (as early as possible) + + if(cfg->getTuneBindToNumaZone() != -1) // -1 means disable binding + { + bool bindRes = System::bindToNumaNode(cfg->getTuneBindToNumaZone() ); + if(!bindRes) + throw InvalidConfigException("Unable to bind to this NUMA zone: " + + StringTk::intToStr(cfg->getTuneBindToNumaZone() ) ); + } + + + // init basic data objects & storage + NumNodeID localNodeNumID; + + preinitStorage(); // locks target dirs => call before anything else that accesses the disk + + initLogging(); + checkTargetsUUIDs(); + initLocalNodeIDs(localNodeNumID); + initDataObjects(); + initBasicNetwork(); + + initStorage(); + + registerSignalHandler(); + + // detach process + if(cfg->getRunDaemonized() ) + daemonize(); + + log->log(Log_NOTICE, "Built " +#ifdef BEEGFS_NVFS + "with" +#else + "without" +#endif + " NVFS RDMA support."); + + // find RDMA interfaces (based on TCP/IP interfaces) + + // note: we do this here, because when we first create an RDMASocket (and this is done in this + // check), the process opens the verbs device. Recent OFED versions have a check if the + // credentials of the opening process match those of the calling process (not only the values + // are compared, but the pointer is checked for equality). Thus, the first open needs to happen + // after the fork, because we need to access the device in the child process. + findAllowedRDMAInterfaces(localNicList); + + // wait for management node heartbeat (required for localNodeNumID and target pre-registration) + bool mgmtWaitRes = waitForMgmtNode(); + if(!mgmtWaitRes) + { // typically user just pressed ctrl+c in this case + log->logErr("Waiting for beegfs-mgmtd canceled"); + appResult = APPCODE_RUNTIME_ERROR; + return; + } + + // retrieve localNodeNumID from management node (if we don't have it yet) + + if(!localNodeNumID) + { // no local num ID yet => try to retrieve one from mgmt + bool preregisterNodeRes = preregisterNode(localNodeNumID); + if(!preregisterNodeRes) + throw InvalidConfigException("Node pre-registration at management node canceled"); + } + + if(!localNodeNumID) // just a sanity check that should never fail + throw InvalidConfigException("Failed to retrieve numeric local node ID from mgmtd"); + + + auto preregisterTargetsRes = preregisterTargets(localNodeNumID); + if(!preregisterTargetsRes) + throw InvalidConfigException("Target pre-registration at management node failed"); + + storageTargets = new StorageTargets(std::move(*preregisterTargetsRes)); + + initPostTargetRegistration(); + + // we have all local node data now => init localNode + + initLocalNode(localNodeNumID); + initLocalNodeNumIDFile(localNodeNumID); + + bool downloadRes = registerAndDownloadMgmtInfo(); + if(!downloadRes) + { + log->log(1, "Downloading target states from management node failed. Shutting down..."); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + // init components + + try + { + initComponents(); + } + catch(ComponentInitException& e) + { + log->logErr(e.what() ); + log->log(1, "A hard error occurred. Shutting down..."); + appResult = APPCODE_INITIALIZATION_ERROR; + return; + } + + // note: storage nodes & mappings must be downloaded before session restore (needed for mirrors) + restoreSessions(); + + // start component threads and join them + + startComponents(); + + // session restore is finished so delete old session files + // clean shutdown will generate a new session file + deleteSessionFiles(); + + // wait for termination + joinComponents(); + + // clean shutdown (at least no cache loss) => generate a new session file + if(sessions) + storeSessions(); + + // close all client sessions + InternodeSyncer::syncClientSessions({}); + + + log->log(Log_CRITICAL, "All components stopped. Exiting now!"); +} + +void App::initLogging() +{ + // check absolute log path to avoid chdir() problems + Path logStdPath(cfg->getLogStdFile() ); + + if(!logStdPath.empty() && !logStdPath.absolute()) + { + throw InvalidConfigException("Path to log file must be absolute"); + } + + Logger::createLogger(cfg->getLogLevel(), cfg->getLogType(), cfg->getLogNoDate(), + cfg->getLogStdFile(), cfg->getLogNumLines(), cfg->getLogNumRotatedFiles()); + this->log = new LogContext("App"); +} + +/** + * Init basic shared objects like work queues, node stores etc. + */ +void App::initDataObjects() +{ + this->mgmtNodes = new NodeStoreServers(NODETYPE_Mgmt, true); + this->storageNodes = new NodeStoreServers(NODETYPE_Storage, true); + + this->targetMapper = new TargetMapper(); + this->storageNodes->attachTargetMapper(targetMapper); + + this->mirrorBuddyGroupMapper = new MirrorBuddyGroupMapper(targetMapper); + this->storagePoolStore = boost::make_unique(mirrorBuddyGroupMapper, + targetMapper); + this->targetStateStore = new TargetStateStore(NODETYPE_Storage); + this->targetMapper->attachStateStore(targetStateStore); + + this->metaNodes = new NodeStoreServers(NODETYPE_Meta, true); + + this->ackStore = new AcknowledgmentStore(); + + this->sessions = new SessionStore(); + + this->nodeOperationStats = new StorageNodeOpStats(); + + this->syncedStoragePaths = new SyncedStoragePaths(); + + this->chunkDirStore = new ChunkStore(); + + this->chunkLockStore = new ChunkLockStore(); +} + +void App::findAllowedRDMAInterfaces(NicAddressList& outList) const +{ + Config* cfg = this->getConfig(); + + if(cfg->getConnUseRDMA() && RDMASocket::rdmaDevicesExist() ) + { + bool foundRdmaInterfaces = NetworkInterfaceCard::checkAndAddRdmaCapability(outList); + if (foundRdmaInterfaces) + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); // re-sort the niclist + } +} + +void App::findAllowedInterfaces(NicAddressList& outList) const +{ + // discover local NICs and filter them + NetworkInterfaceCard::findAllInterfaces(allowedInterfaces, outList); + + if(outList.empty() ) + throw InvalidConfigException("Couldn't find any usable NIC"); + + outList.sort(NetworkInterfaceCard::NicAddrComp{&allowedInterfaces}); +} + +/** + * Init basic networking data structures. + * + * Note: no RDMA is detected here, because this needs to be done later + */ +void App::initBasicNetwork() +{ + // check if management host is defined + if(!cfg->getSysMgmtdHost().length() ) + throw InvalidConfigException("Management host undefined"); + + // prepare filter for outgoing packets/connections + this->netFilter = new NetFilter(cfg->getConnNetFilterFile() ); + this->tcpOnlyFilter = new NetFilter(cfg->getConnTcpOnlyFilterFile() ); + + Config* cfg = this->getConfig(); + + // prepare filter for interfaces + std::string interfacesList = cfg->getConnInterfacesList(); + if(!interfacesList.empty() ) + { + log->log(Log_DEBUG, "Allowed interfaces: " + interfacesList); + StringTk::explodeEx(interfacesList, ',', true, &allowedInterfaces); + } + + findAllowedInterfaces(localNicList); + + noDefaultRouteNets = std::make_shared(); + if(!initNoDefaultRouteList(noDefaultRouteNets.get())) + throw InvalidConfigException("Failed to parse connNoDefaultRoute"); + + initRoutingTable(); + updateRoutingTable(); + + // prepare factory for incoming messages + this->netMessageFactory = new NetMessageFactory(); +} + +/** + * Loads node num ID from disk if it was set. + * Also handles writing out the deprecation notice for to the old string ID files. + */ +void App::initLocalNodeIDs(NumNodeID& outLocalNodeNumID) +{ + for (const auto& path : cfg->getStorageDirectories()) { + StorageTk::deprecateNodeStringIDFiles(path.str()); + } + + // load nodeNumID file (from primary storage dir) + const auto& storagePath = cfg->getStorageDirectories().front(); + StorageTk::readNumIDFile(storagePath.str(), STORAGETK_NODENUMID_FILENAME, &outLocalNodeNumID); + + // note: localNodeNumID is still 0 here if it wasn't loaded from the file +} + + +/** + * create and attach the localNode object. + */ +void App::initLocalNode(NumNodeID localNodeNumID) +{ + unsigned port = cfg->getConnStoragePort(); + NicAddressList nicList = getLocalNicList(); + + // create localNode object. Note the alias (formerly string ID) is not known at this stage so it + // is set to an empty string. It will be set later by registerAndDownloadMgmtInfo(). + this->localNode = std::make_shared(NODETYPE_Storage, "", localNodeNumID, + port, port, nicList); + + // attach to storageNodes store + storageNodes->setLocalNode(this->localNode); +} + +/** + * Store numID file in each of the storage directories + */ +void App::initLocalNodeNumIDFile(NumNodeID localNodeNumID) +{ + for (const auto& path : cfg->getStorageDirectories()) + StorageTk::createNumIDFile(path.str(), STORAGETK_NODENUMID_FILENAME, localNodeNumID.val()); +} + +/** + * this contains things that would actually live inside initStorage() but need to be + * done at an earlier stage (like working dir locking before log file creation). + * + * note: keep in mind that we don't have the logger here yet, because logging can only be + * initialized after the working dir has been locked within this method. + */ +void App::preinitStorage() +{ + this->pidFileLockFD = createAndLockPIDFile(cfg->getPIDFile() ); // ignored if pidFile not defined + + if (cfg->getStorageDirectories().empty()) + throw InvalidConfigException("No storage target directories defined"); + + for (const auto& path: cfg->getStorageDirectories()) + { + if (!path.absolute()) /* (check to avoid problems after chdir) */ + throw InvalidConfigException("Path to storage target directory must be absolute: " + + path.str()); + + if (!cfg->getStoreAllowFirstRunInit() && !StorageTarget::isTargetDir(path)) + throw InvalidConfigException(std::string("Found uninitialized storage target directory " + "and initialization has been disabled: ") + path.str()); + + auto lockFD = StorageTk::lockWorkingDirectory(path.str()); + if (!lockFD.valid()) + throw InvalidConfigException("Invalid storage directory: locking failed"); + + storageTargetLocks.push_back(std::move(lockFD)); + } +} + +void App::initStorage() +{ + setUmask(); + + // change working dir (got no better place to go, so we change to root dir) + const char* chdirPath = "/"; + + int changeDirRes = chdir(chdirPath); + if(changeDirRes) + { // unable to change working directory + throw InvalidConfigException(std::string("Unable to change working directory to: ") + + chdirPath + "(SysErr: " + System::getErrString() + ")"); + } + + // storage target dirs (create subdirs, storage format file etc) + for (const auto& path : cfg->getStorageDirectories()) + StorageTarget::prepareTargetDir(path); + + // raise file descriptor limit + if(cfg->getTuneProcessFDLimit() ) + { + uint64_t oldLimit; + + bool setFDLimitRes = System::incProcessFDLimit(cfg->getTuneProcessFDLimit(), &oldLimit); + if(!setFDLimitRes) + log->log(Log_CRITICAL, std::string("Unable to increase process resource limit for " + "number of file handles. Proceeding with default limit: ") + + StringTk::uintToStr(oldLimit) + " " + + "(SysErr: " + System::getErrString() + ")"); + } + +} + +/** + * Remaining initialization of common objects that can only happen after the local target info is + * complete (i.e. after preregisterTargets() ). + */ +void App::initPostTargetRegistration() +{ + /* init workQueueMap with one queue per targetID. + requires targetIDs, so can only happen after preregisterTargets(). */ + + const auto addWQ = [&] (const auto& mapping) { + workQueueMap[mapping.first] = new MultiWorkQueue(); + + if (cfg->getTuneUsePerUserMsgQueues()) + workQueueMap[mapping.first]->setIndirectWorkList(new UserWorkContainer()); + }; + + if (cfg->getTuneUsePerTargetWorkers()) + std::for_each(storageTargets->getTargets().begin(), storageTargets->getTargets().end(), addWQ); + else + addWQ(std::make_pair(0, nullptr)); // global worker set => create single targetID 0 + + // init exceeded quota stores + for (const auto& mapping : storageTargets->getTargets()) + exceededQuotaStores.add(mapping.first); +} + +void App::initComponents() +{ + this->log->log(Log_DEBUG, "Initializing components..."); + NicAddressList nicList = getLocalNicList(); + this->dgramListener = new DatagramListener( + netFilter, nicList, ackStore, cfg->getConnStoragePort(), + this->cfg->getConnRestrictOutboundInterfaces() ); + if(cfg->getTuneListenerPrioShift() ) + dgramListener->setPriorityShift(cfg->getTuneListenerPrioShift() ); + + streamListenersInit(); + + unsigned short listenPort = cfg->getConnStoragePort(); + + this->connAcceptor = new ConnAcceptor(this, nicList, listenPort); + + this->statsCollector = new StorageStatsCollector(STATSCOLLECTOR_COLLECT_INTERVAL_MS, + STATSCOLLECTOR_HISTORY_LENGTH); + + this->internodeSyncer = new InternodeSyncer(); + + this->storageBenchOperator = new StorageBenchOperator(); + + this->chunkFetcher = new ChunkFetcher(); + + this->buddyResyncer = new BuddyResyncer(); + + workersInit(); + + this->log->log(Log_DEBUG, "Components initialized."); +} + +void App::startComponents() +{ + log->log(Log_DEBUG, "Starting up components..."); + + // make sure child threads don't receive SIGINT/SIGTERM (blocked signals are inherited) + PThread::blockInterruptSignals(); + + timerQueue->start(); + + this->dgramListener->start(); + + streamListenersStart(); + + this->connAcceptor->start(); + + this->statsCollector->start(); + + this->internodeSyncer->start(); + + timerQueue->enqueue(std::chrono::seconds(30), InternodeSyncer::requestBuddyTargetStates); + + workersStart(); + + PThread::unblockInterruptSignals(); // main app thread may receive SIGINT/SIGTERM + + log->log(Log_DEBUG, "Components running."); +} + +void App::stopComponents() +{ + // note: this method may not wait for termination of the components, because that could + // lead to a deadlock (when calling from signal handler) + + workersStop(); + + if (chunkFetcher) + chunkFetcher->stopFetching(); // ignored if not running + + if(internodeSyncer) + internodeSyncer->selfTerminate(); + + if(statsCollector) + statsCollector->selfTerminate(); + + if(connAcceptor) + connAcceptor->selfTerminate(); + + streamListenersStop(); + + if(dgramListener) + { + dgramListener->selfTerminate(); + dgramListener->sendDummyToSelfUDP(); // for faster termination + } + + if(storageBenchOperator) + storageBenchOperator->shutdownBenchmark(); + + this->selfTerminate(); /* this flag can be noticed by thread-independent methods and is also + required e.g. to let App::waitForMgmtNode() know that it should cancel */ +} + +void App::updateLocalNicList(NicAddressList& localNicList) +{ + std::vector allNodes({ mgmtNodes, metaNodes, storageNodes}); + updateLocalNicListAndRoutes(log, localNicList, allNodes); + localNode->updateInterfaces(0, 0, localNicList); + dgramListener->setLocalNicList(localNicList); + connAcceptor->updateLocalNicList(localNicList); +} + +/** + * Handles expections that lead to the termination of a component. + * Initiates an application shutdown. + */ +void App::handleComponentException(std::exception& e) +{ + const char* logContext = "App (component exception handler)"; + LogContext log(logContext); + + const auto componentName = PThread::getCurrentThreadName(); + + log.logErr( + "The component [" + componentName + "] encountered an unrecoverable error. " + + std::string("[SysErr: ") + System::getErrString() + "] " + + std::string("Exception message: ") + e.what() ); + + log.log(Log_WARNING, "Shutting down..."); + + stopComponents(); +} + +/** + * Called when a network device failure has been detected. + */ +void App::handleNetworkInterfaceFailure(const std::string& devname) +{ + LOG(GENERAL, ERR, "Network interface failure.", + ("Device", devname)); + internodeSyncer->setForceCheckNetwork(); +} + +void App::joinComponents() +{ + log->log(Log_DEBUG, "Joining component threads..."); + + /* (note: we need one thread for which we do an untimed join, so this should be a quite reliably + terminating thread) */ + this->statsCollector->join(); + + workersJoin(); + + // (the ChunkFetcher is not a normal component, so it gets special treatment here) + if(chunkFetcher) + chunkFetcher->waitForStopFetching(); + + waitForComponentTermination(dgramListener); + waitForComponentTermination(connAcceptor); + + streamListenersJoin(); + + waitForComponentTermination(internodeSyncer); + + // (the StorageBenchOperator is not a normal component, so it gets special treatment here) + if(storageBenchOperator) + storageBenchOperator->waitForShutdownBenchmark(); + + closeLibZfs(); +} + +void App::streamListenersInit() +{ + this->numStreamListeners = cfg->getTuneNumStreamListeners(); + + for(unsigned i=0; i < numStreamListeners; i++) + { + StreamListenerV2* listener = new StorageStreamListenerV2( + std::string("StreamLis") + StringTk::uintToStr(i+1), this); + + if(cfg->getTuneListenerPrioShift() ) + listener->setPriorityShift(cfg->getTuneListenerPrioShift() ); + + if(cfg->getTuneUseAggressiveStreamPoll() ) + listener->setUseAggressivePoll(); + + streamLisVec.push_back(listener); + } +} + +void App::workersInit() +{ + unsigned numWorkers = cfg->getTuneNumWorkers(); + + unsigned currentTargetNum= 1; /* targetNum is only added to worker name if there are multiple + target queues (i.e. workQueueMap.size > 1) */ + + for(MultiWorkQueueMapIter iter = workQueueMap.begin(); iter != workQueueMap.end(); iter++) + { + for(unsigned i=0; i < numWorkers; i++) + { + Worker* worker = new Worker( + std::string("Worker") + StringTk::uintToStr(i+1) + + ( (workQueueMap.size() > 1) ? "-" + StringTk::uintToStr(currentTargetNum) : ""), + iter->second, QueueWorkType_INDIRECT); + + worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); + + workerList.push_back(worker); + } + + for(unsigned i=0; i < APP_WORKERS_DIRECT_NUM; i++) + { + Worker* worker = new Worker( + std::string("DirectWorker") + StringTk::uintToStr(i+1) + + ( (workQueueMap.size() > 1) ? "-" + StringTk::uintToStr(currentTargetNum) : ""), + iter->second, QueueWorkType_DIRECT); + + worker->setBufLens(cfg->getTuneWorkerBufSize(), cfg->getTuneWorkerBufSize() ); + + workerList.push_back(worker); + } + + currentTargetNum++; + } +} + +void App::streamListenersStart() +{ + unsigned numNumaNodes = System::getNumNumaNodes(); + + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + if(cfg->getTuneListenerNumaAffinity() ) + (*iter)->startOnNumaNode( (++nextNumaBindTarget) % numNumaNodes); + else + (*iter)->start(); + } +} + +void App::workersStart() +{ + unsigned numNumaNodes = System::getNumNumaNodes(); + + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + if(cfg->getTuneWorkerNumaAffinity() ) + (*iter)->startOnNumaNode( (++nextNumaBindTarget) % numNumaNodes); + else + (*iter)->start(); + } + + const std::lock_guard lock(mutexWorkersRunning); + this->workersRunning = true; +} + +void App::streamListenersStop() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + (*iter)->selfTerminate(); + } +} + +void App::workersStop() +{ + // need two loops because we don't know if the worker that handles the work will be the same that + // received the self-terminate-request + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + (*iter)->selfTerminate(); + } + + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + (*iter)->getWorkQueue()->addDirectWork(new DummyWork() ); + } +} + +void App::streamListenersDelete() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + delete(*iter); + } + + streamLisVec.clear(); +} + +void App::workersDelete() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + delete(*iter); + } + + workerList.clear(); +} + +void App::streamListenersJoin() +{ + for(StreamLisVecIter iter = streamLisVec.begin(); iter != streamLisVec.end(); iter++) + { + waitForComponentTermination(*iter); + } +} + +void App::workersJoin() +{ + for(WorkerListIter iter = workerList.begin(); iter != workerList.end(); iter++) + { + waitForComponentTermination(*iter); + } + + const std::lock_guard lock(mutexWorkersRunning); + this->workersRunning = false; +} + +void App::logInfos() +{ + // print software version (BEEGFS_VERSION) + log->log(Log_CRITICAL, std::string("Version: ") + BEEGFS_VERSION); + + // print debug version info + LOG_DEBUG_CONTEXT(*log, Log_CRITICAL, "--DEBUG VERSION--"); + + // print local nodeIDs + log->log(Log_WARNING, "LocalNode: " + localNode->getNodeIDWithTypeStr() ); + + // list usable network interfaces + NicAddressList nicList = getLocalNicList(); + logUsableNICs(log, nicList); + + // print net filters + if(netFilter->getNumFilterEntries() ) + { + log->log(Log_WARNING, std::string("Net filters: ") + + StringTk::uintToStr(netFilter->getNumFilterEntries() ) ); + } + + if(tcpOnlyFilter->getNumFilterEntries() ) + { + this->log->log(Log_WARNING, std::string("TCP-only filters: ") + + StringTk::uintToStr(tcpOnlyFilter->getNumFilterEntries() ) ); + } + + // storage tragets + log->log(Log_WARNING, std::string("Storage targets: ") + + StringTk::uintToStr(storageTargets->getTargets().size())); + + // print numa info + // (getTuneBindToNumaZone==-1 means disable binding) + if(cfg->getTuneListenerNumaAffinity() || cfg->getTuneWorkerNumaAffinity() || + (cfg->getTuneBindToNumaZone() != -1) ) + { + unsigned numNumaNodes = System::getNumNumaNodes(); + + /* note: we use the term "numa areas" instead of "numa nodes" in log messages to avoid + confusion with cluster "nodes" */ + + log->log(Log_NOTICE, std::string("NUMA areas: ") + StringTk::uintToStr(numNumaNodes) ); + + for(unsigned nodeNum=0; nodeNum < numNumaNodes; nodeNum++) + { // print core list for each numa node + cpu_set_t cpuSet; + + System::getNumaCoresByNode(nodeNum, &cpuSet); + + // create core list for current numa node + + std::string coreListStr; + for(unsigned coreNum = 0; coreNum < CPU_SETSIZE; coreNum++) + { + if(CPU_ISSET(coreNum, &cpuSet) ) + coreListStr += StringTk::uintToStr(coreNum) + " "; + } + + log->log(Log_SPAM, "NUMA area " + StringTk::uintToStr(nodeNum) + " cores: " + coreListStr); + } + } +} + +void App::setUmask() +{ + ::umask(APP_STORAGE_UMASK); +} + +void App::daemonize() +{ + int nochdir = 1; // 1 to keep working directory + int noclose = 0; // 1 to keep stdin/-out/-err open + + this->log->log(Log_DEBUG, std::string("Detaching process...") ); + + int detachRes = daemon(nochdir, noclose); + if(detachRes == -1) + throw InvalidConfigException(std::string("Unable to detach process. SysErr: ") + + System::getErrString() ); + + updateLockedPIDFile(pidFileLockFD); // ignored if pidFileFD is -1 +} + +void App::registerSignalHandler() +{ + signal(SIGINT, App::signalHandler); + signal(SIGTERM, App::signalHandler); +} + +void App::signalHandler(int sig) +{ + App* app = Program::getApp(); + + Logger* log = Logger::getLogger(); + const char* logContext = "App::signalHandler"; + + // note: this might deadlock if the signal was thrown while the logger mutex is locked by the + // application thread (depending on whether the default mutex style is recursive). but + // even recursive mutexes are not acceptable in this case. + // we need something like a timed lock for the log mutex. if it succeeds within a + // few seconds, we know that we didn't hold the mutex lock. otherwise we simply skip the + // log message. this will only work if the mutex is non-recusive (which is unknown for + // the default mutex style). + // but it is very unlikely that the application thread holds the log mutex, because it + // joins the component threads and so it doesn't do anything else but sleeping! + + switch(sig) + { + case SIGINT: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received a SIGINT. Shutting down..."); + } break; + + case SIGTERM: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received a SIGTERM. Shutting down..."); + } break; + + default: + { + signal(sig, SIG_DFL); // reset the handler to its default + log->log(1, logContext, "Received an unknown signal. Shutting down..."); + } break; + } + + app->stopComponents(); +} + +/** + * Request mgmt heartbeat and wait for the mgmt node to appear in nodestore. + * + * @return true if mgmt heartbeat received, false on error or thread selftermination order + */ +bool App::waitForMgmtNode() +{ + const unsigned waitTimeoutMS = 0; // infinite wait + const unsigned nameResolutionRetries = 3; + + unsigned udpListenPort = cfg->getConnStoragePort(); + unsigned udpMgmtdPort = cfg->getConnMgmtdPort(); + std::string mgmtdHost = cfg->getSysMgmtdHost(); + NicAddressList nicList = getLocalNicList(); + + RegistrationDatagramListener regDGramLis(netFilter, nicList, ackStore, udpListenPort, + this->cfg->getConnRestrictOutboundInterfaces() ); + + regDGramLis.start(); + + log->log(Log_CRITICAL, "Waiting for beegfs-mgmtd@" + + mgmtdHost + ":" + StringTk::uintToStr(udpMgmtdPort) + "..."); + + + bool gotMgmtd = NodesTk::waitForMgmtHeartbeat( + this, ®DGramLis, mgmtNodes, mgmtdHost, udpMgmtdPort, waitTimeoutMS, nameResolutionRetries); + + regDGramLis.selfTerminate(); + regDGramLis.sendDummyToSelfUDP(); // for faster termination + + regDGramLis.join(); + + return gotMgmtd; +} + +/** + * Pre-register node to get a numeric ID from mgmt. + * This will only do something if we don't have localNodeNumID yet. + * + * @return true if pre-registration successful and localNodeNumID set. + */ +bool App::preregisterNode(NumNodeID& outLocalNodeNumID) +{ + const char* logContext = "Preregister node"; + + static bool registrationFailureLogged = false; // to avoid log spamming + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + { + LogContext(logContext).logErr( + "Unexpected: No management node found in store during node pre-registration."); + return false; + } + + NicAddressList nicList = getLocalNicList(); + // In BeeGFS 8 string IDs were replaced with aliases. The mgmtd now ignores the alias provided in + // the RegisterNodeMsg for meta nodes so just it can just be set to an empty string. It will be + // set later on for the local node as part of registerAndDownloadMgmtInfo(). + RegisterNodeMsg msg("", outLocalNodeNumID, NODETYPE_Storage, &nicList, + cfg->getConnStoragePort(), cfg->getConnStoragePort() ); + auto uuid = UUID::getMachineUUID(); + if (uuid.empty()) { + LogContext(logContext).log(Log_CRITICAL, + "Couldn't determine UUID for machine. Node registration not possible."); + return false; + } + msg.setMachineUUID(uuid); + + Time startTime; + Time lastRetryTime; + unsigned nextRetryDelayMS = 0; + + // wait for mgmt node to appear and periodically resend request + /* note: we usually expect not to loop here, because we already waited for mgmtd in + waitForMgmtNode(), so mgmt should respond immediately. */ + + while(!outLocalNodeNumID && !getSelfTerminate() ) + { + if(lastRetryTime.elapsedMS() < nextRetryDelayMS) + { // wait some time between retries + waitForSelfTerminateOrder(nextRetryDelayMS); + if(getSelfTerminate() ) + break; + } + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_RegisterNodeResp); + + if (respMsg) + { // communication successful + RegisterNodeRespMsg* respMsgCast = (RegisterNodeRespMsg*)respMsg.get(); + + outLocalNodeNumID = respMsgCast->getNodeNumID(); + + if(!outLocalNodeNumID) + { // mgmt rejected our registration + LogContext(logContext).logErr( + "ID reservation request was rejected by this management node: " + + mgmtNode->getTypedNodeID() ); + } + else + LogContext(logContext).log(Log_WARNING, "Node ID reservation successful."); + + break; + } + + // comm failed => log status message + + if(!registrationFailureLogged) + { + LogContext(logContext).log(Log_CRITICAL, + "Node ID reservation failed. Management node offline? Will keep on trying..."); + registrationFailureLogged = true; + } + + // calculate next retry wait time + + lastRetryTime.setToNow(); + nextRetryDelayMS = NodesTk::getRetryDelayMS(startTime.elapsedMS() ); + } + + return bool(outLocalNodeNumID); +} + +/** + * Pre-register all currently unmapped targets to get a numeric ID from mgmt. + * + * @return true if pre-registration successful + * @throw InvalidConfigException on target access error + */ +boost::optional>> App::preregisterTargets( + const NumNodeID localNodeNumID) +{ + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + { + log->logErr("Unexpected: No management node found in store during target pre-registration."); + return boost::none; + } + + // validate IDs for mapped targets (i.e. targets that already have a numID) + + std::map> targets; + + for (const auto& path : cfg->getStorageDirectories()) + { + std::string targetID; + uint16_t targetNumID; + uint16_t newTargetNumID; + + // read or create target string ID file + StorageTk::readOrCreateTargetIDFile(path.str(), localNodeNumID, &targetID); + + // read target numeric ID file + StorageTk::readNumTargetIDFile(path.str(), STORAGETK_TARGETNUMID_FILENAME, &targetNumID); + + // sanity check: make sure we don't have numID without stringID + if(targetNumID && targetID.empty() ) + { + log->logErr("Target sanity problem: " + "Found targetNumID, but no corresponding string ID: " + path.str()); + + return boost::none; + } + + + bool registerRes = preregisterTarget(*mgmtNode, targetID, targetNumID, &newTargetNumID); + if(!registerRes) + { // registration rejected + return boost::none; + } + + if(!targetNumID) + { // got a new numID for this target + log->log(Log_DEBUG, "Retrieved new numeric target ID: " + + targetID + " -> " + StringTk::uintToStr(newTargetNumID) ); + + StorageTk::createNumIDFile(path.str(), STORAGETK_TARGETNUMID_FILENAME, newTargetNumID); + } + + try + { + targets[newTargetNumID] = boost::make_unique(path, newTargetNumID, + *timerQueue, *mgmtNodes, *mirrorBuddyGroupMapper); + targets[newTargetNumID]->setCleanShutdown(StorageTk::checkSessionFileExists(path.str())); + } + catch (const std::system_error& e) + { + LOG(GENERAL, ERR, "Error while initializing target directory.", + ("component", e.what()), + ("error", e.code().message())); + return boost::none; + } + } + + return targets; +} + +/** + * Pre-register target to get a numeric ID from mgmt (or to validate an existing numID). + * + * @param outNewTargetNumID the new targetNumID from mgmt if pre-registration was successful + * @return true if pre-registration successful + */ +bool App::preregisterTarget(Node& mgmtNode, std::string targetID, uint16_t targetNumID, + uint16_t* outNewTargetNumID) +{ + static bool registrationFailureLogged = false; // to avoid log spamming + + *outNewTargetNumID = 0; // "0" means undefined + + RegisterTargetMsg msg(targetID.c_str(), targetNumID); + + Time startTime; + Time lastRetryTime; + unsigned nextRetryDelayMS = 0; + + // wait for mgmt node response and periodically resend request + /* note: we usually expect not to loop here, because we already waited for mgmtd in + waitForMgmtNode(), so mgmt should respond immediately. */ + + while(!getSelfTerminate() ) + { + if(lastRetryTime.elapsedMS() < nextRetryDelayMS) + { // wait some time between retries + waitForSelfTerminateOrder(nextRetryDelayMS); + if(getSelfTerminate() ) + break; + } + + const auto respMsg = MessagingTk::requestResponse(mgmtNode, msg, + NETMSGTYPE_RegisterTargetResp); + + if (respMsg) + { // communication successful + RegisterTargetRespMsg* respMsgCast = (RegisterTargetRespMsg*)respMsg.get(); + + *outNewTargetNumID = respMsgCast->getTargetNumID(); + + if(!*outNewTargetNumID) + { // mgmt rejected target registration + log->logErr("Target ID reservation request was rejected by this mgmt node: " + + mgmtNode.getTypedNodeID() ); + } + else + log->log(Log_DEBUG, "Target ID reservation successful."); + + break; + } + + // comm failed => log status message + + if(!registrationFailureLogged) + { + log->log(Log_CRITICAL, "Target ID reservation failed. Management node offline? " + "Will keep on trying..."); + registrationFailureLogged = true; + } + + // calculate next retry wait time + + lastRetryTime.setToNow(); + nextRetryDelayMS = NodesTk::getRetryDelayMS(startTime.elapsedMS() ); + } + + + return (*outNewTargetNumID != 0); +} + +/* + * register ourself, our targets and download all nodes, mappings etc from mgmt. + * + * @param false if interrupted before download completed. + */ +bool App::registerAndDownloadMgmtInfo() +{ + Config* cfg = this->getConfig(); + + int retrySleepTimeMS = 10000; // 10sec + + unsigned udpListenPort = cfg->getConnStoragePort(); + bool allSuccessful = false; + NicAddressList nicList = getLocalNicList(); + + // start temporary registration datagram listener + + RegistrationDatagramListener regDGramLis(netFilter, nicList, ackStore, udpListenPort, + this->cfg->getConnRestrictOutboundInterfaces() ); + + regDGramLis.start(); + + + // loop until we're registered and everything is downloaded (or until we got interrupted) + + do + { + // register ourself + // (note: node registration needs to be done before downloads to get notified of updates) + + if(!InternodeSyncer::registerNode(®DGramLis) || + !InternodeSyncer::registerTargetMappings() ) + continue; + + // download all mgmt info + + if(!InternodeSyncer::downloadAndSyncNodes() || + !InternodeSyncer::downloadAndSyncTargetMappings() || + !InternodeSyncer::downloadAndSyncStoragePools() || + !InternodeSyncer::downloadAndSyncMirrorBuddyGroups() ) + continue; + + UInt16List targetIDs; + UInt8List reachabilityStates; + UInt8List consistencyStates; + if (!InternodeSyncer::downloadAndSyncTargetStates( + targetIDs, reachabilityStates, consistencyStates) ) + continue; + + TargetStateMap statesFromMgmtd; + StorageTargets::fillTargetStateMap(targetIDs, reachabilityStates, consistencyStates, + statesFromMgmtd); + + TargetStateMap localChangedStates; + storageTargets->decideResync(statesFromMgmtd, localChangedStates); + + for (ZipIterRange + it(targetIDs, reachabilityStates, consistencyStates); + !it.empty(); ++it) + { + const auto change = localChangedStates.find(*it()->first); + if (change != localChangedStates.end()) + { + *it()->second = change->second.reachabilityState; + *it()->third = change->second.consistencyState; + } + } + + + targetStateStore->syncStatesFromLists(targetIDs, reachabilityStates, consistencyStates); + + // If a local primary target needs a resync, wait for poffline timeout before reporting the + // target to mgmt. this ensures that the target will never be use by clients that haven't yet + // seen the state update to needs-resync. + const std::chrono::milliseconds timeout(OfflineWaitTimeoutTk::calculate(cfg)); + for (const auto& mapping : storageTargets->getTargets()) + { + auto& target = *mapping.second; + + if (target.getConsistencyState() == TargetConsistencyState_NEEDS_RESYNC && + mirrorBuddyGroupMapper->getBuddyState(target.getID()) == BuddyState_PRIMARY) + target.setOfflineTimeout(timeout); + } + + if (!InternodeSyncer::downloadAllExceededQuotaLists(storageTargets->getTargets())) + continue; + + // all done + + allSuccessful = true; + break; + + } while(!waitForSelfTerminateOrder(retrySleepTimeMS) ); + + + // stop temporary registration datagram listener + + regDGramLis.selfTerminate(); + regDGramLis.sendDummyToSelfUDP(); // for faster termination + + regDGramLis.join(); + + if(allSuccessful) + log->log(Log_NOTICE, "Registration and management info download complete."); + + + return allSuccessful; +} + +bool App::restoreSessions() +{ + bool retVal = true; + + for (const auto& mapping : storageTargets->getTargets()) + { + auto& target = *mapping.second; + + const auto path = target.getPath().str() + "/" + STORAGETK_SESSIONS_BACKUP_FILE_NAME; + + bool pathRes = StorageTk::pathExists(path); + if(!pathRes) + continue; + + bool loadRes = this->sessions->loadFromFile(path, mapping.first); + if(!loadRes) + { + this->log->logErr("Could not restore all sessions from file " + path + " ; targetID: " + + StringTk::uintToStr(mapping.first) ); + retVal = false; + } + } + + this->log->log(Log_NOTICE, StringTk::uintToStr(this->sessions->getSize() ) + + " sessions restored."); + + return retVal; +} + +bool App::storeSessions() +{ + bool retVal = true; + + for (const auto& mapping : storageTargets->getTargets()) + { + auto& target = *mapping.second; + + const auto path = target.getPath().str() + "/" + STORAGETK_SESSIONS_BACKUP_FILE_NAME; + + bool pathRes = StorageTk::pathExists(path); + if(pathRes) + this->log->log(Log_WARNING, "Overwriting existing session file: " + path); + + bool saveRes = this->sessions->saveToFile(path, mapping.first); + if(!saveRes) + { + this->log->logErr("Could not store all sessions to file " + path + "; " + "targetID: " + StringTk::uintToStr(mapping.first) ); + retVal = false; + } + } + + if(retVal) + { + this->log->log(Log_NOTICE, StringTk::uintToStr(this->sessions->getSize() ) + + " sessions stored."); + } + + return retVal; +} + +bool App::deleteSessionFiles() +{ + bool retVal = true; + + for (const auto& mapping : storageTargets->getTargets()) + { + auto& target = *mapping.second; + + const auto path = target.getPath().str() + "/" + STORAGETK_SESSIONS_BACKUP_FILE_NAME; + + bool pathRes = StorageTk::pathExists(path); + if(!pathRes) + continue; + + if(remove(path.c_str() ) ) + { + this->log->logErr("Could not remove session file " + path + " ; targetID: " + + StringTk::uintToStr(mapping.first) + "; SysErr: " + System::getErrString() ); + retVal = false; + } + } + + return retVal; +} + +bool App::openLibZfs() +{ + if(!dlOpenHandleLibZfs) + { + dlOpenHandleLibZfs = dlopen(APP_LIB_ZFS_NAME, RTLD_LAZY); + + if(!dlOpenHandleLibZfs) + { + LOG(GENERAL, ERR, "Error loading " + std::string(APP_LIB_ZFS_NAME) + ". " + "Please make sure the libzfs2 development packages are installed.", + ("System error", dlerror())); + libZfsErrorReported = true; + + return false; + } + } + + return true; +} + +bool App::closeLibZfs() +{ + if(dlOpenHandleLibZfs) + { + if(dlclose(dlOpenHandleLibZfs) ) + { + LOG(GENERAL, ERR, "Error closing " + std::string(APP_LIB_ZFS_NAME) + ".", + ("System error", dlerror())); + libZfsErrorReported = true; + return false; + } + } + + return true; +} + +void App::checkTargetsUUIDs() +{ + if (!cfg->getStoreFsUUID().empty()) + { + std::list paths = cfg->getStorageDirectories(); + std::list uuid_strs = cfg->getStoreFsUUID(); + + if (paths.size() != uuid_strs.size()) { + throw InvalidConfigException("Storage path list and storage UUID list have different sizes"); + } + + auto path = paths.begin(); + auto cfg_uuid_str = uuid_strs.begin(); + + for(; path != paths.end() && cfg_uuid_str != uuid_strs.end(); ++path, ++cfg_uuid_str) { + std::string uuid_str = UUID::getFsUUID(path->str()); + + if (*cfg_uuid_str != uuid_str) + { + throw InvalidConfigException("UUID of the file system under the storage target " + + path->str() + " (" + uuid_str + + ") does not match the one configured (" + *cfg_uuid_str + ")"); + } + + } + } + else + { + LOG(GENERAL, WARNING, "UUIDs of targets underlying file systems have not been configured and will " + "therefore not be checked. To prevent starting the server accidentally with the wrong " + "data, it is strongly recommended to set the storeFsUUID config parameter to " + "the appropriate UUIDs."); + } + +} diff --git a/storage/source/app/App.h b/storage/source/app/App.h new file mode 100644 index 0000000..c5b4508 --- /dev/null +++ b/storage/source/app/App.h @@ -0,0 +1,424 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#ifndef BEEGFS_VERSION + #error BEEGFS_VERSION undefined +#endif + +// program return codes +#define APPCODE_NO_ERROR 0 +#define APPCODE_INVALID_CONFIG 1 +#define APPCODE_INITIALIZATION_ERROR 2 +#define APPCODE_RUNTIME_ERROR 3 + + +typedef std::list WorkerList; +typedef WorkerList::iterator WorkerListIter; + +typedef std::vector StreamLisVec; +typedef StreamLisVec::iterator StreamLisVecIter; + + +// forward declarations +class LogContext; + +class App : public AbstractApp +{ + public: + App(int argc, char** argv); + virtual ~App(); + + virtual void run() override; + + virtual void stopComponents() override; + virtual void handleComponentException(std::exception& e) override; + virtual void handleNetworkInterfaceFailure(const std::string& devname) override; + + void handleNetworkInterfacesChanged(NicAddressList nicList); + + private: + int appResult; + int argc; + char** argv; + + Config* cfg; + LogContext* log; + std::list allowedInterfaces; + + LockFD pidFileLockFD; + std::vector storageTargetLocks; + + NetFilter* netFilter; // empty filter means "all nets allowed" + NetFilter* tcpOnlyFilter; // for IPs that allow only plain TCP (no RDMA etc) + std::shared_ptr localNode; + + NodeStoreServers* mgmtNodes; + NodeStoreServers* metaNodes; // needed for backward communication introduced with GAM integration + NodeStoreServers* storageNodes; + + TargetMapper* targetMapper; + MirrorBuddyGroupMapper* mirrorBuddyGroupMapper; // maps targets to mirrorBuddyGroups + TargetStateStore* targetStateStore; // map storage targets to a state + + MultiWorkQueueMap workQueueMap; // maps targetIDs to WorkQueues + SessionStore* sessions; + StorageNodeOpStats* nodeOperationStats; // file system operation statistics + AcknowledgmentStore* ackStore; + NetMessageFactory* netMessageFactory; + + StorageTargets* storageTargets; // target IDs and corresponding storage paths + SyncedStoragePaths* syncedStoragePaths; // serializes access to paths (=> entryIDs) + StorageBenchOperator* storageBenchOperator; // benchmark for the storage + + DatagramListener* dgramListener; + ConnAcceptor* connAcceptor; + StatsCollector* statsCollector; + InternodeSyncer* internodeSyncer; + TimerQueue* timerQueue; + + ChunkFetcher* chunkFetcher; + + unsigned numStreamListeners; // value copied from cfg (for performance) + StreamLisVec streamLisVec; + + WorkerList workerList; + bool workersRunning; + Mutex mutexWorkersRunning; + + ChunkStore* chunkDirStore; + + unsigned nextNumaBindTarget; // the numa node to which we will bind the next component thread + + ExceededQuotaPerTarget exceededQuotaStores; + + BuddyResyncer* buddyResyncer; + ChunkLockStore* chunkLockStore; + + std::unique_ptr storagePoolStore; + + void* dlOpenHandleLibZfs; // handle of the libzfs from dlopen + bool libZfsErrorReported; + + void runNormal(); + + void streamListenersInit(); + void streamListenersStart(); + void streamListenersStop(); + void streamListenersDelete(); + void streamListenersJoin(); + + void workersInit(); + void workersStart(); + void workersStop(); + void workersDelete(); + void workersJoin(); + + void initLogging(); + void initDataObjects(); + void initBasicNetwork(); + void initLocalNodeIDs(NumNodeID& outLocalNodeNumID); + void initLocalNode(NumNodeID localNodeNumID); + void initLocalNodeNumIDFile(NumNodeID localNodeNumID) ; + void preinitStorage(); + void checkTargetsUUIDs(); + void initStorage(); + void initPostTargetRegistration(); + void initComponents(); + + void startComponents(); + void joinComponents(); + + bool waitForMgmtNode(); + bool preregisterNode(NumNodeID& outLocalNodeNumID); + boost::optional>> preregisterTargets( + const NumNodeID localNodeNumID); + bool preregisterTarget(Node& mgmtNode, std::string targetID, uint16_t targetNumID, + uint16_t* outNewTargetNumID); + bool registerAndDownloadMgmtInfo(); + + void logInfos(); + + void setUmask(); + + void daemonize(); + + void registerSignalHandler(); + static void signalHandler(int sig); + + bool restoreSessions(); + bool storeSessions(); + bool deleteSessionFiles(); + + bool openLibZfs(); + bool closeLibZfs(); + + + public: + /** + * Get one of the available stream listeners based on the socket file descriptor number. + * This is to load-balance the sockets over all available stream listeners and ensure that + * sockets are not bouncing between different stream listeners. + * + * Note that IB connections eat two fd numbers, so 2 and multiples of 2 might not be a good + * value for number of stream listeners. + */ + virtual StreamListenerV2* getStreamListenerByFD(int fd) override + { + return streamLisVec[fd % numStreamListeners]; + } + + // getters & setters + virtual const ICommonConfig* getCommonConfig() const override + { + return cfg; + } + + virtual const NetFilter* getNetFilter() const override + { + return netFilter; + } + + virtual const NetFilter* getTcpOnlyFilter() const override + { + return tcpOnlyFilter; + } + + virtual const AbstractNetMessageFactory* getNetMessageFactory() const override + { + return netMessageFactory; + } + + AcknowledgmentStore* getAckStore() const + { + return ackStore; + } + + Config* getConfig() const + { + return cfg; + } + + void updateLocalNicList(NicAddressList& localNicList); + + Node& getLocalNode() const + { + return *localNode; + } + + NodeStoreServers* getMgmtNodes() const + { + return mgmtNodes; + } + + NodeStoreServers* getMetaNodes() const + { + return metaNodes; + } + + NodeStoreServers* getStorageNodes() const + { + return storageNodes; + } + + TargetMapper* getTargetMapper() const + { + return targetMapper; + } + + MirrorBuddyGroupMapper* getMirrorBuddyGroupMapper() const + { + return mirrorBuddyGroupMapper; + } + + TargetStateStore* getTargetStateStore() const + { + return targetStateStore; + } + + MultiWorkQueue* getWorkQueue(uint16_t targetID) const + { + MultiWorkQueueMapCIter iter = workQueueMap.find(targetID); + + if(iter != workQueueMap.end() ) + return iter->second; + + /* note: it's not unusual to not find given targetID, e.g. + - when per-target queues are disabled + - or when server restarted without one of its targets (and clients don't know that) + - or if client couldn't provide targetID because it's not a target message */ + + return workQueueMap.begin()->second; + } + + MultiWorkQueueMap* getWorkQueueMap() + { + return &workQueueMap; + } + + SessionStore* getSessions() const + { + return sessions; + } + + StorageNodeOpStats* getNodeOpStats() const + { + return nodeOperationStats; + } + + StorageTargets* getStorageTargets() const + { + return storageTargets; + } + + SyncedStoragePaths* getSyncedStoragePaths() const + { + return syncedStoragePaths; + } + + StorageBenchOperator* getStorageBenchOperator() const + { + return this->storageBenchOperator; + } + + DatagramListener* getDatagramListener() const + { + return dgramListener; + } + + const StreamLisVec* getStreamListenerVec() const + { + return &streamLisVec; + } + + StatsCollector* getStatsCollector() const + { + return statsCollector; + } + + InternodeSyncer* getInternodeSyncer() const + { + return internodeSyncer; + } + + TimerQueue* getTimerQueue() const + { + return timerQueue; + } + + int getAppResult() const + { + return appResult; + } + + bool getWorkersRunning() + { + const std::lock_guard lock(mutexWorkersRunning); + return this->workersRunning; + } + + ChunkStore* getChunkDirStore() const + { + return this->chunkDirStore; + } + + ChunkFetcher* getChunkFetcher() const + { + return this->chunkFetcher; + } + + const ExceededQuotaPerTarget* getExceededQuotaStores() const + { + return &exceededQuotaStores; + } + + BuddyResyncer* getBuddyResyncer() const + { + return this->buddyResyncer; + } + + ChunkLockStore* getChunkLockStore() const + { + return chunkLockStore; + } + + WorkerList* getWorkers() + { + return &workerList; + } + + StoragePoolStore* getStoragePoolStore() const + { + return storagePoolStore.get(); + } + + void setLibZfsErrorReported(bool isReported) + { + libZfsErrorReported = isReported; + } + + void* getDlOpenHandleLibZfs() + { + if(dlOpenHandleLibZfs) + return dlOpenHandleLibZfs; + else + if(cfg->getQuotaDisableZfsSupport() ) + { + if(!libZfsErrorReported) + { + LOG(QUOTA, ERR, "Quota support for ZFS is disabled."); + libZfsErrorReported = true; + } + } + else + if(!libZfsErrorReported) + openLibZfs(); + + return dlOpenHandleLibZfs; + } + + bool isDlOpenHandleLibZfsValid() + { + if(dlOpenHandleLibZfs) + return true; + + return false; + } + + void findAllowedInterfaces(NicAddressList& outList) const; + void findAllowedRDMAInterfaces(NicAddressList& outList) const; + + +}; diff --git a/storage/source/app/config/Config.cpp b/storage/source/app/config/Config.cpp new file mode 100644 index 0000000..a920be0 --- /dev/null +++ b/storage/source/app/config/Config.cpp @@ -0,0 +1,256 @@ +#include +#include +#include "Config.h" + +#define CONFIG_DEFAULT_CFGFILENAME "/etc/beegfs/beegfs-storage.conf" + +#define CONFIG_STORAGETARGETS_DELIMITER ',' + + +Config::Config(int argc, char** argv) : + AbstractConfig(argc, argv) +{ + initConfig(argc, argv, true); +} + +/** + * Sets the default values for each configurable in the configMap. + * + * @param addDashes currently unused + */ +void Config::loadDefaults(bool addDashes) +{ + AbstractConfig::loadDefaults(); + + // re-definitions + configMapRedefine("cfgFile", ""); + + // own definitions + configMapRedefine("connInterfacesFile", ""); + configMapRedefine("connInterfacesList", ""); + + configMapRedefine("storeStorageDirectory", ""); + configMapRedefine("storeFsUUID", ""); + configMapRedefine("storeAllowFirstRunInit", "true"); + + configMapRedefine("tuneNumStreamListeners", "1"); + configMapRedefine("tuneNumWorkers", "8"); + configMapRedefine("tuneWorkerBufSize", "4m"); + configMapRedefine("tuneProcessFDLimit", "50000"); + configMapRedefine("tuneWorkerNumaAffinity", "false"); + configMapRedefine("tuneListenerNumaAffinity", "false"); + configMapRedefine("tuneListenerPrioShift", "-1"); + configMapRedefine("tuneBindToNumaZone", ""); + configMapRedefine("tuneFileReadSize", "32k"); + configMapRedefine("tuneFileReadAheadTriggerSize", "4m"); + configMapRedefine("tuneFileReadAheadSize", "0"); + configMapRedefine("tuneFileWriteSize", "64k"); + configMapRedefine("tuneFileWriteSyncSize", "0"); + configMapRedefine("tuneUsePerUserMsgQueues", "false"); + configMapRedefine("tuneDirCacheLimit", "1024"); + configMapRedefine("tuneEarlyStat", "false"); + configMapRedefine("tuneNumResyncSlaves", "12"); + configMapRedefine("tuneNumResyncGatherSlaves", "6"); + configMapRedefine("tuneUseAggressiveStreamPoll", "false"); + configMapRedefine("tuneUsePerTargetWorkers", "true"); + + configMapRedefine("quotaEnableEnforcement", "false"); + configMapRedefine("quotaDisableZfsSupport", "false"); + + configMapRedefine("sysResyncSafetyThresholdMins", "10"); + configMapRedefine("sysTargetOfflineTimeoutSecs", "180"); + + configMapRedefine("runDaemonized", "false"); + + configMapRedefine("pidFile", ""); +} + +/** + * @param addDashes currently usused + */ +void Config::applyConfigMap(bool enableException, bool addDashes) +{ + AbstractConfig::applyConfigMap(false); + + for (StringMapIter iter = configMap.begin(); iter != configMap.end();) + { + bool unknownElement = false; + + if (iter->first == std::string("logType")) + { + if (iter->second == "syslog") + { + logType = LogType_SYSLOG; + } + else if (iter->second == "logfile") + { + logType = LogType_LOGFILE; + } + else + { + throw InvalidConfigException("The value of config argument logType is invalid."); + } + } + else if (iter->first == std::string("connInterfacesFile")) + connInterfacesFile = iter->second; + else if (iter->first == std::string("connInterfacesList")) + connInterfacesList = iter->second; + else if (iter->first == std::string("storeStorageDirectory")) + { + storageDirectories.clear(); + + std::list split; + + StringTk::explode(iter->second, CONFIG_STORAGETARGETS_DELIMITER, &split); + + std::transform( + split.begin(), split.end(), + std::back_inserter(storageDirectories), + [] (const std::string& p) { + return Path(StringTk::trim(p)); + }); + storageDirectories.remove_if(std::mem_fn(&Path::empty)); + } + else if (iter->first == std::string("storeFsUUID")) + { + storeFsUUID.clear(); + + std::list split; + + StringTk::explode(iter->second, CONFIG_STORAGETARGETS_DELIMITER, &split); + + std::transform( + split.begin(), split.end(), + std::back_inserter(storeFsUUID), + [] (const std::string& p) { + return StringTk::trim(p); + }); + storeFsUUID.remove_if(std::mem_fn(&std::string::empty)); + } + else if (iter->first == std::string("storeAllowFirstRunInit")) + storeAllowFirstRunInit = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneNumStreamListeners")) + tuneNumStreamListeners = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneNumWorkers")) + tuneNumWorkers = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneWorkerBufSize")) + tuneWorkerBufSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneProcessFDLimit")) + tuneProcessFDLimit = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneWorkerNumaAffinity")) + tuneWorkerNumaAffinity = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneListenerNumaAffinity")) + tuneListenerNumaAffinity = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneBindToNumaZone")) + { + if (iter->second.empty()) // not defined => disable + tuneBindToNumaZone = -1; // -1 means disable binding + else + tuneBindToNumaZone = StringTk::strToInt(iter->second); + } + else if (iter->first == std::string("tuneListenerPrioShift")) + tuneListenerPrioShift = StringTk::strToInt(iter->second); + else if (iter->first == std::string("tuneFileReadSize")) + tuneFileReadSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneFileReadAheadTriggerSize")) + tuneFileReadAheadTriggerSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneFileReadAheadSize")) + tuneFileReadAheadSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneFileWriteSize")) + tuneFileWriteSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneFileWriteSyncSize")) + tuneFileWriteSyncSize = UnitTk::strHumanToInt64(iter->second); + else if (iter->first == std::string("tuneUsePerUserMsgQueues")) + tuneUsePerUserMsgQueues = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneDirCacheLimit")) + tuneDirCacheLimit = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneEarlyStat")) + this->tuneEarlyStat = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneNumResyncGatherSlaves")) + this->tuneNumResyncGatherSlaves = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneNumResyncSlaves")) + this->tuneNumResyncSlaves = StringTk::strToUInt(iter->second); + else if (iter->first == std::string("tuneUseAggressiveStreamPoll")) + tuneUseAggressiveStreamPoll = StringTk::strToBool(iter->second); + else if (iter->first == std::string("tuneUsePerTargetWorkers")) + tuneUsePerTargetWorkers = StringTk::strToBool(iter->second); + else if (iter->first == std::string("quotaEnableEnforcement")) + quotaEnableEnforcement = StringTk::strToBool(iter->second); + else if (iter->first == std::string("quotaDisableZfsSupport")) + quotaDisableZfsSupport = StringTk::strToBool(iter->second); + else if (iter->first == std::string("sysResyncSafetyThresholdMins")) + sysResyncSafetyThresholdMins = StringTk::strToInt64(iter->second); + else if (iter->first == std::string("sysTargetOfflineTimeoutSecs")) + { + sysTargetOfflineTimeoutSecs = StringTk::strToUInt(iter->second); + + if (sysTargetOfflineTimeoutSecs < 30) + { + throw InvalidConfigException("Invalid sysTargetOfflineTimeoutSecs value " + + iter->second + " (must be at least 30)"); + } + } + else if (iter->first == std::string("runDaemonized")) + runDaemonized = StringTk::strToBool(iter->second); + else if (iter->first == std::string("pidFile")) + pidFile = iter->second; + else + { + // unknown element occurred + unknownElement = true; + + if (enableException) + { + throw InvalidConfigException("The config argument '" + iter->first + "' is invalid"); + } + } + + if (unknownElement) + { + // just skip the unknown element + iter++; + } + else + { + // remove this element from the map + iter = eraseFromConfigMap(iter); + } + } +} + +void Config::initImplicitVals() +{ + // tuneFileReadAheadTriggerSize (should be ">= tuneFileReadAheadSize") + if(tuneFileReadAheadTriggerSize < tuneFileReadAheadSize) + tuneFileReadAheadTriggerSize = tuneFileReadAheadSize; + + // connInterfacesList(/File) + AbstractConfig::initInterfacesList(connInterfacesFile, connInterfacesList); + + AbstractConfig::initSocketBufferSizes(); + + // check if sync_file_range was enabled on a distro that doesn't support it + #ifndef CONFIG_DISTRO_HAS_SYNC_FILE_RANGE + if(tuneFileWriteSyncSize) + { + throw InvalidConfigException( + "Config option is not supported for this distribution: 'tuneFileWriteSyncSize'"); + } + #endif + + // connAuthHash + AbstractConfig::initConnAuthHash(connAuthFile, &connAuthHash); +} + +std::string Config::createDefaultCfgFilename() const +{ + struct stat statBuf; + + const int statRes = stat(CONFIG_DEFAULT_CFGFILENAME, &statBuf); + + if(!statRes && S_ISREG(statBuf.st_mode) ) + return CONFIG_DEFAULT_CFGFILENAME; // there appears to be a config file + + return ""; // no default file otherwise +} + diff --git a/storage/source/app/config/Config.h b/storage/source/app/config/Config.h new file mode 100644 index 0000000..df7fc08 --- /dev/null +++ b/storage/source/app/config/Config.h @@ -0,0 +1,229 @@ +#pragma once + +#include + +/** + * Find out whether this distro hash sync_file_range() support (added in linux-2.6.17, glibc 2.6). + * Note: Problem is that RHEL 5 defines SYNC_FILE_RANGE_WRITE, but uses glibc 2.5 which has no + * sync_file_range support, so linker complains about undefined reference. + */ +#ifdef __GNUC__ + #include + #include + #if __GLIBC_PREREQ(2, 6) && defined(SYNC_FILE_RANGE_WRITE) + #define CONFIG_DISTRO_HAS_SYNC_FILE_RANGE + #endif +#endif + + +class Config : public AbstractConfig +{ + public: + Config(int argc, char** argv); + + private: + + // configurables + + std::string connInterfacesFile; // implicitly generates connInterfacesList + std::string connInterfacesList; // comma-separated list + + std::list storageDirectories; + std::list storeFsUUID; + bool storeAllowFirstRunInit; + + unsigned tuneNumStreamListeners; + unsigned tuneNumWorkers; + unsigned tuneWorkerBufSize; + unsigned tuneProcessFDLimit; // 0 means "don't touch limit" + bool tuneWorkerNumaAffinity; + bool tuneListenerNumaAffinity; + int tuneBindToNumaZone; // bind all threads to this zone, -1 means no binding + int tuneListenerPrioShift; + ssize_t tuneFileReadSize; + ssize_t tuneFileReadAheadTriggerSize; // after how much seq read to start read-ahead + ssize_t tuneFileReadAheadSize; // read-ahead with posix_fadvise(..., POSIX_FADV_WILLNEED) + ssize_t tuneFileWriteSize; + ssize_t tuneFileWriteSyncSize; // after how many of per session data to sync_file_range() + bool tuneUsePerUserMsgQueues; // true to use UserWorkContainer for MultiWorkQueue + unsigned tuneDirCacheLimit; + bool tuneEarlyStat; // stat the chunk file before closing it + unsigned tuneNumResyncGatherSlaves; + unsigned tuneNumResyncSlaves; + bool tuneUseAggressiveStreamPoll; // true to not sleep on epoll in streamlisv2 + bool tuneUsePerTargetWorkers; // true to have tuneNumWorkers separate for each target + + bool quotaEnableEnforcement; + bool quotaDisableZfsSupport; + + int64_t sysResyncSafetyThresholdMins; // minutes to add to last buddy comm timestamp + unsigned sysTargetOfflineTimeoutSecs; + + bool runDaemonized; + + std::string pidFile; + + + // internals + + virtual void loadDefaults(bool addDashes) override; + virtual void applyConfigMap(bool enableException, bool addDashes) override; + virtual void initImplicitVals() override; + std::string createDefaultCfgFilename() const; + + public: + // getters & setters + const std::string& getConnInterfacesList() const + { + return connInterfacesList; + } + + const std::list& getStorageDirectories() const { return storageDirectories; } + + const std::list& getStoreFsUUID() const + { + return storeFsUUID; + } + + bool getStoreAllowFirstRunInit() const + { + return storeAllowFirstRunInit; + } + + unsigned getTuneNumStreamListeners() const + { + return tuneNumStreamListeners; + } + + unsigned getTuneNumWorkers() const + { + return tuneNumWorkers; + } + + unsigned getTuneWorkerBufSize() const + { + return tuneWorkerBufSize; + } + + unsigned getTuneProcessFDLimit() const + { + return tuneProcessFDLimit; + } + + bool getTuneWorkerNumaAffinity() const + { + return tuneWorkerNumaAffinity; + } + + bool getTuneListenerNumaAffinity() const + { + return tuneListenerNumaAffinity; + } + + int getTuneBindToNumaZone() const + { + return tuneBindToNumaZone; + } + + int getTuneListenerPrioShift() const + { + return tuneListenerPrioShift; + } + + ssize_t getTuneFileReadSize() const + { + return tuneFileReadSize; + } + + ssize_t getTuneFileReadAheadTriggerSize() const + { + return tuneFileReadAheadTriggerSize; + } + + ssize_t getTuneFileReadAheadSize() const + { + return tuneFileReadAheadSize; + } + + ssize_t getTuneFileWriteSize() const + { + return tuneFileWriteSize; + } + + ssize_t getTuneFileWriteSyncSize() const + { + return this->tuneFileWriteSyncSize; + } + + bool getTuneUsePerUserMsgQueues() const + { + return tuneUsePerUserMsgQueues; + } + + bool getRunDaemonized() const + { + return runDaemonized; + } + + const std::string& getPIDFile() const + { + return pidFile; + } + + unsigned getTuneDirCacheLimit() const + { + return tuneDirCacheLimit; + } + + bool getTuneEarlyStat() const + { + return this->tuneEarlyStat; + } + + bool getQuotaEnableEnforcement() const + { + return quotaEnableEnforcement; + } + + void setQuotaEnableEnforcement(bool doQuotaEnforcement) + { + quotaEnableEnforcement = doQuotaEnforcement; + } + + bool getQuotaDisableZfsSupport() const + { + return quotaDisableZfsSupport; + } + + unsigned getTuneNumResyncGatherSlaves() const + { + return tuneNumResyncGatherSlaves; + } + + unsigned getTuneNumResyncSlaves() const + { + return tuneNumResyncSlaves; + } + + bool getTuneUseAggressiveStreamPoll() const + { + return tuneUseAggressiveStreamPoll; + } + + bool getTuneUsePerTargetWorkers() const + { + return tuneUsePerTargetWorkers; + } + + int64_t getSysResyncSafetyThresholdMins() const + { + return sysResyncSafetyThresholdMins; + } + + unsigned getSysTargetOfflineTimeoutSecs() const + { + return sysTargetOfflineTimeoutSecs; + } + +}; + diff --git a/storage/source/components/DatagramListener.cpp b/storage/source/components/DatagramListener.cpp new file mode 100644 index 0000000..dfc40be --- /dev/null +++ b/storage/source/components/DatagramListener.cpp @@ -0,0 +1,61 @@ +#include "DatagramListener.h" + +#include + +DatagramListener::DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, bool restrictOutboundInterfaces): + AbstractDatagramListener("DGramLis", netFilter, localNicList, ackStore, udpPort, + restrictOutboundInterfaces) +{ +} + +DatagramListener::~DatagramListener() +{ +} + +void DatagramListener::handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg) +{ + HighResolutionStats stats; // currently ignored + std::shared_ptr sock = findSenderSock(fromAddr->sin_addr); + if (sock == nullptr) + { + log.log(Log_WARNING, "Could not handle incoming message: no socket"); + return; + } + + NetMessage::ResponseContext rctx(fromAddr, sock.get(), sendBuf, DGRAMMGR_SENDBUF_SIZE, &stats); + + const auto messageType = netMessageTypeToStr(msg->getMsgType()); + + switch(msg->getMsgType() ) + { + // valid messages within this context + case NETMSGTYPE_Ack: + case NETMSGTYPE_Dummy: + case NETMSGTYPE_HeartbeatRequest: + case NETMSGTYPE_Heartbeat: + case NETMSGTYPE_MapTargets: + case NETMSGTYPE_PublishCapacities: + case NETMSGTYPE_RemoveNode: + case NETMSGTYPE_RefreshStoragePools: + case NETMSGTYPE_RefreshTargetStates: + case NETMSGTYPE_SetMirrorBuddyGroup: + { + if(!msg->processIncoming(rctx) ) + { + LOG(GENERAL, WARNING, + "Problem encountered during handling of incoming message.", messageType); + } + } break; + + default: + { // valid, but not within this context + log.logErr( + "Received a message that is invalid within the current context " + "from: " + Socket::ipaddrToStr(fromAddr->sin_addr) + "; " + "type: " + messageType ); + } break; + }; +} + + diff --git a/storage/source/components/DatagramListener.h b/storage/source/components/DatagramListener.h new file mode 100644 index 0000000..de88942 --- /dev/null +++ b/storage/source/components/DatagramListener.h @@ -0,0 +1,20 @@ +#pragma once + +#include + +class DatagramListener : public AbstractDatagramListener +{ + public: + DatagramListener(NetFilter* netFilter, NicAddressList& localNicList, + AcknowledgmentStore* ackStore, unsigned short udpPort, + bool restrictOutboundInterfaces); + virtual ~DatagramListener(); + + + protected: + virtual void handleIncomingMsg(struct sockaddr_in* fromAddr, NetMessage* msg); + + private: + +}; + diff --git a/storage/source/components/InternodeSyncer.cpp b/storage/source/components/InternodeSyncer.cpp new file mode 100644 index 0000000..cee14f5 --- /dev/null +++ b/storage/source/components/InternodeSyncer.cpp @@ -0,0 +1,1185 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "InternodeSyncer.h" + +#include + +// forward declaration +namespace UUID { + std::string getMachineUUID(); +} + +InternodeSyncer::InternodeSyncer(): + PThread("XNodeSync"), + log("XNodeSync"), forceTargetStatesUpdate(true), forcePublishCapacities(true) +{ +} + +InternodeSyncer::~InternodeSyncer() +{ +} + + +void InternodeSyncer::run() +{ + try + { + registerSignalHandler(); + + syncLoop(); + + log.log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } +} + +void InternodeSyncer::syncLoop() +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + const int sleepIntervalMS = 3*1000; // 3sec + + const unsigned sweepNormalMS = 5*1000; // 5sec + const unsigned sweepStressedMS = 2*1000; // 2sec + const unsigned checkNetworkIntervalMS = 60*1000; // 1 minute + const unsigned idleDisconnectIntervalMS = 70*60*1000; /* 70 minutes (must be less than half the + streamlis idle disconnect interval to avoid cases where streamlis disconnects first) */ + const unsigned downloadNodesIntervalMS = 600000; // 10min + const unsigned updateStoragePoolsMS = downloadNodesIntervalMS; + + // If (undocumented) sysUpdateTargetStatesSecs is set in config, use that value, otherwise + // default to 1/6 sysTargetOfflineTimeoutSecs. + const unsigned updateTargetStatesMS = + (cfg->getSysUpdateTargetStatesSecs() != 0) + ? cfg->getSysUpdateTargetStatesSecs() * 1000 + : cfg->getSysTargetOfflineTimeoutSecs() * 166; + + const unsigned updateCapacitiesMS = updateTargetStatesMS * 4; + + Time lastCacheSweepT; + Time lastCheckNetworkT; + Time lastIdleDisconnectT; + Time lastDownloadNodesT; + Time lastTargetStatesUpdateT; + Time lastStoragePoolsUpdateT; + Time lastCapacityUpdateT; + bool doRegisterLocalNode = false; + + unsigned currentCacheSweepMS = sweepNormalMS; // (adapted inside the loop below) + + while(!waitForSelfTerminateOrder(sleepIntervalMS) ) + { + bool targetStatesUpdateForced = getAndResetForceTargetStatesUpdate(); + bool publishCapacitiesForced = getAndResetForcePublishCapacities(); + bool storagePoolsUpdateForced = getAndResetForceStoragePoolsUpdate(); + bool checkNetworkForced = getAndResetForceCheckNetwork(); + + if(lastCacheSweepT.elapsedMS() > currentCacheSweepMS) + { + bool flushTriggered = app->getChunkDirStore()->cacheSweepAsync(); + currentCacheSweepMS = (flushTriggered ? sweepStressedMS : sweepNormalMS); + + lastCacheSweepT.setToNow(); + } + + if (checkNetworkForced || + (lastCheckNetworkT.elapsedMS() > checkNetworkIntervalMS)) + { + if (checkNetwork()) + doRegisterLocalNode = true; + lastCheckNetworkT.setToNow(); + } + + if (doRegisterLocalNode) + doRegisterLocalNode = !registerNode(app->getDatagramListener()); + + if(lastIdleDisconnectT.elapsedMS() > idleDisconnectIntervalMS) + { + dropIdleConns(); + lastIdleDisconnectT.setToNow(); + } + + // download & sync nodes + if(lastDownloadNodesT.elapsedMS() > downloadNodesIntervalMS) + { + downloadAndSyncNodes(); + downloadAndSyncTargetMappings(); + downloadAndSyncMirrorBuddyGroups(); + + lastDownloadNodesT.setToNow(); + } + + if (storagePoolsUpdateForced || + (lastStoragePoolsUpdateT.elapsedMS() > updateStoragePoolsMS)) + { + downloadAndSyncStoragePools(); + + lastStoragePoolsUpdateT.setToNow(); + } + + if( targetStatesUpdateForced || + (lastTargetStatesUpdateT.elapsedMS() > updateTargetStatesMS) ) + { + updateTargetStatesAndBuddyGroups(); + lastTargetStatesUpdateT.setToNow(); + } + + if( publishCapacitiesForced || + (lastCapacityUpdateT.elapsedMS() > updateCapacitiesMS)) + { + publishTargetCapacities(); + lastCapacityUpdateT.setToNow(); + } + } +} + +/** + * Inspect the available and allowed network interfaces for any changes. + */ +bool InternodeSyncer::checkNetwork() +{ + App* app = Program::getApp(); + NicAddressList newLocalNicList; + bool res = false; + + app->findAllowedInterfaces(newLocalNicList); + app->findAllowedRDMAInterfaces(newLocalNicList); + if (!std::equal(newLocalNicList.begin(), newLocalNicList.end(), app->getLocalNicList().begin())) + { + log.log(Log_NOTICE, "checkNetwork: local interfaces have changed"); + app->updateLocalNicList(newLocalNicList); + res = true; + } + + return res; +} + +/** + * Drop/reset idle conns from all server stores. + */ +void InternodeSyncer::dropIdleConns() +{ + App* app = Program::getApp(); + + unsigned numDroppedConns = 0; + + numDroppedConns += dropIdleConnsByStore(app->getMgmtNodes() ); + numDroppedConns += dropIdleConnsByStore(app->getMetaNodes() ); + numDroppedConns += dropIdleConnsByStore(app->getStorageNodes() ); + + if(numDroppedConns) + { + log.log(Log_DEBUG, "Dropped idle connections: " + StringTk::uintToStr(numDroppedConns) ); + } +} + +/** + * Walk over all nodes in the given store and drop/reset idle connections. + * + * @return number of dropped connections + */ +unsigned InternodeSyncer::dropIdleConnsByStore(NodeStoreServers* nodes) +{ + App* app = Program::getApp(); + + unsigned numDroppedConns = 0; + + for (const auto& node : nodes->referenceAllNodes()) + { + /* don't do any idle disconnect stuff with local node + (the LocalNodeConnPool doesn't support and doesn't need this kind of treatment) */ + + if (node.get() != &app->getLocalNode()) + { + NodeConnPool* connPool = node->getConnPool(); + + numDroppedConns += connPool->disconnectAndResetIdleStreams(); + } + } + + return numDroppedConns; +} + +void InternodeSyncer::updateTargetStatesAndBuddyGroups() +{ + const char* logContext = "Update states and mirror groups"; + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, + "Starting target state update."); + + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + TargetStateStore* targetStateStore = app->getTargetStateStore(); + StorageTargets* storageTargets = app->getStorageTargets(); + MirrorBuddyGroupMapper* mirrorBuddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + static bool downloadFailedLogged = false; // to avoid log spamming + static bool publishFailedLogged = false; // to avoid log spamming + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (unlikely(!mgmtNode)) + { // should never happen here, because mgmt is downloaded before InternodeSyncer startup + LogContext(logContext).log(LogTopic_STATES, Log_ERR, "Management node not defined."); + return; + } + + unsigned numRetries = 10; // If publishing states fails 10 times, give up (-> POFFLINE). + + // Note: Publishing states fails if between downloadStatesAndBuddyGroups and + // publishLocalTargetStateChanges, a state on the mgmtd is changed (e.g. because the primary + // sets NEEDS_RESYNC for the secondary). In that case, we will retry. + + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, + "Beginning target state update..."); + bool publishSuccess = false; + + while (!publishSuccess && (numRetries--) ) + { + MirrorBuddyGroupMap buddyGroups; + TargetStateMap states; + + bool downloadRes = NodesTk::downloadStatesAndBuddyGroups(*mgmtNode, NODETYPE_Storage, + buddyGroups, states, true); + + if (!downloadRes) + { + if(!downloadFailedLogged) + { + LogContext(logContext).log(LogTopic_STATES, Log_WARNING, + "Downloading target states from management node failed. " + "Setting all targets to probably-offline."); + downloadFailedLogged = true; + } + + targetStateStore->setAllStates(TargetReachabilityState_POFFLINE); + + break; + } + + downloadFailedLogged = false; + + // before anything else is done, update the targetWasOffline flags in the resyncers. updating + // them later opens a window of opportunity where the target state store says "offline", but + // the resyncer has not noticed - which would erroneously not fail the resync. + for (const auto& state : states) + { + if (state.second.reachabilityState == TargetReachabilityState_OFFLINE) + { + const auto job = app->getBuddyResyncer()->getResyncJob(state.first); + if (job) + job->setTargetOffline(); + } + } + + // Sync buddy groups here, because decideResync depends on it. + // This is not a problem because if pushing target states fails all targets will be + // (p)offline anyway. + targetStateStore->syncStatesAndGroups(mirrorBuddyGroupMapper, states, buddyGroups, + app->getLocalNode().getNumID()); + + TargetStateMap localTargetChangedStates; + storageTargets->decideResync(states, localTargetChangedStates); + + publishSuccess = publishLocalTargetStateChanges(states, localTargetChangedStates); + + if(publishSuccess) + storageTargets->checkBuddyNeedsResync(); + } + + if(!publishSuccess) + { + if(!publishFailedLogged) + { + log.log(LogTopic_STATES, Log_WARNING, + "Pushing local target states to management node failed."); + publishFailedLogged = true; + } + } + else + publishFailedLogged = false; +} + +void InternodeSyncer::publishTargetCapacities() +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + StorageTargets* storageTargets = app->getStorageTargets(); + + log.log(LogTopic_STATES, Log_DEBUG, "Publishing target capacity infos."); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.log(LogTopic_STATES, Log_ERR, "Management node not defined."); + return; + } + + StorageTargetInfoList targetInfoList; + + storageTargets->generateTargetInfoList(targetInfoList); + + SetStorageTargetInfoMsg msg(NODETYPE_Storage, &targetInfoList); + RequestResponseArgs rrArgs(mgmtNode.get(), &msg, NETMSGTYPE_SetStorageTargetInfoResp); + +#ifndef BEEGFS_DEBUG + rrArgs.logFlags |= REQUESTRESPONSEARGS_LOGFLAG_CONNESTABLISHFAILED + | REQUESTRESPONSEARGS_LOGFLAG_RETRY; +#endif + + bool sendRes = MessagingTk::requestResponse(&rrArgs); + + static bool failureLogged = false; + + if (!sendRes) + { + if (!failureLogged) + log.log(LogTopic_STATES, Log_CRITICAL, + "Pushing target free space info to management node failed."); + + failureLogged = true; + return; + } + else + { + const auto respMsgCast = + static_cast(rrArgs.outRespMsg.get()); + + failureLogged = false; + + if ( (FhgfsOpsErr)respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + log.log(LogTopic_STATES, Log_CRITICAL, + "Management did not accept target free space info message."); + return; + } + } + + // If we were just started and are publishing our capacity for the first time, force a pool + // refresh on the mgmtd so we're not stuck in the emergency pool until the first regular + // pool refresh. + static bool firstTimePubilished = true; + if (firstTimePubilished) + { + forceMgmtdPoolsRefresh(); + firstTimePubilished = false; + } +} + +void InternodeSyncer::publishTargetState(uint16_t targetID, TargetConsistencyState targetState) +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + + log.log(Log_DEBUG, "Publishing state for target: " + StringTk::uintToStr(targetID) ); + + UInt16List targetIDs(1, targetID); + UInt8List states(1, targetState); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.logErr("Management node not defined."); + return; + } + + SetTargetConsistencyStatesMsg msg(NODETYPE_Storage, &targetIDs, &states, true); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + + if (!respMsg) + log.log(Log_CRITICAL, "Pushing target state to management node failed."); + else + { + auto* respMsgCast = (SetTargetConsistencyStatesRespMsg*)respMsg.get(); + if ( (FhgfsOpsErr)respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + log.log(Log_CRITICAL, "Management node did not accept target state."); + } +} + +/** + * Gets a list of target states changes (old/new), and reports the local ones (targets which are + * present in this storage server's storageTargetDataMap) to the mgmtd. + */ +bool InternodeSyncer::publishLocalTargetStateChanges(const TargetStateMap& oldStates, + const TargetStateMap& changes) +{ + App* app = Program::getApp(); + StorageTargets* storageTargets = app->getStorageTargets(); + + UInt16List localTargetIDs; + UInt8List localOldStates; + UInt8List localNewStates; + + for (const auto& state : oldStates) + { + const uint16_t targetID = state.first; + auto* const target = storageTargets->getTarget(targetID); + + if (!target) + continue; + + // Don't report targets which have an offline timeout at the moment. + const auto waitRemaining = target->getOfflineTimeout(); + if (waitRemaining) + { + LOG(GENERAL, WARNING, "Target was a primary target and needs a resync. " + "Waiting until it is marked offline on all clients.", + targetID, ("remainingMS", waitRemaining->count())); + continue; + } + + localTargetIDs.push_back(state.first); + localOldStates.push_back(state.second.consistencyState); + + const auto change = changes.find(state.first); + + if (change != changes.end()) + localNewStates.push_back(change->second.consistencyState); + else + localNewStates.push_back(state.second.consistencyState); + } + + return publishTargetStateChanges(localTargetIDs, localOldStates, localNewStates); +} + +/** + * Send a HeartbeatMsg to mgmt. + * + * @return true if node and targets registration successful + */ +bool InternodeSyncer::registerNode(AbstractDatagramListener* dgramLis) +{ + static bool registrationFailureLogged = false; // to avoid log spamming + + const char* logContext = "Register node"; + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + Node& localNode = app->getLocalNode(); + NumNodeID localNodeNumID = localNode.getNumID(); + NicAddressList nicList(localNode.getNicList() ); + + HeartbeatMsg msg(localNode.getAlias(), localNodeNumID, NODETYPE_Storage, &nicList); + msg.setPorts(cfg->getConnStoragePort(), cfg->getConnStoragePort() ); + auto uuid = UUID::getMachineUUID(); + if (uuid.empty()) { + LogContext(logContext).log(Log_CRITICAL, + "Couldn't determine UUID for machine. Node registration not possible."); + return false; + } + msg.setMachineUUID(uuid); + + bool registered = dgramLis->sendToNodeUDPwithAck(mgmtNode, &msg); + + if(registered) + LogContext(logContext).log(Log_WARNING, "Node registration successful."); + else + if(!registrationFailureLogged) + { + LogContext(logContext).log(Log_CRITICAL, "Node registration not successful. " + "Management node offline? Will keep on trying..."); + registrationFailureLogged = true; + } + + return registered; +} + +/** + * Sent a MapTargetsMsg to mgmt. + * + * note: only called once at startup + * + * @return true if targets mapping successful. + */ +bool InternodeSyncer::registerTargetMappings() +{ + static std::map registrationFailureLogged; // one for eacht target; to avoid log + // spamming + static bool commErrorLogged = false; // to avoid log spamming + + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + bool registered = true; + + Node& localNode = app->getLocalNode(); + NumNodeID localNodeID = localNode.getNumID(); + StorageTargets* targets = Program::getApp()->getStorageTargets(); + std::map targetPools; + + MapTargetsRespMsg* respMsgCast; + + // for each target, check if a storagePoolId file exists in the storage dir; if there is, try to + // directly put the target in the specified pool when mapping at mgmtd + // note: if file is not set readNumStoragePoolIDFile will return default pool + for (const auto& mapping : targets->getTargets()) + { + const auto& targetPath = mapping.second->getPath().str(); + + targetPools.emplace(mapping.first, + StorageTk::readNumStoragePoolIDFile(targetPath, STORAGETK_STORAGEPOOLID_FILENAME)); + } + + MapTargetsMsg msg(targetPools, localNodeID); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, NETMSGTYPE_MapTargetsResp); + if (respMsg) + { + // handle result + respMsgCast = (MapTargetsRespMsg*) respMsg.get(); + + const auto& results = respMsgCast->getResults(); + + for (const auto& mapping : targets->getTargets()) + { + const auto targetID = mapping.first; + const auto result = results.find(targetID); + + if (result == results.end()) + { + registered = false; + + LOG(GENERAL, CRITICAL, "Mgmt ignored target registration attempt.", targetID); + registrationFailureLogged[targetID] = true; + } + else if (result->second != FhgfsOpsErr_SUCCESS) + { + registered = false; + + if (!registrationFailureLogged[targetID]) + { + LOG(GENERAL, CRITICAL, "Storage target registration rejected. Will keep on trying.", + targetID, ("error", result->second)); + registrationFailureLogged[targetID] = true; + } + } + else + { + // registered successfully => remove STORAGETK_STORAGEPOOLID_FILENAME for this target, + // because it is only relevant for first registration + const auto& targetPath = mapping.second->getPath().str(); + std::string storagePoolIdFileName = targetPath + "/" + STORAGETK_STORAGEPOOLID_FILENAME; + + int unlinkRes = ::unlink(storagePoolIdFileName.c_str()); + int errorCode = errno; + if ((unlinkRes != 0) && (errorCode != ENOENT)) + { // error; note: if file doesn't exist, that's not considered an error + LOG(GENERAL, WARNING, "Unable to unlink storage pool ID file", targetID, errorCode); + } + } + } + } + else if (!commErrorLogged) + { + LOG(GENERAL, CRITICAL, "Storage targets registration not successful. " + "Management node offline? Will keep on trying."); + commErrorLogged = true; + } + + if (registered) + { + LOG(GENERAL, WARNING, "Storage targets registration successful."); + } + + return registered; +} + +bool InternodeSyncer::publishTargetStateChanges(UInt16List& targetIDs, UInt8List& oldStates, + UInt8List& newStates) +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + bool res; + + log.log(Log_DEBUG, "Publishing target state change"); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.logErr("Management node not defined."); + return true; // Don't stall indefinitely if we don't have a management node. + } + + ChangeTargetConsistencyStatesMsg msg(NODETYPE_Storage, &targetIDs, &oldStates, &newStates); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_ChangeTargetConsistencyStatesResp); + + if (!respMsg) + { + log.log(Log_CRITICAL, "Pushing target state changes to management node failed."); + res = false; // Retry. + } + else + { + auto* respMsgCast = (ChangeTargetConsistencyStatesRespMsg*)respMsg.get(); + + if ( (FhgfsOpsErr)respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + log.log(Log_CRITICAL, "Management node did not accept target state changes."); + res = false; // States were changed while we evaluated the state changed. Try again. + } + else + res = true; + } + + return res; +} + +void InternodeSyncer::requestBuddyTargetStates() +{ + const char* logContext = "Request buddy target states"; + + TimerQueue* timerQ = Program::getApp()->getTimerQueue(); + TargetMapper* targetMapper = Program::getApp()->getTargetMapper(); + MirrorBuddyGroupMapper* buddyGroupMapper = Program::getApp()->getMirrorBuddyGroupMapper(); + StorageTargets* storageTargets = Program::getApp()->getStorageTargets(); + NodeStore* storageNodes = Program::getApp()->getStorageNodes(); + TargetStateStore* targetStateStore = Program::getApp()->getTargetStateStore(); + + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, "Requesting buddy target states."); + + // loop over all local targets + for (const auto& mapping : storageTargets->getTargets()) + { + uint16_t targetID = mapping.first; + + // check if target is part of a buddy group + uint16_t buddyTargetID = buddyGroupMapper->getBuddyTargetID(targetID); + if(!buddyTargetID) + continue; + + // this target is part of a buddy group + + NumNodeID nodeID = targetMapper->getNodeID(buddyTargetID); + if(!nodeID) + { // mapping to node not found + LogContext(logContext).log(LogTopic_STATES, Log_ERR, + "Node-mapping for target ID " + StringTk::uintToStr(buddyTargetID) + " not found."); + continue; + } + + auto node = storageNodes->referenceNode(nodeID); + if(!node) + { // node not found + LogContext(logContext).log(LogTopic_STATES, Log_ERR, + "Unknown storage node. nodeID: " + nodeID.str() + "; targetID: " + + StringTk::uintToStr(targetID)); + continue; + } + + // get reachability state of buddy target ID + CombinedTargetState currentState; + targetStateStore->getState(buddyTargetID, currentState); + + if(currentState.reachabilityState == TargetReachabilityState_ONLINE) + { + // communicate + UInt16Vector queryTargetIDs(1, buddyTargetID); + GetTargetConsistencyStatesMsg msg(queryTargetIDs); + + const auto respMsg = MessagingTk::requestResponse(*node, msg, + NETMSGTYPE_GetTargetConsistencyStatesResp); + if (!respMsg) + { // communication failed + LogContext(logContext).log(LogTopic_STATES, Log_WARNING, + "Communication with buddy target failed. " + "nodeID: " + nodeID.str() + "; buddy targetID: " + + StringTk::uintToStr(buddyTargetID)); + + continue; + } + + // handle response + auto respMsgCast = (GetTargetConsistencyStatesRespMsg*)respMsg.get(); + const auto& targetConsistencyStates = &respMsgCast->getStates(); + + // get received target information + // (note: we only requested a single target info, so the first one must be the + // requested one) + const TargetConsistencyState buddyTargetConsistencyState = + targetConsistencyStates->empty() ? TargetConsistencyState_BAD : + targetConsistencyStates->front(); + + auto& target = *storageTargets->getTargets().at(targetID); + + // set last comm timestamp, but ignore it if we think buddy needs a resync + const bool buddyNeedsResync = target.getBuddyNeedsResync(); + if((buddyTargetConsistencyState == TargetConsistencyState_GOOD) && !buddyNeedsResync) + target.setLastBuddyComm(std::chrono::system_clock::now(), false); + } + } + + // requeue + timerQ->enqueue(std::chrono::seconds(30), requestBuddyTargetStates); +} + +/** + * @param outTargetIDs + * @param outReachabilityStates + * @param outConsistencyStates + * @return false on error. + */ +bool InternodeSyncer::downloadAndSyncTargetStates(UInt16List& outTargetIDs, + UInt8List& outReachabilityStates, UInt8List& outConsistencyStates) +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + TargetStateStore* targetStateStore = app->getTargetStateStore(); + + auto node = mgmtNodes->referenceFirstNode(); + if(!node) + return false; + + bool downloadRes = NodesTk::downloadTargetStates(*node, NODETYPE_Storage, + &outTargetIDs, &outReachabilityStates, &outConsistencyStates, false); + + if(downloadRes) + targetStateStore->syncStatesFromLists(outTargetIDs, outReachabilityStates, + outConsistencyStates); + + return downloadRes; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncNodes() +{ + const char* logContext = "Nodes sync"; + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, "Called."); + + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + NodeStoreServers* metaNodes = app->getMetaNodes(); + NodeStoreServers* storageNodes = app->getStorageNodes(); + Node& localNode = app->getLocalNode(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + + { // storage nodes + std::vector storageNodesList; + NumNodeIDList addedStorageNodes; + NumNodeIDList removedStorageNodes; + + bool storageRes = + NodesTk::downloadNodes(*mgmtNode, NODETYPE_Storage, storageNodesList, true); + if(!storageRes) + goto err_release_mgmt; + + storageNodes->syncNodes(storageNodesList, &addedStorageNodes, &removedStorageNodes, + &localNode); + printSyncNodesResults(NODETYPE_Storage, &addedStorageNodes, &removedStorageNodes); + } + + + { // clients + std::vector clientsList; + StringList addedClients; + StringList removedClients; + + bool clientsRes = NodesTk::downloadNodes(*mgmtNode, NODETYPE_Client, clientsList, true); + if(!clientsRes) + goto err_release_mgmt; + + // note: storage App doesn't have a client node store, thus no clients->syncNodes() here + syncClientSessions(clientsList); + } + + + { // metadata nodes + std::vector metaNodesList; + NumNodeIDList addedMetaNodes; + NumNodeIDList removedMetaNodes; + NumNodeID rootNodeID; + bool rootIsBuddyMirrored; + + bool metaRes = + NodesTk::downloadNodes(*mgmtNode, NODETYPE_Meta, metaNodesList, true, &rootNodeID, + &rootIsBuddyMirrored); + if(!metaRes) + goto err_release_mgmt; + + metaNodes->syncNodes(metaNodesList, &addedMetaNodes, &removedMetaNodes); + + printSyncNodesResults(NODETYPE_Meta, &addedMetaNodes, &removedMetaNodes); + } + + return true; + +err_release_mgmt: + return false; +} + +void InternodeSyncer::printSyncNodesResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes) +{ + const char* logContext = "Sync results"; + + if (!addedNodes->empty()) + LogContext(logContext).log(LogTopic_STATES, Log_WARNING, + std::string("Nodes added: ") + + StringTk::uintToStr(addedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); + + if (!removedNodes->empty()) + LogContext(logContext).log(LogTopic_STATES, Log_WARNING, + std::string("Nodes removed: ") + + StringTk::uintToStr(removedNodes->size() ) + + " (Type: " + boost::lexical_cast(nodeType) + ")"); +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncTargetMappings() +{ + LogContext("Download target mappings").log(LogTopic_STATES, Log_DEBUG, + "Syncing target mappings."); + + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + TargetMapper* targetMapper = app->getTargetMapper(); + + bool retVal = true; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + auto mappings = NodesTk::downloadTargetMappings(*mgmtNode, true); + if (mappings.first) + targetMapper->syncTargets(std::move(mappings.second)); + else + retVal = false; + + return retVal; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAndSyncMirrorBuddyGroups() +{ + LogContext("Downlod mirror groups").log(LogTopic_STATES, Log_DEBUG, + "Syncing mirror groups."); + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + bool retVal = true; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + UInt16List buddyGroupIDs; + UInt16List primaryTargetIDs; + UInt16List secondaryTargetIDs; + + bool downloadRes = NodesTk::downloadMirrorBuddyGroups(*mgmtNode, NODETYPE_Storage, + &buddyGroupIDs, &primaryTargetIDs, &secondaryTargetIDs, true); + + if(downloadRes) + { + buddyGroupMapper->syncGroupsFromLists(buddyGroupIDs, primaryTargetIDs, secondaryTargetIDs, + app->getLocalNode().getNumID()); + } + else + retVal = false; + + return retVal; +} + +bool InternodeSyncer::downloadAndSyncStoragePools() +{ + App* app = Program::getApp(); + NodeStore* mgmtNodes = app->getMgmtNodes(); + StoragePoolStore* storagePoolStore = app->getStoragePoolStore(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + StoragePoolPtrVec storagePools; + + bool downloadPoolsRes = NodesTk::downloadStoragePools(*mgmtNode, storagePools, true); + if(downloadPoolsRes) + storagePoolStore->syncFromVector(storagePools); + + return true; +} + +/** + * Synchronize local client sessions with registered clients from mgmt to release orphaned sessions. + * + * @param clientsList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + */ +void InternodeSyncer::syncClientSessions(const std::vector& clientsList) +{ + const char* logContext = "Client sessions sync"; + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, "Client session sync started."); + + App* app = Program::getApp(); + SessionStore* sessions = app->getSessions(); + + auto removedSessions = sessions->syncSessions(clientsList); + + // print sessions removal results (upfront) + if (!removedSessions.empty()) + { + std::ostringstream logMsgStream; + logMsgStream << "Removing " << removedSessions.size() << " client sessions. "; + LogContext(logContext).log(LogTopic_STATES, Log_DEBUG, logMsgStream.str() ); + } + + + // remove each file of each session + auto sessionIter = removedSessions.begin(); + for( ; sessionIter != removedSessions.end(); sessionIter++) // CLIENT SESSIONS LOOP + { // walk over all client sessions: cleanup each session + auto& session = *sessionIter; + NumNodeID sessionID = session->getSessionID(); + SessionLocalFileStore* sessionFiles = session->getLocalFiles(); + + auto removed = sessionFiles->removeAllSessions(); + + // print sessionFiles results (upfront) + if (removed) + { + std::ostringstream logMsgStream; + logMsgStream << sessionID << ": Removing " << removed << " file sessions."; + LogContext(logContext).log(LogTopic_STATES, Log_NOTICE, logMsgStream.str() ); + } + } // end of client sessions loop +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadExceededQuotaList(uint16_t targetId, QuotaDataType idType, + QuotaLimitType exType, UIntList* outIDList, FhgfsOpsErr& error) +{ + App* app = Program::getApp(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + bool retVal = false; + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if(!mgmtNode) + return false; + + RequestExceededQuotaMsg msg(idType, exType, targetId); + + RequestExceededQuotaRespMsg* respMsgCast = NULL; + + const auto respMsg = MessagingTk::requestResponse(*mgmtNode, msg, + NETMSGTYPE_RequestExceededQuotaResp); + if (!respMsg) + goto err_exit; + + // handle result + respMsgCast = (RequestExceededQuotaRespMsg*)respMsg.get(); + + respMsgCast->getExceededQuotaIDs()->swap(*outIDList); + error = respMsgCast->getError(); + + retVal = true; + +err_exit: + return retVal; +} + +bool InternodeSyncer::downloadAllExceededQuotaLists( + const std::map>& targets) +{ + bool retVal = true; + + // note: this is fairly inefficient, but it is done only one on startup + for (const auto& mapping : targets) + { + if (!downloadAllExceededQuotaLists(mapping.first)) + retVal = false; + } + + return retVal; +} + +/** + * @return false on error + */ +bool InternodeSyncer::downloadAllExceededQuotaLists(uint16_t targetId) +{ + const char* logContext = "Exceeded quota sync"; + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + + if (!exceededQuotaStore) + { + LOG(STORAGEPOOLS, ERR, "Could not access exceeded quota store.", targetId); + return false; + } + + bool retVal = true; + + UIntList tmpExceededUIDsSize; + UIntList tmpExceededGIDsSize; + UIntList tmpExceededUIDsInode; + UIntList tmpExceededGIDsInode; + + FhgfsOpsErr error; + + if (downloadExceededQuotaList(targetId, QuotaDataType_USER, QuotaLimitType_SIZE, + &tmpExceededUIDsSize, error) ) + { + exceededQuotaStore->updateExceededQuota(&tmpExceededUIDsSize, QuotaDataType_USER, + QuotaLimitType_SIZE); + + // enable or disable quota enforcement + if(error == FhgfsOpsErr_NOTSUPP) + { + if(cfg->getQuotaEnableEnforcement() ) + { + LogContext(logContext).log(Log_DEBUG, + "Quota enforcement is enabled in the configuration of this storage server, " + "but not on the management daemon. " + "The configuration from the management daemon overrides the local setting."); + } + else + { + LogContext(logContext).log(Log_DEBUG, "Quota enforcement disabled by management daemon."); + } + + cfg->setQuotaEnableEnforcement(false); + return true; + } + else + { + if(!cfg->getQuotaEnableEnforcement() ) + { + LogContext(logContext).log(Log_DEBUG, + "Quota enforcement is enabled on the management daemon, " + "but not in the configuration of this storage server. " + "The configuration from the management daemon overrides the local setting."); + } + else + { + LogContext(logContext).log(Log_DEBUG, "Quota enforcement enabled by management daemon."); + } + + cfg->setQuotaEnableEnforcement(true); + } + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file size quota for users."); + retVal = false; + } + + if (downloadExceededQuotaList(targetId, QuotaDataType_GROUP, QuotaLimitType_SIZE, + &tmpExceededGIDsSize, error)) + { + exceededQuotaStore->updateExceededQuota(&tmpExceededGIDsSize, QuotaDataType_GROUP, + QuotaLimitType_SIZE); + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file size quota for groups."); + retVal = false; + } + + if (downloadExceededQuotaList(targetId, QuotaDataType_USER, QuotaLimitType_INODE, + &tmpExceededUIDsInode, error)) + { + exceededQuotaStore->updateExceededQuota(&tmpExceededUIDsInode, QuotaDataType_USER, + QuotaLimitType_INODE); + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file number quota for users."); + retVal = false; + } + + if (downloadExceededQuotaList(targetId, QuotaDataType_USER, QuotaLimitType_INODE, + &tmpExceededGIDsInode, error)) + { + exceededQuotaStore->updateExceededQuota(&tmpExceededGIDsInode, QuotaDataType_GROUP, + QuotaLimitType_INODE); + } + else + { // error + LogContext(logContext).logErr("Unable to download exceeded file number quota for groups."); + retVal = false; + } + + return retVal; +} + +/** + * Tell mgmtd to update its capacity pools. + */ +void InternodeSyncer::forceMgmtdPoolsRefresh() +{ + App* app = Program::getApp(); + DatagramListener* dgramLis = app->getDatagramListener(); + NodeStoreServers* mgmtNodes = app->getMgmtNodes(); + + auto mgmtNode = mgmtNodes->referenceFirstNode(); + if (!mgmtNode) + { + log.log(Log_DEBUG, "Management node not defined."); + return; + } + + RefreshCapacityPoolsMsg msg; + + bool ackReceived = dgramLis->sendToNodeUDPwithAck(mgmtNode, &msg); + + if (!ackReceived) + log.log(Log_DEBUG, "Management node did not accept pools refresh request."); +} + diff --git a/storage/source/components/InternodeSyncer.h b/storage/source/components/InternodeSyncer.h new file mode 100644 index 0000000..5c4308a --- /dev/null +++ b/storage/source/components/InternodeSyncer.h @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +class StorageTarget; + +class InternodeSyncer : public PThread +{ + public: + InternodeSyncer(); + virtual ~InternodeSyncer(); + + static bool downloadAndSyncTargetStates(UInt16List& outTargetIDs, + UInt8List& outReachabilityStates, UInt8List& outConsistencyStates); + static bool downloadAndSyncNodes(); + static bool downloadAndSyncTargetMappings(); + static bool downloadAndSyncMirrorBuddyGroups(); + static bool downloadAndSyncStoragePools(); + + static bool downloadAllExceededQuotaLists( + const std::map>& targets); + static bool downloadExceededQuotaList(uint16_t targetId, QuotaDataType idType, + QuotaLimitType exType, UIntList* outIDList, FhgfsOpsErr& error); + + static void syncClientSessions(const std::vector& clientsList); + + void publishTargetState(uint16_t targetID, TargetConsistencyState targetState); + + bool publishLocalTargetStateChanges(const TargetStateMap& oldStates, + const TargetStateMap& changes); + + static bool registerNode(AbstractDatagramListener* dgramLis); + static bool registerTargetMappings(); + static void requestBuddyTargetStates(); + + private: + LogContext log; + + Mutex forceTargetStatesUpdateMutex; + Mutex forcePublishCapacitiesMutex; + Mutex forceStoragePoolsUpdateMutex; + Mutex forceCheckNetworkMutex; + bool forceTargetStatesUpdate; // true to force update of target states + bool forcePublishCapacities; // true to force publishing target capacities + bool forceStoragePoolsUpdate; // true to force update of storage pools + bool forceCheckNetwork; // true to force update of network interfaces + + virtual void run(); + void syncLoop(); + + // returns true if the local interfaces have changed + bool checkNetwork(); + void dropIdleConns(); + unsigned dropIdleConnsByStore(NodeStoreServers* nodes); + + void updateTargetStatesAndBuddyGroups(); + void publishTargetCapacities(); + + void forceMgmtdPoolsRefresh(); + + static void printSyncNodesResults(NodeType nodeType, NumNodeIDList* addedNodes, + NumNodeIDList* removedNodes); + + bool publishTargetStateChanges(UInt16List& targetIDs, UInt8List& oldStates, + UInt8List& newStates); + + static bool downloadAllExceededQuotaLists(uint16_t targetId); + public: + // inliners + void setForceTargetStatesUpdate() + { + std::lock_guard safeLock(forceTargetStatesUpdateMutex); + this->forceTargetStatesUpdate = true; + } + + void setForcePublishCapacities() + { + std::lock_guard safeLock(forcePublishCapacitiesMutex); + this->forcePublishCapacities = true; + } + + void setForceStoragePoolsUpdate() + { + std::lock_guard lock(forceStoragePoolsUpdateMutex); + forceStoragePoolsUpdate = true; + } + + void setForceCheckNetwork() + { + std::lock_guard lock(forceCheckNetworkMutex); + forceCheckNetwork = true; + } + + private: + // inliners + bool getAndResetForceTargetStatesUpdate() + { + std::lock_guard safeLock(forceTargetStatesUpdateMutex); + + bool retVal = this->forceTargetStatesUpdate; + + this->forceTargetStatesUpdate = false; + + return retVal; + } + + bool getAndResetForcePublishCapacities() + { + std::lock_guard safeLock(forcePublishCapacitiesMutex); + + bool retVal = this->forcePublishCapacities; + + this->forcePublishCapacities = false; + + return retVal; + } + + bool getAndResetForceStoragePoolsUpdate() + { + std::lock_guard lock(forceStoragePoolsUpdateMutex); + + bool retVal = forceStoragePoolsUpdate; + forceStoragePoolsUpdate = false; + + return retVal; + } + + bool getAndResetForceCheckNetwork() + { + std::lock_guard lock(forceCheckNetworkMutex); + + bool retVal = forceCheckNetwork; + forceCheckNetwork = false; + + return retVal; + } +}; + + diff --git a/storage/source/components/StorageStatsCollector.cpp b/storage/source/components/StorageStatsCollector.cpp new file mode 100644 index 0000000..9089423 --- /dev/null +++ b/storage/source/components/StorageStatsCollector.cpp @@ -0,0 +1,47 @@ +#include +#include +#include "StorageStatsCollector.h" + +/** + * Note: Other than the common StatsCollector::collectStats(), this method can handle multiple work + * queues. + */ +void StorageStatsCollector::collectStats() +{ + App* app = Program::getApp(); + MultiWorkQueueMap* workQueueMap = app->getWorkQueueMap(); + + HighResolutionStats newStats; + + const std::lock_guard lock(mutex); + + // get stats from first queue as basis + + MultiWorkQueueMapIter iter = workQueueMap->begin(); + + iter->second->getAndResetStats(&newStats); + + // add the stat values from following queues + + iter++; + + for( ; iter != workQueueMap->end(); iter++) + { + HighResolutionStats currentStats; + + iter->second->getAndResetStats(¤tStats); + + HighResolutionStatsTk::addHighResRawStats(currentStats, newStats); + HighResolutionStatsTk::addHighResIncStats(currentStats, newStats); + } + + // set current stats time + newStats.rawVals.statsTimeMS = TimeAbs().getTimeMS(); + + // take care of max history length + if(statsList.size() == historyLength) + statsList.pop_back(); + + // push new stats to front + statsList.push_front(newStats); +} diff --git a/storage/source/components/StorageStatsCollector.h b/storage/source/components/StorageStatsCollector.h new file mode 100644 index 0000000..27851d9 --- /dev/null +++ b/storage/source/components/StorageStatsCollector.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +/** + * Common StatsCollector cannot handle multiple work queues, so this derived class overrides + * the collectStats() method to handle multiple work queues. + */ +class StorageStatsCollector : public StatsCollector +{ + public: + StorageStatsCollector(unsigned collectIntervalMS, unsigned historyLength): + StatsCollector(NULL, collectIntervalMS, historyLength) + { + // nothing to be done here + } + + virtual ~StorageStatsCollector() {} + + + protected: + virtual void collectStats(); + +}; + diff --git a/storage/source/components/benchmarker/StorageBenchOperator.cpp b/storage/source/components/benchmarker/StorageBenchOperator.cpp new file mode 100644 index 0000000..69753fe --- /dev/null +++ b/storage/source/components/benchmarker/StorageBenchOperator.cpp @@ -0,0 +1,38 @@ +#include "StorageBenchOperator.h" + + +int StorageBenchOperator::initAndStartStorageBench(UInt16List* targetIDs, int64_t blocksize, + int64_t size, int threads, bool odirect, StorageBenchType type) +{ + return this->slave.initAndStartStorageBench(targetIDs, blocksize, size, threads, odirect, type); +} + +int StorageBenchOperator::cleanup(UInt16List* targetIDs) +{ + return this->slave.cleanup(targetIDs); +} + +int StorageBenchOperator::stopBenchmark() +{ + return this->slave.stopBenchmark(); +} + +StorageBenchStatus StorageBenchOperator::getStatusWithResults(UInt16List* targetIDs, + StorageBenchResultsMap* outResults) +{ + return this->slave.getStatusWithResults(targetIDs, outResults); +} + +void StorageBenchOperator::shutdownBenchmark() +{ + this->slave.shutdownBenchmark(); +} + +void StorageBenchOperator::waitForShutdownBenchmark() +{ + this->slave.waitForShutdownBenchmark(); +} + + + + diff --git a/storage/source/components/benchmarker/StorageBenchOperator.h b/storage/source/components/benchmarker/StorageBenchOperator.h new file mode 100644 index 0000000..fdd027d --- /dev/null +++ b/storage/source/components/benchmarker/StorageBenchOperator.h @@ -0,0 +1,45 @@ +#pragma once + + +#include "StorageBenchSlave.h" + + +class StorageBenchOperator +{ + public: + StorageBenchOperator() {} + + int initAndStartStorageBench(UInt16List* targetIDs, int64_t blocksize, int64_t size, + int threads, bool odirect, StorageBenchType type); + + int cleanup(UInt16List* targetIDs); + int stopBenchmark(); + StorageBenchStatus getStatusWithResults(UInt16List* targetIDs, + StorageBenchResultsMap* outResults); + void shutdownBenchmark(); + void waitForShutdownBenchmark(); + + private: + StorageBenchSlave slave; + + protected: + + public: + // inliners + + StorageBenchStatus getStatus() + { + return this->slave.getStatus(); + } + + StorageBenchType getType() + { + return this->slave.getType(); + } + + int getLastRunErrorCode() + { + return this->slave.getLastRunErrorCode(); + } +}; + diff --git a/storage/source/components/benchmarker/StorageBenchSlave.cpp b/storage/source/components/benchmarker/StorageBenchSlave.cpp new file mode 100644 index 0000000..58c5c23 --- /dev/null +++ b/storage/source/components/benchmarker/StorageBenchSlave.cpp @@ -0,0 +1,832 @@ +#include +#include +#include +#include +#include +#include "StorageBenchSlave.h" + +#include + +#define STORAGEBENCH_STORAGE_SUBDIR_NAME "benchmark" +#define STORAGEBENCH_READ_PIPE_TIMEOUT_MS 2000 + + +/* + * initialize and starts the storage benchmark with the given informations + * + * @param targetIDs a list with the targetIDs which the benchmark tests + * @param blocksize the blocksize for the benchmark + * @param size the size for the benchmark + * @param threads the number (simulated clients) of threads for the benchmark + * @param type the type of the benchmark + * @return the error code, 0 if the benchmark was initialize successful (STORAGEBENCH_ERROR..) + * + */ +int StorageBenchSlave::initAndStartStorageBench(UInt16List* targetIDs, int64_t blocksize, + int64_t size, int threads, bool odirect, StorageBenchType type) +{ + const char* logContext = "Storage Benchmark (init)"; + + int lastError = STORAGEBENCH_ERROR_NO_ERROR; + int retVal = STORAGEBENCH_ERROR_NO_ERROR; + + this->resetSelfTerminate(); + + const std::lock_guard lock(statusMutex); + + if (STORAGEBENCHSTATUS_IS_ACTIVE(this->status)) + { + LogContext(logContext).logErr( + std::string("Benchmark is already running. It's not possible to start a benchmark if a" + "benchmark is running.")); + + retVal = STORAGEBENCH_ERROR_RUNTIME_IS_RUNNING; + } + else + { + retVal = initStorageBench(targetIDs, blocksize, size, threads, odirect, type); + } + + if(retVal == STORAGEBENCH_ERROR_NO_ERROR) + { + if (this->status != StorageBenchStatus_INITIALISED) + { + LogContext(logContext).logErr( + std::string("Benchmark not correctly initialized.")); + this->lastRunErrorCode = STORAGEBENCH_ERROR_UNINITIALIZED; + this->status = StorageBenchStatus_ERROR; + + retVal = STORAGEBENCH_ERROR_UNINITIALIZED; + } + else + { + try + { + this->start(); + this->status = StorageBenchStatus_RUNNING; + lastError = this->lastRunErrorCode; + } + catch(PThreadCreateException& e) + { + LogContext(logContext).logErr(std::string("Unable to start thread: ") + e.what() ); + this->status = StorageBenchStatus_ERROR; + lastError = this->lastRunErrorCode; + } + } + } + + if(lastError != STORAGEBENCH_ERROR_NO_ERROR) + { + retVal = lastError; + } + + return retVal; +} + +/* + * initialize the storage benchmark with the given informations + * + * @param targetIDs a list with the targetIDs which the benchmark tests + * @param blocksize the blocksize for the benchmark + * @param size the size for the benchmark + * @param threads the number (simulated clients) of threads for the benchmark + * @param type the type of the benchmark + * @return the error code, 0 if the benchmark was initialize successful (STORAGEBENCH_ERROR..) + * + */ +int StorageBenchSlave::initStorageBench(UInt16List* targetIDs, int64_t blocksize, + int64_t size, int threads, bool odirect, StorageBenchType type) +{ + const char* logContext = "Storage Benchmark (init)"; + LogContext(logContext).log(Log_DEBUG, "Initializing benchmark ..."); + + this->benchType = type; + this->targetIDs = new auto(*targetIDs); + this->blocksize = blocksize; + this->size = size; + this->numThreads = threads; + this->odirect = odirect; + this->numThreadsDone = 0; + + initThreadData(); + + if (!initTransferData()) + { + this->lastRunErrorCode = STORAGEBENCH_ERROR_INIT_TRANSFER_DATA; + this->status = StorageBenchStatus_ERROR; + return STORAGEBENCH_ERROR_INIT_TRANSFER_DATA; + } + + if (this->benchType == StorageBenchType_READ) + { + if (!checkReadData()) + { + LogContext(logContext).logErr( + std::string("No (or not enough) data for read benchmark available. " + "Start a write benchmark with the same size parameter before the read benchmark.") ); + + this->lastRunErrorCode = STORAGEBENCH_ERROR_INIT_READ_DATA; + this->status = StorageBenchStatus_ERROR; + return STORAGEBENCH_ERROR_INIT_READ_DATA; + } + } + else + if (this->benchType == StorageBenchType_WRITE) + { + if (!createBenchmarkFolder() ) + { + LogContext(logContext).logErr( + std::string("Couldn't create the benchmark folder.")); + + this->lastRunErrorCode = STORAGEBENCH_ERROR_INIT_CREATE_BENCH_FOLDER; + this->status = StorageBenchStatus_ERROR; + return STORAGEBENCH_ERROR_INIT_CREATE_BENCH_FOLDER; + } + } + else + { + LogContext(logContext).logErr(std::string( + "Unknown benchmark type: " + StringTk::uintToStr(this->benchType) ) ); + return STORAGEBENCH_ERROR_INITIALIZATION_ERROR; + } + + this->lastRunErrorCode = STORAGEBENCH_ERROR_NO_ERROR; + this->status = StorageBenchStatus_INITIALISED; + + LogContext(logContext).log(Log_DEBUG, std::string("Benchmark initialized.")); + + return STORAGEBENCH_ERROR_NO_ERROR; +} + +/* + * initialize the data which will be written to the disk, the size of the transfer data a equal + * to the blocksize and initialized with random characters + * + * @return true if the random data are initialized, + * false if a error occurred + * + */ +bool StorageBenchSlave::initTransferData() +{ + const char* logContext = "Storage Benchmark (init buf)"; + LogContext(logContext).log(Log_DEBUG, std::string("Initializing random data...")); + + void* rawTransferData; + if (posix_memalign(&rawTransferData, 4096, blocksize) != 0) + return false; + transferData.reset(static_cast(rawTransferData)); + + Random randomizer = Random(); + + for (int64_t counter = 0; counter < this->blocksize; counter++) + { + this->transferData[counter] = randomizer.getNextInt(); + } + + LogContext(logContext).log(Log_DEBUG, std::string("Random data initialized.")); + + return true; +} + +/* + * frees the transfer data + */ +void StorageBenchSlave::freeTransferData() +{ + transferData.reset(); +} + +/* + * initialize the informations about the threads + * + */ +void StorageBenchSlave::initThreadData() +{ + const char* logContext = "Storage Benchmark (init)"; + LogContext(logContext).log(Log_DEBUG, std::string("Initializing thread data...")); + + this->threadData.clear(); + + int allThreadCounter = 0; + for (UInt16ListIter iter = targetIDs->begin(); iter != targetIDs->end(); iter++) + { + for (int threadCount = 0; threadCount < this->numThreads; threadCount++) + { + StorageBenchThreadData data; + data.targetID = *iter; + data.targetThreadID = threadCount; + data.engagedSize = 0; + data.fileDescriptor = 0; + data.neededTime = 0; + + + this->threadData[allThreadCounter] = data; + allThreadCounter++; + } + } + + LogContext(logContext).log(Log_DEBUG, "Thread data initialized."); +} + +/* + * starts the benchmark, a read or a write benchmark + * + */ +void StorageBenchSlave::run() +{ + const char* logContext = "Storage Benchmark (run)"; + LogContext(logContext).log(Log_CRITICAL, std::string("Benchmark started...")); + + App* app = Program::getApp(); + + bool openRes = openFiles(); + if (openRes) + { + this->startTime.setToNow(); + + // add a work package into the worker queue for every thread + for(StorageBenchThreadDataMapIter iter = threadData.begin(); + iter != threadData.end(); + iter++) + { + LOG_DEBUG(logContext, Log_DEBUG, std::string("Add work for target: ") + + StringTk::uintToStr(iter->second.targetID) ); + LOG_DEBUG(logContext, Log_DEBUG, std::string("- threadID: ") + + StringTk::intToStr(iter->first) ); + LOG_DEBUG(logContext, Log_DEBUG, std::string("- type: ") + + StringTk::intToStr(this->benchType) ); + + StorageBenchWork* work = new StorageBenchWork(iter->second.targetID, iter->first, + iter->second.fileDescriptor, this->benchType, getNextPackageSize(iter->first), + this->threadCommunication, this->transferData.get()); + + app->getWorkQueue(iter->second.targetID)->addIndirectWork(work); + } + + while(getStatus() == StorageBenchStatus_RUNNING) + { + int threadID = 0; + + if (this->threadCommunication->waitForIncomingData(STORAGEBENCH_READ_PIPE_TIMEOUT_MS)) + { + this->threadCommunication->getReadFD()->readExact(&threadID, sizeof(int)); + } + else + { + threadID = STORAGEBENCH_ERROR_COM_TIMEOUT; + } + + if (this->getSelfTerminate()) + { + LogContext(logContext).logErr(std::string("Abort benchmark.")); + this->lastRunErrorCode = STORAGEBENCH_ERROR_ABORT_BENCHMARK; + setStatus(StorageBenchStatus_STOPPING); + + if (threadID != STORAGEBENCH_ERROR_COM_TIMEOUT) + { + this->threadData[threadID].neededTime = this->startTime.elapsedMS(); + this->numThreadsDone++; + } + + break; + } + else + if (threadID == STORAGEBENCH_ERROR_WORKER_ERROR) + { + LogContext(logContext).logErr(std::string("I/O operation on disk failed.")); + this->lastRunErrorCode = STORAGEBENCH_ERROR_WORKER_ERROR; + setStatus(StorageBenchStatus_STOPPING); + + // increment the thread counter, because the thread which sent this error hasn't a + // work package in the queue of the workers but the response from the other threads + // must be collected + this->numThreadsDone++; + + break; + } + else + if (threadID == STORAGEBENCH_ERROR_COM_TIMEOUT) + { + continue; + } + else + if ( (threadID < -1) || ( ( (unsigned)threadID) >= this->threadData.size() ) ) + { // error if the worker reports an unknown threadID + std::string errorMessage("Unknown thread ID: " + StringTk::intToStr(threadID) + "; " + "map size: " + StringTk::uintToStr(this->threadData.size() ) ); + + LogContext(logContext).logErr(errorMessage); + this->lastRunErrorCode = STORAGEBENCH_ERROR_RUNTIME_ERROR; + setStatus(StorageBenchStatus_STOPPING); + + // increment the thread counter, because the thread which sent this error hasn't a + // work package in the queue of the workers but the response from the other threads + // must be collected + this->numThreadsDone++; + + break; + } + + StorageBenchThreadData* currentData = &this->threadData[threadID]; + int64_t workSize = getNextPackageSize(threadID); + + // add a new work package into the workers queue for the reported thread only if the + // data size for the thread is bigger then 0 + if (workSize != 0) + { + StorageBenchWork* work = new StorageBenchWork(currentData->targetID, threadID, + currentData->fileDescriptor, this->benchType, workSize, this->threadCommunication, + this->transferData.get()); + app->getWorkQueue(currentData->targetID)->addIndirectWork(work); + } + else + { + // the thread has finished his work + currentData->neededTime = this->startTime.elapsedMS(); + this->numThreadsDone++; + } + + if (this->numThreadsDone >= this->threadData.size()) + { + setStatus(StorageBenchStatus_FINISHING); + } + } + + //collect all responses from the worker + while ( (this->numThreadsDone < this->threadData.size()) && app->getWorkersRunning() ) + { + int threadID = 0; + + if (this->threadCommunication->waitForIncomingData(STORAGEBENCH_READ_PIPE_TIMEOUT_MS)) + { + this->threadCommunication->getReadFD()->readExact(&threadID, sizeof(int)); + } + else + { + continue; + } + + LOG_DEBUG(logContext, Log_DEBUG, std::string("Collect response from worker.")); + + if(threadID >= 0) + this->threadData[threadID].neededTime = this->startTime.elapsedMS(); + + this->numThreadsDone++; + } + + // all workers finished/stopped ==> close all files + closeFiles(); + freeTransferData(); + + // all threads have finished the work or the benchmark was stopped, set new status + if (this->getStatus() == StorageBenchStatus_FINISHING) + { + this->setStatus(StorageBenchStatus_FINISHED); + LogContext(logContext).log(Log_CRITICAL, std::string("Benchmark finished.")); + } + else + if (this->getStatus() == StorageBenchStatus_STOPPING) + { + if (this->lastRunErrorCode != STORAGEBENCH_ERROR_NO_ERROR) + { + this->setStatus(StorageBenchStatus_ERROR); + LogContext(logContext).log(Log_CRITICAL, std::string("Benchmark stopped with errors.")); + } + else + { + this->setStatus(StorageBenchStatus_STOPPED); + LogContext(logContext).log(Log_CRITICAL, std::string("Benchmark stopped.")); + } + } + + } + else + { + this->lastRunErrorCode = STORAGEBENCH_ERROR_RUNTIME_OPEN_FILES; + setStatus(StorageBenchStatus_ERROR); + } +} + +/* + * checks the size of the benchmark files, the benchmark files must be big enough for the + * read benchmark + * + * @return true if data for a read benchmark exists, + * false if the files to small or a error occurred + * + */ +bool StorageBenchSlave::checkReadData() +{ + const char* logContext = "Storage Benchmark (check)"; + + for (StorageBenchThreadDataMapIter iter = threadData.begin(); + iter != threadData.end(); iter++) + { + auto* const target = Program::getApp()->getStorageTargets()->getTarget(iter->second.targetID); + if (!target) + { + LogContext(logContext).logErr(std::string("TargetID unknown.")); + return false; + } + + std::string path = target->getPath().str(); + + path = path + "/" + STORAGEBENCH_STORAGE_SUBDIR_NAME + "/" + + StringTk::uintToStr(iter->second.targetThreadID); + + int error = -1; + struct stat fileStat; + error = stat(path.c_str(), &fileStat); + + if (error != -1) + { + if (fileStat.st_size < this->size) + { + LogContext(logContext).logErr(std::string("Existing benchmark file too small. " + "Requested file size: " + StringTk::int64ToStr(this->size) + " " + "File size: " + StringTk::intToStr(fileStat.st_size))); + return false; + } + } + else + { + LogContext(logContext).logErr(std::string("Couldn't stat() benchmark file. SysErr: ") + + System::getErrString() ); + return false; + } + } + + return true; +} + +/* + * creates the benchmark folder in the storage target folder + * + * @return true if all benchmark folders are created, + * false if a error occurred + * + */ +bool StorageBenchSlave::createBenchmarkFolder() +{ + const char* logContext = "Storage Benchmark (mkdir)"; + + for(UInt16ListIter iter = this->targetIDs->begin(); iter != this->targetIDs->end(); iter++) + { + auto* const target = Program::getApp()->getStorageTargets()->getTarget(*iter); + if (!target) + { + LogContext(logContext).logErr("TargetID unknown: " + StringTk::uintToStr(*iter) ); + return false; + } + + Path currentPath(target->getPath() / STORAGEBENCH_STORAGE_SUBDIR_NAME); + if(!StorageTk::createPathOnDisk(currentPath, false)) + { + LogContext(logContext).logErr( + std::string("Unable to create benchmark directory: " + currentPath.str() ) ); + return false; + } + } + + return true; +} + +/* + * opens all needed files for the benchmark. This method will be executed at the start + * of the benchmark + * + * @return true if all files are opened, + * false if a error occurred + * + */ +bool StorageBenchSlave::openFiles() +{ + const char* logContext = "Storage Benchmark (open)"; + mode_t openMode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + + for(StorageBenchThreadDataMapIter iter = threadData.begin(); + iter != threadData.end(); + iter++) + { + auto* const target = Program::getApp()->getStorageTargets()->getTarget(iter->second.targetID); + if (!target) + { + LogContext(logContext).logErr( + "TargetID unknown: " + StringTk::uintToStr(iter->second.targetID) ); + return false; + } + + std::string path = target->getPath().str(); + + path = path + "/" STORAGEBENCH_STORAGE_SUBDIR_NAME "/" + + StringTk::uintToStr(iter->second.targetThreadID); + + int fileDescriptor = -1; + + // open file + + int directFlag = this->odirect ? O_DIRECT : 0; + if(this->benchType == StorageBenchType_READ) + fileDescriptor = open(path.c_str(), O_RDONLY | directFlag); + else + fileDescriptor = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | directFlag, openMode); + + if (fileDescriptor != -1) + iter->second.fileDescriptor = fileDescriptor; + else + { // open failed + LogContext(logContext).logErr("Couldn't open benchmark file: " + path + "; " + "SysErr: " + System::getErrString() ); + return false; + } + } + + return true; +} + +bool StorageBenchSlave::closeFiles() +{ + const char* logContext = "Storage Benchmark (close)"; + + bool retVal = true; + + for(StorageBenchThreadDataMapIter iter = threadData.begin(); + iter != threadData.end(); + iter++) + { + int tmpRetVal = close(iter->second.fileDescriptor); + + if (tmpRetVal != 0) + { + int closeErrno = errno; + + auto* const target = Program::getApp()->getStorageTargets()->getTarget( + iter->second.targetID); + if (!target) + { + LogContext(logContext).logErr( + "TargetID unknown: " + StringTk::uintToStr(iter->second.targetID) ); + return false; + } + + std::string path = target->getPath().str(); + + path = path + "/" + STORAGEBENCH_STORAGE_SUBDIR_NAME + "/" + + StringTk::uintToStr(iter->second.targetThreadID); + + LogContext(logContext).logErr("Couldn't close file: " + path + "; " + "SysErr: " + System::getErrString(closeErrno) ); + + retVal = false; + } + } + + return retVal; +} + +/* + * calculates the size (bytes) of the data which will be written on the disk by the worker with + * the next work package for the given thread + * + * @param threadID the threadID + * @return the size of the data for next work package in bytes, + * if 0 the given thread has written all data + * + */ +int64_t StorageBenchSlave::getNextPackageSize(int threadID) +{ + int64_t retVal = BEEGFS_MIN(this->blocksize, + this->size - this->threadData[threadID].engagedSize); + this->threadData[threadID].engagedSize += retVal; + + return retVal; +} + + +/* + * calculates the throughput (kB/s) of the given target + * + * @param targetID the targetID + * @return the throughput of the given target in kilobytes per second + * + */ +int64_t StorageBenchSlave::getResult(uint16_t targetID) +{ + int64_t size = 0; + int64_t time = 0; + + for(StorageBenchThreadDataMapIter iter = this->threadData.begin(); + iter != this->threadData.end(); + iter++) + { + if (iter->second.targetID == targetID) + { + // summarize the size of the different threads which worked on a target + size += iter->second.engagedSize; + + // search the thread with the longest runtime + if (time < this->threadData[iter->first].neededTime) + time = this->threadData[iter->first].neededTime; + + } + } + + // if the threads are not finished use the needed time up to now + if (time == 0) + time = this->startTime.elapsedMS(); + + // if no results available return zero + if ( (size == 0) || (time == 0) ) + return 0; + + // input: size in bytes, time in milliseconds, + // output: in kilobytes per second + return ( (size * 1000) / (time * 1024) ); +} + +/* + * calculates the throughput (kB/s) of the given targets + * + * @param targetIDs the list of targetIDs + * @param outResults a initialized map for the results, which contains the results after + * execution of the method + * + */ +void StorageBenchSlave::getResults(UInt16List* targetIDs, StorageBenchResultsMap* outResults) +{ + for (UInt16ListIter iter = targetIDs->begin(); iter != targetIDs->end(); iter++) + { + (*outResults)[*iter] = getResult(*iter); + } +} + +/* + * calculates the throughput (kB/s) of all targets + * + * @param outResults a initialized map for the results, which contains the results after + * execution of the method + * + */ +void StorageBenchSlave::getAllResults(StorageBenchResultsMap* outResults) +{ + for (UInt16ListIter iter = this->targetIDs->begin(); iter != this->targetIDs->end(); iter++) + { + (*outResults)[*iter] = getResult(*iter); + } +} + +/* + * calculates the throughput (kB/s) of the given targets and returns the status of the benchmark + * + * @param targetIDs the list of targetIDs + * @param outResults a initialized map for the results, which contains the results after + * execution of the method + * @return the status of the benchmark + * + */ +StorageBenchStatus StorageBenchSlave::getStatusWithResults(UInt16List* targetIDs, + StorageBenchResultsMap* outResults) +{ + getResults(targetIDs, outResults); + return getStatus(); +} + +/* + * stop the benchmark + * + * @return the error code, 0 if the benchmark will stop (STORAGEBENCH_ERROR..) + * + */ +int StorageBenchSlave::stopBenchmark() +{ + const std::lock_guard lock(statusMutex); + + if (this->status == StorageBenchStatus_RUNNING) + { + this->status = StorageBenchStatus_STOPPING; + return STORAGEBENCH_ERROR_NO_ERROR; + } + else + if(this->status == StorageBenchStatus_FINISHING || this->status == StorageBenchStatus_STOPPING) + { + return STORAGEBENCH_ERROR_NO_ERROR; + } + + return STORAGEBENCH_ERROR_NO_ERROR; +} + +/* + * deletes all files in the benchmark folder of the given targets + * + * @param targetIDs the list of targetIDs which will be cleaned + * @return the error code, 0 if the cleanup was successful (STORAGEBENCH_ERROR..) + * + */ +int StorageBenchSlave::cleanup(UInt16List* targetIDs) +{ + const std::lock_guard lock(statusMutex); + const char* logContext = "Storage Benchmark (cleanup)"; + + //cleanup only possible if no benchmark is running + if (STORAGEBENCHSTATUS_IS_ACTIVE(this->status)) + { + LogContext(logContext).logErr("Cleanup not possible benchmark is running"); + + return STORAGEBENCH_ERROR_RUNTIME_CLEANUP_JOB_ACTIVE; + } + + for(UInt16ListIter iter = targetIDs->begin(); iter != targetIDs->end(); iter++) + { + auto* const target = Program::getApp()->getStorageTargets()->getTarget(*iter); + if (!target) + { + LogContext(logContext).logErr(std::string("TargetID unknown.")); + return STORAGEBENCH_ERROR_RUNTIME_UNKNOWN_TARGET; + } + + std::string path = target->getPath().str(); + + path.append("/"); + path.append(STORAGEBENCH_STORAGE_SUBDIR_NAME); + path.append("/"); + + DIR* dir = opendir(path.c_str()); + if (dir == NULL) + { + int openDirErrno = errno; + int errRetVal; + + if (openDirErrno == ENOENT) + { // benchmark directory doesn't exist, no benchmark data for cleanup + errRetVal = STORAGEBENCH_ERROR_NO_ERROR; + } + else + { + this->lastRunErrorCode = STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER; + errRetVal = STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER; + + LogContext(logContext).logErr("Unable to delete files in benchmark directory: " + path + + "; failed with SysErr: " + System::getErrString(errno)); + } + + return errRetVal; + } + + struct dirent* dirEntry = StorageTk::readdirFiltered(dir); + + while (dirEntry) + { + struct stat statData; + std::string filePath(path + dirEntry->d_name); + + int retVal = stat(filePath.c_str(), &statData); + if ((retVal == 0) && (S_ISREG(statData.st_mode)) ) + { + + int error = unlink(filePath.c_str()); + + if(error != 0) + { + LogContext(logContext).logErr( + std::string("Unable to delete files in benchmark directory: " + + path)); + this->lastRunErrorCode = STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER; + + closedir(dir); + return STORAGEBENCH_ERROR_RUNTIME_DELETE_FOLDER; + } + } + else + if(!S_ISREG(statData.st_mode)) + LogContext(logContext).logErr("Unable to delete files in benchmark directory: " + + path + " It's not a regular file."); + else + LogContext(logContext).logErr("Unable to delete files in benchmark directory: " + path); + + dirEntry = StorageTk::readdirFiltered(dir); + } + + closedir(dir); + } + + return STORAGEBENCH_ERROR_NO_ERROR; +} + +/* + * aborts the benchmark, will be used if SIGINT received + * + */ +void StorageBenchSlave::shutdownBenchmark() +{ + this->selfTerminate(); +} + +void StorageBenchSlave::waitForShutdownBenchmark() +{ + const std::lock_guard lock(statusMutex); + + while(STORAGEBENCHSTATUS_IS_ACTIVE(this->status)) + { + this->statusChangeCond.wait(&this->statusMutex); + } +} diff --git a/storage/source/components/benchmarker/StorageBenchSlave.h b/storage/source/components/benchmarker/StorageBenchSlave.h new file mode 100644 index 0000000..99650d7 --- /dev/null +++ b/storage/source/components/benchmarker/StorageBenchSlave.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +// struct for the informations about a thread which simulates a client +struct StorageBenchThreadData +{ + uint16_t targetID; + int targetThreadID; + int64_t engagedSize; // amount of data which was submitted for write/read + int fileDescriptor; + int64_t neededTime; +}; + +// deleter functor for transferData +struct TransferDataDeleter { + void operator()(char* transferData) { free(transferData); } +}; + +// map for the informations about a thread; key: virtual threadID, value: information about thread +typedef std::map StorageBenchThreadDataMap; +typedef StorageBenchThreadDataMap::iterator StorageBenchThreadDataMapIter; +typedef StorageBenchThreadDataMap::const_iterator StorageBenchThreadDataMapCIter; +typedef StorageBenchThreadDataMap::value_type StorageBenchThreadDataMapVal; + + + +class StorageBenchSlave : public PThread +{ + public: + StorageBenchSlave() + : PThread("StorageBenchSlave"), + threadCommunication(new Pipe(false, false) ), + log("Storage Benchmark"), + lastRunErrorCode(STORAGEBENCH_ERROR_NO_ERROR), + status(StorageBenchStatus_UNINITIALIZED), + benchType(StorageBenchType_NONE), + blocksize(1), // useless defaults + size(1), // useless defaults + numThreads(1), // useless defaults + numThreadsDone(0), + targetIDs(NULL), + transferData(nullptr) + { } + + virtual ~StorageBenchSlave() + { + SAFE_DELETE(this->threadCommunication); + SAFE_DELETE(this->targetIDs); + } + + int initAndStartStorageBench(UInt16List* targetIDs, int64_t blocksize, int64_t size, + int threads, bool odirect, StorageBenchType type); + + int cleanup(UInt16List* targetIDs); + int stopBenchmark(); + StorageBenchStatus getStatusWithResults(UInt16List* targetIDs, + StorageBenchResultsMap* outResults); + void shutdownBenchmark(); + void waitForShutdownBenchmark(); + + protected: + + private: + Pipe* threadCommunication; + Mutex statusMutex; + Condition statusChangeCond; + + LogContext log; + int lastRunErrorCode; // STORAGEBENCH_ERROR_... + + StorageBenchStatus status; + StorageBenchType benchType; + int64_t blocksize; + int64_t size; + int numThreads; + bool odirect; + unsigned int numThreadsDone; + + UInt16List* targetIDs; + StorageBenchThreadDataMap threadData; + std::unique_ptr transferData; + + TimeFine startTime; + + + virtual void run(); + + int initStorageBench(UInt16List* targetIDs, int64_t blocksize, int64_t size, + int threads, bool odirect, StorageBenchType type); + bool initTransferData(void); + void initThreadData(); + void freeTransferData(); + + bool checkReadData(void); + bool createBenchmarkFolder(void); + bool openFiles(void); + bool closeFiles(void); + + int64_t getNextPackageSize(int threadID); + int64_t getResult(uint16_t targetID); + void getResults(UInt16List* targetIDs, StorageBenchResultsMap* outResults); + void getAllResults(StorageBenchResultsMap* outResults); + + void setStatus(StorageBenchStatus newStatus) + { + const std::lock_guard lock(statusMutex); + + this->status = newStatus; + this->statusChangeCond.broadcast(); + } + + public: + //public inliners + int getLastRunErrorCode() + { + return this->lastRunErrorCode; + } + + StorageBenchStatus getStatus() + { + const std::lock_guard lock(statusMutex); + + return this->status; + } + + StorageBenchType getType() + { + return this->benchType; + } + + UInt16List* getTargetIDs() + { + return this->targetIDs; + } +}; + diff --git a/storage/source/components/buddyresyncer/BuddyResyncJob.cpp b/storage/source/components/buddyresyncer/BuddyResyncJob.cpp new file mode 100644 index 0000000..9e3730c --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncJob.cpp @@ -0,0 +1,745 @@ +#include + +#include +#include +#include +#include +#include +#include +#include "BuddyResyncJob.h" + +#include + +#define BUDDYRESYNCJOB_MAXDIRWALKDEPTH 2 + +BuddyResyncJob::BuddyResyncJob(uint16_t targetID) : + PThread("BuddyResyncJob_" + StringTk::uintToStr(targetID)), + targetID(targetID), + status(BuddyResyncJobState_NOTSTARTED), + startTime(0), endTime(0) +{ + App* app = Program::getApp(); + unsigned numGatherSlaves = app->getConfig()->getTuneNumResyncGatherSlaves(); + unsigned numSyncSlavesTotal = app->getConfig()->getTuneNumResyncSlaves(); + unsigned numFileSyncSlaves = BEEGFS_MAX((numSyncSlavesTotal / 2), 1); + unsigned numDirSyncSlaves = BEEGFS_MAX((numSyncSlavesTotal / 2), 1); + + // prepare slaves (vectors) and result vector + gatherSlaveVec.resize(numGatherSlaves); + fileSyncSlaveVec.resize(numFileSyncSlaves); + dirSyncSlaveVec.resize(numDirSyncSlaves); +} + +BuddyResyncJob::~BuddyResyncJob() +{ + for(BuddyResyncerGatherSlaveVecIter iter = gatherSlaveVec.begin(); iter != gatherSlaveVec.end(); + iter++) + { + BuddyResyncerGatherSlave* slave = *iter; + SAFE_DELETE(slave); + } + + for(BuddyResyncerFileSyncSlaveVecIter iter = fileSyncSlaveVec.begin(); + iter != fileSyncSlaveVec.end(); iter++) + { + BuddyResyncerFileSyncSlave* slave = *iter; + SAFE_DELETE(slave); + } + + for(BuddyResyncerDirSyncSlaveVecIter iter = dirSyncSlaveVec.begin(); + iter != dirSyncSlaveVec.end(); iter++) + { + BuddyResyncerDirSyncSlave* slave = *iter; + SAFE_DELETE(slave); + } +} + +void BuddyResyncJob::run() +{ + // make sure only one job at a time can run! + { + std::lock_guard mutexLock(statusMutex); + + if (status == BuddyResyncJobState_RUNNING) + { + LogContext(__func__).logErr("Refusing to run same BuddyResyncJob twice!"); + return; + } + else + { + status = BuddyResyncJobState_RUNNING; + startTime = time(NULL); + endTime = 0; + } + } + + App* app = Program::getApp(); + StorageTargets* storageTargets = app->getStorageTargets(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + TargetMapper* targetMapper = app->getTargetMapper(); + NodeStoreServers* storageNodes = app->getStorageNodes(); + WorkerList* workerList = app->getWorkers(); + + bool startGatherSlavesRes; + bool startSyncSlavesRes; + + std::string targetPath; + std::string chunksPath; + + bool buddyCommIsOverride = false; // treat errors during lastbuddycomm read as "0, no override" + int64_t lastBuddyCommTimeSecs; + int64_t lastBuddyCommSafetyThresholdSecs; + bool checkTopLevelDirRes; + bool walkRes; + + auto& target = *storageTargets->getTargets().at(targetID); + + shallAbort.setZero(); + targetWasOffline = false; + + // delete sync candidates and gather queue; just in case there was something from a previous run + syncCandidates.clear(); + gatherSlavesWorkQueue.clear(); + + target.setBuddyResyncInProgress(true); + + LogContext(__func__).log(Log_NOTICE, + "Started resync of targetID " + StringTk::uintToStr(targetID)); + + // before starting the threads make sure every worker knows about the resync (the current work + // package must be finished), for that we use a dummy package + Mutex mutex; + Condition counterIncrementedCond; + + SynchronizedCounter numReadyWorkers; + size_t numWorkers = workerList->size(); + for (WorkerListIter iter = workerList->begin(); iter != workerList->end(); iter++) + { + Worker* worker = *iter; + PersonalWorkQueue* personalQueue = worker->getPersonalWorkQueue(); + MultiWorkQueue* workQueue = worker->getWorkQueue(); + IncSyncedCounterWork* incCounterWork = new IncSyncedCounterWork(&numReadyWorkers); + + workQueue->addPersonalWork(incCounterWork, personalQueue); + } + + numReadyWorkers.waitForCount(numWorkers); + + // notify buddy, that resync started and wait for confirmation + uint16_t buddyTargetID = buddyGroupMapper->getBuddyTargetID(targetID); + NumNodeID buddyNodeID = targetMapper->getNodeID(buddyTargetID); + auto buddyNode = storageNodes->referenceNode(buddyNodeID); + StorageResyncStartedMsg storageResyncStartedMsg(buddyTargetID); + const auto respMsg = MessagingTk::requestResponse(*buddyNode, storageResyncStartedMsg, + NETMSGTYPE_StorageResyncStartedResp); + + std::pair lastBuddyComm; + + if (!respMsg) + { + LOG(MIRRORING, ERR, "Unable to notify buddy about resync attempt. Resync will not start.", + targetID, buddyTargetID); + setStatus(BuddyResyncJobState_FAILURE); + goto cleanup; + } + + startGatherSlavesRes = startGatherSlaves(target); + if (!startGatherSlavesRes) + { + setStatus(BuddyResyncJobState_FAILURE); + goto cleanup; + } + + startSyncSlavesRes = startSyncSlaves(); + if (!startSyncSlavesRes) + { + setStatus(BuddyResyncJobState_FAILURE); + + // terminate gather slaves + for (size_t i = 0; i < gatherSlaveVec.size(); i++) + gatherSlaveVec[i]->selfTerminate(); + + goto cleanup; + } + + numDirsDiscovered.setZero(); + numDirsMatched.setZero(); + + // walk over the directories until we reach a certain level and then pass the direcories to + // gather slaves to parallelize it + targetPath = target.getPath().str(); + chunksPath = targetPath + "/" + CONFIG_BUDDYMIRROR_SUBDIR_NAME; + + lastBuddyComm = target.getLastBuddyComm(); + buddyCommIsOverride = lastBuddyComm.first; + lastBuddyCommTimeSecs = std::chrono::system_clock::to_time_t(lastBuddyComm.second); + + lastBuddyCommSafetyThresholdSecs = app->getConfig()->getSysResyncSafetyThresholdMins()*60; + if ( (lastBuddyCommSafetyThresholdSecs == 0) && (!buddyCommIsOverride) ) // ignore timestamp file + lastBuddyCommTimeSecs = 0; + else + if (lastBuddyCommTimeSecs > lastBuddyCommSafetyThresholdSecs) + lastBuddyCommTimeSecs -= lastBuddyCommSafetyThresholdSecs; + + checkTopLevelDirRes = checkTopLevelDir(chunksPath, lastBuddyCommTimeSecs); + if (!checkTopLevelDirRes) + { + setStatus(BuddyResyncJobState_FAILURE); + + // terminate gather slaves + for (size_t i = 0; i < gatherSlaveVec.size(); i++) + gatherSlaveVec[i]->selfTerminate(); + + // terminate sync slaves + for (size_t i = 0; i < fileSyncSlaveVec.size(); i++) + fileSyncSlaveVec[i]->selfTerminate(); + + for (size_t i = 0; i < dirSyncSlaveVec.size(); i++) + dirSyncSlaveVec[i]->selfTerminate(); + + goto cleanup; + } + + walkRes = walkDirs(chunksPath, "", 0, lastBuddyCommTimeSecs); + if (!walkRes) + { + setStatus(BuddyResyncJobState_FAILURE); + + // terminate gather slaves + for (size_t i = 0; i < gatherSlaveVec.size(); i++) + gatherSlaveVec[i]->selfTerminate(); + + // terminate sync slaves + for (size_t i = 0; i < fileSyncSlaveVec.size(); i++) + fileSyncSlaveVec[i]->selfTerminate(); + + for (size_t i = 0; i < dirSyncSlaveVec.size(); i++) + dirSyncSlaveVec[i]->selfTerminate(); + + goto cleanup; + } + + // all directories are read => tell gather slave to stop when work queue is empty and wait for + // all to stop + for(size_t i = 0; i < gatherSlaveVec.size(); i++) + { + if (likely(shallAbort.read() == 0)) + gatherSlaveVec[i]->setOnlyTerminateIfIdle(true); + else + gatherSlaveVec[i]->setOnlyTerminateIfIdle(false); + + gatherSlaveVec[i]->selfTerminate(); + } + + joinGatherSlaves(); + + // gather slaves have finished => tell sync slaves to stop when work packages are empty and wait + for(size_t i = 0; i < fileSyncSlaveVec.size(); i++) + { + if (likely(shallAbort.read() == 0)) + fileSyncSlaveVec[i]->setOnlyTerminateIfIdle(true); + else + fileSyncSlaveVec[i]->setOnlyTerminateIfIdle(false); + + fileSyncSlaveVec[i]->selfTerminate(); + } + + for(size_t i = 0; i < dirSyncSlaveVec.size(); i++) + { + if (likely(shallAbort.read() == 0)) + dirSyncSlaveVec[i]->setOnlyTerminateIfIdle(true); + else + dirSyncSlaveVec[i]->setOnlyTerminateIfIdle(false); + + dirSyncSlaveVec[i]->selfTerminate(); + } + + joinSyncSlaves(); + +cleanup: + // wait for gather slaves to stop + for(BuddyResyncerGatherSlaveVecIter iter = gatherSlaveVec.begin(); + iter != gatherSlaveVec.end(); iter++) + { + BuddyResyncerGatherSlave* slave = *iter; + if(slave) + { + std::lock_guard safeLock(slave->statusMutex); + while (slave->isRunning) + slave->isRunningChangeCond.wait(&(slave->statusMutex)); + } + } + + bool syncErrors = false; + + // wait for sync slaves to stop and save if any errors occured + for(BuddyResyncerFileSyncSlaveVecIter iter = fileSyncSlaveVec.begin(); + iter != fileSyncSlaveVec.end(); iter++) + { + BuddyResyncerFileSyncSlave* slave = *iter; + if(slave) + { + { + std::lock_guard safeLock(slave->statusMutex); + while (slave->isRunning) + slave->isRunningChangeCond.wait(&(slave->statusMutex)); + } + + if (slave->getErrorCount() != 0) + syncErrors = true; + } + } + + for(BuddyResyncerDirSyncSlaveVecIter iter = dirSyncSlaveVec.begin(); + iter != dirSyncSlaveVec.end(); iter++) + { + BuddyResyncerDirSyncSlave* slave = *iter; + if(slave) + { + { + std::lock_guard safeLock(slave->statusMutex); + while (slave->isRunning) + slave->isRunningChangeCond.wait(&(slave->statusMutex)); + } + + if (slave->getErrorCount() != 0) + syncErrors = true; + } + } + + if (getStatus() == BuddyResyncJobState_RUNNING) // status not set to anything special + { // (e.g. FAILURE) + if (shallAbort.read() != 0) // job aborted? + { + setStatus(BuddyResyncJobState_INTERRUPTED); + informBuddy(); + } + else if (syncErrors || targetWasOffline.read()) // any sync errors or success? + { + // we must set the buddy BAD if it has been offline during any period of time during which + // the resync was also running. we implicitly do this during resync proper, since resync + // slaves abort with errors if the target is offline. if the target goes offline *after* + // the last proper resync messages has been sent and comes *back* before we try to inform + // it we will never detect that it has been offline at all. concurrently executing + // messages (eg TruncFile) may run between our opportunities to detect the offline state + // and may fail to forward their actions *even though they should forward*. this would + // lead to an inconsistent secondary. since the target has gone offline, the only + // reasonable course of action is to fail to resync entirely. + setStatus(BuddyResyncJobState_ERRORS); + informBuddy(); + } + else + { + setStatus(BuddyResyncJobState_SUCCESS); + // unset timestamp override file if an override was set + target.setLastBuddyComm(std::chrono::system_clock::from_time_t(0), true); + // so the target went offline between the previous check "syncErrors || targetWasOffline". + // any message that has tried to forward itself in the intervening time will have seen the + // offline state, but will have been unable to set the buddy to needs-resync because it + // still *is* needs-resync. the resync itself has been perfectly successful, but we have + // to start another one anyway once the target comes back to ensure that no information + // was lost. + target.setBuddyNeedsResync(targetWasOffline.read()); + informBuddy(); + + if (targetWasOffline.read()) + LOG(MIRRORING, WARNING, + "Resync successful, but target went offline during finalization. " + "Setting target to needs-resync again.", targetID); + } + } + + target.setBuddyResyncInProgress(false); + endTime = time(NULL); +} + +void BuddyResyncJob::abort() +{ + shallAbort.set(1); // tell the file walk in this class to abort + + // set setOnlyTerminateIfIdle on the slaves to false; they will be stopped by the main loop then + for(BuddyResyncerGatherSlaveVecIter iter = gatherSlaveVec.begin(); iter != gatherSlaveVec.end(); + iter++) + { + BuddyResyncerGatherSlave* slave = *iter; + if(slave) + { + slave->setOnlyTerminateIfIdle(false); + } + } + + // stop sync slaves + for(BuddyResyncerFileSyncSlaveVecIter iter = fileSyncSlaveVec.begin(); + iter != fileSyncSlaveVec.end(); iter++) + { + BuddyResyncerFileSyncSlave* slave = *iter; + if(slave) + { + slave->setOnlyTerminateIfIdle(false); + } + } + + for(BuddyResyncerDirSyncSlaveVecIter iter = dirSyncSlaveVec.begin(); + iter != dirSyncSlaveVec.end(); iter++) + { + BuddyResyncerDirSyncSlave* slave = *iter; + if(slave) + { + slave->setOnlyTerminateIfIdle(false); + } + } +} + +bool BuddyResyncJob::startGatherSlaves(const StorageTarget& target) +{ + // create a gather slaves if they don't exist yet and start them + for (size_t i = 0; i < gatherSlaveVec.size(); i++) + { + if(!gatherSlaveVec[i]) + gatherSlaveVec[i] = new BuddyResyncerGatherSlave(target, &syncCandidates, + &gatherSlavesWorkQueue, i); + + try + { + gatherSlaveVec[i]->resetSelfTerminate(); + gatherSlaveVec[i]->start(); + gatherSlaveVec[i]->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what()); + + return false; + } + } + + return true; +} + +bool BuddyResyncJob::startSyncSlaves() +{ + // create sync slaves and start them + for(size_t i = 0; i < fileSyncSlaveVec.size(); i++) + { + if(!fileSyncSlaveVec[i]) + fileSyncSlaveVec[i] = new BuddyResyncerFileSyncSlave(targetID, &syncCandidates, i); + + try + { + fileSyncSlaveVec[i]->resetSelfTerminate(); + fileSyncSlaveVec[i]->start(); + fileSyncSlaveVec[i]->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what()); + + // stop already started sync slaves + for(size_t j = 0; j < i; j++) + fileSyncSlaveVec[j]->selfTerminate(); + + return false; + } + } + + for(size_t i = 0; i < dirSyncSlaveVec.size(); i++) + { + if(!dirSyncSlaveVec[i]) + dirSyncSlaveVec[i] = new BuddyResyncerDirSyncSlave(targetID, &syncCandidates, i); + + try + { + dirSyncSlaveVec[i]->resetSelfTerminate(); + dirSyncSlaveVec[i]->start(); + dirSyncSlaveVec[i]->setIsRunning(true); + } + catch (PThreadCreateException& e) + { + LogContext(__func__).logErr(std::string("Unable to start thread: ") + e.what()); + + // stop already started sync slaves + for (size_t j = 0; j < fileSyncSlaveVec.size(); j++) + fileSyncSlaveVec[j]->selfTerminate(); + + for (size_t j = 0; j < i; j++) + dirSyncSlaveVec[j]->selfTerminate(); + + return false; + } + } + + return true; +} + +void BuddyResyncJob::joinGatherSlaves() +{ + for (size_t i = 0; i < gatherSlaveVec.size(); i++) + gatherSlaveVec[i]->join(); +} + +void BuddyResyncJob::joinSyncSlaves() +{ + for (size_t i = 0; i < fileSyncSlaveVec.size(); i++) + fileSyncSlaveVec[i]->join(); + + for (size_t i = 0; i < dirSyncSlaveVec.size(); i++) + dirSyncSlaveVec[i]->join(); +} + +void BuddyResyncJob::getJobStats(StorageBuddyResyncJobStatistics& outStats) +{ + uint64_t discoveredFiles = 0; + uint64_t matchedFiles = 0; + uint64_t discoveredDirs = numDirsDiscovered.read(); + uint64_t matchedDirs = numDirsMatched.read(); + uint64_t syncedFiles = 0; + uint64_t syncedDirs = 0; + uint64_t errorFiles = 0; + uint64_t errorDirs = 0; + + for(size_t i = 0; i < gatherSlaveVec.size(); i++) + { + BuddyResyncerGatherSlave* slave = gatherSlaveVec[i]; + if(slave) + { + uint64_t tmpDiscoveredFiles = 0; + uint64_t tmpMatchedFiles = 0; + uint64_t tmpDiscoveredDirs = 0; + uint64_t tmpMatchedDirs = 0; + slave->getCounters(tmpDiscoveredFiles, tmpMatchedFiles, tmpDiscoveredDirs, tmpMatchedDirs); + + discoveredFiles += tmpDiscoveredFiles; + matchedFiles += tmpMatchedFiles; + discoveredDirs += tmpDiscoveredDirs; + matchedDirs += tmpMatchedDirs; + } + } + + for(size_t i = 0; i < fileSyncSlaveVec.size(); i++) + { + BuddyResyncerFileSyncSlave* slave = fileSyncSlaveVec[i]; + if(slave) + { + syncedFiles += slave->getNumChunksSynced(); + errorFiles += slave->getErrorCount(); + } + } + + for (size_t i = 0; i < dirSyncSlaveVec.size(); i++) + { + BuddyResyncerDirSyncSlave* slave = dirSyncSlaveVec[i]; + if (slave) + { + syncedDirs += slave->getNumDirsSynced(); + discoveredDirs += slave->getNumAdditionalDirsMatched(); + matchedDirs += slave->getNumAdditionalDirsMatched(); + errorDirs += slave->getErrorCount(); + } + } + + outStats = StorageBuddyResyncJobStatistics(status, startTime, endTime, discoveredFiles, + discoveredDirs, matchedFiles, matchedDirs, syncedFiles, syncedDirs, errorFiles, errorDirs); +} + +void BuddyResyncJob::informBuddy() +{ + App* app = Program::getApp(); + NodeStore* storageNodes = app->getStorageNodes(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + TargetMapper* targetMapper = app->getTargetMapper(); + + BuddyResyncJobState status = getStatus(); + TargetConsistencyState newTargetState; + if ( (status == BuddyResyncJobState_ERRORS) || (status == BuddyResyncJobState_INTERRUPTED)) + newTargetState = TargetConsistencyState_BAD; + else + if (status == BuddyResyncJobState_SUCCESS) + newTargetState = TargetConsistencyState_GOOD; + else + { + LogContext(__func__).log(Log_NOTICE, "Refusing to set a state for buddy target, because " + "resync status isn't well-defined. " + "localTargetID: " + StringTk::uintToStr(targetID) + "; " + "resyncState: " + StringTk::intToStr(status)); + return; + } + + uint16_t buddyTargetID = buddyGroupMapper->getBuddyTargetID(targetID); + NumNodeID buddyNodeID = targetMapper->getNodeID(buddyTargetID); + auto storageNode = storageNodes->referenceNode(buddyNodeID); + + if (!storageNode) + { + LogContext(__func__).logErr( + "Unable to inform buddy about finished resync. TargetID: " + StringTk::uintToStr(targetID) + + "; buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; buddyNodeID: " + + buddyNodeID.str() + "; error: unknown storage node"); + return; + } + + SetTargetConsistencyStatesRespMsg* respMsgCast; + FhgfsOpsErr result; + UInt16List targetIDs; + UInt8List states; + + targetIDs.push_back(buddyTargetID); + states.push_back(newTargetState); + + SetTargetConsistencyStatesMsg msg(NODETYPE_Storage, &targetIDs, &states, false); + + const auto respMsg = MessagingTk::requestResponse(*storageNode, msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + if (!respMsg) + { + LogContext(__func__).logErr( + "Unable to inform buddy about finished resync. " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "buddyNodeID: " + buddyNodeID.str() + "; " + "error: Communication error"); + return; + } + + respMsgCast = (SetTargetConsistencyStatesRespMsg*) respMsg.get(); + result = respMsgCast->getResult(); + + if(result != FhgfsOpsErr_SUCCESS) + { + LogContext(__func__).logErr( + "Error while informing buddy about finished resync. " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "buddyNodeID: " + buddyNodeID.str() + "; " + "error: " + boost::lexical_cast(result)); + } +} + +/* + * check the CONFIG_BUDDYMIRROR_SUBDIR_NAME directory + */ +bool BuddyResyncJob::checkTopLevelDir(std::string& path, int64_t lastBuddyCommTimeSecs) +{ + struct stat statBuf; + int statRes = stat(path.c_str(), &statBuf); + + if(statRes != 0) + { + LogContext(__func__).log(Log_WARNING, + "Couldn't stat chunks directory; resync job can't run. targetID: " + + StringTk::uintToStr(targetID) + "; path: " + path + + "; Error: " + System::getErrString(errno)); + + return false; + } + + numDirsDiscovered.increase(); + int64_t dirMTime = (int64_t) statBuf.st_mtim.tv_sec; + if(dirMTime > lastBuddyCommTimeSecs) + { // sync candidate + ChunkSyncCandidateDir candidate("", targetID); + syncCandidates.add(candidate, this); + numDirsMatched.increase(); + } + + return true; +} + +/* + * recursively walk through buddy mir directory until a depth of BUDDYRESYNCJOB_MAXDIRWALKDEPTH is + * reached; everything with a greater depth gets passed to the GatherSlaves to work on it in + * parallel + */ +bool BuddyResyncJob::walkDirs(std::string chunksPath, std::string relPath, int level, + int64_t lastBuddyCommTimeSecs) +{ + bool retVal = true; + + DIR* dirHandle; + struct dirent* dirEntry; + + dirHandle = opendir(std::string(chunksPath + "/" + relPath).c_str()); + + if(!dirHandle) + { + LogContext(__func__).logErr("Unable to open path. " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "Rel. path: " + relPath + "; " + "Error: " + System::getErrString(errno) ); + return false; + } + + while ((dirEntry = StorageTk::readdirFiltered(dirHandle)) != NULL) + { + if(shallAbort.read() != 0) + break; + + // get stat info + std::string currentRelPath; + if(unlikely(relPath.empty())) + currentRelPath = dirEntry->d_name; + else + currentRelPath = relPath + "/" + dirEntry->d_name; + + std::string currentFullPath = chunksPath + "/" + currentRelPath; + struct stat statBuf; + int statRes = stat(currentFullPath.c_str(), &statBuf); + + if(statRes != 0) + { + LogContext(__func__).log(Log_WARNING, + "Couldn't stat directory, which was discovered previously. Resync job might not be " + "complete. targetID " + StringTk::uintToStr(targetID) + "; " + "Rel. path: " + relPath + "; " + "Error: " + System::getErrString(errno)); + + retVal = false; + + break; // => one error aborts it all + } + + if(S_ISDIR(statBuf.st_mode)) + { + // if level of dir is smaller than max, take care of it and recurse into it + if(level < BUDDYRESYNCJOB_MAXDIRWALKDEPTH) + { + numDirsDiscovered.increase(); + int64_t dirMTime = (int64_t) statBuf.st_mtim.tv_sec; + if(dirMTime > lastBuddyCommTimeSecs) + { // sync candidate + ChunkSyncCandidateDir candidate(currentRelPath, targetID); + syncCandidates.add(candidate, this); + numDirsMatched.increase(); + } + + bool walkRes = walkDirs(chunksPath, currentRelPath, level+1, lastBuddyCommTimeSecs); + + if (!walkRes) + retVal = false; + } + else + // otherwise pass it to the slaves; NOTE: gather slave takes full path + gatherSlavesWorkQueue.add(currentFullPath, this); + } + else + { + LOG_DEBUG(__func__, Log_WARNING, "Found a file in directory structure"); + } + } + + if(!dirEntry && errno) // error occured + { + LogContext(__func__).logErr( + "Unable to read all directories; chunksPath: " + chunksPath + "; relativePath: " + relPath + + "; SysErr: " + System::getErrString(errno)); + + retVal = false; + } + + int closedirRes = closedir(dirHandle); + if (closedirRes != 0) + LOG_DEBUG(__func__, Log_WARNING, + "Unable to open path. targetID " + StringTk::uintToStr(targetID) + "; Rel. path: " + + relPath + "; Error: " + System::getErrString(errno)); + + return retVal; +} diff --git a/storage/source/components/buddyresyncer/BuddyResyncJob.h b/storage/source/components/buddyresyncer/BuddyResyncJob.h new file mode 100644 index 0000000..4549255 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncJob.h @@ -0,0 +1,90 @@ +#pragma once + +#include +#include +#include +#include + +#define GATHERSLAVEQUEUE_MAXSIZE 5000 + +class BuddyResyncJob : public PThread +{ + friend class GenericDebugMsgEx; + + public: + BuddyResyncJob(uint16_t targetID); + virtual ~BuddyResyncJob(); + + virtual void run(); + + void abort(); + void getJobStats(StorageBuddyResyncJobStatistics& outStats); + + private: + uint16_t targetID; + Mutex statusMutex; + BuddyResyncJobState status; + + int64_t startTime; + int64_t endTime; + + ChunkSyncCandidateStore syncCandidates; + BuddyResyncerGatherSlaveWorkQueue gatherSlavesWorkQueue; + + BuddyResyncerGatherSlaveVec gatherSlaveVec; + BuddyResyncerFileSyncSlaveVec fileSyncSlaveVec; + BuddyResyncerDirSyncSlaveVec dirSyncSlaveVec; + + // this thread walks over the top dir structures itself, so we need to track that + AtomicUInt64 numDirsDiscovered; + AtomicUInt64 numDirsMatched; + + AtomicInt16 shallAbort; // quasi-boolean + AtomicInt16 targetWasOffline; + + bool checkTopLevelDir(std::string& path, int64_t lastBuddyCommTimeSecs); + bool walkDirs(std::string chunksPath, std::string relPath, int level, + int64_t lastBuddyCommTimeSecs); + + bool startGatherSlaves(const StorageTarget& target); + bool startSyncSlaves(); + void joinGatherSlaves(); + void joinSyncSlaves(); + + public: + uint16_t getTargetID() const + { + return targetID; + } + + BuddyResyncJobState getStatus() + { + std::lock_guard mutexLock(statusMutex); + return status; + } + + bool isRunning() + { + std::lock_guard mutexLock(statusMutex); + return status == BuddyResyncJobState_RUNNING; + } + + void setTargetOffline() + { + targetWasOffline.set(1); + } + + private: + void setStatus(BuddyResyncJobState status) + { + std::lock_guard mutexLock(statusMutex); + this->status = status; + } + + void informBuddy(); +}; + +typedef std::map BuddyResyncJobMap; //mapping: targetID, job +typedef BuddyResyncJobMap::iterator BuddyResyncJobMapIter; + + diff --git a/storage/source/components/buddyresyncer/BuddyResyncer.cpp b/storage/source/components/buddyresyncer/BuddyResyncer.cpp new file mode 100644 index 0000000..268acab --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncer.cpp @@ -0,0 +1,40 @@ +#include + +#include "BuddyResyncer.h" + +BuddyResyncer::~BuddyResyncer() +{ + // delete remaining jobs + for (BuddyResyncJobMapIter iter = resyncJobMap.begin(); iter != resyncJobMap.end(); iter++) + { + BuddyResyncJob* job = iter->second; + if( job->isRunning() ) + { + job->abort(); + job->join(); + } + + SAFE_DELETE(job); + } +} + +/** + * @return FhgfsOpsErr_SUCCESS if everything was successfully started, FhgfsOpsErr_INUSE if already + * running + */ +FhgfsOpsErr BuddyResyncer::startResync(uint16_t targetID) +{ + bool isNewJob; + + // try to add an existing resync job; if it already exists, we get that + BuddyResyncJob* resyncJob = addResyncJob(targetID, isNewJob); + + // Job already exists *and* is already running: + if (!isNewJob && resyncJob->isRunning() ) + return FhgfsOpsErr_INUSE; + + // job is ready and not running + resyncJob->start(); + + return FhgfsOpsErr_SUCCESS; +} diff --git a/storage/source/components/buddyresyncer/BuddyResyncer.h b/storage/source/components/buddyresyncer/BuddyResyncer.h new file mode 100644 index 0000000..02fa996 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncer.h @@ -0,0 +1,59 @@ +#pragma once + +#include + +#include + +/** + * This is not a component that represents a separate thread by itself. Instead, it is the + * controlling frontend for slave threads, which are started and stopped on request (i.e. it is not + * automatically started when the app is started). + * + * Callers should only use methods in this controlling frontend and not access the slave's methods + * directly. + */ +class BuddyResyncer +{ + public: + ~BuddyResyncer(); + + FhgfsOpsErr startResync(uint16_t targetID); + + private: + BuddyResyncJobMap resyncJobMap; + Mutex resyncJobMapMutex; + + public: + BuddyResyncJob* getResyncJob(uint16_t targetID) + { + std::lock_guard mutexLock(resyncJobMapMutex); + + BuddyResyncJobMapIter iter = resyncJobMap.find(targetID); + if (iter != resyncJobMap.end()) + return iter->second; + else + return NULL; + } + + private: + BuddyResyncJob* addResyncJob(uint16_t targetID, bool& outIsNew) + { + + std::lock_guard mutexLock(resyncJobMapMutex); + + BuddyResyncJobMapIter iter = resyncJobMap.find(targetID); + if (iter != resyncJobMap.end()) + { + outIsNew = false; + return iter->second; + } + else + { + BuddyResyncJob* job = new BuddyResyncJob(targetID); + resyncJobMap.insert(BuddyResyncJobMap::value_type(targetID, job) ); + outIsNew = true; + return job; + } + } +}; + diff --git a/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.cpp b/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.cpp new file mode 100644 index 0000000..3f3cc91 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.cpp @@ -0,0 +1,395 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "BuddyResyncerDirSyncSlave.h" + +#include + +#define CHECK_AT_ONCE 50 + +BuddyResyncerDirSyncSlave::BuddyResyncerDirSyncSlave(uint16_t targetID, + ChunkSyncCandidateStore* syncCandidates, uint8_t slaveID) : + PThread("BuddyResyncerDirSyncSlave_" + StringTk::uintToStr(targetID) + "-" + + StringTk::uintToStr(slaveID)) +{ + this->isRunning = false; + this->targetID = targetID; + this->syncCandidates = syncCandidates; +} + +BuddyResyncerDirSyncSlave::~BuddyResyncerDirSyncSlave() +{ +} + +/** + * This is a component, which is started through its control frontend on-demand at + * runtime and terminates when it's done. + * We have to ensure (in cooperation with the control frontend) that we don't get multiple instances + * of this thread running at the same time. + */ +void BuddyResyncerDirSyncSlave::run() +{ + setIsRunning(true); + + try + { + LogContext(__func__).log(Log_DEBUG, "Component started."); + + registerSignalHandler(); + + numAdditionalDirsMatched.setZero(); + numDirsSynced.setZero(); + errorCount.setZero(); + + syncLoop(); + + LogContext(__func__).log(Log_DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +void BuddyResyncerDirSyncSlave::syncLoop() +{ + App* app = Program::getApp(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + while (! getSelfTerminateNotIdle()) + { + if((syncCandidates->isDirsEmpty()) && (getSelfTerminate())) + break; + + ChunkSyncCandidateDir candidate; + + syncCandidates->fetch(candidate, this); + + if (unlikely(candidate.getTargetID() == 0)) // ignore targetID 0 + continue; + + std::string relativePath = candidate.getRelativePath(); + uint16_t localTargetID = candidate.getTargetID(); + + // get buddy targetID + uint16_t buddyTargetID = buddyGroupMapper->getBuddyTargetID(localTargetID); + // perform sync + FhgfsOpsErr resyncRes = doSync(relativePath, localTargetID, buddyTargetID); + if (resyncRes == FhgfsOpsErr_SUCCESS) + numDirsSynced.increase(); + else if (resyncRes != FhgfsOpsErr_INTERRUPTED) + errorCount.increase(); // increment error count if an error occurred; note: if the slaves + // were interrupted from the outside (e.g. ctl) this is not an error + } +} + +FhgfsOpsErr BuddyResyncerDirSyncSlave::doSync(const std::string& dirPath, uint16_t localTargetID, + uint16_t buddyTargetID) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + TargetMapper* targetMapper = app->getTargetMapper(); + NodeStoreServers* storageNodes = app->getStorageNodes(); + + // try to find the node with the buddyTargetID + NumNodeID buddyNodeID = targetMapper->getNodeID(buddyTargetID); + auto node = storageNodes->referenceNode(buddyNodeID); + + if(!node) + { + LogContext(__func__).logErr( + "Storage node does not exist; nodeID " + buddyNodeID.str()); + + return FhgfsOpsErr_UNKNOWNNODE; + } + + int64_t offset = 0; + unsigned entriesFetched; + + do + { + int64_t newOffset; + StringList names; + IntList entryTypes; + + FhgfsOpsErr listRes = getBuddyDirContents(*node, dirPath, buddyTargetID, offset, names, + entryTypes, newOffset); + + if(listRes != FhgfsOpsErr_SUCCESS) + { + retVal = listRes; + break; + } + + offset = newOffset; + entriesFetched = names.size(); + + // match locally + FhgfsOpsErr findRes = findChunks(localTargetID, dirPath, names, entryTypes); + + if(findRes != FhgfsOpsErr_SUCCESS) + { + retVal = findRes; + break; + } + + // delete the remaining chunks/dirs on the buddy + StringList rmPaths; + for (StringListIter iter = names.begin(); iter != names.end(); iter++) + { + std::string path = dirPath + "/" + *iter; + rmPaths.push_back(path); + } + + FhgfsOpsErr rmRes = removeBuddyChunkPaths(*node, localTargetID, buddyTargetID, rmPaths); + + if (rmRes != FhgfsOpsErr_SUCCESS) + { + retVal = rmRes; + break; + } + + if (getSelfTerminateNotIdle()) + { + retVal = FhgfsOpsErr_INTERRUPTED; + break; + } + + } while (entriesFetched == CHECK_AT_ONCE); + + return retVal; +} + +FhgfsOpsErr BuddyResyncerDirSyncSlave::getBuddyDirContents(Node& node, const std::string& dirPath, + uint16_t targetID, int64_t offset, StringList& outNames, IntList& outEntryTypes, + int64_t& outNewOffset) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + unsigned msgRetryIntervalMS = 5000; + + // get a part of the dir contents from the buddy target + ListChunkDirIncrementalMsg listMsg(targetID, true, dirPath, offset, CHECK_AT_ONCE, false, true); + listMsg.setMsgHeaderTargetID(targetID); + + CombinedTargetState state; + bool getStateRes = Program::getApp()->getTargetStateStore()->getState(targetID, state); + + // send request to node and receive response + std::unique_ptr respMsg; + + while ( (!respMsg) && (getStateRes) + && (state.reachabilityState != TargetReachabilityState_OFFLINE) ) + { + respMsg = MessagingTk::requestResponse(node, listMsg, NETMSGTYPE_ListChunkDirIncrementalResp); + + if (!respMsg) + { + LOG_DEBUG(__func__, Log_NOTICE, + "Unable to communicate, but target is not offline; sleeping " + + StringTk::uintToStr(msgRetryIntervalMS) + "ms before retry. targetID: " + + StringTk::uintToStr(targetID)); + + PThread::sleepMS(msgRetryIntervalMS); + + // if thread shall terminate, break loop here + if ( getSelfTerminateNotIdle() ) + break; + + getStateRes = Program::getApp()->getTargetStateStore()->getState(targetID, state); + } + } + + if (!respMsg) + { // communication error + LogContext(__func__).logErr( + "Communication with storage node failed: " + node.getTypedNodeID()); + + retVal = FhgfsOpsErr_COMMUNICATION; + } + else + if(!getStateRes) + { + LogContext(__func__).logErr("No valid state for node ID: " + node.getTypedNodeID() ); + + retVal = FhgfsOpsErr_INTERNAL; + } + else + { + // correct response type received + ListChunkDirIncrementalRespMsg* respMsgCast = (ListChunkDirIncrementalRespMsg*) respMsg.get(); + + FhgfsOpsErr listRes = respMsgCast->getResult(); + + if (listRes == FhgfsOpsErr_SUCCESS) + { + outNewOffset = respMsgCast->getNewOffset(); + respMsgCast->getNames().swap(outNames); + respMsgCast->getEntryTypes().swap(outEntryTypes); + } + else + if (listRes != FhgfsOpsErr_PATHNOTEXISTS) + { // not exists is ok, because path might have been deleted + LogContext(__func__).log(Log_WARNING, "Error listing chunks dir; " + "dirPath: " + dirPath + "; " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "node: " + node.getTypedNodeID() + "; " + "Error: " + boost::lexical_cast(listRes)); + + retVal = listRes; + } + } + + return retVal; +} + +FhgfsOpsErr BuddyResyncerDirSyncSlave::findChunks(uint16_t targetID, const std::string& dirPath, + StringList& inOutNames, IntList& inOutEntryTypes) +{ + App* app = Program::getApp(); + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + + const auto& target = app->getStorageTargets()->getTargets().at(targetID); + + const int targetFD = *target->getMirrorFD(); + + StringListIter namesIter = inOutNames.begin(); + IntListIter typesIter = inOutEntryTypes.begin(); + while (namesIter != inOutNames.end()) + { + std::string entryID = *namesIter; + DirEntryType entryType = (DirEntryType)*typesIter; + + std::string entryPath; + if (likely(!dirPath.empty())) + entryPath = dirPath + "/" + entryID; + else + entryPath = entryID; + + if (DirEntryType_ISDIR(entryType)) + { + bool entryExists = StorageTk::pathExists(targetFD, entryPath); + + if (!entryExists) + { + // dir not found, so we didn't know about it yet => add it to sync candidate store, so + // that it gets checked and we get a list of its contents; + ChunkSyncCandidateDir syncCandidate(entryPath, targetID); + syncCandidates->add(syncCandidate, this); + numAdditionalDirsMatched.increase(); + } + + // no matter if found or not: remove it from the list, because we do not explicitely + // delete directories on the buddy + namesIter = inOutNames.erase(namesIter); + typesIter = inOutEntryTypes.erase(typesIter); + } + else + { + // need to lock the chunk to check it + chunkLockStore->lockChunk(targetID, entryID); + + bool entryExists = StorageTk::pathExists(targetFD, entryPath); + + if (entryExists) + { + // chunk found => delete it from list an unlock it + namesIter = inOutNames.erase(namesIter); + typesIter = inOutEntryTypes.erase(typesIter); + chunkLockStore->unlockChunk(targetID, entryID); + } + else + { + // chunk not found => keep lock; will be unlocked after removal + namesIter++; + typesIter++; + } + + } + } + + return FhgfsOpsErr_SUCCESS; +} + +FhgfsOpsErr BuddyResyncerDirSyncSlave::removeBuddyChunkPaths(Node& node, uint16_t localTargetID, + uint16_t buddyTargetID, StringList& paths) +{ + unsigned msgRetryIntervalMS = 5000; + + ChunkLockStore* chunkLockStore = Program::getApp()->getChunkLockStore(); + RmChunkPathsMsg rmMsg(buddyTargetID, &paths); + rmMsg.addMsgHeaderFeatureFlag(RMCHUNKPATHSMSG_FLAG_BUDDYMIRROR); + rmMsg.setMsgHeaderTargetID(buddyTargetID); + + CombinedTargetState state; + bool getStateRes = Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + + // send request to node and receive response + std::unique_ptr respMsg; + + while ((!respMsg) && (getStateRes) + && (state.reachabilityState != TargetReachabilityState_OFFLINE)) + { + respMsg = MessagingTk::requestResponse(node, rmMsg, NETMSGTYPE_RmChunkPathsResp); + + if (!respMsg) + { + LOG_DEBUG(__func__, Log_NOTICE, + "Unable to communicate, but target is not offline; sleeping " + + StringTk::uintToStr(msgRetryIntervalMS) + "ms before retry. targetID: " + + StringTk::uintToStr(targetID)); + PThread::sleepMS(msgRetryIntervalMS); + + // if thread shall terminate, break loop here + if ( getSelfTerminateNotIdle() ) + break; + + getStateRes = Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + } + } + + // no matter if that succeeded or not we unlock all chunks here first + for (StringListIter iter = paths.begin(); iter != paths.end(); iter++) + { + std::string entryID = StorageTk::getPathBasename(*iter); + chunkLockStore->unlockChunk(localTargetID, entryID); + } + + if (!respMsg) + { // communication error + LogContext(__func__).logErr( + "Communication with storage node failed: " + node.getTypedNodeID()); + + return FhgfsOpsErr_COMMUNICATION; + } + else + if(!getStateRes) + { + LogContext(__func__).logErr("No valid state for node ID: " + node.getTypedNodeID() ); + + return FhgfsOpsErr_INTERNAL; + } + else + { + // correct response type received + RmChunkPathsRespMsg* respMsgCast = (RmChunkPathsRespMsg*) respMsg.get(); + StringList& failedPaths = respMsgCast->getFailedPaths(); + + for(StringListIter iter = failedPaths.begin(); iter != failedPaths.end(); iter++) + { + LogContext(__func__).logErr("Chunk path could not be deleted; " + "path: " + *iter + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "node: " + node.getTypedNodeID()); + } + } + + return FhgfsOpsErr_SUCCESS; +} diff --git a/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.h b/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.h new file mode 100644 index 0000000..3640650 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerDirSyncSlave.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include + +class BuddyResyncerDirSyncSlave : public PThread +{ + friend class BuddyResyncer; // (to grant access to internal mutex) + friend class BuddyResyncJob; // (to grant access to internal mutex) + + public: + BuddyResyncerDirSyncSlave(uint16_t targetID, ChunkSyncCandidateStore* syncCandidates, + uint8_t slaveID); + virtual ~BuddyResyncerDirSyncSlave(); + + private: + Mutex statusMutex; // protects isRunning + Condition isRunningChangeCond; + + AtomicSizeT onlyTerminateIfIdle; + + AtomicUInt64 numDirsSynced; + AtomicUInt64 numAdditionalDirsMatched; + AtomicUInt64 errorCount; + + bool isRunning; // true if an instance of this component is currently running + + uint16_t targetID; + ChunkSyncCandidateStore* syncCandidates; + + virtual void run(); + void syncLoop(); + + FhgfsOpsErr doSync(const std::string& dirPath, uint16_t localTargetID, + uint16_t buddyTargetID); + FhgfsOpsErr getBuddyDirContents(Node& node, const std::string& dirPath, uint16_t targetID, + int64_t offset, StringList& outNames, IntList& outEntryTypes, int64_t& outNewOffset); + FhgfsOpsErr findChunks(uint16_t targetID, const std::string& dirPath, StringList& inOutNames, + IntList& inOutEntryTypes); + FhgfsOpsErr removeBuddyChunkPaths(Node& node, uint16_t localTargetID, uint16_t buddyTargetID, + StringList& paths); + + public: + // getters & setters + bool getIsRunning() + { + const std::lock_guard lock(statusMutex); + + return this->isRunning; + } + + void setOnlyTerminateIfIdle(bool value) + { + if (value) + onlyTerminateIfIdle.set(1); + else + onlyTerminateIfIdle.setZero(); + } + + bool getOnlyTerminateIfIdle() + { + if (onlyTerminateIfIdle.read() == 0) + return false; + else + return true; + } + + uint64_t getNumDirsSynced() + { + return numDirsSynced.read(); + } + + uint64_t getNumAdditionalDirsMatched() + { + return numAdditionalDirsMatched.read(); + } + + uint64_t getErrorCount() + { + return errorCount.read(); + } + + private: + // getters & setters + + void setIsRunning(bool isRunning) + { + const std::lock_guard lock(statusMutex); + + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } + + bool getSelfTerminateNotIdle() + { + return ( (getSelfTerminate() && (!getOnlyTerminateIfIdle())) ); + } +}; + +typedef std::list BuddyResyncerDirSyncSlaveList; +typedef BuddyResyncerDirSyncSlaveList::iterator BuddyResyncerDirSyncSlaveListIter; +typedef std::vector BuddyResyncerDirSyncSlaveVec; +typedef BuddyResyncerDirSyncSlaveVec::iterator BuddyResyncerDirSyncSlaveVecIter; + diff --git a/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.cpp b/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.cpp new file mode 100644 index 0000000..05293d0 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.cpp @@ -0,0 +1,471 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "BuddyResyncerFileSyncSlave.h" + +#include + +#define PROCESS_AT_ONCE 1 +#define SYNC_BLOCK_SIZE (1024*1024) // 1M + +BuddyResyncerFileSyncSlave::BuddyResyncerFileSyncSlave(uint16_t targetID, + ChunkSyncCandidateStore* syncCandidates, uint8_t slaveID) : + PThread("BuddyResyncerFileSyncSlave_" + StringTk::uintToStr(targetID) + "-" + + StringTk::uintToStr(slaveID)) +{ + this->isRunning = false; + this->syncCandidates = syncCandidates; + this->targetID = targetID; +} + +BuddyResyncerFileSyncSlave::~BuddyResyncerFileSyncSlave() +{ +} + +/** + * This is a component, which is started through its control frontend on-demand at + * runtime and terminates when it's done. + * We have to ensure (in cooperation with the control frontend) that we don't get multiple instances + * of this thread running at the same time. + */ +void BuddyResyncerFileSyncSlave::run() +{ + setIsRunning(true); + + try + { + LogContext(__func__).log(Log_DEBUG, "Component started."); + + registerSignalHandler(); + + numChunksSynced.setZero(); + errorCount.setZero(); + + syncLoop(); + + LogContext(__func__).log(Log_DEBUG, "Component stopped."); + } + catch (std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +void BuddyResyncerFileSyncSlave::syncLoop() +{ + App* app = Program::getApp(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + while (! getSelfTerminateNotIdle()) + { + if((syncCandidates->isFilesEmpty()) && (getSelfTerminate())) + break; + + ChunkSyncCandidateFile candidate; + + syncCandidates->fetch(candidate, this); + + if (unlikely(candidate.getTargetID() == 0)) // ignore targetID 0 + continue; + + std::string relativePath = candidate.getRelativePath(); + uint16_t localTargetID = candidate.getTargetID(); + + // get buddy targetID + uint16_t buddyTargetID = buddyGroupMapper->getBuddyTargetID(localTargetID); + // perform sync + FhgfsOpsErr resyncRes = doResync(relativePath, localTargetID, buddyTargetID); + if (resyncRes == FhgfsOpsErr_SUCCESS) + numChunksSynced.increase(); + else + if (resyncRes != FhgfsOpsErr_INTERRUPTED) + errorCount.increase(); + } +} + +FhgfsOpsErr BuddyResyncerFileSyncSlave::doResync(std::string& chunkPathStr, uint16_t localTargetID, + uint16_t buddyTargetID) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + unsigned msgRetryIntervalMS = 5000; + + App* app = Program::getApp(); + TargetMapper* targetMapper = app->getTargetMapper(); + NodeStoreServers* storageNodes = app->getStorageNodes(); + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + + std::string entryID = StorageTk::getPathBasename(chunkPathStr); + + // try to find the node with the buddyTargetID + NumNodeID buddyNodeID = targetMapper->getNodeID(buddyTargetID); + + auto node = storageNodes->referenceNode(buddyNodeID); + + if(!node) + { + LogContext(__func__).log(Log_WARNING, + "Storage node does not exist; nodeID " + buddyNodeID.str()); + + return FhgfsOpsErr_UNKNOWNNODE; + } + + int64_t offset = 0; + ssize_t readRes = 0; + unsigned resyncMsgFlags = 0; + resyncMsgFlags |= RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR; + + LogContext(__func__).log(Log_DEBUG, + "File sync started. chunkPath: " + chunkPathStr + "; localTargetID: " + + StringTk::uintToStr(localTargetID) + "; buddyTargetID" + + StringTk::uintToStr(buddyTargetID)); + + do + { + boost::scoped_array data(new char[SYNC_BLOCK_SIZE]); + + const auto& target = app->getStorageTargets()->getTargets().at(localTargetID); + + // lock the chunk + chunkLockStore->lockChunk(localTargetID, entryID); + + const int fd = openat(*target->getMirrorFD(), chunkPathStr.c_str(), O_RDONLY | O_NOATIME); + + if (fd == -1) + { + int errCode = errno; + + if(errCode == ENOENT) + { // chunk was deleted => no error + // delete the mirror chunk and return + bool rmRes = removeBuddyChunkUnlocked(*node, buddyTargetID, chunkPathStr); + + if (!rmRes) // rm failed; stop resync + { + LogContext(__func__).log(Log_WARNING, + "File sync not started. chunkPath: " + chunkPathStr + "; localTargetID: " + + StringTk::uintToStr(localTargetID) + "; buddyTargetID: " + + StringTk::uintToStr(buddyTargetID)); + + retVal = FhgfsOpsErr_INTERNAL; + } + } + else // error => log and return + { + LogContext(__func__).logErr( + "Open of chunk failed. chunkPath: " + chunkPathStr + "; targetID: " + + StringTk::uintToStr(localTargetID) + "; Error: " + + System::getErrString(errCode)); + + retVal = FhgfsOpsErr_INTERNAL; + } + + chunkLockStore->unlockChunk(localTargetID, entryID); + + goto cleanup; + } + + int seekRes = lseek(fd, offset, SEEK_SET); + + if (seekRes == -1) + { + LogContext(__func__).logErr( + "Seeking in chunk failed. chunkPath: " + chunkPathStr + "; targetID: " + + StringTk::uintToStr(localTargetID) + "; offset: " + StringTk::int64ToStr(offset)); + + chunkLockStore->unlockChunk(localTargetID, entryID); + + goto cleanup; + } + + readRes = read(fd, data.get(), SYNC_BLOCK_SIZE); + + if( readRes == -1) + { + LogContext(__func__).logErr("Error during read; " + "chunkPath: " + chunkPathStr + "; " + "targetID: " + StringTk::uintToStr(localTargetID) + "; " + "BuddyNode: " + node->getTypedNodeID() + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "Error: " + System::getErrString(errno)); + + retVal = FhgfsOpsErr_INTERNAL; + + goto end_of_loop; + } + + if(readRes > 0) + { + const char zeroBuf[RESYNCER_SPARSE_BLOCK_SIZE] = { 0 }; + + // check if sparse blocks are in the buffer + ssize_t bufPos = 0; + bool dataFound = false; + while (bufPos < readRes) + { + size_t cmpLen = BEEGFS_MIN(readRes-bufPos, RESYNCER_SPARSE_BLOCK_SIZE); + + int cmpRes = memcmp(data.get() + bufPos, zeroBuf, cmpLen); + if(cmpRes != 0) + dataFound = true; + else // sparse area detected + { + if(dataFound) // had data before + { + resyncMsgFlags |= RESYNCLOCALFILEMSG_CHECK_SPARSE; // let the receiver do a check + break; // and stop checking here + } + } + + bufPos += cmpLen; + } + + // this inner loop is over and there are only sparse areas + + /* make sure we always send a msg at offset==0 to truncate the file and allow concurrent + writers in a big inital sparse area */ + if(offset && (readRes > 0) && (readRes == SYNC_BLOCK_SIZE) && !dataFound) + { + goto end_of_loop; + // => no transfer needed + } + + /* let the receiver do a check, because we might be sending a sparse block at beginnig or + end of file */ + if(!dataFound) + resyncMsgFlags |= RESYNCLOCALFILEMSG_CHECK_SPARSE; + } + + { + ResyncLocalFileMsg resyncMsg(data.get(), chunkPathStr, buddyTargetID, offset, readRes); + + if (!readRes || (readRes < SYNC_BLOCK_SIZE) ) // last iteration, set attribs and trunc buddy chunk + { + struct stat statBuf; + int statRes = fstat(fd, &statBuf); + + if (statRes == 0) + { + if(statBuf.st_size < offset) + { // in case someone truncated the file while we're reading at a high offset + offset = statBuf.st_size; + resyncMsg.setOffset(offset); + } + else + if(offset && !readRes) + resyncMsgFlags |= RESYNCLOCALFILEMSG_FLAG_TRUNC; + + int mode = statBuf.st_mode; + unsigned userID = statBuf.st_uid; + unsigned groupID = statBuf.st_gid; + int64_t mtimeSecs = statBuf.st_mtim.tv_sec; + int64_t atimeSecs = statBuf.st_atim.tv_sec; + SettableFileAttribs chunkAttribs = {mode, userID,groupID, mtimeSecs, atimeSecs}; + resyncMsg.setChunkAttribs(chunkAttribs); + resyncMsgFlags |= RESYNCLOCALFILEMSG_FLAG_SETATTRIBS; + } + else + { + LogContext(__func__).logErr("Error getting chunk attributes; " + "chunkPath: " + chunkPathStr + "; " + "targetID: " + StringTk::uintToStr(localTargetID) + "; " + "BuddyNode: " + node->getTypedNodeID() + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "Error: " + System::getErrString(errno)); + } + } + + resyncMsg.setMsgHeaderFeatureFlags(resyncMsgFlags); + resyncMsg.setMsgHeaderTargetID(buddyTargetID); + + CombinedTargetState state; + bool getStateRes = + Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + + // send request to node and receive response + std::unique_ptr respMsg; + + while ( (!respMsg) && (getStateRes) + && (state.reachabilityState != TargetReachabilityState_OFFLINE) ) + { + respMsg = MessagingTk::requestResponse(*node, resyncMsg, + NETMSGTYPE_ResyncLocalFileResp); + + if (!respMsg) + { + LOG_DEBUG(__func__, Log_NOTICE, + "Unable to communicate, but target is not offline; sleeping " + + StringTk::uintToStr(msgRetryIntervalMS) + "ms before retry. targetID: " + + StringTk::uintToStr(targetID)); + + PThread::sleepMS(msgRetryIntervalMS); + + // if thread shall terminate, break loop here + if ( getSelfTerminateNotIdle() ) + break; + + getStateRes = + Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + } + } + + if (!respMsg) + { // communication error + LogContext(__func__).log(Log_WARNING, + "Communication with storage node failed: " + node->getTypedNodeID()); + + retVal = FhgfsOpsErr_COMMUNICATION; + + // set readRes to non-zero to force exiting loop + readRes = -2; + } + else + if(!getStateRes) + { + LogContext(__func__).log(Log_WARNING, + "No valid state for node ID: " + node->getTypedNodeID()); + + retVal = FhgfsOpsErr_INTERNAL; + + // set readRes to non-zero to force exiting loop + readRes = -2; + } + else + { + // correct response type received + ResyncLocalFileRespMsg* respMsgCast = (ResyncLocalFileRespMsg*) respMsg.get(); + + FhgfsOpsErr syncRes = respMsgCast->getResult(); + + if(syncRes != FhgfsOpsErr_SUCCESS) + { + LogContext(__func__).log(Log_WARNING, "Error during resync; " + "chunkPath: " + chunkPathStr + "; " + "targetID: " + StringTk::uintToStr(localTargetID) + "; " + "BuddyNode: " + node->getTypedNodeID() + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "Error: " + boost::lexical_cast(syncRes)); + + retVal = syncRes; + + // set readRes to non-zero to force exiting loop + readRes = -2; + } + } + } + + end_of_loop: + int closeRes = close(fd); + if (closeRes == -1) + { + LogContext(__func__).log(Log_WARNING, "Error closing file descriptor; " + "chunkPath: " + chunkPathStr + "; " + "targetID: " + StringTk::uintToStr(localTargetID) + "; " + "BuddyNode: " + node->getTypedNodeID() + "; " + "buddyTargetID: " + StringTk::uintToStr(buddyTargetID) + "; " + "Error: " + System::getErrString(errno)); + } + // unlock the chunk + chunkLockStore->unlockChunk(localTargetID, entryID); + + // increment offset for next iteration + offset += readRes; + + if ( getSelfTerminateNotIdle() ) + { + retVal = FhgfsOpsErr_INTERRUPTED; + break; + } + + } while (readRes == SYNC_BLOCK_SIZE); + +cleanup: + LogContext(__func__).log(Log_DEBUG, "File sync finished. chunkPath: " + chunkPathStr); + + return retVal; +} + +/** + * Note: Chunk has to be locked by caller. + */ +bool BuddyResyncerFileSyncSlave::removeBuddyChunkUnlocked(Node& node, uint16_t buddyTargetID, + std::string& pathStr) +{ + bool retVal = true; + unsigned msgRetryIntervalMS = 5000; + + std::string entryID = StorageTk::getPathBasename(pathStr); + StringList rmPaths; + rmPaths.push_back(pathStr); + + RmChunkPathsMsg rmMsg(buddyTargetID, &rmPaths); + rmMsg.addMsgHeaderFeatureFlag(RMCHUNKPATHSMSG_FLAG_BUDDYMIRROR); + rmMsg.setMsgHeaderTargetID(buddyTargetID); + + CombinedTargetState state; + bool getStateRes = Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + + // send request to node and receive response + std::unique_ptr respMsg; + + while ( (!respMsg) && (getStateRes) + && (state.reachabilityState != TargetReachabilityState_OFFLINE) ) + { + respMsg = MessagingTk::requestResponse(node, rmMsg, NETMSGTYPE_RmChunkPathsResp); + + if (!respMsg) + { + LOG_DEBUG(__func__, Log_NOTICE, + "Unable to communicate, but target is not offline; " + "sleeping " + StringTk::uintToStr(msgRetryIntervalMS) + " ms before retry. " + "targetID: " + StringTk::uintToStr(targetID) ); + + PThread::sleepMS(msgRetryIntervalMS); + + // if thread shall terminate, break loop here + if ( getSelfTerminateNotIdle() ) + break; + + getStateRes = Program::getApp()->getTargetStateStore()->getState(buddyTargetID, state); + } + } + + if (!respMsg) + { // communication error + LogContext(__func__).logErr( + "Communication with storage node failed: " + node.getTypedNodeID() ); + + return false; + } + else + if(!getStateRes) + { + LogContext(__func__).log(Log_WARNING, + "No valid state for node ID: " + node.getTypedNodeID() ); + + return false; + } + else + { + // correct response type received + RmChunkPathsRespMsg* respMsgCast = (RmChunkPathsRespMsg*) respMsg.get(); + StringList& failedPaths = respMsgCast->getFailedPaths(); + + for (StringListIter iter = failedPaths.begin(); iter != failedPaths.end(); iter++) + { + LogContext(__func__).logErr("Chunk path could not be deleted; " + "path: " + *iter + "; " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "node: " + node.getTypedNodeID()); + retVal = false; + } + } + + return retVal; +} diff --git a/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.h b/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.h new file mode 100644 index 0000000..3678d64 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerFileSyncSlave.h @@ -0,0 +1,98 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class BuddyResyncerFileSyncSlave : public PThread +{ + friend class BuddyResyncer; // (to grant access to internal mutex) + friend class BuddyResyncJob; // (to grant access to internal mutex) + + public: + BuddyResyncerFileSyncSlave(uint16_t targetID, ChunkSyncCandidateStore* syncCandidates, + uint8_t slaveID); + virtual ~BuddyResyncerFileSyncSlave(); + + private: + AtomicSizeT onlyTerminateIfIdle; // atomic quasi-bool + + Mutex statusMutex; // protects isRunning + Condition isRunningChangeCond; + + AtomicUInt64 numChunksSynced; + AtomicUInt64 errorCount; + + bool isRunning; // true if an instance of this component is currently running + + uint16_t targetID; + + ChunkSyncCandidateStore* syncCandidates; + + virtual void run(); + void syncLoop(); + FhgfsOpsErr doResync(std::string& chunkPathStr, uint16_t localTargetID, + uint16_t buddyTargetID); + bool removeBuddyChunkUnlocked(Node& node, uint16_t buddyTargetID, std::string& pathStr); + + public: + // getters & setters + bool getIsRunning() + { + const std::lock_guard lock(statusMutex); + + return this->isRunning; + } + + void setOnlyTerminateIfIdle(bool value) + { + if (value) + onlyTerminateIfIdle.set(1); + else + onlyTerminateIfIdle.setZero(); + } + + bool getOnlyTerminateIfIdle() + { + if (onlyTerminateIfIdle.read() == 0) + return false; + else + return true; + } + + uint64_t getNumChunksSynced() + { + return numChunksSynced.read(); + } + + uint64_t getErrorCount() + { + return errorCount.read(); + } + + private: + // getters & setters + + void setIsRunning(bool isRunning) + { + const std::lock_guard lock(statusMutex); + + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } + + bool getSelfTerminateNotIdle() + { + return ( (getSelfTerminate() && (!getOnlyTerminateIfIdle())) ); + } +}; + +typedef std::list BuddyResyncerFileSyncSlaveList; +typedef BuddyResyncerFileSyncSlaveList::iterator BuddyResyncerFileSyncSlaveListIter; + +typedef std::vector BuddyResyncerFileSyncSlaveVec; +typedef BuddyResyncerFileSyncSlaveVec::iterator BuddyResyncerFileSyncSlaveVecIter; + diff --git a/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp b/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp new file mode 100644 index 0000000..146ebd4 --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.cpp @@ -0,0 +1,162 @@ +#include +#include +#include + +#include + +#include + +#include "BuddyResyncerGatherSlave.h" + +Mutex BuddyResyncerGatherSlave::staticGatherSlavesMutex; +std::map BuddyResyncerGatherSlave::staticGatherSlaves; + +BuddyResyncerGatherSlave::BuddyResyncerGatherSlave(const StorageTarget& target, + ChunkSyncCandidateStore* syncCandidates, BuddyResyncerGatherSlaveWorkQueue* workQueue, + uint8_t slaveID) : + PThread("BuddyResyncerGatherSlave_" + StringTk::uintToStr(target.getID()) + "-" + + StringTk::uintToStr(slaveID)), + target(target) +{ + this->isRunning = false; + this->syncCandidates = syncCandidates; + this->workQueue = workQueue; + + const std::lock_guard lock(staticGatherSlavesMutex); + + staticGatherSlaves[this->getName()] = this; +} + +BuddyResyncerGatherSlave::~BuddyResyncerGatherSlave() +{ +} + + +/** + * This is a component, which is started through its control frontend on-demand at + * runtime and terminates when it's done. + * We have to ensure (in cooperation with the control frontend) that we don't get multiple instances + * of this thread running at the same time. + */ +void BuddyResyncerGatherSlave::run() +{ + setIsRunning(true); + + numChunksDiscovered.setZero(); + numChunksMatched.setZero(); + numDirsDiscovered.setZero(); + numDirsMatched.setZero(); + + try + { + LogContext(__func__).log(Log_DEBUG, "Component started."); + + registerSignalHandler(); + + workLoop(); + + LogContext(__func__).log(Log_DEBUG, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +void BuddyResyncerGatherSlave::workLoop() +{ + const unsigned maxOpenFDsNum = 20; // max open FDs => max path sub-depth for efficient traversal + + while (!getSelfTerminateNotIdle()) + { + if ((workQueue->queueEmpty()) && (getSelfTerminate())) + break; + + // get a directory to scan + std::string pathStr = workQueue->fetch(this); + + if(unlikely(pathStr.empty())) + continue; + + int nftwRes = nftw(pathStr.c_str(), handleDiscoveredEntry, maxOpenFDsNum, FTW_ACTIONRETVAL); + if(nftwRes == -1) + { // error occurred + LogContext(__func__).logErr("Error during chunks walk. SysErr: " + System::getErrString()); + } + } +} + +int BuddyResyncerGatherSlave::handleDiscoveredEntry(const char* path, + const struct stat* statBuf, int ftwEntryType, struct FTW* ftwBuf) +{ + std::string chunksPath; + + BuddyResyncerGatherSlave* thisStatic = nullptr; + { + const std::lock_guard lock(staticGatherSlavesMutex); + + thisStatic = staticGatherSlaves[PThread::getCurrentThreadName()]; + } + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + const auto& targetPath = thisStatic->target.getPath().str(); + chunksPath = targetPath + "/" + CONFIG_BUDDYMIRROR_SUBDIR_NAME; + + if (strlen(path) <= chunksPath.length()) + return FTW_CONTINUE; + + std::string relPathStr = path + chunksPath.size() + 1; + + if ( relPathStr.empty() ) + return FTW_CONTINUE; + + const auto lastBuddyComm = thisStatic->target.getLastBuddyComm(); + + const bool buddyCommIsOverride = lastBuddyComm.first; + int64_t lastBuddyCommTimeSecs = std::chrono::system_clock::to_time_t(lastBuddyComm.second); + int64_t lastBuddyCommSafetyThresholdSecs = cfg->getSysResyncSafetyThresholdMins()*60; + if ( (lastBuddyCommSafetyThresholdSecs == 0) && (!buddyCommIsOverride) ) // ignore timestamp file + lastBuddyCommTimeSecs = 0; + else + if (lastBuddyCommTimeSecs > lastBuddyCommSafetyThresholdSecs) + lastBuddyCommTimeSecs -= lastBuddyCommSafetyThresholdSecs; + + if(ftwEntryType == FTW_D) // directory + { + thisStatic->numDirsDiscovered.increase(); + + int64_t dirModificationTime = (int64_t)statBuf->st_mtim.tv_sec; + + if(dirModificationTime > lastBuddyCommTimeSecs) + { // sync candidate + ChunkSyncCandidateDir candidate(relPathStr, thisStatic->target.getID()); + thisStatic->syncCandidates->add(candidate, thisStatic); + thisStatic->numDirsMatched.increase(); + } + } + else + if(ftwEntryType == FTW_F) // file + { + // we found a chunk + thisStatic->numChunksDiscovered.increase(); + + // we need to use ctime here, because mtime can be set manually (even to the future) + time_t chunkChangeTime = statBuf->st_ctim.tv_sec; + + if(chunkChangeTime > lastBuddyCommTimeSecs) + { // sync candidate + std::string relPathStr = path + chunksPath.size() + 1; + + ChunkSyncCandidateFile candidate(relPathStr, thisStatic->target.getID()); + thisStatic->syncCandidates->add(candidate, thisStatic); + + thisStatic->numChunksMatched.increase(); + } + } + + return FTW_CONTINUE; +} diff --git a/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.h b/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.h new file mode 100644 index 0000000..d3277dc --- /dev/null +++ b/storage/source/components/buddyresyncer/BuddyResyncerGatherSlave.h @@ -0,0 +1,182 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class StorageTarget; + +#define GATHERSLAVEQUEUE_MAXSIZE 5000 + +class BuddyResyncerGatherSlaveWorkQueue +{ + /* + * This is more or less just a small class for convenience, that is tightly coupled to + * BuddyResyncerGatherSlave and BuddyResyncerJob + */ + public: + BuddyResyncerGatherSlaveWorkQueue(): gatherSlavesWorkQueueLen(0) { } + + private: + StringList paths; + size_t gatherSlavesWorkQueueLen; // used to avoid constant calling of size() method of list + Mutex mutex; + Condition pathAddedCond; + Condition pathFetchedCond; + + public: + void add(std::string& path, PThread* caller) + { + unsigned waitTimeoutMS = 3000; + + const std::lock_guard lock(mutex); + + while (gatherSlavesWorkQueueLen > GATHERSLAVEQUEUE_MAXSIZE) + { + if((caller) && (unlikely(caller->getSelfTerminate()))) + break; + pathFetchedCond.timedwait(&mutex, waitTimeoutMS); + } + + paths.push_back(path); + gatherSlavesWorkQueueLen++; + pathAddedCond.signal(); + } + + std::string fetch(PThread* caller) + { + unsigned waitTimeoutMS = 3000; + + const std::lock_guard lock(mutex); + + while (paths.empty()) + { + if((caller) && (unlikely(caller->getSelfTerminate()))) + { + return ""; + } + + pathAddedCond.timedwait(&mutex, waitTimeoutMS); + } + + std::string retVal = paths.front(); + paths.pop_front(); + gatherSlavesWorkQueueLen--; + pathFetchedCond.signal(); + + return retVal; + } + + bool queueEmpty() + { + const std::lock_guard lock(mutex); + + return gatherSlavesWorkQueueLen == 0; + } + + void clear() + { + const std::lock_guard lock(mutex); + + paths.clear(); + gatherSlavesWorkQueueLen = 0; + } +}; + +class BuddyResyncerGatherSlave : public PThread +{ + friend class BuddyResyncer; // (to grant access to internal mutex) + friend class BuddyResyncJob; // (to grant access to internal mutex) + + public: + BuddyResyncerGatherSlave(const StorageTarget& target, ChunkSyncCandidateStore* syncCandidates, + BuddyResyncerGatherSlaveWorkQueue* workQueue, uint8_t slaveID); + virtual ~BuddyResyncerGatherSlave(); + + void workLoop(); + + private: + AtomicSizeT onlyTerminateIfIdle; // atomic quasi-bool + + Mutex statusMutex; // protects isRunning + Condition isRunningChangeCond; + + const StorageTarget& target; + + AtomicUInt64 numChunksDiscovered; + AtomicUInt64 numChunksMatched; + + AtomicUInt64 numDirsDiscovered; + AtomicUInt64 numDirsMatched; + + bool isRunning; // true if an instance of this component is currently running + + ChunkSyncCandidateStore* syncCandidates; + BuddyResyncerGatherSlaveWorkQueue* workQueue; + + // nftw() callback needs access the slave threads + static Mutex staticGatherSlavesMutex; + static std::map staticGatherSlaves; + + virtual void run(); + + static int handleDiscoveredEntry(const char* path, const struct stat* statBuf, + int ftwEntryType, struct FTW* ftwBuf); + + public: + // getters & setters + bool getIsRunning() + { + const std::lock_guard lock(statusMutex); + + return this->isRunning; + } + + void getCounters(uint64_t& outNumChunksDiscovered, uint64_t& outNumChunksMatched, + uint64_t& outNumDirsDiscovered, uint64_t& outNumDirsMatched) + { + outNumChunksDiscovered = numChunksDiscovered.read(); + outNumChunksMatched = numChunksMatched.read(); + outNumDirsDiscovered = numDirsDiscovered.read(); + outNumDirsMatched = numDirsMatched.read(); + } + + void setOnlyTerminateIfIdle(bool value) + { + if (value) + onlyTerminateIfIdle.set(1); + else + onlyTerminateIfIdle.setZero(); + } + + bool getOnlyTerminateIfIdle() + { + if (onlyTerminateIfIdle.read() == 0) + return false; + else + return true; + } + + private: + // getters & setters + + void setIsRunning(bool isRunning) + { + const std::lock_guard lock(statusMutex); + + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } + + bool getSelfTerminateNotIdle() + { + return ( (getSelfTerminate() && (!getOnlyTerminateIfIdle())) ); + } +}; + +typedef std::vector BuddyResyncerGatherSlaveVec; +typedef BuddyResyncerGatherSlaveVec::iterator BuddyResyncerGatherSlaveVecIter; + diff --git a/storage/source/components/buddyresyncer/SyncCandidate.h b/storage/source/components/buddyresyncer/SyncCandidate.h new file mode 100644 index 0000000..6e1a301 --- /dev/null +++ b/storage/source/components/buddyresyncer/SyncCandidate.h @@ -0,0 +1,44 @@ +#pragma once + +#include + +#include + +/** + * A storage sync candidate. Has a target ID and a path. + */ +class ChunkSyncCandidateDir +{ + public: + ChunkSyncCandidateDir(const std::string& relativePath, const uint16_t targetID) + : relativePath(relativePath), targetID(targetID) + { } + + ChunkSyncCandidateDir() + : targetID(0) + { } + + private: + std::string relativePath; + uint16_t targetID; + + public: + const std::string& getRelativePath() const { return relativePath; } + uint16_t getTargetID() const { return targetID; } +}; + +/** + * A storage sync candidate that also has an onlyAttribs flag. + */ +class ChunkSyncCandidateFile : public ChunkSyncCandidateDir +{ + public: + ChunkSyncCandidateFile(const std::string& relativePath, uint16_t targetID) + : ChunkSyncCandidateDir(relativePath, targetID) + { } + + ChunkSyncCandidateFile() = default; +}; + +typedef SyncCandidateStore ChunkSyncCandidateStore; + diff --git a/storage/source/components/chunkfetcher/ChunkFetcher.cpp b/storage/source/components/chunkfetcher/ChunkFetcher.cpp new file mode 100644 index 0000000..02c10d7 --- /dev/null +++ b/storage/source/components/chunkfetcher/ChunkFetcher.cpp @@ -0,0 +1,88 @@ +#include "ChunkFetcher.h" + +#include + +#include + +ChunkFetcher::ChunkFetcher() + : log("ChunkFetcher") +{ + // for each targetID, put one fetcher thread into list + for (const auto& mapping : Program::getApp()->getStorageTargets()->getTargets()) + this->slaves.emplace_back(mapping.first); +} + +ChunkFetcher::~ChunkFetcher() +{ +} + +/** + * Start fetcher slaves if they are not running already. + * + * @return true if successfully started or already running, false if startup problem occurred. + */ +bool ChunkFetcher::startFetching() +{ + const char* logContext = "ChunkFetcher (start)"; + bool retVal = true; // false if error occurred + + { + const std::lock_guard lock(chunksListMutex); + isBad = false; + } + + for(ChunkFetcherSlaveListIter iter = slaves.begin(); iter != slaves.end(); iter++) + { + const std::lock_guard lock(iter->statusMutex); + + if(!iter->isRunning) + { + // slave thread not running yet => start it + iter->resetSelfTerminate(); + + try + { + iter->start(); + + iter->isRunning = true; + } + catch (PThreadCreateException& e) + { + LogContext(logContext).logErr(std::string("Unable to start thread: ") + e.what()); + retVal = false; + } + } + } + + return retVal; +} + +void ChunkFetcher::stopFetching() +{ + for(ChunkFetcherSlaveListIter iter = slaves.begin(); iter != slaves.end(); iter++) + { + const std::lock_guard lock(iter->statusMutex); + + if(iter->isRunning) + { + iter->selfTerminate(); + } + } +} + +void ChunkFetcher::waitForStopFetching() +{ + for(ChunkFetcherSlaveListIter iter = slaves.begin(); iter != slaves.end(); iter++) + { + const std::lock_guard lock(iter->statusMutex); + + chunksListFetchedCondition.broadcast(); + + while (iter->isRunning) + { + iter->isRunningChangeCond.wait(&(iter->statusMutex)); + } + + chunksList.clear(); + } +} diff --git a/storage/source/components/chunkfetcher/ChunkFetcher.h b/storage/source/components/chunkfetcher/ChunkFetcher.h new file mode 100644 index 0000000..b4a2a9a --- /dev/null +++ b/storage/source/components/chunkfetcher/ChunkFetcher.h @@ -0,0 +1,101 @@ +#pragma once + +#include +#include + +#include + +#define MAX_CHUNKLIST_SIZE 5000 + +// forward declaration +class ChunkFetcher; + +typedef std::list ChunkFetcherSlaveList; +typedef ChunkFetcherSlaveList::iterator ChunkFetcherSlaveListIter; + +/** + * This is not a component that represents a separate thread. Instead, it contains and controls + * slave threads, which are started and stopped on request (i.e. they are not automatically started + * when the app is started). + * The slave threads will run over all chunks on all targets and read them in a format suitable for + * fsck + */ +class ChunkFetcher +{ + public: + ChunkFetcher(); + virtual ~ChunkFetcher(); + + bool startFetching(); + void stopFetching(); + void waitForStopFetching(); + + private: + LogContext log; + ChunkFetcherSlaveList slaves; + + FsckChunkList chunksList; + Mutex chunksListMutex; + Condition chunksListFetchedCondition; + bool isBad; + + public: + bool getIsBad() + { + const std::lock_guard lock(chunksListMutex); + + return isBad; + } + + void setBad() + { + const std::lock_guard lock(chunksListMutex); + + isBad = true; + } + + void addChunk(FsckChunk& chunk) + { + const std::lock_guard lock(chunksListMutex); + + if (chunksList.size() > MAX_CHUNKLIST_SIZE) + chunksListFetchedCondition.wait(&chunksListMutex); + + chunksList.push_back(chunk); + } + + bool isQueueEmpty() + { + std::lock_guard lock(chunksListMutex); + return chunksList.empty(); + } + + + void getAndDeleteChunks(FsckChunkList& outList, unsigned numChunks) + { + const std::lock_guard lock(chunksListMutex); + + FsckChunkListIter iterEnd = this->chunksList.begin(); + ListTk::advance(this->chunksList, iterEnd, numChunks); + + outList.splice(outList.end(), this->chunksList, this->chunksList.begin(), iterEnd); + + chunksListFetchedCondition.signal(); + } + + unsigned getNumRunning() + { + unsigned retVal = 0; + + for (ChunkFetcherSlaveListIter iter = slaves.begin(); iter != slaves.end(); iter++) + { + const std::lock_guard lock(iter->statusMutex); + + if ( iter->isRunning ) + retVal++; + } + + return retVal; + } +}; + diff --git a/storage/source/components/chunkfetcher/ChunkFetcherSlave.cpp b/storage/source/components/chunkfetcher/ChunkFetcherSlave.cpp new file mode 100644 index 0000000..d90b8df --- /dev/null +++ b/storage/source/components/chunkfetcher/ChunkFetcherSlave.cpp @@ -0,0 +1,165 @@ +#include "ChunkFetcherSlave.h" + +#include + +#include +#include + +ChunkFetcherSlave::ChunkFetcherSlave(uint16_t targetID): + PThread("ChunkFetcherSlave-" + StringTk::uintToStr(targetID) ), + log("ChunkFetcherSlave-" + StringTk::uintToStr(targetID) ), + isRunning(false), + targetID(targetID) +{ +} + +ChunkFetcherSlave::~ChunkFetcherSlave() +{ +} + +void ChunkFetcherSlave::run() +{ + setIsRunning(true); + + try + { + registerSignalHandler(); + + walkAllChunks(); + + log.log(4, "Component stopped."); + } + catch(std::exception& e) + { + PThread::getCurrentThreadApp()->handleComponentException(e); + } + + setIsRunning(false); +} + +/* + * walk over all chunks in that target + */ +void ChunkFetcherSlave::walkAllChunks() +{ + App* app = Program::getApp(); + + log.log(Log_DEBUG, "Starting chunks walk..."); + + const auto& target = *app->getStorageTargets()->getTargets().at(targetID); + + const auto& targetPath = target.getPath().str(); + + // walk over "normal" chunks (i.e. no mirrors) + std::string walkPath = targetPath + "/" + CONFIG_CHUNK_SUBDIR_NAME; + if(!walkChunkPath(walkPath, 0, walkPath.size() ) ) + return; + + // let's find out if this target is part of a buddy mirror group and if it is the primary + // target; if it is, walk over buddy mirror directory + bool isPrimaryTarget; + uint16_t buddyGroupID = app->getMirrorBuddyGroupMapper()->getBuddyGroupID(this->targetID, + &isPrimaryTarget); + + if (isPrimaryTarget) + { + walkPath = targetPath + "/" CONFIG_BUDDYMIRROR_SUBDIR_NAME; + if(!walkChunkPath(walkPath, buddyGroupID, walkPath.size() ) ) + return; + } + + log.log(Log_DEBUG, "End of chunks walk."); +} + +bool ChunkFetcherSlave::walkChunkPath(const std::string& path, uint16_t buddyGroupID, + unsigned basePathLen) +{ + DIR* dir = ::opendir(path.c_str() ); + if(!dir) + { + LOG(GENERAL, WARNING, "Could not open directory.", path, targetID, sysErr); + Program::getApp()->getChunkFetcher()->setBad(); + return false; + } + + int readRes; + bool result = true; + + std::string pathBuf = path; + pathBuf.push_back('/'); + + while(!getSelfTerminate()) + { + ::dirent* item; + + // we really want struct struct dirent to contain a reasonably sized array for the filename + BOOST_STATIC_ASSERT(sizeof(item->d_name) >= NAME_MAX + 1); + +#if USE_READDIR_R + ::dirent entry; + readRes = ::readdir_r(dir, &entry, &item); +#else + errno = 0; + item = readdir(dir); + readRes = item ? 0 : errno; +#endif + if(readRes != 0) + { + LOG(GENERAL, WARNING, "readdir failed.", path, targetID, sysErr(readRes)); + result = false; + break; + } + + if(!item) + break; + + if(::strcmp(item->d_name, ".") == 0 || ::strcmp(item->d_name, "..") == 0) + continue; + + pathBuf.resize(path.size() + 1); + pathBuf += item->d_name; + + struct stat statBuf; + + int statRes = ::stat(pathBuf.c_str(), &statBuf); + if(statRes) + { + LOG(GENERAL, WARNING, "Could not stat directory.", ("path", pathBuf), targetID, sysErr); + result = false; + break; + } + + if(S_ISDIR(statBuf.st_mode) ) + { + result = walkChunkPath(pathBuf, buddyGroupID, basePathLen); + if(!result) + break; + } + else + { + const char* relativeChunkPath = pathBuf.c_str() + basePathLen + 1; + + // get only the dirname part of the path + char* tmpPathCopy = strdup(relativeChunkPath); + Path savedPath(dirname(tmpPathCopy) ); + + free(tmpPathCopy); + + FsckChunk fsckChunk(item->d_name, targetID, savedPath, statBuf.st_size, statBuf.st_blocks, + statBuf.st_ctime, statBuf.st_mtime, statBuf.st_atime, statBuf.st_uid, statBuf.st_gid, + buddyGroupID); + + Program::getApp()->getChunkFetcher()->addChunk(fsckChunk); + } + } + + ::closedir(dir); + + if (getSelfTerminate()) + result = false; + + if(!result) + Program::getApp()->getChunkFetcher()->setBad(); + + return result; +} diff --git a/storage/source/components/chunkfetcher/ChunkFetcherSlave.h b/storage/source/components/chunkfetcher/ChunkFetcherSlave.h new file mode 100644 index 0000000..e7b67d4 --- /dev/null +++ b/storage/source/components/chunkfetcher/ChunkFetcherSlave.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class ChunkFetcher; //forward decl. + +/** + * This component runs over all chunks of one target and gathers information suitable for fsck + * + * This component is not auto-started when the app starts. It is started and stopped by the + * ChunkFetcher. + */ +class ChunkFetcherSlave : public PThread +{ + friend class ChunkFetcher; // (to grant access to internal mutex) + + public: + ChunkFetcherSlave(uint16_t targetID); + virtual ~ChunkFetcherSlave(); + + private: + LogContext log; + + Mutex statusMutex; // protects isRunning + Condition isRunningChangeCond; + + bool isRunning; // true if an instance of this component is currently running + + uint16_t targetID; + + virtual void run(); + + public: + // getters & setters + bool getIsRunning(bool isRunning) + { + const std::lock_guard lock(statusMutex); + + return this->isRunning; + } + + private: + void walkAllChunks(); + + bool walkChunkPath(const std::string& path, uint16_t buddyGroupID, unsigned basePathLen); + + // getters & setters + + void setIsRunning(bool isRunning) + { + const std::lock_guard lock(statusMutex); + + this->isRunning = isRunning; + isRunningChangeCond.broadcast(); + } +}; + diff --git a/storage/source/components/streamlistenerv2/StorageStreamListenerV2.h b/storage/source/components/streamlistenerv2/StorageStreamListenerV2.h new file mode 100644 index 0000000..8e9f46b --- /dev/null +++ b/storage/source/components/streamlistenerv2/StorageStreamListenerV2.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include + + +/** + * Other than common StreamListenerV2, this class can handle mutliple work queues through an + * overridden getWorkQueue() method. + */ +class StorageStreamListenerV2 : public StreamListenerV2 +{ + public: + StorageStreamListenerV2(std::string listenerID, AbstractApp* app): + StreamListenerV2(listenerID, app, NULL) + { + // nothing to be done here + } + + virtual ~StorageStreamListenerV2() {} + + + protected: + // getters & setters + + virtual MultiWorkQueue* getWorkQueue(uint16_t targetID) const + { + return Program::getApp()->getWorkQueue(targetID); + } +}; + diff --git a/storage/source/components/worker/StorageBenchWork.cpp b/storage/source/components/worker/StorageBenchWork.cpp new file mode 100644 index 0000000..d77fbbf --- /dev/null +++ b/storage/source/components/worker/StorageBenchWork.cpp @@ -0,0 +1,84 @@ +#include +#include +#include +#include +#include "StorageBenchWork.h" + +void StorageBenchWork::process(char* bufIn, unsigned bufInLen, char* bufOut, + unsigned bufOutLen) +{ + const char* logContext = "Storage Benchmark (run)"; + + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + int workRes = 0; // return value for benchmark operator + ssize_t ioRes = 0; // read/write result + + if (this->type == StorageBenchType_READ) + { + size_t readSize = cfg->getTuneFileReadSize(); + size_t toBeRead = this->bufLen; + size_t bufOffset = 0; + + while(toBeRead) + { + size_t currentReadSize = BEEGFS_MIN(readSize, toBeRead); + + ioRes = read(this->fileDescriptor, &this->buf[bufOffset], currentReadSize); + if (ioRes <= 0) + break; + + toBeRead -= currentReadSize; + bufOffset += currentReadSize; + } + + app->getNodeOpStats()->updateNodeOp(0, StorageOpCounter_READOPS, + this->bufLen, NETMSG_DEFAULT_USERID); + } + else + if (this->type == StorageBenchType_WRITE) + { + size_t writeSize = cfg->getTuneFileWriteSize(); + size_t toBeWritten = this->bufLen; + size_t bufOffset = 0; + + while(toBeWritten) + { + size_t currentWriteSize = BEEGFS_MIN(writeSize, toBeWritten); + + ioRes = write(this->fileDescriptor, &this->buf[bufOffset], currentWriteSize); + if (ioRes <= 0) + break; + + toBeWritten -= currentWriteSize; + bufOffset += currentWriteSize; + } + + app->getNodeOpStats()->updateNodeOp(0, StorageOpCounter_WRITEOPS, + this->bufLen, NETMSG_DEFAULT_USERID); + } + else + { // unknown benchmark type + workRes = STORAGEBENCH_ERROR_WORKER_ERROR; + LogContext(logContext).logErr("Error: unknown benchmark type"); + } + + if(unlikely(workRes < 0) || unlikely(ioRes == -1) ) + { // error occurred + if (ioRes == -1) + { // read or write operation failed + LogContext(logContext).logErr(std::string("Error: I/O failure. SysErr: ") + + System::getErrString() ); + } + + workRes = STORAGEBENCH_ERROR_WORKER_ERROR; + + this->operatorCommunication->getWriteFD()->write(&workRes, sizeof(int) ); + } + else + { // success + this->operatorCommunication->getWriteFD()->write(&this->threadID, sizeof(int) ); + } +} + diff --git a/storage/source/components/worker/StorageBenchWork.h b/storage/source/components/worker/StorageBenchWork.h new file mode 100644 index 0000000..422d664 --- /dev/null +++ b/storage/source/components/worker/StorageBenchWork.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include +#include + + + +class StorageBenchWork: public Work +{ + public: + StorageBenchWork(uint16_t targetID, int threadID, int fileDescriptor, + StorageBenchType type, int64_t bufLen, Pipe* operatorCommunication, char* buf) + { + this->targetID = targetID; + this->threadID = threadID; + this->fileDescriptor = fileDescriptor; + + this->type = type; + this->bufLen = bufLen; + this->operatorCommunication = operatorCommunication; + this->buf = buf; + } + + virtual ~StorageBenchWork() + { + } + + void process(char* bufIn, unsigned bufInLen, char* bufOut, unsigned bufOutLen); + + protected: + + private: + uint16_t targetID; + int threadID; // virtual threadID + int fileDescriptor; + StorageBenchType type; + int64_t bufLen; + char* buf; + Pipe* operatorCommunication; +}; + diff --git a/storage/source/net/message/NetMessageFactory.cpp b/storage/source/net/message/NetMessageFactory.cpp new file mode 100644 index 0000000..2ee36ec --- /dev/null +++ b/storage/source/net/message/NetMessageFactory.cpp @@ -0,0 +1,203 @@ +// control messages +#include +#include +#include +#include "control/AckMsgEx.h" +#include "control/SetChannelDirectMsgEx.h" + +// nodes messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// storage messages +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// session messages +#include +#include +#include +#include +#include +#include + +#ifdef BEEGFS_NVFS +#include +#include +#endif /* BEEGFS_NVFS */ + +// mon messages +#include + +// fsck +#include +#include +#include + +// storage benchmark +#include +#include + +// chunk balancing +#include +#include +#include + +#include +#include +#include "NetMessageFactory.h" + +/** + * @return NetMessage that must be deleted by the caller + * (msg->msgType is NETMSGTYPE_Invalid on error) + */ +std::unique_ptr NetMessageFactory::createFromMsgType(unsigned short msgType) const +{ + NetMessage* msg; + + switch(msgType) + { + // The following lines are grouped by "type of the message" and ordered alphabetically inside + // the groups. There should always be one message per line to keep a clear layout (although + // this might lead to lines that are longer than usual) + + // control messages + case NETMSGTYPE_Ack: { msg = new AckMsgEx(); } break; + case NETMSGTYPE_AuthenticateChannel: { msg = new AuthenticateChannelMsgEx(); } break; + case NETMSGTYPE_GenericResponse: { msg = new GenericResponseMsg(); } break; + case NETMSGTYPE_SetChannelDirect: { msg = new SetChannelDirectMsgEx(); } break; + case NETMSGTYPE_PeerInfo: { msg = new PeerInfoMsgEx(); } break; + + // nodes messages + case NETMSGTYPE_ChangeTargetConsistencyStatesResp: { msg = new ChangeTargetConsistencyStatesRespMsg(); } break; + case NETMSGTYPE_GenericDebug: { msg = new GenericDebugMsgEx(); } break; + case NETMSGTYPE_GetClientStats: { msg = new GetClientStatsMsgEx(); } break; + case NETMSGTYPE_GetMirrorBuddyGroupsResp: { msg = new GetMirrorBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetNodesResp: { msg = new GetNodesRespMsg(); } break; + case NETMSGTYPE_GetStatesAndBuddyGroupsResp: { msg = new GetStatesAndBuddyGroupsRespMsg(); } break; + case NETMSGTYPE_GetStoragePoolsResp: { msg = new GetStoragePoolsRespMsg(); } break; + case NETMSGTYPE_GetTargetMappingsResp: { msg = new GetTargetMappingsRespMsg(); } break; + case NETMSGTYPE_GetTargetStatesResp: { msg = new GetTargetStatesRespMsg(); } break; + case NETMSGTYPE_HeartbeatRequest: { msg = new HeartbeatRequestMsgEx(); } break; + case NETMSGTYPE_Heartbeat: { msg = new HeartbeatMsgEx(); } break; + case NETMSGTYPE_MapTargets: { msg = new MapTargetsMsgEx(); } break; + case NETMSGTYPE_PublishCapacities: { msg = new PublishCapacitiesMsgEx(); } break; + case NETMSGTYPE_MapTargetsResp: { msg = new MapTargetsRespMsg(); } break; + case NETMSGTYPE_StorageBenchControlMsg: {msg = new StorageBenchControlMsgEx(); } break; + case NETMSGTYPE_RefreshStoragePools: { msg = new RefreshStoragePoolsMsgEx(); } break; + case NETMSGTYPE_RefreshTargetStates: { msg = new RefreshTargetStatesMsgEx(); } break; + case NETMSGTYPE_RegisterNodeResp: { msg = new RegisterNodeRespMsg(); } break; + case NETMSGTYPE_RegisterTargetResp: { msg = new RegisterTargetRespMsg(); } break; + case NETMSGTYPE_RemoveBuddyGroup: { msg = new RemoveBuddyGroupMsgEx(); } break; + case NETMSGTYPE_RemoveNode: { msg = new RemoveNodeMsgEx(); } break; + case NETMSGTYPE_RemoveNodeResp: { msg = new RemoveNodeRespMsg(); } break; + case NETMSGTYPE_SetMirrorBuddyGroup: { msg = new SetMirrorBuddyGroupMsgEx(); } break; + case NETMSGTYPE_SetTargetConsistencyStates: { msg = new SetTargetConsistencyStatesMsgEx(); } break; + case NETMSGTYPE_SetTargetConsistencyStatesResp: { msg = new SetTargetConsistencyStatesRespMsg(); } break; + case NETMSGTYPE_GetTargetConsistencyStates: { msg = new GetTargetConsistencyStatesMsgEx(); } break; + case NETMSGTYPE_GetTargetConsistencyStatesResp: { msg = new GetTargetConsistencyStatesRespMsg(); } break; + + // storage messages + case NETMSGTYPE_CpChunkPaths: { msg = new CpChunkPathsMsgEx(); } break; + case NETMSGTYPE_CpChunkPathsResp: { msg = new CpChunkPathsRespMsg(); } break; + case NETMSGTYPE_FindOwnerResp: { msg = new FindOwnerRespMsg(); } break; + case NETMSGTYPE_GetChunkFileAttribs: { msg = new GetChunkFileAttribsMsgEx(); } break; + case NETMSGTYPE_GetHighResStats: { msg = new GetHighResStatsMsgEx(); } break; + case NETMSGTYPE_GetQuotaInfo: {msg = new GetQuotaInfoMsgEx(); } break; + case NETMSGTYPE_GetStorageResyncStats: { msg = new GetStorageResyncStatsMsgEx(); } break; + case NETMSGTYPE_ListChunkDirIncremental: { msg = new ListChunkDirIncrementalMsgEx(); } break; + case NETMSGTYPE_ListChunkDirIncrementalResp: { msg = new ListChunkDirIncrementalRespMsg(); } break; + case NETMSGTYPE_RequestExceededQuotaResp: {msg = new RequestExceededQuotaRespMsg(); } break; + case NETMSGTYPE_ResyncLocalFile: { msg = new ResyncLocalFileMsgEx(); } break; + case NETMSGTYPE_ResyncLocalFileResp: { msg = new ResyncLocalFileRespMsg(); } break; + case NETMSGTYPE_RmChunkPaths: { msg = new RmChunkPathsMsgEx(); } break; + case NETMSGTYPE_RmChunkPathsResp: { msg = new RmChunkPathsRespMsg(); } break; + case NETMSGTYPE_SetExceededQuota: {msg = new SetExceededQuotaMsgEx(); } break; + case NETMSGTYPE_SetLastBuddyCommOverride: { msg = new SetLastBuddyCommOverrideMsgEx(); } break; + case NETMSGTYPE_SetLocalAttr: { msg = new SetLocalAttrMsgEx(); } break; + case NETMSGTYPE_SetLocalAttrResp: { msg = new SetLocalAttrRespMsg(); } break; + case NETMSGTYPE_SetStorageTargetInfoResp: { msg = new SetStorageTargetInfoRespMsg(); } break; + case NETMSGTYPE_StatStoragePath: { msg = new StatStoragePathMsgEx(); } break; + case NETMSGTYPE_StorageResyncStarted: { msg = new StorageResyncStartedMsgEx(); } break; + case NETMSGTYPE_StorageResyncStartedResp: { msg = new StorageResyncStartedRespMsg(); } break; + case NETMSGTYPE_StripePatternUpdateResp: { msg = new StripePatternUpdateRespMsg(); } break; + case NETMSGTYPE_TruncLocalFile: { msg = new TruncLocalFileMsgEx(); } break; + case NETMSGTYPE_TruncLocalFileResp: { msg = new TruncLocalFileRespMsg(); } break; + case NETMSGTYPE_UnlinkLocalFile: { msg = new UnlinkLocalFileMsgEx(); } break; + case NETMSGTYPE_UnlinkLocalFileResp: { msg = new UnlinkLocalFileRespMsg(); } break; + + // session messages + case NETMSGTYPE_CloseChunkFile: { msg = new CloseChunkFileMsgEx(); } break; + case NETMSGTYPE_CloseChunkFileResp: { msg = new CloseChunkFileRespMsg(); } break; + case NETMSGTYPE_FSyncLocalFile: { msg = new FSyncLocalFileMsgEx(); } break; + case NETMSGTYPE_ReadLocalFileV2: { msg = new ReadLocalFileV2MsgEx(); } break; + case NETMSGTYPE_WriteLocalFile: { msg = new WriteLocalFileMsgEx(); } break; + case NETMSGTYPE_WriteLocalFileResp: { msg = new WriteLocalFileRespMsg(); } break; +#ifdef BEEGFS_NVFS + case NETMSGTYPE_ReadLocalFileRDMA: { msg = new ReadLocalFileRDMAMsgEx(); } break; + case NETMSGTYPE_WriteLocalFileRDMA: { msg = new WriteLocalFileRDMAMsgEx(); } break; +#endif // BEEGFS_NVFS + + // mon message + case NETMSGTYPE_RequestStorageData: { msg = new RequestStorageDataMsgEx(); } break; + + // fsck + case NETMSGTYPE_DeleteChunks: { msg = new DeleteChunksMsgEx(); } break; + case NETMSGTYPE_FetchFsckChunkList: { msg = new FetchFsckChunkListMsgEx(); } break; + case NETMSGTYPE_MoveChunkFile: { msg = new MoveChunkFileMsgEx(); } break; + + default: + { + msg = new SimpleMsg(NETMSGTYPE_Invalid); + } break; + } + + return std::unique_ptr(msg); +} + diff --git a/storage/source/net/message/NetMessageFactory.h b/storage/source/net/message/NetMessageFactory.h new file mode 100644 index 0000000..7331f6c --- /dev/null +++ b/storage/source/net/message/NetMessageFactory.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +class NetMessageFactory : public AbstractNetMessageFactory +{ + public: + NetMessageFactory() {} + + protected: + virtual std::unique_ptr createFromMsgType(unsigned short msgType) const override; +} ; + diff --git a/storage/source/net/message/control/AckMsgEx.cpp b/storage/source/net/message/control/AckMsgEx.cpp new file mode 100644 index 0000000..01590e1 --- /dev/null +++ b/storage/source/net/message/control/AckMsgEx.cpp @@ -0,0 +1,22 @@ +#include +#include "AckMsgEx.h" + +bool AckMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Ack incoming"); + + LOG_DEBUG_CONTEXT(log, 5, std::string("Value: ") + getValue() ); + + AcknowledgmentStore* ackStore = Program::getApp()->getAckStore(); + ackStore->receivedAck(getValue() ); + + // note: this message does not require a response + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_ACK, + getMsgHeaderUserID() ); + + return true; +} + + diff --git a/storage/source/net/message/control/AckMsgEx.h b/storage/source/net/message/control/AckMsgEx.h new file mode 100644 index 0000000..e439d1a --- /dev/null +++ b/storage/source/net/message/control/AckMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +// see class AcknowledgeableMsg (fhgfs_common) for a short description + +class AckMsgEx : public AckMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/control/SetChannelDirectMsgEx.cpp b/storage/source/net/message/control/SetChannelDirectMsgEx.cpp new file mode 100644 index 0000000..c0afd7a --- /dev/null +++ b/storage/source/net/message/control/SetChannelDirectMsgEx.cpp @@ -0,0 +1,19 @@ +#include +#include "SetChannelDirectMsgEx.h" + +bool SetChannelDirectMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("SetChannelDirect incoming"); + + LOG_DEBUG_CONTEXT(log, 5, std::string("Value: ") + StringTk::intToStr(getValue() ) ); + + ctx.getSocket()->setIsDirect(getValue() ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_SETCHANNELDIRECT, getMsgHeaderUserID() ); + + return true; +} + + diff --git a/storage/source/net/message/control/SetChannelDirectMsgEx.h b/storage/source/net/message/control/SetChannelDirectMsgEx.h new file mode 100644 index 0000000..3463086 --- /dev/null +++ b/storage/source/net/message/control/SetChannelDirectMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +// direct means the message is definitely processed on this server and not forwarded to another + +class SetChannelDirectMsgEx : public SetChannelDirectMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/fsck/DeleteChunksMsgEx.cpp b/storage/source/net/message/fsck/DeleteChunksMsgEx.cpp new file mode 100644 index 0000000..8a1dde9 --- /dev/null +++ b/storage/source/net/message/fsck/DeleteChunksMsgEx.cpp @@ -0,0 +1,60 @@ +#include "DeleteChunksMsgEx.h" + +#include +#include + +bool DeleteChunksMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "DeleteChunksMsg incoming"; + + App* app = Program::getApp(); + ChunkStore* chunkDirStore = app->getChunkDirStore(); + + FsckChunkList& chunks = getChunks(); + FsckChunkList failedDeletes; + + for ( FsckChunkListIter iter = chunks.begin(); iter != chunks.end(); iter++ ) + { + std::string chunkDirRelative; + std::string delPathStrRelative; + bool isMirrorFD = iter->getBuddyGroupID(); + + chunkDirRelative = iter->getSavedPath()->str(); + + delPathStrRelative = chunkDirRelative + "/" + iter->getID(); + + auto* const target = app->getStorageTargets()->getTarget(iter->getTargetID()); + + if (!target) + { // unknown targetID + LogContext(logContext).logErr(std::string("Unknown targetID: ") + + StringTk::uintToStr(iter->getTargetID())); + failedDeletes.push_back(*iter); + } + else + { // valid targetID + int targetFD = isMirrorFD ? *target->getMirrorFD() : *target->getChunkFD(); + int unlinkRes = unlinkat(targetFD, delPathStrRelative.c_str(), 0); + if ( (unlinkRes == -1) && (errno != ENOENT) ) + { // error + LogContext(logContext).logErr( + "Unable to unlink file: " + delPathStrRelative + ". " + "SysErr: " + + System::getErrString()); + + failedDeletes.push_back(*iter); + } + + // Now try to rmdir chunkDirPath (checks if it is empty) + if (unlinkRes == 0) + { + Path chunkDirRelativeVec(chunkDirRelative); + chunkDirStore->rmdirChunkDirPath(targetFD, &chunkDirRelativeVec); + } + + } + } + + ctx.sendResponse(DeleteChunksRespMsg(&failedDeletes) ); + + return true; +} diff --git a/storage/source/net/message/fsck/DeleteChunksMsgEx.h b/storage/source/net/message/fsck/DeleteChunksMsgEx.h new file mode 100644 index 0000000..7497da2 --- /dev/null +++ b/storage/source/net/message/fsck/DeleteChunksMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include +#include + +class DeleteChunksMsgEx : public DeleteChunksMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.cpp b/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.cpp new file mode 100644 index 0000000..4b5ed49 --- /dev/null +++ b/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.cpp @@ -0,0 +1,53 @@ +#include "FetchFsckChunkListMsgEx.h" + +#include + +bool FetchFsckChunkListMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + ChunkFetcher* chunkFetcher = app->getChunkFetcher(); + + FetchFsckChunkListStatus status; + FsckChunkList chunkList; + + if (getLastStatus() == FetchFsckChunkListStatus_NOTSTARTED) + { + // This is the first message of a new Fsck run + if (chunkFetcher->getNumRunning() != 0 || !chunkFetcher->isQueueEmpty()) + { + // another fsck is already in progress + if (!getForceRestart()) + { + LOG(GENERAL, NOTICE, "Received request to start fsck although previous run is not finished. " + "Not starting.", ("From", ctx.peerName())); + + ctx.sendResponse(FetchFsckChunkListRespMsg(&chunkList, + FetchFsckChunkListStatus_NOTSTARTED)); + return true; + } + else + { + LOG(GENERAL, NOTICE, "Aborting previous fsck chunk fetcher run by user request.", + ("From", ctx.peerName())); + + chunkFetcher->stopFetching(); + chunkFetcher->waitForStopFetching(); + } + } + + chunkFetcher->startFetching(); + } + + + if(chunkFetcher->getIsBad()) + status = FetchFsckChunkListStatus_READERROR; + else if (chunkFetcher->getNumRunning() == 0) + status = FetchFsckChunkListStatus_FINISHED; + else + status = FetchFsckChunkListStatus_RUNNING; + + chunkFetcher->getAndDeleteChunks(chunkList, getMaxNumChunks()); + + ctx.sendResponse(FetchFsckChunkListRespMsg(&chunkList, status)); + return true; +} diff --git a/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.h b/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.h new file mode 100644 index 0000000..4a56404 --- /dev/null +++ b/storage/source/net/message/fsck/FetchFsckChunkListMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class FetchFsckChunkListMsgEx : public FetchFsckChunkListMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/fsck/MoveChunkFileMsgEx.cpp b/storage/source/net/message/fsck/MoveChunkFileMsgEx.cpp new file mode 100644 index 0000000..8c438d8 --- /dev/null +++ b/storage/source/net/message/fsck/MoveChunkFileMsgEx.cpp @@ -0,0 +1,88 @@ +#include "MoveChunkFileMsgEx.h" + +#include + +bool MoveChunkFileMsgEx::processIncoming(ResponseContext& ctx) +{ + ctx.sendResponse(MoveChunkFileRespMsg(moveChunk())); + return true; +} + +unsigned MoveChunkFileMsgEx::moveChunk() +{ + const char* logContext = "MoveChunkFileMsg incoming"; + + App* app = Program::getApp(); + + std::string chunkName = this->getChunkName(); + std::string oldPath = this->getOldPath(); // relative path to chunks dir + std::string newPath = this->getNewPath(); // relative path to chunks dir + uint16_t targetID = this->getTargetID(); + bool overwriteExisting = this->getOverwriteExisting(); + + int renameRes; + + std::string moveFrom = oldPath + "/" + chunkName; + std::string moveTo = newPath + "/" + chunkName; + + auto* const target = app->getStorageTargets()->getTarget(targetID); + + if (!target) + { + LogContext(logContext).log(Log_CRITICAL, "Could not open path for target ID; targetID: " + + StringTk::uintToStr(targetID)); + return 1; + } + + const auto targetPath = getIsMirrored() + ? target->getPath() / CONFIG_BUDDYMIRROR_SUBDIR_NAME + : target->getPath() / CONFIG_CHUNK_SUBDIR_NAME; + + const int targetFD = getIsMirrored() ? *target->getMirrorFD() : *target->getChunkFD(); + + // if overwriteExisting set to false, make sure, that output file does not exist + if (!overwriteExisting) + { + bool pathExists = StorageTk::pathExists(targetFD, moveTo); + if (pathExists) + { + LogContext(logContext).log(Log_CRITICAL, + "Could not move chunk file. Destination file does already exist; chunkID: " + chunkName + + "; targetID: " + StringTk::uintToStr(targetID) + "; oldChunkPath: " + oldPath + + "; newChunkPath: " + newPath); + return 1; + } + } + + { + // create the parent directory (perhaps it didn't exist) + // can be more efficient if we write a createPathOnDisk that uses mkdirat + const Path moveToPath = targetPath / moveTo; + mode_t dirMode = S_IRWXU | S_IRWXG | S_IRWXO; + bool mkdirRes = StorageTk::createPathOnDisk(moveToPath, true, &dirMode); + + if(!mkdirRes) + { + LogContext(logContext).log(Log_CRITICAL, + "Could not create parent directory for chunk; chunkID: " + chunkName + "; targetID: " + + StringTk::uintToStr(targetID) + "; oldChunkPath: " + oldPath + "; newChunkPath: " + + newPath); + return 1; + } + } + + // perform the actual move + renameRes = renameat(targetFD, moveFrom.c_str(), targetFD, moveTo.c_str() ); + if ( renameRes != 0 ) + { + LogContext(logContext).log(Log_CRITICAL, + "Could not perform move; chunkID: " + chunkName + "; targetID: " + + StringTk::uintToStr(targetID) + "; oldChunkPath: " + oldPath + "; newChunkPath: " + + newPath + "; SysErr: " + System::getErrString()); + return 1; + } + else if (getIsMirrored()) + target->setBuddyNeedsResync(true); + + return 0; +} diff --git a/storage/source/net/message/fsck/MoveChunkFileMsgEx.h b/storage/source/net/message/fsck/MoveChunkFileMsgEx.h new file mode 100644 index 0000000..d4e5e75 --- /dev/null +++ b/storage/source/net/message/fsck/MoveChunkFileMsgEx.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +class MoveChunkFileMsgEx : public MoveChunkFileMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + unsigned moveChunk(); +}; + diff --git a/storage/source/net/message/mon/RequestStorageDataMsgEx.cpp b/storage/source/net/message/mon/RequestStorageDataMsgEx.cpp new file mode 100644 index 0000000..40a283d --- /dev/null +++ b/storage/source/net/message/mon/RequestStorageDataMsgEx.cpp @@ -0,0 +1,68 @@ +#include "RequestStorageDataMsgEx.h" + +bool RequestStorageDataMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + Node& node = app->getLocalNode(); + MultiWorkQueueMap* workQueueMap = app->getWorkQueueMap(); + StorageTargets* storageTargets = app->getStorageTargets(); + + // get disk space of each target + + StorageTargetInfoList storageTargetInfoList; + + storageTargets->generateTargetInfoList(storageTargetInfoList); + + // compute total disk space and total free space + + int64_t diskSpaceTotal = 0; // sum of all targets + int64_t diskSpaceFree = 0; // sum of all targets + + for(StorageTargetInfoListIter iter = storageTargetInfoList.begin(); + iter != storageTargetInfoList.end(); + iter++) + { + if(diskSpaceTotal == -1) + continue; // statfs() failed on this target + + diskSpaceTotal += iter->getDiskSpaceTotal(); + diskSpaceFree += iter->getDiskSpaceFree(); + } + + + unsigned sessionCount = app->getSessions()->getSize(); + + NicAddressList nicList(node.getNicList()); + std::string hostnameid = System::getHostname(); + + // highresStats + HighResStatsList statsHistory; + uint64_t lastStatsMS = getValue(); + + // get stats history + StatsCollector* statsCollector = app->getStatsCollector(); + statsCollector->getStatsSince(lastStatsMS, statsHistory); + + // get work queue stats + unsigned indirectWorkListSize = 0; + unsigned directWorkListSize = 0; + + for(MultiWorkQueueMapCIter iter = workQueueMap->begin(); iter != workQueueMap->end(); iter++) + { + indirectWorkListSize += iter->second->getIndirectWorkListSize(); + directWorkListSize += iter->second->getDirectWorkListSize(); + } + + RequestStorageDataRespMsg requestStorageDataRespMsg(node.getAlias(), hostnameid, node.getNumID(), + &nicList, indirectWorkListSize, directWorkListSize, diskSpaceTotal, diskSpaceFree, + sessionCount, &statsHistory, &storageTargetInfoList); + ctx.sendResponse(requestStorageDataRespMsg); + + LOG_DEBUG(__func__, Log_SPAM, std::string("Sent a message with type: " ) + + StringTk::uintToStr(requestStorageDataRespMsg.getMsgType() ) + std::string(" to mon") ); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_REQUESTSTORAGEDATA, getMsgHeaderUserID() ); + + return true; +} diff --git a/storage/source/net/message/mon/RequestStorageDataMsgEx.h b/storage/source/net/message/mon/RequestStorageDataMsgEx.h new file mode 100644 index 0000000..f6fa646 --- /dev/null +++ b/storage/source/net/message/mon/RequestStorageDataMsgEx.h @@ -0,0 +1,20 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +class RequestStorageDataMsgEx : public RequestStorageDataMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/GenericDebugMsgEx.cpp b/storage/source/net/message/nodes/GenericDebugMsgEx.cpp new file mode 100644 index 0000000..cfc0e41 --- /dev/null +++ b/storage/source/net/message/nodes/GenericDebugMsgEx.cpp @@ -0,0 +1,428 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "GenericDebugMsgEx.h" + + + +#define GENDBGMSG_OP_LISTOPENFILES "listopenfiles" +#define GENDBGMSG_OP_VERSION "version" +#define GENDBGMSG_OP_MSGQUEUESTATS "msgqueuestats" +#define GENDBGMSG_OP_RESYNCQUEUELEN "resyncqueuelen" +#define GENDBGMSG_OP_CHUNKLOCKSTORESIZE "chunklockstoresize" +#define GENDBGMSG_OP_CHUNKLOCKSTORECONTENTS "chunklockstore" +#define GENDBGMSG_OP_SETREJECTIONRATE "setrejectionrate" + + +bool GenericDebugMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("GenericDebugMsg incoming"); + + LOG_DEBUG_CONTEXT(log, 5, std::string("Command string: ") + getCommandStr() ); + + std::string cmdRespStr = processCommand(); + + ctx.sendResponse(GenericDebugRespMsg(cmdRespStr.c_str() ) ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_GENERICDEBUG, + getMsgHeaderUserID() ); + + return true; +} + +/** + * @return command response string + */ +std::string GenericDebugMsgEx::processCommand() +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + std::string responseStr; + std::string operation; + + // load command string into a stream to allow us to use getline + std::istringstream commandStream(getCommandStr() ); + + // get operation type from command string + std::getline(commandStream, operation, ' '); + + if(operation == GENDBGMSG_OP_LISTOPENFILES) + responseStr = processOpListOpenFiles(commandStream); + else + if(operation == GENDBGMSG_OP_VERSION) + responseStr = processOpVersion(commandStream); + else + if(operation == GENDBGMSG_OP_MSGQUEUESTATS) + responseStr = processOpMsgQueueStats(commandStream); + else + if(operation == GENDBGMSG_OP_VARLOGMESSAGES) + responseStr = MsgHelperGenericDebug::processOpVarLogMessages(commandStream); + else + if(operation == GENDBGMSG_OP_VARLOGKERNLOG) + responseStr = MsgHelperGenericDebug::processOpVarLogKernLog(commandStream); + else + if(operation == GENDBGMSG_OP_FHGFSLOG) + responseStr = MsgHelperGenericDebug::processOpFhgfsLog(commandStream); + else + if(operation == GENDBGMSG_OP_LOADAVG) + responseStr = MsgHelperGenericDebug::processOpLoadAvg(commandStream); + else + if(operation == GENDBGMSG_OP_DROPCACHES) + responseStr = MsgHelperGenericDebug::processOpDropCaches(commandStream); + else + if(operation == GENDBGMSG_OP_GETCFG) + responseStr = MsgHelperGenericDebug::processOpCfgFile(commandStream, cfg->getCfgFile() ); + else + if(operation == GENDBGMSG_OP_GETLOGLEVEL) + responseStr = MsgHelperGenericDebug::processOpGetLogLevel(commandStream); + else + if(operation == GENDBGMSG_OP_SETLOGLEVEL) + responseStr = MsgHelperGenericDebug::processOpSetLogLevel(commandStream); + else + if(operation == GENDBGMSG_OP_NETOUT) + responseStr = MsgHelperGenericDebug::processOpNetOut(commandStream, + app->getMgmtNodes(), app->getMetaNodes(), app->getStorageNodes() ); + else + if(operation == GENDBGMSG_OP_QUOTAEXCEEDED) + responseStr = processOpQuotaExceeded(commandStream); + else + if(operation == GENDBGMSG_OP_USEDQUOTA) + responseStr = processOpUsedQuota(commandStream); + else + if(operation == GENDBGMSG_OP_RESYNCQUEUELEN) + responseStr = processOpResyncQueueLen(commandStream); + else + if(operation == GENDBGMSG_OP_CHUNKLOCKSTORESIZE) + responseStr = processOpChunkLockStoreSize(commandStream); + else + if(operation == GENDBGMSG_OP_CHUNKLOCKSTORECONTENTS) + responseStr = processOpChunkLockStoreContents(commandStream); + else + if(operation == GENDBGMSG_OP_LISTSTORAGESTATES) + responseStr = MsgHelperGenericDebug::processOpListTargetStates(commandStream, + app->getTargetStateStore() ); + else + if(operation == GENDBGMSG_OP_SETREJECTIONRATE) + responseStr = processOpSetRejectionRate(commandStream); + else + responseStr = "Unknown/invalid operation"; + + return responseStr; +} + +std::string GenericDebugMsgEx::processOpListOpenFiles(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + SessionStore* sessions = app->getSessions(); + + std::ostringstream responseStream; + NumNodeIDList sessionIDs; + size_t numFilesTotal = 0; + size_t numCheckedSessions = 0; // may defer from number of initially queried sessions + + size_t numSessions = sessions->getAllSessionIDs(&sessionIDs); + + responseStream << "Found " << numSessions << " sessions." << std::endl; + + responseStream << std::endl; + + // walk over all sessions + for(NumNodeIDListCIter iter = sessionIDs.begin(); iter != sessionIDs.end(); iter++) + { + // note: sessionID might have become removed since we queried it, e.g. because client is gone + + auto session = sessions->referenceSession(*iter); + if(!session) + continue; + + numCheckedSessions++; + + SessionLocalFileStore* sessionFiles = session->getLocalFiles(); + + size_t numFiles = sessionFiles->getSize(); + + if(!numFiles) + continue; // only print sessions with open files + + numFilesTotal += numFiles; + + responseStream << *iter << ": " << numFiles << std::endl; + } + + responseStream << std::endl; + + responseStream << "Final results: " << numFilesTotal << " open files in " << + numCheckedSessions << " checked sessions"; + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpVersion(std::istringstream& commandStream) +{ + return BEEGFS_VERSION; +} + +std::string GenericDebugMsgEx::processOpMsgQueueStats(std::istringstream& commandStream) +{ + // protocol: no arguments + + App* app = Program::getApp(); + MultiWorkQueueMap* workQueueMap = app->getWorkQueueMap(); + + std::ostringstream responseStream; + std::string indirectQueueStats; + std::string directQueueStats; + std::string busyStats; + + for(MultiWorkQueueMapCIter iter = workQueueMap->begin(); iter != workQueueMap->end(); iter++) + { + MultiWorkQueue* workQ = iter->second; + + workQ->getStatsAsStr(indirectQueueStats, directQueueStats, busyStats); + + responseStream << "* [queue id " << iter->first << "] " + "general queue stats: " << std::endl << + indirectQueueStats << std::endl; + + responseStream << "* [queue id " << iter->first << "] " + "direct queue stats: " << std::endl << + directQueueStats << std::endl; + + responseStream << "* [queue id " << iter->first << "] " + "busy worker stats: " << std::endl << + busyStats << std::endl; + } + + return responseStream.str(); +} + +std::string GenericDebugMsgEx::processOpQuotaExceeded(std::istringstream& commandStream) +{ + App* app = Program::getApp(); + + std::string targetIdStr; + std::getline(commandStream, targetIdStr, ' '); + uint16_t targetId = StringTk::strToUInt(targetIdStr); + + if(!app->getConfig()->getQuotaEnableEnforcement() ) + return "No quota exceeded IDs on this storage daemon because quota enforcement is" + "disabled."; + + ExceededQuotaStorePtr exQuotaStore = app->getExceededQuotaStores()->get(targetId); + // exQuotaStore may be null;needs to be checked in MsgHelperGenericDebug::processOpQuotaExceeded + return MsgHelperGenericDebug::processOpQuotaExceeded(commandStream, exQuotaStore.get()); +} + +std::string GenericDebugMsgEx::processOpUsedQuota(std::istringstream& commandStream) +{ + App *app = Program::getApp(); + + std::ostringstream responseStream; + + ZfsSession session; + QuotaDataType quotaDataType = QuotaDataType_NONE; + std::string quotaDataTypeStr; + bool forEachTarget = false; + unsigned rangeStart = 0; + unsigned rangeEnd = 0; + + // get parameter from command string + std::string inputString; + while(!commandStream.eof() ) + { + std::getline(commandStream, inputString, ' '); + + if(inputString == "uid") + { + quotaDataType = QuotaDataType_USER; + quotaDataTypeStr = "user"; + } + else + if(inputString == "gid") + { + quotaDataType = QuotaDataType_GROUP; + quotaDataTypeStr = "group"; + } + else + if(inputString == "forEachTarget") + forEachTarget = true; + else + if(inputString == "range") + { + std::string rangeValue; + std::getline(commandStream, rangeValue, ' '); + rangeStart = StringTk::strToUInt(rangeValue); + std::getline(commandStream, rangeValue, ' '); + rangeEnd = StringTk::strToUInt(rangeValue); + } + } + + // verify given parameters + if(quotaDataType == QuotaDataType_NONE) + return "Invalid or missing quota data type argument."; + if(rangeStart == 0 && rangeEnd == 0) + return "Invalid or missing range argument."; + + + if(forEachTarget) + { + const auto& targets = app->getStorageTargets()->getTargets(); + + responseStream << "Quota data of " << targets.size() << " targets." << std::endl; + + for (const auto& mapping : targets) + { + const auto& target = *mapping.second; + + QuotaDataList outQuotaDataList; + + QuotaBlockDeviceMap quotaBlockDevices = { + {mapping.first, target.getQuotaBlockDevice()} + }; + + QuotaTk::requestQuotaForRange("aBlockDevices, rangeStart, rangeEnd, quotaDataType, + &outQuotaDataList, &session); + + responseStream << outQuotaDataList.size() << " used quota for " << quotaDataTypeStr + << " IDs on target: " << mapping.first << std::endl; + + QuotaData::quotaDataListToString(outQuotaDataList, &responseStream); + } + } + else + { + auto& targets = app->getStorageTargets()->getTargets(); + + QuotaBlockDeviceMap quotaBlockDevices; + + std::transform( + targets.begin(), targets.end(), + std::inserter(quotaBlockDevices, quotaBlockDevices.end()), + [] (const auto& target) { + return std::make_pair(target.first, target.second->getQuotaBlockDevice()); + }); + + QuotaDataList outQuotaDataList; + + QuotaTk::requestQuotaForRange("aBlockDevices, rangeStart, rangeEnd, quotaDataType, + &outQuotaDataList, &session); + + QuotaData::quotaDataListToString(outQuotaDataList, &responseStream); + } + + return responseStream.str(); +} + + +std::string GenericDebugMsgEx::processOpResyncQueueLen(std::istringstream& commandStream) +{ + // protocol: targetID files/dirs as argument (e.g. "resyncqueuelen 1234 files") + + // get parameter from command string + std::string targetIDStr; + uint16_t targetID; + std::string typeStr; + std::getline(commandStream, targetIDStr, ' '); + std::getline(commandStream, typeStr, ' '); + targetID = StringTk::strToUInt(targetIDStr); + + if (targetID == 0) + return "Invalid or missing targetID"; + + BuddyResyncJob* resyncJob = Program::getApp()->getBuddyResyncer()->getResyncJob(targetID); + + if (!resyncJob) + return "0"; + + if (typeStr == "files") + { + size_t count = resyncJob->syncCandidates.getNumFiles(); + return StringTk::uintToStr(count); + } + else + if (typeStr == "dirs") + { + size_t count = resyncJob->syncCandidates.getNumDirs(); + return StringTk::uintToStr(count); + } + else + return "Invalid or missing queue type"; +} + +std::string GenericDebugMsgEx::processOpChunkLockStoreSize(std::istringstream& commandStream) +{ + // protocol: targetID as argument (e.g. "chunklockstoresize 1234") + + // get parameter from command string + std::string targetIDStr; + uint16_t targetID; + std::getline(commandStream, targetIDStr, ' '); + targetID = StringTk::strToUInt(targetIDStr); + + if (targetID == 0) + return "Invalid or missing targetID"; + + size_t lockStoreSize = Program::getApp()->getChunkLockStore()->getSize(targetID); + + return StringTk::uintToStr(lockStoreSize); +} + +std::string GenericDebugMsgEx::processOpChunkLockStoreContents(std::istringstream& commandStream) +{ + // protocol: targetID and size limit (optional) as argument (e.g. "chunklockstoresize 1234 50") + std::stringstream outStream; + + // get parameter from command string + std::string targetIDStr; + uint16_t targetID; + std::string maxEntriesStr; + unsigned maxEntries; + std::getline(commandStream, targetIDStr, ' '); + targetID = StringTk::strToUInt(targetIDStr); + std::getline(commandStream, maxEntriesStr, ' '); + maxEntries = StringTk::strToUInt(maxEntriesStr); + + if (targetID == 0) + return "Invalid or missing targetID"; + + StringSet lockStoreContents = Program::getApp()->getChunkLockStore()->getLockStoreCopy(targetID); + unsigned lockStoreSize = lockStoreContents.size(); + StringSetIter lockStoreIter = lockStoreContents.begin(); + + if ( (maxEntries == 0) || (maxEntries > lockStoreSize) ) + maxEntries = lockStoreSize; + + for (unsigned i = 0; i < maxEntries; i++) + { + outStream << *lockStoreIter << std::endl; + lockStoreIter++; + } + + return outStream.str(); +} + +std::string GenericDebugMsgEx::processOpSetRejectionRate(std::istringstream& commandStream) +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + std::string rejectionRateStr; + std::ostringstream responseStream; + + std::getline(commandStream, rejectionRateStr, ' '); + unsigned rejectionRate = StringTk::strToUInt(rejectionRateStr); + + cfg->setConnectionRejectionRate(rejectionRate); + + responseStream << "Setting connection reject rate to " << rejectionRate << std::endl; + return responseStream.str(); +} + diff --git a/storage/source/net/message/nodes/GenericDebugMsgEx.h b/storage/source/net/message/nodes/GenericDebugMsgEx.h new file mode 100644 index 0000000..3b1ce98 --- /dev/null +++ b/storage/source/net/message/nodes/GenericDebugMsgEx.h @@ -0,0 +1,24 @@ +#pragma once + +#include + + +class GenericDebugMsgEx : public GenericDebugMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + std::string processCommand(); + + std::string processOpListOpenFiles(std::istringstream& commandStream); + std::string processOpVersion(std::istringstream& commandStream); + std::string processOpMsgQueueStats(std::istringstream& commandStream); + std::string processOpQuotaExceeded(std::istringstream& commandStream); + std::string processOpUsedQuota(std::istringstream& commandStream); + std::string processOpResyncQueueLen(std::istringstream& commandStream); + std::string processOpChunkLockStoreSize(std::istringstream& commandStream); + std::string processOpChunkLockStoreContents(std::istringstream& commandStream); + std::string processOpSetRejectionRate(std::istringstream& commandStream); +}; + diff --git a/storage/source/net/message/nodes/GetClientStatsMsgEx.cpp b/storage/source/net/message/nodes/GetClientStatsMsgEx.cpp new file mode 100644 index 0000000..0c01090 --- /dev/null +++ b/storage/source/net/message/nodes/GetClientStatsMsgEx.cpp @@ -0,0 +1,30 @@ +#include +#include +#include +#include +#include "GetClientStatsMsgEx.h" +#include +#include + + +/** + * Server side, called when the server gets a GetClientStatsMsgEx request + */ +bool GetClientStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + uint64_t cookieIP = getCookieIP(); // requested is cookie+1 + + // get stats + StorageNodeOpStats* clientOpStats = Program::getApp()->getNodeOpStats(); + + bool wantPerUserStats = isMsgHeaderFeatureFlagSet(GETCLIENTSTATSMSG_FLAG_PERUSERSTATS); + UInt64Vector opStatsVec; + + clientOpStats->mapToUInt64Vec( + cookieIP, GETCLIENTSTATSRESP_MAX_PAYLOAD_LEN, wantPerUserStats, &opStatsVec); + + ctx.sendResponse(GetClientStatsRespMsg(&opStatsVec) ); + + return true; +} + diff --git a/storage/source/net/message/nodes/GetClientStatsMsgEx.h b/storage/source/net/message/nodes/GetClientStatsMsgEx.h new file mode 100644 index 0000000..3f4808b --- /dev/null +++ b/storage/source/net/message/nodes/GetClientStatsMsgEx.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + + +// NOTE: The message factory requires this object to have 'deserialize' and +// 'processIncoming' methods. 'deserialize' is derived from other classes. + +class GetClientStatsMsgEx : public GetClientStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.cpp b/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.cpp new file mode 100644 index 0000000..690b01e --- /dev/null +++ b/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.cpp @@ -0,0 +1,24 @@ +#include +#include + +#include + +#include "GetTargetConsistencyStatesMsgEx.h" + +bool GetTargetConsistencyStatesMsgEx::processIncoming(ResponseContext& ctx) +{ + StorageTargets* storageTargets = Program::getApp()->getStorageTargets(); + + TargetConsistencyStateVec states; + std::transform( + targetIDs.begin(), targetIDs.end(), + std::back_inserter(states), + [storageTargets] (uint16_t targetID) { + auto* const target = storageTargets->getTarget(targetID); + return target ? target->getConsistencyState() : TargetConsistencyState_BAD; + }); + + ctx.sendResponse(GetTargetConsistencyStatesRespMsg(states)); + + return true; +} diff --git a/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.h b/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.h new file mode 100644 index 0000000..8fa63f1 --- /dev/null +++ b/storage/source/net/message/nodes/GetTargetConsistencyStatesMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class GetTargetConsistencyStatesMsgEx : public GetTargetConsistencyStatesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/HeartbeatMsgEx.cpp b/storage/source/net/message/nodes/HeartbeatMsgEx.cpp new file mode 100644 index 0000000..0fa91f2 --- /dev/null +++ b/storage/source/net/message/nodes/HeartbeatMsgEx.cpp @@ -0,0 +1,76 @@ +#include +#include +#include "HeartbeatMsgEx.h" + +#include + +bool HeartbeatMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Heartbeat incoming"); + + App* app = Program::getApp(); + bool isNodeNew; + + // construct node + + NicAddressList& nicList = getNicList(); + + auto node = std::make_shared(getNodeType(), getNodeID(), getNodeNumID(), getPortUDP(), + getPortTCP(), nicList); + + // set local nic capabilities + + NicAddressList localNicList(app->getLocalNicList() ); + NicListCapabilities localNicCaps; + + NetworkInterfaceCard::supportedCapabilities(&localNicList, &localNicCaps); + node->getConnPool()->setLocalNicList(localNicList, localNicCaps); + + std::string nodeIDWithTypeStr = node->getNodeIDWithTypeStr(); + + log.log(Log_DEBUG, std::string("Heartbeat node: ") + nodeIDWithTypeStr); + + // add/update node in store + + AbstractNodeStore* nodes; + + switch(getNodeType() ) + { + case NODETYPE_Meta: + nodes = app->getMetaNodes(); break; + + case NODETYPE_Mgmt: + nodes = app->getMgmtNodes(); break; + + case NODETYPE_Storage: + nodes = app->getStorageNodes(); break; + + default: + { + log.logErr("Invalid/unexpected node type: " + + boost::lexical_cast(getNodeType())); + + goto ack_resp; + } break; + } + + isNodeNew = (nodes->addOrUpdateNode(std::move(node)) == NodeStoreResult::Added); + if(isNodeNew) + { // log info about new server + bool supportsRDMA = NetworkInterfaceCard::supportsRDMA(&nicList); + + log.log(Log_WARNING, std::string("New node: ") + + nodeIDWithTypeStr + "; " + + std::string(supportsRDMA ? "RDMA; " : "") ); + } + + +ack_resp: + acknowledge(ctx); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_HEARTBEAT, + getMsgHeaderUserID() ); + + return true; +} + diff --git a/storage/source/net/message/nodes/HeartbeatMsgEx.h b/storage/source/net/message/nodes/HeartbeatMsgEx.h new file mode 100644 index 0000000..376bc11 --- /dev/null +++ b/storage/source/net/message/nodes/HeartbeatMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class HeartbeatMsgEx : public HeartbeatMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/HeartbeatRequestMsgEx.cpp b/storage/source/net/message/nodes/HeartbeatRequestMsgEx.cpp new file mode 100644 index 0000000..a6f1106 --- /dev/null +++ b/storage/source/net/message/nodes/HeartbeatRequestMsgEx.cpp @@ -0,0 +1,28 @@ +#include +#include +#include +#include "HeartbeatRequestMsgEx.h" + +bool HeartbeatRequestMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("Heartbeat request incoming"); + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + Node& localNode = app->getLocalNode(); + NumNodeID localNodeNumID = localNode.getNumID(); + NicAddressList nicList(localNode.getNicList() ); + + HeartbeatMsg hbMsg(localNode.getAlias(), localNodeNumID, NODETYPE_Storage, &nicList); + hbMsg.setPorts(cfg->getConnStoragePort(), cfg->getConnStoragePort() ); + + ctx.sendResponse(hbMsg); + + log.log(Log_DEBUG, std::string("Heartbeat req ip:") + StringTk::uintToHexStr(ctx.getSocket()->getPeerIP())); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_HEARTBEAT, + getMsgHeaderUserID() ); + + return true; +} + diff --git a/storage/source/net/message/nodes/HeartbeatRequestMsgEx.h b/storage/source/net/message/nodes/HeartbeatRequestMsgEx.h new file mode 100644 index 0000000..e5a50c4 --- /dev/null +++ b/storage/source/net/message/nodes/HeartbeatRequestMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class HeartbeatRequestMsgEx : public HeartbeatRequestMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/MapTargetsMsgEx.cpp b/storage/source/net/message/nodes/MapTargetsMsgEx.cpp new file mode 100644 index 0000000..b868eaf --- /dev/null +++ b/storage/source/net/message/nodes/MapTargetsMsgEx.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include "MapTargetsMsgEx.h" + + +bool MapTargetsMsgEx::processIncoming(ResponseContext& ctx) +{ + LogContext log("MapTargetsMsg incoming"); + + const App* app = Program::getApp(); + const NodeStoreServers* storageNodes = app->getStorageNodes(); + TargetMapper* targetMapper = app->getTargetMapper(); + + const NumNodeID nodeID = getNodeID(); + std::map results; + + for (const auto mapping : getTargets()) + { + const auto targetId = mapping.first; + const auto poolId = mapping.second; + + const auto mapRes = targetMapper->mapTarget(targetId, nodeID, poolId); + + results[targetId] = mapRes.first; + + if ( (mapRes.first != FhgfsOpsErr_SUCCESS) && (mapRes.second) ) + { // target could be mapped and is new + LOG_DEBUG_CONTEXT(log, Log_WARNING, "Mapping " + "target " + StringTk::uintToStr(targetId) + + " => " + + storageNodes->getNodeIDWithTypeStr(nodeID) ); + + IGNORE_UNUSED_VARIABLE(storageNodes); + } + } + + if(!acknowledge(ctx) ) + ctx.sendResponse(MapTargetsRespMsg(results)); + + return true; +} + diff --git a/storage/source/net/message/nodes/MapTargetsMsgEx.h b/storage/source/net/message/nodes/MapTargetsMsgEx.h new file mode 100644 index 0000000..ba521c3 --- /dev/null +++ b/storage/source/net/message/nodes/MapTargetsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class MapTargetsMsgEx : public MapTargetsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/nodes/PublishCapacitiesMsgEx.cpp b/storage/source/net/message/nodes/PublishCapacitiesMsgEx.cpp new file mode 100644 index 0000000..2ff26c8 --- /dev/null +++ b/storage/source/net/message/nodes/PublishCapacitiesMsgEx.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "PublishCapacitiesMsgEx.h" + + +bool PublishCapacitiesMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + InternodeSyncer* syncer = app->getInternodeSyncer(); + + + // force upload of capacity information + syncer->setForcePublishCapacities(); + + // send response + acknowledge(ctx); + + return true; +} + diff --git a/storage/source/net/message/nodes/PublishCapacitiesMsgEx.h b/storage/source/net/message/nodes/PublishCapacitiesMsgEx.h new file mode 100644 index 0000000..be11015 --- /dev/null +++ b/storage/source/net/message/nodes/PublishCapacitiesMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include + + +class PublishCapacitiesMsgEx : public PublishCapacitiesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp b/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp new file mode 100644 index 0000000..e7208aa --- /dev/null +++ b/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.cpp @@ -0,0 +1,21 @@ +#include +#include + +#include "RefreshTargetStatesMsgEx.h" + + +bool RefreshTargetStatesMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + InternodeSyncer* syncer = app->getInternodeSyncer(); + + + // force update of capacity pools + syncer->setForceTargetStatesUpdate(); + + // send response + acknowledge(ctx); + + return true; +} + diff --git a/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.h b/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.h new file mode 100644 index 0000000..e32c878 --- /dev/null +++ b/storage/source/net/message/nodes/RefreshTargetStatesMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + + +class RefreshTargetStatesMsgEx : public RefreshTargetStatesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.cpp b/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.cpp new file mode 100644 index 0000000..da3e388 --- /dev/null +++ b/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.cpp @@ -0,0 +1,124 @@ +#include "RemoveBuddyGroupMsgEx.h" + +#include +#include +#include + +static FhgfsOpsErr checkChunkDirRemovable(const int dirFD) +{ + DIR* dir = fdopendir(dirFD); + std::unique_ptr _dir(dir); + + while (true) + { + struct dirent* result; + +#if USE_READDIR_R + struct dirent buffer; + if (readdir_r(dir, &buffer, &result) != 0) + break; +#else + errno = 0; + result = readdir(dir); + if (!result && errno) + break; +#endif + + if (!result) + return FhgfsOpsErr_SUCCESS; + + if (strcmp(result->d_name, ".") == 0 || strcmp(result->d_name, "..") == 0) + continue; + + struct stat statData; + + const int statRes = ::fstatat(dirfd(dir), result->d_name, &statData, AT_SYMLINK_NOFOLLOW); + if (statRes != 0) + { + LOG(MIRRORING, ERR, "Could not stat something in chunk directory."); + return FhgfsOpsErr_INTERNAL; + } + + if (!S_ISDIR(statData.st_mode)) + return FhgfsOpsErr_NOTEMPTY; + + const int subdir = ::openat(dirfd(dir), result->d_name, O_RDONLY); + if (subdir < 0) + { + LOG(MIRRORING, ERR, "Could not open directory in chunk path."); + return FhgfsOpsErr_INTERNAL; + } + + const FhgfsOpsErr checkRes = checkChunkDirRemovable(subdir); + if (checkRes != FhgfsOpsErr_SUCCESS) + return checkRes; + } + + return FhgfsOpsErr_INTERNAL; +} + +bool RemoveBuddyGroupMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + if (type != NODETYPE_Storage) + { + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + uint16_t targetID = app->getMirrorBuddyGroupMapper()->getPrimaryTargetID(groupID); + if (app->getTargetMapper()->getNodeID(targetID) != app->getLocalNode().getNumID()) + targetID = app->getMirrorBuddyGroupMapper()->getSecondaryTargetID(groupID); + if (app->getTargetMapper()->getNodeID(targetID) != app->getLocalNode().getNumID()) + { + LOG(MIRRORING, ERR, "Group is not mapped on this target.", groupID); + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + LOG(MIRRORING, ERR, "Could not open directory file descriptor.", groupID); + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + const int dirFD = openat(*target->getMirrorFD(), ".", O_RDONLY); + if (dirFD < 0) + { + LOG(MIRRORING, ERR, "Could not open directory file descriptor.", groupID); + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + + const FhgfsOpsErr checkRes = checkChunkDirRemovable(dirFD); + + const bool forceAndNotEmpty = checkRes == FhgfsOpsErr_NOTEMPTY && force; + + if (checkRes == FhgfsOpsErr_SUCCESS || forceAndNotEmpty) + { + if (!checkOnly) + { + auto* const bgm = Program::getApp()->getMirrorBuddyGroupMapper(); + const NumNodeID localID = Program::getApp()->getLocalNode().getNumID(); + + if (!bgm->unmapMirrorBuddyGroup(groupID, localID)) + { + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_INTERNAL)); + return true; + } + } + + ctx.sendResponse(RemoveBuddyGroupRespMsg(FhgfsOpsErr_SUCCESS)); + return true; + } + else + { + ctx.sendResponse(RemoveBuddyGroupRespMsg(checkRes)); + return true; + + } + +} diff --git a/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.h b/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.h new file mode 100644 index 0000000..ff6b04a --- /dev/null +++ b/storage/source/net/message/nodes/RemoveBuddyGroupMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RemoveBuddyGroupMsgEx : public RemoveBuddyGroupMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/RemoveNodeMsgEx.cpp b/storage/source/net/message/nodes/RemoveNodeMsgEx.cpp new file mode 100644 index 0000000..a7e8334 --- /dev/null +++ b/storage/source/net/message/nodes/RemoveNodeMsgEx.cpp @@ -0,0 +1,37 @@ +#include +#include +#include +#include "RemoveNodeMsgEx.h" + + +bool RemoveNodeMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + LOG_DBG(GENERAL, SPAM, "Removing node.", getNodeNumID()); + + if (getNodeType() == NODETYPE_Storage) + { + NodeStoreServers* nodes = app->getStorageNodes(); + auto node = nodes->referenceNode(getNodeNumID()); + bool delRes = nodes->deleteNode(getNodeNumID()); + + // log + if (delRes) + { + LOG(GENERAL, WARNING, "Node removed.", ("node", node->getNodeIDWithTypeStr())); + LOG(GENERAL, WARNING, "Number of nodes in the system:", + ("meta", app->getMetaNodes()->getSize()), + ("storage", app->getStorageNodes()->getSize())); + } + } + + if (!acknowledge(ctx)) + ctx.sendResponse(RemoveNodeRespMsg(0)); + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_REMOVENODE, + getMsgHeaderUserID() ); + + return true; +} + diff --git a/storage/source/net/message/nodes/RemoveNodeMsgEx.h b/storage/source/net/message/nodes/RemoveNodeMsgEx.h new file mode 100644 index 0000000..634668f --- /dev/null +++ b/storage/source/net/message/nodes/RemoveNodeMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class RemoveNodeMsgEx : public RemoveNodeMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp b/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp new file mode 100644 index 0000000..3749c14 --- /dev/null +++ b/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.cpp @@ -0,0 +1,37 @@ +#include +#include +#include + +#include + +#include "SetMirrorBuddyGroupMsgEx.h" + +bool SetMirrorBuddyGroupMsgEx::processIncoming(ResponseContext& ctx) +{ + uint16_t buddyGroupID = this->getBuddyGroupID(); + + if (getNodeType() != NODETYPE_Storage) + { + // The storage server has no mapper for meta buddy groups - nothing to do, just acknowledge + if (!acknowledge(ctx)) + ctx.sendResponse(SetMirrorBuddyGroupRespMsg(FhgfsOpsErr_SUCCESS, buddyGroupID)); + return true; + } + + App* app = Program::getApp(); + MirrorBuddyGroupMapper* buddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + uint16_t primaryTargetID = this->getPrimaryTargetID(); + uint16_t secondaryTargetID = this->getSecondaryTargetID(); + bool allowUpdate = this->getAllowUpdate(); + uint16_t newBuddyGroupID = 0; + + FhgfsOpsErr mapResult = buddyGroupMapper->mapMirrorBuddyGroup(buddyGroupID, primaryTargetID, + secondaryTargetID, app->getLocalNode().getNumID(), allowUpdate, &newBuddyGroupID); + + if(!acknowledge(ctx) ) + ctx.sendResponse(SetMirrorBuddyGroupRespMsg(mapResult, newBuddyGroupID) ); + + return true; +} + diff --git a/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h b/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h new file mode 100644 index 0000000..91930dd --- /dev/null +++ b/storage/source/net/message/nodes/SetMirrorBuddyGroupMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +class SetMirrorBuddyGroupMsgEx : public SetMirrorBuddyGroupMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + + diff --git a/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp b/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp new file mode 100644 index 0000000..3cc1c3f --- /dev/null +++ b/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.cpp @@ -0,0 +1,40 @@ +#include +#include +#include +#include + +#include "SetTargetConsistencyStatesMsgEx.h" + +bool SetTargetConsistencyStatesMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + StorageTargets* storageTargets = app->getStorageTargets(); + FhgfsOpsErr result = FhgfsOpsErr_SUCCESS; + + if (getTargetIDs().size() != getStates().size()) + { + LogContext(__func__).logErr("Different list size of targetIDs and states"); + result = FhgfsOpsErr_INTERNAL; + goto send_response; + } + + for (ZipIterRange idStateIter(getTargetIDs(), getStates()); + !idStateIter.empty(); ++idStateIter) + { + auto* const target = storageTargets->getTarget(*idStateIter()->first); + if (!target) + { + LogContext(__func__).logErr("Unknown targetID: " + + StringTk::uintToStr(*(idStateIter()->first) ) ); + result = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + + target->setState(TargetConsistencyState(*idStateIter()->second)); + } + +send_response: + ctx.sendResponse(SetTargetConsistencyStatesRespMsg(result) ); + + return true; +} diff --git a/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h b/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h new file mode 100644 index 0000000..dcae86b --- /dev/null +++ b/storage/source/net/message/nodes/SetTargetConsistencyStatesMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class SetTargetConsistencyStatesMsgEx : public SetTargetConsistencyStatesMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/StorageBenchControlMsgEx.cpp b/storage/source/net/message/nodes/StorageBenchControlMsgEx.cpp new file mode 100644 index 0000000..6560600 --- /dev/null +++ b/storage/source/net/message/nodes/StorageBenchControlMsgEx.cpp @@ -0,0 +1,66 @@ +#include +#include +#include +#include +#include "StorageBenchControlMsgEx.h" + +bool StorageBenchControlMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "StorageBenchControlMsg incoming"; + + StorageBenchResultsMap results; + int cmdErrorCode = STORAGEBENCH_ERROR_NO_ERROR; + + App* app = Program::getApp(); + StorageBenchOperator* storageBench = app->getStorageBenchOperator(); + + switch(getAction()) + { + case StorageBenchAction_START: + { + cmdErrorCode = storageBench->initAndStartStorageBench(&getTargetIDs(), getBlocksize(), + getSize(), getThreads(), getODirect(), getType() ); + } break; + + case StorageBenchAction_STOP: + { + cmdErrorCode = storageBench->stopBenchmark(); + } break; + + case StorageBenchAction_STATUS: + { + storageBench->getStatusWithResults(&getTargetIDs(), &results); + cmdErrorCode = STORAGEBENCH_ERROR_NO_ERROR; + } break; + + case StorageBenchAction_CLEANUP: + { + cmdErrorCode = storageBench->cleanup(&getTargetIDs()); + } break; + + default: + { + LogContext(logContext).logErr("unknown action!"); + } break; + } + + int errorCode; + + // check if the last command from the fhgfs_cmd was successful, + // if not send the error code of the command to the fhgfs_cmd + // if it was successful, send the error code of the last run or acutely run of the benchmark + if (cmdErrorCode != STORAGEBENCH_ERROR_NO_ERROR) + { + errorCode = cmdErrorCode; + } + else + { + errorCode = storageBench->getLastRunErrorCode(); + } + + ctx.sendResponse( + StorageBenchControlMsgResp(storageBench->getStatus(), getAction(), + storageBench->getType(), errorCode, results) ); + + return true; +} diff --git a/storage/source/net/message/nodes/StorageBenchControlMsgEx.h b/storage/source/net/message/nodes/StorageBenchControlMsgEx.h new file mode 100644 index 0000000..0faf52b --- /dev/null +++ b/storage/source/net/message/nodes/StorageBenchControlMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class StorageBenchControlMsgEx: public StorageBenchControlMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp b/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp new file mode 100644 index 0000000..546f73a --- /dev/null +++ b/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.cpp @@ -0,0 +1,14 @@ +#include "RefreshStoragePoolsMsgEx.h" + +#include + +bool RefreshStoragePoolsMsgEx::processIncoming(ResponseContext& ctx) +{ + Program::getApp()->getInternodeSyncer()->setForceStoragePoolsUpdate(); + + // can only come as an AcknowledgableMsg from mgmtd + acknowledge(ctx); + + return true; +} + diff --git a/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h b/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h new file mode 100644 index 0000000..64490b4 --- /dev/null +++ b/storage/source/net/message/nodes/storagepools/RefreshStoragePoolsMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RefreshStoragePoolsMsgEx : public RefreshStoragePoolsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/session/FSyncLocalFileMsgEx.cpp b/storage/source/net/message/session/FSyncLocalFileMsgEx.cpp new file mode 100644 index 0000000..f36a07e --- /dev/null +++ b/storage/source/net/message/session/FSyncLocalFileMsgEx.cpp @@ -0,0 +1,94 @@ +#include +#include +#include +#include +#include "FSyncLocalFileMsgEx.h" + + +bool FSyncLocalFileMsgEx::processIncoming(ResponseContext& ctx) +{ + ctx.sendResponse(FSyncLocalFileRespMsg(fsync())); + + return true; +} + +FhgfsOpsErr FSyncLocalFileMsgEx::fsync() +{ + const char* logContext = "FSyncLocalFileMsg incoming"; + + FhgfsOpsErr clientRes = FhgfsOpsErr_SUCCESS; + bool isMirrorSession = isMsgHeaderFeatureFlagSet(FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR); + + // do session check only when it is not a mirror session + bool useSessionCheck = isMirrorSession ? false : + isMsgHeaderFeatureFlagSet(FSYNCLOCALFILEMSG_FLAG_SESSION_CHECK); + + App* app = Program::getApp(); + SessionStore* sessions = app->getSessions(); + auto session = sessions->referenceOrAddSession(getSessionID()); + SessionLocalFileStore* sessionLocalFiles = session->getLocalFiles(); + + // select the right targetID + + uint16_t targetID = getTargetID(); + + if(isMirrorSession) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(FSYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + // note: only log message here, error handling will happen below through invalid targetFD + if(unlikely(!targetID) ) + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + } + + auto sessionLocalFile = + sessionLocalFiles->referenceSession(getFileHandleID(), targetID, isMirrorSession); + + if(sessionLocalFile) + { // sessionLocalFile exists => check if open and perform fsync + if (!isMsgHeaderFeatureFlagSet(FSYNCLOCALFILEMSG_FLAG_NO_SYNC) ) + { + auto& fd = sessionLocalFile->getFD(); + if (fd.valid()) + { // file open => sync + int fsyncRes = MsgHelperIO::fsync(*fd); + + if(fsyncRes) + { + LogContext log(logContext); + log.log(Log_WARNING, std::string("fsync of chunk file failed. ") + + std::string("SessionID: ") + getSessionID().str() + + std::string(". SysErr: ") + System::getErrString() ); + + clientRes = FhgfsOpsErr_INTERNAL; + } + } + + } + + if(useSessionCheck && sessionLocalFile->isServerCrashed() ) + { // server crashed during the write, maybe lost some data send error to client + LogContext log(logContext); + log.log(Log_SPAM, "Potential cache loss for open file handle. (Server crash detected.) " + "The session is marked as dirty."); + clientRes = FhgfsOpsErr_STORAGE_SRV_CRASHED; + } + } + else + if (useSessionCheck) + { // the server crashed during a write or before the close was successful + LogContext log(logContext); + log.log(Log_WARNING, "Potential cache loss for open file handle. (Server crash detected.) " + "No session for file available. " + "FileHandleID: " + std::string(getFileHandleID()) ); + + clientRes = FhgfsOpsErr_STORAGE_SRV_CRASHED; + } + + return clientRes; +} diff --git a/storage/source/net/message/session/FSyncLocalFileMsgEx.h b/storage/source/net/message/session/FSyncLocalFileMsgEx.h new file mode 100644 index 0000000..e622603 --- /dev/null +++ b/storage/source/net/message/session/FSyncLocalFileMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class FSyncLocalFileMsgEx : public FSyncLocalFileMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr fsync(); +}; + diff --git a/storage/source/net/message/session/opening/CloseChunkFileMsgEx.cpp b/storage/source/net/message/session/opening/CloseChunkFileMsgEx.cpp new file mode 100644 index 0000000..fb4e461 --- /dev/null +++ b/storage/source/net/message/session/opening/CloseChunkFileMsgEx.cpp @@ -0,0 +1,252 @@ +#include +#include +#include +#include +#include +#include +#include "CloseChunkFileMsgEx.h" + +#include + +bool CloseChunkFileMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + + FhgfsOpsErr closeMsgRes; + DynamicAttribs dynAttribs; + + std::tie(closeMsgRes, dynAttribs) = close(ctx); + // if closeMsgRes == FhgfsOpsErr_COMMUNICATION, a GenericResponseMsg has been sent already + if (closeMsgRes != FhgfsOpsErr_COMMUNICATION) + ctx.sendResponse( + CloseChunkFileRespMsg(closeMsgRes, dynAttribs.filesize, dynAttribs.allocedBlocks, + dynAttribs.modificationTimeSecs, dynAttribs.lastAccessTimeSecs, + dynAttribs.storageVersion) ); + + // update op counters + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_CLOSELOCAL, + getMsgHeaderUserID() ); + + return true; +} + +std::pair CloseChunkFileMsgEx::close( + ResponseContext& ctx) +{ + const char* logContext = "CloseChunkFileMsg incoming"; + + App* app = Program::getApp(); + Config* config = app->getConfig(); + SessionStore* sessions = app->getSessions(); + + uint16_t targetID; + + FhgfsOpsErr closeMsgRes = FhgfsOpsErr_SUCCESS; // the result that will be sent to requestor + DynamicAttribs dynAttribs = {0, 0, 0, 0, 0}; + + std::string fileHandleID(getFileHandleID() ); + bool isMirrorSession = isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR); + + SessionLocalFileStore* sessionLocalFiles; + + // select the right targetID + + targetID = getTargetID(); + + if(isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { // unknown target + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + return {FhgfsOpsErr_UNKNOWNTARGET, {}}; + } + } + + // forward to secondary (if appropriate) + + closeMsgRes = forwardToSecondary(ctx); + if (unlikely(closeMsgRes != FhgfsOpsErr_SUCCESS)) + return {closeMsgRes, dynAttribs}; + + auto session = sessions->referenceOrAddSession(getSessionID()); + sessionLocalFiles = session->getLocalFiles(); + + auto fsState = sessionLocalFiles->removeSession(fileHandleID, targetID, isMirrorSession); + + // get current dynamic file attribs + + if (fsState) + { // file no longer in use => refresh filesize and close file fd + auto& fd = fsState->getFD(); + + /* get dynamic attribs, here before closing the file. + * Note: Depending on the underlying file system the returned st_blocks might be too large + * (pre-allocated blocks, which are only released on close() ). Advantage here is + * that we already have the file descriptor. */ + if( (config->getTuneEarlyStat() ) && + (!isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS) ) ) + getDynamicAttribsByFD(*fd, fileHandleID, targetID, dynAttribs); + + // close fd + + if (!fsState->close()) + closeMsgRes = FhgfsOpsErr_INTERNAL; + + // only get the attributes here, in order to make xfs to release pre-allocated blocks + if( (!config->getTuneEarlyStat() ) && + (!isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS) ) ) + getDynamicAttribsByPath(fileHandleID, targetID, dynAttribs); + + } + else + if(!isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_NODYNAMICATTRIBS) ) + { // file still in use by other threads => get dynamic attribs by path + + bool getRes = getDynamicAttribsByPath(fileHandleID, targetID, dynAttribs); + if (getRes) + { + // LogContext(logContext).log(Log_DEBUG, "Chunk file virtually closed. " + // "HandleID: " + fileHandleID); + } + } + + + // note: "file not exists" is not an error. we just have nothing to do in that case. + + return {closeMsgRes, dynAttribs}; +} + +/** + * If this is a buddy mirror msg and we are the primary, forward this msg to secondary. + * + * @return _COMMUNICATION if forwarding to buddy failed and buddy is not marked offline (in which + * case *outChunkLocked==false is guaranteed). + * @throw SocketException if sending of GenericResponseMsg fails. + */ +FhgfsOpsErr CloseChunkFileMsgEx::forwardToSecondary(ResponseContext& ctx) +{ + const char* logContext = "CloseChunkFileMsg incoming (forward to secondary)"; + + App* app = Program::getApp(); + + if(!isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR) || + isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + return FhgfsOpsErr_SUCCESS; // nothing to do + + // instead of creating a new msg object, we just re-use "this" with "buddymirror second" flag + addMsgHeaderFeatureFlag(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + RequestResponseArgs rrArgs(NULL, this, NETMSGTYPE_CloseChunkFileResp); + RequestResponseTarget rrTarget(getTargetID(), app->getTargetMapper(), app->getStorageNodes(), + app->getTargetStateStore(), app->getMirrorBuddyGroupMapper(), true); + + FhgfsOpsErr commRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + // remove the flag that we just added for secondary + unsetMsgHeaderFeatureFlag(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + if(unlikely( + (commRes == FhgfsOpsErr_COMMUNICATION) && + (rrTarget.outTargetReachabilityState == TargetReachabilityState_OFFLINE) ) ) + { + LOG_DEBUG(logContext, Log_DEBUG, std::string("Secondary is offline and will need resync. ") + + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) );; + return FhgfsOpsErr_SUCCESS; // go ahead with local msg processing + } + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_DEBUG, "Forwarding failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) + "; " + "error: " + boost::lexical_cast(commRes)); + + std::string genericRespStr = "Communication with secondary failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(genericRespStr))); + + return FhgfsOpsErr_COMMUNICATION; + } + + CloseChunkFileRespMsg* respMsg = (CloseChunkFileRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr secondaryRes = respMsg->getResult(); + if(unlikely(secondaryRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_NOTICE, std::string("Secondary reported error: ") + + boost::lexical_cast(secondaryRes) + "; " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + return secondaryRes; + } + + + return FhgfsOpsErr_SUCCESS; +} + +bool CloseChunkFileMsgEx::getDynamicAttribsByFD(const int fd, std::string fileHandleID, + uint16_t targetID, DynamicAttribs& outDynAttribs) +{ + SyncedStoragePaths* syncedPaths = Program::getApp()->getSyncedStoragePaths(); + + std::string fileID(SessionTk::fileIDFromHandleID(fileHandleID) ); + + uint64_t storageVersion = syncedPaths->lockPath(fileID, targetID); // LOCK + + // note: this is locked because we need to get the filesize together with the storageVersion + bool getDynAttribsRes = StorageTkEx::getDynamicFileAttribs(fd, &outDynAttribs.filesize, + &outDynAttribs.allocedBlocks, &outDynAttribs.modificationTimeSecs, + &outDynAttribs.lastAccessTimeSecs); + + if(getDynAttribsRes) + outDynAttribs.storageVersion = storageVersion; + + syncedPaths->unlockPath(fileID, targetID); // UNLOCK + + return getDynAttribsRes; +} + +bool CloseChunkFileMsgEx::getDynamicAttribsByPath(std::string fileHandleID, uint16_t targetID, + DynamicAttribs& outDynAttribs) +{ + const char* logContext = "CloseChunkFileMsg (attribs by path)"; + + App* app = Program::getApp(); + SyncedStoragePaths* syncedPaths = app->getSyncedStoragePaths(); + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { // unknown targetID + LogContext(logContext).logErr("Unknown targetID: " + StringTk::uintToStr(targetID) ); + return false; + } + + const int targetFD = isMsgHeaderFeatureFlagSet(CLOSECHUNKFILEMSG_FLAG_BUDDYMIRROR) + ? *target->getMirrorFD() + : *target->getChunkFD(); + + std::string fileID = SessionTk::fileIDFromHandleID(fileHandleID); + std::string pathStr = StorageTk::getFileChunkPath(getPathInfo(), fileID); + + uint64_t storageVersion = syncedPaths->lockPath(fileID, targetID); // L O C K path + + // note: this is locked because we need to get the filesize together with the storageVersion + bool getDynAttribsRes = StorageTkEx::getDynamicFileAttribs(targetFD, pathStr.c_str(), + &outDynAttribs.filesize, &outDynAttribs.allocedBlocks, &outDynAttribs.modificationTimeSecs, + &outDynAttribs.lastAccessTimeSecs); + + if(getDynAttribsRes) + outDynAttribs.storageVersion = storageVersion; + + syncedPaths->unlockPath(fileID, targetID); // U N L O C K path + + return getDynAttribsRes; +} diff --git a/storage/source/net/message/session/opening/CloseChunkFileMsgEx.h b/storage/source/net/message/session/opening/CloseChunkFileMsgEx.h new file mode 100644 index 0000000..c09e153 --- /dev/null +++ b/storage/source/net/message/session/opening/CloseChunkFileMsgEx.h @@ -0,0 +1,29 @@ +#pragma once + +#include + +class CloseChunkFileMsgEx : public CloseChunkFileMsg +{ + private: + struct DynamicAttribs + { + int64_t filesize; + int64_t allocedBlocks; // allocated 512byte blocks (relevant for sparse files) + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; + }; + + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr forwardToSecondary(ResponseContext& ctx); + bool getDynamicAttribsByFD(int fd, std::string fileHandleID, uint16_t targetID, + DynamicAttribs& outDynAttribs); + bool getDynamicAttribsByPath(std::string fileHandleID, uint16_t targetID, + DynamicAttribs& outDynAttribs); + + std::pair close(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/session/rw/ReadLocalFileRDMAMsgEx.h b/storage/source/net/message/session/rw/ReadLocalFileRDMAMsgEx.h new file mode 100644 index 0000000..87da265 --- /dev/null +++ b/storage/source/net/message/session/rw/ReadLocalFileRDMAMsgEx.h @@ -0,0 +1,114 @@ +#pragma once + +#ifdef BEEGFS_NVFS +#include +#include +#include +#include +#include +#include +#include "ReadLocalFileV2MsgEx.h" + +/** + * Implements RDMA write protocol. + */ +class ReadLocalFileRDMAMsgSender : public ReadLocalFileRDMAMsg +{ + public: + struct ReadState : public ReadStateBase + { + RdmaInfo* rdma; + uint64_t rBuf; + size_t rLen; + uint64_t rOff; + + ReadState(const char* logContext, uint64_t toBeRead, + SessionLocalFile* sessionLocalFile) : + ReadStateBase(logContext, toBeRead, sessionLocalFile) {} + + }; + + private: + friend class ReadLocalFileMsgExBase; + + static std::string logContextPref; + + inline void sendLengthInfo(Socket* sock, int64_t lengthInfo) + { + lengthInfo = HOST_TO_LE_64(lengthInfo); + sock->send(&lengthInfo, sizeof(int64_t), 0); + } + + /** + * RDMA write data to the remote buffer. + */ + inline ssize_t readStateSendData(Socket* sock, ReadState& rs, char* buf, bool isFinal) + { + ssize_t writeRes = sock->write(buf, rs.readRes, 0, rs.rBuf + rs.rOff, rs.rdma->key); + LOG_DEBUG(rs.logContext, Log_DEBUG, + "buf: " + StringTk::uint64ToHexStr((uint64_t)buf) + "; " + "bufLen: " + StringTk::int64ToStr(rs.readRes) + "; " + "rbuf: " + StringTk::uint64ToHexStr(rs.rBuf) + "; " + "rkey: " + StringTk::uintToHexStr(rs.rdma->key) + "; " + "writeRes: " + StringTk::int64ToStr(writeRes)); + + if (unlikely(writeRes != rs.readRes)) + { + LogContext(rs.logContext).logErr("Unable to write file data to client. " + "FileID: " + rs.sessionLocalFile->getFileID() + "; " + "SysErr: " + System::getErrString()); + writeRes = -1; + } + + if (isFinal && likely(writeRes >= 0)) + sendLengthInfo(sock, getCount() - rs.toBeRead); + + return writeRes; + } + + inline ssize_t getReadLength(ReadState& rs, ssize_t len) + { + // Cannot RDMA anything larger than WORKER_BUFOUT_SIZE in a single operation + // because that is the size of the buffer passed in by the Worker. + // TODO: pass around a Buffer with a length instead of unqualified char*. + return BEEGFS_MIN(BEEGFS_MIN(len, ssize_t(rs.rLen - rs.rOff)), WORKER_BUFOUT_SIZE); + } + + inline bool readStateInit(ReadState& rs) + { + rs.rdma = getRdmaInfo(); + if (unlikely(!rs.rdma->next(rs.rBuf, rs.rLen, rs.rOff))) + { + LogContext(rs.logContext).logErr("No entities in RDMA buffers."); + return false; + } + return true; + } + + inline bool readStateNext(ReadState& rs) + { + rs.rOff += rs.readRes; + if (rs.rOff == rs.rLen) + { + if (unlikely(!rs.rdma->next(rs.rBuf, rs.rLen, rs.rOff))) + { + LogContext(rs.logContext).logErr("RDMA buffers exhausted"); + return false; + } + } + return true; + } + + inline size_t getBuffers(ResponseContext& ctx, char** dataBuf, char** sendBuf) + { + *dataBuf = ctx.getBuffer(); + *sendBuf = *dataBuf; + return ctx.getBufferLength(); + } +}; + +typedef ReadLocalFileMsgExBase ReadLocalFileRDMAMsgEx; + +#endif /* BEEGFS_NVFS */ + diff --git a/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.cpp b/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.cpp new file mode 100644 index 0000000..bdddd60 --- /dev/null +++ b/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.cpp @@ -0,0 +1,466 @@ +#include +#include +#include +#include +#include +#include "ReadLocalFileV2MsgEx.h" +#ifdef BEEGFS_NVFS +#include "ReadLocalFileRDMAMsgEx.h" +#endif +#include +#include + +#define READ_USE_TUNEFILEREAD_TRIGGER (4*1024*1024) /* seq IO trigger for tuneFileReadSize */ + +#define READ_BUF_OFFSET_PROTO_MIN (sizeof(int64_t) ) /* for prepended length info */ +#define READ_BUF_END_PROTO_MIN (sizeof(int64_t) ) /* for appended length info */ + + +/* reserve more than necessary at buf start to achieve page cache alignment */ +const size_t READ_BUF_OFFSET = + BEEGFS_MAX( (long)READ_BUF_OFFSET_PROTO_MIN, sysconf(_SC_PAGESIZE) ); +/* reserve more than necessary at buf end to achieve page cache alignment */ +const size_t READ_BUF_END_RESERVE = + BEEGFS_MAX( (long)READ_BUF_END_PROTO_MIN, sysconf(_SC_PAGESIZE) ); +/* read buffer size cutoff for protocol data */ +const size_t READ_BUF_LEN_PROTOCOL_CUTOFF = + READ_BUF_OFFSET + READ_BUF_END_RESERVE; + + +// A linker error occurs for processIncoming without having this forced linkage. +static ReadLocalFileV2MsgEx forcedLinkageV2; +#ifdef BEEGFS_NVFS +static ReadLocalFileRDMAMsgEx forcedLinkageRDMA; +#endif + +std::string ReadLocalFileV2MsgSender::logContextPref = "ReadChunkFileV2Msg"; +#ifdef BEEGFS_NVFS +std::string ReadLocalFileRDMAMsgSender::logContextPref = "ReadChunkFileRDMAMsg"; +#endif + +template +bool ReadLocalFileMsgExBase::processIncoming(NetMessage::ResponseContext& ctx) +{ + std::string logContext = Msg::logContextPref + " incoming"; + + bool retVal = true; // return value + + int64_t readRes = 0; + + std::string fileHandleID(getFileHandleID() ); + bool isMirrorSession = isMsgHeaderFeatureFlagSet(READLOCALFILEMSG_FLAG_BUDDYMIRROR); + + // do session check only when it is not a mirror session + bool useSessionCheck = isMirrorSession ? false : + isMsgHeaderFeatureFlagSet(READLOCALFILEMSG_FLAG_SESSION_CHECK); + + App* app = Program::getApp(); + SessionStore* sessions = app->getSessions(); + auto session = sessions->referenceOrAddSession(getClientNumID()); + this->sessionLocalFiles = session->getLocalFiles(); + + // select the right targetID + + uint16_t targetID = getTargetID(); + + if(isMirrorSession ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(READLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + // note: only log message here, error handling will happen below through invalid targetFD + if(unlikely(!targetID) ) + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + } + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + if (isMirrorSession) + { /* buddy mirrored file => fail with Err_COMMUNICATION to make the requestor retry. + mgmt will mark this target as (p)offline in a few moments. */ + LOG(GENERAL, NOTICE, "Unknown target ID, refusing request.", targetID); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_COMMUNICATION); + return true; + } + + LOG(GENERAL, ERR, "Unknown target ID.", targetID); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_UNKNOWNTARGET); + return true; + } + + // check if we already have a session for this file... + + auto sessionLocalFile = sessionLocalFiles->referenceSession( + fileHandleID, targetID, isMirrorSession); + if(!sessionLocalFile) + { // sessionLocalFile not exists yet => create, insert, re-get it + if(useSessionCheck) + { // server crashed during the write, maybe lost some data send error to client + LogContext log(logContext); + log.log(Log_WARNING, "Potential cache loss for open file handle. (Server crash detected.) " + "No session for file available. " + "FileHandleID: " + fileHandleID); + + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_STORAGE_SRV_CRASHED); + goto release_session; + } + + std::string fileID = SessionTk::fileIDFromHandleID(fileHandleID); + int openFlags = SessionTk::sysOpenFlagsFromFhgfsAccessFlags(getAccessFlags() ); + + auto newFile = boost::make_unique(fileHandleID, targetID, fileID, openFlags, + false); + + if(isMirrorSession) + newFile->setIsMirrorSession(true); + + sessionLocalFile = sessionLocalFiles->addAndReferenceSession(std::move(newFile)); + } + else + { // session file exists + if(useSessionCheck && sessionLocalFile->isServerCrashed() ) + { // server crashed during the write, maybe lost some data send error to client + LogContext log(logContext); + log.log(Log_SPAM, "Potential cache loss for open file handle. (Server crash detected.) " + "The session is marked as dirty. " + "FileHandleID: " + fileHandleID); + + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_STORAGE_SRV_CRASHED); + goto release_session; + } + } + + /* Note: the session file must be unlocked/released before we send the finalizing info, + because otherwise we have a race when the client assumes the read is complete and tries + to close the file (while the handle is actually still referenced on the server). */ + /* Note: we also must be careful to update the current offset before sending the final length + info because otherwise the session file might have been released already and we have no + longer access to the offset. */ + + readRes = -1; + try + { + // prepare file descriptor (if file not open yet then open it if it exists already) + FhgfsOpsErr openRes = openFile(*target, sessionLocalFile.get()); + if(openRes != FhgfsOpsErr_SUCCESS) + { + sendLengthInfo(ctx.getSocket(), -openRes); + goto release_session; + } + + // check if file exists + if(!sessionLocalFile->getFD().valid()) + { // file didn't exist (not an error) => send EOF + sendLengthInfo(ctx.getSocket(), 0); + goto release_session; + } + + // the actual read workhorse... + + readRes = incrementalReadStatefulAndSendV2(ctx, sessionLocalFile.get()); + + LOG_DEBUG(logContext, Log_SPAM, "sending completed. " + "readRes: " + StringTk::int64ToStr(readRes) ); + IGNORE_UNUSED_VARIABLE(readRes); + + } + catch(SocketException& e) + { + LogContext(logContext).logErr(std::string("SocketException occurred: ") + e.what() ); + LogContext(logContext).log(Log_WARNING, "Details: " + "sessionID: " + getClientNumID().str() + "; " + "fileHandle: " + fileHandleID + "; " + "offset: " + StringTk::int64ToStr(getOffset() ) + "; " + "count: " + StringTk::int64ToStr(getCount() ) ); + + sessionLocalFile->setOffset(-1); /* invalidate offset (we can only do this if still locked, + but that's not a prob if we update offset correctly before send - see notes above) */ + + retVal = false; + goto release_session; + } + +release_session: + + // update operation counters + + if(likely(readRes > 0) ) + app->getNodeOpStats()->updateNodeOp( + ctx.getSocket()->getPeerIP(), StorageOpCounter_READOPS, readRes, getMsgHeaderUserID() ); + + return retVal; +} + +inline size_t ReadLocalFileV2MsgSender::getBuffers(ResponseContext& ctx, char** dataBuf, char** sendBuf) +{ + *dataBuf = ctx.getBuffer() + READ_BUF_OFFSET; // offset for prepended data length info + *sendBuf = *dataBuf - READ_BUF_OFFSET_PROTO_MIN; + return ctx.getBufferLength() - READ_BUF_LEN_PROTOCOL_CUTOFF; /* cutoff for + prepended and finalizing length info */ +} + +/** + * Note: This is similar to incrementalReadAndSend, but uses the offset from sessionLocalFile + * to avoid calling seek every time. + * + * Warning: Do not use the returned value to set the new offset, as there might be other threads + * that also did something with the file (i.e. the io-lock is released somewhere within this + * method). + * + * @return number of bytes read or some arbitrary negative value otherwise + */ +template +int64_t ReadLocalFileMsgExBase::incrementalReadStatefulAndSendV2(NetMessage::ResponseContext& ctx, + SessionLocalFile* sessionLocalFile) +{ + /* note on session offset: the session offset must always be set before sending the data to the + client (otherwise the client could send the next request before we updated the offset, which + would lead to a race condition) */ + + std::string logContext = Msg::logContextPref + " (read incremental)"; + Config* cfg = Program::getApp()->getConfig(); + + char* dataBuf; + char* sendBuf; + + if (READ_BUF_LEN_PROTOCOL_CUTOFF >= ctx.getBufferLength()) + { // buffer too small. That shouldn't happen and is an error + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_INTERNAL); + return -1; + } + + const ssize_t dataBufLen = getBuffers(ctx, &dataBuf, &sendBuf); + + auto& fd = sessionLocalFile->getFD(); + int64_t oldOffset = sessionLocalFile->getOffset(); + int64_t newOffset = getOffset(); + + bool skipReadAhead = + unlikely(isMsgHeaderFeatureFlagSet(READLOCALFILEMSG_FLAG_DISABLE_IO) || + sessionLocalFile->getIsDirectIO()); + + ssize_t readAheadSize = skipReadAhead ? 0 : cfg->getTuneFileReadAheadSize(); + ssize_t readAheadTriggerSize = cfg->getTuneFileReadAheadTriggerSize(); + + if( (oldOffset < 0) || (oldOffset != newOffset) ) + { + sessionLocalFile->resetReadCounter(); // reset sequential read counter + sessionLocalFile->resetLastReadAheadTrigger(); + } + else + { // read continues at previous offset + LOG_DEBUG(logContext, Log_SPAM, + "fileID: " + sessionLocalFile->getFileID() + "; " + "offset: " + StringTk::int64ToStr(getOffset() ) ); + } + + size_t maxReadAtOnceLen = dataBufLen; + + // reduce maxReadAtOnceLen to achieve better read/send aync overlap + /* (note: reducing makes only sense if we can rely on the kernel to do some read-ahead, so don't + reduce for direct IO and for random IO) */ + if( (sessionLocalFile->getReadCounter() >= READ_USE_TUNEFILEREAD_TRIGGER) && + !sessionLocalFile->getIsDirectIO() ) + maxReadAtOnceLen = BEEGFS_MIN(dataBufLen, cfg->getTuneFileReadSize() ); + + off_t readOffset = getOffset(); + ReadState readState(logContext.c_str(), getCount(), sessionLocalFile); + + if (!isMsgValid() || !readStateInit(readState)) + { + LogContext(logContext).logErr("Invalid read message."); + sessionLocalFile->setOffset(-1); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_INVAL); + return -1; + } + + for( ; ; ) + { + ssize_t readLength = getReadLength(readState, BEEGFS_MIN(maxReadAtOnceLen, readState.toBeRead)); + + readState.readRes = unlikely(isMsgHeaderFeatureFlagSet(READLOCALFILEMSG_FLAG_DISABLE_IO) ) ? + readLength : MsgHelperIO::pread(*fd, dataBuf, readLength, readOffset); + + LOG_DEBUG(logContext, Log_SPAM, + "toBeRead: " + StringTk::int64ToStr(readState.toBeRead) + "; " + "readLength: " + StringTk::int64ToStr(readLength) + "; " + "readRes: " + StringTk::int64ToStr(readState.readRes) ); + + if(readState.readRes == readLength) + { // simple success case + readState.toBeRead -= readState.readRes; + + readOffset += readState.readRes; + + int64_t newOffset = getOffset() + getCount() - readState.toBeRead; + sessionLocalFile->setOffset(newOffset); // update offset + + sessionLocalFile->incReadCounter(readState.readRes); // update sequential read length + + ctx.getStats()->incVals.diskReadBytes += readState.readRes; // update stats + + bool isFinal = !readState.toBeRead; + + if (readStateSendData(ctx.getSocket(), readState, sendBuf, isFinal) < 0) + { + LogContext(logContext).logErr("readStateSendData failed."); + sessionLocalFile->setOffset(-1); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_COMMUNICATION); + return -1; + } + + checkAndStartReadAhead(sessionLocalFile, readAheadTriggerSize, newOffset, readAheadSize); + + if(isFinal) + { // we reached the end of the requested data + return getCount(); + } + + if (!readStateNext(readState)) + { + LogContext(logContext).logErr("readStateNext failed."); + sessionLocalFile->setOffset(-1); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_COMMUNICATION); + return -1; + } + } + else + { // readRes not as it should be => might be an error or just an end-of-file + + if(readState.readRes == -1) + { // read error occurred + LogContext(logContext).log(Log_WARNING, "Unable to read file data. " + "FileID: " + sessionLocalFile->getFileID() + "; " + "SysErr: " + System::getErrString() ); + + sessionLocalFile->setOffset(-1); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_INTERNAL); + return -1; + } + else + { // just an end of file + LOG_DEBUG(logContext, Log_DEBUG, + "Unable to read all of the requested data (=> end of file)"); + LOG_DEBUG(logContext, Log_DEBUG, + "offset: " + StringTk::int64ToStr(getOffset() ) + "; " + "count: " + StringTk::int64ToStr(getCount() ) + "; " + "readLength: " + StringTk::int64ToStr(readLength) + "; " + + "readRes: " + StringTk::int64ToStr(readState.readRes) + "; " + + "toBeRead: " + StringTk::int64ToStr(readState.toBeRead) ); + + readOffset += readState.readRes; + readState.toBeRead -= readState.readRes; + + sessionLocalFile->setOffset(getOffset() + getCount() - readState.toBeRead); // update offset + + sessionLocalFile->incReadCounter(readState.readRes); // update sequential read length + + ctx.getStats()->incVals.diskReadBytes += readState.readRes; // update stats + + if(readState.readRes > 0) + { + if (readStateSendData(ctx.getSocket(), readState, sendBuf, true) < 0) + { + LogContext(logContext).logErr("readStateSendData failed."); + sessionLocalFile->setOffset(-1); + sendLengthInfo(ctx.getSocket(), -FhgfsOpsErr_COMMUNICATION); + return -1; + } + } + else + sendLengthInfo(ctx.getSocket(), 0); + + return(getCount() - readState.toBeRead); + } + + } + + } // end of for-loop + +} + +/** + * Starts read-ahead if enough sequential data has been read. + * + * Note: if getDisableIO() is true, we assume the caller sets readAheadSize==0, so getDisableIO() + * is not checked explicitly within this function. + * + * @sessionLocalFile lastReadAheadOffset will be updated if read-head was triggered + * @param readAheadTriggerSize the length of sequential IO that triggers read-ahead + * @param currentOffset current file offset (where read-ahead would start) + */ +template +void ReadLocalFileMsgExBase::checkAndStartReadAhead(SessionLocalFile* sessionLocalFile, + ssize_t readAheadTriggerSize, off_t currentOffset, off_t readAheadSize) +{ + std::string logContext = Msg::logContextPref + " (read-ahead)"; + + if(!readAheadSize) + return; + + int64_t readCounter = sessionLocalFile->getReadCounter(); + int64_t nextReadAheadTrigger = sessionLocalFile->getLastReadAheadTrigger() ? + sessionLocalFile->getLastReadAheadTrigger() + readAheadSize : readAheadTriggerSize; + + if(readCounter < nextReadAheadTrigger) + return; // we're not at the trigger point yet + + /* start read-head... + (read-ahead is supposed to be non-blocking if there are free slots in the device IO queue) */ + + LOG_DEBUG(logContext, Log_SPAM, + std::string("Starting read-ahead... ") + + "offset: " + StringTk::int64ToStr(currentOffset) + "; " + "size: " + StringTk::int64ToStr(readAheadSize) ); + + MsgHelperIO::readAhead(*sessionLocalFile->getFD(), currentOffset, readAheadSize); + + // update trigger + + sessionLocalFile->setLastReadAheadTrigger(readCounter); +} + + +/** + * Open the file if a filedescriptor is not already set in sessionLocalFile. + * If the file needs to be opened, this method will check the target consistency state before + * opening. + * + * @return we return the special value FhgfsOpsErr_COMMUNICATION here in some cases to indirectly + * ask the client for a retry (e.g. if target consistency is not good for buddymirrored chunks). + */ +template +FhgfsOpsErr ReadLocalFileMsgExBase::openFile(const StorageTarget& target, + SessionLocalFile* sessionLocalFile) +{ + std::string logContext = Msg::logContextPref + " (open)"; + + bool isBuddyMirrorChunk = sessionLocalFile->getIsMirrorSession(); + + + if (sessionLocalFile->getFD().valid()) + return FhgfsOpsErr_SUCCESS; // file already open => nothing to be done here + + + // file not open yet => get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && isBuddyMirrorChunk) + { // this is a request for a buddymirrored chunk on a non-good target + LogContext(logContext).log(Log_NOTICE, "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID())); + + return FhgfsOpsErr_COMMUNICATION; + } + + FhgfsOpsErr openChunkRes = sessionLocalFile->openFile(targetFD, getPathInfo(), false, NULL); + + return openChunkRes; +} diff --git a/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.h b/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.h new file mode 100644 index 0000000..d2461b1 --- /dev/null +++ b/storage/source/net/message/session/rw/ReadLocalFileV2MsgEx.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include +#include + +class StorageTarget; + +/** + * Contains common data needed by implementations of the network protocol + * that send data to the client. + */ +struct ReadStateBase +{ + const char* logContext; + uint64_t toBeRead; + SessionLocalFile* sessionLocalFile; + ssize_t readRes; + + ReadStateBase(const char* logContext, uint64_t toBeRead, + SessionLocalFile* sessionLocalFile) + { + this->logContext = logContext; + this->toBeRead = toBeRead; + this->sessionLocalFile = sessionLocalFile; + } +}; + +template +class ReadLocalFileMsgExBase : public Msg +{ + public: + bool processIncoming(NetMessage::ResponseContext& ctx); + + private: + SessionLocalFileStore* sessionLocalFiles; + + FhgfsOpsErr openFile(const StorageTarget& target, SessionLocalFile* sessionLocalFile); + + void checkAndStartReadAhead(SessionLocalFile* sessionLocalFile, ssize_t readAheadTriggerSize, + off_t currentOffset, off_t readAheadSize); + + int64_t incrementalReadStatefulAndSendV2(NetMessage::ResponseContext& ctx, + SessionLocalFile* sessionLocalFile); + + inline void sendLengthInfo(Socket* sock, int64_t lengthInfo) + { + static_cast(*this).sendLengthInfo(sock, lengthInfo); + } + + inline bool readStateInit(ReadState& rs) + { + return static_cast(*this).readStateInit(rs); + } + + inline ssize_t readStateSendData(Socket* sock, ReadState& rs, char* buf, bool isFinal) + { + return static_cast(*this).readStateSendData(sock, rs, buf, isFinal); + } + + inline bool readStateNext(ReadState& rs) + { + return static_cast(*this).readStateNext(rs); + } + + inline ssize_t getReadLength(ReadState& rs, ssize_t len) + { + return static_cast(*this).getReadLength(rs, len); + } + + inline size_t getBuffers(NetMessage::ResponseContext& ctx, char** dataBuf, char** sendBuf) + { + return static_cast(*this).getBuffers(ctx, dataBuf, sendBuf); + } + + public: + inline unsigned getMsgHeaderUserID() const + { + return static_cast(*this).getMsgHeaderUserID(); + } + + inline bool isMsgHeaderFeatureFlagSet(unsigned flag) const + { + return static_cast(*this).isMsgHeaderFeatureFlagSet(flag); + } + + inline uint16_t getTargetID() const + { + return static_cast(*this).getTargetID(); + } + + inline int64_t getOffset() const + { + return static_cast(*this).getOffset(); + } + + inline int64_t getCount() const + { + return static_cast(*this).getCount(); + } + + inline const char* getFileHandleID() + { + return static_cast(*this).getFileHandleID(); + } + + inline NumNodeID getClientNumID() const + { + return static_cast(*this).getClientNumID(); + } + + inline unsigned getAccessFlags() const + { + return static_cast(*this).getAccessFlags(); + } + + inline PathInfo* getPathInfo () + { + return static_cast(*this).getPathInfo(); + } + + inline bool isMsgValid() const + { + return static_cast(*this).isMsgValid(); + } + +}; + +/** + * Implements the Version 2 send protocol. It uses a preceding length info for each chunk. + */ +class ReadLocalFileV2MsgSender : public ReadLocalFileV2Msg +{ + /* note on protocol: this works by sending an int64 before each data chunk, which contains the + length of the next data chunk; or a zero if no more data can be read; or a negative fhgfs + error code in case of an error */ + public: + struct ReadState : public ReadStateBase + { + ReadState(const char* logContext, uint64_t toBeRead, + SessionLocalFile* sessionLocalFile) : + ReadStateBase(logContext, toBeRead, sessionLocalFile) {} + }; + + private: + friend class ReadLocalFileMsgExBase; + + static std::string logContextPref; + + /** + * Send only length information without a data packet. Typically used for the final length + * info at the end of the requested data. + */ + inline void sendLengthInfo(Socket* sock, int64_t lengthInfo) + { + lengthInfo = HOST_TO_LE_64(lengthInfo); + sock->send(&lengthInfo, sizeof(int64_t), 0); + } + + /** + * No-op for this implementation. + */ + inline bool readStateInit(ReadState& rs) + { + return true; + } + + /** + * Send length information and the corresponding data packet buffer. + * + * Note: rs.readRes is used to compute buf length for send() + * + * @param rs.readRes must not be negative + * @param buf the buffer with a preceding gap for the length info + * @param isFinal true if this is the last send, i.e. we have read all data + */ + inline ssize_t readStateSendData(Socket* sock, ReadState& rs, char* buf, bool isFinal) + { + ssize_t sendRes; + { + Serializer ser(buf, sizeof(int64_t)); + ser % rs.readRes; + } + + if (isFinal) + { + Serializer ser(buf + sizeof(int64_t) + rs.readRes, sizeof(int64_t)); + ser % int64_t(0); + sendRes = sock->send(buf, (2*sizeof(int64_t) ) + rs.readRes, 0); + } + else + { + sendRes = sock->send(buf, sizeof(int64_t) + rs.readRes, 0); + } + return sendRes; + } + + /** + * No-op for this implementation. + */ + inline bool readStateNext(ReadState& rs) + { + return true; + } + + inline ssize_t getReadLength(ReadState& rs, ssize_t len) + { + return len; + } + + size_t getBuffers(ResponseContext& ctx, char** dataBuf, char** sendBuf); +}; + +typedef ReadLocalFileMsgExBase ReadLocalFileV2MsgEx; + diff --git a/storage/source/net/message/session/rw/WriteLocalFileMsgEx.cpp b/storage/source/net/message/session/rw/WriteLocalFileMsgEx.cpp new file mode 100644 index 0000000..47d7bd3 --- /dev/null +++ b/storage/source/net/message/session/rw/WriteLocalFileMsgEx.cpp @@ -0,0 +1,926 @@ +#include +#include +#include +#include +#include +#include +#include +#include "WriteLocalFileMsgEx.h" +#ifdef BEEGFS_NVFS +#include "WriteLocalFileRDMAMsgEx.h" +#endif + +#include + +static WriteLocalFileMsgEx forcedLinkage; +#ifdef BEEGFS_NVFS +static WriteLocalFileRDMAMsgEx forcedLinkageRDMA; +#endif + +const std::string WriteLocalFileMsgSender::logContextPref = "WriteChunkFileMsg"; +#ifdef BEEGFS_NVFS +const std::string WriteLocalFileRDMAMsgSender::logContextPref = "WriteChunkFileRDMAMsg"; +#endif + +template +bool WriteLocalFileMsgExBase::processIncoming(NetMessage::ResponseContext& ctx) +{ + App* app = Program::getApp(); + + bool success; + int64_t writeClientRes; + + if (!isMsgValid()) + { + sendResponse(ctx, FhgfsOpsErr_INVAL); + return false; + } + + std::tie(success, writeClientRes) = write(ctx); + + if (success) + { + sendResponse(ctx, writeClientRes); + + // update operation counters + + if (likely(writeClientRes > 0)) + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_WRITEOPS, writeClientRes, getMsgHeaderUserID()); + } + + return success; +} + +template +std::pair WriteLocalFileMsgExBase::write(NetMessage::ResponseContext& ctx) +{ + std::string logContext = Msg::logContextPref + " incoming"; + + App* app = Program::getApp(); + + int64_t writeClientRes = -(int64_t)FhgfsOpsErr_INTERNAL; // bytes written or negative fhgfs err + FhgfsOpsErr finishMirroringRes = FhgfsOpsErr_INTERNAL; + std::string fileHandleID(getFileHandleID() ); + bool isMirrorSession = isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR); + + bool serverCrashed = false; + QuotaExceededErrorType quotaExceeded = QuotaExceededErrorType_NOT_EXCEEDED; + + SessionStore* sessions = Program::getApp()->getSessions(); + auto session = sessions->referenceOrAddSession(getClientNumID()); + SessionLocalFileStore* sessionLocalFiles = session->getLocalFiles(); + + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + bool chunkLocked = false; + + // select the right targetID + + uint16_t targetID = getTargetID(); + + if(isMirrorSession) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + // note: only log message here, error handling will happen below through invalid targetFD + if(unlikely(!targetID) ) + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + } + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + if (isMirrorSession) + { /* buddy mirrored file => fail with Err_COMMUNICATION to make the requestor retry. + mgmt will mark this target as (p)offline in a few moments. */ + LOG(GENERAL, NOTICE, "Unknown target ID, refusing request.", targetID); + return {false, FhgfsOpsErr_COMMUNICATION}; + } + + LOG(GENERAL, ERR, "Unknown target ID.", targetID); + return {false, FhgfsOpsErr_UNKNOWNTARGET}; + } + + // check if we already have session for this file... + + auto sessionLocalFile = sessionLocalFiles->referenceSession( + fileHandleID, targetID, isMirrorSession); + + if(!sessionLocalFile) + { // sessionLocalFile not exists yet => create, insert, re-get it + + if(doSessionCheck() ) + { // server crashed during the write, maybe lost some data send error to client + LogContext log(logContext); + log.log(Log_WARNING, "Potential cache loss for open file handle. (Server crash detected.) " + "No session for file available. " + "FileHandleID: " + fileHandleID); + + serverCrashed = true; + } + + std::string fileID = SessionTk::fileIDFromHandleID(fileHandleID); + int openFlags = SessionTk::sysOpenFlagsFromFhgfsAccessFlags(getAccessFlags() ); + + auto newFile = boost::make_unique(fileHandleID, targetID, fileID, openFlags, + serverCrashed); + + if(isMirrorSession) + newFile->setIsMirrorSession(true); + + sessionLocalFile = sessionLocalFiles->addAndReferenceSession(std::move(newFile)); + } + else + { // session file exists + + if(doSessionCheck() && sessionLocalFile->isServerCrashed() ) + { // server crashed during the write, maybe lost some data send error to client + LogContext log(logContext); + log.log(Log_SPAM, "Potential cache loss for open file handle. (Server crash detected.)" + "The session is marked as dirty. " + "FileHandleID: " + fileHandleID); + + serverCrashed = true; + } + } + + // check if the size quota is exceeded for the user or group + if(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_USE_QUOTA) && + app->getConfig()->getQuotaEnableEnforcement() ) + { + quotaExceeded = app->getExceededQuotaStores()->get(targetID)->isQuotaExceeded(getUserID(), + getGroupID(), QuotaLimitType_SIZE); + + if(quotaExceeded != QuotaExceededErrorType_NOT_EXCEEDED) + { + LogContext(logContext).log(Log_NOTICE, + QuotaData::QuotaExceededErrorTypeToString(quotaExceeded) + " " + "UID: " + StringTk::uintToStr(this->getUserID()) + "; " + "GID: " + StringTk::uintToStr(this->getGroupID() ) ); + + // receive the message content before return with error + incrementalRecvPadding(ctx, getCount(), sessionLocalFile.get()); + writeClientRes = -(int64_t) FhgfsOpsErr_DQUOT; + goto cleanup; + } + } + + try + { + if(isMirrorSession && target->getBuddyResyncInProgress()) + { + // mirrored chunk should be modified, check if resync is in progress and lock chunk + std::string chunkID = sessionLocalFile->getFileID(); + chunkLockStore->lockChunk(targetID, chunkID); + chunkLocked = true; + } + + // prepare file descriptor (if file not open yet then create/open it) + FhgfsOpsErr openRes = openFile(*target, sessionLocalFile.get()); + if(unlikely(openRes != FhgfsOpsErr_SUCCESS) ) + { + incrementalRecvPadding(ctx, getCount(), sessionLocalFile.get()); + writeClientRes = -(int64_t)openRes; + goto cleanup; + } + + // store mirror node reference in session and init mirrorToSock member + FhgfsOpsErr prepMirrorRes = prepareMirroring(ctx.getBuffer(), ctx.getBufferLength(), + sessionLocalFile.get(), *target); + if(unlikely(prepMirrorRes != FhgfsOpsErr_SUCCESS) ) + { // mirroring failed + incrementalRecvPadding(ctx, getCount(), sessionLocalFile.get()); + writeClientRes = -(int64_t)prepMirrorRes; + goto cleanup; + } + + + // the actual write workhorse + + int64_t writeLocalRes = incrementalRecvAndWriteStateful(ctx, sessionLocalFile.get()); + + // update client result, offset etc. + + int64_t newOffset; + + if(unlikely(writeLocalRes < 0) ) + newOffset = -1; // writing failed + else + { // writing succeeded + newOffset = getOffset() + writeLocalRes; + ctx.getStats()->incVals.diskWriteBytes += writeLocalRes; // update stats + } + + sessionLocalFile->setOffset(newOffset); + + writeClientRes = writeLocalRes; + + } + catch(SocketException& e) + { + LogContext(logContext).logErr(std::string("SocketException occurred: ") + e.what() ); + LogContext(logContext).log(Log_WARNING, std::string("Details: ") + + "sessionID: " + getClientNumID().str() + "; " + "fileHandle: " + std::string(sessionLocalFile->getFileHandleID() ) + "; " + "offset: " + StringTk::int64ToStr(getOffset() ) + "; " + "count: " + StringTk::int64ToStr(getCount() ) ); + + sessionLocalFile->setOffset(-1); // invalidate offset + + finishMirroring(sessionLocalFile.get(), *target); + + if (chunkLocked) + { + std::string chunkID = sessionLocalFile->getFileID(); + chunkLockStore->unlockChunk(targetID, chunkID); + } + + return {false, -1}; + } + + +cleanup: + finishMirroringRes = finishMirroring(sessionLocalFile.get(), *target); + + // check mirroring result (don't overwrite local error code, if any) + if(likely(writeClientRes > 0) ) + { // no local error => check mirroring result + if(unlikely(finishMirroringRes != FhgfsOpsErr_SUCCESS) ) + writeClientRes = -finishMirroringRes; // mirroring failed => use err code as client result + } + + if (chunkLocked) + { + std::string chunkID = sessionLocalFile->getFileID(); + chunkLockStore->unlockChunk(targetID, chunkID); + } + + if (serverCrashed) + writeClientRes = -(int64_t) FhgfsOpsErr_STORAGE_SRV_CRASHED; + + return {true, writeClientRes}; +} + +ssize_t WriteLocalFileMsgSender::recvPadding(ResponseContext& ctx, int64_t toBeReceived) +{ + Config* cfg = Program::getApp()->getConfig(); + return ctx.getSocket()->recvT(ctx.getBuffer(), + BEEGFS_MIN(toBeReceived, ctx.getBufferLength()), 0, cfg->getConnMsgMediumTimeout()); +} + +#ifdef BEEGFS_NVFS + +ssize_t WriteLocalFileRDMAMsgSender::recvPadding(ResponseContext& ctx, int64_t toBeReceived) +{ + RdmaInfo* rdma = getRdmaInfo(); + uint64_t rBuf; + size_t rLen; + uint64_t rOff; + + if (!rdma->next(rBuf, rLen, rOff)) + return -1; + + ssize_t recvLength = BEEGFS_MIN(ctx.getBufferLength(), toBeReceived); + recvLength = BEEGFS_MIN(recvLength, (ssize_t)(rLen - rOff)); + return ctx.getSocket()->read(ctx.getBuffer(), recvLength, 0, rBuf+rOff, rdma->key); +} + +#endif /* BEEGFS_NVFS */ + +/** + * Note: New offset is saved in the session by the caller afterwards (to make life easier). + * @return number of written bytes or negative fhgfs error code + */ +template +int64_t WriteLocalFileMsgExBase::incrementalRecvAndWriteStateful(NetMessage::ResponseContext& ctx, + SessionLocalFile* sessionLocalFile) +{ + std::string logContext = Msg::logContextPref + " (write incremental)"; + Config* cfg = Program::getApp()->getConfig(); + + // we can securely cast getTuneFileWriteSize to size_t below to make a comparision possible, as + // it can technically never be negative and will therefore always fit into size_t + const ssize_t exactStaticRecvSize = sessionLocalFile->getIsDirectIO() + ? ctx.getBufferLength() + : BEEGFS_MIN(ctx.getBufferLength(), (size_t)cfg->getTuneFileWriteSize() ); + + auto& fd = sessionLocalFile->getFD(); + + int64_t oldOffset = sessionLocalFile->getOffset(); + int64_t newOffset = getOffset(); + bool useSyncRange = false; // true if sync_file_range should be called + + if( (oldOffset < 0) || (oldOffset != newOffset) ) + sessionLocalFile->resetWriteCounter(); // reset sequential write counter + else + { // continue at previous offset => increase sequential write counter + LOG_DEBUG(logContext, Log_SPAM, "Offset: " + StringTk::int64ToStr(getOffset() ) ); + + sessionLocalFile->incWriteCounter(getCount() ); + + ssize_t syncSize = unlikely(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_DISABLE_IO) ) ? + 0 : cfg->getTuneFileWriteSyncSize(); + if (syncSize && (sessionLocalFile->getWriteCounter() >= syncSize) ) + useSyncRange = true; + } + + // incrementally receive file contents... + + WriteState writeState(logContext.c_str(), exactStaticRecvSize, + getCount(), getOffset(), sessionLocalFile); + if (!writeStateInit(writeState)) + return -FhgfsOpsErr_COMMUNICATION; + + do + { + // receive some bytes... + + LOG_DEBUG(logContext, Log_SPAM, + "receiving... (remaining: " + StringTk::intToStr(writeState.toBeReceived) + ")"); + + ssize_t recvRes = writeStateRecvData(ctx, writeState); + if (recvRes < 0) + { + LogContext(logContext).log(Log_WARNING, "Socket data transfer error occurred. "); + return -FhgfsOpsErr_COMMUNICATION; + } + + // forward to mirror... + + FhgfsOpsErr mirrorRes = sendToMirror(ctx.getBuffer(), recvRes, + writeState.writeOffset, writeState.toBeReceived, sessionLocalFile); + if(unlikely(mirrorRes != FhgfsOpsErr_SUCCESS) ) + { // mirroring failed + incrementalRecvPadding(ctx, writeState.toBeReceived, sessionLocalFile); + + return -FhgfsOpsErr_COMMUNICATION; + } + + // write to underlying file system... + + int errCode = 0; + ssize_t writeRes = unlikely(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_DISABLE_IO) ) + ? recvRes + : doWrite(*fd, ctx.getBuffer(), recvRes, writeState.writeOffset, errCode); + + writeState.toBeReceived -= recvRes; + + // handle write errors... + + if(unlikely(writeRes != recvRes) ) + { // didn't write all of the received data + + if(writeRes == -1) + { // write error occurred + LogContext(logContext).log(Log_WARNING, "Write error occurred. " + "FileHandleID: " + sessionLocalFile->getFileHandleID() + "." + "Target: " + StringTk::uintToStr(sessionLocalFile->getTargetID() ) + ". " + "File: " + sessionLocalFile->getFileID() + ". " + "SysErr: " + System::getErrString(errCode) ); + LogContext(logContext).log(Log_NOTICE, std::string("Additional info: " + "FD: ") + StringTk::intToStr(*fd) + " " + + "OpenFlags: " + StringTk::intToStr(sessionLocalFile->getOpenFlags() ) + " " + + "received: " + StringTk::intToStr(recvRes) + "."); + + incrementalRecvPadding(ctx, writeState.toBeReceived, sessionLocalFile); + + return -FhgfsOpsErrTk::fromSysErr(errCode); + } + else + { // wrote only a part of the data, not all of it + LogContext(logContext).log(Log_WARNING, + "Unable to write all of the received data. " + "target: " + StringTk::uintToStr(sessionLocalFile->getTargetID() ) + "; " + "file: " + sessionLocalFile->getFileID() + "; " + "sysErr: " + System::getErrString(errCode) ); + + incrementalRecvPadding(ctx, writeState.toBeReceived, sessionLocalFile); + + // return bytes received so far minus num bytes that were not written with last write + return (getCount() - writeState.toBeReceived) - (recvRes - writeRes); + } + + } + + writeState.writeOffset += writeRes; + recvRes = writeStateNext(writeState, writeRes); + if (recvRes != 0) + return recvRes; + } while(writeState.toBeReceived); + + LOG_DEBUG(logContext, Log_SPAM, + std::string("Received and wrote all the data") ); + + // commit to storage device queue... + + if (useSyncRange) + { + // advise kernel to commit written data to storage device in max_sectors_kb chunks. + + /* note: this is async if there are free slots in the request queue + /sys/block/<...>/nr_requests. (optimal_io_size is not honoured as of linux-3.4) */ + + off64_t syncSize = sessionLocalFile->getWriteCounter(); + off64_t syncOffset = getOffset() + getCount() - syncSize; + + MsgHelperIO::syncFileRange(*fd, syncOffset, syncSize); + sessionLocalFile->resetWriteCounter(); + } + + return getCount(); +} + +/** + * Write until everything was written (handle short-writes) or an error occured + */ +template +ssize_t WriteLocalFileMsgExBase::doWrite(int fd, char* buf, size_t count, off_t offset, int& outErrno) +{ + size_t sumWriteRes = 0; + + do + { + ssize_t writeRes = + MsgHelperIO::pwrite(fd, buf + sumWriteRes, count - sumWriteRes, offset + sumWriteRes); + + if (unlikely(writeRes == -1) ) + { + sumWriteRes = (sumWriteRes > 0) ? sumWriteRes : writeRes; + outErrno = errno; + break; + } + + sumWriteRes += writeRes; + + } while (sumWriteRes != count); + + return sumWriteRes; +} + +/** + * Receive and discard data. + */ +template +void WriteLocalFileMsgExBase::incrementalRecvPadding(NetMessage::ResponseContext& ctx, + int64_t padLen, SessionLocalFile* sessionLocalFile) +{ + uint64_t toBeReceived = padLen; + + while(toBeReceived) + { + ssize_t recvRes = recvPadding(ctx, toBeReceived); + if (recvRes == -1) + break; + // forward to mirror... + + FhgfsOpsErr mirrorRes = sendToMirror(ctx.getBuffer(), recvRes, + getOffset() + padLen - toBeReceived, toBeReceived, sessionLocalFile); + if(unlikely(mirrorRes != FhgfsOpsErr_SUCCESS) ) + { // mirroring failed + /* ... but if we are in this method, then something went wrong anyways, so don't set + needs-resync here or report any error to caller. */ + } + + toBeReceived -= recvRes; + } +} + +template +FhgfsOpsErr WriteLocalFileMsgExBase::openFile(const StorageTarget& target, + SessionLocalFile* sessionLocalFile) +{ + std::string logContext = Msg::logContextPref + " (write incremental)"; + + bool useQuota = isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_USE_QUOTA); + bool enforceQuota = Program::getApp()->getConfig()->getQuotaEnableEnforcement(); + + bool isBuddyMirrorChunk = sessionLocalFile->getIsMirrorSession(); + + + if (sessionLocalFile->getFD().valid()) + return FhgfsOpsErr_SUCCESS; // file already open => nothing to be done here + + + // file not open yet => get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && + isBuddyMirrorChunk && + !isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + { // this is a request for a buddymirrored chunk on a non-good primary + LogContext(logContext).log(Log_NOTICE, "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID())); + + return FhgfsOpsErr_COMMUNICATION; + } + + SessionQuotaInfo quotaInfo(useQuota, enforceQuota, getUserID(), getGroupID() ); + + FhgfsOpsErr openChunkRes = sessionLocalFile->openFile(targetFD, getPathInfo(), true, "aInfo); + + return openChunkRes; +} + + +/** + * Prepares mirroring by storing mirrorNode reference in file session and setting the mirrorToSock + * member variable. + * + * Note: Mirror node reference needs to be released on file session close. + * + * @param buf used to send initial write msg header to mirror. + * @param requestorSock used to receive padding if mirroring fails. + * @return FhgfsOpsErr_COMMUNICATION if communication with mirror failed. + */ +template +FhgfsOpsErr WriteLocalFileMsgExBase::prepareMirroring(char* buf, size_t bufLen, + SessionLocalFile* sessionLocalFile, StorageTarget& target) +{ + std::string logContext = Msg::logContextPref + " (prepare mirroring)"; + + // check if mirroring is enabled + + if(!isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_FORWARD) ) + return FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + TargetStateStore* targetStates = app->getTargetStateStore(); + + // check if secondary is offline or in unclear state + + uint16_t secondaryTargetID = mirrorBuddies->getSecondaryTargetID(getTargetID() ); + if(unlikely(!secondaryTargetID) ) + { + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + + return FhgfsOpsErr_UNKNOWNTARGET; + } + + CombinedTargetState secondaryState; + + bool getSecondaryStateRes = targetStates->getState(secondaryTargetID, secondaryState); + if(unlikely(!getSecondaryStateRes) ) + { + LOG_DEBUG(logContext, Log_DEBUG, + "Refusing request. Secondary target has invalid state. " + "targetID: " + StringTk::uintToStr(secondaryTargetID) ); + return FhgfsOpsErr_COMMUNICATION; + } + + if( (secondaryState.reachabilityState != TargetReachabilityState_ONLINE) || + (secondaryState.consistencyState != TargetConsistencyState_GOOD) ) + { + if(secondaryState.reachabilityState == TargetReachabilityState_OFFLINE) + { // buddy is offline => mark needed resync and continue with local operation + LOG_DEBUG(logContext, Log_DEBUG, + "Secondary is offline and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + // buddy is marked offline, so local msg processing will be done and buddy needs resync + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + if(secondaryState.consistencyState != TargetConsistencyState_NEEDS_RESYNC) + { // unclear buddy state => client must try again + LOG_DEBUG(logContext, Log_DEBUG, + "Unclear secondary state, caller will have to try again later. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + return FhgfsOpsErr_COMMUNICATION; + } + } + + + // store mirror node reference in session... + + NodeHandle mirrorToNode = sessionLocalFile->getMirrorNode(); + + if(!mirrorToNode) + { + NodeStoreServers* storageNodes = app->getStorageNodes(); + TargetMapper* targetMapper = app->getTargetMapper(); + FhgfsOpsErr referenceErr; + + mirrorToNode = storageNodes->referenceNodeByTargetID(secondaryTargetID, targetMapper, + &referenceErr); + + if(unlikely(referenceErr != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).logErr( + "Unable to forward to mirror target: " + StringTk::uintToStr(secondaryTargetID) + "; " + "Error: " + boost::lexical_cast(referenceErr)); + return referenceErr; + } + + mirrorToNode = sessionLocalFile->setMirrorNodeExclusive(mirrorToNode); + } + + // send initial write msg header to mirror (retry loop)... + + for( ; ; ) + { + try + { + // acquire connection to mirror node and send write msg... + + mirrorToSock = mirrorToNode->getConnPool()->acquireStreamSocket(); + + WriteLocalFileMsg mirrorWriteMsg(getClientNumID(), getFileHandleID(), getTargetID(), + getPathInfo(), getAccessFlags(), getOffset(), getCount()); + + if(doSessionCheck() ) + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_SESSION_CHECK); + + if(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_DISABLE_IO) ) + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_DISABLE_IO); + + if(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_USE_QUOTA) ) + mirrorWriteMsg.setUserdataForQuota(getUserID(), getGroupID() ); + + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR); + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + unsigned msgLength = mirrorWriteMsg.serializeMessage(buf, bufLen).second; + mirrorToSock->send(buf, msgLength, 0); + + return FhgfsOpsErr_SUCCESS; + } + catch(SocketConnectException& e) + { + LogContext(logContext).log(Log_CRITICAL, "Unable to connect to mirror node: " + + mirrorToNode->getNodeIDWithTypeStr() + "; " + "Msg: " + e.what() ); + } + catch(SocketException& e) + { + LogContext(logContext).log(Log_CRITICAL, "Communication with mirror node failed: " + + mirrorToNode->getNodeIDWithTypeStr() + "; " + "Msg: " + e.what() ); + + if(mirrorToSock) + mirrorToNode->getConnPool()->invalidateStreamSocket(mirrorToSock); + + mirrorToSock = NULL; + } + + // error occurred if we got here + + if(!mirrorRetriesLeft) + break; + + mirrorRetriesLeft--; + + // next round will be a retry + LogContext(logContext).log(Log_NOTICE, "Retrying mirror communication: " + + mirrorToNode->getNodeIDWithTypeStr() ); + + } // end of retry for-loop + + + // all retries exhausted if we got here + + return FhgfsOpsErr_COMMUNICATION; +} + +/** + * Send file contents to mirror. + * + * Note: Supports retries only at beginning of write msg. + * + * @param buf the buffer that should be sent to the mirror. + * @param offset the offset within the chunk file (only used if communication fails and we need to + * start over with a new WriteMsg to the mirror). + * @param toBeMirrored total remaining mirror data including given bufLen (only used for retries). + * @return FhgfsOpsErr_COMMUNICATION if mirroring fails. + */ +template +FhgfsOpsErr WriteLocalFileMsgExBase::sendToMirror(const char* buf, size_t bufLen, + int64_t offset, int64_t toBeMirrored, SessionLocalFile* sessionLocalFile) +{ + std::string logContext = Msg::logContextPref + " (send to mirror)"; + + // check if mirroring enabled + + if(!mirrorToSock) + return FhgfsOpsErr_SUCCESS; // either no mirroring enabled or all retries exhausted + + bool isRetryRound = false; + + // send raw data (retry loop)... + // (note: if sending fails, retrying requires sending of a new WriteMsg) + + for( ; ; ) + { + try + { + if(unlikely(isRetryRound) ) + { // retry requires reconnect and resend of write msg with current offset + + auto mirrorToNode = sessionLocalFile->getMirrorNode(); + + mirrorToSock = mirrorToNode->getConnPool()->acquireStreamSocket(); + + WriteLocalFileMsg mirrorWriteMsg(getClientNumID(), getFileHandleID(), + getTargetID(), getPathInfo(), getAccessFlags(), offset, toBeMirrored); + + if(doSessionCheck() ) + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_SESSION_CHECK); + + if(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_DISABLE_IO) ) + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_DISABLE_IO); + + if(isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_USE_QUOTA) ) + mirrorWriteMsg.setUserdataForQuota(getUserID(), getGroupID() ); + + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR); + mirrorWriteMsg.addMsgHeaderFeatureFlag(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + const auto mirrorBuf = MessagingTk::createMsgVec(mirrorWriteMsg); + + mirrorToSock->send(&mirrorBuf[0], mirrorBuf.size(), 0); + } + + mirrorToSock->send(buf, bufLen, 0); + + return FhgfsOpsErr_SUCCESS; + } + catch(SocketConnectException& e) + { + auto mirrorToNode = sessionLocalFile->getMirrorNode(); + + LogContext(logContext).log(Log_CRITICAL, "Unable to connect to mirror node: " + + mirrorToNode->getNodeIDWithTypeStr() + "; " + "Msg: " + e.what() ); + } + catch(SocketException& e) + { + LogContext(logContext).log(Log_CRITICAL, "Communication with mirror node failed: " + + sessionLocalFile->getMirrorNode()->getNodeIDWithTypeStr() + "; " + "Msg: " + e.what() ); + + if(mirrorToSock) + sessionLocalFile->getMirrorNode()->getConnPool()->invalidateStreamSocket(mirrorToSock); + + mirrorToSock = NULL; + } + + // error occurred if we got here + + if(!mirrorRetriesLeft) + break; + + // only allow retries if we're still at the beginning of the write msg. + /* (this is because later we don't have all the client data available; and without the mirror + response we don't know for sure whether previously sent data was really written or not.) */ + if(toBeMirrored != getCount() ) + break; + + mirrorRetriesLeft--; + + // next round will be a retry + LogContext(logContext).log(Log_NOTICE, "Retrying mirror communication: " + + sessionLocalFile->getMirrorNode()->getNodeIDWithTypeStr() ); + + isRetryRound = true; + + } // end of retry for-loop + + // all retries exhausted if we got here + + return FhgfsOpsErr_COMMUNICATION; +} + +/** + * Receive response from mirror node, check result, clean up (release mirror sock). + * + * Note: Does not do retries on communication errors + */ +template +FhgfsOpsErr WriteLocalFileMsgExBase::finishMirroring(SessionLocalFile* sessionLocalFile, + StorageTarget& target) +{ + std::string logContext = Msg::logContextPref + " (finish mirroring)"; + + // check if mirroring enabled + + if(!mirrorToSock) + return FhgfsOpsErr_SUCCESS; // mirroring disabled + + App* app = Program::getApp(); + auto mirrorToNode = sessionLocalFile->getMirrorNode(); + + WriteLocalFileRespMsg* writeRespMsg; + int64_t mirrorWriteRes; + + + // receive write msg response from mirror... + /* note: we don't have the file contents that were sent by the client anymore at this point, so + we cannot do retries here with a new WriteMsg. */ + + try + { + // receive write msg response... + + auto resp = MessagingTk::recvMsgBuf(*mirrorToSock); + if (resp.empty()) + { // error + LogContext(logContext).log(Log_WARNING, + "Failed to receive response from mirror: " + mirrorToSock->getPeername() ); + + goto cleanup_commerr; + } + + // got response => deserialize it... + + auto respMsg = app->getNetMessageFactory()->createFromBuf(std::move(resp)); + + if(unlikely(respMsg->getMsgType() != NETMSGTYPE_WriteLocalFileResp) ) + { // response invalid (wrong msgType) + LogContext(logContext).logErr( + "Received invalid response type: " + StringTk::intToStr(respMsg->getMsgType() ) +"; " + "expected type: " + StringTk::intToStr(NETMSGTYPE_WriteLocalFileResp) + ". " + "Disconnecting: " + mirrorToSock->getPeername() ); + + goto cleanup_commerr; + } + + // check mirror result and release mirror socket... + + mirrorToNode->getConnPool()->releaseStreamSocket(mirrorToSock); + + writeRespMsg = (WriteLocalFileRespMsg*)respMsg.get(); + mirrorWriteRes = writeRespMsg->getValue(); + + if(likely(mirrorWriteRes == getCount() ) ) + return FhgfsOpsErr_SUCCESS; // mirror successfully wrote all of the data + + if(mirrorWriteRes >= 0) + { // mirror only wrote a part of the data + LogContext(logContext).log(Log_WARNING, + "Mirror did not write all of the data (no space left); " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) + "; " + "fileHandle: " + sessionLocalFile->getFileHandleID() ); + + return FhgfsOpsErr_NOSPACE; + } + + if(mirrorWriteRes == -FhgfsOpsErr_UNKNOWNTARGET) + { + /* local msg processing shall be done and buddy needs resync + (this is normal when a storage is restarted without a broken secondary target, so we + report success to a client in this case) */ + + LogContext(logContext).log(Log_DEBUG, + "Secondary reports unknown target error and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + if(mirrorWriteRes == -FhgfsOpsErr_STORAGE_SRV_CRASHED) + LogContext(logContext).log(Log_NOTICE, "Potential cache loss for open file handle. " + "(Mirror server crash detected.) " + "FileHandleID: " + sessionLocalFile->getFileHandleID() + "; " + "Mirror: " + mirrorToNode->getNodeIDWithTypeStr() ); + + // mirror encountered an error + return (FhgfsOpsErr)-mirrorWriteRes; // write response contains negative fhgfs error code + + } + catch(SocketException& e) + { + LogContext(logContext).logErr(std::string("SocketException: ") + e.what() ); + LogContext(logContext).log(Log_WARNING, "Additional info: " + "mirror node: " + mirrorToNode->getNodeIDWithTypeStr() + "; " + "fileHandle: " + sessionLocalFile->getFileHandleID() ); + } + + + // cleanup after communication error... + +cleanup_commerr: + mirrorToNode->getConnPool()->invalidateStreamSocket(mirrorToSock); + + return FhgfsOpsErr_COMMUNICATION; +} + +template +bool WriteLocalFileMsgExBase::doSessionCheck() +{ // do session check only when it is not a mirror session + return isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_BUDDYMIRROR) ? false : + isMsgHeaderFeatureFlagSet(WRITELOCALFILEMSG_FLAG_SESSION_CHECK); +} diff --git a/storage/source/net/message/session/rw/WriteLocalFileMsgEx.h b/storage/source/net/message/session/rw/WriteLocalFileMsgEx.h new file mode 100644 index 0000000..86ebafa --- /dev/null +++ b/storage/source/net/message/session/rw/WriteLocalFileMsgEx.h @@ -0,0 +1,213 @@ +#pragma once + +#include +#include +#include +#include + + +#define WRITEMSG_MIRROR_RETRIES_NUM 1 + +class StorageTarget; + +/** + * Contains common data needed by implementations of the network protocol + * that receive data from the client. + */ +struct WriteStateBase +{ + const char* logContext; + ssize_t exactStaticRecvSize; + ssize_t recvLength; + int64_t toBeReceived; + off_t writeOffset; + SessionLocalFile* sessionLocalFile; + + WriteStateBase(const char* logContext, ssize_t exactStaticRecvSize, + int64_t toBeReceived, off_t writeOffset, SessionLocalFile* sessionLocalFile) + { + this->logContext = logContext; + this->exactStaticRecvSize = exactStaticRecvSize; + this->toBeReceived = toBeReceived; + this->writeOffset = writeOffset; + this->sessionLocalFile = sessionLocalFile; + recvLength = BEEGFS_MIN(exactStaticRecvSize, toBeReceived); + } + +}; + + +template +class WriteLocalFileMsgExBase : public Msg +{ + + private: + Socket* mirrorToSock; + unsigned mirrorRetriesLeft; + + public: + bool processIncoming(NetMessage::ResponseContext& ctx); + + WriteLocalFileMsgExBase() : Msg() + { + mirrorToSock = NULL; + mirrorRetriesLeft = WRITEMSG_MIRROR_RETRIES_NUM; + } + + private: + std::pair write(NetMessage::ResponseContext& ctx); + + ssize_t doWrite(int fd, char* buf, size_t count, off_t offset, int& outErrno); + + FhgfsOpsErr openFile(const StorageTarget& target, SessionLocalFile* sessionLocalFile); + + FhgfsOpsErr prepareMirroring(char* buf, size_t bufLen, + SessionLocalFile* sessionLocalFile, StorageTarget& target); + FhgfsOpsErr sendToMirror(const char* buf, size_t bufLen, int64_t offset, int64_t toBeMirrored, + SessionLocalFile* sessionLocalFile); + FhgfsOpsErr finishMirroring(SessionLocalFile* sessionLocalFile, StorageTarget& target); + + bool doSessionCheck(); + + int64_t incrementalRecvAndWriteStateful(NetMessage::ResponseContext& ctx, + SessionLocalFile* sessionLocalFile); + + void incrementalRecvPadding(NetMessage::ResponseContext& ctx, int64_t padLen, + SessionLocalFile* sessionLocalFile); + + inline ssize_t recvPadding(NetMessage::ResponseContext& ctx, int64_t toBeReceived) + { + return static_cast(*this).recvPadding(ctx, toBeReceived); + } + + inline void sendResponse(NetMessage::ResponseContext& ctx, int err) + { + return static_cast(*this).sendResponse(ctx, err); + } + + inline bool writeStateInit(WriteState& ws) + { + return static_cast(*this).writeStateInit(ws); + } + + inline ssize_t writeStateRecvData(NetMessage::ResponseContext& ctx, WriteState& ws) + { + return static_cast(*this).writeStateRecvData(ctx, ws); + } + + inline size_t writeStateNext(WriteState& ws, ssize_t writeRes) + { + return static_cast(*this).writeStateNext(ws, writeRes); + } + + public: + inline bool isMsgValid() const + { + return static_cast(*this).isMsgValid(); + } + + inline bool isMsgHeaderFeatureFlagSet(unsigned flag) const + { + return static_cast(*this).isMsgHeaderFeatureFlagSet(flag); + } + + inline unsigned getMsgHeaderUserID() const + { + return static_cast(*this).getMsgHeaderUserID(); + } + + inline uint16_t getTargetID() const + { + return static_cast(*this).getTargetID(); + } + + inline int64_t getOffset() const + { + return static_cast(*this).getOffset(); + } + + inline unsigned getUserID() const + { + return static_cast(*this).getUserID(); + } + + inline unsigned getGroupID() const + { + return static_cast(*this).getGroupID(); + } + + inline int64_t getCount() const + { + return static_cast(*this).getCount(); + } + + inline const char* getFileHandleID() + { + return static_cast(*this).getFileHandleID(); + } + + inline NumNodeID getClientNumID() const + { + return static_cast(*this).getClientNumID(); + } + + inline unsigned getAccessFlags() const + { + return static_cast(*this).getAccessFlags(); + } + + inline PathInfo* getPathInfo () + { + return static_cast(*this).getPathInfo(); + } +}; + +/** + * Implements the recv protocol. + */ +class WriteLocalFileMsgSender : public WriteLocalFileMsg +{ + public: + struct WriteState : public WriteStateBase + { + WriteState(const char* logContext, ssize_t exactStaticRecvSize, + int64_t toBeReceived, off_t writeOffset, SessionLocalFile* sessionLocalFile) : + WriteStateBase(logContext, exactStaticRecvSize, toBeReceived, writeOffset, + sessionLocalFile) {} + }; + + private: + friend class WriteLocalFileMsgExBase; + + static const std::string logContextPref; + + ssize_t recvPadding(ResponseContext& ctx, int64_t toBeReceived); + + inline void sendResponse(ResponseContext& ctx, int err) + { + ctx.sendResponse(WriteLocalFileRespMsg(err)); + } + + inline bool writeStateInit(WriteState& ws) + { + return true; + } + + inline ssize_t writeStateRecvData(ResponseContext& ctx, WriteState& ws) + { + AbstractApp* app = PThread::getCurrentThreadApp(); + int connMsgMediumTimeout = app->getCommonConfig()->getConnMsgMediumTimeout(); + ws.recvLength = BEEGFS_MIN(ws.exactStaticRecvSize, ws.toBeReceived); + return ctx.getSocket()->recvExactT(ctx.getBuffer(), ws.recvLength, 0, connMsgMediumTimeout); + } + + inline size_t writeStateNext(WriteState& ws, ssize_t writeRes) + { + return 0; + } + +}; + +typedef WriteLocalFileMsgExBase WriteLocalFileMsgEx; + diff --git a/storage/source/net/message/session/rw/WriteLocalFileRDMAMsgEx.h b/storage/source/net/message/session/rw/WriteLocalFileRDMAMsgEx.h new file mode 100644 index 0000000..93f10b8 --- /dev/null +++ b/storage/source/net/message/session/rw/WriteLocalFileRDMAMsgEx.h @@ -0,0 +1,94 @@ +#pragma once + +#ifdef BEEGFS_NVFS +#include +#include +#include +#include +#include +#include "WriteLocalFileMsgEx.h" + + +/** + * Implements RDMA read protocol. + */ +class WriteLocalFileRDMAMsgSender : public WriteLocalFileRDMAMsg +{ + public: + struct WriteState : public WriteStateBase + { + RdmaInfo* rdma; + uint64_t rBuf; + size_t rLen; + uint64_t rOff; + int64_t recvSize; + + WriteState(const char* logContext, ssize_t exactStaticRecvSize, + int64_t toBeReceived, off_t writeOffset, SessionLocalFile* sessionLocalFile) : + WriteStateBase(logContext, exactStaticRecvSize, toBeReceived, writeOffset, + sessionLocalFile) + { + recvSize = toBeReceived; + } + }; + + private: + friend class WriteLocalFileMsgExBase; + + static const std::string logContextPref; + + ssize_t recvPadding(ResponseContext& ctx, int64_t toBeReceived); + + inline void sendResponse(ResponseContext& ctx, int err) + { + ctx.sendResponse(WriteLocalFileRDMARespMsg(err)); + } + + inline bool writeStateInit(WriteState& ws) + { + ws.rdma = getRdmaInfo(); + if (unlikely(!ws.rdma->next(ws.rBuf, ws.rLen, ws.rOff))) + { + LogContext(ws.logContext).logErr("No entities in RDMA buffers."); + return false; + } + return true; + } + + inline ssize_t writeStateRecvData(ResponseContext& ctx, WriteState& ws) + { + // Cannot RDMA anything larger than WORKER_BUFIN_SIZE in a single operation + // because that is the size of the buffer passed in by the Worker. + // TODO: pass around a Buffer with a length instead of unqualified char*. + ws.recvLength = BEEGFS_MIN( + BEEGFS_MIN( + BEEGFS_MIN(ws.exactStaticRecvSize, ws.toBeReceived), + (ssize_t)(ws.rLen - ws.rOff)), + WORKER_BUFIN_SIZE); + return ctx.getSocket()->read(ctx.getBuffer(), ws.recvLength, 0, ws.rBuf + ws.rOff, ws.rdma->key); + } + + inline size_t writeStateNext(WriteState& ws, ssize_t writeRes) + { + ws.rOff += writeRes; + if (ws.toBeReceived > 0 && ws.rOff == ws.rLen) + { + if (unlikely(!ws.rdma->next(ws.rBuf, ws.rLen, ws.rOff))) + { + LogContext(ws.logContext).logErr("RDMA buffers expended but not all data received. toBeReceived=" + + StringTk::uint64ToStr(ws.toBeReceived) + "; " + "target: " + StringTk::uintToStr(ws.sessionLocalFile->getTargetID() ) + "; " + "file: " + ws.sessionLocalFile->getFileID() + "; "); + return ws.recvSize - ws.toBeReceived; + } + } + return 0; + } + +}; + +typedef WriteLocalFileMsgExBase WriteLocalFileRDMAMsgEx; + +#endif /* BEEGFS_NVFS */ + diff --git a/storage/source/net/message/storage/GetHighResStatsMsgEx.cpp b/storage/source/net/message/storage/GetHighResStatsMsgEx.cpp new file mode 100644 index 0000000..c87ebc0 --- /dev/null +++ b/storage/source/net/message/storage/GetHighResStatsMsgEx.cpp @@ -0,0 +1,20 @@ +#include +#include +#include +#include "GetHighResStatsMsgEx.h" + + +bool GetHighResStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + HighResStatsList statsHistory; + uint64_t lastStatsMS = getValue(); + + // get stats history + StatsCollector* statsCollector = Program::getApp()->getStatsCollector(); + statsCollector->getStatsSince(lastStatsMS, statsHistory); + + ctx.sendResponse(GetHighResStatsRespMsg(&statsHistory) ); + + return true; +} + diff --git a/storage/source/net/message/storage/GetHighResStatsMsgEx.h b/storage/source/net/message/storage/GetHighResStatsMsgEx.h new file mode 100644 index 0000000..31ef4d7 --- /dev/null +++ b/storage/source/net/message/storage/GetHighResStatsMsgEx.h @@ -0,0 +1,12 @@ +#pragma once + +#include +#include + + +class GetHighResStatsMsgEx : public GetHighResStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/StatStoragePathMsgEx.cpp b/storage/source/net/message/storage/StatStoragePathMsgEx.cpp new file mode 100644 index 0000000..52c8771 --- /dev/null +++ b/storage/source/net/message/storage/StatStoragePathMsgEx.cpp @@ -0,0 +1,56 @@ +#include +#include +#include +#include "StatStoragePathMsgEx.h" + + +bool StatStoragePathMsgEx::processIncoming(ResponseContext& ctx) +{ + int64_t sizeTotal = 0; + int64_t sizeFree = 0; + int64_t inodesTotal = 0; + int64_t inodesFree = 0; + + FhgfsOpsErr statRes = statStoragePath(&sizeTotal, &sizeFree, &inodesTotal, &inodesFree); + + ctx.sendResponse(StatStoragePathRespMsg(statRes, sizeTotal, sizeFree, inodesTotal, inodesFree) ); + + App* app = Program::getApp(); + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_STATSTORAGEPATH, getMsgHeaderUserID() ); + + return true; +} + +FhgfsOpsErr StatStoragePathMsgEx::statStoragePath(int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree) +{ + const char* logContext = "StatStoragePathMsg (stat path)"; + + App* app = Program::getApp(); + + auto* const target = app->getStorageTargets()->getTarget(getTargetID()); + if (!target) + { + LogContext(logContext).logErr("Unknown targetID: " + StringTk::uintToStr(getTargetID() ) ); + return FhgfsOpsErr_UNKNOWNTARGET; + } + + const auto& targetPath = target->getPath().str(); + + bool statSuccess = StorageTk::statStoragePath(targetPath, outSizeTotal, outSizeFree, + outInodesTotal, outInodesFree); + if(unlikely(!statSuccess) ) + { // error + LogContext(logContext).logErr("Unable to statfs() storage path: " + targetPath + + " (SysErr: " + System::getErrString() ); + + return FhgfsOpsErr_INTERNAL; + } + + // read and use value from manual free space override file (if it exists) + StorageTk::statStoragePathOverride(targetPath, outSizeFree, outInodesFree); + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/storage/source/net/message/storage/StatStoragePathMsgEx.h b/storage/source/net/message/storage/StatStoragePathMsgEx.h new file mode 100644 index 0000000..83912c1 --- /dev/null +++ b/storage/source/net/message/storage/StatStoragePathMsgEx.h @@ -0,0 +1,18 @@ +#pragma once + +#include +#include + +// stat of the path to the storage directory, result is similar to statfs + +class StatStoragePathMsgEx : public StatStoragePathMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr statStoragePath(int64_t* outSizeTotal, int64_t* outSizeFree, + int64_t* outInodesTotal, int64_t* outInodesFree); +}; + + diff --git a/storage/source/net/message/storage/TruncLocalFileMsgEx.cpp b/storage/source/net/message/storage/TruncLocalFileMsgEx.cpp new file mode 100644 index 0000000..c07a728 --- /dev/null +++ b/storage/source/net/message/storage/TruncLocalFileMsgEx.cpp @@ -0,0 +1,432 @@ +#include +#include +#include +#include +#include +#include "TruncLocalFileMsgEx.h" + +#include + + +#define TRUNCLOCALFILE_CHUNKOPENLAGS (O_CREAT|O_WRONLY|O_LARGEFILE) + + +bool TruncLocalFileMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "TruncChunkFileMsg incoming"; + + App* app = Program::getApp(); + + uint16_t targetID; + int targetFD; + bool chunkLocked = false; + FhgfsOpsErr clientErrRes; + DynamicAttribs dynAttribs; // inits storageVersion to 0 (=> initially invalid) + StorageTarget* target; + + + // select the right targetID + + targetID = getTargetID(); + + if(isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { // unknown group ID + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { // unknown targetID + if (isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR)) + { /* buddy mirrored file => fail with GenericResp to make the caller retry. + mgmt will mark this target as (p)offline in a few moments. */ + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, "Unknown target ID")); + return true; + } + + LOG(GENERAL, ERR, "Unknown target ID.", targetID); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + return true; + } + + + { // get targetFD and check consistency state + bool skipResponse = false; + + targetFD = getTargetFD(*target, ctx, &skipResponse); + if(unlikely(targetFD == -1) ) + { // failed => consistency state not good + if(skipResponse) + goto skip_response; // GenericResponseMsg sent + + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + // forward to secondary (if appropriate) + clientErrRes = forwardToSecondary(*target, ctx, &chunkLocked); + if(unlikely(clientErrRes != FhgfsOpsErr_SUCCESS) ) + { + if(clientErrRes == FhgfsOpsErr_COMMUNICATION) + goto skip_response; // GenericResponseMsg sent + + goto send_response; + } + + { // valid targetID + std::string entryID(getEntryID() ); + + // generate path to chunk file... + + Path chunkDirPath; + std::string chunkFilePathStr; + const PathInfo *pathInfo = getPathInfo(); + bool hasOrigFeature = pathInfo->hasOrigFeature(); + + StorageTk::getChunkDirChunkFilePath(pathInfo, entryID, hasOrigFeature, chunkDirPath, + chunkFilePathStr); + + // truncate file... + + clientErrRes = truncFile(targetID, targetFD, &chunkDirPath, chunkFilePathStr, entryID, + hasOrigFeature); + + /* clientErrRes == FhgfsOpsErr_PATHNOTEXISTS && !getFileSize() is special we need to fake + * the attributes, to inform the metaserver about the new file size with storageVersion!=0 */ + if(clientErrRes == FhgfsOpsErr_SUCCESS || + (clientErrRes == FhgfsOpsErr_PATHNOTEXISTS && !getFilesize() ) ) + { // truncation successful + LOG_DEBUG(logContext, Log_DEBUG, "File truncated: " + chunkFilePathStr); + + // get updated dynamic attribs... + + if(!isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_NODYNAMICATTRIBS) ) + { + if (clientErrRes == FhgfsOpsErr_SUCCESS) + getDynamicAttribsByPath(targetFD, chunkFilePathStr.c_str(), targetID, entryID, + dynAttribs); + else + { // clientErrRes == FhgfsOpsErr_PATHNOTEXISTS && !getFileSize() + getFakeDynAttribs(targetID, entryID, dynAttribs); + } + } + + // change to SUCCESS if it was FhgfsOpsErr_PATHNOTEXISTS + clientErrRes = FhgfsOpsErr_SUCCESS; + } + } + + +send_response: + + if(chunkLocked) // unlock chunk + app->getChunkLockStore()->unlockChunk(targetID, getEntryID() ); + + // send response... + ctx.sendResponse( + TruncLocalFileRespMsg(clientErrRes, dynAttribs.filesize, dynAttribs.allocedBlocks, + dynAttribs.modificationTimeSecs, dynAttribs.lastAccessTimeSecs, + dynAttribs.storageVersion) ); + +skip_response: + + // update operation counters + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_TRUNCLOCALFILE, getMsgHeaderUserID() ); + + return true; +} + +/** + * @param outResponseSent true if a response was sent from within this method; can only be true if + * -1 is returned. + * @return -1 if consistency state was not good (in which case a special response is sent within + * this method), otherwise the file descriptor to chunks dir (or mirror dir). + */ +int TruncLocalFileMsgEx::getTargetFD(const StorageTarget& target, ResponseContext& ctx, + bool* outResponseSent) +{ + bool isBuddyMirrorChunk = isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR); + + *outResponseSent = false; + + // get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && + isBuddyMirrorChunk && + !isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + { // this is a msg to a non-good primary + std::string respMsgLogStr = "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID()); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(respMsgLogStr))); + + *outResponseSent = true; + return -1; + } + + return targetFD; +} + +FhgfsOpsErr TruncLocalFileMsgEx::truncFile(uint16_t targetId, int targetFD, + const Path* chunkDirPath, const std::string& chunkFilePathStr, std::string entryID, + bool hasOrigFeature) +{ + const char* logContext = "TruncLocalFileMsg incoming"; + App* app = Program::getApp(); + + FhgfsOpsErr clientErrRes = FhgfsOpsErr_SUCCESS; + + int truncRes = MsgHelperIO::truncateAt(targetFD, chunkFilePathStr.c_str(), getFilesize() ); + if(!truncRes) + return FhgfsOpsErr_SUCCESS; // truncate succeeded + + // file or path just doesn't exist or real error? + + int truncErrCode = errno; + + if(unlikely(truncErrCode != ENOENT) ) + { // error + clientErrRes = FhgfsOpsErrTk::fromSysErr(truncErrCode); + if (clientErrRes == FhgfsOpsErr_INTERNAL) // only log unhandled errors + LogContext(logContext).logErr("Unable to truncate file: " + chunkFilePathStr + ". " + + "SysErr: " + System::getErrString(truncErrCode) ); + + return clientErrRes; + } + + // ENOENT => file (and possibly path to file (dirs) ) doesn't exist + + /* note: if the file doesn't exist, it's generally not an error. + but if it should grow to a certain size, we have to create it... */ + + if(!getFilesize() ) + return FhgfsOpsErr_PATHNOTEXISTS; // nothing to be done + + // create the file and re-size it + + bool useQuota = isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_USE_QUOTA); + bool enforceQuota = app->getConfig()->getQuotaEnableEnforcement(); + SessionQuotaInfo quotaInfo(useQuota, enforceQuota, getUserID(), getGroupID()); + const ExceededQuotaStorePtr exceededQuotaStore = app->getExceededQuotaStores()->get(targetId); + + ChunkStore* chunkDirStore = app->getChunkDirStore(); + int fd; + int openFlags = TRUNCLOCALFILE_CHUNKOPENLAGS; + + FhgfsOpsErr mkChunkRes = chunkDirStore->openChunkFile(targetFD, chunkDirPath, chunkFilePathStr, + hasOrigFeature, openFlags, &fd, "aInfo, exceededQuotaStore); + + if (unlikely(mkChunkRes == FhgfsOpsErr_NOTOWNER && useQuota) ) + { + // it already logs a message, so need to further check this ret value + chunkDirStore->chmodV2ChunkDirPath(targetFD, chunkDirPath, entryID); + + mkChunkRes = chunkDirStore->openChunkFile( + targetFD, chunkDirPath, chunkFilePathStr, hasOrigFeature, openFlags, &fd, "aInfo, + exceededQuotaStore); + } + + if (mkChunkRes != FhgfsOpsErr_SUCCESS) + { + if (mkChunkRes == FhgfsOpsErr_INTERNAL) // only log unhandled errors + LogContext(logContext).logErr("Failed to create chunkFile: " + chunkFilePathStr); + + return mkChunkRes; + } + + // file created => trunc it + + int ftruncRes = ftruncate(fd, getFilesize() ); + if(unlikely(ftruncRes == -1) ) + { // error + clientErrRes = FhgfsOpsErrTk::fromSysErr(errno); + if (clientErrRes == FhgfsOpsErr_INTERNAL) // only log unhandled errors + LogContext(logContext).logErr( + "Unable to truncate file (after creation): " + chunkFilePathStr + ". " + + "Length: " + StringTk::int64ToStr(getFilesize() ) + ". " + + "SysErr: " + System::getErrString() ); + } + + // close file + + int closeRes = close(fd); + if(unlikely(closeRes == -1) ) + { // error + clientErrRes = FhgfsOpsErrTk::fromSysErr(errno); + if (clientErrRes == FhgfsOpsErr_INTERNAL) // only log unhandled errors + LogContext(logContext).logErr( + "Unable to close file (after creation/truncation): " + chunkFilePathStr + ". " + + "Length: " + StringTk::int64ToStr(getFilesize() ) + ". " + + "SysErr: " + System::getErrString() ); + } + + + return clientErrRes; +} + +bool TruncLocalFileMsgEx::getDynamicAttribsByPath(const int dirFD, const char* path, + uint16_t targetID, std::string fileID, DynamicAttribs& outDynAttribs) +{ + SyncedStoragePaths* syncedPaths = Program::getApp()->getSyncedStoragePaths(); + + uint64_t storageVersion = syncedPaths->lockPath(fileID, targetID); // L O C K path + + // note: this is locked because we need to get the filesize together with the storageVersion + bool getDynAttribsRes = StorageTkEx::getDynamicFileAttribs(dirFD, path, + &outDynAttribs.filesize, &outDynAttribs.allocedBlocks, &outDynAttribs.modificationTimeSecs, + &outDynAttribs.lastAccessTimeSecs); + + if(getDynAttribsRes) + outDynAttribs.storageVersion = storageVersion; + + syncedPaths->unlockPath(fileID, targetID); // U N L O C K path + + return getDynAttribsRes; +} + +/** + * Note: only for fileSize == 0 and if the file does not exist yet + */ +bool TruncLocalFileMsgEx::getFakeDynAttribs(uint16_t targetID, std::string fileID, + DynamicAttribs& outDynAttribs) +{ + SyncedStoragePaths* syncedPaths = Program::getApp()->getSyncedStoragePaths(); + uint64_t storageVersion = syncedPaths->lockPath(fileID, targetID); // L O C K path + + int64_t currentTimeSecs = TimeAbs().getTimeval()->tv_sec; + + outDynAttribs.filesize = 0; + outDynAttribs.allocedBlocks = 0; + outDynAttribs.modificationTimeSecs = currentTimeSecs; + outDynAttribs.lastAccessTimeSecs = currentTimeSecs; /* actually not correct, but better than + * 1970 */ + outDynAttribs.storageVersion = storageVersion; + + syncedPaths->unlockPath(fileID, targetID); // U N L O C K path + + return true; +} + +/** + * If this is a buddy mirror msg and we are the primary, forward this msg to secondary. + * + * @return _COMMUNICATION if forwarding to buddy failed and buddy is not marked offline (in which + * case *outChunkLocked==false is guaranteed). + * @throw SocketException if sending of GenericResponseMsg fails. + */ +FhgfsOpsErr TruncLocalFileMsgEx::forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked) +{ + const char* logContext = "TruncLocalFileMsgEx incoming (forward to secondary)"; + + App* app = Program::getApp(); + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + + *outChunkLocked = false; + + if(!isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR) || + isMsgHeaderFeatureFlagSet(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + return FhgfsOpsErr_SUCCESS; // nothing to do + + // mirrored chunk should be modified, check if resync is in progress and lock chunk + *outChunkLocked = target.getBuddyResyncInProgress(); + if(*outChunkLocked) + chunkLockStore->lockChunk(target.getID(), getEntryID() ); // lock chunk + + // instead of creating a new msg object, we just re-use "this" with "buddymirror second" flag + addMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + RequestResponseArgs rrArgs(NULL, this, NETMSGTYPE_TruncLocalFileResp); + RequestResponseTarget rrTarget(getTargetID(), app->getTargetMapper(), app->getStorageNodes(), + app->getTargetStateStore(), app->getMirrorBuddyGroupMapper(), true); + + FhgfsOpsErr commRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + // remove the flag that we just added for secondary + unsetMsgHeaderFeatureFlag(TRUNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + if(unlikely( + (commRes == FhgfsOpsErr_COMMUNICATION) && + (rrTarget.outTargetReachabilityState == TargetReachabilityState_OFFLINE) ) ) + { + LOG_DEBUG(logContext, Log_DEBUG, std::string("Secondary is offline and will need resync. ") + + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + // buddy is marked offline, so local msg processing will be done and buddy needs resync + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; // go ahead with local msg processing + } + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_DEBUG, "Forwarding failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) + "; " + "error: " + boost::lexical_cast(commRes)); + + if(*outChunkLocked) + { // unlock chunk + chunkLockStore->unlockChunk(target.getID(), getEntryID() ); + *outChunkLocked = false; + } + + std::string genericRespStr = "Communication with secondary failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(genericRespStr))); + + return FhgfsOpsErr_COMMUNICATION; + } + + TruncLocalFileRespMsg* respMsg = (TruncLocalFileRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr secondaryRes = respMsg->getResult(); + if(unlikely(secondaryRes != FhgfsOpsErr_SUCCESS) ) + { + if(secondaryRes == FhgfsOpsErr_UNKNOWNTARGET) + { + /* local msg processing shall be done and buddy needs resync + (this is normal when a storage is restarted without a broken secondary target, so we + report success to a client in this case) */ + + LogContext(logContext).log(Log_DEBUG, + "Secondary reports unknown target error and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + if(secondaryRes != FhgfsOpsErr_TOOBIG) // "too big" is a valid error if max filesize exceeded + { + LogContext(logContext).log(Log_NOTICE, std::string("Secondary reported error: ") + + boost::lexical_cast(secondaryRes) + "; " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + } + + return secondaryRes; + } + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/storage/source/net/message/storage/TruncLocalFileMsgEx.h b/storage/source/net/message/storage/TruncLocalFileMsgEx.h new file mode 100644 index 0000000..dc5ebd7 --- /dev/null +++ b/storage/source/net/message/storage/TruncLocalFileMsgEx.h @@ -0,0 +1,37 @@ +#pragma once + +#include +#include +#include + +class StorageTarget; + +class TruncLocalFileMsgEx : public TruncLocalFileMsg +{ + private: + struct DynamicAttribs + { + DynamicAttribs() : filesize(0), allocedBlocks(0), modificationTimeSecs(0), + lastAccessTimeSecs(0), storageVersion(0) {} + + int64_t filesize; + int64_t allocedBlocks; // allocated 512byte blocks (relevant for sparse files) + int64_t modificationTimeSecs; + int64_t lastAccessTimeSecs; + uint64_t storageVersion; + }; + + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr truncFile(uint16_t targetId, int targetFD, const Path* chunkDirPath, + const std::string& chunkFilePathStr, std::string entryID, bool hasOrigFeature); + int getTargetFD(const StorageTarget& target, ResponseContext& ctx, bool* outResponseSent); + bool getDynamicAttribsByPath(const int dirFD, const char* path, uint16_t targetID, + std::string fileID, DynamicAttribs& outDynAttribs); + bool getFakeDynAttribs(uint16_t targetID, std::string fileID, DynamicAttribs& outDynAttribs); + FhgfsOpsErr forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked); +}; + diff --git a/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.cpp b/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.cpp new file mode 100644 index 0000000..37d80f0 --- /dev/null +++ b/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.cpp @@ -0,0 +1,152 @@ +#include +#include +#include +#include +#include "GetChunkFileAttribsMsgEx.h" + + +bool GetChunkFileAttribsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "GetChunkFileAttribsMsg incoming"; + + App* app = Program::getApp(); + + std::string entryID(getEntryID() ); + + FhgfsOpsErr clientErrRes = FhgfsOpsErr_SUCCESS; + int targetFD; + struct stat statbuf{}; + uint64_t storageVersion = 0; + + // select the right targetID + + uint16_t targetID = getTargetID(); + + if(isMsgHeaderFeatureFlagSet(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + // note: only log message here, error handling will happen below through invalid targetFD + if(unlikely(!targetID) ) + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + } + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + if (isMsgHeaderFeatureFlagSet(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR)) + { /* buddy mirrored file => fail with GenericResp to make the caller retry. + mgmt will mark this target as (p)offline in a few moments. */ + LOG(GENERAL, NOTICE, "Unknown target ID, refusing request.", targetID); + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, "Unknown target ID")); + return true; + } + + LOG(GENERAL, ERR, "Unknown target ID.", targetID); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + + { // get targetFD and check consistency state + bool skipResponse = false; + + targetFD = getTargetFD(*target, ctx, &skipResponse); + if(unlikely(targetFD == -1) ) + { // failed => consistency state not good + memset(&statbuf, 0, sizeof(statbuf) ); // (just to mute clang warning) + + if(skipResponse) + goto skip_response; // GenericResponseMsg sent + + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + { // valid targetID + SyncedStoragePaths* syncedPaths = app->getSyncedStoragePaths(); + + int statErrCode = 0; + + std::string chunkPath = StorageTk::getFileChunkPath(getPathInfo(), entryID); + + uint64_t newStorageVersion = syncedPaths->lockPath(entryID, targetID); // L O C K path + + int statRes = fstatat(targetFD, chunkPath.c_str(), &statbuf, 0); + if(statRes) + { // file not exists or error + statErrCode = errno; + } + else + { + storageVersion = newStorageVersion; + } + + syncedPaths->unlockPath(entryID, targetID); // U N L O C K path + + // note: non-existing file is not an error (storage version is 0, so nothing will be + // updated at the metadata node) + + if((statRes == -1) && (statErrCode != ENOENT)) + { // error + clientErrRes = FhgfsOpsErr_INTERNAL; + + LogContext(logContext).logErr( + "Unable to stat file: " + chunkPath + ". " + "SysErr: " + + System::getErrString(statErrCode)); + } + } + +send_response: + ctx.sendResponse( + GetChunkFileAttribsRespMsg(clientErrRes, statbuf.st_size, statbuf.st_blocks, + statbuf.st_mtime, statbuf.st_atime, storageVersion) ); + +skip_response: + + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), + StorageOpCounter_GETLOCALFILESIZE, getMsgHeaderUserID() ); + + return true; +} + +/** + * @param outResponseSent true if a response was sent from within this method; can only be true if + * -1 is returned. + * @return -1 if consistency state was not good (in which case a special response is sent within + * this method), otherwise the file descriptor to chunks dir (or mirror dir). + */ +int GetChunkFileAttribsMsgEx::getTargetFD(const StorageTarget& target, ResponseContext& ctx, + bool* outResponseSent) +{ + bool isBuddyMirrorChunk = isMsgHeaderFeatureFlagSet(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR); + + *outResponseSent = false; + + // get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && + isBuddyMirrorChunk && + !isMsgHeaderFeatureFlagSet(GETCHUNKFILEATTRSMSG_FLAG_BUDDYMIRROR_SECOND) ) + { // this is a msg to a non-good primary + std::string respMsgLogStr = "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID()); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(respMsgLogStr))); + + *outResponseSent = true; + return -1; + } + + return targetFD; +} diff --git a/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.h b/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.h new file mode 100644 index 0000000..d60c0b5 --- /dev/null +++ b/storage/source/net/message/storage/attribs/GetChunkFileAttribsMsgEx.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class StorageTarget; + +class GetChunkFileAttribsMsgEx : public GetChunkFileAttribsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + int getTargetFD(const StorageTarget& target, ResponseContext& ctx, bool* outResponseSent); +}; + diff --git a/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.cpp b/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.cpp new file mode 100644 index 0000000..0b92550 --- /dev/null +++ b/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.cpp @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include "SetLocalAttrMsgEx.h" + +#include + +#include + + +bool SetLocalAttrMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "SetLocalAttrMsgEx incoming"; + + App* app = Program::getApp(); + + const SettableFileAttribs* attribs = getAttribs(); + int validAttribs = getValidAttribs(); + + uint16_t targetID; + bool chunkLocked = false; + int targetFD; + FhgfsOpsErr clientErrRes = FhgfsOpsErr_SUCCESS; + DynamicFileAttribs currentDynAttribs(0, 0, 0, 0, 0); + StorageTarget* target; + + // select the right targetID + + targetID = getTargetID(); + + if(isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { // unknown group ID + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { // unknown targetID + if (isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR)) + { /* buddy mirrored file => fail with GenericResp to make the caller retry. + mgmt will mark this target as (p)offline in a few moments. */ + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, "Unknown target ID")); + return true; + } + + LOG(GENERAL, ERR, "Unknown target ID.", targetID); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + return true; + } + + { // get targetFD and check consistency state + bool skipResponse = false; + + targetFD = getTargetFD(*target, ctx, &skipResponse); + if(unlikely(targetFD == -1) ) + { // failed => consistency state not good + if(skipResponse) + goto skip_response; // GenericResponseMsg sent + + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + // forward to secondary (if appropriate) + clientErrRes = forwardToSecondary(*target, ctx, &chunkLocked); + if(unlikely(clientErrRes != FhgfsOpsErr_SUCCESS) ) + { + if(clientErrRes == FhgfsOpsErr_COMMUNICATION) + goto skip_response; // GenericResponseMsg sent + + goto send_response; + } + + if(validAttribs & (SETATTR_CHANGE_MODIFICATIONTIME | SETATTR_CHANGE_LASTACCESSTIME) ) + { // we only handle access and modification time updates here + struct timespec times[2] = {{0, 0}, {0, 0}}; + + if (validAttribs & SETATTR_CHANGE_LASTACCESSTIME) + { + times[MsgHelperIO_ATIME_POS].tv_sec = attribs->lastAccessTimeSecs; + times[MsgHelperIO_ATIME_POS].tv_nsec = 0; + } + else + times[MsgHelperIO_ATIME_POS].tv_nsec = UTIME_OMIT; + + if (validAttribs & SETATTR_CHANGE_MODIFICATIONTIME) + { + times[MsgHelperIO_MTIME_POS].tv_sec = attribs->modificationTimeSecs; + times[MsgHelperIO_MTIME_POS].tv_nsec = 0; + } + else + times[MsgHelperIO_MTIME_POS].tv_nsec = UTIME_OMIT; + + // generate path to chunk file... + + std::string pathStr; + + pathStr = StorageTk::getFileChunkPath(getPathInfo(), getEntryID() ); + + // update timestamps... + + // in case of a timestamp update we need extra information on the metadata server, namely + // a storageVersion and the current dynamic attribs of the chunk + // => set the new times while holding the lock and return the current attribs and a + // storageVersion in response later + uint64_t storageVersion = Program::getApp()->getSyncedStoragePaths()->lockPath(getEntryID(), + targetID); + + int utimeRes = MsgHelperIO::utimensat(targetFD, pathStr.c_str(), times, 0); + + if (utimeRes == 0) + { + bool getDynAttribsRes = StorageTkEx::getDynamicFileAttribs(targetFD, pathStr.c_str(), + ¤tDynAttribs.fileSize, ¤tDynAttribs.numBlocks, + ¤tDynAttribs.modificationTimeSecs, ¤tDynAttribs.lastAccessTimeSecs); + + // If stat failed (after utimensat worked!), something really bad happened, so the + // attribs are definitely invalid. Otherwise set storageVersion in dynAttribs + if (getDynAttribsRes) + currentDynAttribs.storageVersion = storageVersion; + } + else if (errno == ENOENT) + { + // Entry doesn't exist. Not an error, but we need to return fake dynamic attributes for + // the metadata server to calc the values (fake in this sense means, we send the + // timestamps back that we tried to set, but have real filesize and numBlocks, i.e. 0 + currentDynAttribs.storageVersion = storageVersion; + currentDynAttribs.fileSize = 0; + currentDynAttribs.numBlocks = 0; + currentDynAttribs.modificationTimeSecs = attribs->modificationTimeSecs; + currentDynAttribs.lastAccessTimeSecs = attribs->lastAccessTimeSecs; + } + else + { // error + int errCode = errno; + + LogContext(logContext).logErr("Unable to change file time: " + pathStr + ". " + "SysErr: " + System::getErrString()); + + clientErrRes = FhgfsOpsErrTk::fromSysErr(errCode); + } + + Program::getApp()->getSyncedStoragePaths()->unlockPath(getEntryID(), targetID); + } + + if(isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_USE_QUOTA) && + (validAttribs & (SETATTR_CHANGE_USERID | SETATTR_CHANGE_GROUPID) ) ) + { // we only handle UID and GID updates here + uid_t uid = -1; + gid_t gid = -1; + + if(validAttribs & SETATTR_CHANGE_USERID) + uid = attribs->userID; + + if(validAttribs & SETATTR_CHANGE_GROUPID) + gid = attribs->groupID; + + // generate path to chunk file... + + std::string pathStr; + + pathStr = StorageTk::getFileChunkPath(getPathInfo(), getEntryID() ); + + // update UID and GID... + + int chownRes = fchownat(targetFD, pathStr.c_str(), uid, gid, 0); + if(chownRes == -1) + { // could be an error + int errCode = errno; + + if(errCode != ENOENT) + { // unhandled chown() error + LogContext(logContext).logErr("Unable to change file owner: " + pathStr + ". " + "SysErr: " + System::getErrString() ); + + clientErrRes = FhgfsOpsErrTk::fromSysErr(errCode); + } + } + } + + +send_response: + + if(chunkLocked) // unlock chunk + app->getChunkLockStore()->unlockChunk(targetID, getEntryID() ); + + ctx.sendResponse(SetLocalAttrRespMsg(clientErrRes, currentDynAttribs)); + +skip_response: + + // update operation counters... + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_SETLOCALATTR, + getMsgHeaderUserID() ); + + return true; +} + +/** + * @param outResponseSent true if a response was sent from within this method; can only be true if + * -1 is returned. + * @return -1 if consistency state was not good (in which case a special response is sent within + * this method), otherwise the file descriptor to chunks dir (or mirror dir). + */ +int SetLocalAttrMsgEx::getTargetFD(const StorageTarget& target, ResponseContext& ctx, + bool* outResponseSent) +{ + bool isBuddyMirrorChunk = isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR); + + *outResponseSent = false; + + // get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && + isBuddyMirrorChunk && + !isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND) ) + { // this is a msg to a non-good primary + std::string respMsgLogStr = "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID()); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(respMsgLogStr))); + + *outResponseSent = true; + return -1; + } + + return targetFD; +} + +/** + * If this is a buddy mirror msg and we are the primary, forward this msg to secondary. + * + * @return _COMMUNICATION if forwarding to buddy failed and buddy is not marked offline (in which + * case *outChunkLocked==false is guaranteed). + * @throw SocketException if sending of GenericResponseMsg fails. + */ +FhgfsOpsErr SetLocalAttrMsgEx::forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked) +{ + const char* logContext = "SetLocalAttrMsg incoming (forward to secondary)"; + + App* app = Program::getApp(); + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + + *outChunkLocked = false; + + if(!isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR) || + isMsgHeaderFeatureFlagSet(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND) ) + return FhgfsOpsErr_SUCCESS; // nothing to do + + // mirrored chunk should be modified, check if resync is in progress and lock chunk + *outChunkLocked = target.getBuddyResyncInProgress(); + if(*outChunkLocked) + chunkLockStore->lockChunk(target.getID(), getEntryID() ); // lock chunk + + // instead of creating a new msg object, we just re-use "this" with "buddymirror second" flag + addMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND); + + RequestResponseArgs rrArgs(NULL, this, NETMSGTYPE_SetLocalAttrResp); + RequestResponseTarget rrTarget(getTargetID(), app->getTargetMapper(), app->getStorageNodes(), + app->getTargetStateStore(), app->getMirrorBuddyGroupMapper(), true); + + FhgfsOpsErr commRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + // remove the flag that we just added for secondary + unsetMsgHeaderFeatureFlag(SETLOCALATTRMSG_FLAG_BUDDYMIRROR_SECOND); + + if(unlikely( + (commRes == FhgfsOpsErr_COMMUNICATION) && + (rrTarget.outTargetReachabilityState == TargetReachabilityState_OFFLINE) ) ) + { + LOG_DEBUG(logContext, Log_DEBUG, std::string("Secondary is offline and will need resync. ") + + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + // buddy is marked offline, so local msg processing will be done and buddy needs resync + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; // go ahead with local msg processing + } + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_DEBUG, "Forwarding failed: " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) + "; " + "error: " + boost::lexical_cast(commRes)); + + if(*outChunkLocked) + { // unlock chunk + chunkLockStore->unlockChunk(target.getID(), getEntryID() ); + *outChunkLocked = false; + } + + std::string genericRespStr = "Communication with secondary failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(genericRespStr))); + + return FhgfsOpsErr_COMMUNICATION; + } + + const auto respMsg = (const SetLocalAttrRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr secondaryRes = respMsg->getResult(); + if(unlikely(secondaryRes != FhgfsOpsErr_SUCCESS) ) + { + if(secondaryRes == FhgfsOpsErr_UNKNOWNTARGET) + { + /* local msg processing shall be done and buddy needs resync + (this is normal when a storage is restarted without a broken secondary target, so we + report success to a client in this case) */ + + LogContext(logContext).log(Log_DEBUG, + "Secondary reports unknown target error and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + LogContext(logContext).log(Log_NOTICE, std::string("Secondary reported error: ") + + boost::lexical_cast(secondaryRes) + "; " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + return secondaryRes; + } + + return FhgfsOpsErr_SUCCESS; +} + diff --git a/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.h b/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.h new file mode 100644 index 0000000..5117252 --- /dev/null +++ b/storage/source/net/message/storage/attribs/SetLocalAttrMsgEx.h @@ -0,0 +1,19 @@ +#pragma once + +#include +#include + +class StorageTarget; + +class SetLocalAttrMsgEx : public SetLocalAttrMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + int getTargetFD(const StorageTarget& target, ResponseContext& ctx, bool* outResponseSent); + FhgfsOpsErr forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked); +}; + + diff --git a/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.cpp b/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.cpp new file mode 100644 index 0000000..308f032 --- /dev/null +++ b/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.cpp @@ -0,0 +1,26 @@ +#include +#include +#include +#include + +#include "CpChunkPathsMsgEx.h" + +bool CpChunkPathsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "CpChunkPathsMsg incoming"; + + LogContext(logContext).logErr("This message is not yet implemented. \n It should relay chunk information from metadata to storage and trigger copy chunk operation. "); + + FhgfsOpsErr cpMsgRes = FhgfsOpsErr_SUCCESS; + ctx.sendResponse(CpChunkPathsRespMsg(cpMsgRes)); + return true; +} + + +ChunkBalancerJob* CpChunkPathsMsgEx::addChunkBalanceJob() +{ + std::lock_guard mutexLock(ChunkBalanceJobMutex); + + ChunkBalancerJob* chunkBalanceJob = nullptr; + return chunkBalanceJob; +} \ No newline at end of file diff --git a/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.h b/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.h new file mode 100644 index 0000000..d0bca5a --- /dev/null +++ b/storage/source/net/message/storage/chunkbalancing/CpChunkPathsMsgEx.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +class ChunkBalancerJob; + +class CpChunkPathsMsgEx : public CpChunkPathsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + private: + Mutex ChunkBalanceJobMutex; + ChunkBalancerJob* addChunkBalanceJob(); + +}; + diff --git a/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.cpp b/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.cpp new file mode 100644 index 0000000..6ba1aaa --- /dev/null +++ b/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.cpp @@ -0,0 +1,57 @@ +#include +#include +#include + +#include "RmChunkPathsMsgEx.h" + +bool RmChunkPathsMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "RmChunkPathsMsg incoming"; + + App* app = Program::getApp(); + ChunkStore* chunkStore = app->getChunkDirStore(); + + uint16_t targetID; + StringList& relativePaths = getRelativePaths(); + StringList failedPaths; + + targetID = getTargetID(); + + auto* const target = app->getStorageTargets()->getTarget(targetID); + + if (!target) + { // unknown targetID + LogContext(logContext).logErr("Unknown targetID: " + StringTk::uintToStr(targetID)); + failedPaths = relativePaths; + } + else + { // valid targetID + const int targetFD = isMsgHeaderFeatureFlagSet(RMCHUNKPATHSMSG_FLAG_BUDDYMIRROR) + ? *target->getMirrorFD() + : *target->getChunkFD(); + for(StringListIter iter = relativePaths.begin(); iter != relativePaths.end(); iter++) + { + // remove chunk + int unlinkRes = unlinkat(targetFD, (*iter).c_str(), 0); + + if ( (unlinkRes != 0) && (errno != ENOENT) ) + { + LogContext(logContext).logErr( + "Unable to remove entry: " + *iter + "; error: " + System::getErrString()); + failedPaths.push_back(*iter); + + continue; + } + + // removal succeeded; this might have been the last entry => try to remove parent directory + Path parentDirPath(StorageTk::getPathDirname(*iter)); + + chunkStore->rmdirChunkDirPath(targetFD, &parentDirPath); + } + } + + ctx.sendResponse(RmChunkPathsRespMsg(&failedPaths) ); + + return true; +} + diff --git a/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.h b/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.h new file mode 100644 index 0000000..fb5089a --- /dev/null +++ b/storage/source/net/message/storage/creating/RmChunkPathsMsgEx.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +class RmChunkPathsMsgEx : public RmChunkPathsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.cpp b/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.cpp new file mode 100644 index 0000000..b145c09 --- /dev/null +++ b/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.cpp @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include "UnlinkLocalFileMsgEx.h" + +#include + +bool UnlinkLocalFileMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "UnlinkChunkFileMsg incoming"; + + App* app = Program::getApp(); + ChunkStore* chunkDirStore = app->getChunkDirStore(); + + FhgfsOpsErr clientErrRes = FhgfsOpsErr_SUCCESS; + + uint16_t targetID; + bool chunkLocked = false; + int targetFD = -1; + Path chunkDirPath; + const PathInfo* pathInfo = getPathInfo(); + bool hasOrigFeature = pathInfo->hasOrigFeature(); + int unlinkRes = -1; + StorageTarget* target; + + // select the right targetID + + targetID = getTargetID(); + + if(isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + + targetID = isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { // unknown target + LogContext(logContext).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(getTargetID() ) ); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + if (isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR)) + { /* buddy mirrored file => fail with GenericResp to make the caller retry. + mgmt will mark this target as (p)offline in a few moments. */ + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, "Unknown target ID")); + return true; + } + + LOG(GENERAL, ERR, "Unknown targetID.", targetID); + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + return true; + } + + { // get targetFD and check consistency state + bool skipResponse = false; + + targetFD = getTargetFD(*target, ctx, &skipResponse); + if(unlikely(targetFD == -1) ) + { // failed => consistency state not good + if(skipResponse) + goto skip_response; // GenericResponseMsg sent + + clientErrRes = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + // forward to secondary (if appropriate) + clientErrRes = forwardToSecondary(*target, ctx, &chunkLocked); + if(unlikely(clientErrRes != FhgfsOpsErr_SUCCESS) ) + { + if(clientErrRes == FhgfsOpsErr_COMMUNICATION) + goto skip_response; // GenericResponseMsg sent + + goto send_response; + } + + { // valid targetID + + // generate path to chunk file... + + std::string chunkFilePathStr; // chunkDirPathStr + '/' + entryID + + StorageTk::getChunkDirChunkFilePath(pathInfo, getEntryID(), hasOrigFeature, chunkDirPath, + chunkFilePathStr); + + unlinkRes = unlinkat(targetFD, chunkFilePathStr.c_str(), 0); + + if( (unlinkRes == -1) && (errno != ENOENT) ) + { // error + LogContext(logContext).logErr("Unable to unlink file: " + chunkFilePathStr + ". " + + "SysErr: " + System::getErrString() ); + + clientErrRes = FhgfsOpsErr_INTERNAL; + } + else + { // success + LogContext(logContext).log(Log_DEBUG, "File unlinked: " + chunkFilePathStr); + } + } + + +send_response: + + if(chunkLocked) // unlock chunk + app->getChunkLockStore()->unlockChunk(targetID, getEntryID() ); + + ctx.sendResponse(UnlinkLocalFileRespMsg(clientErrRes) ); + +skip_response: + + // try to rmdir chunkDirPath (in case this was the last chunkfile in a dir) + if (!unlinkRes && hasOrigFeature) + chunkDirStore->rmdirChunkDirPath(targetFD, &chunkDirPath); + + // update operation counters... + app->getNodeOpStats()->updateNodeOp(ctx.getSocket()->getPeerIP(), StorageOpCounter_UNLINK, + getMsgHeaderUserID() ); + + return true; +} + +/** + * @param outResponseSent true if a response was sent from within this method; can only be true if + * -1 is returned. + * @return -1 if consistency state was not good (in which case a special response is sent within + * this method), otherwise the file descriptor to chunks dir (or mirror dir). + */ +int UnlinkLocalFileMsgEx::getTargetFD(const StorageTarget& target, ResponseContext& ctx, + bool* outResponseSent) +{ + bool isBuddyMirrorChunk = isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR); + + *outResponseSent = false; + + // get targetFD and check consistency state + + const auto consistencyState = target.getConsistencyState(); + const int targetFD = isBuddyMirrorChunk ? *target.getMirrorFD() : *target.getChunkFD(); + + if(unlikely(consistencyState != TargetConsistencyState_GOOD) && + isBuddyMirrorChunk && + !isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + { // this is a msg to a non-good primary + std::string respMsgLogStr = "Refusing request. Target consistency is not good. " + "targetID: " + StringTk::uintToStr(target.getID()); + + ctx.sendResponse( + GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, std::move(respMsgLogStr))); + + *outResponseSent = true; + return -1; + } + + return targetFD; +} + +/** + * If this is a buddy mirror msg and we are the primary, forward this msg to secondary. + * + * @return _COMMUNICATION if forwarding to buddy failed and buddy is not marked offline (in which + * case *outChunkLocked==false is guaranteed). + * @throw SocketException if sending of GenericResponseMsg fails. + */ +FhgfsOpsErr UnlinkLocalFileMsgEx::forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked) +{ + const char* logContext = "UnlinkLocalFileMsg incoming (forward to secondary)"; + + App* app = Program::getApp(); + ChunkLockStore* chunkLockStore = app->getChunkLockStore(); + + *outChunkLocked = false; + + if(!isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR) || + isMsgHeaderFeatureFlagSet(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ) + return FhgfsOpsErr_SUCCESS; // nothing to do + + // mirrored chunk should be modified, check if resync is in progress and lock chunk + *outChunkLocked = target.getBuddyResyncInProgress(); + if(*outChunkLocked) + chunkLockStore->lockChunk(target.getID(), getEntryID() ); // lock chunk + + // instead of creating a new msg object, we just re-use "this" with "buddymirror second" flag + addMsgHeaderFeatureFlag(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + RequestResponseArgs rrArgs(NULL, this, NETMSGTYPE_UnlinkLocalFileResp); + RequestResponseTarget rrTarget(getTargetID(), app->getTargetMapper(), app->getStorageNodes(), + app->getTargetStateStore(), app->getMirrorBuddyGroupMapper(), true); + + FhgfsOpsErr commRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + // remove the flag that we just added for secondary + unsetMsgHeaderFeatureFlag(UNLINKLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + if(unlikely( + (commRes == FhgfsOpsErr_COMMUNICATION) && + (rrTarget.outTargetReachabilityState == TargetReachabilityState_OFFLINE) ) ) + { + LOG_DEBUG(logContext, Log_DEBUG, std::string("Secondary is offline and will need resync. ") + + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + // buddy is marked offline, so local msg processing will be done and buddy needs resync + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; // go ahead with local msg processing + } + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_DEBUG, "Forwarding failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) + "; " + "error: " + boost::lexical_cast(commRes)); + + if(*outChunkLocked) + { // unlock chunk + chunkLockStore->unlockChunk(target.getID(), getEntryID() ); + *outChunkLocked = false; + } + + std::string genericRespStr = "Communication with secondary failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ); + + ctx.sendResponse(GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, + std::move(genericRespStr))); + + return FhgfsOpsErr_COMMUNICATION; + } + + UnlinkLocalFileRespMsg* respMsg = (UnlinkLocalFileRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr secondaryRes = respMsg->getResult(); + if(unlikely(secondaryRes != FhgfsOpsErr_SUCCESS) ) + { + if(secondaryRes == FhgfsOpsErr_UNKNOWNTARGET) + { + /* local msg processing shall be done and buddy needs resync + (this is normal when a storage is restarted without a broken secondary target, so we + report success to a client in this case) */ + + LogContext(logContext).log(Log_DEBUG, + "Secondary reports unknown target error and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + LogContext(logContext).log(Log_NOTICE, std::string("Secondary reported error: ") + + boost::lexical_cast(secondaryRes) + "; " + "mirror buddy group ID: " + StringTk::uintToStr(getTargetID() ) ); + + return secondaryRes; + } + + + return FhgfsOpsErr_SUCCESS; +} diff --git a/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.h b/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.h new file mode 100644 index 0000000..830d835 --- /dev/null +++ b/storage/source/net/message/storage/creating/UnlinkLocalFileMsgEx.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +class StorageTarget; + +class UnlinkLocalFileMsgEx : public UnlinkLocalFileMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + int getTargetFD(const StorageTarget& target, ResponseContext& ctx, bool* outResponseSent); + FhgfsOpsErr forwardToSecondary(StorageTarget& target, ResponseContext& ctx, + bool* outChunkLocked); +}; + diff --git a/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.cpp b/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.cpp new file mode 100644 index 0000000..11b2a6d --- /dev/null +++ b/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.cpp @@ -0,0 +1,147 @@ +#include + +#include "ListChunkDirIncrementalMsgEx.h" + +bool ListChunkDirIncrementalMsgEx::processIncoming(ResponseContext& ctx) +{ + uint16_t targetID = getTargetID(); + bool isMirror = getIsMirror(); + std::string relativeDir = getRelativeDir(); + int64_t offset = getOffset(); + unsigned maxOutEntries = getMaxOutEntries(); + bool onlyFiles = getOnlyFiles(); + + FhgfsOpsErr result; + StringList names; + IntList entryTypes; + int64_t newOffset{0}; + + result = readChunks(targetID, isMirror, relativeDir, offset, maxOutEntries, onlyFiles, names, + entryTypes, newOffset); + + // send response... + ctx.sendResponse(ListChunkDirIncrementalRespMsg(result, &names, &entryTypes, newOffset) ); + + return true; +} + +/* + * CAUTION: No locking here! + */ +FhgfsOpsErr ListChunkDirIncrementalMsgEx::readChunks(uint16_t targetID, bool isMirror, + std::string& relativeDir, int64_t offset, unsigned maxOutEntries, bool onlyFiles, + StringList& outNames, IntList& outEntryTypes, int64_t &outNewOffset) +{ + App* app = Program::getApp(); + + uint64_t numEntries = 0; + struct dirent* dirEntry = NULL; + + auto* const target = app->getStorageTargets()->getTarget(targetID); + if (!target) + return FhgfsOpsErr_UNKNOWNTARGET; + + const int targetFD = isMirror ? *target->getMirrorFD() : *target->getChunkFD(); + + int dirFD; + + if (likely(!relativeDir.empty())) + dirFD = openat(targetFD, relativeDir.c_str(), O_RDONLY); + else + { + dirFD = dup(targetFD); + fcntl(dirFD, F_SETFL, O_RDONLY); + } + + if(dirFD == -1) + { + int errCode = errno; + + if((errCode != ENOENT) + || (!getIgnoreNotExists())) + { + LogContext(__func__).logErr( + "Unable to open chunks directory; targetID: " + StringTk::uintToStr(targetID) + + "; isMirror: " + StringTk::intToStr((int) isMirror) + "; relativeDir: " + + relativeDir + ". SysErr: " + System::getErrString(errCode)); + } + else + if (errCode == ENOENT) + return FhgfsOpsErr_PATHNOTEXISTS; + + return FhgfsOpsErr_INTERNAL; + } + + DIR* dirHandle = fdopendir(dirFD); + if(!dirHandle) + { + int errCode = errno; + + close(dirFD); + + if((errCode != ENOENT) + || (!getIgnoreNotExists())) + { + LogContext(__func__).logErr( + "Unable to create dir handle; targetID: " + StringTk::uintToStr(targetID) + + "; isMirror: " + StringTk::intToStr((int) isMirror) + "; relativeDir: " + + relativeDir + ". SysErr: " + System::getErrString(errCode)); + } + else + if (errCode == ENOENT) + return FhgfsOpsErr_PATHNOTEXISTS; + + return FhgfsOpsErr_INTERNAL; + } + + errno = 0; // recommended by posix (readdir(3p) ) + + // seek to offset + seekdir(dirHandle, offset); // (seekdir has no return value) + + // the actual entry reading + for(; (numEntries < maxOutEntries) && (dirEntry = StorageTk::readdirFiltered(dirHandle)); + numEntries++) + { + // get the entry type + DirEntryType entryType; + + if(dirEntry->d_type != DT_UNKNOWN) + entryType = StorageTk::direntToDirEntryType(dirEntry->d_type); + else + { + struct stat statBuf; + int statRes = fstatat(dirFD, dirEntry->d_name, &statBuf, 0); + + if(statRes == 0) + entryType = MetadataTk::posixFileTypeToDirEntryType(statBuf.st_mode); + else + entryType = DirEntryType_INVALID; + } + + if ( (entryType != DirEntryType_DIRECTORY) || (!onlyFiles) ) + { + outNames.push_back(dirEntry->d_name); + outEntryTypes.push_back(int(entryType)); + } + + outNewOffset = dirEntry->d_off; + } + + int errnoCopy = errno; // copy value before closedir() for readdir() error check below + + closedir(dirHandle); + + if(!dirEntry && errnoCopy) + { + LogContext(__func__).logErr("Unable to fetch chunk entries. " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "isMirror: " + StringTk::intToStr((int) isMirror) + "; " + "relativeDir: " + relativeDir + "; " + "SysErr: " + System::getErrString(errnoCopy) ); + + return FhgfsOpsErr_INTERNAL; + } + else + return FhgfsOpsErr_SUCCESS; +} diff --git a/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.h b/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.h new file mode 100644 index 0000000..7ad887e --- /dev/null +++ b/storage/source/net/message/storage/listing/ListChunkDirIncrementalMsgEx.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +class ListChunkDirIncrementalMsgEx : public ListChunkDirIncrementalMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + FhgfsOpsErr readChunks(uint16_t targetID, bool isMirror, std::string& relativeDir, + int64_t offset, unsigned maxOutEntries, bool onlyFiles, StringList& outNames, + IntList& outEntryTypes, int64_t &outNewOffset); +}; + diff --git a/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.cpp b/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.cpp new file mode 100644 index 0000000..6860159 --- /dev/null +++ b/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.cpp @@ -0,0 +1,21 @@ +#include "GetStorageResyncStatsMsgEx.h" + +#include +#include + +bool GetStorageResyncStatsMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + BuddyResyncer* buddyResyncer = app->getBuddyResyncer(); + uint16_t targetID = getTargetID(); + StorageBuddyResyncJobStatistics jobStats; + + BuddyResyncJob* resyncJob = buddyResyncer->getResyncJob(targetID); + + if (resyncJob) + resyncJob->getJobStats(jobStats); + + ctx.sendResponse(GetStorageResyncStatsRespMsg(&jobStats) ); + + return true; +} diff --git a/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.h b/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.h new file mode 100644 index 0000000..9d548e6 --- /dev/null +++ b/storage/source/net/message/storage/mirroring/GetStorageResyncStatsMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class GetStorageResyncStatsMsgEx : public GetStorageResyncStatsMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.cpp b/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.cpp new file mode 100644 index 0000000..f6a2690 --- /dev/null +++ b/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.cpp @@ -0,0 +1,344 @@ +#include +#include +#include +#include +#include + +#include + +#include "ResyncLocalFileMsgEx.h" + +bool ResyncLocalFileMsgEx::processIncoming(ResponseContext& ctx) +{ + App* app = Program::getApp(); + ChunkStore* chunkStore = app->getChunkDirStore(); + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + const char* dataBuf = getDataBuf(); + uint16_t targetID = getResyncToTargetID(); + size_t count = getCount(); + int64_t offset = getOffset(); + std::string relativeChunkPathStr = getRelativePathStr(); + int writeErrno; + bool writeRes; + StorageTarget* target; + + int openFlags = O_WRONLY | O_CREAT; + SessionQuotaInfo quotaInfo(false, false, 0, 0); + // mode_t fileMode = STORAGETK_DEFAULTCHUNKFILEMODE; + + int targetFD; + int fd; + FhgfsOpsErr openRes; + + // should only be used for chunk balancing, to sync data to both buddies + if(isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_CHUNKBALANCE_BUDDYMIRROR) ) + { // given targetID refers to a buddy mirror group + MirrorBuddyGroupMapper* mirrorBuddies = app->getMirrorBuddyGroupMapper(); + targetID = isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND) ? + mirrorBuddies->getSecondaryTargetID(targetID) : + mirrorBuddies->getPrimaryTargetID(targetID); + + if(unlikely(!targetID) ) + { // unknown target + LogContext(__func__).logErr("Invalid mirror buddy group ID: " + + StringTk::uintToStr(!targetID ) ); + retVal = FhgfsOpsErr_UNKNOWNTARGET; + goto send_response; + } + } + + target = app->getStorageTargets()->getTarget(targetID); + if (!target) + { + LogContext(__func__).logErr( + "Error resyncing chunk; Could not open FD; chunkPath: " + + relativeChunkPathStr); + retVal = FhgfsOpsErr_PATHNOTEXISTS; + goto send_response; + } + + retVal = forwardToSecondary(*target, ctx); + //check if path is relative to buddy mirror dir or chunks dir + targetFD = isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR) + ? *target->getMirrorFD() + : *target->getChunkFD(); + + // always truncate when we write the very first block of a file + if(!offset && !isMsgHeaderFeatureFlagSet (RESYNCLOCALFILEMSG_FLAG_NODATA) ) + openFlags |= O_TRUNC; + + openRes = chunkStore->openChunkFile(targetFD, NULL, relativeChunkPathStr, true, + openFlags, &fd, "aInfo, {}); + + if (openRes != FhgfsOpsErr_SUCCESS) + { + LogContext(__func__).logErr( + "Error resyncing chunk; Could not open FD; chunkPath: " + + relativeChunkPathStr); + retVal = FhgfsOpsErr_PATHNOTEXISTS; + + target->setState(TargetConsistencyState_BAD); + + goto send_response; + } + + if(isMsgHeaderFeatureFlagSet (RESYNCLOCALFILEMSG_FLAG_NODATA)) // do not sync actual data + goto set_attribs; + + if(isMsgHeaderFeatureFlagSet (RESYNCLOCALFILEMSG_CHECK_SPARSE)) + writeRes = doWriteSparse(fd, dataBuf, count, offset, writeErrno); + else + writeRes = doWrite(fd, dataBuf, count, offset, writeErrno); + + if(unlikely(!writeRes) ) + { // write error occured (could also be e.g. disk full) + LogContext(__func__).logErr( + "Error resyncing chunk; chunkPath: " + relativeChunkPathStr + "; error: " + + System::getErrString(writeErrno)); + + target->setState(TargetConsistencyState_BAD); + + retVal = FhgfsOpsErrTk::fromSysErr(writeErrno); + } + + if(isMsgHeaderFeatureFlagSet (RESYNCLOCALFILEMSG_FLAG_TRUNC)) + { + int truncErrno; + // we trunc after a possible write, so we need to trunc at offset+count + bool truncRes = doTrunc(fd, offset + count, truncErrno); + + if(!truncRes) + { + LogContext(__func__).logErr( + "Error resyncing chunk; chunkPath: " + relativeChunkPathStr + "; error: " + + System::getErrString(truncErrno)); + + target->setState(TargetConsistencyState_BAD); + + retVal = FhgfsOpsErrTk::fromSysErr(truncErrno); + } + } + +set_attribs: + + if(isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_SETATTRIBS) && + (retVal == FhgfsOpsErr_SUCCESS)) + { + SettableFileAttribs* attribs = getChunkAttribs(); + // update mode + int chmodRes = fchmod(fd, attribs->mode); + if(chmodRes == -1) + { // could be an error + int errCode = errno; + + if(errCode != ENOENT) + { // unhandled chmod() error + LogContext(__func__).logErr("Unable to change file mode: " + relativeChunkPathStr + + ". SysErr: " + System::getErrString()); + } + } + + // update UID and GID... + int chownRes = fchown(fd, attribs->userID, attribs->groupID); + if(chownRes == -1) + { // could be an error + int errCode = errno; + + if(errCode != ENOENT) + { // unhandled chown() error + LogContext(__func__).logErr( "Unable to change file owner: " + relativeChunkPathStr + + ". SysErr: " + System::getErrString()); + } + } + + if((chmodRes == -1) || (chownRes == -1)) + { + target->setState(TargetConsistencyState_BAD); + retVal = FhgfsOpsErr_INTERNAL; + } + } + + close(fd); + + +send_response: + ctx.sendResponse(ResyncLocalFileRespMsg(retVal) ); + + return true; +} + +/** + * Write until everything was written (handle short-writes) or an error occured + */ +bool ResyncLocalFileMsgEx::doWrite(int fd, const char* buf, size_t count, off_t offset, + int& outErrno) +{ + size_t sumWriteRes = 0; + + do + { + ssize_t writeRes = + MsgHelperIO::pwrite(fd, buf + sumWriteRes, count - sumWriteRes, offset + sumWriteRes); + + if (unlikely(writeRes == -1) ) + { + sumWriteRes = (sumWriteRes > 0) ? sumWriteRes : writeRes; + outErrno = errno; + return false; + } + + sumWriteRes += writeRes; + + } while (sumWriteRes != count); + + return true; +} + +/** + * Write until everything was written (handle short-writes) or an error occured + */ +bool ResyncLocalFileMsgEx::doWriteSparse(int fd, const char* buf, size_t count, off_t offset, + int& outErrno) +{ + size_t sumWriteRes = 0; + const char zeroBuf[ RESYNCER_SPARSE_BLOCK_SIZE ] = { 0 }; + + do + { + size_t cmpLen = BEEGFS_MIN(count - sumWriteRes, RESYNCER_SPARSE_BLOCK_SIZE); + int cmpRes = memcmp(buf + sumWriteRes, zeroBuf, cmpLen); + + if(!cmpRes) + { // sparse area + sumWriteRes += cmpLen; + + if(sumWriteRes == count) + { // end of buf + // we must trunc here because this might be the end of the file + int truncRes = ftruncate(fd, offset+count); + + if(unlikely(truncRes == -1) ) + { + outErrno = errno; + return false; + } + } + } + else + { // non-sparse area + ssize_t writeRes = MsgHelperIO::pwrite(fd, buf + sumWriteRes, cmpLen, + offset + sumWriteRes); + + if(unlikely(writeRes == -1)) + { + outErrno = errno; + return false; + } + + sumWriteRes += writeRes; + } + + } while (sumWriteRes != count); + + return true; +} + +bool ResyncLocalFileMsgEx::doTrunc(int fd, off_t length, int& outErrno) +{ + int truncRes = ftruncate(fd, length); + + if (truncRes == -1) + { + outErrno = errno; + return false; + } + + return true; +} + +/** + * If this is a buddy mirror msg and we are the primary, forward this msg to secondary. + * + * @return _COMMUNICATION if forwarding to buddy failed and buddy is not marked offline (in which + * case *outChunkLocked==false is guaranteed). + * @throw SocketException if sending of GenericResponseMsg fails. + */ +FhgfsOpsErr ResyncLocalFileMsgEx::forwardToSecondary(StorageTarget& target, ResponseContext& ctx) +{ + const char* logContext = "ResyncLocalFileMsg incoming (forward to secondary)"; + + App* app = Program::getApp(); + + if(!isMsgHeaderFeatureFlagSet(RESYNCLOCALFILEMSG_FLAG_CHUNKBALANCE_BUDDYMIRROR)) + return FhgfsOpsErr_SUCCESS; // nothing to do + + // instead of creating a new msg object, we just re-use "this" with "buddymirror second" flag + addMsgHeaderFeatureFlag(RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + RequestResponseArgs rrArgs(NULL, this, NETMSGTYPE_ResyncLocalFileResp); + RequestResponseTarget rrTarget(getResyncToTargetID(), app->getTargetMapper(), app->getStorageNodes(), + app->getTargetStateStore(), app->getMirrorBuddyGroupMapper(), true); + + FhgfsOpsErr commRes = MessagingTk::requestResponseTarget(&rrTarget, &rrArgs); + + // remove the flag that we just added for secondary + unsetMsgHeaderFeatureFlag(RESYNCLOCALFILEMSG_FLAG_BUDDYMIRROR_SECOND); + + if(unlikely( + (commRes == FhgfsOpsErr_COMMUNICATION) && + (rrTarget.outTargetReachabilityState == TargetReachabilityState_OFFLINE) ) ) + { + LogContext(logContext).log(Log_DEBUG, "Secondary is offline and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getResyncToTargetID() )); + + // buddy is marked offline, so local msg processing will be done and buddy needs resync + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; // go ahead with local msg processing + } + + if(unlikely(commRes != FhgfsOpsErr_SUCCESS) ) + { + LogContext(logContext).log(Log_DEBUG, "Forwarding failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getResyncToTargetID() ) + "; " + "error: " + std::to_string(commRes)); + + std::string genericRespStr = "Communication with secondary failed. " + "mirror buddy group ID: " + StringTk::uintToStr(getResyncToTargetID() ); + + ctx.sendResponse(GenericResponseMsg(GenericRespMsgCode_INDIRECTCOMMERR, + std::move(genericRespStr))); + + return FhgfsOpsErr_COMMUNICATION; + } + + ResyncLocalFileRespMsg* respMsg = (ResyncLocalFileRespMsg*)rrArgs.outRespMsg.get(); + FhgfsOpsErr secondaryRes = respMsg->getResult(); + if(unlikely(secondaryRes != FhgfsOpsErr_SUCCESS) ) + { + if(secondaryRes == FhgfsOpsErr_UNKNOWNTARGET) + { + /* local msg processing shall be done and buddy needs resync + (this is normal when a storage is restarted without a broken secondary target, so we + report success to a client in this case) */ + + LogContext(logContext).log(Log_DEBUG, + "Secondary reports unknown target error and will need resync. " + "mirror buddy group ID: " + StringTk::uintToStr(getResyncToTargetID() ) ); + + target.setBuddyNeedsResync(true); + + return FhgfsOpsErr_SUCCESS; + } + + LogContext(logContext).log(Log_NOTICE, std::string("Secondary reported error: ") + + std::to_string(secondaryRes) + "; " + "mirror buddy group ID: " + StringTk::uintToStr(getResyncToTargetID()) ); + + return secondaryRes; + } + + + return FhgfsOpsErr_SUCCESS; +} diff --git a/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.h b/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.h new file mode 100644 index 0000000..7fbddd4 --- /dev/null +++ b/storage/source/net/message/storage/mirroring/ResyncLocalFileMsgEx.h @@ -0,0 +1,17 @@ +#pragma once + +#include +#include + +class ResyncLocalFileMsgEx : public ResyncLocalFileMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + bool doWrite(int fd, const char* buf, size_t count, off_t offset, int& outErrno); + bool doWriteSparse(int fd, const char* buf, size_t count, off_t offset, int& outErrno); + bool doTrunc(int fd, off_t length, int& outErrno); + FhgfsOpsErr forwardToSecondary(StorageTarget& target, ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.cpp b/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.cpp new file mode 100644 index 0000000..96ac90f --- /dev/null +++ b/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.cpp @@ -0,0 +1,34 @@ +#include "SetLastBuddyCommOverrideMsgEx.h" + +#include +#include + +bool SetLastBuddyCommOverrideMsgEx::processIncoming(ResponseContext& ctx) +{ + uint16_t targetID = getTargetID(); + int64_t timestamp = getTimestamp(); + bool abortResync = getAbortResync(); + + App* app = Program::getApp(); + StorageTargets* storageTargets = app->getStorageTargets(); + + const auto target = storageTargets->getTarget(targetID); + if (!target) + { + ctx.sendResponse(SetLastBuddyCommOverrideRespMsg(FhgfsOpsErr_UNKNOWNTARGET)); + return true; + } + + target->setLastBuddyComm(std::chrono::system_clock::from_time_t(timestamp), true); + + if (abortResync) + { + BuddyResyncJob* resyncJob = app->getBuddyResyncer()->getResyncJob(targetID); + if (resyncJob) + resyncJob->abort(); + } + + ctx.sendResponse(SetLastBuddyCommOverrideRespMsg(FhgfsOpsErr_SUCCESS)); + + return true; +} diff --git a/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.h b/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.h new file mode 100644 index 0000000..863ffeb --- /dev/null +++ b/storage/source/net/message/storage/mirroring/SetLastBuddyCommOverrideMsgEx.h @@ -0,0 +1,11 @@ +#pragma once + +#include +#include + +class SetLastBuddyCommOverrideMsgEx : public SetLastBuddyCommOverrideMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp b/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp new file mode 100644 index 0000000..2add92e --- /dev/null +++ b/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include + +#include "StorageResyncStartedMsgEx.h" + +bool StorageResyncStartedMsgEx::processIncoming(ResponseContext& ctx) +{ + uint16_t targetID = getValue(); + + deleteMirrorSessions(targetID); + + ctx.sendResponse(StorageResyncStartedRespMsg() ); + + return true; +} + +void StorageResyncStartedMsgEx::deleteMirrorSessions(uint16_t targetID) +{ + SessionStore* sessions = Program::getApp()->getSessions(); + NumNodeIDList sessionIDs; + sessions->getAllSessionIDs(&sessionIDs); + + for (NumNodeIDListCIter iter = sessionIDs.begin(); iter != sessionIDs.end(); iter++) + { + auto session = sessions->referenceSession(*iter); + + if (!session) // meanwhile deleted + continue; + + SessionLocalFileStore* sessionLocalFiles = session->getLocalFiles(); + sessionLocalFiles->removeAllMirrorSessions(targetID); + } +} diff --git a/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h b/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h new file mode 100644 index 0000000..c962e95 --- /dev/null +++ b/storage/source/net/message/storage/mirroring/StorageResyncStartedMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +class StorageResyncStartedMsgEx : public StorageResyncStartedMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); + + private: + void deleteMirrorSessions(uint16_t targetID); +}; + diff --git a/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.cpp b/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.cpp new file mode 100644 index 0000000..e533267 --- /dev/null +++ b/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.cpp @@ -0,0 +1,81 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "GetQuotaInfoMsgEx.h" + + + +bool GetQuotaInfoMsgEx::processIncoming(ResponseContext& ctx) +{ + const char* logContext = "GetQuotaInfo (GetQuotaInfoMsg incoming)"; + + App *app = Program::getApp(); + + QuotaBlockDeviceMap quotaBlockDevices; + QuotaDataList outQuotaDataList; + ZfsSession session; + QuotaInodeSupport quotaInodeSupport = QuotaInodeSupport_UNKNOWN; + + switch(getTargetSelection() ) + { + case GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST: + { + size_t withQuota = 0; + + for (const auto& target : app->getStorageTargets()->getTargets()) + { + quotaBlockDevices.emplace(target.first, target.second->getQuotaBlockDevice()); + if (target.second->getQuotaBlockDevice().supportsInodeQuota()) + withQuota++; + } + + if (withQuota == 0) + quotaInodeSupport = QuotaInodeSupport_NO_BLOCKDEVICES; + else if (withQuota == quotaBlockDevices.size()) + quotaInodeSupport = QuotaInodeSupport_ALL_BLOCKDEVICES; + else + quotaInodeSupport = QuotaInodeSupport_SOME_BLOCKDEVICES; + break; + } + case GETQUOTACONFIG_ALL_TARGETS_ONE_REQUEST_PER_TARGET: + case GETQUOTACONFIG_SINGLE_TARGET: + if (auto* const target = app->getStorageTargets()->getTarget(getTargetNumID())) + { + quotaBlockDevices.emplace(getTargetNumID(), target->getQuotaBlockDevice()); + quotaInodeSupport = target->getQuotaBlockDevice().quotaInodeSupportFromBlockDevice(); + } + break; + } + + if(quotaBlockDevices.empty() ) + /* no quota data available but do not return an error during message processing, it's not + the correct place for error handling in this case */ + LogContext(logContext).logErr("Error: no quota block devices."); + else + { + if(getQueryType() == QUERY_TYPE_SINGLE_ID) + QuotaTk::appendQuotaForID(getIDRangeStart(), getType(), "aBlockDevices, + &outQuotaDataList, &session); + else + if(getQueryType() == QUERY_TYPE_ID_RANGE) + QuotaTk::requestQuotaForRange("aBlockDevices, getIDRangeStart(), + getIDRangeEnd(), getType(), &outQuotaDataList, &session); + else + if(getQueryType() == QUERY_TYPE_ID_LIST) + { + QuotaTk::requestQuotaForList("aBlockDevices, getIDList(), getType(), + &outQuotaDataList, &session); + } + } + + // send response + ctx.sendResponse(GetQuotaInfoRespMsg(&outQuotaDataList, quotaInodeSupport) ); + + return true; +} diff --git a/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.h b/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.h new file mode 100644 index 0000000..af79070 --- /dev/null +++ b/storage/source/net/message/storage/quota/GetQuotaInfoMsgEx.h @@ -0,0 +1,14 @@ +#pragma once + + +#include +#include + + + +class GetQuotaInfoMsgEx : public GetQuotaInfoMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp b/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp new file mode 100644 index 0000000..f7163d6 --- /dev/null +++ b/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.cpp @@ -0,0 +1,56 @@ +#include +#include +#include "program/Program.h" + +#include "SetExceededQuotaMsgEx.h" + + +bool SetExceededQuotaMsgEx::processIncoming(ResponseContext& ctx) +{ + bool retVal = true; + FhgfsOpsErr errorCode = FhgfsOpsErr_SUCCESS; + + if(Program::getApp()->getConfig()->getQuotaEnableEnforcement() ) + { + // get the storage pool for which quota is exceeded + StoragePoolPtr storagePool = + Program::getApp()->getStoragePoolStore()->getPool(getStoragePoolId()); + + if (!storagePool) + { + LOG(QUOTA, WARNING, "Couldn't set exceeded quota, " + "because requested storage pool doesn't exist on storage server.", + ("storagePoolId", getStoragePoolId())); + + errorCode = FhgfsOpsErr_UNKNOWNPOOL; + + goto send_response; + } + + // see if any of our targets belong to this pool and, if yes, set exceeded quota info + for (const auto& mapping : Program::getApp()->getStorageTargets()->getTargets()) + { + if (storagePool->hasTarget(mapping.first)) + { + // update exceeded quota + Program::getApp()->getExceededQuotaStores()->get(mapping.first)-> + updateExceededQuota(getExceededQuotaIDs(), getQuotaDataType(), getExceededType() ); + } + } + } + else + { + LOG(QUOTA, ERR, "Unable to set exceeded quota IDs. Configuration problem detected. " + "The management daemon on " + ctx.peerName() + " has quota enforcement enabled, " + "but not this storage daemon. Fix this configuration problem or quota enforcement " + "will not work correctly. If quota enforcement settings have changed recently in the " + "mgmtd configuration, please restart all BeeGFS services."); + + errorCode = FhgfsOpsErr_INTERNAL; + } + +send_response: + ctx.sendResponse(SetExceededQuotaRespMsg(errorCode) ); + + return retVal; +} diff --git a/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.h b/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.h new file mode 100644 index 0000000..5b28600 --- /dev/null +++ b/storage/source/net/message/storage/quota/SetExceededQuotaMsgEx.h @@ -0,0 +1,13 @@ +#pragma once + + +#include +#include + + +class SetExceededQuotaMsgEx : public SetExceededQuotaMsg +{ + public: + virtual bool processIncoming(ResponseContext& ctx); +}; + diff --git a/storage/source/net/msghelpers/MsgHelperIO.h b/storage/source/net/msghelpers/MsgHelperIO.h new file mode 100644 index 0000000..fbff527 --- /dev/null +++ b/storage/source/net/msghelpers/MsgHelperIO.h @@ -0,0 +1,193 @@ +#pragma once + +#include +#include +#include +#include + +/* The kernel has a weird read-ahead size limitation, but from userspace only. If this ever will + * be abondoned, we need to make a config option for those future kernels. */ +#define MAX_KERNEL_READAHEAD (2 * 1024 * 1024) + + +#if ( (_XOPEN_SOURCE >= 700 && _POSIX_C_SOURCE >= 200809L) && (defined (AT_SYMLINK_NOFOLLOW) ) ) + #define MsgHelperIO_USE_UTIMENSAT +#endif + +// positions in struct timeval and struct timespec +#define MsgHelperIO_ATIME_POS 0 +#define MsgHelperIO_MTIME_POS 1 + +#ifndef UTIME_OMIT +#define UTIME_OMIT ((1l << 30) - 2l) +#endif + + +/** + * Wrappers for basic IO syscalls. + * + * This class has no really good reason to exist since we do no longer have the SingleIOWorker, + * which was based on not directly calling the syscalls from the current thread. + * However, it's still nice to have this central place for IO routines and since it doesn't impose + * any overhead, we just keep it for now. + */ +class MsgHelperIO +{ + public: + + + private: + MsgHelperIO() {} + + + public: + // inliners + static int open(const char* pathname, int flags, mode_t mode) + { + return ::open(pathname, flags, mode); + } + + static int openat(const int dirFD, const char* pathname, int flags, mode_t mode) + { + return ::openat(dirFD, pathname, flags, mode); + } + + static int close(int fd) + { + return ::close(fd); + } + + static ssize_t read(int fd, void* buf, size_t count) + { + return ::read(fd, buf, count); + } + + static ssize_t pread(int fd, void* buf, size_t count, off_t offset) + { + return ::pread(fd, buf, count, offset); + } + + static ssize_t write(int fd, const void* buf, size_t count) + { + return ::write(fd, buf, count); + } + + static ssize_t pwrite(int fd, const void* buf, size_t count, off_t offset) + { + return ::pwrite(fd, buf, count, offset); + } + + + static off_t lseek(int fd, off_t offset, int whence) + { + return ::lseek(fd, offset, whence); + } + + static int fsync(int fd) + { + return ::fsync(fd); + } + + /** + * Sync a given range of the given fd. + * + * Note: This is a linux specific call, which appeared in linux-2.6.17 and glibc 2.6 + * (so it is not available in SLES10/RHEL5). + */ + static int syncFileRange(int fd, off64_t offset, off64_t nbytes) + { + #ifdef CONFIG_DISTRO_HAS_SYNC_FILE_RANGE + return sync_file_range(fd, offset, nbytes, SYNC_FILE_RANGE_WRITE); + #else + return 0; + #endif + } + + /** + * Advise the kernel to read-ahead the given amount of data. Especially for block based + * file systems this is an asynchronous call one the IO has reached the bio layer. + */ + static int readAhead(int fd, off_t offset, off_t remainingLen) + { + int raRes = 0; + + while (remainingLen > 0) + { + size_t readSize = BEEGFS_MIN(MAX_KERNEL_READAHEAD, remainingLen); + + raRes = posix_fadvise(fd, offset, readSize, POSIX_FADV_WILLNEED); + if (unlikely(raRes) ) + break; + + offset += readSize; + remainingLen -= readSize; + + } + + return raRes; + } + + /** + * Simulate non-existing truncateat() + */ + static int truncateAt(const int dirFD, const char *path, const off_t length) + { + int fd = ::openat(dirFD, path, O_WRONLY); + if (fd == -1) + return -1; + + int truncRes = ::ftruncate(fd, length); + + int closeRes = ::close(fd); + + int retVal = 0; + + if (truncRes == -1 || closeRes == -1) + retVal = -1; + + return retVal; + } + + static int utimensat(int dirFD, const char *path, const struct timespec timeSpec[2], + int flags) + { +#ifdef MsgHelperIO_USE_UTIMENSAT + return ::utimensat(dirFD, path, timeSpec, flags); +#else + struct timeval timeVal[2]; + + /* note: Below we rely on the fact that at least either atime or mtime are to be set. + * futimesat() is an intermediate linux only call, deprecated by utimensat, but + * it is available on SLES10 and RHEL5, so we can use it for these old platforms + * where utimensat() was not available yet. */ + + if (timeSpec[MsgHelperIO_ATIME_POS].tv_nsec == UTIME_OMIT) + { /* atime is not supposed to be set, correct would be to stat the current atime and then + * to set this value here. For now we simply set the mtime */ + timeVal[MsgHelperIO_ATIME_POS].tv_sec = timeSpec[MsgHelperIO_MTIME_POS].tv_sec; + timeVal[MsgHelperIO_ATIME_POS].tv_usec = timeSpec[MsgHelperIO_MTIME_POS].tv_nsec / 1000; + } + else + { // client request an atime update + timeVal[MsgHelperIO_ATIME_POS].tv_sec = timeSpec[MsgHelperIO_ATIME_POS].tv_sec; + timeVal[MsgHelperIO_ATIME_POS].tv_usec = timeSpec[MsgHelperIO_ATIME_POS].tv_nsec / 1000; + } + + if (timeSpec[MsgHelperIO_MTIME_POS].tv_nsec == UTIME_OMIT) + { /* similar to atime above, but this time mtime is not supposed to be set, we use the + * the atime value instead */ + timeVal[MsgHelperIO_MTIME_POS].tv_sec = timeSpec[MsgHelperIO_ATIME_POS].tv_sec; + timeVal[MsgHelperIO_MTIME_POS].tv_usec = timeSpec[MsgHelperIO_ATIME_POS].tv_nsec / 1000; + } + else + { // client request an mtime update + timeVal[MsgHelperIO_MTIME_POS].tv_sec = timeSpec[MsgHelperIO_MTIME_POS].tv_sec; + timeVal[MsgHelperIO_MTIME_POS].tv_usec = timeSpec[MsgHelperIO_MTIME_POS].tv_nsec / 1000; + } + + return ::futimesat(dirFD, path, timeVal); + +#endif + } +}; + diff --git a/storage/source/nodes/StorageNodeOpStats.h b/storage/source/nodes/StorageNodeOpStats.h new file mode 100644 index 0000000..8341123 --- /dev/null +++ b/storage/source/nodes/StorageNodeOpStats.h @@ -0,0 +1,115 @@ +#pragma once + + +#include +#include +#include +#include + +/** + * Per-user storage server operation statistics. + */ +struct UserStorageOpStats +{ + AtomicUInt64 numOps; // number of operations done by this user (incl read and write) + AtomicUInt64 numBytesWritten; // number of bytes written by this user + AtomicUInt64 numBytesRead; // number of bytes read by this user +}; + + +/** + * Count storage filesystem operations + */ +class StorageNodeOpStats : public NodeOpStats +{ + public: + + /** + * Update operation counter for the given nodeIP and userID. + * + * @param node IP of the node + * @param operation the filesystem operation to count + * + * NOTE: If you need to change something here, don't forget to update the similar method + * below. + */ + void updateNodeOp(unsigned nodeIP, StorageOpCounterTypes opType, unsigned userID) + { + SafeRWLock safeLock(&lock, SafeRWLock_READ); + + NodeOpCounterMapIter nodeIter = clientCounterMap.find(nodeIP); + NodeOpCounterMapIter userIter = userCounterMap.find(userID); + + if( (nodeIter == clientCounterMap.end() ) || + (userIter == userCounterMap.end() ) ) + { // nodeIP or userID NOT found in map yet, we need a write lock + + safeLock.unlock(); // possible race, but insert below is a NOOP if key already exists + safeLock.lock(SafeRWLock_WRITE); + + if(nodeIter == clientCounterMap.end() ) + { + nodeIter = clientCounterMap.insert( + NodeOpCounterMapVal(nodeIP, StorageOpCounter() ) ).first; + } + + if(userIter == userCounterMap.end() ) + { + userIter = userCounterMap.insert( + NodeOpCounterMapVal(userID, StorageOpCounter() ) ).first; + } + } + + nodeIter->second.increaseOpCounter(opType); + userIter->second.increaseOpCounter(opType); + + safeLock.unlock(); + } + + /** + * Update operation counter for the given nodeIP and userID. + * Almost similar as above, it just takes the additional bytes parameter and calls + * increaseOpBytes(). + * + * NOTE: If you need to change something here, don't forget to update the similar method + * above. + * + * @param opType only StorageOpCounter_WRITEOPS and _READOPS allowed as value, they will be + * internally converted to the corresponding PerUserStorageOpCounter types. + */ + void updateNodeOp(unsigned nodeIP, StorageOpCounterTypes operation, uint64_t bytes, + unsigned userID) + { + SafeRWLock safeLock(&lock, SafeRWLock_READ); + + NodeOpCounterMapIter nodeIter = clientCounterMap.find(nodeIP); + NodeOpCounterMapIter userIter = userCounterMap.find(userID); + + if( (nodeIter == clientCounterMap.end() ) || + (userIter == userCounterMap.end() ) ) + { // nodeID or userID NOT found in map yet, we need a write lock + + safeLock.unlock(); // possible race, but insert below is a NOOP if key already exists + safeLock.lock(SafeRWLock_WRITE); + + if(nodeIter == clientCounterMap.end() ) + { + nodeIter = clientCounterMap.insert( + NodeOpCounterMapVal(nodeIP, StorageOpCounter() ) ).first; + } + + if(userIter == userCounterMap.end() ) + { + userIter = userCounterMap.insert( + NodeOpCounterMapVal(userID, StorageOpCounter() ) ).first; + } + } + + nodeIter->second.increaseStorageOpBytes(operation, bytes); + userIter->second.increaseStorageOpBytes(operation, bytes); + + safeLock.unlock(); + } + +}; + diff --git a/storage/source/program/Main.cpp b/storage/source/program/Main.cpp new file mode 100644 index 0000000..e989ceb --- /dev/null +++ b/storage/source/program/Main.cpp @@ -0,0 +1,7 @@ +#include "Program.h" + +int main(int argc, char** argv) +{ + return Program::main(argc, argv); +} + diff --git a/storage/source/program/Program.cpp b/storage/source/program/Program.cpp new file mode 100644 index 0000000..4794b5f --- /dev/null +++ b/storage/source/program/Program.cpp @@ -0,0 +1,23 @@ +#include +#include "Program.h" + +App* Program::app = NULL; + +int Program::main(int argc, char** argv) +{ + BuildTypeTk::checkDebugBuildTypes(); + + AbstractApp::runTimeInitsAndChecks(); // must be called before creating a new App + + app = new App(argc, argv); + + app->startInCurrentThread(); + + int appRes = app->getAppResult(); + + delete app; + + return appRes; +} + + diff --git a/storage/source/program/Program.h b/storage/source/program/Program.h new file mode 100644 index 0000000..bc5fa7e --- /dev/null +++ b/storage/source/program/Program.h @@ -0,0 +1,23 @@ +#pragma once + +#include + +class Program +{ + public: + static int main(int argc, char** argv); + + private: + Program() {} + + static App* app; + + public: + // getters & setters + static App* getApp() + { + return app; + } + +}; + diff --git a/storage/source/session/Session.cpp b/storage/source/session/Session.cpp new file mode 100644 index 0000000..3e71a15 --- /dev/null +++ b/storage/source/session/Session.cpp @@ -0,0 +1,24 @@ +#include +#include "Session.h" + + +/* Merges the SessionLocalFiles of the given session into this session, only not existing + * SessionLocalFiles will be added to the existing session + * @param session the session which will be merged with this session + */ +void Session::mergeSessionLocalFiles(Session* session) +{ + this->getLocalFiles()->mergeSessionLocalFiles(session->getLocalFiles()); +} + +void Session::serializeForTarget(Serializer& ser, uint16_t targetID) +{ + ser % sessionID; + localFiles.serializeForTarget(ser, targetID); +} + +void Session::deserializeForTarget(Deserializer& des, uint16_t targetID) +{ + des % sessionID; + localFiles.deserializeForTarget(des, targetID); +} diff --git a/storage/source/session/Session.h b/storage/source/session/Session.h new file mode 100644 index 0000000..e8e0729 --- /dev/null +++ b/storage/source/session/Session.h @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include "SessionLocalFileStore.h" + +/* + * A session always belongs to a client ID, therefore the session ID is always the nodeID of the + * corresponding client + */ +class Session +{ + public: + Session(const NumNodeID sessionID) : sessionID(sessionID) {} + + /* + * For deserialization only + */ + Session() {}; + + void mergeSessionLocalFiles(Session* session); + + void serializeForTarget(Serializer& ser, uint16_t targetID); + void deserializeForTarget(Deserializer& des, uint16_t targetID); + + private: + NumNodeID sessionID; + + SessionLocalFileStore localFiles; + + public: + // getters & setters + NumNodeID getSessionID() const + { + return sessionID; + } + + SessionLocalFileStore* getLocalFiles() + { + return &localFiles; + } +}; + + diff --git a/storage/source/session/SessionLocalFile.cpp b/storage/source/session/SessionLocalFile.cpp new file mode 100644 index 0000000..61f550d --- /dev/null +++ b/storage/source/session/SessionLocalFile.cpp @@ -0,0 +1,168 @@ +#include +#include +#include +#include +#include + +#include "SessionLocalFile.h" + +bool SessionLocalFile::Handle::close() +{ + if (!fd.valid()) + return true; + + if (const int err = fd.close()) + { + LOG(GENERAL, ERR, "Unable to close local file.", sysErr(err), id); + return false; + } + else + { + LOG(GENERAL, DEBUG, "Local file closed.", id); + return true; + } +} + +void SessionLocalFile::serializeNodeID(SessionLocalFile* obj, Deserializer& des) +{ + uint16_t mirrorNodeID; + + des % mirrorNodeID; + if (unlikely(!des.good())) + return; + + if(mirrorNodeID) + { + NodeStoreServers* nodeStore = Program::getApp()->getStorageNodes(); + auto node = nodeStore->referenceNode(NumNodeID(mirrorNodeID) ); + + if(!node) + des.setBad(); + + obj->mirrorNode = node; + } +} + +/** + * Open a chunkFile for this session + * + * @param quotaInfo may be NULL if not isWriteOpen (i.e. if the file will not be created). + * @param isWriteOpen if set to true, the file will be created if it didn't exist. + */ +FhgfsOpsErr SessionLocalFile::openFile(int targetFD, const PathInfo* pathInfo, + bool isWriteOpen, const SessionQuotaInfo* quotaInfo) +{ + FhgfsOpsErr retVal = FhgfsOpsErr_SUCCESS; + + App* app = Program::getApp(); + Logger* log = Logger::getLogger(); + const char* logContext = "SessionLocalFile (open)"; + + + if (handle->fd.valid()) // no lock here as optimization, with lock below + return FhgfsOpsErr_SUCCESS; // file already open + + + std::lock_guard const lock(sessionMutex); + + + if (handle->fd.valid()) + { + // file already open (race with another thread) => nothing more to do here + } + else + { // open chunk file (and create dir if necessary)... + + std::string entryID = getFileID(); + Path chunkDirPath; + std::string chunkFilePathStr; + bool hasOrigFeature = pathInfo->hasOrigFeature(); + + StorageTk::getChunkDirChunkFilePath(pathInfo, entryID, hasOrigFeature, chunkDirPath, + chunkFilePathStr); + + ChunkStore* chunkDirStore = app->getChunkDirStore(); + + int fd = -1; + + if (isWriteOpen) + { // chunk needs to be created if not exists + int openFlags = O_CREAT | this->openFlags; + + const ExceededQuotaStorePtr exceededQuotaStore = + app->getExceededQuotaStores()->get(getTargetID()); + + FhgfsOpsErr openChunkRes = chunkDirStore->openChunkFile( + targetFD, &chunkDirPath, chunkFilePathStr, hasOrigFeature, openFlags, &fd, quotaInfo, + exceededQuotaStore); + + // fix chunk path permissions + if (unlikely(openChunkRes == FhgfsOpsErr_NOTOWNER && quotaInfo->useQuota) ) + { + // it already logs a message, so need to further check this ret value + chunkDirStore->chmodV2ChunkDirPath(targetFD, &chunkDirPath, entryID); + + openChunkRes = chunkDirStore->openChunkFile( + targetFD, &chunkDirPath, chunkFilePathStr, hasOrigFeature, openFlags, &fd, + quotaInfo, exceededQuotaStore); + } + + if (openChunkRes != FhgfsOpsErr_SUCCESS) + { + if (openChunkRes == FhgfsOpsErr_INTERNAL) // only log unhandled errors + LogContext(logContext).logErr("Failed to open chunkFile: " + chunkFilePathStr); + + retVal = openChunkRes; + } + } + else + { // just reading the file, no create + mode_t openMode = S_IRWXU|S_IRWXG|S_IRWXO; + + fd = MsgHelperIO::openat(targetFD, chunkFilePathStr.c_str(), this->openFlags, + openMode); + + if(fd == -1) + { // not exists or error + int errCode = errno; + + // we ignore ENOENT (file does not exist), as that's not an error + if(errCode != ENOENT) + { + LOG(SESSIONS, ERR, "Unable to open file.", + ("Chunk File Path", chunkFilePathStr), + ("SysErr", System::getErrString(errCode))); + + retVal = FhgfsOpsErrTk::fromSysErr(errCode); + + } + } + } + + // prepare session data... + handle->fd = FDHandle(fd); + offset = 0; + + log->log(Log_DEBUG, logContext, "File created. ID: " + getFileID() ); + } + + return retVal; +} + +/** + * To avoid races with other writers in same session, apply new mirrorNode only if it was + * NULL before. Otherwise release given mirrorNode and return the existing one. + * + * @mirrorNode will be released if a mirrorNode was set already. + * @return existing mirrorNode if it was not NULL, given mirrorNode otherwise. + */ +NodeHandle SessionLocalFile::setMirrorNodeExclusive(NodeHandle mirrorNode) +{ + std::lock_guard const lock(sessionMutex); + + if (!this->mirrorNode) + this->mirrorNode = mirrorNode; + + return this->mirrorNode; +} + diff --git a/storage/source/session/SessionLocalFile.h b/storage/source/session/SessionLocalFile.h new file mode 100644 index 0000000..42929f8 --- /dev/null +++ b/storage/source/session/SessionLocalFile.h @@ -0,0 +1,284 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include + +/** + * Represents the client session information for an open chunk file. + */ +class SessionLocalFile +{ + public: + class Handle + { + friend class SessionLocalFile; + + public: + Handle() = default; + + Handle(const std::string& id, FDHandle fd): + id(id), fd(std::move(fd)), claimed(0) + { + } + + bool close(); + + const FDHandle& getFD() const { return fd; } + const std::string& getID() const { return id; } + + private: + std::string id; + FDHandle fd; + // for use by SessionLocalFile::releaseLastReference. only one caller may receive the + // handle if multiple threads try to release the last reference concurrently. we could + // also do this under a lock in SessionLocalFileStore but don't since we don't expect + // contention on the release path. + std::atomic claimed; + }; + + public: + /** + * @param fileHandleID format: # + * @param openFlags system flags for open() + * @param serverCrashed true if session was created after a server crash, mark session as + * dirty + */ + SessionLocalFile(const std::string& fileHandleID, uint16_t targetID, std::string fileID, + int openFlags, bool serverCrashed) : + handle(std::make_shared(fileHandleID, FDHandle())), targetID(targetID), + fileID(fileID) + { + this->openFlags = openFlags; + this->offset = -1; // initialize as invalid offset (will be set on file open) + + this->isMirrorSession = false; + + this->writeCounter = 0; + this->readCounter = 0; + this->lastReadAheadTrigger = 0; + + this->serverCrashed = serverCrashed; + } + + /** + * For dezerialisation only + */ + SessionLocalFile(): + handle(std::make_shared()) + { + this->offset = -1; // initialize as invalid offset (will be set on file open) + } + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->handle->id + % obj->targetID + % obj->fileID + % obj->openFlags + % obj->offset + % obj->isMirrorSession + % obj->serverCrashed; + + serializeNodeID(obj, ctx); + + ctx + % serdes::atomicAs(obj->writeCounter) + % serdes::atomicAs(obj->readCounter) + % serdes::atomicAs(obj->lastReadAheadTrigger); + } + + FhgfsOpsErr openFile(int targetFD, const PathInfo* pathInfo, bool isWriteOpen, + const SessionQuotaInfo* quotaInfo); + + NodeHandle setMirrorNodeExclusive(NodeHandle mirrorNode); + + private: + static void serializeNodeID(const SessionLocalFile* obj, Serializer& ser) + { + if (!obj->mirrorNode) + ser % uint16_t(0); + else + ser % uint16_t(obj->mirrorNode->getNumID().val()); + } + + static void serializeNodeID(SessionLocalFile* obj, Deserializer& des); + + private: + // holds information about the underlying filesystem state this session file refers to for + // clients. this handle may be referenced by outsiders to separate removal of session files + // from their respective stores and closing of underlying fs objects. the protocol for such + // a close operation should be as follows: + // 1. remove the session file from its store, retain a shared_ptr + // 2. copy this handle to a temporary + // 3. move the session file reference to a weak_ptr. if the weak_ptr is expired after the + // move, the handle may be closed + // 4. claim the handle, and close() the handle if claiming succeeded + std::shared_ptr handle; + + uint16_t targetID; + std::string fileID; + int32_t openFlags; // system flags for open() + + int64_t offset; // negative value for unspecified/invalid offset + + NodeHandle mirrorNode; // the node to which all writes should be mirrored + bool isMirrorSession; // true if this is the mirror session of a file + + AtomicInt64 writeCounter; // how much sequential data we have written after open/sync_file_range + AtomicInt64 readCounter; // how much sequential data we have read since open / last seek + AtomicInt64 lastReadAheadTrigger; // last readCounter value at which read-ahead was triggered + + Mutex sessionMutex; + + bool serverCrashed; // true if session was created after a server crash, mark session as dirty + + static std::shared_ptr releaseLastReference(std::shared_ptr file) + { + auto handle = file->handle; + std::weak_ptr weak(file); + + // see Handle::claimed for explanation + file.reset(); + if (weak.expired() && !handle->claimed.exchange(true)) + return handle; + else + return nullptr; + } + + public: + bool close() + { + std::lock_guard lock(sessionMutex); + + return handle->close(); + } + + friend std::shared_ptr releaseLastReference(std::shared_ptr&& file) + { + return SessionLocalFile::releaseLastReference(std::move(file)); + } + + std::string getFileHandleID() const + { + return handle->id; + } + + uint16_t getTargetID() const + { + return targetID; + } + + std::string getFileID() const + { + return fileID; + } + + const FDHandle& getFD() + { + if (handle->fd.valid()) // optimization: try without a lock first + return handle->fd; + + std::lock_guard const lock(sessionMutex); + + return handle->fd; + } + + int getOpenFlags() const + { + return openFlags; + } + + bool getIsDirectIO() const + { + return (this->openFlags & (O_DIRECT | O_SYNC) ) != 0; + } + + int64_t getOffset() + { + std::lock_guard const lock(sessionMutex); + + return offset; + } + + void setOffset(int64_t offset) + { + std::lock_guard const lock(sessionMutex); + + this->offset = offset; + } + + NodeHandle getMirrorNode() const + { + return mirrorNode; + } + + bool getIsMirrorSession() const + { + return isMirrorSession; + } + + void setIsMirrorSession(bool isMirrorSession) + { + this->isMirrorSession = isMirrorSession; + } + + void resetWriteCounter() + { + this->writeCounter = 0; + } + + void incWriteCounter(int64_t size) + { + this->writeCounter.increase(size); + } + + int64_t getWriteCounter() + { + return this->writeCounter.read(); + } + + int64_t getReadCounter() + { + return this->readCounter.read(); + } + + void resetReadCounter() + { + this->readCounter.setZero(); + } + + void incReadCounter(int64_t size) + { + this->readCounter.increase(size); + } + + int64_t getLastReadAheadTrigger() + { + return this->lastReadAheadTrigger.read(); + } + + void resetLastReadAheadTrigger() + { + this->lastReadAheadTrigger.setZero(); + } + + void setLastReadAheadTrigger(int64_t lastReadAheadTrigger) + { + this->lastReadAheadTrigger.set(lastReadAheadTrigger); + } + + bool isServerCrashed() + { + return this->serverCrashed; + } +}; + diff --git a/storage/source/session/SessionLocalFileStore.cpp b/storage/source/session/SessionLocalFileStore.cpp new file mode 100644 index 0000000..1cc7426 --- /dev/null +++ b/storage/source/session/SessionLocalFileStore.cpp @@ -0,0 +1,191 @@ +#include +#include +#include "SessionLocalFileStore.h" + + +/** + * Add a new session to the store (if it doesn't exist yet) and return a referenced version + * of the session with this ID. + * + * @param session belongs to the store after calling this method + */ +std::shared_ptr SessionLocalFileStore::addAndReferenceSession( + std::unique_ptr session) +{ + std::string fileHandleID(session->getFileHandleID() ); + uint16_t targetID = session->getTargetID(); + bool isMirrorSession = session->getIsMirrorSession(); + + std::lock_guard const lock(mutex); + + // try to insert the new session + auto insertRes = sessions.insert( + {Key{fileHandleID, targetID, isMirrorSession}, std::move(session)}); + + // reference session (note: insertRes.first is an iterator to the inserted/existing session) + return insertRes.first->second; +} + +/** + * @param targetID really targetID (not buddy group ID for mirrors, because both buddies can be + * attached to the same server). + * @param isMirrorSession true if this is a session for a mirrored chunk file. + * @return NULL if no such session exists. + */ +std::shared_ptr SessionLocalFileStore::referenceSession( + const std::string& fileHandleID, uint16_t targetID, bool isMirrorSession) const +{ + std::lock_guard const lock(mutex); + + auto iter = sessions.find({fileHandleID, targetID, isMirrorSession}); + if (iter != sessions.end()) + return iter->second; + + return nullptr; +} + +/** + * @param isMirrorSession true if this is a session for a mirror chunk file + * @return filesystem state of the session if it was unused, nullptr otherwise. + */ +std::shared_ptr SessionLocalFileStore::removeSession( + const std::string& fileHandleID, uint16_t targetID, bool isMirrorSession) +{ + std::shared_ptr file; + + { + std::lock_guard const lock(mutex); + + auto iter = sessions.find({fileHandleID, targetID, isMirrorSession}); + if (iter == sessions.end()) + return nullptr; + + file = std::move(iter->second); + sessions.erase(iter); + } + + return releaseLastReference(std::move(file)); +} + +size_t SessionLocalFileStore::removeAllSessions() +{ + std::lock_guard const lock(mutex); + + size_t total = sessions.size(); + + sessions.clear(); + + return total; +} + +/** + * Removes all mirror sessions on a specific target. + */ +void SessionLocalFileStore::removeAllMirrorSessions(uint16_t targetID) +{ + std::lock_guard const lock(mutex); + + for (auto iter = sessions.begin(); iter != sessions.end(); ) + { + auto& session = iter->second; + ++iter; + + if (session->getTargetID() == targetID && session->getIsMirrorSession()) + sessions.erase(std::prev(iter)); + } +} + +/* + * Intended to be used for cleanup if deserialization failed, no locking is used + */ +void SessionLocalFileStore::deleteAllSessions() +{ + sessions.clear(); +} + +size_t SessionLocalFileStore::getSize() const +{ + std::lock_guard const lock(mutex); + + return sessions.size(); +} + +/* Merges the SessionLocalFiles of the given SessionLocalFileStore into this SessionLocalFileStore. + * Only not existing SessionLocalFiles will be added to the existing SessionLocalFileStore + * + * @param sessionLocalFileStore the sessionLocalFileStore which will be merged with this + * sessionLocalFileStore + */ +void SessionLocalFileStore::mergeSessionLocalFiles(SessionLocalFileStore* sessionLocalFileStore) +{ + for (auto sessionIter = sessionLocalFileStore->sessions.begin(); + sessionIter != sessionLocalFileStore->sessions.end(); + ++sessionIter) + { + if (sessions.count(sessionIter->first)) + { + const auto& id = sessionIter->first; + + LOG(GENERAL, WARNING, "Found SessionLocalFile with same ID, merge not possible, may be a bug?", + id.fileHandleID, id.targetID, id.isMirrored); + continue; + } + + sessions[sessionIter->first] = std::move(sessionIter->second); + } +} + +void SessionLocalFileStore::serializeForTarget(Serializer& ser, uint16_t targetID) +{ + Serializer atStart = ser.mark(); + + uint32_t elemCount = 0; + ser % elemCount; // needs fixup + + for (auto it = sessions.begin(), end = sessions.end(); it != end; ++it) + { + if (it->first.targetID != targetID) + continue; + + ser + % it->first + % *it->second; + elemCount++; + } + + atStart % elemCount; + + LOG_DEBUG("SessionLocalFileStore serialize", Log_DEBUG, "count of serialized " + "SessionLocalFiles: " + StringTk::uintToStr(elemCount) + " of " + + StringTk::uintToStr(sessions.size())); +} + +void SessionLocalFileStore::deserializeForTarget(Deserializer& des, uint16_t targetID) +{ + uint32_t elemCount; + + des % elemCount; + if (unlikely(!des.good())) + return; + + for (uint32_t i = 0; i < elemCount; i++) + { + Key key; + auto sessionLocalFile = boost::make_unique(); + + des + % key + % *sessionLocalFile; + + if (unlikely(!des.good() || key.targetID != targetID)) + { + des.setBad(); + return; + } + + this->sessions.insert({key, std::move(sessionLocalFile)}); + } + + LOG_DEBUG("SessionLocalFileStore deserialize", Log_DEBUG, "count of deserialized " + "SessionLocalFiles: " + StringTk::uintToStr(elemCount)); +} diff --git a/storage/source/session/SessionLocalFileStore.h b/storage/source/session/SessionLocalFileStore.h new file mode 100644 index 0000000..9dbd13d --- /dev/null +++ b/storage/source/session/SessionLocalFileStore.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include +#include "SessionLocalFile.h" + + + +class SessionLocalFileStore +{ + public: + SessionLocalFileStore() {} + + std::shared_ptr addAndReferenceSession( + std::unique_ptr session); + std::shared_ptr referenceSession(const std::string& fileHandleID, + uint16_t targetID, bool isMirrorSession) const; + std::shared_ptr removeSession( + const std::string& fileHandleID, uint16_t targetID, bool isMirrorSession); + size_t removeAllSessions(); + void removeAllMirrorSessions(uint16_t targetID); + void deleteAllSessions(); + void mergeSessionLocalFiles(SessionLocalFileStore* + sessionLocalFileStore); + + size_t getSize() const; + + void serializeForTarget(Serializer& ser, uint16_t targetID); + void deserializeForTarget(Deserializer& des, uint16_t targetID); + + private: + struct Key + { + std::string fileHandleID; + uint16_t targetID; // really targetID (not buddy group ID for mirrors, because both buddies + // can be attached to the same server) + bool isMirrored; // true if this is a session for a mirror chunk file (has an influence on + // the map key to avoid conflicts with the original session in rotated + // mirrors mode). + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->fileHandleID + % obj->targetID + % obj->isMirrored; + } + + friend bool operator<(const Key& a, const Key& b) + { + return std::tie(a.fileHandleID, a.targetID, a.isMirrored) + < std::tie(b.fileHandleID, b.targetID, b.isMirrored); + } + }; + + std::map> sessions; + + mutable Mutex mutex; +}; + diff --git a/storage/source/session/SessionStore.cpp b/storage/source/session/SessionStore.cpp new file mode 100644 index 0000000..5aa0b06 --- /dev/null +++ b/storage/source/session/SessionStore.cpp @@ -0,0 +1,301 @@ +#include "SessionStore.h" + +#include + +#include + +/** + * @return NULL if no such session exists + */ +std::shared_ptr SessionStore::referenceSession(NumNodeID sessionID) const +{ + std::lock_guard const lock(mutex); + + auto iter = sessions.find(sessionID); + if (iter != sessions.end()) + return iter->second; + + return nullptr; +} + +std::shared_ptr SessionStore::referenceOrAddSession(NumNodeID sessionID) +{ + std::lock_guard lock(mutex); + + auto iter = sessions.find(sessionID); + if (iter != sessions.end()) + return iter->second; + + // add as new session and reference it + LogContext log("SessionStore (ref)"); + log.log(Log_DEBUG, std::string("Creating a new session. SessionID: ") + sessionID.str()); + + auto session = std::make_shared(sessionID); + sessions[sessionID] = session; + return session; +} + +/** + * @param masterList must be ordered; contained nodes will be removed and may no longer be + * accessed after calling this method. + * @return contained sessions must be cleaned up by the caller + */ +std::list> SessionStore::syncSessions( + const std::vector& masterList) +{ + std::lock_guard const lock(mutex); + + std::list> result; + + auto sessionIter = sessions.begin(); + auto masterIter = masterList.begin(); + + while (sessionIter != sessions.end() && masterIter != masterList.end()) + { + NumNodeID currentSession = sessionIter->first; + NumNodeID currentMaster = (*masterIter)->getNumID(); + + if(currentMaster < currentSession) + { // session doesn't exist locally + // note: we add sessions only on demand + masterIter++; + } + else + if(currentSession < currentMaster) + { // session is removed + auto session = std::move(sessionIter->second); + sessionIter++; // (removal invalidates iterator) + + result.push_back(std::move(session)); + sessions.erase(std::prev(sessionIter)); + } + else + { // session unchanged + masterIter++; + sessionIter++; + } + } + + // remaining sessions are removed + while(sessionIter != sessions.end() ) + { + auto session = std::move(sessionIter->second); + sessionIter++; // (removal invalidates iterator) + + result.push_back(std::move(session)); + sessions.erase(std::prev(sessionIter)); + } + + return result; +} + +/** + * @return number of sessions + */ +size_t SessionStore::getAllSessionIDs(NumNodeIDList* outSessionIDs) const +{ + std::lock_guard const lock(mutex); + + size_t retVal = sessions.size(); + + for (auto iter = sessions.begin(); iter != sessions.end(); iter++) + outSessionIDs->push_back(iter->first); + + return retVal; +} + +size_t SessionStore::getSize() const +{ + std::lock_guard const lock(mutex); + + return sessions.size(); +} + +void SessionStore::serializeForTarget(Serializer& ser, uint16_t targetID) const +{ + ser % uint32_t(sessions.size()); + + for (auto it = sessions.begin(), end = sessions.end(); it != end; ++it) + { + ser % it->first; + it->second->serializeForTarget(ser, targetID); + } + + LOG_DEBUG("SessionStore serialize", Log_DEBUG, "count of serialized Sessions: " + + StringTk::uintToStr(sessions.size())); +} + +void SessionStore::deserializeForTarget(Deserializer& des, uint16_t targetID) +{ + uint32_t elemCount; + + des % elemCount; + if (unlikely(!des.good())) + return; + + for(unsigned i = 0; i < elemCount; i++) + { + NumNodeID key; + + des % key; + if (unlikely(!des.good())) + return; + + auto session = boost::make_unique(); + session->deserializeForTarget(des, targetID); + if (unlikely(!des.good())) + { + session->getLocalFiles()->deleteAllSessions(); + return; + } + + auto searchResult = this->sessions.find(key); + if (searchResult == this->sessions.end()) + { + this->sessions.insert({key, std::move(session)}); + } + else + { // exist so local files will merged + searchResult->second->mergeSessionLocalFiles(session.get()); + } + } + + LOG_DEBUG("SessionStore deserialize", Log_DEBUG, "count of deserialized Sessions: " + + StringTk::uintToStr(elemCount)); +} + +bool SessionStore::loadFromFile(std::string filePath, uint16_t targetID) +{ + LogContext log("SessionStore (load)"); + log.log(Log_DEBUG,"load sessions of target: " + StringTk::uintToStr(targetID)); + + bool retVal = false; + boost::scoped_array buf; + int readRes; + + struct stat statBuf; + int retValStat; + + if(!filePath.length() ) + return false; + + std::lock_guard const lock(mutex); + + int fd = open(filePath.c_str(), O_RDONLY, 0); + if(fd == -1) + { // open failed + log.log(Log_DEBUG, "Unable to open session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_unlock; + } + + retValStat = fstat(fd, &statBuf); + if(retValStat) + { // stat failed + log.log(Log_WARNING, "Unable to stat session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_stat; + } + + buf.reset(new char[statBuf.st_size]); + readRes = read(fd, buf.get(), statBuf.st_size); + if(readRes <= 0) + { // reading failed + log.log(Log_WARNING, "Unable to read session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + } + else + { // parse contents + Deserializer des(buf.get(), readRes); + deserializeForTarget(des, targetID); + retVal = des.good(); + } + + if (!retVal) + log.logErr("Could not deserialize SessionStore from file: " + filePath); + +err_stat: + close(fd); + +err_unlock: + return retVal; +} + +/** + * Note: setStorePath must be called before using this. + */ +bool SessionStore::saveToFile(std::string filePath, uint16_t targetID) const +{ + LogContext log("SessionStore (save)"); + log.log(Log_DEBUG,"save sessions of target: " + StringTk::uintToStr(targetID)); + + bool retVal = false; + + boost::scoped_array buf; + unsigned bufLen; + ssize_t writeRes; + + if(!filePath.length() ) + return false; + + std::lock_guard const lock(mutex); + + // create/trunc file + int openFlags = O_CREAT|O_TRUNC|O_WRONLY; + + int fd = open(filePath.c_str(), openFlags, 0666); + if(fd == -1) + { // error + log.logErr("Unable to create session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_unlock; + } + + // file created => store data + { + Serializer lengthCalc; + serializeForTarget(lengthCalc, targetID); + + bufLen = lengthCalc.size(); + buf.reset(new (std::nothrow) char[bufLen]); + if (!buf) + { + LOG(SESSIONS, ERR, "Unable to allocate serializer memory", filePath); + goto err_closefile; + } + } + + { + Serializer ser(buf.get(), bufLen); + serializeForTarget(ser, targetID); + + if (!ser.good()) + { + log.logErr("Unable to serialize session file: " + filePath + "."); + goto err_closefile; + } + } + + writeRes = write(fd, buf.get(), bufLen); + if(writeRes != (ssize_t)bufLen) + { + log.logErr("Unable to store session file: " + filePath + ". " + + "SysErr: " + System::getErrString() ); + + goto err_closefile; + } + + retVal = true; + + LOG_DEBUG_CONTEXT(log, Log_DEBUG, "Session file stored: " + filePath); + + // error compensation +err_closefile: + close(fd); + +err_unlock: + return retVal; +} diff --git a/storage/source/session/SessionStore.h b/storage/source/session/SessionStore.h new file mode 100644 index 0000000..36fdb60 --- /dev/null +++ b/storage/source/session/SessionStore.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include +#include +#include +#include "Session.h" + +/* + * A session always belongs to a client ID, therefore the session ID is always the nodeID of the + * corresponding client + */ +class SessionStore +{ + public: + SessionStore() {} + + std::shared_ptr referenceSession(NumNodeID sessionID) const; + std::shared_ptr referenceOrAddSession(NumNodeID sessionID); + std::list> syncSessions(const std::vector& masterList); + + size_t getAllSessionIDs(NumNodeIDList* outSessionIDs) const; + size_t getSize() const; + + void serializeForTarget(Serializer& ser, uint16_t targetID) const; + void deserializeForTarget(Deserializer& des, uint16_t targetID); + + bool loadFromFile(std::string filePath, uint16_t targetID); + bool saveToFile(std::string filePath, uint16_t targetID) const; + + private: + std::map> sessions; + + mutable Mutex mutex; +}; + diff --git a/storage/source/session/ZfsSession.cpp b/storage/source/session/ZfsSession.cpp new file mode 100644 index 0000000..0a721cf --- /dev/null +++ b/storage/source/session/ZfsSession.cpp @@ -0,0 +1,148 @@ +#include +#include +#include "ZfsSession.h" + +#include + + + +/** + * dummy constructor, constructs a invalid ZfsSession, this constructor doesn't require the libzfs, + * the method initZfsSession can make the session valid if the installed libzfs is working with this + * implementation. The session must be initialized if a zfs block device is detected. + */ +ZfsSession::ZfsSession() +{ + this->dlOpenHandleLibZfs = NULL; + this->libZfsHandle = NULL; + this->zfs_open = NULL; + this->zfs_prop_get_userquota_int = NULL; + this->libzfs_error_description = NULL; + this->libzfs_error_action = NULL; + + this->isValid = false; +} + +/** + * destructor + */ +ZfsSession::~ZfsSession() +{ + QuotaTk::uninitLibZfs(this->dlOpenHandleLibZfs, this->libZfsHandle); +} + +/** + * initialize the ZfsSession, sets the valid flag if all zfs handels and function pointers are + * successful created + * + * @param dlOpenHandleLib the handle from dlopen to the libzfs + * @return true if all zfs handels and function pointers are successful created + */ +bool ZfsSession::initZfsSession(void* dlOpenHandleLib) +{ + App* app = Program::getApp(); + + if(this->isValid) + return true; + + if(!dlOpenHandleLib) + return false; + + + char* dlErrorString; + + this->dlOpenHandleLibZfs = dlOpenHandleLib; + this->isValid = true; + + + this->libZfsHandle = QuotaTk::initLibZfs(this->dlOpenHandleLibZfs); + if(!this->libZfsHandle) + { + this->isValid = false; + return false; + } + + + this->zfs_open = (void* (*)(void*, const char*, int))dlsym(this->dlOpenHandleLibZfs, "zfs_open"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function zfs_open.", dlErrorString); + app->setLibZfsErrorReported(true); + this->isValid = false; + return false; + } + + + this->zfs_prop_get_userquota_int = (int (*)(void*, const char*, uint64_t*))dlsym( + this->dlOpenHandleLibZfs, "zfs_prop_get_userquota_int"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function zfs_prop_get_userquota_int.", + dlErrorString); + app->setLibZfsErrorReported(true); + this->isValid = false; + return false; + } + + + this->libzfs_error_description = (char* (*)(void*))dlsym(this->dlOpenHandleLibZfs, + "libzfs_error_description"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_error_description.", + dlErrorString); + app->setLibZfsErrorReported(true); + this->isValid = false; + return false; + } + + + this->libzfs_error_action = (char* (*)(void*))dlsym(this->dlOpenHandleLibZfs, + "libzfs_error_action"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_error_action.", + dlErrorString); + app->setLibZfsErrorReported(true); + this->isValid = false; + return false; + } + + return this->isValid; +} + +/** + * checks for a existing blockdevice/pool handle (zfs_handle_t*) or creates a new zfs + * blockdevice/pool handle (zfs_handle_t*) for the given path an targetNumID + * + * @param targetNumID the targetNumID of the storage target + * @param path the path of the blockdevice/poolname + * @return returns a zfs blockdevice/pool handle (zfs_handle_t*) or NULL if a error occurs + */ +void* ZfsSession::getZfsDeviceHandle(uint16_t targetNumID, std::string path) +{ + if(!this->isValid) + return NULL; + + ZfsPoolHandleMapIter iter = fsHandles.find(targetNumID); + + if(iter != this->fsHandles.end() ) + return iter->second; + else + { + void* newFsHandle = (*this->zfs_open)(this->libZfsHandle, path.c_str(), ZFSSESSION_ZFS_TYPE); + if(!newFsHandle) + { + LOG(QUOTA, ERR, "Error during create of zfs pool handle.", + ("ErrorAction", (*this->libzfs_error_action)(this->libZfsHandle)), + ("ErrorDescription", (*this->libzfs_error_description)(this->libZfsHandle))); + } + else + { + fsHandles.insert(ZfsPoolHandleMapMapVal(targetNumID, newFsHandle) ); + return newFsHandle; + } + } + + return NULL; +} diff --git a/storage/source/session/ZfsSession.h b/storage/source/session/ZfsSession.h new file mode 100644 index 0000000..4cad520 --- /dev/null +++ b/storage/source/session/ZfsSession.h @@ -0,0 +1,54 @@ +#pragma once + + + + + + +#define ZFSSESSION_ZFS_TYPE 1 // must be same as ZFS_TYPE_FILESYSTEM from libzfs + +typedef std::map ZfsPoolHandleMap; // targetNumID => zfs_handle_t* +typedef ZfsPoolHandleMap::iterator ZfsPoolHandleMapIter; +typedef ZfsPoolHandleMap::value_type ZfsPoolHandleMapMapVal; + + +class ZfsSession +{ + public: + ZfsSession(); + virtual ~ZfsSession(); + bool initZfsSession(void* dlOpenHandleLib); + + void* getZfsDeviceHandle(uint16_t targetNumID, std::string path); + + int (*zfs_prop_get_userquota_int)(void*, const char*, uint64_t*); // fp to get quota data + char* (*libzfs_error_description)(void*); // fp to get error description + char* (*libzfs_error_action)(void*); // fp to get action during the error occurs + + + private: + void* dlOpenHandleLibZfs; // handle of dlOpen from the libzfs + void* libZfsHandle; // handle of the libzfs from libzfs_init() + + ZfsPoolHandleMap fsHandles; // handle of a ZFS pool, the type is zfs_handle_t* + + void* (*zfs_open)(void*, const char*, int); // fp to open zfs pool + + bool isValid; + + + public: + /** + * getter and setter + */ + void* getlibZfsHandle() + { + return this->libZfsHandle; + } + + bool isSessionValid() + { + return this->isValid; + } +}; + diff --git a/storage/source/storage/ChunkDir.h b/storage/source/storage/ChunkDir.h new file mode 100644 index 0000000..0f943f5 --- /dev/null +++ b/storage/source/storage/ChunkDir.h @@ -0,0 +1,51 @@ +#pragma once + +#include + +#include + +/** + * Our inode object, but for directories only. Files are in class FileInode. + */ +class ChunkDir +{ + friend class ChunkStore; + + public: + ChunkDir(std::string id) : id(id) + { + } + + protected: + RWLock rwlock; + + private: + std::string id; // filesystem-wide unique string + + + public: + + // inliners + + void readLock() + { + this->rwlock.readLock(); + } + + void writeLock() + { + this->rwlock.writeLock(); + } + + void unlock() + { + this->rwlock.unlock(); + } + + std::string getID() const + { + return this->id; + } + +}; + diff --git a/storage/source/storage/ChunkLockStore.h b/storage/source/storage/ChunkLockStore.h new file mode 100644 index 0000000..5866a52 --- /dev/null +++ b/storage/source/storage/ChunkLockStore.h @@ -0,0 +1,155 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +struct ChunkLockStoreContents +{ + StringSet lockedChunks; + Mutex lockedChunksMutex; + Condition chunkUnlockedCondition; +}; + +/** + * Can be used to lock access to a chunk, especially needed for resync; locks will be on a very + * high, abstract level, as we do only restrict access by chunk ID. + * + * "locking" here means we successfully insert an element into a set. If we cannot insert, because + * an element with the same ID is already present, it means someone else currently holds the lock. + */ +class ChunkLockStore +{ + friend class GenericDebugMsgEx; + + public: + ChunkLockStore() { }; + + private: + std::map> targetsMap; + RWLock targetsLock; // synchronizes insertion into targetsMap + + ChunkLockStoreContents* getOrInsertTargetLockStore(uint16_t targetID) + { + UniqueRWLock lock(targetsLock, SafeRWLock_READ); + + auto targetsIter = targetsMap.find(targetID); + if (targetsIter != targetsMap.end()) + { + return targetsIter->second.get(); + } + else + { + lock.unlock(); + lock.lock(SafeRWLock_WRITE); + + return targetsMap.insert({targetID, std::make_shared()}) + .first->second.get(); + } + } + + ChunkLockStoreContents* findTargetLockStore(uint16_t targetID) + { + UniqueRWLock lock(targetsLock, SafeRWLock_READ); + auto targetsIter = targetsMap.find(targetID); + + if (unlikely(targetsIter == targetsMap.end())) + { + LogContext(__func__).log(Log_WARNING, + "Tried to access chunk lock, but target not found in lock map. Printing backtrace. " + "targetID: " + StringTk::uintToStr(targetID) ); + LogContext(__func__).logBacktrace(); + + return nullptr; + } + + return targetsIter->second.get(); + } + + public: + void lockChunk(uint16_t targetID, std::string chunkID) + { + auto targetLockStore = getOrInsertTargetLockStore(targetID); + + const std::lock_guard chunksLock(targetLockStore->lockedChunksMutex); + + // loop until we can insert the chunk lock + for( ; ; ) + { + bool insertRes = targetLockStore->lockedChunks.insert(chunkID).second; + + if(insertRes) + break; // new lock successfully inserted + + // chunk lock already exists => wait + targetLockStore->chunkUnlockedCondition.wait(&targetLockStore->lockedChunksMutex); + } + } + + void unlockChunk(uint16_t targetID, std::string chunkID) + { + StringSetIter lockChunksIter; + + auto targetLockStore = findTargetLockStore(targetID); + if(unlikely(targetLockStore == nullptr)) + return; + + targetLockStore->lockedChunksMutex.lock(); + + lockChunksIter = targetLockStore->lockedChunks.find(chunkID); + if(unlikely(lockChunksIter == targetLockStore->lockedChunks.end() ) ) + { + LogContext(__func__).log(Log_WARNING, + "Tried to unlock chunk, but chunk not found in lock set. Printing backtrace. " + "targetID: " + StringTk::uintToStr(targetID) + "; " + "chunkID: " + chunkID); + LogContext(__func__).logBacktrace(); + + goto unlock_chunks; + } + + targetLockStore->lockedChunks.erase(lockChunksIter); + + targetLockStore->chunkUnlockedCondition.broadcast(); // notify lock waiters + + unlock_chunks: + targetLockStore->lockedChunksMutex.unlock(); + } + + protected: + // only used for GenericDebugMsg at the moment + size_t getSize(uint16_t targetID) + { + size_t retVal = 0; + + auto targetLockStore = findTargetLockStore(targetID); + if(targetLockStore) // map does exist + { + const std::lock_guard chunksLock(targetLockStore->lockedChunksMutex); + + retVal = targetLockStore->lockedChunks.size(); + } + + return retVal; + } + + StringSet getLockStoreCopy(uint16_t targetID) + { + StringSet outLockStore; + + auto targetLockStore = findTargetLockStore(targetID); + if(targetLockStore) // map does exist + { + const std::lock_guard chunksLock(targetLockStore->lockedChunksMutex); + + outLockStore = targetLockStore->lockedChunks; + } + + return outLockStore; + } +}; + diff --git a/storage/source/storage/ChunkStore.cpp b/storage/source/storage/ChunkStore.cpp new file mode 100644 index 0000000..7c82abe --- /dev/null +++ b/storage/source/storage/ChunkStore.cpp @@ -0,0 +1,728 @@ +#include +#include +#include + +#include "ChunkStore.h" + + +#define CHUNKSTORE_REFCACHE_REMOVE_SKIP_SYNC (4) /* n-th elements to be removed on sync sweep */ +#define CHUNKSTORE_REFCACHE_REMOVE_SKIP_ASYNC (3) /* n-th elements to be removed on async sweep */ + +/** + * not inlined as we need to include + */ +ChunkStore::ChunkStore() +{ + App* app = Program::getApp(); + Config* cfg = app->getConfig(); + + this->refCacheSyncLimit = cfg->getTuneDirCacheLimit(); + this->refCacheAsyncLimit = refCacheSyncLimit - (refCacheSyncLimit/2); +} + +bool ChunkStore::dirInStoreUnlocked(std::string dirID) +{ + DirectoryMapIter iter = this->dirs.find(dirID); + return iter != this->dirs.end(); +} + + +/** + * Note: remember to call releaseDir() + * + * @param forceLoad Force to load the ChunkDir from disk. Defaults to false. + * @return NULL if no such dir exists (or if the dir is being moved), but cannot be NULL if + * forceLoad is false + */ +ChunkDir* ChunkStore::referenceDir(std::string dirID) +{ + const char* logContext = "DirReferencer referenceChunkDir"; + ChunkDir* dir = NULL; + bool wasReferenced = true; /* Only try to add to cache if not in memory yet. + * Any attempt to add it to the cache causes a cache sweep, which is + * rather expensive. + * Note: when set to false we also need a write-lock! */ + + SafeRWLock safeLock(&this->rwlock, SafeRWLock_READ); // L O C K + + DirectoryMapIter iter; + int retries = 0; // 0 -> read-locked + while (retries < RWLOCK_LOCK_UPGRADE_RACY_RETRIES) // one as read-lock and one as write-lock + { + iter = this->dirs.find(dirID); + if (iter == this->dirs.end() && retries == 0) + { + safeLock.unlock(); + safeLock.lock(SafeRWLock_WRITE); + } + retries++; + } + + if(iter == this->dirs.end() ) + { // Not in map yet => try to load it. We must be write-locked here! + InsertChunkDirUnlocked(dirID, iter); // (will set "iter != end" if loaded) + wasReferenced = false; + } + + if (likely(iter != dirs.end() ) ) + { // exists in map + ChunkDirReferencer* dirRefer = iter->second; + + dir = dirRefer->reference(); + + // LOG_DEBUG(logContext, Log_SPAM, std::string("DirID: ") + dir->getID() + + // " Refcount: " + StringTk::intToStr(dirRefer->getRefCount() ) ); + IGNORE_UNUSED_VARIABLE(logContext); + + if (!wasReferenced) + cacheAddUnlocked(dirID, dirRefer); + + } + + safeLock.unlock(); // U N L O C K + + return dir; +} + +/** + * Release reduce the refcounter of an ChunkDir here + */ +void ChunkStore::releaseDir(std::string dirID) +{ + SafeRWLock safeLock(&this->rwlock, SafeRWLock_WRITE); // L O C K + + releaseDirUnlocked(dirID); + + safeLock.unlock(); // U N L O C K +} + +void ChunkStore::releaseDirUnlocked(std::string dirID) +{ + const char* logContext = "DirReferencer releaseChunkDir"; + + DirectoryMapIter iter = this->dirs.find(dirID); + if(likely(iter != this->dirs.end() ) ) + { // dir exists => decrease refCount + ChunkDirReferencer* dirRefer = iter->second; + + if (likely(dirRefer->getRefCount() ) ) + { + dirRefer->release(); + + // LOG_DEBUG(logContext, Log_SPAM, std::string("DirID: ") + dirID + + // " Refcount: " + StringTk::intToStr(dirRefer->getRefCount() ) ); + + if(!dirRefer->getRefCount() ) + { // dropped last reference => unload dir + delete(dirRefer); + this->dirs.erase(iter); + } + } + else + { // attempt to release a Dir without a refCount + std::string logMsg = std::string("Bug: Refusing to release dir with a zero refCount") + + std::string("dirID: ") + dirID; + LogContext(logContext).logErr(logMsg); + this->dirs.erase(iter); + } + } + else + { + LogContext(logContext).logErr("Bug: releaseDir requested, but dir not referenced! " + "DirID: " + dirID); + LogContext(logContext).logBacktrace(); + } +} + + +/** + * Creates and empty ChunkDir and inserts it into the map. + * + * Note: We only need to hold a read-lock here, as we check if inserting an entry into the map + * succeeded. + * + * @return newElemIter only valid if true is returned, untouched otherwise + */ +void ChunkStore::InsertChunkDirUnlocked(std::string dirID, DirectoryMapIter& newElemIter) +{ + ChunkDir* inode = new ChunkDir(dirID); + if (unlikely (!inode) ) + return; + + std::pair pairRes = + this->dirs.insert(DirectoryMapVal(dirID, new ChunkDirReferencer(inode) ) ); + + if (!pairRes.second) + { + // element already exists in the map, we raced with another thread + delete inode; + + newElemIter = this->dirs.find(dirID); + } + else + { + newElemIter = pairRes.first; + } + +} + + +void ChunkStore::clearStoreUnlocked() +{ + LOG_DEBUG("DirectoryStore::clearStoreUnlocked", Log_DEBUG, + std::string("# of loaded entries to be cleared: ") + StringTk::intToStr(dirs.size() ) ); + + cacheRemoveAllUnlocked(); + + for(DirectoryMapIter iter = dirs.begin(); iter != dirs.end(); iter++) + { + ChunkDirReferencer* dirRef = iter->second; + + // will also call destructor for dirInode and sub-objects as dirInode->fileStore + delete(dirRef); + } + + dirs.clear(); +} + +/** + * Note: Make sure to call this only after the new reference has been taken by the caller + * (otherwise it might happen that the new element is deleted during sweep if it was cached + * before and appears to be unneeded now). + */ +void ChunkStore::cacheAddUnlocked(std::string& dirID, ChunkDirReferencer* dirRefer) +{ + const char* logContext = "DirReferencer cache add ChunkDir"; + + // (we do cache sweeping before insertion to make sure we don't sweep the new entry) + cacheSweepUnlocked(true); + + if(refCache.insert(DirCacheMapVal(dirID, dirRefer) ).second) + { // new insert => inc refcount + dirRefer->reference(); + + // LOG_DEBUG(logContext, Log_SPAM, std::string("DirID: ") + dirID + + // " Refcount: " + StringTk::intToStr(dirRefer->getRefCount() ) ); + IGNORE_UNUSED_VARIABLE(logContext); + } + +} + +void ChunkStore::cacheRemoveUnlocked(std::string& dirID) +{ + DirCacheMapIter iter = refCache.find(dirID); + if(iter == refCache.end() ) + return; + + releaseDirUnlocked(dirID); + refCache.erase(iter); +} + +void ChunkStore::cacheRemoveAllUnlocked() +{ + for(DirCacheMapIter iter = refCache.begin(); iter != refCache.end(); /* iter inc inside loop */) + { + releaseDirUnlocked(iter->first); + + DirCacheMapIter iterNext(iter); + iterNext++; + + // cppcheck-suppress erase [special comment to mute false cppcheck alarm] + refCache.erase(iter); + + iter = iterNext; + } +} + +/** + * @param isSyncSweep true if this is a synchronous sweep (e.g. we need to free a few elements to + * allow quick insertion of a new element), false is this is an asynchronous sweep (that might take + * a bit longer). + * @return true if a cache flush was triggered, false otherwise + */ +bool ChunkStore::cacheSweepUnlocked(bool isSyncSweep) +{ + // sweeping means we remove every n-th element from the cache, starting with a random element + // in the range 0 to n + size_t cacheLimit; + size_t removeSkipNum; + + // check type of sweep and set removal parameters accordingly + + if(isSyncSweep) + { // sync sweep settings + cacheLimit = refCacheSyncLimit; + removeSkipNum = CHUNKSTORE_REFCACHE_REMOVE_SKIP_SYNC; + } + else + { // async sweep settings + cacheLimit = refCacheAsyncLimit; + removeSkipNum = CHUNKSTORE_REFCACHE_REMOVE_SKIP_ASYNC; + } + + if(refCache.size() <= cacheLimit) + return false; + + + // pick a random start element (note: the element will be removed in first loop pass below) + + unsigned randStart = randGen.getNextInRange(0, removeSkipNum - 1); + DirCacheMapIter iter = refCache.begin(); + + while(randStart--) + iter++; + + + // walk over all cached elements and remove every n-th element + + unsigned i = removeSkipNum-1; /* counts to every n-th element ("remoteSkipNum-1" to remove the + random start element in the first loop pass) */ + + while(iter != refCache.end() ) + { + i++; + + if(i == removeSkipNum) + { + releaseDirUnlocked(iter->first); + + DirCacheMapIter iterNext(iter); + iterNext++; + + refCache.erase(iter); + + iter = iterNext; + + i = 0; + } + else + iter++; + } + + return true; +} + +/** + * @return true if a cache flush was triggered, false otherwise + */ +bool ChunkStore::cacheSweepAsync() +{ + const char* logContext = "Async cache sweep"; + + //LOG_DEBUG(logContext, Log_SPAM, "Start cache sweep."); // debug in + IGNORE_UNUSED_VARIABLE(logContext); + + SafeRWLock safeLock(&rwlock, SafeRWLock_WRITE); // L O C K + + bool retVal = cacheSweepUnlocked(false); + + safeLock.unlock(); // U N L O C K + + // LOG_DEBUG(logContext, Log_SPAM, "Stop cache sweep."); // debug in + + return retVal; +} + +/** + * @return current number of cached entries + */ +size_t ChunkStore::getCacheSize() +{ + SafeRWLock safeLock(&rwlock, SafeRWLock_READ); // L O C K + + size_t dirsSize = refCache.size(); + + safeLock.unlock(); // U N L O C K + + return dirsSize; +} + + +/** + * Iterate through chunkDirPath and rmdir each path-element beginning with uidXYZ. + * + * Note: Only path elements starting with uidXYZ will be removed. + * Note: chunkDirPath will be modified - all elements except the first one will be removed. + */ +bool ChunkStore::rmdirChunkDirPath(int targetFD, Path* chunkDirPath) +{ + const char* logContext = "ChunkDirStore rmdir chunkdir path"; + bool retVal = true; + + int uidPos = STORAGETK_CHUNKDIR_VEC_UIDPOS; + + int chunkDirPos = chunkDirPath->size() - 1; + + // Iterate over all path elements in reverse order and try to rmdir up to uidXYZ + while (chunkDirPos >= uidPos) + { + std::string chunkDirID = getUniqueDirID(chunkDirPath->back(), chunkDirPos); + + /* Note: We only write-lock the current dir element. mkdir needs to (read) lock + * parent + current, until current was created. */ + ChunkDir* chunkDir = referenceDir(chunkDirID); + + if (likely(chunkDir) ) + chunkDir->writeLock(); // LOCK, Note: SafeRWLock does not work due to the if-condition + else + LogContext(logContext).logErr("Bug: Failed to reference chunkDir: " + chunkDirID); + + std::string rmDirPath = chunkDirPath->str(); + + int rmdirRes = unlinkat(targetFD, rmDirPath.c_str(), AT_REMOVEDIR); + + int rmDirErrno = errno; + + if (likely(chunkDir) ) + { + chunkDir->unlock(); // UNLOCK + releaseDir(chunkDirID); + } + + if (rmdirRes == -1) + { + if ( (rmDirErrno != ENOENT) && (rmDirErrno != ENOTEMPTY) ) + { + LogContext(logContext).logErr("Unable to rmdir chunk path: " + rmDirPath + ". " + + "SysErr: " + System::getErrString() ); + retVal = false; + } + + break; + } + + *chunkDirPath = chunkDirPath->dirname(); + chunkDirPos = chunkDirPath->size() - 1; + } + + return retVal; +} + + +/** + * Create V2 (deprecated) chunkDirPath (chunks/hash1/hash2/). + * Note: No locking required. + */ +bool ChunkStore::mkdirV2ChunkDirPath(int targetFD, const Path* chunkDirPath) +{ + const char* logContext = "ChunkDirStore mkdir V2 chunkdir path"; + bool retVal = true; + unsigned pathElemIndex = 0; + + std::string mkdirPath; + + // Iterate and create basic chunks or mirror paths, we don't need any locking here + while (pathElemIndex != chunkDirPath->size()) + { + mkdirPath += (*chunkDirPath)[pathElemIndex]; + + int mkdirRes = mkdirat(targetFD, mkdirPath.c_str(), STORAGETK_DEFAULTCHUNKDIRMODE); + if (mkdirRes && errno != EEXIST) + { + LogContext(logContext).logErr("Unable to create chunk path: " + mkdirPath + ". " + + "SysErr: " + System::getErrString() ); + + retVal = false; + break; + } + + mkdirPath = mkdirPath + '/'; // path must be relative, so only add it here + pathElemIndex++; + } + + return retVal; +} + +/** + * Iterate through chunkDirPath and rmdir each path-element beginning with uidXYZ. + * + * @param outChunkDir The last path element, needs to be unlocked and release by the caller, + * once the caller has created a new file in it. + */ +bool ChunkStore::mkdirChunkDirPath(int targetFD, const Path* chunkDirPath, bool hasOrigFeature, + ChunkDir** outChunkDir) +{ + const char* logContext = "ChunkDirStore mkdir chunkdir path"; + bool retVal; + *outChunkDir = NULL; + + // V2 version for 2012.10 style layout ... + + if (!hasOrigFeature) + return mkdirV2ChunkDirPath(targetFD, chunkDirPath); + + + // V3 version for 2014.01 style layout (chunks/uidXYZ/level1/level2/parentID/) ... + + + unsigned uidPos = STORAGETK_CHUNKDIR_VEC_UIDPOS; + + std::string mkdirPath; + unsigned depth = 0; + std::string uidStr; + + ChunkDir* chunkDir = NULL; + ChunkDir* parentChunkDir = NULL; + + // Iterate and create basic paths, we don't need any locking here + while (depth < uidPos && depth < chunkDirPath->size()) + { + mkdirPath += (*chunkDirPath)[depth]; + + int mkdirRes = mkdirat(targetFD, mkdirPath.c_str(), STORAGETK_DEFAULTCHUNKDIRMODE); + if (mkdirRes && errno != EEXIST) + { + LogContext(logContext).logErr("Unable to create chunk path: " + mkdirPath + ". " + + "SysErr: " + System::getErrString() ); + + retVal = false; + goto out; + } + + mkdirPath = mkdirPath + '/'; // path must be relative, so only add it here + depth++; + } + + if (depth != chunkDirPath->size()) + (*chunkDirPath)[depth]; + + /* Iterate over the remaining path elements (beginning with uidXYZ), + * lock their IDs and try to create them */ + while (depth < chunkDirPath->size()) + { + std::string currentElement = (*chunkDirPath)[depth]; + mkdirPath += currentElement; + + std::string chunkDirID = getUniqueDirID(currentElement, depth); + + chunkDir = referenceDir(chunkDirID); + if (likely(chunkDir) ) + chunkDir->rwlock.readLock(); + else + LogContext(logContext).logErr("Bug: Failed to reference chunkDir " + mkdirPath + "!"); + + int mkdirRes = mkdirat(targetFD, mkdirPath.c_str(), STORAGETK_DEFAULTCHUNKDIRMODE); + + int mkdirErrno = errno; + + if (parentChunkDir) + { + /* Once we keep a lock on the current dir and created it we can give up the lock of the + * parent - a racing rmdir on parent will fail with ENOTEMPTY. + * If mkdir failed we do not care, as something is wrong anyway. */ + parentChunkDir->rwlock.unlock(); + releaseDir(parentChunkDir->getID() ); + } + + if (mkdirRes && mkdirErrno != EEXIST) + { + LogContext(logContext).logErr("Unable to create chunk path: " + mkdirPath + ". " + + "SysErr: " + System::getErrString() ); + + if (likely(chunkDir) ) + { + chunkDir->rwlock.unlock(); + releaseDir(chunkDirID); + } + + retVal = false; + goto out; + } + + mkdirPath = mkdirPath + '/'; // path must be relative, so only add it here + depth++; + parentChunkDir = chunkDir; + } + + if (likely(chunkDir) ) + { + *outChunkDir = chunkDir; + retVal = true; + } + else + retVal = false; + +out: + return retVal; +} + +std::pair ChunkStore::openAndChown(const int targetFD, const std::string& path, + const int openFlags, const SessionQuotaInfo& quota) +{ + // if we aren't using quota, we don't care about the file owner at all and may simply create the + // file if it does exist (and if openFlags requests it). + // + // if we are using quota, we must ensure that the owner information in of the file is correct. + // this is slightly complicated by the fact that chunk files are created *at first write*, not + // during file create itself. lazily creating chunk files enables races: + // * process A creates the chunk file by writing to it, with quota information (U1, G1) + // * process B runs chown on the file with quota information (U2, G2) + // if the chown is processed before the write the chunk file to be chowned does not exist yet, + // and subsequently will be created with incorrect quota information. fsck will detect these as + // incorrect chunk attributes when run. + // + // to reduce the impact of this, we chown() the file every time we open it - even when we open + // it only for reading. this enables the same race as before, but since reads and writes are + // expected to happen much more often than chown it is more likely that we fix a previously + // "broken" attribute set than break it. + // + // the previous implementation used setfsuid/setfsgid to create files with the same correct(racy) + // owner information, but never changed the owner afterwards. performance testing has shown that + // always calling chown() is as expensive or even cheaper than changing fsuid/fsgid twice per + // open. always calling chown() is also cheaper than checking whether the file was created (by + // calling create with O_EXCL first, then without O_CREAT if that failed). + + const int fd = openat(targetFD, path.c_str(), openFlags, STORAGETK_DEFAULTCHUNKFILEMODE); + if (fd < 0) + { + if (errno == EACCES) + return {FhgfsOpsErr_NOTOWNER, -1}; + else + return {FhgfsOpsErrTk::fromSysErr(errno), -1}; + } + + if (!quota.useQuota) + return {FhgfsOpsErr_SUCCESS, fd}; + + if (fchown(fd, quota.uid, quota.gid)) + { + LOG(GENERAL, ERR, "Failed to chown().", path); + unlink(path.c_str()); + close(fd); + return {FhgfsOpsErr_INTERNAL, -1}; + } + + return {FhgfsOpsErr_SUCCESS, fd}; +} + +/** + * Create the chunkFile given by chunkFilePathStr and if that fails try to create the + * chunk directories (path) leading holding the chunk file. + * + * @param chunkDirPath can be NULL, then it will be constructed from chunkFilePathStr if needed + * @param exQuotaStore ptr to the exceeded quota store handling this target; may be NULL if quota + * is not needed + */ +FhgfsOpsErr ChunkStore::openChunkFile(int targetFD, const Path* chunkDirPath, + const std::string& chunkFilePathStr, bool hasOrigFeature, int openFlags, int* outFD, + const SessionQuotaInfo* quotaInfo, const ExceededQuotaStorePtr exQuotaStore) +{ + const char* logContext = "ChunkStore create chunkFile"; + FhgfsOpsErr retVal = FhgfsOpsErr_INTERNAL; + + // enforce quota only when the client has quota enabled + if(quotaInfo->useQuota && quotaInfo->enforceQuota && exQuotaStore) + { + // check if exceeded quotas exists, before doing a more expensive and explicit check + if(exQuotaStore->someQuotaExceeded() ) + { + // check if size and inode quota is exceeded + QuotaExceededErrorType exceededError = exQuotaStore->isQuotaExceeded(quotaInfo->uid, + quotaInfo->gid); + + if(exceededError != QuotaExceededErrorType_NOT_EXCEEDED) + { + //check if a new chunk file is required, last check because it is a expensive operation! + if(!StorageTk::pathExists(chunkFilePathStr.c_str() ) ) + { //report error only when quota is exceeded and a new chunk file is required + LogContext(logContext).log(Log_NOTICE, QuotaData::QuotaExceededErrorTypeToString( + exceededError) + " UID: " + StringTk::uintToStr(quotaInfo->uid) + " ; GID: " + + StringTk::uintToStr(quotaInfo->gid) ); + + return FhgfsOpsErr_DQUOT; + } + } + } + } + + std::tie(retVal, *outFD) = openAndChown(targetFD, chunkFilePathStr, openFlags, *quotaInfo); + if (retVal == FhgfsOpsErr_SUCCESS) + return FhgfsOpsErr_SUCCESS; + + // hash dir didn't exist yet or real error? + if (retVal == FhgfsOpsErr_PATHNOTEXISTS) + { // hash dir just didn't exist yet => create it and open again + Path chunkDirPathTmp; + if (!chunkDirPath) + { + chunkDirPathTmp = chunkFilePathStr; + chunkDirPathTmp = chunkDirPathTmp.dirname(); + chunkDirPath = &chunkDirPathTmp; + } + + ChunkDir* lastChunkDirElement; + + bool createPathRes = mkdirChunkDirPath(targetFD, chunkDirPath, hasOrigFeature, + &lastChunkDirElement); + if(!createPathRes) + { + int errCode = errno; + + LOG(GENERAL, ERR, "Unable to create path for file.", chunkFilePathStr, sysErr); + return FhgfsOpsErrTk::fromSysErr(errCode); + } + + // dir created => try file open/create again... + std::tie(retVal, *outFD) = openAndChown(targetFD, chunkFilePathStr, openFlags, *quotaInfo); + + if (lastChunkDirElement) // old V2 files do not get this + { + /* Unlock and release the last element once we have created + * (or at least tried to create) the file. */ + lastChunkDirElement->unlock(); + releaseDir(lastChunkDirElement->getID() ); + } + } + + if (retVal != FhgfsOpsErr_SUCCESS) + LOG(GENERAL, ERR, "Failed to create file.", chunkFilePathStr, retVal); + + return retVal; +} + + +/** + * V2 chunkDirs (2012.10 style layout: hashDir1/hashDir2) might have wrong permssions, correct that. + */ +bool ChunkStore::chmodV2ChunkDirPath(int targetFD, const Path* chunkDirPath, + const std::string& entryID) +{ + const char* logContext = "ChunkDirStore chmod V2 chunkdir path"; + bool retVal = true; + size_t pathElemIndex = 0; + bool didEntryID = false; + + std::string chmodPath; + + // Iterate and create basic chunks or mirror paths, we don't need any locking here + while ((pathElemIndex != chunkDirPath->size()) || didEntryID) + { + + if (pathElemIndex != chunkDirPath->size()) + { + chmodPath += (*chunkDirPath)[pathElemIndex]; + } + else + { + chmodPath += entryID; + didEntryID = true; + } + + int chmodRes = ::fchmodat(targetFD, chmodPath.c_str(), STORAGETK_DEFAULTCHUNKDIRMODE, 0); + if (chmodRes && errno != ENOENT) + { + LogContext(logContext).logErr("Unable to change chunk path permissions: " + chmodPath + + ". " +"SysErr: " + System::getErrString() ); + + retVal = false; + break; + } + + chmodPath = chmodPath + '/'; // path must be relative, so only add it here + + if (pathElemIndex < chunkDirPath->size()) + pathElemIndex++; + } + + return retVal; +} diff --git a/storage/source/storage/ChunkStore.h b/storage/source/storage/ChunkStore.h new file mode 100644 index 0000000..d6fe8fe --- /dev/null +++ b/storage/source/storage/ChunkStore.h @@ -0,0 +1,106 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ChunkDir.h" + + +#define PATH_DEPTH_IDENTIFIER 'l' // we use 'l' (level) instead of 'd', as d is part of hex numbers + + +class ChunkDir; + +typedef AtomicObjectReferencer ChunkDirReferencer; +typedef std::map DirectoryMap; +typedef DirectoryMap::iterator DirectoryMapIter; +typedef DirectoryMap::const_iterator DirectoryMapCIter; +typedef DirectoryMap::value_type DirectoryMapVal; + +typedef std::map DirCacheMap; // keys are dirIDs (same as DirMap) +typedef DirCacheMap::iterator DirCacheMapIter; +typedef DirCacheMap::const_iterator DirCacheMapCIter; +typedef DirCacheMap::value_type DirCacheMapVal; + +/** + * Layer in between our inodes and the data on the underlying file system. So we read/write from/to + * underlying files and this class is to do this corresponding data access. + * This object is used for for _directories_ only. + */ +class ChunkStore +{ + public: + ChunkStore(); + + ~ChunkStore() + { + this->clearStoreUnlocked(); + } + + bool dirInStoreUnlocked(std::string dirID); + ChunkDir* referenceDir(std::string dirID); + void releaseDir(std::string dirID); + + size_t getCacheSize(); + + bool cacheSweepAsync(); + + bool rmdirChunkDirPath(int targetFD, Path* chunkDirPath); + + FhgfsOpsErr openChunkFile(int targetFD, const Path* chunkDirPath, + const std::string& chunkFilePathStr, bool hasOrigFeature, int openFlags, int* outFD, + const SessionQuotaInfo* quotaInfo, const ExceededQuotaStorePtr exQuotaStore); + + bool chmodV2ChunkDirPath(int targetFD, const Path* chunkDirPath, const std::string& entryID); + + + private: + DirectoryMap dirs; + + size_t refCacheSyncLimit; // synchronous access limit (=> async limit plus some grace size) + size_t refCacheAsyncLimit; // asynchronous cleanup limit (this is what the user configures) + Random randGen; // for random cache removal + DirCacheMap refCache; + + RWLock rwlock; + + void InsertChunkDirUnlocked(std::string dirID, DirectoryMapIter& newElemIter); + + void releaseDirUnlocked(std::string dirID); + + void clearStoreUnlocked(); + + void cacheAddUnlocked(std::string& dirID, ChunkDirReferencer* dirRefer); + void cacheRemoveUnlocked(std::string& dirID); + void cacheRemoveAllUnlocked(); + bool cacheSweepUnlocked(bool isSyncSweep); + + bool mkdirV2ChunkDirPath(int targetFD, const Path* chunkDirPath); + + bool mkdirChunkDirPath(int targetFD, const Path* chunkDirPath, bool hasOrigFeature, + ChunkDir** outChunkDir); + + std::pair openAndChown(const int targetFD, const std::string& path, + const int openFlags, const SessionQuotaInfo& quota); + + // inlined + + /** + * Return a unique path element identifier. + * + * Note: All callers should use depth=0 for the first path element. + */ + std::string getUniqueDirID(std::string pathElement, unsigned pathDepth) + { + // Use snprintf here directly to make it cheaper? + return pathElement + "-l" + StringTk::uintToStr(pathDepth); + } + +}; + diff --git a/storage/source/storage/QuotaBlockDevice.cpp b/storage/source/storage/QuotaBlockDevice.cpp new file mode 100644 index 0000000..b70ded6 --- /dev/null +++ b/storage/source/storage/QuotaBlockDevice.cpp @@ -0,0 +1,108 @@ +#include +#include +#include +#include +#include "QuotaBlockDevice.h" + +#include +#include +#include + +#include +#include + +/** + * checks one storage target path and creates a QuotaBlockDevice + * + * @param targetPath path to block device to check + * @param targetNumID targetNumID of the storage target to check + */ +QuotaBlockDevice QuotaBlockDevice::getBlockDeviceOfTarget(const std::string& targetPath, + uint16_t targetNumID) +{ + const auto mountFound = StorageTk::findMountForPath(targetPath); + if (mountFound.first) + { + QuotaBlockDevice blockDevice(mountFound.second.path, + mountFound.second.device, + getFsType(targetPath), + targetPath); + + // check if the installed libzfs is compatible with implementation + if(blockDevice.getFsType() == QuotaBlockDeviceFsType_ZFS) + { + // note: if the installed zfslib doesn't support inode quota, + // QuotaBlockDeviceFsType_ZFSOLD will be set inside of checkRequiredLibZfsFunctions + if(!Program::getApp()->isDlOpenHandleLibZfsValid() ) + QuotaTk::checkRequiredLibZfsFunctions(&blockDevice, targetNumID); + } + + return blockDevice; + } + + return {}; +} + +/** + * checks all storage target paths and creates a QuotaBlockDevice + * + * @param targetPaths list with the paths of the storage target to check for enabled quota + * @param outBlockDevices the list with all QuotaBlockDevice + */ +void QuotaBlockDevice::getBlockDevicesOfTargets(TargetPathMap* targetPaths, + QuotaBlockDeviceMap* outBlockDevices) +{ + for(TargetPathMapIter iter = targetPaths->begin(); iter != targetPaths->end(); iter++) + { + uint16_t targetNumID = iter->first; + std::string storageTargetPath = iter->second; + + QuotaBlockDevice blockDevice = getBlockDeviceOfTarget(storageTargetPath, targetNumID); + outBlockDevices->insert(QuotaBlockDeviceMapVal(targetNumID, blockDevice) ); + } +} + + +QuotaInodeSupport QuotaBlockDevice::quotaInodeSupportFromBlockDevice() const +{ + switch(fsType) + { + case QuotaBlockDeviceFsType_EXTX: //no break because both FS requires the same handling + case QuotaBlockDeviceFsType_XFS: + return QuotaInodeSupport_ALL_BLOCKDEVICES; + break; + case QuotaBlockDeviceFsType_ZFS: + return QuotaInodeSupport_ALL_BLOCKDEVICES; + break; + case QuotaBlockDeviceFsType_ZFSOLD: + return QuotaInodeSupport_NO_BLOCKDEVICES; + break; + default: + return QuotaInodeSupport_UNKNOWN; + break; + } +} + +QuotaBlockDeviceFsType QuotaBlockDevice::getFsType(const std::string& path) +{ + struct statfs stats; + const auto result = statfs(path.c_str(), &stats); + + if (result != 0) + { + const auto err = errno; + throw std::runtime_error("Error getting FS Type of " + path + ":" + strerror(err)); + } + + switch (stats.f_type) + { + case EXT4_SUPER_MAGIC: /* same magic for EXT2/3 */ + return QuotaBlockDeviceFsType_EXTX; + case 0x58465342: /* from statfs man page */ + return QuotaBlockDeviceFsType_XFS; + case 0x2fc12fc1: /* found by running statfs on a zfs */ + return QuotaBlockDeviceFsType_ZFS; + } + + return QuotaBlockDeviceFsType_UNKNOWN; +} diff --git a/storage/source/storage/QuotaBlockDevice.h b/storage/source/storage/QuotaBlockDevice.h new file mode 100644 index 0000000..417bf54 --- /dev/null +++ b/storage/source/storage/QuotaBlockDevice.h @@ -0,0 +1,102 @@ +#pragma once + +#include +#include +#include +#include + + + +class QuotaBlockDevice; //forward declaration + + +typedef std::map QuotaBlockDeviceMap; +typedef QuotaBlockDeviceMap::iterator QuotaBlockDeviceMapIter; +typedef QuotaBlockDeviceMap::const_iterator QuotaBlockDeviceMapConstIter; +typedef QuotaBlockDeviceMap::value_type QuotaBlockDeviceMapVal; + + +class QuotaBlockDevice +{ + public: + QuotaBlockDevice() + { + this->storageTargetPath = ""; + this->mountPath = ""; + this->blockDevicePath = ""; + this->fsType = QuotaBlockDeviceFsType_UNKNOWN; + } + + QuotaBlockDevice(std::string mountPath, std::string blockDevicePath, + QuotaBlockDeviceFsType fsType, std::string storageTargetPath) + { + this->mountPath = mountPath; + this->blockDevicePath = blockDevicePath; + this->fsType = fsType; + + // normalize storage target path + Path storagePath(storageTargetPath); // to get a normalized path string + std::string normalizedStoragePath(storagePath.str()); + this->storageTargetPath = normalizedStoragePath; + } + + static QuotaBlockDevice getBlockDeviceOfTarget(const std::string& targetPath, + uint16_t targetNumID); + static void getBlockDevicesOfTargets(TargetPathMap* targetPaths, + QuotaBlockDeviceMap* outBlockDevices); + + QuotaInodeSupport quotaInodeSupportFromBlockDevice() const; + + static QuotaBlockDeviceFsType getFsType(const std::string& path); + + + private: + std::string storageTargetPath; + std::string mountPath; + std::string blockDevicePath; + QuotaBlockDeviceFsType fsType; + + + public: + // getter & setter + std::string getMountPath() const + { + return mountPath; + } + + void setMountPath(std::string mountPath) + { + this->mountPath = mountPath; + } + + std::string getBlockDevicePath() const + { + return blockDevicePath; + } + + void setBlockDevicePath(std::string blockDevicePath) + { + this->blockDevicePath = blockDevicePath; + } + + QuotaBlockDeviceFsType getFsType() const + { + return fsType; + } + + std::string getStorageTargetPath() const + { + return storageTargetPath; + } + + void setFsType(QuotaBlockDeviceFsType fsType) + { + this->fsType = fsType; + } + + bool supportsInodeQuota() const + { + return quotaInodeSupportFromBlockDevice() == QuotaInodeSupport_ALL_BLOCKDEVICES; + } +}; + diff --git a/storage/source/storage/StorageTargets.cpp b/storage/source/storage/StorageTargets.cpp new file mode 100644 index 0000000..2d68363 --- /dev/null +++ b/storage/source/storage/StorageTargets.cpp @@ -0,0 +1,558 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "StorageTargets.h" + +#include + + +#define STORAGETARGETS_TARGETDIR_MKDIR_MODE (S_IRWXU) +#define STORAGETARGETS_CHUNKDIR_MKDIR_MODE (S_IRWXU | S_IRWXG | S_IRWXO) + +#define BUDDY_NEEDS_RESYNC_FILENAME ".buddyneedsresync" +#define LAST_BUDDY_COMM_TIMESTAMP_FILENAME ".lastbuddycomm" + + +StorageTarget::StorageTarget(Path path, uint16_t targetID, TimerQueue& timerQueue, + NodeStoreServers& mgmtNodes, MirrorBuddyGroupMapper& buddyGroupMapper): + path(std::move(path)), id(targetID), + buddyNeedsResyncFile((this->path / BUDDY_NEEDS_RESYNC_FILENAME).str(), S_IRUSR | S_IWUSR), + lastBuddyCommFile((this->path / LAST_BUDDY_COMM_TIMESTAMP_FILENAME).str(), S_IRUSR | S_IWUSR), + timerQueue(timerQueue), mgmtNodes(mgmtNodes), + buddyGroupMapper(buddyGroupMapper), buddyResyncInProgress(false), + consistencyState(TargetConsistencyState_GOOD), cleanShutdown(false) +{ + const auto chunkPath = this->path / CONFIG_CHUNK_SUBDIR_NAME; + chunkFD = FDHandle(open(chunkPath.str().c_str(), O_RDONLY | O_DIRECTORY)); + if (!chunkFD.valid()) + throw std::system_error(errno, std::system_category(), chunkPath.str()); + + // mirror chunks path + + const auto mirrorPath = this->path / CONFIG_BUDDYMIRROR_SUBDIR_NAME; + mirrorFD = FDHandle(open(mirrorPath.str().c_str(), O_RDONLY | O_DIRECTORY)); + if (!mirrorFD.valid()) + throw std::system_error(errno, std::system_category(), mirrorPath.str()); + + quotaBlockDevice = QuotaBlockDevice::getBlockDeviceOfTarget(this->path.str(), targetID); + + if (buddyNeedsResyncFile.read().get_value_or(0) & BUDDY_RESYNC_UNACKED_FLAG) + { + setBuddyNeedsResyncEntry = timerQueue.enqueue(std::chrono::seconds(0), [this] { + retrySetBuddyNeedsResyncComm(); + }); + } +} + +void StorageTarget::prepareTargetDir(const Path& path) +{ + mode_t targetMkdirMode = STORAGETARGETS_TARGETDIR_MKDIR_MODE; + mode_t chunkMkDirMode = STORAGETARGETS_CHUNKDIR_MKDIR_MODE; + + if (!StorageTk::createPathOnDisk(path, false, &targetMkdirMode)) + throw InvalidConfigException("Unable to create storage directory: " + path.str()); + + // storage format file + if (!StorageTk::createStorageFormatFile(path.str(), STORAGETK_FORMAT_CURRENT_VERSION)) + throw InvalidConfigException("Unable to create storage format file in: " + path.str()); + + StorageTk::loadAndUpdateStorageFormatFile(path.str(), STORAGETK_FORMAT_MIN_VERSION, + STORAGETK_FORMAT_CURRENT_VERSION); + + // chunks directory + Path chunksPath = path / CONFIG_CHUNK_SUBDIR_NAME; + + if (!StorageTk::createPathOnDisk(chunksPath, false, &chunkMkDirMode)) + throw InvalidConfigException("Unable to create chunks directory: " + chunksPath.str()); + + // buddy mirror directory + Path mirrorPath = path / CONFIG_BUDDYMIRROR_SUBDIR_NAME; + if (!StorageTk::createPathOnDisk(mirrorPath, false, &chunkMkDirMode)) + throw InvalidConfigException("Unable to create buddy mirror directory: " + mirrorPath.str()); +} + +bool StorageTarget::isTargetDir(const Path& path) +{ + return StorageTk::checkStorageFormatFileExists(path.str()); +} + +void StorageTarget::setBuddyNeedsResync(bool needsResync) +{ + const RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + const auto oldState = buddyNeedsResyncFile.read().get_value_or(BUDDY_RESYNC_NOT_REQUIRED); + const auto newState = needsResync + ? BUDDY_RESYNC_REQUIRED_UNACKED + : BUDDY_RESYNC_NOT_REQUIRED_UNACKED; + + // if the change has already been requested by some other thread, we should not request it + // again - even if the change is unacked, as retrying immediately after a failed communication + // attempt is not likely to be successful, we must handle externally started resyncs however, + // which *do not* change the buddyneedsresync file contents but *do* use this mechanism to + // communicate that a resync has finished and the buddy is good again - these only use us to + // set the buddy to "needs no resync" though, so we can still skip setting needs-resync when that + // is already pending. + if (needsResync + && (oldState & BUDDY_RESYNC_REQUIRED_FLAG) == (newState & BUDDY_RESYNC_REQUIRED_FLAG)) + return; + + // cancel any pending retries, we will send a message to mgmt anyway. + if (setBuddyNeedsResyncEntry) + setBuddyNeedsResyncEntry->cancel(); + + buddyNeedsResyncFile.write(newState); + + if (!setBuddyNeedsResyncComm()) + LOG(GENERAL, CRITICAL, "Could not reach mgmt for state update, will retry.", + ("primary target", id), ("buddyNeedsResync", needsResync)); +} + +bool StorageTarget::setBuddyNeedsResyncComm() +{ + // this is a timer callback. as such we must be prepared to deal with the fact that we were + // cancelled *after* we were dequeued and started executing, but were blocked on the lock in + // retrySetBuddyNeedsResyncComm. always reading the current state and sending that fixes this: + // if the state is not unacked we can return without doing anything, and if it is nobody can + // change it while we are using it. + const auto state = buddyNeedsResyncFile.read().get_value_or(BUDDY_RESYNC_NOT_REQUIRED); + const bool needsResync = state & BUDDY_RESYNC_REQUIRED_FLAG; + + if (!(state & BUDDY_RESYNC_UNACKED_FLAG)) + return true; + + const TargetConsistencyState stateToSet = needsResync + ? TargetConsistencyState_NEEDS_RESYNC + : TargetConsistencyState_GOOD; + + bool currentIsPrimary; + const uint16_t buddyTargetID = buddyGroupMapper.getBuddyTargetID(id, ¤tIsPrimary); + + // until mgmt handles resync decision, refuse to set a primary to needs-resync locally. + if (!currentIsPrimary) + return true; + + UInt16List targetIDList(1, buddyTargetID); + UInt8List stateList(1, stateToSet); + + SetTargetConsistencyStatesMsg msg(NODETYPE_Storage, &targetIDList, &stateList, false); + + const auto respMsg = MessagingTk::requestResponse(*mgmtNodes.referenceFirstNode(), msg, + NETMSGTYPE_SetTargetConsistencyStatesResp); + + if (!respMsg) + { + setBuddyNeedsResyncEntry = timerQueue.enqueue(std::chrono::seconds(5), [this] { + retrySetBuddyNeedsResyncComm(); + }); + return false; + } + + auto* respMsgCast = (SetTargetConsistencyStatesRespMsg*)respMsg.get(); + + if (respMsgCast->getValue() != FhgfsOpsErr_SUCCESS) + { + LOG(GENERAL, CRITICAL, "Management node did not accept target states.", buddyTargetID, + needsResync); + return true; + } + + buddyNeedsResyncFile.write(state & ~BUDDY_RESYNC_UNACKED_FLAG); + if (state & BUDDY_RESYNC_REQUIRED_FLAG) + LOG(GENERAL, CRITICAL, "Marked secondary buddy for needed resync.", ("primary target", id)); + return true; +} + +void StorageTarget::handleTargetStateChange() +{ + const auto newState = getConsistencyState(); + + LOG(GENERAL, DEBUG, "Notifying management node of target state change.", id, newState); + + if (getOfflineTimeout()) + Program::getApp()->getInternodeSyncer()->publishTargetState(id, newState); +} + + +/** + * Decide whether a resync is needed for any of the storage targets, and set its targetState to + * TargetState_RESYNCING if so. + */ +void StorageTargets::decideResync(const TargetStateMap& statesFromMgmtd, + TargetStateMap& outLocalStateChanges) +{ + const char* logContext = "Decide resync"; + + App* app = Program::getApp(); + MirrorBuddyGroupMapper* mirrorBuddyGroupMapper = app->getMirrorBuddyGroupMapper(); + + std::map targetBuddyGroupMap; + { + MirrorBuddyGroupMap groupMap; + mirrorBuddyGroupMapper->getMirrorBuddyGroups(groupMap); + + for (const auto& group : groupMap) + { + targetBuddyGroupMap[group.second.firstTargetID] = group.first; + targetBuddyGroupMap[group.second.secondTargetID] = group.first; + } + } + + + // Make a map of targetID->Iterator on statesFromMgmtd containing only the local target + // to make lookup under lock quicker. + typedef std::map TargetStateIterMap; + TargetStateIterMap stateFromMgmtdIterMap; + // First we make a list of local target IDs (so later, when we iterate over all local states + // in the "main loop" holding a read lock, we don't have to look them up in the (potentially + // big) statesFromMgmtd, only in the smaller stateFromMgmtdIterMap. + UInt16List localTargetIDs; + { + for (StorageTargetMapCIter localTargetIt = this->storageTargetMap.begin(); + localTargetIt != this->storageTargetMap.end(); ++localTargetIt) + localTargetIDs.push_back(localTargetIt->first); + } + + // Fill the map (only operating on local data structures -> no lock needed). + for (UInt16ListConstIter localTargetIDIt = localTargetIDs.begin(); + localTargetIDIt != localTargetIDs.end(); ++localTargetIDIt) + { + const uint16_t targetID = *localTargetIDIt; + + TargetStateMapCIter targetStateMapIter = statesFromMgmtd.find(targetID); + if (targetStateMapIter == statesFromMgmtd.end() ) + { + LOG_DEBUG(logContext, Log_DEBUG, "Local target " + StringTk::uintToStr(targetID) + + " not found in management node state report."); + continue; + } + + stateFromMgmtdIterMap.insert(std::pair( + targetID, targetStateMapIter) ); + } + + // Now we can iterate over the storageTargetMap and do the actual resync decision. + for (auto storageTargetIt = this->storageTargetMap.begin(); + storageTargetIt != this->storageTargetMap.end(); ++storageTargetIt) + { + uint16_t targetID = storageTargetIt->first; + StorageTarget& targetData = *storageTargetIt->second; + + TargetStateIterMap::const_iterator newStateFromMgmtdIt = stateFromMgmtdIterMap.find(targetID); + if (newStateFromMgmtdIt == stateFromMgmtdIterMap.end() ) + continue; // Don't need to log - has been logged above. + + // Note: This still references the state stored in the inOutStatesFromMgmtd map. + const CombinedTargetState& newStateFromMgmtd = newStateFromMgmtdIt->second->second; + + MirrorBuddyState buddyState = BuddyState_UNMAPPED; + + { + const auto bgIDIter = targetBuddyGroupMap.find(targetID); + + if (bgIDIter != targetBuddyGroupMap.end()) + buddyState = mirrorBuddyGroupMapper->getBuddyState(targetID, bgIDIter->second); + } + + if (buddyState == BuddyState_UNMAPPED) // Unmapped targets can never be resyncing. + { + LOG_DBG(STATES, DEBUG, + "Setting target to state good because it is not part of a mirror group.", + ("oldState", TargetStateStore::stateToStr(targetData.getConsistencyState())), + targetID); + + targetData.setConsistencyState(TargetConsistencyState_GOOD); + + outLocalStateChanges.insert(TargetStateMapVal(targetID, + CombinedTargetState(TargetReachabilityState_ONLINE, TargetConsistencyState_GOOD) ) ); + + // Setting the cleanShutdown flag has two purposes here: + // * An unmapped target does not have a buddy to resync from, therefore even if it's not + // clean nothing can be done about it. + // * A newly created target has to be marked as "clean" before it can join any mirror buddy + // group. + targetData.setCleanShutdown(true); + + continue; + } + + // BAD targets will stay BAD unless server is restarted. + if (targetData.getConsistencyState() == TargetConsistencyState_BAD) + continue; + + const TargetConsistencyState oldState = targetData.getConsistencyState(); + + const bool isResyncing = + newStateFromMgmtd.consistencyState == TargetConsistencyState_NEEDS_RESYNC; + const bool isBad = newStateFromMgmtd.consistencyState == TargetConsistencyState_BAD; + const bool isClean = targetData.getCleanShutdown(); + + // cleanShutdown should only be handled once at startup, so it is reset here after its value + // has been copied. + targetData.setCleanShutdown(true); + + if (storageTargetIt->second->getOfflineTimeout()) + { + // Targets with a waiting-for-offline timeout will always be NEEDS_RESYNC. + targetData.setConsistencyState(TargetConsistencyState_NEEDS_RESYNC); + outLocalStateChanges.insert(TargetStateMapVal(targetID, CombinedTargetState( + TargetReachabilityState_ONLINE, TargetConsistencyState_NEEDS_RESYNC) ) ); + } + else + if (oldState == TargetConsistencyState_NEEDS_RESYNC) + { + // If our local state is already RESYNCING, this state can only be left + // when our primary tells us the resync is finished. + targetData.setConsistencyState(TargetConsistencyState_NEEDS_RESYNC); + outLocalStateChanges.insert(TargetStateMapVal(targetID, CombinedTargetState( + TargetReachabilityState_ONLINE, TargetConsistencyState_NEEDS_RESYNC) ) ); + } + else + if(!isClean || isResyncing || isBad ) + { + // This condition implements the following decision graph: + // RESYNC? ->o ,-->o-->(clean shutdown)--> NO + // |\ ,->(!resyncing)--' `-(dirty)--. + // \ `-------------->o->(resyncing)---------------------,`---> YES + // `------->(bad)-------------------------------------' + + targetData.setConsistencyState(TargetConsistencyState_NEEDS_RESYNC); + outLocalStateChanges.insert(TargetStateMapVal(targetID, CombinedTargetState( + TargetReachabilityState_ONLINE, TargetConsistencyState_NEEDS_RESYNC) ) ); + } + else + { + // If mgmtd reports the target is (P)OFFLINE, then the storage server knows better and we + // set the target to GOOD / ONLINE. Otherwise we accept the state reported by the mgmtd. + if( (newStateFromMgmtd.reachabilityState == TargetReachabilityState_OFFLINE) + || (newStateFromMgmtd.reachabilityState == TargetReachabilityState_POFFLINE) ) + { + targetData.setConsistencyState(TargetConsistencyState_GOOD); + outLocalStateChanges.insert(TargetStateMapVal(targetID, CombinedTargetState( + TargetReachabilityState_ONLINE, TargetConsistencyState_GOOD) ) ); + + } + else + { + targetData.setConsistencyState(newStateFromMgmtd.consistencyState); + outLocalStateChanges.insert(TargetStateMapVal(targetID, CombinedTargetState( + TargetReachabilityState_ONLINE, newStateFromMgmtd.consistencyState) ) ); + } + } + + if ( targetData.getConsistencyState() != oldState + || targetData.getConsistencyState() != newStateFromMgmtd.consistencyState + || newStateFromMgmtd.reachabilityState != TargetReachabilityState_ONLINE) + { + LOG_DEBUG(logContext, Log_DEBUG, "Target " + StringTk::uintToStr(targetID) + + "; State (from management node): " + TargetStateStore::stateToStr(newStateFromMgmtd) + + "; resyncing: " + (isResyncing ? "yes" : "no") + + "; clean: " + (isClean ? "yes" : "no") + + "; bad: " + (isBad ? "yes" : "no") + "." + ); + + LogContext(logContext).log(Log_NOTICE, "Target " + StringTk::uintToStr(targetID) + + ": Setting new target state: " + + TargetStateStore::stateToStr(targetData.getConsistencyState()) + + " (old state: " + TargetStateStore::stateToStr(oldState) + ")" ); + } + } +} + + +/** + * Checks all local primary buddies whether there is a buddyneedsresync file, and if so, sets the + * buddy's consistency state on the mgmtd to NEEDS_RESYNC. Afterwards, checks the all the target + * state of the secondary buddies belonging to local primaries: if any secondary is ONLINE and + * NEEDS_RESYNC, a ResyncJob is started. + */ +void StorageTargets::checkBuddyNeedsResync() +{ + const char* logContext = "Check buddy needs resync"; + App* app = Program::getApp(); + MirrorBuddyGroupMapper* mirrorBuddyGroupMapper = app->getMirrorBuddyGroupMapper(); + TargetStateStore* targetStateStore = app->getTargetStateStore(); + BuddyResyncer* buddyResyncer = app->getBuddyResyncer(); + + // For targets where primary is on this storage server: + // Maps targetID->targetPath. + std::map targetPathMap; + // Maps primary->secondary. (Note: Not the same as TargetBuddyGroupMap, which maps target->group) + typedef std::map BuddyGroupMap; + typedef BuddyGroupMap::const_iterator BuddyGroupMapCIter; + typedef BuddyGroupMap::value_type BuddyGroupMapVal; + BuddyGroupMap buddyGroupMap; + + { + for (auto targetDataIt = this->storageTargetMap.begin(); + targetDataIt != this->storageTargetMap.end(); ++targetDataIt) + { + uint16_t targetID = targetDataIt->first; + + targetPathMap.insert({targetID, targetDataIt->second->getPath()}); + + bool isPrimary; + uint16_t buddyTargetID = mirrorBuddyGroupMapper->getBuddyTargetID(targetID, &isPrimary); + + if (isPrimary) + buddyGroupMap.insert(BuddyGroupMapVal(targetID, buddyTargetID) ); + } + + } + + // Now, check all the primary targets for the existence of a buddyneedsresync file. + for (const auto& targetPath : targetPathMap) + { + const uint16_t targetID = targetPath.first; + + bool isPrimary; + const uint16_t buddyTargetID = mirrorBuddyGroupMapper->getBuddyTargetID(targetID, &isPrimary); + + if (!isPrimary) + continue; + + const bool buddyNeedsResync = storageTargetMap.at(targetID)->getBuddyNeedsResync(); + + if (buddyNeedsResync) + { + LOG_DEBUG(logContext, Log_DEBUG, "buddyneedsresync indication found for target " + + StringTk::uintToStr(targetID) ); + + CombinedTargetState state = CombinedTargetState(TargetReachabilityState_ONLINE, + TargetConsistencyState_NEEDS_RESYNC); + targetStateStore->getState(buddyTargetID, state); + + // Only send message if buddy was still reported as GOOD before (otherwise the mgmtd + // already knows it needs a resync, or it's BAD and shouldn't be resynced anyway. + if (state.consistencyState == TargetConsistencyState_GOOD) + { + storageTargetMap.at(targetID)->setBuddyNeedsResync(true); + + LogContext(logContext).log(Log_NOTICE, "Set needs-resync state for buddy target " + + StringTk::uintToStr(buddyTargetID) ); + } + } + } + + // And check all secondary targets whether they are in NEEDS_RESYNC state. + for (BuddyGroupMapCIter buddyGroupIter = buddyGroupMap.begin(); + buddyGroupIter != buddyGroupMap.end(); ++buddyGroupIter) + { + const uint16_t targetID = buddyGroupIter->first; + const uint16_t buddyTargetID = buddyGroupIter->second; + + const auto targetConsistencyState = storageTargetMap.at(targetID)->getConsistencyState(); + + // If primary is not GOOD, don't start a resync (wait until InternodeSyncer sets the primary + // to GOOD again). + if (targetConsistencyState != TargetConsistencyState_GOOD) + { + LOG_DEBUG(logContext, Log_DEBUG, "Target state is not good, won't check buddy target state" + "; target ID " + StringTk::uintToStr(targetID) ); + continue; + } + + CombinedTargetState buddyTargetState; + if (!targetStateStore->getState(buddyTargetID, buddyTargetState) ) + { + LOG_DEBUG(logContext, Log_DEBUG, "Buddy target state invalid for target ID " + + StringTk::uintToStr(buddyTargetID) ); + continue; + } + + if (buddyTargetState == + CombinedTargetState(TargetReachabilityState_ONLINE, TargetConsistencyState_NEEDS_RESYNC) ) + { + FhgfsOpsErr resyncRes = buddyResyncer->startResync(targetID); + + if (resyncRes == FhgfsOpsErr_SUCCESS) + { + LogContext(logContext).log(Log_WARNING, "Target: " + StringTk::uintToStr(targetID) + + " start buddy resync job."); + } + else + { + LOG_DEBUG(logContext, Log_DEBUG, "Target: " + StringTk::uintToStr(targetID) + + " start buddy resync job failed (" + + boost::lexical_cast(resyncRes) + ")"); + } + } + } +} + +void StorageTargets::generateTargetInfoList(StorageTargetInfoList& outTargetInfoList) +{ + for (const auto& mapping : storageTargetMap) + { + uint16_t targetID = mapping.first; + const auto& target = mapping.second; + std::string targetPathStr; + int64_t sizeTotal = 0; + int64_t sizeFree = 0; + int64_t inodesTotal = 0; + int64_t inodesFree = 0; + TargetConsistencyState targetState = TargetConsistencyState_BAD; + + targetPathStr = target->getPath().str(); + + getStatInfo(targetPathStr, &sizeTotal, &sizeFree, &inodesTotal, &inodesFree); + + targetState = target->getConsistencyState(); + + StorageTargetInfo targetInfo(targetID, targetPathStr, sizeTotal, sizeFree, inodesTotal, + inodesFree, targetState); + outTargetInfoList.push_back(targetInfo); + } + +} + +/** + * note: sets "-1" for all out-values if statfs failed. + */ +void StorageTargets::getStatInfo(std::string& targetPathStr, int64_t* outSizeTotal, + int64_t* outSizeFree, int64_t* outInodesTotal, int64_t* outInodesFree) +{ + const char* logContext = "Get storage target info"; + + bool statSuccess = StorageTk::statStoragePath(targetPathStr, outSizeTotal, outSizeFree, + outInodesTotal, outInodesFree); + + if(unlikely(!statSuccess) ) + { // error + LogContext(logContext).logErr("Unable to statfs() storage path: " + targetPathStr + + " (SysErr: " + System::getErrString() ); + + *outSizeTotal = -1; + *outSizeFree = -1; + *outInodesTotal = -1; + *outInodesFree = -1; + } + + // read and use value from manual free space override file (if it exists) + StorageTk::statStoragePathOverride(targetPathStr, outSizeFree, outInodesFree); +} + +/** + * Fill a storageTargetMap with the contents of the three lists passed. + * targetIDs, reachabilityStates and consistencyStates is interpreted as a 1:1 mapping. + */ +void StorageTargets::fillTargetStateMap(const UInt16List& targetIDs, + const UInt8List& reachabilityStates, const UInt8List& consistencyStates, + TargetStateMap& outStateMap) +{ + for (ZipConstIterRange + iter(targetIDs, reachabilityStates, consistencyStates); + !iter.empty(); ++iter) + { + outStateMap.insert(TargetStateMapVal(*iter()->first, CombinedTargetState( + static_cast(*iter()->second), + static_cast(*iter()->third) + ) ) ); + } +} diff --git a/storage/source/storage/StorageTargets.h b/storage/source/storage/StorageTargets.h new file mode 100644 index 0000000..651ed92 --- /dev/null +++ b/storage/source/storage/StorageTargets.h @@ -0,0 +1,254 @@ +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +class NodeStoreServers; +class MirrorBuddyGroupMapper; + +class StorageTarget +{ + public: + StorageTarget(Path path, uint16_t targetID, TimerQueue& timerQueue, + NodeStoreServers& mgmtNodes, MirrorBuddyGroupMapper& buddyGroupMapper); + + ~StorageTarget() + { + if (setBuddyNeedsResyncEntry) + setBuddyNeedsResyncEntry->cancel(); + } + + static void prepareTargetDir(const Path& path); + + static bool isTargetDir(const Path& path); + + const Path& getPath() const { return path; } + uint16_t getID() const { return id; } + const FDHandle& getChunkFD() const { return chunkFD; } + const FDHandle& getMirrorFD() const { return mirrorFD; } + const QuotaBlockDevice& getQuotaBlockDevice() const { return quotaBlockDevice; } + + TargetConsistencyState getConsistencyState() const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + return consistencyState; + } + + void setConsistencyState(TargetConsistencyState tcs) + { + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + consistencyState = tcs; + } + + bool getBuddyResyncInProgress() const { return buddyResyncInProgress; } + void setBuddyResyncInProgress(bool b) { buddyResyncInProgress = b; } + + bool getCleanShutdown() const + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + return cleanShutdown; + } + + void setCleanShutdown(bool b) + { + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + cleanShutdown = b; + } + + void setOfflineTimeout(std::chrono::milliseconds timeout) + { + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + offlineTimeoutEnd = std::chrono::steady_clock::now() + timeout; + } + + boost::optional getOfflineTimeout() const + { + { + RWLockGuard const lock(rwlock, SafeRWLock_READ); + if (!offlineTimeoutEnd) + return boost::none; + + const auto remaining = *offlineTimeoutEnd - std::chrono::steady_clock::now(); + if (remaining.count() > 0) + return std::chrono::duration_cast(remaining); + } + + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + offlineTimeoutEnd = boost::none; + return boost::none; + } + + bool getBuddyNeedsResync() const + { + return (buddyNeedsResyncFile.read().get_value_or(0) & BUDDY_RESYNC_REQUIRED_FLAG) != 0; + } + + void setBuddyNeedsResync(bool needsResync); + + std::pair getLastBuddyComm() const + { + const auto state = lastBuddyCommFile.read().get_value_or({}); + if (state.overrideSecs == 0) + return {false, std::chrono::system_clock::from_time_t(state.lastCommSecs)}; + else + return {true, std::chrono::system_clock::from_time_t(state.overrideSecs)}; + } + + void setLastBuddyComm(std::chrono::system_clock::time_point time, bool isOverride) + { + RWLockGuard const lock(rwlock, SafeRWLock_WRITE); + + auto state = lastBuddyCommFile.read().get_value_or({}); + if (isOverride) + state.overrideSecs = std::chrono::system_clock::to_time_t(time); + else + state.lastCommSecs = std::chrono::system_clock::to_time_t(time); + + lastBuddyCommFile.write(state); + } + + void setState(TargetConsistencyState newState) + { + const auto stateChanged = [&] { + const RWLockGuard lock(rwlock, SafeRWLock_WRITE); + + const auto oldState = consistencyState; + consistencyState = newState; + LOG_DBG(STATES, DEBUG, "Changing target consistency state.", id, + ("oldState", TargetStateStore::stateToStr(oldState)), + ("newState", TargetStateStore::stateToStr(newState)), + ("Called from", Backtrace<3>())); + + return oldState != newState; + }(); + + if (stateChanged) + handleTargetStateChange(); + } + + private: + struct LastBuddyComm + { + int64_t lastCommSecs; + int64_t overrideSecs; + + template + static void serialize(This obj, Ctx& ctx) + { + ctx + % obj->lastCommSecs + % obj->overrideSecs; + } + }; + + enum { + BUDDY_RESYNC_UNACKED_FLAG = 1, + BUDDY_RESYNC_REQUIRED_FLAG = 2, + + BUDDY_RESYNC_NOT_REQUIRED = 0, + BUDDY_RESYNC_NOT_REQUIRED_UNACKED = BUDDY_RESYNC_UNACKED_FLAG, + BUDDY_RESYNC_REQUIRED = BUDDY_RESYNC_REQUIRED_FLAG, + BUDDY_RESYNC_REQUIRED_UNACKED = BUDDY_RESYNC_REQUIRED_FLAG | BUDDY_RESYNC_UNACKED_FLAG, + }; + + Path path; + uint16_t id; + FDHandle chunkFD; + FDHandle mirrorFD; + PreallocatedFile buddyNeedsResyncFile; + PreallocatedFile lastBuddyCommFile; + QuotaBlockDevice quotaBlockDevice; // quota related information about the block device + TimerQueue& timerQueue; + NodeStoreServers& mgmtNodes; + MirrorBuddyGroupMapper& buddyGroupMapper; + + std::atomic buddyResyncInProgress; + + mutable RWLock rwlock; + TargetConsistencyState consistencyState; + bool cleanShutdown; // Was the previous session cleanly shut down? + mutable boost::optional offlineTimeoutEnd; + boost::optional setBuddyNeedsResyncEntry; + + bool setBuddyNeedsResyncComm(); + + void handleTargetStateChange(); + + void retrySetBuddyNeedsResyncComm() + { + const RWLockGuard lock(rwlock, SafeRWLock_WRITE); + setBuddyNeedsResyncComm(); + } +}; + +typedef std::map> StorageTargetMap; +typedef StorageTargetMap::iterator StorageTargetMapIter; +typedef StorageTargetMap::const_iterator StorageTargetMapCIter; + +typedef std::map ConsistencyStateMap; +typedef ConsistencyStateMap::iterator ConsistencyStateMapIter; +typedef ConsistencyStateMap::const_iterator ConsistencyStateMapCIter; +typedef ConsistencyStateMap::value_type ConsistencyStateMapVal; + +typedef std::map TargetNeedsResyncCountMap; + +class StorageTargets +{ + public: + StorageTargets(std::map> targets): + storageTargetMap(std::move(targets)) + { + } + + void decideResync(const TargetStateMap& statesFromMgmtd, + TargetStateMap& outLocalStateChanges); + void checkBuddyNeedsResync(); + + void generateTargetInfoList(StorageTargetInfoList& outTargetInfoList); + + static void fillTargetStateMap(const UInt16List& targetIDs, + const UInt8List& reachabilityStates, const UInt8List& consistencyStates, + TargetStateMap& outStateMap); + + private: + const StorageTargetMap storageTargetMap; // keys: targetIDs; values: StorageTargetData + + void handleTargetStateChange(uint16_t targetID, TargetConsistencyState newState); + + static void getStatInfo(std::string& targetPathStr, int64_t* outSizeTotal, + int64_t* outSizeFree, int64_t* outInodesTotal, int64_t* outInodesFree); + + public: + const StorageTarget* getTarget(uint16_t id) const + { + return const_cast(this)->getTarget(id); + } + + StorageTarget* getTarget(uint16_t id) + { + const auto it = storageTargetMap.find(id); + return it != storageTargetMap.end() + ? it->second.get() + : nullptr; + } + + const std::map>& getTargets() const + { + return storageTargetMap; + } +}; + + diff --git a/storage/source/storage/SyncedStoragePaths.h b/storage/source/storage/SyncedStoragePaths.h new file mode 100644 index 0000000..244e12b --- /dev/null +++ b/storage/source/storage/SyncedStoragePaths.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include + +#include + +class SyncedStoragePaths +{ + // type definitions + typedef std::set StoragePathsSet; // key: string (path and target) + typedef StoragePathsSet::iterator StoragePathsSetIter; + typedef StoragePathsSet::value_type StoragePathsSetVal; + + + public: + SyncedStoragePaths() + { + initStorageVersion(); + } + + + private: + Mutex mutex; + Condition eraseCond; // broadcasted when path erased from map + uint64_t storageVersion; // zero is the invalid version! + StoragePathsSet paths; // for currently locked paths + + + // inliners + + void initStorageVersion() + { + /* note: we assume here that the clock doesn't jump too much backwards between restarts of + the daemon and that we don't have more than 2^32 increase ops per second (the latter + shouldn't be a problem for the next years) */ + + uint64_t currentSecs = System::getCurrentTimeSecs(); + + this->storageVersion = (currentSecs << 32); + } + + /** + * Note: Caller must hold the mutex. + */ + uint64_t incStorageVersion() + { + return ++storageVersion; + } + + + public: + // inliners + + /** + * Locks a path and creates a new monotonic increasing storage version for it. + * + * Note: Make sure to unlock the same path later via unlockPath(). + * + * @return storage version for this path lock + */ + uint64_t lockPath(const std::string path, uint16_t targetID) + { + /* we just have to make sure that each target+path is inserted (=>locked) only once and + that the next one who wants to insert the same path will wait until the old path is + erased (=> unlocked) */ + + std::string targetPath(path + "@" + StringTk::uint64ToHexStr(targetID) ); + + const std::lock_guard lock(mutex); + + while(!paths.insert(targetPath).second) + eraseCond.wait(&mutex); + + return incStorageVersion(); + } + + void unlockPath(const std::string path, uint16_t targetID) + { + // unlocking just means we erase the target+path from the map, so the next one can lock it + + std::string targetPath(path + "@" + StringTk::uintToHexStr(targetID) ); + + const std::lock_guard lock(mutex); + + size_t numErased = paths.erase(targetPath); + if(unlikely(!numErased) ) + { + LOG_DEBUG("SyncedStorgePaths::unlockPath", Log_ERR, + "Attempt to unlock a path that wasn't locked: " + targetPath); + } + + eraseCond.broadcast(); + } + +}; + + diff --git a/storage/source/toolkit/QuotaTk.cpp b/storage/source/toolkit/QuotaTk.cpp new file mode 100644 index 0000000..ee669ba --- /dev/null +++ b/storage/source/toolkit/QuotaTk.cpp @@ -0,0 +1,430 @@ +#include +#include +#include +#include "QuotaTk.h" + +#include + +#include +// xfs/xfs.h includes linux/fs.h, which (sometimes) defines MS_RDONLY. +// sys/mount.h is the canonical source of MS_RDONLY, but declares it as an enum - which is then +// broken by linux/fs.h when included after it +#include +#include +#include + +#define QUOTATK_ZFS_USER_QUOTA "userused@" +#define QUOTATK_ZFS_GROUP_QUOTA "groupused@" +#define QUOTATK_ZFS_USER_INODE_QUOTA "userobjused@" +#define QUOTATK_ZFS_GROUP_INODE_QUOTA "groupobjused@" + +/** + * Get quota data for a single ID and append it to the outQuotaDataList. + * + * @param id the user or group ID to check + * @param type the type of the ID user or group, enum QuotaDataType + * @param blockDevices the QuotaBlockDevice to check + * @param outQuotaDataList the return list with the quota data + * @param session a session for all required lib handles if zfs is used, it can be an uninitialized + * session, the initialization can be done by this function + * + * @return false on error (in which case outData is empty) + */ +bool QuotaTk::appendQuotaForID(unsigned id, QuotaDataType type, QuotaBlockDeviceMap* blockDevices, + QuotaDataList* outQuotaDataList, ZfsSession* session) +{ + QuotaData data(id, type); + bool retVal = checkQuota(blockDevices, &data, session); + + if(retVal && (data.getSize() != 0 || data.getInodes() != 0) ) + outQuotaDataList->push_back(data); + + return retVal; +} + +/** + * get quota for the a range of IDs + * + * @param blockDevices the QuotaBlockDevice to check + * @param rangeStart the first ID of the ID range + * @param rangeEnd the last ID of the ID range + * @param type the quota data ID type user/group, QuotaDataType_... + * @param outQuotaDataList the return list with the quota data + * @param session a session for all required lib handles if zfs is used, it can be an uninitialized + * session, the initialization can be done by this function + * + * @return false on error (in which case outData is empty) + */ +bool QuotaTk::requestQuotaForRange(QuotaBlockDeviceMap* blockDevices, unsigned rangeStart, + unsigned rangeEnd, QuotaDataType type, QuotaDataList* outQuotaDataList, ZfsSession* session) +{ + bool retVal = true; + + for(unsigned id = rangeStart; id <= rangeEnd; id++) + { + bool errorVal = appendQuotaForID(id, type, blockDevices, outQuotaDataList, session); + + if(!errorVal) + retVal = false; + } + + return retVal; +} + +/** + * get quota for the a range of IDs + * + * @param blockDevices the QuotaBlockDevice to check + * @param idList the list with the IDs to check + * @param type the quota data ID type user/group, QuotaDataType_... + * @param outQuotaDataList the return list with the quota data + * @param session a session for all required lib handles if zfs is used, it can be an uninitialized + * session, the initialization can be done by this function + * + * @return false on error (in which case outData is empty) + */ +bool QuotaTk::requestQuotaForList(QuotaBlockDeviceMap* blockDevices, UIntList* idList, + QuotaDataType type, QuotaDataList* outQuotaDataList, ZfsSession* session) +{ + bool retVal = true; + + for(UIntListIter iter = idList->begin(); iter != idList->end(); iter++) + { + bool errorVal = appendQuotaForID(*iter, type, blockDevices, outQuotaDataList, session); + + if(!errorVal) + retVal = false; + } + + return retVal; +} + +/** + * get QuotaData for the given QuotaData which is initialized with type and ID + * + * @param blockDevices the QuotaBlockDevice to check + * @param outData needs to be initialized with type and ID + * @param session a session for all required lib handles if zfs is used, it can be an uninitialized + * session, the initialization can be done by this function + * + * @return false on error (in which case outData is not initialized) + */ +bool QuotaTk::checkQuota(QuotaBlockDeviceMap* blockDevices, QuotaData* outData, ZfsSession* session) +{ + bool retVal = true; + int errorCode = 0; + + dqblk quotaData; // required for extX + fs_disk_quota xfsQuotaData; // required for XFS + QuotaData tmpQuotaData(outData->getID(), outData->getType() ); // required for ZFS + + QuotaDataType quotaType = outData->getType(); + + for(QuotaBlockDeviceMapIter iter = blockDevices->begin(); iter != blockDevices->end(); iter++) + { + QuotaBlockDeviceFsType fstype = iter->second.getFsType(); + if(fstype == QuotaBlockDeviceFsType_XFS) + { + if(quotaType == QuotaDataType_USER) + errorCode = quotactl(QCMD(Q_XGETQUOTA, USRQUOTA), + iter->second.getBlockDevicePath().c_str(), + outData->getID(), (caddr_t)&xfsQuotaData); + else + if (quotaType == QuotaDataType_GROUP) + errorCode = quotactl(QCMD(Q_XGETQUOTA, GRPQUOTA), + iter->second.getBlockDevicePath().c_str(), + outData->getID(), (caddr_t)&xfsQuotaData); + else + { + LOG(QUOTA, ERR, "Quota request - useless quota type.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID())); + + return false; + } + } + else + if ( (fstype == QuotaBlockDeviceFsType_ZFS) || (fstype == QuotaBlockDeviceFsType_ZFSOLD) ) + { + if(!session->isSessionValid() ) + { + if(!session->initZfsSession(Program::getApp()->getDlOpenHandleLibZfs() ) ) + return false; + } + + if(QuotaTk::requestQuotaFromZFS(&iter->second, iter->first, &tmpQuotaData, session)) + errorCode = FhgfsOpsErr_SUCCESS; + else + { + LOG(QUOTA, ERR, "Quota request - useless quota type.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID())); + + return false; + } + } + else + { + if(quotaType == QuotaDataType_USER) + errorCode = quotactl(QCMD(Q_GETQUOTA, USRQUOTA), + iter->second.getBlockDevicePath().c_str(), + outData->getID(), (caddr_t)"aData); + else + if (quotaType == QuotaDataType_GROUP) + errorCode = quotactl(QCMD(Q_GETQUOTA, GRPQUOTA), + iter->second.getBlockDevicePath().c_str(), + outData->getID(), (caddr_t)"aData); + else + { + LOG(QUOTA, ERR, "Quota request - useless quota type.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID())); + + return false; + } + } + + if (errorCode != 0) + { + errorCode = errno; + + // ignore error if no quota for user found (ESRCH) , especially for XFS (ENOENT) + if( (errorCode != ESRCH) && + !((iter->second.getFsType() == QuotaBlockDeviceFsType_XFS) && (errorCode == ENOENT) ) ) + { + LOG(QUOTA, ERR, "Quota request - quotactl failed.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID()), + ("sysErr", System::getErrString(errorCode)), + ("fstype", boost::lexical_cast(iter->second.getFsType()))); + + return false; + } + + continue; + } + + if(fstype == QuotaBlockDeviceFsType_XFS) + { + uint64_t blocks = UnitTk::quotaBlockCountToByte(xfsQuotaData.d_bcount, + iter->second.getFsType() ); + outData->forceMergeQuotaDataCounter(blocks, xfsQuotaData.d_icount); + } + else + if ( (fstype == QuotaBlockDeviceFsType_ZFS) || (fstype == QuotaBlockDeviceFsType_ZFSOLD) ) + { + if(tmpQuotaData.isValid() ) + { + uint64_t blocks = UnitTk::quotaBlockCountToByte(tmpQuotaData.getSize(), + iter->second.getFsType() ); + outData->forceMergeQuotaDataCounter(blocks, tmpQuotaData.getInodes() ); + tmpQuotaData.setIsValid(false); + } + else + { + // set return value to false, but do not abort the quota request from the other targets, + // maybe the other targets returns valid values + LOG(QUOTA, ERR, "Quota request - values not valid.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID())); + + retVal = false; + } + } + else + { + if((quotaData.dqb_valid & QIF_USAGE) != 0) + { + uint64_t blocks = UnitTk::quotaBlockCountToByte(quotaData.dqb_curspace, + iter->second.getFsType()); + + outData->forceMergeQuotaDataCounter(blocks, quotaData.dqb_curinodes); + } + else + { + // set return value to false, but do not abort the quota request from the other targets, + // maybe the other targets returns valid values + LOG(QUOTA, ERR, "Quota request - values not valid.", + ("Type", QuotaData::QuotaDataTypeToString(outData->getType())), + ("ID", outData->getID())); + + retVal = false; + } + } + } + + return retVal; +} + +/** + * initialize the lib zfs + * + * @param dlHandle the handle from dlopen to the libzfs + * @return the libzfs handle (libzfs_handle_t*), which is required for all libzfs calls + */ +void* QuotaTk::initLibZfs(void* dlHandle) +{ + App* app = Program::getApp(); + + char* dlErrorString; + void* (*libzfs_init)(); + + libzfs_init = (void* (*)())dlsym(dlHandle, "libzfs_init"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_init.", dlErrorString); + app->setLibZfsErrorReported(true); + return NULL; + } + + void* libZfsHandle = (*libzfs_init)(); + if(!libZfsHandle) + { + LOG(QUOTA, ERR, "Error during create of libzfs_handle_t."); + app->setLibZfsErrorReported(true); + return NULL; + } + + return libZfsHandle; +} + +/** + * uninitialize the lib zfs + * + * @param dlHandle the handle from dlopen to the libzfs + * @param libZfsHandle the libzfs handle (libzfs_handle_t*) to the libzfs handle (libzfs_handle_t*) + * @return false in case of error + */ +bool QuotaTk::uninitLibZfs(void* dlHandle, void* libZfsHandle) +{ + App* app = Program::getApp(); + + if( (!dlHandle) || (!libZfsHandle) ) + return true; + + void (*libzfs_fini)(void*); + char* dlErrorString; + + libzfs_fini = (void (*)(void*))dlsym(dlHandle, "libzfs_fini"); + if ( (dlErrorString = dlerror() ) != NULL) + { + LOG(QUOTA, ERR, "Error during dynamic load of function libzfs_fini.", dlErrorString); + app->setLibZfsErrorReported(true); + return false; + } + + (*libzfs_fini)(libZfsHandle); + + libZfsHandle = NULL; + + return true; +} + +/** + * checks if all required functions of the installed libzfs are available + * + * @param blockDevice a QuotaBlockDevice for a test the get quota workflow + * @param targetNumID the targetNumID of the storage target + * @return true if all function pointers are available, false if a error occurred + */ +bool QuotaTk::checkRequiredLibZfsFunctions(QuotaBlockDevice* blockDevice, uint16_t targetNumID) +{ + App* app = Program::getApp(); + + ZfsSession session; + + // check if all function names in the lib available + if(!session.initZfsSession(app->getDlOpenHandleLibZfs() ) ) + { + if(!app->getConfig()->getQuotaDisableZfsSupport() ) + LOG(QUOTA, ERR, "Cannot initialize libzfs. " + "The quota system for all zfs blockdevices/pools on this machine won't work."); + + return false; + } + + // check if the signature of all function pointers are compatible + QuotaData quotaData(0, QuotaDataType_USER); // test request for user root + if (!requestQuotaFromZFS(blockDevice, targetNumID, "aData, &session)) + { // quota update failed; try to get quota without inode quota (only supported on zfs >= 0.7.4) + // i.e. set the fsType to QuotaBlockDeviceFsType_ZFSOLD and try again + // if it still doesn't work, libzfs is not compatible at all, if it works now we fall back to + // quota without inode quota + blockDevice->setFsType(QuotaBlockDeviceFsType_ZFSOLD); + if (!requestQuotaFromZFS(blockDevice, targetNumID, "aData, &session)) + return false; + } + + // check function pointers which are required in case of errors happens + std::string errorDec( (*session.libzfs_error_description)(session.getlibZfsHandle() ) ); + std::string errorAct( (*session.libzfs_error_action)(session.getlibZfsHandle() ) ); + + return true; +} + +/** + * get QuotaData from zfs for the given QuotaData which is initialized with type and ID + * + * @param blockDevice the QuotaBlockDevice to check + * @param targetNumID the targetNumID of the storage target + * @param outData needs to be initialized with type and ID + * @param session a session for all required lib handles if zfs is used, it can be an uninitialized + * session, the initialization can be done by this function + * @return false on error (in which case outData is not initialized) + */ +bool QuotaTk::requestQuotaFromZFS(QuotaBlockDevice* blockDevice, uint16_t targetNumID, + QuotaData* outData, ZfsSession* session) +{ + if(!session->isSessionValid() ) + { + outData->setIsValid(false); + return false; + } + + void* zfsHandle = session->getZfsDeviceHandle(targetNumID, blockDevice->getBlockDevicePath() ); + if(!zfsHandle) + return false; + + uint64_t usedSizeValue = 0; + uint64_t usedInodesValue = 0; + + std::string sizeProp; + std::string inodeProp; + + if(outData->getType() == QuotaDataType_USER) + { + sizeProp = QUOTATK_ZFS_USER_QUOTA + StringTk::uintToStr(outData->getID()); + inodeProp = QUOTATK_ZFS_USER_INODE_QUOTA + StringTk::uintToStr(outData->getID()); + } + else + { + sizeProp = QUOTATK_ZFS_GROUP_QUOTA + StringTk::uintToStr(outData->getID()); + inodeProp = QUOTATK_ZFS_GROUP_INODE_QUOTA + StringTk::uintToStr(outData->getID()); + } + + if ((*session->zfs_prop_get_userquota_int)(zfsHandle, sizeProp.c_str(), &usedSizeValue)) + { + LOG(QUOTA, ERR, "Error during request quota data.", + ("ErrorAction", (*session->libzfs_error_action)(session->getlibZfsHandle())), + ("ErrorDescription", (*session->libzfs_error_description)(session->getlibZfsHandle())) + ); + return false; + } + + if (blockDevice->getFsType() != QuotaBlockDeviceFsType_ZFSOLD) // no inode support on zfs<=0.7.4 + { + if ((*session->zfs_prop_get_userquota_int)(zfsHandle, inodeProp.c_str(), &usedInodesValue)) + { + // could not read inode quota (which is only supported since zfs 0.7.4) + // log the error and set 0 as inode quota + LOG(QUOTA, ERR, "Inode quota could not be requested. Please note that inode quota on" + " ZFS is not supported for zfs versions prior to 0.7.4.", + ("ZFS Error", (*session->libzfs_error_description)(session->getlibZfsHandle()))); + return false; + } + } + + outData->setQuotaData(usedSizeValue, usedInodesValue); + + return true; +} diff --git a/storage/source/toolkit/QuotaTk.h b/storage/source/toolkit/QuotaTk.h new file mode 100644 index 0000000..88e7eec --- /dev/null +++ b/storage/source/toolkit/QuotaTk.h @@ -0,0 +1,43 @@ +#pragma once + + + +#include +#include +#include +#include + + +class QuotaTk +{ + public: + /** + * functions for extX and XFS support + */ + static bool appendQuotaForID(unsigned id, QuotaDataType type, + QuotaBlockDeviceMap* blockDevices, QuotaDataList* outQuotaDataList, ZfsSession* session); + static bool requestQuotaForRange(QuotaBlockDeviceMap* blockDevices, unsigned rangeStart, + unsigned rangeEnd, QuotaDataType type, QuotaDataList* outQuotaDataList, + ZfsSession* session); + static bool requestQuotaForList(QuotaBlockDeviceMap* blockDevices, UIntList* idList, + QuotaDataType type, QuotaDataList* outQuotaDataList, ZfsSession* session); + + static bool checkQuota(QuotaBlockDeviceMap* blockDevices, QuotaData* outData, + ZfsSession* session); + + + /** + * functions for ZFS support + */ + static void* initLibZfs(void* dlHandle); + static bool uninitLibZfs(void* dlHandle, void* libZfsHandle); + static bool checkRequiredLibZfsFunctions(QuotaBlockDevice* blockDevice, uint16_t targetNumID); + + static bool requestQuotaFromZFS(QuotaBlockDevice* blockDevice, uint16_t targetNumID, + QuotaData* outData, ZfsSession* session); + + private: + QuotaTk(); + +}; + diff --git a/storage/source/toolkit/StorageTkEx.h b/storage/source/toolkit/StorageTkEx.h new file mode 100644 index 0000000..09d8346 --- /dev/null +++ b/storage/source/toolkit/StorageTkEx.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#define STORAGETK_FORMAT_MIN_VERSION 2 +#define STORAGETK_FORMAT_CURRENT_VERSION 3 +#define MAX_SERIAL_ENTRYINFO_SIZE 4096 + +#define STORAGETK_DEFAULTCHUNKDIRMODE (S_IRWXU | S_IRWXG | S_IRWXO) +#define STORAGETK_DEFAULTCHUNKFILEMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) + +class StorageTkEx +{ + private: + StorageTkEx() {} + + + public: + static bool getDynamicFileAttribs(const int fd, int64_t* outFilesize, + int64_t* outAllocedBlocks, int64_t* outModificationTimeSecs, + int64_t* outLastAccessTimeSecs) + { + struct stat statbuf; + int statRes = fstat(fd, &statbuf); + if (statRes) + return false; + + *outFilesize = statbuf.st_size; + *outAllocedBlocks = statbuf.st_blocks; + *outModificationTimeSecs = statbuf.st_mtime; + *outLastAccessTimeSecs = statbuf.st_atime; + + return true; + } + + static bool getDynamicFileAttribs(const int dirFD, const char* path, int64_t* outFilesize, + int64_t* outAllocedBlocks, int64_t* outModificationTimeSecs, + int64_t* outLastAccessTimeSecs) + { + struct stat statbuf; + int statRes = fstatat(dirFD, path, &statbuf, 0); + if (statRes) + return false; + + *outFilesize = statbuf.st_size; + *outAllocedBlocks = statbuf.st_blocks; + *outModificationTimeSecs = statbuf.st_mtime; + *outLastAccessTimeSecs = statbuf.st_atime; + + return true; + } +}; + diff --git a/storage/tests/TestConfig.cpp b/storage/tests/TestConfig.cpp new file mode 100644 index 0000000..d9c2f32 --- /dev/null +++ b/storage/tests/TestConfig.cpp @@ -0,0 +1,99 @@ +#include "TestConfig.h" + +TestConfig::TestConfig() +{ +} + +TestConfig::~TestConfig() +{ +} + +void TestConfig::SetUp() +{ + emptyConfigFile = DUMMY_EMPTY_CONFIG_FILE; + this->dummyConfigFile = DUMMY_NOEXIST_CONFIG_FILE; +} + +void TestConfig::TearDown() +{ + // delete generated config file + if (StorageTk::pathExists(this->emptyConfigFile)) + { + /* TODO : return value of remove is ignored now; + * maybe we should notify the user here (but that + * would break test output) + */ + remove(this->emptyConfigFile.c_str()); + } +} + +TEST_F(TestConfig, missingConfigFile) +{ + // generate a bogus name for a config file + /* normally the default file should be nonexistant, but to be absolutely sure, + we check that and, in case, append a number */ + int appendix = 0; + while (StorageTk::pathExists(this->dummyConfigFile)) + { + appendix++; + this->dummyConfigFile = DUMMY_NOEXIST_CONFIG_FILE + StringTk::intToStr(appendix); + } + + int argc = 2; + char* argv[2]; + + std::string appNameStr = APP_NAME; + appNameStr += '\0'; + + std::string cfgLineStr = "cfgFile=" + this->dummyConfigFile; + cfgLineStr += '\0'; + + argv[0] = &appNameStr[0]; + argv[1] = &cfgLineStr[0]; + + // should throw InvalidConfigException now + ASSERT_THROW(Config config(argc, argv), InvalidConfigException); +} + +TEST_F(TestConfig, defaultConfigFile) +{ + // get the path where the binary resides + int BUFSIZE = 255; + char exePathBuf[BUFSIZE]; + // read only BUFSIZE-1, as we need to terminate the string manually later + ssize_t len = readlink("/proc/self/exe", exePathBuf, BUFSIZE-1); + + /* In case of an error, failure will indicate the error */ + if (len < 0) + FAIL() << "Internal error"; + + /* in case of insufficient buffer size, failure will indicate the error */ + if (len >= BUFSIZE) + FAIL() << "Internal error"; + + // readlink does NOT null terminate the string, so we do it here to be safe + exePathBuf[len] = '\0'; + + // construct the path to the default config file + std::string defaultFileName = std::string(dirname(exePathBuf)) + + "/" + DEFAULT_CONFIG_FILE_RELATIVE; + + // create config with the default file and see what happens while parsing + int argc = 2; + char* argv[2]; + + std::string appNameStr = APP_NAME; + appNameStr += '\0'; + + std::string cfgLineStr = "cfgFile=" + defaultFileName; + cfgLineStr += '\0'; + + argv[0] = &appNameStr[0]; + argv[1] = &cfgLineStr[0]; + + try { + Config config(argc, argv); + } catch (ConnAuthFileException& e) { + return; + } +} diff --git a/storage/tests/TestConfig.h b/storage/tests/TestConfig.h new file mode 100644 index 0000000..589b13c --- /dev/null +++ b/storage/tests/TestConfig.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#define DUMMY_NOEXIST_CONFIG_FILE "/tmp/nonExistantConfigFile.conf.storage" +#define DUMMY_EMPTY_CONFIG_FILE "/tmp/emptyConfigFile.conf.storage" +#define DEFAULT_CONFIG_FILE_RELATIVE "dist/etc/beegfs-storage.conf" +#define APP_NAME "beegfs-storage"; + +class TestConfig: public ::testing::Test +{ + public: + TestConfig(); + virtual ~TestConfig(); + + void SetUp() override; + void TearDown() override; + + protected: + // we have these filenames as member variables because + // we might need to delete them in tearDown function + std::string dummyConfigFile; + std::string emptyConfigFile; +}; + diff --git a/thirdparty/build/Makefile b/thirdparty/build/Makefile new file mode 100644 index 0000000..674d8ab --- /dev/null +++ b/thirdparty/build/Makefile @@ -0,0 +1,23 @@ +SHELL := /bin/bash + +SOURCE_PATH = $(shell echo `pwd`/../source ) +BUILD_PATH = $(shell echo `pwd` ) + +BUILDTYPE = $(shell `pwd`/get-build-type.sh) + +FINDBUGS_HOME = $(shell echo `pwd`/../source/findbugs) + +# note: cppunit not included in "all" by default, because we prefer system lib by default +all: gtest + +GTEST_DIR = ../source/gtest/googletest +GTEST_SRC = $(GTEST_DIR)/src/gtest-all.cc $(GTEST_DIR)/src/gtest_main.cc + +libgtest.a: $(GTEST_SRC) + $(CXX) -fPIC -isystem $(GTEST_DIR)/include -I$(GTEST_DIR) -pthread -c $^ + $(AR) -r libgtest.a gtest-all.o gtest_main.o + +gtest: libgtest.a + +clean: + rm -f gtest-all.o gtest_main.o libgtest.a diff --git a/thirdparty/build/get-build-type.sh b/thirdparty/build/get-build-type.sh new file mode 100755 index 0000000..ce70fed --- /dev/null +++ b/thirdparty/build/get-build-type.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +# Print the build type suitable to be used for autoconf +# we need this for cppunit and tilera architechture + +# ./configure --build the-build-type + +# on success print the build type and exit +print_build_type() +{ + cmd="$@" + build_type=`eval $cmd 2>/dev/null` + if [ $? -eq 0 ]; then + echo --build $build_type + exit 0 + fi + +} + +# try rpm first +print_build_type "rpm --eval %{_host}" + +# rpm failed, now dpkg +print_build_type "dpkg-architecture -qDEB_BUILD_GNU_TYPE" + + +# unknown build type +exit 1 diff --git a/thirdparty/source/boost b/thirdparty/source/boost new file mode 120000 index 0000000..6001f32 --- /dev/null +++ b/thirdparty/source/boost @@ -0,0 +1 @@ +boost_1_61_0 \ No newline at end of file diff --git a/thirdparty/source/boost_1_61_0/Jamroot b/thirdparty/source/boost_1_61_0/Jamroot new file mode 100644 index 0000000..a8105c8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/Jamroot @@ -0,0 +1,291 @@ +# Copyright Vladimir Prus 2002-2006. +# Copyright Dave Abrahams 2005-2006. +# Copyright Rene Rivera 2005-2007. +# Copyright Douglas Gregor 2005. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt) + +# Usage: +# +# b2 [options] [properties] [install|stage] +# +# Builds and installs Boost. +# +# Targets and Related Options: +# +# install Install headers and compiled library files to the +# ======= configured locations (below). +# +# --prefix= Install architecture independent files here. +# Default; C:\Boost on Win32 +# Default; /usr/local on Unix. Linux, etc. +# +# --exec-prefix= Install architecture dependent files here. +# Default; +# +# --libdir= Install library files here. +# Default; /lib +# +# --includedir= Install header files here. +# Default; /include +# +# stage Build and install only compiled library files to the +# ===== stage directory. +# +# --stagedir= Install library files here +# Default; ./stage +# +# Other Options: +# +# --build-type= Build the specified pre-defined set of variations of +# the libraries. Note, that which variants get built +# depends on what each library supports. +# +# -- minimal -- (default) Builds a minimal set of +# variants. On Windows, these are static +# multithreaded libraries in debug and release +# modes, using shared runtime. On Linux, these are +# static and shared multithreaded libraries in +# release mode. +# +# -- complete -- Build all possible variations. +# +# --build-dir=DIR Build in this location instead of building within +# the distribution tree. Recommended! +# +# --show-libraries Display the list of Boost libraries that require +# build and installation steps, and then exit. +# +# --layout= Determine whether to choose library names and header +# locations such that multiple versions of Boost or +# multiple compilers can be used on the same system. +# +# -- versioned -- Names of boost binaries include +# the Boost version number, name and version of +# the compiler and encoded build properties. Boost +# headers are installed in a subdirectory of +# whose name contains the Boost version +# number. +# +# -- tagged -- Names of boost binaries include the +# encoded build properties such as variant and +# threading, but do not including compiler name +# and version, or Boost version. This option is +# useful if you build several variants of Boost, +# using the same compiler. +# +# -- system -- Binaries names do not include the +# Boost version number or the name and version +# number of the compiler. Boost headers are +# installed directly into . This option is +# intended for system integrators building +# distribution packages. +# +# The default value is 'versioned' on Windows, and +# 'system' on Unix. +# +# --buildid=ID Add the specified ID to the name of built libraries. +# The default is to not add anything. +# +# --python-buildid=ID Add the specified ID to the name of built libraries +# that depend on Python. The default is to not add +# anything. This ID is added in addition to --buildid. +# +# --help This message. +# +# --with- Build and install the specified . If this +# option is used, only libraries specified using this +# option will be built. +# +# --without- Do not build, stage, or install the specified +# . By default, all libraries are built. +# +# Properties: +# +# toolset=toolset Indicate the toolset to build with. +# +# variant=debug|release Select the build variant +# +# link=static|shared Whether to build static or shared libraries +# +# threading=single|multi Whether to build single or multithreaded binaries +# +# runtime-link=static|shared +# Whether to link to static or shared C and C++ +# runtime. +# + +# TODO: +# - handle boost version +# - handle python options such as pydebug + +import boostcpp ; +import package ; + +import sequence ; +import xsltproc ; +import set ; +import path ; +import link ; + +path-constant BOOST_ROOT : . ; +constant BOOST_VERSION : 1.61.0 ; +constant BOOST_JAMROOT_MODULE : $(__name__) ; + +boostcpp.set-version $(BOOST_VERSION) ; + +use-project /boost/architecture : libs/config/checks/architecture ; + +local all-headers = + [ MATCH .*libs/(.*)/include/boost : [ glob libs/*/include/boost libs/*/*/include/boost ] ] ; + +for dir in $(all-headers) +{ + link-directory $(dir)-headers : libs/$(dir)/include/boost : . ; + explicit $(dir)-headers ; +} + +if $(all-headers) +{ + constant BOOST_MODULARLAYOUT : $(all-headers) ; +} + +project boost + : requirements . + + [ boostcpp.architecture ] + [ boostcpp.address-model ] + + # Disable auto-linking for all targets here, primarily because it caused + # troubles with V2. + BOOST_ALL_NO_LIB=1 + # Used to encode variant in target name. See the 'tag' rule below. + @$(__name__).tag + @handle-static-runtime + # Comeau does not support shared lib + como:static + como-linux:_GNU_SOURCE=1 + # When building docs within Boost, we want the standard Boost style + boost.defaults=Boost + : usage-requirements . + : build-dir bin.v2 + ; + +# This rule is called by Boost.Build to determine the name of target. We use it +# to encode the build variant, compiler name and boost version in the target +# name. +# +rule tag ( name : type ? : property-set ) +{ + return [ boostcpp.tag $(name) : $(type) : $(property-set) ] ; +} + +rule handle-static-runtime ( properties * ) +{ + # Using static runtime with shared libraries is impossible on Linux, and + # dangerous on Windows. Therefore, we disallow it. This might be drastic, + # but it was disabled for a while without anybody complaining. + + # For CW, static runtime is needed so that std::locale works. + if shared in $(properties) && static in $(properties) && + ! ( cw in $(properties) ) + { + ECHO "error: link=shared together with runtime-link=static is not allowed" ; + ECHO "error: such property combination is either impossible " ; + ECHO "error: or too dangerious to be of any use" ; + EXIT ; + } +} + +all-libraries = [ MATCH .*libs/(.*)/build/.* : [ glob libs/*/build/Jamfile.v2 ] + [ glob libs/*/build/Jamfile ] ] ; + +all-libraries = [ sequence.unique $(all-libraries) ] ; +# The function_types library has a Jamfile, but it's used for maintenance +# purposes, there's no library to build and install. +all-libraries = [ set.difference $(all-libraries) : function_types ] ; + +# Setup convenient aliases for all libraries. + +local rule explicit-alias ( id : targets + ) +{ + alias $(id) : $(targets) ; + explicit $(id) ; +} + +# First, the complicated libraries: where the target name in Jamfile is +# different from its directory name. +explicit-alias prg_exec_monitor : libs/test/build//boost_prg_exec_monitor ; +explicit-alias test_exec_monitor : libs/test/build//boost_test_exec_monitor ; +explicit-alias unit_test_framework : libs/test/build//boost_unit_test_framework ; +explicit-alias bgl-vis : libs/graps/build//bgl-vis ; +explicit-alias serialization : libs/serialization/build//boost_serialization ; +explicit-alias wserialization : libs/serialization/build//boost_wserialization ; +for local l in $(all-libraries) +{ + if ! $(l) in test graph serialization + { + explicit-alias $(l) : libs/$(l)/build//boost_$(l) ; + } +} + +# Log has an additional target +explicit-alias log_setup : libs/log/build//boost_log_setup ; + +alias headers : $(all-headers)-headers : : : . ; +explicit headers ; + +# Make project ids of all libraries known. +for local l in $(all-libraries) +{ + use-project /boost/$(l) : libs/$(l)/build ; +} + +if [ path.exists $(BOOST_ROOT)/tools/inspect ] +{ + use-project /boost/tools/inspect : tools/inspect/build ; +} + +if [ path.exists $(BOOST_ROOT)/libs/wave/tool ] +{ + use-project /boost/libs/wave/tool : libs/wave/tool/build ; +} + +# This rule should be called from libraries' Jamfiles and will create two +# targets, "install" and "stage", that will install or stage that library. The +# --prefix option is respected, but --with and --without options, naturally, are +# ignored. +# +# - libraries -- list of library targets to install. +# +rule boost-install ( libraries * ) +{ + package.install install + : /boost//install-proper-headers $(install-requirements) + : # No binaries + : $(libraries) + : # No headers, it is handled by the dependency. + ; + + install stage : $(libraries) : $(BOOST_STAGE_LOCATE) ; + + module [ CALLER_MODULE ] + { + explicit stage ; + explicit install ; + } +} + +headers = + # The .SUNWCCh files are present in tr1 include directory and have to be + # installed (see http://lists.boost.org/Archives/boost/2007/05/121430.php). + [ path.glob-tree $(BOOST_ROOT)/boost : *.hpp *.ipp *.h *.inc *.SUNWCCh : CVS .svn ] + [ path.glob-tree $(BOOST_ROOT)/boost/compatibility/cpp_c_headers : c* : CVS .svn ] + [ path.glob boost/tr1/tr1 : * : bcc32 sun CVS .svn ] + ; + +# Declare special top-level targets that build and install the desired variants +# of the libraries. +boostcpp.declare-targets $(all-libraries) : $(headers) ; diff --git a/thirdparty/source/boost_1_61_0/LICENSE_1_0.txt b/thirdparty/source/boost_1_61_0/LICENSE_1_0.txt new file mode 100644 index 0000000..36b7cd9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/LICENSE_1_0.txt @@ -0,0 +1,23 @@ +Boost Software License - Version 1.0 - August 17th, 2003 + +Permission is hereby granted, free of charge, to any person or organization +obtaining a copy of the software and accompanying documentation covered by +this license (the "Software") to use, reproduce, display, distribute, +execute, and transmit the Software, and to prepare derivative works of the +Software, and to permit third-parties to whom the Software is furnished to +do so, all subject to the following: + +The copyright notices in the Software and this entire statement, including +the above license grant, this restriction and the following disclaimer, +must be included in all copies of the Software, in whole or in part, and +all derivative works of the Software, unless such copies or derivative +works are solely in the form of machine-executable object code generated by +a source language processor. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT +SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE +FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. diff --git a/thirdparty/source/boost_1_61_0/boost.png b/thirdparty/source/boost_1_61_0/boost.png new file mode 100644 index 0000000..b4d51fc Binary files /dev/null and b/thirdparty/source/boost_1_61_0/boost.png differ diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/cxx11/all_of.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/cxx11/all_of.hpp new file mode 100644 index 0000000..39cab39 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/cxx11/all_of.hpp @@ -0,0 +1,86 @@ +/* + Copyright (c) Marshall Clow 2008-2012. + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +*/ + +/// \file all_of.hpp +/// \brief Test ranges to see if all elements match a value or predicate. +/// \author Marshall Clow + +#ifndef BOOST_ALGORITHM_ALL_OF_HPP +#define BOOST_ALGORITHM_ALL_OF_HPP + +#include // for std::all_of, if available +#include +#include + +namespace boost { namespace algorithm { + +/// \fn all_of ( InputIterator first, InputIterator last, Predicate p ) +/// \return true if all elements in [first, last) satisfy the predicate 'p' +/// \note returns true on an empty range +/// +/// \param first The start of the input sequence +/// \param last One past the end of the input sequence +/// \param p A predicate for testing the elements of the sequence +/// +/// \note This function is part of the C++2011 standard library. +/// We will use the standard one if it is available, +/// otherwise we have our own implementation. +template +bool all_of ( InputIterator first, InputIterator last, Predicate p ) +{ + for ( ; first != last; ++first ) + if ( !p(*first)) + return false; + return true; +} + +/// \fn all_of ( const Range &r, Predicate p ) +/// \return true if all elements in the range satisfy the predicate 'p' +/// \note returns true on an empty range +/// +/// \param r The input range +/// \param p A predicate for testing the elements of the range +/// +template +bool all_of ( const Range &r, Predicate p ) +{ + return boost::algorithm::all_of ( boost::begin (r), boost::end (r), p ); +} + +/// \fn all_of_equal ( InputIterator first, InputIterator last, const T &val ) +/// \return true if all elements in [first, last) are equal to 'val' +/// \note returns true on an empty range +/// +/// \param first The start of the input sequence +/// \param last One past the end of the input sequence +/// \param val A value to compare against +/// +template +bool all_of_equal ( InputIterator first, InputIterator last, const T &val ) +{ + for ( ; first != last; ++first ) + if ( val != *first ) + return false; + return true; +} + +/// \fn all_of_equal ( const Range &r, const T &val ) +/// \return true if all elements in the range are equal to 'val' +/// \note returns true on an empty range +/// +/// \param r The input range +/// \param val A value to compare against +/// +template +bool all_of_equal ( const Range &r, const T &val ) +{ + return boost::algorithm::all_of_equal ( boost::begin (r), boost::end (r), val ); +} + +}} // namespace boost and algorithm + +#endif // BOOST_ALGORITHM_ALL_OF_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string.hpp new file mode 100644 index 0000000..0771517 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string.hpp @@ -0,0 +1,31 @@ +// Boost string_algo library string_algo.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2004. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_ALGO_HPP +#define BOOST_STRING_ALGO_HPP + +/*! \file + Cumulative include for string_algo library +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#endif // BOOST_STRING_ALGO_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/case_conv.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/case_conv.hpp new file mode 100644 index 0000000..683340b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/case_conv.hpp @@ -0,0 +1,176 @@ +// Boost string_algo library case_conv.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CASE_CONV_HPP +#define BOOST_STRING_CASE_CONV_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +/*! \file + Defines sequence case-conversion algorithms. + Algorithms convert each element in the input sequence to the + desired case using provided locales. +*/ + +namespace boost { + namespace algorithm { + +// to_lower -----------------------------------------------// + + //! Convert to lower case + /*! + Each element of the input sequence is converted to lower + case. The result is a copy of the input converted to lower case. + It is returned as a sequence or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input range + \param Loc A locale used for conversion + \return + An output iterator pointing just after the last inserted character or + a copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + + */ + template + inline OutputIteratorT + to_lower_copy( + OutputIteratorT Output, + const RangeT& Input, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::detail::transform_range_copy( + Output, + ::boost::as_literal(Input), + ::boost::algorithm::detail::to_lowerF< + typename range_value::type >(Loc)); + } + + //! Convert to lower case + /*! + \overload + */ + template + inline SequenceT to_lower_copy( + const SequenceT& Input, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::detail::transform_range_copy( + Input, + ::boost::algorithm::detail::to_lowerF< + typename range_value::type >(Loc)); + } + + //! Convert to lower case + /*! + Each element of the input sequence is converted to lower + case. The input sequence is modified in-place. + + \param Input A range + \param Loc a locale used for conversion + */ + template + inline void to_lower( + WritableRangeT& Input, + const std::locale& Loc=std::locale()) + { + ::boost::algorithm::detail::transform_range( + ::boost::as_literal(Input), + ::boost::algorithm::detail::to_lowerF< + typename range_value::type >(Loc)); + } + +// to_upper -----------------------------------------------// + + //! Convert to upper case + /*! + Each element of the input sequence is converted to upper + case. The result is a copy of the input converted to upper case. + It is returned as a sequence or copied to the output iterator + + \param Output An output iterator to which the result will be copied + \param Input An input range + \param Loc A locale used for conversion + \return + An output iterator pointing just after the last inserted character or + a copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template + inline OutputIteratorT + to_upper_copy( + OutputIteratorT Output, + const RangeT& Input, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::detail::transform_range_copy( + Output, + ::boost::as_literal(Input), + ::boost::algorithm::detail::to_upperF< + typename range_value::type >(Loc)); + } + + //! Convert to upper case + /*! + \overload + */ + template + inline SequenceT to_upper_copy( + const SequenceT& Input, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::detail::transform_range_copy( + Input, + ::boost::algorithm::detail::to_upperF< + typename range_value::type >(Loc)); + } + + //! Convert to upper case + /*! + Each element of the input sequence is converted to upper + case. The input sequence is modified in-place. + + \param Input An input range + \param Loc a locale used for conversion + */ + template + inline void to_upper( + WritableRangeT& Input, + const std::locale& Loc=std::locale()) + { + ::boost::algorithm::detail::transform_range( + ::boost::as_literal(Input), + ::boost::algorithm::detail::to_upperF< + typename range_value::type >(Loc)); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::to_lower; + using algorithm::to_lower_copy; + using algorithm::to_upper; + using algorithm::to_upper_copy; + +} // namespace boost + +#endif // BOOST_STRING_CASE_CONV_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/classification.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/classification.hpp new file mode 100644 index 0000000..ca43602 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/classification.hpp @@ -0,0 +1,312 @@ +// Boost string_algo library classification.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CLASSIFICATION_HPP +#define BOOST_STRING_CLASSIFICATION_HPP + +#include +#include +#include +#include +#include +#include + + +/*! \file + Classification predicates are included in the library to give + some more convenience when using algorithms like \c trim() and \c all(). + They wrap functionality of STL classification functions ( e.g. \c std::isspace() ) + into generic functors. +*/ + +namespace boost { + namespace algorithm { + +// classification functor generator -------------------------------------// + + //! is_classified predicate + /*! + Construct the \c is_classified predicate. This predicate holds if the input is + of specified \c std::ctype category. + + \param Type A \c std::ctype category + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_classified(std::ctype_base::mask Type, const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(Type, Loc); + } + + //! is_space predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::space category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_space(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::space, Loc); + } + + //! is_alnum predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::alnum category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_alnum(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::alnum, Loc); + } + + //! is_alpha predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::alpha category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_alpha(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::alpha, Loc); + } + + //! is_cntrl predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::cntrl category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_cntrl(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::cntrl, Loc); + } + + //! is_digit predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::digit category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_digit(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::digit, Loc); + } + + //! is_graph predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::graph category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_graph(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::graph, Loc); + } + + //! is_lower predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::lower category. + + \param Loc A locale used for classification + \return An instance of \c is_classified predicate + */ + inline detail::is_classifiedF + is_lower(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::lower, Loc); + } + + //! is_print predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::print category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_print(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::print, Loc); + } + + //! is_punct predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::punct category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_punct(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::punct, Loc); + } + + //! is_upper predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::upper category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_upper(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::upper, Loc); + } + + //! is_xdigit predicate + /*! + Construct the \c is_classified predicate for the \c ctype_base::xdigit category. + + \param Loc A locale used for classification + \return An instance of the \c is_classified predicate + */ + inline detail::is_classifiedF + is_xdigit(const std::locale& Loc=std::locale()) + { + return detail::is_classifiedF(std::ctype_base::xdigit, Loc); + } + + //! is_any_of predicate + /*! + Construct the \c is_any_of predicate. The predicate holds if the input + is included in the specified set of characters. + + \param Set A set of characters to be recognized + \return An instance of the \c is_any_of predicate + */ + template + inline detail::is_any_ofF< + BOOST_STRING_TYPENAME range_value::type> + is_any_of( const RangeT& Set ) + { + iterator_range::type> lit_set(boost::as_literal(Set)); + return detail::is_any_ofF::type>(lit_set); + } + + //! is_from_range predicate + /*! + Construct the \c is_from_range predicate. The predicate holds if the input + is included in the specified range. (i.e. From <= Ch <= To ) + + \param From The start of the range + \param To The end of the range + \return An instance of the \c is_from_range predicate + */ + template + inline detail::is_from_rangeF is_from_range(CharT From, CharT To) + { + return detail::is_from_rangeF(From,To); + } + + // predicate combinators ---------------------------------------------------// + + //! predicate 'and' composition predicate + /*! + Construct the \c class_and predicate. This predicate can be used + to logically combine two classification predicates. \c class_and holds, + if both predicates return true. + + \param Pred1 The first predicate + \param Pred2 The second predicate + \return An instance of the \c class_and predicate + */ + template + inline detail::pred_andF + operator&&( + const predicate_facade& Pred1, + const predicate_facade& Pred2 ) + { + // Doing the static_cast with the pointer instead of the reference + // is a workaround for some compilers which have problems with + // static_cast's of template references, i.e. CW8. /grafik/ + return detail::pred_andF( + *static_cast(&Pred1), + *static_cast(&Pred2) ); + } + + //! predicate 'or' composition predicate + /*! + Construct the \c class_or predicate. This predicate can be used + to logically combine two classification predicates. \c class_or holds, + if one of the predicates return true. + + \param Pred1 The first predicate + \param Pred2 The second predicate + \return An instance of the \c class_or predicate + */ + template + inline detail::pred_orF + operator||( + const predicate_facade& Pred1, + const predicate_facade& Pred2 ) + { + // Doing the static_cast with the pointer instead of the reference + // is a workaround for some compilers which have problems with + // static_cast's of template references, i.e. CW8. /grafik/ + return detail::pred_orF( + *static_cast(&Pred1), + *static_cast(&Pred2)); + } + + //! predicate negation operator + /*! + Construct the \c class_not predicate. This predicate represents a negation. + \c class_or holds if of the predicates return false. + + \param Pred The predicate to be negated + \return An instance of the \c class_not predicate + */ + template + inline detail::pred_notF + operator!( const predicate_facade& Pred ) + { + // Doing the static_cast with the pointer instead of the reference + // is a workaround for some compilers which have problems with + // static_cast's of template references, i.e. CW8. /grafik/ + return detail::pred_notF(*static_cast(&Pred)); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::is_classified; + using algorithm::is_space; + using algorithm::is_alnum; + using algorithm::is_alpha; + using algorithm::is_cntrl; + using algorithm::is_digit; + using algorithm::is_graph; + using algorithm::is_lower; + using algorithm::is_upper; + using algorithm::is_print; + using algorithm::is_punct; + using algorithm::is_xdigit; + using algorithm::is_any_of; + using algorithm::is_from_range; + +} // namespace boost + +#endif // BOOST_STRING_PREDICATE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/compare.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/compare.hpp new file mode 100644 index 0000000..734303a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/compare.hpp @@ -0,0 +1,199 @@ +// Boost string_algo library compare.hpp header file -------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_COMPARE_HPP +#define BOOST_STRING_COMPARE_HPP + +#include +#include + +/*! \file + Defines element comparison predicates. Many algorithms in this library can + take an additional argument with a predicate used to compare elements. + This makes it possible, for instance, to have case insensitive versions + of the algorithms. +*/ + +namespace boost { + namespace algorithm { + + // is_equal functor -----------------------------------------------// + + //! is_equal functor + /*! + Standard STL equal_to only handle comparison between arguments + of the same type. This is a less restrictive version which wraps operator ==. + */ + struct is_equal + { + //! Function operator + /*! + Compare two operands for equality + */ + template< typename T1, typename T2 > + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + return Arg1==Arg2; + } + }; + + //! case insensitive version of is_equal + /*! + Case insensitive comparison predicate. Comparison is done using + specified locales. + */ + struct is_iequal + { + //! Constructor + /*! + \param Loc locales used for comparison + */ + is_iequal( const std::locale& Loc=std::locale() ) : + m_Loc( Loc ) {} + + //! Function operator + /*! + Compare two operands. Case is ignored. + */ + template< typename T1, typename T2 > + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564) && !defined(_USE_OLD_RW_STL) + return std::toupper(Arg1)==std::toupper(Arg2); + #else + return std::toupper(Arg1,m_Loc)==std::toupper(Arg2,m_Loc); + #endif + } + + private: + std::locale m_Loc; + }; + + // is_less functor -----------------------------------------------// + + //! is_less functor + /*! + Convenient version of standard std::less. Operation is templated, therefore it is + not required to specify the exact types upon the construction + */ + struct is_less + { + //! Functor operation + /*! + Compare two operands using > operator + */ + template< typename T1, typename T2 > + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + return Arg1 + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564) && !defined(_USE_OLD_RW_STL) + return std::toupper(Arg1)(Arg1,m_Loc)(Arg2,m_Loc); + #endif + } + + private: + std::locale m_Loc; + }; + + // is_not_greater functor -----------------------------------------------// + + //! is_not_greater functor + /*! + Convenient version of standard std::not_greater_to. Operation is templated, therefore it is + not required to specify the exact types upon the construction + */ + struct is_not_greater + { + //! Functor operation + /*! + Compare two operands using > operator + */ + template< typename T1, typename T2 > + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + return Arg1<=Arg2; + } + }; + + + //! case insensitive version of is_not_greater + /*! + Case insensitive comparison predicate. Comparison is done using + specified locales. + */ + struct is_not_igreater + { + //! Constructor + /*! + \param Loc locales used for comparison + */ + is_not_igreater( const std::locale& Loc=std::locale() ) : + m_Loc( Loc ) {} + + //! Function operator + /*! + Compare two operands. Case is ignored. + */ + template< typename T1, typename T2 > + bool operator()( const T1& Arg1, const T2& Arg2 ) const + { + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564) && !defined(_USE_OLD_RW_STL) + return std::toupper(Arg1)<=std::toupper(Arg2); + #else + return std::toupper(Arg1,m_Loc)<=std::toupper(Arg2,m_Loc); + #endif + } + + private: + std::locale m_Loc; + }; + + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::is_equal; + using algorithm::is_iequal; + using algorithm::is_less; + using algorithm::is_iless; + using algorithm::is_not_greater; + using algorithm::is_not_igreater; + +} // namespace boost + + +#endif // BOOST_STRING_COMPARE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/concept.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/concept.hpp new file mode 100644 index 0000000..17e8349 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/concept.hpp @@ -0,0 +1,83 @@ +// Boost string_algo library concept.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CONCEPT_HPP +#define BOOST_STRING_CONCEPT_HPP + +#include +#include +#include +#include + +/*! \file + Defines concepts used in string_algo library +*/ + +namespace boost { + namespace algorithm { + + //! Finder concept + /*! + Defines the Finder concept. Finder is a functor which selects + an arbitrary part of a string. Search is performed on + the range specified by starting and ending iterators. + + Result of the find operation must be convertible to iterator_range. + */ + template + struct FinderConcept + { + private: + typedef iterator_range range; + public: + void constraints() + { + // Operation + r=(*pF)(i,i); + } + private: + range r; + IteratorT i; + FinderT* pF; + }; // Finder_concept + + + //! Formatter concept + /*! + Defines the Formatter concept. Formatter is a functor, which + takes a result from a finder operation and transforms it + in a specific way. + + Result must be a container supported by container_traits, + or a reference to it. + */ + template + struct FormatterConcept + { + public: + void constraints() + { + // Operation + ::boost::begin((*pFo)( (*pF)(i,i) )); + ::boost::end((*pFo)( (*pF)(i,i) )); + } + private: + IteratorT i; + FinderT* pF; + FormatterT *pFo; + }; // FormatterConcept; + + } // namespace algorithm +} // namespace boost + + + + +#endif // BOOST_STRING_CONCEPT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/config.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/config.hpp new file mode 100644 index 0000000..559750a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/config.hpp @@ -0,0 +1,28 @@ +// Boost string_algo library config.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CONFIG_HPP +#define BOOST_STRING_CONFIG_HPP + +#include +#include + +#ifdef BOOST_STRING_DEDUCED_TYPENAME +# error "macro already defined!" +#endif + +#define BOOST_STRING_TYPENAME BOOST_DEDUCED_TYPENAME + +// Metrowerks workaround +#if BOOST_WORKAROUND(__MWERKS__, <= 0x3003) // 8.x +#pragma parse_func_templ off +#endif + +#endif // BOOST_STRING_CONFIG_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/constants.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/constants.hpp new file mode 100644 index 0000000..6ed70ef --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/constants.hpp @@ -0,0 +1,36 @@ +// Boost string_algo library constants.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CONSTANTS_HPP +#define BOOST_STRING_CONSTANTS_HPP + +namespace boost { + namespace algorithm { + + //! Token compression mode + /*! + Specifies token compression mode for the token_finder. + */ + enum token_compress_mode_type + { + token_compress_on, //!< Compress adjacent tokens + token_compress_off //!< Do not compress adjacent tokens + }; + + } // namespace algorithm + + // pull the names to the boost namespace + using algorithm::token_compress_on; + using algorithm::token_compress_off; + +} // namespace boost + +#endif // BOOST_STRING_CONSTANTS_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/case_conv.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/case_conv.hpp new file mode 100644 index 0000000..42621c7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/case_conv.hpp @@ -0,0 +1,123 @@ +// Boost string_algo library string_funct.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CASE_CONV_DETAIL_HPP +#define BOOST_STRING_CASE_CONV_DETAIL_HPP + +#include +#include +#include + +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// case conversion functors -----------------------------------------------// + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +#pragma warning(push) +#pragma warning(disable:4512) //assignment operator could not be generated +#endif + + // a tolower functor + template + struct to_lowerF : public std::unary_function + { + // Constructor + to_lowerF( const std::locale& Loc ) : m_Loc( &Loc ) {} + + // Operation + CharT operator ()( CharT Ch ) const + { + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564) && !defined(_USE_OLD_RW_STL) + return std::tolower( static_cast::type> ( Ch )); + #else + return std::tolower( Ch, *m_Loc ); + #endif + } + private: + const std::locale* m_Loc; + }; + + // a toupper functor + template + struct to_upperF : public std::unary_function + { + // Constructor + to_upperF( const std::locale& Loc ) : m_Loc( &Loc ) {} + + // Operation + CharT operator ()( CharT Ch ) const + { + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x564) && !defined(_USE_OLD_RW_STL) + return std::toupper( static_cast::type> ( Ch )); + #else + return std::toupper( Ch, *m_Loc ); + #endif + } + private: + const std::locale* m_Loc; + }; + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +#pragma warning(pop) +#endif + +// algorithm implementation ------------------------------------------------------------------------- + + // Transform a range + template + OutputIteratorT transform_range_copy( + OutputIteratorT Output, + const RangeT& Input, + FunctorT Functor) + { + return std::transform( + ::boost::begin(Input), + ::boost::end(Input), + Output, + Functor); + } + + // Transform a range (in-place) + template + void transform_range( + const RangeT& Input, + FunctorT Functor) + { + std::transform( + ::boost::begin(Input), + ::boost::end(Input), + ::boost::begin(Input), + Functor); + } + + template + inline SequenceT transform_range_copy( + const RangeT& Input, + FunctorT Functor) + { + return SequenceT( + ::boost::make_transform_iterator( + ::boost::begin(Input), + Functor), + ::boost::make_transform_iterator( + ::boost::end(Input), + Functor)); + } + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_CASE_CONV_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/classification.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/classification.hpp new file mode 100644 index 0000000..704d9d2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/classification.hpp @@ -0,0 +1,353 @@ +// Boost string_algo library classification.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_CLASSIFICATION_DETAIL_HPP +#define BOOST_STRING_CLASSIFICATION_DETAIL_HPP + +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// classification functors -----------------------------------------------// + + // is_classified functor + struct is_classifiedF : + public predicate_facade + { + // Boost.ResultOf support + typedef bool result_type; + + // Constructor from a locale + is_classifiedF(std::ctype_base::mask Type, std::locale const & Loc = std::locale()) : + m_Type(Type), m_Locale(Loc) {} + // Operation + template + bool operator()( CharT Ch ) const + { + return std::use_facet< std::ctype >(m_Locale).is( m_Type, Ch ); + } + + #if defined(__BORLANDC__) && (__BORLANDC__ >= 0x560) && (__BORLANDC__ <= 0x582) && !defined(_USE_OLD_RW_STL) + template<> + bool operator()( char const Ch ) const + { + return std::use_facet< std::ctype >(m_Locale).is( m_Type, Ch ); + } + #endif + + private: + std::ctype_base::mask m_Type; + std::locale m_Locale; + }; + + + // is_any_of functor + /* + returns true if the value is from the specified set + */ + template + struct is_any_ofF : + public predicate_facade > + { + private: + // set cannot operate on const value-type + typedef typename ::boost::remove_const::type set_value_type; + + public: + // Boost.ResultOf support + typedef bool result_type; + + // Constructor + template + is_any_ofF( const RangeT& Range ) : m_Size(0) + { + // Prepare storage + m_Storage.m_dynSet=0; + + std::size_t Size=::boost::distance(Range); + m_Size=Size; + set_value_type* Storage=0; + + if(use_fixed_storage(m_Size)) + { + // Use fixed storage + Storage=&m_Storage.m_fixSet[0]; + } + else + { + // Use dynamic storage + m_Storage.m_dynSet=new set_value_type[m_Size]; + Storage=m_Storage.m_dynSet; + } + + // Use fixed storage + ::std::copy(::boost::begin(Range), ::boost::end(Range), Storage); + ::std::sort(Storage, Storage+m_Size); + } + + // Copy constructor + is_any_ofF(const is_any_ofF& Other) : m_Size(Other.m_Size) + { + // Prepare storage + m_Storage.m_dynSet=0; + const set_value_type* SrcStorage=0; + set_value_type* DestStorage=0; + + if(use_fixed_storage(m_Size)) + { + // Use fixed storage + DestStorage=&m_Storage.m_fixSet[0]; + SrcStorage=&Other.m_Storage.m_fixSet[0]; + } + else + { + // Use dynamic storage + m_Storage.m_dynSet=new set_value_type[m_Size]; + DestStorage=m_Storage.m_dynSet; + SrcStorage=Other.m_Storage.m_dynSet; + } + + // Use fixed storage + ::std::memcpy(DestStorage, SrcStorage, sizeof(set_value_type)*m_Size); + } + + // Destructor + ~is_any_ofF() + { + if(!use_fixed_storage(m_Size) && m_Storage.m_dynSet!=0) + { + delete [] m_Storage.m_dynSet; + } + } + + // Assignment + is_any_ofF& operator=(const is_any_ofF& Other) + { + // Handle self assignment + if(this==&Other) return *this; + + // Prepare storage + const set_value_type* SrcStorage; + set_value_type* DestStorage; + + if(use_fixed_storage(Other.m_Size)) + { + // Use fixed storage + DestStorage=&m_Storage.m_fixSet[0]; + SrcStorage=&Other.m_Storage.m_fixSet[0]; + + // Delete old storage if was present + if(!use_fixed_storage(m_Size) && m_Storage.m_dynSet!=0) + { + delete [] m_Storage.m_dynSet; + } + + // Set new size + m_Size=Other.m_Size; + } + else + { + // Other uses dynamic storage + SrcStorage=Other.m_Storage.m_dynSet; + + // Check what kind of storage are we using right now + if(use_fixed_storage(m_Size)) + { + // Using fixed storage, allocate new + set_value_type* pTemp=new set_value_type[Other.m_Size]; + DestStorage=pTemp; + m_Storage.m_dynSet=pTemp; + m_Size=Other.m_Size; + } + else + { + // Using dynamic storage, check if can reuse + if(m_Storage.m_dynSet!=0 && m_Size>=Other.m_Size && m_Size + bool operator()( Char2T Ch ) const + { + const set_value_type* Storage= + (use_fixed_storage(m_Size)) + ? &m_Storage.m_fixSet[0] + : m_Storage.m_dynSet; + + return ::std::binary_search(Storage, Storage+m_Size, Ch); + } + private: + // check if the size is eligible for fixed storage + static bool use_fixed_storage(std::size_t size) + { + return size<=sizeof(set_value_type*)*2; + } + + + private: + // storage + // The actual used storage is selected on the type + union + { + set_value_type* m_dynSet; + set_value_type m_fixSet[sizeof(set_value_type*)*2]; + } + m_Storage; + + // storage size + ::std::size_t m_Size; + }; + + // is_from_range functor + /* + returns true if the value is from the specified range. + (i.e. x>=From && x>=To) + */ + template + struct is_from_rangeF : + public predicate_facade< is_from_rangeF > + { + // Boost.ResultOf support + typedef bool result_type; + + // Constructor + is_from_rangeF( CharT From, CharT To ) : m_From(From), m_To(To) {} + + // Operation + template + bool operator()( Char2T Ch ) const + { + return ( m_From <= Ch ) && ( Ch <= m_To ); + } + + private: + CharT m_From; + CharT m_To; + }; + + // class_and composition predicate + template + struct pred_andF : + public predicate_facade< pred_andF > + { + public: + + // Boost.ResultOf support + typedef bool result_type; + + // Constructor + pred_andF( Pred1T Pred1, Pred2T Pred2 ) : + m_Pred1(Pred1), m_Pred2(Pred2) {} + + // Operation + template + bool operator()( CharT Ch ) const + { + return m_Pred1(Ch) && m_Pred2(Ch); + } + + private: + Pred1T m_Pred1; + Pred2T m_Pred2; + }; + + // class_or composition predicate + template + struct pred_orF : + public predicate_facade< pred_orF > + { + public: + // Boost.ResultOf support + typedef bool result_type; + + // Constructor + pred_orF( Pred1T Pred1, Pred2T Pred2 ) : + m_Pred1(Pred1), m_Pred2(Pred2) {} + + // Operation + template + bool operator()( CharT Ch ) const + { + return m_Pred1(Ch) || m_Pred2(Ch); + } + + private: + Pred1T m_Pred1; + Pred2T m_Pred2; + }; + + // class_not composition predicate + template< typename PredT > + struct pred_notF : + public predicate_facade< pred_notF > + { + public: + // Boost.ResultOf support + typedef bool result_type; + + // Constructor + pred_notF( PredT Pred ) : m_Pred(Pred) {} + + // Operation + template + bool operator()( CharT Ch ) const + { + return !m_Pred(Ch); + } + + private: + PredT m_Pred; + }; + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_CLASSIFICATION_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format.hpp new file mode 100644 index 0000000..b398750 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format.hpp @@ -0,0 +1,204 @@ +// Boost string_algo library find_format.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_FORMAT_DETAIL_HPP +#define BOOST_STRING_FIND_FORMAT_DETAIL_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// find_format_copy (iterator variant) implementation -------------------------------// + + template< + typename OutputIteratorT, + typename InputT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline OutputIteratorT find_format_copy_impl2( + OutputIteratorT Output, + const InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult, + const FormatResultT& FormatResult ) + { + typedef find_format_store< + BOOST_STRING_TYPENAME + range_const_iterator::type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + if ( !M ) + { + // Match not found - return original sequence + Output = std::copy( ::boost::begin(Input), ::boost::end(Input), Output ); + return Output; + } + + // Copy the beginning of the sequence + Output = std::copy( ::boost::begin(Input), ::boost::begin(M), Output ); + // Format find result + // Copy formatted result + Output = std::copy( ::boost::begin(M.format_result()), ::boost::end(M.format_result()), Output ); + // Copy the rest of the sequence + Output = std::copy( M.end(), ::boost::end(Input), Output ); + + return Output; + } + + template< + typename OutputIteratorT, + typename InputT, + typename FormatterT, + typename FindResultT > + inline OutputIteratorT find_format_copy_impl( + OutputIteratorT Output, + const InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult ) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + return ::boost::algorithm::detail::find_format_copy_impl2( + Output, + Input, + Formatter, + FindResult, + Formatter(FindResult) ); + } else { + return std::copy( ::boost::begin(Input), ::boost::end(Input), Output ); + } + } + + +// find_format_copy implementation --------------------------------------------------// + + template< + typename InputT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline InputT find_format_copy_impl2( + const InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult, + const FormatResultT& FormatResult) + { + typedef find_format_store< + BOOST_STRING_TYPENAME + range_const_iterator::type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + if ( !M ) + { + // Match not found - return original sequence + return InputT( Input ); + } + + InputT Output; + // Copy the beginning of the sequence + boost::algorithm::detail::insert( Output, ::boost::end(Output), ::boost::begin(Input), M.begin() ); + // Copy formatted result + boost::algorithm::detail::insert( Output, ::boost::end(Output), M.format_result() ); + // Copy the rest of the sequence + boost::algorithm::detail::insert( Output, ::boost::end(Output), M.end(), ::boost::end(Input) ); + + return Output; + } + + template< + typename InputT, + typename FormatterT, + typename FindResultT > + inline InputT find_format_copy_impl( + const InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + return ::boost::algorithm::detail::find_format_copy_impl2( + Input, + Formatter, + FindResult, + Formatter(FindResult) ); + } else { + return Input; + } + } + + // replace implementation ----------------------------------------------------// + + template< + typename InputT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline void find_format_impl2( + InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult, + const FormatResultT& FormatResult) + { + typedef find_format_store< + BOOST_STRING_TYPENAME + range_iterator::type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + if ( !M ) + { + // Search not found - return original sequence + return; + } + + // Replace match + ::boost::algorithm::detail::replace( Input, M.begin(), M.end(), M.format_result() ); + } + + template< + typename InputT, + typename FormatterT, + typename FindResultT > + inline void find_format_impl( + InputT& Input, + FormatterT Formatter, + const FindResultT& FindResult) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + ::boost::algorithm::detail::find_format_impl2( + Input, + Formatter, + FindResult, + Formatter(FindResult) ); + } + } + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FIND_FORMAT_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_all.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_all.hpp new file mode 100644 index 0000000..52930c8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_all.hpp @@ -0,0 +1,273 @@ +// Boost string_algo library find_format_all.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_FORMAT_ALL_DETAIL_HPP +#define BOOST_STRING_FIND_FORMAT_ALL_DETAIL_HPP + +#include +#include +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// find_format_all_copy (iterator variant) implementation ---------------------------// + + template< + typename OutputIteratorT, + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline OutputIteratorT find_format_all_copy_impl2( + OutputIteratorT Output, + const InputT& Input, + FinderT Finder, + FormatterT Formatter, + const FindResultT& FindResult, + const FormatResultT& FormatResult ) + { + typedef BOOST_STRING_TYPENAME + range_const_iterator::type input_iterator_type; + + typedef find_format_store< + input_iterator_type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + // Initialize last match + input_iterator_type LastMatch=::boost::begin(Input); + + // Iterate through all matches + while( M ) + { + // Copy the beginning of the sequence + Output = std::copy( LastMatch, M.begin(), Output ); + // Copy formatted result + Output = std::copy( ::boost::begin(M.format_result()), ::boost::end(M.format_result()), Output ); + + // Proceed to the next match + LastMatch=M.end(); + M=Finder( LastMatch, ::boost::end(Input) ); + } + + // Copy the rest of the sequence + Output = std::copy( LastMatch, ::boost::end(Input), Output ); + + return Output; + } + + template< + typename OutputIteratorT, + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT > + inline OutputIteratorT find_format_all_copy_impl( + OutputIteratorT Output, + const InputT& Input, + FinderT Finder, + FormatterT Formatter, + const FindResultT& FindResult ) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + return ::boost::algorithm::detail::find_format_all_copy_impl2( + Output, + Input, + Finder, + Formatter, + FindResult, + Formatter(FindResult) ); + } else { + return std::copy( ::boost::begin(Input), ::boost::end(Input), Output ); + } + } + + // find_format_all_copy implementation ----------------------------------------------// + + template< + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline InputT find_format_all_copy_impl2( + const InputT& Input, + FinderT Finder, + FormatterT Formatter, + const FindResultT& FindResult, + const FormatResultT& FormatResult) + { + typedef BOOST_STRING_TYPENAME + range_const_iterator::type input_iterator_type; + + typedef find_format_store< + input_iterator_type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + // Initialize last match + input_iterator_type LastMatch=::boost::begin(Input); + + // Output temporary + InputT Output; + + // Iterate through all matches + while( M ) + { + // Copy the beginning of the sequence + boost::algorithm::detail::insert( Output, ::boost::end(Output), LastMatch, M.begin() ); + // Copy formatted result + boost::algorithm::detail::insert( Output, ::boost::end(Output), M.format_result() ); + + // Proceed to the next match + LastMatch=M.end(); + M=Finder( LastMatch, ::boost::end(Input) ); + } + + // Copy the rest of the sequence + ::boost::algorithm::detail::insert( Output, ::boost::end(Output), LastMatch, ::boost::end(Input) ); + + return Output; + } + + template< + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT > + inline InputT find_format_all_copy_impl( + const InputT& Input, + FinderT Finder, + FormatterT Formatter, + const FindResultT& FindResult) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + return ::boost::algorithm::detail::find_format_all_copy_impl2( + Input, + Finder, + Formatter, + FindResult, + Formatter(FindResult) ); + } else { + return Input; + } + } + + // find_format_all implementation ------------------------------------------------// + + template< + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT, + typename FormatResultT > + inline void find_format_all_impl2( + InputT& Input, + FinderT Finder, + FormatterT Formatter, + FindResultT FindResult, + FormatResultT FormatResult) + { + typedef BOOST_STRING_TYPENAME + range_iterator::type input_iterator_type; + typedef find_format_store< + input_iterator_type, + FormatterT, + FormatResultT > store_type; + + // Create store for the find result + store_type M( FindResult, FormatResult, Formatter ); + + // Instantiate replacement storage + std::deque< + BOOST_STRING_TYPENAME range_value::type> Storage; + + // Initialize replacement iterators + input_iterator_type InsertIt=::boost::begin(Input); + input_iterator_type SearchIt=::boost::begin(Input); + + while( M ) + { + // process the segment + InsertIt=process_segment( + Storage, + Input, + InsertIt, + SearchIt, + M.begin() ); + + // Adjust search iterator + SearchIt=M.end(); + + // Copy formatted replace to the storage + ::boost::algorithm::detail::copy_to_storage( Storage, M.format_result() ); + + // Find range for a next match + M=Finder( SearchIt, ::boost::end(Input) ); + } + + // process the last segment + InsertIt=::boost::algorithm::detail::process_segment( + Storage, + Input, + InsertIt, + SearchIt, + ::boost::end(Input) ); + + if ( Storage.empty() ) + { + // Truncate input + ::boost::algorithm::detail::erase( Input, InsertIt, ::boost::end(Input) ); + } + else + { + // Copy remaining data to the end of input + ::boost::algorithm::detail::insert( Input, ::boost::end(Input), Storage.begin(), Storage.end() ); + } + } + + template< + typename InputT, + typename FinderT, + typename FormatterT, + typename FindResultT > + inline void find_format_all_impl( + InputT& Input, + FinderT Finder, + FormatterT Formatter, + FindResultT FindResult) + { + if( ::boost::algorithm::detail::check_find_result(Input, FindResult) ) { + ::boost::algorithm::detail::find_format_all_impl2( + Input, + Finder, + Formatter, + FindResult, + Formatter(FindResult) ); + } + } + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FIND_FORMAT_ALL_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_store.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_store.hpp new file mode 100644 index 0000000..b9f4a88 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_format_store.hpp @@ -0,0 +1,89 @@ +// Boost string_algo library find_format_store.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_FORMAT_STORE_DETAIL_HPP +#define BOOST_STRING_FIND_FORMAT_STORE_DETAIL_HPP + +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// temporary format and find result storage --------------------------------// + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +#pragma warning(push) +#pragma warning(disable:4512) //assignment operator could not be generated +#endif + template< + typename ForwardIteratorT, + typename FormatterT, + typename FormatResultT > + class find_format_store : + public iterator_range + { + public: + // typedefs + typedef iterator_range base_type; + typedef FormatterT formatter_type; + typedef FormatResultT format_result_type; + + public: + // Construction + find_format_store( + const base_type& FindResult, + const format_result_type& FormatResult, + const formatter_type& Formatter ) : + base_type(FindResult), + m_FormatResult(FormatResult), + m_Formatter(Formatter) {} + + // Assignment + template< typename FindResultT > + find_format_store& operator=( FindResultT FindResult ) + { + iterator_range::operator=(FindResult); + if( !this->empty() ) { + m_FormatResult=m_Formatter(FindResult); + } + + return *this; + } + + // Retrieve format result + const format_result_type& format_result() + { + return m_FormatResult; + } + + private: + format_result_type m_FormatResult; + const formatter_type& m_Formatter; + }; + + template + bool check_find_result(InputT&, FindResultT& FindResult) + { + typedef BOOST_STRING_TYPENAME + range_const_iterator::type input_iterator_type; + iterator_range ResultRange(FindResult); + return !ResultRange.empty(); + } + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +#pragma warning(pop) +#endif + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FIND_FORMAT_STORE_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_iterator.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_iterator.hpp new file mode 100644 index 0000000..9b78a0f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/find_iterator.hpp @@ -0,0 +1,87 @@ +// Boost string_algo library find_iterator.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_ITERATOR_DETAIL_HPP +#define BOOST_STRING_FIND_ITERATOR_DETAIL_HPP + +#include +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// find_iterator base -----------------------------------------------// + + // Find iterator base + template + class find_iterator_base + { + protected: + // typedefs + typedef IteratorT input_iterator_type; + typedef iterator_range match_type; + typedef function2< + match_type, + input_iterator_type, + input_iterator_type> finder_type; + + protected: + // Protected construction/destruction + + // Default constructor + find_iterator_base() {}; + // Copy construction + find_iterator_base( const find_iterator_base& Other ) : + m_Finder(Other.m_Finder) {} + + // Constructor + template + find_iterator_base( FinderT Finder, int ) : + m_Finder(Finder) {} + + // Destructor + ~find_iterator_base() {} + + // Find operation + match_type do_find( + input_iterator_type Begin, + input_iterator_type End ) const + { + if (!m_Finder.empty()) + { + return m_Finder(Begin,End); + } + else + { + return match_type(End,End); + } + } + + // Check + bool is_null() const + { + return m_Finder.empty(); + } + + private: + // Finder + finder_type m_Finder; + }; + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_FIND_ITERATOR_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder.hpp new file mode 100644 index 0000000..a2a9582 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder.hpp @@ -0,0 +1,639 @@ +// Boost string_algo library finder.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FINDER_DETAIL_HPP +#define BOOST_STRING_FINDER_DETAIL_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + + +// find first functor -----------------------------------------------// + + // find a subsequence in the sequence ( functor ) + /* + Returns a pair marking the subsequence in the sequence. + If the find fails, functor returns + */ + template + struct first_finderF + { + typedef SearchIteratorT search_iterator_type; + + // Construction + template< typename SearchT > + first_finderF( const SearchT& Search, PredicateT Comp ) : + m_Search(::boost::begin(Search), ::boost::end(Search)), m_Comp(Comp) {} + first_finderF( + search_iterator_type SearchBegin, + search_iterator_type SearchEnd, + PredicateT Comp ) : + m_Search(SearchBegin, SearchEnd), m_Comp(Comp) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + typedef iterator_range result_type; + typedef ForwardIteratorT input_iterator_type; + + // Outer loop + for(input_iterator_type OuterIt=Begin; + OuterIt!=End; + ++OuterIt) + { + // Sanity check + if( boost::empty(m_Search) ) + return result_type( End, End ); + + input_iterator_type InnerIt=OuterIt; + search_iterator_type SubstrIt=m_Search.begin(); + for(; + InnerIt!=End && SubstrIt!=m_Search.end(); + ++InnerIt,++SubstrIt) + { + if( !( m_Comp(*InnerIt,*SubstrIt) ) ) + break; + } + + // Substring matching succeeded + if ( SubstrIt==m_Search.end() ) + return result_type( OuterIt, InnerIt ); + } + + return result_type( End, End ); + } + + private: + iterator_range m_Search; + PredicateT m_Comp; + }; + +// find last functor -----------------------------------------------// + + // find the last match a subsequence in the sequence ( functor ) + /* + Returns a pair marking the subsequence in the sequence. + If the find fails, returns + */ + template + struct last_finderF + { + typedef SearchIteratorT search_iterator_type; + typedef first_finderF< + search_iterator_type, + PredicateT> first_finder_type; + + // Construction + template< typename SearchT > + last_finderF( const SearchT& Search, PredicateT Comp ) : + m_Search(::boost::begin(Search), ::boost::end(Search)), m_Comp(Comp) {} + last_finderF( + search_iterator_type SearchBegin, + search_iterator_type SearchEnd, + PredicateT Comp ) : + m_Search(SearchBegin, SearchEnd), m_Comp(Comp) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + typedef iterator_range result_type; + + if( boost::empty(m_Search) ) + return result_type( End, End ); + + typedef BOOST_STRING_TYPENAME boost::detail:: + iterator_traits::iterator_category category; + + return findit( Begin, End, category() ); + } + + private: + // forward iterator + template< typename ForwardIteratorT > + iterator_range + findit( + ForwardIteratorT Begin, + ForwardIteratorT End, + std::forward_iterator_tag ) const + { + typedef iterator_range result_type; + + first_finder_type first_finder( + m_Search.begin(), m_Search.end(), m_Comp ); + + result_type M=first_finder( Begin, End ); + result_type Last=M; + + while( M ) + { + Last=M; + M=first_finder( ::boost::end(M), End ); + } + + return Last; + } + + // bidirectional iterator + template< typename ForwardIteratorT > + iterator_range + findit( + ForwardIteratorT Begin, + ForwardIteratorT End, + std::bidirectional_iterator_tag ) const + { + typedef iterator_range result_type; + typedef ForwardIteratorT input_iterator_type; + + // Outer loop + for(input_iterator_type OuterIt=End; + OuterIt!=Begin; ) + { + input_iterator_type OuterIt2=--OuterIt; + + input_iterator_type InnerIt=OuterIt2; + search_iterator_type SubstrIt=m_Search.begin(); + for(; + InnerIt!=End && SubstrIt!=m_Search.end(); + ++InnerIt,++SubstrIt) + { + if( !( m_Comp(*InnerIt,*SubstrIt) ) ) + break; + } + + // Substring matching succeeded + if( SubstrIt==m_Search.end() ) + return result_type( OuterIt2, InnerIt ); + } + + return result_type( End, End ); + } + + private: + iterator_range m_Search; + PredicateT m_Comp; + }; + +// find n-th functor -----------------------------------------------// + + // find the n-th match of a subsequence in the sequence ( functor ) + /* + Returns a pair marking the subsequence in the sequence. + If the find fails, returns + */ + template + struct nth_finderF + { + typedef SearchIteratorT search_iterator_type; + typedef first_finderF< + search_iterator_type, + PredicateT> first_finder_type; + typedef last_finderF< + search_iterator_type, + PredicateT> last_finder_type; + + // Construction + template< typename SearchT > + nth_finderF( + const SearchT& Search, + int Nth, + PredicateT Comp) : + m_Search(::boost::begin(Search), ::boost::end(Search)), + m_Nth(Nth), + m_Comp(Comp) {} + nth_finderF( + search_iterator_type SearchBegin, + search_iterator_type SearchEnd, + int Nth, + PredicateT Comp) : + m_Search(SearchBegin, SearchEnd), + m_Nth(Nth), + m_Comp(Comp) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + if(m_Nth>=0) + { + return find_forward(Begin, End, m_Nth); + } + else + { + return find_backward(Begin, End, -m_Nth); + } + + } + + private: + // Implementation helpers + template< typename ForwardIteratorT > + iterator_range + find_forward( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N) const + { + typedef iterator_range result_type; + + // Sanity check + if( boost::empty(m_Search) ) + return result_type( End, End ); + + // Instantiate find functor + first_finder_type first_finder( + m_Search.begin(), m_Search.end(), m_Comp ); + + result_type M( Begin, Begin ); + + for( unsigned int n=0; n<=N; ++n ) + { + // find next match + M=first_finder( ::boost::end(M), End ); + + if ( !M ) + { + // Subsequence not found, return + return M; + } + } + + return M; + } + + template< typename ForwardIteratorT > + iterator_range + find_backward( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N) const + { + typedef iterator_range result_type; + + // Sanity check + if( boost::empty(m_Search) ) + return result_type( End, End ); + + // Instantiate find functor + last_finder_type last_finder( + m_Search.begin(), m_Search.end(), m_Comp ); + + result_type M( End, End ); + + for( unsigned int n=1; n<=N; ++n ) + { + // find next match + M=last_finder( Begin, ::boost::begin(M) ); + + if ( !M ) + { + // Subsequence not found, return + return M; + } + } + + return M; + } + + + private: + iterator_range m_Search; + int m_Nth; + PredicateT m_Comp; + }; + +// find head/tail implementation helpers ---------------------------// + + template + iterator_range + find_head_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N, + std::forward_iterator_tag ) + { + typedef ForwardIteratorT input_iterator_type; + typedef iterator_range result_type; + + input_iterator_type It=Begin; + for( + unsigned int Index=0; + Index + iterator_range + find_head_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N, + std::random_access_iterator_tag ) + { + typedef iterator_range result_type; + + if ( (End<=Begin) || ( static_cast(End-Begin) < N ) ) + return result_type( Begin, End ); + + return result_type(Begin,Begin+N); + } + + // Find head implementation + template + iterator_range + find_head_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N ) + { + typedef BOOST_STRING_TYPENAME boost::detail:: + iterator_traits::iterator_category category; + + return ::boost::algorithm::detail::find_head_impl( Begin, End, N, category() ); + } + + template< typename ForwardIteratorT > + iterator_range + find_tail_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N, + std::forward_iterator_tag ) + { + typedef ForwardIteratorT input_iterator_type; + typedef iterator_range result_type; + + unsigned int Index=0; + input_iterator_type It=Begin; + input_iterator_type It2=Begin; + + // Advance It2 by N increments + for( Index=0; Index + iterator_range + find_tail_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N, + std::bidirectional_iterator_tag ) + { + typedef ForwardIteratorT input_iterator_type; + typedef iterator_range result_type; + + input_iterator_type It=End; + for( + unsigned int Index=0; + Index + iterator_range + find_tail_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N, + std::random_access_iterator_tag ) + { + typedef iterator_range result_type; + + if ( (End<=Begin) || ( static_cast(End-Begin) < N ) ) + return result_type( Begin, End ); + + return result_type( End-N, End ); + } + + // Operation + template< typename ForwardIteratorT > + iterator_range + find_tail_impl( + ForwardIteratorT Begin, + ForwardIteratorT End, + unsigned int N ) + { + typedef BOOST_STRING_TYPENAME boost::detail:: + iterator_traits::iterator_category category; + + return ::boost::algorithm::detail::find_tail_impl( Begin, End, N, category() ); + } + + + +// find head functor -----------------------------------------------// + + + // find a head in the sequence ( functor ) + /* + This functor find a head of the specified range. For + a specified N, the head is a subsequence of N starting + elements of the range. + */ + struct head_finderF + { + // Construction + head_finderF( int N ) : m_N(N) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + if(m_N>=0) + { + return ::boost::algorithm::detail::find_head_impl( Begin, End, m_N ); + } + else + { + iterator_range Res= + ::boost::algorithm::detail::find_tail_impl( Begin, End, -m_N ); + + return ::boost::make_iterator_range(Begin, Res.begin()); + } + } + + private: + int m_N; + }; + +// find tail functor -----------------------------------------------// + + + // find a tail in the sequence ( functor ) + /* + This functor find a tail of the specified range. For + a specified N, the head is a subsequence of N starting + elements of the range. + */ + struct tail_finderF + { + // Construction + tail_finderF( int N ) : m_N(N) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + if(m_N>=0) + { + return ::boost::algorithm::detail::find_tail_impl( Begin, End, m_N ); + } + else + { + iterator_range Res= + ::boost::algorithm::detail::find_head_impl( Begin, End, -m_N ); + + return ::boost::make_iterator_range(Res.end(), End); + } + } + + private: + int m_N; + }; + +// find token functor -----------------------------------------------// + + // find a token in a sequence ( functor ) + /* + This find functor finds a token specified be a predicate + in a sequence. It is equivalent of std::find algorithm, + with an exception that it return range instead of a single + iterator. + + If bCompress is set to true, adjacent matching tokens are + concatenated into one match. + */ + template< typename PredicateT > + struct token_finderF + { + // Construction + token_finderF( + PredicateT Pred, + token_compress_mode_type eCompress=token_compress_off ) : + m_Pred(Pred), m_eCompress(eCompress) {} + + // Operation + template< typename ForwardIteratorT > + iterator_range + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + typedef iterator_range result_type; + + ForwardIteratorT It=std::find_if( Begin, End, m_Pred ); + + if( It==End ) + { + return result_type( End, End ); + } + else + { + ForwardIteratorT It2=It; + + if( m_eCompress==token_compress_on ) + { + // Find first non-matching character + while( It2!=End && m_Pred(*It2) ) ++It2; + } + else + { + // Advance by one position + ++It2; + } + + return result_type( It, It2 ); + } + } + + private: + PredicateT m_Pred; + token_compress_mode_type m_eCompress; + }; + +// find range functor -----------------------------------------------// + + // find a range in the sequence ( functor ) + /* + This functor actually does not perform any find operation. + It always returns given iterator range as a result. + */ + template + struct range_finderF + { + typedef ForwardIterator1T input_iterator_type; + typedef iterator_range result_type; + + // Construction + range_finderF( + input_iterator_type Begin, + input_iterator_type End ) : m_Range(Begin, End) {} + + range_finderF(const iterator_range& Range) : + m_Range(Range) {} + + // Operation + template< typename ForwardIterator2T > + iterator_range + operator()( + ForwardIterator2T, + ForwardIterator2T ) const + { +#if BOOST_WORKAROUND( __MWERKS__, <= 0x3003 ) + return iterator_range(this->m_Range); +#else + return m_Range; +#endif + } + + private: + iterator_range m_Range; + }; + + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FINDER_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder_regex.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder_regex.hpp new file mode 100644 index 0000000..9cb01cf --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/finder_regex.hpp @@ -0,0 +1,122 @@ +// Boost string_algo library find_regex.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FINDER_REGEX_DETAIL_HPP +#define BOOST_STRING_FINDER_REGEX_DETAIL_HPP + +#include +#include + +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// regex find functor -----------------------------------------------// + + // regex search result + template + struct regex_search_result : + public iterator_range + { + typedef regex_search_result type; + typedef iterator_range base_type; + typedef BOOST_STRING_TYPENAME base_type::value_type value_type; + typedef BOOST_STRING_TYPENAME base_type::difference_type difference_type; + typedef BOOST_STRING_TYPENAME base_type::const_iterator const_iterator; + typedef BOOST_STRING_TYPENAME base_type::iterator iterator; + typedef boost::match_results match_results_type; + + // Construction + + // Construction from the match result + regex_search_result( const match_results_type& MatchResults ) : + base_type( MatchResults[0].first, MatchResults[0].second ), + m_MatchResults( MatchResults ) {} + + // Construction of empty match. End iterator has to be specified + regex_search_result( IteratorT End ) : + base_type( End, End ) {} + + regex_search_result( const regex_search_result& Other ) : + base_type( Other.begin(), Other.end() ), + m_MatchResults( Other.m_MatchResults ) {} + + // Assignment + regex_search_result& operator=( const regex_search_result& Other ) + { + base_type::operator=( Other ); + m_MatchResults=Other.m_MatchResults; + return *this; + } + + // Match result retrieval + const match_results_type& match_results() const + { + return m_MatchResults; + } + + private: + // Saved match result + match_results_type m_MatchResults; + }; + + // find_regex + /* + Regex based search functor + */ + template + struct find_regexF + { + typedef RegExT regex_type; + typedef const RegExT& regex_reference_type; + + // Construction + find_regexF( regex_reference_type Rx, match_flag_type MatchFlags = match_default ) : + m_Rx(Rx), m_MatchFlags(MatchFlags) {} + + // Operation + template< typename ForwardIteratorT > + regex_search_result + operator()( + ForwardIteratorT Begin, + ForwardIteratorT End ) const + { + typedef ForwardIteratorT input_iterator_type; + typedef regex_search_result result_type; + + // instantiate match result + match_results result; + // search for a match + if ( ::boost::regex_search( Begin, End, result, m_Rx, m_MatchFlags ) ) + { + // construct a result + return result_type( result ); + } + else + { + // empty result + return result_type( End ); + } + } + + private: + regex_reference_type m_Rx; // Regexp + match_flag_type m_MatchFlags; // match flags + }; + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FIND_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter.hpp new file mode 100644 index 0000000..c071822 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter.hpp @@ -0,0 +1,119 @@ +// Boost string_algo library formatter.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FORMATTER_DETAIL_HPP +#define BOOST_STRING_FORMATTER_DETAIL_HPP + + +#include +#include +#include +#include + +#include + +// generic replace functors -----------------------------------------------// + +namespace boost { + namespace algorithm { + namespace detail { + +// const format functor ----------------------------------------------------// + + // constant format functor + template + struct const_formatF + { + private: + typedef BOOST_STRING_TYPENAME + range_const_iterator::type format_iterator; + typedef iterator_range result_type; + + public: + // Construction + const_formatF(const RangeT& Format) : + m_Format(::boost::begin(Format), ::boost::end(Format)) {} + + // Operation +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) + template + result_type& operator()(const Range2T&) + { + return m_Format; + } +#endif + + template + const result_type& operator()(const Range2T&) const + { + return m_Format; + } + + private: + result_type m_Format; + }; + +// identity format functor ----------------------------------------------------// + + // identity format functor + template + struct identity_formatF + { + // Operation + template< typename Range2T > + const RangeT& operator()(const Range2T& Replace) const + { + return RangeT(::boost::begin(Replace), ::boost::end(Replace)); + } + }; + +// empty format functor ( used by erase ) ------------------------------------// + + // empty format functor + template< typename CharT > + struct empty_formatF + { + template< typename ReplaceT > + empty_container operator()(const ReplaceT&) const + { + return empty_container(); + } + }; + +// dissect format functor ----------------------------------------------------// + + // dissect format functor + template + struct dissect_formatF + { + public: + // Construction + dissect_formatF(FinderT Finder) : + m_Finder(Finder) {} + + // Operation + template + inline iterator_range< + BOOST_STRING_TYPENAME range_const_iterator::type> + operator()(const RangeT& Replace) const + { + return m_Finder(::boost::begin(Replace), ::boost::end(Replace)); + } + + private: + FinderT m_Finder; + }; + + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FORMATTER_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter_regex.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter_regex.hpp new file mode 100644 index 0000000..5f26407 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/formatter_regex.hpp @@ -0,0 +1,61 @@ +// Boost string_algo library formatter_regex.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FORMATTER_REGEX_DETAIL_HPP +#define BOOST_STRING_FORMATTER_REGEX_DETAIL_HPP + +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// regex format functor -----------------------------------------// + + // regex format functor + template + struct regex_formatF + { + private: + typedef StringT result_type; + typedef BOOST_STRING_TYPENAME StringT::value_type char_type; + + public: + // Construction + regex_formatF( const StringT& Fmt, match_flag_type Flags=format_default ) : + m_Fmt(Fmt), m_Flags( Flags ) {} + + template + result_type operator()( + const regex_search_result& Replace ) const + { + if ( Replace.empty() ) + { + return result_type(); + } + else + { + return Replace.match_results().format( m_Fmt, m_Flags ); + } + } + private: + const StringT& m_Fmt; + match_flag_type m_Flags; + }; + + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_FORMATTER_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/predicate.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/predicate.hpp new file mode 100644 index 0000000..5acf3cc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/predicate.hpp @@ -0,0 +1,77 @@ +// Boost string_algo library predicate.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_PREDICATE_DETAIL_HPP +#define BOOST_STRING_PREDICATE_DETAIL_HPP + +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// ends_with predicate implementation ----------------------------------// + + template< + typename ForwardIterator1T, + typename ForwardIterator2T, + typename PredicateT> + inline bool ends_with_iter_select( + ForwardIterator1T Begin, + ForwardIterator1T End, + ForwardIterator2T SubBegin, + ForwardIterator2T SubEnd, + PredicateT Comp, + std::bidirectional_iterator_tag) + { + ForwardIterator1T it=End; + ForwardIterator2T pit=SubEnd; + for(;it!=Begin && pit!=SubBegin;) + { + if( !(Comp(*(--it),*(--pit))) ) + return false; + } + + return pit==SubBegin; + } + + template< + typename ForwardIterator1T, + typename ForwardIterator2T, + typename PredicateT> + inline bool ends_with_iter_select( + ForwardIterator1T Begin, + ForwardIterator1T End, + ForwardIterator2T SubBegin, + ForwardIterator2T SubEnd, + PredicateT Comp, + std::forward_iterator_tag) + { + if ( SubBegin==SubEnd ) + { + // empty subsequence check + return true; + } + + iterator_range Result + =last_finder( + ::boost::make_iterator_range(SubBegin, SubEnd), + Comp)(Begin, End); + + return !Result.empty() && Result.end()==End; + } + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_PREDICATE_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/replace_storage.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/replace_storage.hpp new file mode 100644 index 0000000..db35e4c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/replace_storage.hpp @@ -0,0 +1,159 @@ +// Boost string_algo library replace_storage.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_REPLACE_STORAGE_DETAIL_HPP +#define BOOST_STRING_REPLACE_STORAGE_DETAIL_HPP + +#include +#include +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// storage handling routines -----------------------------------------------// + + template< typename StorageT, typename OutputIteratorT > + inline OutputIteratorT move_from_storage( + StorageT& Storage, + OutputIteratorT DestBegin, + OutputIteratorT DestEnd ) + { + OutputIteratorT OutputIt=DestBegin; + + while( !Storage.empty() && OutputIt!=DestEnd ) + { + *OutputIt=Storage.front(); + Storage.pop_front(); + ++OutputIt; + } + + return OutputIt; + } + + template< typename StorageT, typename WhatT > + inline void copy_to_storage( + StorageT& Storage, + const WhatT& What ) + { + Storage.insert( Storage.end(), ::boost::begin(What), ::boost::end(What) ); + } + + +// process segment routine -----------------------------------------------// + + template< bool HasStableIterators > + struct process_segment_helper + { + // Optimized version of process_segment for generic sequence + template< + typename StorageT, + typename InputT, + typename ForwardIteratorT > + ForwardIteratorT operator()( + StorageT& Storage, + InputT& /*Input*/, + ForwardIteratorT InsertIt, + ForwardIteratorT SegmentBegin, + ForwardIteratorT SegmentEnd ) + { + // Copy data from the storage until the beginning of the segment + ForwardIteratorT It=::boost::algorithm::detail::move_from_storage( Storage, InsertIt, SegmentBegin ); + + // 3 cases are possible : + // a) Storage is empty, It==SegmentBegin + // b) Storage is empty, It!=SegmentBegin + // c) Storage is not empty + + if( Storage.empty() ) + { + if( It==SegmentBegin ) + { + // Case a) everything is grand, just return end of segment + return SegmentEnd; + } + else + { + // Case b) move the segment backwards + return std::copy( SegmentBegin, SegmentEnd, It ); + } + } + else + { + // Case c) -> shift the segment to the left and keep the overlap in the storage + while( It!=SegmentEnd ) + { + // Store value into storage + Storage.push_back( *It ); + // Get the top from the storage and put it here + *It=Storage.front(); + Storage.pop_front(); + + // Advance + ++It; + } + + return It; + } + } + }; + + template<> + struct process_segment_helper< true > + { + // Optimized version of process_segment for list-like sequence + template< + typename StorageT, + typename InputT, + typename ForwardIteratorT > + ForwardIteratorT operator()( + StorageT& Storage, + InputT& Input, + ForwardIteratorT InsertIt, + ForwardIteratorT SegmentBegin, + ForwardIteratorT SegmentEnd ) + + { + // Call replace to do the job + ::boost::algorithm::detail::replace( Input, InsertIt, SegmentBegin, Storage ); + // Empty the storage + Storage.clear(); + // Iterators were not changed, simply return the end of segment + return SegmentEnd; + } + }; + + // Process one segment in the replace_all algorithm + template< + typename StorageT, + typename InputT, + typename ForwardIteratorT > + inline ForwardIteratorT process_segment( + StorageT& Storage, + InputT& Input, + ForwardIteratorT InsertIt, + ForwardIteratorT SegmentBegin, + ForwardIteratorT SegmentEnd ) + { + return + process_segment_helper< + has_stable_iterators::value>()( + Storage, Input, InsertIt, SegmentBegin, SegmentEnd ); + } + + + } // namespace detail + } // namespace algorithm +} // namespace boost + +#endif // BOOST_STRING_REPLACE_STORAGE_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/sequence.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/sequence.hpp new file mode 100644 index 0000000..dc47409 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/sequence.hpp @@ -0,0 +1,200 @@ +// Boost string_algo library sequence.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_DETAIL_SEQUENCE_HPP +#define BOOST_STRING_DETAIL_SEQUENCE_HPP + +#include +#include +#include +#include +#include + +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// insert helpers -------------------------------------------------// + + template< typename InputT, typename ForwardIteratorT > + inline void insert( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator At, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + Input.insert( At, Begin, End ); + } + + template< typename InputT, typename InsertT > + inline void insert( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator At, + const InsertT& Insert ) + { + ::boost::algorithm::detail::insert( Input, At, ::boost::begin(Insert), ::boost::end(Insert) ); + } + +// erase helper ---------------------------------------------------// + + // Erase a range in the sequence + /* + Returns the iterator pointing just after the erase subrange + */ + template< typename InputT > + inline typename InputT::iterator erase( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To ) + { + return Input.erase( From, To ); + } + +// replace helper implementation ----------------------------------// + + // Optimized version of replace for generic sequence containers + // Assumption: insert and erase are expensive + template< bool HasConstTimeOperations > + struct replace_const_time_helper + { + template< typename InputT, typename ForwardIteratorT > + void operator()( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + // Copy data to the container ( as much as possible ) + ForwardIteratorT InsertIt=Begin; + BOOST_STRING_TYPENAME InputT::iterator InputIt=From; + for(; InsertIt!=End && InputIt!=To; InsertIt++, InputIt++ ) + { + *InputIt=*InsertIt; + } + + if ( InsertIt!=End ) + { + // Replace sequence is longer, insert it + Input.insert( InputIt, InsertIt, End ); + } + else + { + if ( InputIt!=To ) + { + // Replace sequence is shorter, erase the rest + Input.erase( InputIt, To ); + } + } + } + }; + + template<> + struct replace_const_time_helper< true > + { + // Const-time erase and insert methods -> use them + template< typename InputT, typename ForwardIteratorT > + void operator()( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + BOOST_STRING_TYPENAME InputT::iterator At=Input.erase( From, To ); + if ( Begin!=End ) + { + if(!Input.empty()) + { + Input.insert( At, Begin, End ); + } + else + { + Input.insert( Input.begin(), Begin, End ); + } + } + } + }; + + // No native replace method + template< bool HasNative > + struct replace_native_helper + { + template< typename InputT, typename ForwardIteratorT > + void operator()( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + replace_const_time_helper< + boost::mpl::and_< + has_const_time_insert, + has_const_time_erase >::value >()( + Input, From, To, Begin, End ); + } + }; + + // Container has native replace method + template<> + struct replace_native_helper< true > + { + template< typename InputT, typename ForwardIteratorT > + void operator()( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + Input.replace( From, To, Begin, End ); + } + }; + +// replace helper -------------------------------------------------// + + template< typename InputT, typename ForwardIteratorT > + inline void replace( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + replace_native_helper< has_native_replace::value >()( + Input, From, To, Begin, End ); + } + + template< typename InputT, typename InsertT > + inline void replace( + InputT& Input, + BOOST_STRING_TYPENAME InputT::iterator From, + BOOST_STRING_TYPENAME InputT::iterator To, + const InsertT& Insert ) + { + if(From!=To) + { + ::boost::algorithm::detail::replace( Input, From, To, ::boost::begin(Insert), ::boost::end(Insert) ); + } + else + { + ::boost::algorithm::detail::insert( Input, From, ::boost::begin(Insert), ::boost::end(Insert) ); + } + } + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_DETAIL_SEQUENCE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/trim.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/trim.hpp new file mode 100644 index 0000000..1233e49 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/trim.hpp @@ -0,0 +1,95 @@ +// Boost string_algo library trim.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_TRIM_DETAIL_HPP +#define BOOST_STRING_TRIM_DETAIL_HPP + +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// trim iterator helper -----------------------------------------------// + + template< typename ForwardIteratorT, typename PredicateT > + inline ForwardIteratorT trim_end_iter_select( + ForwardIteratorT InBegin, + ForwardIteratorT InEnd, + PredicateT IsSpace, + std::forward_iterator_tag ) + { + ForwardIteratorT TrimIt=InBegin; + + for( ForwardIteratorT It=InBegin; It!=InEnd; ++It ) + { + if ( !IsSpace(*It) ) + { + TrimIt=It; + ++TrimIt; + } + } + + return TrimIt; + } + + template< typename ForwardIteratorT, typename PredicateT > + inline ForwardIteratorT trim_end_iter_select( + ForwardIteratorT InBegin, + ForwardIteratorT InEnd, + PredicateT IsSpace, + std::bidirectional_iterator_tag ) + { + for( ForwardIteratorT It=InEnd; It!=InBegin; ) + { + if ( !IsSpace(*(--It)) ) + return ++It; + } + + return InBegin; + } + // Search for first non matching character from the beginning of the sequence + template< typename ForwardIteratorT, typename PredicateT > + inline ForwardIteratorT trim_begin( + ForwardIteratorT InBegin, + ForwardIteratorT InEnd, + PredicateT IsSpace ) + { + ForwardIteratorT It=InBegin; + for(; It!=InEnd; ++It ) + { + if (!IsSpace(*It)) + return It; + } + + return It; + } + + // Search for first non matching character from the end of the sequence + template< typename ForwardIteratorT, typename PredicateT > + inline ForwardIteratorT trim_end( + ForwardIteratorT InBegin, + ForwardIteratorT InEnd, + PredicateT IsSpace ) + { + typedef BOOST_STRING_TYPENAME boost::detail:: + iterator_traits::iterator_category category; + + return ::boost::algorithm::detail::trim_end_iter_select( InBegin, InEnd, IsSpace, category() ); + } + + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_TRIM_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/util.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/util.hpp new file mode 100644 index 0000000..cf4a8b1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/detail/util.hpp @@ -0,0 +1,106 @@ +// Boost string_algo library util.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_UTIL_DETAIL_HPP +#define BOOST_STRING_UTIL_DETAIL_HPP + +#include +#include +#include + +namespace boost { + namespace algorithm { + namespace detail { + +// empty container -----------------------------------------------// + + // empty_container + /* + This class represents always empty container, + containing elements of type CharT. + + It is supposed to be used in a const version only + */ + template< typename CharT > + struct empty_container + { + typedef empty_container type; + typedef CharT value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + typedef const value_type& reference; + typedef const value_type& const_reference; + typedef const value_type* iterator; + typedef const value_type* const_iterator; + + + // Operations + const_iterator begin() const + { + return reinterpret_cast(0); + } + + const_iterator end() const + { + return reinterpret_cast(0); + } + + bool empty() const + { + return false; + } + + size_type size() const + { + return 0; + } + }; + +// bounded copy algorithm -----------------------------------------------// + + // Bounded version of the std::copy algorithm + template + inline OutputIteratorT bounded_copy( + InputIteratorT First, + InputIteratorT Last, + OutputIteratorT DestFirst, + OutputIteratorT DestLast ) + { + InputIteratorT InputIt=First; + OutputIteratorT OutputIt=DestFirst; + for(; InputIt!=Last && OutputIt!=DestLast; InputIt++, OutputIt++ ) + { + *OutputIt=*InputIt; + } + + return OutputIt; + } + +// iterator range utilities -----------------------------------------// + + // copy range functor + template< + typename SeqT, + typename IteratorT=BOOST_STRING_TYPENAME SeqT::const_iterator > + struct copy_iterator_rangeF : + public std::unary_function< iterator_range, SeqT > + { + SeqT operator()( const iterator_range& Range ) const + { + return copy_range(Range); + } + }; + + } // namespace detail + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_UTIL_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/erase.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/erase.hpp new file mode 100644 index 0000000..6883790 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/erase.hpp @@ -0,0 +1,844 @@ +// Boost string_algo library erase.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_ERASE_HPP +#define BOOST_STRING_ERASE_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines various erase algorithms. Each algorithm removes + part(s) of the input according to a searching criteria. +*/ + +namespace boost { + namespace algorithm { + +// erase_range -------------------------------------------------------// + + //! Erase range algorithm + /*! + Remove the given range from the input. The result is a modified copy of + the input. It is returned as a sequence or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input sequence + \param SearchRange A range in the input to be removed + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template + inline OutputIteratorT erase_range_copy( + OutputIteratorT Output, + const RangeT& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_const_iterator::type>& SearchRange ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase range algorithm + /*! + \overload + */ + template + inline SequenceT erase_range_copy( + const SequenceT& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_const_iterator::type>& SearchRange ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase range algorithm + /*! + Remove the given range from the input. + The input sequence is modified in-place. + + \param Input An input sequence + \param SearchRange A range in the input to be removed + */ + template + inline void erase_range( + SequenceT& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_iterator::type>& SearchRange ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_first --------------------------------------------------------// + + //! Erase first algorithm + /*! + Remove the first occurrence of the substring from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT erase_first_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase first algorithm + /*! + \overload + */ + template + inline SequenceT erase_first_copy( + const SequenceT& Input, + const RangeT& Search ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase first algorithm + /*! + Remove the first occurrence of the substring from the input. + The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for. + */ + template + inline void erase_first( + SequenceT& Input, + const RangeT& Search ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_first ( case insensitive ) ------------------------------------// + + //! Erase first algorithm ( case insensitive ) + /*! + Remove the first occurrence of the substring from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT ierase_first_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase first algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ierase_first_copy( + const SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase first algorithm ( case insensitive ) + /*! + Remove the first occurrence of the substring from the input. + The input sequence is modified in-place. Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Loc A locale used for case insensitive comparison + */ + template + inline void ierase_first( + SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_last --------------------------------------------------------// + + //! Erase last algorithm + /*! + Remove the last occurrence of the substring from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for. + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT erase_last_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase last algorithm + /*! + \overload + */ + template + inline SequenceT erase_last_copy( + const SequenceT& Input, + const RangeT& Search ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase last algorithm + /*! + Remove the last occurrence of the substring from the input. + The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for + */ + template + inline void erase_last( + SequenceT& Input, + const RangeT& Search ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_last ( case insensitive ) ------------------------------------// + + //! Erase last algorithm ( case insensitive ) + /*! + Remove the last occurrence of the substring from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT ierase_last_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase last algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ierase_last_copy( + const SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase last algorithm ( case insensitive ) + /*! + Remove the last occurrence of the substring from the input. + The input sequence is modified in-place. Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Loc A locale used for case insensitive comparison + */ + template + inline void ierase_last( + SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_nth --------------------------------------------------------------------// + + //! Erase nth algorithm + /*! + Remove the Nth occurrence of the substring in the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT erase_nth_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + int Nth ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase nth algorithm + /*! + \overload + */ + template + inline SequenceT erase_nth_copy( + const SequenceT& Input, + const RangeT& Search, + int Nth ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase nth algorithm + /*! + Remove the Nth occurrence of the substring in the input. + The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for. + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + */ + template + inline void erase_nth( + SequenceT& Input, + const RangeT& Search, + int Nth ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_nth ( case insensitive ) ---------------------------------------------// + + //! Erase nth algorithm ( case insensitive ) + /*! + Remove the Nth occurrence of the substring in the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for. + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT ierase_nth_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + int Nth, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase nth algorithm + /*! + \overload + */ + template + inline SequenceT ierase_nth_copy( + const SequenceT& Input, + const RangeT& Search, + int Nth, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc)), + empty_formatter(Input) ); + } + + //! Erase nth algorithm + /*! + Remove the Nth occurrence of the substring in the input. + The input sequence is modified in-place. Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for. + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Loc A locale used for case insensitive comparison + */ + template + inline void ierase_nth( + SequenceT& Input, + const RangeT& Search, + int Nth, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + +// erase_all --------------------------------------------------------// + + //! Erase all algorithm + /*! + Remove all the occurrences of the string from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + + \param Output An output iterator to which the result will be copied + \param Input An input sequence + \param Search A substring to be searched for. + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT erase_all_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase all algorithm + /*! + \overload + */ + template + inline SequenceT erase_all_copy( + const SequenceT& Input, + const RangeT& Search ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase all algorithm + /*! + Remove all the occurrences of the string from the input. + The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for. + */ + template + inline void erase_all( + SequenceT& Input, + const RangeT& Search ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_all ( case insensitive ) ------------------------------------// + + //! Erase all algorithm ( case insensitive ) + /*! + Remove all the occurrences of the string from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT ierase_all_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase all algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ierase_all_copy( + const SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + + //! Erase all algorithm ( case insensitive ) + /*! + Remove all the occurrences of the string from the input. + The input sequence is modified in-place. Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for. + \param Loc A locale used for case insensitive comparison + */ + template + inline void ierase_all( + SequenceT& Input, + const RangeT& Search, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::empty_formatter(Input) ); + } + +// erase_head --------------------------------------------------------------------// + + //! Erase head algorithm + /*! + Remove the head from the input. The head is a prefix of a sequence of given size. + If the sequence is shorter then required, the whole string is + considered to be the head. The result is a modified copy of the input. + It is returned as a sequence or copied to the output iterator. + + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param N Length of the head. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT> + inline OutputIteratorT erase_head_copy( + OutputIteratorT Output, + const RangeT& Input, + int N ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase head algorithm + /*! + \overload + */ + template + inline SequenceT erase_head_copy( + const SequenceT& Input, + int N ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase head algorithm + /*! + Remove the head from the input. The head is a prefix of a sequence of given size. + If the sequence is shorter then required, the whole string is + considered to be the head. The input sequence is modified in-place. + + \param Input An input string + \param N Length of the head + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + */ + template + inline void erase_head( + SequenceT& Input, + int N ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + +// erase_tail --------------------------------------------------------------------// + + //! Erase tail algorithm + /*! + Remove the tail from the input. The tail is a suffix of a sequence of given size. + If the sequence is shorter then required, the whole string is + considered to be the tail. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param N Length of the tail. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT> + inline OutputIteratorT erase_tail_copy( + OutputIteratorT Output, + const RangeT& Input, + int N ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase tail algorithm + /*! + \overload + */ + template + inline SequenceT erase_tail_copy( + const SequenceT& Input, + int N ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase tail algorithm + /*! + Remove the tail from the input. The tail is a suffix of a sequence of given size. + If the sequence is shorter then required, the whole string is + considered to be the tail. The input sequence is modified in-place. + + \param Input An input string + \param N Length of the tail + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + */ + template + inline void erase_tail( + SequenceT& Input, + int N ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::empty_formatter( Input ) ); + } + + } // namespace algorithm + + // pull names into the boost namespace + using algorithm::erase_range_copy; + using algorithm::erase_range; + using algorithm::erase_first_copy; + using algorithm::erase_first; + using algorithm::ierase_first_copy; + using algorithm::ierase_first; + using algorithm::erase_last_copy; + using algorithm::erase_last; + using algorithm::ierase_last_copy; + using algorithm::ierase_last; + using algorithm::erase_nth_copy; + using algorithm::erase_nth; + using algorithm::ierase_nth_copy; + using algorithm::ierase_nth; + using algorithm::erase_all_copy; + using algorithm::erase_all; + using algorithm::ierase_all_copy; + using algorithm::ierase_all; + using algorithm::erase_head_copy; + using algorithm::erase_head; + using algorithm::erase_tail_copy; + using algorithm::erase_tail; + +} // namespace boost + + +#endif // BOOST_ERASE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/find.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find.hpp new file mode 100644 index 0000000..f2c2926 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find.hpp @@ -0,0 +1,334 @@ +// Boost string_algo library find.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_HPP +#define BOOST_STRING_FIND_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines a set of find algorithms. The algorithms are searching + for a substring of the input. The result is given as an \c iterator_range + delimiting the substring. +*/ + +namespace boost { + namespace algorithm { + +// Generic find -----------------------------------------------// + + //! Generic find algorithm + /*! + Search the input using the given finder. + + \param Input A string which will be searched. + \param Finder Finder object used for searching. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c RangeT::iterator or + \c RangeT::const_iterator, depending on the constness of + the input parameter. + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find( + RangeT& Input, + const FinderT& Finder) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + + return Finder(::boost::begin(lit_input),::boost::end(lit_input)); + } + +// find_first -----------------------------------------------// + + //! Find first algorithm + /*! + Search for the first occurrence of the substring in the input. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c RangeT::iterator or + \c RangeT::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_first( + Range1T& Input, + const Range2T& Search) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::first_finder(Search)); + } + + //! Find first algorithm ( case insensitive ) + /*! + Search for the first occurrence of the substring in the input. + Searching is case insensitive. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \param Loc A locale used for case insensitive comparison + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + ifind_first( + Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::first_finder(Search,is_iequal(Loc))); + } + +// find_last -----------------------------------------------// + + //! Find last algorithm + /*! + Search for the last occurrence of the substring in the input. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_last( + Range1T& Input, + const Range2T& Search) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::last_finder(Search)); + } + + //! Find last algorithm ( case insensitive ) + /*! + Search for the last match a string in the input. + Searching is case insensitive. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \param Loc A locale used for case insensitive comparison + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + ifind_last( + Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::last_finder(Search, is_iequal(Loc))); + } + +// find_nth ----------------------------------------------------------------------// + + //! Find n-th algorithm + /*! + Search for the n-th (zero-indexed) occurrence of the substring in the + input. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \param Nth An index (zero-indexed) of the match to be found. + For negative N, the matches are counted from the end of string. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_nth( + Range1T& Input, + const Range2T& Search, + int Nth) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::nth_finder(Search,Nth)); + } + + //! Find n-th algorithm ( case insensitive ). + /*! + Search for the n-th (zero-indexed) occurrence of the substring in the + input. Searching is case insensitive. + + \param Input A string which will be searched. + \param Search A substring to be searched for. + \param Nth An index (zero-indexed) of the match to be found. + For negative N, the matches are counted from the end of string. + \param Loc A locale used for case insensitive comparison + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + ifind_nth( + Range1T& Input, + const Range2T& Search, + int Nth, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::nth_finder(Search,Nth,is_iequal(Loc))); + } + +// find_head ----------------------------------------------------------------------// + + //! Find head algorithm + /*! + Get the head of the input. Head is a prefix of the string of the + given size. If the input is shorter then required, whole input is considered + to be the head. + + \param Input An input string + \param N Length of the head + For N>=0, at most N characters are extracted. + For N<0, at most size(Input)-|N| characters are extracted. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c Range1T::iterator or + \c Range1T::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_head( + RangeT& Input, + int N) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::head_finder(N)); + } + +// find_tail ----------------------------------------------------------------------// + + //! Find tail algorithm + /*! + Get the tail of the input. Tail is a suffix of the string of the + given size. If the input is shorter then required, whole input is considered + to be the tail. + + \param Input An input string + \param N Length of the tail. + For N>=0, at most N characters are extracted. + For N<0, at most size(Input)-|N| characters are extracted. + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c RangeT::iterator or + \c RangeT::const_iterator, depending on the constness of + the input parameter. + + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_tail( + RangeT& Input, + int N) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::tail_finder(N)); + } + +// find_token --------------------------------------------------------------------// + + //! Find token algorithm + /*! + Look for a given token in the string. Token is a character that matches the + given predicate. + If the "token compress mode" is enabled, adjacent tokens are considered to be one match. + + \param Input A input string. + \param Pred A unary predicate to identify a token + \param eCompress Enable/Disable compressing of adjacent tokens + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c RangeT::iterator or + \c RangeT::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type> + find_token( + RangeT& Input, + PredicateT Pred, + token_compress_mode_type eCompress=token_compress_off) + { + return ::boost::algorithm::find(Input, ::boost::algorithm::token_finder(Pred, eCompress)); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::find; + using algorithm::find_first; + using algorithm::ifind_first; + using algorithm::find_last; + using algorithm::ifind_last; + using algorithm::find_nth; + using algorithm::ifind_nth; + using algorithm::find_head; + using algorithm::find_tail; + using algorithm::find_token; + +} // namespace boost + + +#endif // BOOST_STRING_FIND_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_format.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_format.hpp new file mode 100644 index 0000000..0e84a4e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_format.hpp @@ -0,0 +1,287 @@ +// Boost string_algo library find_format.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_FORMAT_HPP +#define BOOST_STRING_FIND_FORMAT_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines generic replace algorithms. Each algorithm replaces + part(s) of the input. The part to be replaced is looked up using a Finder object. + Result of finding is then used by a Formatter object to generate the replacement. +*/ + +namespace boost { + namespace algorithm { + +// generic replace -----------------------------------------------------------------// + + //! Generic replace algorithm + /*! + Use the Finder to search for a substring. Use the Formatter to format + this substring and replace it in the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input sequence + \param Finder A Finder object used to search for a match to be replaced + \param Formatter A Formatter object used to format a match + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename FinderT, + typename FormatterT> + inline OutputIteratorT find_format_copy( + OutputIteratorT Output, + const RangeT& Input, + FinderT Finder, + FormatterT Formatter ) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + iterator_range::type> lit_input(::boost::as_literal(Input)); + + return detail::find_format_copy_impl( + Output, + lit_input, + Formatter, + Finder( ::boost::begin(lit_input), ::boost::end(lit_input) ) ); + } + + //! Generic replace algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename FinderT, + typename FormatterT> + inline SequenceT find_format_copy( + const SequenceT& Input, + FinderT Finder, + FormatterT Formatter ) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + return detail::find_format_copy_impl( + Input, + Formatter, + Finder(::boost::begin(Input), ::boost::end(Input))); + } + + //! Generic replace algorithm + /*! + Use the Finder to search for a substring. Use the Formatter to format + this substring and replace it in the input. The input is modified in-place. + + \param Input An input sequence + \param Finder A Finder object used to search for a match to be replaced + \param Formatter A Formatter object used to format a match + */ + template< + typename SequenceT, + typename FinderT, + typename FormatterT> + inline void find_format( + SequenceT& Input, + FinderT Finder, + FormatterT Formatter) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + detail::find_format_impl( + Input, + Formatter, + Finder(::boost::begin(Input), ::boost::end(Input))); + } + + +// find_format_all generic ----------------------------------------------------------------// + + //! Generic replace all algorithm + /*! + Use the Finder to search for a substring. Use the Formatter to format + this substring and replace it in the input. Repeat this for all matching + substrings. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input sequence + \param Finder A Finder object used to search for a match to be replaced + \param Formatter A Formatter object used to format a match + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename FinderT, + typename FormatterT> + inline OutputIteratorT find_format_all_copy( + OutputIteratorT Output, + const RangeT& Input, + FinderT Finder, + FormatterT Formatter) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + iterator_range::type> lit_input(::boost::as_literal(Input)); + + return detail::find_format_all_copy_impl( + Output, + lit_input, + Finder, + Formatter, + Finder(::boost::begin(lit_input), ::boost::end(lit_input))); + } + + //! Generic replace all algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename FinderT, + typename FormatterT > + inline SequenceT find_format_all_copy( + const SequenceT& Input, + FinderT Finder, + FormatterT Formatter ) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + return detail::find_format_all_copy_impl( + Input, + Finder, + Formatter, + Finder( ::boost::begin(Input), ::boost::end(Input) ) ); + } + + //! Generic replace all algorithm + /*! + Use the Finder to search for a substring. Use the Formatter to format + this substring and replace it in the input. Repeat this for all matching + substrings.The input is modified in-place. + + \param Input An input sequence + \param Finder A Finder object used to search for a match to be replaced + \param Formatter A Formatter object used to format a match + */ + template< + typename SequenceT, + typename FinderT, + typename FormatterT > + inline void find_format_all( + SequenceT& Input, + FinderT Finder, + FormatterT Formatter ) + { + // Concept check + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_const_iterator::type> + )); + BOOST_CONCEPT_ASSERT(( + FormatterConcept< + FormatterT, + FinderT,BOOST_STRING_TYPENAME range_const_iterator::type> + )); + + detail::find_format_all_impl( + Input, + Finder, + Formatter, + Finder(::boost::begin(Input), ::boost::end(Input))); + + } + + } // namespace algorithm + + // pull the names to the boost namespace + using algorithm::find_format_copy; + using algorithm::find_format; + using algorithm::find_format_all_copy; + using algorithm::find_format_all; + +} // namespace boost + + +#endif // BOOST_STRING_FIND_FORMAT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_iterator.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_iterator.hpp new file mode 100644 index 0000000..5a52d92 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/find_iterator.hpp @@ -0,0 +1,388 @@ +// Boost string_algo library find_iterator.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2004. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FIND_ITERATOR_HPP +#define BOOST_STRING_FIND_ITERATOR_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +/*! \file + Defines find iterator classes. Find iterator repeatedly applies a Finder + to the specified input string to search for matches. Dereferencing + the iterator yields the current match or a range between the last and the current + match depending on the iterator used. +*/ + +namespace boost { + namespace algorithm { + +// find_iterator -----------------------------------------------// + + //! find_iterator + /*! + Find iterator encapsulates a Finder and allows + for incremental searching in a string. + Each increment moves the iterator to the next match. + + Find iterator is a readable forward traversal iterator. + + Dereferencing the iterator yields an iterator_range delimiting + the current match. + */ + template + class find_iterator : + public iterator_facade< + find_iterator, + const iterator_range, + forward_traversal_tag >, + private detail::find_iterator_base + { + private: + // facade support + friend class ::boost::iterator_core_access; + + private: + // typedefs + + typedef detail::find_iterator_base base_type; + typedef BOOST_STRING_TYPENAME + base_type::input_iterator_type input_iterator_type; + typedef BOOST_STRING_TYPENAME + base_type::match_type match_type; + + public: + //! Default constructor + /*! + Construct null iterator. All null iterators are equal. + + \post eof()==true + */ + find_iterator() {} + + //! Copy constructor + /*! + Construct a copy of the find_iterator + */ + find_iterator( const find_iterator& Other ) : + base_type(Other), + m_Match(Other.m_Match), + m_End(Other.m_End) {} + + //! Constructor + /*! + Construct new find_iterator for a given finder + and a range. + */ + template + find_iterator( + IteratorT Begin, + IteratorT End, + FinderT Finder ) : + detail::find_iterator_base(Finder,0), + m_Match(Begin,Begin), + m_End(End) + { + increment(); + } + + //! Constructor + /*! + Construct new find_iterator for a given finder + and a range. + */ + template + find_iterator( + RangeT& Col, + FinderT Finder ) : + detail::find_iterator_base(Finder,0) + { + iterator_range::type> lit_col(::boost::as_literal(Col)); + m_Match=::boost::make_iterator_range(::boost::begin(lit_col), ::boost::begin(lit_col)); + m_End=::boost::end(lit_col); + + increment(); + } + + private: + // iterator operations + + // dereference + const match_type& dereference() const + { + return m_Match; + } + + // increment + void increment() + { + m_Match=this->do_find(m_Match.end(),m_End); + } + + // comparison + bool equal( const find_iterator& Other ) const + { + bool bEof=eof(); + bool bOtherEof=Other.eof(); + + return bEof || bOtherEof ? bEof==bOtherEof : + ( + m_Match==Other.m_Match && + m_End==Other.m_End + ); + } + + public: + // operations + + //! Eof check + /*! + Check the eof condition. Eof condition means that + there is nothing more to be searched i.e. find_iterator + is after the last match. + */ + bool eof() const + { + return + this->is_null() || + ( + m_Match.begin() == m_End && + m_Match.end() == m_End + ); + } + + private: + // Attributes + match_type m_Match; + input_iterator_type m_End; + }; + + //! find iterator construction helper + /*! + * Construct a find iterator to iterate through the specified string + */ + template + inline find_iterator< + BOOST_STRING_TYPENAME range_iterator::type> + make_find_iterator( + RangeT& Collection, + FinderT Finder) + { + return find_iterator::type>( + Collection, Finder); + } + +// split iterator -----------------------------------------------// + + //! split_iterator + /*! + Split iterator encapsulates a Finder and allows + for incremental searching in a string. + Unlike the find iterator, split iterator iterates + through gaps between matches. + + Find iterator is a readable forward traversal iterator. + + Dereferencing the iterator yields an iterator_range delimiting + the current match. + */ + template + class split_iterator : + public iterator_facade< + split_iterator, + const iterator_range, + forward_traversal_tag >, + private detail::find_iterator_base + { + private: + // facade support + friend class ::boost::iterator_core_access; + + private: + // typedefs + + typedef detail::find_iterator_base base_type; + typedef BOOST_STRING_TYPENAME + base_type::input_iterator_type input_iterator_type; + typedef BOOST_STRING_TYPENAME + base_type::match_type match_type; + + public: + //! Default constructor + /*! + Construct null iterator. All null iterators are equal. + + \post eof()==true + */ + split_iterator() : + m_Next(), + m_End(), + m_bEof(true) + {} + + //! Copy constructor + /*! + Construct a copy of the split_iterator + */ + split_iterator( const split_iterator& Other ) : + base_type(Other), + m_Match(Other.m_Match), + m_Next(Other.m_Next), + m_End(Other.m_End), + m_bEof(Other.m_bEof) + {} + + //! Constructor + /*! + Construct new split_iterator for a given finder + and a range. + */ + template + split_iterator( + IteratorT Begin, + IteratorT End, + FinderT Finder ) : + detail::find_iterator_base(Finder,0), + m_Match(Begin,Begin), + m_Next(Begin), + m_End(End), + m_bEof(false) + { + // force the correct behavior for empty sequences and yield at least one token + if(Begin!=End) + { + increment(); + } + } + //! Constructor + /*! + Construct new split_iterator for a given finder + and a collection. + */ + template + split_iterator( + RangeT& Col, + FinderT Finder ) : + detail::find_iterator_base(Finder,0), + m_bEof(false) + { + iterator_range::type> lit_col(::boost::as_literal(Col)); + m_Match=make_iterator_range(::boost::begin(lit_col), ::boost::begin(lit_col)); + m_Next=::boost::begin(lit_col); + m_End=::boost::end(lit_col); + + // force the correct behavior for empty sequences and yield at least one token + if(m_Next!=m_End) + { + increment(); + } + } + + + private: + // iterator operations + + // dereference + const match_type& dereference() const + { + return m_Match; + } + + // increment + void increment() + { + match_type FindMatch=this->do_find( m_Next, m_End ); + + if(FindMatch.begin()==m_End && FindMatch.end()==m_End) + { + if(m_Match.end()==m_End) + { + // Mark iterator as eof + m_bEof=true; + } + } + + m_Match=match_type( m_Next, FindMatch.begin() ); + m_Next=FindMatch.end(); + } + + // comparison + bool equal( const split_iterator& Other ) const + { + bool bEof=eof(); + bool bOtherEof=Other.eof(); + + return bEof || bOtherEof ? bEof==bOtherEof : + ( + m_Match==Other.m_Match && + m_Next==Other.m_Next && + m_End==Other.m_End + ); + } + + public: + // operations + + //! Eof check + /*! + Check the eof condition. Eof condition means that + there is nothing more to be searched i.e. find_iterator + is after the last match. + */ + bool eof() const + { + return this->is_null() || m_bEof; + } + + private: + // Attributes + match_type m_Match; + input_iterator_type m_Next; + input_iterator_type m_End; + bool m_bEof; + }; + + //! split iterator construction helper + /*! + * Construct a split iterator to iterate through the specified collection + */ + template + inline split_iterator< + BOOST_STRING_TYPENAME range_iterator::type> + make_split_iterator( + RangeT& Collection, + FinderT Finder) + { + return split_iterator::type>( + Collection, Finder); + } + + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::find_iterator; + using algorithm::make_find_iterator; + using algorithm::split_iterator; + using algorithm::make_split_iterator; + +} // namespace boost + + +#endif // BOOST_STRING_FIND_ITERATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/finder.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/finder.hpp new file mode 100644 index 0000000..93f7ec3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/finder.hpp @@ -0,0 +1,270 @@ +// Boost string_algo library finder.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FINDER_HPP +#define BOOST_STRING_FINDER_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines Finder generators. Finder object is a functor which is able to + find a substring matching a specific criteria in the input. + Finders are used as a pluggable components for replace, find + and split facilities. This header contains generator functions + for finders provided in this library. +*/ + +namespace boost { + namespace algorithm { + +// Finder generators ------------------------------------------// + + //! "First" finder + /*! + Construct the \c first_finder. The finder searches for the first + occurrence of the string in a given input. + The result is given as an \c iterator_range delimiting the match. + + \param Search A substring to be searched for. + \param Comp An element comparison predicate + \return An instance of the \c first_finder object + */ + template + inline detail::first_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + is_equal> + first_finder( const RangeT& Search ) + { + return + detail::first_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + is_equal>( ::boost::as_literal(Search), is_equal() ) ; + } + + //! "First" finder + /*! + \overload + */ + template + inline detail::first_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + PredicateT> + first_finder( + const RangeT& Search, PredicateT Comp ) + { + return + detail::first_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + PredicateT>( ::boost::as_literal(Search), Comp ); + } + + //! "Last" finder + /*! + Construct the \c last_finder. The finder searches for the last + occurrence of the string in a given input. + The result is given as an \c iterator_range delimiting the match. + + \param Search A substring to be searched for. + \param Comp An element comparison predicate + \return An instance of the \c last_finder object + */ + template + inline detail::last_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + is_equal> + last_finder( const RangeT& Search ) + { + return + detail::last_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + is_equal>( ::boost::as_literal(Search), is_equal() ); + } + //! "Last" finder + /*! + \overload + */ + template + inline detail::last_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + PredicateT> + last_finder( const RangeT& Search, PredicateT Comp ) + { + return + detail::last_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + PredicateT>( ::boost::as_literal(Search), Comp ) ; + } + + //! "Nth" finder + /*! + Construct the \c nth_finder. The finder searches for the n-th (zero-indexed) + occurrence of the string in a given input. + The result is given as an \c iterator_range delimiting the match. + + \param Search A substring to be searched for. + \param Nth An index of the match to be find + \param Comp An element comparison predicate + \return An instance of the \c nth_finder object + */ + template + inline detail::nth_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + is_equal> + nth_finder( + const RangeT& Search, + int Nth) + { + return + detail::nth_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + is_equal>( ::boost::as_literal(Search), Nth, is_equal() ) ; + } + //! "Nth" finder + /*! + \overload + */ + template + inline detail::nth_finderF< + BOOST_STRING_TYPENAME range_const_iterator::type, + PredicateT> + nth_finder( + const RangeT& Search, + int Nth, + PredicateT Comp ) + { + return + detail::nth_finderF< + BOOST_STRING_TYPENAME + range_const_iterator::type, + PredicateT>( ::boost::as_literal(Search), Nth, Comp ); + } + + //! "Head" finder + /*! + Construct the \c head_finder. The finder returns a head of a given + input. The head is a prefix of a string up to n elements in + size. If an input has less then n elements, whole input is + considered a head. + The result is given as an \c iterator_range delimiting the match. + + \param N The size of the head + \return An instance of the \c head_finder object + */ + inline detail::head_finderF + head_finder( int N ) + { + return detail::head_finderF(N); + } + + //! "Tail" finder + /*! + Construct the \c tail_finder. The finder returns a tail of a given + input. The tail is a suffix of a string up to n elements in + size. If an input has less then n elements, whole input is + considered a head. + The result is given as an \c iterator_range delimiting the match. + + \param N The size of the head + \return An instance of the \c tail_finder object + */ + inline detail::tail_finderF + tail_finder( int N ) + { + return detail::tail_finderF(N); + } + + //! "Token" finder + /*! + Construct the \c token_finder. The finder searches for a token + specified by a predicate. It is similar to std::find_if + algorithm, with an exception that it return a range of + instead of a single iterator. + + If "compress token mode" is enabled, adjacent matching tokens are + concatenated into one match. Thus the finder can be used to + search for continuous segments of characters satisfying the + given predicate. + + The result is given as an \c iterator_range delimiting the match. + + \param Pred An element selection predicate + \param eCompress Compress flag + \return An instance of the \c token_finder object + */ + template< typename PredicateT > + inline detail::token_finderF + token_finder( + PredicateT Pred, + token_compress_mode_type eCompress=token_compress_off ) + { + return detail::token_finderF( Pred, eCompress ); + } + + //! "Range" finder + /*! + Construct the \c range_finder. The finder does not perform + any operation. It simply returns the given range for + any input. + + \param Begin Beginning of the range + \param End End of the range + \param Range The range. + \return An instance of the \c range_finger object + */ + template< typename ForwardIteratorT > + inline detail::range_finderF + range_finder( + ForwardIteratorT Begin, + ForwardIteratorT End ) + { + return detail::range_finderF( Begin, End ); + } + + //! "Range" finder + /*! + \overload + */ + template< typename ForwardIteratorT > + inline detail::range_finderF + range_finder( iterator_range Range ) + { + return detail::range_finderF( Range ); + } + + } // namespace algorithm + + // pull the names to the boost namespace + using algorithm::first_finder; + using algorithm::last_finder; + using algorithm::nth_finder; + using algorithm::head_finder; + using algorithm::tail_finder; + using algorithm::token_finder; + using algorithm::range_finder; + +} // namespace boost + + +#endif // BOOST_STRING_FINDER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/formatter.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/formatter.hpp new file mode 100644 index 0000000..de8681b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/formatter.hpp @@ -0,0 +1,120 @@ +// Boost string_algo library formatter.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_FORMATTER_HPP +#define BOOST_STRING_FORMATTER_HPP + +#include +#include +#include +#include + +#include + +/*! \file + Defines Formatter generators. Formatter is a functor which formats + a string according to given parameters. A Formatter works + in conjunction with a Finder. A Finder can provide additional information + for a specific Formatter. An example of such a cooperation is regex_finder + and regex_formatter. + + Formatters are used as pluggable components for replace facilities. + This header contains generator functions for the Formatters provided in this library. +*/ + +namespace boost { + namespace algorithm { + +// generic formatters ---------------------------------------------------------------// + + //! Constant formatter + /*! + Constructs a \c const_formatter. Const formatter always returns + the same value, regardless of the parameter. + + \param Format A predefined value used as a result for formatting + \return An instance of the \c const_formatter object. + */ + template + inline detail::const_formatF< + iterator_range< + BOOST_STRING_TYPENAME range_const_iterator::type> > + const_formatter(const RangeT& Format) + { + return detail::const_formatF< + iterator_range< + BOOST_STRING_TYPENAME range_const_iterator::type> >(::boost::as_literal(Format)); + } + + //! Identity formatter + /*! + Constructs an \c identity_formatter. Identity formatter always returns + the parameter. + + \return An instance of the \c identity_formatter object. + */ + template + inline detail::identity_formatF< + iterator_range< + BOOST_STRING_TYPENAME range_const_iterator::type> > + identity_formatter() + { + return detail::identity_formatF< + iterator_range< + BOOST_STRING_TYPENAME range_const_iterator::type> >(); + } + + //! Empty formatter + /*! + Constructs an \c empty_formatter. Empty formatter always returns an empty + sequence. + + \param Input container used to select a correct value_type for the + resulting empty_container<>. + \return An instance of the \c empty_formatter object. + */ + template + inline detail::empty_formatF< + BOOST_STRING_TYPENAME range_value::type> + empty_formatter(const RangeT&) + { + return detail::empty_formatF< + BOOST_STRING_TYPENAME range_value::type>(); + } + + //! Empty formatter + /*! + Constructs a \c dissect_formatter. Dissect formatter uses a specified finder + to extract a portion of the formatted sequence. The first finder's match is returned + as a result + + \param Finder a finder used to select a portion of the formatted sequence + \return An instance of the \c dissect_formatter object. + */ + template + inline detail::dissect_formatF< FinderT > + dissect_formatter(const FinderT& Finder) + { + return detail::dissect_formatF(Finder); + } + + + } // namespace algorithm + + // pull the names to the boost namespace + using algorithm::const_formatter; + using algorithm::identity_formatter; + using algorithm::empty_formatter; + using algorithm::dissect_formatter; + +} // namespace boost + + +#endif // BOOST_FORMATTER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/iter_find.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/iter_find.hpp new file mode 100644 index 0000000..10424ab --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/iter_find.hpp @@ -0,0 +1,193 @@ +// Boost string_algo library iter_find.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_ITER_FIND_HPP +#define BOOST_STRING_ITER_FIND_HPP + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines generic split algorithms. Split algorithms can be + used to divide a sequence into several part according + to a given criteria. Result is given as a 'container + of containers' where elements are copies or references + to extracted parts. + + There are two algorithms provided. One iterates over matching + substrings, the other one over the gaps between these matches. +*/ + +namespace boost { + namespace algorithm { + +// iterate find ---------------------------------------------------// + + //! Iter find algorithm + /*! + This algorithm executes a given finder in iteration on the input, + until the end of input is reached, or no match is found. + Iteration is done using built-in find_iterator, so the real + searching is performed only when needed. + In each iteration new match is found and added to the result. + + \param Result A 'container container' to contain the result of search. + Both outer and inner container must have constructor taking a pair + of iterators as an argument. + Typical type of the result is + \c std::vector> + (each element of such a vector will container a range delimiting + a match). + \param Input A container which will be searched. + \param Finder A Finder object used for searching + \return A reference to the result + + \note Prior content of the result will be overwritten. + */ + template< + typename SequenceSequenceT, + typename RangeT, + typename FinderT > + inline SequenceSequenceT& + iter_find( + SequenceSequenceT& Result, + RangeT& Input, + FinderT Finder ) + { + BOOST_CONCEPT_ASSERT(( + FinderConcept< + FinderT, + BOOST_STRING_TYPENAME range_iterator::type> + )); + + iterator_range::type> lit_input(::boost::as_literal(Input)); + + typedef BOOST_STRING_TYPENAME + range_iterator::type input_iterator_type; + typedef find_iterator find_iterator_type; + typedef detail::copy_iterator_rangeF< + BOOST_STRING_TYPENAME + range_value::type, + input_iterator_type> copy_range_type; + + input_iterator_type InputEnd=::boost::end(lit_input); + + typedef transform_iterator + transform_iter_type; + + transform_iter_type itBegin= + ::boost::make_transform_iterator( + find_iterator_type( ::boost::begin(lit_input), InputEnd, Finder ), + copy_range_type()); + + transform_iter_type itEnd= + ::boost::make_transform_iterator( + find_iterator_type(), + copy_range_type()); + + SequenceSequenceT Tmp(itBegin, itEnd); + + Result.swap(Tmp); + return Result; + } + +// iterate split ---------------------------------------------------// + + //! Split find algorithm + /*! + This algorithm executes a given finder in iteration on the input, + until the end of input is reached, or no match is found. + Iteration is done using built-in find_iterator, so the real + searching is performed only when needed. + Each match is used as a separator of segments. These segments are then + returned in the result. + + \param Result A 'container container' to contain the result of search. + Both outer and inner container must have constructor taking a pair + of iterators as an argument. + Typical type of the result is + \c std::vector> + (each element of such a vector will container a range delimiting + a match). + \param Input A container which will be searched. + \param Finder A finder object used for searching + \return A reference to the result + + \note Prior content of the result will be overwritten. + */ + template< + typename SequenceSequenceT, + typename RangeT, + typename FinderT > + inline SequenceSequenceT& + iter_split( + SequenceSequenceT& Result, + RangeT& Input, + FinderT Finder ) + { + BOOST_CONCEPT_ASSERT(( + FinderConcept::type> + )); + + iterator_range::type> lit_input(::boost::as_literal(Input)); + + typedef BOOST_STRING_TYPENAME + range_iterator::type input_iterator_type; + typedef split_iterator find_iterator_type; + typedef detail::copy_iterator_rangeF< + BOOST_STRING_TYPENAME + range_value::type, + input_iterator_type> copy_range_type; + + input_iterator_type InputEnd=::boost::end(lit_input); + + typedef transform_iterator + transform_iter_type; + + transform_iter_type itBegin= + ::boost::make_transform_iterator( + find_iterator_type( ::boost::begin(lit_input), InputEnd, Finder ), + copy_range_type() ); + + transform_iter_type itEnd= + ::boost::make_transform_iterator( + find_iterator_type(), + copy_range_type() ); + + SequenceSequenceT Tmp(itBegin, itEnd); + + Result.swap(Tmp); + return Result; + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::iter_find; + using algorithm::iter_split; + +} // namespace boost + + +#endif // BOOST_STRING_ITER_FIND_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/join.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/join.hpp new file mode 100644 index 0000000..b871eb4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/join.hpp @@ -0,0 +1,145 @@ +// Boost string_algo library join.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_JOIN_HPP +#define BOOST_STRING_JOIN_HPP + +#include +#include +#include +#include + +/*! \file + Defines join algorithm. + + Join algorithm is a counterpart to split algorithms. + It joins strings from a 'list' by adding user defined separator. + Additionally there is a version that allows simple filtering + by providing a predicate. +*/ + +namespace boost { + namespace algorithm { + +// join --------------------------------------------------------------// + + //! Join algorithm + /*! + This algorithm joins all strings in a 'list' into one long string. + Segments are concatenated by given separator. + + \param Input A container that holds the input strings. It must be a container-of-containers. + \param Separator A string that will separate the joined segments. + \return Concatenated string. + + \note This function provides the strong exception-safety guarantee + */ + template< typename SequenceSequenceT, typename Range1T> + inline typename range_value::type + join( + const SequenceSequenceT& Input, + const Range1T& Separator) + { + // Define working types + typedef typename range_value::type ResultT; + typedef typename range_const_iterator::type InputIteratorT; + + // Parse input + InputIteratorT itBegin=::boost::begin(Input); + InputIteratorT itEnd=::boost::end(Input); + + // Construct container to hold the result + ResultT Result; + + // Append first element + if(itBegin!=itEnd) + { + detail::insert(Result, ::boost::end(Result), *itBegin); + ++itBegin; + } + + for(;itBegin!=itEnd; ++itBegin) + { + // Add separator + detail::insert(Result, ::boost::end(Result), ::boost::as_literal(Separator)); + // Add element + detail::insert(Result, ::boost::end(Result), *itBegin); + } + + return Result; + } + +// join_if ----------------------------------------------------------// + + //! Conditional join algorithm + /*! + This algorithm joins all strings in a 'list' into one long string. + Segments are concatenated by given separator. Only segments that + satisfy the predicate will be added to the result. + + \param Input A container that holds the input strings. It must be a container-of-containers. + \param Separator A string that will separate the joined segments. + \param Pred A segment selection predicate + \return Concatenated string. + + \note This function provides the strong exception-safety guarantee + */ + template< typename SequenceSequenceT, typename Range1T, typename PredicateT> + inline typename range_value::type + join_if( + const SequenceSequenceT& Input, + const Range1T& Separator, + PredicateT Pred) + { + // Define working types + typedef typename range_value::type ResultT; + typedef typename range_const_iterator::type InputIteratorT; + + // Parse input + InputIteratorT itBegin=::boost::begin(Input); + InputIteratorT itEnd=::boost::end(Input); + + // Construct container to hold the result + ResultT Result; + + // Roll to the first element that will be added + while(itBegin!=itEnd && !Pred(*itBegin)) ++itBegin; + // Add this element + if(itBegin!=itEnd) + { + detail::insert(Result, ::boost::end(Result), *itBegin); + ++itBegin; + } + + for(;itBegin!=itEnd; ++itBegin) + { + if(Pred(*itBegin)) + { + // Add separator + detail::insert(Result, ::boost::end(Result), ::boost::as_literal(Separator)); + // Add element + detail::insert(Result, ::boost::end(Result), *itBegin); + } + } + + return Result; + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::join; + using algorithm::join_if; + +} // namespace boost + + +#endif // BOOST_STRING_JOIN_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate.hpp new file mode 100644 index 0000000..0879829 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate.hpp @@ -0,0 +1,475 @@ +// Boost string_algo library predicate.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_PREDICATE_HPP +#define BOOST_STRING_PREDICATE_HPP + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file boost/algorithm/string/predicate.hpp + Defines string-related predicates. + The predicates determine whether a substring is contained in the input string + under various conditions: a string starts with the substring, ends with the + substring, simply contains the substring or if both strings are equal. + Additionaly the algorithm \c all() checks all elements of a container to satisfy a + condition. + + All predicates provide the strong exception guarantee. +*/ + +namespace boost { + namespace algorithm { + +// starts_with predicate -----------------------------------------------// + + //! 'Starts with' predicate + /*! + This predicate holds when the test string is a prefix of the Input. + In other words, if the input starts with the test. + When the optional predicate is specified, it is used for character-wise + comparison. + + \param Input An input sequence + \param Test A test sequence + \param Comp An element comparison predicate + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool starts_with( + const Range1T& Input, + const Range2T& Test, + PredicateT Comp) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + iterator_range::type> lit_test(::boost::as_literal(Test)); + + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator1T; + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator2T; + + Iterator1T InputEnd=::boost::end(lit_input); + Iterator2T TestEnd=::boost::end(lit_test); + + Iterator1T it=::boost::begin(lit_input); + Iterator2T pit=::boost::begin(lit_test); + for(; + it!=InputEnd && pit!=TestEnd; + ++it,++pit) + { + if( !(Comp(*it,*pit)) ) + return false; + } + + return pit==TestEnd; + } + + //! 'Starts with' predicate + /*! + \overload + */ + template + inline bool starts_with( + const Range1T& Input, + const Range2T& Test) + { + return ::boost::algorithm::starts_with(Input, Test, is_equal()); + } + + //! 'Starts with' predicate ( case insensitive ) + /*! + This predicate holds when the test string is a prefix of the Input. + In other words, if the input starts with the test. + Elements are compared case insensitively. + + \param Input An input sequence + \param Test A test sequence + \param Loc A locale used for case insensitive comparison + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool istarts_with( + const Range1T& Input, + const Range2T& Test, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::starts_with(Input, Test, is_iequal(Loc)); + } + + +// ends_with predicate -----------------------------------------------// + + //! 'Ends with' predicate + /*! + This predicate holds when the test string is a suffix of the Input. + In other words, if the input ends with the test. + When the optional predicate is specified, it is used for character-wise + comparison. + + + \param Input An input sequence + \param Test A test sequence + \param Comp An element comparison predicate + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool ends_with( + const Range1T& Input, + const Range2T& Test, + PredicateT Comp) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + iterator_range::type> lit_test(::boost::as_literal(Test)); + + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator1T; + typedef BOOST_STRING_TYPENAME boost::detail:: + iterator_traits::iterator_category category; + + return detail:: + ends_with_iter_select( + ::boost::begin(lit_input), + ::boost::end(lit_input), + ::boost::begin(lit_test), + ::boost::end(lit_test), + Comp, + category()); + } + + + //! 'Ends with' predicate + /*! + \overload + */ + template + inline bool ends_with( + const Range1T& Input, + const Range2T& Test) + { + return ::boost::algorithm::ends_with(Input, Test, is_equal()); + } + + //! 'Ends with' predicate ( case insensitive ) + /*! + This predicate holds when the test container is a suffix of the Input. + In other words, if the input ends with the test. + Elements are compared case insensitively. + + \param Input An input sequence + \param Test A test sequence + \param Loc A locale used for case insensitive comparison + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool iends_with( + const Range1T& Input, + const Range2T& Test, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::ends_with(Input, Test, is_iequal(Loc)); + } + +// contains predicate -----------------------------------------------// + + //! 'Contains' predicate + /*! + This predicate holds when the test container is contained in the Input. + When the optional predicate is specified, it is used for character-wise + comparison. + + \param Input An input sequence + \param Test A test sequence + \param Comp An element comparison predicate + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool contains( + const Range1T& Input, + const Range2T& Test, + PredicateT Comp) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + iterator_range::type> lit_test(::boost::as_literal(Test)); + + if (::boost::empty(lit_test)) + { + // Empty range is contained always + return true; + } + + // Use the temporary variable to make VACPP happy + bool bResult=(::boost::algorithm::first_finder(lit_test,Comp)(::boost::begin(lit_input), ::boost::end(lit_input))); + return bResult; + } + + //! 'Contains' predicate + /*! + \overload + */ + template + inline bool contains( + const Range1T& Input, + const Range2T& Test) + { + return ::boost::algorithm::contains(Input, Test, is_equal()); + } + + //! 'Contains' predicate ( case insensitive ) + /*! + This predicate holds when the test container is contained in the Input. + Elements are compared case insensitively. + + \param Input An input sequence + \param Test A test sequence + \param Loc A locale used for case insensitive comparison + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool icontains( + const Range1T& Input, + const Range2T& Test, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::contains(Input, Test, is_iequal(Loc)); + } + +// equals predicate -----------------------------------------------// + + //! 'Equals' predicate + /*! + This predicate holds when the test container is equal to the + input container i.e. all elements in both containers are same. + When the optional predicate is specified, it is used for character-wise + comparison. + + \param Input An input sequence + \param Test A test sequence + \param Comp An element comparison predicate + \return The result of the test + + \note This is a two-way version of \c std::equal algorithm + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool equals( + const Range1T& Input, + const Range2T& Test, + PredicateT Comp) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + iterator_range::type> lit_test(::boost::as_literal(Test)); + + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator1T; + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator2T; + + Iterator1T InputEnd=::boost::end(lit_input); + Iterator2T TestEnd=::boost::end(lit_test); + + Iterator1T it=::boost::begin(lit_input); + Iterator2T pit=::boost::begin(lit_test); + for(; + it!=InputEnd && pit!=TestEnd; + ++it,++pit) + { + if( !(Comp(*it,*pit)) ) + return false; + } + + return (pit==TestEnd) && (it==InputEnd); + } + + //! 'Equals' predicate + /*! + \overload + */ + template + inline bool equals( + const Range1T& Input, + const Range2T& Test) + { + return ::boost::algorithm::equals(Input, Test, is_equal()); + } + + //! 'Equals' predicate ( case insensitive ) + /*! + This predicate holds when the test container is equal to the + input container i.e. all elements in both containers are same. + Elements are compared case insensitively. + + \param Input An input sequence + \param Test A test sequence + \param Loc A locale used for case insensitive comparison + \return The result of the test + + \note This is a two-way version of \c std::equal algorithm + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool iequals( + const Range1T& Input, + const Range2T& Test, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::equals(Input, Test, is_iequal(Loc)); + } + +// lexicographical_compare predicate -----------------------------// + + //! Lexicographical compare predicate + /*! + This predicate is an overload of std::lexicographical_compare + for range arguments + + It check whether the first argument is lexicographically less + then the second one. + + If the optional predicate is specified, it is used for character-wise + comparison + + \param Arg1 First argument + \param Arg2 Second argument + \param Pred Comparison predicate + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool lexicographical_compare( + const Range1T& Arg1, + const Range2T& Arg2, + PredicateT Pred) + { + iterator_range::type> lit_arg1(::boost::as_literal(Arg1)); + iterator_range::type> lit_arg2(::boost::as_literal(Arg2)); + + return std::lexicographical_compare( + ::boost::begin(lit_arg1), + ::boost::end(lit_arg1), + ::boost::begin(lit_arg2), + ::boost::end(lit_arg2), + Pred); + } + + //! Lexicographical compare predicate + /*! + \overload + */ + template + inline bool lexicographical_compare( + const Range1T& Arg1, + const Range2T& Arg2) + { + return ::boost::algorithm::lexicographical_compare(Arg1, Arg2, is_less()); + } + + //! Lexicographical compare predicate (case-insensitive) + /*! + This predicate is an overload of std::lexicographical_compare + for range arguments. + It check whether the first argument is lexicographically less + then the second one. + Elements are compared case insensitively + + + \param Arg1 First argument + \param Arg2 Second argument + \param Loc A locale used for case insensitive comparison + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool ilexicographical_compare( + const Range1T& Arg1, + const Range2T& Arg2, + const std::locale& Loc=std::locale()) + { + return ::boost::algorithm::lexicographical_compare(Arg1, Arg2, is_iless(Loc)); + } + + +// all predicate -----------------------------------------------// + + //! 'All' predicate + /*! + This predicate holds it all its elements satisfy a given + condition, represented by the predicate. + + \param Input An input sequence + \param Pred A predicate + \return The result of the test + + \note This function provides the strong exception-safety guarantee + */ + template + inline bool all( + const RangeT& Input, + PredicateT Pred) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + + typedef BOOST_STRING_TYPENAME + range_const_iterator::type Iterator1T; + + Iterator1T InputEnd=::boost::end(lit_input); + for( Iterator1T It=::boost::begin(lit_input); It!=InputEnd; ++It) + { + if (!Pred(*It)) + return false; + } + + return true; + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::starts_with; + using algorithm::istarts_with; + using algorithm::ends_with; + using algorithm::iends_with; + using algorithm::contains; + using algorithm::icontains; + using algorithm::equals; + using algorithm::iequals; + using algorithm::all; + using algorithm::lexicographical_compare; + using algorithm::ilexicographical_compare; + +} // namespace boost + + +#endif // BOOST_STRING_PREDICATE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate_facade.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate_facade.hpp new file mode 100644 index 0000000..a9753fc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/predicate_facade.hpp @@ -0,0 +1,42 @@ +// Boost string_algo library predicate_facade.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_PREDICATE_FACADE_HPP +#define BOOST_STRING_PREDICATE_FACADE_HPP + +#include + +/* + \file boost/algorith/string/predicate_facade.hpp + This file contains predicate_facade definition. This template class is used + to identify classification predicates, so they can be combined using + composition operators. +*/ + +namespace boost { + namespace algorithm { + +// predicate facade ------------------------------------------------------// + + //! Predicate facade + /*! + This class allows to recognize classification + predicates, so that they can be combined using + composition operators. + Every classification predicate must be derived from this class. + */ + template + struct predicate_facade {}; + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_CLASSIFICATION_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex.hpp new file mode 100644 index 0000000..a6c7c60 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex.hpp @@ -0,0 +1,646 @@ +// Boost string_algo library regex.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_REGEX_HPP +#define BOOST_STRING_REGEX_HPP + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*! \file + Defines regex variants of the algorithms. +*/ + +namespace boost { + namespace algorithm { + +// find_regex -----------------------------------------------// + + //! Find regex algorithm + /*! + Search for a substring matching the given regex in the input. + + \param Input A container which will be searched. + \param Rx A regular expression + \param Flags Regex options + \return + An \c iterator_range delimiting the match. + Returned iterator is either \c RangeT::iterator or + \c RangeT::const_iterator, depending on the constness of + the input parameter. + + \note This function provides the strong exception-safety guarantee + */ + template< + typename RangeT, + typename CharT, + typename RegexTraitsT> + inline iterator_range< + BOOST_STRING_TYPENAME range_iterator::type > + find_regex( + RangeT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + iterator_range::type> lit_input(::boost::as_literal(Input)); + + return ::boost::algorithm::regex_finder(Rx,Flags)( + ::boost::begin(lit_input), ::boost::end(lit_input) ); + } + +// replace_regex --------------------------------------------------------------------// + + //! Replace regex algorithm + /*! + Search for a substring matching given regex and format it with + the specified format. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Rx A regular expression + \param Format Regex format definition + \param Flags Regex options + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline OutputIteratorT replace_regex_copy( + OutputIteratorT Output, + const RangeT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + + //! Replace regex algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline SequenceT replace_regex_copy( + const SequenceT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + + //! Replace regex algorithm + /*! + Search for a substring matching given regex and format it with + the specified format. The input string is modified in-place. + + \param Input An input string + \param Rx A regular expression + \param Format Regex format definition + \param Flags Regex options + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline void replace_regex( + SequenceT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + +// replace_all_regex --------------------------------------------------------------------// + + //! Replace all regex algorithm + /*! + Format all substrings, matching given regex, with the specified format. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Rx A regular expression + \param Format Regex format definition + \param Flags Regex options + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline OutputIteratorT replace_all_regex_copy( + OutputIteratorT Output, + const RangeT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + + //! Replace all regex algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline SequenceT replace_all_regex_copy( + const SequenceT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + + //! Replace all regex algorithm + /*! + Format all substrings, matching given regex, with the specified format. + The input string is modified in-place. + + \param Input An input string + \param Rx A regular expression + \param Format Regex format definition + \param Flags Regex options + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT, + typename FormatStringTraitsT, typename FormatStringAllocatorT > + inline void replace_all_regex( + SequenceT& Input, + const basic_regex& Rx, + const std::basic_string& Format, + match_flag_type Flags=match_default | format_default ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::regex_formatter( Format, Flags ) ); + } + +// erase_regex --------------------------------------------------------------------// + + //! Erase regex algorithm + /*! + Remove a substring matching given regex from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Rx A regular expression + \param Flags Regex options + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename CharT, + typename RegexTraitsT > + inline OutputIteratorT erase_regex_copy( + OutputIteratorT Output, + const RangeT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase regex algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT > + inline SequenceT erase_regex_copy( + const SequenceT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase regex algorithm + /*! + Remove a substring matching given regex from the input. + The input string is modified in-place. + + \param Input An input string + \param Rx A regular expression + \param Flags Regex options + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT > + inline void erase_regex( + SequenceT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + +// erase_all_regex --------------------------------------------------------------------// + + //! Erase all regex algorithm + /*! + Erase all substrings, matching given regex, from the input. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Rx A regular expression + \param Flags Regex options + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename RangeT, + typename CharT, + typename RegexTraitsT > + inline OutputIteratorT erase_all_regex_copy( + OutputIteratorT Output, + const RangeT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase all regex algorithm + /*! + \overload + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT > + inline SequenceT erase_all_regex_copy( + const SequenceT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + + //! Erase all regex algorithm + /*! + Erase all substrings, matching given regex, from the input. + The input string is modified in-place. + + \param Input An input string + \param Rx A regular expression + \param Flags Regex options + */ + template< + typename SequenceT, + typename CharT, + typename RegexTraitsT> + inline void erase_all_regex( + SequenceT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::regex_finder( Rx, Flags ), + ::boost::algorithm::empty_formatter( Input ) ); + } + +// find_all_regex ------------------------------------------------------------------// + + //! Find all regex algorithm + /*! + This algorithm finds all substrings matching the give regex + in the input. + + Each part is copied and added as a new element to the output container. + Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> + + \param Result A container that can hold copies of references to the substrings. + \param Input A container which will be searched. + \param Rx A regular expression + \param Flags Regex options + \return A reference to the result + + \note Prior content of the result will be overwritten. + + \note This function provides the strong exception-safety guarantee + */ + template< + typename SequenceSequenceT, + typename RangeT, + typename CharT, + typename RegexTraitsT > + inline SequenceSequenceT& find_all_regex( + SequenceSequenceT& Result, + const RangeT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::iter_find( + Result, + Input, + ::boost::algorithm::regex_finder(Rx,Flags) ); + } + +// split_regex ------------------------------------------------------------------// + + //! Split regex algorithm + /*! + Tokenize expression. This function is equivalent to C strtok. Input + sequence is split into tokens, separated by separators. Separator + is an every match of the given regex. + Each part is copied and added as a new element to the output container. + Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> + + \param Result A container that can hold copies of references to the substrings. + \param Input A container which will be searched. + \param Rx A regular expression + \param Flags Regex options + \return A reference to the result + + \note Prior content of the result will be overwritten. + + \note This function provides the strong exception-safety guarantee + */ + template< + typename SequenceSequenceT, + typename RangeT, + typename CharT, + typename RegexTraitsT > + inline SequenceSequenceT& split_regex( + SequenceSequenceT& Result, + const RangeT& Input, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + return ::boost::algorithm::iter_split( + Result, + Input, + ::boost::algorithm::regex_finder(Rx,Flags) ); + } + +// join_if ------------------------------------------------------------------// + +#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + + //! Conditional join algorithm + /*! + This algorithm joins all strings in a 'list' into one long string. + Segments are concatenated by given separator. Only segments that + match the given regular expression will be added to the result + + This is a specialization of join_if algorithm. + + \param Input A container that holds the input strings. It must be a container-of-containers. + \param Separator A string that will separate the joined segments. + \param Rx A regular expression + \param Flags Regex options + \return Concatenated string. + + \note This function provides the strong exception-safety guarantee + */ + template< + typename SequenceSequenceT, + typename Range1T, + typename CharT, + typename RegexTraitsT > + inline typename range_value::type + join_if( + const SequenceSequenceT& Input, + const Range1T& Separator, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + // Define working types + typedef typename range_value::type ResultT; + typedef typename range_const_iterator::type InputIteratorT; + + // Parse input + InputIteratorT itBegin=::boost::begin(Input); + InputIteratorT itEnd=::boost::end(Input); + + // Construct container to hold the result + ResultT Result; + + + // Roll to the first element that will be added + while( + itBegin!=itEnd && + !::boost::regex_match(::boost::begin(*itBegin), ::boost::end(*itBegin), Rx, Flags)) ++itBegin; + + // Add this element + if(itBegin!=itEnd) + { + detail::insert(Result, ::boost::end(Result), *itBegin); + ++itBegin; + } + + for(;itBegin!=itEnd; ++itBegin) + { + if(::boost::regex_match(::boost::begin(*itBegin), ::boost::end(*itBegin), Rx, Flags)) + { + // Add separator + detail::insert(Result, ::boost::end(Result), ::boost::as_literal(Separator)); + // Add element + detail::insert(Result, ::boost::end(Result), *itBegin); + } + } + + return Result; + } + +#else // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + + //! Conditional join algorithm + /*! + This algorithm joins all strings in a 'list' into one long string. + Segments are concatenated by given separator. Only segments that + match the given regular expression will be added to the result + + This is a specialization of join_if algorithm. + + \param Input A container that holds the input strings. It must be a container-of-containers. + \param Separator A string that will separate the joined segments. + \param Rx A regular expression + \param Flags Regex options + \return Concatenated string. + + \note This function provides the strong exception-safety guarantee + */ + template< + typename SequenceSequenceT, + typename Range1T, + typename CharT, + typename RegexTraitsT > + inline typename range_value::type + join_if_regex( + const SequenceSequenceT& Input, + const Range1T& Separator, + const basic_regex& Rx, + match_flag_type Flags=match_default ) + { + // Define working types + typedef typename range_value::type ResultT; + typedef typename range_const_iterator::type InputIteratorT; + + // Parse input + InputIteratorT itBegin=::boost::begin(Input); + InputIteratorT itEnd=::boost::end(Input); + + // Construct container to hold the result + ResultT Result; + + + // Roll to the first element that will be added + while( + itBegin!=itEnd && + !::boost::regex_match(::boost::begin(*itBegin), ::boost::end(*itBegin), Rx, Flags)) ++itBegin; + + // Add this element + if(itBegin!=itEnd) + { + detail::insert(Result, ::boost::end(Result), *itBegin); + ++itBegin; + } + + for(;itBegin!=itEnd; ++itBegin) + { + if(::boost::regex_match(::boost::begin(*itBegin), ::boost::end(*itBegin), Rx, Flags)) + { + // Add separator + detail::insert(Result, ::boost::end(Result), ::boost::as_literal(Separator)); + // Add element + detail::insert(Result, ::boost::end(Result), *itBegin); + } + } + + return Result; + } + + +#endif // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + + } // namespace algorithm + + // pull names into the boost namespace + using algorithm::find_regex; + using algorithm::replace_regex; + using algorithm::replace_regex_copy; + using algorithm::replace_all_regex; + using algorithm::replace_all_regex_copy; + using algorithm::erase_regex; + using algorithm::erase_regex_copy; + using algorithm::erase_all_regex; + using algorithm::erase_all_regex_copy; + using algorithm::find_all_regex; + using algorithm::split_regex; + +#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + using algorithm::join_if; +#else // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + using algorithm::join_if_regex; +#endif // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + +} // namespace boost + + +#endif // BOOST_STRING_REGEX_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex_find_format.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex_find_format.hpp new file mode 100644 index 0000000..409afc2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/regex_find_format.hpp @@ -0,0 +1,90 @@ +// Boost string_algo library regex_find_format.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_REGEX_FIND_FORMAT_HPP +#define BOOST_STRING_REGEX_FIND_FORMAT_HPP + +#include +#include +#include +#include + +/*! \file + Defines the \c regex_finder and \c regex_formatter generators. These two functors + are designed to work together. \c regex_formatter uses additional information + about a match contained in the regex_finder search result. +*/ + +namespace boost { + namespace algorithm { + +// regex_finder -----------------------------------------------// + + //! "Regex" finder + /*! + Construct the \c regex_finder. Finder uses the regex engine to search + for a match. + Result is given in \c regex_search_result. This is an extension + of the iterator_range. In addition it contains match results + from the \c regex_search algorithm. + + \param Rx A regular expression + \param MatchFlags Regex search options + \return An instance of the \c regex_finder object + */ + template< + typename CharT, + typename RegexTraitsT> + inline detail::find_regexF< basic_regex > + regex_finder( + const basic_regex& Rx, + match_flag_type MatchFlags=match_default ) + { + return detail:: + find_regexF< + basic_regex >( Rx, MatchFlags ); + } + +// regex_formater ---------------------------------------------// + + //! Regex formatter + /*! + Construct the \c regex_formatter. Regex formatter uses the regex engine to + format a match found by the \c regex_finder. + This formatted it designed to closely cooperate with \c regex_finder. + + \param Format Regex format definition + \param Flags Format flags + \return An instance of the \c regex_formatter functor + */ + template< + typename CharT, + typename TraitsT, typename AllocT > + inline detail::regex_formatF< std::basic_string< CharT, TraitsT, AllocT > > + regex_formatter( + const std::basic_string& Format, + match_flag_type Flags=format_default ) + { + return + detail::regex_formatF< std::basic_string >( + Format, + Flags ); + } + + } // namespace algorithm + + // pull the names to the boost namespace + using algorithm::regex_finder; + using algorithm::regex_formatter; + +} // namespace boost + + +#endif // BOOST_STRING_REGEX_FIND_FORMAT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/replace.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/replace.hpp new file mode 100644 index 0000000..0c04e47 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/replace.hpp @@ -0,0 +1,928 @@ +// Boost string_algo library replace.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_REPLACE_HPP +#define BOOST_STRING_REPLACE_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +/*! \file + Defines various replace algorithms. Each algorithm replaces + part(s) of the input according to set of searching and replace criteria. +*/ + +namespace boost { + namespace algorithm { + +// replace_range --------------------------------------------------------------------// + + //! Replace range algorithm + /*! + Replace the given range in the input string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param SearchRange A range in the input to be substituted + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT replace_range_copy( + OutputIteratorT Output, + const Range1T& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_const_iterator::type>& SearchRange, + const Range2T& Format) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::const_formatter(Format)); + } + + //! Replace range algorithm + /*! + \overload + */ + template + inline SequenceT replace_range_copy( + const SequenceT& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_const_iterator::type>& SearchRange, + const RangeT& Format) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::const_formatter(Format)); + } + + //! Replace range algorithm + /*! + Replace the given range in the input string. + The input sequence is modified in-place. + + \param Input An input string + \param SearchRange A range in the input to be substituted + \param Format A substitute string + */ + template + inline void replace_range( + SequenceT& Input, + const iterator_range< + BOOST_STRING_TYPENAME + range_iterator::type>& SearchRange, + const RangeT& Format) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::range_finder(SearchRange), + ::boost::algorithm::const_formatter(Format)); + } + +// replace_first --------------------------------------------------------------------// + + //! Replace first algorithm + /*! + Replace the first match of the search substring in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT replace_first_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace first algorithm + /*! + \overload + */ + template + inline SequenceT replace_first_copy( + const SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace first algorithm + /*! + replace the first match of the search substring in the input + with the format string. The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + */ + template + inline void replace_first( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_first ( case insensitive ) ---------------------------------------------// + + //! Replace first algorithm ( case insensitive ) + /*! + Replace the first match of the search substring in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT ireplace_first_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace first algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ireplace_first_copy( + const SequenceT& Input, + const Range2T& Search, + const Range1T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace first algorithm ( case insensitive ) + /*! + Replace the first match of the search substring in the input + with the format string. Input sequence is modified in-place. + Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + */ + template + inline void ireplace_first( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_last --------------------------------------------------------------------// + + //! Replace last algorithm + /*! + Replace the last match of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT replace_last_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace last algorithm + /*! + \overload + */ + template + inline SequenceT replace_last_copy( + const SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace last algorithm + /*! + Replace the last match of the search string in the input + with the format string. Input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + */ + template + inline void replace_last( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::last_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_last ( case insensitive ) -----------------------------------------------// + + //! Replace last algorithm ( case insensitive ) + /*! + Replace the last match of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT ireplace_last_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace last algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ireplace_last_copy( + const SequenceT& Input, + const Range1T& Search, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace last algorithm ( case insensitive ) + /*! + Replace the last match of the search string in the input + with the format string.The input sequence is modified in-place. + Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + \return A reference to the modified input + */ + template + inline void ireplace_last( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::last_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_nth --------------------------------------------------------------------// + + //! Replace nth algorithm + /*! + Replace an Nth (zero-indexed) match of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT replace_nth_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + int Nth, + const Range3T& Format ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace nth algorithm + /*! + \overload + */ + template + inline SequenceT replace_nth_copy( + const SequenceT& Input, + const Range1T& Search, + int Nth, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace nth algorithm + /*! + Replace an Nth (zero-indexed) match of the search string in the input + with the format string. Input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Format A substitute string + */ + template + inline void replace_nth( + SequenceT& Input, + const Range1T& Search, + int Nth, + const Range2T& Format ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::nth_finder(Search, Nth), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_nth ( case insensitive ) -----------------------------------------------// + + //! Replace nth algorithm ( case insensitive ) + /*! + Replace an Nth (zero-indexed) match of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT ireplace_nth_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + int Nth, + const Range3T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc) ), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace nth algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ireplace_nth_copy( + const SequenceT& Input, + const Range1T& Search, + int Nth, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace nth algorithm ( case insensitive ) + /*! + Replace an Nth (zero-indexed) match of the search string in the input + with the format string. Input sequence is modified in-place. + Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Nth An index of the match to be replaced. The index is 0-based. + For negative N, matches are counted from the end of string. + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + */ + template + inline void ireplace_nth( + SequenceT& Input, + const Range1T& Search, + int Nth, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::nth_finder(Search, Nth, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_all --------------------------------------------------------------------// + + //! Replace all algorithm + /*! + Replace all occurrences of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT replace_all_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace all algorithm + /*! + \overload + */ + template + inline SequenceT replace_all_copy( + const SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace all algorithm + /*! + Replace all occurrences of the search string in the input + with the format string. The input sequence is modified in-place. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \return A reference to the modified input + */ + template + inline void replace_all( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::first_finder(Search), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_all ( case insensitive ) -----------------------------------------------// + + //! Replace all algorithm ( case insensitive ) + /*! + Replace all occurrences of the search string in the input + with the format string. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + Searching is case insensitive. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T, + typename Range3T> + inline OutputIteratorT ireplace_all_copy( + OutputIteratorT Output, + const Range1T& Input, + const Range2T& Search, + const Range3T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_all_copy( + Output, + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace all algorithm ( case insensitive ) + /*! + \overload + */ + template + inline SequenceT ireplace_all_copy( + const SequenceT& Input, + const Range1T& Search, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::find_format_all_copy( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace all algorithm ( case insensitive ) + /*! + Replace all occurrences of the search string in the input + with the format string.The input sequence is modified in-place. + Searching is case insensitive. + + \param Input An input string + \param Search A substring to be searched for + \param Format A substitute string + \param Loc A locale used for case insensitive comparison + */ + template + inline void ireplace_all( + SequenceT& Input, + const Range1T& Search, + const Range2T& Format, + const std::locale& Loc=std::locale() ) + { + ::boost::algorithm::find_format_all( + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc)), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_head --------------------------------------------------------------------// + + //! Replace head algorithm + /*! + Replace the head of the input with the given format string. + The head is a prefix of a string of given size. + If the sequence is shorter then required, whole string if + considered to be the head. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param N Length of the head. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT replace_head_copy( + OutputIteratorT Output, + const Range1T& Input, + int N, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace head algorithm + /*! + \overload + */ + template + inline SequenceT replace_head_copy( + const SequenceT& Input, + int N, + const RangeT& Format ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace head algorithm + /*! + Replace the head of the input with the given format string. + The head is a prefix of a string of given size. + If the sequence is shorter then required, the whole string is + considered to be the head. The input sequence is modified in-place. + + \param Input An input string + \param N Length of the head. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \param Format A substitute string + */ + template + inline void replace_head( + SequenceT& Input, + int N, + const RangeT& Format ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::head_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + +// replace_tail --------------------------------------------------------------------// + + //! Replace tail algorithm + /*! + Replace the tail of the input with the given format string. + The tail is a suffix of a string of given size. + If the sequence is shorter then required, whole string is + considered to be the tail. + The result is a modified copy of the input. It is returned as a sequence + or copied to the output iterator. + + \param Output An output iterator to which the result will be copied + \param Input An input string + \param N Length of the tail. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \param Format A substitute string + \return An output iterator pointing just after the last inserted character or + a modified copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template< + typename OutputIteratorT, + typename Range1T, + typename Range2T> + inline OutputIteratorT replace_tail_copy( + OutputIteratorT Output, + const Range1T& Input, + int N, + const Range2T& Format ) + { + return ::boost::algorithm::find_format_copy( + Output, + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace tail algorithm + /*! + \overload + */ + template + inline SequenceT replace_tail_copy( + const SequenceT& Input, + int N, + const RangeT& Format ) + { + return ::boost::algorithm::find_format_copy( + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + + //! Replace tail algorithm + /*! + Replace the tail of the input with the given format sequence. + The tail is a suffix of a string of given size. + If the sequence is shorter then required, the whole string is + considered to be the tail. The input sequence is modified in-place. + + \param Input An input string + \param N Length of the tail. + For N>=0, at most N characters are extracted. + For N<0, size(Input)-|N| characters are extracted. + \param Format A substitute string + */ + template + inline void replace_tail( + SequenceT& Input, + int N, + const RangeT& Format ) + { + ::boost::algorithm::find_format( + Input, + ::boost::algorithm::tail_finder(N), + ::boost::algorithm::const_formatter(Format) ); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::replace_range_copy; + using algorithm::replace_range; + using algorithm::replace_first_copy; + using algorithm::replace_first; + using algorithm::ireplace_first_copy; + using algorithm::ireplace_first; + using algorithm::replace_last_copy; + using algorithm::replace_last; + using algorithm::ireplace_last_copy; + using algorithm::ireplace_last; + using algorithm::replace_nth_copy; + using algorithm::replace_nth; + using algorithm::ireplace_nth_copy; + using algorithm::ireplace_nth; + using algorithm::replace_all_copy; + using algorithm::replace_all; + using algorithm::ireplace_all_copy; + using algorithm::ireplace_all; + using algorithm::replace_head_copy; + using algorithm::replace_head; + using algorithm::replace_tail_copy; + using algorithm::replace_tail; + +} // namespace boost + +#endif // BOOST_REPLACE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/sequence_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/sequence_traits.hpp new file mode 100644 index 0000000..be151f8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/sequence_traits.hpp @@ -0,0 +1,120 @@ +// Boost string_algo library sequence_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_SEQUENCE_TRAITS_HPP +#define BOOST_STRING_SEQUENCE_TRAITS_HPP + +#include +#include +#include + +/*! \file + Traits defined in this header are used by various algorithms to achieve + better performance for specific containers. + Traits provide fail-safe defaults. If a container supports some of these + features, it is possible to specialize the specific trait for this container. + For lacking compilers, it is possible of define an override for a specific tester + function. + + Due to a language restriction, it is not currently possible to define specializations for + stl containers without including the corresponding header. To decrease the overhead + needed by this inclusion, user can selectively include a specialization + header for a specific container. They are located in boost/algorithm/string/stl + directory. Alternatively she can include boost/algorithm/string/std_collection_traits.hpp + header which contains specializations for all stl containers. +*/ + +namespace boost { + namespace algorithm { + +// sequence traits -----------------------------------------------// + + + //! Native replace trait + /*! + This trait specifies that the sequence has \c std::string like replace method + */ + template< typename T > + class has_native_replace + { + + public: +# if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = false }; +# else + BOOST_STATIC_CONSTANT(bool, value=false); +# endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + + + typedef mpl::bool_::value> type; + }; + + + //! Stable iterators trait + /*! + This trait specifies that the sequence has stable iterators. It means + that operations like insert/erase/replace do not invalidate iterators. + */ + template< typename T > + class has_stable_iterators + { + public: +# if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = false }; +# else + BOOST_STATIC_CONSTANT(bool, value=false); +# endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + + typedef mpl::bool_::value> type; + }; + + + //! Const time insert trait + /*! + This trait specifies that the sequence's insert method has + constant time complexity. + */ + template< typename T > + class has_const_time_insert + { + public: +# if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = false }; +# else + BOOST_STATIC_CONSTANT(bool, value=false); +# endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + + typedef mpl::bool_::value> type; + }; + + + //! Const time erase trait + /*! + This trait specifies that the sequence's erase method has + constant time complexity. + */ + template< typename T > + class has_const_time_erase + { + public: +# if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = false }; +# else + BOOST_STATIC_CONSTANT(bool, value=false); +# endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + + typedef mpl::bool_::value> type; + }; + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_SEQUENCE_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/split.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/split.hpp new file mode 100644 index 0000000..cae712c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/split.hpp @@ -0,0 +1,163 @@ +// Boost string_algo library split.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2006. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_SPLIT_HPP +#define BOOST_STRING_SPLIT_HPP + +#include + +#include +#include +#include + +/*! \file + Defines basic split algorithms. + Split algorithms can be used to divide a string + into several parts according to given criteria. + + Each part is copied and added as a new element to the + output container. + Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> +*/ + +namespace boost { + namespace algorithm { + +// find_all ------------------------------------------------------------// + + //! Find all algorithm + /*! + This algorithm finds all occurrences of the search string + in the input. + + Each part is copied and added as a new element to the + output container. + Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> + + \param Result A container that can hold copies of references to the substrings + \param Input A container which will be searched. + \param Search A substring to be searched for. + \return A reference the result + + \note Prior content of the result will be overwritten. + + \note This function provides the strong exception-safety guarantee + */ + template< typename SequenceSequenceT, typename Range1T, typename Range2T > + inline SequenceSequenceT& find_all( + SequenceSequenceT& Result, + Range1T& Input, + const Range2T& Search) + { + return ::boost::algorithm::iter_find( + Result, + Input, + ::boost::algorithm::first_finder(Search) ); + } + + //! Find all algorithm ( case insensitive ) + /*! + This algorithm finds all occurrences of the search string + in the input. + Each part is copied and added as a new element to the + output container. Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> + + Searching is case insensitive. + + \param Result A container that can hold copies of references to the substrings + \param Input A container which will be searched. + \param Search A substring to be searched for. + \param Loc A locale used for case insensitive comparison + \return A reference the result + + \note Prior content of the result will be overwritten. + + \note This function provides the strong exception-safety guarantee + */ + template< typename SequenceSequenceT, typename Range1T, typename Range2T > + inline SequenceSequenceT& ifind_all( + SequenceSequenceT& Result, + Range1T& Input, + const Range2T& Search, + const std::locale& Loc=std::locale() ) + { + return ::boost::algorithm::iter_find( + Result, + Input, + ::boost::algorithm::first_finder(Search, is_iequal(Loc) ) ); + } + + +// tokenize -------------------------------------------------------------// + + //! Split algorithm + /*! + Tokenize expression. This function is equivalent to C strtok. Input + sequence is split into tokens, separated by separators. Separators + are given by means of the predicate. + + Each part is copied and added as a new element to the + output container. + Thus the result container must be able to hold copies + of the matches (in a compatible structure like std::string) or + a reference to it (e.g. using the iterator range class). + Examples of such a container are \c std::vector + or \c std::list> + + \param Result A container that can hold copies of references to the substrings + \param Input A container which will be searched. + \param Pred A predicate to identify separators. This predicate is + supposed to return true if a given element is a separator. + \param eCompress If eCompress argument is set to token_compress_on, adjacent + separators are merged together. Otherwise, every two separators + delimit a token. + \return A reference the result + + \note Prior content of the result will be overwritten. + + \note This function provides the strong exception-safety guarantee + */ + template< typename SequenceSequenceT, typename RangeT, typename PredicateT > + inline SequenceSequenceT& split( + SequenceSequenceT& Result, + RangeT& Input, + PredicateT Pred, + token_compress_mode_type eCompress=token_compress_off ) + { + return ::boost::algorithm::iter_split( + Result, + Input, + ::boost::algorithm::token_finder( Pred, eCompress ) ); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::find_all; + using algorithm::ifind_all; + using algorithm::split; + +} // namespace boost + + +#endif // BOOST_STRING_SPLIT_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/list_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/list_traits.hpp new file mode 100644 index 0000000..a3cf7bb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/list_traits.hpp @@ -0,0 +1,68 @@ +// Boost string_algo library list_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_STD_LIST_TRAITS_HPP +#define BOOST_STRING_STD_LIST_TRAITS_HPP + +#include +#include +#include + +namespace boost { + namespace algorithm { + +// std::list<> traits -----------------------------------------------// + + + // stable iterators trait + template + class has_stable_iterators< ::std::list > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + // const time insert trait + template + class has_const_time_insert< ::std::list > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + // const time erase trait + template + class has_const_time_erase< ::std::list > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_STD_LIST_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/rope_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/rope_traits.hpp new file mode 100644 index 0000000..637059a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/rope_traits.hpp @@ -0,0 +1,81 @@ +// Boost string_algo library string_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_STD_ROPE_TRAITS_HPP +#define BOOST_STRING_STD_ROPE_TRAITS_HPP + +#include +#include +#include + +namespace boost { + namespace algorithm { + +// SGI's std::rope<> traits -----------------------------------------------// + + + // native replace trait + template + class has_native_replace< std::rope > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_ type; + }; + + // stable iterators trait + template + class has_stable_iterators< std::rope > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_ type; + }; + + // const time insert trait + template + class has_const_time_insert< std::rope > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_ type; + }; + + // const time erase trait + template + class has_const_time_erase< std::rope > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_ type; + }; + + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_ROPE_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/slist_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/slist_traits.hpp new file mode 100644 index 0000000..c30b93c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/slist_traits.hpp @@ -0,0 +1,69 @@ +// Boost string_algo library slist_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_STD_SLIST_TRAITS_HPP +#define BOOST_STRING_STD_SLIST_TRAITS_HPP + +#include +#include +#include BOOST_SLIST_HEADER +#include + +namespace boost { + namespace algorithm { + +// SGI's std::slist<> traits -----------------------------------------------// + + + // stable iterators trait + template + class has_stable_iterators< BOOST_STD_EXTENSION_NAMESPACE::slist > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + // const time insert trait + template + class has_const_time_insert< BOOST_STD_EXTENSION_NAMESPACE::slist > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + // const time erase trait + template + class has_const_time_erase< BOOST_STD_EXTENSION_NAMESPACE::slist > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true }; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + typedef mpl::bool_::value> type; + }; + + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_STD_LIST_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/string_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/string_traits.hpp new file mode 100644 index 0000000..c940830 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std/string_traits.hpp @@ -0,0 +1,44 @@ +// Boost string_algo library string_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_STD_STRING_TRAITS_HPP +#define BOOST_STRING_STD_STRING_TRAITS_HPP + +#include +#include +#include + +namespace boost { + namespace algorithm { + +// std::basic_string<> traits -----------------------------------------------// + + + // native replace trait + template + class has_native_replace< std::basic_string > + { + public: +#if BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + enum { value = true } ; +#else + BOOST_STATIC_CONSTANT(bool, value=true); +#endif // BOOST_WORKAROUND( __IBMCPP__, <= 600 ) + + typedef mpl::bool_::value> type; + }; + + + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_LIST_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/std_containers_traits.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std_containers_traits.hpp new file mode 100644 index 0000000..3f02246 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/std_containers_traits.hpp @@ -0,0 +1,26 @@ +// Boost string_algo library std_containers_traits.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_STD_CONTAINERS_TRAITS_HPP +#define BOOST_STRING_STD_CONTAINERS_TRAITS_HPP + +/*!\file + This file includes sequence traits for stl containers. +*/ + +#include +#include +#include + +#ifdef BOOST_HAS_SLIST +# include +#endif + +#endif // BOOST_STRING_STD_CONTAINERS_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim.hpp new file mode 100644 index 0000000..e740d57 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim.hpp @@ -0,0 +1,398 @@ +// Boost string_algo library trim.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_TRIM_HPP +#define BOOST_STRING_TRIM_HPP + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +/*! \file + Defines trim algorithms. + Trim algorithms are used to remove trailing and leading spaces from a + sequence (string). Space is recognized using given locales. + + Parametric (\c _if) variants use a predicate (functor) to select which characters + are to be trimmed.. + Functions take a selection predicate as a parameter, which is used to determine + whether a character is a space. Common predicates are provided in classification.hpp header. + +*/ + +namespace boost { + namespace algorithm { + + // left trim -----------------------------------------------// + + + //! Left trim - parametric + /*! + Remove all leading spaces from the input. + The supplied predicate is used to determine which characters are considered spaces. + The result is a trimmed copy of the input. It is returned as a sequence + or copied to the output iterator + + \param Output An output iterator to which the result will be copied + \param Input An input range + \param IsSpace A unary predicate identifying spaces + \return + An output iterator pointing just after the last inserted character or + a copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template + inline OutputIteratorT trim_left_copy_if( + OutputIteratorT Output, + const RangeT& Input, + PredicateT IsSpace) + { + iterator_range::type> lit_range(::boost::as_literal(Input)); + + std::copy( + ::boost::algorithm::detail::trim_begin( + ::boost::begin(lit_range), + ::boost::end(lit_range), + IsSpace ), + ::boost::end(lit_range), + Output); + + return Output; + } + + //! Left trim - parametric + /*! + \overload + */ + template + inline SequenceT trim_left_copy_if(const SequenceT& Input, PredicateT IsSpace) + { + return SequenceT( + ::boost::algorithm::detail::trim_begin( + ::boost::begin(Input), + ::boost::end(Input), + IsSpace ), + ::boost::end(Input)); + } + + //! Left trim - parametric + /*! + Remove all leading spaces from the input. + The result is a trimmed copy of the input. + + \param Input An input sequence + \param Loc a locale used for 'space' classification + \return A trimmed copy of the input + + \note This function provides the strong exception-safety guarantee + */ + template + inline SequenceT trim_left_copy(const SequenceT& Input, const std::locale& Loc=std::locale()) + { + return + ::boost::algorithm::trim_left_copy_if( + Input, + is_space(Loc)); + } + + //! Left trim + /*! + Remove all leading spaces from the input. The supplied predicate is + used to determine which characters are considered spaces. + The input sequence is modified in-place. + + \param Input An input sequence + \param IsSpace A unary predicate identifying spaces + */ + template + inline void trim_left_if(SequenceT& Input, PredicateT IsSpace) + { + Input.erase( + ::boost::begin(Input), + ::boost::algorithm::detail::trim_begin( + ::boost::begin(Input), + ::boost::end(Input), + IsSpace)); + } + + //! Left trim + /*! + Remove all leading spaces from the input. + The Input sequence is modified in-place. + + \param Input An input sequence + \param Loc A locale used for 'space' classification + */ + template + inline void trim_left(SequenceT& Input, const std::locale& Loc=std::locale()) + { + ::boost::algorithm::trim_left_if( + Input, + is_space(Loc)); + } + + // right trim -----------------------------------------------// + + //! Right trim - parametric + /*! + Remove all trailing spaces from the input. + The supplied predicate is used to determine which characters are considered spaces. + The result is a trimmed copy of the input. It is returned as a sequence + or copied to the output iterator + + \param Output An output iterator to which the result will be copied + \param Input An input range + \param IsSpace A unary predicate identifying spaces + \return + An output iterator pointing just after the last inserted character or + a copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template + inline OutputIteratorT trim_right_copy_if( + OutputIteratorT Output, + const RangeT& Input, + PredicateT IsSpace ) + { + iterator_range::type> lit_range(::boost::as_literal(Input)); + + std::copy( + ::boost::begin(lit_range), + ::boost::algorithm::detail::trim_end( + ::boost::begin(lit_range), + ::boost::end(lit_range), + IsSpace ), + Output ); + + return Output; + } + + //! Right trim - parametric + /*! + \overload + */ + template + inline SequenceT trim_right_copy_if(const SequenceT& Input, PredicateT IsSpace) + { + return SequenceT( + ::boost::begin(Input), + ::boost::algorithm::detail::trim_end( + ::boost::begin(Input), + ::boost::end(Input), + IsSpace) + ); + } + + //! Right trim + /*! + Remove all trailing spaces from the input. + The result is a trimmed copy of the input + + \param Input An input sequence + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + + \note This function provides the strong exception-safety guarantee + */ + template + inline SequenceT trim_right_copy(const SequenceT& Input, const std::locale& Loc=std::locale()) + { + return + ::boost::algorithm::trim_right_copy_if( + Input, + is_space(Loc)); + } + + + //! Right trim - parametric + /*! + Remove all trailing spaces from the input. + The supplied predicate is used to determine which characters are considered spaces. + The input sequence is modified in-place. + + \param Input An input sequence + \param IsSpace A unary predicate identifying spaces + */ + template + inline void trim_right_if(SequenceT& Input, PredicateT IsSpace) + { + Input.erase( + ::boost::algorithm::detail::trim_end( + ::boost::begin(Input), + ::boost::end(Input), + IsSpace ), + ::boost::end(Input) + ); + } + + + //! Right trim + /*! + Remove all trailing spaces from the input. + The input sequence is modified in-place. + + \param Input An input sequence + \param Loc A locale used for 'space' classification + */ + template + inline void trim_right(SequenceT& Input, const std::locale& Loc=std::locale()) + { + ::boost::algorithm::trim_right_if( + Input, + is_space(Loc) ); + } + + // both side trim -----------------------------------------------// + + //! Trim - parametric + /*! + Remove all trailing and leading spaces from the input. + The supplied predicate is used to determine which characters are considered spaces. + The result is a trimmed copy of the input. It is returned as a sequence + or copied to the output iterator + + \param Output An output iterator to which the result will be copied + \param Input An input range + \param IsSpace A unary predicate identifying spaces + \return + An output iterator pointing just after the last inserted character or + a copy of the input + + \note The second variant of this function provides the strong exception-safety guarantee + */ + template + inline OutputIteratorT trim_copy_if( + OutputIteratorT Output, + const RangeT& Input, + PredicateT IsSpace) + { + iterator_range::type> lit_range(::boost::as_literal(Input)); + + BOOST_STRING_TYPENAME + range_const_iterator::type TrimEnd= + ::boost::algorithm::detail::trim_end( + ::boost::begin(lit_range), + ::boost::end(lit_range), + IsSpace); + + std::copy( + detail::trim_begin( + ::boost::begin(lit_range), TrimEnd, IsSpace), + TrimEnd, + Output + ); + + return Output; + } + + //! Trim - parametric + /*! + \overload + */ + template + inline SequenceT trim_copy_if(const SequenceT& Input, PredicateT IsSpace) + { + BOOST_STRING_TYPENAME + range_const_iterator::type TrimEnd= + ::boost::algorithm::detail::trim_end( + ::boost::begin(Input), + ::boost::end(Input), + IsSpace); + + return SequenceT( + detail::trim_begin( + ::boost::begin(Input), + TrimEnd, + IsSpace), + TrimEnd + ); + } + + //! Trim + /*! + Remove all leading and trailing spaces from the input. + The result is a trimmed copy of the input + + \param Input An input sequence + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + + \note This function provides the strong exception-safety guarantee + */ + template + inline SequenceT trim_copy( const SequenceT& Input, const std::locale& Loc=std::locale() ) + { + return + ::boost::algorithm::trim_copy_if( + Input, + is_space(Loc) ); + } + + //! Trim + /*! + Remove all leading and trailing spaces from the input. + The supplied predicate is used to determine which characters are considered spaces. + The input sequence is modified in-place. + + \param Input An input sequence + \param IsSpace A unary predicate identifying spaces + */ + template + inline void trim_if(SequenceT& Input, PredicateT IsSpace) + { + ::boost::algorithm::trim_right_if( Input, IsSpace ); + ::boost::algorithm::trim_left_if( Input, IsSpace ); + } + + //! Trim + /*! + Remove all leading and trailing spaces from the input. + The input sequence is modified in-place. + + \param Input An input sequence + \param Loc A locale used for 'space' classification + */ + template + inline void trim(SequenceT& Input, const std::locale& Loc=std::locale()) + { + ::boost::algorithm::trim_if( + Input, + is_space( Loc ) ); + } + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::trim_left; + using algorithm::trim_left_if; + using algorithm::trim_left_copy; + using algorithm::trim_left_copy_if; + using algorithm::trim_right; + using algorithm::trim_right_if; + using algorithm::trim_right_copy; + using algorithm::trim_right_copy_if; + using algorithm::trim; + using algorithm::trim_if; + using algorithm::trim_copy; + using algorithm::trim_copy_if; + +} // namespace boost + +#endif // BOOST_STRING_TRIM_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim_all.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim_all.hpp new file mode 100644 index 0000000..a616f7f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/trim_all.hpp @@ -0,0 +1,217 @@ +// Boost string_algo library trim.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_TRIM_ALL_HPP +#define BOOST_STRING_TRIM_ALL_HPP + +#include + +#include +#include +#include +#include +#include +#include + +/*! \file + Defines trim_all algorithms. + + Just like \c trim, \c trim_all removes all trailing and leading spaces from a + sequence (string). In addition, spaces in the middle of the sequence are truncated + to just one character. Space is recognized using given locales. + + \c trim_fill acts as trim_all, but the spaces in the middle are replaces with + a user-define sequence of character. + + Parametric (\c _if) variants use a predicate (functor) to select which characters + are to be trimmed.. + Functions take a selection predicate as a parameter, which is used to determine + whether a character is a space. Common predicates are provided in classification.hpp header. + +*/ + +namespace boost { + namespace algorithm { + + // multi line trim ----------------------------------------------- // + + //! Trim All - parametric + /*! + Remove all leading and trailing spaces from the input and + compress all other spaces to a single character. + The result is a trimmed copy of the input + + \param Input An input sequence + \param IsSpace A unary predicate identifying spaces + \return A trimmed copy of the input + */ + template + inline SequenceT trim_all_copy_if(const SequenceT& Input, PredicateT IsSpace) + { + return + ::boost::find_format_all_copy( + ::boost::trim_copy_if(Input, IsSpace), + ::boost::token_finder(IsSpace, ::boost::token_compress_on), + ::boost::dissect_formatter(::boost::head_finder(1))); + } + + + //! Trim All + /*! + Remove all leading and trailing spaces from the input and + compress all other spaces to a single character. + The input sequence is modified in-place. + + \param Input An input sequence + \param IsSpace A unary predicate identifying spaces + */ + template + inline void trim_all_if(SequenceT& Input, PredicateT IsSpace) + { + ::boost::trim_if(Input, IsSpace); + ::boost::find_format_all( + Input, + ::boost::token_finder(IsSpace, ::boost::token_compress_on), + ::boost::dissect_formatter(::boost::head_finder(1))); + } + + + //! Trim All + /*! + Remove all leading and trailing spaces from the input and + compress all other spaces to a single character. + The result is a trimmed copy of the input + + \param Input An input sequence + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + */ + template + inline SequenceT trim_all_copy(const SequenceT& Input, const std::locale& Loc =std::locale()) + { + return trim_all_copy_if(Input, ::boost::is_space(Loc)); + } + + + //! Trim All + /*! + Remove all leading and trailing spaces from the input and + compress all other spaces to a single character. + The input sequence is modified in-place. + + \param Input An input sequence + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + */ + template + inline void trim_all(SequenceT& Input, const std::locale& Loc =std::locale()) + { + trim_all_if(Input, ::boost::is_space(Loc)); + } + + + //! Trim Fill - parametric + /*! + Remove all leading and trailing spaces from the input and + replace all every block of consecutive spaces with a fill string + defined by user. + The result is a trimmed copy of the input + + \param Input An input sequence + \param Fill A string used to fill the inner spaces + \param IsSpace A unary predicate identifying spaces + \return A trimmed copy of the input + */ + template + inline SequenceT trim_fill_copy_if(const SequenceT& Input, const RangeT& Fill, PredicateT IsSpace) + { + return + ::boost::find_format_all_copy( + ::boost::trim_copy_if(Input, IsSpace), + ::boost::token_finder(IsSpace, ::boost::token_compress_on), + ::boost::const_formatter(::boost::as_literal(Fill))); + } + + + //! Trim Fill + /*! + Remove all leading and trailing spaces from the input and + replace all every block of consecutive spaces with a fill string + defined by user. + The input sequence is modified in-place. + + \param Input An input sequence + \param Fill A string used to fill the inner spaces + \param IsSpace A unary predicate identifying spaces + */ + template + inline void trim_fill_if(SequenceT& Input, const RangeT& Fill, PredicateT IsSpace) + { + ::boost::trim_if(Input, IsSpace); + ::boost::find_format_all( + Input, + ::boost::token_finder(IsSpace, ::boost::token_compress_on), + ::boost::const_formatter(::boost::as_literal(Fill))); + } + + + //! Trim Fill + /*! + Remove all leading and trailing spaces from the input and + replace all every block of consecutive spaces with a fill string + defined by user. + The result is a trimmed copy of the input + + \param Input An input sequence + \param Fill A string used to fill the inner spaces + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + */ + template + inline SequenceT trim_fill_copy(const SequenceT& Input, const RangeT& Fill, const std::locale& Loc =std::locale()) + { + return trim_fill_copy_if(Input, Fill, ::boost::is_space(Loc)); + } + + + //! Trim Fill + /*! + Remove all leading and trailing spaces from the input and + replace all every block of consecutive spaces with a fill string + defined by user. + The input sequence is modified in-place. + + \param Input An input sequence + \param Fill A string used to fill the inner spaces + \param Loc A locale used for 'space' classification + \return A trimmed copy of the input + */ + template + inline void trim_fill(SequenceT& Input, const RangeT& Fill, const std::locale& Loc =std::locale()) + { + trim_fill_if(Input, Fill, ::boost::is_space(Loc)); + } + + + } // namespace algorithm + + // pull names to the boost namespace + using algorithm::trim_all; + using algorithm::trim_all_if; + using algorithm::trim_all_copy; + using algorithm::trim_all_copy_if; + using algorithm::trim_fill; + using algorithm::trim_fill_if; + using algorithm::trim_fill_copy; + using algorithm::trim_fill_copy_if; + +} // namespace boost + +#endif // BOOST_STRING_TRIM_ALL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/algorithm/string/yes_no_type.hpp b/thirdparty/source/boost_1_61_0/boost/algorithm/string/yes_no_type.hpp new file mode 100644 index 0000000..b76cc6c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/algorithm/string/yes_no_type.hpp @@ -0,0 +1,33 @@ +// Boost string_algo library yes_no_type.hpp header file ---------------------------// + +// Copyright Pavol Droba 2002-2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for updates, documentation, and revision history. + +#ifndef BOOST_STRING_YES_NO_TYPE_DETAIL_HPP +#define BOOST_STRING_YES_NO_TYPE_DETAIL_HPP + +namespace boost { + namespace algorithm { + + // taken from boost mailing-list + // when yes_no_type will become officially + // a part of boost distribution, this header + // will be deprecated + template struct size_descriptor + { + typedef char (& type)[I]; + }; + + typedef size_descriptor<1>::type yes_type; + typedef size_descriptor<2>::type no_type; + + } // namespace algorithm +} // namespace boost + + +#endif // BOOST_STRING_YES_NO_TYPE_DETAIL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/align/align.hpp b/thirdparty/source/boost_1_61_0/boost/align/align.hpp new file mode 100644 index 0000000..3582dcc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/align/align.hpp @@ -0,0 +1,20 @@ +/* +(c) 2014-2015 Glen Joseph Fernandes + + +Distributed under the Boost Software +License, Version 1.0. +http://boost.org/LICENSE_1_0.txt +*/ +#ifndef BOOST_ALIGN_ALIGN_HPP +#define BOOST_ALIGN_ALIGN_HPP + +#include + +#if !defined(BOOST_NO_CXX11_STD_ALIGN) +#include +#else +#include +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/align/detail/align.hpp b/thirdparty/source/boost_1_61_0/boost/align/detail/align.hpp new file mode 100644 index 0000000..0828c58 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/align/detail/align.hpp @@ -0,0 +1,38 @@ +/* +(c) 2014 Glen Joseph Fernandes + + +Distributed under the Boost Software +License, Version 1.0. +http://boost.org/LICENSE_1_0.txt +*/ +#ifndef BOOST_ALIGN_DETAIL_ALIGN_HPP +#define BOOST_ALIGN_DETAIL_ALIGN_HPP + +#include +#include + +namespace boost { +namespace alignment { + +inline void* align(std::size_t alignment, std::size_t size, + void*& ptr, std::size_t& space) +{ + BOOST_ASSERT(detail::is_alignment(alignment)); + if (size <= space) { + char* p = reinterpret_cast((reinterpret_cast(ptr) + alignment - 1) & ~(alignment - 1)); + std::ptrdiff_t n = p - static_cast(ptr); + if (size <= space - n) { + ptr = p; + space -= n; + return p; + } + } + return 0; +} + +} /* .alignment */ +} /* .boost */ + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/align/detail/align_cxx11.hpp b/thirdparty/source/boost_1_61_0/boost/align/detail/align_cxx11.hpp new file mode 100644 index 0000000..a95b84c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/align/detail/align_cxx11.hpp @@ -0,0 +1,22 @@ +/* +(c) 2014 Glen Joseph Fernandes + + +Distributed under the Boost Software +License, Version 1.0. +http://boost.org/LICENSE_1_0.txt +*/ +#ifndef BOOST_ALIGN_DETAIL_ALIGN_CXX11_HPP +#define BOOST_ALIGN_DETAIL_ALIGN_CXX11_HPP + +#include + +namespace boost { +namespace alignment { + +using std::align; + +} /* .alignment */ +} /* .boost */ + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/align/detail/is_alignment.hpp b/thirdparty/source/boost_1_61_0/boost/align/detail/is_alignment.hpp new file mode 100644 index 0000000..12d8df9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/align/detail/is_alignment.hpp @@ -0,0 +1,29 @@ +/* +(c) 2014 Glen Joseph Fernandes + + +Distributed under the Boost Software +License, Version 1.0. +http://boost.org/LICENSE_1_0.txt +*/ +#ifndef BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP +#define BOOST_ALIGN_DETAIL_IS_ALIGNMENT_HPP + +#include +#include + +namespace boost { +namespace alignment { +namespace detail { + +BOOST_CONSTEXPR inline bool is_alignment(std::size_t value) + BOOST_NOEXCEPT +{ + return (value > 0) && ((value & (value - 1)) == 0); +} + +} /* .detail */ +} /* .alignment */ +} /* .boost */ + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/aligned_storage.hpp b/thirdparty/source/boost_1_61_0/boost/aligned_storage.hpp new file mode 100644 index 0000000..f400fa9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/aligned_storage.hpp @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------------- +// boost aligned_storage.hpp header file +// See http://www.boost.org for updates, documentation, and revision history. +//----------------------------------------------------------------------------- +// +// Copyright (c) 2002-2003 +// Eric Friedman, Itay Maman +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_ALIGNED_STORAGE_HPP +#define BOOST_ALIGNED_STORAGE_HPP + +#include + +#endif // BOOST_ALIGNED_STORAGE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/array.hpp b/thirdparty/source/boost_1_61_0/boost/array.hpp new file mode 100644 index 0000000..fa06fa9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/array.hpp @@ -0,0 +1,446 @@ +/* The following code declares class array, + * an STL container (as wrapper) for arrays of constant size. + * + * See + * http://www.boost.org/libs/array/ + * for documentation. + * + * The original author site is at: http://www.josuttis.com/ + * + * (C) Copyright Nicolai M. Josuttis 2001. + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * 14 Apr 2012 - (mtc) Added support for boost::hash + * 28 Dec 2010 - (mtc) Added cbegin and cend (and crbegin and crend) for C++Ox compatibility. + * 10 Mar 2010 - (mtc) fill method added, matching resolution of the standard library working group. + * See or Trac issue #3168 + * Eventually, we should remove "assign" which is now a synonym for "fill" (Marshall Clow) + * 10 Mar 2010 - added workaround for SUNCC and !STLPort [trac #3893] (Marshall Clow) + * 29 Jan 2004 - c_array() added, BOOST_NO_PRIVATE_IN_AGGREGATE removed (Nico Josuttis) + * 23 Aug 2002 - fix for Non-MSVC compilers combined with MSVC libraries. + * 05 Aug 2001 - minor update (Nico Josuttis) + * 20 Jan 2001 - STLport fix (Beman Dawes) + * 29 Sep 2000 - Initial Revision (Nico Josuttis) + * + * Jan 29, 2004 + */ +#ifndef BOOST_ARRAY_HPP +#define BOOST_ARRAY_HPP + +#include + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +# pragma warning(push) +# pragma warning(disable:4996) // 'std::equal': Function call with parameters that may be unsafe +# pragma warning(disable:4510) // boost::array' : default constructor could not be generated +# pragma warning(disable:4610) // warning C4610: class 'boost::array' can never be instantiated - user defined constructor required +#endif + +#include +#include +#include +#include + +// Handles broken standard libraries better than +#include +#include +#include +#include + +// FIXES for broken compilers +#include + + +namespace boost { + + template + class array { + public: + T elems[N]; // fixed-size array of elements of type T + + public: + // type definitions + typedef T value_type; + typedef T* iterator; + typedef const T* const_iterator; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // iterator support + iterator begin() { return elems; } + const_iterator begin() const { return elems; } + const_iterator cbegin() const { return elems; } + + iterator end() { return elems+N; } + const_iterator end() const { return elems+N; } + const_iterator cend() const { return elems+N; } + + // reverse iterator support +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS) + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310) + // workaround for broken reverse_iterator in VC7 + typedef std::reverse_iterator > reverse_iterator; + typedef std::reverse_iterator > const_reverse_iterator; +#elif defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#else + // workaround for broken reverse_iterator implementations + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#endif + + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator(end()); + } + + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crend() const { + return const_reverse_iterator(begin()); + } + + // operator[] + reference operator[](size_type i) + { + BOOST_ASSERT_MSG( i < N, "out of range" ); + return elems[i]; + } + + const_reference operator[](size_type i) const + { + BOOST_ASSERT_MSG( i < N, "out of range" ); + return elems[i]; + } + + // at() with range check + reference at(size_type i) { rangecheck(i); return elems[i]; } + const_reference at(size_type i) const { rangecheck(i); return elems[i]; } + + // front() and back() + reference front() + { + return elems[0]; + } + + const_reference front() const + { + return elems[0]; + } + + reference back() + { + return elems[N-1]; + } + + const_reference back() const + { + return elems[N-1]; + } + + // size is constant + static size_type size() { return N; } + static bool empty() { return false; } + static size_type max_size() { return N; } + enum { static_size = N }; + + // swap (note: linear complexity) + void swap (array& y) { + for (size_type i = 0; i < N; ++i) + boost::swap(elems[i],y.elems[i]); + } + + // direct access to data (read-only) + const T* data() const { return elems; } + T* data() { return elems; } + + // use array as C array (direct read/write access to data) + T* c_array() { return elems; } + + // assignment with type conversion + template + array& operator= (const array& rhs) { + std::copy(rhs.begin(),rhs.end(), begin()); + return *this; + } + + // assign one value to all elements + void assign (const T& value) { fill ( value ); } // A synonym for fill + void fill (const T& value) + { + std::fill_n(begin(),size(),value); + } + + // check range (may be private because it is static) + static void rangecheck (size_type i) { + if (i >= size()) { + std::out_of_range e("array<>: index out of range"); + boost::throw_exception(e); + } + } + + }; + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) + template< class T > + class array< T, 0 > { + + public: + // type definitions + typedef T value_type; + typedef T* iterator; + typedef const T* const_iterator; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + // iterator support + iterator begin() { return iterator( reinterpret_cast< T * >( this ) ); } + const_iterator begin() const { return const_iterator( reinterpret_cast< const T * >( this ) ); } + const_iterator cbegin() const { return const_iterator( reinterpret_cast< const T * >( this ) ); } + + iterator end() { return begin(); } + const_iterator end() const { return begin(); } + const_iterator cend() const { return cbegin(); } + + // reverse iterator support +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_MSVC_STD_ITERATOR) && !defined(BOOST_NO_STD_ITERATOR_TRAITS) + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#elif defined(_MSC_VER) && (_MSC_VER == 1300) && defined(BOOST_DINKUMWARE_STDLIB) && (BOOST_DINKUMWARE_STDLIB == 310) + // workaround for broken reverse_iterator in VC7 + typedef std::reverse_iterator > reverse_iterator; + typedef std::reverse_iterator > const_reverse_iterator; +#elif defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#else + // workaround for broken reverse_iterator implementations + typedef std::reverse_iterator reverse_iterator; + typedef std::reverse_iterator const_reverse_iterator; +#endif + + reverse_iterator rbegin() { return reverse_iterator(end()); } + const_reverse_iterator rbegin() const { + return const_reverse_iterator(end()); + } + const_reverse_iterator crbegin() const { + return const_reverse_iterator(end()); + } + + reverse_iterator rend() { return reverse_iterator(begin()); } + const_reverse_iterator rend() const { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crend() const { + return const_reverse_iterator(begin()); + } + + // operator[] + reference operator[](size_type /*i*/) + { + return failed_rangecheck(); + } + + const_reference operator[](size_type /*i*/) const + { + return failed_rangecheck(); + } + + // at() with range check + reference at(size_type /*i*/) { return failed_rangecheck(); } + const_reference at(size_type /*i*/) const { return failed_rangecheck(); } + + // front() and back() + reference front() + { + return failed_rangecheck(); + } + + const_reference front() const + { + return failed_rangecheck(); + } + + reference back() + { + return failed_rangecheck(); + } + + const_reference back() const + { + return failed_rangecheck(); + } + + // size is constant + static size_type size() { return 0; } + static bool empty() { return true; } + static size_type max_size() { return 0; } + enum { static_size = 0 }; + + void swap (array& /*y*/) { + } + + // direct access to data (read-only) + const T* data() const { return 0; } + T* data() { return 0; } + + // use array as C array (direct read/write access to data) + T* c_array() { return 0; } + + // assignment with type conversion + template + array& operator= (const array& ) { + return *this; + } + + // assign one value to all elements + void assign (const T& value) { fill ( value ); } + void fill (const T& ) {} + + // check range (may be private because it is static) + static reference failed_rangecheck () { + std::out_of_range e("attempt to access element of an empty array"); + boost::throw_exception(e); +#if defined(BOOST_NO_EXCEPTIONS) || (!defined(BOOST_MSVC) && !defined(__PATHSCALE__)) + // + // We need to return something here to keep + // some compilers happy: however we will never + // actually get here.... + // + static T placeholder; + return placeholder; +#endif + } + }; +#endif + + // comparisons + template + bool operator== (const array& x, const array& y) { + return std::equal(x.begin(), x.end(), y.begin()); + } + template + bool operator< (const array& x, const array& y) { + return std::lexicographical_compare(x.begin(),x.end(),y.begin(),y.end()); + } + template + bool operator!= (const array& x, const array& y) { + return !(x==y); + } + template + bool operator> (const array& x, const array& y) { + return y + bool operator<= (const array& x, const array& y) { + return !(y + bool operator>= (const array& x, const array& y) { + return !(x + inline void swap (array& x, array& y) { + x.swap(y); + } + +#if defined(__SUNPRO_CC) +// Trac ticket #4757; the Sun Solaris compiler can't handle +// syntax like 'T(&get_c_array(boost::array& arg))[N]' +// +// We can't just use this for all compilers, because the +// borland compilers can't handle this form. + namespace detail { + template struct c_array + { + typedef T type[N]; + }; + } + + // Specific for boost::array: simply returns its elems data member. + template + typename detail::c_array::type& get_c_array(boost::array& arg) + { + return arg.elems; + } + + // Specific for boost::array: simply returns its elems data member. + template + typename const detail::c_array::type& get_c_array(const boost::array& arg) + { + return arg.elems; + } +#else +// Specific for boost::array: simply returns its elems data member. + template + T(&get_c_array(boost::array& arg))[N] + { + return arg.elems; + } + + // Const version. + template + const T(&get_c_array(const boost::array& arg))[N] + { + return arg.elems; + } +#endif + +#if 0 + // Overload for std::array, assuming that std::array will have + // explicit conversion functions as discussed at the WG21 meeting + // in Summit, March 2009. + template + T(&get_c_array(std::array& arg))[N] + { + return static_cast(arg); + } + + // Const version. + template + const T(&get_c_array(const std::array& arg))[N] + { + return static_cast(arg); + } +#endif + + + template + std::size_t hash_value(const array& arr) + { + return boost::hash_range(arr.begin(), arr.end()); + } + +} /* namespace boost */ + + +#if BOOST_WORKAROUND(BOOST_MSVC, >= 1400) +# pragma warning(pop) +#endif + +#endif /*BOOST_ARRAY_HPP*/ diff --git a/thirdparty/source/boost_1_61_0/boost/assert.hpp b/thirdparty/source/boost_1_61_0/boost/assert.hpp new file mode 100644 index 0000000..9650d7a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/assert.hpp @@ -0,0 +1,85 @@ +// +// boost/assert.hpp - BOOST_ASSERT(expr) +// BOOST_ASSERT_MSG(expr, msg) +// BOOST_VERIFY(expr) +// BOOST_VERIFY_MSG(expr, msg) +// BOOST_ASSERT_IS_VOID +// +// Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2007, 2014 Peter Dimov +// Copyright (c) Beman Dawes 2011 +// Copyright (c) 2015 Ion Gaztanaga +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// Note: There are no include guards. This is intentional. +// +// See http://www.boost.org/libs/assert/assert.html for documentation. +// + +// +// Stop inspect complaining about use of 'assert': +// +// boostinspect:naassert_macro +// + +// +// BOOST_ASSERT, BOOST_ASSERT_MSG, BOOST_ASSERT_IS_VOID +// + +#undef BOOST_ASSERT +#undef BOOST_ASSERT_MSG +#undef BOOST_ASSERT_IS_VOID + +#if defined(BOOST_DISABLE_ASSERTS) || ( defined(BOOST_ENABLE_ASSERT_DEBUG_HANDLER) && defined(NDEBUG) ) + +# define BOOST_ASSERT(expr) ((void)0) +# define BOOST_ASSERT_MSG(expr, msg) ((void)0) +# define BOOST_ASSERT_IS_VOID + +#elif defined(BOOST_ENABLE_ASSERT_HANDLER) || ( defined(BOOST_ENABLE_ASSERT_DEBUG_HANDLER) && !defined(NDEBUG) ) + +#include // for BOOST_LIKELY +#include + +namespace boost +{ + void assertion_failed(char const * expr, char const * function, char const * file, long line); // user defined + void assertion_failed_msg(char const * expr, char const * msg, char const * function, char const * file, long line); // user defined +} // namespace boost + +#define BOOST_ASSERT(expr) (BOOST_LIKELY(!!(expr))? ((void)0): ::boost::assertion_failed(#expr, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) +#define BOOST_ASSERT_MSG(expr, msg) (BOOST_LIKELY(!!(expr))? ((void)0): ::boost::assertion_failed_msg(#expr, msg, BOOST_CURRENT_FUNCTION, __FILE__, __LINE__)) + +#else + +# include // .h to support old libraries w/o - effect is the same + +# define BOOST_ASSERT(expr) assert(expr) +# define BOOST_ASSERT_MSG(expr, msg) assert((expr)&&(msg)) +#if defined(NDEBUG) +# define BOOST_ASSERT_IS_VOID +#endif + +#endif + +// +// BOOST_VERIFY, BOOST_VERIFY_MSG +// + +#undef BOOST_VERIFY +#undef BOOST_VERIFY_MSG + +#if defined(BOOST_DISABLE_ASSERTS) || ( !defined(BOOST_ENABLE_ASSERT_HANDLER) && defined(NDEBUG) ) + +# define BOOST_VERIFY(expr) ((void)(expr)) +# define BOOST_VERIFY_MSG(expr, msg) ((void)(expr)) + +#else + +# define BOOST_VERIFY(expr) BOOST_ASSERT(expr) +# define BOOST_VERIFY_MSG(expr, msg) BOOST_ASSERT_MSG(expr,msg) + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/atomic.hpp b/thirdparty/source/boost_1_61_0/boost/atomic.hpp new file mode 100644 index 0000000..cc28b1a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic.hpp @@ -0,0 +1,18 @@ +#ifndef BOOST_ATOMIC_HPP +#define BOOST_ATOMIC_HPP + +// Copyright (c) 2011 Helge Bahmann +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// This header includes all Boost.Atomic public headers + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/atomic.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/atomic.hpp new file mode 100644 index 0000000..8b0bdd1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/atomic.hpp @@ -0,0 +1,93 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/atomic.hpp + * + * This header contains definition of \c atomic template and \c atomic_flag. + */ + +#ifndef BOOST_ATOMIC_ATOMIC_HPP_INCLUDED_ +#define BOOST_ATOMIC_ATOMIC_HPP_INCLUDED_ + +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { + +using atomics::atomic; + +using atomics::atomic_char; +using atomics::atomic_uchar; +using atomics::atomic_schar; +using atomics::atomic_uint8_t; +using atomics::atomic_int8_t; +using atomics::atomic_ushort; +using atomics::atomic_short; +using atomics::atomic_uint16_t; +using atomics::atomic_int16_t; +using atomics::atomic_uint; +using atomics::atomic_int; +using atomics::atomic_uint32_t; +using atomics::atomic_int32_t; +using atomics::atomic_ulong; +using atomics::atomic_long; +using atomics::atomic_uint64_t; +using atomics::atomic_int64_t; +#ifdef BOOST_HAS_LONG_LONG +using atomics::atomic_ullong; +using atomics::atomic_llong; +#endif +using atomics::atomic_address; +using atomics::atomic_bool; +using atomics::atomic_wchar_t; +#if !defined(BOOST_NO_CXX11_CHAR16_T) +using atomics::atomic_char16_t; +#endif +#if !defined(BOOST_NO_CXX11_CHAR32_T) +using atomics::atomic_char32_t; +#endif + +using atomics::atomic_int_least8_t; +using atomics::atomic_uint_least8_t; +using atomics::atomic_int_least16_t; +using atomics::atomic_uint_least16_t; +using atomics::atomic_int_least32_t; +using atomics::atomic_uint_least32_t; +using atomics::atomic_int_least64_t; +using atomics::atomic_uint_least64_t; +using atomics::atomic_int_fast8_t; +using atomics::atomic_uint_fast8_t; +using atomics::atomic_int_fast16_t; +using atomics::atomic_uint_fast16_t; +using atomics::atomic_int_fast32_t; +using atomics::atomic_uint_fast32_t; +using atomics::atomic_int_fast64_t; +using atomics::atomic_uint_fast64_t; +using atomics::atomic_intmax_t; +using atomics::atomic_uintmax_t; + +using atomics::atomic_size_t; +using atomics::atomic_ptrdiff_t; + +#if defined(BOOST_HAS_INTPTR_T) +using atomics::atomic_intptr_t; +using atomics::atomic_uintptr_t; +#endif + +} // namespace boost + +#endif // BOOST_ATOMIC_ATOMIC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/atomic_flag.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/atomic_flag.hpp new file mode 100644 index 0000000..ac296bc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/atomic_flag.hpp @@ -0,0 +1,33 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/atomic_flag.hpp + * + * This header contains definition of \c atomic_flag. + */ + +#ifndef BOOST_ATOMIC_ATOMIC_FLAG_HPP_INCLUDED_ +#define BOOST_ATOMIC_ATOMIC_FLAG_HPP_INCLUDED_ + +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { + +using atomics::atomic_flag; + +} // namespace boost + +#endif // BOOST_ATOMIC_ATOMIC_FLAG_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/capabilities.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/capabilities.hpp new file mode 100644 index 0000000..05bbb0f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/capabilities.hpp @@ -0,0 +1,161 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/capabilities.hpp + * + * This header defines feature capabilities macros. + */ + +#ifndef BOOST_ATOMIC_CAPABILITIES_HPP_INCLUDED_ +#define BOOST_ATOMIC_CAPABILITIES_HPP_INCLUDED_ + +#include +#include +#include + +#if !defined(BOOST_ATOMIC_EMULATED) +#include BOOST_ATOMIC_DETAIL_HEADER(boost/atomic/detail/caps_) +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#ifndef BOOST_ATOMIC_INT8_LOCK_FREE +#define BOOST_ATOMIC_INT8_LOCK_FREE 0 +#endif + +#ifndef BOOST_ATOMIC_INT16_LOCK_FREE +#define BOOST_ATOMIC_INT16_LOCK_FREE 0 +#endif + +#ifndef BOOST_ATOMIC_INT32_LOCK_FREE +#define BOOST_ATOMIC_INT32_LOCK_FREE 0 +#endif + +#ifndef BOOST_ATOMIC_INT64_LOCK_FREE +#define BOOST_ATOMIC_INT64_LOCK_FREE 0 +#endif + +#ifndef BOOST_ATOMIC_INT128_LOCK_FREE +#define BOOST_ATOMIC_INT128_LOCK_FREE 0 +#endif + + +#ifndef BOOST_ATOMIC_CHAR_LOCK_FREE +#define BOOST_ATOMIC_CHAR_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#endif + +#ifndef BOOST_ATOMIC_CHAR16_T_LOCK_FREE +#define BOOST_ATOMIC_CHAR16_T_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#endif + +#ifndef BOOST_ATOMIC_CHAR32_T_LOCK_FREE +#define BOOST_ATOMIC_CHAR32_T_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#endif + +#ifndef BOOST_ATOMIC_WCHAR_T_LOCK_FREE +#if BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 1 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 2 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 4 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 8 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#else +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE 0 +#endif +#endif + +#ifndef BOOST_ATOMIC_SHORT_LOCK_FREE +#if BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 1 +#define BOOST_ATOMIC_SHORT_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 2 +#define BOOST_ATOMIC_SHORT_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 4 +#define BOOST_ATOMIC_SHORT_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 8 +#define BOOST_ATOMIC_SHORT_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#else +#define BOOST_ATOMIC_SHORT_LOCK_FREE 0 +#endif +#endif + +#ifndef BOOST_ATOMIC_INT_LOCK_FREE +#if BOOST_ATOMIC_DETAIL_SIZEOF_INT == 1 +#define BOOST_ATOMIC_INT_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 2 +#define BOOST_ATOMIC_INT_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 4 +#define BOOST_ATOMIC_INT_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 8 +#define BOOST_ATOMIC_INT_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#else +#define BOOST_ATOMIC_INT_LOCK_FREE 0 +#endif +#endif + +#ifndef BOOST_ATOMIC_LONG_LOCK_FREE +#if BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 1 +#define BOOST_ATOMIC_LONG_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 2 +#define BOOST_ATOMIC_LONG_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 4 +#define BOOST_ATOMIC_LONG_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 8 +#define BOOST_ATOMIC_LONG_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#else +#define BOOST_ATOMIC_LONG_LOCK_FREE 0 +#endif +#endif + +#ifndef BOOST_ATOMIC_LLONG_LOCK_FREE +#if BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 1 +#define BOOST_ATOMIC_LLONG_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 2 +#define BOOST_ATOMIC_LLONG_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 4 +#define BOOST_ATOMIC_LLONG_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 8 +#define BOOST_ATOMIC_LLONG_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#else +#define BOOST_ATOMIC_LLONG_LOCK_FREE 0 +#endif +#endif + +#ifndef BOOST_ATOMIC_POINTER_LOCK_FREE +#if (BOOST_ATOMIC_DETAIL_SIZEOF_POINTER + 0) == 8 +#define BOOST_ATOMIC_POINTER_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#elif (BOOST_ATOMIC_DETAIL_SIZEOF_POINTER + 0) == 4 +#define BOOST_ATOMIC_POINTER_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#else +#define BOOST_ATOMIC_POINTER_LOCK_FREE 0 +#endif +#endif + +#define BOOST_ATOMIC_ADDRESS_LOCK_FREE BOOST_ATOMIC_POINTER_LOCK_FREE + +#ifndef BOOST_ATOMIC_BOOL_LOCK_FREE +// We store bools in 1-byte storage in all backends +#define BOOST_ATOMIC_BOOL_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#endif + +#ifndef BOOST_ATOMIC_FLAG_LOCK_FREE +#define BOOST_ATOMIC_FLAG_LOCK_FREE BOOST_ATOMIC_BOOL_LOCK_FREE +#endif + +#ifndef BOOST_ATOMIC_THREAD_FENCE +#define BOOST_ATOMIC_THREAD_FENCE 0 +#endif + +#ifndef BOOST_ATOMIC_SIGNAL_FENCE +#define BOOST_ATOMIC_SIGNAL_FENCE 0 +#endif + +#endif // BOOST_ATOMIC_CAPABILITIES_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_flag.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_flag.hpp new file mode 100644 index 0000000..7fb44cd --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_flag.hpp @@ -0,0 +1,70 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/atomic_flag.hpp + * + * This header contains interface definition of \c atomic_flag. + */ + +#ifndef BOOST_ATOMIC_DETAIL_ATOMIC_FLAG_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_ATOMIC_FLAG_HPP_INCLUDED_ + +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +/* + * IMPLEMENTATION NOTE: All interface functions MUST be declared with BOOST_FORCEINLINE, + * see comment for convert_memory_order_to_gcc in ops_gcc_atomic.hpp. + */ + +namespace boost { +namespace atomics { + +#if defined(BOOST_NO_CXX11_CONSTEXPR) || defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) +#define BOOST_ATOMIC_NO_ATOMIC_FLAG_INIT +#else +#define BOOST_ATOMIC_FLAG_INIT {} +#endif + +struct atomic_flag +{ + typedef atomics::detail::operations< 1u, false > operations; + typedef operations::storage_type storage_type; + + operations::aligned_storage_type m_storage; + + BOOST_FORCEINLINE BOOST_CONSTEXPR atomic_flag() BOOST_NOEXCEPT : m_storage(0) + { + } + + BOOST_FORCEINLINE bool test_and_set(memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return operations::test_and_set(m_storage.value, order); + } + + BOOST_FORCEINLINE void clear(memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + operations::clear(m_storage.value, order); + } + + BOOST_DELETED_FUNCTION(atomic_flag(atomic_flag const&)) + BOOST_DELETED_FUNCTION(atomic_flag& operator= (atomic_flag const&)) +}; + +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_ATOMIC_FLAG_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_template.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_template.hpp new file mode 100644 index 0000000..2deaded --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/atomic_template.hpp @@ -0,0 +1,774 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/atomic_template.hpp + * + * This header contains interface definition of \c atomic template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_ATOMIC_TEMPLATE_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_ATOMIC_TEMPLATE_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(BOOST_MSVC) +#pragma warning(push) +// 'boost::atomics::atomic' : multiple assignment operators specified +#pragma warning(disable: 4522) +#endif + +/* + * IMPLEMENTATION NOTE: All interface functions MUST be declared with BOOST_FORCEINLINE, + * see comment for convert_memory_order_to_gcc in ops_gcc_atomic.hpp. + */ + +namespace boost { +namespace atomics { +namespace detail { + +BOOST_FORCEINLINE BOOST_CONSTEXPR memory_order deduce_failure_order(memory_order order) BOOST_NOEXCEPT +{ + return order == memory_order_acq_rel ? memory_order_acquire : (order == memory_order_release ? memory_order_relaxed : order); +} + +BOOST_FORCEINLINE BOOST_CONSTEXPR bool cas_failure_order_must_not_be_stronger_than_success_order(memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT +{ + // 15 == (memory_order_seq_cst | memory_order_consume), see memory_order.hpp + // Given the enum values we can test the strength of memory order requirements with this single condition. + return (failure_order & 15u) <= (success_order & 15u); +} + +template< typename T, bool IsInt = boost::is_integral< T >::value > +struct classify +{ + typedef void type; +}; + +template< typename T > +struct classify< T, true > { typedef int type; }; + +template< typename T > +struct classify< T*, false > { typedef void* type; }; + +template< typename T, typename Kind > +class base_atomic; + +//! Implementation for integers +template< typename T > +class base_atomic< T, int > +{ +private: + typedef T value_type; + typedef T difference_type; + typedef atomics::detail::operations< storage_size_of< value_type >::value, boost::is_signed< T >::value > operations; + +protected: + typedef value_type value_arg_type; + +public: + typedef typename operations::storage_type storage_type; + +protected: + typename operations::aligned_storage_type m_storage; + +public: + BOOST_DEFAULTED_FUNCTION(base_atomic(), {}) + BOOST_CONSTEXPR explicit base_atomic(value_type v) BOOST_NOEXCEPT : m_storage(v) {} + + BOOST_FORCEINLINE void store(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_consume); + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + + operations::store(m_storage.value, static_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE value_type load(memory_order order = memory_order_seq_cst) const volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_release); + BOOST_ASSERT(order != memory_order_acq_rel); + + return static_cast< value_type >(operations::load(m_storage.value, order)); + } + + BOOST_FORCEINLINE value_type fetch_add(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::fetch_add(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type fetch_sub(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::fetch_sub(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type exchange(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::exchange(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = static_cast< storage_type >(expected); + const bool res = operations::compare_exchange_strong(m_storage.value, old_value, static_cast< storage_type >(desired), success_order, failure_order); + expected = static_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_strong(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = static_cast< storage_type >(expected); + const bool res = operations::compare_exchange_weak(m_storage.value, old_value, static_cast< storage_type >(desired), success_order, failure_order); + expected = static_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_weak(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE value_type fetch_and(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::fetch_and(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type fetch_or(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::fetch_or(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type fetch_xor(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return static_cast< value_type >(operations::fetch_xor(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE bool is_lock_free() const volatile BOOST_NOEXCEPT + { + return operations::is_lock_free(m_storage.value); + } + + BOOST_FORCEINLINE value_type operator++(int) volatile BOOST_NOEXCEPT + { + return fetch_add(1); + } + + BOOST_FORCEINLINE value_type operator++() volatile BOOST_NOEXCEPT + { + return fetch_add(1) + 1; + } + + BOOST_FORCEINLINE value_type operator--(int) volatile BOOST_NOEXCEPT + { + return fetch_sub(1); + } + + BOOST_FORCEINLINE value_type operator--() volatile BOOST_NOEXCEPT + { + return fetch_sub(1) - 1; + } + + BOOST_FORCEINLINE value_type operator+=(difference_type v) volatile BOOST_NOEXCEPT + { + return fetch_add(v) + v; + } + + BOOST_FORCEINLINE value_type operator-=(difference_type v) volatile BOOST_NOEXCEPT + { + return fetch_sub(v) - v; + } + + BOOST_FORCEINLINE value_type operator&=(value_type v) volatile BOOST_NOEXCEPT + { + return fetch_and(v) & v; + } + + BOOST_FORCEINLINE value_type operator|=(value_type v) volatile BOOST_NOEXCEPT + { + return fetch_or(v) | v; + } + + BOOST_FORCEINLINE value_type operator^=(value_type v) volatile BOOST_NOEXCEPT + { + return fetch_xor(v) ^ v; + } + + BOOST_DELETED_FUNCTION(base_atomic(base_atomic const&)) + BOOST_DELETED_FUNCTION(base_atomic& operator=(base_atomic const&)) +}; + +//! Implementation for bool +template< > +class base_atomic< bool, int > +{ +private: + typedef bool value_type; + typedef atomics::detail::operations< 1u, false > operations; + +protected: + typedef value_type value_arg_type; + +public: + typedef operations::storage_type storage_type; + +protected: + operations::aligned_storage_type m_storage; + +public: + BOOST_DEFAULTED_FUNCTION(base_atomic(), {}) + BOOST_CONSTEXPR explicit base_atomic(value_type v) BOOST_NOEXCEPT : m_storage(v) {} + + BOOST_FORCEINLINE void store(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_consume); + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + + operations::store(m_storage.value, static_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE value_type load(memory_order order = memory_order_seq_cst) const volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_release); + BOOST_ASSERT(order != memory_order_acq_rel); + + return !!operations::load(m_storage.value, order); + } + + BOOST_FORCEINLINE value_type exchange(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return !!operations::exchange(m_storage.value, static_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = static_cast< storage_type >(expected); + const bool res = operations::compare_exchange_strong(m_storage.value, old_value, static_cast< storage_type >(desired), success_order, failure_order); + expected = !!old_value; + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_strong(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = static_cast< storage_type >(expected); + const bool res = operations::compare_exchange_weak(m_storage.value, old_value, static_cast< storage_type >(desired), success_order, failure_order); + expected = !!old_value; + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_weak(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool is_lock_free() const volatile BOOST_NOEXCEPT + { + return operations::is_lock_free(m_storage.value); + } + + BOOST_DELETED_FUNCTION(base_atomic(base_atomic const&)) + BOOST_DELETED_FUNCTION(base_atomic& operator=(base_atomic const&)) +}; + + +//! Implementation for user-defined types, such as structs and enums +template< typename T > +class base_atomic< T, void > +{ +private: + typedef T value_type; + typedef atomics::detail::operations< storage_size_of< value_type >::value, false > operations; + +protected: + typedef value_type const& value_arg_type; + +public: + typedef typename operations::storage_type storage_type; + +protected: + typename operations::aligned_storage_type m_storage; + +public: + BOOST_FORCEINLINE explicit base_atomic(value_type const& v = value_type()) BOOST_NOEXCEPT : m_storage(atomics::detail::bitwise_cast< storage_type >(v)) + { + } + + BOOST_FORCEINLINE void store(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_consume); + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + + operations::store(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE value_type load(memory_order order = memory_order_seq_cst) const volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_release); + BOOST_ASSERT(order != memory_order_acq_rel); + + return atomics::detail::bitwise_cast< value_type >(operations::load(m_storage.value, order)); + } + + BOOST_FORCEINLINE value_type exchange(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::exchange(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_strong(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_strong(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_weak(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_weak(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool is_lock_free() const volatile BOOST_NOEXCEPT + { + return operations::is_lock_free(m_storage.value); + } + + BOOST_DELETED_FUNCTION(base_atomic(base_atomic const&)) + BOOST_DELETED_FUNCTION(base_atomic& operator=(base_atomic const&)) +}; + + +//! Implementation for pointers +template< typename T > +class base_atomic< T*, void* > +{ +private: + typedef T* value_type; + typedef std::ptrdiff_t difference_type; + typedef atomics::detail::operations< storage_size_of< value_type >::value, false > operations; + +protected: + typedef value_type value_arg_type; + +public: + typedef typename operations::storage_type storage_type; + +protected: + typename operations::aligned_storage_type m_storage; + +public: + BOOST_DEFAULTED_FUNCTION(base_atomic(), {}) + BOOST_FORCEINLINE explicit base_atomic(value_type const& v) BOOST_NOEXCEPT : m_storage(atomics::detail::bitwise_cast< storage_type >(v)) + { + } + + BOOST_FORCEINLINE void store(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_consume); + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + + operations::store(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE value_type load(memory_order order = memory_order_seq_cst) const volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_release); + BOOST_ASSERT(order != memory_order_acq_rel); + + return atomics::detail::bitwise_cast< value_type >(operations::load(m_storage.value, order)); + } + + BOOST_FORCEINLINE value_type fetch_add(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::fetch_add(m_storage.value, static_cast< storage_type >(v * sizeof(T)), order)); + } + + BOOST_FORCEINLINE value_type fetch_sub(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::fetch_sub(m_storage.value, static_cast< storage_type >(v * sizeof(T)), order)); + } + + BOOST_FORCEINLINE value_type exchange(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::exchange(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_strong(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_strong(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_weak(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_weak(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool is_lock_free() const volatile BOOST_NOEXCEPT + { + return operations::is_lock_free(m_storage.value); + } + + BOOST_FORCEINLINE value_type operator++(int) volatile BOOST_NOEXCEPT + { + return fetch_add(1); + } + + BOOST_FORCEINLINE value_type operator++() volatile BOOST_NOEXCEPT + { + return fetch_add(1) + 1; + } + + BOOST_FORCEINLINE value_type operator--(int) volatile BOOST_NOEXCEPT + { + return fetch_sub(1); + } + + BOOST_FORCEINLINE value_type operator--() volatile BOOST_NOEXCEPT + { + return fetch_sub(1) - 1; + } + + BOOST_FORCEINLINE value_type operator+=(difference_type v) volatile BOOST_NOEXCEPT + { + return fetch_add(v) + v; + } + + BOOST_FORCEINLINE value_type operator-=(difference_type v) volatile BOOST_NOEXCEPT + { + return fetch_sub(v) - v; + } + + BOOST_DELETED_FUNCTION(base_atomic(base_atomic const&)) + BOOST_DELETED_FUNCTION(base_atomic& operator=(base_atomic const&)) +}; + + +//! Implementation for void pointers +template< > +class base_atomic< void*, void* > +{ +private: + typedef void* value_type; + typedef std::ptrdiff_t difference_type; + typedef atomics::detail::operations< storage_size_of< value_type >::value, false > operations; + +protected: + typedef value_type value_arg_type; + +public: + typedef operations::storage_type storage_type; + +protected: + operations::aligned_storage_type m_storage; + +public: + BOOST_DEFAULTED_FUNCTION(base_atomic(), {}) + BOOST_FORCEINLINE explicit base_atomic(value_type const& v) BOOST_NOEXCEPT : m_storage(atomics::detail::bitwise_cast< storage_type >(v)) + { + } + + BOOST_FORCEINLINE void store(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_consume); + BOOST_ASSERT(order != memory_order_acquire); + BOOST_ASSERT(order != memory_order_acq_rel); + + operations::store(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order); + } + + BOOST_FORCEINLINE value_type load(memory_order order = memory_order_seq_cst) const volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(order != memory_order_release); + BOOST_ASSERT(order != memory_order_acq_rel); + + return atomics::detail::bitwise_cast< value_type >(operations::load(m_storage.value, order)); + } + + BOOST_FORCEINLINE value_type fetch_add(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::fetch_add(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type fetch_sub(difference_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::fetch_sub(m_storage.value, static_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE value_type exchange(value_type v, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return atomics::detail::bitwise_cast< value_type >(operations::exchange(m_storage.value, atomics::detail::bitwise_cast< storage_type >(v), order)); + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_strong(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_strong(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_strong(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order success_order, memory_order failure_order) volatile BOOST_NOEXCEPT + { + BOOST_ASSERT(failure_order != memory_order_release); + BOOST_ASSERT(failure_order != memory_order_acq_rel); + BOOST_ASSERT(cas_failure_order_must_not_be_stronger_than_success_order(success_order, failure_order)); + + storage_type old_value = atomics::detail::bitwise_cast< storage_type >(expected); + const bool res = operations::compare_exchange_weak(m_storage.value, old_value, atomics::detail::bitwise_cast< storage_type >(desired), success_order, failure_order); + expected = atomics::detail::bitwise_cast< value_type >(old_value); + return res; + } + + BOOST_FORCEINLINE bool compare_exchange_weak(value_type& expected, value_type desired, memory_order order = memory_order_seq_cst) volatile BOOST_NOEXCEPT + { + return compare_exchange_weak(expected, desired, order, atomics::detail::deduce_failure_order(order)); + } + + BOOST_FORCEINLINE bool is_lock_free() const volatile BOOST_NOEXCEPT + { + return operations::is_lock_free(m_storage.value); + } + + BOOST_FORCEINLINE value_type operator++(int) volatile BOOST_NOEXCEPT + { + return fetch_add(1); + } + + BOOST_FORCEINLINE value_type operator++() volatile BOOST_NOEXCEPT + { + return (char*)fetch_add(1) + 1; + } + + BOOST_FORCEINLINE value_type operator--(int) volatile BOOST_NOEXCEPT + { + return fetch_sub(1); + } + + BOOST_FORCEINLINE value_type operator--() volatile BOOST_NOEXCEPT + { + return (char*)fetch_sub(1) - 1; + } + + BOOST_FORCEINLINE value_type operator+=(difference_type v) volatile BOOST_NOEXCEPT + { + return (char*)fetch_add(v) + v; + } + + BOOST_FORCEINLINE value_type operator-=(difference_type v) volatile BOOST_NOEXCEPT + { + return (char*)fetch_sub(v) - v; + } + + BOOST_DELETED_FUNCTION(base_atomic(base_atomic const&)) + BOOST_DELETED_FUNCTION(base_atomic& operator=(base_atomic const&)) +}; + +} // namespace detail + +template< typename T > +class atomic : + public atomics::detail::base_atomic< T, typename atomics::detail::classify< T >::type > +{ +private: + typedef T value_type; + typedef atomics::detail::base_atomic< T, typename atomics::detail::classify< T >::type > base_type; + typedef typename base_type::value_arg_type value_arg_type; + +public: + typedef typename base_type::storage_type storage_type; + +public: + BOOST_DEFAULTED_FUNCTION(atomic(), BOOST_NOEXCEPT {}) + + // NOTE: The constructor is made explicit because gcc 4.7 complains that + // operator=(value_arg_type) is considered ambiguous with operator=(atomic const&) + // in assignment expressions, even though conversion to atomic<> is less preferred + // than conversion to value_arg_type. + BOOST_FORCEINLINE explicit BOOST_CONSTEXPR atomic(value_arg_type v) BOOST_NOEXCEPT : base_type(v) {} + + BOOST_FORCEINLINE value_type operator= (value_arg_type v) volatile BOOST_NOEXCEPT + { + this->store(v); + return v; + } + + BOOST_FORCEINLINE operator value_type() volatile const BOOST_NOEXCEPT + { + return this->load(); + } + + BOOST_FORCEINLINE storage_type& storage() BOOST_NOEXCEPT { return this->m_storage.value; } + BOOST_FORCEINLINE storage_type volatile& storage() volatile BOOST_NOEXCEPT { return this->m_storage.value; } + BOOST_FORCEINLINE storage_type const& storage() const BOOST_NOEXCEPT { return this->m_storage.value; } + BOOST_FORCEINLINE storage_type const volatile& storage() const volatile BOOST_NOEXCEPT { return this->m_storage.value; } + + BOOST_DELETED_FUNCTION(atomic(atomic const&)) + BOOST_DELETED_FUNCTION(atomic& operator= (atomic const&)) + BOOST_DELETED_FUNCTION(atomic& operator= (atomic const&) volatile) +}; + +typedef atomic< char > atomic_char; +typedef atomic< unsigned char > atomic_uchar; +typedef atomic< signed char > atomic_schar; +typedef atomic< uint8_t > atomic_uint8_t; +typedef atomic< int8_t > atomic_int8_t; +typedef atomic< unsigned short > atomic_ushort; +typedef atomic< short > atomic_short; +typedef atomic< uint16_t > atomic_uint16_t; +typedef atomic< int16_t > atomic_int16_t; +typedef atomic< unsigned int > atomic_uint; +typedef atomic< int > atomic_int; +typedef atomic< uint32_t > atomic_uint32_t; +typedef atomic< int32_t > atomic_int32_t; +typedef atomic< unsigned long > atomic_ulong; +typedef atomic< long > atomic_long; +typedef atomic< uint64_t > atomic_uint64_t; +typedef atomic< int64_t > atomic_int64_t; +#ifdef BOOST_HAS_LONG_LONG +typedef atomic< boost::ulong_long_type > atomic_ullong; +typedef atomic< boost::long_long_type > atomic_llong; +#endif +typedef atomic< void* > atomic_address; +typedef atomic< bool > atomic_bool; +typedef atomic< wchar_t > atomic_wchar_t; +#if !defined(BOOST_NO_CXX11_CHAR16_T) +typedef atomic< char16_t > atomic_char16_t; +#endif +#if !defined(BOOST_NO_CXX11_CHAR32_T) +typedef atomic< char32_t > atomic_char32_t; +#endif + +typedef atomic< int_least8_t > atomic_int_least8_t; +typedef atomic< uint_least8_t > atomic_uint_least8_t; +typedef atomic< int_least16_t > atomic_int_least16_t; +typedef atomic< uint_least16_t > atomic_uint_least16_t; +typedef atomic< int_least32_t > atomic_int_least32_t; +typedef atomic< uint_least32_t > atomic_uint_least32_t; +typedef atomic< int_least64_t > atomic_int_least64_t; +typedef atomic< uint_least64_t > atomic_uint_least64_t; +typedef atomic< int_fast8_t > atomic_int_fast8_t; +typedef atomic< uint_fast8_t > atomic_uint_fast8_t; +typedef atomic< int_fast16_t > atomic_int_fast16_t; +typedef atomic< uint_fast16_t > atomic_uint_fast16_t; +typedef atomic< int_fast32_t > atomic_int_fast32_t; +typedef atomic< uint_fast32_t > atomic_uint_fast32_t; +typedef atomic< int_fast64_t > atomic_int_fast64_t; +typedef atomic< uint_fast64_t > atomic_uint_fast64_t; +typedef atomic< intmax_t > atomic_intmax_t; +typedef atomic< uintmax_t > atomic_uintmax_t; + +typedef atomic< std::size_t > atomic_size_t; +typedef atomic< std::ptrdiff_t > atomic_ptrdiff_t; + +#if defined(BOOST_HAS_INTPTR_T) +typedef atomic< intptr_t > atomic_intptr_t; +typedef atomic< uintptr_t > atomic_uintptr_t; +#endif + +} // namespace atomics +} // namespace boost + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif // BOOST_ATOMIC_DETAIL_ATOMIC_TEMPLATE_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/bitwise_cast.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/bitwise_cast.hpp new file mode 100644 index 0000000..8654d10 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/bitwise_cast.hpp @@ -0,0 +1,53 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2013 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/bitwise_cast.hpp + * + * This header defines \c bitwise_cast used to convert between storage and value types + */ + +#ifndef BOOST_ATOMIC_DETAIL_BITWISE_CAST_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_BITWISE_CAST_HPP_INCLUDED_ + +#include +#if !defined(BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCPY) +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< typename To, typename From > +BOOST_FORCEINLINE To bitwise_cast(From const& from) BOOST_NOEXCEPT +{ + struct + { + To to; + } + value = {}; + BOOST_ATOMIC_DETAIL_MEMCPY + ( + &reinterpret_cast< char& >(value.to), + &reinterpret_cast< const char& >(from), + (sizeof(From) < sizeof(To) ? sizeof(From) : sizeof(To)) + ); + return value.to; +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_BITWISE_CAST_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_alpha.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_alpha.hpp new file mode 100644 index 0000000..861432f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_alpha.hpp @@ -0,0 +1,34 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_alpha.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_ALPHA_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_ALPHA_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_ALPHA_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_arm.hpp new file mode 100644 index 0000000..b827c64 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_arm.hpp @@ -0,0 +1,56 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2009 Phil Endecott + * Copyright (c) 2013 Tim Blechmann + * ARM Code by Phil Endecott, based on other architectures. + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_arm.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_ARM_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !(defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6Z__) || defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6ZK__)) +// ARMv7 and later have dmb instruction +#define BOOST_ATOMIC_DETAIL_ARM_HAS_DMB 1 +#endif + +#if !(defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) || defined(__ARM_ARCH_6Z__)) +// ARMv6k and ARMv7 have 8 and 16 ldrex/strex variants +#define BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXB_STREXB 1 +#define BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXH_STREXH 1 +#if !(((defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6ZK__)) && defined(__thumb__)) || defined(__ARM_ARCH_7M__)) +// ARMv6k and ARMv7 except ARMv7-M have 64-bit ldrex/strex variants. +// Unfortunately, GCC (at least 4.7.3 on Ubuntu) does not allocate register pairs properly when targeting ARMv6k Thumb, +// which is required for ldrexd/strexd instructions, so we disable 64-bit support. When targeting ARMv6k ARM +// or ARMv7 (both ARM and Thumb 2) it works as expected. +#define BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXD_STREXD 1 +#endif +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#if defined(BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXD_STREXD) +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#endif +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_atomic.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_atomic.hpp new file mode 100644 index 0000000..f4e7a70 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_atomic.hpp @@ -0,0 +1,134 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_atomic.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_ATOMIC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_ATOMIC_HPP_INCLUDED_ + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__i386__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B 1 +#endif + +#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B 1 +#endif + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) && (defined(BOOST_HAS_INT128) || !defined(BOOST_NO_ALIGNMENT)) +#define BOOST_ATOMIC_INT128_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_INT128_LOCK_FREE 0 +#endif + +#if __GCC_ATOMIC_LLONG_LOCK_FREE == 2 +#define BOOST_ATOMIC_LLONG_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_LLONG_LOCK_FREE BOOST_ATOMIC_INT128_LOCK_FREE +#endif + +#if __GCC_ATOMIC_LONG_LOCK_FREE == 2 +#define BOOST_ATOMIC_LONG_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_LONG_LOCK_FREE BOOST_ATOMIC_LLONG_LOCK_FREE +#endif + +#if __GCC_ATOMIC_INT_LOCK_FREE == 2 +#define BOOST_ATOMIC_INT_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_INT_LOCK_FREE BOOST_ATOMIC_LONG_LOCK_FREE +#endif + +#if __GCC_ATOMIC_SHORT_LOCK_FREE == 2 +#define BOOST_ATOMIC_SHORT_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_SHORT_LOCK_FREE BOOST_ATOMIC_INT_LOCK_FREE +#endif + +#if __GCC_ATOMIC_CHAR_LOCK_FREE == 2 +#define BOOST_ATOMIC_CHAR_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_CHAR_LOCK_FREE BOOST_ATOMIC_SHORT_LOCK_FREE +#endif + +#if __GCC_ATOMIC_POINTER_LOCK_FREE == 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 +#else +#define BOOST_ATOMIC_POINTER_LOCK_FREE 0 +#endif + + +#define BOOST_ATOMIC_INT8_LOCK_FREE BOOST_ATOMIC_CHAR_LOCK_FREE + +#if BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE BOOST_ATOMIC_SHORT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE BOOST_ATOMIC_INT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE BOOST_ATOMIC_LONG_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE BOOST_ATOMIC_LLONG_LOCK_FREE +#else +#define BOOST_ATOMIC_INT16_LOCK_FREE 0 +#endif + +#if BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 4 +#define BOOST_ATOMIC_INT32_LOCK_FREE BOOST_ATOMIC_SHORT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 4 +#define BOOST_ATOMIC_INT32_LOCK_FREE BOOST_ATOMIC_INT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 4 +#define BOOST_ATOMIC_INT32_LOCK_FREE BOOST_ATOMIC_LONG_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 4 +#define BOOST_ATOMIC_INT32_LOCK_FREE BOOST_ATOMIC_LLONG_LOCK_FREE +#else +#define BOOST_ATOMIC_INT32_LOCK_FREE 0 +#endif + +#if BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 8 +#define BOOST_ATOMIC_INT64_LOCK_FREE BOOST_ATOMIC_SHORT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_INT == 8 +#define BOOST_ATOMIC_INT64_LOCK_FREE BOOST_ATOMIC_INT_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 8 +#define BOOST_ATOMIC_INT64_LOCK_FREE BOOST_ATOMIC_LONG_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 8 +#define BOOST_ATOMIC_INT64_LOCK_FREE BOOST_ATOMIC_LLONG_LOCK_FREE +#else +#define BOOST_ATOMIC_INT64_LOCK_FREE 0 +#endif + + +#if __GCC_ATOMIC_WCHAR_T_LOCK_FREE == 2 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE 2 +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 8 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT64_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 4 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 2 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE +#elif BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 1 +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE BOOST_ATOMIC_INT8_LOCK_FREE +#else +#define BOOST_ATOMIC_WCHAR_T_LOCK_FREE 0 +#endif + +#define BOOST_ATOMIC_CHAR32_T_LOCK_FREE BOOST_ATOMIC_INT32_LOCK_FREE +#define BOOST_ATOMIC_CHAR16_T_LOCK_FREE BOOST_ATOMIC_INT16_LOCK_FREE + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_ATOMIC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_ppc.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_ppc.hpp new file mode 100644 index 0000000..ee23460 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_ppc.hpp @@ -0,0 +1,36 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_ppc.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_PPC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_PPC_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#if defined(__powerpc64__) || defined(__PPC64__) +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#endif +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_PPC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sparc.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sparc.hpp new file mode 100644 index 0000000..5806684 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sparc.hpp @@ -0,0 +1,34 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2010 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_sparc.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_SPARC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_SPARC_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_SPARC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sync.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sync.hpp new file mode 100644 index 0000000..7fac07a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_sync.hpp @@ -0,0 +1,62 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_sync.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_SYNC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_SYNC_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__i386__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B 1 +#endif + +#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B 1 +#endif + +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#endif +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#endif +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#endif +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8)\ + || defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#endif +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_INT128_LOCK_FREE 2 +#endif + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_SYNC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_x86.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_x86.hpp new file mode 100644 index 0000000..0696bf1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_gcc_x86.hpp @@ -0,0 +1,52 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2013 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_gcc_x86.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_GCC_X86_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_GCC_X86_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__i386__) &&\ + (\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) ||\ + defined(__i586__) || defined(__i686__) || defined(__pentium4__) || defined(__nocona__) || defined(__core2__) || defined(__corei7__) ||\ + defined(__k6__) || defined(__athlon__) || defined(__k8__) || defined(__amdfam10__) || defined(__bdver1__) || defined(__bdver2__) || defined(__bdver3__) || defined(__btver1__) || defined(__btver2__)\ + ) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B 1 +#endif + +#if defined(__x86_64__) && defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B 1 +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#if defined(__x86_64__) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#endif +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) && (defined(BOOST_HAS_INT128) || !defined(BOOST_NO_ALIGNMENT)) +#define BOOST_ATOMIC_INT128_LOCK_FREE 2 +#endif +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_GCC_X86_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_linux_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_linux_arm.hpp new file mode 100644 index 0000000..abe6fb8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_linux_arm.hpp @@ -0,0 +1,35 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009, 2011 Helge Bahmann + * Copyright (c) 2009 Phil Endecott + * Copyright (c) 2013 Tim Blechmann + * Linux-specific code by Phil Endecott + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_linux_arm.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_LINUX_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_LINUX_ARM_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_LINUX_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_arm.hpp new file mode 100644 index 0000000..6b3c61f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_arm.hpp @@ -0,0 +1,34 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2012 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_msvc_arm.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_MSVC_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_MSVC_ARM_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_MSVC_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_x86.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_x86.hpp new file mode 100644 index 0000000..5661a5b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_msvc_x86.hpp @@ -0,0 +1,50 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2012 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_msvc_x86.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_MSVC_X86_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_MSVC_X86_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(_M_IX86) && _M_IX86 >= 500 +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B 1 +#endif + +#if _MSC_VER >= 1500 && defined(_M_AMD64) && !defined(BOOST_ATOMIC_NO_CMPXCHG16B) +#define BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B 1 +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 + +#if defined(_M_AMD64) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) +#define BOOST_ATOMIC_INT64_LOCK_FREE 2 +#endif + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) && (defined(BOOST_HAS_INT128) || !defined(BOOST_NO_ALIGNMENT)) +#define BOOST_ATOMIC_INT128_LOCK_FREE 2 +#endif + +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_MSVC_X86_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_windows.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_windows.hpp new file mode 100644 index 0000000..1cc0ded --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/caps_windows.hpp @@ -0,0 +1,33 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2012 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/caps_windows.hpp + * + * This header defines feature capabilities macros + */ + +#ifndef BOOST_ATOMIC_DETAIL_CAPS_WINDOWS_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CAPS_WINDOWS_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_INT8_LOCK_FREE 2 +#define BOOST_ATOMIC_INT16_LOCK_FREE 2 +#define BOOST_ATOMIC_INT32_LOCK_FREE 2 +#define BOOST_ATOMIC_POINTER_LOCK_FREE 2 + +#define BOOST_ATOMIC_THREAD_FENCE 2 +#define BOOST_ATOMIC_SIGNAL_FENCE 2 + +#endif // BOOST_ATOMIC_DETAIL_CAPS_WINDOWS_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/config.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/config.hpp new file mode 100644 index 0000000..489281c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/config.hpp @@ -0,0 +1,75 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2012 Hartmut Kaiser + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/config.hpp + * + * This header defines configuraion macros for Boost.Atomic + */ + +#ifndef BOOST_ATOMIC_DETAIL_CONFIG_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_CONFIG_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__has_builtin) +#if __has_builtin(__builtin_memcpy) +#define BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCPY +#endif +#if __has_builtin(__builtin_memcmp) +#define BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCMP +#endif +#elif defined(BOOST_GCC) +#define BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCPY +#define BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCMP +#endif + +#if defined(BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCPY) +#define BOOST_ATOMIC_DETAIL_MEMCPY __builtin_memcpy +#else +#define BOOST_ATOMIC_DETAIL_MEMCPY std::memcpy +#endif + +#if defined(BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCMP) +#define BOOST_ATOMIC_DETAIL_MEMCMP __builtin_memcmp +#else +#define BOOST_ATOMIC_DETAIL_MEMCMP std::memcmp +#endif + +#if defined(__CUDACC__) +// nvcc does not support alternatives in asm statement constraints +#define BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES +// nvcc does not support condition code register ("cc") clobber in asm statements +#define BOOST_ATOMIC_DETAIL_NO_ASM_CLOBBER_CC +#endif + +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CLOBBER_CC) +#define BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC "cc" +#define BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "cc", +#else +#define BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC +#define BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA +#endif + +#if (defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) && (defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) < 403) +// This macro indicates we're using older binutils that don't support implied zero displacements for memory opereands, +// making code like this invalid: +// movl 4+(%%edx), %%eax +#define BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS +#endif + +#if defined(__clang__) || (defined(BOOST_GCC) && (BOOST_GCC+0) < 40500) +// This macro indicates that the compiler does not support allocating rax:rdx register pairs ("A") in asm blocks +#define BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS +#endif + +#endif // BOOST_ATOMIC_DETAIL_CONFIG_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/int_sizes.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/int_sizes.hpp new file mode 100644 index 0000000..eada4ff --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/int_sizes.hpp @@ -0,0 +1,140 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/int_sizes.hpp + * + * This header defines macros for testing buitin integer type sizes + */ + +#ifndef BOOST_ATOMIC_DETAIL_INT_SIZES_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_INT_SIZES_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +// GCC and compatible compilers define internal macros with builtin type traits +#if defined(__SIZEOF_SHORT__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_SHORT __SIZEOF_SHORT__ +#endif +#if defined(__SIZEOF_INT__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_INT __SIZEOF_INT__ +#endif +#if defined(__SIZEOF_LONG__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_LONG __SIZEOF_LONG__ +#endif +#if defined(__SIZEOF_LONG_LONG__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG __SIZEOF_LONG_LONG__ +#endif +#if defined(__SIZEOF_WCHAR_T__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T __SIZEOF_WCHAR_T__ +#endif +#if defined(__SIZEOF_POINTER__) +#define BOOST_ATOMIC_DETAIL_SIZEOF_POINTER __SIZEOF_POINTER__ +#elif defined(_MSC_VER) +#if defined(_M_AMD64) || defined(_M_IA64) +#define BOOST_ATOMIC_DETAIL_SIZEOF_POINTER 8 +#else +#define BOOST_ATOMIC_DETAIL_SIZEOF_POINTER 4 +#endif +#endif + +#if !defined(BOOST_ATOMIC_DETAIL_SIZEOF_SHORT) || !defined(BOOST_ATOMIC_DETAIL_SIZEOF_INT) ||\ + !defined(BOOST_ATOMIC_DETAIL_SIZEOF_LONG) || !defined(BOOST_ATOMIC_DETAIL_SIZEOF_LLONG) + +// Try to deduce sizes from limits +#include +#include + +#if (USHRT_MAX + 0) == 0xff +#define BOOST_ATOMIC_DETAIL_SIZEOF_SHORT 1 +#elif (USHRT_MAX + 0) == 0xffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_SHORT 2 +#elif (USHRT_MAX + 0) == 0xffffffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_SHORT 4 +#elif (USHRT_MAX + 0) == UINT64_C(0xffffffffffffffff) +#define BOOST_ATOMIC_DETAIL_SIZEOF_SHORT 8 +#endif + +#if (UINT_MAX + 0) == 0xff +#define BOOST_ATOMIC_DETAIL_SIZEOF_INT 1 +#elif (UINT_MAX + 0) == 0xffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_INT 2 +#elif (UINT_MAX + 0) == 0xffffffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_INT 4 +#elif (UINT_MAX + 0) == UINT64_C(0xffffffffffffffff) +#define BOOST_ATOMIC_DETAIL_SIZEOF_INT 8 +#endif + +#if (ULONG_MAX + 0) == 0xff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LONG 1 +#elif (ULONG_MAX + 0) == 0xffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LONG 2 +#elif (ULONG_MAX + 0) == 0xffffffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LONG 4 +#elif (ULONG_MAX + 0) == UINT64_C(0xffffffffffffffff) +#define BOOST_ATOMIC_DETAIL_SIZEOF_LONG 8 +#endif + +#if defined(__hpux) // HP-UX's value of ULONG_LONG_MAX is unusable in preprocessor expressions +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG 8 +#else + +// The list of the non-standard macros (the ones except ULLONG_MAX) is taken from cstdint.hpp +#if defined(ULLONG_MAX) +#define BOOST_ATOMIC_DETAIL_ULLONG_MAX ULLONG_MAX +#elif defined(ULONG_LONG_MAX) +#define BOOST_ATOMIC_DETAIL_ULLONG_MAX ULONG_LONG_MAX +#elif defined(ULONGLONG_MAX) +#define BOOST_ATOMIC_DETAIL_ULLONG_MAX ULONGLONG_MAX +#elif defined(_LLONG_MAX) // strangely enough, this one seems to be holding the limit for the unsigned integer +#define BOOST_ATOMIC_DETAIL_ULLONG_MAX _LLONG_MAX +#endif + +#if (BOOST_ATOMIC_DETAIL_ULLONG_MAX + 0) == 0xff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG 1 +#elif (BOOST_ATOMIC_DETAIL_ULLONG_MAX + 0) == 0xffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG 2 +#elif (BOOST_ATOMIC_DETAIL_ULLONG_MAX + 0) == 0xffffffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG 4 +#elif (BOOST_ATOMIC_DETAIL_ULLONG_MAX + 0) == UINT64_C(0xffffffffffffffff) +#define BOOST_ATOMIC_DETAIL_SIZEOF_LLONG 8 +#endif + +#endif // defined(__hpux) + +#endif + +#if !defined(BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T) + +#include +#include + + #if defined(_MSC_VER) && ( _MSC_VER <= 1310 || defined(UNDER_CE) && _MSC_VER <= 1500 ) +// MSVC 7.1 and MSVC 8 (arm) define WCHAR_MAX to a value not suitable for constant expressions +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T 2 +#elif (WCHAR_MAX + 0) == 0xff || (WCHAR_MAX + 0) == 0x7f +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T 1 +#elif (WCHAR_MAX + 0) == 0xffff || (WCHAR_MAX + 0) == 0x7fff +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T 2 +#elif (WCHAR_MAX + 0) == 0xffffffff || (WCHAR_MAX + 0) == 0x7fffffff +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T 4 +#elif (WCHAR_MAX + 0) == UINT64_C(0xffffffffffffffff) || (WCHAR_MAX + 0) == INT64_C(0x7fffffffffffffff) +#define BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T 8 +#endif +#endif + +#if !defined(BOOST_ATOMIC_DETAIL_SIZEOF_SHORT) || !defined(BOOST_ATOMIC_DETAIL_SIZEOF_INT) ||\ + !defined(BOOST_ATOMIC_DETAIL_SIZEOF_LONG) || !defined(BOOST_ATOMIC_DETAIL_SIZEOF_LLONG) ||\ + !defined(BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T) +#error Boost.Atomic: Failed to determine builtin integer sizes, the target platform is not supported. Please, report to the developers. +#endif + +#endif // BOOST_ATOMIC_DETAIL_INT_SIZES_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/interlocked.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/interlocked.hpp new file mode 100644 index 0000000..1c62396 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/interlocked.hpp @@ -0,0 +1,481 @@ +#ifndef BOOST_ATOMIC_DETAIL_INTERLOCKED_HPP +#define BOOST_ATOMIC_DETAIL_INTERLOCKED_HPP + +// Copyright (c) 2009 Helge Bahmann +// Copyright (c) 2012 - 2014 Andrey Semashev +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(_WIN32_WCE) + +#if _WIN32_WCE >= 0x600 + +extern "C" long __cdecl _InterlockedCompareExchange( long volatile *, long, long ); +extern "C" long __cdecl _InterlockedExchangeAdd( long volatile *, long ); +extern "C" long __cdecl _InterlockedExchange( long volatile *, long ); + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) _InterlockedCompareExchange((long*)(dest), exchange, compare) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) _InterlockedExchangeAdd((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) _InterlockedExchange((long*)(dest), (long)(newval)) + +#else // _WIN32_WCE >= 0x600 + +extern "C" long __cdecl InterlockedCompareExchange( long*, long, long ); +extern "C" long __cdecl InterlockedExchangeAdd( long*, long ); +extern "C" long __cdecl InterlockedExchange( long*, long ); + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) InterlockedCompareExchange((long*)(dest), exchange, compare) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) InterlockedExchangeAdd((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) InterlockedExchange((long*)(dest), (long)(newval)) + +#endif // _WIN32_WCE >= 0x600 + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) ((void*)BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE((long*)(dest), (long)(exchange), (long)(compare))) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, exchange) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE((long*)(dest), (long)(exchange))) + +#elif defined(_MSC_VER) && _MSC_VER >= 1310 + +#if _MSC_VER < 1400 + +extern "C" long __cdecl _InterlockedCompareExchange( long volatile *, long, long ); +extern "C" long __cdecl _InterlockedExchangeAdd( long volatile *, long ); +extern "C" long __cdecl _InterlockedExchange( long volatile *, long ); + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedExchangeAdd) +#pragma intrinsic(_InterlockedExchange) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) _InterlockedCompareExchange((long*)(dest), exchange, compare) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) _InterlockedExchangeAdd((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) _InterlockedExchange((long*)(dest), (long)(newval)) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) ((void*)BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE((long*)(dest), (long)(exchange), (long)(compare))) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, exchange) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE((long*)(dest), (long)(exchange))) + +#else // _MSC_VER < 1400 + +#include + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange) +#pragma intrinsic(_InterlockedExchangeAdd) +#pragma intrinsic(_InterlockedExchange) +#pragma intrinsic(_InterlockedAnd) +#pragma intrinsic(_InterlockedOr) +#pragma intrinsic(_InterlockedXor) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) _InterlockedCompareExchange((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) _InterlockedExchangeAdd((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) _InterlockedExchange((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_AND(dest, arg) _InterlockedAnd((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR(dest, arg) _InterlockedOr((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR(dest, arg) _InterlockedXor((long*)(dest), (long)(arg)) + +#if (defined(_M_IX86) && _M_IX86 >= 500) || defined(_M_AMD64) || defined(_M_IA64) +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange64) +#endif +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(dest, exchange, compare) _InterlockedCompareExchange64((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#endif + +#if _MSC_VER >= 1500 && defined(_M_AMD64) +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange128) +#endif +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE128(dest, exchange, compare) _InterlockedCompareExchange128((__int64*)(dest), ((const __int64*)(&exchange))[1], ((const __int64*)(&exchange))[0], (__int64*)(compare)) +#endif + +#if _MSC_VER >= 1600 + +// MSVC 2010 and later provide intrinsics for 8 and 16 bit integers. +// Note that for each bit count these macros must be either all defined or all not defined. +// Otherwise atomic<> operations will be implemented inconsistently. + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange8) +#pragma intrinsic(_InterlockedExchangeAdd8) +#pragma intrinsic(_InterlockedExchange8) +#pragma intrinsic(_InterlockedAnd8) +#pragma intrinsic(_InterlockedOr8) +#pragma intrinsic(_InterlockedXor8) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8(dest, exchange, compare) _InterlockedCompareExchange8((char*)(dest), (char)(exchange), (char)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8(dest, addend) _InterlockedExchangeAdd8((char*)(dest), (char)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE8(dest, newval) _InterlockedExchange8((char*)(dest), (char)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_AND8(dest, arg) _InterlockedAnd8((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR8(dest, arg) _InterlockedOr8((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR8(dest, arg) _InterlockedXor8((char*)(dest), (char)(arg)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange16) +#pragma intrinsic(_InterlockedExchangeAdd16) +#pragma intrinsic(_InterlockedExchange16) +#pragma intrinsic(_InterlockedAnd16) +#pragma intrinsic(_InterlockedOr16) +#pragma intrinsic(_InterlockedXor16) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16(dest, exchange, compare) _InterlockedCompareExchange16((short*)(dest), (short)(exchange), (short)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16(dest, addend) _InterlockedExchangeAdd16((short*)(dest), (short)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE16(dest, newval) _InterlockedExchange16((short*)(dest), (short)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_AND16(dest, arg) _InterlockedAnd16((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR16(dest, arg) _InterlockedOr16((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR16(dest, arg) _InterlockedXor16((short*)(dest), (short)(arg)) + +#endif // _MSC_VER >= 1600 + +#if defined(_M_AMD64) || defined(_M_IA64) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedExchangeAdd64) +#pragma intrinsic(_InterlockedExchange64) +#pragma intrinsic(_InterlockedAnd64) +#pragma intrinsic(_InterlockedOr64) +#pragma intrinsic(_InterlockedXor64) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, addend) _InterlockedExchangeAdd64((__int64*)(dest), (__int64)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(dest, newval) _InterlockedExchange64((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_AND64(dest, arg) _InterlockedAnd64((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR64(dest, arg) _InterlockedOr64((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR64(dest, arg) _InterlockedXor64((__int64*)(dest), (__int64)(arg)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchangePointer) +#pragma intrinsic(_InterlockedExchangePointer) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) _InterlockedCompareExchangePointer((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) _InterlockedExchangePointer((void**)(dest), (void*)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64((long*)(dest), byte_offset)) + +#elif defined(_M_IX86) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) ((void*)_InterlockedCompareExchange((long*)(dest), (long)(exchange), (long)(compare))) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) ((void*)_InterlockedExchange((long*)(dest), (long)(newval))) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD((long*)(dest), byte_offset)) + +#endif + +#if _MSC_VER >= 1700 && defined(_M_ARM) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedExchangeAdd64) +#pragma intrinsic(_InterlockedExchange64) +#pragma intrinsic(_InterlockedAnd64) +#pragma intrinsic(_InterlockedOr64) +#pragma intrinsic(_InterlockedXor64) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, addend) _InterlockedExchangeAdd64((__int64*)(dest), (__int64)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(dest, newval) _InterlockedExchange64((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_AND64(dest, arg) _InterlockedAnd64((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR64(dest, arg) _InterlockedOr64((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR64(dest, arg) _InterlockedXor64((__int64*)(dest), (__int64)(arg)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedCompareExchange8_nf) +#pragma intrinsic(_InterlockedCompareExchange8_acq) +#pragma intrinsic(_InterlockedCompareExchange8_rel) +#pragma intrinsic(_InterlockedCompareExchange16_nf) +#pragma intrinsic(_InterlockedCompareExchange16_acq) +#pragma intrinsic(_InterlockedCompareExchange16_rel) +#pragma intrinsic(_InterlockedCompareExchange_nf) +#pragma intrinsic(_InterlockedCompareExchange_acq) +#pragma intrinsic(_InterlockedCompareExchange_rel) +#pragma intrinsic(_InterlockedCompareExchange64) +#pragma intrinsic(_InterlockedCompareExchange64_nf) +#pragma intrinsic(_InterlockedCompareExchange64_acq) +#pragma intrinsic(_InterlockedCompareExchange64_rel) +#pragma intrinsic(_InterlockedCompareExchangePointer) +#pragma intrinsic(_InterlockedCompareExchangePointer_nf) +#pragma intrinsic(_InterlockedCompareExchangePointer_acq) +#pragma intrinsic(_InterlockedCompareExchangePointer_rel) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_RELAXED(dest, exchange, compare) _InterlockedCompareExchange8_nf((char*)(dest), (char)(exchange), (char)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_ACQUIRE(dest, exchange, compare) _InterlockedCompareExchange8_acq((char*)(dest), (char)(exchange), (char)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_RELEASE(dest, exchange, compare) _InterlockedCompareExchange8_rel((char*)(dest), (char)(exchange), (char)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_RELAXED(dest, exchange, compare) _InterlockedCompareExchange16_nf((short*)(dest), (short)(exchange), (short)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_ACQUIRE(dest, exchange, compare) _InterlockedCompareExchange16_acq((short*)(dest), (short)(exchange), (short)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_RELEASE(dest, exchange, compare) _InterlockedCompareExchange16_rel((short*)(dest), (short)(exchange), (short)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_RELAXED(dest, exchange, compare) _InterlockedCompareExchange_nf((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_ACQUIRE(dest, exchange, compare) _InterlockedCompareExchange_acq((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_RELEASE(dest, exchange, compare) _InterlockedCompareExchange_rel((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(dest, exchange, compare) _InterlockedCompareExchange64((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_RELAXED(dest, exchange, compare) _InterlockedCompareExchange64_nf((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_ACQUIRE(dest, exchange, compare) _InterlockedCompareExchange64_acq((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_RELEASE(dest, exchange, compare) _InterlockedCompareExchange64_rel((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) _InterlockedCompareExchangePointer((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER_RELAXED(dest, exchange, compare) _InterlockedCompareExchangePointer_nf((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER_ACQUIRE(dest, exchange, compare) _InterlockedCompareExchangePointer_acq((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER_RELEASE(dest, exchange, compare) _InterlockedCompareExchangePointer_rel((void**)(dest), (void*)(exchange), (void*)(compare)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedExchangeAdd8_nf) +#pragma intrinsic(_InterlockedExchangeAdd8_acq) +#pragma intrinsic(_InterlockedExchangeAdd8_rel) +#pragma intrinsic(_InterlockedExchangeAdd16_nf) +#pragma intrinsic(_InterlockedExchangeAdd16_acq) +#pragma intrinsic(_InterlockedExchangeAdd16_rel) +#pragma intrinsic(_InterlockedExchangeAdd_nf) +#pragma intrinsic(_InterlockedExchangeAdd_acq) +#pragma intrinsic(_InterlockedExchangeAdd_rel) +#pragma intrinsic(_InterlockedExchangeAdd64_nf) +#pragma intrinsic(_InterlockedExchangeAdd64_acq) +#pragma intrinsic(_InterlockedExchangeAdd64_rel) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_RELAXED(dest, addend) _InterlockedExchangeAdd8_nf((char*)(dest), (char)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_ACQUIRE(dest, addend) _InterlockedExchangeAdd8_acq((char*)(dest), (char)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_RELEASE(dest, addend) _InterlockedExchangeAdd8_rel((char*)(dest), (char)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_RELAXED(dest, addend) _InterlockedExchangeAdd16_nf((short*)(dest), (short)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_ACQUIRE(dest, addend) _InterlockedExchangeAdd16_acq((short*)(dest), (short)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_RELEASE(dest, addend) _InterlockedExchangeAdd16_rel((short*)(dest), (short)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELAXED(dest, addend) _InterlockedExchangeAdd_nf((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_ACQUIRE(dest, addend) _InterlockedExchangeAdd_acq((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELEASE(dest, addend) _InterlockedExchangeAdd_rel((long*)(dest), (long)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_RELAXED(dest, addend) _InterlockedExchangeAdd64_nf((__int64*)(dest), (__int64)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_ACQUIRE(dest, addend) _InterlockedExchangeAdd64_acq((__int64*)(dest), (__int64)(addend)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_RELEASE(dest, addend) _InterlockedExchangeAdd64_rel((__int64*)(dest), (__int64)(addend)) + +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD((long*)(dest), byte_offset)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER_RELAXED(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELAXED((long*)(dest), byte_offset)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER_ACQUIRE(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_ACQUIRE((long*)(dest), byte_offset)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER_RELEASE(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELEASE((long*)(dest), byte_offset)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedExchange8_nf) +#pragma intrinsic(_InterlockedExchange8_acq) +#pragma intrinsic(_InterlockedExchange16_nf) +#pragma intrinsic(_InterlockedExchange16_acq) +#pragma intrinsic(_InterlockedExchange_nf) +#pragma intrinsic(_InterlockedExchange_acq) +#pragma intrinsic(_InterlockedExchange64_nf) +#pragma intrinsic(_InterlockedExchange64_acq) +#pragma intrinsic(_InterlockedExchangePointer) +#pragma intrinsic(_InterlockedExchangePointer_nf) +#pragma intrinsic(_InterlockedExchangePointer_acq) +#if _MSC_VER >= 1800 +#pragma intrinsic(_InterlockedExchange8_rel) +#pragma intrinsic(_InterlockedExchange16_rel) +#pragma intrinsic(_InterlockedExchange_rel) +#pragma intrinsic(_InterlockedExchange64_rel) +#pragma intrinsic(_InterlockedExchangePointer_rel) +#endif +#endif + +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_RELAXED(dest, newval) _InterlockedExchange8_nf((char*)(dest), (char)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_ACQUIRE(dest, newval) _InterlockedExchange8_acq((char*)(dest), (char)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_RELAXED(dest, newval) _InterlockedExchange16_nf((short*)(dest), (short)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_ACQUIRE(dest, newval) _InterlockedExchange16_acq((short*)(dest), (short)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_RELAXED(dest, newval) _InterlockedExchange_nf((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ACQUIRE(dest, newval) _InterlockedExchange_acq((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_RELAXED(dest, newval) _InterlockedExchange64_nf((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_ACQUIRE(dest, newval) _InterlockedExchange64_acq((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) _InterlockedExchangePointer((void**)(dest), (void*)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER_RELAXED(dest, newval) _InterlockedExchangePointer_nf((void**)(dest), (void*)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER_ACQUIRE(dest, newval) _InterlockedExchangePointer_acq((void**)(dest), (void*)(newval)) + +#if _MSC_VER >= 1800 +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_RELEASE(dest, newval) _InterlockedExchange8_rel((char*)(dest), (char)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_RELEASE(dest, newval) _InterlockedExchange16_rel((short*)(dest), (short)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_RELEASE(dest, newval) _InterlockedExchange_rel((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_RELEASE(dest, newval) _InterlockedExchange64_rel((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER_RELEASE(dest, newval) _InterlockedExchangePointer_rel((void**)(dest), (void*)(newval)) +#else +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_RELEASE(dest, newval) BOOST_ATOMIC_INTERLOCKED_EXCHANGE8(dest, newval) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_RELEASE(dest, newval) BOOST_ATOMIC_INTERLOCKED_EXCHANGE16(dest, newval) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_RELEASE(dest, newval) BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_RELEASE(dest, newval) BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(dest, newval) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER_RELEASE(dest, newval) BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) +#endif + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedAnd8_nf) +#pragma intrinsic(_InterlockedAnd8_acq) +#pragma intrinsic(_InterlockedAnd8_rel) +#pragma intrinsic(_InterlockedAnd16_nf) +#pragma intrinsic(_InterlockedAnd16_acq) +#pragma intrinsic(_InterlockedAnd16_rel) +#pragma intrinsic(_InterlockedAnd_nf) +#pragma intrinsic(_InterlockedAnd_acq) +#pragma intrinsic(_InterlockedAnd_rel) +#pragma intrinsic(_InterlockedAnd64_nf) +#pragma intrinsic(_InterlockedAnd64_acq) +#pragma intrinsic(_InterlockedAnd64_rel) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_AND8_RELAXED(dest, arg) _InterlockedAnd8_nf((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND8_ACQUIRE(dest, arg) _InterlockedAnd8_acq((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND8_RELEASE(dest, arg) _InterlockedAnd8_rel((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND16_RELAXED(dest, arg) _InterlockedAnd16_nf((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND16_ACQUIRE(dest, arg) _InterlockedAnd16_acq((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND16_RELEASE(dest, arg) _InterlockedAnd16_rel((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND_RELAXED(dest, arg) _InterlockedAnd_nf((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND_ACQUIRE(dest, arg) _InterlockedAnd_acq((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND_RELEASE(dest, arg) _InterlockedAnd_rel((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND64_RELAXED(dest, arg) _InterlockedAnd64_nf((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND64_ACQUIRE(dest, arg) _InterlockedAnd64_acq((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_AND64_RELEASE(dest, arg) _InterlockedAnd64_rel((__int64*)(dest), (__int64)(arg)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedOr8_nf) +#pragma intrinsic(_InterlockedOr8_acq) +#pragma intrinsic(_InterlockedOr8_rel) +#pragma intrinsic(_InterlockedOr16_nf) +#pragma intrinsic(_InterlockedOr16_acq) +#pragma intrinsic(_InterlockedOr16_rel) +#pragma intrinsic(_InterlockedOr_nf) +#pragma intrinsic(_InterlockedOr_acq) +#pragma intrinsic(_InterlockedOr_rel) +#pragma intrinsic(_InterlockedOr64_nf) +#pragma intrinsic(_InterlockedOr64_acq) +#pragma intrinsic(_InterlockedOr64_rel) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_OR8_RELAXED(dest, arg) _InterlockedOr8_nf((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR8_ACQUIRE(dest, arg) _InterlockedOr8_acq((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR8_RELEASE(dest, arg) _InterlockedOr8_rel((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR16_RELAXED(dest, arg) _InterlockedOr16_nf((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR16_ACQUIRE(dest, arg) _InterlockedOr16_acq((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR16_RELEASE(dest, arg) _InterlockedOr16_rel((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR_RELAXED(dest, arg) _InterlockedOr_nf((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR_ACQUIRE(dest, arg) _InterlockedOr_acq((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR_RELEASE(dest, arg) _InterlockedOr_rel((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR64_RELAXED(dest, arg) _InterlockedOr64_nf((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR64_ACQUIRE(dest, arg) _InterlockedOr64_acq((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_OR64_RELEASE(dest, arg) _InterlockedOr64_rel((__int64*)(dest), (__int64)(arg)) + +#if defined(BOOST_MSVC) +#pragma intrinsic(_InterlockedXor8_nf) +#pragma intrinsic(_InterlockedXor8_acq) +#pragma intrinsic(_InterlockedXor8_rel) +#pragma intrinsic(_InterlockedXor16_nf) +#pragma intrinsic(_InterlockedXor16_acq) +#pragma intrinsic(_InterlockedXor16_rel) +#pragma intrinsic(_InterlockedXor_nf) +#pragma intrinsic(_InterlockedXor_acq) +#pragma intrinsic(_InterlockedXor_rel) +#pragma intrinsic(_InterlockedXor64_nf) +#pragma intrinsic(_InterlockedXor64_acq) +#pragma intrinsic(_InterlockedXor64_rel) +#endif + +#define BOOST_ATOMIC_INTERLOCKED_XOR8_RELAXED(dest, arg) _InterlockedXor8_nf((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR8_ACQUIRE(dest, arg) _InterlockedXor8_acq((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR8_RELEASE(dest, arg) _InterlockedXor8_rel((char*)(dest), (char)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR16_RELAXED(dest, arg) _InterlockedXor16_nf((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR16_ACQUIRE(dest, arg) _InterlockedXor16_acq((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR16_RELEASE(dest, arg) _InterlockedXor16_rel((short*)(dest), (short)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR_RELAXED(dest, arg) _InterlockedXor_nf((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR_ACQUIRE(dest, arg) _InterlockedXor_acq((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR_RELEASE(dest, arg) _InterlockedXor_rel((long*)(dest), (long)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR64_RELAXED(dest, arg) _InterlockedXor64_nf((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR64_ACQUIRE(dest, arg) _InterlockedXor64_acq((__int64*)(dest), (__int64)(arg)) +#define BOOST_ATOMIC_INTERLOCKED_XOR64_RELEASE(dest, arg) _InterlockedXor64_rel((__int64*)(dest), (__int64)(arg)) + +#endif // _MSC_VER >= 1700 && defined(_M_ARM) + +#endif // _MSC_VER < 1400 + +#else // defined(_MSC_VER) && _MSC_VER >= 1310 + +#if defined(BOOST_USE_WINDOWS_H) + +#include + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) InterlockedCompareExchange((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) InterlockedExchange((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) InterlockedExchangeAdd((long*)(dest), (long)(addend)) + +#if defined(_WIN64) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(dest, exchange, compare) InterlockedCompareExchange64((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(dest, newval) InterlockedExchange64((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, addend) InterlockedExchangeAdd64((__int64*)(dest), (__int64)(addend)) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) InterlockedCompareExchangePointer((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) InterlockedExchangePointer((void**)(dest), (void*)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, byte_offset)) + +#else // defined(_WIN64) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) ((void*)BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, byte_offset)) + +#endif // defined(_WIN64) + +#else // defined(BOOST_USE_WINDOWS_H) + +#if defined(__MINGW64__) +#define BOOST_ATOMIC_INTERLOCKED_IMPORT +#else +#define BOOST_ATOMIC_INTERLOCKED_IMPORT __declspec(dllimport) +#endif + +namespace boost { +namespace atomics { +namespace detail { + +extern "C" { + +BOOST_ATOMIC_INTERLOCKED_IMPORT long __stdcall InterlockedCompareExchange(long volatile*, long, long); +BOOST_ATOMIC_INTERLOCKED_IMPORT long __stdcall InterlockedExchange(long volatile*, long); +BOOST_ATOMIC_INTERLOCKED_IMPORT long __stdcall InterlockedExchangeAdd(long volatile*, long); + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare) boost::atomics::detail::InterlockedCompareExchange((long*)(dest), (long)(exchange), (long)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval) boost::atomics::detail::InterlockedExchange((long*)(dest), (long)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, addend) boost::atomics::detail::InterlockedExchangeAdd((long*)(dest), (long)(addend)) + +#if defined(_WIN64) + +BOOST_ATOMIC_INTERLOCKED_IMPORT __int64 __stdcall InterlockedCompareExchange64(__int64 volatile*, __int64, __int64); +BOOST_ATOMIC_INTERLOCKED_IMPORT __int64 __stdcall InterlockedExchange64(__int64 volatile*, __int64); +BOOST_ATOMIC_INTERLOCKED_IMPORT __int64 __stdcall InterlockedExchangeAdd64(__int64 volatile*, __int64); + +BOOST_ATOMIC_INTERLOCKED_IMPORT void* __stdcall InterlockedCompareExchangePointer(void* volatile *, void*, void*); +BOOST_ATOMIC_INTERLOCKED_IMPORT void* __stdcall InterlockedExchangePointer(void* volatile *, void*); + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(dest, exchange, compare) boost::atomics::detail::InterlockedCompareExchange64((__int64*)(dest), (__int64)(exchange), (__int64)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(dest, newval) boost::atomics::detail::InterlockedExchange64((__int64*)(dest), (__int64)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, addend) boost::atomics::detail::InterlockedExchangeAdd64((__int64*)(dest), (__int64)(addend)) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) boost::atomics::detail::InterlockedCompareExchangePointer((void**)(dest), (void*)(exchange), (void*)(compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) boost::atomics::detail::InterlockedExchangePointer((void**)(dest), (void*)(newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(dest, byte_offset)) + +#else // defined(_WIN64) + +#define BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest, exchange, compare) ((void*)BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(dest, exchange, compare)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_POINTER(dest, newval) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE(dest, newval)) +#define BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_POINTER(dest, byte_offset) ((void*)BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(dest, byte_offset)) + +#endif // defined(_WIN64) + +} // extern "C" + +} // namespace detail +} // namespace atomics +} // namespace boost + +#undef BOOST_ATOMIC_INTERLOCKED_IMPORT + +#endif // defined(BOOST_USE_WINDOWS_H) + +#endif // defined(_MSC_VER) + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/link.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/link.hpp new file mode 100644 index 0000000..4f522ac --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/link.hpp @@ -0,0 +1,58 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2012 Hartmut Kaiser + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/config.hpp + * + * This header defines macros for linking with compiled library of Boost.Atomic + */ + +#ifndef BOOST_ATOMIC_DETAIL_LINK_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_LINK_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Set up dll import/export options +#if (defined(BOOST_ATOMIC_DYN_LINK) || defined(BOOST_ALL_DYN_LINK)) && \ + !defined(BOOST_ATOMIC_STATIC_LINK) + +#if defined(BOOST_ATOMIC_SOURCE) +#define BOOST_ATOMIC_DECL BOOST_SYMBOL_EXPORT +#define BOOST_ATOMIC_BUILD_DLL +#else +#define BOOST_ATOMIC_DECL BOOST_SYMBOL_IMPORT +#endif + +#endif // building a shared library + +#ifndef BOOST_ATOMIC_DECL +#define BOOST_ATOMIC_DECL +#endif + +/////////////////////////////////////////////////////////////////////////////// +// Auto library naming +#if !defined(BOOST_ATOMIC_SOURCE) && !defined(BOOST_ALL_NO_LIB) && \ + !defined(BOOST_ATOMIC_NO_LIB) + +#define BOOST_LIB_NAME boost_atomic + +// tell the auto-link code to select a dll when required: +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_ATOMIC_DYN_LINK) +#define BOOST_DYN_LINK +#endif + +#include + +#endif // auto-linking disabled + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/lockpool.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/lockpool.hpp new file mode 100644 index 0000000..4e249aa --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/lockpool.hpp @@ -0,0 +1,51 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013-2014 Andrey Semashev + */ +/*! + * \file atomic/detail/lockpool.hpp + * + * This header contains declaration of the lockpool used to emulate atomic ops. + */ + +#ifndef BOOST_ATOMIC_DETAIL_LOCKPOOL_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_LOCKPOOL_HPP_INCLUDED_ + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +struct lockpool +{ + class scoped_lock + { + void* m_lock; + + public: + explicit BOOST_ATOMIC_DECL scoped_lock(const volatile void* addr) BOOST_NOEXCEPT; + BOOST_ATOMIC_DECL ~scoped_lock() BOOST_NOEXCEPT; + + BOOST_DELETED_FUNCTION(scoped_lock(scoped_lock const&)) + BOOST_DELETED_FUNCTION(scoped_lock& operator=(scoped_lock const&)) + }; + + static BOOST_ATOMIC_DECL void thread_fence() BOOST_NOEXCEPT; + static BOOST_ATOMIC_DECL void signal_fence() BOOST_NOEXCEPT; +}; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_LOCKPOOL_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations.hpp new file mode 100644 index 0000000..d81399a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations.hpp @@ -0,0 +1,24 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/operations.hpp + * + * This header defines atomic operations, including the emulated version. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPERATIONS_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPERATIONS_HPP_INCLUDED_ + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#endif // BOOST_ATOMIC_DETAIL_OPERATIONS_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_fwd.hpp new file mode 100644 index 0000000..efd4970 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_fwd.hpp @@ -0,0 +1,35 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/operations_fwd.hpp + * + * This header contains forward declaration of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPERATIONS_FWD_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPERATIONS_FWD_HPP_INCLUDED_ + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< std::size_t Size, bool Signed > +struct operations; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPERATIONS_FWD_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_lockfree.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_lockfree.hpp new file mode 100644 index 0000000..b465403 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/operations_lockfree.hpp @@ -0,0 +1,30 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/operations_lockfree.hpp + * + * This header defines lockfree atomic operations. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPERATIONS_LOCKFREE_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPERATIONS_LOCKFREE_HPP_INCLUDED_ + +#include +#include + +#if !defined(BOOST_ATOMIC_EMULATED) +#include BOOST_ATOMIC_DETAIL_HEADER(boost/atomic/detail/ops_) +#else +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#endif // BOOST_ATOMIC_DETAIL_OPERATIONS_LOCKFREE_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_cas_based.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_cas_based.hpp new file mode 100644 index 0000000..504cedb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_cas_based.hpp @@ -0,0 +1,105 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_cas_based.hpp + * + * This header contains CAS-based implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_CAS_BASED_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_CAS_BASED_HPP_INCLUDED_ + +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< typename Base > +struct cas_based_exchange : + public Base +{ + typedef typename Base::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, v, order, memory_order_relaxed)) {} + return old_val; + } +}; + +template< typename Base > +struct cas_based_operations : + public Base +{ + typedef typename Base::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, old_val + v, order, memory_order_relaxed)) {} + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, old_val - v, order, memory_order_relaxed)) {} + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, old_val & v, order, memory_order_relaxed)) {} + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, old_val | v, order, memory_order_relaxed)) {} + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + while (!Base::compare_exchange_weak(storage, old_val, old_val ^ v, order, memory_order_relaxed)) {} + return old_val; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!Base::exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + Base::store(storage, (storage_type)0, order); + } +}; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_CAS_BASED_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_emulated.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_emulated.hpp new file mode 100644 index 0000000..1703291 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_emulated.hpp @@ -0,0 +1,161 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_emulated.hpp + * + * This header contains lockpool-based implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_EMULATED_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_EMULATED_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< typename T > +struct emulated_operations +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + lockpool::scoped_lock lock(&storage); + const_cast< storage_type& >(storage) = v; + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order) BOOST_NOEXCEPT + { + lockpool::scoped_lock lock(&storage); + return const_cast< storage_type const& >(storage); + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s += v; + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s -= v; + return old_val; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s = v; + return old_val; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + const bool res = old_val == expected; + if (res) + s = desired; + expected = old_val; + + return res; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + // Note: This function is the exact copy of compare_exchange_strong. The reason we're not just forwarding the call + // is that MSVC-12 ICEs in this case. + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + const bool res = old_val == expected; + if (res) + s = desired; + expected = old_val; + + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s &= v; + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s |= v; + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type& s = const_cast< storage_type& >(storage); + lockpool::scoped_lock lock(&storage); + storage_type old_val = s; + s ^= v; + return old_val; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, (storage_type)0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return false; + } +}; + +template< std::size_t Size, bool Signed > +struct operations : + public emulated_operations< typename make_storage_type< Size, Signed >::type > +{ + typedef typename make_storage_type< Size, Signed >::aligned aligned_storage_type; +}; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_EMULATED_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_extending_cas_based.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_extending_cas_based.hpp new file mode 100644 index 0000000..3f21031 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_extending_cas_based.hpp @@ -0,0 +1,68 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_extending_cas_based.hpp + * + * This header contains a boilerplate of the \c operations template implementation that requires sign/zero extension in arithmetic operations. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_EXTENDING_CAS_BASED_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_EXTENDING_CAS_BASED_HPP_INCLUDED_ + +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< typename Base, std::size_t Size, bool Signed > +struct extending_cas_based_operations : + public Base +{ + typedef typename Base::storage_type storage_type; + typedef typename make_storage_type< Size, Signed >::type emulated_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + emulated_storage_type new_val; + do + { + new_val = static_cast< emulated_storage_type >(old_val) + static_cast< emulated_storage_type >(v); + } + while (!Base::compare_exchange_weak(storage, old_val, static_cast< storage_type >(new_val), order, memory_order_relaxed)); + return old_val; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type old_val; + atomics::detail::non_atomic_load(storage, old_val); + emulated_storage_type new_val; + do + { + new_val = static_cast< emulated_storage_type >(old_val) - static_cast< emulated_storage_type >(v); + } + while (!Base::compare_exchange_weak(storage, old_val, static_cast< storage_type >(new_val), order, memory_order_relaxed)); + return old_val; + } +}; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_EXTENDING_CAS_BASED_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_alpha.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_alpha.hpp new file mode 100644 index 0000000..3c0e258 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_alpha.hpp @@ -0,0 +1,876 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_alpha.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_ALPHA_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_ALPHA_HPP_INCLUDED_ + +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +/* + Refer to http://h71000.www7.hp.com/doc/82final/5601/5601pro_004.html + (HP OpenVMS systems documentation) and the Alpha Architecture Reference Manual. + */ + +/* + NB: The most natural thing would be to write the increment/decrement + operators along the following lines: + + __asm__ __volatile__ + ( + "1: ldl_l %0,%1 \n" + "addl %0,1,%0 \n" + "stl_c %0,%1 \n" + "beq %0,1b\n" + : "=&b" (tmp) + : "m" (value) + : "cc" + ); + + However according to the comments on the HP website and matching + comments in the Linux kernel sources this defies branch prediction, + as the cpu assumes that backward branches are always taken; so + instead copy the trick from the Linux kernel, introduce a forward + branch and back again. + + I have, however, had a hard time measuring the difference between + the two versions in microbenchmarks -- I am leaving it in nevertheless + as it apparently does not hurt either. +*/ + +struct gcc_alpha_operations_base +{ + static BOOST_FORCEINLINE void fence_before(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + __asm__ __volatile__ ("mb" ::: "memory"); + } + + static BOOST_FORCEINLINE void fence_after(memory_order order) BOOST_NOEXCEPT + { + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + __asm__ __volatile__ ("mb" ::: "memory"); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("mb" ::: "memory"); + } +}; + + +template< bool Signed > +struct operations< 4u, Signed > : + public gcc_alpha_operations_base +{ + typedef typename make_storage_type< 4u, Signed >::type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "mov %3, %1\n" + "ldl_l %0, %2\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (tmp) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + int success; + storage_type current; + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %2, %4\n" // current = *(&storage) + "cmpeq %2, %0, %3\n" // success = current == expected + "mov %2, %0\n" // expected = current + "beq %3, 2f\n" // if (success == 0) goto end + "stl_c %1, %4\n" // storage = desired; desired = store succeeded + "mov %1, %3\n" // success = desired + "2:\n" + : "+&r" (expected), // %0 + "+&r" (desired), // %1 + "=&r" (current), // %2 + "=&r" (success) // %3 + : "m" (storage) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + storage_type current, tmp; + fence_before(success_order); + __asm__ __volatile__ + ( + "1:\n" + "mov %5, %1\n" // tmp = desired + "ldl_l %2, %4\n" // current = *(&storage) + "cmpeq %2, %0, %3\n" // success = current == expected + "mov %2, %0\n" // expected = current + "beq %3, 2f\n" // if (success == 0) goto end + "stl_c %1, %4\n" // storage = tmp; tmp = store succeeded + "beq %1, 3f\n" // if (tmp == 0) goto retry + "mov %1, %3\n" // success = tmp + "2:\n" + + ".subsection 2\n" + "3: br 1b\n" + ".previous\n" + + : "+&r" (expected), // %0 + "=&r" (tmp), // %1 + "=&r" (current), // %2 + "=&r" (success) // %3 + : "m" (storage), // %4 + "r" (desired) // %5 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "addl %0, %3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "subl %0, %3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "and %0, %3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "bis %0, %3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "xor %0, %3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + + +template< > +struct operations< 1u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "addl %0, %3, %1\n" + "zapnot %1, #1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "subl %0, %3, %1\n" + "zapnot %1, #1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 1u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "addl %0, %3, %1\n" + "sextb %1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "subl %0, %3, %1\n" + "sextb %1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +template< > +struct operations< 2u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "addl %0, %3, %1\n" + "zapnot %1, #3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "subl %0, %3, %1\n" + "zapnot %1, #3, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 2u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "addl %0, %3, %1\n" + "sextw %1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldl_l %0, %2\n" + "subl %0, %3, %1\n" + "sextw %1, %1\n" + "stl_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +template< bool Signed > +struct operations< 8u, Signed > : + public gcc_alpha_operations_base +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "mov %3, %1\n" + "ldq_l %0, %2\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (tmp) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + int success; + storage_type current; + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %2, %4\n" // current = *(&storage) + "cmpeq %2, %0, %3\n" // success = current == expected + "mov %2, %0\n" // expected = current + "beq %3, 2f\n" // if (success == 0) goto end + "stq_c %1, %4\n" // storage = desired; desired = store succeeded + "mov %1, %3\n" // success = desired + "2:\n" + : "+&r" (expected), // %0 + "+&r" (desired), // %1 + "=&r" (current), // %2 + "=&r" (success) // %3 + : "m" (storage) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + storage_type current, tmp; + fence_before(success_order); + __asm__ __volatile__ + ( + "1:\n" + "mov %5, %1\n" // tmp = desired + "ldq_l %2, %4\n" // current = *(&storage) + "cmpeq %2, %0, %3\n" // success = current == expected + "mov %2, %0\n" // expected = current + "beq %3, 2f\n" // if (success == 0) goto end + "stq_c %1, %4\n" // storage = tmp; tmp = store succeeded + "beq %1, 3f\n" // if (tmp == 0) goto retry + "mov %1, %3\n" // success = tmp + "2:\n" + + ".subsection 2\n" + "3: br 1b\n" + ".previous\n" + + : "+&r" (expected), // %0 + "=&r" (tmp), // %1 + "=&r" (current), // %2 + "=&r" (success) // %3 + : "m" (storage), // %4 + "r" (desired) // %5 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %0, %2\n" + "addq %0, %3, %1\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %0, %2\n" + "subq %0, %3, %1\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %0, %2\n" + "and %0, %3, %1\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %0, %2\n" + "bis %0, %3, %1\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, modified; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n" + "ldq_l %0, %2\n" + "xor %0, %3, %1\n" + "stq_c %1, %2\n" + "beq %1, 2f\n" + + ".subsection 2\n" + "2: br 1b\n" + ".previous\n" + + : "=&r" (original), // %0 + "=&r" (modified) // %1 + : "m" (storage), // %2 + "r" (v) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("mb" ::: "memory"); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_ALPHA_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_arm.hpp new file mode 100644 index 0000000..d2c2f39 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_arm.hpp @@ -0,0 +1,973 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_arm.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_ARM_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +// From the ARM Architecture Reference Manual for architecture v6: +// +// LDREX{} , [] +// Specifies the destination register for the memory word addressed by +// Specifies the register containing the address. +// +// STREX{} , , [] +// Specifies the destination register for the returned status value. +// 0 if the operation updates memory +// 1 if the operation fails to update memory +// Specifies the register containing the word to be stored to memory. +// Specifies the register containing the address. +// Rd must not be the same register as Rm or Rn. +// +// ARM v7 is like ARM v6 plus: +// There are half-word and byte versions of the LDREX and STREX instructions, +// LDREXH, LDREXB, STREXH and STREXB. +// There are also double-word versions, LDREXD and STREXD. +// (Actually it looks like these are available from version 6k onwards.) +// FIXME these are not yet used; should be mostly a matter of copy-and-paste. +// I think you can supply an immediate offset to the address. +// +// A memory barrier is effected using a "co-processor 15" instruction, +// though a separate assembler mnemonic is available for it in v7. +// +// "Thumb 1" is a subset of the ARM instruction set that uses a 16-bit encoding. It +// doesn't include all instructions and in particular it doesn't include the co-processor +// instruction used for the memory barrier or the load-locked/store-conditional +// instructions. So, if we're compiling in "Thumb 1" mode, we need to wrap all of our +// asm blocks with code to temporarily change to ARM mode. +// +// You can only change between ARM and Thumb modes when branching using the bx instruction. +// bx takes an address specified in a register. The least significant bit of the address +// indicates the mode, so 1 is added to indicate that the destination code is Thumb. +// A temporary register is needed for the address and is passed as an argument to these +// macros. It must be one of the "low" registers accessible to Thumb code, specified +// using the "l" attribute in the asm statement. +// +// Architecture v7 introduces "Thumb 2", which does include (almost?) all of the ARM +// instruction set. (Actually, there was an extension of v6 called v6T2 which supported +// "Thumb 2" mode, but its architecture manual is no longer available, referring to v7.) +// So in v7 we don't need to change to ARM mode; we can write "universal +// assembler" which will assemble to Thumb 2 or ARM code as appropriate. The only thing +// we need to do to make this "universal" assembler mode work is to insert "IT" instructions +// to annotate the conditional instructions. These are ignored in other modes (e.g. v6), +// so they can always be present. + +// A note about memory_order_consume. Technically, this architecture allows to avoid +// unnecessary memory barrier after consume load since it supports data dependency ordering. +// However, some compiler optimizations may break a seemingly valid code relying on data +// dependency tracking by injecting bogus branches to aid out of order execution. +// This may happen not only in Boost.Atomic code but also in user's code, which we have no +// control of. See this thread: http://lists.boost.org/Archives/boost/2014/06/213890.php. +// For this reason we promote memory_order_consume to memory_order_acquire. + +#if defined(__thumb__) && !defined(__thumb2__) +#define BOOST_ATOMIC_DETAIL_ARM_ASM_START(TMPREG) "adr " #TMPREG ", 8f\n" "bx " #TMPREG "\n" ".arm\n" ".align 4\n" "8:\n" +#define BOOST_ATOMIC_DETAIL_ARM_ASM_END(TMPREG) "adr " #TMPREG ", 9f + 1\n" "bx " #TMPREG "\n" ".thumb\n" ".align 2\n" "9:\n" +#define BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(var) "=&l" (var) +#else +// The tmpreg may be wasted in this case, which is non-optimal. +#define BOOST_ATOMIC_DETAIL_ARM_ASM_START(TMPREG) +#define BOOST_ATOMIC_DETAIL_ARM_ASM_END(TMPREG) +#define BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(var) "=&r" (var) +#endif + +struct gcc_arm_operations_base +{ + static BOOST_FORCEINLINE void fence_before(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void fence_after(memory_order order) BOOST_NOEXCEPT + { + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void hardware_full_fence() BOOST_NOEXCEPT + { +#if defined(BOOST_ATOMIC_DETAIL_ARM_HAS_DMB) + // Older binutils (supposedly, older than 2.21.1) didn't support symbolic or numeric arguments of the "dmb" instruction such as "ish" or "#11". + // As a workaround we have to inject encoded bytes of the instruction. There are two encodings for the instruction: ARM and Thumb. See ARM Architecture Reference Manual, A8.8.43. + // Since we cannot detect binutils version at compile time, we'll have to always use this hack. + __asm__ __volatile__ + ( +#if defined(__thumb2__) + ".short 0xF3BF, 0x8F5B\n" // dmb ish +#else + ".word 0xF57FF05B\n" // dmb ish +#endif + : + : + : "memory" + ); +#else + int tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "mcr\tp15, 0, r0, c7, c10, 5\n" + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : "=&l" (tmp) + : + : "memory" + ); +#endif + } +}; + + +template< bool Signed > +struct operations< 4u, Signed > : + public gcc_arm_operations_base +{ + typedef typename make_storage_type< 4u, Signed >::type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original; + fence_before(order); + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // load the original value + "strex %[tmp], %[value], %[storage]\n" // store the replacement, tmp = store failed + "teq %[tmp], #0\n" // check if store succeeded + "bne 1b\n" + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [tmp] "=&l" (tmp), [original] "=&r" (original), [storage] "+Q" (storage) + : [value] "r" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + uint32_t success; + uint32_t tmp; + storage_type original; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "mov %[success], #0\n" // success = 0 + "ldrex %[original], %[storage]\n" // original = *(&storage) + "cmp %[original], %[expected]\n" // flags = original==expected + "itt eq\n" // [hint that the following 2 instructions are conditional on flags.equal] + "strexeq %[success], %[desired], %[storage]\n" // if (flags.equal) *(&storage) = desired, success = store failed + "eoreq %[success], %[success], #1\n" // if (flags.equal) success ^= 1 (i.e. make it 1 if store succeeded) + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [success] "=&r" (success), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [expected] "r" (expected), // %4 + [desired] "r" (desired) // %5 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = original; + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + uint32_t success; + uint32_t tmp; + storage_type original; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "mov %[success], #0\n" // success = 0 + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "cmp %[original], %[expected]\n" // flags = original==expected + "bne 2f\n" // if (!flags.equal) goto end + "strex %[success], %[desired], %[storage]\n" // *(&storage) = desired, success = store failed + "eors %[success], %[success], #1\n" // success ^= 1 (i.e. make it 1 if store succeeded); flags.equal = success == 0 + "beq 1b\n" // if (flags.equal) goto retry + "2:\n" + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [success] "=&r" (success), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [expected] "r" (expected), // %4 + [desired] "r" (desired) // %5 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = original; + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "add %[result], %[original], %[value]\n" // result = original + value + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "sub %[result], %[original], %[value]\n" // result = original - value + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "and %[result], %[original], %[value]\n" // result = original & value + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "orr %[result], %[original], %[value]\n" // result = original | value + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "eor %[result], %[original], %[value]\n" // result = original ^ value + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + + +template< > +struct operations< 1u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "add %[result], %[original], %[value]\n" // result = original + value + "uxtb %[result], %[result]\n" // zero extend result from 8 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "sub %[result], %[original], %[value]\n" // result = original - value + "uxtb %[result], %[result]\n" // zero extend result from 8 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 1u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "add %[result], %[original], %[value]\n" // result = original + value + "sxtb %[result], %[result]\n" // sign extend result from 8 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "sub %[result], %[original], %[value]\n" // result = original - value + "sxtb %[result], %[result]\n" // sign extend result from 8 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +template< > +struct operations< 2u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "add %[result], %[original], %[value]\n" // result = original + value + "uxth %[result], %[result]\n" // zero extend result from 16 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "sub %[result], %[original], %[value]\n" // result = original - value + "uxth %[result], %[result]\n" // zero extend result from 16 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 2u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "add %[result], %[original], %[value]\n" // result = original + value + "sxth %[result], %[result]\n" // sign extend result from 16 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + uint32_t tmp; + storage_type original, result; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%[tmp]) + "1:\n" + "ldrex %[original], %[storage]\n" // original = *(&storage) + "sub %[result], %[original], %[value]\n" // result = original - value + "sxth %[result], %[result]\n" // sign extend result from 16 to 32 bits + "strex %[tmp], %[result], %[storage]\n" // *(&storage) = result, tmp = store failed + "teq %[tmp], #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%[tmp]) + : [original] "=&r" (original), // %0 + [result] "=&r" (result), // %1 + [tmp] "=&l" (tmp), // %2 + [storage] "+Q" (storage) // %3 + : [value] "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +#if defined(BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXD_STREXD) + +// Unlike 32-bit operations, for 64-bit loads and stores we must use ldrexd/strexd. +// Any other instructions result in a non-atomic sequence of 32-bit accesses. +// See "ARM Architecture Reference Manual ARMv7-A and ARMv7-R edition", +// Section A3.5.3 "Atomicity in the ARM architecture". + +// In the asm blocks below we have to use 32-bit register pairs to compose 64-bit values. +// In order to pass the 64-bit operands to/from asm blocks, we use undocumented gcc feature: +// the lower half (Rt) of the operand is accessible normally, via the numbered placeholder (e.g. %0), +// and the upper half (Rt2) - via the same placeholder with an 'H' after the '%' sign (e.g. %H0). +// See: http://hardwarebug.org/2010/07/06/arm-inline-asm-secrets/ + +template< bool Signed > +struct operations< 8u, Signed > : + public gcc_arm_operations_base +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + exchange(storage, v, order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type original; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "ldrexd %1, %H1, [%2]\n" + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original) // %1 + : "r" (&storage) // %2 + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original; + fence_before(order); + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // load the original value + "strexd %0, %2, %H2, [%3]\n" // store the replacement, tmp = store failed + "teq %0, #0\n" // check if store succeeded + "bne 1b\n" + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original) // %1 + : "r" (v), // %2 + "r" (&storage) // %3 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + uint32_t tmp; + storage_type original, old_val = expected; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "cmp %1, %2\n" // flags = original.lo==old_val.lo + "ittt eq\n" // [hint that the following 3 instructions are conditional on flags.equal] + "cmpeq %H1, %H2\n" // if (flags.equal) flags = original.hi==old_val.hi + "strexdeq %0, %4, %H4, [%3]\n" // if (flags.equal) *(&storage) = desired, tmp = store failed + "teqeq %0, #0\n" // if (flags.equal) flags = tmp==0 + "ite eq\n" // [hint that the following 2 instructions are conditional on flags.equal] + "moveq %2, #1\n" // if (flags.equal) old_val.lo = 1 + "movne %2, #0\n" // if (!flags.equal) old_val.lo = 0 + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "+r" (old_val) // %2 + : "r" (&storage), // %3 + "r" (desired) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + const uint32_t success = (uint32_t)old_val; + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = original; + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + uint32_t tmp; + storage_type original, old_val = expected; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "cmp %1, %2\n" // flags = original.lo==old_val.lo + "it eq\n" // [hint that the following instruction is conditional on flags.equal] + "cmpeq %H1, %H2\n" // if (flags.equal) flags = original.hi==old_val.hi + "bne 2f\n" // if (!flags.equal) goto end + "strexd %0, %4, %H4, [%3]\n" // *(&storage) = desired, tmp = store failed + "teq %0, #0\n" // flags.equal = tmp == 0 + "bne 1b\n" // if (flags.equal) goto retry + "2:\n" + "ite eq\n" // [hint that the following 2 instructions are conditional on flags.equal] + "moveq %2, #1\n" // if (flags.equal) old_val.lo = 1 + "movne %2, #0\n" // if (!flags.equal) old_val.lo = 0 + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "+r" (old_val) // %2 + : "r" (&storage), // %3 + "r" (desired) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + const uint32_t success = (uint32_t)old_val; + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = original; + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage_type original, result; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "adds %2, %1, %4\n" // result = original + value + "adc %H2, %H1, %H4\n" + "strexd %0, %2, %H2, [%3]\n" // *(&storage) = result, tmp = store failed + "teq %0, #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "=&r" (result) // %2 + : "r" (&storage), // %3 + "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage_type original, result; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "subs %2, %1, %4\n" // result = original - value + "sbc %H2, %H1, %H4\n" + "strexd %0, %2, %H2, [%3]\n" // *(&storage) = result, tmp = store failed + "teq %0, #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "=&r" (result) // %2 + : "r" (&storage), // %3 + "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage_type original, result; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "and %2, %1, %4\n" // result = original & value + "and %H2, %H1, %H4\n" + "strexd %0, %2, %H2, [%3]\n" // *(&storage) = result, tmp = store failed + "teq %0, #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "=&r" (result) // %2 + : "r" (&storage), // %3 + "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage_type original, result; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "orr %2, %1, %4\n" // result = original | value + "orr %H2, %H1, %H4\n" + "strexd %0, %2, %H2, [%3]\n" // *(&storage) = result, tmp = store failed + "teq %0, #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "=&r" (result) // %2 + : "r" (&storage), // %3 + "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + storage_type original, result; + uint32_t tmp; + __asm__ __volatile__ + ( + BOOST_ATOMIC_DETAIL_ARM_ASM_START(%0) + "1:\n" + "ldrexd %1, %H1, [%3]\n" // original = *(&storage) + "eor %2, %1, %4\n" // result = original ^ value + "eor %H2, %H1, %H4\n" + "strexd %0, %2, %H2, [%3]\n" // *(&storage) = result, tmp = store failed + "teq %0, #0\n" // flags = tmp==0 + "bne 1b\n" // if (!flags.equal) goto retry + BOOST_ATOMIC_DETAIL_ARM_ASM_END(%0) + : BOOST_ATOMIC_DETAIL_ARM_ASM_TMPREG_CONSTRAINT(tmp), // %0 + "=&r" (original), // %1 + "=&r" (result) // %2 + : "r" (&storage), // %3 + "r" (v) // %4 + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +#endif // defined(BOOST_ATOMIC_DETAIL_ARM_HAS_LDREXD_STREXD) + + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + gcc_arm_operations_base::hardware_full_fence(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_atomic.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_atomic.hpp new file mode 100644 index 0000000..573a695 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_atomic.hpp @@ -0,0 +1,395 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_atomic.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_ATOMIC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_ATOMIC_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#if defined(__clang__) && (defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B)) +#include +#include +#endif + +#if __GCC_ATOMIC_LLONG_LOCK_FREE != BOOST_ATOMIC_LLONG_LOCK_FREE || __GCC_ATOMIC_LONG_LOCK_FREE != BOOST_ATOMIC_LONG_LOCK_FREE ||\ + __GCC_ATOMIC_INT_LOCK_FREE != BOOST_ATOMIC_INT_LOCK_FREE || __GCC_ATOMIC_SHORT_LOCK_FREE != BOOST_ATOMIC_SHORT_LOCK_FREE ||\ + __GCC_ATOMIC_CHAR_LOCK_FREE != BOOST_ATOMIC_CHAR_LOCK_FREE || __GCC_ATOMIC_BOOL_LOCK_FREE != BOOST_ATOMIC_BOOL_LOCK_FREE ||\ + __GCC_ATOMIC_WCHAR_T_LOCK_FREE != BOOST_ATOMIC_WCHAR_T_LOCK_FREE +// There are platforms where we need to use larger storage types +#include +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__INTEL_COMPILER) +// This is used to suppress warning #32013 described below for Intel Compiler. +// In debug builds the compiler does not inline any functions, so basically +// every atomic function call results in this warning. I don't know any other +// way to selectively disable just this one warning. +#pragma system_header +#endif + +namespace boost { +namespace atomics { +namespace detail { + +/*! + * The function converts \c boost::memory_order values to the compiler-specific constants. + * + * NOTE: The intention is that the function is optimized away by the compiler, and the + * compiler-specific constants are passed to the intrinsics. I know constexpr doesn't + * work in this case because the standard atomics interface require memory ordering + * constants to be passed as function arguments, at which point they stop being constexpr. + * However it is crucial that the compiler sees constants and not runtime values, + * because otherwise it just ignores the ordering value and always uses seq_cst. + * This is the case with Intel C++ Compiler 14.0.3 (Composer XE 2013 SP1, update 3) and + * gcc 4.8.2. Intel Compiler issues a warning in this case: + * + * warning #32013: Invalid memory order specified. Defaulting to seq_cst memory order. + * + * while gcc acts silently. + * + * To mitigate the problem ALL functions, including the atomic<> members must be + * declared with BOOST_FORCEINLINE. In this case the compilers are able to see that + * all functions are called with constant orderings and call intrinstcts properly. + * + * Unfortunately, this still doesn't work in debug mode as the compiler doesn't + * inline functions even when marked with BOOST_FORCEINLINE. In this case all atomic + * operaions will be executed with seq_cst semantics. + */ +BOOST_FORCEINLINE BOOST_CONSTEXPR int convert_memory_order_to_gcc(memory_order order) BOOST_NOEXCEPT +{ + return (order == memory_order_relaxed ? __ATOMIC_RELAXED : (order == memory_order_consume ? __ATOMIC_CONSUME : + (order == memory_order_acquire ? __ATOMIC_ACQUIRE : (order == memory_order_release ? __ATOMIC_RELEASE : + (order == memory_order_acq_rel ? __ATOMIC_ACQ_REL : __ATOMIC_SEQ_CST))))); +} + +template< typename T > +struct gcc_atomic_operations +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + __atomic_store_n(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return __atomic_load_n(&storage, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_fetch_add(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_fetch_sub(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_exchange_n(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return __atomic_compare_exchange_n + ( + &storage, &expected, desired, false, + atomics::detail::convert_memory_order_to_gcc(success_order), + atomics::detail::convert_memory_order_to_gcc(failure_order) + ); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return __atomic_compare_exchange_n + ( + &storage, &expected, desired, true, + atomics::detail::convert_memory_order_to_gcc(success_order), + atomics::detail::convert_memory_order_to_gcc(failure_order) + ); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_fetch_and(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_fetch_or(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return __atomic_fetch_xor(&storage, v, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return __atomic_test_and_set(&storage, atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + __atomic_clear(const_cast< storage_type* >(&storage), atomics::detail::convert_memory_order_to_gcc(order)); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile& storage) BOOST_NOEXCEPT + { + return __atomic_is_lock_free(sizeof(storage_type), &storage); + } +}; + +#if BOOST_ATOMIC_INT128_LOCK_FREE > 0 +#if defined(__clang__) && defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +// Workaround for clang bug: http://llvm.org/bugs/show_bug.cgi?id=19149 +// Clang 3.4 does not implement 128-bit __atomic* intrinsics even though it defines __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16 +template< bool Signed > +struct operations< 16u, Signed > : + public cas_based_operations< gcc_dcas_x86_64< Signed > > +{ +}; + +#else + +template< bool Signed > +struct operations< 16u, Signed > : + public gcc_atomic_operations< typename make_storage_type< 16u, Signed >::type > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; + +#endif +#endif + + +#if BOOST_ATOMIC_INT64_LOCK_FREE > 0 +#if defined(__clang__) && defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) + +// Workaround for clang bug http://llvm.org/bugs/show_bug.cgi?id=19355 +template< bool Signed > +struct operations< 8u, Signed > : + public cas_based_operations< gcc_dcas_x86< Signed > > +{ +}; + +#elif (BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 8 && __GCC_ATOMIC_LLONG_LOCK_FREE != BOOST_ATOMIC_LLONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 8 && __GCC_ATOMIC_LONG_LOCK_FREE != BOOST_ATOMIC_LONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_INT == 8 && __GCC_ATOMIC_INT_LOCK_FREE != BOOST_ATOMIC_INT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 8 && __GCC_ATOMIC_SHORT_LOCK_FREE != BOOST_ATOMIC_SHORT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 8 && __GCC_ATOMIC_WCHAR_T_LOCK_FREE != BOOST_ATOMIC_WCHAR_T_LOCK_FREE) + +#define BOOST_ATOMIC_DETAIL_INT64_EXTENDED + +template< bool Signed > +struct operations< 8u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 16u, Signed >::type >, 8u, Signed > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; + +#else + +template< bool Signed > +struct operations< 8u, Signed > : + public gcc_atomic_operations< typename make_storage_type< 8u, Signed >::type > +{ + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +}; + +#endif +#endif + +#if BOOST_ATOMIC_INT32_LOCK_FREE > 0 +#if (BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 4 && __GCC_ATOMIC_LLONG_LOCK_FREE != BOOST_ATOMIC_LLONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 4 && __GCC_ATOMIC_LONG_LOCK_FREE != BOOST_ATOMIC_LONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_INT == 4 && __GCC_ATOMIC_INT_LOCK_FREE != BOOST_ATOMIC_INT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 4 && __GCC_ATOMIC_SHORT_LOCK_FREE != BOOST_ATOMIC_SHORT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 4 && __GCC_ATOMIC_WCHAR_T_LOCK_FREE != BOOST_ATOMIC_WCHAR_T_LOCK_FREE) + +#define BOOST_ATOMIC_DETAIL_INT32_EXTENDED + +#if !defined(BOOST_ATOMIC_DETAIL_INT64_EXTENDED) + +template< bool Signed > +struct operations< 4u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 8u, Signed >::type >, 4u, Signed > +{ + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +}; + +#else // !defined(BOOST_ATOMIC_DETAIL_INT64_EXTENDED) + +template< bool Signed > +struct operations< 4u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 16u, Signed >::type >, 4u, Signed > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; + +#endif // !defined(BOOST_ATOMIC_DETAIL_INT64_EXTENDED) + +#else + +template< bool Signed > +struct operations< 4u, Signed > : + public gcc_atomic_operations< typename make_storage_type< 4u, Signed >::type > +{ + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +}; + +#endif +#endif + +#if BOOST_ATOMIC_INT16_LOCK_FREE > 0 +#if (BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 2 && __GCC_ATOMIC_LLONG_LOCK_FREE != BOOST_ATOMIC_LLONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 2 && __GCC_ATOMIC_LONG_LOCK_FREE != BOOST_ATOMIC_LONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_INT == 2 && __GCC_ATOMIC_INT_LOCK_FREE != BOOST_ATOMIC_INT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 2 && __GCC_ATOMIC_SHORT_LOCK_FREE != BOOST_ATOMIC_SHORT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 2 && __GCC_ATOMIC_WCHAR_T_LOCK_FREE != BOOST_ATOMIC_WCHAR_T_LOCK_FREE) + +#define BOOST_ATOMIC_DETAIL_INT16_EXTENDED + +#if !defined(BOOST_ATOMIC_DETAIL_INT32_EXTENDED) + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 4u, Signed >::type >, 2u, Signed > +{ + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +}; + +#elif !defined(BOOST_ATOMIC_DETAIL_INT64_EXTENDED) + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 8u, Signed >::type >, 2u, Signed > +{ + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +}; + +#else + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 16u, Signed >::type >, 2u, Signed > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; + +#endif + +#else + +template< bool Signed > +struct operations< 2u, Signed > : + public gcc_atomic_operations< typename make_storage_type< 2u, Signed >::type > +{ + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; +}; + +#endif +#endif + +#if BOOST_ATOMIC_INT8_LOCK_FREE > 0 +#if (BOOST_ATOMIC_DETAIL_SIZEOF_LLONG == 1 && __GCC_ATOMIC_LLONG_LOCK_FREE != BOOST_ATOMIC_LLONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_LONG == 1 && __GCC_ATOMIC_LONG_LOCK_FREE != BOOST_ATOMIC_LONG_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_INT == 1 && __GCC_ATOMIC_INT_LOCK_FREE != BOOST_ATOMIC_INT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_SHORT == 1 && __GCC_ATOMIC_SHORT_LOCK_FREE != BOOST_ATOMIC_SHORT_LOCK_FREE) ||\ + (BOOST_ATOMIC_DETAIL_SIZEOF_WCHAR_T == 1 && __GCC_ATOMIC_WCHAR_T_LOCK_FREE != BOOST_ATOMIC_WCHAR_T_LOCK_FREE) ||\ + (__GCC_ATOMIC_CHAR_LOCK_FREE != BOOST_ATOMIC_CHAR_LOCK_FREE) ||\ + (__GCC_ATOMIC_BOOL_LOCK_FREE != BOOST_ATOMIC_BOOL_LOCK_FREE) + +#if !defined(BOOST_ATOMIC_DETAIL_INT16_EXTENDED) + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 2u, Signed >::type >, 1u, Signed > +{ + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; +}; + +#elif !defined(BOOST_ATOMIC_DETAIL_INT32_EXTENDED) + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 4u, Signed >::type >, 1u, Signed > +{ + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +}; + +#elif !defined(BOOST_ATOMIC_DETAIL_INT64_EXTENDED) + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 8u, Signed >::type >, 1u, Signed > +{ + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +}; + +#else + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< gcc_atomic_operations< typename make_storage_type< 16u, Signed >::type >, 1u, Signed > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; + +#endif + +#else + +template< bool Signed > +struct operations< 1u, Signed > : + public gcc_atomic_operations< typename make_storage_type< 1u, Signed >::type > +{ + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; +}; + +#endif +#endif + +#undef BOOST_ATOMIC_DETAIL_INT16_EXTENDED +#undef BOOST_ATOMIC_DETAIL_INT32_EXTENDED +#undef BOOST_ATOMIC_DETAIL_INT64_EXTENDED + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + __atomic_thread_fence(atomics::detail::convert_memory_order_to_gcc(order)); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + __atomic_signal_fence(atomics::detail::convert_memory_order_to_gcc(order)); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_ATOMIC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_ppc.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_ppc.hpp new file mode 100644 index 0000000..9131791 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_ppc.hpp @@ -0,0 +1,802 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_ppc.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_PPC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_PPC_HPP_INCLUDED_ + +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +// The implementation below uses information from this document: +// http://www.rdrop.com/users/paulmck/scalability/paper/N2745r.2010.02.19a.html + +/* + Refer to: Motorola: "Programming Environments Manual for 32-Bit + Implementations of the PowerPC Architecture", Appendix E: + "Synchronization Programming Examples" for an explanation of what is + going on here (can be found on the web at various places by the + name "MPCFPE32B.pdf", Google is your friend...) + + Most of the atomic operations map to instructions in a relatively + straight-forward fashion, but "load"s may at first glance appear + a bit strange as they map to: + + lwz %rX, addr + cmpw %rX, %rX + bne- 1f + 1: + + That is, the CPU is forced to perform a branch that "formally" depends + on the value retrieved from memory. This scheme has an overhead of + about 1-2 clock cycles per load, but it allows to map "acquire" to + the "isync" instruction instead of "sync" uniformly and for all type + of atomic operations. Since "isync" has a cost of about 15 clock + cycles, while "sync" hast a cost of about 50 clock cycles, the small + penalty to atomic loads more than compensates for this. + + Byte- and halfword-sized atomic values are realized by encoding the + value to be represented into a word, performing sign/zero extension + as appropriate. This means that after add/sub operations the value + needs fixing up to accurately preserve the wrap-around semantic of + the smaller type. (Nothing special needs to be done for the bit-wise + and the "exchange type" operators as the compiler already sees to + it that values carried in registers are extended appropriately and + everything falls into place naturally). + + The register constraint "b" instructs gcc to use any register + except r0; this is sometimes required because the encoding for + r0 is used to signify "constant zero" in a number of instructions, + making r0 unusable in this place. For simplicity this constraint + is used everywhere since I am to lazy to look this up on a + per-instruction basis, and ppc has enough registers for this not + to pose a problem. +*/ + +// A note about memory_order_consume. Technically, this architecture allows to avoid +// unnecessary memory barrier after consume load since it supports data dependency ordering. +// However, some compiler optimizations may break a seemingly valid code relying on data +// dependency tracking by injecting bogus branches to aid out of order execution. +// This may happen not only in Boost.Atomic code but also in user's code, which we have no +// control of. See this thread: http://lists.boost.org/Archives/boost/2014/06/213890.php. +// For this reason we promote memory_order_consume to memory_order_acquire. + +struct gcc_ppc_operations_base +{ + static BOOST_FORCEINLINE void fence_before(memory_order order) BOOST_NOEXCEPT + { +#if defined(__powerpc64__) || defined(__PPC64__) + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("sync" ::: "memory"); + else if ((order & memory_order_release) != 0) + __asm__ __volatile__ ("lwsync" ::: "memory"); +#else + if ((order & memory_order_release) != 0) + __asm__ __volatile__ ("sync" ::: "memory"); +#endif + } + + static BOOST_FORCEINLINE void fence_after(memory_order order) BOOST_NOEXCEPT + { + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + __asm__ __volatile__ ("isync" ::: "memory"); + } +}; + + +template< bool Signed > +struct operations< 4u, Signed > : + public gcc_ppc_operations_base +{ + typedef typename make_storage_type< 4u, Signed >::type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + __asm__ __volatile__ + ( + "stw %1, %0\n\t" + : "+m" (storage) + : "r" (v) + ); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v; + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("sync" ::: "memory"); + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + { + __asm__ __volatile__ + ( + "lwz %0, %1\n\t" + "cmpw %0, %0\n\t" + "bne- 1f\n\t" + "1:\n\t" + "isync\n\t" + : "=&r" (v) + : "m" (storage) + : "cr0", "memory" + ); + } + else + { + __asm__ __volatile__ + ( + "lwz %0, %1\n\t" + : "=&r" (v) + : "m" (storage) + ); + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y1\n\t" + "stwcx. %2,%y1\n\t" + "bne- 1b\n\t" + : "=&b" (original), "+Z" (storage) + : "b" (v) + : "cr0" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + fence_before(success_order); + __asm__ __volatile__ + ( + "li %1, 0\n\t" + "lwarx %0,%y2\n\t" + "cmpw %0, %3\n\t" + "bne- 1f\n\t" + "stwcx. %4,%y2\n\t" + "bne- 1f\n\t" + "li %1, 1\n\t" + "1:\n\t" + : "=&b" (expected), "=&b" (success), "+Z" (storage) + : "b" (expected), "b" (desired) + : "cr0" + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + fence_before(success_order); + __asm__ __volatile__ + ( + "li %1, 0\n\t" + "0: lwarx %0,%y2\n\t" + "cmpw %0, %3\n\t" + "bne- 1f\n\t" + "stwcx. %4,%y2\n\t" + "bne- 0b\n\t" + "li %1, 1\n\t" + "1:\n\t" + : "=&b" (expected), "=&b" (success), "+Z" (storage) + : "b" (expected), "b" (desired) + : "cr0" + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "and %1,%0,%3\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "or %1,%0,%3\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "xor %1,%0,%3\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + + +template< > +struct operations< 1u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "rlwinm %1, %1, 0, 0xff\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "rlwinm %1, %1, 0, 0xff\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 1u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "extsb %1, %1\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "extsb %1, %1\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +template< > +struct operations< 2u, false > : + public operations< 4u, false > +{ + typedef operations< 4u, false > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "rlwinm %1, %1, 0, 0xffff\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "rlwinm %1, %1, 0, 0xffff\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + +template< > +struct operations< 2u, true > : + public operations< 4u, true > +{ + typedef operations< 4u, true > base_type; + typedef base_type::storage_type storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "extsh %1, %1\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "lwarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "extsh %1, %1\n\t" + "stwcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } +}; + + +#if defined(__powerpc64__) || defined(__PPC64__) + +template< bool Signed > +struct operations< 8u, Signed > : + public gcc_ppc_operations_base +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before(order); + __asm__ __volatile__ + ( + "std %1, %0\n\t" + : "+m" (storage) + : "r" (v) + ); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v; + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("sync" ::: "memory"); + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + { + __asm__ __volatile__ + ( + "ld %0, %1\n\t" + "cmpd %0, %0\n\t" + "bne- 1f\n\t" + "1:\n\t" + "isync\n\t" + : "=&b" (v) + : "m" (storage) + : "cr0", "memory" + ); + } + else + { + __asm__ __volatile__ + ( + "ld %0, %1\n\t" + : "=&b" (v) + : "m" (storage) + ); + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y1\n\t" + "stdcx. %2,%y1\n\t" + "bne- 1b\n\t" + : "=&b" (original), "+Z" (storage) + : "b" (v) + : "cr0" + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + fence_before(success_order); + __asm__ __volatile__ + ( + "li %1, 0\n\t" + "ldarx %0,%y2\n\t" + "cmpd %0, %3\n\t" + "bne- 1f\n\t" + "stdcx. %4,%y2\n\t" + "bne- 1f\n\t" + "li %1, 1\n\t" + "1:" + : "=&b" (expected), "=&b" (success), "+Z" (storage) + : "b" (expected), "b" (desired) + : "cr0" + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + int success; + fence_before(success_order); + __asm__ __volatile__ + ( + "li %1, 0\n\t" + "0: ldarx %0,%y2\n\t" + "cmpd %0, %3\n\t" + "bne- 1f\n\t" + "stdcx. %4,%y2\n\t" + "bne- 0b\n\t" + "li %1, 1\n\t" + "1:\n\t" + : "=&b" (expected), "=&b" (success), "+Z" (storage) + : "b" (expected), "b" (desired) + : "cr0" + ); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + return !!success; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y2\n\t" + "add %1,%0,%3\n\t" + "stdcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y2\n\t" + "sub %1,%0,%3\n\t" + "stdcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y2\n\t" + "and %1,%0,%3\n\t" + "stdcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y2\n\t" + "or %1,%0,%3\n\t" + "stdcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type original, tmp; + fence_before(order); + __asm__ __volatile__ + ( + "1:\n\t" + "ldarx %0,%y2\n\t" + "xor %1,%0,%3\n\t" + "stdcx. %1,%y2\n\t" + "bne- 1b\n\t" + : "=&b" (original), "=&b" (tmp), "+Z" (storage) + : "b" (v) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC + ); + fence_after(order); + return original; + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, 0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +#endif // defined(__powerpc64__) || defined(__PPC64__) + + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + switch (order) + { + case memory_order_consume: + case memory_order_acquire: + case memory_order_release: + case memory_order_acq_rel: +#if defined(__powerpc64__) || defined(__PPC64__) + __asm__ __volatile__ ("lwsync" ::: "memory"); + break; +#endif + case memory_order_seq_cst: + __asm__ __volatile__ ("sync" ::: "memory"); + break; + default:; + } +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) +#if defined(__ibmxl__) || defined(__IBMCPP__) + __fence(); +#else + __asm__ __volatile__ ("" ::: "memory"); +#endif +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_PPC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sparc.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sparc.hpp new file mode 100644 index 0000000..020882b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sparc.hpp @@ -0,0 +1,240 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2010 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_sparc.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_SPARC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_SPARC_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +struct gcc_sparc_cas_base +{ + static BOOST_FORCEINLINE void fence_before(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("membar #Sync" ::: "memory"); + else if ((order & memory_order_release) != 0) + __asm__ __volatile__ ("membar #StoreStore | #LoadStore" ::: "memory"); + } + + static BOOST_FORCEINLINE void fence_after(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("membar #Sync" ::: "memory"); + else if ((order & (memory_order_consume | memory_order_acquire)) != 0) + __asm__ __volatile__ ("membar #StoreStore | #LoadStore" ::: "memory"); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + __asm__ __volatile__ ("membar #Sync" ::: "memory"); + } +}; + +template< bool Signed > +struct gcc_sparc_cas32 : + public gcc_sparc_cas_base +{ + typedef typename make_storage_type< 4u, Signed >::type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before_store(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + storage_type previous = expected; + __asm__ __volatile__ + ( + "cas [%1], %2, %0" + : "+r" (desired) + : "r" (&storage), "r" (previous) + : "memory" + ); + const bool success = (desired == previous); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = desired; + return success; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + __asm__ __volatile__ + ( + "swap [%1], %0" + : "+r" (v) + : "r" (&storage) + : "memory" + ); + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public cas_based_operations< gcc_sparc_cas32< Signed > > +{ +}; + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 1u, Signed > +{ +}; + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 2u, Signed > +{ +}; + +template< bool Signed > +struct gcc_sparc_cas64 : + public gcc_sparc_cas_base +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before_store(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + fence_before(success_order); + storage_type previous = expected; + __asm__ __volatile__ + ( + "casx [%1], %2, %0" + : "+r" (desired) + : "r" (&storage), "r" (previous) + : "memory" + ); + const bool success = (desired == previous); + if (success) + fence_after(success_order); + else + fence_after(failure_order); + expected = desired; + return success; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 8u, Signed > : + public cas_based_operations< cas_based_exchange< gcc_sparc_cas64< Signed > > > +{ +}; + + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + switch (order) + { + case memory_order_release: + __asm__ __volatile__ ("membar #StoreStore | #LoadStore" ::: "memory"); + break; + case memory_order_consume: + case memory_order_acquire: + __asm__ __volatile__ ("membar #LoadLoad | #LoadStore" ::: "memory"); + break; + case memory_order_acq_rel: + __asm__ __volatile__ ("membar #LoadLoad | #LoadStore | #StoreStore" ::: "memory"); + break; + case memory_order_seq_cst: + __asm__ __volatile__ ("membar #Sync" ::: "memory"); + break; + case memory_order_relaxed: + default: + break; + } +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_SPARC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sync.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sync.hpp new file mode 100644 index 0000000..87f2f53 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_sync.hpp @@ -0,0 +1,270 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_sync.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_SYNC_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_SYNC_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +struct gcc_sync_operations_base +{ + static BOOST_FORCEINLINE void fence_before_store(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + __sync_synchronize(); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + __sync_synchronize(); + } + + static BOOST_FORCEINLINE void fence_after_load(memory_order order) BOOST_NOEXCEPT + { + if ((order & (memory_order_acquire | memory_order_consume)) != 0) + __sync_synchronize(); + } +}; + +template< typename T > +struct gcc_sync_operations : + public gcc_sync_operations_base +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before_store(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return __sync_fetch_and_add(&storage, v); + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return __sync_fetch_and_sub(&storage, v); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + // GCC docs mention that not all architectures may support full exchange semantics for this intrinsic. However, GCC's implementation of + // std::atomic<> uses this intrinsic unconditionally. We do so as well. In case if some architectures actually don't support this, we can always + // add a check here and fall back to a CAS loop. + if ((order & memory_order_release) != 0) + __sync_synchronize(); + return __sync_lock_test_and_set(&storage, v); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type expected2 = expected; + storage_type old_val = __sync_val_compare_and_swap(&storage, expected2, desired); + + if (old_val == expected2) + { + return true; + } + else + { + expected = old_val; + return false; + } + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return __sync_fetch_and_and(&storage, v); + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return __sync_fetch_and_or(&storage, v); + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return __sync_fetch_and_xor(&storage, v); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + __sync_synchronize(); + return !!__sync_lock_test_and_set(&storage, 1); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + __sync_lock_release(&storage); + if (order == memory_order_seq_cst) + __sync_synchronize(); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +#if BOOST_ATOMIC_INT8_LOCK_FREE > 0 +template< bool Signed > +struct operations< 1u, Signed > : +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) + public gcc_sync_operations< typename make_storage_type< 1u, Signed >::type > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 2u, Signed >::type >, 1u, Signed > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 4u, Signed >::type >, 1u, Signed > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 8u, Signed >::type >, 1u, Signed > +#else + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 16u, Signed >::type >, 1u, Signed > +#endif +{ +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +#else + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +#endif +}; +#endif + +#if BOOST_ATOMIC_INT16_LOCK_FREE > 0 +template< bool Signed > +struct operations< 2u, Signed > : +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) + public gcc_sync_operations< typename make_storage_type< 2u, Signed >::type > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 4u, Signed >::type >, 2u, Signed > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 8u, Signed >::type >, 2u, Signed > +#else + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 16u, Signed >::type >, 2u, Signed > +#endif +{ +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +#else + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +#endif +}; +#endif + +#if BOOST_ATOMIC_INT32_LOCK_FREE > 0 +template< bool Signed > +struct operations< 4u, Signed > : +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + public gcc_sync_operations< typename make_storage_type< 4u, Signed >::type > +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 8u, Signed >::type >, 4u, Signed > +#else + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 16u, Signed >::type >, 4u, Signed > +#endif +{ +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; +#elif defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +#else + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +#endif +}; +#endif + +#if BOOST_ATOMIC_INT64_LOCK_FREE > 0 +template< bool Signed > +struct operations< 8u, Signed > : +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + public gcc_sync_operations< typename make_storage_type< 8u, Signed >::type > +#else + public extending_cas_based_operations< gcc_sync_operations< typename make_storage_type< 16u, Signed >::type >, 8u, Signed > +#endif +{ +#if defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; +#else + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +#endif +}; +#endif + +#if BOOST_ATOMIC_INT128_LOCK_FREE > 0 +template< bool Signed > +struct operations< 16u, Signed > : + public gcc_sync_operations< typename make_storage_type< 16u, Signed >::type > +{ + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; +}; +#endif + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __sync_synchronize(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_SYNC_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86.hpp new file mode 100644 index 0000000..f68125c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86.hpp @@ -0,0 +1,514 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_x86.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_X86_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_X86_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) +#include +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__x86_64__) +#define BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "rdx" +#else +#define BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "edx" +#endif + +namespace boost { +namespace atomics { +namespace detail { + +struct gcc_x86_operations_base +{ + static BOOST_FORCEINLINE void fence_before(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + __asm__ __volatile__ ("" ::: "memory"); + } + + static BOOST_FORCEINLINE void fence_after(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_acquire) != 0) + __asm__ __volatile__ ("" ::: "memory"); + } +}; + +template< typename T, typename Derived > +struct gcc_x86_operations : + public gcc_x86_operations_base +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + if (order != memory_order_seq_cst) + { + fence_before(order); + storage = v; + fence_after(order); + } + else + { + Derived::exchange(storage, v, order); + } + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + return Derived::fetch_add(storage, -v, order); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return Derived::compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!Derived::exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, (storage_type)0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 1u, Signed > : + public gcc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > +{ + typedef gcc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "lock; xaddb %0, %1" + : "+q" (v), "+m" (storage) + : + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "xchgb %0, %1" + : "+q" (v), "+m" (storage) + : + : "memory" + ); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchgb %3, %1\n\t" + "sete %2" + : "+a" (previous), "+m" (storage), "=q" (success) + : "q" (desired) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + expected = previous; + return success; + } + +#define BOOST_ATOMIC_DETAIL_CAS_LOOP(op, argument, result)\ + __asm__ __volatile__\ + (\ + "xor %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER ", %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "\n\t"\ + ".align 16\n\t"\ + "1: movb %[arg], %%dl\n\t"\ + op " %%al, %%dl\n\t"\ + "lock; cmpxchgb %%dl, %[storage]\n\t"\ + "jne 1b"\ + : [res] "+a" (result), [storage] "+m" (storage)\ + : [arg] "q" (argument)\ + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER, "memory"\ + ) + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("andb", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("orb", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("xorb", v, res); + return res; + } + +#undef BOOST_ATOMIC_DETAIL_CAS_LOOP +}; + +template< bool Signed > +struct operations< 2u, Signed > : + public gcc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > +{ + typedef gcc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "lock; xaddw %0, %1" + : "+q" (v), "+m" (storage) + : + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "xchgw %0, %1" + : "+q" (v), "+m" (storage) + : + : "memory" + ); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchgw %3, %1\n\t" + "sete %2" + : "+a" (previous), "+m" (storage), "=q" (success) + : "q" (desired) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + expected = previous; + return success; + } + +#define BOOST_ATOMIC_DETAIL_CAS_LOOP(op, argument, result)\ + __asm__ __volatile__\ + (\ + "xor %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER ", %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "\n\t"\ + ".align 16\n\t"\ + "1: movw %[arg], %%dx\n\t"\ + op " %%ax, %%dx\n\t"\ + "lock; cmpxchgw %%dx, %[storage]\n\t"\ + "jne 1b"\ + : [res] "+a" (result), [storage] "+m" (storage)\ + : [arg] "q" (argument)\ + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER, "memory"\ + ) + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("andw", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("orw", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("xorw", v, res); + return res; + } + +#undef BOOST_ATOMIC_DETAIL_CAS_LOOP +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public gcc_x86_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > +{ + typedef gcc_x86_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "lock; xaddl %0, %1" + : "+r" (v), "+m" (storage) + : + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "xchgl %0, %1" + : "+r" (v), "+m" (storage) + : + : "memory" + ); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchgl %3, %1\n\t" + "sete %2" + : "+a" (previous), "+m" (storage), "=q" (success) + : "r" (desired) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + expected = previous; + return success; + } + +#define BOOST_ATOMIC_DETAIL_CAS_LOOP(op, argument, result)\ + __asm__ __volatile__\ + (\ + "xor %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER ", %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "\n\t"\ + ".align 16\n\t"\ + "1: movl %[arg], %%edx\n\t"\ + op " %%eax, %%edx\n\t"\ + "lock; cmpxchgl %%edx, %[storage]\n\t"\ + "jne 1b"\ + : [res] "+a" (result), [storage] "+m" (storage)\ + : [arg] "r" (argument)\ + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER, "memory"\ + ) + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("andl", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("orl", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("xorl", v, res); + return res; + } + +#undef BOOST_ATOMIC_DETAIL_CAS_LOOP +}; + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) + +template< bool Signed > +struct operations< 8u, Signed > : + public cas_based_operations< gcc_dcas_x86< Signed > > +{ +}; + +#elif defined(__x86_64__) + +template< bool Signed > +struct operations< 8u, Signed > : + public gcc_x86_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > +{ + typedef gcc_x86_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "lock; xaddq %0, %1" + : "+r" (v), "+m" (storage) + : + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + __asm__ __volatile__ + ( + "xchgq %0, %1" + : "+r" (v), "+m" (storage) + : + : "memory" + ); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchgq %3, %1\n\t" + "sete %2" + : "+a" (previous), "+m" (storage), "=q" (success) + : "r" (desired) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + expected = previous; + return success; + } + +#define BOOST_ATOMIC_DETAIL_CAS_LOOP(op, argument, result)\ + __asm__ __volatile__\ + (\ + "xor %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER ", %%" BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER "\n\t"\ + ".align 16\n\t"\ + "1: movq %[arg], %%rdx\n\t"\ + op " %%rax, %%rdx\n\t"\ + "lock; cmpxchgq %%rdx, %[storage]\n\t"\ + "jne 1b"\ + : [res] "+a" (result), [storage] "+m" (storage)\ + : [arg] "r" (argument)\ + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER, "memory"\ + ) + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("andq", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("orq", v, res); + return res; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type res = storage; + BOOST_ATOMIC_DETAIL_CAS_LOOP("xorq", v, res); + return res; + } + +#undef BOOST_ATOMIC_DETAIL_CAS_LOOP +}; + +#endif + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +template< bool Signed > +struct operations< 16u, Signed > : + public cas_based_operations< gcc_dcas_x86_64< Signed > > +{ +}; + +#endif // defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order == memory_order_seq_cst) + { + __asm__ __volatile__ + ( +#if defined(__x86_64__) || defined(__SSE2__) + "mfence\n" +#else + "lock; addl $0, (%%esp)\n" +#endif + ::: "memory" + ); + } + else if ((order & (memory_order_acquire | memory_order_release)) != 0) + { + __asm__ __volatile__ ("" ::: "memory"); + } +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#undef BOOST_ATOMIC_DETAIL_TEMP_CAS_REGISTER + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_X86_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86_dcas.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86_dcas.hpp new file mode 100644 index 0000000..47cc36d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_gcc_x86_dcas.hpp @@ -0,0 +1,616 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_gcc_x86_dcas.hpp + * + * This header contains implementation of the double-width CAS primitive for x86. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_GCC_X86_DCAS_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_GCC_X86_DCAS_HPP_INCLUDED_ + +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) + +template< bool Signed > +struct gcc_dcas_x86 +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + if ((((uint32_t)&storage) & 0x00000007) == 0) + { +#if defined(__SSE2__) + __asm__ __volatile__ + ( +#if defined(__AVX__) + "vmovq %1, %%xmm4\n\t" + "vmovq %%xmm4, %0\n\t" +#else + "movq %1, %%xmm4\n\t" + "movq %%xmm4, %0\n\t" +#endif + : "=m" (storage) + : "m" (v) + : "memory", "xmm4" + ); +#else + __asm__ __volatile__ + ( + "fildll %1\n\t" + "fistpll %0\n\t" + : "=m" (storage) + : "m" (v) + : "memory" + ); +#endif + } + else + { +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) +#if defined(__PIC__) + uint32_t scratch; + __asm__ __volatile__ + ( + "movl %%ebx, %[scratch]\n\t" + "movl %[value_lo], %%ebx\n\t" + "movl %[dest], %%eax\n\t" + "movl 4+%[dest], %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b %[dest]\n\t" + "jne 1b\n\t" + "movl %[scratch], %%ebx\n\t" + : [scratch] "=m" (scratch), [dest] "=o" (storage) + : [value_lo] "a" ((uint32_t)v), "c" ((uint32_t)(v >> 32)) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "edx", "memory" + ); +#else // defined(__PIC__) + __asm__ __volatile__ + ( + "movl %[dest], %%eax\n\t" + "movl 4+%[dest], %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b %[dest]\n\t" + "jne 1b\n\t" + : [dest] "=o" (storage) + : [value_lo] "b" ((uint32_t)v), "c" ((uint32_t)(v >> 32)) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "eax", "edx", "memory" + ); +#endif // defined(__PIC__) +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) +#if defined(__PIC__) + uint32_t scratch; + __asm__ __volatile__ + ( + "movl %%ebx, %[scratch]\n\t" + "movl %[value_lo], %%ebx\n\t" + "movl 0(%[dest]), %%eax\n\t" + "movl 4(%[dest]), %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b 0(%[dest])\n\t" + "jne 1b\n\t" + "movl %[scratch], %%ebx\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : [scratch] "=m,m" (scratch) + : [value_lo] "a,a" ((uint32_t)v), "c,c" ((uint32_t)(v >> 32)), [dest] "D,S" (&storage) +#else + : [scratch] "=m" (scratch) + : [value_lo] "a" ((uint32_t)v), "c" ((uint32_t)(v >> 32)), [dest] "D" (&storage) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "edx", "memory" + ); +#else // defined(__PIC__) + __asm__ __volatile__ + ( + "movl 0(%[dest]), %%eax\n\t" + "movl 4(%[dest]), %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b 0(%[dest])\n\t" + "jne 1b\n\t" + : +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : [value_lo] "b,b" ((uint32_t)v), "c,c" ((uint32_t)(v >> 32)), [dest] "D,S" (&storage) +#else + : [value_lo] "b" ((uint32_t)v), "c" ((uint32_t)(v >> 32)), [dest] "D" (&storage) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "eax", "edx", "memory" + ); +#endif // defined(__PIC__) +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + } + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order) BOOST_NOEXCEPT + { + storage_type value; + + if ((((uint32_t)&storage) & 0x00000007) == 0) + { +#if defined(__SSE2__) + __asm__ __volatile__ + ( +#if defined(__AVX__) + "vmovq %1, %%xmm4\n\t" + "vmovq %%xmm4, %0\n\t" +#else + "movq %1, %%xmm4\n\t" + "movq %%xmm4, %0\n\t" +#endif + : "=m" (value) + : "m" (storage) + : "memory", "xmm4" + ); +#else + __asm__ __volatile__ + ( + "fildll %1\n\t" + "fistpll %0\n\t" + : "=m" (value) + : "m" (storage) + : "memory" + ); +#endif + } + else + { +#if defined(__clang__) + // Clang cannot allocate eax:edx register pairs but it has sync intrinsics + value = __sync_val_compare_and_swap(&storage, (storage_type)0, (storage_type)0); +#else + // We don't care for comparison result here; the previous value will be stored into value anyway. + // Also we don't care for ebx and ecx values, they just have to be equal to eax and edx before cmpxchg8b. + __asm__ __volatile__ + ( + "movl %%ebx, %%eax\n\t" + "movl %%ecx, %%edx\n\t" + "lock; cmpxchg8b %[storage]\n\t" + : "=&A" (value) + : [storage] "m" (storage) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); +#endif + } + + return value; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { +#if defined(__clang__) + // Clang cannot allocate eax:edx register pairs but it has sync intrinsics + storage_type old_expected = expected; + expected = __sync_val_compare_and_swap(&storage, old_expected, desired); + return expected == old_expected; +#elif defined(__PIC__) + // Make sure ebx is saved and restored properly in case + // of position independent code. To make this work + // setup register constraints such that ebx can not be + // used by accident e.g. as base address for the variable + // to be modified. Accessing "scratch" should always be okay, + // as it can only be placed on the stack (and therefore + // accessed through ebp or esp only). + // + // In theory, could push/pop ebx onto/off the stack, but movs + // to a prepared stack slot turn out to be faster. + + uint32_t scratch; + bool success; + __asm__ __volatile__ + ( + "movl %%ebx, %[scratch]\n\t" + "movl %[desired_lo], %%ebx\n\t" + "lock; cmpxchg8b %[dest]\n\t" + "movl %[scratch], %%ebx\n\t" + "sete %[success]\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : "+A,A,A,A,A,A" (expected), [dest] "+m,m,m,m,m,m" (storage), [scratch] "=m,m,m,m,m,m" (scratch), [success] "=q,m,q,m,q,m" (success) + : [desired_lo] "S,S,D,D,m,m" ((uint32_t)desired), "c,c,c,c,c,c" ((uint32_t)(desired >> 32)) +#else + : "+A" (expected), [dest] "+m" (storage), [scratch] "=m" (scratch), [success] "=q" (success) + : [desired_lo] "S" ((uint32_t)desired), "c" ((uint32_t)(desired >> 32)) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return success; +#else + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchg8b %[dest]\n\t" + "sete %[success]\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : "+A,A" (expected), [dest] "+m,m" (storage), [success] "=q,m" (success) + : "b,b" ((uint32_t)desired), "c,c" ((uint32_t)(desired >> 32)) +#else + : "+A" (expected), [dest] "+m" (storage), [success] "=q" (success) + : "b" ((uint32_t)desired), "c" ((uint32_t)(desired >> 32)) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return success; +#endif + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { +#if defined(__clang__) + // Clang cannot allocate eax:edx register pairs but it has sync intrinsics + storage_type old_val = storage; + while (true) + { + storage_type val = __sync_val_compare_and_swap(&storage, old_val, v); + if (val == old_val) + return val; + old_val = val; + } +#elif !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) +#if defined(__PIC__) + uint32_t scratch; + __asm__ __volatile__ + ( + "movl %%ebx, %[scratch]\n\t" + "movl %%eax, %%ebx\n\t" + "movl %%edx, %%ecx\n\t" + "movl %[dest], %%eax\n\t" + "movl 4+%[dest], %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b %[dest]\n\t" + "jne 1b\n\t" + "movl %[scratch], %%ebx\n\t" + : "+A" (v), [scratch] "=m" (scratch), [dest] "+o" (storage) + : + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "ecx", "memory" + ); + return v; +#else // defined(__PIC__) + __asm__ __volatile__ + ( + "movl %[dest], %%eax\n\t" + "movl 4+%[dest], %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b %[dest]\n\t" + "jne 1b\n\t" + : "=A" (v), [dest] "+o" (storage) + : "b" ((uint32_t)v), "c" ((uint32_t)(v >> 32)) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; +#endif // defined(__PIC__) +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) +#if defined(__PIC__) + uint32_t scratch; + __asm__ __volatile__ + ( + "movl %%ebx, %[scratch]\n\t" + "movl %%eax, %%ebx\n\t" + "movl %%edx, %%ecx\n\t" + "movl 0(%[dest]), %%eax\n\t" + "movl 4(%[dest]), %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b 0(%[dest])\n\t" + "jne 1b\n\t" + "movl %[scratch], %%ebx\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : "+A,A" (v), [scratch] "=m,m" (scratch) + : [dest] "D,S" (&storage) +#else + : "+A" (v), [scratch] "=m" (scratch) + : [dest] "D" (&storage) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "ecx", "memory" + ); + return v; +#else // defined(__PIC__) + __asm__ __volatile__ + ( + "movl 0(%[dest]), %%eax\n\t" + "movl 4(%[dest]), %%edx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg8b 0(%[dest])\n\t" + "jne 1b\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : "=A,A" (v) + : "b,b" ((uint32_t)v), "c,c" ((uint32_t)(v >> 32)), [dest] "D,S" (&storage) +#else + : "=A" (v) + : "b" ((uint32_t)v), "c" ((uint32_t)(v >> 32)), [dest] "D" (&storage) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return v; +#endif // defined(__PIC__) +#endif + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +#endif // defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +template< bool Signed > +struct gcc_dcas_x86_64 +{ + typedef typename make_storage_type< 16u, Signed >::type storage_type; + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + uint64_t const* p_value = (uint64_t const*)&v; +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %[dest], %%rax\n\t" + "movq 8+%[dest], %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b %[dest]\n\t" + "jne 1b\n\t" + : [dest] "=o" (storage) + : "b" (p_value[0]), "c" (p_value[1]) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "rax", "rdx", "memory" + ); +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq 0(%[dest]), %%rax\n\t" + "movq 8(%[dest]), %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b 0(%[dest])\n\t" + "jne 1b\n\t" + : + : "b" (p_value[0]), "c" (p_value[1]), [dest] "r" (&storage) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "rax", "rdx", "memory" + ); +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order) BOOST_NOEXCEPT + { +#if defined(__clang__) + // Clang cannot allocate rax:rdx register pairs but it has sync intrinsics + storage_type value = storage_type(); + return __sync_val_compare_and_swap(&storage, value, value); +#elif defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + // GCC 4.4 can't allocate rax:rdx register pair either but it also doesn't support 128-bit __sync_val_compare_and_swap + storage_type value; + + // We don't care for comparison result here; the previous value will be stored into value anyway. + // Also we don't care for rbx and rcx values, they just have to be equal to rax and rdx before cmpxchg16b. +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %%rbx, %%rax\n\t" + "movq %%rcx, %%rdx\n\t" + "lock; cmpxchg16b %[storage]\n\t" + "movq %%rax, %[value]\n\t" + "movq %%rdx, 8+%[value]\n\t" + : [value] "=o" (value) + : [storage] "m" (storage) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %%rbx, %%rax\n\t" + "movq %%rcx, %%rdx\n\t" + "lock; cmpxchg16b %[storage]\n\t" + "movq %%rax, 0(%[value])\n\t" + "movq %%rdx, 8(%[value])\n\t" + : + : [storage] "m" (storage), [value] "r" (&value) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + + return value; +#else // defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + storage_type value; + + // We don't care for comparison result here; the previous value will be stored into value anyway. + // Also we don't care for rbx and rcx values, they just have to be equal to rax and rdx before cmpxchg16b. + __asm__ __volatile__ + ( + "movq %%rbx, %%rax\n\t" + "movq %%rcx, %%rdx\n\t" + "lock; cmpxchg16b %[storage]\n\t" + : "=&A" (value) + : [storage] "m" (storage) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + + return value; +#endif + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { +#if defined(__clang__) + // Clang cannot allocate rax:rdx register pairs but it has sync intrinsics + storage_type old_expected = expected; + expected = __sync_val_compare_and_swap(&storage, old_expected, desired); + return expected == old_expected; +#elif defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + // GCC 4.4 can't allocate rax:rdx register pair either but it also doesn't support 128-bit __sync_val_compare_and_swap + uint64_t const* p_desired = (uint64_t const*)&desired; + bool success; +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %[expected], %%rax\n\t" + "movq 8+%[expected], %%rdx\n\t" + "lock; cmpxchg16b %[dest]\n\t" + "sete %[success]\n\t" + "movq %%rax, %[expected]\n\t" + "movq %%rdx, 8+%[expected]\n\t" + : [dest] "+m" (storage), [expected] "+o" (expected), [success] "=q" (success) + : "b" (p_desired[0]), "c" (p_desired[1]) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq 0(%[expected]), %%rax\n\t" + "movq 8(%[expected]), %%rdx\n\t" + "lock; cmpxchg16b %[dest]\n\t" + "sete %[success]\n\t" + "movq %%rax, 0(%[expected])\n\t" + "movq %%rdx, 8(%[expected])\n\t" + : [dest] "+m" (storage), [success] "=q" (success) + : "b" (p_desired[0]), "c" (p_desired[1]), [expected] "r" (&expected) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + + return success; +#else // defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + uint64_t const* p_desired = (uint64_t const*)&desired; + bool success; + __asm__ __volatile__ + ( + "lock; cmpxchg16b %[dest]\n\t" + "sete %[success]\n\t" +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_CONSTRAINT_ALTERNATIVES) + : "+A,A" (expected), [dest] "+m,m" (storage), [success] "=q,m" (success) + : "b,b" (p_desired[0]), "c,c" (p_desired[1]) +#else + : "+A" (expected), [dest] "+m" (storage), [success] "=q" (success) + : "b" (p_desired[0]), "c" (p_desired[1]) +#endif + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); + return success; +#endif + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { +#if defined(__clang__) + // Clang cannot allocate eax:edx register pairs but it has sync intrinsics + storage_type old_val = storage; + while (true) + { + storage_type val = __sync_val_compare_and_swap(&storage, old_val, v); + if (val == old_val) + return val; + old_val = val; + } +#elif defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + // GCC 4.4 can't allocate rax:rdx register pair either but it also doesn't support 128-bit __sync_val_compare_and_swap + storage_type old_value; + uint64_t const* p_value = (uint64_t const*)&v; +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %[dest], %%rax\n\t" + "movq 8+%[dest], %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b %[dest]\n\t" + "jne 1b\n\t" + "movq %%rax, %[old_value]\n\t" + "movq %%rdx, 8+%[old_value]\n\t" + : [dest] "+o" (storage), [old_value] "=o" (old_value) + : "b" (p_value[0]), "c" (p_value[1]) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq 0(%[dest]), %%rax\n\t" + "movq 8(%[dest]), %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b 0(%[dest])\n\t" + "jne 1b\n\t" + "movq %%rax, 0(%[old_value])\n\t" + "movq %%rdx, 8(%[old_value])\n\t" + : + : "b" (p_value[0]), "c" (p_value[1]), [dest] "r" (&storage), [old_value] "r" (&old_value) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory", "rax", "rdx" + ); +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + + return old_value; +#else // defined(BOOST_ATOMIC_DETAIL_NO_ASM_RAX_RDX_PAIRS) + uint64_t const* p_value = (uint64_t const*)&v; +#if !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq %[dest], %%rax\n\t" + "movq 8+%[dest], %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b %[dest]\n\t" + "jne 1b\n\t" + : "=&A" (v), [dest] "+o" (storage) + : "b" (p_value[0]), "c" (p_value[1]) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); +#else // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + __asm__ __volatile__ + ( + "movq 0(%[dest]), %%rax\n\t" + "movq 8(%[dest]), %%rdx\n\t" + ".align 16\n\t" + "1: lock; cmpxchg16b 0(%[dest])\n\t" + "jne 1b\n\t" + : "=&A" (v) + : "b" (p_value[0]), "c" (p_value[1]), [dest] "r" (&storage) + : BOOST_ATOMIC_DETAIL_ASM_CLOBBER_CC_COMMA "memory" + ); +#endif // !defined(BOOST_ATOMIC_DETAIL_NO_ASM_IMPLIED_ZERO_DISPLACEMENTS) + + return v; +#endif + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +#endif // defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_GCC_X86_DCAS_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_linux_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_linux_arm.hpp new file mode 100644 index 0000000..41713a3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_linux_arm.hpp @@ -0,0 +1,178 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009, 2011 Helge Bahmann + * Copyright (c) 2009 Phil Endecott + * Copyright (c) 2013 Tim Blechmann + * Linux-specific code by Phil Endecott + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_linux_arm.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_LINUX_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_LINUX_ARM_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +// Different ARM processors have different atomic instructions. In particular, +// architecture versions before v6 (which are still in widespread use, e.g. the +// Intel/Marvell XScale chips like the one in the NSLU2) have only atomic swap. +// On Linux the kernel provides some support that lets us abstract away from +// these differences: it provides emulated CAS and barrier functions at special +// addresses that are guaranteed not to be interrupted by the kernel. Using +// this facility is slightly slower than inline assembler would be, but much +// faster than a system call. +// +// While this emulated CAS is "strong" in the sense that it does not fail +// "spuriously" (i.e.: it never fails to perform the exchange when the value +// found equals the value expected), it does not return the found value on +// failure. To satisfy the atomic API, compare_exchange_{weak|strong} must +// return the found value on failure, and we have to manually load this value +// after the emulated CAS reports failure. This in turn introduces a race +// between the CAS failing (due to the "wrong" value being found) and subsequently +// loading (which might turn up the "right" value). From an application's +// point of view this looks like "spurious failure", and therefore the +// emulated CAS is only good enough to provide compare_exchange_weak +// semantics. + +struct linux_arm_cas_base +{ + static BOOST_FORCEINLINE void fence_before_store(memory_order order) BOOST_NOEXCEPT + { + if ((order & memory_order_release) != 0) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + if (order == memory_order_seq_cst) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void fence_after_load(memory_order order) BOOST_NOEXCEPT + { + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + hardware_full_fence(); + } + + static BOOST_FORCEINLINE void hardware_full_fence() BOOST_NOEXCEPT + { + typedef void (*kernel_dmb_t)(void); + ((kernel_dmb_t)0xffff0fa0)(); + } +}; + +template< bool Signed > +struct linux_arm_cas : + public linux_arm_cas_base +{ + typedef typename make_storage_type< 4u, Signed >::type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + fence_before_store(order); + storage = v; + fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + while (true) + { + storage_type tmp = expected; + if (compare_exchange_weak(storage, tmp, desired, success_order, failure_order)) + return true; + if (tmp != expected) + { + expected = tmp; + return false; + } + } + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + typedef storage_type (*kernel_cmpxchg32_t)(storage_type oldval, storage_type newval, volatile storage_type* ptr); + + if (((kernel_cmpxchg32_t)0xffff0fc0)(expected, desired, &storage) == 0) + { + return true; + } + else + { + expected = storage; + return false; + } + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< cas_based_operations< cas_based_exchange< linux_arm_cas< Signed > > >, 1u, Signed > +{ +}; + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< cas_based_operations< cas_based_exchange< linux_arm_cas< Signed > > >, 2u, Signed > +{ +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public cas_based_operations< cas_based_exchange< linux_arm_cas< Signed > > > +{ +}; + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + linux_arm_cas_base::hardware_full_fence(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + __asm__ __volatile__ ("" ::: "memory"); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_LINUX_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_arm.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_arm.hpp new file mode 100644 index 0000000..ff953d6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_arm.hpp @@ -0,0 +1,824 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_msvc_arm.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_MSVC_ARM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_MSVC_ARM_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#define BOOST_ATOMIC_DETAIL_ARM_LOAD8(p) __iso_volatile_load8((const volatile __int8*)(p)) +#define BOOST_ATOMIC_DETAIL_ARM_LOAD16(p) __iso_volatile_load16((const volatile __int16*)(p)) +#define BOOST_ATOMIC_DETAIL_ARM_LOAD32(p) __iso_volatile_load32((const volatile __int32*)(p)) +#define BOOST_ATOMIC_DETAIL_ARM_LOAD64(p) __iso_volatile_load64((const volatile __int64*)(p)) +#define BOOST_ATOMIC_DETAIL_ARM_STORE8(p, v) __iso_volatile_store8((volatile __int8*)(p), (__int8)(v)) +#define BOOST_ATOMIC_DETAIL_ARM_STORE16(p, v) __iso_volatile_store16((volatile __int16*)(p), (__int16)(v)) +#define BOOST_ATOMIC_DETAIL_ARM_STORE32(p, v) __iso_volatile_store32((volatile __int32*)(p), (__int32)(v)) +#define BOOST_ATOMIC_DETAIL_ARM_STORE64(p, v) __iso_volatile_store64((volatile __int64*)(p), (__int64)(v)) + +namespace boost { +namespace atomics { +namespace detail { + +// A note about memory_order_consume. Technically, this architecture allows to avoid +// unnecessary memory barrier after consume load since it supports data dependency ordering. +// However, some compiler optimizations may break a seemingly valid code relying on data +// dependency tracking by injecting bogus branches to aid out of order execution. +// This may happen not only in Boost.Atomic code but also in user's code, which we have no +// control of. See this thread: http://lists.boost.org/Archives/boost/2014/06/213890.php. +// For this reason we promote memory_order_consume to memory_order_acquire. + +struct msvc_arm_operations_base +{ + static BOOST_FORCEINLINE void hardware_full_fence() BOOST_NOEXCEPT + { + __dmb(0xB); // _ARM_BARRIER_ISH, see armintr.h from MSVC 11 and later + } + + static BOOST_FORCEINLINE void fence_before_store(memory_order order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + if ((order & memory_order_release) != 0) + hardware_full_fence(); + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE void fence_after_store(memory_order order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + if (order == memory_order_seq_cst) + hardware_full_fence(); + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE void fence_after_load(memory_order order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + if ((order & (memory_order_consume | memory_order_acquire)) != 0) + hardware_full_fence(); + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE BOOST_CONSTEXPR memory_order cas_common_order(memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + // Combine order flags together and promote memory_order_consume to memory_order_acquire + return static_cast< memory_order >(((failure_order | success_order) & ~memory_order_consume) | (((failure_order | success_order) & memory_order_consume) << 1u)); + } +}; + +template< typename T, typename Derived > +struct msvc_arm_operations : + public msvc_arm_operations_base +{ + typedef T storage_type; + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + typedef typename make_signed< storage_type >::type signed_storage_type; + return Derived::fetch_add(storage, static_cast< storage_type >(-static_cast< signed_storage_type >(v)), order); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return Derived::compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!Derived::exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + Derived::store(storage, (storage_type)0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 1u, Signed > : + public msvc_arm_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > +{ + typedef msvc_arm_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before_store(order); + BOOST_ATOMIC_DETAIL_ARM_STORE8(&storage, v); + base_type::fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = BOOST_ATOMIC_DETAIL_ARM_LOAD8(&storage); + base_type::fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE8_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE8(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + storage_type previous = expected, old_val; + + switch (cas_common_order(success_order, failure_order)) + { + case memory_order_relaxed: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_RELAXED(&storage, desired, previous)); + break; + case memory_order_consume: + case memory_order_acquire: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_ACQUIRE(&storage, desired, previous)); + break; + case memory_order_release: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8_RELEASE(&storage, desired, previous)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8(&storage, desired, previous)); + break; + } + expected = old_val; + + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND8_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND8_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND8_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND8(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR8_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR8_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR8_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR8(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR8_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR8_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR8_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR8(&storage, v)); + break; + } + return v; + } +}; + +template< bool Signed > +struct operations< 2u, Signed > : + public msvc_arm_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > +{ + typedef msvc_arm_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before_store(order); + BOOST_ATOMIC_DETAIL_ARM_STORE16(&storage, v); + base_type::fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = BOOST_ATOMIC_DETAIL_ARM_LOAD16(&storage); + base_type::fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE16_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE16(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + storage_type previous = expected, old_val; + + switch (cas_common_order(success_order, failure_order)) + { + case memory_order_relaxed: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_RELAXED(&storage, desired, previous)); + break; + case memory_order_consume: + case memory_order_acquire: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_ACQUIRE(&storage, desired, previous)); + break; + case memory_order_release: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16_RELEASE(&storage, desired, previous)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16(&storage, desired, previous)); + break; + } + expected = old_val; + + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND16_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND16_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND16_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND16(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR16_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR16_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR16_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR16(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR16_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR16_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR16_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR16(&storage, v)); + break; + } + return v; + } +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public msvc_arm_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > +{ + typedef msvc_arm_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before_store(order); + BOOST_ATOMIC_DETAIL_ARM_STORE32(&storage, v); + base_type::fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = BOOST_ATOMIC_DETAIL_ARM_LOAD32(&storage); + base_type::fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + storage_type previous = expected, old_val; + + switch (cas_common_order(success_order, failure_order)) + { + case memory_order_relaxed: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_RELAXED(&storage, desired, previous)); + break; + case memory_order_consume: + case memory_order_acquire: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_ACQUIRE(&storage, desired, previous)); + break; + case memory_order_release: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE_RELEASE(&storage, desired, previous)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(&storage, desired, previous)); + break; + } + expected = old_val; + + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR(&storage, v)); + break; + } + return v; + } +}; + +template< bool Signed > +struct operations< 8u, Signed > : + public msvc_arm_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > +{ + typedef msvc_arm_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before_store(order); + BOOST_ATOMIC_DETAIL_ARM_STORE64(&storage, v); + base_type::fence_after_store(order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = BOOST_ATOMIC_DETAIL_ARM_LOAD64(&storage); + base_type::fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE64_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + storage_type previous = expected, old_val; + + switch (cas_common_order(success_order, failure_order)) + { + case memory_order_relaxed: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_RELAXED(&storage, desired, previous)); + break; + case memory_order_consume: + case memory_order_acquire: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_ACQUIRE(&storage, desired, previous)); + break; + case memory_order_release: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64_RELEASE(&storage, desired, previous)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(&storage, desired, previous)); + break; + } + expected = old_val; + + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND64_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND64_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND64_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND64(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR64_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR64_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR64_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR64(&storage, v)); + break; + } + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + switch (order) + { + case memory_order_relaxed: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR64_RELAXED(&storage, v)); + break; + case memory_order_consume: + case memory_order_acquire: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR64_ACQUIRE(&storage, v)); + break; + case memory_order_release: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR64_RELEASE(&storage, v)); + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + default: + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR64(&storage, v)); + break; + } + return v; + } +}; + + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + if (order != memory_order_relaxed) + msvc_arm_operations_base::hardware_full_fence(); + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#undef BOOST_ATOMIC_DETAIL_ARM_LOAD8 +#undef BOOST_ATOMIC_DETAIL_ARM_LOAD16 +#undef BOOST_ATOMIC_DETAIL_ARM_LOAD32 +#undef BOOST_ATOMIC_DETAIL_ARM_LOAD64 +#undef BOOST_ATOMIC_DETAIL_ARM_STORE8 +#undef BOOST_ATOMIC_DETAIL_ARM_STORE16 +#undef BOOST_ATOMIC_DETAIL_ARM_STORE32 +#undef BOOST_ATOMIC_DETAIL_ARM_STORE64 + +#endif // BOOST_ATOMIC_DETAIL_OPS_MSVC_ARM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_common.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_common.hpp new file mode 100644 index 0000000..53628f3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_common.hpp @@ -0,0 +1,38 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_msvc_common.hpp + * + * This header contains common tools for MSVC implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_MSVC_COMMON_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_MSVC_COMMON_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +// Define compiler barriers +#if defined(__INTEL_COMPILER) +#define BOOST_ATOMIC_DETAIL_COMPILER_BARRIER() __memory_barrier() +#elif defined(_MSC_VER) && !defined(_WIN32_WCE) +extern "C" void _ReadWriteBarrier(void); +#pragma intrinsic(_ReadWriteBarrier) +#define BOOST_ATOMIC_DETAIL_COMPILER_BARRIER() _ReadWriteBarrier() +#endif + +#ifndef BOOST_ATOMIC_DETAIL_COMPILER_BARRIER +#define BOOST_ATOMIC_DETAIL_COMPILER_BARRIER() +#endif + +#endif // BOOST_ATOMIC_DETAIL_OPS_MSVC_COMMON_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_x86.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_x86.hpp new file mode 100644 index 0000000..589c029 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_msvc_x86.hpp @@ -0,0 +1,928 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_msvc_x86.hpp + * + * This header contains implementation of the \c operations template. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_MSVC_X86_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_MSVC_X86_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) || defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) +#include +#include +#endif +#include +#if !defined(_M_IX86) && !(defined(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8) && defined(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16)) +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(BOOST_MSVC) +#pragma warning(push) +// frame pointer register 'ebx' modified by inline assembly code. See the note below. +#pragma warning(disable: 4731) +#endif + +#if defined(_MSC_VER) && (defined(_M_AMD64) || (defined(_M_IX86) && defined(_M_IX86_FP) && _M_IX86_FP >= 2)) +extern "C" void _mm_mfence(void); +#if defined(BOOST_MSVC) +#pragma intrinsic(_mm_mfence) +#endif +#endif + +namespace boost { +namespace atomics { +namespace detail { + +/* + * Implementation note for asm blocks. + * + * http://msdn.microsoft.com/en-us/data/k1a8ss06%28v=vs.105%29 + * + * Some SSE types require eight-byte stack alignment, forcing the compiler to emit dynamic stack-alignment code. + * To be able to access both the local variables and the function parameters after the alignment, the compiler + * maintains two frame pointers. If the compiler performs frame pointer omission (FPO), it will use EBP and ESP. + * If the compiler does not perform FPO, it will use EBX and EBP. To ensure code runs correctly, do not modify EBX + * in asm code if the function requires dynamic stack alignment as it could modify the frame pointer. + * Either move the eight-byte aligned types out of the function, or avoid using EBX. + * + * Since we have no way of knowing that the compiler uses FPO, we have to always save and restore ebx + * whenever we have to clobber it. Additionally, we disable warning C4731 above so that the compiler + * doesn't spam about ebx use. + */ + +struct msvc_x86_operations_base +{ + static BOOST_FORCEINLINE void hardware_full_fence() BOOST_NOEXCEPT + { +#if defined(_MSC_VER) && (defined(_M_AMD64) || (defined(_M_IX86) && defined(_M_IX86_FP) && _M_IX86_FP >= 2)) + // Use mfence only if SSE2 is available + _mm_mfence(); +#else + long tmp; + BOOST_ATOMIC_INTERLOCKED_EXCHANGE(&tmp, 0); +#endif + } + + static BOOST_FORCEINLINE void fence_before(memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE void fence_after(memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE void fence_after_load(memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + // On x86 and x86_64 there is no need for a hardware barrier, + // even if seq_cst memory order is requested, because all + // seq_cst writes are implemented with lock-prefixed operations + // or xchg which has implied lock prefix. Therefore normal loads + // are already ordered with seq_cst stores on these architectures. + } +}; + +template< typename T, typename Derived > +struct msvc_x86_operations : + public msvc_x86_operations_base +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + if (order != memory_order_seq_cst) + { + fence_before(order); + storage = v; + fence_after(order); + } + else + { + Derived::exchange(storage, v, order); + } + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + storage_type v = storage; + fence_after_load(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + typedef typename make_signed< storage_type >::type signed_storage_type; + return Derived::fetch_add(storage, static_cast< storage_type >(-static_cast< signed_storage_type >(v)), order); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return Derived::compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!Derived::exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, (storage_type)0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public msvc_x86_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE(&storage, v)); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + storage_type old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(&storage, desired, previous)); + expected = old_val; + return (previous == old_val); + } + +#if defined(BOOST_ATOMIC_INTERLOCKED_AND) + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND(&storage, v)); + } +#else + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res & v, order, memory_order_relaxed)) {} + return res; + } +#endif + +#if defined(BOOST_ATOMIC_INTERLOCKED_OR) + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR(&storage, v)); + } +#else + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res | v, order, memory_order_relaxed)) {} + return res; + } +#endif + +#if defined(BOOST_ATOMIC_INTERLOCKED_XOR) + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR(&storage, v)); + } +#else + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res ^ v, order, memory_order_relaxed)) {} + return res; + } +#endif +}; + +#if defined(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8) + +template< bool Signed > +struct operations< 1u, Signed > : + public msvc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD8(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE8(&storage, v)); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + storage_type old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE8(&storage, desired, previous)); + expected = old_val; + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND8(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR8(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR8(&storage, v)); + } +}; + +#elif defined(_M_IX86) + +template< bool Signed > +struct operations< 1u, Signed > : + public msvc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 1u, Signed >::type, operations< 1u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 1u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + __asm + { + mov edx, storage + movzx eax, v + lock xadd byte ptr [edx], al + mov v, al + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + __asm + { + mov edx, storage + movzx eax, v + xchg byte ptr [edx], al + mov v, al + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order) BOOST_NOEXCEPT + { + base_type::fence_before(success_order); + bool success; + __asm + { + mov esi, expected + mov edi, storage + movzx eax, byte ptr [esi] + movzx edx, desired + lock cmpxchg byte ptr [edi], dl + mov byte ptr [esi], al + sete success + }; + // The success and failure fences are equivalent anyway + base_type::fence_after(success_order); + return success; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, byte ptr [edi] + align 16 + again: + mov dl, al + and dl, bl + lock cmpxchg byte ptr [edi], dl + jne again + mov v, al + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, byte ptr [edi] + align 16 + again: + mov dl, al + or dl, bl + lock cmpxchg byte ptr [edi], dl + jne again + mov v, al + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, byte ptr [edi] + align 16 + again: + mov dl, al + xor dl, bl + lock cmpxchg byte ptr [edi], dl + jne again + mov v, al + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } +}; + +#else + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 1u, Signed > +{ +}; + +#endif + +#if defined(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16) + +template< bool Signed > +struct operations< 2u, Signed > : + public msvc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD16(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE16(&storage, v)); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + storage_type old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE16(&storage, desired, previous)); + expected = old_val; + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND16(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR16(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR16(&storage, v)); + } +}; + +#elif defined(_M_IX86) + +template< bool Signed > +struct operations< 2u, Signed > : + public msvc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 2u, Signed >::type, operations< 2u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 2u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + __asm + { + mov edx, storage + movzx eax, v + lock xadd word ptr [edx], ax + mov v, ax + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + __asm + { + mov edx, storage + movzx eax, v + xchg word ptr [edx], ax + mov v, ax + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order) BOOST_NOEXCEPT + { + base_type::fence_before(success_order); + bool success; + __asm + { + mov esi, expected + mov edi, storage + movzx eax, word ptr [esi] + movzx edx, desired + lock cmpxchg word ptr [edi], dx + mov word ptr [esi], ax + sete success + }; + // The success and failure fences are equivalent anyway + base_type::fence_after(success_order); + return success; + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, word ptr [edi] + align 16 + again: + mov dx, ax + and dx, bx + lock cmpxchg word ptr [edi], dx + jne again + mov v, ax + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, word ptr [edi] + align 16 + again: + mov dx, ax + or dx, bx + lock cmpxchg word ptr [edi], dx + jne again + mov v, ax + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + int backup; + __asm + { + mov backup, ebx + xor edx, edx + mov edi, storage + movzx ebx, v + movzx eax, word ptr [edi] + align 16 + again: + mov dx, ax + xor dx, bx + lock cmpxchg word ptr [edi], dx + jne again + mov v, ax + mov ebx, backup + }; + base_type::fence_after(order); + return v; + } +}; + +#else + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 2u, Signed > +{ +}; + +#endif + + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG8B) + +template< bool Signed > +struct msvc_dcas_x86 +{ + typedef typename make_storage_type< 8u, Signed >::type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + // Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 3A, 8.1.1. Guaranteed Atomic Operations: + // + // The Pentium processor (and newer processors since) guarantees that the following additional memory operations will always be carried out atomically: + // * Reading or writing a quadword aligned on a 64-bit boundary + // + // Luckily, the memory is almost always 8-byte aligned in our case because atomic<> uses 64 bit native types for storage and dynamic memory allocations + // have at least 8 byte alignment. The only unfortunate case is when atomic is placed on the stack and it is not 8-byte aligned (like on 32 bit Windows). + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + storage_type volatile* p = &storage; + if (((uint32_t)p & 0x00000007) == 0) + { +#if defined(_M_IX86_FP) && _M_IX86_FP >= 2 +#if defined(__AVX__) + __asm + { + mov edx, p + vmovq xmm4, v + vmovq qword ptr [edx], xmm4 + }; +#else + __asm + { + mov edx, p + movq xmm4, v + movq qword ptr [edx], xmm4 + }; +#endif +#else + __asm + { + mov edx, p + fild v + fistp qword ptr [edx] + }; +#endif + } + else + { + int backup; + __asm + { + mov backup, ebx + mov edi, p + mov ebx, dword ptr [v] + mov ecx, dword ptr [v + 4] + mov eax, dword ptr [edi] + mov edx, dword ptr [edi + 4] + align 16 + again: + lock cmpxchg8b qword ptr [edi] + jne again + mov ebx, backup + }; + } + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + storage_type const volatile* p = &storage; + storage_type value; + + if (((uint32_t)p & 0x00000007) == 0) + { +#if defined(_M_IX86_FP) && _M_IX86_FP >= 2 +#if defined(__AVX__) + __asm + { + mov edx, p + vmovq xmm4, qword ptr [edx] + vmovq value, xmm4 + }; +#else + __asm + { + mov edx, p + movq xmm4, qword ptr [edx] + movq value, xmm4 + }; +#endif +#else + __asm + { + mov edx, p + fild qword ptr [edx] + fistp value + }; +#endif + } + else + { + // We don't care for comparison result here; the previous value will be stored into value anyway. + // Also we don't care for ebx and ecx values, they just have to be equal to eax and edx before cmpxchg8b. + __asm + { + mov edi, p + mov eax, ebx + mov edx, ecx + lock cmpxchg8b qword ptr [edi] + mov dword ptr [value], eax + mov dword ptr [value + 4], edx + }; + } + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + return value; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + // MSVC-11 in 32-bit mode sometimes generates messed up code without compiler barriers, + // even though the _InterlockedCompareExchange64 intrinsic already provides one. + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + storage_type volatile* p = &storage; +#if defined(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64) + const storage_type old_val = (storage_type)BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(p, desired, expected); + const bool result = (old_val == expected); + expected = old_val; +#else + bool result; + int backup; + __asm + { + mov backup, ebx + mov edi, p + mov esi, expected + mov ebx, dword ptr [desired] + mov ecx, dword ptr [desired + 4] + mov eax, dword ptr [esi] + mov edx, dword ptr [esi + 4] + lock cmpxchg8b qword ptr [edi] + mov dword ptr [esi], eax + mov dword ptr [esi + 4], edx + mov ebx, backup + sete result + }; +#endif + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + return result; + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + storage_type volatile* p = &storage; + int backup; + __asm + { + mov backup, ebx + mov edi, p + mov ebx, dword ptr [v] + mov ecx, dword ptr [v + 4] + mov eax, dword ptr [edi] + mov edx, dword ptr [edi + 4] + align 16 + again: + lock cmpxchg8b qword ptr [edi] + jne again + mov ebx, backup + mov dword ptr [v], eax + mov dword ptr [v + 4], edx + }; + + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + + return v; + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 8u, Signed > : + public cas_based_operations< msvc_dcas_x86< Signed > > +{ +}; + +#elif defined(_M_AMD64) + +template< bool Signed > +struct operations< 8u, Signed > : + public msvc_x86_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > +{ + typedef msvc_x86_operations< typename make_storage_type< 8u, Signed >::type, operations< 8u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 8u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD64(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE64(&storage, v)); + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + storage_type old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE64(&storage, desired, previous)); + expected = old_val; + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND64(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR64(&storage, v)); + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + return static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR64(&storage, v)); + } +}; + +#endif + +#if defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +template< bool Signed > +struct msvc_dcas_x86_64 +{ + typedef typename make_storage_type< 16u, Signed >::type storage_type; + typedef typename make_storage_type< 16u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order) BOOST_NOEXCEPT + { + storage_type value = const_cast< storage_type& >(storage); + while (!BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE128(&storage, v, &value)) {} + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order) BOOST_NOEXCEPT + { + storage_type value = storage_type(); + BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE128(&storage, value, &value); + return value; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order, memory_order) BOOST_NOEXCEPT + { + return !!BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE128(&storage, desired, &expected); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 16u, Signed > : + public cas_based_operations< cas_based_exchange< msvc_dcas_x86_64< Signed > > > +{ +}; + +#endif // defined(BOOST_ATOMIC_DETAIL_X86_HAS_CMPXCHG16B) + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + if (order == memory_order_seq_cst) + msvc_x86_operations_base::hardware_full_fence(); + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif // BOOST_ATOMIC_DETAIL_OPS_MSVC_X86_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_windows.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_windows.hpp new file mode 100644 index 0000000..191eb84 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/ops_windows.hpp @@ -0,0 +1,216 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/ops_windows.hpp + * + * This header contains implementation of the \c operations template. + * + * This implementation is the most basic version for Windows. It should + * work for any non-MSVC-like compilers as long as there are Interlocked WinAPI + * functions available. This version is also used for WinCE. + * + * Notably, this implementation is not as efficient as other + * versions based on compiler intrinsics. + */ + +#ifndef BOOST_ATOMIC_DETAIL_OPS_WINDOWS_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_OPS_WINDOWS_HPP_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +struct windows_operations_base +{ + static BOOST_FORCEINLINE void hardware_full_fence() BOOST_NOEXCEPT + { + long tmp; + BOOST_ATOMIC_INTERLOCKED_EXCHANGE(&tmp, 0); + } + + static BOOST_FORCEINLINE void fence_before(memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } + + static BOOST_FORCEINLINE void fence_after(memory_order) BOOST_NOEXCEPT + { + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + } +}; + +template< typename T, typename Derived > +struct windows_operations : + public windows_operations_base +{ + typedef T storage_type; + + static BOOST_FORCEINLINE void store(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + Derived::exchange(storage, v, order); + } + + static BOOST_FORCEINLINE storage_type load(storage_type const volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return Derived::fetch_add(const_cast< storage_type volatile& >(storage), (storage_type)0, order); + } + + static BOOST_FORCEINLINE storage_type fetch_sub(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + typedef typename make_signed< storage_type >::type signed_storage_type; + return Derived::fetch_add(storage, static_cast< storage_type >(-static_cast< signed_storage_type >(v)), order); + } + + static BOOST_FORCEINLINE bool compare_exchange_weak( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + return Derived::compare_exchange_strong(storage, expected, desired, success_order, failure_order); + } + + static BOOST_FORCEINLINE bool test_and_set(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + return !!Derived::exchange(storage, (storage_type)1, order); + } + + static BOOST_FORCEINLINE void clear(storage_type volatile& storage, memory_order order) BOOST_NOEXCEPT + { + store(storage, (storage_type)0, order); + } + + static BOOST_FORCEINLINE bool is_lock_free(storage_type const volatile&) BOOST_NOEXCEPT + { + return true; + } +}; + +template< bool Signed > +struct operations< 4u, Signed > : + public windows_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > +{ + typedef windows_operations< typename make_storage_type< 4u, Signed >::type, operations< 4u, Signed > > base_type; + typedef typename base_type::storage_type storage_type; + typedef typename make_storage_type< 4u, Signed >::aligned aligned_storage_type; + + static BOOST_FORCEINLINE storage_type fetch_add(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE_ADD(&storage, v)); + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE storage_type exchange(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { + base_type::fence_before(order); + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_EXCHANGE(&storage, v)); + base_type::fence_after(order); + return v; + } + + static BOOST_FORCEINLINE bool compare_exchange_strong( + storage_type volatile& storage, storage_type& expected, storage_type desired, memory_order success_order, memory_order failure_order) BOOST_NOEXCEPT + { + storage_type previous = expected; + base_type::fence_before(success_order); + storage_type old_val = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_COMPARE_EXCHANGE(&storage, desired, previous)); + expected = old_val; + // The success and failure fences are the same anyway + base_type::fence_after(success_order); + return (previous == old_val); + } + + static BOOST_FORCEINLINE storage_type fetch_and(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { +#if defined(BOOST_ATOMIC_INTERLOCKED_AND) + base_type::fence_before(order); + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_AND(&storage, v)); + base_type::fence_after(order); + return v; +#else + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res & v, order, memory_order_relaxed)) {} + return res; +#endif + } + + static BOOST_FORCEINLINE storage_type fetch_or(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { +#if defined(BOOST_ATOMIC_INTERLOCKED_OR) + base_type::fence_before(order); + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_OR(&storage, v)); + base_type::fence_after(order); + return v; +#else + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res | v, order, memory_order_relaxed)) {} + return res; +#endif + } + + static BOOST_FORCEINLINE storage_type fetch_xor(storage_type volatile& storage, storage_type v, memory_order order) BOOST_NOEXCEPT + { +#if defined(BOOST_ATOMIC_INTERLOCKED_XOR) + base_type::fence_before(order); + v = static_cast< storage_type >(BOOST_ATOMIC_INTERLOCKED_XOR(&storage, v)); + base_type::fence_after(order); + return v; +#else + storage_type res = storage; + while (!compare_exchange_strong(storage, res, res ^ v, order, memory_order_relaxed)) {} + return res; +#endif + } +}; + +template< bool Signed > +struct operations< 1u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 1u, Signed > +{ +}; + +template< bool Signed > +struct operations< 2u, Signed > : + public extending_cas_based_operations< operations< 4u, Signed >, 2u, Signed > +{ +}; + +BOOST_FORCEINLINE void thread_fence(memory_order order) BOOST_NOEXCEPT +{ + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); + if (order == memory_order_seq_cst) + windows_operations_base::hardware_full_fence(); + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +BOOST_FORCEINLINE void signal_fence(memory_order order) BOOST_NOEXCEPT +{ + if (order != memory_order_relaxed) + BOOST_ATOMIC_DETAIL_COMPILER_BARRIER(); +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_OPS_WINDOWS_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/pause.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/pause.hpp new file mode 100644 index 0000000..37aa5ca --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/pause.hpp @@ -0,0 +1,43 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * (C) Copyright 2013 Tim Blechmann + * (C) Copyright 2013 Andrey Semashev + */ + +#ifndef BOOST_ATOMIC_DETAIL_PAUSE_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_PAUSE_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(_MSC_VER) && (defined(_M_AMD64) || defined(_M_IX86)) +extern "C" void _mm_pause(void); +#if defined(BOOST_MSVC) +#pragma intrinsic(_mm_pause) +#endif +#endif + +namespace boost { +namespace atomics { +namespace detail { + +BOOST_FORCEINLINE void pause() BOOST_NOEXCEPT +{ +#if defined(_MSC_VER) && (defined(_M_AMD64) || defined(_M_IX86)) + _mm_pause(); +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + __asm__ __volatile__("pause;"); +#endif +} + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_PAUSE_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/platform.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/platform.hpp new file mode 100644 index 0000000..b6c48ef --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/platform.hpp @@ -0,0 +1,121 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/platform.hpp + * + * This header defines macros for the target platform detection + */ + +#ifndef BOOST_ATOMIC_DETAIL_PLATFORM_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_PLATFORM_HPP_INCLUDED_ + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined(BOOST_ATOMIC_FORCE_FALLBACK) + +// Compiler-based backends +#if (defined(__ibmxl__) || defined(__IBMCPP__)) && defined(__PPC__) + +// IBM XL C++ Compiler has to be checked before GCC/Clang as it pretends to be one but does not support __atomic* intrinsics. +// It does support GCC inline assembler though. +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_ppc + +#elif ((defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 407)) ||\ + (defined(BOOST_CLANG) && ((__clang_major__ * 100 + __clang_minor__) >= 302))) &&\ + (\ + (__GCC_ATOMIC_BOOL_LOCK_FREE + 0) == 2 ||\ + (__GCC_ATOMIC_CHAR_LOCK_FREE + 0) == 2 ||\ + (__GCC_ATOMIC_SHORT_LOCK_FREE + 0) == 2 ||\ + (__GCC_ATOMIC_INT_LOCK_FREE + 0) == 2 ||\ + (__GCC_ATOMIC_LONG_LOCK_FREE + 0) == 2 ||\ + (__GCC_ATOMIC_LLONG_LOCK_FREE + 0) == 2\ + ) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_atomic + +#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__)) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_x86 + +#elif defined(__GNUC__) && (defined(__POWERPC__) || defined(__PPC__)) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_ppc + +// This list of ARM architecture versions comes from Apple's arm/arch.h header. +// I don't know how complete it is. +#elif defined(__GNUC__) &&\ + (\ + defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_6J__) ||\ + defined(__ARM_ARCH_6K__) || defined(__ARM_ARCH_6Z__) ||\ + defined(__ARM_ARCH_6ZK__) ||\ + defined(__ARM_ARCH_7__) || defined(__ARM_ARCH_7A__) ||\ + defined(__ARM_ARCH_7R__) || defined(__ARM_ARCH_7M__) ||\ + defined(__ARM_ARCH_7EM__) || defined(__ARM_ARCH_7S__)\ + ) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_arm + +#elif defined(__GNUC__) && defined(__sparc_v9__) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_sparc + +#elif defined(__GNUC__) && defined(__alpha__) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_alpha + +#elif defined(__GNUC__) && ((__GNUC__ * 100 + __GNUC_MINOR__) >= 401) &&\ + (\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_1) ||\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_2) ||\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_4) ||\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) ||\ + defined(__GCC_HAVE_SYNC_COMPARE_AND_SWAP_16)\ + ) + +#define BOOST_ATOMIC_DETAIL_PLATFORM gcc_sync + +#elif defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64)) + +#define BOOST_ATOMIC_DETAIL_PLATFORM msvc_x86 + +#elif defined(_MSC_VER) && _MSC_VER >= 1700 && defined(_M_ARM) + +#define BOOST_ATOMIC_DETAIL_PLATFORM msvc_arm + +#endif + +// OS-based backends +#if !defined(BOOST_ATOMIC_DETAIL_PLATFORM) + +#if defined(__linux__) && defined(__arm__) + +#define BOOST_ATOMIC_DETAIL_PLATFORM linux_arm + +#elif defined(BOOST_WINDOWS) || defined(_WIN32_CE) + +#define BOOST_ATOMIC_DETAIL_PLATFORM windows + +#endif + +#endif // !defined(BOOST_ATOMIC_DETAIL_PLATFORM) + +#endif // !defined(BOOST_ATOMIC_FORCE_FALLBACK) + +#if !defined(BOOST_ATOMIC_DETAIL_PLATFORM) +#define BOOST_ATOMIC_DETAIL_PLATFORM emulated +#define BOOST_ATOMIC_EMULATED +#endif + +#define BOOST_ATOMIC_DETAIL_HEADER(prefix) + +#endif // BOOST_ATOMIC_DETAIL_PLATFORM_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/detail/storage_type.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/detail/storage_type.hpp new file mode 100644 index 0000000..63a7cef --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/detail/storage_type.hpp @@ -0,0 +1,280 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2009 Helge Bahmann + * Copyright (c) 2012 Tim Blechmann + * Copyright (c) 2013 - 2014 Andrey Semashev + */ +/*! + * \file atomic/detail/storage_type.hpp + * + * This header defines underlying types used as storage + */ + +#ifndef BOOST_ATOMIC_DETAIL_STORAGE_TYPE_HPP_INCLUDED_ +#define BOOST_ATOMIC_DETAIL_STORAGE_TYPE_HPP_INCLUDED_ + +#include +#include +#include +#if !defined(BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCMP) || !defined(BOOST_ATOMIC_DETAIL_HAS_BUILTIN_MEMCPY) +#include +#endif + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { +namespace atomics { +namespace detail { + +template< typename T > +BOOST_FORCEINLINE void non_atomic_load(T const volatile& from, T& to) BOOST_NOEXCEPT +{ + to = from; +} + +template< std::size_t Size > +struct buffer_storage +{ + BOOST_ALIGNMENT(16) unsigned char data[Size]; + + BOOST_FORCEINLINE bool operator! () const BOOST_NOEXCEPT + { + return (data[0] == 0u && BOOST_ATOMIC_DETAIL_MEMCMP(data, data + 1, Size - 1) == 0); + } + + BOOST_FORCEINLINE bool operator== (buffer_storage const& that) const BOOST_NOEXCEPT + { + return BOOST_ATOMIC_DETAIL_MEMCMP(data, that.data, Size) == 0; + } + + BOOST_FORCEINLINE bool operator!= (buffer_storage const& that) const BOOST_NOEXCEPT + { + return BOOST_ATOMIC_DETAIL_MEMCMP(data, that.data, Size) != 0; + } +}; + +template< std::size_t Size > +BOOST_FORCEINLINE void non_atomic_load(buffer_storage< Size > const volatile& from, buffer_storage< Size >& to) BOOST_NOEXCEPT +{ + BOOST_ATOMIC_DETAIL_MEMCPY(to.data, const_cast< unsigned char const* >(from.data), Size); +} + +template< std::size_t Size, bool Signed > +struct make_storage_type +{ + typedef buffer_storage< Size > type; + + struct aligned + { + type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type const& v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 1u, false > +{ + typedef boost::uint8_t type; + + struct aligned + { + type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 1u, true > +{ + typedef boost::int8_t type; + + struct aligned + { + type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 2u, false > +{ + typedef boost::uint16_t type; + + struct aligned + { + BOOST_ALIGNMENT(2) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 2u, true > +{ + typedef boost::int16_t type; + + struct aligned + { + BOOST_ALIGNMENT(2) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 4u, false > +{ + typedef boost::uint32_t type; + + struct aligned + { + BOOST_ALIGNMENT(4) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 4u, true > +{ + typedef boost::int32_t type; + + struct aligned + { + BOOST_ALIGNMENT(4) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 8u, false > +{ + typedef boost::uint64_t type; + + struct aligned + { + BOOST_ALIGNMENT(8) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 8u, true > +{ + typedef boost::int64_t type; + + struct aligned + { + BOOST_ALIGNMENT(8) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +#if defined(BOOST_HAS_INT128) + +template< > +struct make_storage_type< 16u, false > +{ + typedef boost::uint128_type type; + + struct aligned + { + BOOST_ALIGNMENT(16) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +template< > +struct make_storage_type< 16u, true > +{ + typedef boost::int128_type type; + + struct aligned + { + BOOST_ALIGNMENT(16) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +#elif !defined(BOOST_NO_ALIGNMENT) + +struct storage128_t +{ + boost::uint64_t data[2]; + + BOOST_FORCEINLINE bool operator! () const BOOST_NOEXCEPT + { + return data[0] == 0 && data[1] == 0; + } +}; + +BOOST_FORCEINLINE bool operator== (storage128_t const& left, storage128_t const& right) BOOST_NOEXCEPT +{ + return left.data[0] == right.data[0] && left.data[1] == right.data[1]; +} +BOOST_FORCEINLINE bool operator!= (storage128_t const& left, storage128_t const& right) BOOST_NOEXCEPT +{ + return !(left == right); +} + +BOOST_FORCEINLINE void non_atomic_load(storage128_t const volatile& from, storage128_t& to) BOOST_NOEXCEPT +{ + to.data[0] = from.data[0]; + to.data[1] = from.data[1]; +} + +template< bool Signed > +struct make_storage_type< 16u, Signed > +{ + typedef storage128_t type; + + struct aligned + { + BOOST_ALIGNMENT(16) type value; + + BOOST_DEFAULTED_FUNCTION(aligned(), {}) + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit aligned(type const& v) BOOST_NOEXCEPT : value(v) {} + }; +}; + +#endif + +template< typename T > +struct storage_size_of +{ + enum _ + { + size = sizeof(T), + value = (size == 3 ? 4 : (size >= 5 && size <= 7 ? 8 : (size >= 9 && size <= 15 ? 16 : size))) + }; +}; + +} // namespace detail +} // namespace atomics +} // namespace boost + +#endif // BOOST_ATOMIC_DETAIL_STORAGE_TYPE_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/atomic/fences.hpp b/thirdparty/source/boost_1_61_0/boost/atomic/fences.hpp new file mode 100644 index 0000000..31e3040 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/atomic/fences.hpp @@ -0,0 +1,67 @@ +/* + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * Copyright (c) 2011 Helge Bahmann + * Copyright (c) 2013 Tim Blechmann + * Copyright (c) 2014 Andrey Semashev + */ +/*! + * \file atomic/fences.hpp + * + * This header contains definition of \c atomic_thread_fence and \c atomic_signal_fence functions. + */ + +#ifndef BOOST_ATOMIC_FENCES_HPP_INCLUDED_ +#define BOOST_ATOMIC_FENCES_HPP_INCLUDED_ + +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +/* + * IMPLEMENTATION NOTE: All interface functions MUST be declared with BOOST_FORCEINLINE, + * see comment for convert_memory_order_to_gcc in ops_gcc_atomic.hpp. + */ + +namespace boost { + +namespace atomics { + +#if BOOST_ATOMIC_THREAD_FENCE > 0 +BOOST_FORCEINLINE void atomic_thread_fence(memory_order order) BOOST_NOEXCEPT +{ + detail::thread_fence(order); +} +#else +BOOST_FORCEINLINE void atomic_thread_fence(memory_order) BOOST_NOEXCEPT +{ + detail::lockpool::thread_fence(); +} +#endif + +#if BOOST_ATOMIC_SIGNAL_FENCE > 0 +BOOST_FORCEINLINE void atomic_signal_fence(memory_order order) BOOST_NOEXCEPT +{ + detail::signal_fence(order); +} +#else +BOOST_FORCEINLINE void atomic_signal_fence(memory_order) BOOST_NOEXCEPT +{ + detail::lockpool::signal_fence(); +} +#endif + +} // namespace atomics + +using atomics::atomic_thread_fence; +using atomics::atomic_signal_fence; + +} // namespace boost + +#endif // BOOST_ATOMIC_FENCES_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/bind.hpp b/thirdparty/source/boost_1_61_0/boost/bind.hpp new file mode 100644 index 0000000..450120c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind.hpp @@ -0,0 +1,41 @@ +#ifndef BOOST_BIND_HPP_INCLUDED +#define BOOST_BIND_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// bind.hpp - binds function objects to arguments +// +// Copyright (c) 2009, 2015 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +#include + +#ifndef BOOST_BIND_NO_PLACEHOLDERS + +#if defined(BOOST_CLANG) +# pragma clang diagnostic push +# if __has_warning("-Wheader-hygiene") +# pragma clang diagnostic ignored "-Wheader-hygiene" +# endif +#endif + +using namespace boost::placeholders; + +#if defined(BOOST_CLANG) +# pragma clang diagnostic pop +#endif + +#endif // #ifndef BOOST_BIND_NO_PLACEHOLDERS + +#endif // #ifndef BOOST_BIND_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/bind/arg.hpp b/thirdparty/source/boost_1_61_0/boost/bind/arg.hpp new file mode 100644 index 0000000..a74b829 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/arg.hpp @@ -0,0 +1,62 @@ +#ifndef BOOST_BIND_ARG_HPP_INCLUDED +#define BOOST_BIND_ARG_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// bind/arg.hpp +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +#include +#include +#include + +namespace boost +{ + +template< int I > struct arg +{ + BOOST_CONSTEXPR arg() + { + } + + template< class T > BOOST_CONSTEXPR arg( T const & /* t */ ) + { + BOOST_STATIC_ASSERT( I == is_placeholder::value ); + } +}; + +template< int I > BOOST_CONSTEXPR bool operator==( arg const &, arg const & ) +{ + return true; +} + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template< int I > struct is_placeholder< arg > +{ + enum _vt { value = I }; +}; + +template< int I > struct is_placeholder< arg (*) () > +{ + enum _vt { value = I }; +}; + +#endif + +} // namespace boost + +#endif // #ifndef BOOST_BIND_ARG_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/bind/bind.hpp b/thirdparty/source/boost_1_61_0/boost/bind/bind.hpp new file mode 100644 index 0000000..f793551 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/bind.hpp @@ -0,0 +1,2256 @@ +#ifndef BOOST_BIND_BIND_HPP_INCLUDED +#define BOOST_BIND_BIND_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// bind.hpp - binds function objects to arguments +// +// Copyright (c) 2001-2004 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2001 David Abrahams +// Copyright (c) 2005 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) +#include // std::forward +#endif + +// Borland-specific bug, visit_each() silently fails to produce code + +#if defined(__BORLANDC__) +# define BOOST_BIND_VISIT_EACH boost::visit_each +#else +# define BOOST_BIND_VISIT_EACH visit_each +#endif + +#include + +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4512) // assignment operator could not be generated +#endif + +namespace boost +{ + +template class weak_ptr; + +namespace _bi // implementation details +{ + +// result_traits + +template struct result_traits +{ + typedef R type; +}; + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + +struct unspecified {}; + +template struct result_traits +{ + typedef typename F::result_type type; +}; + +template struct result_traits< unspecified, reference_wrapper > +{ + typedef typename F::result_type type; +}; + +#endif + +// ref_compare + +template bool ref_compare( T const & a, T const & b, long ) +{ + return a == b; +} + +template bool ref_compare( arg const &, arg const &, int ) +{ + return true; +} + +template bool ref_compare( arg (*) (), arg (*) (), int ) +{ + return true; +} + +template bool ref_compare( reference_wrapper const & a, reference_wrapper const & b, int ) +{ + return a.get_pointer() == b.get_pointer(); +} + +// bind_t forward declaration for listN + +template class bind_t; + +template bool ref_compare( bind_t const & a, bind_t const & b, int ) +{ + return a.compare( b ); +} + +// value + +template class value +{ +public: + + value(T const & t): t_(t) {} + + T & get() { return t_; } + T const & get() const { return t_; } + + bool operator==(value const & rhs) const + { + return t_ == rhs.t_; + } + +private: + + T t_; +}; + +// ref_compare for weak_ptr + +template bool ref_compare( value< weak_ptr > const & a, value< weak_ptr > const & b, int ) +{ + return !(a.get() < b.get()) && !(b.get() < a.get()); +} + +// type + +template class type {}; + +// unwrap + +template struct unwrapper +{ + static inline F & unwrap( F & f, long ) + { + return f; + } + + template static inline F2 & unwrap( reference_wrapper rf, int ) + { + return rf.get(); + } + + template static inline _mfi::dm unwrap( R T::* pm, int ) + { + return _mfi::dm( pm ); + } +}; + +// listN + +class list0 +{ +public: + + list0() {} + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A &, long) + { + return unwrapper::unwrap(f, 0)(); + } + + template R operator()(type, F const & f, A &, long) const + { + return unwrapper::unwrap(f, 0)(); + } + + template void operator()(type, F & f, A &, int) + { + unwrapper::unwrap(f, 0)(); + } + + template void operator()(type, F const & f, A &, int) const + { + unwrapper::unwrap(f, 0)(); + } + + template void accept(V &) const + { + } + + bool operator==(list0 const &) const + { + return true; + } +}; + +#ifdef BOOST_MSVC +// MSVC is bright enough to realise that the parameter rhs +// in operator==may be unused for some template argument types: +#pragma warning(push) +#pragma warning(disable:4100) +#endif + +template< class A1 > class list1: private storage1< A1 > +{ +private: + + typedef storage1< A1 > base_type; + +public: + + explicit list1( A1 a1 ): base_type( a1 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list1 const & rhs) const + { + return ref_compare(base_type::a1_, rhs.a1_, 0); + } +}; + +struct logical_and; +struct logical_or; + +template< class A1, class A2 > class list2: private storage2< A1, A2 > +{ +private: + + typedef storage2< A1, A2 > base_type; + +public: + + list2( A1 a1, A2 a2 ): base_type( a1, a2 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_]); + } + + template bool operator()( type, logical_and & /*f*/, A & a, int ) + { + return a[ base_type::a1_ ] && a[ base_type::a2_ ]; + } + + template bool operator()( type, logical_and const & /*f*/, A & a, int ) const + { + return a[ base_type::a1_ ] && a[ base_type::a2_ ]; + } + + template bool operator()( type, logical_or & /*f*/, A & a, int ) + { + return a[ base_type::a1_ ] || a[ base_type::a2_ ]; + } + + template bool operator()( type, logical_or const & /*f*/, A & a, int ) const + { + return a[ base_type::a1_ ] || a[ base_type::a2_ ]; + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list2 const & rhs) const + { + return ref_compare(base_type::a1_, rhs.a1_, 0) && ref_compare(base_type::a2_, rhs.a2_, 0); + } +}; + +template< class A1, class A2, class A3 > class list3: private storage3< A1, A2, A3 > +{ +private: + + typedef storage3< A1, A2, A3 > base_type; + +public: + + list3( A1 a1, A2 a2, A3 a3 ): base_type( a1, a2, a3 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list3 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ); + } +}; + +template< class A1, class A2, class A3, class A4 > class list4: private storage4< A1, A2, A3, A4 > +{ +private: + + typedef storage4< A1, A2, A3, A4 > base_type; + +public: + + list4( A1 a1, A2 a2, A3 a3, A4 a4 ): base_type( a1, a2, a3, a4 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list4 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ); + } +}; + +template< class A1, class A2, class A3, class A4, class A5 > class list5: private storage5< A1, A2, A3, A4, A5 > +{ +private: + + typedef storage5< A1, A2, A3, A4, A5 > base_type; + +public: + + list5( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5 ): base_type( a1, a2, a3, a4, a5 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + A5 operator[] (boost::arg<5>) const { return base_type::a5_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + A5 operator[] (boost::arg<5> (*) ()) const { return base_type::a5_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list5 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ) && + ref_compare( base_type::a5_, rhs.a5_, 0 ); + } +}; + +template class list6: private storage6< A1, A2, A3, A4, A5, A6 > +{ +private: + + typedef storage6< A1, A2, A3, A4, A5, A6 > base_type; + +public: + + list6( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6 ): base_type( a1, a2, a3, a4, a5, a6 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + A5 operator[] (boost::arg<5>) const { return base_type::a5_; } + A6 operator[] (boost::arg<6>) const { return base_type::a6_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + A5 operator[] (boost::arg<5> (*) ()) const { return base_type::a5_; } + A6 operator[] (boost::arg<6> (*) ()) const { return base_type::a6_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list6 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ) && + ref_compare( base_type::a5_, rhs.a5_, 0 ) && + ref_compare( base_type::a6_, rhs.a6_, 0 ); + } +}; + +template class list7: private storage7< A1, A2, A3, A4, A5, A6, A7 > +{ +private: + + typedef storage7< A1, A2, A3, A4, A5, A6, A7 > base_type; + +public: + + list7( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7 ): base_type( a1, a2, a3, a4, a5, a6, a7 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + A5 operator[] (boost::arg<5>) const { return base_type::a5_; } + A6 operator[] (boost::arg<6>) const { return base_type::a6_; } + A7 operator[] (boost::arg<7>) const { return base_type::a7_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + A5 operator[] (boost::arg<5> (*) ()) const { return base_type::a5_; } + A6 operator[] (boost::arg<6> (*) ()) const { return base_type::a6_; } + A7 operator[] (boost::arg<7> (*) ()) const { return base_type::a7_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list7 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ) && + ref_compare( base_type::a5_, rhs.a5_, 0 ) && + ref_compare( base_type::a6_, rhs.a6_, 0 ) && + ref_compare( base_type::a7_, rhs.a7_, 0 ); + } +}; + +template< class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8 > class list8: private storage8< A1, A2, A3, A4, A5, A6, A7, A8 > +{ +private: + + typedef storage8< A1, A2, A3, A4, A5, A6, A7, A8 > base_type; + +public: + + list8( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8 ): base_type( a1, a2, a3, a4, a5, a6, a7, a8 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + A5 operator[] (boost::arg<5>) const { return base_type::a5_; } + A6 operator[] (boost::arg<6>) const { return base_type::a6_; } + A7 operator[] (boost::arg<7>) const { return base_type::a7_; } + A8 operator[] (boost::arg<8>) const { return base_type::a8_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + A5 operator[] (boost::arg<5> (*) ()) const { return base_type::a5_; } + A6 operator[] (boost::arg<6> (*) ()) const { return base_type::a6_; } + A7 operator[] (boost::arg<7> (*) ()) const { return base_type::a7_; } + A8 operator[] (boost::arg<8> (*) ()) const { return base_type::a8_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list8 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ) && + ref_compare( base_type::a5_, rhs.a5_, 0 ) && + ref_compare( base_type::a6_, rhs.a6_, 0 ) && + ref_compare( base_type::a7_, rhs.a7_, 0 ) && + ref_compare( base_type::a8_, rhs.a8_, 0 ); + } +}; + +template class list9: private storage9< A1, A2, A3, A4, A5, A6, A7, A8, A9 > +{ +private: + + typedef storage9< A1, A2, A3, A4, A5, A6, A7, A8, A9 > base_type; + +public: + + list9( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9 ): base_type( a1, a2, a3, a4, a5, a6, a7, a8, a9 ) {} + + A1 operator[] (boost::arg<1>) const { return base_type::a1_; } + A2 operator[] (boost::arg<2>) const { return base_type::a2_; } + A3 operator[] (boost::arg<3>) const { return base_type::a3_; } + A4 operator[] (boost::arg<4>) const { return base_type::a4_; } + A5 operator[] (boost::arg<5>) const { return base_type::a5_; } + A6 operator[] (boost::arg<6>) const { return base_type::a6_; } + A7 operator[] (boost::arg<7>) const { return base_type::a7_; } + A8 operator[] (boost::arg<8>) const { return base_type::a8_; } + A9 operator[] (boost::arg<9>) const { return base_type::a9_; } + + A1 operator[] (boost::arg<1> (*) ()) const { return base_type::a1_; } + A2 operator[] (boost::arg<2> (*) ()) const { return base_type::a2_; } + A3 operator[] (boost::arg<3> (*) ()) const { return base_type::a3_; } + A4 operator[] (boost::arg<4> (*) ()) const { return base_type::a4_; } + A5 operator[] (boost::arg<5> (*) ()) const { return base_type::a5_; } + A6 operator[] (boost::arg<6> (*) ()) const { return base_type::a6_; } + A7 operator[] (boost::arg<7> (*) ()) const { return base_type::a7_; } + A8 operator[] (boost::arg<8> (*) ()) const { return base_type::a8_; } + A9 operator[] (boost::arg<9> (*) ()) const { return base_type::a9_; } + + template T & operator[] (_bi::value & v) const { return v.get(); } + + template T const & operator[] (_bi::value const & v) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } + + template R operator()(type, F & f, A & a, long) + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_], a[base_type::a9_]); + } + + template R operator()(type, F const & f, A & a, long) const + { + return unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_], a[base_type::a9_]); + } + + template void operator()(type, F & f, A & a, int) + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_], a[base_type::a9_]); + } + + template void operator()(type, F const & f, A & a, int) const + { + unwrapper::unwrap(f, 0)(a[base_type::a1_], a[base_type::a2_], a[base_type::a3_], a[base_type::a4_], a[base_type::a5_], a[base_type::a6_], a[base_type::a7_], a[base_type::a8_], a[base_type::a9_]); + } + + template void accept(V & v) const + { + base_type::accept(v); + } + + bool operator==(list9 const & rhs) const + { + return + + ref_compare( base_type::a1_, rhs.a1_, 0 ) && + ref_compare( base_type::a2_, rhs.a2_, 0 ) && + ref_compare( base_type::a3_, rhs.a3_, 0 ) && + ref_compare( base_type::a4_, rhs.a4_, 0 ) && + ref_compare( base_type::a5_, rhs.a5_, 0 ) && + ref_compare( base_type::a6_, rhs.a6_, 0 ) && + ref_compare( base_type::a7_, rhs.a7_, 0 ) && + ref_compare( base_type::a8_, rhs.a8_, 0 ) && + ref_compare( base_type::a9_, rhs.a9_, 0 ); + } +}; + +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + +// bind_t + +#if !defined( BOOST_NO_CXX11_RVALUE_REFERENCES ) + +template< class A1 > class rrlist1 +{ +private: + + A1 & a1_; // not A1&& because of msvc-10.0 + +public: + + explicit rrlist1( A1 & a1 ): a1_( a1 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } // not static_cast because of g++ 4.9 + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2 > class rrlist2 +{ +private: + + A1 & a1_; + A2 & a2_; + +public: + + rrlist2( A1 & a1, A2 & a2 ): a1_( a1 ), a2_( a2 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3 > class rrlist3 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + +public: + + rrlist3( A1 & a1, A2 & a2, A3 & a3 ): a1_( a1 ), a2_( a2 ), a3_( a3 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4 > class rrlist4 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + +public: + + rrlist4( A1 & a1, A2 & a2, A3 & a3, A4 & a4 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4, class A5 > class rrlist5 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + A5 & a5_; + +public: + + rrlist5( A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ), a5_( a5 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5>) const { return std::forward( a5_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5> (*) ()) const { return std::forward( a5_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4, class A5, class A6 > class rrlist6 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + A5 & a5_; + A6 & a6_; + +public: + + rrlist6( A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ), a5_( a5 ), a6_( a6 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5>) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6>) const { return std::forward( a6_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5> (*) ()) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6> (*) ()) const { return std::forward( a6_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4, class A5, class A6, class A7 > class rrlist7 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + A5 & a5_; + A6 & a6_; + A7 & a7_; + +public: + + rrlist7( A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ), a5_( a5 ), a6_( a6 ), a7_( a7 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5>) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6>) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7>) const { return std::forward( a7_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5> (*) ()) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6> (*) ()) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7> (*) ()) const { return std::forward( a7_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8 > class rrlist8 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + A5 & a5_; + A6 & a6_; + A7 & a7_; + A8 & a8_; + +public: + + rrlist8( A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ), a5_( a5 ), a6_( a6 ), a7_( a7 ), a8_( a8 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5>) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6>) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7>) const { return std::forward( a7_ ); } + A8 && operator[] (boost::arg<8>) const { return std::forward( a8_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5> (*) ()) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6> (*) ()) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7> (*) ()) const { return std::forward( a7_ ); } + A8 && operator[] (boost::arg<8> (*) ()) const { return std::forward( a8_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template< class A1, class A2, class A3, class A4, class A5, class A6, class A7, class A8, class A9 > class rrlist9 +{ +private: + + A1 & a1_; + A2 & a2_; + A3 & a3_; + A4 & a4_; + A5 & a5_; + A6 & a6_; + A7 & a7_; + A8 & a8_; + A9 & a9_; + +public: + + rrlist9( A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8, A9 & a9 ): a1_( a1 ), a2_( a2 ), a3_( a3 ), a4_( a4 ), a5_( a5 ), a6_( a6 ), a7_( a7 ), a8_( a8 ), a9_( a9 ) {} + + A1 && operator[] (boost::arg<1>) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2>) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3>) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4>) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5>) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6>) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7>) const { return std::forward( a7_ ); } + A8 && operator[] (boost::arg<8>) const { return std::forward( a8_ ); } + A9 && operator[] (boost::arg<9>) const { return std::forward( a9_ ); } + + A1 && operator[] (boost::arg<1> (*) ()) const { return std::forward( a1_ ); } + A2 && operator[] (boost::arg<2> (*) ()) const { return std::forward( a2_ ); } + A3 && operator[] (boost::arg<3> (*) ()) const { return std::forward( a3_ ); } + A4 && operator[] (boost::arg<4> (*) ()) const { return std::forward( a4_ ); } + A5 && operator[] (boost::arg<5> (*) ()) const { return std::forward( a5_ ); } + A6 && operator[] (boost::arg<6> (*) ()) const { return std::forward( a6_ ); } + A7 && operator[] (boost::arg<7> (*) ()) const { return std::forward( a7_ ); } + A8 && operator[] (boost::arg<8> (*) ()) const { return std::forward( a8_ ); } + A9 && operator[] (boost::arg<9> (*) ()) const { return std::forward( a9_ ); } + + template T & operator[] ( _bi::value & v ) const { return v.get(); } + + template T const & operator[] ( _bi::value const & v ) const { return v.get(); } + + template T & operator[] (reference_wrapper const & v) const { return v.get(); } + + template typename result_traits::type operator[] (bind_t & b) const { return b.eval(*this); } + + template typename result_traits::type operator[] (bind_t const & b) const { return b.eval(*this); } +}; + +template class bind_t +{ +private: + + F f_; + L l_; + +public: + + typedef typename result_traits::type result_type; + typedef bind_t this_type; + + bind_t( F f, L const & l ): f_( f ), l_( l ) {} + + // + + result_type operator()() + { + list0 a; + return l_( type(), f_, a, 0 ); + } + + result_type operator()() const + { + list0 a; + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1 ) + { + rrlist1< A1 > a( a1 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1 ) const + { + rrlist1< A1 > a( a1 ); + return l_(type(), f_, a, 0); + } + + template result_type operator()( A1 && a1, A2 && a2 ) + { + rrlist2< A1, A2 > a( a1, a2 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2 ) const + { + rrlist2< A1, A2 > a( a1, a2 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3 ) + { + rrlist3< A1, A2, A3 > a( a1, a2, a3 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3 ) const + { + rrlist3< A1, A2, A3 > a( a1, a2, a3 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4 ) + { + rrlist4< A1, A2, A3, A4 > a( a1, a2, a3, a4 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4 ) const + { + rrlist4< A1, A2, A3, A4 > a( a1, a2, a3, a4 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5 ) + { + rrlist5< A1, A2, A3, A4, A5 > a( a1, a2, a3, a4, a5 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5 ) const + { + rrlist5< A1, A2, A3, A4, A5 > a( a1, a2, a3, a4, a5 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6 ) + { + rrlist6< A1, A2, A3, A4, A5, A6 > a( a1, a2, a3, a4, a5, a6 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6 ) const + { + rrlist6< A1, A2, A3, A4, A5, A6 > a( a1, a2, a3, a4, a5, a6 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7 ) + { + rrlist7< A1, A2, A3, A4, A5, A6, A7 > a( a1, a2, a3, a4, a5, a6, a7 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7 ) const + { + rrlist7< A1, A2, A3, A4, A5, A6, A7 > a( a1, a2, a3, a4, a5, a6, a7 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7, A8 && a8 ) + { + rrlist8< A1, A2, A3, A4, A5, A6, A7, A8 > a( a1, a2, a3, a4, a5, a6, a7, a8 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7, A8 && a8 ) const + { + rrlist8< A1, A2, A3, A4, A5, A6, A7, A8 > a( a1, a2, a3, a4, a5, a6, a7, a8 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7, A8 && a8, A9 && a9 ) + { + rrlist9< A1, A2, A3, A4, A5, A6, A7, A8, A9 > a( a1, a2, a3, a4, a5, a6, a7, a8, a9 ); + return l_( type(), f_, a, 0 ); + } + + template result_type operator()( A1 && a1, A2 && a2, A3 && a3, A4 && a4, A5 && a5, A6 && a6, A7 && a7, A8 && a8, A9 && a9 ) const + { + rrlist9< A1, A2, A3, A4, A5, A6, A7, A8, A9 > a( a1, a2, a3, a4, a5, a6, a7, a8, a9 ); + return l_( type(), f_, a, 0 ); + } + + // + + template result_type eval( A & a ) + { + return l_( type(), f_, a, 0 ); + } + + template result_type eval( A & a ) const + { + return l_( type(), f_, a, 0 ); + } + + template void accept( V & v ) const + { +#if !defined( BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP ) && !defined( __BORLANDC__ ) + using boost::visit_each; +#endif + + BOOST_BIND_VISIT_EACH( v, f_, 0 ); + l_.accept( v ); + } + + bool compare( this_type const & rhs ) const + { + return ref_compare( f_, rhs.f_, 0 ) && l_ == rhs.l_; + } +}; + +#elif !defined( BOOST_NO_VOID_RETURNS ) + +template class bind_t +{ +public: + + typedef bind_t this_type; + + bind_t(F f, L const & l): f_(f), l_(l) {} + +#define BOOST_BIND_RETURN return +#include +#undef BOOST_BIND_RETURN + +}; + +#else // no void returns + +template struct bind_t_generator +{ + +template class implementation +{ +public: + + typedef implementation this_type; + + implementation(F f, L const & l): f_(f), l_(l) {} + +#define BOOST_BIND_RETURN return +#include +#undef BOOST_BIND_RETURN + +}; + +}; + +template<> struct bind_t_generator +{ + +template class implementation +{ +private: + + typedef void R; + +public: + + typedef implementation this_type; + + implementation(F f, L const & l): f_(f), l_(l) {} + +#define BOOST_BIND_RETURN +#include +#undef BOOST_BIND_RETURN + +}; + +}; + +template class bind_t: public bind_t_generator::BOOST_NESTED_TEMPLATE implementation +{ +public: + + bind_t(F f, L const & l): bind_t_generator::BOOST_NESTED_TEMPLATE implementation(f, l) {} + +}; + +#endif + +// function_equal + +#ifndef BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP + +// put overloads in _bi, rely on ADL + +# ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + +template bool function_equal( bind_t const & a, bind_t const & b ) +{ + return a.compare(b); +} + +# else + +template bool function_equal_impl( bind_t const & a, bind_t const & b, int ) +{ + return a.compare(b); +} + +# endif // #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + +#else // BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP + +// put overloads in boost + +} // namespace _bi + +# ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + +template bool function_equal( _bi::bind_t const & a, _bi::bind_t const & b ) +{ + return a.compare(b); +} + +# else + +template bool function_equal_impl( _bi::bind_t const & a, _bi::bind_t const & b, int ) +{ + return a.compare(b); +} + +# endif // #ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING + +namespace _bi +{ + +#endif // BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP + +// add_value + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) || (__SUNPRO_CC >= 0x530) + +#if defined( __BORLANDC__ ) && BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0x582) ) + +template struct add_value +{ + typedef _bi::value type; +}; + +#else + +template< class T, int I > struct add_value_2 +{ + typedef boost::arg type; +}; + +template< class T > struct add_value_2< T, 0 > +{ + typedef _bi::value< T > type; +}; + +template struct add_value +{ + typedef typename add_value_2< T, boost::is_placeholder< T >::value >::type type; +}; + +#endif + +template struct add_value< value > +{ + typedef _bi::value type; +}; + +template struct add_value< reference_wrapper > +{ + typedef reference_wrapper type; +}; + +template struct add_value< arg > +{ + typedef boost::arg type; +}; + +template struct add_value< arg (*) () > +{ + typedef boost::arg (*type) (); +}; + +template struct add_value< bind_t > +{ + typedef bind_t type; +}; + +#else + +template struct _avt_0; + +template<> struct _avt_0<1> +{ + template struct inner + { + typedef T type; + }; +}; + +template<> struct _avt_0<2> +{ + template struct inner + { + typedef value type; + }; +}; + +typedef char (&_avt_r1) [1]; +typedef char (&_avt_r2) [2]; + +template _avt_r1 _avt_f(value); +template _avt_r1 _avt_f(reference_wrapper); +template _avt_r1 _avt_f(arg); +template _avt_r1 _avt_f(arg (*) ()); +template _avt_r1 _avt_f(bind_t); + +_avt_r2 _avt_f(...); + +template struct add_value +{ + static T t(); + typedef typename _avt_0::template inner::type type; +}; + +#endif + +// list_av_N + +template struct list_av_1 +{ + typedef typename add_value::type B1; + typedef list1 type; +}; + +template struct list_av_2 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef list2 type; +}; + +template struct list_av_3 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef list3 type; +}; + +template struct list_av_4 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef list4 type; +}; + +template struct list_av_5 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef typename add_value::type B5; + typedef list5 type; +}; + +template struct list_av_6 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef typename add_value::type B5; + typedef typename add_value::type B6; + typedef list6 type; +}; + +template struct list_av_7 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef typename add_value::type B5; + typedef typename add_value::type B6; + typedef typename add_value::type B7; + typedef list7 type; +}; + +template struct list_av_8 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef typename add_value::type B5; + typedef typename add_value::type B6; + typedef typename add_value::type B7; + typedef typename add_value::type B8; + typedef list8 type; +}; + +template struct list_av_9 +{ + typedef typename add_value::type B1; + typedef typename add_value::type B2; + typedef typename add_value::type B3; + typedef typename add_value::type B4; + typedef typename add_value::type B5; + typedef typename add_value::type B6; + typedef typename add_value::type B7; + typedef typename add_value::type B8; + typedef typename add_value::type B9; + typedef list9 type; +}; + +// operator! + +struct logical_not +{ + template bool operator()(V const & v) const { return !v; } +}; + +template + bind_t< bool, logical_not, list1< bind_t > > + operator! (bind_t const & f) +{ + typedef list1< bind_t > list_type; + return bind_t ( logical_not(), list_type(f) ); +} + +// relational operators + +#define BOOST_BIND_OPERATOR( op, name ) \ +\ +struct name \ +{ \ + template bool operator()(V const & v, W const & w) const { return v op w; } \ +}; \ + \ +template \ + bind_t< bool, name, list2< bind_t, typename add_value::type > > \ + operator op (bind_t const & f, A2 a2) \ +{ \ + typedef typename add_value::type B2; \ + typedef list2< bind_t, B2> list_type; \ + return bind_t ( name(), list_type(f, a2) ); \ +} + +BOOST_BIND_OPERATOR( ==, equal ) +BOOST_BIND_OPERATOR( !=, not_equal ) + +BOOST_BIND_OPERATOR( <, less ) +BOOST_BIND_OPERATOR( <=, less_equal ) + +BOOST_BIND_OPERATOR( >, greater ) +BOOST_BIND_OPERATOR( >=, greater_equal ) + +BOOST_BIND_OPERATOR( &&, logical_and ) +BOOST_BIND_OPERATOR( ||, logical_or ) + +#undef BOOST_BIND_OPERATOR + +#if defined(__GNUC__) && BOOST_WORKAROUND(__GNUC__, < 3) + +// resolve ambiguity with rel_ops + +#define BOOST_BIND_OPERATOR( op, name ) \ +\ +template \ + bind_t< bool, name, list2< bind_t, bind_t > > \ + operator op (bind_t const & f, bind_t const & g) \ +{ \ + typedef list2< bind_t, bind_t > list_type; \ + return bind_t ( name(), list_type(f, g) ); \ +} + +BOOST_BIND_OPERATOR( !=, not_equal ) +BOOST_BIND_OPERATOR( <=, less_equal ) +BOOST_BIND_OPERATOR( >, greater ) +BOOST_BIND_OPERATOR( >=, greater_equal ) + +#endif + +// visit_each, ADL + +#if !defined( BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP ) && !defined( __BORLANDC__ ) \ + && !(defined(__GNUC__) && __GNUC__ == 3 && __GNUC_MINOR__ <= 3) + +template void visit_each( V & v, value const & t, int ) +{ + using boost::visit_each; + BOOST_BIND_VISIT_EACH( v, t.get(), 0 ); +} + +template void visit_each( V & v, bind_t const & t, int ) +{ + t.accept( v ); +} + +#endif + +} // namespace _bi + +// visit_each, no ADL + +#if defined( BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP ) || defined( __BORLANDC__ ) \ + || (defined(__GNUC__) && __GNUC__ == 3 && __GNUC_MINOR__ <= 3) + +template void visit_each( V & v, _bi::value const & t, int ) +{ + BOOST_BIND_VISIT_EACH( v, t.get(), 0 ); +} + +template void visit_each( V & v, _bi::bind_t const & t, int ) +{ + t.accept( v ); +} + +#endif + +// is_bind_expression + +template< class T > struct is_bind_expression +{ + enum _vt { value = 0 }; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template< class R, class F, class L > struct is_bind_expression< _bi::bind_t< R, F, L > > +{ + enum _vt { value = 1 }; +}; + +#endif + +// bind + +#ifndef BOOST_BIND +#define BOOST_BIND bind +#endif + +// generic function objects + +template + _bi::bind_t + BOOST_BIND(F f) +{ + typedef _bi::list0 list_type; + return _bi::bind_t (f, list_type()); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1) +{ + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t (f, list_type(a1)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2) +{ + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t (f, list_type(a1, a2)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3) +{ + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +// generic function objects, alternative syntax + +template + _bi::bind_t + BOOST_BIND(boost::type, F f) +{ + typedef _bi::list0 list_type; + return _bi::bind_t (f, list_type()); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1) +{ + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t (f, list_type(a1)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2) +{ + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t (f, list_type(a1, a2)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3) +{ + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t::type> + BOOST_BIND(boost::type, F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + +// adaptable function objects + +template + _bi::bind_t<_bi::unspecified, F, _bi::list0> + BOOST_BIND(F f) +{ + typedef _bi::list0 list_type; + return _bi::bind_t<_bi::unspecified, F, list_type> (f, list_type()); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_1::type> + BOOST_BIND(F f, A1 a1) +{ + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type> (f, list_type(a1)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_2::type> + BOOST_BIND(F f, A1 a1, A2 a2) +{ + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type> (f, list_type(a1, a2)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_3::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3) +{ + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_4::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_5::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_6::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_7::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_8::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t<_bi::unspecified, F, typename _bi::list_av_9::type> + BOOST_BIND(F f, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t<_bi::unspecified, F, list_type>(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +#endif // !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + +// function pointers + +#define BOOST_BIND_CC +#define BOOST_BIND_ST + +#include + +#undef BOOST_BIND_CC +#undef BOOST_BIND_ST + +#ifdef BOOST_BIND_ENABLE_STDCALL + +#define BOOST_BIND_CC __stdcall +#define BOOST_BIND_ST + +#include + +#undef BOOST_BIND_CC +#undef BOOST_BIND_ST + +#endif + +#ifdef BOOST_BIND_ENABLE_FASTCALL + +#define BOOST_BIND_CC __fastcall +#define BOOST_BIND_ST + +#include + +#undef BOOST_BIND_CC +#undef BOOST_BIND_ST + +#endif + +#ifdef BOOST_BIND_ENABLE_PASCAL + +#define BOOST_BIND_ST pascal +#define BOOST_BIND_CC + +#include + +#undef BOOST_BIND_ST +#undef BOOST_BIND_CC + +#endif + +// member function pointers + +#define BOOST_BIND_MF_NAME(X) X +#define BOOST_BIND_MF_CC + +#include +#include + +#undef BOOST_BIND_MF_NAME +#undef BOOST_BIND_MF_CC + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_BIND_MF_NAME(X) X##_cdecl +#define BOOST_BIND_MF_CC __cdecl + +#include +#include + +#undef BOOST_BIND_MF_NAME +#undef BOOST_BIND_MF_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_BIND_MF_NAME(X) X##_stdcall +#define BOOST_BIND_MF_CC __stdcall + +#include +#include + +#undef BOOST_BIND_MF_NAME +#undef BOOST_BIND_MF_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_BIND_MF_NAME(X) X##_fastcall +#define BOOST_BIND_MF_CC __fastcall + +#include +#include + +#undef BOOST_BIND_MF_NAME +#undef BOOST_BIND_MF_CC + +#endif + +// data member pointers + +#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) || defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + || ( defined(__BORLANDC__) && BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT( 0x620 ) ) ) + +template +_bi::bind_t< R, _mfi::dm, typename _bi::list_av_1::type > + BOOST_BIND(R T::*f, A1 a1) +{ + typedef _mfi::dm F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t( F(f), list_type(a1) ); +} + +#else + +namespace _bi +{ + +template< class Pm, int I > struct add_cref; + +template< class M, class T > struct add_cref< M T::*, 0 > +{ + typedef M type; +}; + +template< class M, class T > struct add_cref< M T::*, 1 > +{ +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable:4180) +#endif + typedef M const & type; +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif +}; + +template< class R, class T > struct add_cref< R (T::*) (), 1 > +{ + typedef void type; +}; + +#if !defined(__IBMCPP__) || __IBMCPP_FUNC_CV_TMPL_ARG_DEDUCTION + +template< class R, class T > struct add_cref< R (T::*) () const, 1 > +{ + typedef void type; +}; + +#endif // __IBMCPP__ + +template struct isref +{ + enum value_type { value = 0 }; +}; + +template struct isref< R& > +{ + enum value_type { value = 1 }; +}; + +template struct isref< R* > +{ + enum value_type { value = 1 }; +}; + +template struct dm_result +{ + typedef typename add_cref< Pm, 1 >::type type; +}; + +template struct dm_result< Pm, bind_t > +{ + typedef typename bind_t::result_type result_type; + typedef typename add_cref< Pm, isref< result_type >::value >::type type; +}; + +} // namespace _bi + +template< class A1, class M, class T > + +_bi::bind_t< + typename _bi::dm_result< M T::*, A1 >::type, + _mfi::dm, + typename _bi::list_av_1::type +> + +BOOST_BIND( M T::*f, A1 a1 ) +{ + typedef typename _bi::dm_result< M T::*, A1 >::type result_type; + typedef _mfi::dm F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t< result_type, F, list_type >( F( f ), list_type( a1 ) ); +} + +#endif + +} // namespace boost + +#ifndef BOOST_BIND_NO_PLACEHOLDERS + +# include + +#endif + +#ifdef BOOST_MSVC +# pragma warning(default: 4512) // assignment operator could not be generated +# pragma warning(pop) +#endif + +#endif // #ifndef BOOST_BIND_BIND_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/bind/bind_cc.hpp b/thirdparty/source/boost_1_61_0/boost/bind/bind_cc.hpp new file mode 100644 index 0000000..35f8ece --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/bind_cc.hpp @@ -0,0 +1,117 @@ +// +// bind/bind_cc.hpp - support for different calling conventions +// +// Do not include this header directly. +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +template + _bi::bind_t + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) ()) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (); + typedef _bi::list0 list_type; + return _bi::bind_t (f, list_type()); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1), A1 a1) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1); + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t (f, list_type(a1)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2), A1 a1, A2 a2) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2); + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t (f, list_type(a1, a2)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3), A1 a1, A2 a2, A3 a3) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3); + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4), A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4); + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4, B5), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4, B5); + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4, B5, B6), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4, B5, B6); + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4, B5, B6, B7), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4, B5, B6, B7); + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4, B5, B6, B7, B8), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4, B5, B6, B7, B8); + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t::type> + BOOST_BIND(BOOST_BIND_ST R (BOOST_BIND_CC *f) (B1, B2, B3, B4, B5, B6, B7, B8, B9), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef BOOST_BIND_ST R (BOOST_BIND_CC *F) (B1, B2, B3, B4, B5, B6, B7, B8, B9); + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(f, list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} diff --git a/thirdparty/source/boost_1_61_0/boost/bind/bind_mf2_cc.hpp b/thirdparty/source/boost_1_61_0/boost/bind/bind_mf2_cc.hpp new file mode 100644 index 0000000..66476bc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/bind_mf2_cc.hpp @@ -0,0 +1,228 @@ +// +// bind/bind_mf2_cc.hpp - member functions, type<> syntax +// +// Do not include this header directly. +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +// 0 + +template + _bi::bind_t, typename _bi::list_av_1::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (), A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +template + _bi::bind_t, typename _bi::list_av_1::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) () const, A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +// 1 + +template + _bi::bind_t, typename _bi::list_av_2::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1), A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +template + _bi::bind_t, typename _bi::list_av_2::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1) const, A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +// 2 + +template + _bi::bind_t, typename _bi::list_av_3::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2), A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +template + _bi::bind_t, typename _bi::list_av_3::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2) const, A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +// 3 + +template + _bi::bind_t, typename _bi::list_av_4::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3), A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t, typename _bi::list_av_4::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3) const, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +// 4 + +template + _bi::bind_t, typename _bi::list_av_5::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t, typename _bi::list_av_5::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +// 5 + +template + _bi::bind_t, typename _bi::list_av_6::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t, typename _bi::list_av_6::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +// 6 + +template + _bi::bind_t, typename _bi::list_av_7::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t, typename _bi::list_av_7::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +// 7 + +template + _bi::bind_t, typename _bi::list_av_8::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t, typename _bi::list_av_8::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +// 8 + +template + _bi::bind_t, typename _bi::list_av_9::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template + _bi::bind_t, typename _bi::list_av_9::type> + BOOST_BIND(boost::type, R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} diff --git a/thirdparty/source/boost_1_61_0/boost/bind/bind_mf_cc.hpp b/thirdparty/source/boost_1_61_0/boost/bind/bind_mf_cc.hpp new file mode 100644 index 0000000..e149384 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/bind_mf_cc.hpp @@ -0,0 +1,441 @@ +// +// bind/bind_mf_cc.hpp - support for different calling conventions +// +// Do not include this header directly. +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +// 0 + +template + _bi::bind_t, typename _bi::list_av_1::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (), A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +template + _bi::bind_t, typename _bi::list_av_1::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) () const, A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_1::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (), A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_1::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) () const, A1 a1) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf0) F; + typedef typename _bi::list_av_1::type list_type; + return _bi::bind_t(F(f), list_type(a1)); +} + +// 1 + +template + _bi::bind_t, typename _bi::list_av_2::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1), A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +template + _bi::bind_t, typename _bi::list_av_2::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1) const, A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_2::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1), A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_2::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1) const, A1 a1, A2 a2) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf1) F; + typedef typename _bi::list_av_2::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2)); +} + +// 2 + +template + _bi::bind_t, typename _bi::list_av_3::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2), A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +template + _bi::bind_t, typename _bi::list_av_3::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2) const, A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_3::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2), A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_3::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2) const, A1 a1, A2 a2, A3 a3) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf2) F; + typedef typename _bi::list_av_3::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3)); +} + +// 3 + +template + _bi::bind_t, typename _bi::list_av_4::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3), A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +template + _bi::bind_t, typename _bi::list_av_4::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3) const, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_4::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3), A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_4::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3) const, A1 a1, A2 a2, A3 a3, A4 a4) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf3) F; + typedef typename _bi::list_av_4::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4)); +} + +// 4 + +template + _bi::bind_t, typename _bi::list_av_5::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +template + _bi::bind_t, typename _bi::list_av_5::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_5::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_5::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf4) F; + typedef typename _bi::list_av_5::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5)); +} + +// 5 + +template + _bi::bind_t, typename _bi::list_av_6::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +template + _bi::bind_t, typename _bi::list_av_6::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_6::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_6::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf5) F; + typedef typename _bi::list_av_6::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6)); +} + +// 6 + +template + _bi::bind_t, typename _bi::list_av_7::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + _bi::bind_t, typename _bi::list_av_7::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_7::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_7::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf6) F; + typedef typename _bi::list_av_7::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7)); +} + +// 7 + +template + _bi::bind_t, typename _bi::list_av_8::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + _bi::bind_t, typename _bi::list_av_8::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_8::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_8::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf7) F; + typedef typename _bi::list_av_8::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8)); +} + +// 8 + +template + _bi::bind_t, typename _bi::list_av_9::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template + _bi::bind_t, typename _bi::list_av_9::type> + BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_9::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8), A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(mf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} + +template + typename boost::enable_if_c::value, + _bi::bind_t, typename _bi::list_av_9::type> + >::type BOOST_BIND(R (BOOST_BIND_MF_CC T::*f) (B1, B2, B3, B4, B5, B6, B7, B8) const, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9) +{ + typedef _mfi::BOOST_BIND_MF_NAME(cmf8) F; + typedef typename _bi::list_av_9::type list_type; + return _bi::bind_t(F(f), list_type(a1, a2, a3, a4, a5, a6, a7, a8, a9)); +} diff --git a/thirdparty/source/boost_1_61_0/boost/bind/bind_template.hpp b/thirdparty/source/boost_1_61_0/boost/bind/bind_template.hpp new file mode 100644 index 0000000..411d20c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/bind_template.hpp @@ -0,0 +1,345 @@ +// +// bind/bind_template.hpp +// +// Do not include this header directly. +// +// Copyright (c) 2001-2004 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + + typedef typename result_traits::type result_type; + + result_type operator()() + { + list0 a; + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + result_type operator()() const + { + list0 a; + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1) + { + list1 a(a1); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1) const + { + list1 a(a1); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1) + { + list1 a(a1); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1) const + { + list1 a(a1); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2) + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2) const + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 & a2) + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 & a2) const + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + + template result_type operator()(A1 & a1, A2 const & a2) + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 const & a2) const + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + + template result_type operator()(A1 const & a1, A2 const & a2) + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2) const + { + list2 a(a1, a2); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3) + { + list3 a(a1, a2, a3); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3) const + { + list3 a(a1, a2, a3); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3) + { + list3 a(a1, a2, a3); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3) const + { + list3 a(a1, a2, a3); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4) + { + list4 a(a1, a2, a3, a4); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4) const + { + list4 a(a1, a2, a3, a4); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4) + { + list4 a(a1, a2, a3, a4); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4) const + { + list4 a(a1, a2, a3, a4); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5) + { + list5 a(a1, a2, a3, a4, a5); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5) const + { + list5 a(a1, a2, a3, a4, a5); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5) + { + list5 a(a1, a2, a3, a4, a5); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5) const + { + list5 a(a1, a2, a3, a4, a5); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6) + { + list6 a(a1, a2, a3, a4, a5, a6); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6) const + { + list6 a(a1, a2, a3, a4, a5, a6); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6) + { + list6 a(a1, a2, a3, a4, a5, a6); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6) const + { + list6 a(a1, a2, a3, a4, a5, a6); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7) + { + list7 a(a1, a2, a3, a4, a5, a6, a7); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7) const + { + list7 a(a1, a2, a3, a4, a5, a6, a7); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7) + { + list7 a(a1, a2, a3, a4, a5, a6, a7); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7) const + { + list7 a(a1, a2, a3, a4, a5, a6, a7); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8) + { + list8 a(a1, a2, a3, a4, a5, a6, a7, a8); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8) const + { + list8 a(a1, a2, a3, a4, a5, a6, a7, a8); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7, A8 const & a8) + { + list8 a(a1, a2, a3, a4, a5, a6, a7, a8); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7, A8 const & a8) const + { + list8 a(a1, a2, a3, a4, a5, a6, a7, a8); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8, A9 & a9) + { + list9 a(a1, a2, a3, a4, a5, a6, a7, a8, a9); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 & a1, A2 & a2, A3 & a3, A4 & a4, A5 & a5, A6 & a6, A7 & a7, A8 & a8, A9 & a9) const + { + list9 a(a1, a2, a3, a4, a5, a6, a7, a8, a9); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) \ + && !BOOST_WORKAROUND(__EDG_VERSION__, <= 238) + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7, A8 const & a8, A9 const & a9) + { + list9 a(a1, a2, a3, a4, a5, a6, a7, a8, a9); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type operator()(A1 const & a1, A2 const & a2, A3 const & a3, A4 const & a4, A5 const & a5, A6 const & a6, A7 const & a7, A8 const & a8, A9 const & a9) const + { + list9 a(a1, a2, a3, a4, a5, a6, a7, a8, a9); + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + +#endif + + template result_type eval(A & a) + { + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template result_type eval(A & a) const + { + BOOST_BIND_RETURN l_(type(), f_, a, 0); + } + + template void accept(V & v) const + { +#if !defined( BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP ) && !defined( __BORLANDC__ ) + + using boost::visit_each; + +#endif + BOOST_BIND_VISIT_EACH(v, f_, 0); + l_.accept(v); + } + + bool compare(this_type const & rhs) const + { + return ref_compare(f_, rhs.f_, 0) && l_ == rhs.l_; + } + +private: + + F f_; + L l_; diff --git a/thirdparty/source/boost_1_61_0/boost/bind/mem_fn.hpp b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn.hpp new file mode 100644 index 0000000..956e7d8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn.hpp @@ -0,0 +1,389 @@ +#ifndef BOOST_BIND_MEM_FN_HPP_INCLUDED +#define BOOST_BIND_MEM_FN_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// mem_fn.hpp - a generalization of std::mem_fun[_ref] +// +// Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2001 David Abrahams +// Copyright (c) 2003-2005 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/mem_fn.html for documentation. +// + +#include +#include +#include + +namespace boost +{ + +#if defined(BOOST_NO_VOID_RETURNS) + +#define BOOST_MEM_FN_CLASS_F , class F +#define BOOST_MEM_FN_TYPEDEF(X) + +namespace _mfi // mem_fun_impl +{ + +template struct mf +{ + +#define BOOST_MEM_FN_RETURN return + +#define BOOST_MEM_FN_NAME(X) inner_##X +#define BOOST_MEM_FN_CC + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_cdecl +#define BOOST_MEM_FN_CC __cdecl + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_stdcall +#define BOOST_MEM_FN_CC __stdcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_fastcall +#define BOOST_MEM_FN_CC __fastcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#undef BOOST_MEM_FN_RETURN + +}; // struct mf + +template<> struct mf +{ + +#define BOOST_MEM_FN_RETURN + +#define BOOST_MEM_FN_NAME(X) inner_##X +#define BOOST_MEM_FN_CC + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_cdecl +#define BOOST_MEM_FN_CC __cdecl + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_stdcall +#define BOOST_MEM_FN_CC __stdcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_MEM_FN_NAME(X) inner_##X##_fastcall +#define BOOST_MEM_FN_CC __fastcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#undef BOOST_MEM_FN_RETURN + +}; // struct mf + +#undef BOOST_MEM_FN_CLASS_F +#undef BOOST_MEM_FN_TYPEDEF_F + +#define BOOST_MEM_FN_NAME(X) X +#define BOOST_MEM_FN_NAME2(X) inner_##X +#define BOOST_MEM_FN_CC + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_NAME2 +#undef BOOST_MEM_FN_CC + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_MEM_FN_NAME(X) X##_cdecl +#define BOOST_MEM_FN_NAME2(X) inner_##X##_cdecl +#define BOOST_MEM_FN_CC __cdecl + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_NAME2 +#undef BOOST_MEM_FN_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_MEM_FN_NAME(X) X##_stdcall +#define BOOST_MEM_FN_NAME2(X) inner_##X##_stdcall +#define BOOST_MEM_FN_CC __stdcall + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_NAME2 +#undef BOOST_MEM_FN_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_MEM_FN_NAME(X) X##_fastcall +#define BOOST_MEM_FN_NAME2(X) inner_##X##_fastcall +#define BOOST_MEM_FN_CC __fastcall + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_NAME2 +#undef BOOST_MEM_FN_CC + +#endif + +} // namespace _mfi + +#else // #ifdef BOOST_NO_VOID_RETURNS + +#define BOOST_MEM_FN_CLASS_F +#define BOOST_MEM_FN_TYPEDEF(X) typedef X; + +namespace _mfi +{ + +#define BOOST_MEM_FN_RETURN return + +#define BOOST_MEM_FN_NAME(X) X +#define BOOST_MEM_FN_CC + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_MEM_FN_NAME(X) X##_cdecl +#define BOOST_MEM_FN_CC __cdecl + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_MEM_FN_NAME(X) X##_stdcall +#define BOOST_MEM_FN_CC __stdcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_MEM_FN_NAME(X) X##_fastcall +#define BOOST_MEM_FN_CC __fastcall + +#include + +#undef BOOST_MEM_FN_CC +#undef BOOST_MEM_FN_NAME + +#endif + +#undef BOOST_MEM_FN_RETURN + +} // namespace _mfi + +#undef BOOST_MEM_FN_CLASS_F +#undef BOOST_MEM_FN_TYPEDEF + +#endif // #ifdef BOOST_NO_VOID_RETURNS + +#define BOOST_MEM_FN_NAME(X) X +#define BOOST_MEM_FN_CC + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_CC + +#ifdef BOOST_MEM_FN_ENABLE_CDECL + +#define BOOST_MEM_FN_NAME(X) X##_cdecl +#define BOOST_MEM_FN_CC __cdecl + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_STDCALL + +#define BOOST_MEM_FN_NAME(X) X##_stdcall +#define BOOST_MEM_FN_CC __stdcall + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_CC + +#endif + +#ifdef BOOST_MEM_FN_ENABLE_FASTCALL + +#define BOOST_MEM_FN_NAME(X) X##_fastcall +#define BOOST_MEM_FN_CC __fastcall + +#include + +#undef BOOST_MEM_FN_NAME +#undef BOOST_MEM_FN_CC + +#endif + +// data member support + +namespace _mfi +{ + +template class dm +{ +public: + + typedef R const & result_type; + typedef T const * argument_type; + +private: + + typedef R (T::*F); + F f_; + + template R const & call(U & u, T const *) const + { + return (u.*f_); + } + + template R const & call(U & u, void const *) const + { + return (get_pointer(u)->*f_); + } + +public: + + explicit dm(F f): f_(f) {} + + R & operator()(T * p) const + { + return (p->*f_); + } + + R const & operator()(T const * p) const + { + return (p->*f_); + } + + template R const & operator()(U const & u) const + { + return call(u, &u); + } + +#if !BOOST_WORKAROUND(BOOST_MSVC, <= 1300) && !BOOST_WORKAROUND(__MWERKS__, < 0x3200) + + R & operator()(T & t) const + { + return (t.*f_); + } + + R const & operator()(T const & t) const + { + return (t.*f_); + } + +#endif + + bool operator==(dm const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(dm const & rhs) const + { + return f_ != rhs.f_; + } +}; + +} // namespace _mfi + +template _mfi::dm mem_fn(R T::*f) +{ + return _mfi::dm(f); +} + +} // namespace boost + +#endif // #ifndef BOOST_BIND_MEM_FN_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_cc.hpp b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_cc.hpp new file mode 100644 index 0000000..8b6ea0b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_cc.hpp @@ -0,0 +1,103 @@ +// +// bind/mem_fn_cc.hpp - support for different calling conventions +// +// Do not include this header directly. +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/mem_fn.html for documentation. +// + +template _mfi::BOOST_MEM_FN_NAME(mf0) mem_fn(R (BOOST_MEM_FN_CC T::*f) ()) +{ + return _mfi::BOOST_MEM_FN_NAME(mf0)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf0) mem_fn(R (BOOST_MEM_FN_CC T::*f) () const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf0)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf1) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf1)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf1) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf1)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf2) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf2)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf2) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf2)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf3) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf3)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf3) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf3)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf4) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf4)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf4) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf4)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf5) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf5)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf5) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf5)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf6) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf6)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf6) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf6)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf7) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6, A7)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf7)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf7) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6, A7) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf7)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(mf8) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6, A7, A8)) +{ + return _mfi::BOOST_MEM_FN_NAME(mf8)(f); +} + +template _mfi::BOOST_MEM_FN_NAME(cmf8) mem_fn(R (BOOST_MEM_FN_CC T::*f) (A1, A2, A3, A4, A5, A6, A7, A8) const) +{ + return _mfi::BOOST_MEM_FN_NAME(cmf8)(f); +} diff --git a/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_template.hpp b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_template.hpp new file mode 100644 index 0000000..b26d585 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_template.hpp @@ -0,0 +1,1047 @@ +// +// bind/mem_fn_template.hpp +// +// Do not include this header directly +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/mem_fn.html for documentation. +// + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) +# define BOOST_MEM_FN_ENABLE_CONST_OVERLOADS +#endif + +// mf0 + +template class BOOST_MEM_FN_NAME(mf0) +{ +public: + + typedef R result_type; + typedef T * argument_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) ()) + F f_; + + template R call(U & u, T const *) const + { + BOOST_MEM_FN_RETURN (u.*f_)(); + } + + template R call(U & u, void const *) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf0)(F f): f_(f) {} + + R operator()(T * p) const + { + BOOST_MEM_FN_RETURN (p->*f_)(); + } + + template R operator()(U & u) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p); + } + +#endif + + R operator()(T & t) const + { + BOOST_MEM_FN_RETURN (t.*f_)(); + } + + bool operator==(BOOST_MEM_FN_NAME(mf0) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf0) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf0 + +template class BOOST_MEM_FN_NAME(cmf0) +{ +public: + + typedef R result_type; + typedef T const * argument_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) () const) + F f_; + + template R call(U & u, T const *) const + { + BOOST_MEM_FN_RETURN (u.*f_)(); + } + + template R call(U & u, void const *) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf0)(F f): f_(f) {} + + template R operator()(U const & u) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p); + } + + R operator()(T const & t) const + { + BOOST_MEM_FN_RETURN (t.*f_)(); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf0) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf0) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf1 + +template class BOOST_MEM_FN_NAME(mf1) +{ +public: + + typedef R result_type; + typedef T * first_argument_type; + typedef A1 second_argument_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1)) + F f_; + + template R call(U & u, T const *, B1 & b1) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1); + } + + template R call(U & u, void const *, B1 & b1) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf1)(F f): f_(f) {} + + R operator()(T * p, A1 a1) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1); + } + + template R operator()(U & u, A1 a1) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1); + } + +#endif + + R operator()(T & t, A1 a1) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1); + } + + bool operator==(BOOST_MEM_FN_NAME(mf1) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf1) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf1 + +template class BOOST_MEM_FN_NAME(cmf1) +{ +public: + + typedef R result_type; + typedef T const * first_argument_type; + typedef A1 second_argument_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1) const) + F f_; + + template R call(U & u, T const *, B1 & b1) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1); + } + + template R call(U & u, void const *, B1 & b1) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf1)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1); + } + + R operator()(T const & t, A1 a1) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf1) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf1) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf2 + +template class BOOST_MEM_FN_NAME(mf2) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf2)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2); + } + + template R operator()(U & u, A1 a1, A2 a2) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2); + } + + bool operator==(BOOST_MEM_FN_NAME(mf2) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf2) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf2 + +template class BOOST_MEM_FN_NAME(cmf2) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf2)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2); + } + + R operator()(T const & t, A1 a1, A2 a2) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf2) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf2) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf3 + +template class BOOST_MEM_FN_NAME(mf3) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf3)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3); + } + + bool operator==(BOOST_MEM_FN_NAME(mf3) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf3) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf3 + +template class BOOST_MEM_FN_NAME(cmf3) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf3)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf3) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf3) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf4 + +template class BOOST_MEM_FN_NAME(mf4) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf4)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3, A4 a4) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3, A4 a4) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3, A4 a4) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4); + } + + bool operator==(BOOST_MEM_FN_NAME(mf4) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf4) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf4 + +template class BOOST_MEM_FN_NAME(cmf4) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf4)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3, A4 a4) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf4) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf4) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf5 + +template class BOOST_MEM_FN_NAME(mf5) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf5)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4, a5); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5); + } + + bool operator==(BOOST_MEM_FN_NAME(mf5) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf5) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf5 + +template class BOOST_MEM_FN_NAME(cmf5) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf5)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf5) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf5) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf6 + +template class BOOST_MEM_FN_NAME(mf6) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf6)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4, a5, a6); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6); + } + + bool operator==(BOOST_MEM_FN_NAME(mf6) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf6) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf6 + +template class BOOST_MEM_FN_NAME(cmf6) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf6)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf6) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf6) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf7 + +template class BOOST_MEM_FN_NAME(mf7) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6, b7); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6, b7); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf7)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4, a5, a6, a7); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6, a7); + } + + bool operator==(BOOST_MEM_FN_NAME(mf7) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf7) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf7 + +template class BOOST_MEM_FN_NAME(cmf7) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6, b7); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6, b7); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf7)(F f): f_(f) {} + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6, a7); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf7) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf7) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// mf8 + +template class BOOST_MEM_FN_NAME(mf8) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7, A8)) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7, B8 & b8) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6, b7, b8); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7, B8 & b8) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6, b7, b8); + } + +public: + + explicit BOOST_MEM_FN_NAME(mf8)(F f): f_(f) {} + + R operator()(T * p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + template R operator()(U & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7, a8); + } + +#ifdef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7, a8); + } + +#endif + + R operator()(T & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + bool operator==(BOOST_MEM_FN_NAME(mf8) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(mf8) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +// cmf8 + +template class BOOST_MEM_FN_NAME(cmf8) +{ +public: + + typedef R result_type; + +private: + + BOOST_MEM_FN_TYPEDEF(R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7, A8) const) + F f_; + + template R call(U & u, T const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7, B8 & b8) const + { + BOOST_MEM_FN_RETURN (u.*f_)(b1, b2, b3, b4, b5, b6, b7, b8); + } + + template R call(U & u, void const *, B1 & b1, B2 & b2, B3 & b3, B4 & b4, B5 & b5, B6 & b6, B7 & b7, B8 & b8) const + { + BOOST_MEM_FN_RETURN (get_pointer(u)->*f_)(b1, b2, b3, b4, b5, b6, b7, b8); + } + +public: + + explicit BOOST_MEM_FN_NAME(cmf8)(F f): f_(f) {} + + R operator()(T const * p, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + BOOST_MEM_FN_RETURN (p->*f_)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + template R operator()(U const & u, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + U const * p = 0; + BOOST_MEM_FN_RETURN call(u, p, a1, a2, a3, a4, a5, a6, a7, a8); + } + + R operator()(T const & t, A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8) const + { + BOOST_MEM_FN_RETURN (t.*f_)(a1, a2, a3, a4, a5, a6, a7, a8); + } + + bool operator==(BOOST_MEM_FN_NAME(cmf8) const & rhs) const + { + return f_ == rhs.f_; + } + + bool operator!=(BOOST_MEM_FN_NAME(cmf8) const & rhs) const + { + return f_ != rhs.f_; + } +}; + +#undef BOOST_MEM_FN_ENABLE_CONST_OVERLOADS diff --git a/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_vw.hpp b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_vw.hpp new file mode 100644 index 0000000..f3fc58d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/mem_fn_vw.hpp @@ -0,0 +1,130 @@ +// +// bind/mem_fn_vw.hpp - void return helper wrappers +// +// Do not include this header directly +// +// Copyright (c) 2001 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/bind/mem_fn.html for documentation. +// + +template struct BOOST_MEM_FN_NAME(mf0): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf0) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (); + explicit BOOST_MEM_FN_NAME(mf0)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf0)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf0): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf0) +{ + typedef R (BOOST_MEM_FN_CC T::*F) () const; + explicit BOOST_MEM_FN_NAME(cmf0)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf0)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf1): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf1) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1); + explicit BOOST_MEM_FN_NAME(mf1)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf1)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf1): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf1) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1) const; + explicit BOOST_MEM_FN_NAME(cmf1)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf1)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf2): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf2) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2); + explicit BOOST_MEM_FN_NAME(mf2)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf2)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf2): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf2) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2) const; + explicit BOOST_MEM_FN_NAME(cmf2)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf2)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf3): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf3) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3); + explicit BOOST_MEM_FN_NAME(mf3)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf3)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf3): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf3) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3) const; + explicit BOOST_MEM_FN_NAME(cmf3)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf3)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf4): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf4) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4); + explicit BOOST_MEM_FN_NAME(mf4)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf4)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf4): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf4) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4) const; + explicit BOOST_MEM_FN_NAME(cmf4)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf4)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf5): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf5) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5); + explicit BOOST_MEM_FN_NAME(mf5)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf5)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf5): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf5) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5) const; + explicit BOOST_MEM_FN_NAME(cmf5)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf5)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf6): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf6) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6); + explicit BOOST_MEM_FN_NAME(mf6)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf6)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf6): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf6) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6) const; + explicit BOOST_MEM_FN_NAME(cmf6)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf6)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf7): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf7) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7); + explicit BOOST_MEM_FN_NAME(mf7)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf7)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf7): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf7) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7) const; + explicit BOOST_MEM_FN_NAME(cmf7)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf7)(f) {} +}; + + +template struct BOOST_MEM_FN_NAME(mf8): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf8) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7, A8); + explicit BOOST_MEM_FN_NAME(mf8)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(mf8)(f) {} +}; + +template struct BOOST_MEM_FN_NAME(cmf8): public mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf8) +{ + typedef R (BOOST_MEM_FN_CC T::*F) (A1, A2, A3, A4, A5, A6, A7, A8) const; + explicit BOOST_MEM_FN_NAME(cmf8)(F f): mf::BOOST_NESTED_TEMPLATE BOOST_MEM_FN_NAME2(cmf8)(f) {} +}; + diff --git a/thirdparty/source/boost_1_61_0/boost/bind/placeholders.hpp b/thirdparty/source/boost_1_61_0/boost/bind/placeholders.hpp new file mode 100644 index 0000000..b819ef4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/placeholders.hpp @@ -0,0 +1,62 @@ +#ifndef BOOST_BIND_PLACEHOLDERS_HPP_INCLUDED +#define BOOST_BIND_PLACEHOLDERS_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// bind/placeholders.hpp - _N definitions +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// Copyright 2015 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +#include +#include + +namespace boost +{ + +namespace placeholders +{ + +#if defined(__BORLANDC__) || defined(__GNUC__) && (__GNUC__ < 4) + +inline boost::arg<1> _1() { return boost::arg<1>(); } +inline boost::arg<2> _2() { return boost::arg<2>(); } +inline boost::arg<3> _3() { return boost::arg<3>(); } +inline boost::arg<4> _4() { return boost::arg<4>(); } +inline boost::arg<5> _5() { return boost::arg<5>(); } +inline boost::arg<6> _6() { return boost::arg<6>(); } +inline boost::arg<7> _7() { return boost::arg<7>(); } +inline boost::arg<8> _8() { return boost::arg<8>(); } +inline boost::arg<9> _9() { return boost::arg<9>(); } + +#else + +BOOST_STATIC_CONSTEXPR boost::arg<1> _1; +BOOST_STATIC_CONSTEXPR boost::arg<2> _2; +BOOST_STATIC_CONSTEXPR boost::arg<3> _3; +BOOST_STATIC_CONSTEXPR boost::arg<4> _4; +BOOST_STATIC_CONSTEXPR boost::arg<5> _5; +BOOST_STATIC_CONSTEXPR boost::arg<6> _6; +BOOST_STATIC_CONSTEXPR boost::arg<7> _7; +BOOST_STATIC_CONSTEXPR boost::arg<8> _8; +BOOST_STATIC_CONSTEXPR boost::arg<9> _9; + +#endif + +} // namespace placeholders + +} // namespace boost + +#endif // #ifndef BOOST_BIND_PLACEHOLDERS_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/bind/storage.hpp b/thirdparty/source/boost_1_61_0/boost/bind/storage.hpp new file mode 100644 index 0000000..be490b0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/bind/storage.hpp @@ -0,0 +1,475 @@ +#ifndef BOOST_BIND_STORAGE_HPP_INCLUDED +#define BOOST_BIND_STORAGE_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// bind/storage.hpp +// +// boost/bind.hpp support header, optimized storage +// +// Copyright (c) 2006 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// See http://www.boost.org/libs/bind/bind.html for documentation. +// + +#include +#include + +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable: 4512) // assignment operator could not be generated +#endif + +namespace boost +{ + +namespace _bi +{ + +// 1 + +template struct storage1 +{ + explicit storage1( A1 a1 ): a1_( a1 ) {} + + template void accept(V & v) const + { + BOOST_BIND_VISIT_EACH(v, a1_, 0); + } + + A1 a1_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) && !defined( __BORLANDC__ ) + +template struct storage1< boost::arg > +{ + explicit storage1( boost::arg ) {} + + template void accept(V &) const { } + + static boost::arg a1_() { return boost::arg(); } +}; + +template struct storage1< boost::arg (*) () > +{ + explicit storage1( boost::arg (*) () ) {} + + template void accept(V &) const { } + + static boost::arg a1_() { return boost::arg(); } +}; + +#endif + +// 2 + +template struct storage2: public storage1 +{ + typedef storage1 inherited; + + storage2( A1 a1, A2 a2 ): storage1( a1 ), a2_( a2 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a2_, 0); + } + + A2 a2_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage2< A1, boost::arg >: public storage1 +{ + typedef storage1 inherited; + + storage2( A1 a1, boost::arg ): storage1( a1 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a2_() { return boost::arg(); } +}; + +template struct storage2< A1, boost::arg (*) () >: public storage1 +{ + typedef storage1 inherited; + + storage2( A1 a1, boost::arg (*) () ): storage1( a1 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a2_() { return boost::arg(); } +}; + +#endif + +// 3 + +template struct storage3: public storage2< A1, A2 > +{ + typedef storage2 inherited; + + storage3( A1 a1, A2 a2, A3 a3 ): storage2( a1, a2 ), a3_( a3 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a3_, 0); + } + + A3 a3_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage3< A1, A2, boost::arg >: public storage2< A1, A2 > +{ + typedef storage2 inherited; + + storage3( A1 a1, A2 a2, boost::arg ): storage2( a1, a2 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a3_() { return boost::arg(); } +}; + +template struct storage3< A1, A2, boost::arg (*) () >: public storage2< A1, A2 > +{ + typedef storage2 inherited; + + storage3( A1 a1, A2 a2, boost::arg (*) () ): storage2( a1, a2 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a3_() { return boost::arg(); } +}; + +#endif + +// 4 + +template struct storage4: public storage3< A1, A2, A3 > +{ + typedef storage3 inherited; + + storage4( A1 a1, A2 a2, A3 a3, A4 a4 ): storage3( a1, a2, a3 ), a4_( a4 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a4_, 0); + } + + A4 a4_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage4< A1, A2, A3, boost::arg >: public storage3< A1, A2, A3 > +{ + typedef storage3 inherited; + + storage4( A1 a1, A2 a2, A3 a3, boost::arg ): storage3( a1, a2, a3 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a4_() { return boost::arg(); } +}; + +template struct storage4< A1, A2, A3, boost::arg (*) () >: public storage3< A1, A2, A3 > +{ + typedef storage3 inherited; + + storage4( A1 a1, A2 a2, A3 a3, boost::arg (*) () ): storage3( a1, a2, a3 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a4_() { return boost::arg(); } +}; + +#endif + +// 5 + +template struct storage5: public storage4< A1, A2, A3, A4 > +{ + typedef storage4 inherited; + + storage5( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5 ): storage4( a1, a2, a3, a4 ), a5_( a5 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a5_, 0); + } + + A5 a5_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage5< A1, A2, A3, A4, boost::arg >: public storage4< A1, A2, A3, A4 > +{ + typedef storage4 inherited; + + storage5( A1 a1, A2 a2, A3 a3, A4 a4, boost::arg ): storage4( a1, a2, a3, a4 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a5_() { return boost::arg(); } +}; + +template struct storage5< A1, A2, A3, A4, boost::arg (*) () >: public storage4< A1, A2, A3, A4 > +{ + typedef storage4 inherited; + + storage5( A1 a1, A2 a2, A3 a3, A4 a4, boost::arg (*) () ): storage4( a1, a2, a3, a4 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a5_() { return boost::arg(); } +}; + +#endif + +// 6 + +template struct storage6: public storage5< A1, A2, A3, A4, A5 > +{ + typedef storage5 inherited; + + storage6( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6 ): storage5( a1, a2, a3, a4, a5 ), a6_( a6 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a6_, 0); + } + + A6 a6_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage6< A1, A2, A3, A4, A5, boost::arg >: public storage5< A1, A2, A3, A4, A5 > +{ + typedef storage5 inherited; + + storage6( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, boost::arg ): storage5( a1, a2, a3, a4, a5 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a6_() { return boost::arg(); } +}; + +template struct storage6< A1, A2, A3, A4, A5, boost::arg (*) () >: public storage5< A1, A2, A3, A4, A5 > +{ + typedef storage5 inherited; + + storage6( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, boost::arg (*) () ): storage5( a1, a2, a3, a4, a5 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a6_() { return boost::arg(); } +}; + +#endif + +// 7 + +template struct storage7: public storage6< A1, A2, A3, A4, A5, A6 > +{ + typedef storage6 inherited; + + storage7( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7 ): storage6( a1, a2, a3, a4, a5, a6 ), a7_( a7 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a7_, 0); + } + + A7 a7_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage7< A1, A2, A3, A4, A5, A6, boost::arg >: public storage6< A1, A2, A3, A4, A5, A6 > +{ + typedef storage6 inherited; + + storage7( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, boost::arg ): storage6( a1, a2, a3, a4, a5, a6 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a7_() { return boost::arg(); } +}; + +template struct storage7< A1, A2, A3, A4, A5, A6, boost::arg (*) () >: public storage6< A1, A2, A3, A4, A5, A6 > +{ + typedef storage6 inherited; + + storage7( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, boost::arg (*) () ): storage6( a1, a2, a3, a4, a5, a6 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a7_() { return boost::arg(); } +}; + +#endif + +// 8 + +template struct storage8: public storage7< A1, A2, A3, A4, A5, A6, A7 > +{ + typedef storage7 inherited; + + storage8( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8 ): storage7( a1, a2, a3, a4, a5, a6, a7 ), a8_( a8 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a8_, 0); + } + + A8 a8_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage8< A1, A2, A3, A4, A5, A6, A7, boost::arg >: public storage7< A1, A2, A3, A4, A5, A6, A7 > +{ + typedef storage7 inherited; + + storage8( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, boost::arg ): storage7( a1, a2, a3, a4, a5, a6, a7 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a8_() { return boost::arg(); } +}; + +template struct storage8< A1, A2, A3, A4, A5, A6, A7, boost::arg (*) () >: public storage7< A1, A2, A3, A4, A5, A6, A7 > +{ + typedef storage7 inherited; + + storage8( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, boost::arg (*) () ): storage7( a1, a2, a3, a4, a5, a6, a7 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a8_() { return boost::arg(); } +}; + +#endif + +// 9 + +template struct storage9: public storage8< A1, A2, A3, A4, A5, A6, A7, A8 > +{ + typedef storage8 inherited; + + storage9( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, A9 a9 ): storage8( a1, a2, a3, a4, a5, a6, a7, a8 ), a9_( a9 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + BOOST_BIND_VISIT_EACH(v, a9_, 0); + } + + A9 a9_; +}; + +#if !defined( BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION ) + +template struct storage9< A1, A2, A3, A4, A5, A6, A7, A8, boost::arg >: public storage8< A1, A2, A3, A4, A5, A6, A7, A8 > +{ + typedef storage8 inherited; + + storage9( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, boost::arg ): storage8( a1, a2, a3, a4, a5, a6, a7, a8 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a9_() { return boost::arg(); } +}; + +template struct storage9< A1, A2, A3, A4, A5, A6, A7, A8, boost::arg (*) () >: public storage8< A1, A2, A3, A4, A5, A6, A7, A8 > +{ + typedef storage8 inherited; + + storage9( A1 a1, A2 a2, A3 a3, A4 a4, A5 a5, A6 a6, A7 a7, A8 a8, boost::arg (*) () ): storage8( a1, a2, a3, a4, a5, a6, a7, a8 ) {} + + template void accept(V & v) const + { + inherited::accept(v); + } + + static boost::arg a9_() { return boost::arg(); } +}; + +#endif + +} // namespace _bi + +} // namespace boost + +#ifdef BOOST_MSVC +# pragma warning(default: 4512) // assignment operator could not be generated +# pragma warning(pop) +#endif + +#endif // #ifndef BOOST_BIND_STORAGE_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/call_traits.hpp b/thirdparty/source/boost_1_61_0/boost/call_traits.hpp new file mode 100644 index 0000000..2c1328e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/call_traits.hpp @@ -0,0 +1,20 @@ +// (C) Copyright Steve Cleary, Beman Dawes, Howard Hinnant & John Maddock 2000. +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). +// +// See http://www.boost.org/libs/utility for most recent version including documentation. + +// See boost/detail/call_traits.hpp +// for full copyright notices. + +#ifndef BOOST_CALL_TRAITS_HPP +#define BOOST_CALL_TRAITS_HPP + +#ifndef BOOST_CONFIG_HPP +#include +#endif + +#include + +#endif // BOOST_CALL_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/cerrno.hpp b/thirdparty/source/boost_1_61_0/boost/cerrno.hpp new file mode 100644 index 0000000..6f26698 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/cerrno.hpp @@ -0,0 +1,331 @@ +// Boost cerrno.hpp header -------------------------------------------------// + +// Copyright Beman Dawes 2005. +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See library home page at http://www.boost.org/libs/system + +#ifndef BOOST_CERRNO_HPP +#define BOOST_CERRNO_HPP + +#include + +// supply errno values likely to be missing, particularly on Windows + +#ifndef EAFNOSUPPORT +#define EAFNOSUPPORT 9901 +#endif + +#ifndef EADDRINUSE +#define EADDRINUSE 9902 +#endif + +#ifndef EADDRNOTAVAIL +#define EADDRNOTAVAIL 9903 +#endif + +#ifndef EISCONN +#define EISCONN 9904 +#endif + +#ifndef EBADMSG +#define EBADMSG 9905 +#endif + +#ifndef ECONNABORTED +#define ECONNABORTED 9906 +#endif + +#ifndef EALREADY +#define EALREADY 9907 +#endif + +#ifndef ECONNREFUSED +#define ECONNREFUSED 9908 +#endif + +#ifndef ECONNRESET +#define ECONNRESET 9909 +#endif + +#ifndef EDESTADDRREQ +#define EDESTADDRREQ 9910 +#endif + +#ifndef EHOSTUNREACH +#define EHOSTUNREACH 9911 +#endif + +#ifndef EIDRM +#define EIDRM 9912 +#endif + +#ifndef EMSGSIZE +#define EMSGSIZE 9913 +#endif + +#ifndef ENETDOWN +#define ENETDOWN 9914 +#endif + +#ifndef ENETRESET +#define ENETRESET 9915 +#endif + +#ifndef ENETUNREACH +#define ENETUNREACH 9916 +#endif + +#ifndef ENOBUFS +#define ENOBUFS 9917 +#endif + +#ifndef ENOLINK +#define ENOLINK 9918 +#endif + +#ifndef ENODATA +#define ENODATA 9919 +#endif + +#ifndef ENOMSG +#define ENOMSG 9920 +#endif + +#ifndef ENOPROTOOPT +#define ENOPROTOOPT 9921 +#endif + +#ifndef ENOSR +#define ENOSR 9922 +#endif + +#ifndef ENOTSOCK +#define ENOTSOCK 9923 +#endif + +#ifndef ENOSTR +#define ENOSTR 9924 +#endif + +#ifndef ENOTCONN +#define ENOTCONN 9925 +#endif + +#ifndef ENOTSUP +#define ENOTSUP 9926 +#endif + +#ifndef ECANCELED +#define ECANCELED 9927 +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS 9928 +#endif + +#ifndef EOPNOTSUPP +#define EOPNOTSUPP 9929 +#endif + +#ifndef EWOULDBLOCK +#define EWOULDBLOCK 9930 +#endif + +#ifndef EOWNERDEAD +#define EOWNERDEAD 9931 +#endif + +#ifndef EPROTO +#define EPROTO 9932 +#endif + +#ifndef EPROTONOSUPPORT +#define EPROTONOSUPPORT 9933 +#endif + +#ifndef ENOTRECOVERABLE +#define ENOTRECOVERABLE 9934 +#endif + +#ifndef ETIME +#define ETIME 9935 +#endif + +#ifndef ETXTBSY +#define ETXTBSY 9936 +#endif + +#ifndef ETIMEDOUT +#define ETIMEDOUT 9938 +#endif + +#ifndef ELOOP +#define ELOOP 9939 +#endif + +#ifndef EOVERFLOW +#define EOVERFLOW 9940 +#endif + +#ifndef EPROTOTYPE +#define EPROTOTYPE 9941 +#endif + +#ifndef ENOSYS +#define ENOSYS 9942 +#endif + +#ifndef EINVAL +#define EINVAL 9943 +#endif + +#ifndef ERANGE +#define ERANGE 9944 +#endif + +#ifndef EILSEQ +#define EILSEQ 9945 +#endif + +// Windows Mobile doesn't appear to define these: + +#ifndef E2BIG +#define E2BIG 9946 +#endif + +#ifndef EDOM +#define EDOM 9947 +#endif + +#ifndef EFAULT +#define EFAULT 9948 +#endif + +#ifndef EBADF +#define EBADF 9949 +#endif + +#ifndef EPIPE +#define EPIPE 9950 +#endif + +#ifndef EXDEV +#define EXDEV 9951 +#endif + +#ifndef EBUSY +#define EBUSY 9952 +#endif + +#ifndef ENOTEMPTY +#define ENOTEMPTY 9953 +#endif + +#ifndef ENOEXEC +#define ENOEXEC 9954 +#endif + +#ifndef EEXIST +#define EEXIST 9955 +#endif + +#ifndef EFBIG +#define EFBIG 9956 +#endif + +#ifndef ENAMETOOLONG +#define ENAMETOOLONG 9957 +#endif + +#ifndef ENOTTY +#define ENOTTY 9958 +#endif + +#ifndef EINTR +#define EINTR 9959 +#endif + +#ifndef ESPIPE +#define ESPIPE 9960 +#endif + +#ifndef EIO +#define EIO 9961 +#endif + +#ifndef EISDIR +#define EISDIR 9962 +#endif + +#ifndef ECHILD +#define ECHILD 9963 +#endif + +#ifndef ENOLCK +#define ENOLCK 9964 +#endif + +#ifndef ENOSPC +#define ENOSPC 9965 +#endif + +#ifndef ENXIO +#define ENXIO 9966 +#endif + +#ifndef ENODEV +#define ENODEV 9967 +#endif + +#ifndef ENOENT +#define ENOENT 9968 +#endif + +#ifndef ESRCH +#define ESRCH 9969 +#endif + +#ifndef ENOTDIR +#define ENOTDIR 9970 +#endif + +#ifndef ENOMEM +#define ENOMEM 9971 +#endif + +#ifndef EPERM +#define EPERM 9972 +#endif + +#ifndef EACCES +#define EACCES 9973 +#endif + +#ifndef EROFS +#define EROFS 9974 +#endif + +#ifndef EDEADLK +#define EDEADLK 9975 +#endif + +#ifndef EAGAIN +#define EAGAIN 9976 +#endif + +#ifndef ENFILE +#define ENFILE 9977 +#endif + +#ifndef EMFILE +#define EMFILE 9978 +#endif + +#ifndef EMLINK +#define EMLINK 9979 +#endif + +#endif // include guard diff --git a/thirdparty/source/boost_1_61_0/boost/checked_delete.hpp b/thirdparty/source/boost_1_61_0/boost/checked_delete.hpp new file mode 100644 index 0000000..fb71c78 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/checked_delete.hpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 Glen Fernandes + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_CHECKED_DELETE_HPP +#define BOOST_CHECKED_DELETE_HPP + +// The header file at this path is deprecated; +// use boost/core/checked_delete.hpp instead. + +#include + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/ceil.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/ceil.hpp new file mode 100644 index 0000000..7fbf9dd --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/ceil.hpp @@ -0,0 +1,36 @@ +// boost/chrono/round.hpp ------------------------------------------------------------// + +// (C) Copyright Howard Hinnant +// Copyright 2011 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/chrono for documentation. + +#ifndef BOOST_CHRONO_CEIL_HPP +#define BOOST_CHRONO_CEIL_HPP + +#include + +namespace boost +{ + namespace chrono + { + + /** + * rounds up + */ + template + To ceil(const duration& d) + { + To t = duration_cast(d); + if (t < d) + ++t; + return t; + } + + } // namespace chrono +} // namespace boost + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/chrono.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/chrono.hpp new file mode 100644 index 0000000..ebc29d8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/chrono.hpp @@ -0,0 +1,15 @@ +// chrono.hpp --------------------------------------------------------------// + +// Copyright 2009-2011 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHRONO_CHRONO_HPP +#define BOOST_CHRONO_CHRONO_HPP + +#include +#include +#include + +#endif // BOOST_CHRONO_CHRONO_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/clock_string.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/clock_string.hpp new file mode 100644 index 0000000..af025f2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/clock_string.hpp @@ -0,0 +1,25 @@ +// +// (C) Copyright 2010-2011 Vicente J. Botet Escriba +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). +// + +#ifndef BOOST_CHRONO_CLOCK_STRING_HPP +#define BOOST_CHRONO_CLOCK_STRING_HPP + +#include + +namespace boost +{ + namespace chrono + { + + template + struct clock_string; + + } // chrono + +} // boost + +#endif // BOOST_CHRONO_CLOCK_STRING_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/config.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/config.hpp new file mode 100644 index 0000000..1045ba3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/config.hpp @@ -0,0 +1,216 @@ +// boost/chrono/config.hpp -------------------------------------------------// + +// Copyright Beman Dawes 2003, 2006, 2008 +// Copyright 2009-2011 Vicente J. Botet Escriba +// Copyright (c) Microsoft Corporation 2014 + +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/chrono for documentation. + +#ifndef BOOST_CHRONO_CONFIG_HPP +#define BOOST_CHRONO_CONFIG_HPP + +#include +#include + +#if !defined BOOST_CHRONO_VERSION +#define BOOST_CHRONO_VERSION 1 +#else +#if BOOST_CHRONO_VERSION!=1 && BOOST_CHRONO_VERSION!=2 +#error "BOOST_CHRONO_VERSION must be 1 or 2" +#endif +#endif + +#if defined(BOOST_CHRONO_SOURCE) && !defined(BOOST_USE_WINDOWS_H) +#define BOOST_USE_WINDOWS_H +#endif + +#if ! defined BOOST_CHRONO_PROVIDES_DATE_IO_FOR_SYSTEM_CLOCK_TIME_POINT \ + && ! defined BOOST_CHRONO_DONT_PROVIDE_DATE_IO_FOR_SYSTEM_CLOCK_TIME_POINT + +# define BOOST_CHRONO_PROVIDES_DATE_IO_FOR_SYSTEM_CLOCK_TIME_POINT + +#endif + +// BOOST_CHRONO_POSIX_API, BOOST_CHRONO_MAC_API, or BOOST_CHRONO_WINDOWS_API +// can be defined by the user to specify which API should be used + +#if defined(BOOST_CHRONO_WINDOWS_API) +# warning Boost.Chrono will use the Windows API +#elif defined(BOOST_CHRONO_MAC_API) +# warning Boost.Chrono will use the Mac API +#elif defined(BOOST_CHRONO_POSIX_API) +# warning Boost.Chrono will use the POSIX API +#endif + +# if defined( BOOST_CHRONO_WINDOWS_API ) && defined( BOOST_CHRONO_POSIX_API ) +# error both BOOST_CHRONO_WINDOWS_API and BOOST_CHRONO_POSIX_API are defined +# elif defined( BOOST_CHRONO_WINDOWS_API ) && defined( BOOST_CHRONO_MAC_API ) +# error both BOOST_CHRONO_WINDOWS_API and BOOST_CHRONO_MAC_API are defined +# elif defined( BOOST_CHRONO_MAC_API ) && defined( BOOST_CHRONO_POSIX_API ) +# error both BOOST_CHRONO_MAC_API and BOOST_CHRONO_POSIX_API are defined +# elif !defined( BOOST_CHRONO_WINDOWS_API ) && !defined( BOOST_CHRONO_MAC_API ) && !defined( BOOST_CHRONO_POSIX_API ) +# if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) +# define BOOST_CHRONO_WINDOWS_API +# elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) +# define BOOST_CHRONO_MAC_API +# else +# define BOOST_CHRONO_POSIX_API +# endif +# endif + +# if defined( BOOST_CHRONO_WINDOWS_API ) +# ifndef UNDER_CE +# define BOOST_CHRONO_HAS_PROCESS_CLOCKS +# endif +# define BOOST_CHRONO_HAS_CLOCK_STEADY +# if BOOST_PLAT_WINDOWS_DESKTOP +# define BOOST_CHRONO_HAS_THREAD_CLOCK +# endif +# define BOOST_CHRONO_THREAD_CLOCK_IS_STEADY true +# endif + +# if defined( BOOST_CHRONO_MAC_API ) +# define BOOST_CHRONO_HAS_PROCESS_CLOCKS +# define BOOST_CHRONO_HAS_CLOCK_STEADY +# define BOOST_CHRONO_HAS_THREAD_CLOCK +# define BOOST_CHRONO_THREAD_CLOCK_IS_STEADY true +# endif + +# if defined( BOOST_CHRONO_POSIX_API ) +# define BOOST_CHRONO_HAS_PROCESS_CLOCKS +# include //to check for CLOCK_REALTIME and CLOCK_MONOTONIC and _POSIX_THREAD_CPUTIME +# if defined(CLOCK_MONOTONIC) +# define BOOST_CHRONO_HAS_CLOCK_STEADY +# endif +# if defined(_POSIX_THREAD_CPUTIME) && !defined(BOOST_DISABLE_THREADS) +# define BOOST_CHRONO_HAS_THREAD_CLOCK +# define BOOST_CHRONO_THREAD_CLOCK_IS_STEADY true +# endif +# if defined(CLOCK_THREAD_CPUTIME_ID) && !defined(BOOST_DISABLE_THREADS) +# define BOOST_CHRONO_HAS_THREAD_CLOCK +# define BOOST_CHRONO_THREAD_CLOCK_IS_STEADY true +# endif +# if defined(sun) || defined(__sun) +# undef BOOST_CHRONO_HAS_THREAD_CLOCK +# undef BOOST_CHRONO_THREAD_CLOCK_IS_STEADY +# endif +# if (defined(__HP_aCC) || defined(__GNUC__)) && defined(__hpux) +# undef BOOST_CHRONO_HAS_THREAD_CLOCK +# undef BOOST_CHRONO_THREAD_CLOCK_IS_STEADY +# endif +# if defined(__VXWORKS__) +# undef BOOST_CHRONO_HAS_PROCESS_CLOCKS +# endif +# endif + +#if defined(BOOST_CHRONO_THREAD_DISABLED) && defined(BOOST_CHRONO_HAS_THREAD_CLOCK) +#undef BOOST_CHRONO_HAS_THREAD_CLOCK +#undef BOOST_CHRONO_THREAD_CLOCK_IS_STEADY +#endif + +// unicode support ------------------------------// + +#if defined(BOOST_NO_CXX11_UNICODE_LITERALS) || defined(BOOST_NO_CXX11_CHAR16_T) || defined(BOOST_NO_CXX11_CHAR32_T) +//~ #define BOOST_CHRONO_HAS_UNICODE_SUPPORT +#else +#define BOOST_CHRONO_HAS_UNICODE_SUPPORT 1 +#endif + +#ifndef BOOST_CHRONO_LIB_CONSTEXPR +#if defined( BOOST_NO_CXX11_NUMERIC_LIMITS ) +#define BOOST_CHRONO_LIB_CONSTEXPR +#elif defined(_LIBCPP_VERSION) && !defined(_LIBCPP_CONSTEXPR) + #define BOOST_CHRONO_LIB_CONSTEXPR +#else + #define BOOST_CHRONO_LIB_CONSTEXPR BOOST_CONSTEXPR +#endif +#endif + +#if defined( BOOST_NO_CXX11_NUMERIC_LIMITS ) +# define BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW throw() +#else +#ifdef BOOST_NO_CXX11_NOEXCEPT +# define BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW throw() +#else +# define BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW noexcept +#endif +#endif + +#if defined BOOST_CHRONO_PROVIDE_HYBRID_ERROR_HANDLING \ + && defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +#error "BOOST_CHRONO_PROVIDE_HYBRID_ERROR_HANDLING && BOOST_CHRONO_PROVIDE_HYBRID_ERROR_HANDLING defined" +#endif + +#if defined BOOST_CHRONO_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 \ + && defined BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 +#error "BOOST_CHRONO_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 && BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 defined" +#endif + +#if ! defined BOOST_CHRONO_PROVIDE_HYBRID_ERROR_HANDLING \ + && ! defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +#define BOOST_CHRONO_PROVIDE_HYBRID_ERROR_HANDLING +#endif + +#if (BOOST_CHRONO_VERSION == 2) +#if ! defined BOOST_CHRONO_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 \ + && ! defined BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 +#define BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 +#endif +#endif + +#ifdef BOOST_CHRONO_HEADER_ONLY +#define BOOST_CHRONO_INLINE inline +#define BOOST_CHRONO_STATIC inline +#define BOOST_CHRONO_DECL + +#else +#define BOOST_CHRONO_INLINE +#define BOOST_CHRONO_STATIC static + +// enable dynamic linking on Windows ---------------------------------------// + +// we need to import/export our code only if the user has specifically +// asked for it by defining either BOOST_ALL_DYN_LINK if they want all boost +// libraries to be dynamically linked, or BOOST_CHRONO_DYN_LINK +// if they want just this one to be dynamically liked: +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_CHRONO_DYN_LINK) +// export if this is our own source, otherwise import: +#ifdef BOOST_CHRONO_SOURCE +# define BOOST_CHRONO_DECL BOOST_SYMBOL_EXPORT +#else +# define BOOST_CHRONO_DECL BOOST_SYMBOL_IMPORT +#endif // BOOST_CHRONO_SOURCE +#endif // DYN_LINK +// +// if BOOST_CHRONO_DECL isn't defined yet define it now: +#ifndef BOOST_CHRONO_DECL +#define BOOST_CHRONO_DECL +#endif + + + +// enable automatic library variant selection ------------------------------// + +#if !defined(BOOST_CHRONO_SOURCE) && !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_CHRONO_NO_LIB) +// +// Set the name of our library; this will get undef'ed by auto_link.hpp +// once it's done with it: +// +#define BOOST_LIB_NAME boost_chrono +// +// If we're importing code from a dll, then tell auto_link.hpp about it: +// +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_CHRONO_DYN_LINK) +# define BOOST_DYN_LINK +#endif +// +// And include the header that does the work: +// +#include +#endif // auto-linking disabled +#endif // BOOST_CHRONO_HEADER_ONLY +#endif // BOOST_CHRONO_CONFIG_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/chrono.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/chrono.hpp new file mode 100644 index 0000000..0278843 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/chrono.hpp @@ -0,0 +1,44 @@ +// chrono.cpp --------------------------------------------------------------// + +// Copyright Beman Dawes 2008 +// Copyright Vicente J. Botet Escriba 2009 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHRONO_DETAIL_INLINED_CHRONO_HPP +#define BOOST_CHRONO_DETAIL_INLINED_CHRONO_HPP + +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------// +// // +// Platform-specific Implementations // +// // +//----------------------------------------------------------------------------// + +//----------------------------------------------------------------------------// +// Windows // +//----------------------------------------------------------------------------// +#if defined(BOOST_CHRONO_WINDOWS_API) +#include + +//----------------------------------------------------------------------------// +// Mac // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_MAC_API) +#include + +//----------------------------------------------------------------------------// +// POSIX // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_POSIX_API) +#include + +#endif // POSIX + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/chrono.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/chrono.hpp new file mode 100644 index 0000000..5c32a8e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/chrono.hpp @@ -0,0 +1,241 @@ +// mac/chrono.cpp --------------------------------------------------------------// + +// Copyright Beman Dawes 2008 +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +//----------------------------------------------------------------------------// +// Mac // +//----------------------------------------------------------------------------// + +#include //for gettimeofday and timeval +#include // mach_absolute_time, mach_timebase_info_data_t + +namespace boost +{ +namespace chrono +{ + +// system_clock + +// gettimeofday is the most precise "system time" available on this platform. +// It returns the number of microseconds since New Years 1970 in a struct called timeval +// which has a field for seconds and a field for microseconds. +// Fill in the timeval and then convert that to the time_point +system_clock::time_point +system_clock::now() BOOST_NOEXCEPT +{ + timeval tv; + gettimeofday(&tv, 0); + return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec)); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +system_clock::time_point +system_clock::now(system::error_code & ec) +{ + timeval tv; + gettimeofday(&tv, 0); + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(seconds(tv.tv_sec) + microseconds(tv.tv_usec)); +} +#endif +// Take advantage of the fact that on this platform time_t is nothing but +// an integral count of seconds since New Years 1970 (same epoch as timeval). +// Just get the duration out of the time_point and truncate it to seconds. +time_t +system_clock::to_time_t(const time_point& t) BOOST_NOEXCEPT +{ + return time_t(duration_cast(t.time_since_epoch()).count()); +} + +// Just turn the time_t into a count of seconds and construct a time_point with it. +system_clock::time_point +system_clock::from_time_t(time_t t) BOOST_NOEXCEPT +{ + return system_clock::time_point(seconds(t)); +} + +namespace chrono_detail +{ + +// steady_clock + +// Note, in this implementation steady_clock and high_resolution_clock +// are the same clock. They are both based on mach_absolute_time(). +// mach_absolute_time() * MachInfo.numer / MachInfo.denom is the number of +// nanoseconds since the computer booted up. MachInfo.numer and MachInfo.denom +// are run time constants supplied by the OS. This clock has no relationship +// to the Gregorian calendar. It's main use is as a high resolution timer. + +// MachInfo.numer / MachInfo.denom is often 1 on the latest equipment. Specialize +// for that case as an optimization. +BOOST_CHRONO_STATIC +steady_clock::rep +steady_simplified() +{ + return mach_absolute_time(); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +BOOST_CHRONO_STATIC +steady_clock::rep +steady_simplified_ec(system::error_code & ec) +{ + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return mach_absolute_time(); +} +#endif + +BOOST_CHRONO_STATIC +double +compute_steady_factor(kern_return_t& err) +{ + mach_timebase_info_data_t MachInfo; + err = mach_timebase_info(&MachInfo); + if ( err != 0 ) { + return 0; + } + return static_cast(MachInfo.numer) / MachInfo.denom; +} + +BOOST_CHRONO_STATIC +steady_clock::rep +steady_full() +{ + kern_return_t err; + const double factor = chrono_detail::compute_steady_factor(err); + if (err != 0) + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + return static_cast(mach_absolute_time() * factor); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +BOOST_CHRONO_STATIC +steady_clock::rep +steady_full_ec(system::error_code & ec) +{ + kern_return_t err; + const double factor = chrono_detail::compute_steady_factor(err); + if (err != 0) + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + err, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::steady_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return steady_clock::rep(); + } + } + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return static_cast(mach_absolute_time() * factor); +} +#endif + +typedef steady_clock::rep (*FP)(); +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +typedef steady_clock::rep (*FP_ec)(system::error_code &); +#endif + +BOOST_CHRONO_STATIC +FP +init_steady_clock(kern_return_t & err) +{ + mach_timebase_info_data_t MachInfo; + err = mach_timebase_info(&MachInfo); + if ( err != 0 ) + { + return 0; + } + + if (MachInfo.numer == MachInfo.denom) + { + return &chrono_detail::steady_simplified; + } + return &chrono_detail::steady_full; +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +BOOST_CHRONO_STATIC +FP_ec +init_steady_clock_ec(kern_return_t & err) +{ + mach_timebase_info_data_t MachInfo; + err = mach_timebase_info(&MachInfo); + if ( err != 0 ) + { + return 0; + } + + if (MachInfo.numer == MachInfo.denom) + { + return &chrono_detail::steady_simplified_ec; + } + return &chrono_detail::steady_full_ec; +} +#endif +} + +steady_clock::time_point +steady_clock::now() BOOST_NOEXCEPT +{ + kern_return_t err; + chrono_detail::FP fp = chrono_detail::init_steady_clock(err); + if ( err != 0 ) + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + return time_point(duration(fp())); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +steady_clock::time_point +steady_clock::now(system::error_code & ec) +{ + kern_return_t err; + chrono_detail::FP_ec fp = chrono_detail::init_steady_clock_ec(err); + if ( err != 0 ) + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + err, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::steady_clock" )); + } + else + { + ec.assign( err, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration(fp(ec))); +} +#endif +} // namespace chrono +} // namespace boost diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/process_cpu_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/process_cpu_clocks.hpp new file mode 100644 index 0000000..6e55b0f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/process_cpu_clocks.hpp @@ -0,0 +1,356 @@ +// boost process_cpu_clocks.cpp -----------------------------------------------------------// + +// Copyright Beman Dawes 1994, 2006, 2008 +// Copyright Vicente J. Botet Escriba 2009 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// + +#include +#include +#include + +#include //for gettimeofday and timeval +#include //for times +# include + +namespace boost +{ + namespace chrono + { + namespace chrono_detail + { + + inline long tick_factor() // multiplier to convert ticks + // to nanoseconds; -1 if unknown + { + long factor = 0; + if (!factor) + { + if ((factor = ::sysconf(_SC_CLK_TCK)) <= 0) + factor = -1; + else + { + BOOST_ASSERT(factor <= 1000000000l); // doesn't handle large ticks + factor = 1000000000l / factor; // compute factor + if (!factor) + factor = -1; + } + } + return factor; + } + } + + + process_real_cpu_clock::time_point process_real_cpu_clock::now() BOOST_NOEXCEPT + { +#if 1 + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + return time_point(nanoseconds(c * factor)); + } else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); +#else + clock_t c = ::clock(); + if (c == clock_t(-1)) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + return time_point(nanoseconds(c * factor)); + } else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); +#endif + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + process_real_cpu_clock::time_point process_real_cpu_clock::now(system::error_code & ec) + { + +#if 1 + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_real_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(nanoseconds(c * factor)); + } else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_real_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } + } +#else + clock_t c = ::clock(); + if (c == clock_t(-1)) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_real_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(nanoseconds(c * factor)); + } else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_real_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } + } +#endif + + } +#endif + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + process_user_cpu_clock::time_point process_user_cpu_clock::now(system::error_code & ec) + { + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_user_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(nanoseconds((tm.tms_utime + tm.tms_cutime) * factor)); + } else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_user_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } + } + } +#endif + + process_user_cpu_clock::time_point process_user_cpu_clock::now() BOOST_NOEXCEPT + { + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + return time_point(nanoseconds((tm.tms_utime + tm.tms_cutime) + * factor)); + } else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); + } + process_system_cpu_clock::time_point process_system_cpu_clock::now() BOOST_NOEXCEPT + { + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + return time_point(nanoseconds((tm.tms_stime + tm.tms_cstime) + * factor)); + } else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + process_system_cpu_clock::time_point process_system_cpu_clock::now(system::error_code & ec) + { + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_system_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(nanoseconds((tm.tms_stime + tm.tms_cstime) * factor)); + } else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_system_cpu_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } + } + } +#endif + + process_cpu_clock::time_point process_cpu_clock::now() BOOST_NOEXCEPT + { + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + time_point::rep + r(c * factor, (tm.tms_utime + tm.tms_cutime) * factor, (tm.tms_stime + + tm.tms_cstime) * factor); + return time_point(duration(r)); + } else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + process_cpu_clock::time_point process_cpu_clock::now(system::error_code & ec) + { + + tms tm; + clock_t c = ::times(&tm); + if (c == clock_t(-1)) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } else + { + long factor = chrono_detail::tick_factor(); + if (factor != -1) + { + time_point::rep + r(c * factor, (tm.tms_utime + tm.tms_cutime) * factor, (tm.tms_stime + + tm.tms_cstime) * factor); + return time_point(duration(r)); + } else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception(system::system_error(errno, BOOST_CHRONO_SYSTEM_CATEGORY, "chrono::process_clock")); + } else + { + ec.assign(errno, BOOST_CHRONO_SYSTEM_CATEGORY); + return time_point(); + } + } + } + + } +#endif + + } +} diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/thread_clock.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/thread_clock.hpp new file mode 100644 index 0000000..1a4406b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/mac/thread_clock.hpp @@ -0,0 +1,91 @@ +// boost thread_clock.cpp -----------------------------------------------------------// + +// Copyright Beman Dawes 1994, 2006, 2008 +// Copyright Vicente J. Botet Escriba 2009-2011 +// Copyright Christopher Brown 2013 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// + +#include +#include +#include + +# include +# include + +namespace boost { namespace chrono { + + thread_clock::time_point thread_clock::now( ) BOOST_NOEXCEPT + { + // get the thread port (borrowing pthread's reference) + mach_port_t port = pthread_mach_thread_np(pthread_self()); + + // get the thread info + thread_basic_info_data_t info; + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + if ( thread_info(port, THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS ) + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + + // convert to nanoseconds + duration user = duration( + static_cast( info.user_time.seconds ) * 1000000000 + + static_cast(info.user_time.microseconds ) * 1000); + + duration system = duration( + static_cast( info.system_time.seconds ) * 1000000000 + + static_cast( info.system_time.microseconds ) * 1000); + + return time_point( user + system ); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + thread_clock::time_point thread_clock::now( system::error_code & ec ) + { + // get the thread port (borrowing pthread's reference) + mach_port_t port = pthread_mach_thread_np(pthread_self()); + + // get the thread info + thread_basic_info_data_t info; + mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT; + if ( thread_info(port, THREAD_BASIC_INFO, (thread_info_t)&info, &count) != KERN_SUCCESS ) + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + EINVAL, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::thread_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + + // convert to nanoseconds + duration user = duration( + static_cast( info.user_time.seconds ) * 1000000000 + + static_cast(info.user_time.microseconds ) * 1000); + + duration system = duration( + static_cast( info.system_time.seconds ) * 1000000000 + + static_cast( info.system_time.microseconds ) * 1000); + + return time_point( user + system ); + } +#endif +} } diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/chrono.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/chrono.hpp new file mode 100644 index 0000000..e35a7ce --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/chrono.hpp @@ -0,0 +1,120 @@ +// posix/chrono.cpp --------------------------------------------------------------// + +// Copyright Beman Dawes 2008 +// Copyright Vicente J. Botet Escriba 2009 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +//----------------------------------------------------------------------------// +// POSIX // +//----------------------------------------------------------------------------// + +#include // for clock_gettime + +namespace boost +{ +namespace chrono +{ + + system_clock::time_point system_clock::now() BOOST_NOEXCEPT + { + timespec ts; + if ( ::clock_gettime( CLOCK_REALTIME, &ts ) ) + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + system_clock::time_point system_clock::now(system::error_code & ec) + { + timespec ts; + if ( ::clock_gettime( CLOCK_REALTIME, &ts ) ) + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::system_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + } +#endif + + std::time_t system_clock::to_time_t(const system_clock::time_point& t) BOOST_NOEXCEPT + { + return static_cast( t.time_since_epoch().count() / 1000000000 ); + } + + system_clock::time_point system_clock::from_time_t(std::time_t t) BOOST_NOEXCEPT + { + return time_point(duration(static_cast(t) * 1000000000)); + } + +#ifdef BOOST_CHRONO_HAS_CLOCK_STEADY + + steady_clock::time_point steady_clock::now() BOOST_NOEXCEPT + { + timespec ts; + if ( ::clock_gettime( CLOCK_MONOTONIC, &ts ) ) + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + steady_clock::time_point steady_clock::now(system::error_code & ec) + { + timespec ts; + if ( ::clock_gettime( CLOCK_MONOTONIC, &ts ) ) + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::steady_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + } +#endif +#endif + +} // namespace chrono +} // namespace boost + + diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/process_cpu_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/process_cpu_clocks.hpp new file mode 100644 index 0000000..feecc86 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/process_cpu_clocks.hpp @@ -0,0 +1,354 @@ +// boost process_cpu_clocks.cpp -----------------------------------------------------------// + +// Copyright Beman Dawes 1994, 2006, 2008 +// Copyright Vicente J. Botet Escriba 2009 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// + +#include +#include +#include + +#include +#include +#include // for clock_gettime + + +namespace boost { namespace chrono { +namespace chrono_detail +{ + inline nanoseconds::rep tick_factor() // multiplier to convert ticks + // to nanoseconds; -1 if unknown + { + long factor = 0; + if ( !factor ) + { + if ( (factor = ::sysconf( _SC_CLK_TCK )) <= 0 ) + factor = -1; + else + { + BOOST_ASSERT( factor <= 1000000000l ); // doesn't handle large ticks + factor = 1000000000l / factor; // compute factor + if ( !factor ) factor = -1; + } + } + return factor; + } +} + +process_real_cpu_clock::time_point process_real_cpu_clock::now() BOOST_NOEXCEPT +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + return time_point( + nanoseconds(c*chrono_detail::tick_factor())); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_real_cpu_clock::time_point process_real_cpu_clock::now( + system::error_code & ec) +{ + + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_real_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point( + nanoseconds(c*chrono_detail::tick_factor())); + } + else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_real_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + } +} +#endif + +process_user_cpu_clock::time_point process_user_cpu_clock::now() BOOST_NOEXCEPT +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + return time_point( + nanoseconds((tm.tms_utime + tm.tms_cutime)*chrono_detail::tick_factor())); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_user_cpu_clock::time_point process_user_cpu_clock::now( + system::error_code & ec) +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_user_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point( + nanoseconds((tm.tms_utime + tm.tms_cutime)*chrono_detail::tick_factor())); + } + else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_user_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + } +} +#endif + +process_system_cpu_clock::time_point process_system_cpu_clock::now() BOOST_NOEXCEPT +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + return time_point( + nanoseconds((tm.tms_stime + tm.tms_cstime)*chrono_detail::tick_factor())); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + } +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_system_cpu_clock::time_point process_system_cpu_clock::now( + system::error_code & ec) +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_system_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point( + nanoseconds((tm.tms_stime + tm.tms_cstime)*chrono_detail::tick_factor())); + } + else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_system_cpu_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + } +} +#endif + +process_cpu_clock::time_point process_cpu_clock::now() BOOST_NOEXCEPT +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + else + { + nanoseconds::rep factor = chrono_detail::tick_factor(); + if ( factor != -1 ) + { + time_point::rep r( + c*factor, + (tm.tms_utime + tm.tms_cutime)*factor, + (tm.tms_stime + tm.tms_cstime)*factor); + return time_point(duration(r)); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + } + return time_point(); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_cpu_clock::time_point process_cpu_clock::now( + system::error_code & ec ) +{ + tms tm; + clock_t c = ::times( &tm ); + if ( c == clock_t(-1) ) // error + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + else + { + if ( chrono_detail::tick_factor() != -1 ) + { + time_point::rep r( + c*chrono_detail::tick_factor(), + (tm.tms_utime + tm.tms_cutime)*chrono_detail::tick_factor(), + (tm.tms_stime + tm.tms_cstime)*chrono_detail::tick_factor()); + return time_point(duration(r)); + } + else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + } + +} +#endif + +} } diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/thread_clock.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/thread_clock.hpp new file mode 100644 index 0000000..a101224 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/posix/thread_clock.hpp @@ -0,0 +1,91 @@ +// boost thread_clock.cpp -----------------------------------------------------------// + +// Copyright Beman Dawes 1994, 2006, 2008 +// Copyright Vicente J. Botet Escriba 2009-2011 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// + +#include +#include +#include + +#if !defined(__VXWORKS__) +# include +#endif +# include +# include + +namespace boost { namespace chrono { + + thread_clock::time_point thread_clock::now( ) BOOST_NOEXCEPT + { + struct timespec ts; +#if defined CLOCK_THREAD_CPUTIME_ID + // get the timespec associated to the thread clock + if ( ::clock_gettime( CLOCK_THREAD_CPUTIME_ID, &ts ) ) +#else + // get the current thread + pthread_t pth=pthread_self(); + // get the clock_id associated to the current thread + clockid_t clock_id; + pthread_getcpuclockid(pth, &clock_id); + // get the timespec associated to the thread clock + if ( ::clock_gettime( clock_id, &ts ) ) +#endif + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + + // transform to nanoseconds + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + thread_clock::time_point thread_clock::now( system::error_code & ec ) + { + struct timespec ts; +#if defined CLOCK_THREAD_CPUTIME_ID + // get the timespec associated to the thread clock + if ( ::clock_gettime( CLOCK_THREAD_CPUTIME_ID, &ts ) ) +#else + // get the current thread + pthread_t pth=pthread_self(); + // get the clock_id associated to the current thread + clockid_t clock_id; + pthread_getcpuclockid(pth, &clock_id); + // get the timespec associated to the thread clock + if ( ::clock_gettime( clock_id, &ts ) ) +#endif + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::thread_clock" )); + } + else + { + ec.assign( errno, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + // transform to nanoseconds + return time_point(duration( + static_cast( ts.tv_sec ) * 1000000000 + ts.tv_nsec)); + + } +#endif +} } diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/process_cpu_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/process_cpu_clocks.hpp new file mode 100644 index 0000000..d37f675 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/process_cpu_clocks.hpp @@ -0,0 +1,45 @@ +// boost process_cpu_clocks.cpp -----------------------------------------------------------// + +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// +#ifndef BOOST_CHRONO_DETAIL_INLINED_PROCESS_CPU_CLOCKS_HPP +#define BOOST_CHRONO_DETAIL_INLINED_PROCESS_CPU_CLOCKS_HPP + + +#include +#if defined(BOOST_CHRONO_HAS_PROCESS_CLOCKS) + +#include +#include +#include +#include + +//----------------------------------------------------------------------------// +// Windows // +//----------------------------------------------------------------------------// +#if defined(BOOST_CHRONO_WINDOWS_API) +#include + +//----------------------------------------------------------------------------// +// Mac // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_MAC_API) +#include + +//----------------------------------------------------------------------------// +// POSIX // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_POSIX_API) +#include + +#endif // POSIX + +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/thread_clock.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/thread_clock.hpp new file mode 100644 index 0000000..16d19ef --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/thread_clock.hpp @@ -0,0 +1,44 @@ +// boost thread_clock.cpp -----------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// +#ifndef BOOST_CHRONO_DETAIL_INLINED_THREAD_CLOCK_HPP +#define BOOST_CHRONO_DETAIL_INLINED_THREAD_CLOCK_HPP + +#include +#include +#if defined(BOOST_CHRONO_HAS_THREAD_CLOCK) +#include +#include +#include +#include +#include + +//----------------------------------------------------------------------------// +// Windows // +//----------------------------------------------------------------------------// +#if defined(BOOST_CHRONO_WINDOWS_API) +#include + +//----------------------------------------------------------------------------// +// Mac // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_MAC_API) +#include + +//----------------------------------------------------------------------------// +// POSIX // +//----------------------------------------------------------------------------// +#elif defined(BOOST_CHRONO_POSIX_API) +#include + +#endif // POSIX + +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/chrono.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/chrono.hpp new file mode 100644 index 0000000..16e8c51 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/chrono.hpp @@ -0,0 +1,149 @@ +// win/chrono.cpp --------------------------------------------------------------// + +// Copyright Beman Dawes 2008 +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +//----------------------------------------------------------------------------// +// Windows // +//----------------------------------------------------------------------------// +#ifndef BOOST_CHRONO_DETAIL_INLINED_WIN_CHRONO_HPP +#define BOOST_CHRONO_DETAIL_INLINED_WIN_CHRONO_HPP + +#include +#include +#include + +namespace boost +{ +namespace chrono +{ +namespace chrono_detail +{ + + BOOST_CHRONO_INLINE double get_nanosecs_per_tic() BOOST_NOEXCEPT + { + boost::detail::winapi::LARGE_INTEGER_ freq; + if ( !boost::detail::winapi::QueryPerformanceFrequency( &freq ) ) + return 0.0L; + return double(1000000000.0L / freq.QuadPart); + } + +} + + steady_clock::time_point steady_clock::now() BOOST_NOEXCEPT + { + double nanosecs_per_tic = chrono_detail::get_nanosecs_per_tic(); + + boost::detail::winapi::LARGE_INTEGER_ pcount; + if ( nanosecs_per_tic <= 0.0L ) + { + BOOST_ASSERT(0 && "Boost::Chrono - get_nanosecs_per_tic Internal Error"); + return steady_clock::time_point(); + } + unsigned times=0; + while ( ! boost::detail::winapi::QueryPerformanceCounter( &pcount ) ) + { + if ( ++times > 3 ) + { + BOOST_ASSERT(0 && "Boost::Chrono - QueryPerformanceCounter Internal Error"); + return steady_clock::time_point(); + } + } + + return steady_clock::time_point(steady_clock::duration( + static_cast((nanosecs_per_tic) * pcount.QuadPart))); + } + + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + steady_clock::time_point steady_clock::now( system::error_code & ec ) + { + double nanosecs_per_tic = chrono_detail::get_nanosecs_per_tic(); + + boost::detail::winapi::LARGE_INTEGER_ pcount; + if ( (nanosecs_per_tic <= 0.0L) + || (!boost::detail::winapi::QueryPerformanceCounter( &pcount )) ) + { + boost::detail::winapi::DWORD_ cause = + ((nanosecs_per_tic <= 0.0L) + ? ERROR_NOT_SUPPORTED + : boost::detail::winapi::GetLastError()); + if (BOOST_CHRONO_IS_THROWS(ec)) { + boost::throw_exception( + system::system_error( + cause, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::steady_clock" )); + } + else + { + ec.assign( cause, BOOST_CHRONO_SYSTEM_CATEGORY ); + return steady_clock::time_point(duration(0)); + } + } + + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration( + static_cast(nanosecs_per_tic * pcount.QuadPart))); + } +#endif + + BOOST_CHRONO_INLINE + system_clock::time_point system_clock::now() BOOST_NOEXCEPT + { + boost::detail::winapi::FILETIME_ ft; + boost::detail::winapi::GetSystemTimeAsFileTime( &ft ); // never fails + return system_clock::time_point( + system_clock::duration( + ((static_cast<__int64>( ft.dwHighDateTime ) << 32) | ft.dwLowDateTime) + - 116444736000000000LL + //- (134775LL*864000000000LL) + ) + ); + } + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + BOOST_CHRONO_INLINE + system_clock::time_point system_clock::now( system::error_code & ec ) + { + boost::detail::winapi::FILETIME_ ft; + boost::detail::winapi::GetSystemTimeAsFileTime( &ft ); // never fails + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return system_clock::time_point( + system_clock::duration( + ((static_cast<__int64>( ft.dwHighDateTime ) << 32) | ft.dwLowDateTime) + - 116444736000000000LL + //- (134775LL*864000000000LL) + )); + } +#endif + + BOOST_CHRONO_INLINE + std::time_t system_clock::to_time_t(const system_clock::time_point& t) BOOST_NOEXCEPT + { + __int64 temp = t.time_since_epoch().count(); + temp /= 10000000; + return static_cast( temp ); + } + + BOOST_CHRONO_INLINE + system_clock::time_point system_clock::from_time_t(std::time_t t) BOOST_NOEXCEPT + { + __int64 temp = t; + temp *= 10000000; + return time_point(duration(temp)); + } + +} // namespace chrono +} // namespace boost + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/process_cpu_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/process_cpu_clocks.hpp new file mode 100644 index 0000000..e97bfe5 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/process_cpu_clocks.hpp @@ -0,0 +1,281 @@ +// boost process_timer.cpp -----------------------------------------------------------// + +// Copyright Beman Dawes 1994, 2006, 2008 +// Copyright 2009-2010 Vicente J. Botet Escriba +// Copyright (c) Microsoft Corporation 2014 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// +#ifndef BOOST_CHRONO_DETAIL_INLINED_WIN_PROCESS_CLOCK_HPP +#define BOOST_CHRONO_DETAIL_INLINED_WIN_PROCESS_CLOCK_HPP + +#include +//#include +#include +#include +#include + +#include +#include +#if BOOST_PLAT_WINDOWS_DESKTOP +#include +#endif + +namespace boost +{ +namespace chrono +{ + +process_real_cpu_clock::time_point process_real_cpu_clock::now() BOOST_NOEXCEPT +{ + clock_t c = ::clock(); + if ( c == clock_t(-1) ) // error + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + } + typedef ratio_divide >::type R; + return time_point( + duration(static_cast(c)*R::num/R::den) + ); +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_real_cpu_clock::time_point process_real_cpu_clock::now( + system::error_code & ec) +{ + clock_t c = ::clock(); + if ( c == clock_t(-1) ) // error + { + boost::throw_exception( + system::system_error( + errno, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_real_cpu_clock" )); + } + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + typedef ratio_divide >::type R; + return time_point( + duration(static_cast(c)*R::num/R::den) + ); +} +#endif + +#if BOOST_PLAT_WINDOWS_DESKTOP +process_user_cpu_clock::time_point process_user_cpu_clock::now() BOOST_NOEXCEPT +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + return time_point(duration( + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime) * 100 + )); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_user_cpu_clock::time_point process_user_cpu_clock::now( + system::error_code & ec) +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration( + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime) * 100 + )); + } + else + { + boost::detail::winapi::DWORD_ cause = boost::detail::winapi::GetLastError(); + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + cause, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_user_cpu_clock" )); + } + else + { + ec.assign( cause, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + +} +#endif + +process_system_cpu_clock::time_point process_system_cpu_clock::now() BOOST_NOEXCEPT +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + return time_point(duration( + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime) * 100 + )); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_system_cpu_clock::time_point process_system_cpu_clock::now( + system::error_code & ec) +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(duration( + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime) * 100 + )); + } + else + { + boost::detail::winapi::DWORD_ cause = boost::detail::winapi::GetLastError(); + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + cause, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_system_cpu_clock" )); + } + else + { + ec.assign( cause, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + +} +#endif + +process_cpu_clock::time_point process_cpu_clock::now() BOOST_NOEXCEPT +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + time_point::rep r(process_real_cpu_clock::now().time_since_epoch().count() + , + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime + ) * 100, + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime + ) * 100 + ); + return time_point(duration(r)); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + +} + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +process_cpu_clock::time_point process_cpu_clock::now( + system::error_code & ec ) +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetProcessTimes( + boost::detail::winapi::GetCurrentProcess(), &creation, &exit, + &system_time, &user_time ) ) + { + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + time_point::rep r(process_real_cpu_clock::now().time_since_epoch().count() + , + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime + ) * 100, + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime + ) * 100 + ); + return time_point(duration(r)); + } + else + { + boost::detail::winapi::DWORD_ cause = boost::detail::winapi::GetLastError(); + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + cause, + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::process_cpu_clock" )); + } + else + { + ec.assign( cause, BOOST_CHRONO_SYSTEM_CATEGORY ); + return time_point(); + } + } + +} +#endif +#endif +} // namespace chrono +} // namespace boost + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/thread_clock.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/thread_clock.hpp new file mode 100644 index 0000000..e47c481 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/inlined/win/thread_clock.hpp @@ -0,0 +1,102 @@ +// boost thread_clock.cpp -----------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/chrono for documentation. + +//--------------------------------------------------------------------------------------// +#ifndef BOOST_CHRONO_DETAIL_INLINED_WIN_THREAD_CLOCK_HPP +#define BOOST_CHRONO_DETAIL_INLINED_WIN_THREAD_CLOCK_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost +{ +namespace chrono +{ + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING +thread_clock::time_point thread_clock::now( system::error_code & ec ) +{ + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetThreadTimes( + boost::detail::winapi::GetCurrentThread (), &creation, &exit, + &system_time, &user_time ) ) + { + duration user = duration( + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime) * 100 ); + + duration system = duration( + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime) * 100 ); + + if (!BOOST_CHRONO_IS_THROWS(ec)) + { + ec.clear(); + } + return time_point(system+user); + + } + else + { + if (BOOST_CHRONO_IS_THROWS(ec)) + { + boost::throw_exception( + system::system_error( + boost::detail::winapi::GetLastError(), + BOOST_CHRONO_SYSTEM_CATEGORY, + "chrono::thread_clock" )); + } + else + { + ec.assign( boost::detail::winapi::GetLastError(), BOOST_CHRONO_SYSTEM_CATEGORY ); + return thread_clock::time_point(duration(0)); + } + } +} +#endif + +thread_clock::time_point thread_clock::now() BOOST_NOEXCEPT +{ + + // note that Windows uses 100 nanosecond ticks for FILETIME + boost::detail::winapi::FILETIME_ creation, exit, user_time, system_time; + + if ( boost::detail::winapi::GetThreadTimes( + boost::detail::winapi::GetCurrentThread (), &creation, &exit, + &system_time, &user_time ) ) + { + duration user = duration( + ((static_cast(user_time.dwHighDateTime) << 32) + | user_time.dwLowDateTime) * 100 ); + + duration system = duration( + ((static_cast(system_time.dwHighDateTime) << 32) + | system_time.dwLowDateTime) * 100 ); + + return time_point(system+user); + } + else + { + BOOST_ASSERT(0 && "Boost::Chrono - Internal Error"); + return time_point(); + } + +} + +} // namespace chrono +} // namespace boost + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/is_evenly_divisible_by.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/is_evenly_divisible_by.hpp new file mode 100644 index 0000000..960a208 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/is_evenly_divisible_by.hpp @@ -0,0 +1,31 @@ +// is_evenly_divisible_by.hpp --------------------------------------------------------------// + +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHRONO_DETAIL_IS_EVENLY_DIVISIBLE_BY_HPP +#define BOOST_CHRONO_DETAIL_IS_EVENLY_DIVISIBLE_BY_HPP + +#include + +#include +#include + +namespace boost { +namespace chrono { +namespace chrono_detail { + +// template +// struct is_evenly_divisible_by : public boost::mpl::bool_ < ratio_divide::type::den == 1 > +// {}; + template + struct is_evenly_divisible_by : public boost::ratio_detail::is_evenly_divisible_by + {}; + +} // namespace chrono_detail +} // namespace detail +} // namespace chrono + +#endif // BOOST_CHRONO_DETAIL_IS_EVENLY_DIVISIBLE_BY_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/static_assert.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/static_assert.hpp new file mode 100644 index 0000000..8615194 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/static_assert.hpp @@ -0,0 +1,30 @@ +// static_assert.hpp --------------------------------------------------------------// + +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_CHRONO_DETAIL_STATIC_ASSERT_HPP +#define BOOST_CHRONO_DETAIL_STATIC_ASSERT_HPP + +#include + +#ifndef BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_CHRONO_STATIC_ASSERT(CND, MSG, TYPES) static_assert(CND,MSG) +#elif defined(BOOST_CHRONO_USES_STATIC_ASSERT) +#include +#define BOOST_CHRONO_STATIC_ASSERT(CND, MSG, TYPES) BOOST_STATIC_ASSERT(CND) +#elif defined(BOOST_CHRONO_USES_MPL_ASSERT) +#include +#include +#define BOOST_CHRONO_STATIC_ASSERT(CND, MSG, TYPES) \ + BOOST_MPL_ASSERT_MSG(boost::mpl::bool_< (CND) >::type::value, MSG, TYPES) +#else +//~ #elif defined(BOOST_CHRONO_USES_ARRAY_ASSERT) +#define BOOST_CHRONO_STATIC_ASSERT(CND, MSG, TYPES) static char BOOST_JOIN(boost_chrono_test_,__LINE__)[(CND)?1:-1] +//~ #define BOOST_CHRONO_STATIC_ASSERT(CND, MSG, TYPES) +#endif + +#endif // BOOST_CHRONO_DETAIL_STATIC_ASSERT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/detail/system.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/detail/system.hpp new file mode 100644 index 0000000..0dcffe8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/detail/system.hpp @@ -0,0 +1,29 @@ +// Copyright 2009-2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CHRONO_DETAIL_SYSTEM_HPP +#define BOOST_CHRONO_DETAIL_SYSTEM_HPP + +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + +#include +#include + +#if ((BOOST_VERSION / 100000) < 2) && ((BOOST_VERSION / 100 % 1000) < 44) +#define BOOST_CHRONO_SYSTEM_CATEGORY boost::system::system_category +#else +#define BOOST_CHRONO_SYSTEM_CATEGORY boost::system::system_category() +#endif + +#ifdef BOOST_SYSTEM_NO_DEPRECATED +#define BOOST_CHRONO_THROWS boost::throws() +#define BOOST_CHRONO_IS_THROWS(EC) (&EC==&boost::throws()) +#else +#define BOOST_CHRONO_THROWS boost::system::throws +#define BOOST_CHRONO_IS_THROWS(EC) (&EC==&boost::system::throws) +#endif + +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/duration.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/duration.hpp new file mode 100644 index 0000000..ac4cfb4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/duration.hpp @@ -0,0 +1,800 @@ +// duration.hpp --------------------------------------------------------------// + +// Copyright 2008 Howard Hinnant +// Copyright 2008 Beman Dawes +// Copyright 2009-2011 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +/* + +This code was derived by Beman Dawes from Howard Hinnant's time2_demo prototype. +Many thanks to Howard for making his code available under the Boost license. +The original code was modified to conform to Boost conventions and to section +20.9 Time utilities [time] of the C++ committee's working paper N2798. +See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf. + +time2_demo contained this comment: + + Much thanks to Andrei Alexandrescu, + Walter Brown, + Peter Dimov, + Jeff Garland, + Terry Golubiewski, + Daniel Krugler, + Anthony Williams. +*/ + + +#ifndef BOOST_CHRONO_DURATION_HPP +#define BOOST_CHRONO_DURATION_HPP + +#include +#include + +#include +#include + + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#if !defined(BOOST_NO_CXX11_STATIC_ASSERT) || !defined(BOOST_CHRONO_USES_MPL_ASSERT) +#define BOOST_CHRONO_A_DURATION_REPRESENTATION_CAN_NOT_BE_A_DURATION "A duration representation can not be a duration" +#define BOOST_CHRONO_SECOND_TEMPLATE_PARAMETER_OF_DURATION_MUST_BE_A_STD_RATIO "Second template parameter of duration must be a boost::ratio" +#define BOOST_CHRONO_DURATION_PERIOD_MUST_BE_POSITIVE "duration period must be positive" +#define BOOST_CHRONO_SECOND_TEMPLATE_PARAMETER_OF_TIME_POINT_MUST_BE_A_BOOST_CHRONO_DURATION "Second template parameter of time_point must be a boost::chrono::duration" +#endif + +#ifndef BOOST_CHRONO_HEADER_ONLY +// this must occur after all of the includes and before any code appears: +#include // must be the last #include +#endif + +//----------------------------------------------------------------------------// +// // +// 20.9 Time utilities [time] // +// synopsis // +// // +//----------------------------------------------------------------------------// + +namespace boost { +namespace chrono { + + template > + class duration; + + namespace detail + { + template + struct is_duration + : boost::false_type {}; + + template + struct is_duration > + : boost::true_type {}; + + template ::value> + struct duration_divide_result + { + }; + + template ::type>::value)) + && ((boost::is_convertible::type>::value)) + ) + > + struct duration_divide_imp + { + }; + + template + struct duration_divide_imp, Rep2, true> + { + typedef duration::type, Period> type; + }; + + template + struct duration_divide_result, Rep2, false> + : duration_divide_imp, Rep2> + { + }; + +/// + template ::value> + struct duration_divide_result2 + { + }; + + template ::type>::value)) + && ((boost::is_convertible::type>::value)) + ) + > + struct duration_divide_imp2 + { + }; + + template + struct duration_divide_imp2, true> + { + //typedef typename common_type::type type; + typedef double type; + }; + + template + struct duration_divide_result2, false> + : duration_divide_imp2 > + { + }; + +/// + template ::value> + struct duration_modulo_result + { + }; + + template ::type>::value + //&& + boost::is_convertible::type>::value + ) + > + struct duration_modulo_imp + { + }; + + template + struct duration_modulo_imp, Rep2, true> + { + typedef duration::type, Period> type; + }; + + template + struct duration_modulo_result, Rep2, false> + : duration_modulo_imp, Rep2> + { + }; + +} // namespace detail +} // namespace chrono + + +// common_type trait specializations + +template +struct common_type, + chrono::duration >; + + +namespace chrono { + + // customization traits + template struct treat_as_floating_point; + template struct duration_values; + + // convenience typedefs + typedef duration nanoseconds; // at least 64 bits needed + typedef duration microseconds; // at least 55 bits needed + typedef duration milliseconds; // at least 45 bits needed + typedef duration seconds; // at least 35 bits needed + typedef duration > minutes; // at least 29 bits needed + typedef duration > hours; // at least 23 bits needed + +//----------------------------------------------------------------------------// +// duration helpers // +//----------------------------------------------------------------------------// + +namespace detail +{ + + // duration_cast + + // duration_cast is the heart of this whole prototype. It can convert any + // duration to any other. It is also (implicitly) used in converting + // time_points. The conversion is always exact if possible. And it is + // always as efficient as hand written code. If different representations + // are involved, care is taken to never require implicit conversions. + // Instead static_cast is used explicitly for every required conversion. + // If there are a mixture of integral and floating point representations, + // the use of common_type ensures that the most logical "intermediate" + // representation is used. + template + struct duration_cast_aux; + + // When the two periods are the same, all that is left to do is static_cast from + // the source representation to the target representation (which may be a no-op). + // This conversion is always exact as long as the static_cast from the source + // representation to the destination representation is exact. + template + struct duration_cast_aux + { + BOOST_CONSTEXPR ToDuration operator()(const FromDuration& fd) const + { + return ToDuration(static_cast(fd.count())); + } + }; + + // When the numerator of FromPeriod / ToPeriod is 1, then all we need to do is + // divide by the denominator of FromPeriod / ToPeriod. The common_type of + // the two representations is used for the intermediate computation before + // static_cast'ing to the destination. + // This conversion is generally not exact because of the division (but could be + // if you get lucky on the run time value of fd.count()). + template + struct duration_cast_aux + { + BOOST_CONSTEXPR ToDuration operator()(const FromDuration& fd) const + { + typedef typename common_type< + typename ToDuration::rep, + typename FromDuration::rep, + boost::intmax_t>::type C; + return ToDuration(static_cast( + static_cast(fd.count()) / static_cast(Period::den))); + } + }; + + // When the denominator of FromPeriod / ToPeriod is 1, then all we need to do is + // multiply by the numerator of FromPeriod / ToPeriod. The common_type of + // the two representations is used for the intermediate computation before + // static_cast'ing to the destination. + // This conversion is always exact as long as the static_cast's involved are exact. + template + struct duration_cast_aux + { + BOOST_CONSTEXPR ToDuration operator()(const FromDuration& fd) const + { + typedef typename common_type< + typename ToDuration::rep, + typename FromDuration::rep, + boost::intmax_t>::type C; + return ToDuration(static_cast( + static_cast(fd.count()) * static_cast(Period::num))); + } + }; + + // When neither the numerator or denominator of FromPeriod / ToPeriod is 1, then we need to + // multiply by the numerator and divide by the denominator of FromPeriod / ToPeriod. The + // common_type of the two representations is used for the intermediate computation before + // static_cast'ing to the destination. + // This conversion is generally not exact because of the division (but could be + // if you get lucky on the run time value of fd.count()). + template + struct duration_cast_aux + { + BOOST_CONSTEXPR ToDuration operator()(const FromDuration& fd) const + { + typedef typename common_type< + typename ToDuration::rep, + typename FromDuration::rep, + boost::intmax_t>::type C; + return ToDuration(static_cast( + static_cast(fd.count()) * static_cast(Period::num) + / static_cast(Period::den))); + } + }; + + template + struct duration_cast { + typedef typename ratio_divide::type Period; + typedef duration_cast_aux< + FromDuration, + ToDuration, + Period, + Period::num == 1, + Period::den == 1 + > Aux; + BOOST_CONSTEXPR ToDuration operator()(const FromDuration& fd) const + { + return Aux()(fd); + } + }; + +} // namespace detail + +//----------------------------------------------------------------------------// +// // +// 20.9.2 Time-related traits [time.traits] // +// // +//----------------------------------------------------------------------------// +//----------------------------------------------------------------------------// +// 20.9.2.1 treat_as_floating_point [time.traits.is_fp] // +// Probably should have been treat_as_floating_point. Editor notifed. // +//----------------------------------------------------------------------------// + + // Support bidirectional (non-exact) conversions for floating point rep types + // (or user defined rep types which specialize treat_as_floating_point). + template + struct treat_as_floating_point : boost::is_floating_point {}; + +//----------------------------------------------------------------------------// +// 20.9.2.2 duration_values [time.traits.duration_values] // +//----------------------------------------------------------------------------// + +namespace detail { + template ::value> + struct chrono_numeric_limits { + static BOOST_CHRONO_LIB_CONSTEXPR T lowest() BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW {return (std::numeric_limits::min) ();} + }; + + template + struct chrono_numeric_limits { + static BOOST_CHRONO_LIB_CONSTEXPR T lowest() BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW {return (std::numeric_limits::min) ();} + }; + + template <> + struct chrono_numeric_limits { + static BOOST_CHRONO_LIB_CONSTEXPR float lowest() BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW + { + return -(std::numeric_limits::max) (); + } + }; + + template <> + struct chrono_numeric_limits { + static BOOST_CHRONO_LIB_CONSTEXPR double lowest() BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW + { + return -(std::numeric_limits::max) (); + } + }; + + template <> + struct chrono_numeric_limits { + static BOOST_CHRONO_LIB_CONSTEXPR long double lowest() BOOST_CHRONO_LIB_NOEXCEPT_OR_THROW + { + return -(std::numeric_limits::max)(); + } + }; + + template + struct numeric_limits : chrono_numeric_limits::type> + {}; + +} +template +struct duration_values +{ + static BOOST_CONSTEXPR Rep zero() {return Rep(0);} + static BOOST_CHRONO_LIB_CONSTEXPR Rep max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return (std::numeric_limits::max)(); + } + + static BOOST_CHRONO_LIB_CONSTEXPR Rep min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return detail::numeric_limits::lowest(); + } +}; + +} // namespace chrono + +//----------------------------------------------------------------------------// +// 20.9.2.3 Specializations of common_type [time.traits.specializations] // +//----------------------------------------------------------------------------// + +template +struct common_type, + chrono::duration > +{ + typedef chrono::duration::type, + typename boost::ratio_gcd::type> type; +}; + + +//----------------------------------------------------------------------------// +// // +// 20.9.3 Class template duration [time.duration] // +// // +//----------------------------------------------------------------------------// + + +namespace chrono { + + template + class BOOST_SYMBOL_VISIBLE duration + { + //BOOST_CHRONO_STATIC_ASSERT(boost::is_integral::value, BOOST_CHRONO_A_DURATION_REPRESENTATION_MUST_BE_INTEGRAL, ()); + BOOST_CHRONO_STATIC_ASSERT(!boost::chrono::detail::is_duration::value, + BOOST_CHRONO_A_DURATION_REPRESENTATION_CAN_NOT_BE_A_DURATION, ()); + BOOST_CHRONO_STATIC_ASSERT(boost::ratio_detail::is_ratio::value, + BOOST_CHRONO_SECOND_TEMPLATE_PARAMETER_OF_DURATION_MUST_BE_A_STD_RATIO, ()); + BOOST_CHRONO_STATIC_ASSERT(Period::num>0, + BOOST_CHRONO_DURATION_PERIOD_MUST_BE_POSITIVE, ()); + public: + typedef Rep rep; + typedef Period period; + private: + rep rep_; + public: + +#if defined BOOST_NO_CXX11_DEFAULTED_FUNCTIONS || \ + defined BOOST_CHRONO_DURATION_DEFAULTS_TO_ZERO + BOOST_FORCEINLINE BOOST_CONSTEXPR + duration() : rep_(duration_values::zero()) { } +#else + BOOST_CONSTEXPR duration() BOOST_NOEXCEPT {}; +#endif + template + BOOST_SYMBOL_VISIBLE BOOST_FORCEINLINE BOOST_CONSTEXPR + explicit duration(const Rep2& r + , typename boost::enable_if < + mpl::and_ < + boost::is_convertible, + mpl::or_ < + treat_as_floating_point, + mpl::and_ < + mpl::not_ < treat_as_floating_point >, + mpl::not_ < treat_as_floating_point > + > + > + > + >::type* = 0 + ) : rep_(r) { } +#if defined BOOST_NO_CXX11_DEFAULTED_FUNCTIONS + duration& operator=(const duration& rhs) + { + if (&rhs != this) rep_= rhs.rep_; + return *this; + } +#else + duration& operator=(const duration& rhs) = default; +#endif + // conversions + template + BOOST_FORCEINLINE BOOST_CONSTEXPR + duration(const duration& d + , typename boost::enable_if < + mpl::or_ < + treat_as_floating_point, + mpl::and_ < + chrono_detail::is_evenly_divisible_by, + mpl::not_ < treat_as_floating_point > + > + > + >::type* = 0 + ) + : rep_(chrono::detail::duration_cast, duration>()(d).count()) {} + + // observer + + BOOST_CONSTEXPR + rep count() const {return rep_;} + + // arithmetic + + BOOST_CONSTEXPR + duration operator+() const {return duration(rep_);;} + BOOST_CONSTEXPR + duration operator-() const {return duration(-rep_);} + duration& operator++() {++rep_; return *this;} + duration operator++(int) {return duration(rep_++);} + duration& operator--() {--rep_; return *this;} + duration operator--(int) {return duration(rep_--);} + + duration& operator+=(const duration& d) + { + rep_ += d.count(); return *this; + } + duration& operator-=(const duration& d) + { + rep_ -= d.count(); return *this; + } + + duration& operator*=(const rep& rhs) {rep_ *= rhs; return *this;} + duration& operator/=(const rep& rhs) {rep_ /= rhs; return *this;} + duration& operator%=(const rep& rhs) {rep_ %= rhs; return *this;} + duration& operator%=(const duration& rhs) + { + rep_ %= rhs.count(); return *this; + } + // 20.9.3.4 duration special values [time.duration.special] + + static BOOST_CONSTEXPR duration zero() + { + return duration(duration_values::zero()); + } + static BOOST_CHRONO_LIB_CONSTEXPR duration min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return duration((duration_values::min)()); + } + static BOOST_CHRONO_LIB_CONSTEXPR duration max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return duration((duration_values::max)()); + } + }; + +//----------------------------------------------------------------------------// +// 20.9.3.5 duration non-member arithmetic [time.duration.nonmember] // +//----------------------------------------------------------------------------// + + // Duration + + + template + inline BOOST_CONSTEXPR + typename common_type, duration >::type + operator+(const duration& lhs, + const duration& rhs) + { + typedef typename common_type, + duration >::type CD; + return CD(CD(lhs).count()+CD(rhs).count()); + } + + // Duration - + + template + inline BOOST_CONSTEXPR + typename common_type, duration >::type + operator-(const duration& lhs, + const duration& rhs) + { + typedef typename common_type, + duration >::type CD; + return CD(CD(lhs).count()-CD(rhs).count()); + } + + // Duration * + + template + inline BOOST_CONSTEXPR + typename boost::enable_if < + mpl::and_ < + boost::is_convertible::type>, + boost::is_convertible::type> + >, + duration::type, Period> + >::type + operator*(const duration& d, const Rep2& s) + { + typedef typename common_type::type CR; + typedef duration CD; + return CD(CD(d).count()*static_cast(s)); + } + + template + inline BOOST_CONSTEXPR + typename boost::enable_if < + mpl::and_ < + boost::is_convertible::type>, + boost::is_convertible::type> + >, + duration::type, Period> + >::type + operator*(const Rep1& s, const duration& d) + { + return d * s; + } + + // Duration / + + template + inline BOOST_CONSTEXPR + typename boost::disable_if , + typename boost::chrono::detail::duration_divide_result< + duration, Rep2>::type + >::type + operator/(const duration& d, const Rep2& s) + { + typedef typename common_type::type CR; + typedef duration CD; + + return CD(CD(d).count()/static_cast(s)); + } + + template + inline BOOST_CONSTEXPR + typename common_type::type + operator/(const duration& lhs, const duration& rhs) + { + typedef typename common_type, + duration >::type CD; + return CD(lhs).count() / CD(rhs).count(); + } + + #ifdef BOOST_CHRONO_EXTENSIONS + template + inline BOOST_CONSTEXPR + typename boost::disable_if , + typename boost::chrono::detail::duration_divide_result2< + Rep1, duration >::type + >::type + operator/(const Rep1& s, const duration& d) + { + typedef typename common_type::type CR; + typedef duration CD; + + return static_cast(s)/CD(d).count(); + } + #endif + // Duration % + + template + inline BOOST_CONSTEXPR + typename boost::disable_if , + typename boost::chrono::detail::duration_modulo_result< + duration, Rep2>::type + >::type + operator%(const duration& d, const Rep2& s) + { + typedef typename common_type::type CR; + typedef duration CD; + + return CD(CD(d).count()%static_cast(s)); + } + + template + inline BOOST_CONSTEXPR + typename common_type, duration >::type + operator%(const duration& lhs, + const duration& rhs) { + typedef typename common_type, + duration >::type CD; + + return CD(CD(lhs).count()%CD(rhs).count()); + } + + +//----------------------------------------------------------------------------// +// 20.9.3.6 duration comparisons [time.duration.comparisons] // +//----------------------------------------------------------------------------// + +namespace detail +{ + template + struct duration_eq + { + BOOST_CONSTEXPR bool operator()(const LhsDuration& lhs, const RhsDuration& rhs) const + { + typedef typename common_type::type CD; + return CD(lhs).count() == CD(rhs).count(); + } + }; + + template + struct duration_eq + { + BOOST_CONSTEXPR bool operator()(const LhsDuration& lhs, const LhsDuration& rhs) const + { + return lhs.count() == rhs.count(); + } + }; + + template + struct duration_lt + { + BOOST_CONSTEXPR bool operator()(const LhsDuration& lhs, const RhsDuration& rhs) const + { + typedef typename common_type::type CD; + return CD(lhs).count() < CD(rhs).count(); + } + }; + + template + struct duration_lt + { + BOOST_CONSTEXPR bool operator()(const LhsDuration& lhs, const LhsDuration& rhs) const + { + return lhs.count() < rhs.count(); + } + }; + +} // namespace detail + + // Duration == + + template + inline BOOST_CONSTEXPR + bool + operator==(const duration& lhs, + const duration& rhs) + { + return boost::chrono::detail::duration_eq< + duration, duration >()(lhs, rhs); + } + + // Duration != + + template + inline BOOST_CONSTEXPR + bool + operator!=(const duration& lhs, + const duration& rhs) + { + return !(lhs == rhs); + } + + // Duration < + + template + inline BOOST_CONSTEXPR + bool + operator< (const duration& lhs, + const duration& rhs) + { + return boost::chrono::detail::duration_lt< + duration, duration >()(lhs, rhs); + } + + // Duration > + + template + inline BOOST_CONSTEXPR + bool + operator> (const duration& lhs, + const duration& rhs) + { + return rhs < lhs; + } + + // Duration <= + + template + inline BOOST_CONSTEXPR + bool + operator<=(const duration& lhs, + const duration& rhs) + { + return !(rhs < lhs); + } + + // Duration >= + + template + inline BOOST_CONSTEXPR + bool + operator>=(const duration& lhs, + const duration& rhs) + { + return !(lhs < rhs); + } + +//----------------------------------------------------------------------------// +// 20.9.3.7 duration_cast [time.duration.cast] // +//----------------------------------------------------------------------------// + + // Compile-time select the most efficient algorithm for the conversion... + template + inline BOOST_CONSTEXPR + typename boost::enable_if < + boost::chrono::detail::is_duration, ToDuration>::type + duration_cast(const duration& fd) + { + return boost::chrono::detail::duration_cast< + duration, ToDuration>()(fd); + } + +} // namespace chrono +} // namespace boost + +#ifndef BOOST_CHRONO_HEADER_ONLY +// the suffix header occurs after all of our code: +#include // pops abi_prefix.hpp pragmas +#endif + +#endif // BOOST_CHRONO_DURATION_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/process_cpu_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/process_cpu_clocks.hpp new file mode 100644 index 0000000..2488375 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/process_cpu_clocks.hpp @@ -0,0 +1,522 @@ +// boost/chrono/process_cpu_clocks.hpp -----------------------------------------------------------// + +// Copyright 2009-2011 Vicente J. Botet Escriba +// Copyright (c) Microsoft Corporation 2014 + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/system for documentation. + +#ifndef BOOST_CHRONO_PROCESS_CPU_CLOCKS_HPP +#define BOOST_CHRONO_PROCESS_CPU_CLOCKS_HPP + +#include + + +#if defined(BOOST_CHRONO_HAS_PROCESS_CLOCKS) + +#include +#include +#include +#include +#include +#include +#include + +#ifndef BOOST_CHRONO_HEADER_ONLY +#include // must be the last #include +#endif + +namespace boost { namespace chrono { + + class BOOST_CHRONO_DECL process_real_cpu_clock { + public: + typedef nanoseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = true; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec ); +#endif + }; + +#if ! BOOST_OS_WINDOWS || BOOST_PLAT_WINDOWS_DESKTOP + class BOOST_CHRONO_DECL process_user_cpu_clock { + public: + typedef nanoseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = true; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec ); +#endif + }; + + class BOOST_CHRONO_DECL process_system_cpu_clock { + public: + typedef nanoseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = true; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec ); +#endif + }; +#endif + + template + struct process_times + : arithmetic, + multiplicative, Rep, + less_than_comparable > > > + { + //typedef process_real_cpu_clock::rep rep; + typedef Rep rep; + process_times() + : real(0) + , user(0) + , system(0){} + +#if ! defined BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 + template + explicit process_times( + Rep2 r) + : real(r) + , user(r) + , system(r){} +#endif + template + explicit process_times( + process_times const& rhs) + : real(rhs.real) + , user(rhs.user) + , system(rhs.system){} + process_times( + rep r, + rep u, + rep s) + : real(r) + , user(u) + , system(s){} + + rep real; // real (i.e wall clock) time + rep user; // user cpu time + rep system; // system cpu time + +#if ! defined BOOST_CHRONO_DONT_PROVIDES_DEPRECATED_IO_SINCE_V2_0_0 + operator rep() const + { + return real; + } +#endif + template + bool operator==(process_times const& rhs) { + return (real==rhs.real && + user==rhs.user && + system==rhs.system); + } + + process_times& operator+=( + process_times const& rhs) + { + real+=rhs.real; + user+=rhs.user; + system+=rhs.system; + return *this; + } + process_times& operator-=( + process_times const& rhs) + { + real-=rhs.real; + user-=rhs.user; + system-=rhs.system; + return *this; + } + process_times& operator*=( + process_times const& rhs) + { + real*=rhs.real; + user*=rhs.user; + system*=rhs.system; + return *this; + } + process_times& operator*=(rep const& rhs) + { + real*=rhs; + user*=rhs; + system*=rhs; + return *this; + } + process_times& operator/=(process_times const& rhs) + { + real/=rhs.real; + user/=rhs.user; + system/=rhs.system; + return *this; + } + process_times& operator/=(rep const& rhs) + { + real/=rhs; + user/=rhs; + system/=rhs; + return *this; + } + bool operator<(process_times const & rhs) const + { + if (real < rhs.real) return true; + if (real > rhs.real) return false; + if (user < rhs.user) return true; + if (user > rhs.user) return false; + if (system < rhs.system) return true; + else return false; + } + + template + void print(std::basic_ostream& os) const + { + os << "{"<< real <<";"<< user <<";"<< system << "}"; + } + + template + void read(std::basic_istream& is) + { + typedef std::istreambuf_iterator in_iterator; + in_iterator i(is); + in_iterator e; + if (i == e || *i != '{') // mandatory '{' + { + is.setstate(is.failbit | is.eofbit); + return; + } + CharT x,y,z; + is >> real >> x >> user >> y >> system >> z; + if (!is.good() || (x != ';')|| (y != ';')|| (z != '}')) + { + is.setstate(is.failbit); + } + } + }; +} +template +struct common_type< + chrono::process_times, + chrono::process_times +> +{ + typedef chrono::process_times::type> type; +}; + +template +struct common_type< + chrono::process_times, + Rep2 +> +{ + typedef chrono::process_times::type> type; +}; + +template +struct common_type< + Rep1, + chrono::process_times +> +{ + typedef chrono::process_times::type> type; +}; + + +namespace chrono +{ + template + inline BOOST_CONSTEXPR + bool + operator==(const duration, Period1>& lhs, + const duration, Period2>& rhs) + { + return boost::chrono::detail::duration_eq< + duration, Period1>, duration, Period2> >()(lhs, rhs); + } + + template + inline BOOST_CONSTEXPR + bool + operator==(const duration, Period1>& lhs, + const duration& rhs) + { + return boost::chrono::detail::duration_eq< + duration, duration >()(duration(lhs.count().real), rhs); + } + + template + inline BOOST_CONSTEXPR + bool + operator==(const duration& lhs, + const duration, Period2>& rhs) + { + return rhs == lhs; + } + + + // Duration < + + template + inline BOOST_CONSTEXPR + bool + operator< (const duration, Period1>& lhs, + const duration& rhs) + { + return boost::chrono::detail::duration_lt< + duration, duration >()(duration(lhs.count().real), rhs); + } + + template + inline BOOST_CONSTEXPR + bool + operator< (const duration& lhs, + const duration, Period2>& rhs) + { + return rhs < lhs; + } + + template + inline BOOST_CONSTEXPR + bool + operator< (const duration, Period1>& lhs, + const duration, Period2>& rhs) + { + return boost::chrono::detail::duration_lt< + duration, duration >()(lhs, rhs); + } + + + typedef process_times process_cpu_clock_times; +#if ! BOOST_OS_WINDOWS || BOOST_PLAT_WINDOWS_DESKTOP + class BOOST_CHRONO_DECL process_cpu_clock + { + public: + + typedef process_cpu_clock_times times; + typedef boost::chrono::duration duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = true; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec ); +#endif + }; +#endif + + template + std::basic_ostream& + operator<<(std::basic_ostream& os, + process_times const& rhs) + { + rhs.print(os); + return os; + } + + template + std::basic_istream& + operator>>(std::basic_istream& is, + process_times& rhs) + { + rhs.read(is); + return is; + } + + template + struct duration_values > + { + typedef process_times Res; + public: + static Res zero() + { + return Res(); + } + static Res max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return Res((std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + } + static Res min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return Res((std::numeric_limits::min)(), + (std::numeric_limits::min)(), + (std::numeric_limits::min)()); + } + }; + + template + struct clock_string + { + static std::basic_string name() + { + static const CharT + u[] = + { 'p', 'r', 'o', 'c', 'e', 's', 's', '_', 'r', 'e', 'a', 'l', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT + u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'p', 'r', 'o', 'c', 'e', 's', 's', ' ', 's', 't', 'a', 'r', 't', '-', 'u', 'p' }; + const std::basic_string str(u, u + sizeof(u) / sizeof(u[0])); + return str; + } + }; + +#if ! BOOST_OS_WINDOWS || BOOST_PLAT_WINDOWS_DESKTOP + template + struct clock_string + { + static std::basic_string name() + { + static const CharT + u[] = + { 'p', 'r', 'o', 'c', 'e', 's', 's', '_', 'u', 's', 'e', 'r', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT + u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'p', 'r', 'o', 'c', 'e', 's', 's', ' ', 's', 't', 'a', 'r', 't', '-', 'u', 'p' }; + const std::basic_string str(u, u + sizeof(u) / sizeof(u[0])); + return str; + } + }; + + template + struct clock_string + { + static std::basic_string name() + { + static const CharT + u[] = + { 'p', 'r', 'o', 'c', 'e', 's', 's', '_', 's', 'y', 's', 't', 't', 'e', 'm', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT + u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'p', 'r', 'o', 'c', 'e', 's', 's', ' ', 's', 't', 'a', 'r', 't', '-', 'u', 'p' }; + const std::basic_string str(u, u + sizeof(u) / sizeof(u[0])); + return str; + } + }; + + template + struct clock_string + { + static std::basic_string name() + { + static const CharT u[] = + { 'p', 'r', 'o', 'c', 'e', 's', 's', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT + u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'p', 'r', 'o', 'c', 'e', 's', 's', ' ', 's', 't', 'a', 'r', 't', '-', 'u', 'p' }; + const std::basic_string str(u, u + sizeof(u) / sizeof(u[0])); + return str; + } + }; +#endif + +} // namespace chrono +} // namespace boost + +namespace std { + + template + struct numeric_limits > + { + typedef boost::chrono::process_times Res; + + public: + static const bool is_specialized = true; + static Res min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return Res((std::numeric_limits::min)(), + (std::numeric_limits::min)(), + (std::numeric_limits::min)()); + } + static Res max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return Res((std::numeric_limits::max)(), + (std::numeric_limits::max)(), + (std::numeric_limits::max)()); + } + static Res lowest() throw() + { + return (min)(); + } + static const int digits = std::numeric_limits::digits+ + std::numeric_limits::digits+ + std::numeric_limits::digits; + static const int digits10 = std::numeric_limits::digits10+ + std::numeric_limits::digits10+ + std::numeric_limits::digits10; + static const bool is_signed = Rep::is_signed; + static const bool is_integer = Rep::is_integer; + static const bool is_exact = Rep::is_exact; + static const int radix = 0; + //~ static Res epsilon() throw() { return 0; } + //~ static Res round_error() throw() { return 0; } + //~ static const int min_exponent = 0; + //~ static const int min_exponent10 = 0; + //~ static const int max_exponent = 0; + //~ static const int max_exponent10 = 0; + //~ static const bool has_infinity = false; + //~ static const bool has_quiet_NaN = false; + //~ static const bool has_signaling_NaN = false; + //~ static const float_denorm_style has_denorm = denorm_absent; + //~ static const bool has_denorm_loss = false; + //~ static Res infinity() throw() { return 0; } + //~ static Res quiet_NaN() throw() { return 0; } + //~ static Res signaling_NaN() throw() { return 0; } + //~ static Res denorm_min() throw() { return 0; } + //~ static const bool is_iec559 = false; + //~ static const bool is_bounded = true; + //~ static const bool is_modulo = false; + //~ static const bool traps = false; + //~ static const bool tinyness_before = false; + //~ static const float_round_style round_style = round_toward_zero; + + }; +} + +#ifndef BOOST_CHRONO_HEADER_ONLY +#include // pops abi_prefix.hpp pragmas +#else +#include +#endif +#endif + +#endif // BOOST_CHRONO_PROCESS_CPU_CLOCKS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/system_clocks.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/system_clocks.hpp new file mode 100644 index 0000000..5ba6a3b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/system_clocks.hpp @@ -0,0 +1,233 @@ +// boost/chrono/system_clocks.hpp --------------------------------------------------------------// + +// Copyright 2008 Howard Hinnant +// Copyright 2008 Beman Dawes +// Copyright 2009-2011 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +/* + +This code was derived by Beman Dawes from Howard Hinnant's time2_demo prototype. +Many thanks to Howard for making his code available under the Boost license. +The original code was modified to conform to Boost conventions and to section +20.9 Time utilities [time] of the C++ committee's working paper N2798. +See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf. + +time2_demo contained this comment: + + Much thanks to Andrei Alexandrescu, + Walter Brown, + Peter Dimov, + Jeff Garland, + Terry Golubiewski, + Daniel Krugler, + Anthony Williams. +*/ + +/* + +TODO: + + * Fully implement error handling, with test cases. + * Consider issues raised by Michael Marcin: + + > In the past I've seen QueryPerformanceCounter give incorrect results, + > especially with SpeedStep processors on laptops. This was many years ago and + > might have been fixed by service packs and drivers. + > + > Typically you check the results of QPC against GetTickCount to see if the + > results are reasonable. + > http://support.microsoft.com/kb/274323 + > + > I've also heard of problems with QueryPerformanceCounter in multi-processor + > systems. + > + > I know some people SetThreadAffinityMask to 1 for the current thread call + > their QueryPerformance* functions then restore SetThreadAffinityMask. This + > seems horrible to me because it forces your program to jump to another + > physical processor if it isn't already on cpu0 but they claim it worked well + > in practice because they called the timing functions infrequently. + > + > In the past I have chosen to use timeGetTime with timeBeginPeriod(1) for + > high resolution timers to avoid these issues. + +*/ + +#ifndef BOOST_CHRONO_SYSTEM_CLOCKS_HPP +#define BOOST_CHRONO_SYSTEM_CLOCKS_HPP + +#include +#include +#include +#include +#include + +#include + +# if defined( BOOST_CHRONO_POSIX_API ) +# if ! defined(CLOCK_REALTIME) && ! defined (__hpux__) +# error does not supply CLOCK_REALTIME +# endif +# endif + +#ifdef BOOST_CHRONO_WINDOWS_API +// The system_clock tick is 100 nanoseconds +# define BOOST_SYSTEM_CLOCK_DURATION boost::chrono::duration > +#else +# define BOOST_SYSTEM_CLOCK_DURATION boost::chrono::nanoseconds +#endif + +// this must occur after all of the includes and before any code appears: +#ifndef BOOST_CHRONO_HEADER_ONLY +#include // must be the last #include +#endif + + +//----------------------------------------------------------------------------// +// // +// 20.9 Time utilities [time] // +// synopsis // +// // +//----------------------------------------------------------------------------// + +namespace boost { +namespace chrono { + + // Clocks + class BOOST_CHRONO_DECL system_clock; +#ifdef BOOST_CHRONO_HAS_CLOCK_STEADY + class BOOST_CHRONO_DECL steady_clock; +#endif + +#ifdef BOOST_CHRONO_HAS_CLOCK_STEADY + typedef steady_clock high_resolution_clock; // as permitted by [time.clock.hires] +#else + typedef system_clock high_resolution_clock; // as permitted by [time.clock.hires] +#endif + +//----------------------------------------------------------------------------// +// // +// 20.9.5 Clocks [time.clock] // +// // +//----------------------------------------------------------------------------// + +// If you're porting, clocks are the system-specific (non-portable) part. +// You'll need to know how to get the current time and implement that under now(). +// You'll need to know what units (tick period) and representation makes the most +// sense for your clock and set those accordingly. +// If you know how to map this clock to time_t (perhaps your clock is std::time, which +// makes that trivial), then you can fill out system_clock's to_time_t() and from_time_t(). + +//----------------------------------------------------------------------------// +// 20.9.5.1 Class system_clock [time.clock.system] // +//----------------------------------------------------------------------------// + + class BOOST_CHRONO_DECL system_clock + { + public: + typedef BOOST_SYSTEM_CLOCK_DURATION duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = false; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec); +#endif + + static BOOST_CHRONO_INLINE std::time_t to_time_t(const time_point& t) BOOST_NOEXCEPT; + static BOOST_CHRONO_INLINE time_point from_time_t(std::time_t t) BOOST_NOEXCEPT; + }; + +//----------------------------------------------------------------------------// +// 20.9.5.2 Class steady_clock [time.clock.steady] // +//----------------------------------------------------------------------------// + +// As permitted by [time.clock.steady] +// The class steady_clock is conditionally supported. + +#ifdef BOOST_CHRONO_HAS_CLOCK_STEADY + class BOOST_CHRONO_DECL steady_clock + { + public: + typedef nanoseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = true; + + static BOOST_CHRONO_INLINE time_point now() BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now(system::error_code & ec); +#endif + }; +#endif +//----------------------------------------------------------------------------// +// 20.9.5.3 Class high_resolution_clock [time.clock.hires] // +//----------------------------------------------------------------------------// + +// As permitted, steady_clock or system_clock is a typedef for high_resolution_clock. +// See synopsis. + + + template + struct clock_string + { + static std::basic_string name() + { + static const CharT u[] = + { 's', 'y', 's', 't', 'e', 'm', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + static const CharT + u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'J', 'a', 'n', ' ', '1', ',', ' ', '1', '9', '7', '0' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + }; + +#ifdef BOOST_CHRONO_HAS_CLOCK_STEADY + + template + struct clock_string + { + static std::basic_string name() + { + static const CharT + u[] = + { 's', 't', 'e', 'a', 'd', 'y', '_', 'c', 'l', 'o', 'c', 'k' }; + static const std::basic_string str(u, u + sizeof(u) + / sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 'b', 'o', 'o', 't' }; + const std::basic_string str(u, u + sizeof(u) / sizeof(u[0])); + return str; + } + }; + +#endif + +} // namespace chrono +} // namespace boost + +#ifndef BOOST_CHRONO_HEADER_ONLY +// the suffix header occurs after all of our code: +#include // pops abi_prefix.hpp pragmas +#else +#include +#endif + +#endif // BOOST_CHRONO_SYSTEM_CLOCKS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/thread_clock.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/thread_clock.hpp new file mode 100644 index 0000000..207697b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/thread_clock.hpp @@ -0,0 +1,75 @@ +// boost/chrono/thread_clock.hpp -----------------------------------------------------------// + +// Copyright 2009-2011 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +// See http://www.boost.org/libs/system for documentation. + +#include + +#ifndef BOOST_CHRONO_THREAD_CLOCK_HPP +#define BOOST_CHRONO_THREAD_CLOCK_HPP + +#if defined(BOOST_CHRONO_HAS_THREAD_CLOCK) + +#include +#include +#include +#include +#include + +#ifndef BOOST_CHRONO_HEADER_ONLY +#include // must be the last #include +#endif + +namespace boost { namespace chrono { + +class BOOST_CHRONO_DECL thread_clock { +public: + typedef nanoseconds duration; + typedef duration::rep rep; + typedef duration::period period; + typedef chrono::time_point time_point; + BOOST_STATIC_CONSTEXPR bool is_steady = BOOST_CHRONO_THREAD_CLOCK_IS_STEADY; + + static BOOST_CHRONO_INLINE time_point now( ) BOOST_NOEXCEPT; +#if !defined BOOST_CHRONO_DONT_PROVIDE_HYBRID_ERROR_HANDLING + static BOOST_CHRONO_INLINE time_point now( system::error_code & ec ); +#endif +}; + +template +struct clock_string +{ + static std::basic_string name() + { + static const CharT u[] = + { 't', 'h', 'r', 'e', 'a', 'd', '_', + 'c', 'l','o', 'c', 'k'}; + static const std::basic_string str(u, u + sizeof(u)/sizeof(u[0])); + return str; + } + static std::basic_string since() + { + const CharT u[] = + { ' ', 's', 'i', 'n', 'c', 'e', ' ', 't', 'h', 'r', 'e', 'a', 'd', ' ', 's', 't', 'a', 'r', 't', '-', 'u', 'p'}; + const std::basic_string str(u, u + sizeof(u)/sizeof(u[0])); + return str; + } +}; + +} // namespace chrono +} // namespace boost + + +#ifndef BOOST_CHRONO_HEADER_ONLY +#include // pops abi_prefix.hpp pragmas +#else +#include +#endif + +#endif + +#endif // BOOST_CHRONO_THREAD_CLOCK_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/chrono/time_point.hpp b/thirdparty/source/boost_1_61_0/boost/chrono/time_point.hpp new file mode 100644 index 0000000..6449fac --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/chrono/time_point.hpp @@ -0,0 +1,380 @@ +// duration.hpp --------------------------------------------------------------// + +// Copyright 2008 Howard Hinnant +// Copyright 2008 Beman Dawes +// Copyright 2009-2012 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +/* + +This code was derived by Beman Dawes from Howard Hinnant's time2_demo prototype. +Many thanks to Howard for making his code available under the Boost license. +The original code was modified to conform to Boost conventions and to section +20.9 Time utilities [time] of the C++ committee's working paper N2798. +See http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2798.pdf. + +time2_demo contained this comment: + + Much thanks to Andrei Alexandrescu, + Walter Brown, + Peter Dimov, + Jeff Garland, + Terry Golubiewski, + Daniel Krugler, + Anthony Williams. +*/ + + +#ifndef BOOST_CHRONO_TIME_POINT_HPP +#define BOOST_CHRONO_TIME_POINT_HPP + +#include +#include + +#ifndef BOOST_CHRONO_HEADER_ONLY +// this must occur after all of the includes and before any code appears: +#include // must be the last #include +#endif + +//----------------------------------------------------------------------------// +// // +// 20.9 Time utilities [time] // +// synopsis // +// // +//----------------------------------------------------------------------------// + +namespace boost { +namespace chrono { + + template + class time_point; + + +} // namespace chrono + + +// common_type trait specializations + +template + struct common_type, + chrono::time_point >; + + +//----------------------------------------------------------------------------// +// 20.9.2.3 Specializations of common_type [time.traits.specializations] // +//----------------------------------------------------------------------------// + + +template +struct common_type, + chrono::time_point > +{ + typedef chrono::time_point::type> type; +}; + + + +namespace chrono { + + // time_point arithmetic + template + inline BOOST_CONSTEXPR + time_point >::type> + operator+( + const time_point& lhs, + const duration& rhs); + template + inline BOOST_CONSTEXPR + time_point, Duration2>::type> + operator+( + const duration& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + time_point >::type> + operator-( + const time_point& lhs, + const duration& rhs); + template + inline BOOST_CONSTEXPR + typename common_type::type + operator-( + const time_point& lhs, + const time_point& rhs); + + // time_point comparisons + template + inline BOOST_CONSTEXPR + bool operator==( + const time_point& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + bool operator!=( + const time_point& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + bool operator< ( + const time_point& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + bool operator<=( + const time_point& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + bool operator> ( + const time_point& lhs, + const time_point& rhs); + template + inline BOOST_CONSTEXPR + bool operator>=( + const time_point& lhs, + const time_point& rhs); + + // time_point_cast + template + inline BOOST_CONSTEXPR + time_point time_point_cast(const time_point& t); + +//----------------------------------------------------------------------------// +// // +// 20.9.4 Class template time_point [time.point] // +// // +//----------------------------------------------------------------------------// + + template + class time_point + { + BOOST_CHRONO_STATIC_ASSERT(boost::chrono::detail::is_duration::value, + BOOST_CHRONO_SECOND_TEMPLATE_PARAMETER_OF_TIME_POINT_MUST_BE_A_BOOST_CHRONO_DURATION, (Duration)); + public: + typedef Clock clock; + typedef Duration duration; + typedef typename duration::rep rep; + typedef typename duration::period period; + typedef Duration difference_type; + + private: + duration d_; + + public: + BOOST_FORCEINLINE BOOST_CONSTEXPR + time_point() : d_(duration::zero()) + {} + BOOST_FORCEINLINE BOOST_CONSTEXPR + explicit time_point(const duration& d) + : d_(d) + {} + + // conversions + template + BOOST_FORCEINLINE BOOST_CONSTEXPR + time_point(const time_point& t + , typename boost::enable_if + < + boost::is_convertible + >::type* = 0 + ) + : d_(t.time_since_epoch()) + { + } + // observer + + BOOST_CONSTEXPR + duration time_since_epoch() const + { + return d_; + } + + // arithmetic + +#ifdef BOOST_CHRONO_EXTENSIONS + BOOST_CONSTEXPR + time_point operator+() const {return *this;} + BOOST_CONSTEXPR + time_point operator-() const {return time_point(-d_);} + time_point& operator++() {++d_; return *this;} + time_point operator++(int) {return time_point(d_++);} + time_point& operator--() {--d_; return *this;} + time_point operator--(int) {return time_point(d_--);} + + time_point& operator+=(const rep& r) {d_ += duration(r); return *this;} + time_point& operator-=(const rep& r) {d_ -= duration(r); return *this;} + +#endif + + time_point& operator+=(const duration& d) {d_ += d; return *this;} + time_point& operator-=(const duration& d) {d_ -= d; return *this;} + + // special values + + static BOOST_CHRONO_LIB_CONSTEXPR time_point + min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return time_point((duration::min)()); + } + static BOOST_CHRONO_LIB_CONSTEXPR time_point + max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return time_point((duration::max)()); + } + }; + +//----------------------------------------------------------------------------// +// 20.9.4.5 time_point non-member arithmetic [time.point.nonmember] // +//----------------------------------------------------------------------------// + + // time_point operator+(time_point x, duration y); + + template + inline BOOST_CONSTEXPR + time_point >::type> + operator+(const time_point& lhs, + const duration& rhs) + { + typedef typename common_type >::type CDuration; + typedef time_point< + Clock, + CDuration + > TimeResult; + return TimeResult(lhs.time_since_epoch() + CDuration(rhs)); + } + + // time_point operator+(duration x, time_point y); + + template + inline BOOST_CONSTEXPR + time_point, Duration2>::type> + operator+(const duration& lhs, + const time_point& rhs) + { + return rhs + lhs; + } + + // time_point operator-(time_point x, duration y); + + template + inline BOOST_CONSTEXPR + time_point >::type> + operator-(const time_point& lhs, + const duration& rhs) + { + return lhs + (-rhs); + } + + // duration operator-(time_point x, time_point y); + + template + inline BOOST_CONSTEXPR + typename common_type::type + operator-(const time_point& lhs, + const time_point& rhs) + { + return lhs.time_since_epoch() - rhs.time_since_epoch(); + } + +//----------------------------------------------------------------------------// +// 20.9.4.6 time_point comparisons [time.point.comparisons] // +//----------------------------------------------------------------------------// + + // time_point == + + template + inline BOOST_CONSTEXPR + bool + operator==(const time_point& lhs, + const time_point& rhs) + { + return lhs.time_since_epoch() == rhs.time_since_epoch(); + } + + // time_point != + + template + inline BOOST_CONSTEXPR + bool + operator!=(const time_point& lhs, + const time_point& rhs) + { + return !(lhs == rhs); + } + + // time_point < + + template + inline BOOST_CONSTEXPR + bool + operator<(const time_point& lhs, + const time_point& rhs) + { + return lhs.time_since_epoch() < rhs.time_since_epoch(); + } + + // time_point > + + template + inline BOOST_CONSTEXPR + bool + operator>(const time_point& lhs, + const time_point& rhs) + { + return rhs < lhs; + } + + // time_point <= + + template + inline BOOST_CONSTEXPR + bool + operator<=(const time_point& lhs, + const time_point& rhs) + { + return !(rhs < lhs); + } + + // time_point >= + + template + inline BOOST_CONSTEXPR + bool + operator>=(const time_point& lhs, + const time_point& rhs) + { + return !(lhs < rhs); + } + +//----------------------------------------------------------------------------// +// 20.9.4.7 time_point_cast [time.point.cast] // +//----------------------------------------------------------------------------// + + template + inline BOOST_CONSTEXPR + time_point + time_point_cast(const time_point& t) + { + return time_point( + duration_cast(t.time_since_epoch())); + } + +} // namespace chrono +} // namespace boost + +#ifndef BOOST_CHRONO_HEADER_ONLY +// the suffix header occurs after all of our code: +#include // pops abi_prefix.hpp pragmas +#endif + +#endif // BOOST_CHRONO_TIME_POINT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/compressed_pair.hpp b/thirdparty/source/boost_1_61_0/boost/compressed_pair.hpp new file mode 100644 index 0000000..a7be0f2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/compressed_pair.hpp @@ -0,0 +1,20 @@ +// (C) Copyright Steve Cleary, Beman Dawes, Howard Hinnant & John Maddock 2000. +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). +// +// See http://www.boost.org/libs/utility for most recent version including documentation. + +// See boost/detail/compressed_pair.hpp +// for full copyright notices. + +#ifndef BOOST_COMPRESSED_PAIR_HPP +#define BOOST_COMPRESSED_PAIR_HPP + +#ifndef BOOST_CONFIG_HPP +#include +#endif + +#include + +#endif // BOOST_COMPRESSED_PAIR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/assert.hpp b/thirdparty/source/boost_1_61_0/boost/concept/assert.hpp new file mode 100644 index 0000000..cf98179 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/assert.hpp @@ -0,0 +1,45 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_ASSERT_DWA2006430_HPP +# define BOOST_CONCEPT_ASSERT_DWA2006430_HPP + +# include +# include + +// The old protocol used a constraints() member function in concept +// checking classes. If the compiler supports SFINAE, we can detect +// that function and seamlessly support the old concept checking +// classes. In this release, backward compatibility with the old +// concept checking classes is enabled by default, where available. +// The old protocol is deprecated, though, and backward compatibility +// will no longer be the default in the next release. + +# if !defined(BOOST_NO_OLD_CONCEPT_SUPPORT) \ + && !defined(BOOST_NO_SFINAE) \ + \ + && !(BOOST_WORKAROUND(__GNUC__, == 3) && BOOST_WORKAROUND(__GNUC_MINOR__, < 4)) + +// Note: gcc-2.96 through 3.3.x have some SFINAE, but no ability to +// check for the presence of particularmember functions. + +# define BOOST_OLD_CONCEPT_SUPPORT + +# endif + +# ifdef BOOST_MSVC +# include +# elif BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +# include +# else +# include +# endif + + // Usage, in class or function context: + // + // BOOST_CONCEPT_ASSERT((UnaryFunctionConcept)); + // +# define BOOST_CONCEPT_ASSERT(ModelInParens) \ + BOOST_CONCEPT_ASSERT_FN(void(*)ModelInParens) + +#endif // BOOST_CONCEPT_ASSERT_DWA2006430_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/backward_compatibility.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/backward_compatibility.hpp new file mode 100644 index 0000000..66d573e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/backward_compatibility.hpp @@ -0,0 +1,16 @@ +// Copyright David Abrahams 2009. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_BACKWARD_COMPATIBILITY_DWA200968_HPP +# define BOOST_CONCEPT_BACKWARD_COMPATIBILITY_DWA200968_HPP + +namespace boost +{ + namespace concepts {} + +# if defined(BOOST_HAS_CONCEPTS) && !defined(BOOST_CONCEPT_NO_BACKWARD_KEYWORD) + namespace concept = concepts; +# endif +} // namespace boost::concept + +#endif // BOOST_CONCEPT_BACKWARD_COMPATIBILITY_DWA200968_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/borland.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/borland.hpp new file mode 100644 index 0000000..300d5d4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/borland.hpp @@ -0,0 +1,30 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_DETAIL_BORLAND_DWA2006429_HPP +# define BOOST_CONCEPT_DETAIL_BORLAND_DWA2006429_HPP + +# include +# include + +namespace boost { namespace concepts { + +template +struct require; + +template +struct require +{ + enum { instantiate = sizeof((((Model*)0)->~Model()), 3) }; +}; + +# define BOOST_CONCEPT_ASSERT_FN( ModelFnPtr ) \ + enum \ + { \ + BOOST_PP_CAT(boost_concept_check,__LINE__) = \ + boost::concepts::require::instantiate \ + } + +}} // namespace boost::concept + +#endif // BOOST_CONCEPT_DETAIL_BORLAND_DWA2006429_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_def.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_def.hpp new file mode 100644 index 0000000..750561e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_def.hpp @@ -0,0 +1,34 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_DETAIL_CONCEPT_DEF_DWA200651_HPP +# define BOOST_CONCEPT_DETAIL_CONCEPT_DEF_DWA200651_HPP +# include +# include +# include +# include +#endif // BOOST_CONCEPT_DETAIL_CONCEPT_DEF_DWA200651_HPP + +// BOOST_concept(SomeName, (p1)(p2)...(pN)) +// +// Expands to "template struct SomeName" +// +// Also defines an equivalent SomeNameConcept for backward compatibility. +// Maybe in the next release we can kill off the "Concept" suffix for good. +# define BOOST_concept(name, params) \ + template < BOOST_PP_SEQ_FOR_EACH_I(BOOST_CONCEPT_typename,~,params) > \ + struct name; /* forward declaration */ \ + \ + template < BOOST_PP_SEQ_FOR_EACH_I(BOOST_CONCEPT_typename,~,params) > \ + struct BOOST_PP_CAT(name,Concept) \ + : name< BOOST_PP_SEQ_ENUM(params) > \ + { \ + }; \ + \ + template < BOOST_PP_SEQ_FOR_EACH_I(BOOST_CONCEPT_typename,~,params) > \ + struct name + +// Helper for BOOST_concept, above. +# define BOOST_CONCEPT_typename(r, ignored, index, t) \ + BOOST_PP_COMMA_IF(index) typename t + diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_undef.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_undef.hpp new file mode 100644 index 0000000..713db89 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/concept_undef.hpp @@ -0,0 +1,5 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +# undef BOOST_concept_typename +# undef BOOST_concept diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/general.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/general.hpp new file mode 100644 index 0000000..525ea65 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/general.hpp @@ -0,0 +1,77 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_DETAIL_GENERAL_DWA2006429_HPP +# define BOOST_CONCEPT_DETAIL_GENERAL_DWA2006429_HPP + +# include +# include +# include + +# ifdef BOOST_OLD_CONCEPT_SUPPORT +# include +# include +# endif + +// This implementation works on Comeau and GCC, all the way back to +// 2.95 +namespace boost { namespace concepts { + +template +struct requirement_; + +namespace detail +{ + template struct instantiate {}; +} + +template +struct requirement +{ + static void failed() { ((Model*)0)->~Model(); } +}; + +struct failed {}; + +template +struct requirement +{ + static void failed() { ((Model*)0)->~Model(); } +}; + +# ifdef BOOST_OLD_CONCEPT_SUPPORT + +template +struct constraint +{ + static void failed() { ((Model*)0)->constraints(); } +}; + +template +struct requirement_ + : mpl::if_< + concepts::not_satisfied + , constraint + , requirement + >::type +{}; + +# else + +// For GCC-2.x, these can't have exactly the same name +template +struct requirement_ + : requirement +{}; + +# endif + +# define BOOST_CONCEPT_ASSERT_FN( ModelFnPtr ) \ + typedef ::boost::concepts::detail::instantiate< \ + &::boost::concepts::requirement_::failed> \ + BOOST_PP_CAT(boost_concept_check,__LINE__) \ + BOOST_ATTRIBUTE_UNUSED + +}} + +#endif // BOOST_CONCEPT_DETAIL_GENERAL_DWA2006429_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/has_constraints.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/has_constraints.hpp new file mode 100644 index 0000000..a309db3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/has_constraints.hpp @@ -0,0 +1,50 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_DETAIL_HAS_CONSTRAINTS_DWA2006429_HPP +# define BOOST_CONCEPT_DETAIL_HAS_CONSTRAINTS_DWA2006429_HPP + +# include +# include +# include + +namespace boost { namespace concepts { + +namespace detail +{ + +// Here we implement the metafunction that detects whether a +// constraints metafunction exists + typedef char yes; + typedef char (&no)[2]; + + template + struct wrap_constraints {}; + +#if BOOST_WORKAROUND(__SUNPRO_CC, <= 0x580) || defined(__CUDACC__) + // Work around the following bogus error in Sun Studio 11, by + // turning off the has_constraints function entirely: + // Error: complex expression not allowed in dependent template + // argument expression + inline no has_constraints_(...); +#else + template + inline yes has_constraints_(Model*, wrap_constraints* = 0); + inline no has_constraints_(...); +#endif +} + +// This would be called "detail::has_constraints," but it has a strong +// tendency to show up in error messages. +template +struct not_satisfied +{ + BOOST_STATIC_CONSTANT( + bool + , value = sizeof( detail::has_constraints_((Model*)0) ) == sizeof(detail::yes) ); + typedef mpl::bool_ type; +}; + +}} // namespace boost::concepts::detail + +#endif // BOOST_CONCEPT_DETAIL_HAS_CONSTRAINTS_DWA2006429_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/detail/msvc.hpp b/thirdparty/source/boost_1_61_0/boost/concept/detail/msvc.hpp new file mode 100644 index 0000000..078dd22 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/detail/msvc.hpp @@ -0,0 +1,123 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_CHECK_MSVC_DWA2006429_HPP +# define BOOST_CONCEPT_CHECK_MSVC_DWA2006429_HPP + +# include +# include +# include + +# ifdef BOOST_OLD_CONCEPT_SUPPORT +# include +# include +# endif + +# ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable:4100) +# endif + +namespace boost { namespace concepts { + + +template +struct check +{ + virtual void failed(Model* x) + { + x->~Model(); + } +}; + +# ifndef BOOST_NO_PARTIAL_SPECIALIZATION +struct failed {}; +template +struct check +{ + virtual void failed(Model* x) + { + x->~Model(); + } +}; +# endif + +# ifdef BOOST_OLD_CONCEPT_SUPPORT + +namespace detail +{ + // No need for a virtual function here, since evaluating + // not_satisfied below will have already instantiated the + // constraints() member. + struct constraint {}; +} + +template +struct require + : mpl::if_c< + not_satisfied::value + , detail::constraint +# ifndef BOOST_NO_PARTIAL_SPECIALIZATION + , check +# else + , check +# endif + >::type +{}; + +# else + +template +struct require +# ifndef BOOST_NO_PARTIAL_SPECIALIZATION + : check +# else + : check +# endif +{}; + +# endif + +# if BOOST_WORKAROUND(BOOST_MSVC, == 1310) + +// +// The iterator library sees some really strange errors unless we +// do things this way. +// +template +struct require +{ + virtual void failed(Model*) + { + require(); + } +}; + +# define BOOST_CONCEPT_ASSERT_FN( ModelFnPtr ) \ +enum \ +{ \ + BOOST_PP_CAT(boost_concept_check,__LINE__) = \ + sizeof(::boost::concepts::require) \ +} + +# else // Not vc-7.1 + +template +require +require_(void(*)(Model)); + +# define BOOST_CONCEPT_ASSERT_FN( ModelFnPtr ) \ +enum \ +{ \ + BOOST_PP_CAT(boost_concept_check,__LINE__) = \ + sizeof(::boost::concepts::require_((ModelFnPtr)0)) \ +} + +# endif +}} + +# ifdef BOOST_MSVC +# pragma warning(pop) +# endif + +#endif // BOOST_CONCEPT_CHECK_MSVC_DWA2006429_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept/usage.hpp b/thirdparty/source/boost_1_61_0/boost/concept/usage.hpp new file mode 100644 index 0000000..e73370f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept/usage.hpp @@ -0,0 +1,36 @@ +// Copyright David Abrahams 2006. Distributed under the Boost +// Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +#ifndef BOOST_CONCEPT_USAGE_DWA2006919_HPP +# define BOOST_CONCEPT_USAGE_DWA2006919_HPP + +# include +# include +# include + +namespace boost { namespace concepts { + +template +struct usage_requirements +{ + ~usage_requirements() { ((Model*)0)->~Model(); } +}; + +# if BOOST_WORKAROUND(__GNUC__, <= 3) + +# define BOOST_CONCEPT_USAGE(model) \ + model(); /* at least 2.96 and 3.4.3 both need this :( */ \ + BOOST_CONCEPT_ASSERT((boost::concepts::usage_requirements)); \ + ~model() + +# else + +# define BOOST_CONCEPT_USAGE(model) \ + BOOST_CONCEPT_ASSERT((boost::concepts::usage_requirements)); \ + ~model() + +# endif + +}} // namespace boost::concepts + +#endif // BOOST_CONCEPT_USAGE_DWA2006919_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/concept_check.hpp b/thirdparty/source/boost_1_61_0/boost/concept_check.hpp new file mode 100644 index 0000000..25f118b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/concept_check.hpp @@ -0,0 +1,1082 @@ +// +// (C) Copyright Jeremy Siek 2000. +// Copyright 2002 The Trustees of Indiana University. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Revision History: +// 05 May 2001: Workarounds for HP aCC from Thomas Matelich. (Jeremy Siek) +// 02 April 2001: Removed limits header altogether. (Jeremy Siek) +// 01 April 2001: Modified to use new header. (JMaddock) +// + +// See http://www.boost.org/libs/concept_check for documentation. + +#ifndef BOOST_CONCEPT_CHECKS_HPP +# define BOOST_CONCEPT_CHECKS_HPP + +# include + +# include +# include +# include +# include +# include +# include +# include +# include + +# include +# include + +#if (defined _MSC_VER) +# pragma warning( push ) +# pragma warning( disable : 4510 ) // default constructor could not be generated +# pragma warning( disable : 4610 ) // object 'class' can never be instantiated - user-defined constructor required +#endif + +namespace boost +{ + + // + // Backward compatibility + // + + template + inline void function_requires(Model* = 0) + { + BOOST_CONCEPT_ASSERT((Model)); + } + template inline void ignore_unused_variable_warning(T const&) {} + +# define BOOST_CLASS_REQUIRE(type_var, ns, concept) \ + BOOST_CONCEPT_ASSERT((ns::concept)) + +# define BOOST_CLASS_REQUIRE2(type_var1, type_var2, ns, concept) \ + BOOST_CONCEPT_ASSERT((ns::concept)) + +# define BOOST_CLASS_REQUIRE3(tv1, tv2, tv3, ns, concept) \ + BOOST_CONCEPT_ASSERT((ns::concept)) + +# define BOOST_CLASS_REQUIRE4(tv1, tv2, tv3, tv4, ns, concept) \ + BOOST_CONCEPT_ASSERT((ns::concept)) + + + // + // Begin concept definitions + // + BOOST_concept(Integer, (T)) + { + BOOST_CONCEPT_USAGE(Integer) + { + x.error_type_must_be_an_integer_type(); + } + private: + T x; + }; + + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; + template <> struct Integer {}; +# if defined(BOOST_HAS_LONG_LONG) + template <> struct Integer< ::boost::long_long_type> {}; + template <> struct Integer< ::boost::ulong_long_type> {}; +# elif defined(BOOST_HAS_MS_INT64) + template <> struct Integer<__int64> {}; + template <> struct Integer {}; +# endif + + BOOST_concept(SignedInteger,(T)) { + BOOST_CONCEPT_USAGE(SignedInteger) { + x.error_type_must_be_a_signed_integer_type(); + } + private: + T x; + }; + template <> struct SignedInteger { }; + template <> struct SignedInteger {}; + template <> struct SignedInteger {}; + template <> struct SignedInteger {}; +# if defined(BOOST_HAS_LONG_LONG) + template <> struct SignedInteger< ::boost::long_long_type> {}; +# elif defined(BOOST_HAS_MS_INT64) + template <> struct SignedInteger<__int64> {}; +# endif + + BOOST_concept(UnsignedInteger,(T)) { + BOOST_CONCEPT_USAGE(UnsignedInteger) { + x.error_type_must_be_an_unsigned_integer_type(); + } + private: + T x; + }; + + template <> struct UnsignedInteger {}; + template <> struct UnsignedInteger {}; + template <> struct UnsignedInteger {}; + template <> struct UnsignedInteger {}; +# if defined(BOOST_HAS_LONG_LONG) + template <> struct UnsignedInteger< ::boost::ulong_long_type> {}; +# elif defined(BOOST_HAS_MS_INT64) + template <> struct UnsignedInteger {}; +# endif + + //=========================================================================== + // Basic Concepts + + BOOST_concept(DefaultConstructible,(TT)) + { + BOOST_CONCEPT_USAGE(DefaultConstructible) { + TT a; // require default constructor + ignore_unused_variable_warning(a); + } + }; + + BOOST_concept(Assignable,(TT)) + { + BOOST_CONCEPT_USAGE(Assignable) { +#if !defined(_ITERATOR_) // back_insert_iterator broken for VC++ STL + a = b; // require assignment operator +#endif + const_constraints(b); + } + private: + void const_constraints(const TT& x) { +#if !defined(_ITERATOR_) // back_insert_iterator broken for VC++ STL + a = x; // const required for argument to assignment +#else + ignore_unused_variable_warning(x); +#endif + } + private: + TT a; + TT b; + }; + + + BOOST_concept(CopyConstructible,(TT)) + { + BOOST_CONCEPT_USAGE(CopyConstructible) { + TT a(b); // require copy constructor + TT* ptr = &a; // require address of operator + const_constraints(a); + ignore_unused_variable_warning(ptr); + } + private: + void const_constraints(const TT& a) { + TT c(a); // require const copy constructor + const TT* ptr = &a; // require const address of operator + ignore_unused_variable_warning(c); + ignore_unused_variable_warning(ptr); + } + TT b; + }; + + // The SGI STL version of Assignable requires copy constructor and operator= + BOOST_concept(SGIAssignable,(TT)) + { + BOOST_CONCEPT_USAGE(SGIAssignable) { + TT c(a); +#if !defined(_ITERATOR_) // back_insert_iterator broken for VC++ STL + a = b; // require assignment operator +#endif + const_constraints(b); + ignore_unused_variable_warning(c); + } + private: + void const_constraints(const TT& x) { + TT c(x); +#if !defined(_ITERATOR_) // back_insert_iterator broken for VC++ STL + a = x; // const required for argument to assignment +#endif + ignore_unused_variable_warning(c); + } + TT a; + TT b; + }; + + BOOST_concept(Convertible,(X)(Y)) + { + BOOST_CONCEPT_USAGE(Convertible) { + Y y = x; + ignore_unused_variable_warning(y); + } + private: + X x; + }; + + // The C++ standard requirements for many concepts talk about return + // types that must be "convertible to bool". The problem with this + // requirement is that it leaves the door open for evil proxies that + // define things like operator|| with strange return types. Two + // possible solutions are: + // 1) require the return type to be exactly bool + // 2) stay with convertible to bool, and also + // specify stuff about all the logical operators. + // For now we just test for convertible to bool. + template + void require_boolean_expr(const TT& t) { + bool x = t; + ignore_unused_variable_warning(x); + } + + BOOST_concept(EqualityComparable,(TT)) + { + BOOST_CONCEPT_USAGE(EqualityComparable) { + require_boolean_expr(a == b); + require_boolean_expr(a != b); + } + private: + TT a, b; + }; + + BOOST_concept(LessThanComparable,(TT)) + { + BOOST_CONCEPT_USAGE(LessThanComparable) { + require_boolean_expr(a < b); + } + private: + TT a, b; + }; + + // This is equivalent to SGI STL's LessThanComparable. + BOOST_concept(Comparable,(TT)) + { + BOOST_CONCEPT_USAGE(Comparable) { + require_boolean_expr(a < b); + require_boolean_expr(a > b); + require_boolean_expr(a <= b); + require_boolean_expr(a >= b); + } + private: + TT a, b; + }; + +#define BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(OP,NAME) \ + BOOST_concept(NAME, (First)(Second)) \ + { \ + BOOST_CONCEPT_USAGE(NAME) { (void)constraints_(); } \ + private: \ + bool constraints_() { return a OP b; } \ + First a; \ + Second b; \ + } + +#define BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(OP,NAME) \ + BOOST_concept(NAME, (Ret)(First)(Second)) \ + { \ + BOOST_CONCEPT_USAGE(NAME) { (void)constraints_(); } \ + private: \ + Ret constraints_() { return a OP b; } \ + First a; \ + Second b; \ + } + + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(==, EqualOp); + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(!=, NotEqualOp); + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(<, LessThanOp); + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(<=, LessEqualOp); + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(>, GreaterThanOp); + BOOST_DEFINE_BINARY_PREDICATE_OP_CONSTRAINT(>=, GreaterEqualOp); + + BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(+, PlusOp); + BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(*, TimesOp); + BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(/, DivideOp); + BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(-, SubtractOp); + BOOST_DEFINE_BINARY_OPERATOR_CONSTRAINT(%, ModOp); + + //=========================================================================== + // Function Object Concepts + + BOOST_concept(Generator,(Func)(Return)) + { + BOOST_CONCEPT_USAGE(Generator) { test(is_void()); } + + private: + void test(boost::mpl::false_) + { + // Do we really want a reference here? + const Return& r = f(); + ignore_unused_variable_warning(r); + } + + void test(boost::mpl::true_) + { + f(); + } + + Func f; + }; + + BOOST_concept(UnaryFunction,(Func)(Return)(Arg)) + { + BOOST_CONCEPT_USAGE(UnaryFunction) { test(is_void()); } + + private: + void test(boost::mpl::false_) + { + f(arg); // "priming the pump" this way keeps msvc6 happy (ICE) + Return r = f(arg); + ignore_unused_variable_warning(r); + } + + void test(boost::mpl::true_) + { + f(arg); + } + +#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \ + && BOOST_WORKAROUND(__GNUC__, > 3))) + // Declare a dummy construktor to make gcc happy. + // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type. + // (warning: non-static reference "const double& boost::UnaryFunction::arg" + // in class without a constructor [-Wuninitialized]) + UnaryFunction(); +#endif + + Func f; + Arg arg; + }; + + BOOST_concept(BinaryFunction,(Func)(Return)(First)(Second)) + { + BOOST_CONCEPT_USAGE(BinaryFunction) { test(is_void()); } + private: + void test(boost::mpl::false_) + { + f(first,second); + Return r = f(first, second); // require operator() + (void)r; + } + + void test(boost::mpl::true_) + { + f(first,second); + } + +#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \ + && BOOST_WORKAROUND(__GNUC__, > 3))) + // Declare a dummy constructor to make gcc happy. + // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type. + // (warning: non-static reference "const double& boost::BinaryFunction::arg" + // in class without a constructor [-Wuninitialized]) + BinaryFunction(); +#endif + + Func f; + First first; + Second second; + }; + + BOOST_concept(UnaryPredicate,(Func)(Arg)) + { + BOOST_CONCEPT_USAGE(UnaryPredicate) { + require_boolean_expr(f(arg)); // require operator() returning bool + } + private: +#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \ + && BOOST_WORKAROUND(__GNUC__, > 3))) + // Declare a dummy constructor to make gcc happy. + // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type. + // (warning: non-static reference "const double& boost::UnaryPredicate::arg" + // in class without a constructor [-Wuninitialized]) + UnaryPredicate(); +#endif + + Func f; + Arg arg; + }; + + BOOST_concept(BinaryPredicate,(Func)(First)(Second)) + { + BOOST_CONCEPT_USAGE(BinaryPredicate) { + require_boolean_expr(f(a, b)); // require operator() returning bool + } + private: +#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \ + && BOOST_WORKAROUND(__GNUC__, > 3))) + // Declare a dummy constructor to make gcc happy. + // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type. + // (warning: non-static reference "const double& boost::BinaryPredicate::arg" + // in class without a constructor [-Wuninitialized]) + BinaryPredicate(); +#endif + Func f; + First a; + Second b; + }; + + // use this when functor is used inside a container class like std::set + BOOST_concept(Const_BinaryPredicate,(Func)(First)(Second)) + : BinaryPredicate + { + BOOST_CONCEPT_USAGE(Const_BinaryPredicate) { + const_constraints(f); + } + private: + void const_constraints(const Func& fun) { + // operator() must be a const member function + require_boolean_expr(fun(a, b)); + } +#if (BOOST_WORKAROUND(__GNUC__, BOOST_TESTED_AT(4) \ + && BOOST_WORKAROUND(__GNUC__, > 3))) + // Declare a dummy constructor to make gcc happy. + // It seems the compiler can not generate a sensible constructor when this is instantiated with a refence type. + // (warning: non-static reference "const double& boost::Const_BinaryPredicate::arg" + // in class without a constructor [-Wuninitialized]) + Const_BinaryPredicate(); +#endif + + Func f; + First a; + Second b; + }; + + BOOST_concept(AdaptableGenerator,(Func)(Return)) + : Generator + { + typedef typename Func::result_type result_type; + + BOOST_CONCEPT_USAGE(AdaptableGenerator) + { + BOOST_CONCEPT_ASSERT((Convertible)); + } + }; + + BOOST_concept(AdaptableUnaryFunction,(Func)(Return)(Arg)) + : UnaryFunction + { + typedef typename Func::argument_type argument_type; + typedef typename Func::result_type result_type; + + ~AdaptableUnaryFunction() + { + BOOST_CONCEPT_ASSERT((Convertible)); + BOOST_CONCEPT_ASSERT((Convertible)); + } + }; + + BOOST_concept(AdaptableBinaryFunction,(Func)(Return)(First)(Second)) + : BinaryFunction< + Func + , typename Func::result_type + , typename Func::first_argument_type + , typename Func::second_argument_type + > + { + typedef typename Func::first_argument_type first_argument_type; + typedef typename Func::second_argument_type second_argument_type; + typedef typename Func::result_type result_type; + + ~AdaptableBinaryFunction() + { + BOOST_CONCEPT_ASSERT((Convertible)); + BOOST_CONCEPT_ASSERT((Convertible)); + BOOST_CONCEPT_ASSERT((Convertible)); + } + }; + + BOOST_concept(AdaptablePredicate,(Func)(Arg)) + : UnaryPredicate + , AdaptableUnaryFunction + { + }; + + BOOST_concept(AdaptableBinaryPredicate,(Func)(First)(Second)) + : BinaryPredicate + , AdaptableBinaryFunction + { + }; + + //=========================================================================== + // Iterator Concepts + + BOOST_concept(InputIterator,(TT)) + : Assignable + , EqualityComparable + { + typedef typename std::iterator_traits::value_type value_type; + typedef typename std::iterator_traits::difference_type difference_type; + typedef typename std::iterator_traits::reference reference; + typedef typename std::iterator_traits::pointer pointer; + typedef typename std::iterator_traits::iterator_category iterator_category; + + BOOST_CONCEPT_USAGE(InputIterator) + { + BOOST_CONCEPT_ASSERT((SignedInteger)); + BOOST_CONCEPT_ASSERT((Convertible)); + + TT j(i); + (void)*i; // require dereference operator + ++j; // require preincrement operator + i++; // require postincrement operator + } + private: + TT i; + }; + + BOOST_concept(OutputIterator,(TT)(ValueT)) + : Assignable + { + BOOST_CONCEPT_USAGE(OutputIterator) { + + ++i; // require preincrement operator + i++; // require postincrement operator + *i++ = t; // require postincrement and assignment + } + private: + TT i, j; + ValueT t; + }; + + BOOST_concept(ForwardIterator,(TT)) + : InputIterator + { + BOOST_CONCEPT_USAGE(ForwardIterator) + { + BOOST_CONCEPT_ASSERT((Convertible< + BOOST_DEDUCED_TYPENAME ForwardIterator::iterator_category + , std::forward_iterator_tag + >)); + + typename InputIterator::reference r = *i; + ignore_unused_variable_warning(r); + } + + private: + TT i; + }; + + BOOST_concept(Mutable_ForwardIterator,(TT)) + : ForwardIterator + { + BOOST_CONCEPT_USAGE(Mutable_ForwardIterator) { + *i++ = *j; // require postincrement and assignment + } + private: + TT i, j; + }; + + BOOST_concept(BidirectionalIterator,(TT)) + : ForwardIterator + { + BOOST_CONCEPT_USAGE(BidirectionalIterator) + { + BOOST_CONCEPT_ASSERT((Convertible< + BOOST_DEDUCED_TYPENAME BidirectionalIterator::iterator_category + , std::bidirectional_iterator_tag + >)); + + --i; // require predecrement operator + i--; // require postdecrement operator + } + private: + TT i; + }; + + BOOST_concept(Mutable_BidirectionalIterator,(TT)) + : BidirectionalIterator + , Mutable_ForwardIterator + { + BOOST_CONCEPT_USAGE(Mutable_BidirectionalIterator) + { + *i-- = *j; // require postdecrement and assignment + } + private: + TT i, j; + }; + + BOOST_concept(RandomAccessIterator,(TT)) + : BidirectionalIterator + , Comparable + { + BOOST_CONCEPT_USAGE(RandomAccessIterator) + { + BOOST_CONCEPT_ASSERT((Convertible< + BOOST_DEDUCED_TYPENAME BidirectionalIterator::iterator_category + , std::random_access_iterator_tag + >)); + + i += n; // require assignment addition operator + i = i + n; i = n + i; // require addition with difference type + i -= n; // require assignment subtraction operator + i = i - n; // require subtraction with difference type + n = i - j; // require difference operator + (void)i[n]; // require element access operator + } + + private: + TT a, b; + TT i, j; + typename std::iterator_traits::difference_type n; + }; + + BOOST_concept(Mutable_RandomAccessIterator,(TT)) + : RandomAccessIterator + , Mutable_BidirectionalIterator + { + BOOST_CONCEPT_USAGE(Mutable_RandomAccessIterator) + { + i[n] = *i; // require element access and assignment + } + private: + TT i; + typename std::iterator_traits::difference_type n; + }; + + //=========================================================================== + // Container s + + BOOST_concept(Container,(C)) + : Assignable + { + typedef typename C::value_type value_type; + typedef typename C::difference_type difference_type; + typedef typename C::size_type size_type; + typedef typename C::const_reference const_reference; + typedef typename C::const_pointer const_pointer; + typedef typename C::const_iterator const_iterator; + + BOOST_CONCEPT_USAGE(Container) + { + BOOST_CONCEPT_ASSERT((InputIterator)); + const_constraints(c); + } + + private: + void const_constraints(const C& cc) { + i = cc.begin(); + i = cc.end(); + n = cc.size(); + n = cc.max_size(); + b = cc.empty(); + } + C c; + bool b; + const_iterator i; + size_type n; + }; + + BOOST_concept(Mutable_Container,(C)) + : Container + { + typedef typename C::reference reference; + typedef typename C::iterator iterator; + typedef typename C::pointer pointer; + + BOOST_CONCEPT_USAGE(Mutable_Container) + { + BOOST_CONCEPT_ASSERT(( + Assignable)); + + BOOST_CONCEPT_ASSERT((InputIterator)); + + i = c.begin(); + i = c.end(); + c.swap(c2); + } + + private: + iterator i; + C c, c2; + }; + + BOOST_concept(ForwardContainer,(C)) + : Container + { + BOOST_CONCEPT_USAGE(ForwardContainer) + { + BOOST_CONCEPT_ASSERT(( + ForwardIterator< + typename ForwardContainer::const_iterator + >)); + } + }; + + BOOST_concept(Mutable_ForwardContainer,(C)) + : ForwardContainer + , Mutable_Container + { + BOOST_CONCEPT_USAGE(Mutable_ForwardContainer) + { + BOOST_CONCEPT_ASSERT(( + Mutable_ForwardIterator< + typename Mutable_ForwardContainer::iterator + >)); + } + }; + + BOOST_concept(ReversibleContainer,(C)) + : ForwardContainer + { + typedef typename + C::const_reverse_iterator + const_reverse_iterator; + + BOOST_CONCEPT_USAGE(ReversibleContainer) + { + BOOST_CONCEPT_ASSERT(( + BidirectionalIterator< + typename ReversibleContainer::const_iterator>)); + + BOOST_CONCEPT_ASSERT((BidirectionalIterator)); + + const_constraints(c); + } + private: + void const_constraints(const C& cc) + { + const_reverse_iterator i = cc.rbegin(); + i = cc.rend(); + } + C c; + }; + + BOOST_concept(Mutable_ReversibleContainer,(C)) + : Mutable_ForwardContainer + , ReversibleContainer + { + typedef typename C::reverse_iterator reverse_iterator; + + BOOST_CONCEPT_USAGE(Mutable_ReversibleContainer) + { + typedef typename Mutable_ForwardContainer::iterator iterator; + BOOST_CONCEPT_ASSERT((Mutable_BidirectionalIterator)); + BOOST_CONCEPT_ASSERT((Mutable_BidirectionalIterator)); + + reverse_iterator i = c.rbegin(); + i = c.rend(); + } + private: + C c; + }; + + BOOST_concept(RandomAccessContainer,(C)) + : ReversibleContainer + { + typedef typename C::size_type size_type; + typedef typename C::const_reference const_reference; + + BOOST_CONCEPT_USAGE(RandomAccessContainer) + { + BOOST_CONCEPT_ASSERT(( + RandomAccessIterator< + typename RandomAccessContainer::const_iterator + >)); + + const_constraints(c); + } + private: + void const_constraints(const C& cc) + { + const_reference r = cc[n]; + ignore_unused_variable_warning(r); + } + + C c; + size_type n; + }; + + BOOST_concept(Mutable_RandomAccessContainer,(C)) + : Mutable_ReversibleContainer + , RandomAccessContainer + { + private: + typedef Mutable_RandomAccessContainer self; + public: + BOOST_CONCEPT_USAGE(Mutable_RandomAccessContainer) + { + BOOST_CONCEPT_ASSERT((Mutable_RandomAccessIterator)); + BOOST_CONCEPT_ASSERT((Mutable_RandomAccessIterator)); + + typename self::reference r = c[i]; + ignore_unused_variable_warning(r); + } + + private: + typename Mutable_ReversibleContainer::size_type i; + C c; + }; + + // A Sequence is inherently mutable + BOOST_concept(Sequence,(S)) + : Mutable_ForwardContainer + // Matt Austern's book puts DefaultConstructible here, the C++ + // standard places it in Container --JGS + // ... so why aren't we following the standard? --DWA + , DefaultConstructible + { + BOOST_CONCEPT_USAGE(Sequence) + { + S + c(n, t), + c2(first, last); + + c.insert(p, t); + c.insert(p, n, t); + c.insert(p, first, last); + + c.erase(p); + c.erase(p, q); + + typename Sequence::reference r = c.front(); + + ignore_unused_variable_warning(c); + ignore_unused_variable_warning(c2); + ignore_unused_variable_warning(r); + const_constraints(c); + } + private: + void const_constraints(const S& c) { + typename Sequence::const_reference r = c.front(); + ignore_unused_variable_warning(r); + } + + typename S::value_type t; + typename S::size_type n; + typename S::value_type* first, *last; + typename S::iterator p, q; + }; + + BOOST_concept(FrontInsertionSequence,(S)) + : Sequence + { + BOOST_CONCEPT_USAGE(FrontInsertionSequence) + { + c.push_front(t); + c.pop_front(); + } + private: + S c; + typename S::value_type t; + }; + + BOOST_concept(BackInsertionSequence,(S)) + : Sequence + { + BOOST_CONCEPT_USAGE(BackInsertionSequence) + { + c.push_back(t); + c.pop_back(); + typename BackInsertionSequence::reference r = c.back(); + ignore_unused_variable_warning(r); + const_constraints(c); + } + private: + void const_constraints(const S& cc) { + typename BackInsertionSequence::const_reference + r = cc.back(); + ignore_unused_variable_warning(r); + } + S c; + typename S::value_type t; + }; + + BOOST_concept(AssociativeContainer,(C)) + : ForwardContainer + , DefaultConstructible + { + typedef typename C::key_type key_type; + typedef typename C::key_compare key_compare; + typedef typename C::value_compare value_compare; + typedef typename C::iterator iterator; + + BOOST_CONCEPT_USAGE(AssociativeContainer) + { + i = c.find(k); + r = c.equal_range(k); + c.erase(k); + c.erase(i); + c.erase(r.first, r.second); + const_constraints(c); + BOOST_CONCEPT_ASSERT((BinaryPredicate)); + + typedef typename AssociativeContainer::value_type value_type_; + BOOST_CONCEPT_ASSERT((BinaryPredicate)); + } + + // Redundant with the base concept, but it helps below. + typedef typename C::const_iterator const_iterator; + private: + void const_constraints(const C& cc) + { + ci = cc.find(k); + n = cc.count(k); + cr = cc.equal_range(k); + } + + C c; + iterator i; + std::pair r; + const_iterator ci; + std::pair cr; + typename C::key_type k; + typename C::size_type n; + }; + + BOOST_concept(UniqueAssociativeContainer,(C)) + : AssociativeContainer + { + BOOST_CONCEPT_USAGE(UniqueAssociativeContainer) + { + C c(first, last); + + pos_flag = c.insert(t); + c.insert(first, last); + + ignore_unused_variable_warning(c); + } + private: + std::pair pos_flag; + typename C::value_type t; + typename C::value_type* first, *last; + }; + + BOOST_concept(MultipleAssociativeContainer,(C)) + : AssociativeContainer + { + BOOST_CONCEPT_USAGE(MultipleAssociativeContainer) + { + C c(first, last); + + pos = c.insert(t); + c.insert(first, last); + + ignore_unused_variable_warning(c); + ignore_unused_variable_warning(pos); + } + private: + typename C::iterator pos; + typename C::value_type t; + typename C::value_type* first, *last; + }; + + BOOST_concept(SimpleAssociativeContainer,(C)) + : AssociativeContainer + { + BOOST_CONCEPT_USAGE(SimpleAssociativeContainer) + { + typedef typename C::key_type key_type; + typedef typename C::value_type value_type; + BOOST_MPL_ASSERT((boost::is_same)); + } + }; + + BOOST_concept(PairAssociativeContainer,(C)) + : AssociativeContainer + { + BOOST_CONCEPT_USAGE(PairAssociativeContainer) + { + typedef typename C::key_type key_type; + typedef typename C::value_type value_type; + typedef typename C::mapped_type mapped_type; + typedef std::pair required_value_type; + BOOST_MPL_ASSERT((boost::is_same)); + } + }; + + BOOST_concept(SortedAssociativeContainer,(C)) + : AssociativeContainer + , ReversibleContainer + { + BOOST_CONCEPT_USAGE(SortedAssociativeContainer) + { + C + c(kc), + c2(first, last), + c3(first, last, kc); + + p = c.upper_bound(k); + p = c.lower_bound(k); + r = c.equal_range(k); + + c.insert(p, t); + + ignore_unused_variable_warning(c); + ignore_unused_variable_warning(c2); + ignore_unused_variable_warning(c3); + const_constraints(c); + } + + void const_constraints(const C& c) + { + kc = c.key_comp(); + vc = c.value_comp(); + + cp = c.upper_bound(k); + cp = c.lower_bound(k); + cr = c.equal_range(k); + } + + private: + typename C::key_compare kc; + typename C::value_compare vc; + typename C::value_type t; + typename C::key_type k; + typedef typename C::iterator iterator; + typedef typename C::const_iterator const_iterator; + + typedef SortedAssociativeContainer self; + iterator p; + const_iterator cp; + std::pair r; + std::pair cr; + typename C::value_type* first, *last; + }; + + // HashedAssociativeContainer + + BOOST_concept(Collection,(C)) + { + BOOST_CONCEPT_USAGE(Collection) + { + boost::function_requires >(); + boost::function_requires >(); + boost::function_requires >(); + const_constraints(c); + i = c.begin(); + i = c.end(); + c.swap(c); + } + + void const_constraints(const C& cc) { + ci = cc.begin(); + ci = cc.end(); + n = cc.size(); + b = cc.empty(); + } + + private: + typedef typename C::value_type value_type; + typedef typename C::iterator iterator; + typedef typename C::const_iterator const_iterator; + typedef typename C::reference reference; + typedef typename C::const_reference const_reference; + // typedef typename C::pointer pointer; + typedef typename C::difference_type difference_type; + typedef typename C::size_type size_type; + + C c; + bool b; + iterator i; + const_iterator ci; + size_type n; + }; +} // namespace boost + +#if (defined _MSC_VER) +# pragma warning( pop ) +#endif + +# include + +#endif // BOOST_CONCEPT_CHECKS_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/config.hpp b/thirdparty/source/boost_1_61_0/boost/config.hpp new file mode 100644 index 0000000..d49bb27 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config.hpp @@ -0,0 +1,67 @@ +// Boost config.hpp configuration header file ------------------------------// + +// (C) Copyright John Maddock 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/config for most recent version. + +// Boost config.hpp policy and rationale documentation has been moved to +// http://www.boost.org/libs/config +// +// CAUTION: This file is intended to be completely stable - +// DO NOT MODIFY THIS FILE! +// + +#ifndef BOOST_CONFIG_HPP +#define BOOST_CONFIG_HPP + +// if we don't have a user config, then use the default location: +#if !defined(BOOST_USER_CONFIG) && !defined(BOOST_NO_USER_CONFIG) +# define BOOST_USER_CONFIG +#if 0 +// For dependency trackers: +# include +#endif +#endif +// include it first: +#ifdef BOOST_USER_CONFIG +# include BOOST_USER_CONFIG +#endif + +// if we don't have a compiler config set, try and find one: +#if !defined(BOOST_COMPILER_CONFIG) && !defined(BOOST_NO_COMPILER_CONFIG) && !defined(BOOST_NO_CONFIG) +# include +#endif +// if we have a compiler config, include it now: +#ifdef BOOST_COMPILER_CONFIG +# include BOOST_COMPILER_CONFIG +#endif + +// if we don't have a std library config set, try and find one: +#if !defined(BOOST_STDLIB_CONFIG) && !defined(BOOST_NO_STDLIB_CONFIG) && !defined(BOOST_NO_CONFIG) && defined(__cplusplus) +# include +#endif +// if we have a std library config, include it now: +#ifdef BOOST_STDLIB_CONFIG +# include BOOST_STDLIB_CONFIG +#endif + +// if we don't have a platform config set, try and find one: +#if !defined(BOOST_PLATFORM_CONFIG) && !defined(BOOST_NO_PLATFORM_CONFIG) && !defined(BOOST_NO_CONFIG) +# include +#endif +// if we have a platform config, include it now: +#ifdef BOOST_PLATFORM_CONFIG +# include BOOST_PLATFORM_CONFIG +#endif + +// get config suffix code: +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#endif // BOOST_CONFIG_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi/borland_prefix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi/borland_prefix.hpp new file mode 100644 index 0000000..3a0e5ae --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi/borland_prefix.hpp @@ -0,0 +1,27 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// for C++ Builder the following options effect the ABI: +// +// -b (on or off - effect emum sizes) +// -Vx (on or off - empty members) +// -Ve (on or off - empty base classes) +// -aX (alignment - 5 options). +// -pX (Calling convention - 4 options) +// -VmX (member pointer size and layout - 5 options) +// -VC (on or off, changes name mangling) +// -Vl (on or off, changes struct layout). + +// In addition the following warnings are sufficiently annoying (and +// unfixable) to have them turned off by default: +// +// 8027 - functions containing [for|while] loops are not expanded inline +// 8026 - functions taking class by value arguments are not expanded inline + +#pragma nopushoptwarn +# pragma option push -a8 -Vx- -Ve- -b- -pc -Vmv -VC- -Vl- -w-8027 -w-8026 + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi/borland_suffix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi/borland_suffix.hpp new file mode 100644 index 0000000..940535f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi/borland_suffix.hpp @@ -0,0 +1,12 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +# pragma option pop +#pragma nopushoptwarn + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_prefix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_prefix.hpp new file mode 100644 index 0000000..97f06cd --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_prefix.hpp @@ -0,0 +1,22 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// +// Boost binaries are built with the compiler's default ABI settings, +// if the user changes their default alignment in the VS IDE then their +// code will no longer be binary compatible with the bjam built binaries +// unless this header is included to force Boost code into a consistent ABI. +// +// Note that inclusion of this header is only necessary for libraries with +// separate source, header only libraries DO NOT need this as long as all +// translation units are built with the same options. +// +#if defined(_M_X64) +# pragma pack(push,16) +#else +# pragma pack(push,8) +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_suffix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_suffix.hpp new file mode 100644 index 0000000..a64d783 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi/msvc_suffix.hpp @@ -0,0 +1,8 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma pack(pop) + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi_prefix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi_prefix.hpp new file mode 100644 index 0000000..3b13474 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi_prefix.hpp @@ -0,0 +1,25 @@ +// abi_prefix header -------------------------------------------------------// + +// (c) Copyright John Maddock 2003 + +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). + +#ifndef BOOST_CONFIG_ABI_PREFIX_HPP +# define BOOST_CONFIG_ABI_PREFIX_HPP +#else +# error double inclusion of header boost/config/abi_prefix.hpp is an error +#endif + +#include + +// this must occur after all other includes and before any code appears: +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_PREFIX +#endif + +#if defined( __BORLANDC__ ) +#pragma nopushoptwarn +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/abi_suffix.hpp b/thirdparty/source/boost_1_61_0/boost/config/abi_suffix.hpp new file mode 100644 index 0000000..9391616 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/abi_suffix.hpp @@ -0,0 +1,27 @@ +// abi_sufffix header -------------------------------------------------------// + +// (c) Copyright John Maddock 2003 + +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). + +// This header should be #included AFTER code that was preceded by a #include +// . + +#ifndef BOOST_CONFIG_ABI_PREFIX_HPP +# error Header boost/config/abi_suffix.hpp must only be used after boost/config/abi_prefix.hpp +#else +# undef BOOST_CONFIG_ABI_PREFIX_HPP +#endif + +// the suffix header occurs after all of our code: +#ifdef BOOST_HAS_ABI_HEADERS +# include BOOST_ABI_SUFFIX +#endif + +#if defined( __BORLANDC__ ) +#pragma nopushoptwarn +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/auto_link.hpp b/thirdparty/source/boost_1_61_0/boost/config/auto_link.hpp new file mode 100644 index 0000000..56a16b0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/auto_link.hpp @@ -0,0 +1,439 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + /* + * LOCATION: see http://www.boost.org for most recent version. + * FILE auto_link.hpp + * VERSION see + * DESCRIPTION: Automatic library inclusion for Borland/Microsoft compilers. + */ + +/************************************************************************* + +USAGE: +~~~~~~ + +Before including this header you must define one or more of define the following macros: + +BOOST_LIB_NAME: Required: A string containing the basename of the library, + for example boost_regex. +BOOST_LIB_TOOLSET: Optional: the base name of the toolset. +BOOST_DYN_LINK: Optional: when set link to dll rather than static library. +BOOST_LIB_DIAGNOSTIC: Optional: when set the header will print out the name + of the library selected (useful for debugging). +BOOST_AUTO_LINK_NOMANGLE: Specifies that we should link to BOOST_LIB_NAME.lib, + rather than a mangled-name version. +BOOST_AUTO_LINK_TAGGED: Specifies that we link to libraries built with the --layout=tagged option. + This is essentially the same as the default name-mangled version, but without + the compiler name and version, or the Boost version. Just the build options. + +These macros will be undef'ed at the end of the header, further this header +has no include guards - so be sure to include it only once from your library! + +Algorithm: +~~~~~~~~~~ + +Libraries for Borland and Microsoft compilers are automatically +selected here, the name of the lib is selected according to the following +formula: + +BOOST_LIB_PREFIX + + BOOST_LIB_NAME + + "_" + + BOOST_LIB_TOOLSET + + BOOST_LIB_THREAD_OPT + + BOOST_LIB_RT_OPT + "-" + + BOOST_LIB_VERSION + +These are defined as: + +BOOST_LIB_PREFIX: "lib" for static libraries otherwise "". + +BOOST_LIB_NAME: The base name of the lib ( for example boost_regex). + +BOOST_LIB_TOOLSET: The compiler toolset name (vc6, vc7, bcb5 etc). + +BOOST_LIB_THREAD_OPT: "-mt" for multithread builds, otherwise nothing. + +BOOST_LIB_RT_OPT: A suffix that indicates the runtime library used, + contains one or more of the following letters after + a hyphen: + + s static runtime (dynamic if not present). + g debug/diagnostic runtime (release if not present). + y Python debug/diagnostic runtime (release if not present). + d debug build (release if not present). + p STLport build. + n STLport build without its IOStreams. + +BOOST_LIB_VERSION: The Boost version, in the form x_y, for Boost version x.y. + + +***************************************************************************/ + +#ifdef __cplusplus +# ifndef BOOST_CONFIG_HPP +# include +# endif +#elif defined(_MSC_VER) && !defined(__MWERKS__) && !defined(__EDG_VERSION__) +// +// C language compatability (no, honestly) +// +# define BOOST_MSVC _MSC_VER +# define BOOST_STRINGIZE(X) BOOST_DO_STRINGIZE(X) +# define BOOST_DO_STRINGIZE(X) #X +#endif +// +// Only include what follows for known and supported compilers: +// +#if defined(BOOST_MSVC) \ + || defined(__BORLANDC__) \ + || (defined(__MWERKS__) && defined(_WIN32) && (__MWERKS__ >= 0x3000)) \ + || (defined(__ICL) && defined(_MSC_EXTENSIONS) && (_MSC_VER >= 1200)) + +#ifndef BOOST_VERSION_HPP +# include +#endif + +#ifndef BOOST_LIB_NAME +# error "Macro BOOST_LIB_NAME not set (internal error)" +#endif + +// +// error check: +// +#if defined(__MSVC_RUNTIME_CHECKS) && !defined(_DEBUG) +# pragma message("Using the /RTC option without specifying a debug runtime will lead to linker errors") +# pragma message("Hint: go to the code generation options and switch to one of the debugging runtimes") +# error "Incompatible build options" +#endif +// +// select toolset if not defined already: +// +#ifndef BOOST_LIB_TOOLSET +# if defined(BOOST_MSVC) && (BOOST_MSVC < 1200) + // Note: no compilers before 1200 are supported +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1300) + +# ifdef UNDER_CE + // eVC4: +# define BOOST_LIB_TOOLSET "evc4" +# else + // vc6: +# define BOOST_LIB_TOOLSET "vc6" +# endif + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1310) + + // vc7: +# define BOOST_LIB_TOOLSET "vc7" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1400) + + // vc71: +# define BOOST_LIB_TOOLSET "vc71" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1500) + + // vc80: +# define BOOST_LIB_TOOLSET "vc80" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1600) + + // vc90: +# define BOOST_LIB_TOOLSET "vc90" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1700) + + // vc10: +# define BOOST_LIB_TOOLSET "vc100" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1800) + + // vc11: +# define BOOST_LIB_TOOLSET "vc110" + +# elif defined(BOOST_MSVC) && (BOOST_MSVC < 1900) + + // vc12: +# define BOOST_LIB_TOOLSET "vc120" + +# elif defined(BOOST_MSVC) + + // vc14: +# define BOOST_LIB_TOOLSET "vc140" + +# elif defined(__BORLANDC__) + + // CBuilder 6: +# define BOOST_LIB_TOOLSET "bcb" + +# elif defined(__ICL) + + // Intel C++, no version number: +# define BOOST_LIB_TOOLSET "iw" + +# elif defined(__MWERKS__) && (__MWERKS__ <= 0x31FF ) + + // Metrowerks CodeWarrior 8.x +# define BOOST_LIB_TOOLSET "cw8" + +# elif defined(__MWERKS__) && (__MWERKS__ <= 0x32FF ) + + // Metrowerks CodeWarrior 9.x +# define BOOST_LIB_TOOLSET "cw9" + +# endif +#endif // BOOST_LIB_TOOLSET + +// +// select thread opt: +// +#if defined(_MT) || defined(__MT__) +# define BOOST_LIB_THREAD_OPT "-mt" +#else +# define BOOST_LIB_THREAD_OPT +#endif + +#if defined(_MSC_VER) || defined(__MWERKS__) + +# ifdef _DLL + +# if (defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION)) && (defined(_STLP_OWN_IOSTREAMS) || defined(__STL_OWN_IOSTREAMS)) + +# if defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG))\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-gydp" +# elif defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG)) +# define BOOST_LIB_RT_OPT "-gdp" +# elif defined(_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-gydp" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-gdp" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# else +# define BOOST_LIB_RT_OPT "-p" +# endif + +# elif defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) + +# if defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG))\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-gydpn" +# elif defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG)) +# define BOOST_LIB_RT_OPT "-gdpn" +# elif defined(_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-gydpn" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-gdpn" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# else +# define BOOST_LIB_RT_OPT "-pn" +# endif + +# else + +# if defined(_DEBUG) && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-gyd" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-gd" +# else +# define BOOST_LIB_RT_OPT +# endif + +# endif + +# else + +# if (defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION)) && (defined(_STLP_OWN_IOSTREAMS) || defined(__STL_OWN_IOSTREAMS)) + +# if defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG))\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sgydp" +# elif defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG)) +# define BOOST_LIB_RT_OPT "-sgdp" +# elif defined(_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sgydp" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-sgdp" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# else +# define BOOST_LIB_RT_OPT "-sp" +# endif + +# elif defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) + +# if defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG))\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sgydpn" +# elif defined(_DEBUG) && (defined(__STL_DEBUG) || defined(_STLP_DEBUG)) +# define BOOST_LIB_RT_OPT "-sgdpn" +# elif defined(_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sgydpn" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-sgdpn" +# pragma message("warning: STLport debug versions are built with /D_STLP_DEBUG=1") +# error "Build options aren't compatible with pre-built libraries" +# else +# define BOOST_LIB_RT_OPT "-spn" +# endif + +# else + +# if defined(_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sgyd" +# elif defined(_DEBUG) +# define BOOST_LIB_RT_OPT "-sgd" +# else +# define BOOST_LIB_RT_OPT "-s" +# endif + +# endif + +# endif + +#elif defined(__BORLANDC__) + +// +// figure out whether we want the debug builds or not: +// +#if __BORLANDC__ > 0x561 +#pragma defineonoption BOOST_BORLAND_DEBUG -v +#endif +// +// sanity check: +// +#if defined(__STL_DEBUG) || defined(_STLP_DEBUG) +#error "Pre-built versions of the Boost libraries are not provided in STLport-debug form" +#endif + +# ifdef _RTLDLL + +# if defined(BOOST_BORLAND_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-yd" +# elif defined(BOOST_BORLAND_DEBUG) +# define BOOST_LIB_RT_OPT "-d" +# elif defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT -y +# else +# define BOOST_LIB_RT_OPT +# endif + +# else + +# if defined(BOOST_BORLAND_DEBUG)\ + && defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-syd" +# elif defined(BOOST_BORLAND_DEBUG) +# define BOOST_LIB_RT_OPT "-sd" +# elif defined(BOOST_DEBUG_PYTHON) && defined(BOOST_LINKING_PYTHON) +# define BOOST_LIB_RT_OPT "-sy" +# else +# define BOOST_LIB_RT_OPT "-s" +# endif + +# endif + +#endif + +// +// select linkage opt: +// +#if (defined(_DLL) || defined(_RTLDLL)) && defined(BOOST_DYN_LINK) +# define BOOST_LIB_PREFIX +#elif defined(BOOST_DYN_LINK) +# error "Mixing a dll boost library with a static runtime is a really bad idea..." +#else +# define BOOST_LIB_PREFIX "lib" +#endif + +// +// now include the lib: +// +#if defined(BOOST_LIB_NAME) \ + && defined(BOOST_LIB_PREFIX) \ + && defined(BOOST_LIB_TOOLSET) \ + && defined(BOOST_LIB_THREAD_OPT) \ + && defined(BOOST_LIB_RT_OPT) \ + && defined(BOOST_LIB_VERSION) + +#ifdef BOOST_AUTO_LINK_TAGGED +# pragma comment(lib, BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT ".lib") +# ifdef BOOST_LIB_DIAGNOSTIC +# pragma message ("Linking to lib file: " BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT ".lib") +# endif +#elif defined(BOOST_AUTO_LINK_NOMANGLE) +# pragma comment(lib, BOOST_STRINGIZE(BOOST_LIB_NAME) ".lib") +# ifdef BOOST_LIB_DIAGNOSTIC +# pragma message ("Linking to lib file: " BOOST_STRINGIZE(BOOST_LIB_NAME) ".lib") +# endif +#elif defined(BOOST_LIB_BUILDID) +# pragma comment(lib, BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) "-" BOOST_LIB_TOOLSET BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT "-" BOOST_LIB_VERSION "-" BOOST_STRINGIZE(BOOST_LIB_BUILDID) ".lib") +# ifdef BOOST_LIB_DIAGNOSTIC +# pragma message ("Linking to lib file: " BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) "-" BOOST_LIB_TOOLSET BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT "-" BOOST_LIB_VERSION "-" BOOST_STRINGIZE(BOOST_LIB_BUILDID) ".lib") +# endif +#else +# pragma comment(lib, BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) "-" BOOST_LIB_TOOLSET BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT "-" BOOST_LIB_VERSION ".lib") +# ifdef BOOST_LIB_DIAGNOSTIC +# pragma message ("Linking to lib file: " BOOST_LIB_PREFIX BOOST_STRINGIZE(BOOST_LIB_NAME) "-" BOOST_LIB_TOOLSET BOOST_LIB_THREAD_OPT BOOST_LIB_RT_OPT "-" BOOST_LIB_VERSION ".lib") +# endif +#endif + +#else +# error "some required macros where not defined (internal logic error)." +#endif + + +#endif // _MSC_VER || __BORLANDC__ + +// +// finally undef any macros we may have set: +// +#ifdef BOOST_LIB_PREFIX +# undef BOOST_LIB_PREFIX +#endif +#if defined(BOOST_LIB_NAME) +# undef BOOST_LIB_NAME +#endif +// Don't undef this one: it can be set by the user and should be the +// same for all libraries: +//#if defined(BOOST_LIB_TOOLSET) +//# undef BOOST_LIB_TOOLSET +//#endif +#if defined(BOOST_LIB_THREAD_OPT) +# undef BOOST_LIB_THREAD_OPT +#endif +#if defined(BOOST_LIB_RT_OPT) +# undef BOOST_LIB_RT_OPT +#endif +#if defined(BOOST_LIB_LINK_OPT) +# undef BOOST_LIB_LINK_OPT +#endif +#if defined(BOOST_LIB_DEBUG_OPT) +# undef BOOST_LIB_DEBUG_OPT +#endif +#if defined(BOOST_DYN_LINK) +# undef BOOST_DYN_LINK +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/borland.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/borland.hpp new file mode 100644 index 0000000..80dd230 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/borland.hpp @@ -0,0 +1,318 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Aleksey Gurtovoy 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Borland C++ compiler setup: + +// +// versions check: +// we don't support Borland prior to version 5.4: +#if __BORLANDC__ < 0x540 +# error "Compiler not supported or configured - please reconfigure" +#endif + +// last known compiler version: +#if (__BORLANDC__ > 0x613) +//# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +//# else +//# pragma message( "Unknown compiler version - please run the configure tests and report the results") +//# endif +#elif (__BORLANDC__ == 0x600) +# error "CBuilderX preview compiler is no longer supported" +#endif + +// +// Support macros to help with standard library detection +#if (__BORLANDC__ < 0x560) || defined(_USE_OLD_RW_STL) +# define BOOST_BCB_WITH_ROGUE_WAVE +#elif __BORLANDC__ < 0x570 +# define BOOST_BCB_WITH_STLPORT +#else +# define BOOST_BCB_WITH_DINKUMWARE +#endif + +// +// Version 5.0 and below: +# if __BORLANDC__ <= 0x0550 +// Borland C++Builder 4 and 5: +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +# if __BORLANDC__ == 0x0550 +// Borland C++Builder 5, command-line compiler 5.5: +# define BOOST_NO_OPERATORS_IN_NAMESPACE +# endif +// Variadic macros do not exist for C++ Builder versions 5 and below +#define BOOST_NO_CXX11_VARIADIC_MACROS +# endif + +// Version 5.51 and below: +#if (__BORLANDC__ <= 0x551) +# define BOOST_NO_CV_SPECIALIZATIONS +# define BOOST_NO_CV_VOID_SPECIALIZATIONS +# define BOOST_NO_DEDUCED_TYPENAME +// workaround for missing WCHAR_MAX/WCHAR_MIN: +#ifdef __cplusplus +#include +#include +#else +#include +#include +#endif // __cplusplus +#ifndef WCHAR_MAX +# define WCHAR_MAX 0xffff +#endif +#ifndef WCHAR_MIN +# define WCHAR_MIN 0 +#endif +#endif + +// Borland C++ Builder 6 and below: +#if (__BORLANDC__ <= 0x564) + +# if defined(NDEBUG) && defined(__cplusplus) + // fix broken so that Boost.test works: +# include +# undef strcmp +# endif + // fix broken errno declaration: +# include +# ifndef errno +# define errno errno +# endif + +#endif + +// +// new bug in 5.61: +#if (__BORLANDC__ >= 0x561) && (__BORLANDC__ <= 0x580) + // this seems to be needed by the command line compiler, but not the IDE: +# define BOOST_NO_MEMBER_FUNCTION_SPECIALIZATIONS +#endif + +// Borland C++ Builder 2006 Update 2 and below: +#if (__BORLANDC__ <= 0x582) +# define BOOST_NO_SFINAE +# define BOOST_BCB_PARTIAL_SPECIALIZATION_BUG +# define BOOST_NO_TEMPLATE_TEMPLATES + +# define BOOST_NO_PRIVATE_IN_AGGREGATE + +# ifdef _WIN32 +# define BOOST_NO_SWPRINTF +# elif defined(linux) || defined(__linux__) || defined(__linux) + // we should really be able to do without this + // but the wcs* functions aren't imported into std:: +# define BOOST_NO_STDC_NAMESPACE + // _CPPUNWIND doesn't get automatically set for some reason: +# pragma defineonoption BOOST_CPPUNWIND -x +# endif +#endif + +#if (__BORLANDC__ <= 0x613) // Beman has asked Alisdair for more info + // we shouldn't really need this - but too many things choke + // without it, this needs more investigation: +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +# define BOOST_NO_IS_ABSTRACT +# define BOOST_NO_FUNCTION_TYPE_SPECIALIZATIONS +# define BOOST_NO_USING_TEMPLATE +# define BOOST_SP_NO_SP_CONVERTIBLE + +// Temporary workaround +#define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS +#endif + +// Borland C++ Builder 2008 and below: +# define BOOST_NO_INTEGRAL_INT64_T +# define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +# define BOOST_NO_DEPENDENT_NESTED_DERIVATIONS +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +# define BOOST_NO_USING_DECLARATION_OVERLOADS_FROM_TYPENAME_BASE +# define BOOST_NO_NESTED_FRIENDSHIP +# define BOOST_NO_TYPENAME_WITH_CTOR +#if (__BORLANDC__ < 0x600) +# define BOOST_ILLEGAL_CV_REFERENCES +#endif + +// +// Positive Feature detection +// +// Borland C++ Builder 2008 and below: +#if (__BORLANDC__ >= 0x599) +# pragma defineonoption BOOST_CODEGEAR_0X_SUPPORT -Ax +#endif +// +// C++0x Macros: +// +#if !defined( BOOST_CODEGEAR_0X_SUPPORT ) || (__BORLANDC__ < 0x610) +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +# define BOOST_NO_CXX11_DECLTYPE +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_EXTERN_TEMPLATE +# define BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_NO_CXX11_SCOPED_ENUMS +# define BOOST_NO_CXX11_STATIC_ASSERT +#else +# define BOOST_HAS_ALIGNOF +# define BOOST_HAS_CHAR16_T +# define BOOST_HAS_CHAR32_T +# define BOOST_HAS_DECLTYPE +# define BOOST_HAS_EXPLICIT_CONVERSION_OPS +# define BOOST_HAS_REF_QUALIFIER +# define BOOST_HAS_RVALUE_REFS +# define BOOST_HAS_STATIC_ASSERT +#endif + +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS // UTF-8 still not supported +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#if __BORLANDC__ >= 0x590 +# define BOOST_HAS_TR1_HASH + +# define BOOST_HAS_MACRO_USE_FACET +#endif + +// +// Post 0x561 we have long long and stdint.h: +#if __BORLANDC__ >= 0x561 +# ifndef __NO_LONG_LONG +# define BOOST_HAS_LONG_LONG +# else +# define BOOST_NO_LONG_LONG +# endif + // On non-Win32 platforms let the platform config figure this out: +# ifdef _WIN32 +# define BOOST_HAS_STDINT_H +# endif +#endif + +// Borland C++Builder 6 defaults to using STLPort. If _USE_OLD_RW_STL is +// defined, then we have 0x560 or greater with the Rogue Wave implementation +// which presumably has the std::DBL_MAX bug. +#if defined( BOOST_BCB_WITH_ROGUE_WAVE ) +// is partly broken, some macros define symbols that are really in +// namespace std, so you end up having to use illegal constructs like +// std::DBL_MAX, as a fix we'll just include float.h and have done with: +#include +#endif +// +// __int64: +// +#if (__BORLANDC__ >= 0x530) && !defined(__STRICT_ANSI__) +# define BOOST_HAS_MS_INT64 +#endif +// +// check for exception handling support: +// +#if !defined(_CPPUNWIND) && !defined(BOOST_CPPUNWIND) && !defined(__EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif +// +// all versions have a : +// +#ifndef __STRICT_ANSI__ +# define BOOST_HAS_DIRENT_H +#endif +// +// all versions support __declspec: +// +#if defined(__STRICT_ANSI__) +// config/platform/win32.hpp will define BOOST_SYMBOL_EXPORT, etc., unless already defined +# define BOOST_SYMBOL_EXPORT +#endif +// +// ABI fixing headers: +// +#if __BORLANDC__ != 0x600 // not implemented for version 6 compiler yet +#ifndef BOOST_ABI_PREFIX +# define BOOST_ABI_PREFIX "boost/config/abi/borland_prefix.hpp" +#endif +#ifndef BOOST_ABI_SUFFIX +# define BOOST_ABI_SUFFIX "boost/config/abi/borland_suffix.hpp" +#endif +#endif +// +// Disable Win32 support in ANSI mode: +// +#if __BORLANDC__ < 0x600 +# pragma defineonoption BOOST_DISABLE_WIN32 -A +#elif defined(__STRICT_ANSI__) +# define BOOST_DISABLE_WIN32 +#endif +// +// MSVC compatibility mode does some nasty things: +// TODO: look up if this doesn't apply to the whole 12xx range +// +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +# define BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP +# define BOOST_NO_VOID_RETURNS +#endif + +// Borland did not implement value-initialization completely, as I reported +// in 2007, Borland Report 51854, "Value-initialization: POD struct should be +// zero-initialized", http://qc.embarcadero.com/wc/qcmain.aspx?d=51854 +// See also: http://www.boost.org/libs/utility/value_init.htm#compiler_issues +// (Niels Dekker, LKEB, April 2010) +#define BOOST_NO_COMPLETE_VALUE_INITIALIZATION + +#define BOOST_COMPILER "Borland C++ version " BOOST_STRINGIZE(__BORLANDC__) diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/clang.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/clang.hpp new file mode 100644 index 0000000..01355bb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/clang.hpp @@ -0,0 +1,288 @@ +// (C) Copyright Douglas Gregor 2010 +// +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Clang compiler setup. + +#define BOOST_HAS_PRAGMA_ONCE + +// Detecting `-fms-extension` compiler flag assuming that _MSC_VER defined when that flag is used. +#if defined (_MSC_VER) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4)) +# define BOOST_HAS_PRAGMA_DETECT_MISMATCH +#endif + +// When compiling with clang before __has_extension was defined, +// even if one writes 'defined(__has_extension) && __has_extension(xxx)', +// clang reports a compiler error. So the only workaround found is: + +#ifndef __has_extension +#define __has_extension __has_feature +#endif + +#ifndef __has_attribute +#define __has_attribute(x) 0 +#endif + +#if !__has_feature(cxx_exceptions) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + +#if !__has_feature(cxx_rtti) && !defined(BOOST_NO_RTTI) +# define BOOST_NO_RTTI +#endif + +#if !__has_feature(cxx_rtti) && !defined(BOOST_NO_TYPEID) +# define BOOST_NO_TYPEID +#endif + +#if defined(__int64) && !defined(__GNUC__) +# define BOOST_HAS_MS_INT64 +#endif + +#define BOOST_HAS_NRVO + +// Branch prediction hints +#if defined(__has_builtin) +#if __has_builtin(__builtin_expect) +#define BOOST_LIKELY(x) __builtin_expect(x, 1) +#define BOOST_UNLIKELY(x) __builtin_expect(x, 0) +#endif +#endif + +// Clang supports "long long" in all compilation modes. +#define BOOST_HAS_LONG_LONG + +// +// We disable this if the compiler is really nvcc with C++03 as it +// doesn't actually support __int128 as of CUDA_VERSION=7500 +// even though it defines __SIZEOF_INT128__. +// See https://svn.boost.org/trac/boost/ticket/10418 +// https://svn.boost.org/trac/boost/ticket/11852 +// Only re-enable this for nvcc if you're absolutely sure +// of the circumstances under which it's supported. +// Similarly __SIZEOF_INT128__ is defined when targetting msvc +// compatibility even though the required support functions are absent. +// +#if defined(__CUDACC__) +# if defined(BOOST_GCC_CXX11) +# define BOOST_NVCC_CXX11 +# else +# define BOOST_NVCC_CXX03 +# endif +#endif + +#if defined(__SIZEOF_INT128__) && !defined(BOOST_NVCC_CXX03) && !defined(_MSC_VER) +# define BOOST_HAS_INT128 +#endif + + +// +// Dynamic shared object (DSO) and dynamic-link library (DLL) support +// +#if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32) +# define BOOST_SYMBOL_EXPORT __attribute__((__visibility__("default"))) +# define BOOST_SYMBOL_IMPORT +# define BOOST_SYMBOL_VISIBLE __attribute__((__visibility__("default"))) +#endif + +// +// The BOOST_FALLTHROUGH macro can be used to annotate implicit fall-through +// between switch labels. +// +#if __cplusplus >= 201103L && defined(__has_warning) +# if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough") +# define BOOST_FALLTHROUGH [[clang::fallthrough]] +# endif +#endif + +#if !__has_feature(cxx_auto_type) +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#endif + +// +// Currently clang on Windows using VC++ RTL does not support C++11's char16_t or char32_t +// +#if defined(_MSC_VER) || !(defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L) +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +#endif + +#if !__has_feature(cxx_constexpr) +# define BOOST_NO_CXX11_CONSTEXPR +#endif + +#if !__has_feature(cxx_decltype) +# define BOOST_NO_CXX11_DECLTYPE +#endif + +#if !__has_feature(cxx_decltype_incomplete_return_types) +# define BOOST_NO_CXX11_DECLTYPE_N3276 +#endif + +#if !__has_feature(cxx_defaulted_functions) +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#endif + +#if !__has_feature(cxx_deleted_functions) +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +#endif + +#if !__has_feature(cxx_explicit_conversions) +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#endif + +#if !__has_feature(cxx_default_function_template_args) +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#endif + +#if !__has_feature(cxx_generalized_initializers) +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#endif + +#if !__has_feature(cxx_lambdas) +# define BOOST_NO_CXX11_LAMBDAS +#endif + +#if !__has_feature(cxx_local_type_template_args) +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#endif + +#if !__has_feature(cxx_noexcept) +# define BOOST_NO_CXX11_NOEXCEPT +#endif + +#if !__has_feature(cxx_nullptr) +# define BOOST_NO_CXX11_NULLPTR +#endif + +#if !__has_feature(cxx_range_for) +# define BOOST_NO_CXX11_RANGE_BASED_FOR +#endif + +#if !__has_feature(cxx_raw_string_literals) +# define BOOST_NO_CXX11_RAW_LITERALS +#endif + +#if !__has_feature(cxx_reference_qualified_functions) +# define BOOST_NO_CXX11_REF_QUALIFIERS +#endif + +#if !__has_feature(cxx_generalized_initializers) +# define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#endif + +#if !__has_feature(cxx_rvalue_references) +# define BOOST_NO_CXX11_RVALUE_REFERENCES +#endif + +#if !__has_feature(cxx_strong_enums) +# define BOOST_NO_CXX11_SCOPED_ENUMS +#endif + +#if !__has_feature(cxx_static_assert) +# define BOOST_NO_CXX11_STATIC_ASSERT +#endif + +#if !__has_feature(cxx_alias_templates) +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +#endif + +#if !__has_feature(cxx_unicode_literals) +# define BOOST_NO_CXX11_UNICODE_LITERALS +#endif + +#if !__has_feature(cxx_variadic_templates) +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif + +#if !__has_feature(cxx_user_literals) +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#endif + +#if !__has_feature(cxx_alignas) +# define BOOST_NO_CXX11_ALIGNAS +#endif + +#if !__has_feature(cxx_trailing_return) +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#endif + +#if !__has_feature(cxx_inline_namespaces) +# define BOOST_NO_CXX11_INLINE_NAMESPACES +#endif + +#if !__has_feature(cxx_override_control) +# define BOOST_NO_CXX11_FINAL +#endif + +#if !(__has_feature(__cxx_binary_literals__) || __has_extension(__cxx_binary_literals__)) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif + +#if !__has_feature(__cxx_decltype_auto__) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif + +#if !__has_feature(__cxx_aggregate_nsdmi__) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif + +#if !__has_feature(__cxx_init_captures__) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif + +#if !__has_feature(__cxx_generic_lambdas__) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif + +// clang < 3.5 has a defect with dependent type, like following. +// +// template +// constexpr typename enable_if >::type foo(T &) +// { } // error: no return statement in constexpr function +// +// This issue also affects C++11 mode, but C++11 constexpr requires return stmt. +// Therefore we don't care such case. +// +// Note that we can't check Clang version directly as the numbering system changes depending who's +// creating the Clang release (see https://github.com/boostorg/config/pull/39#issuecomment-59927873) +// so instead verify that we have a feature that was introduced at the same time as working C++14 +// constexpr (generic lambda's in this case): +// +#if !__has_feature(__cxx_generic_lambdas__) || !__has_feature(__cxx_relaxed_constexpr__) +# define BOOST_NO_CXX14_CONSTEXPR +#endif + +#if !__has_feature(__cxx_return_type_deduction__) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif + +#if !__has_feature(__cxx_variable_templates__) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#if __cplusplus < 201400 +// All versions with __cplusplus above this value seem to support this: +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +// +// __builtin_unreachable: +#if defined(__has_builtin) && __has_builtin(__builtin_unreachable) +#define BOOST_UNREACHABLE_RETURN(x) __builtin_unreachable(); +#endif + +// Clang has supported the 'unused' attribute since the first release. +#define BOOST_ATTRIBUTE_UNUSED __attribute__((__unused__)) + +#ifndef BOOST_COMPILER +# define BOOST_COMPILER "Clang version " __clang_version__ +#endif + +// Macro used to identify the Clang compiler. +#define BOOST_CLANG 1 + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/codegear.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/codegear.hpp new file mode 100644 index 0000000..02bd792 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/codegear.hpp @@ -0,0 +1,220 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Aleksey Gurtovoy 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// CodeGear C++ compiler setup: + +#if !defined( BOOST_WITH_CODEGEAR_WARNINGS ) +// these warnings occur frequently in optimized template code +# pragma warn -8004 // var assigned value, but never used +# pragma warn -8008 // condition always true/false +# pragma warn -8066 // dead code can never execute +# pragma warn -8104 // static members with ctors not threadsafe +# pragma warn -8105 // reference member in class without ctors +#endif +// +// versions check: +// last known and checked version is 0x621 +#if (__CODEGEARC__ > 0x621) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# else +# pragma message( "Unknown compiler version - please run the configure tests and report the results") +# endif +#endif + +// CodeGear C++ Builder 2009 +#if (__CODEGEARC__ <= 0x613) +# define BOOST_NO_INTEGRAL_INT64_T +# define BOOST_NO_DEPENDENT_NESTED_DERIVATIONS +# define BOOST_NO_PRIVATE_IN_AGGREGATE +# define BOOST_NO_USING_DECLARATION_OVERLOADS_FROM_TYPENAME_BASE + // we shouldn't really need this - but too many things choke + // without it, this needs more investigation: +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +# define BOOST_SP_NO_SP_CONVERTIBLE +#endif + +// CodeGear C++ Builder 2010 +#if (__CODEGEARC__ <= 0x621) +# define BOOST_NO_TYPENAME_WITH_CTOR // Cannot use typename keyword when making temporaries of a dependant type +# define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +# define BOOST_NO_NESTED_FRIENDSHIP // TC1 gives nested classes access rights as any other member +# define BOOST_NO_USING_TEMPLATE +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +// Temporary hack, until specific MPL preprocessed headers are generated +# define BOOST_MPL_CFG_NO_PREPROCESSED_HEADERS + +// CodeGear has not yet completely implemented value-initialization, for +// example for array types, as I reported in 2010: Embarcadero Report 83751, +// "Value-initialization: arrays should have each element value-initialized", +// http://qc.embarcadero.com/wc/qcmain.aspx?d=83751 +// Last checked version: Embarcadero C++ 6.21 +// See also: http://www.boost.org/libs/utility/value_init.htm#compiler_issues +// (Niels Dekker, LKEB, April 2010) +# define BOOST_NO_COMPLETE_VALUE_INITIALIZATION + +# if defined(NDEBUG) && defined(__cplusplus) + // fix broken so that Boost.test works: +# include +# undef strcmp +# endif + // fix broken errno declaration: +# include +# ifndef errno +# define errno errno +# endif + +#endif + +// Reportedly, #pragma once is supported since C++ Builder 2010 +#if (__CODEGEARC__ >= 0x620) +# define BOOST_HAS_PRAGMA_ONCE +#endif + +// +// C++0x macros: +// +#if (__CODEGEARC__ <= 0x620) +#define BOOST_NO_CXX11_STATIC_ASSERT +#else +#define BOOST_HAS_STATIC_ASSERT +#endif +#define BOOST_HAS_CHAR16_T +#define BOOST_HAS_CHAR32_T +#define BOOST_HAS_LONG_LONG +// #define BOOST_HAS_ALIGNOF +#define BOOST_HAS_DECLTYPE +#define BOOST_HAS_EXPLICIT_CONVERSION_OPS +// #define BOOST_HAS_RVALUE_REFS +#define BOOST_HAS_SCOPED_ENUM +// #define BOOST_HAS_STATIC_ASSERT +#define BOOST_HAS_STD_TYPE_TRAITS + +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +// +// TR1 macros: +// +#define BOOST_HAS_TR1_HASH +#define BOOST_HAS_TR1_TYPE_TRAITS +#define BOOST_HAS_TR1_UNORDERED_MAP +#define BOOST_HAS_TR1_UNORDERED_SET + +#define BOOST_HAS_MACRO_USE_FACET + +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST + +// On non-Win32 platforms let the platform config figure this out: +#ifdef _WIN32 +# define BOOST_HAS_STDINT_H +#endif + +// +// __int64: +// +#if !defined(__STRICT_ANSI__) +# define BOOST_HAS_MS_INT64 +#endif +// +// check for exception handling support: +// +#if !defined(_CPPUNWIND) && !defined(BOOST_CPPUNWIND) && !defined(__EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif +// +// all versions have a : +// +#if !defined(__STRICT_ANSI__) +# define BOOST_HAS_DIRENT_H +#endif +// +// all versions support __declspec: +// +#if defined(__STRICT_ANSI__) +// config/platform/win32.hpp will define BOOST_SYMBOL_EXPORT, etc., unless already defined +# define BOOST_SYMBOL_EXPORT +#endif +// +// ABI fixing headers: +// +#ifndef BOOST_ABI_PREFIX +# define BOOST_ABI_PREFIX "boost/config/abi/borland_prefix.hpp" +#endif +#ifndef BOOST_ABI_SUFFIX +# define BOOST_ABI_SUFFIX "boost/config/abi/borland_suffix.hpp" +#endif +// +// Disable Win32 support in ANSI mode: +// +# pragma defineonoption BOOST_DISABLE_WIN32 -A +// +// MSVC compatibility mode does some nasty things: +// TODO: look up if this doesn't apply to the whole 12xx range +// +#if defined(_MSC_VER) && (_MSC_VER <= 1200) +# define BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP +# define BOOST_NO_VOID_RETURNS +#endif + +#define BOOST_COMPILER "CodeGear C++ version " BOOST_STRINGIZE(__CODEGEARC__) + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/comeau.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/comeau.hpp new file mode 100644 index 0000000..278222d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/comeau.hpp @@ -0,0 +1,59 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright Douglas Gregor 2001. +// (C) Copyright Peter Dimov 2001. +// (C) Copyright Aleksey Gurtovoy 2003. +// (C) Copyright Beman Dawes 2003. +// (C) Copyright Jens Maurer 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Comeau C++ compiler setup: + +#include "boost/config/compiler/common_edg.hpp" + +#if (__COMO_VERSION__ <= 4245) + +# if defined(_MSC_VER) && _MSC_VER <= 1300 +# if _MSC_VER > 100 + // only set this in non-strict mode: +# define BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP +# endif +# endif + +// Void returns don't work when emulating VC 6 (Peter Dimov) +// TODO: look up if this doesn't apply to the whole 12xx range +# if defined(_MSC_VER) && (_MSC_VER < 1300) +# define BOOST_NO_VOID_RETURNS +# endif + +#endif // version 4245 + +// +// enable __int64 support in VC emulation mode +// +# if defined(_MSC_VER) && (_MSC_VER >= 1200) +# define BOOST_HAS_MS_INT64 +# endif + +#define BOOST_COMPILER "Comeau compiler version " BOOST_STRINGIZE(__COMO_VERSION__) + +// +// versions check: +// we don't know Comeau prior to version 4245: +#if __COMO_VERSION__ < 4245 +# error "Compiler not configured - please reconfigure" +#endif +// +// last known and checked version is 4245: +#if (__COMO_VERSION__ > 4245) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/common_edg.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/common_edg.hpp new file mode 100644 index 0000000..b92e574 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/common_edg.hpp @@ -0,0 +1,143 @@ +// (C) Copyright John Maddock 2001 - 2002. +// (C) Copyright Jens Maurer 2001. +// (C) Copyright David Abrahams 2002. +// (C) Copyright Aleksey Gurtovoy 2002. +// (C) Copyright Markus Schoepflin 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// +// Options common to all edg based compilers. +// +// This is included from within the individual compiler mini-configs. + +#ifndef __EDG_VERSION__ +# error This file requires that __EDG_VERSION__ be defined. +#endif + +#if (__EDG_VERSION__ <= 238) +# define BOOST_NO_INTEGRAL_INT64_T +# define BOOST_NO_SFINAE +#endif + +#if (__EDG_VERSION__ <= 240) +# define BOOST_NO_VOID_RETURNS +#endif + +#if (__EDG_VERSION__ <= 241) && !defined(BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP) +# define BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP +#endif + +#if (__EDG_VERSION__ <= 244) && !defined(BOOST_NO_TEMPLATE_TEMPLATES) +# define BOOST_NO_TEMPLATE_TEMPLATES +#endif + +#if (__EDG_VERSION__ < 300) && !defined(BOOST_NO_IS_ABSTRACT) +# define BOOST_NO_IS_ABSTRACT +#endif + +#if (__EDG_VERSION__ <= 303) && !defined(BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL) +# define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +#endif + +// See also kai.hpp which checks a Kai-specific symbol for EH +# if !defined(__KCC) && !defined(__EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +# endif + +# if !defined(__NO_LONG_LONG) +# define BOOST_HAS_LONG_LONG +# else +# define BOOST_NO_LONG_LONG +# endif + +// Not sure what version was the first to support #pragma once, but +// different EDG-based compilers (e.g. Intel) supported it for ages. +// Add a proper version check if it causes problems. +#define BOOST_HAS_PRAGMA_ONCE + +// +// C++0x features +// +// See above for BOOST_NO_LONG_LONG +// +#if (__EDG_VERSION__ < 310) +# define BOOST_NO_CXX11_EXTERN_TEMPLATE +#endif +#if (__EDG_VERSION__ <= 310) +// No support for initializer lists +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#endif +#if (__EDG_VERSION__ < 400) +# define BOOST_NO_CXX11_VARIADIC_MACROS +#endif + +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#ifdef c_plusplus +// EDG has "long long" in non-strict mode +// However, some libraries have insufficient "long long" support +// #define BOOST_HAS_LONG_LONG +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/compaq_cxx.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/compaq_cxx.hpp new file mode 100644 index 0000000..b44486c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/compaq_cxx.hpp @@ -0,0 +1,19 @@ +// (C) Copyright John Maddock 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Tru64 C++ compiler setup (now HP): + +#define BOOST_COMPILER "HP Tru64 C++ " BOOST_STRINGIZE(__DECCXX_VER) + +#include "boost/config/compiler/common_edg.hpp" + +// +// versions check: +// Nothing to do here? + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/cray.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/cray.hpp new file mode 100644 index 0000000..3f66043 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/cray.hpp @@ -0,0 +1,92 @@ +// (C) Copyright John Maddock 2011. +// (C) Copyright Cray, Inc. 2013 +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Greenhills C compiler setup: + +#define BOOST_COMPILER "Cray C version " BOOST_STRINGIZE(_RELEASE) + +#if _RELEASE < 8 +# error "Boost is not configured for Cray compilers prior to version 8, please try the configure script." +#endif + +// +// Check this is a recent EDG based compiler, otherwise we don't support it here: +// +#ifndef __EDG_VERSION__ +# error "Unsupported Cray compiler, please try running the configure script." +#endif + +#include "boost/config/compiler/common_edg.hpp" + + +// +// +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_HAS_NRVO +#define BOOST_NO_CXX11_VARIADIC_MACROS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#define BOOST_HAS_NRVO +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + + +//#define BOOST_BCB_PARTIAL_SPECIALIZATION_BUG +#define BOOST_MATH_DISABLE_STD_FPCLASSIFY +//#define BOOST_HAS_FPCLASSIFY + +#define BOOST_SP_USE_PTHREADS +#define BOOST_AC_USE_PTHREADS + +/* everything that follows is working around what are thought to be + * compiler shortcomings. Revist all of these regularly. + */ + +//#define BOOST_USE_ENUM_STATIC_ASSERT +//#define BOOST_BUGGY_INTEGRAL_CONSTANT_EXPRESSIONS //(this may be implied by the previous #define + +// These constants should be provided by the +// compiler, at least when -hgnu is asserted on the command line. + +#ifndef __ATOMIC_RELAXED +#define __ATOMIC_RELAXED 0 +#define __ATOMIC_CONSUME 1 +#define __ATOMIC_ACQUIRE 2 +#define __ATOMIC_RELEASE 3 +#define __ATOMIC_ACQ_REL 4 +#define __ATOMIC_SEQ_CST 5 +#endif + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/digitalmars.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/digitalmars.hpp new file mode 100644 index 0000000..a3d293c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/digitalmars.hpp @@ -0,0 +1,124 @@ +// Copyright (C) Christof Meerwald 2003 +// Copyright (C) Dan Watkins 2003 +// +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Digital Mars C++ compiler setup: +#define BOOST_COMPILER __DMC_VERSION_STRING__ + +#define BOOST_HAS_LONG_LONG +#define BOOST_HAS_PRAGMA_ONCE + +#if !defined(BOOST_STRICT_CONFIG) +#define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +#define BOOST_NO_OPERATORS_IN_NAMESPACE +#define BOOST_NO_UNREACHABLE_RETURN_DETECTION +#define BOOST_NO_SFINAE +#define BOOST_NO_USING_TEMPLATE +#define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +#endif + +// +// has macros: +#define BOOST_HAS_DIRENT_H +#define BOOST_HAS_STDINT_H +#define BOOST_HAS_WINTHREADS + +#if (__DMC__ >= 0x847) +#define BOOST_HAS_EXPM1 +#define BOOST_HAS_LOG1P +#endif + +// +// Is this really the best way to detect whether the std lib is in namespace std? +// +#ifdef __cplusplus +#include +#endif +#if !defined(__STL_IMPORT_VENDOR_CSTD) && !defined(_STLP_IMPORT_VENDOR_CSTD) +# define BOOST_NO_STDC_NAMESPACE +#endif + + +// check for exception handling support: +#if !defined(_CPPUNWIND) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + +// +// C++0x features +// +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#if (__DMC__ <= 0x840) +#error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version is ...: +#if (__DMC__ > 0x848) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc.hpp new file mode 100644 index 0000000..fbd3dd9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc.hpp @@ -0,0 +1,327 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Darin Adler 2001 - 2002. +// (C) Copyright Jens Maurer 2001 - 2002. +// (C) Copyright Beman Dawes 2001 - 2003. +// (C) Copyright Douglas Gregor 2002. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Synge Todo 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// GNU C++ compiler setup. + +// +// Define BOOST_GCC so we know this is "real" GCC and not some pretender: +// +#define BOOST_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#if !defined(__CUDACC__) +#define BOOST_GCC BOOST_GCC_VERSION +#endif + +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103L) +# define BOOST_GCC_CXX11 +#endif + +#if __GNUC__ == 3 +# if defined (__PATHSCALE__) +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +# define BOOST_NO_IS_ABSTRACT +# endif + +# if __GNUC_MINOR__ < 4 +# define BOOST_NO_IS_ABSTRACT +# endif +# define BOOST_NO_CXX11_EXTERN_TEMPLATE +#endif +#if __GNUC__ < 4 +// +// All problems to gcc-3.x and earlier here: +// +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +# ifdef __OPEN64__ +# define BOOST_NO_IS_ABSTRACT +# endif +#endif + +// GCC prior to 3.4 had #pragma once too but it didn't work well with filesystem links +#if BOOST_GCC_VERSION >= 30400 +#define BOOST_HAS_PRAGMA_ONCE +#endif + +#if BOOST_GCC_VERSION < 40400 +// Previous versions of GCC did not completely implement value-initialization: +// GCC Bug 30111, "Value-initialization of POD base class doesn't initialize +// members", reported by Jonathan Wakely in 2006, +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=30111 (fixed for GCC 4.4) +// GCC Bug 33916, "Default constructor fails to initialize array members", +// reported by Michael Elizabeth Chastain in 2007, +// http://gcc.gnu.org/bugzilla/show_bug.cgi?id=33916 (fixed for GCC 4.2.4) +// See also: http://www.boost.org/libs/utility/value_init.htm#compiler_issues +#define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +#endif + +#if !defined(__EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + + +// +// Threading support: Turn this on unconditionally here (except for +// those platforms where we can know for sure). It will get turned off again +// later if no threading API is detected. +// +#if !defined(__MINGW32__) && !defined(linux) && !defined(__linux) && !defined(__linux__) +# define BOOST_HAS_THREADS +#endif + +// +// gcc has "long long" +// Except on Darwin with standard compliance enabled (-pedantic) +// Apple gcc helpfully defines this macro we can query +// +#if !defined(__DARWIN_NO_LONG_LONG) +# define BOOST_HAS_LONG_LONG +#endif + +// +// gcc implements the named return value optimization since version 3.1 +// +#define BOOST_HAS_NRVO + +// Branch prediction hints +#define BOOST_LIKELY(x) __builtin_expect(x, 1) +#define BOOST_UNLIKELY(x) __builtin_expect(x, 0) + +// +// Dynamic shared object (DSO) and dynamic-link library (DLL) support +// +#if __GNUC__ >= 4 +# if (defined(_WIN32) || defined(__WIN32__) || defined(WIN32)) && !defined(__CYGWIN__) + // All Win32 development environments, including 64-bit Windows and MinGW, define + // _WIN32 or one of its variant spellings. Note that Cygwin is a POSIX environment, + // so does not define _WIN32 or its variants. +# define BOOST_HAS_DECLSPEC +# define BOOST_SYMBOL_EXPORT __attribute__((__dllexport__)) +# define BOOST_SYMBOL_IMPORT __attribute__((__dllimport__)) +# else +# define BOOST_SYMBOL_EXPORT __attribute__((__visibility__("default"))) +# define BOOST_SYMBOL_IMPORT +# endif +# define BOOST_SYMBOL_VISIBLE __attribute__((__visibility__("default"))) +#else +// config/platform/win32.hpp will define BOOST_SYMBOL_EXPORT, etc., unless already defined +# define BOOST_SYMBOL_EXPORT +#endif + +// +// RTTI and typeinfo detection is possible post gcc-4.3: +// +#if BOOST_GCC_VERSION > 40300 +# ifndef __GXX_RTTI +# ifndef BOOST_NO_TYPEID +# define BOOST_NO_TYPEID +# endif +# ifndef BOOST_NO_RTTI +# define BOOST_NO_RTTI +# endif +# endif +#endif + +// +// Recent GCC versions have __int128 when in 64-bit mode. +// +// We disable this if the compiler is really nvcc with C++03 as it +// doesn't actually support __int128 as of CUDA_VERSION=7500 +// even though it defines __SIZEOF_INT128__. +// See https://svn.boost.org/trac/boost/ticket/8048 +// https://svn.boost.org/trac/boost/ticket/11852 +// Only re-enable this for nvcc if you're absolutely sure +// of the circumstances under which it's supported: +// +#if defined(__CUDACC__) +# if defined(BOOST_GCC_CXX11) +# define BOOST_NVCC_CXX11 +# else +# define BOOST_NVCC_CXX03 +# endif +#endif + +#if defined(__SIZEOF_INT128__) && !defined(BOOST_NVCC_CXX03) +# define BOOST_HAS_INT128 +#endif +// +// Recent GCC versions have a __float128 native type, we need to +// include a std lib header to detect this - not ideal, but we'll +// be including later anyway when we select the std lib. +// +// Nevertheless, as of CUDA 7.5, using __float128 with the host +// compiler in pre-C++11 mode is still not supported. +// See https://svn.boost.org/trac/boost/ticket/11852 +// +#ifdef __cplusplus +#include +#else +#include +#endif +#if defined(_GLIBCXX_USE_FLOAT128) && !defined(__STRICT_ANSI__) && !defined(BOOST_NVCC_CXX03) +# define BOOST_HAS_FLOAT128 +#endif + +// C++0x features in 4.3.n and later +// +#if (BOOST_GCC_VERSION >= 40300) && defined(BOOST_GCC_CXX11) +// C++0x features are only enabled when -std=c++0x or -std=gnu++0x are +// passed on the command line, which in turn defines +// __GXX_EXPERIMENTAL_CXX0X__. +# define BOOST_HAS_DECLTYPE +# define BOOST_HAS_RVALUE_REFS +# define BOOST_HAS_STATIC_ASSERT +# define BOOST_HAS_VARIADIC_TMPL +#else +# define BOOST_NO_CXX11_DECLTYPE +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +# define BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_NO_CXX11_STATIC_ASSERT +#endif + +// C++0x features in 4.4.n and later +// +#if (BOOST_GCC_VERSION < 40400) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +# define BOOST_NO_CXX11_INLINE_NAMESPACES +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif + +#if BOOST_GCC_VERSION < 40500 +# define BOOST_NO_SFINAE_EXPR +#endif + +// GCC 4.5 forbids declaration of defaulted functions in private or protected sections +#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ == 5) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS +#endif + +// C++0x features in 4.5.0 and later +// +#if (BOOST_GCC_VERSION < 40500) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_LAMBDAS +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +# define BOOST_NO_CXX11_RAW_LITERALS +# define BOOST_NO_CXX11_UNICODE_LITERALS +#endif + +// C++0x features in 4.5.1 and later +// +#if (BOOST_GCC_VERSION < 40501) || !defined(BOOST_GCC_CXX11) +// scoped enums have a serious bug in 4.4.0, so define BOOST_NO_CXX11_SCOPED_ENUMS before 4.5.1 +// See http://gcc.gnu.org/bugzilla/show_bug.cgi?id=38064 +# define BOOST_NO_CXX11_SCOPED_ENUMS +#endif + +// C++0x features in 4.6.n and later +// +#if (BOOST_GCC_VERSION < 40600) || !defined(BOOST_GCC_CXX11) +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#endif + +// C++0x features in 4.7.n and later +// +#if (BOOST_GCC_VERSION < 40700) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_FINAL +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +# define BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS +#endif + +// C++0x features in 4.8.n and later +// +#if (BOOST_GCC_VERSION < 40800) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_ALIGNAS +#endif + +// C++0x features in 4.8.1 and later +// +#if (BOOST_GCC_VERSION < 40801) || !defined(BOOST_GCC_CXX11) +# define BOOST_NO_CXX11_DECLTYPE_N3276 +# define BOOST_NO_CXX11_REF_QUALIFIERS +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif + +// C++14 features in 4.9.0 and later +// +#if (BOOST_GCC_VERSION < 40900) || (__cplusplus < 201300) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +# define BOOST_NO_CXX14_DECLTYPE_AUTO +# if !((BOOST_GCC_VERSION >= 40801) && (BOOST_GCC_VERSION < 40900) && defined(BOOST_GCC_CXX11)) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +# endif +#endif + + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +// +// Unused attribute: +#if __GNUC__ >= 4 +# define BOOST_ATTRIBUTE_UNUSED __attribute__((__unused__)) +#endif +// +// __builtin_unreachable: +#if BOOST_GCC_VERSION >= 40800 +#define BOOST_UNREACHABLE_RETURN(x) __builtin_unreachable(); +#endif + +#ifndef BOOST_COMPILER +# define BOOST_COMPILER "GNU C++ version " __VERSION__ +#endif + +// ConceptGCC compiler: +// http://www.generic-programming.org/software/ConceptGCC/ +#ifdef __GXX_CONCEPTS__ +# define BOOST_HAS_CONCEPTS +# define BOOST_COMPILER "ConceptGCC version " __VERSION__ +#endif + +// versions check: +// we don't know gcc prior to version 3.30: +#if (BOOST_GCC_VERSION< 30300) +# error "Compiler not configured - please reconfigure" +#endif +// +// last known and checked version is 4.9: +#if (BOOST_GCC_VERSION > 40900) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# else +// we don't emit warnings here anymore since there are no defect macros defined for +// gcc post 3.4, so any failures are gcc regressions... +//# warning "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc_xml.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc_xml.hpp new file mode 100644 index 0000000..c11f29d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/gcc_xml.hpp @@ -0,0 +1,95 @@ +// (C) Copyright John Maddock 2006. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// GCC-XML C++ compiler setup: + +# if !defined(__GCCXML_GNUC__) || ((__GCCXML_GNUC__ <= 3) && (__GCCXML_GNUC_MINOR__ <= 3)) +# define BOOST_NO_IS_ABSTRACT +# endif + +// +// Threading support: Turn this on unconditionally here (except for +// those platforms where we can know for sure). It will get turned off again +// later if no threading API is detected. +// +#if !defined(__MINGW32__) && !defined(_MSC_VER) && !defined(linux) && !defined(__linux) && !defined(__linux__) +# define BOOST_HAS_THREADS +#endif + +// +// gcc has "long long" +// +#define BOOST_HAS_LONG_LONG + +// C++0x features: +// +# define BOOST_NO_CXX11_CONSTEXPR +# define BOOST_NO_CXX11_NULLPTR +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +# define BOOST_NO_CXX11_DECLTYPE +# define BOOST_NO_CXX11_DECLTYPE_N3276 +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +# define BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_NO_CXX11_STATIC_ASSERT +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +# define BOOST_NO_CXX11_VARIADIC_MACROS +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_SCOPED_ENUMS +# define BOOST_NO_SFINAE_EXPR +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_LAMBDAS +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +# define BOOST_NO_CXX11_RANGE_BASED_FOR +# define BOOST_NO_CXX11_RAW_LITERALS +# define BOOST_NO_CXX11_UNICODE_LITERALS +# define BOOST_NO_CXX11_NOEXCEPT +# define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +# define BOOST_NO_CXX11_ALIGNAS +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +# define BOOST_NO_CXX11_INLINE_NAMESPACES +# define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#define BOOST_COMPILER "GCC-XML C++ version " __GCCXML__ + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/greenhills.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/greenhills.hpp new file mode 100644 index 0000000..038b6b2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/greenhills.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Greenhills C++ compiler setup: + +#define BOOST_COMPILER "Greenhills C++ version " BOOST_STRINGIZE(__ghs) + +#include "boost/config/compiler/common_edg.hpp" + +// +// versions check: +// we don't support Greenhills prior to version 0: +#if __ghs < 0 +# error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version is 0: +#if (__ghs > 0) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/hp_acc.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/hp_acc.hpp new file mode 100644 index 0000000..fb63839 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/hp_acc.hpp @@ -0,0 +1,145 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001 - 2003. +// (C) Copyright Aleksey Gurtovoy 2002. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Toon Knapen 2003. +// (C) Copyright Boris Gubenko 2006 - 2007. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// HP aCC C++ compiler setup: + +#if defined(__EDG__) +#include "boost/config/compiler/common_edg.hpp" +#endif + +#if (__HP_aCC <= 33100) +# define BOOST_NO_INTEGRAL_INT64_T +# define BOOST_NO_OPERATORS_IN_NAMESPACE +# if !defined(_NAMESPACE_STD) +# define BOOST_NO_STD_LOCALE +# define BOOST_NO_STRINGSTREAM +# endif +#endif + +#if (__HP_aCC <= 33300) +// member templates are sufficiently broken that we disable them for now +# define BOOST_NO_MEMBER_TEMPLATES +# define BOOST_NO_DEPENDENT_NESTED_DERIVATIONS +# define BOOST_NO_USING_DECLARATION_OVERLOADS_FROM_TYPENAME_BASE +#endif + +#if (__HP_aCC <= 38000) +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#endif + +#if (__HP_aCC > 50000) && (__HP_aCC < 60000) +# define BOOST_NO_UNREACHABLE_RETURN_DETECTION +# define BOOST_NO_TEMPLATE_TEMPLATES +# define BOOST_NO_SWPRINTF +# define BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS +# define BOOST_NO_IS_ABSTRACT +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +#endif + +// optional features rather than defects: +#if (__HP_aCC >= 33900) +# define BOOST_HAS_LONG_LONG +# define BOOST_HAS_PARTIAL_STD_ALLOCATOR +#endif + +#if (__HP_aCC >= 50000 ) && (__HP_aCC <= 53800 ) || (__HP_aCC < 31300 ) +# define BOOST_NO_MEMBER_TEMPLATE_KEYWORD +#endif + +// This macro should not be defined when compiling in strict ansi +// mode, but, currently, we don't have the ability to determine +// what standard mode we are compiling with. Some future version +// of aCC6 compiler will provide predefined macros reflecting the +// compilation options, including the standard mode. +#if (__HP_aCC >= 60000) || ((__HP_aCC > 38000) && defined(__hpxstd98)) +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#endif + +#define BOOST_COMPILER "HP aCC version " BOOST_STRINGIZE(__HP_aCC) + +// +// versions check: +// we don't support HP aCC prior to version 33000: +#if __HP_aCC < 33000 +# error "Compiler not supported or configured - please reconfigure" +#endif + +// +// Extended checks for supporting aCC on PA-RISC +#if __HP_aCC > 30000 && __HP_aCC < 50000 +# if __HP_aCC < 38000 + // versions prior to version A.03.80 not supported +# error "Compiler version not supported - version A.03.80 or higher is required" +# elif !defined(__hpxstd98) + // must compile using the option +hpxstd98 with version A.03.80 and above +# error "Compiler option '+hpxstd98' is required for proper support" +# endif //PA-RISC +#endif + +// +// C++0x features +// +// See boost\config\suffix.hpp for BOOST_NO_LONG_LONG +// +#if !defined(__EDG__) + +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS + +/* + See https://forums13.itrc.hp.com/service/forums/questionanswer.do?threadId=1443331 and + https://forums13.itrc.hp.com/service/forums/questionanswer.do?threadId=1443436 +*/ + +#if (__HP_aCC < 62500) || !defined(HP_CXX0x_SOURCE) + #define BOOST_NO_CXX11_VARIADIC_MACROS +#endif + +#endif + +// +// last known and checked version for HP-UX/ia64 is 61300 +// last known and checked version for PA-RISC is 38000 +#if ((__HP_aCC > 61300) || ((__HP_aCC > 38000) && defined(__hpxstd98))) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/intel.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/intel.hpp new file mode 100644 index 0000000..88ac023 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/intel.hpp @@ -0,0 +1,543 @@ +// (C) Copyright John Maddock 2001-8. +// (C) Copyright Peter Dimov 2001. +// (C) Copyright Jens Maurer 2001. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Aleksey Gurtovoy 2002 - 2003. +// (C) Copyright Guillaume Melquiond 2002 - 2003. +// (C) Copyright Beman Dawes 2003. +// (C) Copyright Martin Wille 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Intel compiler setup: + +#if defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 1500) && (defined(_MSC_VER) || defined(__GNUC__)) + +#ifdef _MSC_VER + +#include + +#undef BOOST_MSVC +#undef BOOST_MSVC_FULL_VER + +#if (__INTEL_COMPILER >= 1500) && (_MSC_VER >= 1900) +// +// These appear to be supported, even though VC++ may not support them: +// +#define BOOST_HAS_EXPM1 +#define BOOST_HAS_LOG1P +#undef BOOST_NO_CXX14_BINARY_LITERALS +// This one may be a little risky to enable?? +#undef BOOST_NO_SFINAE_EXPR + +#endif + +#else + +#include + +#undef BOOST_GCC_VERSION +#undef BOOST_GCC_CXX11 + +#endif + +#undef BOOST_COMPILER + +#if defined(__INTEL_COMPILER) +#if __INTEL_COMPILER == 9999 +# define BOOST_INTEL_CXX_VERSION 1200 // Intel bug in 12.1. +#else +# define BOOST_INTEL_CXX_VERSION __INTEL_COMPILER +#endif +#elif defined(__ICL) +# define BOOST_INTEL_CXX_VERSION __ICL +#elif defined(__ICC) +# define BOOST_INTEL_CXX_VERSION __ICC +#elif defined(__ECC) +# define BOOST_INTEL_CXX_VERSION __ECC +#endif + +// Flags determined by comparing output of 'icpc -dM -E' with and without '-std=c++0x' +#if (!(defined(_WIN32) || defined(_WIN64)) && defined(__STDC_HOSTED__) && (__STDC_HOSTED__ && (BOOST_INTEL_CXX_VERSION <= 1200))) || defined(__GXX_EXPERIMENTAL_CPP0X__) || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define BOOST_INTEL_STDCXX0X +#endif +#if defined(_MSC_VER) && (_MSC_VER >= 1600) +# define BOOST_INTEL_STDCXX0X +#endif + +#ifdef __GNUC__ +# define BOOST_INTEL_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#if !defined(BOOST_COMPILER) +# if defined(BOOST_INTEL_STDCXX0X) +# define BOOST_COMPILER "Intel C++ C++0x mode version " BOOST_STRINGIZE(BOOST_INTEL_CXX_VERSION) +# else +# define BOOST_COMPILER "Intel C++ version " BOOST_STRINGIZE(BOOST_INTEL_CXX_VERSION) +# endif +#endif + +#define BOOST_INTEL BOOST_INTEL_CXX_VERSION + +#if defined(_WIN32) || defined(_WIN64) +# define BOOST_INTEL_WIN BOOST_INTEL +#else +# define BOOST_INTEL_LINUX BOOST_INTEL +#endif + +#else + +#include "boost/config/compiler/common_edg.hpp" + +#if defined(__INTEL_COMPILER) +#if __INTEL_COMPILER == 9999 +# define BOOST_INTEL_CXX_VERSION 1200 // Intel bug in 12.1. +#else +# define BOOST_INTEL_CXX_VERSION __INTEL_COMPILER +#endif +#elif defined(__ICL) +# define BOOST_INTEL_CXX_VERSION __ICL +#elif defined(__ICC) +# define BOOST_INTEL_CXX_VERSION __ICC +#elif defined(__ECC) +# define BOOST_INTEL_CXX_VERSION __ECC +#endif + +// Flags determined by comparing output of 'icpc -dM -E' with and without '-std=c++0x' +#if (!(defined(_WIN32) || defined(_WIN64)) && defined(__STDC_HOSTED__) && (__STDC_HOSTED__ && (BOOST_INTEL_CXX_VERSION <= 1200))) || defined(__GXX_EXPERIMENTAL_CPP0X__) || defined(__GXX_EXPERIMENTAL_CXX0X__) +# define BOOST_INTEL_STDCXX0X +#endif +#if defined(_MSC_VER) && (_MSC_VER >= 1600) +# define BOOST_INTEL_STDCXX0X +#endif + +#ifdef __GNUC__ +# define BOOST_INTEL_GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +#if !defined(BOOST_COMPILER) +# if defined(BOOST_INTEL_STDCXX0X) +# define BOOST_COMPILER "Intel C++ C++0x mode version " BOOST_STRINGIZE(BOOST_INTEL_CXX_VERSION) +# else +# define BOOST_COMPILER "Intel C++ version " BOOST_STRINGIZE(BOOST_INTEL_CXX_VERSION) +# endif +#endif + +#define BOOST_INTEL BOOST_INTEL_CXX_VERSION + +#if defined(_WIN32) || defined(_WIN64) +# define BOOST_INTEL_WIN BOOST_INTEL +#else +# define BOOST_INTEL_LINUX BOOST_INTEL +#endif + +#if (BOOST_INTEL_CXX_VERSION <= 600) + +# if defined(_MSC_VER) && (_MSC_VER <= 1300) // added check for <= VC 7 (Peter Dimov) + +// Boost libraries assume strong standard conformance unless otherwise +// indicated by a config macro. As configured by Intel, the EDG front-end +// requires certain compiler options be set to achieve that strong conformance. +// Particularly /Qoption,c,--arg_dep_lookup (reported by Kirk Klobe & Thomas Witt) +// and /Zc:wchar_t,forScope. See boost-root/tools/build/intel-win32-tools.jam for +// details as they apply to particular versions of the compiler. When the +// compiler does not predefine a macro indicating if an option has been set, +// this config file simply assumes the option has been set. +// Thus BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP will not be defined, even if +// the compiler option is not enabled. + +# define BOOST_NO_SWPRINTF +# endif + +// Void returns, 64 bit integrals don't work when emulating VC 6 (Peter Dimov) + +# if defined(_MSC_VER) && (_MSC_VER <= 1200) +# define BOOST_NO_VOID_RETURNS +# define BOOST_NO_INTEGRAL_INT64_T +# endif + +#endif + +#if (BOOST_INTEL_CXX_VERSION <= 710) && defined(_WIN32) +# define BOOST_NO_POINTER_TO_MEMBER_TEMPLATE_PARAMETERS +#endif + +// See http://aspn.activestate.com/ASPN/Mail/Message/boost/1614864 +#if BOOST_INTEL_CXX_VERSION < 600 +# define BOOST_NO_INTRINSIC_WCHAR_T +#else +// We should test the macro _WCHAR_T_DEFINED to check if the compiler +// supports wchar_t natively. *BUT* there is a problem here: the standard +// headers define this macro if they typedef wchar_t. Anyway, we're lucky +// because they define it without a value, while Intel C++ defines it +// to 1. So we can check its value to see if the macro was defined natively +// or not. +// Under UNIX, the situation is exactly the same, but the macro _WCHAR_T +// is used instead. +# if ((_WCHAR_T_DEFINED + 0) == 0) && ((_WCHAR_T + 0) == 0) +# define BOOST_NO_INTRINSIC_WCHAR_T +# endif +#endif + +#if defined(__GNUC__) && !defined(BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL) +// +// Figure out when Intel is emulating this gcc bug +// (All Intel versions prior to 9.0.26, and versions +// later than that if they are set up to emulate gcc 3.2 +// or earlier): +// +# if ((__GNUC__ == 3) && (__GNUC_MINOR__ <= 2)) || (BOOST_INTEL < 900) || (__INTEL_COMPILER_BUILD_DATE < 20050912) +# define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +# endif +#endif +#if (defined(__GNUC__) && (__GNUC__ < 4)) || (defined(_WIN32) && (BOOST_INTEL_CXX_VERSION <= 1200)) || (BOOST_INTEL_CXX_VERSION <= 1200) +// GCC or VC emulation: +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#endif +// +// Verify that we have actually got BOOST_NO_INTRINSIC_WCHAR_T +// set correctly, if we don't do this now, we will get errors later +// in type_traits code among other things, getting this correct +// for the Intel compiler is actually remarkably fragile and tricky: +// +#ifdef __cplusplus +#if defined(BOOST_NO_INTRINSIC_WCHAR_T) +#include +template< typename T > struct assert_no_intrinsic_wchar_t; +template<> struct assert_no_intrinsic_wchar_t { typedef void type; }; +// if you see an error here then you need to unset BOOST_NO_INTRINSIC_WCHAR_T +// where it is defined above: +typedef assert_no_intrinsic_wchar_t::type assert_no_intrinsic_wchar_t_; +#else +template< typename T > struct assert_intrinsic_wchar_t; +template<> struct assert_intrinsic_wchar_t {}; +// if you see an error here then define BOOST_NO_INTRINSIC_WCHAR_T on the command line: +template<> struct assert_intrinsic_wchar_t {}; +#endif +#endif + +#if defined(_MSC_VER) && (_MSC_VER+0 >= 1000) +# if _MSC_VER >= 1200 +# define BOOST_HAS_MS_INT64 +# endif +# define BOOST_NO_SWPRINTF +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#elif defined(_WIN32) +# define BOOST_DISABLE_WIN32 +#endif + +// I checked version 6.0 build 020312Z, it implements the NRVO. +// Correct this as you find out which version of the compiler +// implemented the NRVO first. (Daniel Frey) +#if (BOOST_INTEL_CXX_VERSION >= 600) +# define BOOST_HAS_NRVO +#endif + +// Branch prediction hints +// I'm not sure 8.0 was the first version to support these builtins, +// update the condition if the version is not accurate. (Andrey Semashev) +#if defined(__GNUC__) && BOOST_INTEL_CXX_VERSION >= 800 +#define BOOST_LIKELY(x) __builtin_expect(x, 1) +#define BOOST_UNLIKELY(x) __builtin_expect(x, 0) +#endif + +// RTTI +// __RTTI is the EDG macro +// __INTEL_RTTI__ is the Intel macro +// __GXX_RTTI is the g++ macro +// _CPPRTTI is the MSVC++ macro +#if !defined(__RTTI) && !defined(__INTEL_RTTI__) && !defined(__GXX_RTTI) && !defined(_CPPRTTI) + +#if !defined(BOOST_NO_RTTI) +# define BOOST_NO_RTTI +#endif + +// in MS mode, static typeid works even when RTTI is off +#if !defined(_MSC_VER) && !defined(BOOST_NO_TYPEID) +# define BOOST_NO_TYPEID +#endif + +#endif + +// +// versions check: +// we don't support Intel prior to version 6.0: +#if BOOST_INTEL_CXX_VERSION < 600 +# error "Compiler not supported or configured - please reconfigure" +#endif + +// Intel on MacOS requires +#if defined(__APPLE__) && defined(__INTEL_COMPILER) +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#endif + +// Intel on Altix Itanium +#if defined(__itanium__) && defined(__INTEL_COMPILER) +# define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#endif + +// +// An attempt to value-initialize a pointer-to-member may trigger an +// internal error on Intel <= 11.1 (last checked version), as was +// reported by John Maddock, Intel support issue 589832, May 2010. +// Moreover, according to test results from Huang-Vista-x86_32_intel, +// intel-vc9-win-11.1 may leave a non-POD array uninitialized, in some +// cases when it should be value-initialized. +// (Niels Dekker, LKEB, May 2010) +// Apparently Intel 12.1 (compiler version number 9999 !!) has the same issue (compiler regression). +#if defined(__INTEL_COMPILER) +# if (__INTEL_COMPILER <= 1110) || (__INTEL_COMPILER == 9999) || (defined(_WIN32) && (__INTEL_COMPILER < 1600)) +# define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +# endif +#endif + +// +// Dynamic shared object (DSO) and dynamic-link library (DLL) support +// +#if defined(__GNUC__) && (__GNUC__ >= 4) +# define BOOST_SYMBOL_EXPORT __attribute__((visibility("default"))) +# define BOOST_SYMBOL_IMPORT +# define BOOST_SYMBOL_VISIBLE __attribute__((visibility("default"))) +#endif +// +// C++0x features +// For each feature we need to check both the Intel compiler version, +// and the version of MSVC or GCC that we are emulating. +// See http://software.intel.com/en-us/articles/c0x-features-supported-by-intel-c-compiler/ +// for a list of which features were implemented in which Intel releases. +// +#if defined(BOOST_INTEL_STDCXX0X) +// BOOST_NO_CXX11_CONSTEXPR: +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40600)) && !defined(_MSC_VER) +// Available in earlier Intel versions, but fail our tests: +# undef BOOST_NO_CXX11_CONSTEXPR +#endif +// BOOST_NO_CXX11_NULLPTR: +#if (BOOST_INTEL_CXX_VERSION >= 1210) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40600)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_NULLPTR +#endif +// BOOST_NO_CXX11_TEMPLATE_ALIASES +#if (BOOST_INTEL_CXX_VERSION >= 1210) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40700)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_TEMPLATE_ALIASES +#endif + +// BOOST_NO_CXX11_DECLTYPE +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40300)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_DECLTYPE +#endif + +// BOOST_NO_CXX11_DECLTYPE_N3276 +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40800)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_DECLTYPE_N3276 +#endif + +// BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40300)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#endif + +// BOOST_NO_CXX11_RVALUE_REFERENCES +#if (BOOST_INTEL_CXX_VERSION >= 1300) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40300)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +// This is available from earlier Intel versions, but breaks Filesystem and other libraries: +# undef BOOST_NO_CXX11_RVALUE_REFERENCES +#endif + +// BOOST_NO_CXX11_STATIC_ASSERT +#if (BOOST_INTEL_CXX_VERSION >= 1110) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40300)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_STATIC_ASSERT +#endif + +// BOOST_NO_CXX11_VARIADIC_TEMPLATES +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif + +// BOOST_NO_CXX11_VARIADIC_MACROS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40200)) && (!defined(_MSC_VER) || (_MSC_VER >= 1400)) +# undef BOOST_NO_CXX11_VARIADIC_MACROS +#endif + +// BOOST_NO_CXX11_AUTO_DECLARATIONS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_AUTO_DECLARATIONS +#endif + +// BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#endif + +// BOOST_NO_CXX11_CHAR16_T +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +# undef BOOST_NO_CXX11_CHAR16_T +#endif + +// BOOST_NO_CXX11_CHAR32_T +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +# undef BOOST_NO_CXX11_CHAR32_T +#endif + +// BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#endif + +// BOOST_NO_CXX11_DELETED_FUNCTIONS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_DELETED_FUNCTIONS +#endif + +// BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_VER >= 1700)) +# undef BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#endif + +// BOOST_NO_CXX11_SCOPED_ENUMS +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40501)) && (!defined(_MSC_VER) || (_MSC_VER >= 1700)) +// This is available but broken in earlier Intel releases. +# undef BOOST_NO_CXX11_SCOPED_ENUMS +#endif + +// BOOST_NO_SFINAE_EXPR +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +# undef BOOST_NO_SFINAE_EXPR +#endif + +// BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +// This is available in earlier Intel releases, but breaks Multiprecision: +# undef BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#endif + +// BOOST_NO_CXX11_LAMBDAS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || (_MSC_VER >= 1600)) +# undef BOOST_NO_CXX11_LAMBDAS +#endif + +// BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) +# undef BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#endif + +// BOOST_NO_CXX11_RANGE_BASED_FOR +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40600)) && (!defined(_MSC_VER) || (_MSC_VER >= 1700)) +# undef BOOST_NO_CXX11_RANGE_BASED_FOR +#endif + +// BOOST_NO_CXX11_RAW_LITERALS +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_RAW_LITERALS +#endif + +// BOOST_NO_CXX11_UNICODE_LITERALS +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40500)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +# undef BOOST_NO_CXX11_UNICODE_LITERALS +#endif + +// BOOST_NO_CXX11_NOEXCEPT +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40600)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +// Available in earlier Intel release, but generates errors when used with +// conditional exception specifications, for example in multiprecision: +# undef BOOST_NO_CXX11_NOEXCEPT +#endif + +// BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40600)) && (!defined(_MSC_VER) || (_MSC_VER >= 9999)) +# undef BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#endif + +// BOOST_NO_CXX11_USER_DEFINED_LITERALS +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40700)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 190021730)) +# undef BOOST_NO_CXX11_USER_DEFINED_LITERALS +#endif + +// BOOST_NO_CXX11_ALIGNAS +#if (BOOST_INTEL_CXX_VERSION >= 1500) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40800)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 190021730)) +# undef BOOST_NO_CXX11_ALIGNAS +#endif + +// BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#if (BOOST_INTEL_CXX_VERSION >= 1200) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 180020827)) +# undef BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#endif + +// BOOST_NO_CXX11_INLINE_NAMESPACES +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40400)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 190021730)) +# undef BOOST_NO_CXX11_INLINE_NAMESPACES +#endif + +// BOOST_NO_CXX11_REF_QUALIFIERS +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40800)) && (!defined(_MSC_VER) || (_MSC_FULL_VER >= 190021730)) +# undef BOOST_NO_CXX11_REF_QUALIFIERS +#endif + +// BOOST_NO_CXX11_FINAL +#if (BOOST_INTEL_CXX_VERSION >= 1400) && (!defined(BOOST_INTEL_GCC_VERSION) || (BOOST_INTEL_GCC_VERSION >= 40700)) && (!defined(_MSC_VER) || (_MSC_VER >= 1700)) +# undef BOOST_NO_CXX11_FINAL +#endif + +#endif + +// +// Broken in all versions up to 15: +#define BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS + +#if defined(BOOST_INTEL_STDCXX0X) && (BOOST_INTEL_CXX_VERSION <= 1310) +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#endif + +#if defined(BOOST_INTEL_STDCXX0X) && (BOOST_INTEL_CXX_VERSION == 1400) +// A regression in Intel's compiler means that seems to be broken in this release as well as : +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_TUPLE +#endif + +#if (BOOST_INTEL_CXX_VERSION < 1200) +// +// fenv.h appears not to work with Intel prior to 12.0: +// +# define BOOST_NO_FENV_H +#endif + +// Intel 13.10 fails to access defaulted functions of a base class declared in private or protected sections, +// producing the following errors: +// error #453: protected function "..." (declared at ...") is not accessible through a "..." pointer or object +#if (BOOST_INTEL_CXX_VERSION <= 1310) +# define BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS +#endif + +#if defined(_MSC_VER) && (_MSC_VER >= 1600) +# define BOOST_HAS_STDINT_H +#endif + +#if defined(__CUDACC__) +# if defined(BOOST_GCC_CXX11) +# define BOOST_NVCC_CXX11 +# else +# define BOOST_NVCC_CXX03 +# endif +#endif + +#if defined(__LP64__) && defined(__GNUC__) && (BOOST_INTEL_CXX_VERSION >= 1310) && !defined(BOOST_NVCC_CXX03) +# define BOOST_HAS_INT128 +#endif + +#endif +// +// last known and checked version: +#if (BOOST_INTEL_CXX_VERSION > 1500) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# elif defined(_MSC_VER) +// +// We don't emit this warning any more, since we have so few +// defect macros set anyway (just the one). +// +//# pragma message("Unknown compiler version - please run the configure tests and report the results") +# endif +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/kai.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/kai.hpp new file mode 100644 index 0000000..2337e6a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/kai.hpp @@ -0,0 +1,33 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright David Abrahams 2002. +// (C) Copyright Aleksey Gurtovoy 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Kai C++ compiler setup: + +#include "boost/config/compiler/common_edg.hpp" + +# if (__KCC_VERSION <= 4001) || !defined(BOOST_STRICT_CONFIG) + // at least on Sun, the contents of is not in namespace std +# define BOOST_NO_STDC_NAMESPACE +# endif + +// see also common_edg.hpp which needs a special check for __KCC +# if !defined(_EXCEPTIONS) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +# endif + +// +// last known and checked version is 4001: +#if (__KCC_VERSION > 4001) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/metrowerks.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/metrowerks.hpp new file mode 100644 index 0000000..c930143 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/metrowerks.hpp @@ -0,0 +1,179 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright Darin Adler 2001. +// (C) Copyright Peter Dimov 2001. +// (C) Copyright David Abrahams 2001 - 2002. +// (C) Copyright Beman Dawes 2001 - 2003. +// (C) Copyright Stefan Slapeta 2004. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Metrowerks C++ compiler setup: + +// locale support is disabled when linking with the dynamic runtime +# ifdef _MSL_NO_LOCALE +# define BOOST_NO_STD_LOCALE +# endif + +# if __MWERKS__ <= 0x2301 // 5.3 +# define BOOST_NO_FUNCTION_TEMPLATE_ORDERING +# define BOOST_NO_POINTER_TO_MEMBER_CONST +# define BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS +# define BOOST_NO_MEMBER_TEMPLATE_KEYWORD +# endif + +# if __MWERKS__ <= 0x2401 // 6.2 +//# define BOOST_NO_FUNCTION_TEMPLATE_ORDERING +# endif + +# if(__MWERKS__ <= 0x2407) // 7.x +# define BOOST_NO_MEMBER_FUNCTION_SPECIALIZATIONS +# define BOOST_NO_UNREACHABLE_RETURN_DETECTION +# endif + +# if(__MWERKS__ <= 0x3003) // 8.x +# define BOOST_NO_SFINAE +# endif + +// the "|| !defined(BOOST_STRICT_CONFIG)" part should apply to the last +// tested version *only*: +# if(__MWERKS__ <= 0x3207) || !defined(BOOST_STRICT_CONFIG) // 9.6 +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +# define BOOST_NO_IS_ABSTRACT +# endif + +#if !__option(wchar_type) +# define BOOST_NO_INTRINSIC_WCHAR_T +#endif + +#if !__option(exceptions) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + +#if (__INTEL__ && _WIN32) || (__POWERPC__ && macintosh) +# if __MWERKS__ == 0x3000 +# define BOOST_COMPILER_VERSION 8.0 +# elif __MWERKS__ == 0x3001 +# define BOOST_COMPILER_VERSION 8.1 +# elif __MWERKS__ == 0x3002 +# define BOOST_COMPILER_VERSION 8.2 +# elif __MWERKS__ == 0x3003 +# define BOOST_COMPILER_VERSION 8.3 +# elif __MWERKS__ == 0x3200 +# define BOOST_COMPILER_VERSION 9.0 +# elif __MWERKS__ == 0x3201 +# define BOOST_COMPILER_VERSION 9.1 +# elif __MWERKS__ == 0x3202 +# define BOOST_COMPILER_VERSION 9.2 +# elif __MWERKS__ == 0x3204 +# define BOOST_COMPILER_VERSION 9.3 +# elif __MWERKS__ == 0x3205 +# define BOOST_COMPILER_VERSION 9.4 +# elif __MWERKS__ == 0x3206 +# define BOOST_COMPILER_VERSION 9.5 +# elif __MWERKS__ == 0x3207 +# define BOOST_COMPILER_VERSION 9.6 +# else +# define BOOST_COMPILER_VERSION __MWERKS__ +# endif +#else +# define BOOST_COMPILER_VERSION __MWERKS__ +#endif + +// +// C++0x features +// +// See boost\config\suffix.hpp for BOOST_NO_LONG_LONG +// +#if __MWERKS__ > 0x3206 && __option(rvalue_refs) +# define BOOST_HAS_RVALUE_REFS +#else +# define BOOST_NO_CXX11_RVALUE_REFERENCES +#endif +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_VARIADIC_MACROS +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#define BOOST_COMPILER "Metrowerks CodeWarrior C++ version " BOOST_STRINGIZE(BOOST_COMPILER_VERSION) + +// +// versions check: +// we don't support Metrowerks prior to version 5.3: +#if __MWERKS__ < 0x2301 +# error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version: +#if (__MWERKS__ > 0x3205) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + + + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/mpw.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/mpw.hpp new file mode 100644 index 0000000..76045bc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/mpw.hpp @@ -0,0 +1,121 @@ +// (C) Copyright John Maddock 2001 - 2002. +// (C) Copyright Aleksey Gurtovoy 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// MPW C++ compilers setup: + +# if defined(__SC__) +# define BOOST_COMPILER "MPW SCpp version " BOOST_STRINGIZE(__SC__) +# elif defined(__MRC__) +# define BOOST_COMPILER "MPW MrCpp version " BOOST_STRINGIZE(__MRC__) +# else +# error "Using MPW compiler configuration by mistake. Please update." +# endif + +// +// MPW 8.90: +// +#if (MPW_CPLUS <= 0x890) || !defined(BOOST_STRICT_CONFIG) +# define BOOST_NO_CV_SPECIALIZATIONS +# define BOOST_NO_DEPENDENT_NESTED_DERIVATIONS +# define BOOST_NO_DEPENDENT_TYPES_IN_TEMPLATE_VALUE_PARAMETERS +# define BOOST_NO_INCLASS_MEMBER_INITIALIZATION +# define BOOST_NO_INTRINSIC_WCHAR_T +# define BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION +# define BOOST_NO_USING_TEMPLATE + +# define BOOST_NO_CWCHAR +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + +# define BOOST_NO_STD_ALLOCATOR /* actually a bug with const reference overloading */ + +#endif + +// +// C++0x features +// +// See boost\config\suffix.hpp for BOOST_NO_LONG_LONG +// +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_VARIADIC_MACROS +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +// +// versions check: +// we don't support MPW prior to version 8.9: +#if MPW_CPLUS < 0x890 +# error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version is 0x890: +#if (MPW_CPLUS > 0x890) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/nvcc.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/nvcc.hpp new file mode 100644 index 0000000..5a04707 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/nvcc.hpp @@ -0,0 +1,24 @@ +// (C) Copyright Eric Jourdanneau, Joel Falcou 2010 +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// NVIDIA CUDA C++ compiler setup + +#ifndef BOOST_COMPILER +# define BOOST_COMPILER "NVIDIA CUDA C++ Compiler" +#endif + +// NVIDIA Specific support +// BOOST_GPU_ENABLED : Flag a function or a method as being enabled on the host and device +#define BOOST_GPU_ENABLED __host__ __device__ + +// A bug in version 7.0 of CUDA prevents use of variadic templates in some occasions +// https://svn.boost.org/trac/boost/ticket/11897 +// This is fixed in 7.5. As the following version macro was introduced in 7.5 an existance +// check is enough to detect versions < 7.5 +#if !defined(__CUDACC_VER__) || (__CUDACC_VER__ < 70500) +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/pathscale.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/pathscale.hpp new file mode 100644 index 0000000..7c211c4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/pathscale.hpp @@ -0,0 +1,114 @@ +// (C) Copyright Bryce Lelbach 2011 + +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// PathScale EKOPath C++ Compiler + +#ifndef BOOST_COMPILER +# define BOOST_COMPILER "PathScale EKOPath C++ Compiler version " __PATHSCALE__ +#endif + +#if __PATHCC__ >= 4 +# define BOOST_MSVC6_MEMBER_TEMPLATES +# define BOOST_HAS_UNISTD_H +# define BOOST_HAS_STDINT_H +# define BOOST_HAS_SIGACTION +# define BOOST_HAS_SCHED_YIELD +# define BOOST_HAS_THREADS +# define BOOST_HAS_PTHREADS +# define BOOST_HAS_PTHREAD_YIELD +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_PARTIAL_STD_ALLOCATOR +# define BOOST_HAS_NRVO +# define BOOST_HAS_NL_TYPES_H +# define BOOST_HAS_NANOSLEEP +# define BOOST_HAS_LONG_LONG +# define BOOST_HAS_LOG1P +# define BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_EXPM1 +# define BOOST_HAS_DIRENT_H +# define BOOST_HAS_CLOCK_GETTIME +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +# define BOOST_NO_CXX11_UNICODE_LITERALS +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +# define BOOST_NO_CXX11_STATIC_ASSERT +# define BOOST_NO_SFINAE_EXPR +# define BOOST_NO_CXX11_SCOPED_ENUMS +# define BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_NO_CXX11_RANGE_BASED_FOR +# define BOOST_NO_CXX11_RAW_LITERALS +# define BOOST_NO_CXX11_NULLPTR +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_NOEXCEPT +# define BOOST_NO_CXX11_LAMBDAS +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +# define BOOST_NO_CXX11_DECLTYPE +# define BOOST_NO_CXX11_DECLTYPE_N3276 +# define BOOST_NO_CXX11_CONSTEXPR +# define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +# define BOOST_NO_CXX11_CHAR32_T +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +# define BOOST_NO_CXX11_ALIGNAS +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +# define BOOST_NO_CXX11_INLINE_NAMESPACES +# define BOOST_NO_CXX11_REF_QUALIFIERS +# define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/pgi.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/pgi.hpp new file mode 100644 index 0000000..e5605c9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/pgi.hpp @@ -0,0 +1,155 @@ +// (C) Copyright Noel Belcourt 2007. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// PGI C++ compiler setup: + +#define BOOST_COMPILER_VERSION __PGIC__##__PGIC_MINOR__ +#define BOOST_COMPILER "PGI compiler version " BOOST_STRINGIZE(BOOST_COMPILER_VERSION) + +// +// Threading support: +// Turn this on unconditionally here, it will get turned off again later +// if no threading API is detected. +// + +#if __PGIC__ >= 11 + +// options requested by configure --enable-test +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_THREADS +#define BOOST_HAS_PTHREAD_YIELD +#define BOOST_HAS_NRVO +#define BOOST_HAS_LONG_LONG + +// options --enable-test wants undefined +#undef BOOST_NO_STDC_NAMESPACE +#undef BOOST_NO_EXCEPTION_STD_NAMESPACE +#undef BOOST_DEDUCED_TYPENAME + +#define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_AUTO_DECLARATIONS + +#elif __PGIC__ >= 10 + +// options requested by configure --enable-test +#define BOOST_HAS_THREADS +#define BOOST_HAS_NRVO +#define BOOST_HAS_LONG_LONG +#if defined(linux) || defined(__linux) || defined(__linux__) +# define BOOST_HAS_STDINT_H +#endif + +// options --enable-test wants undefined +#undef BOOST_NO_STDC_NAMESPACE +#undef BOOST_NO_EXCEPTION_STD_NAMESPACE +#undef BOOST_DEDUCED_TYPENAME + +#elif __PGIC__ >= 7 + +#define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#define BOOST_NO_SWPRINTF +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_AUTO_DECLARATIONS + +#else + +# error "Pgi compiler not configured - please reconfigure" + +#endif +// +// C++0x features +// +// See boost\config\suffix.hpp for BOOST_NO_LONG_LONG +// +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_NUMERIC_LIMITS +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_SWPRINTF +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_VARIADIC_MACROS +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX + +#define BOOST_NO_CXX11_HDR_UNORDERED_SET +#define BOOST_NO_CXX11_HDR_UNORDERED_MAP +#define BOOST_NO_CXX11_HDR_TYPEINDEX +#define BOOST_NO_CXX11_HDR_TYPE_TRAITS +#define BOOST_NO_CXX11_HDR_TUPLE +#define BOOST_NO_CXX11_HDR_THREAD +#define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +#define BOOST_NO_CXX11_HDR_REGEX +#define BOOST_NO_CXX11_HDR_RATIO +#define BOOST_NO_CXX11_HDR_RANDOM +#define BOOST_NO_CXX11_HDR_MUTEX +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_HDR_FUTURE +#define BOOST_NO_CXX11_HDR_FORWARD_LIST +#define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +#define BOOST_NO_CXX11_HDR_CODECVT +#define BOOST_NO_CXX11_HDR_CHRONO +#define BOOST_NO_CXX11_HDR_ARRAY +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif +// +// version check: +// probably nothing to do here? + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/sgi_mipspro.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/sgi_mipspro.hpp new file mode 100644 index 0000000..9068831 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/sgi_mipspro.hpp @@ -0,0 +1,29 @@ +// (C) Copyright John Maddock 2001 - 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// SGI C++ compiler setup: + +#define BOOST_COMPILER "SGI Irix compiler version " BOOST_STRINGIZE(_COMPILER_VERSION) + +#include "boost/config/compiler/common_edg.hpp" + +// +// Threading support: +// Turn this on unconditionally here, it will get turned off again later +// if no threading API is detected. +// +#define BOOST_HAS_THREADS +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP + +#undef BOOST_NO_SWPRINTF +#undef BOOST_DEDUCED_TYPENAME + +// +// version check: +// probably nothing to do here? + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/sunpro_cc.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/sunpro_cc.hpp new file mode 100644 index 0000000..6017660 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/sunpro_cc.hpp @@ -0,0 +1,190 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright Jens Maurer 2001 - 2003. +// (C) Copyright Peter Dimov 2002. +// (C) Copyright Aleksey Gurtovoy 2002 - 2003. +// (C) Copyright David Abrahams 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Sun C++ compiler setup: + +# if __SUNPRO_CC <= 0x500 +# define BOOST_NO_MEMBER_TEMPLATES +# define BOOST_NO_FUNCTION_TEMPLATE_ORDERING +# endif + +# if (__SUNPRO_CC <= 0x520) + // + // Sunpro 5.2 and earler: + // + // although sunpro 5.2 supports the syntax for + // inline initialization it often gets the value + // wrong, especially where the value is computed + // from other constants (J Maddock 6th May 2001) +# define BOOST_NO_INCLASS_MEMBER_INITIALIZATION + + // Although sunpro 5.2 supports the syntax for + // partial specialization, it often seems to + // bind to the wrong specialization. Better + // to disable it until suppport becomes more stable + // (J Maddock 6th May 2001). +# define BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION +# endif + +# if (__SUNPRO_CC <= 0x530) + // Requesting debug info (-g) with Boost.Python results + // in an internal compiler error for "static const" + // initialized in-class. + // >> Assertion: (../links/dbg_cstabs.cc, line 611) + // while processing ../test.cpp at line 0. + // (Jens Maurer according to Gottfried Ganssauge 04 Mar 2002) +# define BOOST_NO_INCLASS_MEMBER_INITIALIZATION + + // SunPro 5.3 has better support for partial specialization, + // but breaks when compiling std::less > + // (Jens Maurer 4 Nov 2001). + + // std::less specialization fixed as reported by George + // Heintzelman; partial specialization re-enabled + // (Peter Dimov 17 Jan 2002) + +//# define BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION + + // integral constant expressions with 64 bit numbers fail +# define BOOST_NO_INTEGRAL_INT64_T +# endif + +# if (__SUNPRO_CC < 0x570) +# define BOOST_NO_TEMPLATE_TEMPLATES + // see http://lists.boost.org/MailArchives/boost/msg47184.php + // and http://lists.boost.org/MailArchives/boost/msg47220.php +# define BOOST_NO_INCLASS_MEMBER_INITIALIZATION +# define BOOST_NO_SFINAE +# define BOOST_NO_ARRAY_TYPE_SPECIALIZATIONS +# endif +# if (__SUNPRO_CC <= 0x580) +# define BOOST_NO_IS_ABSTRACT +# endif + +# if (__SUNPRO_CC <= 0x5100) + // Sun 5.10 may not correctly value-initialize objects of + // some user defined types, as was reported in April 2010 + // (CR 6947016), and confirmed by Steve Clamage. + // (Niels Dekker, LKEB, May 2010). +# define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +# endif + +// +// Dynamic shared object (DSO) and dynamic-link library (DLL) support +// +#if __SUNPRO_CC > 0x500 +# define BOOST_SYMBOL_EXPORT __global +# define BOOST_SYMBOL_IMPORT __global +# define BOOST_SYMBOL_VISIBLE __global +#endif + +#if (__SUNPRO_CC < 0x5130) +// C++03 features in 12.4: +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_ADL_BARRIER +#define BOOST_NO_CXX11_VARIADIC_MACROS +#endif + +#if (__SUNPRO_CC < 0x5130) || (__cplusplus < 201100) +// C++11 only featuires in 12.4: +#define BOOST_NO_CXX11_AUTO_DECLARATIONS +#define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#define BOOST_NO_CXX11_CHAR16_T +#define BOOST_NO_CXX11_CHAR32_T +#define BOOST_NO_CXX11_CONSTEXPR +#define BOOST_NO_CXX11_DECLTYPE +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_RVALUE_REFERENCES +#define BOOST_NO_CXX11_SCOPED_ENUMS +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_FINAL +#endif + +#if (__SUNPRO_CC < 0x5140) || (__cplusplus < 201103) +#define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#define BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#define BOOST_NO_CXX11_REF_QUALIFIERS +#endif + +#define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +// +// C++0x features +// +# define BOOST_HAS_LONG_LONG + + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif +// +// Version +// + +#define BOOST_COMPILER "Sun compiler version " BOOST_STRINGIZE(__SUNPRO_CC) + +// +// versions check: +// we don't support sunpro prior to version 4: +#if __SUNPRO_CC < 0x400 +#error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version is 0x590: +#if (__SUNPRO_CC > 0x590) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/vacpp.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/vacpp.hpp new file mode 100644 index 0000000..6c228ea --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/vacpp.hpp @@ -0,0 +1,162 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Toon Knapen 2001 - 2003. +// (C) Copyright Lie-Quan Lee 2001. +// (C) Copyright Markus Schoepflin 2002 - 2003. +// (C) Copyright Beman Dawes 2002 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Visual Age (IBM) C++ compiler setup: + +#if __IBMCPP__ <= 501 +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +# define BOOST_NO_MEMBER_FUNCTION_SPECIALIZATIONS +#endif + +#if (__IBMCPP__ <= 502) +// Actually the compiler supports inclass member initialization but it +// requires a definition for the class member and it doesn't recognize +// it as an integral constant expression when used as a template argument. +# define BOOST_NO_INCLASS_MEMBER_INITIALIZATION +# define BOOST_NO_INTEGRAL_INT64_T +# define BOOST_NO_MEMBER_TEMPLATE_KEYWORD +#endif + +#if (__IBMCPP__ <= 600) || !defined(BOOST_STRICT_CONFIG) +# define BOOST_NO_POINTER_TO_MEMBER_TEMPLATE_PARAMETERS +#endif + +#if (__IBMCPP__ <= 1110) +// XL C++ V11.1 and earlier versions may not always value-initialize +// a temporary object T(), when T is a non-POD aggregate class type. +// Michael Wong (IBM Canada Ltd) has confirmed this issue and gave it +// high priority. -- Niels Dekker (LKEB), May 2010. +# define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +#endif + +// +// On AIX thread support seems to be indicated by _THREAD_SAFE: +// +#ifdef _THREAD_SAFE +# define BOOST_HAS_THREADS +#endif + +#define BOOST_COMPILER "IBM Visual Age version " BOOST_STRINGIZE(__IBMCPP__) + +// +// versions check: +// we don't support Visual age prior to version 5: +#if __IBMCPP__ < 500 +#error "Compiler not supported or configured - please reconfigure" +#endif +// +// last known and checked version is 1210: +#if (__IBMCPP__ > 1210) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# endif +#endif + +// Some versions of the compiler have issues with default arguments on partial specializations +#if __IBMCPP__ <= 1010 +#define BOOST_NO_PARTIAL_SPECIALIZATION_IMPLICIT_DEFAULT_ARGS +#endif + +// +// C++0x features +// +// See boost\config\suffix.hpp for BOOST_NO_LONG_LONG +// +#if ! __IBMCPP_AUTO_TYPEDEDUCTION +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#endif +#if ! __IBMCPP_UTF_LITERAL__ +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +#endif +#if ! __IBMCPP_CONSTEXPR +# define BOOST_NO_CXX11_CONSTEXPR +#endif +#if ! __IBMCPP_DECLTYPE +# define BOOST_NO_CXX11_DECLTYPE +#else +# define BOOST_HAS_DECLTYPE +#endif +#define BOOST_NO_CXX11_DECLTYPE_N3276 +#define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#define BOOST_NO_CXX11_DELETED_FUNCTIONS +#if ! __IBMCPP_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#endif +#if ! __IBMCPP_EXTERN_TEMPLATE +# define BOOST_NO_CXX11_EXTERN_TEMPLATE +#endif +#if ! __IBMCPP_VARIADIC_TEMPLATES +// not enabled separately at this time +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#endif +#define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#define BOOST_NO_CXX11_LAMBDAS +#define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#define BOOST_NO_CXX11_NOEXCEPT +#define BOOST_NO_CXX11_NULLPTR +#define BOOST_NO_CXX11_RANGE_BASED_FOR +#define BOOST_NO_CXX11_RAW_LITERALS +#define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#if ! __IBMCPP_RVALUE_REFERENCES +# define BOOST_NO_CXX11_RVALUE_REFERENCES +#endif +#if ! __IBMCPP_SCOPED_ENUM +# define BOOST_NO_CXX11_SCOPED_ENUMS +#endif +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#if ! __IBMCPP_STATIC_ASSERT +# define BOOST_NO_CXX11_STATIC_ASSERT +#endif +#define BOOST_NO_CXX11_TEMPLATE_ALIASES +#define BOOST_NO_CXX11_UNICODE_LITERALS +#if ! __IBMCPP_VARIADIC_TEMPLATES +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif +#if ! __C99_MACRO_WITH_VA_ARGS +# define BOOST_NO_CXX11_VARIADIC_MACROS +#endif +#define BOOST_NO_CXX11_ALIGNAS +#define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#define BOOST_NO_CXX11_INLINE_NAMESPACES +#define BOOST_NO_CXX11_REF_QUALIFIERS +#define BOOST_NO_CXX11_FINAL + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_binary_literals) || (__cpp_binary_literals < 201304) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_decltype_auto) || (__cpp_decltype_auto < 201304) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif +#if (__cplusplus < 201304) // There's no SD6 check for this.... +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif +#if !defined(__cpp_generic_lambdas) || (__cpp_generic_lambdas < 201304) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif +#if !defined(__cpp_init_captures) || (__cpp_init_captures < 201304) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif +#if !defined(__cpp_return_type_deduction) || (__cpp_return_type_deduction < 201304) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/visualc.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/visualc.hpp new file mode 100644 index 0000000..baaab58 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/visualc.hpp @@ -0,0 +1,298 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Darin Adler 2001 - 2002. +// (C) Copyright Peter Dimov 2001. +// (C) Copyright Aleksey Gurtovoy 2002. +// (C) Copyright David Abrahams 2002 - 2003. +// (C) Copyright Beman Dawes 2002 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. +// +// Microsoft Visual C++ compiler setup: +// +// We need to be careful with the checks in this file, as contrary +// to popular belief there are versions with _MSC_VER with the final +// digit non-zero (mainly the MIPS cross compiler). +// +// So we either test _MSC_VER >= XXXX or else _MSC_VER < XXXX. +// No other comparisons (==, >, or <=) are safe. +// + +#define BOOST_MSVC _MSC_VER + +// +// Helper macro BOOST_MSVC_FULL_VER for use in Boost code: +// +#if _MSC_FULL_VER > 100000000 +# define BOOST_MSVC_FULL_VER _MSC_FULL_VER +#else +# define BOOST_MSVC_FULL_VER (_MSC_FULL_VER * 10) +#endif + +// Attempt to suppress VC6 warnings about the length of decorated names (obsolete): +#pragma warning( disable : 4503 ) // warning: decorated name length exceeded + +#define BOOST_HAS_PRAGMA_ONCE + +// +// versions check: +// we don't support Visual C++ prior to version 7.1: +#if _MSC_VER < 1310 +# error "Compiler not supported or configured - please reconfigure" +#endif + +#if _MSC_FULL_VER < 180020827 +# define BOOST_NO_FENV_H +#endif + +#if _MSC_VER < 1400 +// although a conforming signature for swprint exists in VC7.1 +// it appears not to actually work: +# define BOOST_NO_SWPRINTF +// Our extern template tests also fail for this compiler: +# define BOOST_NO_CXX11_EXTERN_TEMPLATE +// Variadic macros do not exist for VC7.1 and lower +# define BOOST_NO_CXX11_VARIADIC_MACROS +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#endif + +#if _MSC_VER < 1500 // 140X == VC++ 8.0 +# define BOOST_NO_MEMBER_TEMPLATE_FRIENDS +#endif + +#if _MSC_VER < 1600 // 150X == VC++ 9.0 + // A bug in VC9: +# define BOOST_NO_ADL_BARRIER +#endif + + +#ifndef _NATIVE_WCHAR_T_DEFINED +# define BOOST_NO_INTRINSIC_WCHAR_T +#endif + +// +// check for exception handling support: +#if !defined(_CPPUNWIND) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + +// +// __int64 support: +// +#define BOOST_HAS_MS_INT64 +#if defined(_MSC_EXTENSIONS) || (_MSC_VER >= 1400) +# define BOOST_HAS_LONG_LONG +#else +# define BOOST_NO_LONG_LONG +#endif +#if (_MSC_VER >= 1400) && !defined(_DEBUG) +# define BOOST_HAS_NRVO +#endif +#if _MSC_VER >= 1600 // 160X == VC++ 10.0 +# define BOOST_HAS_PRAGMA_DETECT_MISMATCH +#endif +// +// disable Win32 API's if compiler extensions are +// turned off: +// +#if !defined(_MSC_EXTENSIONS) && !defined(BOOST_DISABLE_WIN32) +# define BOOST_DISABLE_WIN32 +#endif +#if !defined(_CPPRTTI) && !defined(BOOST_NO_RTTI) +# define BOOST_NO_RTTI +#endif + +// +// TR1 features: +// +#if _MSC_VER >= 1700 +// # define BOOST_HAS_TR1_HASH // don't know if this is true yet. +// # define BOOST_HAS_TR1_TYPE_TRAITS // don't know if this is true yet. +# define BOOST_HAS_TR1_UNORDERED_MAP +# define BOOST_HAS_TR1_UNORDERED_SET +#endif + +// +// C++0x features +// +// See above for BOOST_NO_LONG_LONG + +// C++ features supported by VC++ 10 (aka 2010) +// +#if _MSC_VER < 1600 +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +# define BOOST_NO_CXX11_LAMBDAS +# define BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_NO_CXX11_STATIC_ASSERT +# define BOOST_NO_CXX11_NULLPTR +# define BOOST_NO_CXX11_DECLTYPE +#endif // _MSC_VER < 1600 + +#if _MSC_VER >= 1600 +# define BOOST_HAS_STDINT_H +#endif + +// C++11 features supported by VC++ 11 (aka 2012) +// +#if _MSC_VER < 1700 +# define BOOST_NO_CXX11_FINAL +# define BOOST_NO_CXX11_RANGE_BASED_FOR +# define BOOST_NO_CXX11_SCOPED_ENUMS +#endif // _MSC_VER < 1700 + +// C++11 features supported by VC++ 12 (aka 2013). +// +#if _MSC_FULL_VER < 180020827 +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +# define BOOST_NO_CXX11_RAW_LITERALS +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +# define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +# define BOOST_NO_CXX11_DECLTYPE_N3276 +#endif + +// C++11 features supported by VC++ 14 (aka 2015) +// +#if (_MSC_FULL_VER < 190023026) +# define BOOST_NO_CXX11_NOEXCEPT +# define BOOST_NO_CXX11_REF_QUALIFIERS +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +# define BOOST_NO_CXX11_ALIGNAS +# define BOOST_NO_CXX11_INLINE_NAMESPACES +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +# define BOOST_NO_CXX11_UNICODE_LITERALS +# define BOOST_NO_CXX14_DECLTYPE_AUTO +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +# define BOOST_NO_CXX14_BINARY_LITERALS +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif + +// MSVC including version 14 has not yet completely +// implemented value-initialization, as is reported: +// "VC++ does not value-initialize members of derived classes without +// user-declared constructor", reported in 2009 by Sylvester Hesp: +// https://connect.microsoft.com/VisualStudio/feedback/details/484295 +// "Presence of copy constructor breaks member class initialization", +// reported in 2009 by Alex Vakulenko: +// https://connect.microsoft.com/VisualStudio/feedback/details/499606 +// "Value-initialization in new-expression", reported in 2005 by +// Pavel Kuznetsov (MetaCommunications Engineering): +// https://connect.microsoft.com/VisualStudio/feedback/details/100744 +// Reported again by John Maddock in 2015 for VC14: +// https://connect.microsoft.com/VisualStudio/feedback/details/1582233/c-subobjects-still-not-value-initialized-correctly +// See also: http://www.boost.org/libs/utility/value_init.htm#compiler_issues +// (Niels Dekker, LKEB, May 2010) +#define BOOST_NO_COMPLETE_VALUE_INITIALIZATION +// C++11 features not supported by any versions +#define BOOST_NO_SFINAE_EXPR +#define BOOST_NO_TWO_PHASE_NAME_LOOKUP +// +// This is somewhat supported in VC14, but we may need to wait for +// a service release before enabling: +// +#define BOOST_NO_CXX11_CONSTEXPR + +// C++ 14: +#if !defined(__cpp_aggregate_nsdmi) || (__cpp_aggregate_nsdmi < 201304) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif +#if !defined(__cpp_constexpr) || (__cpp_constexpr < 201304) +# define BOOST_NO_CXX14_CONSTEXPR +#endif +#if !defined(__cpp_variable_templates) || (__cpp_variable_templates < 201304) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +// +// prefix and suffix headers: +// +#ifndef BOOST_ABI_PREFIX +# define BOOST_ABI_PREFIX "boost/config/abi/msvc_prefix.hpp" +#endif +#ifndef BOOST_ABI_SUFFIX +# define BOOST_ABI_SUFFIX "boost/config/abi/msvc_suffix.hpp" +#endif + +#ifndef BOOST_COMPILER +// TODO: +// these things are mostly bogus. 1200 means version 12.0 of the compiler. The +// artificial versions assigned to them only refer to the versions of some IDE +// these compilers have been shipped with, and even that is not all of it. Some +// were shipped with freely downloadable SDKs, others as crosscompilers in eVC. +// IOW, you can't use these 'versions' in any sensible way. Sorry. +# if defined(UNDER_CE) +# if _MSC_VER < 1400 + // Note: I'm not aware of any CE compiler with version 13xx +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown EVC++ compiler version - please run the configure tests and report the results" +# else +# pragma message("Unknown EVC++ compiler version - please run the configure tests and report the results") +# endif +# elif _MSC_VER < 1500 +# define BOOST_COMPILER_VERSION evc8 +# elif _MSC_VER < 1600 +# define BOOST_COMPILER_VERSION evc9 +# elif _MSC_VER < 1700 +# define BOOST_COMPILER_VERSION evc10 +# elif _MSC_VER < 1800 +# define BOOST_COMPILER_VERSION evc11 +# elif _MSC_VER < 1900 +# define BOOST_COMPILER_VERSION evc12 +# elif _MSC_VER < 2000 +# define BOOST_COMPILER_VERSION evc14 +# else +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown EVC++ compiler version - please run the configure tests and report the results" +# else +# pragma message("Unknown EVC++ compiler version - please run the configure tests and report the results") +# endif +# endif +# else +# if _MSC_VER < 1310 + // Note: Versions up to 7.0 aren't supported. +# define BOOST_COMPILER_VERSION 5.0 +# elif _MSC_VER < 1300 +# define BOOST_COMPILER_VERSION 6.0 +# elif _MSC_VER < 1310 +# define BOOST_COMPILER_VERSION 7.0 +# elif _MSC_VER < 1400 +# define BOOST_COMPILER_VERSION 7.1 +# elif _MSC_VER < 1500 +# define BOOST_COMPILER_VERSION 8.0 +# elif _MSC_VER < 1600 +# define BOOST_COMPILER_VERSION 9.0 +# elif _MSC_VER < 1700 +# define BOOST_COMPILER_VERSION 10.0 +# elif _MSC_VER < 1800 +# define BOOST_COMPILER_VERSION 11.0 +# elif _MSC_VER < 1900 +# define BOOST_COMPILER_VERSION 12.0 +# elif _MSC_VER < 2000 +# define BOOST_COMPILER_VERSION 14.0 +# else +# define BOOST_COMPILER_VERSION _MSC_VER +# endif +# endif + +# define BOOST_COMPILER "Microsoft Visual C++ version " BOOST_STRINGIZE(BOOST_COMPILER_VERSION) +#endif + +// +// last known and checked version is 19.00.23026 (VC++ 2015 RTM): +#if (_MSC_VER > 1900) +# if defined(BOOST_ASSERT_CONFIG) +# error "Unknown compiler version - please run the configure tests and report the results" +# else +# pragma message("Unknown compiler version - please run the configure tests and report the results") +# endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/compiler/xlcpp.hpp b/thirdparty/source/boost_1_61_0/boost/config/compiler/xlcpp.hpp new file mode 100644 index 0000000..e369ece --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/compiler/xlcpp.hpp @@ -0,0 +1,258 @@ +// (C) Copyright Douglas Gregor 2010 +// +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// compiler setup for IBM XL C/C++ for Linux (Little Endian) based on clang. + +#define BOOST_HAS_PRAGMA_ONCE + +// Detecting `-fms-extension` compiler flag assuming that _MSC_VER defined when that flag is used. +#if defined (_MSC_VER) && (__clang_major__ > 3 || (__clang_major__ == 3 && __clang_minor__ >= 4)) +# define BOOST_HAS_PRAGMA_DETECT_MISMATCH +#endif + +// When compiling with clang before __has_extension was defined, +// even if one writes 'defined(__has_extension) && __has_extension(xxx)', +// clang reports a compiler error. So the only workaround found is: + +#ifndef __has_extension +#define __has_extension __has_feature +#endif + +#if !__has_feature(cxx_exceptions) && !defined(BOOST_NO_EXCEPTIONS) +# define BOOST_NO_EXCEPTIONS +#endif + +#if !__has_feature(cxx_rtti) && !defined(BOOST_NO_RTTI) +# define BOOST_NO_RTTI +#endif + +#if !__has_feature(cxx_rtti) && !defined(BOOST_NO_TYPEID) +# define BOOST_NO_TYPEID +#endif + +#if defined(__int64) && !defined(__GNUC__) +# define BOOST_HAS_MS_INT64 +#endif + +#define BOOST_HAS_NRVO + +// Branch prediction hints +#if defined(__has_builtin) +#if __has_builtin(__builtin_expect) +#define BOOST_LIKELY(x) __builtin_expect(x, 1) +#define BOOST_UNLIKELY(x) __builtin_expect(x, 0) +#endif +#endif + +// Clang supports "long long" in all compilation modes. +#define BOOST_HAS_LONG_LONG + +// +// Dynamic shared object (DSO) and dynamic-link library (DLL) support +// +#if !defined(_WIN32) && !defined(__WIN32__) && !defined(WIN32) +# define BOOST_SYMBOL_EXPORT __attribute__((__visibility__("default"))) +# define BOOST_SYMBOL_IMPORT +# define BOOST_SYMBOL_VISIBLE __attribute__((__visibility__("default"))) +#endif + +// +// The BOOST_FALLTHROUGH macro can be used to annotate implicit fall-through +// between switch labels. +// +#if __cplusplus >= 201103L && defined(__has_warning) +# if __has_feature(cxx_attributes) && __has_warning("-Wimplicit-fallthrough") +# define BOOST_FALLTHROUGH [[clang::fallthrough]] +# endif +#endif + +#if !__has_feature(cxx_auto_type) +# define BOOST_NO_CXX11_AUTO_DECLARATIONS +# define BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS +#endif + +// +// Currently clang on Windows using VC++ RTL does not support C++11's char16_t or char32_t +// +#if defined(_MSC_VER) || !(defined(__GXX_EXPERIMENTAL_CXX0X__) || __cplusplus >= 201103L) +# define BOOST_NO_CXX11_CHAR16_T +# define BOOST_NO_CXX11_CHAR32_T +#endif + +#if !__has_feature(cxx_constexpr) +# define BOOST_NO_CXX11_CONSTEXPR +#endif + +#if !__has_feature(cxx_decltype) +# define BOOST_NO_CXX11_DECLTYPE +#endif + +#if !__has_feature(cxx_decltype_incomplete_return_types) +# define BOOST_NO_CXX11_DECLTYPE_N3276 +#endif + +#if !__has_feature(cxx_defaulted_functions) +# define BOOST_NO_CXX11_DEFAULTED_FUNCTIONS +#endif + +#if !__has_feature(cxx_deleted_functions) +# define BOOST_NO_CXX11_DELETED_FUNCTIONS +#endif + +#if !__has_feature(cxx_explicit_conversions) +# define BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS +#endif + +#if !__has_feature(cxx_default_function_template_args) +# define BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS +#endif + +#if !__has_feature(cxx_generalized_initializers) +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +#endif + +#if !__has_feature(cxx_lambdas) +# define BOOST_NO_CXX11_LAMBDAS +#endif + +#if !__has_feature(cxx_local_type_template_args) +# define BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS +#endif + +#if !__has_feature(cxx_noexcept) +# define BOOST_NO_CXX11_NOEXCEPT +#endif + +#if !__has_feature(cxx_nullptr) +# define BOOST_NO_CXX11_NULLPTR +#endif + +#if !__has_feature(cxx_range_for) +# define BOOST_NO_CXX11_RANGE_BASED_FOR +#endif + +#if !__has_feature(cxx_raw_string_literals) +# define BOOST_NO_CXX11_RAW_LITERALS +#endif + +#if !__has_feature(cxx_reference_qualified_functions) +# define BOOST_NO_CXX11_REF_QUALIFIERS +#endif + +#if !__has_feature(cxx_generalized_initializers) +# define BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX +#endif + +#if !__has_feature(cxx_rvalue_references) +# define BOOST_NO_CXX11_RVALUE_REFERENCES +#endif + +#if !__has_feature(cxx_strong_enums) +# define BOOST_NO_CXX11_SCOPED_ENUMS +#endif + +#if !__has_feature(cxx_static_assert) +# define BOOST_NO_CXX11_STATIC_ASSERT +#endif + +#if !__has_feature(cxx_alias_templates) +# define BOOST_NO_CXX11_TEMPLATE_ALIASES +#endif + +#if !__has_feature(cxx_unicode_literals) +# define BOOST_NO_CXX11_UNICODE_LITERALS +#endif + +#if !__has_feature(cxx_variadic_templates) +# define BOOST_NO_CXX11_VARIADIC_TEMPLATES +#endif + +#if !__has_feature(cxx_user_literals) +# define BOOST_NO_CXX11_USER_DEFINED_LITERALS +#endif + +#if !__has_feature(cxx_alignas) +# define BOOST_NO_CXX11_ALIGNAS +#endif + +#if !__has_feature(cxx_trailing_return) +# define BOOST_NO_CXX11_TRAILING_RESULT_TYPES +#endif + +#if !__has_feature(cxx_inline_namespaces) +# define BOOST_NO_CXX11_INLINE_NAMESPACES +#endif + +#if !__has_feature(cxx_override_control) +# define BOOST_NO_CXX11_FINAL +#endif + +#if !(__has_feature(__cxx_binary_literals__) || __has_extension(__cxx_binary_literals__)) +# define BOOST_NO_CXX14_BINARY_LITERALS +#endif + +#if !__has_feature(__cxx_decltype_auto__) +# define BOOST_NO_CXX14_DECLTYPE_AUTO +#endif + +#if !__has_feature(__cxx_aggregate_nsdmi__) +# define BOOST_NO_CXX14_AGGREGATE_NSDMI +#endif + +#if !__has_feature(__cxx_init_captures__) +# define BOOST_NO_CXX14_INITIALIZED_LAMBDA_CAPTURES +#endif + +#if !__has_feature(__cxx_generic_lambdas__) +# define BOOST_NO_CXX14_GENERIC_LAMBDAS +#endif + +// clang < 3.5 has a defect with dependent type, like following. +// +// template +// constexpr typename enable_if >::type foo(T &) +// { } // error: no return statement in constexpr function +// +// This issue also affects C++11 mode, but C++11 constexpr requires return stmt. +// Therefore we don't care such case. +// +// Note that we can't check Clang version directly as the numbering system changes depending who's +// creating the Clang release (see https://github.com/boostorg/config/pull/39#issuecomment-59927873) +// so instead verify that we have a feature that was introduced at the same time as working C++14 +// constexpr (generic lambda's in this case): +// +#if !__has_feature(__cxx_generic_lambdas__) || !__has_feature(__cxx_relaxed_constexpr__) +# define BOOST_NO_CXX14_CONSTEXPR +#endif + +#if !__has_feature(__cxx_return_type_deduction__) +# define BOOST_NO_CXX14_RETURN_TYPE_DEDUCTION +#endif + +#if !__has_feature(__cxx_variable_templates__) +# define BOOST_NO_CXX14_VARIABLE_TEMPLATES +#endif + +#if __cplusplus < 201400 +// All versions with __cplusplus above this value seem to support this: +# define BOOST_NO_CXX14_DIGIT_SEPARATORS +#endif + + +// Unused attribute: +#if defined(__GNUC__) && (__GNUC__ >= 4) +# define BOOST_ATTRIBUTE_UNUSED __attribute__((unused)) +#endif + +#ifndef BOOST_COMPILER +# define BOOST_COMPILER "Clang version " __clang_version__ +#endif + +// Macro used to identify the Clang compiler. +#define BOOST_CLANG 1 + diff --git a/thirdparty/source/boost_1_61_0/boost/config/no_tr1/cmath.hpp b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/cmath.hpp new file mode 100644 index 0000000..d8268d8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/cmath.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2008. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// The aim of this header is just to include but to do +// so in a way that does not result in recursive inclusion of +// the Boost TR1 components if boost/tr1/tr1/cmath is in the +// include search path. We have to do this to avoid circular +// dependencies: +// + +#ifndef BOOST_CONFIG_CMATH +# define BOOST_CONFIG_CMATH + +# ifndef BOOST_TR1_NO_RECURSION +# define BOOST_TR1_NO_RECURSION +# define BOOST_CONFIG_NO_CMATH_RECURSION +# endif + +# include + +# ifdef BOOST_CONFIG_NO_CMATH_RECURSION +# undef BOOST_TR1_NO_RECURSION +# undef BOOST_CONFIG_NO_CMATH_RECURSION +# endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/no_tr1/complex.hpp b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/complex.hpp new file mode 100644 index 0000000..ca20092 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/complex.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// The aim of this header is just to include but to do +// so in a way that does not result in recursive inclusion of +// the Boost TR1 components if boost/tr1/tr1/complex is in the +// include search path. We have to do this to avoid circular +// dependencies: +// + +#ifndef BOOST_CONFIG_COMPLEX +# define BOOST_CONFIG_COMPLEX + +# ifndef BOOST_TR1_NO_RECURSION +# define BOOST_TR1_NO_RECURSION +# define BOOST_CONFIG_NO_COMPLEX_RECURSION +# endif + +# include + +# ifdef BOOST_CONFIG_NO_COMPLEX_RECURSION +# undef BOOST_TR1_NO_RECURSION +# undef BOOST_CONFIG_NO_COMPLEX_RECURSION +# endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/no_tr1/functional.hpp b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/functional.hpp new file mode 100644 index 0000000..e395efc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/functional.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// The aim of this header is just to include but to do +// so in a way that does not result in recursive inclusion of +// the Boost TR1 components if boost/tr1/tr1/functional is in the +// include search path. We have to do this to avoid circular +// dependencies: +// + +#ifndef BOOST_CONFIG_FUNCTIONAL +# define BOOST_CONFIG_FUNCTIONAL + +# ifndef BOOST_TR1_NO_RECURSION +# define BOOST_TR1_NO_RECURSION +# define BOOST_CONFIG_NO_FUNCTIONAL_RECURSION +# endif + +# include + +# ifdef BOOST_CONFIG_NO_FUNCTIONAL_RECURSION +# undef BOOST_TR1_NO_RECURSION +# undef BOOST_CONFIG_NO_FUNCTIONAL_RECURSION +# endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/no_tr1/memory.hpp b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/memory.hpp new file mode 100644 index 0000000..2b5d208 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/memory.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// The aim of this header is just to include but to do +// so in a way that does not result in recursive inclusion of +// the Boost TR1 components if boost/tr1/tr1/memory is in the +// include search path. We have to do this to avoid circular +// dependencies: +// + +#ifndef BOOST_CONFIG_MEMORY +# define BOOST_CONFIG_MEMORY + +# ifndef BOOST_TR1_NO_RECURSION +# define BOOST_TR1_NO_RECURSION +# define BOOST_CONFIG_NO_MEMORY_RECURSION +# endif + +# include + +# ifdef BOOST_CONFIG_NO_MEMORY_RECURSION +# undef BOOST_TR1_NO_RECURSION +# undef BOOST_CONFIG_NO_MEMORY_RECURSION +# endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/no_tr1/utility.hpp b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/utility.hpp new file mode 100644 index 0000000..dea8f11 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/no_tr1/utility.hpp @@ -0,0 +1,28 @@ +// (C) Copyright John Maddock 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// The aim of this header is just to include but to do +// so in a way that does not result in recursive inclusion of +// the Boost TR1 components if boost/tr1/tr1/utility is in the +// include search path. We have to do this to avoid circular +// dependencies: +// + +#ifndef BOOST_CONFIG_UTILITY +# define BOOST_CONFIG_UTILITY + +# ifndef BOOST_TR1_NO_RECURSION +# define BOOST_TR1_NO_RECURSION +# define BOOST_CONFIG_NO_UTILITY_RECURSION +# endif + +# include + +# ifdef BOOST_CONFIG_NO_UTILITY_RECURSION +# undef BOOST_TR1_NO_RECURSION +# undef BOOST_CONFIG_NO_UTILITY_RECURSION +# endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/aix.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/aix.hpp new file mode 100644 index 0000000..894ef42 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/aix.hpp @@ -0,0 +1,33 @@ +// (C) Copyright John Maddock 2001 - 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// IBM/Aix specific config options: + +#define BOOST_PLATFORM "IBM Aix" + +#define BOOST_HAS_UNISTD_H +#define BOOST_HAS_NL_TYPES_H +#define BOOST_HAS_NANOSLEEP +#define BOOST_HAS_CLOCK_GETTIME + +// This needs support in "boost/cstdint.hpp" exactly like FreeBSD. +// This platform has header named which includes all +// the things needed. +#define BOOST_HAS_STDINT_H + +// Threading API's: +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_PTHREAD_DELAY_NP +#define BOOST_HAS_SCHED_YIELD +//#define BOOST_HAS_PTHREAD_YIELD + +// boilerplate code: +#include + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/amigaos.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/amigaos.hpp new file mode 100644 index 0000000..34bcf41 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/amigaos.hpp @@ -0,0 +1,15 @@ +// (C) Copyright John Maddock 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +#define BOOST_PLATFORM "AmigaOS" + +#define BOOST_DISABLE_THREADS +#define BOOST_NO_CWCHAR +#define BOOST_NO_STD_WSTRING +#define BOOST_NO_INTRINSIC_WCHAR_T + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/beos.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/beos.hpp new file mode 100644 index 0000000..48c3d8d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/beos.hpp @@ -0,0 +1,26 @@ +// (C) Copyright John Maddock 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// BeOS specific config options: + +#define BOOST_PLATFORM "BeOS" + +#define BOOST_NO_CWCHAR +#define BOOST_NO_CWCTYPE +#define BOOST_HAS_UNISTD_H + +#define BOOST_HAS_BETHREADS + +#ifndef BOOST_DISABLE_THREADS +# define BOOST_HAS_THREADS +#endif + +// boilerplate code: +#include + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/bsd.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/bsd.hpp new file mode 100644 index 0000000..a014297 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/bsd.hpp @@ -0,0 +1,86 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Darin Adler 2001. +// (C) Copyright Douglas Gregor 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// generic BSD config options: + +#if !defined(__FreeBSD__) && !defined(__NetBSD__) && !defined(__OpenBSD__) && !defined(__DragonFly__) +#error "This platform is not BSD" +#endif + +#ifdef __FreeBSD__ +#define BOOST_PLATFORM "FreeBSD " BOOST_STRINGIZE(__FreeBSD__) +#elif defined(__NetBSD__) +#define BOOST_PLATFORM "NetBSD " BOOST_STRINGIZE(__NetBSD__) +#elif defined(__OpenBSD__) +#define BOOST_PLATFORM "OpenBSD " BOOST_STRINGIZE(__OpenBSD__) +#elif defined(__DragonFly__) +#define BOOST_PLATFORM "DragonFly " BOOST_STRINGIZE(__DragonFly__) +#endif + +// +// is this the correct version check? +// FreeBSD has but does not +// advertise the fact in : +// +#if (defined(__FreeBSD__) && (__FreeBSD__ >= 3)) || defined(__DragonFly__) +# define BOOST_HAS_NL_TYPES_H +#endif + +// +// FreeBSD 3.x has pthreads support, but defines _POSIX_THREADS in +// and not in +// +#if (defined(__FreeBSD__) && (__FreeBSD__ <= 3))\ + || defined(__OpenBSD__) || defined(__DragonFly__) +# define BOOST_HAS_PTHREADS +#endif + +// +// No wide character support in the BSD header files: +// +#if defined(__NetBSD__) +#define __NetBSD_GCC__ (__GNUC__ * 1000000 \ + + __GNUC_MINOR__ * 1000 \ + + __GNUC_PATCHLEVEL__) +// XXX - the following is required until c++config.h +// defines _GLIBCXX_HAVE_SWPRINTF and friends +// or the preprocessor conditionals are removed +// from the cwchar header. +#define _GLIBCXX_HAVE_SWPRINTF 1 +#endif + +#if !((defined(__FreeBSD__) && (__FreeBSD__ >= 5)) \ + || (defined(__NetBSD_GCC__) && (__NetBSD_GCC__ >= 2095003)) || defined(__DragonFly__)) +# define BOOST_NO_CWCHAR +#endif +// +// The BSD has macros only, no functions: +// +#if !defined(__OpenBSD__) || defined(__DragonFly__) +# define BOOST_NO_CTYPE_FUNCTIONS +#endif + +// +// thread API's not auto detected: +// +#define BOOST_HAS_SCHED_YIELD +#define BOOST_HAS_NANOSLEEP +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +#define BOOST_HAS_SIGACTION + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include + + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/cloudabi.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/cloudabi.hpp new file mode 100644 index 0000000..bed7b63 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/cloudabi.hpp @@ -0,0 +1,18 @@ +// Copyright Nuxi, https://nuxi.nl/ 2015. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_PLATFORM "CloudABI" + +#define BOOST_HAS_DIRENT_H +#define BOOST_HAS_STDINT_H +#define BOOST_HAS_UNISTD_H + +#define BOOST_HAS_CLOCK_GETTIME +#define BOOST_HAS_EXPM1 +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_LOG1P +#define BOOST_HAS_NANOSLEEP +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_SCHED_YIELD diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/cray.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/cray.hpp new file mode 100644 index 0000000..5c476e4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/cray.hpp @@ -0,0 +1,18 @@ +// (C) Copyright John Maddock 2011. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// See http://www.boost.org for most recent version. + +// SGI Irix specific config options: + +#define BOOST_PLATFORM "Cray" + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/cygwin.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/cygwin.hpp new file mode 100644 index 0000000..b7ef572 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/cygwin.hpp @@ -0,0 +1,58 @@ +// (C) Copyright John Maddock 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// cygwin specific config options: + +#define BOOST_PLATFORM "Cygwin" +#define BOOST_HAS_DIRENT_H +#define BOOST_HAS_LOG1P +#define BOOST_HAS_EXPM1 + +// +// Threading API: +// See if we have POSIX threads, if we do use them, otherwise +// revert to native Win threads. +#define BOOST_HAS_UNISTD_H +#include +#if defined(_POSIX_THREADS) && (_POSIX_THREADS+0 >= 0) && !defined(BOOST_HAS_WINTHREADS) +# define BOOST_HAS_PTHREADS +# define BOOST_HAS_SCHED_YIELD +# define BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_SIGACTION +#else +# if !defined(BOOST_HAS_WINTHREADS) +# define BOOST_HAS_WINTHREADS +# endif +# define BOOST_HAS_FTIME +#endif + +// +// find out if we have a stdint.h, there should be a better way to do this: +// +#include +#ifdef _STDINT_H +#define BOOST_HAS_STDINT_H +#endif + +/// Cygwin has no fenv.h +#define BOOST_NO_FENV_H + +// boilerplate code: +#include + +// +// Cygwin lies about XSI conformance, there is no nl_types.h: +// +#ifdef BOOST_HAS_NL_TYPES_H +# undef BOOST_HAS_NL_TYPES_H +#endif + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/haiku.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/haiku.hpp new file mode 100644 index 0000000..750866c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/haiku.hpp @@ -0,0 +1,31 @@ +// (C) Copyright Jessica Hamilton 2014. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Haiku specific config options: + +#define BOOST_PLATFORM "Haiku" + +#define BOOST_HAS_UNISTD_H +#define BOOST_HAS_STDINT_H + +#ifndef BOOST_DISABLE_THREADS +# define BOOST_HAS_THREADS +#endif + +#define BOOST_NO_CXX11_HDR_TYPE_TRAITS +#define BOOST_NO_CXX11_ATOMIC_SMART_PTR +#define BOOST_NO_CXX11_STATIC_ASSERT +#define BOOST_NO_CXX11_VARIADIC_MACROS + +// +// thread API's not auto detected: +// +#define BOOST_HAS_SCHED_YIELD +#define BOOST_HAS_GETTIMEOFDAY + +// boilerplate code: +#include diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/hpux.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/hpux.hpp new file mode 100644 index 0000000..19ce68e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/hpux.hpp @@ -0,0 +1,87 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001 - 2003. +// (C) Copyright David Abrahams 2002. +// (C) Copyright Toon Knapen 2003. +// (C) Copyright Boris Gubenko 2006 - 2007. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// hpux specific config options: + +#define BOOST_PLATFORM "HP-UX" + +// In principle, HP-UX has a nice under the name +// However, it has the following problem: +// Use of UINT32_C(0) results in "0u l" for the preprocessed source +// (verifyable with gcc 2.95.3) +#if (defined(__GNUC__) && (__GNUC__ >= 3)) || defined(__HP_aCC) +# define BOOST_HAS_STDINT_H +#endif + +#if !(defined(__HP_aCC) || !defined(_INCLUDE__STDC_A1_SOURCE)) +# define BOOST_NO_SWPRINTF +#endif +#if defined(__HP_aCC) && !defined(_INCLUDE__STDC_A1_SOURCE) +# define BOOST_NO_CWCTYPE +#endif + +#if defined(__GNUC__) +# if (__GNUC__ < 3) || ((__GNUC__ == 3) && (__GNUC_MINOR__ < 3)) + // GNU C on HP-UX does not support threads (checked up to gcc 3.3) +# define BOOST_DISABLE_THREADS +# elif !defined(BOOST_DISABLE_THREADS) + // threads supported from gcc-3.3 onwards: +# define BOOST_HAS_THREADS +# define BOOST_HAS_PTHREADS +# endif +#elif defined(__HP_aCC) && !defined(BOOST_DISABLE_THREADS) +# define BOOST_HAS_PTHREADS +#endif + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include + +// the following are always available: +#ifndef BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_GETTIMEOFDAY +#endif +#ifndef BOOST_HAS_SCHED_YIELD +# define BOOST_HAS_SCHED_YIELD +#endif +#ifndef BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +#endif +#ifndef BOOST_HAS_NL_TYPES_H +# define BOOST_HAS_NL_TYPES_H +#endif +#ifndef BOOST_HAS_NANOSLEEP +# define BOOST_HAS_NANOSLEEP +#endif +#ifndef BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_GETTIMEOFDAY +#endif +#ifndef BOOST_HAS_DIRENT_H +# define BOOST_HAS_DIRENT_H +#endif +#ifndef BOOST_HAS_CLOCK_GETTIME +# define BOOST_HAS_CLOCK_GETTIME +#endif +#ifndef BOOST_HAS_SIGACTION +# define BOOST_HAS_SIGACTION +#endif +#ifndef BOOST_HAS_NRVO +# ifndef __parisc +# define BOOST_HAS_NRVO +# endif +#endif +#ifndef BOOST_HAS_LOG1P +# define BOOST_HAS_LOG1P +#endif +#ifndef BOOST_HAS_EXPM1 +# define BOOST_HAS_EXPM1 +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/irix.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/irix.hpp new file mode 100644 index 0000000..aeae49c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/irix.hpp @@ -0,0 +1,31 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// See http://www.boost.org for most recent version. + +// SGI Irix specific config options: + +#define BOOST_PLATFORM "SGI Irix" + +#define BOOST_NO_SWPRINTF +// +// these are not auto detected by POSIX feature tests: +// +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE + +#ifdef __GNUC__ + // GNU C on IRIX does not support threads (checked up to gcc 3.3) +# define BOOST_DISABLE_THREADS +#endif + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/linux.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/linux.hpp new file mode 100644 index 0000000..6fa5f45 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/linux.hpp @@ -0,0 +1,105 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// linux specific config options: + +#define BOOST_PLATFORM "linux" + +// make sure we have __GLIBC_PREREQ if available at all +#ifdef __cplusplus +#include +#else +#include +#endif + +// +// added to glibc 2.1.1 +// We can only test for 2.1 though: +// +#if defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1))) + // defines int64_t unconditionally, but defines + // int64_t only if __GNUC__. Thus, assume a fully usable + // only when using GCC. +# if defined __GNUC__ +# define BOOST_HAS_STDINT_H +# endif +#endif + +#if defined(__LIBCOMO__) + // + // como on linux doesn't have std:: c functions: + // NOTE: versions of libcomo prior to beta28 have octal version numbering, + // e.g. version 25 is 21 (dec) + // +# if __LIBCOMO_VERSION__ <= 20 +# define BOOST_NO_STDC_NAMESPACE +# endif + +# if __LIBCOMO_VERSION__ <= 21 +# define BOOST_NO_SWPRINTF +# endif + +#endif + +// +// If glibc is past version 2 then we definitely have +// gettimeofday, earlier versions may or may not have it: +// +#if defined(__GLIBC__) && (__GLIBC__ >= 2) +# define BOOST_HAS_GETTIMEOFDAY +#endif + +#ifdef __USE_POSIX199309 +# define BOOST_HAS_NANOSLEEP +#endif + +#if defined(__GLIBC__) && defined(__GLIBC_PREREQ) +// __GLIBC_PREREQ is available since 2.1.2 + + // swprintf is available since glibc 2.2.0 +# if !__GLIBC_PREREQ(2,2) || (!defined(__USE_ISOC99) && !defined(__USE_UNIX98)) +# define BOOST_NO_SWPRINTF +# endif +#else +# define BOOST_NO_SWPRINTF +#endif + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include +#ifdef __USE_GNU +#define BOOST_HAS_PTHREAD_YIELD +#endif + +#ifndef __GNUC__ +// +// if the compiler is not gcc we still need to be able to parse +// the GNU system headers, some of which (mainly ) +// use GNU specific extensions: +// +# ifndef __extension__ +# define __extension__ +# endif +# ifndef __const__ +# define __const__ const +# endif +# ifndef __volatile__ +# define __volatile__ volatile +# endif +# ifndef __signed__ +# define __signed__ signed +# endif +# ifndef __typeof__ +# define __typeof__ typeof +# endif +# ifndef __inline__ +# define __inline__ inline +# endif +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/macos.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/macos.hpp new file mode 100644 index 0000000..5be4e3b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/macos.hpp @@ -0,0 +1,87 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Darin Adler 2001 - 2002. +// (C) Copyright Bill Kempf 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Mac OS specific config options: + +#define BOOST_PLATFORM "Mac OS" + +#if __MACH__ && !defined(_MSL_USING_MSL_C) + +// Using the Mac OS X system BSD-style C library. + +# ifndef BOOST_HAS_UNISTD_H +# define BOOST_HAS_UNISTD_H +# endif +// +// Begin by including our boilerplate code for POSIX +// feature detection, this is safe even when using +// the MSL as Metrowerks supply their own +// to replace the platform-native BSD one. G++ users +// should also always be able to do this on MaxOS X. +// +# include +# ifndef BOOST_HAS_STDINT_H +# define BOOST_HAS_STDINT_H +# endif + +// +// BSD runtime has pthreads, sigaction, sched_yield and gettimeofday, +// of these only pthreads are advertised in , so set the +// other options explicitly: +// +# define BOOST_HAS_SCHED_YIELD +# define BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_SIGACTION + +# if (__GNUC__ < 3) && !defined( __APPLE_CC__) + +// GCC strange "ignore std" mode works better if you pretend everything +// is in the std namespace, for the most part. + +# define BOOST_NO_STDC_NAMESPACE +# endif + +# if (__GNUC__ >= 4) + +// Both gcc and intel require these. +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_NANOSLEEP + +# endif + +#else + +// Using the MSL C library. + +// We will eventually support threads in non-Carbon builds, but we do +// not support this yet. +# if ( defined(TARGET_API_MAC_CARBON) && TARGET_API_MAC_CARBON ) || ( defined(TARGET_CARBON) && TARGET_CARBON ) + +# if !defined(BOOST_HAS_PTHREADS) +// MPTasks support is deprecated/removed from Boost: +//# define BOOST_HAS_MPTASKS +# elif ( __dest_os == __mac_os_x ) +// We are doing a Carbon/Mach-O/MSL build which has pthreads, but only the +// gettimeofday and no posix. +# define BOOST_HAS_GETTIMEOFDAY +# endif + +#ifdef BOOST_HAS_PTHREADS +# define BOOST_HAS_THREADS +#endif + +// The remote call manager depends on this. +# define BOOST_BIND_ENABLE_PASCAL + +# endif + +#endif + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/qnxnto.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/qnxnto.hpp new file mode 100644 index 0000000..b1377c8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/qnxnto.hpp @@ -0,0 +1,31 @@ +// (C) Copyright Jim Douglas 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// QNX specific config options: + +#define BOOST_PLATFORM "QNX" + +#define BOOST_HAS_UNISTD_H +#include + +// QNX claims XOpen version 5 compatibility, but doesn't have an nl_types.h +// or log1p and expm1: +#undef BOOST_HAS_NL_TYPES_H +#undef BOOST_HAS_LOG1P +#undef BOOST_HAS_EXPM1 + +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE + +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_CLOCK_GETTIME +#define BOOST_HAS_NANOSLEEP + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/solaris.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/solaris.hpp new file mode 100644 index 0000000..6e4efc9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/solaris.hpp @@ -0,0 +1,31 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// sun specific config options: + +#define BOOST_PLATFORM "Sun Solaris" + +#define BOOST_HAS_GETTIMEOFDAY + +// boilerplate code: +#define BOOST_HAS_UNISTD_H +#include + +// +// pthreads don't actually work with gcc unless _PTHREADS is defined: +// +#if defined(__GNUC__) && defined(_POSIX_THREADS) && !defined(_PTHREADS) +# undef BOOST_HAS_PTHREADS +#endif + +#define BOOST_HAS_STDINT_H +#define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +#define BOOST_HAS_LOG1P +#define BOOST_HAS_EXPM1 + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/symbian.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/symbian.hpp new file mode 100644 index 0000000..e02a778 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/symbian.hpp @@ -0,0 +1,97 @@ +// (C) Copyright Yuriy Krasnoschek 2009. +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// symbian specific config options: + + +#define BOOST_PLATFORM "Symbian" +#define BOOST_SYMBIAN 1 + + +#if defined(__S60_3X__) +// Open C / C++ plugin was introdused in this SDK, earlier versions don't have CRT / STL +# define BOOST_S60_3rd_EDITION_FP2_OR_LATER_SDK +// make sure we have __GLIBC_PREREQ if available at all +#ifdef __cplusplus +#include +#else +#include +#endif// boilerplate code: +# define BOOST_HAS_UNISTD_H +# include +// S60 SDK defines _POSIX_VERSION as POSIX.1 +# ifndef BOOST_HAS_STDINT_H +# define BOOST_HAS_STDINT_H +# endif +# ifndef BOOST_HAS_GETTIMEOFDAY +# define BOOST_HAS_GETTIMEOFDAY +# endif +# ifndef BOOST_HAS_DIRENT_H +# define BOOST_HAS_DIRENT_H +# endif +# ifndef BOOST_HAS_SIGACTION +# define BOOST_HAS_SIGACTION +# endif +# ifndef BOOST_HAS_PTHREADS +# define BOOST_HAS_PTHREADS +# endif +# ifndef BOOST_HAS_NANOSLEEP +# define BOOST_HAS_NANOSLEEP +# endif +# ifndef BOOST_HAS_SCHED_YIELD +# define BOOST_HAS_SCHED_YIELD +# endif +# ifndef BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# endif +# ifndef BOOST_HAS_LOG1P +# define BOOST_HAS_LOG1P +# endif +# ifndef BOOST_HAS_EXPM1 +# define BOOST_HAS_EXPM1 +# endif +# ifndef BOOST_POSIX_API +# define BOOST_POSIX_API +# endif +// endianess support +# include +// Symbian SDK provides _BYTE_ORDER instead of __BYTE_ORDER +# ifndef __LITTLE_ENDIAN +# ifdef _LITTLE_ENDIAN +# define __LITTLE_ENDIAN _LITTLE_ENDIAN +# else +# define __LITTLE_ENDIAN 1234 +# endif +# endif +# ifndef __BIG_ENDIAN +# ifdef _BIG_ENDIAN +# define __BIG_ENDIAN _BIG_ENDIAN +# else +# define __BIG_ENDIAN 4321 +# endif +# endif +# ifndef __BYTE_ORDER +# define __BYTE_ORDER __LITTLE_ENDIAN // Symbian is LE +# endif +// Known limitations +# define BOOST_ASIO_DISABLE_SERIAL_PORT +# define BOOST_DATE_TIME_NO_LOCALE +# define BOOST_NO_STD_WSTRING +# define BOOST_EXCEPTION_DISABLE +# define BOOST_NO_EXCEPTIONS + +#else // TODO: More platform support e.g. UIQ +# error "Unsuppoted Symbian SDK" +#endif + +#if defined(__WINSCW__) && !defined(BOOST_DISABLE_WIN32) +# define BOOST_DISABLE_WIN32 // winscw defines WIN32 macro +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/vms.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/vms.hpp new file mode 100644 index 0000000..f70efcf --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/vms.hpp @@ -0,0 +1,25 @@ +// (C) Copyright Artyom Beilis 2010. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_CONFIG_PLATFORM_VMS_HPP +#define BOOST_CONFIG_PLATFORM_VMS_HPP + +#define BOOST_PLATFORM "OpenVMS" + +#undef BOOST_HAS_STDINT_H +#define BOOST_HAS_UNISTD_H +#define BOOST_HAS_NL_TYPES_H +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_DIRENT_H +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_NANOSLEEP +#define BOOST_HAS_CLOCK_GETTIME +#define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +#define BOOST_HAS_LOG1P +#define BOOST_HAS_EXPM1 +#define BOOST_HAS_THREADS +#undef BOOST_HAS_SCHED_YIELD + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/vxworks.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/vxworks.hpp new file mode 100644 index 0000000..cdda015 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/vxworks.hpp @@ -0,0 +1,369 @@ +// (C) Copyright Dustin Spicuzza 2009. +// Adapted to vxWorks 6.9 by Peter Brockamp 2012. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Since WRS does not yet properly support boost under vxWorks +// and this file was badly outdated, but I was keen on using it, +// I patched boost myself to make things work. This has been tested +// and adapted by me for vxWorks 6.9 *only*, as I'm lacking access +// to earlier 6.X versions! The only thing I know for sure is that +// very old versions of vxWorks (namely everything below 6.x) are +// absolutely unable to use boost. This is mainly due to the completely +// outdated libraries and ancient compiler (GCC 2.96 or worse). Do +// not even think of getting this to work, a miserable failure will +// be guaranteed! +// Equally, this file has been tested for RTPs (Real Time Processes) +// only, not for DKMs (Downloadable Kernel Modules). These two types +// of executables differ largely in the available functionality of +// the C-library, STL, and so on. A DKM uses a library similar to those +// of vxWorks 5.X - with all its limitations and incompatibilities +// with respect to ANSI C++ and STL. So probably there might be problems +// with the usage of boost from DKMs. WRS or any voluteers are free to +// prove the opposite! + +// ==================================================================== +// +// Some important information regarding the usage of POSIX semaphores: +// ------------------------------------------------------------------- +// +// VxWorks as a real time operating system handles threads somewhat +// different from what "normal" OSes do, regarding their scheduling! +// This could lead to a scenario called "priority inversion" when using +// semaphores, see http://en.wikipedia.org/wiki/Priority_inversion. +// +// Now, VxWorks POSIX-semaphores for DKM's default to the usage of +// priority inverting semaphores, which is fine. On the other hand, +// for RTP's it defaults to using non priority inverting semaphores, +// which could easily pose a serious problem for a real time process, +// i.e. deadlocks! To overcome this two possibilities do exist: +// +// a) Patch every piece of boost that uses semaphores to instanciate +// the proper type of semaphores. This is non-intrusive with respect +// to the OS and could relatively easy been done by giving all +// semaphores attributes deviating from the default (for in-depth +// information see the POSIX functions pthread_mutexattr_init() +// and pthread_mutexattr_setprotocol()). However this breaks all +// too easily, as with every new version some boost library could +// all in a sudden start using semaphores, resurrecting the very +// same, hard to locate problem over and over again! +// +// b) We could change the default properties for POSIX-semaphores +// that VxWorks uses for RTP's and this is being suggested here, +// as it will more or less seamlessly integrate with boost. I got +// the following information from WRS how to do this, compare +// Wind River TSR# 1209768: +// +// Instructions for changing the default properties of POSIX- +// semaphores for RTP's in VxWorks 6.9: +// - Edit the file /vxworks-6.9/target/usr/src/posix/pthreadLib.c +// in the root of your Workbench-installation. +// - Around line 917 there should be the definition of the default +// mutex attributes: +// +// LOCAL pthread_mutexattr_t defaultMutexAttr = +// { +// PTHREAD_INITIALIZED_OBJ, PTHREAD_PRIO_NONE, 0, +// PTHREAD_MUTEX_DEFAULT +// }; +// +// Here, replace PTHREAD_PRIO_NONE by PTHREAD_PRIO_INHERIT. +// - Around line 1236 there should be a definition for the function +// pthread_mutexattr_init(). A couple of lines below you should +// find a block of code like this: +// +// pAttr->mutexAttrStatus = PTHREAD_INITIALIZED_OBJ; +// pAttr->mutexAttrProtocol = PTHREAD_PRIO_NONE; +// pAttr->mutexAttrPrioceiling = 0; +// pAttr->mutexAttrType = PTHREAD_MUTEX_DEFAULT; +// +// Here again, replace PTHREAD_PRIO_NONE by PTHREAD_PRIO_INHERIT. +// - Finally, rebuild your VSB. This will create a new VxWorks kernel +// with the changed properties. That's it! Now, using boost should +// no longer cause any problems with task deadlocks! +// +// And here's another useful piece of information concerning VxWorks' +// POSIX-functionality in general: +// VxWorks is not a genuine POSIX-OS in itself, rather it is using a +// kind of compatibility layer (sort of a wrapper) to emulate the +// POSIX-functionality by using its own resources and functions. +// At the time a task (thread) calls it's first POSIX-function during +// runtime it is being transformed by the OS into a POSIX-thread. +// This transformation does include a call to malloc() to allocate the +// memory required for the housekeeping of POSIX-threads. In a high +// priority RTP this malloc() call may be highly undesirable, as its +// timing is more or less unpredictable (depending on what your actual +// heap looks like). You can circumvent this problem by calling the +// function thread_self() at a well defined point in the code of the +// task, e.g. shortly after the task spawns up. Thereby you are able +// to define the time when the task-transformation will take place and +// you could shift it to an uncritical point where a malloc() call is +// tolerable. So, if this could pose a problem for your code, remember +// to call thread_self() from the affected task at an early stage. +// +// ==================================================================== + +// Block out all versions before vxWorks 6.x, as these don't work: +// Include header with the vxWorks version information and query them +#include +#if !defined(_WRS_VXWORKS_MAJOR) || (_WRS_VXWORKS_MAJOR < 6) +# error "The vxWorks version you're using is so badly outdated,\ + it doesn't work at all with boost, sorry, no chance!" +#endif + +// Handle versions above 5.X but below 6.9 +#if (_WRS_VXWORKS_MAJOR == 6) && (_WRS_VXWORKS_MINOR < 9) +// TODO: Starting from what version does vxWorks work with boost? +// We can't reasonably insert a #warning "" as a user hint here, +// as this will show up with every file including some boost header, +// badly bugging the user... So for the time being we just leave it. +#endif + +// vxWorks specific config options: +// -------------------------------- +#define BOOST_PLATFORM "vxWorks" + +// Special behaviour for DKMs: +#ifdef _WRS_KERNEL + // DKMs do not have the -header, + // but apparently they do have an intrinsic wchar_t meanwhile! +# define BOOST_NO_CWCHAR + + // Lots of wide-functions and -headers are unavailable for DKMs as well: +# define BOOST_NO_CWCTYPE +# define BOOST_NO_SWPRINTF +# define BOOST_NO_STD_WSTRING +# define BOOST_NO_STD_WSTREAMBUF +#endif + +// Generally available headers: +#define BOOST_HAS_UNISTD_H +#define BOOST_HAS_STDINT_H +#define BOOST_HAS_DIRENT_H +#define BOOST_HAS_SLIST + +// vxWorks does not have installed an iconv-library by default, +// so unfortunately no Unicode support from scratch is available! +// Thus, instead it is suggested to switch to ICU, as this seems +// to be the most complete and portable option... +#define BOOST_LOCALE_WITH_ICU + +// Generally available functionality: +#define BOOST_HAS_THREADS +#define BOOST_HAS_NANOSLEEP +#define BOOST_HAS_GETTIMEOFDAY +#define BOOST_HAS_CLOCK_GETTIME +#define BOOST_HAS_MACRO_USE_FACET + +// Generally unavailable functionality, delivered by boost's test function: +//#define BOOST_NO_DEDUCED_TYPENAME // Commented this out, boost's test gives an errorneous result! +#define BOOST_NO_CXX11_EXTERN_TEMPLATE +#define BOOST_NO_CXX11_VARIADIC_MACROS + +// Generally available threading API's: +#define BOOST_HAS_PTHREADS +#define BOOST_HAS_SCHED_YIELD +#define BOOST_HAS_SIGACTION + +// Functionality available for RTPs only: +#ifdef __RTP__ +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# define BOOST_HAS_LOG1P +# define BOOST_HAS_EXPM1 +#endif + +// Functionality available for DKMs only: +#ifdef _WRS_KERNEL + // Luckily, at the moment there seems to be none! +#endif + +// These #defines allow posix_features to work, since vxWorks doesn't +// #define them itself for DKMs (for RTPs on the contrary it does): +#ifdef _WRS_KERNEL +# ifndef _POSIX_TIMERS +# define _POSIX_TIMERS 1 +# endif +# ifndef _POSIX_THREADS +# define _POSIX_THREADS 1 +# endif +#endif + +// vxWorks doesn't work with asio serial ports: +#define BOOST_ASIO_DISABLE_SERIAL_PORT +// TODO: The problem here seems to bee that vxWorks uses its own, very specific +// ways to handle serial ports, incompatible with POSIX or anything... +// Maybe a specific implementation would be possible, but until the +// straight need arises... This implementation would presumably consist +// of some vxWorks specific ioctl-calls, etc. Any voluteers? + +// vxWorks-around: #defines CLOCKS_PER_SEC as sysClkRateGet() but +// miserably fails to #include the required to make +// sysClkRateGet() available! So we manually include it here. +#ifdef __RTP__ +# include +# include +#endif + +// vxWorks-around: In the macros INT32_C(), UINT32_C(), INT64_C() and +// UINT64_C() are defined errorneously, yielding not a signed/ +// unsigned long/long long type, but a signed/unsigned int/long +// type. Eventually this leads to compile errors in ratio_fwd.hpp, +// when trying to define several constants which do not fit into a +// long type! We correct them here by redefining. +#include + +// Some macro-magic to do the job +#define VX_JOIN(X, Y) VX_DO_JOIN(X, Y) +#define VX_DO_JOIN(X, Y) VX_DO_JOIN2(X, Y) +#define VX_DO_JOIN2(X, Y) X##Y + +// Correctly setup the macros +#undef INT32_C +#undef UINT32_C +#undef INT64_C +#undef UINT64_C +#define INT32_C(x) VX_JOIN(x, L) +#define UINT32_C(x) VX_JOIN(x, UL) +#define INT64_C(x) VX_JOIN(x, LL) +#define UINT64_C(x) VX_JOIN(x, ULL) + +// #include Libraries required for the following function adaption +#include +#include +#include + +// Use C-linkage for the following helper functions +extern "C" { + +// vxWorks-around: The required functions getrlimit() and getrlimit() are missing. +// But we have the similar functions getprlimit() and setprlimit(), +// which may serve the purpose. +// Problem: The vxWorks-documentation regarding these functions +// doesn't deserve its name! It isn't documented what the first two +// parameters idtype and id mean, so we must fall back to an educated +// guess - null, argh... :-/ + +// TODO: getprlimit() and setprlimit() do exist for RTPs only, for whatever reason. +// Thus for DKMs there would have to be another implementation. +#ifdef __RTP__ + inline int getrlimit(int resource, struct rlimit *rlp){ + return getprlimit(0, 0, resource, rlp); + } + + inline int setrlimit(int resource, const struct rlimit *rlp){ + return setprlimit(0, 0, resource, const_cast(rlp)); + } +#endif + +// vxWorks has ftruncate() only, so we do simulate truncate(): +inline int truncate(const char *p, off_t l){ + int fd = open(p, O_WRONLY); + if (fd == -1){ + errno = EACCES; + return -1; + } + if (ftruncate(fd, l) == -1){ + close(fd); + errno = EACCES; + return -1; + } + return close(fd); +} + +// Fake symlink handling by dummy functions: +inline int symlink(const char*, const char*){ + // vxWorks has no symlinks -> always return an error! + errno = EACCES; + return -1; +} + +inline ssize_t readlink(const char*, char*, size_t){ + // vxWorks has no symlinks -> always return an error! + errno = EACCES; + return -1; +} + +// vxWorks claims to implement gettimeofday in sys/time.h +// but nevertheless does not provide it! See +// https://support.windriver.com/olsPortal/faces/maintenance/techtipDetail_noHeader.jspx?docId=16442&contentId=WR_TECHTIP_006256 +// We implement a surrogate version here via clock_gettime: +inline int gettimeofday(struct timeval *tv, void * /*tzv*/) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + tv->tv_sec = ts.tv_sec; + tv->tv_usec = ts.tv_nsec / 1000; + return 0; +} + +// vxWorks does provide neither struct tms nor function times()! +// We implement an empty dummy-function, simply setting the user +// and system time to the half of thew actual system ticks-value +// and the child user and system time to 0. +// Rather ugly but at least it suppresses compiler errors... +// Unfortunately, this of course *does* have an severe impact on +// dependant libraries, actually this is chrono only! Here it will +// not be possible to correctly use user and system times! But +// as vxWorks is lacking the ability to calculate user and system +// process times there seems to be no other possible solution. +struct tms{ + clock_t tms_utime; // User CPU time + clock_t tms_stime; // System CPU time + clock_t tms_cutime; // User CPU time of terminated child processes + clock_t tms_cstime; // System CPU time of terminated child processes +}; + +inline clock_t times(struct tms *t){ + struct timespec ts; + clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts); + clock_t ticks(static_cast(static_cast(ts.tv_sec) * CLOCKS_PER_SEC + + static_cast(ts.tv_nsec) * CLOCKS_PER_SEC / 1000000.0)); + t->tms_utime = ticks/2U; + t->tms_stime = ticks/2U; + t->tms_cutime = 0; // vxWorks is lacking the concept of a child process! + t->tms_cstime = 0; // -> Set the wait times for childs to 0 + return ticks; +} + +} // extern "C" + +// Put the selfmade functions into the std-namespace, just in case +namespace std { +# ifdef __RTP__ + using ::getrlimit; + using ::setrlimit; +# endif + using ::truncate; + using ::symlink; + using ::readlink; + using ::times; + using ::gettimeofday; +} + +// Some more macro-magic: +// vxWorks-around: Some functions are not present or broken in vxWorks +// but may be patched to life via helper macros... + +// Include signal.h which might contain a typo to be corrected here +#include + +#define getpagesize() sysconf(_SC_PAGESIZE) // getpagesize is deprecated anyway! +#ifndef S_ISSOCK +# define S_ISSOCK(mode) ((mode & S_IFMT) == S_IFSOCK) // Is file a socket? +#endif +#define lstat(p, b) stat(p, b) // lstat() == stat(), as vxWorks has no symlinks! +#ifndef FPE_FLTINV +# define FPE_FLTINV (FPE_FLTSUB+1) // vxWorks has no FPE_FLTINV, so define one as a dummy +#endif +#if !defined(BUS_ADRALN) && defined(BUS_ADRALNR) +# define BUS_ADRALN BUS_ADRALNR // Correct a supposed typo in vxWorks' +#endif +//typedef int locale_t; // locale_t is a POSIX-extension, currently unpresent in vxWorks! + +// #include boilerplate code: +#include + +// vxWorks lies about XSI conformance, there is no nl_types.h: +#undef BOOST_HAS_NL_TYPES_H diff --git a/thirdparty/source/boost_1_61_0/boost/config/platform/win32.hpp b/thirdparty/source/boost_1_61_0/boost/config/platform/win32.hpp new file mode 100644 index 0000000..450158f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/platform/win32.hpp @@ -0,0 +1,90 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Bill Kempf 2001. +// (C) Copyright Aleksey Gurtovoy 2003. +// (C) Copyright Rene Rivera 2005. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Win32 specific config options: + +#define BOOST_PLATFORM "Win32" + +// Get the information about the MinGW runtime, i.e. __MINGW32_*VERSION. +#if defined(__MINGW32__) +# include <_mingw.h> +#endif + +#if defined(__GNUC__) && !defined(BOOST_NO_SWPRINTF) +# define BOOST_NO_SWPRINTF +#endif + +// Default defines for BOOST_SYMBOL_EXPORT and BOOST_SYMBOL_IMPORT +// If a compiler doesn't support __declspec(dllexport)/__declspec(dllimport), +// its boost/config/compiler/ file must define BOOST_SYMBOL_EXPORT and +// BOOST_SYMBOL_IMPORT +#ifndef BOOST_SYMBOL_EXPORT +# define BOOST_HAS_DECLSPEC +# define BOOST_SYMBOL_EXPORT __declspec(dllexport) +# define BOOST_SYMBOL_IMPORT __declspec(dllimport) +#endif + +#if defined(__MINGW32__) && ((__MINGW32_MAJOR_VERSION > 2) || ((__MINGW32_MAJOR_VERSION == 2) && (__MINGW32_MINOR_VERSION >= 0))) +# define BOOST_HAS_STDINT_H +# ifndef __STDC_LIMIT_MACROS +# define __STDC_LIMIT_MACROS +# endif +# define BOOST_HAS_DIRENT_H +# define BOOST_HAS_UNISTD_H +#endif + +#if defined(__MINGW32__) && (__GNUC__ >= 4) +// Mingw has these functions but there are persistent problems +// with calls to these crashing, so disable for now: +//# define BOOST_HAS_EXPM1 +//# define BOOST_HAS_LOG1P +# define BOOST_HAS_GETTIMEOFDAY +#endif +// +// Win32 will normally be using native Win32 threads, +// but there is a pthread library avaliable as an option, +// we used to disable this when BOOST_DISABLE_WIN32 was +// defined but no longer - this should allow some +// files to be compiled in strict mode - while maintaining +// a consistent setting of BOOST_HAS_THREADS across +// all translation units (needed for shared_ptr etc). +// + +#ifndef BOOST_HAS_PTHREADS +# define BOOST_HAS_WINTHREADS +#endif + +// +// WinCE configuration: +// +#if defined(_WIN32_WCE) || defined(UNDER_CE) +# define BOOST_NO_ANSI_APIS +// Windows CE does not have a conforming signature for swprintf +# define BOOST_NO_SWPRINTF +#else +# define BOOST_HAS_GETSYSTEMTIMEASFILETIME +# define BOOST_HAS_THREADEX +# define BOOST_HAS_GETSYSTEMTIMEASFILETIME +#endif + +// +// Windows Runtime +// +#if defined(WINAPI_FAMILY) && \ + (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) +# define BOOST_NO_ANSI_APIS +#endif + +#ifndef BOOST_DISABLE_WIN32 +// WEK: Added +#define BOOST_HAS_FTIME +#define BOOST_WINDOWS 1 + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/posix_features.hpp b/thirdparty/source/boost_1_61_0/boost/config/posix_features.hpp new file mode 100644 index 0000000..d129547 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/posix_features.hpp @@ -0,0 +1,95 @@ +// (C) Copyright John Maddock 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// See http://www.boost.org for most recent version. + +// All POSIX feature tests go in this file, +// Note that we test _POSIX_C_SOURCE and _XOPEN_SOURCE as well +// _POSIX_VERSION and _XOPEN_VERSION: on some systems POSIX API's +// may be present but none-functional unless _POSIX_C_SOURCE and +// _XOPEN_SOURCE have been defined to the right value (it's up +// to the user to do this *before* including any header, although +// in most cases the compiler will do this for you). + +# if defined(BOOST_HAS_UNISTD_H) +# include + + // XOpen has , but is this the correct version check? +# if defined(_XOPEN_VERSION) && (_XOPEN_VERSION >= 3) +# define BOOST_HAS_NL_TYPES_H +# endif + + // POSIX version 6 requires +# if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 200100) +# define BOOST_HAS_STDINT_H +# endif + + // POSIX version 2 requires +# if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 199009L) +# define BOOST_HAS_DIRENT_H +# endif + + // POSIX version 3 requires to have sigaction: +# if defined(_POSIX_VERSION) && (_POSIX_VERSION >= 199506L) +# define BOOST_HAS_SIGACTION +# endif + // POSIX defines _POSIX_THREADS > 0 for pthread support, + // however some platforms define _POSIX_THREADS without + // a value, hence the (_POSIX_THREADS+0 >= 0) check. + // Strictly speaking this may catch platforms with a + // non-functioning stub , but such occurrences should + // occur very rarely if at all. +# if defined(_POSIX_THREADS) && (_POSIX_THREADS+0 >= 0) && !defined(BOOST_HAS_WINTHREADS) && !defined(BOOST_HAS_MPTASKS) +# define BOOST_HAS_PTHREADS +# endif + + // BOOST_HAS_NANOSLEEP: + // This is predicated on _POSIX_TIMERS or _XOPEN_REALTIME: +# if (defined(_POSIX_TIMERS) && (_POSIX_TIMERS+0 >= 0)) \ + || (defined(_XOPEN_REALTIME) && (_XOPEN_REALTIME+0 >= 0)) +# define BOOST_HAS_NANOSLEEP +# endif + + // BOOST_HAS_CLOCK_GETTIME: + // This is predicated on _POSIX_TIMERS (also on _XOPEN_REALTIME + // but at least one platform - linux - defines that flag without + // defining clock_gettime): +# if (defined(_POSIX_TIMERS) && (_POSIX_TIMERS+0 >= 0)) +# define BOOST_HAS_CLOCK_GETTIME +# endif + + // BOOST_HAS_SCHED_YIELD: + // This is predicated on _POSIX_PRIORITY_SCHEDULING or + // on _POSIX_THREAD_PRIORITY_SCHEDULING or on _XOPEN_REALTIME. +# if defined(_POSIX_PRIORITY_SCHEDULING) && (_POSIX_PRIORITY_SCHEDULING+0 > 0)\ + || (defined(_POSIX_THREAD_PRIORITY_SCHEDULING) && (_POSIX_THREAD_PRIORITY_SCHEDULING+0 > 0))\ + || (defined(_XOPEN_REALTIME) && (_XOPEN_REALTIME+0 >= 0)) +# define BOOST_HAS_SCHED_YIELD +# endif + + // BOOST_HAS_GETTIMEOFDAY: + // BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE: + // These are predicated on _XOPEN_VERSION, and appears to be first released + // in issue 4, version 2 (_XOPEN_VERSION > 500). + // Likewise for the functions log1p and expm1. +# if defined(_XOPEN_VERSION) && (_XOPEN_VERSION+0 >= 500) +# define BOOST_HAS_GETTIMEOFDAY +# if defined(_XOPEN_SOURCE) && (_XOPEN_SOURCE+0 >= 500) +# define BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# endif +# ifndef BOOST_HAS_LOG1P +# define BOOST_HAS_LOG1P +# endif +# ifndef BOOST_HAS_EXPM1 +# define BOOST_HAS_EXPM1 +# endif +# endif + +# endif + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/requires_threads.hpp b/thirdparty/source/boost_1_61_0/boost/config/requires_threads.hpp new file mode 100644 index 0000000..cfaff23 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/requires_threads.hpp @@ -0,0 +1,92 @@ +// (C) Copyright John Maddock 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +#ifndef BOOST_CONFIG_REQUIRES_THREADS_HPP +#define BOOST_CONFIG_REQUIRES_THREADS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_DISABLE_THREADS) + +// +// special case to handle versions of gcc which don't currently support threads: +// +#if defined(__GNUC__) && ((__GNUC__ < 3) || (__GNUC_MINOR__ <= 3) || !defined(BOOST_STRICT_CONFIG)) +// +// this is checked up to gcc 3.3: +// +#if defined(__sgi) || defined(__hpux) +# error "Multi-threaded programs are not supported by gcc on HPUX or Irix (last checked with gcc 3.3)" +#endif + +#endif + +# error "Threading support unavaliable: it has been explicitly disabled with BOOST_DISABLE_THREADS" + +#elif !defined(BOOST_HAS_THREADS) + +# if defined __COMO__ +// Comeau C++ +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -D_MT (Windows) or -D_REENTRANT (Unix)" + +#elif defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC) +// Intel +#ifdef _WIN32 +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: either /MT /MTd /MD or /MDd" +#else +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -openmp" +#endif + +# elif defined __GNUC__ +// GNU C++: +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -pthread (Linux), -pthreads (Solaris) or -mthreads (Mingw32)" + +#elif defined __sgi +// SGI MIPSpro C++ +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -D_SGI_MP_SOURCE" + +#elif defined __DECCXX +// Compaq Tru64 Unix cxx +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -pthread" + +#elif defined __BORLANDC__ +// Borland +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -tWM" + +#elif defined __MWERKS__ +// Metrowerks CodeWarrior +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: either -runtime sm, -runtime smd, -runtime dm, or -runtime dmd" + +#elif defined __SUNPRO_CC +// Sun Workshop Compiler C++ +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -mt" + +#elif defined __HP_aCC +// HP aCC +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: -mt" + +#elif defined(__IBMCPP__) +// IBM Visual Age +# error "Compiler threading support is not turned on. Please compile the code with the xlC_r compiler" + +#elif defined _MSC_VER +// Microsoft Visual C++ +// +// Must remain the last #elif since some other vendors (Metrowerks, for +// example) also #define _MSC_VER +# error "Compiler threading support is not turned on. Please set the correct command line options for threading: either /MT /MTd /MD or /MDd" + +#else + +# error "Compiler threading support is not turned on. Please consult your compiler's documentation for the appropriate options to use" + +#endif // compilers + +#endif // BOOST_HAS_THREADS + +#endif // BOOST_CONFIG_REQUIRES_THREADS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/config/select_compiler_config.hpp b/thirdparty/source/boost_1_61_0/boost/config/select_compiler_config.hpp new file mode 100644 index 0000000..4d87093 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/select_compiler_config.hpp @@ -0,0 +1,148 @@ +// Boost compiler configuration selection header file + +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Martin Wille 2003. +// (C) Copyright Guillaume Melquiond 2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for most recent version. + +// locate which compiler we are using and define +// BOOST_COMPILER_CONFIG as needed: + +#if defined __CUDACC__ +// NVIDIA CUDA C++ compiler for GPU +# include "boost/config/compiler/nvcc.hpp" + +#endif + +#if defined(__GCCXML__) +// GCC-XML emulates other compilers, it has to appear first here! +# define BOOST_COMPILER_CONFIG "boost/config/compiler/gcc_xml.hpp" + +#elif defined(_CRAYC) +// EDG based Cray compiler: +# define BOOST_COMPILER_CONFIG "boost/config/compiler/cray.hpp" + +#elif defined __COMO__ +// Comeau C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/comeau.hpp" + +#elif defined(__PATHSCALE__) && (__PATHCC__ >= 4) +// PathScale EKOPath compiler (has to come before clang and gcc) +# define BOOST_COMPILER_CONFIG "boost/config/compiler/pathscale.hpp" + +#elif defined(__INTEL_COMPILER) || defined(__ICL) || defined(__ICC) || defined(__ECC) +// Intel +# define BOOST_COMPILER_CONFIG "boost/config/compiler/intel.hpp" + +#elif defined __clang__ && !defined(__CUDACC__) && !defined(__ibmxl__) +// when using clang and cuda at same time, you want to appear as gcc +// Clang C++ emulates GCC, so it has to appear early. +# define BOOST_COMPILER_CONFIG "boost/config/compiler/clang.hpp" + +#elif defined __DMC__ +// Digital Mars C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/digitalmars.hpp" + +# elif defined(__GNUC__) && !defined(__ibmxl__) +// GNU C++: +# define BOOST_COMPILER_CONFIG "boost/config/compiler/gcc.hpp" + +#elif defined __KCC +// Kai C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/kai.hpp" + +#elif defined __sgi +// SGI MIPSpro C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/sgi_mipspro.hpp" + +#elif defined __DECCXX +// Compaq Tru64 Unix cxx +# define BOOST_COMPILER_CONFIG "boost/config/compiler/compaq_cxx.hpp" + +#elif defined __ghs +// Greenhills C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/greenhills.hpp" + +#elif defined __CODEGEARC__ +// CodeGear - must be checked for before Borland +# define BOOST_COMPILER_CONFIG "boost/config/compiler/codegear.hpp" + +#elif defined __BORLANDC__ +// Borland +# define BOOST_COMPILER_CONFIG "boost/config/compiler/borland.hpp" + +#elif defined __MWERKS__ +// Metrowerks CodeWarrior +# define BOOST_COMPILER_CONFIG "boost/config/compiler/metrowerks.hpp" + +#elif defined __SUNPRO_CC +// Sun Workshop Compiler C++ +# define BOOST_COMPILER_CONFIG "boost/config/compiler/sunpro_cc.hpp" + +#elif defined __HP_aCC +// HP aCC +# define BOOST_COMPILER_CONFIG "boost/config/compiler/hp_acc.hpp" + +#elif defined(__MRC__) || defined(__SC__) +// MPW MrCpp or SCpp +# define BOOST_COMPILER_CONFIG "boost/config/compiler/mpw.hpp" + +#elif defined(__ibmxl__) +// IBM XL C/C++ for Linux (Little Endian) +# define BOOST_COMPILER_CONFIG "boost/config/compiler/xlcpp.hpp" + +#elif defined(__IBMCPP__) +// IBM Visual Age or IBM XL C/C++ for Linux (Big Endian) +# define BOOST_COMPILER_CONFIG "boost/config/compiler/vacpp.hpp" + +#elif defined(__PGI) +// Portland Group Inc. +# define BOOST_COMPILER_CONFIG "boost/config/compiler/pgi.hpp" + +#elif defined _MSC_VER +// Microsoft Visual C++ +// +// Must remain the last #elif since some other vendors (Metrowerks, for +// example) also #define _MSC_VER +# define BOOST_COMPILER_CONFIG "boost/config/compiler/visualc.hpp" + +#elif defined (BOOST_ASSERT_CONFIG) +// this must come last - generate an error if we don't +// recognise the compiler: +# error "Unknown compiler - please configure (http://www.boost.org/libs/config/config.htm#configuring) and report the results to the main boost mailing list (http://www.boost.org/more/mailing_lists.htm#main)" + +#endif + +#if 0 +// +// This section allows dependency scanners to find all the headers we *might* include: +// +#include "boost/config/compiler/gcc_xml.hpp" +#include "boost/config/compiler/cray.hpp" +#include "boost/config/compiler/comeau.hpp" +#include "boost/config/compiler/pathscale.hpp" +#include "boost/config/compiler/intel.hpp" +#include "boost/config/compiler/clang.hpp" +#include "boost/config/compiler/digitalmars.hpp" +#include "boost/config/compiler/gcc.hpp" +#include "boost/config/compiler/kai.hpp" +#include "boost/config/compiler/sgi_mipspro.hpp" +#include "boost/config/compiler/compaq_cxx.hpp" +#include "boost/config/compiler/greenhills.hpp" +#include "boost/config/compiler/codegear.hpp" +#include "boost/config/compiler/borland.hpp" +#include "boost/config/compiler/metrowerks.hpp" +#include "boost/config/compiler/sunpro_cc.hpp" +#include "boost/config/compiler/hp_acc.hpp" +#include "boost/config/compiler/mpw.hpp" +#include "boost/config/compiler/vacpp.hpp" +#include "boost/config/compiler/pgi.hpp" +#include "boost/config/compiler/visualc.hpp" + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/select_platform_config.hpp b/thirdparty/source/boost_1_61_0/boost/config/select_platform_config.hpp new file mode 100644 index 0000000..62fd818 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/select_platform_config.hpp @@ -0,0 +1,137 @@ +// Boost compiler configuration selection header file + +// (C) Copyright John Maddock 2001 - 2002. +// (C) Copyright Jens Maurer 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// locate which platform we are on and define BOOST_PLATFORM_CONFIG as needed. +// Note that we define the headers to include using "header_name" not +// in order to prevent macro expansion within the header +// name (for example "linux" is a macro on linux systems). + +#if (defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)) && !defined(_CRAYC) +// linux, also other platforms (Hurd etc) that use GLIBC, should these really have their own config headers though? +# define BOOST_PLATFORM_CONFIG "boost/config/platform/linux.hpp" + +#elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +// BSD: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/bsd.hpp" + +#elif defined(sun) || defined(__sun) +// solaris: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/solaris.hpp" + +#elif defined(__sgi) +// SGI Irix: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/irix.hpp" + +#elif defined(__hpux) +// hp unix: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/hpux.hpp" + +#elif defined(__CYGWIN__) +// cygwin is not win32: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/cygwin.hpp" + +#elif defined(_WIN32) || defined(__WIN32__) || defined(WIN32) +// win32: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/win32.hpp" + +#elif defined(__HAIKU__) +// Haiku +# define BOOST_PLATFORM_CONFIG "boost/config/platform/haiku.hpp" + +#elif defined(__BEOS__) +// BeOS +# define BOOST_PLATFORM_CONFIG "boost/config/platform/beos.hpp" + +#elif defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) +// MacOS +# define BOOST_PLATFORM_CONFIG "boost/config/platform/macos.hpp" + +#elif defined(__IBMCPP__) || defined(_AIX) +// IBM +# define BOOST_PLATFORM_CONFIG "boost/config/platform/aix.hpp" + +#elif defined(__amigaos__) +// AmigaOS +# define BOOST_PLATFORM_CONFIG "boost/config/platform/amigaos.hpp" + +#elif defined(__QNXNTO__) +// QNX: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/qnxnto.hpp" + +#elif defined(__VXWORKS__) +// vxWorks: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/vxworks.hpp" + +#elif defined(__SYMBIAN32__) +// Symbian: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/symbian.hpp" + +#elif defined(_CRAYC) +// Cray: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/cray.hpp" + +#elif defined(__VMS) +// VMS: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/vms.hpp" + +#elif defined(__CloudABI__) +// Nuxi CloudABI: +# define BOOST_PLATFORM_CONFIG "boost/config/platform/cloudabi.hpp" +#else + +# if defined(unix) \ + || defined(__unix) \ + || defined(_XOPEN_SOURCE) \ + || defined(_POSIX_SOURCE) + + // generic unix platform: + +# ifndef BOOST_HAS_UNISTD_H +# define BOOST_HAS_UNISTD_H +# endif + +# include + +# endif + +# if defined (BOOST_ASSERT_CONFIG) + // this must come last - generate an error if we don't + // recognise the platform: +# error "Unknown platform - please configure and report the results to boost.org" +# endif + +#endif + +#if 0 +// +// This section allows dependency scanners to find all the files we *might* include: +// +# include "boost/config/platform/linux.hpp" +# include "boost/config/platform/bsd.hpp" +# include "boost/config/platform/solaris.hpp" +# include "boost/config/platform/irix.hpp" +# include "boost/config/platform/hpux.hpp" +# include "boost/config/platform/cygwin.hpp" +# include "boost/config/platform/win32.hpp" +# include "boost/config/platform/beos.hpp" +# include "boost/config/platform/macos.hpp" +# include "boost/config/platform/aix.hpp" +# include "boost/config/platform/amigaos.hpp" +# include "boost/config/platform/qnxnto.hpp" +# include "boost/config/platform/vxworks.hpp" +# include "boost/config/platform/symbian.hpp" +# include "boost/config/platform/cray.hpp" +# include "boost/config/platform/vms.hpp" +# include + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/select_stdlib_config.hpp b/thirdparty/source/boost_1_61_0/boost/config/select_stdlib_config.hpp new file mode 100644 index 0000000..e270a88 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/select_stdlib_config.hpp @@ -0,0 +1,105 @@ +// Boost compiler configuration selection header file + +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001 - 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +// See http://www.boost.org for most recent version. + +// locate which std lib we are using and define BOOST_STDLIB_CONFIG as needed: + +// First include to determine if some version of STLport is in use as the std lib +// (do not rely on this header being included since users can short-circuit this header +// if they know whose std lib they are using.) +#ifdef __cplusplus +# include +#else +# include +#endif + +#if defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) +// STLPort library; this _must_ come first, otherwise since +// STLport typically sits on top of some other library, we +// can end up detecting that first rather than STLport: +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/stlport.hpp" + +#else + +// If our std lib was not some version of STLport, and has not otherwise +// been detected, then include as it is about +// the smallest of the std lib headers that includes real C++ stuff. +// Some std libs do not include their C++-related macros in +// so this additional include makes sure we get those definitions. +// Note: do not rely on this header being included since users can short-circuit this +// #include if they know whose std lib they are using. +#if !defined(__LIBCOMO__) && !defined(__STD_RWCOMPILER_H__) && !defined(_RWSTD_VER)\ + && !defined(_LIBCPP_VERSION) && !defined(__GLIBCPP__) && !defined(__GLIBCXX__)\ + && !defined(__STL_CONFIG_H) && !defined(__MSL_CPP__) && !defined(__IBMCPP__)\ + && !defined(MSIPL_COMPILE_H) && !defined(_YVALS) && !defined(_CPPLIB_VER) +#include +#endif + +#if defined(__LIBCOMO__) +// Comeau STL: +#define BOOST_STDLIB_CONFIG "boost/config/stdlib/libcomo.hpp" + +#elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) +// Rogue Wave library: +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/roguewave.hpp" + +#elif defined(_LIBCPP_VERSION) +// libc++ +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/libcpp.hpp" + +#elif defined(__GLIBCPP__) || defined(__GLIBCXX__) +// GNU libstdc++ 3 +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/libstdcpp3.hpp" + +#elif defined(__STL_CONFIG_H) +// generic SGI STL +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/sgi.hpp" + +#elif defined(__MSL_CPP__) +// MSL standard lib: +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/msl.hpp" + +#elif defined(__IBMCPP__) +// take the default VACPP std lib +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/vacpp.hpp" + +#elif defined(MSIPL_COMPILE_H) +// Modena C++ standard library +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/modena.hpp" + +#elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) +// Dinkumware Library (this has to appear after any possible replacement libraries): +# define BOOST_STDLIB_CONFIG "boost/config/stdlib/dinkumware.hpp" + +#elif defined (BOOST_ASSERT_CONFIG) +// this must come last - generate an error if we don't +// recognise the library: +# error "Unknown standard library - please configure and report the results to boost.org" + +#endif + +#endif + +#if 0 +// +// This section allows dependency scanners to find all the files we *might* include: +// +# include "boost/config/stdlib/stlport.hpp" +# include "boost/config/stdlib/libcomo.hpp" +# include "boost/config/stdlib/roguewave.hpp" +# include "boost/config/stdlib/libcpp.hpp" +# include "boost/config/stdlib/libstdcpp3.hpp" +# include "boost/config/stdlib/sgi.hpp" +# include "boost/config/stdlib/msl.hpp" +# include "boost/config/stdlib/vacpp.hpp" +# include "boost/config/stdlib/modena.hpp" +# include "boost/config/stdlib/dinkumware.hpp" +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/dinkumware.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/dinkumware.hpp new file mode 100644 index 0000000..af8ddda --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/dinkumware.hpp @@ -0,0 +1,198 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001. +// (C) Copyright Peter Dimov 2001. +// (C) Copyright David Abrahams 2002. +// (C) Copyright Guillaume Melquiond 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Dinkumware standard library config: + +#if !defined(_YVALS) && !defined(_CPPLIB_VER) +#include +#if !defined(_YVALS) && !defined(_CPPLIB_VER) +#error This is not the Dinkumware lib! +#endif +#endif + + +#if defined(_CPPLIB_VER) && (_CPPLIB_VER >= 306) + // full dinkumware 3.06 and above + // fully conforming provided the compiler supports it: +# if !(defined(_GLOBAL_USING) && (_GLOBAL_USING+0 > 0)) && !defined(__BORLANDC__) && !defined(_STD) && !(defined(__ICC) && (__ICC >= 700)) // can be defined in yvals.h +# define BOOST_NO_STDC_NAMESPACE +# endif +# if !(defined(_HAS_MEMBER_TEMPLATES_REBIND) && (_HAS_MEMBER_TEMPLATES_REBIND+0 > 0)) && !(defined(_MSC_VER) && (_MSC_VER > 1300)) && defined(BOOST_MSVC) +# define BOOST_NO_STD_ALLOCATOR +# endif +# define BOOST_HAS_PARTIAL_STD_ALLOCATOR +# if defined(BOOST_MSVC) && (BOOST_MSVC < 1300) + // if this lib version is set up for vc6 then there is no std::use_facet: +# define BOOST_NO_STD_USE_FACET +# define BOOST_HAS_TWO_ARG_USE_FACET + // C lib functions aren't in namespace std either: +# define BOOST_NO_STDC_NAMESPACE + // and nor is +# define BOOST_NO_EXCEPTION_STD_NAMESPACE +# endif +// There's no numeric_limits support unless _LONGLONG is defined: +# if !defined(_LONGLONG) && (_CPPLIB_VER <= 310) +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +# endif +// 3.06 appears to have (non-sgi versions of) & , +// and no at all +#else +# define BOOST_MSVC_STD_ITERATOR 1 +# define BOOST_NO_STD_ITERATOR +# define BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS +# define BOOST_NO_STD_ALLOCATOR +# define BOOST_NO_STDC_NAMESPACE +# define BOOST_NO_STD_USE_FACET +# define BOOST_NO_STD_OUTPUT_ITERATOR_ASSIGN +# define BOOST_HAS_MACRO_USE_FACET +# ifndef _CPPLIB_VER + // Updated Dinkum library defines this, and provides + // its own min and max definitions, as does MTA version. +# ifndef __MTA__ +# define BOOST_NO_STD_MIN_MAX +# endif +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +# endif +#endif + +// +// std extension namespace is stdext for vc7.1 and later, +// the same applies to other compilers that sit on top +// of vc7.1 (Intel and Comeau): +// +#if defined(_MSC_VER) && (_MSC_VER >= 1310) && !defined(__BORLANDC__) +# define BOOST_STD_EXTENSION_NAMESPACE stdext +#endif + + +#if (defined(_MSC_VER) && (_MSC_VER <= 1300) && !defined(__BORLANDC__)) || !defined(_CPPLIB_VER) || (_CPPLIB_VER < 306) + // if we're using a dinkum lib that's + // been configured for VC6/7 then there is + // no iterator traits (true even for icl) +# define BOOST_NO_STD_ITERATOR_TRAITS +#endif + +#if defined(__ICL) && (__ICL < 800) && defined(_CPPLIB_VER) && (_CPPLIB_VER <= 310) +// Intel C++ chokes over any non-trivial use of +// this may be an overly restrictive define, but regex fails without it: +# define BOOST_NO_STD_LOCALE +#endif + +// Fix for VC++ 8.0 on up ( I do not have a previous version to test ) +// or clang-cl. If exceptions are off you must manually include the +// header before including the header. Admittedly +// trying to use Boost libraries or the standard C++ libraries without +// exception support is not suggested but currently clang-cl ( v 3.4 ) +// does not support exceptions and must be compiled with exceptions off. +#if !_HAS_EXCEPTIONS && ((defined(BOOST_MSVC) && BOOST_MSVC >= 1400) || (defined(__clang__) && defined(_MSC_VER))) +#include +#endif +#include +#if ( (!_HAS_EXCEPTIONS && !defined(__ghs__)) || (!_HAS_NAMESPACE && defined(__ghs__)) ) && !defined(__TI_COMPILER_VERSION__) && !defined(__VISUALDSPVERSION__) +# define BOOST_NO_STD_TYPEINFO +#endif + +// C++0x headers implemented in 520 (as shipped by Microsoft) +// +#if !defined(_CPPLIB_VER) || _CPPLIB_VER < 520 +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_SMART_PTR +#endif + +#if ((!defined(_HAS_TR1_IMPORTS) || (_HAS_TR1_IMPORTS+0 == 0)) && !defined(BOOST_NO_CXX11_HDR_TUPLE)) \ + && (!defined(_CPPLIB_VER) || _CPPLIB_VER < 610) +# define BOOST_NO_CXX11_HDR_TUPLE +#endif + +// C++0x headers implemented in 540 (as shipped by Microsoft) +// +#if !defined(_CPPLIB_VER) || _CPPLIB_VER < 540 +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +#endif + +// C++0x headers implemented in 610 (as shipped by Microsoft) +// +#if !defined(_CPPLIB_VER) || _CPPLIB_VER < 610 +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_ALLOCATOR +// 540 has std::align but it is not a conforming implementation +# define BOOST_NO_CXX11_STD_ALIGN +#endif + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#elif !defined(_CPPLIB_VER) || (_CPPLIB_VER < 650) +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#if defined(BOOST_INTEL) && (BOOST_INTEL <= 1400) +// Intel's compiler can't handle this header yet: +# define BOOST_NO_CXX11_HDR_ATOMIC +#endif + + +// 520..610 have std::addressof, but it doesn't support functions +// +#if !defined(_CPPLIB_VER) || _CPPLIB_VER < 650 +# define BOOST_NO_CXX11_ADDRESSOF +#endif + +// Bug specific to VC14, +// See https://connect.microsoft.com/VisualStudio/feedback/details/1348277/link-error-when-using-std-codecvt-utf8-utf16-char16-t +// and discussion here: http://blogs.msdn.com/b/vcblog/archive/2014/11/12/visual-studio-2015-preview-now-available.aspx?PageIndex=2 +#if defined(_CPPLIB_VER) && (_CPPLIB_VER == 650) +# define BOOST_NO_CXX11_HDR_CODECVT +#endif + +#if defined(_CPPLIB_VER) && (_CPPLIB_VER >= 650) +// If _HAS_AUTO_PTR_ETC is defined to 0, std::auto_ptr is not available. +// See https://www.visualstudio.com/en-us/news/vs2015-vs.aspx#C++ +// and http://blogs.msdn.com/b/vcblog/archive/2015/06/19/c-11-14-17-features-in-vs-2015-rtm.aspx +# if defined(_HAS_AUTO_PTR_ETC) && (_HAS_AUTO_PTR_ETC == 0) +# define BOOST_NO_AUTO_PTR +# endif +#endif + +#ifdef _CPPLIB_VER +# define BOOST_DINKUMWARE_STDLIB _CPPLIB_VER +#else +# define BOOST_DINKUMWARE_STDLIB 1 +#endif + +#ifdef _CPPLIB_VER +# define BOOST_STDLIB "Dinkumware standard library version " BOOST_STRINGIZE(_CPPLIB_VER) +#else +# define BOOST_STDLIB "Dinkumware standard library version 1.x" +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcomo.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcomo.hpp new file mode 100644 index 0000000..941498d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcomo.hpp @@ -0,0 +1,83 @@ +// (C) Copyright John Maddock 2002 - 2003. +// (C) Copyright Jens Maurer 2002 - 2003. +// (C) Copyright Beman Dawes 2002 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Comeau STL: + +#if !defined(__LIBCOMO__) +# include +# if !defined(__LIBCOMO__) +# error "This is not the Comeau STL!" +# endif +#endif + +// +// std::streambuf is non-standard +// NOTE: versions of libcomo prior to beta28 have octal version numbering, +// e.g. version 25 is 21 (dec) +#if __LIBCOMO_VERSION__ <= 22 +# define BOOST_NO_STD_WSTREAMBUF +#endif + +#if (__LIBCOMO_VERSION__ <= 31) && defined(_WIN32) +#define BOOST_NO_SWPRINTF +#endif + +#if __LIBCOMO_VERSION__ >= 31 +# define BOOST_HAS_HASH +# define BOOST_HAS_SLIST +#endif + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +// +// Intrinsic type_traits support. +// The SGI STL has it's own __type_traits class, which +// has intrinsic compiler support with SGI's compilers. +// Whatever map SGI style type traits to boost equivalents: +// +#define BOOST_HAS_SGI_TYPE_TRAITS + +#define BOOST_STDLIB "Comeau standard library " BOOST_STRINGIZE(__LIBCOMO_VERSION__) diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcpp.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcpp.hpp new file mode 100644 index 0000000..645bb63 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libcpp.hpp @@ -0,0 +1,88 @@ +// (C) Copyright Christopher Jefferson 2011. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// config for libc++ +// Might need more in here later. + +#if !defined(_LIBCPP_VERSION) +# include +# if !defined(_LIBCPP_VERSION) +# error "This is not libc++!" +# endif +#endif + +#define BOOST_STDLIB "libc++ version " BOOST_STRINGIZE(_LIBCPP_VERSION) + +#define BOOST_HAS_THREADS + +#ifdef _LIBCPP_HAS_NO_VARIADICS +# define BOOST_NO_CXX11_HDR_TUPLE +#endif + +// BOOST_NO_CXX11_ALLOCATOR should imply no support for the C++11 +// allocator model. The C++11 allocator model requires a conforming +// std::allocator_traits which is only possible with C++11 template +// aliases since members rebind_alloc and rebind_traits require it. +#if defined(_LIBCPP_HAS_NO_TEMPLATE_ALIASES) +# define BOOST_NO_CXX11_ALLOCATOR +#endif + +#if __cplusplus < 201103 +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_FUTURE +#elif _LIBCPP_VERSION < 3700 +// +// These appear to be unusable/incomplete so far: +// +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_FUTURE +#endif + + +#if _LIBCPP_VERSION < 3700 +// libc++ uses a non-standard messages_base +#define BOOST_NO_STD_MESSAGES +#endif + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus <= 201103 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +// --- end --- diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/libstdcpp3.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libstdcpp3.hpp new file mode 100644 index 0000000..9718bed --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/libstdcpp3.hpp @@ -0,0 +1,281 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright Jens Maurer 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// config for libstdc++ v3 +// not much to go in here: + +#define BOOST_GNU_STDLIB 1 + +#ifdef __GLIBCXX__ +#define BOOST_STDLIB "GNU libstdc++ version " BOOST_STRINGIZE(__GLIBCXX__) +#else +#define BOOST_STDLIB "GNU libstdc++ version " BOOST_STRINGIZE(__GLIBCPP__) +#endif + +#if !defined(_GLIBCPP_USE_WCHAR_T) && !defined(_GLIBCXX_USE_WCHAR_T) +# define BOOST_NO_CWCHAR +# define BOOST_NO_CWCTYPE +# define BOOST_NO_STD_WSTRING +# define BOOST_NO_STD_WSTREAMBUF +#endif + +#if defined(__osf__) && !defined(_REENTRANT) \ + && ( defined(_GLIBCXX_HAVE_GTHR_DEFAULT) || defined(_GLIBCPP_HAVE_GTHR_DEFAULT) ) +// GCC 3 on Tru64 forces the definition of _REENTRANT when any std lib header +// file is included, therefore for consistency we define it here as well. +# define _REENTRANT +#endif + +#ifdef __GLIBCXX__ // gcc 3.4 and greater: +# if defined(_GLIBCXX_HAVE_GTHR_DEFAULT) \ + || defined(_GLIBCXX__PTHREADS) \ + || defined(_GLIBCXX_HAS_GTHREADS) \ + || defined(_WIN32) \ + || defined(_AIX) \ + || defined(__HAIKU__) + // + // If the std lib has thread support turned on, then turn it on in Boost + // as well. We do this because some gcc-3.4 std lib headers define _REENTANT + // while others do not... + // +# define BOOST_HAS_THREADS +# else +# define BOOST_DISABLE_THREADS +# endif +#elif defined(__GLIBCPP__) \ + && !defined(_GLIBCPP_HAVE_GTHR_DEFAULT) \ + && !defined(_GLIBCPP__PTHREADS) + // disable thread support if the std lib was built single threaded: +# define BOOST_DISABLE_THREADS +#endif + +#if (defined(linux) || defined(__linux) || defined(__linux__)) && defined(__arm__) && defined(_GLIBCPP_HAVE_GTHR_DEFAULT) +// linux on arm apparently doesn't define _REENTRANT +// so just turn on threading support whenever the std lib is thread safe: +# define BOOST_HAS_THREADS +#endif + +#if !defined(_GLIBCPP_USE_LONG_LONG) \ + && !defined(_GLIBCXX_USE_LONG_LONG)\ + && defined(BOOST_HAS_LONG_LONG) +// May have been set by compiler/*.hpp, but "long long" without library +// support is useless. +# undef BOOST_HAS_LONG_LONG +#endif + +// Apple doesn't seem to reliably defined a *unix* macro +#if !defined(CYGWIN) && ( defined(__unix__) \ + || defined(__unix) \ + || defined(unix) \ + || defined(__APPLE__) \ + || defined(__APPLE) \ + || defined(APPLE)) +# include +#endif + +#if defined(__GLIBCXX__) || (defined(__GLIBCPP__) && __GLIBCPP__>=20020514) // GCC >= 3.1.0 +# define BOOST_STD_EXTENSION_NAMESPACE __gnu_cxx +# define BOOST_HAS_SLIST +# define BOOST_HAS_HASH +# define BOOST_SLIST_HEADER +# if !defined(__GNUC__) || __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3) +# define BOOST_HASH_SET_HEADER +# define BOOST_HASH_MAP_HEADER +# else +# define BOOST_HASH_SET_HEADER +# define BOOST_HASH_MAP_HEADER +# endif +#endif + +// +// Decide whether we have C++11 support turned on: +// +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || (__cplusplus >= 201103) +# define BOOST_LIBSTDCXX11 +#endif +// +// Decide which version of libstdc++ we have, normally +// stdlibc++ C++0x support is detected via __GNUC__, __GNUC_MINOR__, and possibly +// __GNUC_PATCHLEVEL__ at the suggestion of Jonathan Wakely, one of the stdlibc++ +// developers. He also commented: +// +// "I'm not sure how useful __GLIBCXX__ is for your purposes, for instance in +// GCC 4.2.4 it is set to 20080519 but in GCC 4.3.0 it is set to 20080305. +// Although 4.3.0 was released earlier than 4.2.4, it has better C++0x support +// than any release in the 4.2 series." +// +// Another resource for understanding stdlibc++ features is: +// http://gcc.gnu.org/onlinedocs/libstdc++/manual/status.html#manual.intro.status.standard.200x +// +// However, using the GCC version number fails when the compiler is clang since this +// only ever claims to emulate GCC-4.2, see https://svn.boost.org/trac/boost/ticket/7473 +// for a long discussion on this issue. What we can do though is use clang's __has_include +// to detect the presence of a C++11 header that was introduced with a specific GCC release. +// We still have to be careful though as many such headers were buggy and/or incomplete when +// first introduced, so we only check for headers that were fully featured from day 1, and then +// use that to infer the underlying GCC version: +// +#ifdef __clang__ + +#if __has_include() +# define BOOST_LIBSTDCXX_VERSION 50100 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40900 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40800 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40700 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40600 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40500 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40400 +#elif __has_include() +# define BOOST_LIBSTDCXX_VERSION 40300 +#endif +// +// GCC 4.8 and 9 add working versions of and respectively. +// However, we have no test for these as the headers were present but broken +// in early GCC versions. +// +#endif + +#if defined(__SUNPRO_CC) && (__SUNPRO_CC >= 0x5130) && (__cplusplus >= 201103L) +// +// Oracle Solaris compiler uses it's own verison of libstdc++ but doesn't +// set __GNUC__ +// +#define BOOST_LIBSTDCXX_VERSION 40800 +#endif + +#if !defined(BOOST_LIBSTDCXX_VERSION) +# define BOOST_LIBSTDCXX_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) +#endif + +// C++0x headers in GCC 4.3.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40300) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +#endif + +// C++0x headers in GCC 4.4.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40400) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_SMART_PTR +#else +# define BOOST_HAS_TR1_COMPLEX_INVERSE_TRIG +# define BOOST_HAS_TR1_COMPLEX_OVERLOADS +#endif + +// C++0x features in GCC 4.5.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40500) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_RANDOM +#endif + +// C++0x features in GCC 4.6.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40600) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_ADDRESSOF +#endif + +// C++0x features in GCC 4.7.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40700) || !defined(BOOST_LIBSTDCXX11) +// Note that although existed prior to 4.7, "steady_clock" is spelled "monotonic_clock" +// so 4.7.0 is the first truely conforming one. +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_ALLOCATOR +#endif +// C++0x features in GCC 4.8.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40800) || !defined(BOOST_LIBSTDCXX11) +// Note that although existed prior to gcc 4.8 it was largely unimplemented for many types: +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_HDR_THREAD +#endif +// C++0x features in GCC 4.9.0 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 40900) || !defined(BOOST_LIBSTDCXX11) +// Although is present and compilable against, the actual implementation is not functional +// even for the simplest patterns such as "\d" or "[0-9]". This is the case at least in gcc up to 4.8, inclusively. +# define BOOST_NO_CXX11_HDR_REGEX +#endif + +#if defined(__clang_major__) && ((__clang_major__ < 3) || ((__clang_major__ == 3) && (__clang_minor__ < 7))) +// As of clang-3.6, libstdc++ header throws up errors with clang: +# define BOOST_NO_CXX11_HDR_ATOMIC +#endif +// +// C++0x features in GCC 5.1 and later +// +#if (BOOST_LIBSTDCXX_VERSION < 50100) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_STD_ALIGN +#endif + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus <= 201103 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#elif __cplusplus < 201402 || (BOOST_LIBSTDCXX_VERSION < 40900) || !defined(BOOST_LIBSTDCXX11) +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +// +// Headers not present on Solaris with the Oracle compiler: +#if defined(__SUNPRO_CC) +#define BOOST_NO_CXX11_HDR_FUTURE +#define BOOST_NO_CXX11_HDR_FORWARD_LIST +#define BOOST_NO_CXX11_HDR_ATOMIC +// shared_ptr is present, but is not convertible to bool +// which causes all kinds of problems especially in Boost.Thread +// but probably elsewhere as well. +#define BOOST_NO_CXX11_SMART_PTR +#endif + +#if (!defined(_GLIBCXX_HAS_GTHREADS) || !defined(_GLIBCXX_USE_C99_STDINT_TR1)) + // Headers not always available: +# ifndef BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# endif +# ifndef BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_MUTEX +# endif +# ifndef BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_THREAD +# endif +# ifndef BOOST_NO_CXX14_HDR_SHARED_MUTEX +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +# endif +#endif + +#if (!defined(_GTHREAD_USE_MUTEX_TIMEDLOCK) || (_GTHREAD_USE_MUTEX_TIMEDLOCK == 0)) && !defined(BOOST_NO_CXX11_HDR_MUTEX) +// Timed mutexes are not always available: +# define BOOST_NO_CXX11_HDR_MUTEX +#endif + +// --- end --- diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/modena.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/modena.hpp new file mode 100644 index 0000000..7a85e0c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/modena.hpp @@ -0,0 +1,69 @@ +// (C) Copyright Jens Maurer 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Modena C++ standard library (comes with KAI C++) + +#if !defined(MSIPL_COMPILE_H) +# include +# if !defined(__MSIPL_COMPILE_H) +# error "This is not the Modena C++ library!" +# endif +#endif + +#ifndef MSIPL_NL_TYPES +#define BOOST_NO_STD_MESSAGES +#endif + +#ifndef MSIPL_WCHART +#define BOOST_NO_STD_WSTRING +#endif + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#define BOOST_STDLIB "Modena C++ standard library" + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/msl.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/msl.hpp new file mode 100644 index 0000000..dd2775e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/msl.hpp @@ -0,0 +1,88 @@ +// (C) Copyright John Maddock 2001. +// (C) Copyright Darin Adler 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Metrowerks standard library: + +#ifndef __MSL_CPP__ +# include +# ifndef __MSL_CPP__ +# error This is not the MSL standard library! +# endif +#endif + +#if __MSL_CPP__ >= 0x6000 // Pro 6 +# define BOOST_HAS_HASH +# define BOOST_STD_EXTENSION_NAMESPACE Metrowerks +#endif +#define BOOST_HAS_SLIST + +#if __MSL_CPP__ < 0x6209 +# define BOOST_NO_STD_MESSAGES +#endif + +// check C lib version for +#include + +#if defined(__MSL__) && (__MSL__ >= 0x5000) +# define BOOST_HAS_STDINT_H +# if !defined(__PALMOS_TRAPS__) +# define BOOST_HAS_UNISTD_H +# endif + // boilerplate code: +# include +#endif + +#if defined(_MWMT) || _MSL_THREADSAFE +# define BOOST_HAS_THREADS +#endif + +#ifdef _MSL_NO_EXPLICIT_FUNC_TEMPLATE_ARG +# define BOOST_NO_STD_USE_FACET +# define BOOST_HAS_TWO_ARG_USE_FACET +#endif + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#define BOOST_STDLIB "Metrowerks Standard Library version " BOOST_STRINGIZE(__MSL_CPP__) diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/roguewave.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/roguewave.hpp new file mode 100644 index 0000000..97a2b0b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/roguewave.hpp @@ -0,0 +1,198 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Jens Maurer 2001. +// (C) Copyright David Abrahams 2003. +// (C) Copyright Boris Gubenko 2007. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// Rogue Wave std lib: + +#define BOOST_RW_STDLIB 1 + +#if !defined(__STD_RWCOMPILER_H__) && !defined(_RWSTD_VER) +# include +# if !defined(__STD_RWCOMPILER_H__) && !defined(_RWSTD_VER) +# error This is not the Rogue Wave standard library +# endif +#endif +// +// figure out a consistent version number: +// +#ifndef _RWSTD_VER +# define BOOST_RWSTD_VER 0x010000 +#elif _RWSTD_VER < 0x010000 +# define BOOST_RWSTD_VER (_RWSTD_VER << 8) +#else +# define BOOST_RWSTD_VER _RWSTD_VER +#endif + +#ifndef _RWSTD_VER +# define BOOST_STDLIB "Rogue Wave standard library version (Unknown version)" +#elif _RWSTD_VER < 0x04010200 + # define BOOST_STDLIB "Rogue Wave standard library version " BOOST_STRINGIZE(_RWSTD_VER) +#else +# ifdef _RWSTD_VER_STR +# define BOOST_STDLIB "Apache STDCXX standard library version " _RWSTD_VER_STR +# else +# define BOOST_STDLIB "Apache STDCXX standard library version " BOOST_STRINGIZE(_RWSTD_VER) +# endif +#endif + +// +// Prior to version 2.2.0 the primary template for std::numeric_limits +// does not have compile time constants, even though specializations of that +// template do: +// +#if BOOST_RWSTD_VER < 0x020200 +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +#endif + +// Sun CC 5.5 patch 113817-07 adds long long specialization, but does not change the +// library version number (http://sunsolve6.sun.com/search/document.do?assetkey=1-21-113817): +#if BOOST_RWSTD_VER <= 0x020101 && (!defined(__SUNPRO_CC) || (__SUNPRO_CC < 0x550)) +# define BOOST_NO_LONG_LONG_NUMERIC_LIMITS +# endif + +// +// Borland version of numeric_limits lacks __int64 specialisation: +// +#ifdef __BORLANDC__ +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +#endif + +// +// No std::iterator if it can't figure out default template args: +// +#if defined(_RWSTD_NO_SIMPLE_DEFAULT_TEMPLATES) || defined(RWSTD_NO_SIMPLE_DEFAULT_TEMPLATES) || (BOOST_RWSTD_VER < 0x020000) +# define BOOST_NO_STD_ITERATOR +#endif + +// +// No iterator traits without partial specialization: +// +#if defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) || defined(RWSTD_NO_CLASS_PARTIAL_SPEC) +# define BOOST_NO_STD_ITERATOR_TRAITS +#endif + +// +// Prior to version 2.0, std::auto_ptr was buggy, and there were no +// new-style iostreams, and no conformant std::allocator: +// +#if (BOOST_RWSTD_VER < 0x020000) +# define BOOST_NO_AUTO_PTR +# define BOOST_NO_STRINGSTREAM +# define BOOST_NO_STD_ALLOCATOR +# define BOOST_NO_STD_LOCALE +#endif + +// +// No template iterator constructors without member template support: +// +#if defined(RWSTD_NO_MEMBER_TEMPLATES) || defined(_RWSTD_NO_MEMBER_TEMPLATES) +# define BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS +#endif + +// +// RW defines _RWSTD_ALLOCATOR if the allocator is conformant and in use +// (the or _HPACC_ part is a hack - the library seems to define _RWSTD_ALLOCATOR +// on HP aCC systems even though the allocator is in fact broken): +// +#if !defined(_RWSTD_ALLOCATOR) || (defined(__HP_aCC) && __HP_aCC <= 33100) +# define BOOST_NO_STD_ALLOCATOR +#endif + +// +// If we have a std::locale, we still may not have std::use_facet: +// +#if defined(_RWSTD_NO_TEMPLATE_ON_RETURN_TYPE) && !defined(BOOST_NO_STD_LOCALE) +# define BOOST_NO_STD_USE_FACET +# define BOOST_HAS_TWO_ARG_USE_FACET +#endif + +// +// There's no std::distance prior to version 2, or without +// partial specialization support: +// +#if (BOOST_RWSTD_VER < 0x020000) || defined(_RWSTD_NO_CLASS_PARTIAL_SPEC) + #define BOOST_NO_STD_DISTANCE +#endif + +// +// Some versions of the rogue wave library don't have assignable +// OutputIterators: +// +#if BOOST_RWSTD_VER < 0x020100 +# define BOOST_NO_STD_OUTPUT_ITERATOR_ASSIGN +#endif + +// +// Disable BOOST_HAS_LONG_LONG when the library has no support for it. +// +#if !defined(_RWSTD_LONG_LONG) && defined(BOOST_HAS_LONG_LONG) +# undef BOOST_HAS_LONG_LONG +#endif + +// +// check that on HP-UX, the proper RW library is used +// +#if defined(__HP_aCC) && !defined(_HP_NAMESPACE_STD) +# error "Boost requires Standard RW library. Please compile and link with -AA" +#endif + +// +// Define macros specific to RW V2.2 on HP-UX +// +#if defined(__HP_aCC) && (BOOST_RWSTD_VER == 0x02020100) +# ifndef __HP_TC1_MAKE_PAIR +# define __HP_TC1_MAKE_PAIR +# endif +# ifndef _HP_INSTANTIATE_STD2_VL +# define _HP_INSTANTIATE_STD2_VL +# endif +#endif + +#if _RWSTD_VER < 0x05000000 +# define BOOST_NO_CXX11_HDR_ARRAY +#endif +// type_traits header is incomplete: +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +// +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/sgi.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/sgi.hpp new file mode 100644 index 0000000..c805271 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/sgi.hpp @@ -0,0 +1,158 @@ +// (C) Copyright John Maddock 2001 - 2003. +// (C) Copyright Darin Adler 2001. +// (C) Copyright Jens Maurer 2001 - 2003. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// generic SGI STL: + +#if !defined(__STL_CONFIG_H) +# include +# if !defined(__STL_CONFIG_H) +# error "This is not the SGI STL!" +# endif +#endif + +// +// No std::iterator traits without partial specialisation: +// +#if !defined(__STL_CLASS_PARTIAL_SPECIALIZATION) +# define BOOST_NO_STD_ITERATOR_TRAITS +#endif + +// +// No std::stringstream with gcc < 3 +// +#if defined(__GNUC__) && (__GNUC__ < 3) && \ + ((__GNUC_MINOR__ < 95) || (__GNUC_MINOR__ == 96)) && \ + !defined(__STL_USE_NEW_IOSTREAMS) || \ + defined(__APPLE_CC__) + // Note that we only set this for GNU C++ prior to 2.95 since the + // latest patches for that release do contain a minimal + // If you are running a 2.95 release prior to 2.95.3 then this will need + // setting, but there is no way to detect that automatically (other + // than by running the configure script). + // Also, the unofficial GNU C++ 2.96 included in RedHat 7.1 doesn't + // have . +# define BOOST_NO_STRINGSTREAM +#endif + +// Apple doesn't seem to reliably defined a *unix* macro +#if !defined(CYGWIN) && ( defined(__unix__) \ + || defined(__unix) \ + || defined(unix) \ + || defined(__APPLE__) \ + || defined(__APPLE) \ + || defined(APPLE)) +# include +#endif + + +// +// Assume no std::locale without own iostreams (this may be an +// incorrect assumption in some cases): +// +#if !defined(__SGI_STL_OWN_IOSTREAMS) && !defined(__STL_USE_NEW_IOSTREAMS) +# define BOOST_NO_STD_LOCALE +#endif + +// +// Original native SGI streams have non-standard std::messages facet: +// +#if defined(__sgi) && (_COMPILER_VERSION <= 650) && !defined(__SGI_STL_OWN_IOSTREAMS) +# define BOOST_NO_STD_LOCALE +#endif + +// +// SGI's new iostreams have missing "const" in messages<>::open +// +#if defined(__sgi) && (_COMPILER_VERSION <= 740) && defined(__STL_USE_NEW_IOSTREAMS) +# define BOOST_NO_STD_MESSAGES +#endif + +// +// No template iterator constructors, or std::allocator +// without member templates: +// +#if !defined(__STL_MEMBER_TEMPLATES) +# define BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS +# define BOOST_NO_STD_ALLOCATOR +#endif + +// +// We always have SGI style hash_set, hash_map, and slist: +// +#define BOOST_HAS_HASH +#define BOOST_HAS_SLIST + +// +// If this is GNU libstdc++2, then no and no std::wstring: +// +#if (defined(__GNUC__) && (__GNUC__ < 3)) +# include +# if defined(__BASTRING__) +# define BOOST_NO_LIMITS +// Note: will provide compile-time constants +# undef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +# define BOOST_NO_STD_WSTRING +# endif +#endif + +// +// There is no standard iterator unless we have namespace support: +// +#if !defined(__STL_USE_NAMESPACES) +# define BOOST_NO_STD_ITERATOR +#endif + +// +// Intrinsic type_traits support. +// The SGI STL has it's own __type_traits class, which +// has intrinsic compiler support with SGI's compilers. +// Whatever map SGI style type traits to boost equivalents: +// +#define BOOST_HAS_SGI_TYPE_TRAITS + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#define BOOST_STDLIB "SGI standard library" \ No newline at end of file diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/stlport.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/stlport.hpp new file mode 100644 index 0000000..bbc4176 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/stlport.hpp @@ -0,0 +1,248 @@ +// (C) Copyright John Maddock 2001 - 2002. +// (C) Copyright Darin Adler 2001. +// (C) Copyright Jens Maurer 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +// STLPort standard library config: + +#if !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) +# include +# if !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) +# error "This is not STLPort!" +# endif +#endif + +// Apple doesn't seem to reliably defined a *unix* macro +#if !defined(CYGWIN) && ( defined(__unix__) \ + || defined(__unix) \ + || defined(unix) \ + || defined(__APPLE__) \ + || defined(__APPLE) \ + || defined(APPLE)) +# include +#endif + +// +// __STL_STATIC_CONST_INIT_BUG implies BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +// for versions prior to 4.1(beta) +// +#if (defined(__STL_STATIC_CONST_INIT_BUG) || defined(_STLP_STATIC_CONST_INIT_BUG)) && (__SGI_STL_PORT <= 0x400) +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +#endif + +// +// If STLport thinks that there is no partial specialisation, then there is no +// std::iterator traits: +// +#if !(defined(_STLP_CLASS_PARTIAL_SPECIALIZATION) || defined(__STL_CLASS_PARTIAL_SPECIALIZATION)) +# define BOOST_NO_STD_ITERATOR_TRAITS +#endif + +// +// No new style iostreams on GCC without STLport's iostreams enabled: +// +#if (defined(__GNUC__) && (__GNUC__ < 3)) && !(defined(__SGI_STL_OWN_IOSTREAMS) || defined(_STLP_OWN_IOSTREAMS)) +# define BOOST_NO_STRINGSTREAM +#endif + +// +// No new iostreams implies no std::locale, and no std::stringstream: +// +#if defined(__STL_NO_IOSTREAMS) || defined(__STL_NO_NEW_IOSTREAMS) || defined(_STLP_NO_IOSTREAMS) || defined(_STLP_NO_NEW_IOSTREAMS) +# define BOOST_NO_STD_LOCALE +# define BOOST_NO_STRINGSTREAM +#endif + +// +// If the streams are not native, and we have a "using ::x" compiler bug +// then the io stream facets are not available in namespace std:: +// +#ifdef _STLPORT_VERSION +# if !(_STLPORT_VERSION >= 0x500) && !defined(_STLP_OWN_IOSTREAMS) && defined(_STLP_USE_NAMESPACES) && defined(BOOST_NO_USING_TEMPLATE) && !defined(__BORLANDC__) +# define BOOST_NO_STD_LOCALE +# endif +#else +# if !defined(__SGI_STL_OWN_IOSTREAMS) && defined(__STL_USE_NAMESPACES) && defined(BOOST_NO_USING_TEMPLATE) && !defined(__BORLANDC__) +# define BOOST_NO_STD_LOCALE +# endif +#endif + +#if defined(_STLPORT_VERSION) && (_STLPORT_VERSION >= 0x520) +# define BOOST_HAS_TR1_UNORDERED_SET +# define BOOST_HAS_TR1_UNORDERED_MAP +#endif +// +// Without member template support enabled, their are no template +// iterate constructors, and no std::allocator: +// +#if !(defined(__STL_MEMBER_TEMPLATES) || defined(_STLP_MEMBER_TEMPLATES)) +# define BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS +# define BOOST_NO_STD_ALLOCATOR +#endif +// +// however we always have at least a partial allocator: +// +#define BOOST_HAS_PARTIAL_STD_ALLOCATOR + +#if !defined(_STLP_MEMBER_TEMPLATE_CLASSES) || defined(_STLP_DONT_SUPPORT_REBIND_MEMBER_TEMPLATE) +# define BOOST_NO_STD_ALLOCATOR +#endif + +#if defined(_STLP_NO_MEMBER_TEMPLATE_KEYWORD) && defined(BOOST_MSVC) && (BOOST_MSVC <= 1300) +# define BOOST_NO_STD_ALLOCATOR +#endif + +// +// If STLport thinks there is no wchar_t at all, then we have to disable +// the support for the relevant specilazations of std:: templates. +// +#if !defined(_STLP_HAS_WCHAR_T) && !defined(_STLP_WCHAR_T_IS_USHORT) +# ifndef BOOST_NO_STD_WSTRING +# define BOOST_NO_STD_WSTRING +# endif +# ifndef BOOST_NO_STD_WSTREAMBUF +# define BOOST_NO_STD_WSTREAMBUF +# endif +#endif + +// +// We always have SGI style hash_set, hash_map, and slist: +// +#ifndef _STLP_NO_EXTENSIONS +#define BOOST_HAS_HASH +#define BOOST_HAS_SLIST +#endif + +// +// STLport does a good job of importing names into namespace std::, +// but doesn't always get them all, define BOOST_NO_STDC_NAMESPACE, since our +// workaround does not conflict with STLports: +// +// +// Harold Howe says: +// Borland switched to STLport in BCB6. Defining BOOST_NO_STDC_NAMESPACE with +// BCB6 does cause problems. If we detect C++ Builder, then don't define +// BOOST_NO_STDC_NAMESPACE +// +#if !defined(__BORLANDC__) && !defined(__DMC__) +// +// If STLport is using it's own namespace, and the real names are in +// the global namespace, then we duplicate STLport's using declarations +// (by defining BOOST_NO_STDC_NAMESPACE), we do this because STLport doesn't +// necessarily import all the names we need into namespace std:: +// +# if (defined(__STL_IMPORT_VENDOR_CSTD) \ + || defined(__STL_USE_OWN_NAMESPACE) \ + || defined(_STLP_IMPORT_VENDOR_CSTD) \ + || defined(_STLP_USE_OWN_NAMESPACE)) \ + && (defined(__STL_VENDOR_GLOBAL_CSTD) || defined (_STLP_VENDOR_GLOBAL_CSTD)) +# define BOOST_NO_STDC_NAMESPACE +# define BOOST_NO_EXCEPTION_STD_NAMESPACE +# endif +#elif defined(__BORLANDC__) && __BORLANDC__ < 0x560 +// STLport doesn't import std::abs correctly: +#include +namespace std { using ::abs; } +// and strcmp/strcpy don't get imported either ('cos they are macros) +#include +#ifdef strcpy +# undef strcpy +#endif +#ifdef strcmp +# undef strcmp +#endif +#ifdef _STLP_VENDOR_CSTD +namespace std{ using _STLP_VENDOR_CSTD::strcmp; using _STLP_VENDOR_CSTD::strcpy; } +#endif +#endif + +// +// std::use_facet may be non-standard, uses a class instead: +// +#if defined(__STL_NO_EXPLICIT_FUNCTION_TMPL_ARGS) || defined(_STLP_NO_EXPLICIT_FUNCTION_TMPL_ARGS) +# define BOOST_NO_STD_USE_FACET +# define BOOST_HAS_STLP_USE_FACET +#endif + +// +// If STLport thinks there are no wide functions, etc. is not working; but +// only if BOOST_NO_STDC_NAMESPACE is not defined (if it is then we do the import +// into std:: ourselves). +// +#if defined(_STLP_NO_NATIVE_WIDE_FUNCTIONS) && !defined(BOOST_NO_STDC_NAMESPACE) +# define BOOST_NO_CWCHAR +# define BOOST_NO_CWCTYPE +#endif + +// +// If STLport for some reason was configured so that it thinks that wchar_t +// is not an intrinsic type, then we have to disable the support for it as +// well (we would be missing required specializations otherwise). +// +#if !defined( _STLP_HAS_WCHAR_T) || defined(_STLP_WCHAR_T_IS_USHORT) +# undef BOOST_NO_INTRINSIC_WCHAR_T +# define BOOST_NO_INTRINSIC_WCHAR_T +#endif + +// +// Borland ships a version of STLport with C++ Builder 6 that lacks +// hashtables and the like: +// +#if defined(__BORLANDC__) && (__BORLANDC__ == 0x560) +# undef BOOST_HAS_HASH +#endif + +// +// gcc-2.95.3/STLPort does not like the using declarations we use to get ADL with std::min/max +// +#if defined(__GNUC__) && (__GNUC__ < 3) +# include // for std::min and std::max +# define BOOST_USING_STD_MIN() ((void)0) +# define BOOST_USING_STD_MAX() ((void)0) +namespace boost { using std::min; using std::max; } +#endif + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#define BOOST_STDLIB "STLPort standard library version " BOOST_STRINGIZE(__SGI_STL_PORT) diff --git a/thirdparty/source/boost_1_61_0/boost/config/stdlib/vacpp.hpp b/thirdparty/source/boost_1_61_0/boost/config/stdlib/vacpp.hpp new file mode 100644 index 0000000..4ccd0d2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/stdlib/vacpp.hpp @@ -0,0 +1,64 @@ +// (C) Copyright John Maddock 2001 - 2002. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for most recent version. + +#if __IBMCPP__ <= 501 +# define BOOST_NO_STD_ALLOCATOR +#endif + +#define BOOST_HAS_MACRO_USE_FACET +#define BOOST_NO_STD_MESSAGES + +// Apple doesn't seem to reliably defined a *unix* macro +#if !defined(CYGWIN) && ( defined(__unix__) \ + || defined(__unix) \ + || defined(unix) \ + || defined(__APPLE__) \ + || defined(__APPLE) \ + || defined(APPLE)) +# include +#endif + +// C++0x headers not yet implemented +// +# define BOOST_NO_CXX11_HDR_ARRAY +# define BOOST_NO_CXX11_HDR_CHRONO +# define BOOST_NO_CXX11_HDR_CODECVT +# define BOOST_NO_CXX11_HDR_CONDITION_VARIABLE +# define BOOST_NO_CXX11_HDR_FORWARD_LIST +# define BOOST_NO_CXX11_HDR_FUTURE +# define BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# define BOOST_NO_CXX11_HDR_MUTEX +# define BOOST_NO_CXX11_HDR_RANDOM +# define BOOST_NO_CXX11_HDR_RATIO +# define BOOST_NO_CXX11_HDR_REGEX +# define BOOST_NO_CXX11_HDR_SYSTEM_ERROR +# define BOOST_NO_CXX11_HDR_THREAD +# define BOOST_NO_CXX11_HDR_TUPLE +# define BOOST_NO_CXX11_HDR_TYPE_TRAITS +# define BOOST_NO_CXX11_HDR_TYPEINDEX +# define BOOST_NO_CXX11_HDR_UNORDERED_MAP +# define BOOST_NO_CXX11_HDR_UNORDERED_SET +# define BOOST_NO_CXX11_NUMERIC_LIMITS +# define BOOST_NO_CXX11_ALLOCATOR +# define BOOST_NO_CXX11_ATOMIC_SMART_PTR +# define BOOST_NO_CXX11_SMART_PTR +# define BOOST_NO_CXX11_HDR_FUNCTIONAL +# define BOOST_NO_CXX11_HDR_ATOMIC +# define BOOST_NO_CXX11_STD_ALIGN +# define BOOST_NO_CXX11_ADDRESSOF + +#if defined(__has_include) +#if !__has_include() +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#elif __cplusplus < 201402 +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif +#else +# define BOOST_NO_CXX14_HDR_SHARED_MUTEX +#endif + +#define BOOST_STDLIB "Visual Age default standard library" diff --git a/thirdparty/source/boost_1_61_0/boost/config/suffix.hpp b/thirdparty/source/boost_1_61_0/boost/config/suffix.hpp new file mode 100644 index 0000000..17bf102 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/suffix.hpp @@ -0,0 +1,1007 @@ +// Boost config.hpp configuration header file ------------------------------// +// boostinspect:ndprecated_macros -- tell the inspect tool to ignore this file + +// Copyright (c) 2001-2003 John Maddock +// Copyright (c) 2001 Darin Adler +// Copyright (c) 2001 Peter Dimov +// Copyright (c) 2002 Bill Kempf +// Copyright (c) 2002 Jens Maurer +// Copyright (c) 2002-2003 David Abrahams +// Copyright (c) 2003 Gennaro Prota +// Copyright (c) 2003 Eric Friedman +// Copyright (c) 2010 Eric Jourdanneau, Joel Falcou +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/ for most recent version. + +// Boost config.hpp policy and rationale documentation has been moved to +// http://www.boost.org/libs/config/ +// +// This file is intended to be stable, and relatively unchanging. +// It should contain boilerplate code only - no compiler specific +// code unless it is unavoidable - no changes unless unavoidable. + +#ifndef BOOST_CONFIG_SUFFIX_HPP +#define BOOST_CONFIG_SUFFIX_HPP + +#if defined(__GNUC__) && (__GNUC__ >= 4) +// +// Some GCC-4.x versions issue warnings even when __extension__ is used, +// so use this as a workaround: +// +#pragma GCC system_header +#endif + +// +// ensure that visibility macros are always defined, thus symplifying use +// +#ifndef BOOST_SYMBOL_EXPORT +# define BOOST_SYMBOL_EXPORT +#endif +#ifndef BOOST_SYMBOL_IMPORT +# define BOOST_SYMBOL_IMPORT +#endif +#ifndef BOOST_SYMBOL_VISIBLE +# define BOOST_SYMBOL_VISIBLE +#endif + +// +// look for long long by looking for the appropriate macros in . +// Note that we use limits.h rather than climits for maximal portability, +// remember that since these just declare a bunch of macros, there should be +// no namespace issues from this. +// +#if !defined(BOOST_HAS_LONG_LONG) && !defined(BOOST_NO_LONG_LONG) \ + && !defined(BOOST_MSVC) && !defined(__BORLANDC__) +# include +# if (defined(ULLONG_MAX) || defined(ULONG_LONG_MAX) || defined(ULONGLONG_MAX)) +# define BOOST_HAS_LONG_LONG +# else +# define BOOST_NO_LONG_LONG +# endif +#endif + +// GCC 3.x will clean up all of those nasty macro definitions that +// BOOST_NO_CTYPE_FUNCTIONS is intended to help work around, so undefine +// it under GCC 3.x. +#if defined(__GNUC__) && (__GNUC__ >= 3) && defined(BOOST_NO_CTYPE_FUNCTIONS) +# undef BOOST_NO_CTYPE_FUNCTIONS +#endif + +// +// Assume any extensions are in namespace std:: unless stated otherwise: +// +# ifndef BOOST_STD_EXTENSION_NAMESPACE +# define BOOST_STD_EXTENSION_NAMESPACE std +# endif + +// +// If cv-qualified specializations are not allowed, then neither are cv-void ones: +// +# if defined(BOOST_NO_CV_SPECIALIZATIONS) \ + && !defined(BOOST_NO_CV_VOID_SPECIALIZATIONS) +# define BOOST_NO_CV_VOID_SPECIALIZATIONS +# endif + +// +// If there is no numeric_limits template, then it can't have any compile time +// constants either! +// +# if defined(BOOST_NO_LIMITS) \ + && !defined(BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS) +# define BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +# define BOOST_NO_LONG_LONG_NUMERIC_LIMITS +# endif + +// +// if there is no long long then there is no specialisation +// for numeric_limits either: +// +#if !defined(BOOST_HAS_LONG_LONG) && !defined(BOOST_NO_LONG_LONG_NUMERIC_LIMITS) +# define BOOST_NO_LONG_LONG_NUMERIC_LIMITS +#endif + +// +// if there is no __int64 then there is no specialisation +// for numeric_limits<__int64> either: +// +#if !defined(BOOST_HAS_MS_INT64) && !defined(BOOST_NO_MS_INT64_NUMERIC_LIMITS) +# define BOOST_NO_MS_INT64_NUMERIC_LIMITS +#endif + +// +// if member templates are supported then so is the +// VC6 subset of member templates: +// +# if !defined(BOOST_NO_MEMBER_TEMPLATES) \ + && !defined(BOOST_MSVC6_MEMBER_TEMPLATES) +# define BOOST_MSVC6_MEMBER_TEMPLATES +# endif + +// +// Without partial specialization, can't test for partial specialisation bugs: +// +# if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) \ + && !defined(BOOST_BCB_PARTIAL_SPECIALIZATION_BUG) +# define BOOST_BCB_PARTIAL_SPECIALIZATION_BUG +# endif + +// +// Without partial specialization, we can't have array-type partial specialisations: +// +# if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) \ + && !defined(BOOST_NO_ARRAY_TYPE_SPECIALIZATIONS) +# define BOOST_NO_ARRAY_TYPE_SPECIALIZATIONS +# endif + +// +// Without partial specialization, std::iterator_traits can't work: +// +# if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) \ + && !defined(BOOST_NO_STD_ITERATOR_TRAITS) +# define BOOST_NO_STD_ITERATOR_TRAITS +# endif + +// +// Without partial specialization, partial +// specialization with default args won't work either: +// +# if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) \ + && !defined(BOOST_NO_PARTIAL_SPECIALIZATION_IMPLICIT_DEFAULT_ARGS) +# define BOOST_NO_PARTIAL_SPECIALIZATION_IMPLICIT_DEFAULT_ARGS +# endif + +// +// Without member template support, we can't have template constructors +// in the standard library either: +// +# if defined(BOOST_NO_MEMBER_TEMPLATES) \ + && !defined(BOOST_MSVC6_MEMBER_TEMPLATES) \ + && !defined(BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS) +# define BOOST_NO_TEMPLATED_ITERATOR_CONSTRUCTORS +# endif + +// +// Without member template support, we can't have a conforming +// std::allocator template either: +// +# if defined(BOOST_NO_MEMBER_TEMPLATES) \ + && !defined(BOOST_MSVC6_MEMBER_TEMPLATES) \ + && !defined(BOOST_NO_STD_ALLOCATOR) +# define BOOST_NO_STD_ALLOCATOR +# endif + +// +// without ADL support then using declarations will break ADL as well: +// +#if defined(BOOST_NO_ARGUMENT_DEPENDENT_LOOKUP) && !defined(BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL) +# define BOOST_FUNCTION_SCOPE_USING_DECLARATION_BREAKS_ADL +#endif + +// +// Without typeid support we have no dynamic RTTI either: +// +#if defined(BOOST_NO_TYPEID) && !defined(BOOST_NO_RTTI) +# define BOOST_NO_RTTI +#endif + +// +// If we have a standard allocator, then we have a partial one as well: +// +#if !defined(BOOST_NO_STD_ALLOCATOR) +# define BOOST_HAS_PARTIAL_STD_ALLOCATOR +#endif + +// +// We can't have a working std::use_facet if there is no std::locale: +// +# if defined(BOOST_NO_STD_LOCALE) && !defined(BOOST_NO_STD_USE_FACET) +# define BOOST_NO_STD_USE_FACET +# endif + +// +// We can't have a std::messages facet if there is no std::locale: +// +# if defined(BOOST_NO_STD_LOCALE) && !defined(BOOST_NO_STD_MESSAGES) +# define BOOST_NO_STD_MESSAGES +# endif + +// +// We can't have a working std::wstreambuf if there is no std::locale: +// +# if defined(BOOST_NO_STD_LOCALE) && !defined(BOOST_NO_STD_WSTREAMBUF) +# define BOOST_NO_STD_WSTREAMBUF +# endif + +// +// We can't have a if there is no : +// +# if defined(BOOST_NO_CWCHAR) && !defined(BOOST_NO_CWCTYPE) +# define BOOST_NO_CWCTYPE +# endif + +// +// We can't have a swprintf if there is no : +// +# if defined(BOOST_NO_CWCHAR) && !defined(BOOST_NO_SWPRINTF) +# define BOOST_NO_SWPRINTF +# endif + +// +// If Win32 support is turned off, then we must turn off +// threading support also, unless there is some other +// thread API enabled: +// +#if defined(BOOST_DISABLE_WIN32) && defined(_WIN32) \ + && !defined(BOOST_DISABLE_THREADS) && !defined(BOOST_HAS_PTHREADS) +# define BOOST_DISABLE_THREADS +#endif + +// +// Turn on threading support if the compiler thinks that it's in +// multithreaded mode. We put this here because there are only a +// limited number of macros that identify this (if there's any missing +// from here then add to the appropriate compiler section): +// +#if (defined(__MT__) || defined(_MT) || defined(_REENTRANT) \ + || defined(_PTHREADS) || defined(__APPLE__) || defined(__DragonFly__)) \ + && !defined(BOOST_HAS_THREADS) +# define BOOST_HAS_THREADS +#endif + +// +// Turn threading support off if BOOST_DISABLE_THREADS is defined: +// +#if defined(BOOST_DISABLE_THREADS) && defined(BOOST_HAS_THREADS) +# undef BOOST_HAS_THREADS +#endif + +// +// Turn threading support off if we don't recognise the threading API: +// +#if defined(BOOST_HAS_THREADS) && !defined(BOOST_HAS_PTHREADS)\ + && !defined(BOOST_HAS_WINTHREADS) && !defined(BOOST_HAS_BETHREADS)\ + && !defined(BOOST_HAS_MPTASKS) +# undef BOOST_HAS_THREADS +#endif + +// +// Turn threading detail macros off if we don't (want to) use threading +// +#ifndef BOOST_HAS_THREADS +# undef BOOST_HAS_PTHREADS +# undef BOOST_HAS_PTHREAD_MUTEXATTR_SETTYPE +# undef BOOST_HAS_PTHREAD_YIELD +# undef BOOST_HAS_PTHREAD_DELAY_NP +# undef BOOST_HAS_WINTHREADS +# undef BOOST_HAS_BETHREADS +# undef BOOST_HAS_MPTASKS +#endif + +// +// If the compiler claims to be C99 conformant, then it had better +// have a : +// +# if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) +# define BOOST_HAS_STDINT_H +# ifndef BOOST_HAS_LOG1P +# define BOOST_HAS_LOG1P +# endif +# ifndef BOOST_HAS_EXPM1 +# define BOOST_HAS_EXPM1 +# endif +# endif + +// +// Define BOOST_NO_SLIST and BOOST_NO_HASH if required. +// Note that this is for backwards compatibility only. +// +# if !defined(BOOST_HAS_SLIST) && !defined(BOOST_NO_SLIST) +# define BOOST_NO_SLIST +# endif + +# if !defined(BOOST_HAS_HASH) && !defined(BOOST_NO_HASH) +# define BOOST_NO_HASH +# endif + +// +// Set BOOST_SLIST_HEADER if not set already: +// +#if defined(BOOST_HAS_SLIST) && !defined(BOOST_SLIST_HEADER) +# define BOOST_SLIST_HEADER +#endif + +// +// Set BOOST_HASH_SET_HEADER if not set already: +// +#if defined(BOOST_HAS_HASH) && !defined(BOOST_HASH_SET_HEADER) +# define BOOST_HASH_SET_HEADER +#endif + +// +// Set BOOST_HASH_MAP_HEADER if not set already: +// +#if defined(BOOST_HAS_HASH) && !defined(BOOST_HASH_MAP_HEADER) +# define BOOST_HASH_MAP_HEADER +#endif + +// BOOST_HAS_ABI_HEADERS +// This macro gets set if we have headers that fix the ABI, +// and prevent ODR violations when linking to external libraries: +#if defined(BOOST_ABI_PREFIX) && defined(BOOST_ABI_SUFFIX) && !defined(BOOST_HAS_ABI_HEADERS) +# define BOOST_HAS_ABI_HEADERS +#endif + +#if defined(BOOST_HAS_ABI_HEADERS) && defined(BOOST_DISABLE_ABI_HEADERS) +# undef BOOST_HAS_ABI_HEADERS +#endif + +// BOOST_NO_STDC_NAMESPACE workaround --------------------------------------// +// Because std::size_t usage is so common, even in boost headers which do not +// otherwise use the C library, the workaround is included here so +// that ugly workaround code need not appear in many other boost headers. +// NOTE WELL: This is a workaround for non-conforming compilers; +// must still be #included in the usual places so that inclusion +// works as expected with standard conforming compilers. The resulting +// double inclusion of is harmless. + +# if defined(BOOST_NO_STDC_NAMESPACE) && defined(__cplusplus) +# include + namespace std { using ::ptrdiff_t; using ::size_t; } +# endif + +// Workaround for the unfortunate min/max macros defined by some platform headers + +#define BOOST_PREVENT_MACRO_SUBSTITUTION + +#ifndef BOOST_USING_STD_MIN +# define BOOST_USING_STD_MIN() using std::min +#endif + +#ifndef BOOST_USING_STD_MAX +# define BOOST_USING_STD_MAX() using std::max +#endif + +// BOOST_NO_STD_MIN_MAX workaround -----------------------------------------// + +# if defined(BOOST_NO_STD_MIN_MAX) && defined(__cplusplus) + +namespace std { + template + inline const _Tp& min BOOST_PREVENT_MACRO_SUBSTITUTION (const _Tp& __a, const _Tp& __b) { + return __b < __a ? __b : __a; + } + template + inline const _Tp& max BOOST_PREVENT_MACRO_SUBSTITUTION (const _Tp& __a, const _Tp& __b) { + return __a < __b ? __b : __a; + } +} + +# endif + +// BOOST_STATIC_CONSTANT workaround --------------------------------------- // +// On compilers which don't allow in-class initialization of static integral +// constant members, we must use enums as a workaround if we want the constants +// to be available at compile-time. This macro gives us a convenient way to +// declare such constants. + +# ifdef BOOST_NO_INCLASS_MEMBER_INITIALIZATION +# define BOOST_STATIC_CONSTANT(type, assignment) enum { assignment } +# else +# define BOOST_STATIC_CONSTANT(type, assignment) static const type assignment +# endif + +// BOOST_USE_FACET / HAS_FACET workaround ----------------------------------// +// When the standard library does not have a conforming std::use_facet there +// are various workarounds available, but they differ from library to library. +// The same problem occurs with has_facet. +// These macros provide a consistent way to access a locale's facets. +// Usage: +// replace +// std::use_facet(loc); +// with +// BOOST_USE_FACET(Type, loc); +// Note do not add a std:: prefix to the front of BOOST_USE_FACET! +// Use for BOOST_HAS_FACET is analogous. + +#if defined(BOOST_NO_STD_USE_FACET) +# ifdef BOOST_HAS_TWO_ARG_USE_FACET +# define BOOST_USE_FACET(Type, loc) std::use_facet(loc, static_cast(0)) +# define BOOST_HAS_FACET(Type, loc) std::has_facet(loc, static_cast(0)) +# elif defined(BOOST_HAS_MACRO_USE_FACET) +# define BOOST_USE_FACET(Type, loc) std::_USE(loc, Type) +# define BOOST_HAS_FACET(Type, loc) std::_HAS(loc, Type) +# elif defined(BOOST_HAS_STLP_USE_FACET) +# define BOOST_USE_FACET(Type, loc) (*std::_Use_facet(loc)) +# define BOOST_HAS_FACET(Type, loc) std::has_facet< Type >(loc) +# endif +#else +# define BOOST_USE_FACET(Type, loc) std::use_facet< Type >(loc) +# define BOOST_HAS_FACET(Type, loc) std::has_facet< Type >(loc) +#endif + +// BOOST_NESTED_TEMPLATE workaround ------------------------------------------// +// Member templates are supported by some compilers even though they can't use +// the A::template member syntax, as a workaround replace: +// +// typedef typename A::template rebind binder; +// +// with: +// +// typedef typename A::BOOST_NESTED_TEMPLATE rebind binder; + +#ifndef BOOST_NO_MEMBER_TEMPLATE_KEYWORD +# define BOOST_NESTED_TEMPLATE template +#else +# define BOOST_NESTED_TEMPLATE +#endif + +// BOOST_UNREACHABLE_RETURN(x) workaround -------------------------------------// +// Normally evaluates to nothing, unless BOOST_NO_UNREACHABLE_RETURN_DETECTION +// is defined, in which case it evaluates to return x; Use when you have a return +// statement that can never be reached. + +#ifndef BOOST_UNREACHABLE_RETURN +# ifdef BOOST_NO_UNREACHABLE_RETURN_DETECTION +# define BOOST_UNREACHABLE_RETURN(x) return x; +# else +# define BOOST_UNREACHABLE_RETURN(x) +# endif +#endif + +// BOOST_DEDUCED_TYPENAME workaround ------------------------------------------// +// +// Some compilers don't support the use of `typename' for dependent +// types in deduced contexts, e.g. +// +// template void f(T, typename T::type); +// ^^^^^^^^ +// Replace these declarations with: +// +// template void f(T, BOOST_DEDUCED_TYPENAME T::type); + +#ifndef BOOST_NO_DEDUCED_TYPENAME +# define BOOST_DEDUCED_TYPENAME typename +#else +# define BOOST_DEDUCED_TYPENAME +#endif + +#ifndef BOOST_NO_TYPENAME_WITH_CTOR +# define BOOST_CTOR_TYPENAME typename +#else +# define BOOST_CTOR_TYPENAME +#endif + +// long long workaround ------------------------------------------// +// On gcc (and maybe other compilers?) long long is alway supported +// but it's use may generate either warnings (with -ansi), or errors +// (with -pedantic -ansi) unless it's use is prefixed by __extension__ +// +#if defined(BOOST_HAS_LONG_LONG) && defined(__cplusplus) +namespace boost{ +# ifdef __GNUC__ + __extension__ typedef long long long_long_type; + __extension__ typedef unsigned long long ulong_long_type; +# else + typedef long long long_long_type; + typedef unsigned long long ulong_long_type; +# endif +} +#endif +// same again for __int128: +#if defined(BOOST_HAS_INT128) && defined(__cplusplus) +namespace boost{ +# ifdef __GNUC__ + __extension__ typedef __int128 int128_type; + __extension__ typedef unsigned __int128 uint128_type; +# else + typedef __int128 int128_type; + typedef unsigned __int128 uint128_type; +# endif +} +#endif +// same again for __float128: +#if defined(BOOST_HAS_FLOAT128) && defined(__cplusplus) +namespace boost { +# ifdef __GNUC__ + __extension__ typedef __float128 float128_type; +# else + typedef __float128 float128_type; +# endif +} +#endif + +// BOOST_[APPEND_]EXPLICIT_TEMPLATE_[NON_]TYPE macros --------------------------// + +// These macros are obsolete. Port away and remove. + +# define BOOST_EXPLICIT_TEMPLATE_TYPE(t) +# define BOOST_EXPLICIT_TEMPLATE_TYPE_SPEC(t) +# define BOOST_EXPLICIT_TEMPLATE_NON_TYPE(t, v) +# define BOOST_EXPLICIT_TEMPLATE_NON_TYPE_SPEC(t, v) + +# define BOOST_APPEND_EXPLICIT_TEMPLATE_TYPE(t) +# define BOOST_APPEND_EXPLICIT_TEMPLATE_TYPE_SPEC(t) +# define BOOST_APPEND_EXPLICIT_TEMPLATE_NON_TYPE(t, v) +# define BOOST_APPEND_EXPLICIT_TEMPLATE_NON_TYPE_SPEC(t, v) + +// When BOOST_NO_STD_TYPEINFO is defined, we can just import +// the global definition into std namespace: +#if defined(BOOST_NO_STD_TYPEINFO) && defined(__cplusplus) +#include +namespace std{ using ::type_info; } +#endif + +// ---------------------------------------------------------------------------// + +// +// Helper macro BOOST_STRINGIZE: +// Converts the parameter X to a string after macro replacement +// on X has been performed. +// +#define BOOST_STRINGIZE(X) BOOST_DO_STRINGIZE(X) +#define BOOST_DO_STRINGIZE(X) #X + +// +// Helper macro BOOST_JOIN: +// The following piece of macro magic joins the two +// arguments together, even when one of the arguments is +// itself a macro (see 16.3.1 in C++ standard). The key +// is that macro expansion of macro arguments does not +// occur in BOOST_DO_JOIN2 but does in BOOST_DO_JOIN. +// +#define BOOST_JOIN( X, Y ) BOOST_DO_JOIN( X, Y ) +#define BOOST_DO_JOIN( X, Y ) BOOST_DO_JOIN2(X,Y) +#define BOOST_DO_JOIN2( X, Y ) X##Y + +// +// Set some default values for compiler/library/platform names. +// These are for debugging config setup only: +// +# ifndef BOOST_COMPILER +# define BOOST_COMPILER "Unknown ISO C++ Compiler" +# endif +# ifndef BOOST_STDLIB +# define BOOST_STDLIB "Unknown ISO standard library" +# endif +# ifndef BOOST_PLATFORM +# if defined(unix) || defined(__unix) || defined(_XOPEN_SOURCE) \ + || defined(_POSIX_SOURCE) +# define BOOST_PLATFORM "Generic Unix" +# else +# define BOOST_PLATFORM "Unknown" +# endif +# endif + +// +// Set some default values GPU support +// +# ifndef BOOST_GPU_ENABLED +# define BOOST_GPU_ENABLED +# endif + +// BOOST_FORCEINLINE ---------------------------------------------// +// Macro to use in place of 'inline' to force a function to be inline +#if !defined(BOOST_FORCEINLINE) +# if defined(_MSC_VER) +# define BOOST_FORCEINLINE __forceinline +# elif defined(__GNUC__) && __GNUC__ > 3 + // Clang also defines __GNUC__ (as 4) +# define BOOST_FORCEINLINE inline __attribute__ ((__always_inline__)) +# else +# define BOOST_FORCEINLINE inline +# endif +#endif + +// BOOST_NOINLINE ---------------------------------------------// +// Macro to use in place of 'inline' to prevent a function to be inlined +#if !defined(BOOST_NOINLINE) +# if defined(_MSC_VER) +# define BOOST_NOINLINE __declspec(noinline) +# elif defined(__GNUC__) && __GNUC__ > 3 + // Clang also defines __GNUC__ (as 4) +# if defined(__CUDACC__) + // nvcc doesn't always parse __noinline__, + // see: https://svn.boost.org/trac/boost/ticket/9392 +# define BOOST_NOINLINE __attribute__ ((noinline)) +# else +# define BOOST_NOINLINE __attribute__ ((__noinline__)) +# endif +# else +# define BOOST_NOINLINE +# endif +#endif + +// BOOST_NORETURN ---------------------------------------------// +// Macro to use before a function declaration/definition to designate +// the function as not returning normally (i.e. with a return statement +// or by leaving the function scope, if the function return type is void). +#if !defined(BOOST_NORETURN) +# if defined(_MSC_VER) +# define BOOST_NORETURN __declspec(noreturn) +# elif defined(__GNUC__) +# define BOOST_NORETURN __attribute__ ((__noreturn__)) +# else +# define BOOST_NO_NORETURN +# define BOOST_NORETURN +# endif +#endif + +// Branch prediction hints +// These macros are intended to wrap conditional expressions that yield true or false +// +// if (BOOST_LIKELY(var == 10)) +// { +// // the most probable code here +// } +// +#if !defined(BOOST_LIKELY) +# define BOOST_LIKELY(x) x +#endif +#if !defined(BOOST_UNLIKELY) +# define BOOST_UNLIKELY(x) x +#endif + +// Type and data alignment specification +// +#if !defined(BOOST_NO_CXX11_ALIGNAS) +# define BOOST_ALIGNMENT(x) alignas(x) +#elif defined(_MSC_VER) +# define BOOST_ALIGNMENT(x) __declspec(align(x)) +#elif defined(__GNUC__) +# define BOOST_ALIGNMENT(x) __attribute__ ((__aligned__(x))) +#else +# define BOOST_NO_ALIGNMENT +# define BOOST_ALIGNMENT(x) +#endif + +// Lack of non-public defaulted functions is implied by the lack of any defaulted functions +#if !defined(BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS) && defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) +# define BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS +#endif + +// Defaulted and deleted function declaration helpers +// These macros are intended to be inside a class definition. +// BOOST_DEFAULTED_FUNCTION accepts the function declaration and its +// body, which will be used if the compiler doesn't support defaulted functions. +// BOOST_DELETED_FUNCTION only accepts the function declaration. It +// will expand to a private function declaration, if the compiler doesn't support +// deleted functions. Because of this it is recommended to use BOOST_DELETED_FUNCTION +// in the end of the class definition. +// +// class my_class +// { +// public: +// // Default-constructible +// BOOST_DEFAULTED_FUNCTION(my_class(), {}) +// // Copying prohibited +// BOOST_DELETED_FUNCTION(my_class(my_class const&)) +// BOOST_DELETED_FUNCTION(my_class& operator= (my_class const&)) +// }; +// +#if !(defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) || defined(BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS)) +# define BOOST_DEFAULTED_FUNCTION(fun, body) fun = default; +#else +# define BOOST_DEFAULTED_FUNCTION(fun, body) fun body +#endif + +#if !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) +# define BOOST_DELETED_FUNCTION(fun) fun = delete; +#else +# define BOOST_DELETED_FUNCTION(fun) private: fun; +#endif + +// +// Set BOOST_NO_DECLTYPE_N3276 when BOOST_NO_DECLTYPE is defined +// +#if defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_DECLTYPE_N3276) +#define BOOST_NO_CXX11_DECLTYPE_N3276 BOOST_NO_CXX11_DECLTYPE +#endif + +// -------------------- Deprecated macros for 1.50 --------------------------- +// These will go away in a future release + +// Use BOOST_NO_CXX11_HDR_UNORDERED_SET or BOOST_NO_CXX11_HDR_UNORDERED_MAP +// instead of BOOST_NO_STD_UNORDERED +#if defined(BOOST_NO_CXX11_HDR_UNORDERED_MAP) || defined (BOOST_NO_CXX11_HDR_UNORDERED_SET) +# ifndef BOOST_NO_CXX11_STD_UNORDERED +# define BOOST_NO_CXX11_STD_UNORDERED +# endif +#endif + +// Use BOOST_NO_CXX11_HDR_INITIALIZER_LIST instead of BOOST_NO_INITIALIZER_LISTS +#if defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) && !defined(BOOST_NO_INITIALIZER_LISTS) +# define BOOST_NO_INITIALIZER_LISTS +#endif + +// Use BOOST_NO_CXX11_HDR_ARRAY instead of BOOST_NO_0X_HDR_ARRAY +#if defined(BOOST_NO_CXX11_HDR_ARRAY) && !defined(BOOST_NO_0X_HDR_ARRAY) +# define BOOST_NO_0X_HDR_ARRAY +#endif +// Use BOOST_NO_CXX11_HDR_CHRONO instead of BOOST_NO_0X_HDR_CHRONO +#if defined(BOOST_NO_CXX11_HDR_CHRONO) && !defined(BOOST_NO_0X_HDR_CHRONO) +# define BOOST_NO_0X_HDR_CHRONO +#endif +// Use BOOST_NO_CXX11_HDR_CODECVT instead of BOOST_NO_0X_HDR_CODECVT +#if defined(BOOST_NO_CXX11_HDR_CODECVT) && !defined(BOOST_NO_0X_HDR_CODECVT) +# define BOOST_NO_0X_HDR_CODECVT +#endif +// Use BOOST_NO_CXX11_HDR_CONDITION_VARIABLE instead of BOOST_NO_0X_HDR_CONDITION_VARIABLE +#if defined(BOOST_NO_CXX11_HDR_CONDITION_VARIABLE) && !defined(BOOST_NO_0X_HDR_CONDITION_VARIABLE) +# define BOOST_NO_0X_HDR_CONDITION_VARIABLE +#endif +// Use BOOST_NO_CXX11_HDR_FORWARD_LIST instead of BOOST_NO_0X_HDR_FORWARD_LIST +#if defined(BOOST_NO_CXX11_HDR_FORWARD_LIST) && !defined(BOOST_NO_0X_HDR_FORWARD_LIST) +# define BOOST_NO_0X_HDR_FORWARD_LIST +#endif +// Use BOOST_NO_CXX11_HDR_FUTURE instead of BOOST_NO_0X_HDR_FUTURE +#if defined(BOOST_NO_CXX11_HDR_FUTURE) && !defined(BOOST_NO_0X_HDR_FUTURE) +# define BOOST_NO_0X_HDR_FUTURE +#endif + +// Use BOOST_NO_CXX11_HDR_INITIALIZER_LIST +// instead of BOOST_NO_0X_HDR_INITIALIZER_LIST or BOOST_NO_INITIALIZER_LISTS +#ifdef BOOST_NO_CXX11_HDR_INITIALIZER_LIST +# ifndef BOOST_NO_0X_HDR_INITIALIZER_LIST +# define BOOST_NO_0X_HDR_INITIALIZER_LIST +# endif +# ifndef BOOST_NO_INITIALIZER_LISTS +# define BOOST_NO_INITIALIZER_LISTS +# endif +#endif + +// Use BOOST_NO_CXX11_HDR_MUTEX instead of BOOST_NO_0X_HDR_MUTEX +#if defined(BOOST_NO_CXX11_HDR_MUTEX) && !defined(BOOST_NO_0X_HDR_MUTEX) +# define BOOST_NO_0X_HDR_MUTEX +#endif +// Use BOOST_NO_CXX11_HDR_RANDOM instead of BOOST_NO_0X_HDR_RANDOM +#if defined(BOOST_NO_CXX11_HDR_RANDOM) && !defined(BOOST_NO_0X_HDR_RANDOM) +# define BOOST_NO_0X_HDR_RANDOM +#endif +// Use BOOST_NO_CXX11_HDR_RATIO instead of BOOST_NO_0X_HDR_RATIO +#if defined(BOOST_NO_CXX11_HDR_RATIO) && !defined(BOOST_NO_0X_HDR_RATIO) +# define BOOST_NO_0X_HDR_RATIO +#endif +// Use BOOST_NO_CXX11_HDR_REGEX instead of BOOST_NO_0X_HDR_REGEX +#if defined(BOOST_NO_CXX11_HDR_REGEX) && !defined(BOOST_NO_0X_HDR_REGEX) +# define BOOST_NO_0X_HDR_REGEX +#endif +// Use BOOST_NO_CXX11_HDR_SYSTEM_ERROR instead of BOOST_NO_0X_HDR_SYSTEM_ERROR +#if defined(BOOST_NO_CXX11_HDR_SYSTEM_ERROR) && !defined(BOOST_NO_0X_HDR_SYSTEM_ERROR) +# define BOOST_NO_0X_HDR_SYSTEM_ERROR +#endif +// Use BOOST_NO_CXX11_HDR_THREAD instead of BOOST_NO_0X_HDR_THREAD +#if defined(BOOST_NO_CXX11_HDR_THREAD) && !defined(BOOST_NO_0X_HDR_THREAD) +# define BOOST_NO_0X_HDR_THREAD +#endif +// Use BOOST_NO_CXX11_HDR_TUPLE instead of BOOST_NO_0X_HDR_TUPLE +#if defined(BOOST_NO_CXX11_HDR_TUPLE) && !defined(BOOST_NO_0X_HDR_TUPLE) +# define BOOST_NO_0X_HDR_TUPLE +#endif +// Use BOOST_NO_CXX11_HDR_TYPE_TRAITS instead of BOOST_NO_0X_HDR_TYPE_TRAITS +#if defined(BOOST_NO_CXX11_HDR_TYPE_TRAITS) && !defined(BOOST_NO_0X_HDR_TYPE_TRAITS) +# define BOOST_NO_0X_HDR_TYPE_TRAITS +#endif +// Use BOOST_NO_CXX11_HDR_TYPEINDEX instead of BOOST_NO_0X_HDR_TYPEINDEX +#if defined(BOOST_NO_CXX11_HDR_TYPEINDEX) && !defined(BOOST_NO_0X_HDR_TYPEINDEX) +# define BOOST_NO_0X_HDR_TYPEINDEX +#endif +// Use BOOST_NO_CXX11_HDR_UNORDERED_MAP instead of BOOST_NO_0X_HDR_UNORDERED_MAP +#if defined(BOOST_NO_CXX11_HDR_UNORDERED_MAP) && !defined(BOOST_NO_0X_HDR_UNORDERED_MAP) +# define BOOST_NO_0X_HDR_UNORDERED_MAP +#endif +// Use BOOST_NO_CXX11_HDR_UNORDERED_SET instead of BOOST_NO_0X_HDR_UNORDERED_SET +#if defined(BOOST_NO_CXX11_HDR_UNORDERED_SET) && !defined(BOOST_NO_0X_HDR_UNORDERED_SET) +# define BOOST_NO_0X_HDR_UNORDERED_SET +#endif + +// ------------------ End of deprecated macros for 1.50 --------------------------- + +// -------------------- Deprecated macros for 1.51 --------------------------- +// These will go away in a future release + +// Use BOOST_NO_CXX11_AUTO_DECLARATIONS instead of BOOST_NO_AUTO_DECLARATIONS +#if defined(BOOST_NO_CXX11_AUTO_DECLARATIONS) && !defined(BOOST_NO_AUTO_DECLARATIONS) +# define BOOST_NO_AUTO_DECLARATIONS +#endif +// Use BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS instead of BOOST_NO_AUTO_MULTIDECLARATIONS +#if defined(BOOST_NO_CXX11_AUTO_MULTIDECLARATIONS) && !defined(BOOST_NO_AUTO_MULTIDECLARATIONS) +# define BOOST_NO_AUTO_MULTIDECLARATIONS +#endif +// Use BOOST_NO_CXX11_CHAR16_T instead of BOOST_NO_CHAR16_T +#if defined(BOOST_NO_CXX11_CHAR16_T) && !defined(BOOST_NO_CHAR16_T) +# define BOOST_NO_CHAR16_T +#endif +// Use BOOST_NO_CXX11_CHAR32_T instead of BOOST_NO_CHAR32_T +#if defined(BOOST_NO_CXX11_CHAR32_T) && !defined(BOOST_NO_CHAR32_T) +# define BOOST_NO_CHAR32_T +#endif +// Use BOOST_NO_CXX11_TEMPLATE_ALIASES instead of BOOST_NO_TEMPLATE_ALIASES +#if defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) && !defined(BOOST_NO_TEMPLATE_ALIASES) +# define BOOST_NO_TEMPLATE_ALIASES +#endif +// Use BOOST_NO_CXX11_CONSTEXPR instead of BOOST_NO_CONSTEXPR +#if defined(BOOST_NO_CXX11_CONSTEXPR) && !defined(BOOST_NO_CONSTEXPR) +# define BOOST_NO_CONSTEXPR +#endif +// Use BOOST_NO_CXX11_DECLTYPE_N3276 instead of BOOST_NO_DECLTYPE_N3276 +#if defined(BOOST_NO_CXX11_DECLTYPE_N3276) && !defined(BOOST_NO_DECLTYPE_N3276) +# define BOOST_NO_DECLTYPE_N3276 +#endif +// Use BOOST_NO_CXX11_DECLTYPE instead of BOOST_NO_DECLTYPE +#if defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_DECLTYPE) +# define BOOST_NO_DECLTYPE +#endif +// Use BOOST_NO_CXX11_DEFAULTED_FUNCTIONS instead of BOOST_NO_DEFAULTED_FUNCTIONS +#if defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) && !defined(BOOST_NO_DEFAULTED_FUNCTIONS) +# define BOOST_NO_DEFAULTED_FUNCTIONS +#endif +// Use BOOST_NO_CXX11_DELETED_FUNCTIONS instead of BOOST_NO_DELETED_FUNCTIONS +#if defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) && !defined(BOOST_NO_DELETED_FUNCTIONS) +# define BOOST_NO_DELETED_FUNCTIONS +#endif +// Use BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS instead of BOOST_NO_EXPLICIT_CONVERSION_OPERATORS +#if defined(BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS) && !defined(BOOST_NO_EXPLICIT_CONVERSION_OPERATORS) +# define BOOST_NO_EXPLICIT_CONVERSION_OPERATORS +#endif +// Use BOOST_NO_CXX11_EXTERN_TEMPLATE instead of BOOST_NO_EXTERN_TEMPLATE +#if defined(BOOST_NO_CXX11_EXTERN_TEMPLATE) && !defined(BOOST_NO_EXTERN_TEMPLATE) +# define BOOST_NO_EXTERN_TEMPLATE +#endif +// Use BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS instead of BOOST_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS +#if defined(BOOST_NO_CXX11_FUNCTION_TEMPLATE_DEFAULT_ARGS) && !defined(BOOST_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS) +# define BOOST_NO_FUNCTION_TEMPLATE_DEFAULT_ARGS +#endif +// Use BOOST_NO_CXX11_LAMBDAS instead of BOOST_NO_LAMBDAS +#if defined(BOOST_NO_CXX11_LAMBDAS) && !defined(BOOST_NO_LAMBDAS) +# define BOOST_NO_LAMBDAS +#endif +// Use BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS instead of BOOST_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS +#if defined(BOOST_NO_CXX11_LOCAL_CLASS_TEMPLATE_PARAMETERS) && !defined(BOOST_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS) +# define BOOST_NO_LOCAL_CLASS_TEMPLATE_PARAMETERS +#endif +// Use BOOST_NO_CXX11_NOEXCEPT instead of BOOST_NO_NOEXCEPT +#if defined(BOOST_NO_CXX11_NOEXCEPT) && !defined(BOOST_NO_NOEXCEPT) +# define BOOST_NO_NOEXCEPT +#endif +// Use BOOST_NO_CXX11_NULLPTR instead of BOOST_NO_NULLPTR +#if defined(BOOST_NO_CXX11_NULLPTR) && !defined(BOOST_NO_NULLPTR) +# define BOOST_NO_NULLPTR +#endif +// Use BOOST_NO_CXX11_RAW_LITERALS instead of BOOST_NO_RAW_LITERALS +#if defined(BOOST_NO_CXX11_RAW_LITERALS) && !defined(BOOST_NO_RAW_LITERALS) +# define BOOST_NO_RAW_LITERALS +#endif +// Use BOOST_NO_CXX11_RVALUE_REFERENCES instead of BOOST_NO_RVALUE_REFERENCES +#if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) && !defined(BOOST_NO_RVALUE_REFERENCES) +# define BOOST_NO_RVALUE_REFERENCES +#endif +// Use BOOST_NO_CXX11_SCOPED_ENUMS instead of BOOST_NO_SCOPED_ENUMS +#if defined(BOOST_NO_CXX11_SCOPED_ENUMS) && !defined(BOOST_NO_SCOPED_ENUMS) +# define BOOST_NO_SCOPED_ENUMS +#endif +// Use BOOST_NO_CXX11_STATIC_ASSERT instead of BOOST_NO_STATIC_ASSERT +#if defined(BOOST_NO_CXX11_STATIC_ASSERT) && !defined(BOOST_NO_STATIC_ASSERT) +# define BOOST_NO_STATIC_ASSERT +#endif +// Use BOOST_NO_CXX11_STD_UNORDERED instead of BOOST_NO_STD_UNORDERED +#if defined(BOOST_NO_CXX11_STD_UNORDERED) && !defined(BOOST_NO_STD_UNORDERED) +# define BOOST_NO_STD_UNORDERED +#endif +// Use BOOST_NO_CXX11_UNICODE_LITERALS instead of BOOST_NO_UNICODE_LITERALS +#if defined(BOOST_NO_CXX11_UNICODE_LITERALS) && !defined(BOOST_NO_UNICODE_LITERALS) +# define BOOST_NO_UNICODE_LITERALS +#endif +// Use BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX instead of BOOST_NO_UNIFIED_INITIALIZATION_SYNTAX +#if defined(BOOST_NO_CXX11_UNIFIED_INITIALIZATION_SYNTAX) && !defined(BOOST_NO_UNIFIED_INITIALIZATION_SYNTAX) +# define BOOST_NO_UNIFIED_INITIALIZATION_SYNTAX +#endif +// Use BOOST_NO_CXX11_VARIADIC_TEMPLATES instead of BOOST_NO_VARIADIC_TEMPLATES +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_NO_VARIADIC_TEMPLATES) +# define BOOST_NO_VARIADIC_TEMPLATES +#endif +// Use BOOST_NO_CXX11_VARIADIC_MACROS instead of BOOST_NO_VARIADIC_MACROS +#if defined(BOOST_NO_CXX11_VARIADIC_MACROS) && !defined(BOOST_NO_VARIADIC_MACROS) +# define BOOST_NO_VARIADIC_MACROS +#endif +// Use BOOST_NO_CXX11_NUMERIC_LIMITS instead of BOOST_NO_NUMERIC_LIMITS_LOWEST +#if defined(BOOST_NO_CXX11_NUMERIC_LIMITS) && !defined(BOOST_NO_NUMERIC_LIMITS_LOWEST) +# define BOOST_NO_NUMERIC_LIMITS_LOWEST +#endif +// ------------------ End of deprecated macros for 1.51 --------------------------- + + + +// +// Helper macros BOOST_NOEXCEPT, BOOST_NOEXCEPT_IF, BOOST_NOEXCEPT_EXPR +// These aid the transition to C++11 while still supporting C++03 compilers +// +#ifdef BOOST_NO_CXX11_NOEXCEPT +# define BOOST_NOEXCEPT +# define BOOST_NOEXCEPT_OR_NOTHROW throw() +# define BOOST_NOEXCEPT_IF(Predicate) +# define BOOST_NOEXCEPT_EXPR(Expression) false +#else +# define BOOST_NOEXCEPT noexcept +# define BOOST_NOEXCEPT_OR_NOTHROW noexcept +# define BOOST_NOEXCEPT_IF(Predicate) noexcept((Predicate)) +# define BOOST_NOEXCEPT_EXPR(Expression) noexcept((Expression)) +#endif +// +// Helper macro BOOST_FALLTHROUGH +// Fallback definition of BOOST_FALLTHROUGH macro used to mark intended +// fall-through between case labels in a switch statement. We use a definition +// that requires a semicolon after it to avoid at least one type of misuse even +// on unsupported compilers. +// +#ifndef BOOST_FALLTHROUGH +# define BOOST_FALLTHROUGH ((void)0) +#endif + +// +// constexpr workarounds +// +#if defined(BOOST_NO_CXX11_CONSTEXPR) +#define BOOST_CONSTEXPR +#define BOOST_CONSTEXPR_OR_CONST const +#else +#define BOOST_CONSTEXPR constexpr +#define BOOST_CONSTEXPR_OR_CONST constexpr +#endif +#if defined(BOOST_NO_CXX14_CONSTEXPR) +#define BOOST_CXX14_CONSTEXPR +#else +#define BOOST_CXX14_CONSTEXPR constexpr +#endif + +// +// Unused variable/typedef workarounds: +// +#ifndef BOOST_ATTRIBUTE_UNUSED +# define BOOST_ATTRIBUTE_UNUSED +#endif + +#define BOOST_STATIC_CONSTEXPR static BOOST_CONSTEXPR_OR_CONST + +// +// Set BOOST_HAS_STATIC_ASSERT when BOOST_NO_CXX11_STATIC_ASSERT is not defined +// +#if !defined(BOOST_NO_CXX11_STATIC_ASSERT) && !defined(BOOST_HAS_STATIC_ASSERT) +# define BOOST_HAS_STATIC_ASSERT +#endif + +// +// Set BOOST_HAS_RVALUE_REFS when BOOST_NO_CXX11_RVALUE_REFERENCES is not defined +// +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) && !defined(BOOST_HAS_RVALUE_REFS) +#define BOOST_HAS_RVALUE_REFS +#endif + +// +// Set BOOST_HAS_VARIADIC_TMPL when BOOST_NO_CXX11_VARIADIC_TEMPLATES is not defined +// +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_HAS_VARIADIC_TMPL) +#define BOOST_HAS_VARIADIC_TMPL +#endif +// +// Set BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS when +// BOOST_NO_CXX11_VARIADIC_TEMPLATES is set: +// +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS) +# define BOOST_NO_CXX11_FIXED_LENGTH_VARIADIC_TEMPLATE_EXPANSION_PACKS +#endif + +// +// Finish off with checks for macros that are depricated / no longer supported, +// if any of these are set then it's very likely that much of Boost will no +// longer work. So stop with a #error for now, but give the user a chance +// to continue at their own risk if they really want to: +// +#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) && !defined(BOOST_CONFIG_ALLOW_DEPRECATED) +# error "You are using a compiler which lacks features which are now a minimum requirement in order to use Boost, define BOOST_CONFIG_ALLOW_DEPRECATED if you want to continue at your own risk!!!" +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/config/user.hpp b/thirdparty/source/boost_1_61_0/boost/config/user.hpp new file mode 100644 index 0000000..28e7476 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/user.hpp @@ -0,0 +1,133 @@ +// boost/config/user.hpp ---------------------------------------------------// + +// (C) Copyright John Maddock 2001. +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Do not check in modified versions of this file, +// This file may be customized by the end user, but not by boost. + +// +// Use this file to define a site and compiler specific +// configuration policy: +// + +// define this to locate a compiler config file: +// #define BOOST_COMPILER_CONFIG + +// define this to locate a stdlib config file: +// #define BOOST_STDLIB_CONFIG + +// define this to locate a platform config file: +// #define BOOST_PLATFORM_CONFIG + +// define this to disable compiler config, +// use if your compiler config has nothing to set: +// #define BOOST_NO_COMPILER_CONFIG + +// define this to disable stdlib config, +// use if your stdlib config has nothing to set: +// #define BOOST_NO_STDLIB_CONFIG + +// define this to disable platform config, +// use if your platform config has nothing to set: +// #define BOOST_NO_PLATFORM_CONFIG + +// define this to disable all config options, +// excluding the user config. Use if your +// setup is fully ISO compliant, and has no +// useful extensions, or for autoconf generated +// setups: +// #define BOOST_NO_CONFIG + +// define this to make the config "optimistic" +// about unknown compiler versions. Normally +// unknown compiler versions are assumed to have +// all the defects of the last known version, however +// setting this flag, causes the config to assume +// that unknown compiler versions are fully conformant +// with the standard: +// #define BOOST_STRICT_CONFIG + +// define this to cause the config to halt compilation +// with an #error if it encounters anything unknown -- +// either an unknown compiler version or an unknown +// compiler/platform/library: +// #define BOOST_ASSERT_CONFIG + + +// define if you want to disable threading support, even +// when available: +// #define BOOST_DISABLE_THREADS + +// define when you want to disable Win32 specific features +// even when available: +// #define BOOST_DISABLE_WIN32 + +// BOOST_DISABLE_ABI_HEADERS: Stops boost headers from including any +// prefix/suffix headers that normally control things like struct +// packing and alignment. +// #define BOOST_DISABLE_ABI_HEADERS + +// BOOST_ABI_PREFIX: A prefix header to include in place of whatever +// boost.config would normally select, any replacement should set up +// struct packing and alignment options as required. +// #define BOOST_ABI_PREFIX my-header-name + +// BOOST_ABI_SUFFIX: A suffix header to include in place of whatever +// boost.config would normally select, any replacement should undo +// the effects of the prefix header. +// #define BOOST_ABI_SUFFIX my-header-name + +// BOOST_ALL_DYN_LINK: Forces all libraries that have separate source, +// to be linked as dll's rather than static libraries on Microsoft Windows +// (this macro is used to turn on __declspec(dllimport) modifiers, so that +// the compiler knows which symbols to look for in a dll rather than in a +// static library). Note that there may be some libraries that can only +// be linked in one way (statically or dynamically), in these cases this +// macro has no effect. +// #define BOOST_ALL_DYN_LINK + +// BOOST_WHATEVER_DYN_LINK: Forces library "whatever" to be linked as a dll +// rather than a static library on Microsoft Windows: replace the WHATEVER +// part of the macro name with the name of the library that you want to +// dynamically link to, for example use BOOST_DATE_TIME_DYN_LINK or +// BOOST_REGEX_DYN_LINK etc (this macro is used to turn on __declspec(dllimport) +// modifiers, so that the compiler knows which symbols to look for in a dll +// rather than in a static library). +// Note that there may be some libraries that can only +// be linked in one way (statically or dynamically), +// in these cases this macro is unsupported. +// #define BOOST_WHATEVER_DYN_LINK + +// BOOST_ALL_NO_LIB: Tells the config system not to automatically select +// which libraries to link against. +// Normally if a compiler supports #pragma lib, then the correct library +// build variant will be automatically selected and linked against, +// simply by the act of including one of that library's headers. +// This macro turns that feature off. +// #define BOOST_ALL_NO_LIB + +// BOOST_WHATEVER_NO_LIB: Tells the config system not to automatically +// select which library to link against for library "whatever", +// replace WHATEVER in the macro name with the name of the library; +// for example BOOST_DATE_TIME_NO_LIB or BOOST_REGEX_NO_LIB. +// Normally if a compiler supports #pragma lib, then the correct library +// build variant will be automatically selected and linked against, simply +// by the act of including one of that library's headers. This macro turns +// that feature off. +// #define BOOST_WHATEVER_NO_LIB + +// BOOST_LIB_BUILDID: Set to the same value as the value passed to Boost.Build's +// --buildid command line option. For example if you built using: +// +// bjam address-model=64 --buildid=amd64 +// +// then compile your code with: +// +// -DBOOST_LIB_BUILDID = amd64 +// +// to ensure the correct libraries are selected at link time. +// #define BOOST_LIB_BUILDID amd64 + diff --git a/thirdparty/source/boost_1_61_0/boost/config/warning_disable.hpp b/thirdparty/source/boost_1_61_0/boost/config/warning_disable.hpp new file mode 100644 index 0000000..fea8e82 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/config/warning_disable.hpp @@ -0,0 +1,47 @@ +// Copyright John Maddock 2008 +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// This file exists to turn off some overly-pedantic warning emitted +// by certain compilers. You should include this header only in: +// +// * A test case, before any other headers, or, +// * A library source file before any other headers. +// +// IT SHOULD NOT BE INCLUDED BY ANY BOOST HEADER. +// +// YOU SHOULD NOT INCLUDE IT IF YOU CAN REASONABLY FIX THE WARNING. +// +// The only warnings disabled here are those that are: +// +// * Quite unreasonably pedantic. +// * Generally only emitted by a single compiler. +// * Can't easily be fixed: for example if the vendors own std lib +// code emits these warnings! +// +// Note that THIS HEADER MUST NOT INCLUDE ANY OTHER HEADERS: +// not even std library ones! Doing so may turn the warning +// off too late to be of any use. For example the VC++ C4996 +// warning can be emitted from if that header is included +// before or by this one :-( +// + +#ifndef BOOST_CONFIG_WARNING_DISABLE_HPP +#define BOOST_CONFIG_WARNING_DISABLE_HPP + +#if defined(_MSC_VER) && (_MSC_VER >= 1400) + // Error 'function': was declared deprecated + // http://msdn2.microsoft.com/en-us/library/ttcz0bys(VS.80).aspx + // This error is emitted when you use some perfectly conforming + // std lib functions in a perfectly correct way, and also by + // some of Microsoft's own std lib code ! +# pragma warning(disable:4996) +#endif +#if defined(__INTEL_COMPILER) || defined(__ICL) + // As above: gives warning when a "deprecated" + // std library function is encountered. +# pragma warning(disable:1786) +#endif + +#endif // BOOST_CONFIG_WARNING_DISABLE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/allocator_traits.hpp b/thirdparty/source/boost_1_61_0/boost/container/allocator_traits.hpp new file mode 100644 index 0000000..e6a882e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/allocator_traits.hpp @@ -0,0 +1,477 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Pablo Halpern 2009. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2011-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_ALLOCATOR_ALLOCATOR_TRAITS_HPP +#define BOOST_CONTAINER_ALLOCATOR_ALLOCATOR_TRAITS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +// container +#include +#include +#include //is_empty +#include +#ifndef BOOST_CONTAINER_DETAIL_STD_FWD_HPP +#include +#endif +// intrusive +#include +#include +// move +#include +// move/detail +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif +// other boost +#include + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME allocate +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG namespace boost { namespace container { namespace container_detail { +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END }}} +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN 2 +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX 2 +#include + +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME destroy +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG namespace boost { namespace container { namespace container_detail { +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END }}} +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN 1 +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX 1 +#include + +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME construct +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG namespace boost { namespace container { namespace container_detail { +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END }}} +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN 1 +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX 9 +#include + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +namespace boost { +namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +template +class small_vector_allocator; + +namespace allocator_traits_detail { + +BOOST_INTRUSIVE_HAS_STATIC_MEMBER_FUNC_SIGNATURE(has_max_size, max_size) +BOOST_INTRUSIVE_HAS_STATIC_MEMBER_FUNC_SIGNATURE(has_select_on_container_copy_construction, select_on_container_copy_construction) + +} //namespace allocator_traits_detail { + +namespace container_detail { + +//workaround needed for C++03 compilers with no construct() +//supporting rvalue references +template +struct is_std_allocator +{ static const bool value = false; }; + +template +struct is_std_allocator< std::allocator > +{ static const bool value = true; }; + +template +struct is_std_allocator< small_vector_allocator< std::allocator > > +{ static const bool value = true; }; + +template +struct is_not_std_allocator +{ static const bool value = !is_std_allocator::value; }; + +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(pointer) +BOOST_INTRUSIVE_INSTANTIATE_EVAL_DEFAULT_TYPE_TMPLT(const_pointer) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(reference) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(const_reference) +BOOST_INTRUSIVE_INSTANTIATE_EVAL_DEFAULT_TYPE_TMPLT(void_pointer) +BOOST_INTRUSIVE_INSTANTIATE_EVAL_DEFAULT_TYPE_TMPLT(const_void_pointer) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(size_type) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(propagate_on_container_copy_assignment) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(propagate_on_container_move_assignment) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(propagate_on_container_swap) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(is_always_equal) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(difference_type) +BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(is_partially_propagable) + +} //namespace container_detail { + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! The class template allocator_traits supplies a uniform interface to all allocator types. +//! This class is a C++03-compatible implementation of std::allocator_traits +template +struct allocator_traits +{ + //allocator_type + typedef Allocator allocator_type; + //value_type + typedef typename allocator_type::value_type value_type; + + #if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + //! Allocator::pointer if such a type exists; otherwise, value_type* + //! + typedef unspecified pointer; + //! Allocator::const_pointer if such a type exists ; otherwise, pointer_traits::rebind::rebind. + //! + typedef see_documentation void_pointer; + //! Allocator::const_void_pointer if such a type exists ; otherwis e, pointer_traits::rebind::difference_type. + //! + typedef see_documentation difference_type; + //! Allocator::size_type if such a type exists ; otherwise, make_unsigned::type + //! + typedef see_documentation size_type; + //! Allocator::propagate_on_container_copy_assignment if such a type exists, otherwise a type + //! with an internal constant static boolean member value == false. + typedef see_documentation propagate_on_container_copy_assignment; + //! Allocator::propagate_on_container_move_assignment if such a type exists, otherwise a type + //! with an internal constant static boolean member value == false. + typedef see_documentation propagate_on_container_move_assignment; + //! Allocator::propagate_on_container_swap if such a type exists, otherwise a type + //! with an internal constant static boolean member value == false. + typedef see_documentation propagate_on_container_swap; + //! Allocator::is_always_equal if such a type exists, otherwise a type + //! with an internal constant static boolean member value == is_empty::value + typedef see_documentation is_always_equal; + //! Allocator::is_partially_propagable if such a type exists, otherwise a type + //! with an internal constant static boolean member value == false + //! Note: Non-standard extension used to implement `small_vector_allocator`. + typedef see_documentation is_partially_propagable; + //! Defines an allocator: Allocator::rebind::other if such a type exists; otherwise, Allocator + //! if Allocator is a class template instantiation of the form Allocator, where Args is zero or + //! more type arguments ; otherwise, the instantiation of rebind_alloc is ill-formed. + //! + //! In C++03 compilers rebind_alloc is a struct derived from an allocator + //! deduced by previously detailed rules. + template using rebind_alloc = see_documentation; + + //! In C++03 compilers rebind_traits is a struct derived from + //! allocator_traits, where OtherAlloc is + //! the allocator deduced by rules explained in rebind_alloc. + template using rebind_traits = allocator_traits >; + + //! Non-standard extension: Portable allocator rebind for C++03 and C++11 compilers. + //! type is an allocator related to Allocator deduced deduced by rules explained in rebind_alloc. + template + struct portable_rebind_alloc + { typedef see_documentation type; }; + #else + //pointer + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + pointer, value_type*) + pointer; + //const_pointer + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_EVAL_DEFAULT(boost::container::container_detail::, Allocator, + const_pointer, typename boost::intrusive::pointer_traits::template + rebind_pointer) + const_pointer; + //reference + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + reference, typename container_detail::unvoid_ref::type) + reference; + //const_reference + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + const_reference, typename container_detail::unvoid_ref::type) + const_reference; + //void_pointer + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_EVAL_DEFAULT(boost::container::container_detail::, Allocator, + void_pointer, typename boost::intrusive::pointer_traits::template + rebind_pointer) + void_pointer; + //const_void_pointer + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_EVAL_DEFAULT(boost::container::container_detail::, Allocator, + const_void_pointer, typename boost::intrusive::pointer_traits::template + rebind_pointer) + const_void_pointer; + //difference_type + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + difference_type, std::ptrdiff_t) + difference_type; + //size_type + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + size_type, std::size_t) + size_type; + //propagate_on_container_copy_assignment + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + propagate_on_container_copy_assignment, container_detail::false_type) + propagate_on_container_copy_assignment; + //propagate_on_container_move_assignment + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + propagate_on_container_move_assignment, container_detail::false_type) + propagate_on_container_move_assignment; + //propagate_on_container_swap + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + propagate_on_container_swap, container_detail::false_type) + propagate_on_container_swap; + //is_always_equal + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + is_always_equal, container_detail::is_empty) + is_always_equal; + //is_partially_propagable + typedef BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(boost::container::container_detail::, Allocator, + is_partially_propagable, container_detail::false_type) + is_partially_propagable; + + //rebind_alloc & rebind_traits + #if !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) + //C++11 + template using rebind_alloc = typename boost::intrusive::pointer_rebind::type; + template using rebind_traits = allocator_traits< rebind_alloc >; + #else // #if !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) + //Some workaround for C++03 or C++11 compilers with no template aliases + template + struct rebind_alloc : boost::intrusive::pointer_rebind::type + { + typedef typename boost::intrusive::pointer_rebind::type Base; + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + rebind_alloc(BOOST_FWD_REF(Args)... args) : Base(boost::forward(args)...) {} + #else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + #define BOOST_CONTAINER_ALLOCATOR_TRAITS_REBIND_ALLOC(N) \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N\ + explicit rebind_alloc(BOOST_MOVE_UREF##N) : Base(BOOST_MOVE_FWD##N){}\ + // + BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_ALLOCATOR_TRAITS_REBIND_ALLOC) + #undef BOOST_CONTAINER_ALLOCATOR_TRAITS_REBIND_ALLOC + #endif // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + }; + + template + struct rebind_traits + : allocator_traits::type> + {}; + #endif // #if !defined(BOOST_NO_CXX11_TEMPLATE_ALIASES) + + //portable_rebind_alloc + template + struct portable_rebind_alloc + { typedef typename boost::intrusive::pointer_rebind::type type; }; + #endif //BOOST_CONTAINER_DOXYGEN_INVOKED + + //! Returns: a.allocate(n) + //! + static pointer allocate(Allocator &a, size_type n) + { return a.allocate(n); } + + //! Returns: a.deallocate(p, n) + //! + //! Throws: Nothing + static void deallocate(Allocator &a, pointer p, size_type n) + { a.deallocate(p, n); } + + //! Effects: calls a.allocate(n, p) if that call is well-formed; + //! otherwise, invokes a.allocate(n) + static pointer allocate(Allocator &a, size_type n, const_void_pointer p) + { + const bool value = boost::container::container_detail:: + has_member_function_callable_with_allocate + ::value; + container_detail::bool_ flag; + return allocator_traits::priv_allocate(flag, a, n, p); + } + + //! Effects: calls a.destroy(p) if that call is well-formed; + //! otherwise, invokes p->~T(). + template + static void destroy(Allocator &a, T*p) BOOST_NOEXCEPT_OR_NOTHROW + { + typedef T* destroy_pointer; + const bool value = boost::container::container_detail:: + has_member_function_callable_with_destroy + ::value; + container_detail::bool_ flag; + allocator_traits::priv_destroy(flag, a, p); + } + + //! Returns: a.max_size() if that expression is well-formed; otherwise, + //! numeric_limits::max(). + static size_type max_size(const Allocator &a) BOOST_NOEXCEPT_OR_NOTHROW + { + const bool value = allocator_traits_detail::has_max_size::value; + container_detail::bool_ flag; + return allocator_traits::priv_max_size(flag, a); + } + + //! Returns: a.select_on_container_copy_construction() if that expression is well-formed; + //! otherwise, a. + static BOOST_CONTAINER_DOC1ST(Allocator, + typename container_detail::if_c + < allocator_traits_detail::has_select_on_container_copy_construction::value + BOOST_MOVE_I Allocator BOOST_MOVE_I const Allocator & >::type) + select_on_container_copy_construction(const Allocator &a) + { + const bool value = allocator_traits_detail::has_select_on_container_copy_construction + ::value; + container_detail::bool_ flag; + return allocator_traits::priv_select_on_container_copy_construction(flag, a); + } + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + //! Effects: calls a.construct(p, std::forward(args)...) if that call is well-formed; + //! otherwise, invokes ::new (static_cast(p)) T(std::forward(args)...) + template + static void construct(Allocator & a, T* p, BOOST_FWD_REF(Args)... args) + { + static const bool value = ::boost::move_detail::and_ + < container_detail::is_not_std_allocator + , boost::container::container_detail::has_member_function_callable_with_construct + < Allocator, T*, Args... > + >::value; + container_detail::bool_ flag; + allocator_traits::priv_construct(flag, a, p, ::boost::forward(args)...); + } + #endif + + //! Returns: a.storage_is_unpropagable(p) if is_partially_propagable::value is true; otherwise, + //! false. + static bool storage_is_unpropagable(const Allocator &a, pointer p) BOOST_NOEXCEPT_OR_NOTHROW + { + container_detail::bool_ flag; + return allocator_traits::priv_storage_is_unpropagable(flag, a, p); + } + + //! Returns: true if is_always_equal::value == true, otherwise, + //! a == b. + static bool equal(const Allocator &a, const Allocator &b) BOOST_NOEXCEPT_OR_NOTHROW + { + container_detail::bool_ flag; + return allocator_traits::priv_equal(flag, a, b); + } + + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + private: + static pointer priv_allocate(container_detail::true_type, Allocator &a, size_type n, const_void_pointer p) + { return a.allocate(n, p); } + + static pointer priv_allocate(container_detail::false_type, Allocator &a, size_type n, const_void_pointer) + { return a.allocate(n); } + + template + static void priv_destroy(container_detail::true_type, Allocator &a, T* p) BOOST_NOEXCEPT_OR_NOTHROW + { a.destroy(p); } + + template + static void priv_destroy(container_detail::false_type, Allocator &, T* p) BOOST_NOEXCEPT_OR_NOTHROW + { p->~T(); (void)p; } + + static size_type priv_max_size(container_detail::true_type, const Allocator &a) BOOST_NOEXCEPT_OR_NOTHROW + { return a.max_size(); } + + static size_type priv_max_size(container_detail::false_type, const Allocator &) BOOST_NOEXCEPT_OR_NOTHROW + { return size_type(-1)/sizeof(value_type); } + + static Allocator priv_select_on_container_copy_construction(container_detail::true_type, const Allocator &a) + { return a.select_on_container_copy_construction(); } + + static const Allocator &priv_select_on_container_copy_construction(container_detail::false_type, const Allocator &a) BOOST_NOEXCEPT_OR_NOTHROW + { return a; } + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + static void priv_construct(container_detail::true_type, Allocator &a, T *p, BOOST_FWD_REF(Args) ...args) + { a.construct( p, ::boost::forward(args)...); } + + template + static void priv_construct(container_detail::false_type, Allocator &, T *p, BOOST_FWD_REF(Args) ...args) + { ::new((void*)p, boost_container_new_t()) T(::boost::forward(args)...); } + #else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + public: + + #define BOOST_CONTAINER_ALLOCATOR_TRAITS_CONSTRUCT_IMPL(N) \ + template\ + static void construct(Allocator &a, T *p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + {\ + static const bool value = ::boost::move_detail::and_ \ + < container_detail::is_not_std_allocator \ + , boost::container::container_detail::has_member_function_callable_with_construct \ + < Allocator, T* BOOST_MOVE_I##N BOOST_MOVE_FWD_T##N > \ + >::value; \ + container_detail::bool_ flag;\ + (priv_construct)(flag, a, p BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\ + }\ + // + BOOST_MOVE_ITERATE_0TO8(BOOST_CONTAINER_ALLOCATOR_TRAITS_CONSTRUCT_IMPL) + #undef BOOST_CONTAINER_ALLOCATOR_TRAITS_CONSTRUCT_IMPL + + private: + ///////////////////////////////// + // priv_construct + ///////////////////////////////// + #define BOOST_CONTAINER_ALLOCATOR_TRAITS_PRIV_CONSTRUCT_IMPL(N) \ + template\ + static void priv_construct(container_detail::true_type, Allocator &a, T *p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + { a.construct( p BOOST_MOVE_I##N BOOST_MOVE_FWD##N ); }\ + \ + template\ + static void priv_construct(container_detail::false_type, Allocator &, T *p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + { ::new((void*)p, boost_container_new_t()) T(BOOST_MOVE_FWD##N); }\ + // + BOOST_MOVE_ITERATE_0TO8(BOOST_CONTAINER_ALLOCATOR_TRAITS_PRIV_CONSTRUCT_IMPL) + #undef BOOST_CONTAINER_ALLOCATOR_TRAITS_PRIV_CONSTRUCT_IMPL + + #endif // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + template + static void priv_construct(container_detail::false_type, Allocator &, T *p, const ::boost::container::default_init_t&) + { ::new((void*)p, boost_container_new_t()) T; } + + static bool priv_storage_is_unpropagable(container_detail::true_type, const Allocator &a, pointer p) + { return a.storage_is_unpropagable(p); } + + static bool priv_storage_is_unpropagable(container_detail::false_type, const Allocator &, pointer) + { return false; } + + static bool priv_equal(container_detail::true_type, const Allocator &, const Allocator &) + { return true; } + + static bool priv_equal(container_detail::false_type, const Allocator &a, const Allocator &b) + { return a == b; } + + #endif //#if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) +}; + +} //namespace container { +} //namespace boost { + +#include + +#endif // ! defined(BOOST_CONTAINER_ALLOCATOR_ALLOCATOR_TRAITS_HPP) diff --git a/thirdparty/source/boost_1_61_0/boost/container/container_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/container/container_fwd.hpp new file mode 100644 index 0000000..e85a6ce --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/container_fwd.hpp @@ -0,0 +1,317 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2014. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_CONTAINER_FWD_HPP +#define BOOST_CONTAINER_CONTAINER_FWD_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +//! \file +//! This header file forward declares the following containers: +//! - boost::container::vector +//! - boost::container::stable_vector +//! - boost::container::static_vector +//! - boost::container::small_vector +//! - boost::container::slist +//! - boost::container::list +//! - boost::container::set +//! - boost::container::multiset +//! - boost::container::map +//! - boost::container::multimap +//! - boost::container::flat_set +//! - boost::container::flat_multiset +//! - boost::container::flat_map +//! - boost::container::flat_multimap +//! - boost::container::basic_string +//! - boost::container::string +//! - boost::container::wstring +//! +//! Forward declares the following allocators: +//! - boost::container::allocator +//! - boost::container::node_allocator +//! - boost::container::adaptive_pool +//! +//! Forward declares the following polymorphic resource classes: +//! - boost::container::pmr::memory_resource +//! - boost::container::pmr::polymorphic_allocator +//! - boost::container::pmr::monotonic_buffer_resource +//! - boost::container::pmr::pool_options +//! - boost::container::pmr::unsynchronized_pool_resource +//! - boost::container::pmr::synchronized_pool_resource +//! +//! And finally it defines the following types + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//Std forward declarations +#ifndef BOOST_CONTAINER_DETAIL_STD_FWD_HPP + #include +#endif + +namespace boost{ +namespace intrusive{ +namespace detail{ + //Create namespace to avoid compilation errors +}}} + +namespace boost{ namespace container{ namespace container_detail{ + namespace bi = boost::intrusive; + namespace bid = boost::intrusive::detail; +}}} + +namespace boost{ namespace container{ namespace pmr{ + namespace bi = boost::intrusive; + namespace bid = boost::intrusive::detail; +}}} + +#include + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +////////////////////////////////////////////////////////////////////////////// +// Containers +////////////////////////////////////////////////////////////////////////////// + +namespace boost { +namespace container { + +//! Enumeration used to configure ordered associative containers +//! with a concrete tree implementation. +enum tree_type_enum +{ + red_black_tree, + avl_tree, + scapegoat_tree, + splay_tree +}; + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +template +class new_allocator; + +template > +class vector; + +template > +class stable_vector; + +template +class static_vector; + +template < class T, std::size_t N + , class Allocator= new_allocator > +class small_vector; + +template > +class deque; + +template > +class list; + +template > +class slist; + +template +struct tree_opt; + +typedef tree_opt tree_assoc_defaults; + +template + ,class Allocator = new_allocator + ,class Options = tree_assoc_defaults > +class set; + +template + ,class Allocator = new_allocator + ,class Options = tree_assoc_defaults > +class multiset; + +template + ,class Allocator = new_allocator > + ,class Options = tree_assoc_defaults > +class map; + +template + ,class Allocator = new_allocator > + ,class Options = tree_assoc_defaults > +class multimap; + +template + ,class Allocator = new_allocator > +class flat_set; + +template + ,class Allocator = new_allocator > +class flat_multiset; + +template + ,class Allocator = new_allocator > > +class flat_map; + +template + ,class Allocator = new_allocator > > +class flat_multimap; + +template + ,class Allocator = new_allocator > +class basic_string; + +typedef basic_string + + ,new_allocator > +string; + +typedef basic_string + + ,new_allocator > +wstring; + +static const std::size_t ADP_nodes_per_block = 256u; +static const std::size_t ADP_max_free_blocks = 2u; +static const std::size_t ADP_overhead_percent = 1u; +static const std::size_t ADP_only_alignment = 0u; + +template < class T + , std::size_t NodesPerBlock = ADP_nodes_per_block + , std::size_t MaxFreeBlocks = ADP_max_free_blocks + , std::size_t OverheadPercent = ADP_overhead_percent + , unsigned Version = 2 + > +class adaptive_pool; + +template < class T + , unsigned Version = 2 + , unsigned int AllocationDisableMask = 0> +class allocator; + +static const std::size_t NodeAlloc_nodes_per_block = 256u; + +template + < class T + , std::size_t NodesPerBlock = NodeAlloc_nodes_per_block + , std::size_t Version = 2> +class node_allocator; + +namespace pmr { + +class memory_resource; + +template +class polymorphic_allocator; + +class monotonic_buffer_resource; + +struct pool_options; + +template +class resource_adaptor_imp; + +class unsynchronized_pool_resource; + +class synchronized_pool_resource; + +} //namespace pmr { + +#else + +//! Default options for tree-based associative containers +//! - tree_type +//! - optimize_size +typedef implementation_defined tree_assoc_defaults; + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! Type used to tag that the input range is +//! guaranteed to be ordered +struct ordered_range_t +{}; + +//! Value used to tag that the input range is +//! guaranteed to be ordered +static const ordered_range_t ordered_range = ordered_range_t(); + +//! Type used to tag that the input range is +//! guaranteed to be ordered and unique +struct ordered_unique_range_t + : public ordered_range_t +{}; + +//! Value used to tag that the input range is +//! guaranteed to be ordered and unique +static const ordered_unique_range_t ordered_unique_range = ordered_unique_range_t(); + +//! Type used to tag that the inserted values +//! should be default initialized +struct default_init_t +{}; + +//! Value used to tag that the inserted values +//! should be default initialized +static const default_init_t default_init = default_init_t(); +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! Type used to tag that the inserted values +//! should be value initialized +struct value_init_t +{}; + +//! Value used to tag that the inserted values +//! should be value initialized +static const value_init_t value_init = value_init_t(); + +namespace container_detail_really_deep_namespace { + +//Otherwise, gcc issues a warning of previously defined +//anonymous_instance and unique_instance +struct dummy +{ + dummy() + { + (void)ordered_range; + (void)ordered_unique_range; + (void)default_init; + } +}; + +} //detail_really_deep_namespace { + + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +}} //namespace boost { namespace container { + +#endif //#ifndef BOOST_CONTAINER_CONTAINER_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/addressof.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/addressof.hpp new file mode 100644 index 0000000..cc582c4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/addressof.hpp @@ -0,0 +1,41 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_ADDRESSOF_HPP +#define BOOST_CONTAINER_DETAIL_ADDRESSOF_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +inline T* addressof(T& obj) +{ + return static_cast( + static_cast( + const_cast( + &reinterpret_cast(obj) + ))); +} + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ADDRESSOF_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/advanced_insert_int.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/advanced_insert_int.hpp new file mode 100644 index 0000000..56df588 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/advanced_insert_int.hpp @@ -0,0 +1,477 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2008-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_ADVANCED_INSERT_INT_HPP +#define BOOST_CONTAINER_ADVANCED_INSERT_INT_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +// container +#include +// container/detail +#include +#include +#include +#include +#include +#include +#include +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif +// move +#include +// other +#include +#include + +namespace boost { namespace container { namespace container_detail { + +template +struct move_insert_range_proxy +{ + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::value_type value_type; + + explicit move_insert_range_proxy(FwdIt first) + : first_(first) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) + { + this->first_ = ::boost::container::uninitialized_move_alloc_n_source + (a, this->first_, n, p); + } + + void copy_n_and_update(Allocator &, Iterator p, size_type n) + { + this->first_ = ::boost::container::move_n_source(this->first_, n, p); + } + + FwdIt first_; +}; + + +template +struct insert_range_proxy +{ + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::value_type value_type; + + explicit insert_range_proxy(FwdIt first) + : first_(first) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) + { + this->first_ = ::boost::container::uninitialized_copy_alloc_n_source(a, this->first_, n, p); + } + + void copy_n_and_update(Allocator &, Iterator p, size_type n) + { + this->first_ = ::boost::container::copy_n_source(this->first_, n, p); + } + + FwdIt first_; +}; + + +template +struct insert_n_copies_proxy +{ + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::value_type value_type; + + explicit insert_n_copies_proxy(const value_type &v) + : v_(v) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) const + { boost::container::uninitialized_fill_alloc_n(a, v_, n, p); } + + void copy_n_and_update(Allocator &, Iterator p, size_type n) const + { + for (; 0 < n; --n, ++p){ + *p = v_; + } + } + + const value_type &v_; +}; + +template +struct insert_value_initialized_n_proxy +{ + typedef ::boost::container::allocator_traits alloc_traits; + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::value_type value_type; + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) const + { boost::container::uninitialized_value_init_alloc_n(a, n, p); } + + void copy_n_and_update(Allocator &, Iterator, size_type) const + { BOOST_ASSERT(false); } +}; + +template +struct insert_default_initialized_n_proxy +{ + typedef ::boost::container::allocator_traits alloc_traits; + typedef typename allocator_traits::size_type size_type; + typedef typename allocator_traits::value_type value_type; + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) const + { boost::container::uninitialized_default_init_alloc_n(a, n, p); } + + void copy_n_and_update(Allocator &, Iterator, size_type) const + { BOOST_ASSERT(false); } +}; + +template +struct insert_copy_proxy +{ + typedef boost::container::allocator_traits alloc_traits; + typedef typename alloc_traits::size_type size_type; + typedef typename alloc_traits::value_type value_type; + + explicit insert_copy_proxy(const value_type &v) + : v_(v) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) const + { + BOOST_ASSERT(n == 1); (void)n; + alloc_traits::construct( a, iterator_to_raw_pointer(p), v_); + } + + void copy_n_and_update(Allocator &, Iterator p, size_type n) const + { + BOOST_ASSERT(n == 1); (void)n; + *p =v_; + } + + const value_type &v_; +}; + + +template +struct insert_move_proxy +{ + typedef boost::container::allocator_traits alloc_traits; + typedef typename alloc_traits::size_type size_type; + typedef typename alloc_traits::value_type value_type; + + explicit insert_move_proxy(value_type &v) + : v_(v) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) const + { + BOOST_ASSERT(n == 1); (void)n; + alloc_traits::construct( a, iterator_to_raw_pointer(p), ::boost::move(v_) ); + } + + void copy_n_and_update(Allocator &, Iterator p, size_type n) const + { + BOOST_ASSERT(n == 1); (void)n; + *p = ::boost::move(v_); + } + + value_type &v_; +}; + +template +insert_move_proxy get_insert_value_proxy(BOOST_RV_REF(typename boost::container::iterator_traits::value_type) v) +{ + return insert_move_proxy(v); +} + +template +insert_copy_proxy get_insert_value_proxy(const typename boost::container::iterator_traits::value_type &v) +{ + return insert_copy_proxy(v); +} + +}}} //namespace boost { namespace container { namespace container_detail { + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#include +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +struct insert_nonmovable_emplace_proxy +{ + typedef boost::container::allocator_traits alloc_traits; + typedef typename alloc_traits::size_type size_type; + typedef typename alloc_traits::value_type value_type; + + typedef typename build_number_seq::type index_tuple_t; + + explicit insert_nonmovable_emplace_proxy(BOOST_FWD_REF(Args)... args) + : args_(args...) + {} + + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n) + { this->priv_uninitialized_copy_some_and_update(a, index_tuple_t(), p, n); } + + private: + template + void priv_uninitialized_copy_some_and_update(Allocator &a, const index_tuple&, Iterator p, size_type n) + { + BOOST_ASSERT(n == 1); (void)n; + alloc_traits::construct( a, iterator_to_raw_pointer(p), ::boost::forward(get(this->args_))... ); + } + + protected: + tuple args_; +}; + +template +struct insert_emplace_proxy + : public insert_nonmovable_emplace_proxy +{ + typedef insert_nonmovable_emplace_proxy base_t; + typedef boost::container::allocator_traits alloc_traits; + typedef typename base_t::value_type value_type; + typedef typename base_t::size_type size_type; + typedef typename base_t::index_tuple_t index_tuple_t; + + explicit insert_emplace_proxy(BOOST_FWD_REF(Args)... args) + : base_t(::boost::forward(args)...) + {} + + void copy_n_and_update(Allocator &a, Iterator p, size_type n) + { this->priv_copy_some_and_update(a, index_tuple_t(), p, n); } + + private: + + template + void priv_copy_some_and_update(Allocator &a, const index_tuple&, Iterator p, size_type n) + { + BOOST_ASSERT(n ==1); (void)n; + typename aligned_storage::value>::type v; + value_type *vp = static_cast(static_cast(&v)); + alloc_traits::construct(a, vp, + ::boost::forward(get(this->args_))...); + BOOST_TRY{ + *p = ::boost::move(*vp); + } + BOOST_CATCH(...){ + alloc_traits::destroy(a, vp); + BOOST_RETHROW + } + BOOST_CATCH_END + alloc_traits::destroy(a, vp); + } +}; + +//Specializations to avoid an unneeded temporary when emplacing from a single argument o type value_type +template +struct insert_emplace_proxy::value_type> + : public insert_move_proxy +{ + explicit insert_emplace_proxy(typename boost::container::allocator_traits::value_type &&v) + : insert_move_proxy(v) + {} +}; + +//We use "add_const" here as adding "const" only confuses MSVC12(and maybe later) provoking +//compiler error C2752 ("more than one partial specialization matches"). +//Any problem is solvable with an extra layer of indirection? ;-) +template +struct insert_emplace_proxy::value_type>::type + > + : public insert_copy_proxy +{ + explicit insert_emplace_proxy(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +template +struct insert_emplace_proxy::value_type &> + : public insert_copy_proxy +{ + explicit insert_emplace_proxy(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +template +struct insert_emplace_proxy::value_type>::type & + > + : public insert_copy_proxy +{ + explicit insert_emplace_proxy(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +}}} //namespace boost { namespace container { namespace container_detail { + +#else // !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#include + +namespace boost { +namespace container { +namespace container_detail { + +#define BOOST_CONTAINER_ADVANCED_INSERT_INT_CODE(N) \ +template< class Allocator, class Iterator BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\ +struct insert_nonmovable_emplace_proxy##N\ +{\ + typedef boost::container::allocator_traits alloc_traits;\ + typedef typename alloc_traits::size_type size_type;\ + typedef typename alloc_traits::value_type value_type;\ + \ + explicit insert_nonmovable_emplace_proxy##N(BOOST_MOVE_UREF##N)\ + BOOST_MOVE_COLON##N BOOST_MOVE_FWD_INIT##N {}\ + \ + void uninitialized_copy_n_and_update(Allocator &a, Iterator p, size_type n)\ + {\ + BOOST_ASSERT(n == 1); (void)n;\ + alloc_traits::construct(a, iterator_to_raw_pointer(p) BOOST_MOVE_I##N BOOST_MOVE_MFWD##N);\ + }\ + \ + void copy_n_and_update(Allocator &, Iterator, size_type)\ + { BOOST_ASSERT(false); }\ + \ + protected:\ + BOOST_MOVE_MREF##N\ +};\ +\ +template< class Allocator, class Iterator BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\ +struct insert_emplace_proxy_arg##N\ + : insert_nonmovable_emplace_proxy##N< Allocator, Iterator BOOST_MOVE_I##N BOOST_MOVE_TARG##N >\ +{\ + typedef insert_nonmovable_emplace_proxy##N\ + < Allocator, Iterator BOOST_MOVE_I##N BOOST_MOVE_TARG##N > base_t;\ + typedef typename base_t::value_type value_type;\ + typedef typename base_t::size_type size_type;\ + typedef boost::container::allocator_traits alloc_traits;\ + \ + explicit insert_emplace_proxy_arg##N(BOOST_MOVE_UREF##N)\ + : base_t(BOOST_MOVE_FWD##N){}\ + \ + void copy_n_and_update(Allocator &a, Iterator p, size_type n)\ + {\ + BOOST_ASSERT(n == 1); (void)n;\ + typename aligned_storage::value>::type v;\ + BOOST_ASSERT((((size_type)(&v)) % alignment_of::value) == 0);\ + value_type *vp = static_cast(static_cast(&v));\ + alloc_traits::construct(a, vp BOOST_MOVE_I##N BOOST_MOVE_MFWD##N);\ + BOOST_TRY{\ + *p = ::boost::move(*vp);\ + }\ + BOOST_CATCH(...){\ + alloc_traits::destroy(a, vp);\ + BOOST_RETHROW\ + }\ + BOOST_CATCH_END\ + alloc_traits::destroy(a, vp);\ + }\ +};\ +// +BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_ADVANCED_INSERT_INT_CODE) +#undef BOOST_CONTAINER_ADVANCED_INSERT_INT_CODE + +#if defined(BOOST_NO_CXX11_RVALUE_REFERENCES) + +//Specializations to avoid an unneeded temporary when emplacing from a single argument o type value_type +template +struct insert_emplace_proxy_arg1::value_type> > + : public insert_move_proxy +{ + explicit insert_emplace_proxy_arg1(typename boost::container::allocator_traits::value_type &v) + : insert_move_proxy(v) + {} +}; + +template +struct insert_emplace_proxy_arg1::value_type> + : public insert_copy_proxy +{ + explicit insert_emplace_proxy_arg1(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +#else //e.g. MSVC10 & MSVC11 + +//Specializations to avoid an unneeded temporary when emplacing from a single argument o type value_type +template +struct insert_emplace_proxy_arg1::value_type> + : public insert_move_proxy +{ + explicit insert_emplace_proxy_arg1(typename boost::container::allocator_traits::value_type &&v) + : insert_move_proxy(v) + {} +}; + +//We use "add_const" here as adding "const" only confuses MSVC10&11 provoking +//compiler error C2752 ("more than one partial specialization matches"). +//Any problem is solvable with an extra layer of indirection? ;-) +template +struct insert_emplace_proxy_arg1::value_type>::type + > + : public insert_copy_proxy +{ + explicit insert_emplace_proxy_arg1(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +template +struct insert_emplace_proxy_arg1::value_type &> + : public insert_copy_proxy +{ + explicit insert_emplace_proxy_arg1(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +template +struct insert_emplace_proxy_arg1::value_type>::type & + > + : public insert_copy_proxy +{ + explicit insert_emplace_proxy_arg1(const typename boost::container::allocator_traits::value_type &v) + : insert_copy_proxy(v) + {} +}; + +#endif + +}}} //namespace boost { namespace container { namespace container_detail { + +#endif // !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#include + +#endif //#ifndef BOOST_CONTAINER_ADVANCED_INSERT_INT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/algorithm.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/algorithm.hpp new file mode 100644 index 0000000..67e7876 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/algorithm.hpp @@ -0,0 +1,35 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_ALGORITHM_HPP +#define BOOST_CONTAINER_DETAIL_ALGORITHM_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include + +namespace boost { +namespace container { + +using boost::intrusive::algo_equal; +using boost::intrusive::algo_lexicographical_compare; + +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ALGORITHM_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/alloc_helpers.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/alloc_helpers.hpp new file mode 100644 index 0000000..656e0c2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/alloc_helpers.hpp @@ -0,0 +1,60 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_ALLOC_TRAITS_HPP +#define BOOST_CONTAINER_DETAIL_ALLOC_TRAITS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +// move +#include +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +inline void swap_alloc(AllocatorType &, AllocatorType &, container_detail::false_type) + BOOST_NOEXCEPT_OR_NOTHROW +{} + +template +inline void swap_alloc(AllocatorType &l, AllocatorType &r, container_detail::true_type) +{ boost::adl_move_swap(l, r); } + +template +inline void assign_alloc(AllocatorType &, const AllocatorType &, container_detail::false_type) + BOOST_NOEXCEPT_OR_NOTHROW +{} + +template +inline void assign_alloc(AllocatorType &l, const AllocatorType &r, container_detail::true_type) +{ l = r; } + +template +inline void move_alloc(AllocatorType &, AllocatorType &, container_detail::false_type) + BOOST_NOEXCEPT_OR_NOTHROW +{} + +template +inline void move_alloc(AllocatorType &l, AllocatorType &r, container_detail::true_type) +{ l = ::boost::move(r); } + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ALLOC_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/allocation_type.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/allocation_type.hpp new file mode 100644 index 0000000..1e8aa67 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/allocation_type.hpp @@ -0,0 +1,58 @@ +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_ALLOCATION_TYPE_HPP +#define BOOST_CONTAINER_ALLOCATION_TYPE_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +namespace boost { +namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED +enum allocation_type_v +{ + // constants for allocation commands + allocate_new_v = 0x01, + expand_fwd_v = 0x02, + expand_bwd_v = 0x04, +// expand_both = expand_fwd | expand_bwd, +// expand_or_new = allocate_new | expand_both, + shrink_in_place_v = 0x08, + nothrow_allocation_v = 0x10, + zero_memory_v = 0x20, + try_shrink_in_place_v = 0x40 +}; + +typedef unsigned int allocation_type; +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED +static const allocation_type allocate_new = (allocation_type)allocate_new_v; +static const allocation_type expand_fwd = (allocation_type)expand_fwd_v; +static const allocation_type expand_bwd = (allocation_type)expand_bwd_v; +static const allocation_type shrink_in_place = (allocation_type)shrink_in_place_v; +static const allocation_type try_shrink_in_place= (allocation_type)try_shrink_in_place_v; +static const allocation_type nothrow_allocation = (allocation_type)nothrow_allocation_v; +static const allocation_type zero_memory = (allocation_type)zero_memory_v; + +} //namespace container { +} //namespace boost { + +#include + +#endif //BOOST_CONTAINER_ALLOCATION_TYPE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/config_begin.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/config_begin.hpp new file mode 100644 index 0000000..4df9e35 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/config_begin.hpp @@ -0,0 +1,53 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_CONFIG_INCLUDED +#define BOOST_CONTAINER_CONTAINER_DETAIL_CONFIG_INCLUDED +#ifndef BOOST_CONFIG_HPP +#include +#endif + +#endif //BOOST_CONTAINER_CONTAINER_DETAIL_CONFIG_INCLUDED + +#ifdef BOOST_MSVC + #pragma warning (push) + #pragma warning (disable : 4127) // conditional expression is constant + #pragma warning (disable : 4146) // unary minus operator applied to unsigned type, result still unsigned + #pragma warning (disable : 4197) // top-level volatile in cast is ignored + #pragma warning (disable : 4244) // possible loss of data + #pragma warning (disable : 4251) // "identifier" : class "type" needs to have dll-interface to be used by clients of class "type2" + #pragma warning (disable : 4267) // conversion from "X" to "Y", possible loss of data + #pragma warning (disable : 4275) // non DLL-interface classkey "identifier" used as base for DLL-interface classkey "identifier" + #pragma warning (disable : 4284) // odd return type for operator-> + #pragma warning (disable : 4290) // C++ exception specification ignored except to indicate a function is not __declspec(nothrow) + #pragma warning (disable : 4324) // structure was padded due to __declspec(align( + #pragma warning (disable : 4345) // behavior change: an object of POD type constructed with an initializer of the form () will be default-initialized + #pragma warning (disable : 4355) // "this" : used in base member initializer list + #pragma warning (disable : 4503) // "identifier" : decorated name length exceeded, name was truncated + #pragma warning (disable : 4510) // default constructor could not be generated + #pragma warning (disable : 4511) // copy constructor could not be generated + #pragma warning (disable : 4512) // assignment operator could not be generated + #pragma warning (disable : 4514) // unreferenced inline removed + #pragma warning (disable : 4521) // Disable "multiple copy constructors specified" + #pragma warning (disable : 4522) // "class" : multiple assignment operators specified + #pragma warning (disable : 4541) // 'typeid' used on polymorphic type '' with /GR-; unpredictable behavior may result + #pragma warning (disable : 4584) // X is already a base-class of Y + #pragma warning (disable : 4610) // struct can never be instantiated - user defined constructor required + #pragma warning (disable : 4671) // the copy constructor is inaccessible + #pragma warning (disable : 4673) // throwing '' the following types will not be considered at the catch site + #pragma warning (disable : 4675) // "method" should be declared "static" and have exactly one parameter + #pragma warning (disable : 4702) // unreachable code + #pragma warning (disable : 4706) // assignment within conditional expression + #pragma warning (disable : 4710) // function not inlined + #pragma warning (disable : 4714) // "function": marked as __forceinline not inlined + #pragma warning (disable : 4711) // function selected for automatic inline expansion + #pragma warning (disable : 4786) // identifier truncated in debug info + #pragma warning (disable : 4996) // "function": was declared deprecated + +#endif //BOOST_MSVC diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/config_end.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/config_end.hpp new file mode 100644 index 0000000..f93c8f6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/config_end.hpp @@ -0,0 +1,13 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#if defined BOOST_MSVC + #pragma warning (pop) +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/copy_move_algo.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/copy_move_algo.hpp new file mode 100644 index 0000000..f590a8a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/copy_move_algo.hpp @@ -0,0 +1,1142 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_UTILITIES_HPP +#define BOOST_CONTAINER_DETAIL_UTILITIES_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +// container +#include +// container/detail +#include +#include +#include +#include +// move +#include +#include +#include +// other +#include +// std +#include //for emmove/memcpy + +namespace boost { +namespace container { +namespace container_detail { + +template +struct are_elements_contiguous +{ + static const bool value = false; +}; + +///////////////////////// +// raw pointers +///////////////////////// + +template +struct are_elements_contiguous +{ + static const bool value = true; +}; + +///////////////////////// +// move iterators +///////////////////////// + +template +struct are_elements_contiguous< ::boost::move_iterator > + : are_elements_contiguous +{}; + +///////////////////////// +// predeclarations +///////////////////////// + +#ifndef BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +template +class vector_iterator; + +template +class vector_const_iterator; + +#endif //BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +} //namespace container_detail { +} //namespace container { + +namespace interprocess { + +template +class offset_ptr; + +} //namespace interprocess { + +namespace container { + +namespace container_detail { + +///////////////////////// +//vector_[const_]iterator +///////////////////////// + +#ifndef BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +template +struct are_elements_contiguous > +{ + static const bool value = true; +}; + +template +struct are_elements_contiguous > +{ + static const bool value = true; +}; + +#endif //BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +///////////////////////// +// offset_ptr +///////////////////////// + +template +struct are_elements_contiguous< ::boost::interprocess::offset_ptr > +{ + static const bool value = true; +}; + +template +struct are_contiguous_and_same + : boost::move_detail::and_ + < are_elements_contiguous + , are_elements_contiguous + , is_same< typename remove_const< typename ::boost::container::iterator_traits::value_type >::type + , typename ::boost::container::iterator_traits::value_type + > + > +{}; + +template +struct is_memtransfer_copy_assignable + : boost::move_detail::and_ + < are_contiguous_and_same + , container_detail::is_trivially_copy_assignable< typename ::boost::container::iterator_traits::value_type > + > +{}; + +template +struct is_memtransfer_copy_constructible + : boost::move_detail::and_ + < are_contiguous_and_same + , container_detail::is_trivially_copy_constructible< typename ::boost::container::iterator_traits::value_type > + > +{}; + +template +struct enable_if_memtransfer_copy_constructible + : enable_if, R> +{}; + +template +struct disable_if_memtransfer_copy_constructible + : disable_if, R> +{}; + +template +struct enable_if_memtransfer_copy_assignable + : enable_if, R> +{}; + +template +struct disable_if_memtransfer_copy_assignable + : disable_if, R> +{}; + +template + // F models ForwardIterator +inline F memmove(I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ + typedef typename boost::container::iterator_traits::value_type value_type; + typename boost::container::iterator_traits::difference_type n = boost::container::iterator_distance(f, l); + if(n){ + std::memmove((iterator_to_raw_pointer)(r), (iterator_to_raw_pointer)(f), sizeof(value_type)*n); + boost::container::iterator_advance(r, n); + } + return r; +} + +template + // F models ForwardIterator +F memmove_n(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ + typedef typename boost::container::iterator_traits::value_type value_type; + if(n){ + std::memmove((iterator_to_raw_pointer)(r), (iterator_to_raw_pointer)(f), sizeof(value_type)*n); + boost::container::iterator_advance(r, n); + } + return r; +} + +template + // F models ForwardIterator +I memmove_n_source(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ + if(n){ + typedef typename boost::container::iterator_traits::value_type value_type; + std::memmove((iterator_to_raw_pointer)(r), (iterator_to_raw_pointer)(f), sizeof(value_type)*n); + boost::container::iterator_advance(f, n); + } + return f; +} + +template + // F models ForwardIterator +I memmove_n_source_dest(I f, typename boost::container::iterator_traits::difference_type n, F &r) BOOST_NOEXCEPT_OR_NOTHROW +{ + typedef typename boost::container::iterator_traits::value_type value_type; + if(n){ + std::memmove((iterator_to_raw_pointer)(r), (iterator_to_raw_pointer)(f), sizeof(value_type)*n); + boost::container::iterator_advance(f, n); + boost::container::iterator_advance(r, n); + } + return f; +} + +template +struct is_memzero_initializable +{ + typedef typename ::boost::container::iterator_traits::value_type value_type; + static const bool value = are_elements_contiguous::value && + ( container_detail::is_integral::value || container_detail::is_enum::value + #if defined(BOOST_CONTAINER_MEMZEROED_POINTER_IS_NULL) + || container_detail::is_pointer::value + #endif + #if defined(BOOST_CONTAINER_MEMZEROED_FLOATING_POINT_IS_ZERO) + || container_detail::is_floating_point::value + #endif + #if defined(BOOST_CONTAINER_MEMZEROED_FLOATING_POINT_IS_ZERO) && defined(BOOST_CONTAINER_MEMZEROED_POINTER_IS_NULL) + || container_detail::is_pod::value + #endif + ); +}; + +template +struct enable_if_memzero_initializable + : enable_if_c::value, R> +{}; + +template +struct disable_if_memzero_initializable + : enable_if_c::value, R> +{}; + +template +struct enable_if_trivially_destructible + : enable_if_c < container_detail::is_trivially_destructible + ::value_type>::value + , R> +{}; + +template +struct disable_if_trivially_destructible + : enable_if_c ::value_type>::value + , R> +{}; + +} //namespace container_detail { + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_move_alloc +// +////////////////////////////////////////////////////////////////////////////// + + +//! Effects: +//! \code +//! for (; f != l; ++r, ++f) +//! allocator_traits::construct(a, &*r, boost::move(*f)); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc(Allocator &a, I f, I l, F r) +{ + F back = r; + BOOST_TRY{ + while (f != l) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), boost::move(*f)); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc(Allocator &, I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove(f, l, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_move_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r, boost::move(*f)); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc_n(Allocator &a, I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), boost::move(*f)); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc_n(Allocator &, I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_move_alloc_n_source +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r, boost::move(*f)); +//! \endcode +//! +//! Returns: f (after incremented) +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc_n_source(Allocator &a, I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), boost::move(*f)); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_move_alloc_n_source(Allocator &, I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_copy_alloc +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; f != l; ++r, ++f) +//! allocator_traits::construct(a, &*r, *f); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc(Allocator &a, I f, I l, F r) +{ + F back = r; + BOOST_TRY{ + while (f != l) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), *f); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc(Allocator &, I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove(f, l, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_copy_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r, *f); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc_n(Allocator &a, I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), *f); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc_n(Allocator &, I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_copy_alloc_n_source +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r, *f); +//! \endcode +//! +//! Returns: f (after incremented) +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc_n_source(Allocator &a, I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), *f); + ++f; ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_constructible::type + uninitialized_copy_alloc_n_source(Allocator &, I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_value_init_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline typename container_detail::disable_if_memzero_initializable::type + uninitialized_value_init_alloc_n(Allocator &a, typename allocator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r)); + ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memzero_initializable::type + uninitialized_value_init_alloc_n(Allocator &, typename allocator_traits::difference_type n, F r) +{ + typedef typename boost::container::iterator_traits::value_type value_type; + std::memset((void*)container_detail::iterator_to_raw_pointer(r), 0, sizeof(value_type)*n); + boost::container::iterator_advance(r, n); + return r; +} + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_default_init_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline F uninitialized_default_init_alloc_n(Allocator &a, typename allocator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), default_init); + ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_fill_alloc +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; f != l; ++r, ++f) +//! allocator_traits::construct(a, &*r, *f); +//! \endcode +//! +//! Returns: r +template + +inline void uninitialized_fill_alloc(Allocator &a, F f, F l, const T &t) +{ + F back = f; + BOOST_TRY{ + while (f != l) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(f), t); + ++f; + } + } + BOOST_CATCH(...){ + for (; back != l; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END +} + + +////////////////////////////////////////////////////////////////////////////// +// +// uninitialized_fill_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +//! Effects: +//! \code +//! for (; n--; ++r, ++f) +//! allocator_traits::construct(a, &*r, v); +//! \endcode +//! +//! Returns: r +template + // F models ForwardIterator +inline F uninitialized_fill_alloc_n(Allocator &a, const T &v, typename allocator_traits::difference_type n, F r) +{ + F back = r; + BOOST_TRY{ + while (n--) { + allocator_traits::construct(a, container_detail::iterator_to_raw_pointer(r), v); + ++r; + } + } + BOOST_CATCH(...){ + for (; back != r; ++back){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(back)); + } + BOOST_RETHROW; + } + BOOST_CATCH_END + return r; +} + +////////////////////////////////////////////////////////////////////////////// +// +// copy +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + copy(I f, I l, F r) +{ + while (f != l) { + *r = *f; + ++f; ++r; + } + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + copy(I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove(f, l, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// copy_n +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + copy_n(I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + while (n--) { + *r = *f; + ++f; ++r; + } + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + copy_n(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// copy_n_source +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + copy_n_source(I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + while (n--) { + *r = *f; + ++f; ++r; + } + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + copy_n_source(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// copy_n_source_dest +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + copy_n_source_dest(I f, typename boost::container::iterator_traits::difference_type n, F &r) +{ + while (n--) { + *r = *f; + ++f; ++r; + } + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + copy_n_source_dest(I f, typename boost::container::iterator_traits::difference_type n, F &r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source_dest(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// move +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + move(I f, I l, F r) +{ + while (f != l) { + *r = ::boost::move(*f); + ++f; ++r; + } + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + move(I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove(f, l, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// move_n +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + move_n(I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + while (n--) { + *r = ::boost::move(*f); + ++f; ++r; + } + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + move_n(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n(f, n, r); } + + +////////////////////////////////////////////////////////////////////////////// +// +// move_backward +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + move_backward(I f, I l, F r) +{ + while (f != l) { + --l; --r; + *r = ::boost::move(*l); + } + return r; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + move_backward(I f, I l, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ + typedef typename boost::container::iterator_traits::value_type value_type; + const typename boost::container::iterator_traits::difference_type n = boost::container::iterator_distance(f, l); + r -= n; + std::memmove((container_detail::iterator_to_raw_pointer)(r), (container_detail::iterator_to_raw_pointer)(f), sizeof(value_type)*n); + return r; +} + +////////////////////////////////////////////////////////////////////////////// +// +// move_n_source_dest +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + move_n_source_dest(I f, typename boost::container::iterator_traits::difference_type n, F &r) +{ + while (n--) { + *r = ::boost::move(*f); + ++f; ++r; + } + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + move_n_source_dest(I f, typename boost::container::iterator_traits::difference_type n, F &r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source_dest(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// move_n_source +// +////////////////////////////////////////////////////////////////////////////// + +template + // F models ForwardIterator +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + move_n_source(I f, typename boost::container::iterator_traits::difference_type n, F r) +{ + while (n--) { + *r = ::boost::move(*f); + ++f; ++r; + } + return f; +} + +template + // F models ForwardIterator +inline typename container_detail::enable_if_memtransfer_copy_assignable::type + move_n_source(I f, typename boost::container::iterator_traits::difference_type n, F r) BOOST_NOEXCEPT_OR_NOTHROW +{ return container_detail::memmove_n_source(f, n, r); } + +////////////////////////////////////////////////////////////////////////////// +// +// destroy_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +template + // U models unsigned integral constant +inline typename container_detail::disable_if_trivially_destructible::type + destroy_alloc_n(Allocator &a, I f, U n) +{ + while(n--){ + allocator_traits::destroy(a, container_detail::iterator_to_raw_pointer(f)); + ++f; + } +} + +template + // U models unsigned integral constant +inline typename container_detail::enable_if_trivially_destructible::type + destroy_alloc_n(Allocator &, I, U) +{} + +////////////////////////////////////////////////////////////////////////////// +// +// deep_swap_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +template + +inline typename container_detail::disable_if_memtransfer_copy_assignable::type + deep_swap_alloc_n( Allocator &a, F short_range_f, typename allocator_traits::size_type n_i + , G large_range_f, typename allocator_traits::size_type n_j) +{ + typename allocator_traits::size_type n = 0; + for (; n != n_i ; ++short_range_f, ++large_range_f, ++n){ + boost::adl_move_swap(*short_range_f, *large_range_f); + } + boost::container::uninitialized_move_alloc_n(a, large_range_f, n_j - n_i, short_range_f); // may throw + boost::container::destroy_alloc_n(a, large_range_f, n_j - n_i); +} + +static const std::size_t DeepSwapAllocNMaxStorage = std::size_t(1) << std::size_t(11); //2K bytes + +template + +inline typename container_detail::enable_if_c + < container_detail::is_memtransfer_copy_assignable::value && (MaxTmpBytes <= DeepSwapAllocNMaxStorage) && false + , void>::type + deep_swap_alloc_n( Allocator &a, F short_range_f, typename allocator_traits::size_type n_i + , G large_range_f, typename allocator_traits::size_type n_j) +{ + typedef typename allocator_traits::value_type value_type; + typedef typename container_detail::aligned_storage + ::value>::type storage_type; + storage_type storage; + + const std::size_t n_i_bytes = sizeof(value_type)*n_i; + void *const large_ptr = static_cast(container_detail::iterator_to_raw_pointer(large_range_f)); + void *const short_ptr = static_cast(container_detail::iterator_to_raw_pointer(short_range_f)); + void *const stora_ptr = static_cast(container_detail::iterator_to_raw_pointer(storage)); + std::memcpy(stora_ptr, large_ptr, n_i_bytes); + std::memcpy(large_ptr, short_ptr, n_i_bytes); + std::memcpy(short_ptr, stora_ptr, n_i_bytes); + boost::container::iterator_advance(large_range_f, n_i); + boost::container::iterator_advance(short_range_f, n_i); + boost::container::uninitialized_move_alloc_n(a, large_range_f, n_j - n_i, short_range_f); // may throw + boost::container::destroy_alloc_n(a, large_range_f, n_j - n_i); +} + +template + +inline typename container_detail::enable_if_c + < container_detail::is_memtransfer_copy_assignable::value && true//(MaxTmpBytes > DeepSwapAllocNMaxStorage) + , void>::type + deep_swap_alloc_n( Allocator &a, F short_range_f, typename allocator_traits::size_type n_i + , G large_range_f, typename allocator_traits::size_type n_j) +{ + typedef typename allocator_traits::value_type value_type; + typedef typename container_detail::aligned_storage + ::value>::type storage_type; + storage_type storage; + const std::size_t sizeof_storage = sizeof(storage); + + std::size_t n_i_bytes = sizeof(value_type)*n_i; + char *large_ptr = static_cast(static_cast(container_detail::iterator_to_raw_pointer(large_range_f))); + char *short_ptr = static_cast(static_cast(container_detail::iterator_to_raw_pointer(short_range_f))); + char *stora_ptr = static_cast(static_cast(&storage)); + + std::size_t szt_times = n_i_bytes/sizeof_storage; + const std::size_t szt_rem = n_i_bytes%sizeof_storage; + + //Loop unrolling using Duff's device, as it seems it helps on some architectures + const std::size_t Unroll = 4; + std::size_t n = (szt_times + (Unroll-1))/Unroll; + const std::size_t branch_number = (!szt_times)*Unroll + (szt_times % Unroll); + switch(branch_number){ + case 4: + break; + case 0: do{ + std::memcpy(stora_ptr, large_ptr, sizeof_storage); + std::memcpy(large_ptr, short_ptr, sizeof_storage); + std::memcpy(short_ptr, stora_ptr, sizeof_storage); + large_ptr += sizeof_storage; + short_ptr += sizeof_storage; + BOOST_CONTAINER_FALLTHOUGH + case 3: + std::memcpy(stora_ptr, large_ptr, sizeof_storage); + std::memcpy(large_ptr, short_ptr, sizeof_storage); + std::memcpy(short_ptr, stora_ptr, sizeof_storage); + large_ptr += sizeof_storage; + short_ptr += sizeof_storage; + BOOST_CONTAINER_FALLTHOUGH + case 2: + std::memcpy(stora_ptr, large_ptr, sizeof_storage); + std::memcpy(large_ptr, short_ptr, sizeof_storage); + std::memcpy(short_ptr, stora_ptr, sizeof_storage); + large_ptr += sizeof_storage; + short_ptr += sizeof_storage; + BOOST_CONTAINER_FALLTHOUGH + case 1: + std::memcpy(stora_ptr, large_ptr, sizeof_storage); + std::memcpy(large_ptr, short_ptr, sizeof_storage); + std::memcpy(short_ptr, stora_ptr, sizeof_storage); + large_ptr += sizeof_storage; + short_ptr += sizeof_storage; + } while(--n); + } + std::memcpy(stora_ptr, large_ptr, szt_rem); + std::memcpy(large_ptr, short_ptr, szt_rem); + std::memcpy(short_ptr, stora_ptr, szt_rem); + boost::container::iterator_advance(large_range_f, n_i); + boost::container::iterator_advance(short_range_f, n_i); + boost::container::uninitialized_move_alloc_n(a, large_range_f, n_j - n_i, short_range_f); // may throw + boost::container::destroy_alloc_n(a, large_range_f, n_j - n_i); +} + + +////////////////////////////////////////////////////////////////////////////// +// +// copy_assign_range_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +template + +void copy_assign_range_alloc_n( Allocator &a, I inp_start, typename allocator_traits::size_type n_i + , O out_start, typename allocator_traits::size_type n_o ) +{ + if (n_o < n_i){ + inp_start = boost::container::copy_n_source_dest(inp_start, n_o, out_start); // may throw + boost::container::uninitialized_copy_alloc_n(a, inp_start, n_i - n_o, out_start);// may throw + } + else{ + out_start = boost::container::copy_n(inp_start, n_i, out_start); // may throw + boost::container::destroy_alloc_n(a, out_start, n_o - n_i); + } +} + +////////////////////////////////////////////////////////////////////////////// +// +// move_assign_range_alloc_n +// +////////////////////////////////////////////////////////////////////////////// + +template + +void move_assign_range_alloc_n( Allocator &a, I inp_start, typename allocator_traits::size_type n_i + , O out_start, typename allocator_traits::size_type n_o ) +{ + if (n_o < n_i){ + inp_start = boost::container::move_n_source_dest(inp_start, n_o, out_start); // may throw + boost::container::uninitialized_move_alloc_n(a, inp_start, n_i - n_o, out_start); // may throw + } + else{ + out_start = boost::container::move_n(inp_start, n_i, out_start); // may throw + boost::container::destroy_alloc_n(a, out_start, n_o - n_i); + } +} + +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_UTILITIES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/destroyers.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/destroyers.hpp new file mode 100644 index 0000000..52b44c0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/destroyers.hpp @@ -0,0 +1,378 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DESTROYERS_HPP +#define BOOST_CONTAINER_DESTROYERS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +#include +#include +#include + +namespace boost { +namespace container { +namespace container_detail { + +//!A deleter for scoped_ptr that deallocates the memory +//!allocated for an object using a STL allocator. +template +struct scoped_deallocator +{ + typedef allocator_traits allocator_traits_type; + typedef typename allocator_traits_type::pointer pointer; + typedef container_detail::integral_constant::value> alloc_version; + + private: + void priv_deallocate(version_1) + { m_alloc.deallocate(m_ptr, 1); } + + void priv_deallocate(version_2) + { m_alloc.deallocate_one(m_ptr); } + + BOOST_MOVABLE_BUT_NOT_COPYABLE(scoped_deallocator) + + public: + + pointer m_ptr; + Allocator& m_alloc; + + scoped_deallocator(pointer p, Allocator& a) + : m_ptr(p), m_alloc(a) + {} + + ~scoped_deallocator() + { if (m_ptr)priv_deallocate(alloc_version()); } + + scoped_deallocator(BOOST_RV_REF(scoped_deallocator) o) + : m_ptr(o.m_ptr), m_alloc(o.m_alloc) + { o.release(); } + + pointer get() const + { return m_ptr; } + + void set(const pointer &p) + { m_ptr = p; } + + void release() + { m_ptr = 0; } +}; + +template +struct null_scoped_deallocator +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::size_type size_type; + + null_scoped_deallocator(pointer, Allocator&, size_type) + {} + + void release() + {} + + pointer get() const + { return pointer(); } + + void set(const pointer &) + {} +}; + +//!A deleter for scoped_ptr that deallocates the memory +//!allocated for an array of objects using a STL allocator. +template +struct scoped_array_deallocator +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::size_type size_type; + + scoped_array_deallocator(pointer p, Allocator& a, size_type length) + : m_ptr(p), m_alloc(a), m_length(length) {} + + ~scoped_array_deallocator() + { if (m_ptr) m_alloc.deallocate(m_ptr, m_length); } + + void release() + { m_ptr = 0; } + + private: + pointer m_ptr; + Allocator& m_alloc; + size_type m_length; +}; + +template +struct null_scoped_array_deallocator +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::size_type size_type; + + null_scoped_array_deallocator(pointer, Allocator&, size_type) + {} + + void release() + {} +}; + +template +struct scoped_destroy_deallocator +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::size_type size_type; + typedef container_detail::integral_constant::value> alloc_version; + + scoped_destroy_deallocator(pointer p, Allocator& a) + : m_ptr(p), m_alloc(a) {} + + ~scoped_destroy_deallocator() + { + if(m_ptr){ + AllocTraits::destroy(m_alloc, container_detail::to_raw_pointer(m_ptr)); + priv_deallocate(m_ptr, alloc_version()); + } + } + + void release() + { m_ptr = 0; } + + private: + + void priv_deallocate(const pointer &p, version_1) + { AllocTraits::deallocate(m_alloc, p, 1); } + + void priv_deallocate(const pointer &p, version_2) + { m_alloc.deallocate_one(p); } + + pointer m_ptr; + Allocator& m_alloc; +}; + + +//!A deleter for scoped_ptr that destroys +//!an object using a STL allocator. +template +struct scoped_destructor_n +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::value_type value_type; + typedef typename AllocTraits::size_type size_type; + + scoped_destructor_n(pointer p, Allocator& a, size_type n) + : m_p(p), m_a(a), m_n(n) + {} + + void release() + { m_p = 0; } + + void increment_size(size_type inc) + { m_n += inc; } + + void increment_size_backwards(size_type inc) + { m_n += inc; m_p -= inc; } + + void shrink_forward(size_type inc) + { m_n -= inc; m_p += inc; } + + ~scoped_destructor_n() + { + if(!m_p) return; + value_type *raw_ptr = container_detail::to_raw_pointer(m_p); + while(m_n--){ + AllocTraits::destroy(m_a, raw_ptr++); + } + } + + private: + pointer m_p; + Allocator & m_a; + size_type m_n; +}; + +//!A deleter for scoped_ptr that destroys +//!an object using a STL allocator. +template +struct null_scoped_destructor_n +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::pointer pointer; + typedef typename AllocTraits::size_type size_type; + + null_scoped_destructor_n(pointer, Allocator&, size_type) + {} + + void increment_size(size_type) + {} + + void increment_size_backwards(size_type) + {} + + void shrink_forward(size_type) + {} + + void release() + {} +}; + +template +class scoped_destructor +{ + typedef boost::container::allocator_traits AllocTraits; + public: + typedef typename Allocator::value_type value_type; + scoped_destructor(Allocator &a, value_type *pv) + : pv_(pv), a_(a) + {} + + ~scoped_destructor() + { + if(pv_){ + AllocTraits::destroy(a_, pv_); + } + } + + void release() + { pv_ = 0; } + + + void set(value_type *ptr) { pv_ = ptr; } + + value_type *get() const { return pv_; } + + private: + value_type *pv_; + Allocator &a_; +}; + + +template +class value_destructor +{ + typedef boost::container::allocator_traits AllocTraits; + public: + typedef typename Allocator::value_type value_type; + value_destructor(Allocator &a, value_type &rv) + : rv_(rv), a_(a) + {} + + ~value_destructor() + { + AllocTraits::destroy(a_, &rv_); + } + + private: + value_type &rv_; + Allocator &a_; +}; + +template +class allocator_destroyer +{ + typedef boost::container::allocator_traits AllocTraits; + typedef typename AllocTraits::value_type value_type; + typedef typename AllocTraits::pointer pointer; + typedef container_detail::integral_constant::value> alloc_version; + + private: + Allocator & a_; + + private: + void priv_deallocate(const pointer &p, version_1) + { AllocTraits::deallocate(a_,p, 1); } + + void priv_deallocate(const pointer &p, version_2) + { a_.deallocate_one(p); } + + public: + explicit allocator_destroyer(Allocator &a) + : a_(a) + {} + + void operator()(const pointer &p) + { + AllocTraits::destroy(a_, container_detail::to_raw_pointer(p)); + this->priv_deallocate(p, alloc_version()); + } +}; + +template +class allocator_destroyer_and_chain_builder +{ + typedef allocator_traits allocator_traits_type; + typedef typename allocator_traits_type::value_type value_type; + typedef typename Allocator::multiallocation_chain multiallocation_chain; + + Allocator & a_; + multiallocation_chain &c_; + + public: + allocator_destroyer_and_chain_builder(Allocator &a, multiallocation_chain &c) + : a_(a), c_(c) + {} + + void operator()(const typename Allocator::pointer &p) + { + allocator_traits::destroy(a_, container_detail::to_raw_pointer(p)); + c_.push_back(p); + } +}; + +template +class allocator_multialloc_chain_node_deallocator +{ + typedef allocator_traits allocator_traits_type; + typedef typename allocator_traits_type::value_type value_type; + typedef typename Allocator::multiallocation_chain multiallocation_chain; + typedef allocator_destroyer_and_chain_builder chain_builder; + + Allocator & a_; + multiallocation_chain c_; + + public: + allocator_multialloc_chain_node_deallocator(Allocator &a) + : a_(a), c_() + {} + + chain_builder get_chain_builder() + { return chain_builder(a_, c_); } + + ~allocator_multialloc_chain_node_deallocator() + { + a_.deallocate_individual(c_); + } +}; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#include + +#endif //#ifndef BOOST_CONTAINER_DESTROYERS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/dispatch_uses_allocator.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/dispatch_uses_allocator.hpp new file mode 100644 index 0000000..3000f7c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/dispatch_uses_allocator.hpp @@ -0,0 +1,293 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2015-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DISPATCH_USES_ALLOCATOR_HPP +#define BOOST_CONTAINER_DISPATCH_USES_ALLOCATOR_HPP + +#if defined (_MSC_VER) +# pragma once +#endif + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif +#include + +#include + +namespace boost { namespace container { + +namespace container_detail { + + +// Check if we can detect is_convertible using advanced SFINAE expressions +#if !defined(BOOST_NO_CXX11_DECLTYPE) && !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + //! Code inspired by Mathias Gaunard's is_convertible.cpp found in the Boost mailing list + //! http://boost.2283326.n4.nabble.com/type-traits-is-constructible-when-decltype-is-supported-td3575452.html + //! Thanks Mathias! + + //With variadic templates, we need a single class to implement the trait + template + struct is_constructible + { + typedef char yes_type; + struct no_type + { char padding[2]; }; + + template + struct dummy; + + template + static decltype(X(boost::move_detail::declval()...), true_type()) test(int); + + template + static no_type test(...); + + static const bool value = sizeof(test(0)) == sizeof(yes_type); + }; + + template + struct is_constructible_with_allocator_prefix + : is_constructible + {}; + +#else // #if !defined(BOOST_NO_SFINAE_EXPR) && !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + //Without advanced SFINAE expressions, we can't use is_constructible + //so backup to constructible_with_allocator_xxx + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + template + struct is_constructible_with_allocator_prefix + : constructible_with_allocator_prefix + {}; + + template + struct is_constructible_with_allocator_suffix + : constructible_with_allocator_suffix + {}; + + #else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + template + struct is_constructible_with_allocator_prefix + : constructible_with_allocator_prefix + {}; + + template + struct is_constructible_with_allocator_suffix + : constructible_with_allocator_suffix + {}; + + #endif // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#endif // #if !defined(BOOST_NO_SFINAE_EXPR) + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template < typename ConstructAlloc + , typename ArgAlloc + , typename T + , class ...Args + > +inline typename container_detail::enable_if_and + < void + , container_detail::is_not_pair + , container_detail::not_< uses_allocator > + >::type dispatch_uses_allocator + ( ConstructAlloc & construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p, BOOST_FWD_REF(Args)...args) +{ + (void)arg_alloc; + allocator_traits::construct(construct_alloc, p, ::boost::forward(args)...); +} + +// allocator_arg_t +template < typename ConstructAlloc + , typename ArgAlloc + , typename T + , class ...Args + > +inline typename container_detail::enable_if_and + < void + , container_detail::is_not_pair + , uses_allocator + , is_constructible_with_allocator_prefix + >::type dispatch_uses_allocator + ( ConstructAlloc& construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p, BOOST_FWD_REF(Args) ...args) +{ + allocator_traits::construct + ( construct_alloc, p, allocator_arg + , ::boost::forward(arg_alloc), ::boost::forward(args)...); +} + +// allocator suffix +template < typename ConstructAlloc + , typename ArgAlloc + , typename T + , class ...Args + > +inline typename container_detail::enable_if_and + < void + , container_detail::is_not_pair + , uses_allocator + , container_detail::not_ > + >::type dispatch_uses_allocator + ( ConstructAlloc& construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p, BOOST_FWD_REF(Args)...args) +{ + allocator_traits::construct + (construct_alloc, p, ::boost::forward(args)..., ::boost::forward(arg_alloc)); +} + +#else //#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#define BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE(N) \ + template \ + inline typename container_detail::enable_if_and\ + < void\ + , container_detail::is_not_pair\ + , container_detail::not_ >\ + >::type\ + dispatch_uses_allocator\ + (ConstructAlloc &construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + {\ + (void)arg_alloc;\ + allocator_traits::construct(construct_alloc, p BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\ + }\ +// +BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE) +#undef BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE + +#define BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE(N) \ + template < typename ConstructAlloc, typename ArgAlloc, typename T BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\ + inline typename container_detail::enable_if_and\ + < void\ + , container_detail::is_not_pair\ + , uses_allocator\ + , is_constructible_with_allocator_prefix\ + >::type\ + dispatch_uses_allocator\ + (ConstructAlloc& construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + {\ + allocator_traits::construct\ + (construct_alloc, p, allocator_arg, ::boost::forward(arg_alloc) BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\ + }\ +// +BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE) +#undef BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE + +#define BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE(N) \ + template < typename ConstructAlloc, typename ArgAlloc, typename T BOOST_MOVE_I##N BOOST_MOVE_CLASS##N >\ + inline typename container_detail::enable_if_and\ + < void\ + , container_detail::is_not_pair\ + , uses_allocator\ + , container_detail::not_ >\ + >::type\ + dispatch_uses_allocator\ + (ConstructAlloc& construct_alloc, BOOST_FWD_REF(ArgAlloc) arg_alloc, T* p BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + {\ + allocator_traits::construct\ + (construct_alloc, p BOOST_MOVE_I##N BOOST_MOVE_FWD##N, ::boost::forward(arg_alloc));\ + }\ +// +BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE) +#undef BOOST_CONTAINER_SCOPED_ALLOCATOR_DISPATCH_USES_ALLOCATOR_CODE + +#endif //#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template < typename ConstructAlloc + , typename ArgAlloc + , typename Pair + > inline +BOOST_CONTAINER_DOC1ST(void, typename container_detail::enable_if >::type) + dispatch_uses_allocator + ( ConstructAlloc & construct_alloc + , ArgAlloc & arg_alloc + , Pair* p) +{ + (dispatch_uses_allocator)(construct_alloc, arg_alloc, container_detail::addressof(p->first)); + BOOST_TRY{ + (dispatch_uses_allocator)(construct_alloc, arg_alloc, container_detail::addressof(p->second)); + } + BOOST_CATCH(...) { + allocator_traits::destroy(construct_alloc, container_detail::addressof(p->first)); + BOOST_RETHROW + } + BOOST_CATCH_END +} + + +template < typename ConstructAlloc + , typename ArgAlloc + , class Pair, class U, class V> +BOOST_CONTAINER_DOC1ST(void, typename container_detail::enable_if >::type) + dispatch_uses_allocator + ( ConstructAlloc & construct_alloc + , ArgAlloc & arg_alloc + , Pair* p, BOOST_FWD_REF(U) x, BOOST_FWD_REF(V) y) +{ + (dispatch_uses_allocator)(construct_alloc, arg_alloc, container_detail::addressof(p->first), ::boost::forward(x)); + BOOST_TRY{ + (dispatch_uses_allocator)(construct_alloc, arg_alloc, container_detail::addressof(p->second), ::boost::forward(y)); + } + BOOST_CATCH(...){ + allocator_traits::destroy(construct_alloc, container_detail::addressof(p->first)); + BOOST_RETHROW + } + BOOST_CATCH_END +} + +template < typename ConstructAlloc + , typename ArgAlloc + , class Pair, class Pair2> +BOOST_CONTAINER_DOC1ST(void, typename container_detail::enable_if< container_detail::is_pair >::type) + dispatch_uses_allocator + (ConstructAlloc & construct_alloc + , ArgAlloc & arg_alloc + , Pair* p, Pair2& x) +{ (dispatch_uses_allocator)(construct_alloc, arg_alloc, p, x.first, x.second); } + +template < typename ConstructAlloc + , typename ArgAlloc + , class Pair, class Pair2> +typename container_detail::enable_if_and + < void + , container_detail::is_pair + , container_detail::not_ > >::type //This is needed for MSVC10 and ambiguous overloads + dispatch_uses_allocator + (ConstructAlloc & construct_alloc + , ArgAlloc & arg_alloc + , Pair* p, BOOST_RV_REF_BEG Pair2 BOOST_RV_REF_END x) +{ (dispatch_uses_allocator)(construct_alloc, arg_alloc, p, ::boost::move(x.first), ::boost::move(x.second)); } + +//template +//void dispatch_uses_allocator( ConstructAlloc & construct_alloc, ArgAlloc & arg_alloc +// , pair* p, piecewise_construct_t, tuple x, tuple y); + +} //namespace container_detail + +}} // namespace boost { namespace container { + +#include + +#endif // BOOST_CONTAINER_DISPATCH_USES_ALLOCATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/iterator.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/iterator.hpp new file mode 100644 index 0000000..8538acc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/iterator.hpp @@ -0,0 +1,40 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_ITERATOR_HPP +#define BOOST_CONTAINER_DETAIL_ITERATOR_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include + +namespace boost { +namespace container { + +using ::boost::intrusive::iterator_traits; +using ::boost::intrusive::iterator_distance; +using ::boost::intrusive::iterator_advance; +using ::boost::intrusive::iterator; +using ::boost::intrusive::iterator_enable_if_tag; +using ::boost::intrusive::iterator_disable_if_tag; +using ::boost::intrusive::iterator_arrow_result; + +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ITERATORS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/iterator_to_raw_pointer.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/iterator_to_raw_pointer.hpp new file mode 100644 index 0000000..83736d8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/iterator_to_raw_pointer.hpp @@ -0,0 +1,58 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_ITERATOR_TO_RAW_POINTER_HPP +#define BOOST_CONTAINER_DETAIL_ITERATOR_TO_RAW_POINTER_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +inline T* iterator_to_pointer(T* i) +{ return i; } + +template +inline typename boost::container::iterator_traits::pointer + iterator_to_pointer(const Iterator &i) +{ return i.operator->(); } + +template +struct iterator_to_element_ptr +{ + typedef typename boost::container::iterator_traits::pointer pointer; + typedef typename boost::intrusive::pointer_traits::element_type element_type; + typedef element_type* type; +}; + +template +inline typename iterator_to_element_ptr::type + iterator_to_raw_pointer(const Iterator &i) +{ + return ::boost::intrusive::detail::to_raw_pointer + ( ::boost::container::container_detail::iterator_to_pointer(i) ); +} + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ITERATOR_TO_RAW_POINTER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/iterators.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/iterators.hpp new file mode 100644 index 0000000..32ff32f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/iterators.hpp @@ -0,0 +1,828 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. +// (C) Copyright Gennaro Prota 2003 - 2004. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_ITERATORS_HPP +#define BOOST_CONTAINER_DETAIL_ITERATORS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#else +#include +#endif +#include + +namespace boost { +namespace container { + +template +class constant_iterator + : public ::boost::container::iterator + +{ + typedef constant_iterator this_type; + + public: + explicit constant_iterator(const T &ref, Difference range_size) + : m_ptr(&ref), m_num(range_size){} + + //Constructors + constant_iterator() + : m_ptr(0), m_num(0){} + + constant_iterator& operator++() + { increment(); return *this; } + + constant_iterator operator++(int) + { + constant_iterator result (*this); + increment(); + return result; + } + + constant_iterator& operator--() + { decrement(); return *this; } + + constant_iterator operator--(int) + { + constant_iterator result (*this); + decrement(); + return result; + } + + friend bool operator== (const constant_iterator& i, const constant_iterator& i2) + { return i.equal(i2); } + + friend bool operator!= (const constant_iterator& i, const constant_iterator& i2) + { return !(i == i2); } + + friend bool operator< (const constant_iterator& i, const constant_iterator& i2) + { return i.less(i2); } + + friend bool operator> (const constant_iterator& i, const constant_iterator& i2) + { return i2 < i; } + + friend bool operator<= (const constant_iterator& i, const constant_iterator& i2) + { return !(i > i2); } + + friend bool operator>= (const constant_iterator& i, const constant_iterator& i2) + { return !(i < i2); } + + friend Difference operator- (const constant_iterator& i, const constant_iterator& i2) + { return i2.distance_to(i); } + + //Arithmetic + constant_iterator& operator+=(Difference off) + { this->advance(off); return *this; } + + constant_iterator operator+(Difference off) const + { + constant_iterator other(*this); + other.advance(off); + return other; + } + + friend constant_iterator operator+(Difference off, const constant_iterator& right) + { return right + off; } + + constant_iterator& operator-=(Difference off) + { this->advance(-off); return *this; } + + constant_iterator operator-(Difference off) const + { return *this + (-off); } + + const T& operator*() const + { return dereference(); } + + const T& operator[] (Difference ) const + { return dereference(); } + + const T* operator->() const + { return &(dereference()); } + + private: + const T * m_ptr; + Difference m_num; + + void increment() + { --m_num; } + + void decrement() + { ++m_num; } + + bool equal(const this_type &other) const + { return m_num == other.m_num; } + + bool less(const this_type &other) const + { return other.m_num < m_num; } + + const T & dereference() const + { return *m_ptr; } + + void advance(Difference n) + { m_num -= n; } + + Difference distance_to(const this_type &other)const + { return m_num - other.m_num; } +}; + +template +class value_init_construct_iterator + : public ::boost::container::iterator + +{ + typedef value_init_construct_iterator this_type; + + public: + explicit value_init_construct_iterator(Difference range_size) + : m_num(range_size){} + + //Constructors + value_init_construct_iterator() + : m_num(0){} + + value_init_construct_iterator& operator++() + { increment(); return *this; } + + value_init_construct_iterator operator++(int) + { + value_init_construct_iterator result (*this); + increment(); + return result; + } + + value_init_construct_iterator& operator--() + { decrement(); return *this; } + + value_init_construct_iterator operator--(int) + { + value_init_construct_iterator result (*this); + decrement(); + return result; + } + + friend bool operator== (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return i.equal(i2); } + + friend bool operator!= (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return !(i == i2); } + + friend bool operator< (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return i.less(i2); } + + friend bool operator> (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return i2 < i; } + + friend bool operator<= (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return !(i > i2); } + + friend bool operator>= (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return !(i < i2); } + + friend Difference operator- (const value_init_construct_iterator& i, const value_init_construct_iterator& i2) + { return i2.distance_to(i); } + + //Arithmetic + value_init_construct_iterator& operator+=(Difference off) + { this->advance(off); return *this; } + + value_init_construct_iterator operator+(Difference off) const + { + value_init_construct_iterator other(*this); + other.advance(off); + return other; + } + + friend value_init_construct_iterator operator+(Difference off, const value_init_construct_iterator& right) + { return right + off; } + + value_init_construct_iterator& operator-=(Difference off) + { this->advance(-off); return *this; } + + value_init_construct_iterator operator-(Difference off) const + { return *this + (-off); } + + //This pseudo-iterator's dereference operations have no sense since value is not + //constructed until ::boost::container::construct_in_place is called. + //So comment them to catch bad uses + //const T& operator*() const; + //const T& operator[](difference_type) const; + //const T* operator->() const; + + private: + Difference m_num; + + void increment() + { --m_num; } + + void decrement() + { ++m_num; } + + bool equal(const this_type &other) const + { return m_num == other.m_num; } + + bool less(const this_type &other) const + { return other.m_num < m_num; } + + const T & dereference() const + { + static T dummy; + return dummy; + } + + void advance(Difference n) + { m_num -= n; } + + Difference distance_to(const this_type &other)const + { return m_num - other.m_num; } +}; + +template +class default_init_construct_iterator + : public ::boost::container::iterator + +{ + typedef default_init_construct_iterator this_type; + + public: + explicit default_init_construct_iterator(Difference range_size) + : m_num(range_size){} + + //Constructors + default_init_construct_iterator() + : m_num(0){} + + default_init_construct_iterator& operator++() + { increment(); return *this; } + + default_init_construct_iterator operator++(int) + { + default_init_construct_iterator result (*this); + increment(); + return result; + } + + default_init_construct_iterator& operator--() + { decrement(); return *this; } + + default_init_construct_iterator operator--(int) + { + default_init_construct_iterator result (*this); + decrement(); + return result; + } + + friend bool operator== (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return i.equal(i2); } + + friend bool operator!= (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return !(i == i2); } + + friend bool operator< (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return i.less(i2); } + + friend bool operator> (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return i2 < i; } + + friend bool operator<= (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return !(i > i2); } + + friend bool operator>= (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return !(i < i2); } + + friend Difference operator- (const default_init_construct_iterator& i, const default_init_construct_iterator& i2) + { return i2.distance_to(i); } + + //Arithmetic + default_init_construct_iterator& operator+=(Difference off) + { this->advance(off); return *this; } + + default_init_construct_iterator operator+(Difference off) const + { + default_init_construct_iterator other(*this); + other.advance(off); + return other; + } + + friend default_init_construct_iterator operator+(Difference off, const default_init_construct_iterator& right) + { return right + off; } + + default_init_construct_iterator& operator-=(Difference off) + { this->advance(-off); return *this; } + + default_init_construct_iterator operator-(Difference off) const + { return *this + (-off); } + + //This pseudo-iterator's dereference operations have no sense since value is not + //constructed until ::boost::container::construct_in_place is called. + //So comment them to catch bad uses + //const T& operator*() const; + //const T& operator[](difference_type) const; + //const T* operator->() const; + + private: + Difference m_num; + + void increment() + { --m_num; } + + void decrement() + { ++m_num; } + + bool equal(const this_type &other) const + { return m_num == other.m_num; } + + bool less(const this_type &other) const + { return other.m_num < m_num; } + + const T & dereference() const + { + static T dummy; + return dummy; + } + + void advance(Difference n) + { m_num -= n; } + + Difference distance_to(const this_type &other)const + { return m_num - other.m_num; } +}; + + +template +class repeat_iterator + : public ::boost::container::iterator + +{ + typedef repeat_iterator this_type; + public: + explicit repeat_iterator(T &ref, Difference range_size) + : m_ptr(&ref), m_num(range_size){} + + //Constructors + repeat_iterator() + : m_ptr(0), m_num(0){} + + this_type& operator++() + { increment(); return *this; } + + this_type operator++(int) + { + this_type result (*this); + increment(); + return result; + } + + this_type& operator--() + { increment(); return *this; } + + this_type operator--(int) + { + this_type result (*this); + increment(); + return result; + } + + friend bool operator== (const this_type& i, const this_type& i2) + { return i.equal(i2); } + + friend bool operator!= (const this_type& i, const this_type& i2) + { return !(i == i2); } + + friend bool operator< (const this_type& i, const this_type& i2) + { return i.less(i2); } + + friend bool operator> (const this_type& i, const this_type& i2) + { return i2 < i; } + + friend bool operator<= (const this_type& i, const this_type& i2) + { return !(i > i2); } + + friend bool operator>= (const this_type& i, const this_type& i2) + { return !(i < i2); } + + friend Difference operator- (const this_type& i, const this_type& i2) + { return i2.distance_to(i); } + + //Arithmetic + this_type& operator+=(Difference off) + { this->advance(off); return *this; } + + this_type operator+(Difference off) const + { + this_type other(*this); + other.advance(off); + return other; + } + + friend this_type operator+(Difference off, const this_type& right) + { return right + off; } + + this_type& operator-=(Difference off) + { this->advance(-off); return *this; } + + this_type operator-(Difference off) const + { return *this + (-off); } + + T& operator*() const + { return dereference(); } + + T& operator[] (Difference ) const + { return dereference(); } + + T *operator->() const + { return &(dereference()); } + + private: + T * m_ptr; + Difference m_num; + + void increment() + { --m_num; } + + void decrement() + { ++m_num; } + + bool equal(const this_type &other) const + { return m_num == other.m_num; } + + bool less(const this_type &other) const + { return other.m_num < m_num; } + + T & dereference() const + { return *m_ptr; } + + void advance(Difference n) + { m_num -= n; } + + Difference distance_to(const this_type &other)const + { return m_num - other.m_num; } +}; + +template +class emplace_iterator + : public ::boost::container::iterator + +{ + typedef emplace_iterator this_type; + + public: + typedef Difference difference_type; + BOOST_CONTAINER_FORCEINLINE explicit emplace_iterator(EmplaceFunctor&e) + : m_num(1), m_pe(&e){} + + BOOST_CONTAINER_FORCEINLINE emplace_iterator() + : m_num(0), m_pe(0){} + + BOOST_CONTAINER_FORCEINLINE this_type& operator++() + { increment(); return *this; } + + this_type operator++(int) + { + this_type result (*this); + increment(); + return result; + } + + BOOST_CONTAINER_FORCEINLINE this_type& operator--() + { decrement(); return *this; } + + this_type operator--(int) + { + this_type result (*this); + decrement(); + return result; + } + + BOOST_CONTAINER_FORCEINLINE friend bool operator== (const this_type& i, const this_type& i2) + { return i.equal(i2); } + + BOOST_CONTAINER_FORCEINLINE friend bool operator!= (const this_type& i, const this_type& i2) + { return !(i == i2); } + + BOOST_CONTAINER_FORCEINLINE friend bool operator< (const this_type& i, const this_type& i2) + { return i.less(i2); } + + BOOST_CONTAINER_FORCEINLINE friend bool operator> (const this_type& i, const this_type& i2) + { return i2 < i; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator<= (const this_type& i, const this_type& i2) + { return !(i > i2); } + + BOOST_CONTAINER_FORCEINLINE friend bool operator>= (const this_type& i, const this_type& i2) + { return !(i < i2); } + + BOOST_CONTAINER_FORCEINLINE friend difference_type operator- (const this_type& i, const this_type& i2) + { return i2.distance_to(i); } + + //Arithmetic + BOOST_CONTAINER_FORCEINLINE this_type& operator+=(difference_type off) + { this->advance(off); return *this; } + + this_type operator+(difference_type off) const + { + this_type other(*this); + other.advance(off); + return other; + } + + BOOST_CONTAINER_FORCEINLINE friend this_type operator+(difference_type off, const this_type& right) + { return right + off; } + + BOOST_CONTAINER_FORCEINLINE this_type& operator-=(difference_type off) + { this->advance(-off); return *this; } + + BOOST_CONTAINER_FORCEINLINE this_type operator-(difference_type off) const + { return *this + (-off); } + + //This pseudo-iterator's dereference operations have no sense since value is not + //constructed until ::boost::container::construct_in_place is called. + //So comment them to catch bad uses + //const T& operator*() const; + //const T& operator[](difference_type) const; + //const T* operator->() const; + + template + void construct_in_place(Allocator &a, T* ptr) + { (*m_pe)(a, ptr); } + + private: + difference_type m_num; + EmplaceFunctor * m_pe; + + BOOST_CONTAINER_FORCEINLINE void increment() + { --m_num; } + + BOOST_CONTAINER_FORCEINLINE void decrement() + { ++m_num; } + + BOOST_CONTAINER_FORCEINLINE bool equal(const this_type &other) const + { return m_num == other.m_num; } + + BOOST_CONTAINER_FORCEINLINE bool less(const this_type &other) const + { return other.m_num < m_num; } + + BOOST_CONTAINER_FORCEINLINE const T & dereference() const + { + static T dummy; + return dummy; + } + + BOOST_CONTAINER_FORCEINLINE void advance(difference_type n) + { m_num -= n; } + + BOOST_CONTAINER_FORCEINLINE difference_type distance_to(const this_type &other)const + { return difference_type(m_num - other.m_num); } +}; + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template +struct emplace_functor +{ + typedef typename container_detail::build_number_seq::type index_tuple_t; + + emplace_functor(BOOST_FWD_REF(Args)... args) + : args_(args...) + {} + + template + void operator()(Allocator &a, T *ptr) + { emplace_functor::inplace_impl(a, ptr, index_tuple_t()); } + + template + BOOST_CONTAINER_FORCEINLINE void inplace_impl(Allocator &a, T* ptr, const container_detail::index_tuple&) + { + allocator_traits::construct + (a, ptr, ::boost::forward(container_detail::get(args_))...); + } + + container_detail::tuple args_; +}; + +#else // !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#define BOOST_MOVE_ITERATOR_EMPLACE_FUNCTOR_CODE(N) \ +BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ +struct emplace_functor##N\ +{\ + explicit emplace_functor##N( BOOST_MOVE_UREF##N )\ + BOOST_MOVE_COLON##N BOOST_MOVE_FWD_INIT##N{}\ + \ + template\ + void operator()(Allocator &a, T *ptr)\ + { allocator_traits::construct(a, ptr BOOST_MOVE_I##N BOOST_MOVE_MFWD##N); }\ + \ + BOOST_MOVE_MREF##N\ +};\ +// +BOOST_MOVE_ITERATE_0TO9(BOOST_MOVE_ITERATOR_EMPLACE_FUNCTOR_CODE) +#undef BOOST_MOVE_ITERATOR_EMPLACE_FUNCTOR_CODE + +#endif + +namespace container_detail { + +template +struct has_iterator_category +{ + struct two { char _[2]; }; + + template + static char test(int, typename X::iterator_category*); + + template + static two test(int, ...); + + static const bool value = (1 == sizeof(test(0, 0))); +}; + + +template::value > +struct is_input_iterator +{ + static const bool value = is_same::value; +}; + +template +struct is_input_iterator +{ + static const bool value = false; +}; + +template +struct is_not_input_iterator +{ + static const bool value = !is_input_iterator::value; +}; + +template::value > +struct is_forward_iterator +{ + static const bool value = is_same::value; +}; + +template +struct is_forward_iterator +{ + static const bool value = false; +}; + +template::value > +struct is_bidirectional_iterator +{ + static const bool value = is_same::value; +}; + +template +struct is_bidirectional_iterator +{ + static const bool value = false; +}; + +template +struct iiterator_node_value_type { + typedef typename IINodeType::value_type type; +}; + +template +struct iiterator_types +{ + typedef typename IIterator::value_type it_value_type; + typedef typename iiterator_node_value_type::type value_type; + typedef typename boost::container::iterator_traits::pointer it_pointer; + typedef typename boost::container::iterator_traits::difference_type difference_type; + typedef typename ::boost::intrusive::pointer_traits:: + template rebind_pointer::type pointer; + typedef typename ::boost::intrusive::pointer_traits:: + template rebind_pointer::type const_pointer; + typedef typename ::boost::intrusive:: + pointer_traits::reference reference; + typedef typename ::boost::intrusive:: + pointer_traits::reference const_reference; + typedef typename IIterator::iterator_category iterator_category; +}; + +template +struct iterator_types +{ + typedef typename ::boost::container::iterator + < typename iiterator_types::iterator_category + , typename iiterator_types::value_type + , typename iiterator_types::difference_type + , typename iiterator_types::const_pointer + , typename iiterator_types::const_reference> type; +}; + +template +struct iterator_types +{ + typedef typename ::boost::container::iterator + < typename iiterator_types::iterator_category + , typename iiterator_types::value_type + , typename iiterator_types::difference_type + , typename iiterator_types::pointer + , typename iiterator_types::reference> type; +}; + +template +class iterator_from_iiterator +{ + typedef typename iterator_types::type types_t; + + public: + typedef typename types_t::pointer pointer; + typedef typename types_t::reference reference; + typedef typename types_t::difference_type difference_type; + typedef typename types_t::iterator_category iterator_category; + typedef typename types_t::value_type value_type; + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator() + {} + + BOOST_CONTAINER_FORCEINLINE explicit iterator_from_iiterator(IIterator iit) BOOST_NOEXCEPT_OR_NOTHROW + : m_iit(iit) + {} + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator(iterator_from_iiterator const& other) BOOST_NOEXCEPT_OR_NOTHROW + : m_iit(other.get()) + {} + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator& operator++() BOOST_NOEXCEPT_OR_NOTHROW + { ++this->m_iit; return *this; } + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator operator++(int) BOOST_NOEXCEPT_OR_NOTHROW + { + iterator_from_iiterator result (*this); + ++this->m_iit; + return result; + } + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator& operator--() BOOST_NOEXCEPT_OR_NOTHROW + { + //If the iterator_from_iiterator is not a bidirectional iterator, operator-- should not exist + BOOST_STATIC_ASSERT((is_bidirectional_iterator::value)); + --this->m_iit; return *this; + } + + BOOST_CONTAINER_FORCEINLINE iterator_from_iiterator operator--(int) BOOST_NOEXCEPT_OR_NOTHROW + { + iterator_from_iiterator result (*this); + --this->m_iit; + return result; + } + + BOOST_CONTAINER_FORCEINLINE friend bool operator== (const iterator_from_iiterator& l, const iterator_from_iiterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_iit == r.m_iit; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator!= (const iterator_from_iiterator& l, const iterator_from_iiterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return !(l == r); } + + BOOST_CONTAINER_FORCEINLINE reference operator*() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_iit->get_data(); } + + BOOST_CONTAINER_FORCEINLINE pointer operator->() const BOOST_NOEXCEPT_OR_NOTHROW + { return ::boost::intrusive::pointer_traits::pointer_to(this->operator*()); } + + BOOST_CONTAINER_FORCEINLINE const IIterator &get() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_iit; } + + private: + IIterator m_iit; +}; + +} //namespace container_detail { + +using ::boost::intrusive::reverse_iterator; + +} //namespace container { +} //namespace boost { + +#include + +#endif //#ifndef BOOST_CONTAINER_DETAIL_ITERATORS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/min_max.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/min_max.hpp new file mode 100644 index 0000000..7486db7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/min_max.hpp @@ -0,0 +1,37 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_MIN_MAX_HPP +#define BOOST_CONTAINER_DETAIL_MIN_MAX_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +namespace boost { +namespace container { +namespace container_detail { + +template +const T &max_value(const T &a, const T &b) +{ return a > b ? a : b; } + +template +const T &min_value(const T &a, const T &b) +{ return a < b ? a : b; } + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_MIN_MAX_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/mpl.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/mpl.hpp new file mode 100644 index 0000000..e1684ea --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/mpl.hpp @@ -0,0 +1,87 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_MPL_HPP +#define BOOST_CONTAINER_CONTAINER_DETAIL_MPL_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include + +#include + +namespace boost { +namespace container { +namespace container_detail { + +using boost::move_detail::integral_constant; +using boost::move_detail::true_type; +using boost::move_detail::false_type; +using boost::move_detail::enable_if_c; +using boost::move_detail::enable_if; +using boost::move_detail::enable_if_convertible; +using boost::move_detail::disable_if_c; +using boost::move_detail::disable_if; +using boost::move_detail::disable_if_convertible; +using boost::move_detail::is_convertible; +using boost::move_detail::if_c; +using boost::move_detail::if_; +using boost::move_detail::identity; +using boost::move_detail::bool_; +using boost::move_detail::true_; +using boost::move_detail::false_; +using boost::move_detail::yes_type; +using boost::move_detail::no_type; +using boost::move_detail::bool_; +using boost::move_detail::true_; +using boost::move_detail::false_; +using boost::move_detail::unvoid_ref; +using boost::move_detail::and_; +using boost::move_detail::or_; +using boost::move_detail::not_; +using boost::move_detail::enable_if_and; +using boost::move_detail::disable_if_and; +using boost::move_detail::enable_if_or; +using boost::move_detail::disable_if_or; + + +template +struct select1st +{ + typedef Pair argument_type; + typedef typename Pair::first_type result_type; + + template + const typename Pair::first_type& operator()(const OtherPair& x) const + { return x.first; } + + const typename Pair::first_type& operator()(const typename Pair::first_type& x) const + { return x; } +}; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#include + +#endif //#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_MPL_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/next_capacity.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/next_capacity.hpp new file mode 100644 index 0000000..3bc98a3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/next_capacity.hpp @@ -0,0 +1,75 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_NEXT_CAPACITY_HPP +#define BOOST_CONTAINER_DETAIL_NEXT_CAPACITY_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +// container +#include +// container/detail +#include + +namespace boost { +namespace container { +namespace container_detail { + +enum NextCapacityOption { NextCapacityDouble, NextCapacity60Percent }; + +template +struct next_capacity_calculator; + +template +struct next_capacity_calculator +{ + static SizeType get(const SizeType max_size + ,const SizeType capacity + ,const SizeType n) + { + const SizeType remaining = max_size - capacity; + if ( remaining < n ) + boost::container::throw_length_error("get_next_capacity, allocator's max_size reached"); + const SizeType additional = max_value(n, capacity); + return ( remaining < additional ) ? max_size : ( capacity + additional ); + } +}; + +template +struct next_capacity_calculator +{ + static SizeType get(const SizeType max_size + ,const SizeType capacity + ,const SizeType n) + { + const SizeType remaining = max_size - capacity; + if ( remaining < n ) + boost::container::throw_length_error("get_next_capacity, allocator's max_size reached"); + const SizeType m3 = max_size/3; + + if (capacity < m3) + return capacity + max_value(3*(capacity+1)/5, n); + + if (capacity < m3*2) + return capacity + max_value((capacity+1)/2, n); + return max_size; + } +}; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_NEXT_CAPACITY_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/pair.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/pair.hpp new file mode 100644 index 0000000..134760e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/pair.hpp @@ -0,0 +1,337 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_PAIR_HPP +#define BOOST_CONTAINER_CONTAINER_DETAIL_PAIR_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +#include +#include +#include +#include +#include //swap + +#include //pair +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +struct pair; + +template +struct is_pair +{ + static const bool value = false; +}; + +template +struct is_pair< pair > +{ + static const bool value = true; +}; + +template +struct is_pair< std::pair > +{ + static const bool value = true; +}; + +template +struct is_not_pair +{ + static const bool value = !is_pair::value; +}; + +template +struct is_std_pair +{ + static const bool value = false; +}; + +template +struct is_std_pair< std::pair > +{ + static const bool value = true; +}; + +struct pair_nat; + +template +struct pair +{ + private: + BOOST_COPYABLE_AND_MOVABLE(pair) + + public: + typedef T1 first_type; + typedef T2 second_type; + + T1 first; + T2 second; + + //Default constructor + pair() + : first(), second() + {} + + //pair copy assignment + pair(const pair& x) + : first(x.first), second(x.second) + {} + + //pair move constructor + pair(BOOST_RV_REF(pair) p) + : first(::boost::move(p.first)), second(::boost::move(p.second)) + {} + + template + pair(const pair &p) + : first(p.first), second(p.second) + {} + + template + pair(BOOST_RV_REF_BEG pair BOOST_RV_REF_END p) + : first(::boost::move(p.first)), second(::boost::move(p.second)) + {} + + //pair from two values + pair(const T1 &t1, const T2 &t2) + : first(t1) + , second(t2) + {} + + template + pair(BOOST_FWD_REF(U) u, BOOST_FWD_REF(V) v) + : first(::boost::forward(u)) + , second(::boost::forward(v)) + {} + + //And now compatibility with std::pair + pair(const std::pair& x) + : first(x.first), second(x.second) + {} + + template + pair(const std::pair& p) + : first(p.first), second(p.second) + {} + + pair(BOOST_RV_REF_BEG std::pair BOOST_RV_REF_END p) + : first(::boost::move(p.first)), second(::boost::move(p.second)) + {} + + template + pair(BOOST_RV_REF_BEG std::pair BOOST_RV_REF_END p) + : first(::boost::move(p.first)), second(::boost::move(p.second)) + {} + + //piecewise_construct missing + //template pair(pair&& p); + //template + // pair(piecewise_construct_t, tuple first_args, + // tuple second_args); + + //pair copy assignment + pair& operator=(BOOST_COPY_ASSIGN_REF(pair) p) + { + first = p.first; + second = p.second; + return *this; + } + + //pair move assignment + pair& operator=(BOOST_RV_REF(pair) p) + { + first = ::boost::move(p.first); + second = ::boost::move(p.second); + return *this; + } + + template + typename ::boost::container::container_detail::disable_if_or + < pair & + , ::boost::container::container_detail::is_same + , ::boost::container::container_detail::is_same + >::type + operator=(const pair&p) + { + first = p.first; + second = p.second; + return *this; + } + + template + typename ::boost::container::container_detail::disable_if_or + < pair & + , ::boost::container::container_detail::is_same + , ::boost::container::container_detail::is_same + >::type + operator=(BOOST_RV_REF_BEG pair BOOST_RV_REF_END p) + { + first = ::boost::move(p.first); + second = ::boost::move(p.second); + return *this; + } +//std::pair copy assignment + pair& operator=(const std::pair &p) + { + first = p.first; + second = p.second; + return *this; + } + + template + pair& operator=(const std::pair &p) + { + first = ::boost::move(p.first); + second = ::boost::move(p.second); + return *this; + } + + //std::pair move assignment + pair& operator=(BOOST_RV_REF_BEG std::pair BOOST_RV_REF_END p) + { + first = ::boost::move(p.first); + second = ::boost::move(p.second); + return *this; + } + + template + pair& operator=(BOOST_RV_REF_BEG std::pair BOOST_RV_REF_END p) + { + first = ::boost::move(p.first); + second = ::boost::move(p.second); + return *this; + } + + //swap + void swap(pair& p) + { + ::boost::adl_move_swap(this->first, p.first); + ::boost::adl_move_swap(this->second, p.second); + } +}; + +template +inline bool operator==(const pair& x, const pair& y) +{ return static_cast(x.first == y.first && x.second == y.second); } + +template +inline bool operator< (const pair& x, const pair& y) +{ return static_cast(x.first < y.first || + (!(y.first < x.first) && x.second < y.second)); } + +template +inline bool operator!=(const pair& x, const pair& y) +{ return static_cast(!(x == y)); } + +template +inline bool operator> (const pair& x, const pair& y) +{ return y < x; } + +template +inline bool operator>=(const pair& x, const pair& y) +{ return static_cast(!(x < y)); } + +template +inline bool operator<=(const pair& x, const pair& y) +{ return static_cast(!(y < x)); } + +template +inline pair make_pair(T1 x, T2 y) +{ return pair(x, y); } + +template +inline void swap(pair& x, pair& y) +{ x.swap(y); } + +} //namespace container_detail { +} //namespace container { + + +//Without this specialization recursive flat_(multi)map instantiation fails +//because is_enum needs to instantiate the recursive pair, leading to a compilation error). +//This breaks the cycle clearly stating that pair is not an enum avoiding any instantiation. +template +struct is_enum; + +template +struct is_enum< ::boost::container::container_detail::pair > +{ + static const bool value = false; +}; + +template +struct is_class; + +//This specialization is needed to avoid instantiation of pair in +//is_class, and allow recursive maps. +template +struct is_class< ::boost::container::container_detail::pair > +{ + static const bool value = true; +}; + +#ifdef BOOST_NO_CXX11_RVALUE_REFERENCES + +template +struct has_move_emulation_enabled< ::boost::container::container_detail::pair > +{ + static const bool value = true; +}; + +#endif + +namespace move_detail{ + +template +struct is_class_or_union; + +template +struct is_class_or_union< ::boost::container::container_detail::pair > +//This specialization is needed to avoid instantiation of pair in +//is_class, and allow recursive maps. +{ + static const bool value = true; +}; + +template +struct is_class_or_union< std::pair > +//This specialization is needed to avoid instantiation of pair in +//is_class, and allow recursive maps. +{ + static const bool value = true; +}; + + + + +} //namespace move_detail{ + +} //namespace boost { + +#include + +#endif //#ifndef BOOST_CONTAINER_DETAIL_PAIR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/placement_new.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/placement_new.hpp new file mode 100644 index 0000000..c50981f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/placement_new.hpp @@ -0,0 +1,30 @@ +#ifndef BOOST_CONTAINER_DETAIL_PLACEMENT_NEW_HPP +#define BOOST_CONTAINER_DETAIL_PLACEMENT_NEW_HPP +/////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +struct boost_container_new_t{}; + +//avoid including +inline void *operator new(std::size_t, void *p, boost_container_new_t) +{ return p; } + +inline void operator delete(void *, void *, boost_container_new_t) +{} + +#endif //BOOST_CONTAINER_DETAIL_PLACEMENT_NEW_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/std_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/std_fwd.hpp new file mode 100644 index 0000000..0967812 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/std_fwd.hpp @@ -0,0 +1,56 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_STD_FWD_HPP +#define BOOST_CONTAINER_DETAIL_STD_FWD_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +////////////////////////////////////////////////////////////////////////////// +// Standard predeclarations +////////////////////////////////////////////////////////////////////////////// + +#include +BOOST_MOVE_STD_NS_BEG + +template +class allocator; + +template +struct less; + +template +struct pair; + +template +struct char_traits; + +struct input_iterator_tag; +struct forward_iterator_tag; +struct bidirectional_iterator_tag; +struct random_access_iterator_tag; + +template +class insert_iterator; + +struct allocator_arg_t; + +struct piecewise_construct_t; + +BOOST_MOVE_STD_NS_END +#include + +#endif //#ifndef BOOST_CONTAINER_DETAIL_STD_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/to_raw_pointer.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/to_raw_pointer.hpp new file mode 100644 index 0000000..0b4445a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/to_raw_pointer.hpp @@ -0,0 +1,33 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_DETAIL_TO_RAW_POINTER_HPP +#define BOOST_CONTAINER_DETAIL_TO_RAW_POINTER_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include + +namespace boost { +namespace container { +namespace container_detail { + +using ::boost::intrusive::detail::to_raw_pointer; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_DETAIL_TO_RAW_POINTER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/type_traits.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/type_traits.hpp new file mode 100644 index 0000000..e1453a6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/type_traits.hpp @@ -0,0 +1,70 @@ +////////////////////////////////////////////////////////////////////////////// +// (C) Copyright John Maddock 2000. +// (C) Copyright Ion Gaztanaga 2005-2015. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +// The alignment and Type traits implementation comes from +// John Maddock's TypeTraits library. +// +// Some other tricks come from Howard Hinnant's papers and StackOverflow replies +////////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_TYPE_TRAITS_HPP +#define BOOST_CONTAINER_CONTAINER_DETAIL_TYPE_TRAITS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include + +namespace boost { +namespace container { +namespace container_detail { + +using ::boost::move_detail::enable_if; +using ::boost::move_detail::enable_if_and; +using ::boost::move_detail::is_same; +using ::boost::move_detail::is_different; +using ::boost::move_detail::is_pointer; +using ::boost::move_detail::add_reference; +using ::boost::move_detail::add_const; +using ::boost::move_detail::add_const_reference; +using ::boost::move_detail::remove_const; +using ::boost::move_detail::remove_reference; +using ::boost::move_detail::make_unsigned; +using ::boost::move_detail::is_floating_point; +using ::boost::move_detail::is_integral; +using ::boost::move_detail::is_enum; +using ::boost::move_detail::is_pod; +using ::boost::move_detail::is_empty; +using ::boost::move_detail::is_trivially_destructible; +using ::boost::move_detail::is_trivially_default_constructible; +using ::boost::move_detail::is_trivially_copy_constructible; +using ::boost::move_detail::is_trivially_move_constructible; +using ::boost::move_detail::is_trivially_copy_assignable; +using ::boost::move_detail::is_trivially_move_assignable; +using ::boost::move_detail::is_nothrow_default_constructible; +using ::boost::move_detail::is_nothrow_copy_constructible; +using ::boost::move_detail::is_nothrow_move_constructible; +using ::boost::move_detail::is_nothrow_copy_assignable; +using ::boost::move_detail::is_nothrow_move_assignable; +using ::boost::move_detail::is_nothrow_swappable; +using ::boost::move_detail::alignment_of; +using ::boost::move_detail::aligned_storage; +using ::boost::move_detail::nat; +using ::boost::move_detail::max_align_t; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#endif //#ifndef BOOST_CONTAINER_CONTAINER_DETAIL_TYPE_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/value_init.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/value_init.hpp new file mode 100644 index 0000000..eb4c976 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/value_init.hpp @@ -0,0 +1,49 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_VALUE_INIT_HPP +#define BOOST_CONTAINER_DETAIL_VALUE_INIT_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +namespace boost { +namespace container { +namespace container_detail { + +template +struct value_init +{ + value_init() + : m_t() + {} + + operator T &() { return m_t; } + + T m_t; +}; + +} //namespace container_detail { +} //namespace container { +} //namespace boost { + +#include + +#endif //#ifndef BOOST_CONTAINER_DETAIL_VALUE_INIT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/variadic_templates_tools.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/variadic_templates_tools.hpp new file mode 100644 index 0000000..d8c8443 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/variadic_templates_tools.hpp @@ -0,0 +1,158 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2008-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_VARIADIC_TEMPLATES_TOOLS_HPP +#define BOOST_CONTAINER_DETAIL_VARIADIC_TEMPLATES_TOOLS_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +#include +#include //std::size_t + +namespace boost { +namespace container { +namespace container_detail { + +template +class tuple; + +template<> class tuple<> +{}; + +template +class tuple + : private tuple +{ + typedef tuple inherited; + + public: + tuple() { } + + // implicit copy-constructor is okay + // Construct tuple from separate arguments. + tuple(typename add_const_reference::type v, + typename add_const_reference::type... vtail) + : inherited(vtail...), m_head(v) + {} + + // Construct tuple from another tuple. + template + tuple(const tuple& other) + : inherited(other.tail()), m_head(other.head()) + {} + + template + tuple& operator=(const tuple& other) + { + m_head = other.head(); + tail() = other.tail(); + return this; + } + + typename add_reference::type head() { return m_head; } + typename add_reference::type head() const { return m_head; } + + inherited& tail() { return *this; } + const inherited& tail() const { return *this; } + + protected: + Head m_head; +}; + + +template +tuple tie_forward(Values&&... values) +{ return tuple(values...); } + +template +struct tuple_element; + +template +struct tuple_element > +{ + typedef typename tuple_element >::type type; +}; + +template +struct tuple_element<0, tuple > +{ + typedef Head type; +}; + +template +class get_impl; + +template +class get_impl > +{ + typedef typename tuple_element >::type Element; + typedef get_impl > Next; + + public: + typedef typename add_reference::type type; + typedef typename add_const_reference::type const_type; + static type get(tuple& t) { return Next::get(t.tail()); } + static const_type get(const tuple& t) { return Next::get(t.tail()); } +}; + +template +class get_impl<0, tuple > +{ + public: + typedef typename add_reference::type type; + typedef typename add_const_reference::type const_type; + static type get(tuple& t) { return t.head(); } + static const_type get(const tuple& t){ return t.head(); } +}; + +template +typename get_impl >::type get(tuple& t) +{ return get_impl >::get(t); } + +template +typename get_impl >::const_type get(const tuple& t) +{ return get_impl >::get(t); } + +//////////////////////////////////////////////////// +// Builds an index_tuple<0, 1, 2, ..., Num-1>, that will +// be used to "unpack" into comma-separated values +// in a function call. +//////////////////////////////////////////////////// + +template +struct index_tuple{}; + +template > +struct build_number_seq; + +template +struct build_number_seq > + : build_number_seq > +{}; + +template +struct build_number_seq<0, index_tuple > +{ typedef index_tuple type; }; + + +}}} //namespace boost { namespace container { namespace container_detail { + +#include + +#endif //#ifndef BOOST_CONTAINER_DETAIL_VARIADIC_TEMPLATES_TOOLS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/version_type.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/version_type.hpp new file mode 100644 index 0000000..a20b3ee --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/version_type.hpp @@ -0,0 +1,110 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// +// +// This code comes from N1953 document by Howard E. Hinnant +// +////////////////////////////////////////////////////////////////////////////// + + +#ifndef BOOST_CONTAINER_DETAIL_VERSION_TYPE_HPP +#define BOOST_CONTAINER_DETAIL_VERSION_TYPE_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +#include +#include + +namespace boost{ +namespace container { +namespace container_detail { + +template +struct version_type + : public container_detail::integral_constant +{ + typedef T type; + + version_type(const version_type&); +}; + +namespace impl{ + +template , typename T::version>::value> +struct extract_version +{ + static const unsigned value = 1; +}; + +template +struct extract_version +{ + static const unsigned value = T::version::value; +}; + +template +struct has_version +{ + private: + struct two {char _[2];}; + template static two test(...); + template static char test(const typename U::version*); + public: + static const bool value = sizeof(test(0)) == 1; + void dummy(){} +}; + +template ::value> +struct version +{ + static const unsigned value = 1; +}; + +template +struct version +{ + static const unsigned value = extract_version::value; +}; + +} //namespace impl + +template +struct version + : public container_detail::integral_constant::value> +{}; + +template +struct is_version +{ + static const bool value = + is_same< typename version::type, integral_constant >::value; +}; + +} //namespace container_detail { + +typedef container_detail::integral_constant version_0; +typedef container_detail::integral_constant version_1; +typedef container_detail::integral_constant version_2; + +} //namespace container { +} //namespace boost{ + +#include + +#endif //#define BOOST_CONTAINER_DETAIL_VERSION_TYPE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/detail/workaround.hpp b/thirdparty/source/boost_1_61_0/boost/container/detail/workaround.hpp new file mode 100644 index 0000000..ae9151c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/detail/workaround.hpp @@ -0,0 +1,92 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_DETAIL_WORKAROUND_HPP +#define BOOST_CONTAINER_DETAIL_WORKAROUND_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) && !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES)\ + && !defined(BOOST_INTERPROCESS_DISABLE_VARIADIC_TMPL) + #define BOOST_CONTAINER_PERFECT_FORWARDING +#endif + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && defined(__GXX_EXPERIMENTAL_CXX0X__)\ + && (__GNUC__*10000 + __GNUC_MINOR__*100 + __GNUC_PATCHLEVEL__ < 40700) + #define BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST +#endif + +#if !defined(BOOST_FALLTHOUGH) + #define BOOST_CONTAINER_FALLTHOUGH +#else + #define BOOST_CONTAINER_FALLTHOUGH BOOST_FALLTHOUGH; +#endif + +//Macros for documentation purposes. For code, expands to the argument +#define BOOST_CONTAINER_IMPDEF(TYPE) TYPE +#define BOOST_CONTAINER_SEEDOC(TYPE) TYPE + +//Macros for memset optimization. In most platforms +//memsetting pointers and floatings is safe and faster. +// +//If your platform does not offer these guarantees +//define these to value zero. +#ifndef BOOST_CONTAINER_MEMZEROED_FLOATING_POINT_IS_NOT_ZERO +#define BOOST_CONTAINER_MEMZEROED_FLOATING_POINT_IS_ZERO 1 +#endif + +#ifndef BOOST_CONTAINER_MEMZEROED_POINTER_IS_NOT_NULL +#define BOOST_CONTAINER_MEMZEROED_POINTER_IS_NULL +#endif + +#define BOOST_CONTAINER_DOC1ST(TYPE1, TYPE2) TYPE2 +#define BOOST_CONTAINER_I , +#define BOOST_CONTAINER_DOCIGN(T) T +#define BOOST_CONTAINER_DOCONLY(T) + +/* + we need to import/export our code only if the user has specifically + asked for it by defining either BOOST_ALL_DYN_LINK if they want all boost + libraries to be dynamically linked, or BOOST_CONTAINER_DYN_LINK + if they want just this one to be dynamically liked: +*/ +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_CONTAINER_DYN_LINK) + + /* export if this is our own source, otherwise import: */ + #ifdef BOOST_CONTAINER_SOURCE + # define BOOST_CONTAINER_DECL BOOST_SYMBOL_EXPORT + #else + # define BOOST_CONTAINER_DECL BOOST_SYMBOL_IMPORT + + #endif /* BOOST_CONTAINER_SOURCE */ +#else + #define BOOST_CONTAINER_DECL +#endif /* DYN_LINK */ + +//#define BOOST_CONTAINER_DISABLE_FORCEINLINE + +#if defined(BOOST_CONTAINER_DISABLE_FORCEINLINE) + #define BOOST_CONTAINER_FORCEINLINE inline +#elif defined(BOOST_CONTAINER_FORCEINLINE_IS_BOOST_FORCELINE) + #define BOOST_CONTAINER_FORCEINLINE BOOST_FORCEINLINE +#elif defined(BOOST_MSVC) && defined(_DEBUG) + //"__forceinline" and MSVC seems to have some bugs in debug mode + #define BOOST_CONTAINER_FORCEINLINE inline +#else + #define BOOST_CONTAINER_FORCEINLINE BOOST_FORCEINLINE +#endif + +#endif //#ifndef BOOST_CONTAINER_DETAIL_WORKAROUND_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/new_allocator.hpp b/thirdparty/source/boost_1_61_0/boost/container/new_allocator.hpp new file mode 100644 index 0000000..6a1d8c7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/new_allocator.hpp @@ -0,0 +1,179 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_NEW_ALLOCATOR_HPP +#define BOOST_CONTAINER_NEW_ALLOCATOR_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include + +//!\file + +namespace boost { +namespace container { + +/// @cond + +template +struct new_allocator_bool +{ static const bool value = Value; }; + +template +class new_allocator; + +/// @endcond + +//! Specialization of new_allocator for void types +template<> +class new_allocator +{ + public: + typedef void value_type; + typedef void * pointer; + typedef const void* const_pointer; + //!A integral constant of type bool with value true + typedef BOOST_CONTAINER_IMPDEF(new_allocator_bool) propagate_on_container_move_assignment; + //!A integral constant of type bool with value true + typedef BOOST_CONTAINER_IMPDEF(new_allocator_bool) is_always_equal; + // reference-to-void members are impossible + + //!Obtains an new_allocator that allocates + //!objects of type T2 + template + struct rebind + { + typedef new_allocator< T2> other; + }; + + //!Default constructor + //!Never throws + new_allocator() BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Constructor from other new_allocator. + //!Never throws + new_allocator(const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Constructor from related new_allocator. + //!Never throws + template + new_allocator(const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Swaps two allocators, does nothing + //!because this new_allocator is stateless + friend void swap(new_allocator &, new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!An new_allocator always compares to true, as memory allocated with one + //!instance can be deallocated by another instance + friend bool operator==(const new_allocator &, const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + { return true; } + + //!An new_allocator always compares to false, as memory allocated with one + //!instance can be deallocated by another instance + friend bool operator!=(const new_allocator &, const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + { return false; } +}; + + +//! This class is a reduced STL-compatible allocator that allocates memory using operator new +template +class new_allocator +{ + public: + typedef T value_type; + typedef T * pointer; + typedef const T * const_pointer; + typedef T & reference; + typedef const T & const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + //!A integral constant of type bool with value true + typedef BOOST_CONTAINER_IMPDEF(new_allocator_bool) propagate_on_container_move_assignment; + //!A integral constant of type bool with value true + typedef BOOST_CONTAINER_IMPDEF(new_allocator_bool) is_always_equal; + + //!Obtains an new_allocator that allocates + //!objects of type T2 + template + struct rebind + { + typedef new_allocator other; + }; + + //!Default constructor + //!Never throws + new_allocator() BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Constructor from other new_allocator. + //!Never throws + new_allocator(const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Constructor from related new_allocator. + //!Never throws + template + new_allocator(const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!Allocates memory for an array of count elements. + //!Throws std::bad_alloc if there is no enough memory + pointer allocate(size_type count) + { + if(BOOST_UNLIKELY(count > this->max_size())) + throw_bad_alloc(); + return static_cast(::operator new(count*sizeof(T))); + } + + //!Deallocates previously allocated memory. + //!Never throws + void deallocate(pointer ptr, size_type) BOOST_NOEXCEPT_OR_NOTHROW + { ::operator delete((void*)ptr); } + + //!Returns the maximum number of elements that could be allocated. + //!Never throws + size_type max_size() const BOOST_NOEXCEPT_OR_NOTHROW + { return size_type(-1)/sizeof(T); } + + //!Swaps two allocators, does nothing + //!because this new_allocator is stateless + friend void swap(new_allocator &, new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + {} + + //!An new_allocator always compares to true, as memory allocated with one + //!instance can be deallocated by another instance + friend bool operator==(const new_allocator &, const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + { return true; } + + //!An new_allocator always compares to false, as memory allocated with one + //!instance can be deallocated by another instance + friend bool operator!=(const new_allocator &, const new_allocator &) BOOST_NOEXCEPT_OR_NOTHROW + { return false; } +}; + +} //namespace container { +} //namespace boost { + +#include + +#endif //BOOST_CONTAINER_ALLOCATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator.hpp b/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator.hpp new file mode 100644 index 0000000..6a041a6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator.hpp @@ -0,0 +1,907 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Pablo Halpern 2009. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2011-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_HPP +#define BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_HPP + +#if defined (_MSC_VER) +# pragma once +#endif + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif +#include + +#include + +namespace boost { namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +namespace container_detail { + +template +struct is_scoped_allocator_imp +{ + typedef char yes_type; + struct no_type{ char dummy[2]; }; + + template + static yes_type test(typename T::outer_allocator_type*); + + template + static int test(...); + + static const bool value = (sizeof(yes_type) == sizeof(test(0))); +}; + +template::value > +struct outermost_allocator_type_impl +{ + typedef typename MaybeScopedAlloc::outer_allocator_type outer_type; + typedef typename outermost_allocator_type_impl::type type; +}; + +template +struct outermost_allocator_type_impl +{ + typedef MaybeScopedAlloc type; +}; + +template::value > +struct outermost_allocator_imp +{ + typedef MaybeScopedAlloc type; + + static type &get(MaybeScopedAlloc &a) + { return a; } + + static const type &get(const MaybeScopedAlloc &a) + { return a; } +}; + +template +struct outermost_allocator_imp +{ + typedef typename MaybeScopedAlloc::outer_allocator_type outer_type; + typedef typename outermost_allocator_type_impl::type type; + + static type &get(MaybeScopedAlloc &a) + { return outermost_allocator_imp::get(a.outer_allocator()); } + + static const type &get(const MaybeScopedAlloc &a) + { return outermost_allocator_imp::get(a.outer_allocator()); } +}; + +} //namespace container_detail { + +template +struct is_scoped_allocator + : container_detail::is_scoped_allocator_imp +{}; + +template +struct outermost_allocator + : container_detail::outermost_allocator_imp +{}; + +template +typename outermost_allocator::type & + get_outermost_allocator(Allocator &a) +{ return outermost_allocator::get(a); } + +template +const typename outermost_allocator::type & + get_outermost_allocator(const Allocator &a) +{ return outermost_allocator::get(a); } + +namespace container_detail { + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template +class scoped_allocator_adaptor_base + : public OuterAlloc +{ + typedef allocator_traits outer_traits_type; + BOOST_COPYABLE_AND_MOVABLE(scoped_allocator_adaptor_base) + + public: + template + struct rebind_base + { + typedef scoped_allocator_adaptor_base other; + }; + + typedef OuterAlloc outer_allocator_type; + typedef scoped_allocator_adaptor inner_allocator_type; + typedef allocator_traits inner_traits_type; + typedef scoped_allocator_adaptor + scoped_allocator_type; + typedef container_detail::bool_< + outer_traits_type::propagate_on_container_copy_assignment::value || + inner_allocator_type::propagate_on_container_copy_assignment::value + > propagate_on_container_copy_assignment; + typedef container_detail::bool_< + outer_traits_type::propagate_on_container_move_assignment::value || + inner_allocator_type::propagate_on_container_move_assignment::value + > propagate_on_container_move_assignment; + typedef container_detail::bool_< + outer_traits_type::propagate_on_container_swap::value || + inner_allocator_type::propagate_on_container_swap::value + > propagate_on_container_swap; + typedef container_detail::bool_< + outer_traits_type::is_always_equal::value && + inner_allocator_type::is_always_equal::value + > is_always_equal; + + scoped_allocator_adaptor_base() + {} + + template + scoped_allocator_adaptor_base(BOOST_FWD_REF(OuterA2) outerAlloc, const InnerAllocs &...args) + : outer_allocator_type(::boost::forward(outerAlloc)) + , m_inner(args...) + {} + + scoped_allocator_adaptor_base(const scoped_allocator_adaptor_base& other) + : outer_allocator_type(other.outer_allocator()) + , m_inner(other.inner_allocator()) + {} + + scoped_allocator_adaptor_base(BOOST_RV_REF(scoped_allocator_adaptor_base) other) + : outer_allocator_type(::boost::move(other.outer_allocator())) + , m_inner(::boost::move(other.inner_allocator())) + {} + + template + scoped_allocator_adaptor_base + (const scoped_allocator_adaptor_base& other) + : outer_allocator_type(other.outer_allocator()) + , m_inner(other.inner_allocator()) + {} + + template + scoped_allocator_adaptor_base + (BOOST_RV_REF_BEG scoped_allocator_adaptor_base + BOOST_RV_REF_END other) + : outer_allocator_type(other.outer_allocator()) + , m_inner(other.inner_allocator()) + {} + + public: + struct internal_type_t{}; + + template + scoped_allocator_adaptor_base + ( internal_type_t + , BOOST_FWD_REF(OuterA2) outerAlloc + , const inner_allocator_type &inner) + : outer_allocator_type(::boost::forward(outerAlloc)) + , m_inner(inner) + {} + + public: + + scoped_allocator_adaptor_base &operator= + (BOOST_COPY_ASSIGN_REF(scoped_allocator_adaptor_base) other) + { + outer_allocator_type::operator=(other.outer_allocator()); + m_inner = other.inner_allocator(); + return *this; + } + + scoped_allocator_adaptor_base &operator=(BOOST_RV_REF(scoped_allocator_adaptor_base) other) + { + outer_allocator_type::operator=(boost::move(other.outer_allocator())); + m_inner = ::boost::move(other.inner_allocator()); + return *this; + } + + void swap(scoped_allocator_adaptor_base &r) + { + boost::adl_move_swap(this->outer_allocator(), r.outer_allocator()); + boost::adl_move_swap(this->m_inner, r.inner_allocator()); + } + + friend void swap(scoped_allocator_adaptor_base &l, scoped_allocator_adaptor_base &r) + { l.swap(r); } + + inner_allocator_type& inner_allocator() BOOST_NOEXCEPT_OR_NOTHROW + { return m_inner; } + + inner_allocator_type const& inner_allocator() const BOOST_NOEXCEPT_OR_NOTHROW + { return m_inner; } + + outer_allocator_type & outer_allocator() BOOST_NOEXCEPT_OR_NOTHROW + { return static_cast(*this); } + + const outer_allocator_type &outer_allocator() const BOOST_NOEXCEPT_OR_NOTHROW + { return static_cast(*this); } + + scoped_allocator_type select_on_container_copy_construction() const + { + return scoped_allocator_type + (internal_type_t() + ,outer_traits_type::select_on_container_copy_construction(this->outer_allocator()) + ,inner_traits_type::select_on_container_copy_construction(this->inner_allocator()) + ); + } + + private: + inner_allocator_type m_inner; +}; + +#else //#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +//Let's add a dummy first template parameter to allow creating +//specializations up to maximum InnerAlloc count +template +class scoped_allocator_adaptor_base; + +//Specializations for the adaptor with InnerAlloc allocators + +#define BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_BASE_CODE(N)\ +template \ +class scoped_allocator_adaptor_base\ + : public OuterAlloc\ +{\ + typedef allocator_traits outer_traits_type;\ + BOOST_COPYABLE_AND_MOVABLE(scoped_allocator_adaptor_base)\ + \ + public:\ + template \ + struct rebind_base\ + {\ + typedef scoped_allocator_adaptor_base other;\ + };\ + \ + typedef OuterAlloc outer_allocator_type;\ + typedef scoped_allocator_adaptor inner_allocator_type;\ + typedef scoped_allocator_adaptor scoped_allocator_type;\ + typedef allocator_traits inner_traits_type;\ + typedef container_detail::bool_<\ + outer_traits_type::propagate_on_container_copy_assignment::value ||\ + inner_allocator_type::propagate_on_container_copy_assignment::value\ + > propagate_on_container_copy_assignment;\ + typedef container_detail::bool_<\ + outer_traits_type::propagate_on_container_move_assignment::value ||\ + inner_allocator_type::propagate_on_container_move_assignment::value\ + > propagate_on_container_move_assignment;\ + typedef container_detail::bool_<\ + outer_traits_type::propagate_on_container_swap::value ||\ + inner_allocator_type::propagate_on_container_swap::value\ + > propagate_on_container_swap;\ + \ + typedef container_detail::bool_<\ + outer_traits_type::is_always_equal::value &&\ + inner_allocator_type::is_always_equal::value\ + > is_always_equal;\ + \ + scoped_allocator_adaptor_base(){}\ + \ + template \ + scoped_allocator_adaptor_base(BOOST_FWD_REF(OuterA2) outerAlloc, BOOST_MOVE_CREF##N)\ + : outer_allocator_type(::boost::forward(outerAlloc))\ + , m_inner(BOOST_MOVE_ARG##N)\ + {}\ + \ + scoped_allocator_adaptor_base(const scoped_allocator_adaptor_base& other)\ + : outer_allocator_type(other.outer_allocator())\ + , m_inner(other.inner_allocator())\ + {}\ + \ + scoped_allocator_adaptor_base(BOOST_RV_REF(scoped_allocator_adaptor_base) other)\ + : outer_allocator_type(::boost::move(other.outer_allocator()))\ + , m_inner(::boost::move(other.inner_allocator()))\ + {}\ + \ + template \ + scoped_allocator_adaptor_base\ + (const scoped_allocator_adaptor_base& other)\ + : outer_allocator_type(other.outer_allocator())\ + , m_inner(other.inner_allocator())\ + {}\ + \ + template \ + scoped_allocator_adaptor_base\ + (BOOST_RV_REF_BEG scoped_allocator_adaptor_base BOOST_RV_REF_END other)\ + : outer_allocator_type(other.outer_allocator())\ + , m_inner(other.inner_allocator())\ + {}\ + \ + public:\ + struct internal_type_t{};\ + \ + template \ + scoped_allocator_adaptor_base\ + ( internal_type_t, BOOST_FWD_REF(OuterA2) outerAlloc, const inner_allocator_type &inner)\ + : outer_allocator_type(::boost::forward(outerAlloc))\ + , m_inner(inner)\ + {}\ + \ + public:\ + scoped_allocator_adaptor_base &operator=\ + (BOOST_COPY_ASSIGN_REF(scoped_allocator_adaptor_base) other)\ + {\ + outer_allocator_type::operator=(other.outer_allocator());\ + m_inner = other.inner_allocator();\ + return *this;\ + }\ + \ + scoped_allocator_adaptor_base &operator=(BOOST_RV_REF(scoped_allocator_adaptor_base) other)\ + {\ + outer_allocator_type::operator=(boost::move(other.outer_allocator()));\ + m_inner = ::boost::move(other.inner_allocator());\ + return *this;\ + }\ + \ + void swap(scoped_allocator_adaptor_base &r)\ + {\ + boost::adl_move_swap(this->outer_allocator(), r.outer_allocator());\ + boost::adl_move_swap(this->m_inner, r.inner_allocator());\ + }\ + \ + friend void swap(scoped_allocator_adaptor_base &l, scoped_allocator_adaptor_base &r)\ + { l.swap(r); }\ + \ + inner_allocator_type& inner_allocator()\ + { return m_inner; }\ + \ + inner_allocator_type const& inner_allocator() const\ + { return m_inner; }\ + \ + outer_allocator_type & outer_allocator()\ + { return static_cast(*this); }\ + \ + const outer_allocator_type &outer_allocator() const\ + { return static_cast(*this); }\ + \ + scoped_allocator_type select_on_container_copy_construction() const\ + {\ + return scoped_allocator_type\ + (internal_type_t()\ + ,outer_traits_type::select_on_container_copy_construction(this->outer_allocator())\ + ,inner_traits_type::select_on_container_copy_construction(this->inner_allocator())\ + );\ + }\ + private:\ + inner_allocator_type m_inner;\ +};\ +//! +BOOST_MOVE_ITERATE_1TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_BASE_CODE) +#undef BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_BASE_CODE + +#endif //#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + #define BOOST_CONTAINER_SCOPEDALLOC_DUMMYTRUE ,true + #define BOOST_CONTAINER_SCOPEDALLOC_ALLINNER BOOST_MOVE_TARG9 + #define BOOST_CONTAINER_SCOPEDALLOC_ALLINNERCLASS BOOST_MOVE_CLASS9 +#else + #define BOOST_CONTAINER_SCOPEDALLOC_DUMMYTRUE + #define BOOST_CONTAINER_SCOPEDALLOC_ALLINNER InnerAllocs... + #define BOOST_CONTAINER_SCOPEDALLOC_ALLINNERCLASS typename... InnerAllocs +#endif + +//Specialization for adaptor without any InnerAlloc +template +class scoped_allocator_adaptor_base< OuterAlloc BOOST_CONTAINER_SCOPEDALLOC_DUMMYTRUE> + : public OuterAlloc +{ + BOOST_COPYABLE_AND_MOVABLE(scoped_allocator_adaptor_base) + public: + + template + struct rebind_base + { + typedef scoped_allocator_adaptor_base + ::template portable_rebind_alloc::type + BOOST_CONTAINER_SCOPEDALLOC_DUMMYTRUE > other; + }; + + typedef OuterAlloc outer_allocator_type; + typedef allocator_traits outer_traits_type; + typedef scoped_allocator_adaptor inner_allocator_type; + typedef inner_allocator_type scoped_allocator_type; + typedef allocator_traits inner_traits_type; + typedef typename outer_traits_type:: + propagate_on_container_copy_assignment propagate_on_container_copy_assignment; + typedef typename outer_traits_type:: + propagate_on_container_move_assignment propagate_on_container_move_assignment; + typedef typename outer_traits_type:: + propagate_on_container_swap propagate_on_container_swap; + typedef typename outer_traits_type:: + is_always_equal is_always_equal; + + scoped_allocator_adaptor_base() + {} + + template + scoped_allocator_adaptor_base(BOOST_FWD_REF(OuterA2) outerAlloc) + : outer_allocator_type(::boost::forward(outerAlloc)) + {} + + scoped_allocator_adaptor_base(const scoped_allocator_adaptor_base& other) + : outer_allocator_type(other.outer_allocator()) + {} + + scoped_allocator_adaptor_base(BOOST_RV_REF(scoped_allocator_adaptor_base) other) + : outer_allocator_type(::boost::move(other.outer_allocator())) + {} + + template + scoped_allocator_adaptor_base + (const scoped_allocator_adaptor_base& other) + : outer_allocator_type(other.outer_allocator()) + {} + + template + scoped_allocator_adaptor_base + (BOOST_RV_REF_BEG scoped_allocator_adaptor_base BOOST_RV_REF_END other) + : outer_allocator_type(other.outer_allocator()) + {} + + public: + struct internal_type_t{}; + + template + scoped_allocator_adaptor_base(internal_type_t, BOOST_FWD_REF(OuterA2) outerAlloc, const inner_allocator_type &) + : outer_allocator_type(::boost::forward(outerAlloc)) + {} + + public: + scoped_allocator_adaptor_base &operator=(BOOST_COPY_ASSIGN_REF(scoped_allocator_adaptor_base) other) + { + outer_allocator_type::operator=(other.outer_allocator()); + return *this; + } + + scoped_allocator_adaptor_base &operator=(BOOST_RV_REF(scoped_allocator_adaptor_base) other) + { + outer_allocator_type::operator=(boost::move(other.outer_allocator())); + return *this; + } + + void swap(scoped_allocator_adaptor_base &r) + { + boost::adl_move_swap(this->outer_allocator(), r.outer_allocator()); + } + + friend void swap(scoped_allocator_adaptor_base &l, scoped_allocator_adaptor_base &r) + { l.swap(r); } + + inner_allocator_type& inner_allocator() + { return static_cast(*this); } + + inner_allocator_type const& inner_allocator() const + { return static_cast(*this); } + + outer_allocator_type & outer_allocator() + { return static_cast(*this); } + + const outer_allocator_type &outer_allocator() const + { return static_cast(*this); } + + scoped_allocator_type select_on_container_copy_construction() const + { + return scoped_allocator_type + (internal_type_t() + ,outer_traits_type::select_on_container_copy_construction(this->outer_allocator()) + //Don't use inner_traits_type::select_on_container_copy_construction(this->inner_allocator()) + //as inner_allocator() is equal to *this and that would trigger an infinite loop + , this->inner_allocator() + ); + } +}; + +} //namespace container_detail { + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//Scoped allocator +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + +#if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + +//! This class is a C++03-compatible implementation of std::scoped_allocator_adaptor. +//! The class template scoped_allocator_adaptor is an allocator template that specifies +//! the memory resource (the outer allocator) to be used by a container (as any other +//! allocator does) and also specifies an inner allocator resource to be passed to +//! the constructor of every element within the container. +//! +//! This adaptor is +//! instantiated with one outer and zero or more inner allocator types. If +//! instantiated with only one allocator type, the inner allocator becomes the +//! scoped_allocator_adaptor itself, thus using the same allocator resource for the +//! container and every element within the container and, if the elements themselves +//! are containers, each of their elements recursively. If instantiated with more than +//! one allocator, the first allocator is the outer allocator for use by the container, +//! the second allocator is passed to the constructors of the container's elements, +//! and, if the elements themselves are containers, the third allocator is passed to +//! the elements' elements, and so on. If containers are nested to a depth greater +//! than the number of allocators, the last allocator is used repeatedly, as in the +//! single-allocator case, for any remaining recursions. +//! +//! [Note: The +//! scoped_allocator_adaptor is derived from the outer allocator type so it can be +//! substituted for the outer allocator type in most expressions. -end note] +//! +//! In the construct member functions, OUTERMOST(x) is x if x does not have +//! an outer_allocator() member function and +//! OUTERMOST(x.outer_allocator()) otherwise; OUTERMOST_ALLOC_TRAITS(x) is +//! allocator_traits. +//! +//! [Note: OUTERMOST(x) and +//! OUTERMOST_ALLOC_TRAITS(x) are recursive operations. It is incumbent upon +//! the definition of outer_allocator() to ensure that the recursion terminates. +//! It will terminate for all instantiations of scoped_allocator_adaptor. -end note] +template +class scoped_allocator_adaptor + +#else // #if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + +template +class scoped_allocator_adaptor + +#endif // #if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + +#else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + +template +class scoped_allocator_adaptor +#endif + + : public container_detail::scoped_allocator_adaptor_base + +{ + BOOST_COPYABLE_AND_MOVABLE(scoped_allocator_adaptor) + + public: + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + typedef container_detail::scoped_allocator_adaptor_base + base_type; + typedef typename base_type::internal_type_t internal_type_t; + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + typedef OuterAlloc outer_allocator_type; + //! Type: For exposition only + //! + typedef allocator_traits outer_traits_type; + //! Type: scoped_allocator_adaptor if sizeof...(InnerAllocs) is zero; otherwise, + //! scoped_allocator_adaptor. + typedef typename base_type::inner_allocator_type inner_allocator_type; + typedef allocator_traits inner_traits_type; + typedef typename outer_traits_type::value_type value_type; + typedef typename outer_traits_type::size_type size_type; + typedef typename outer_traits_type::difference_type difference_type; + typedef typename outer_traits_type::pointer pointer; + typedef typename outer_traits_type::const_pointer const_pointer; + typedef typename outer_traits_type::void_pointer void_pointer; + typedef typename outer_traits_type::const_void_pointer const_void_pointer; + //! Type: A type with a constant boolean value == true if + //!`allocator_traits:: propagate_on_container_copy_assignment::value` is + //! true for any Allocator in the set of OuterAlloc and InnerAllocs..., false otherwise. + typedef typename base_type:: + propagate_on_container_copy_assignment propagate_on_container_copy_assignment; + //! Type: A type with a constant boolean value == true if + //!`allocator_traits:: propagate_on_container_move_assignment::value` is + //! true for any Allocator in the set of OuterAlloc and InnerAllocs..., false otherwise. + typedef typename base_type:: + propagate_on_container_move_assignment propagate_on_container_move_assignment; + + //! Type: A type with a constant boolean value == true if + //! `allocator_traits:: propagate_on_container_swap::value` is + //! true for any Allocator in the set of OuterAlloc and InnerAllocs..., false otherwise. + typedef typename base_type:: + propagate_on_container_swap propagate_on_container_swap; + + //! Type: A type with a constant boolean value == true if + //!`allocator_traits:: is_always_equal::value` is + //! true for all Allocator in the set of OuterAlloc and InnerAllocs..., false otherwise. + typedef typename base_type:: + is_always_equal is_always_equal; + + //! Type: Rebinds scoped allocator to + //! typedef scoped_allocator_adaptor + //! < typename outer_traits_type::template portable_rebind_alloc::type + //! , InnerAllocs... > + template + struct rebind + { + typedef scoped_allocator_adaptor + < typename outer_traits_type::template portable_rebind_alloc::type + , BOOST_CONTAINER_SCOPEDALLOC_ALLINNER> other; + }; + + //! Effects: value-initializes the OuterAlloc base class + //! and the inner allocator object. + scoped_allocator_adaptor() + {} + + ~scoped_allocator_adaptor() + {} + + //! Effects: initializes each allocator within the adaptor with + //! the corresponding allocator from other. + scoped_allocator_adaptor(const scoped_allocator_adaptor& other) + : base_type(other.base()) + {} + + //! Effects: move constructs each allocator within the adaptor with + //! the corresponding allocator from other. + scoped_allocator_adaptor(BOOST_RV_REF(scoped_allocator_adaptor) other) + : base_type(::boost::move(other.base())) + {} + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Requires: OuterAlloc shall be constructible from OuterA2. + //! + //! Effects: initializes the OuterAlloc base class with boost::forward(outerAlloc) and inner + //! with innerAllocs...(hence recursively initializing each allocator within the adaptor with the + //! corresponding allocator from the argument list). + template + scoped_allocator_adaptor(BOOST_FWD_REF(OuterA2) outerAlloc, const InnerAllocs & ...innerAllocs) + : base_type(::boost::forward(outerAlloc), innerAllocs...) + {} + #else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + #define BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_RELATED_ALLOCATOR_CONSTRUCTOR_CODE(N)\ + template \ + scoped_allocator_adaptor(BOOST_FWD_REF(OuterA2) outerAlloc BOOST_MOVE_I##N BOOST_MOVE_CREF##N)\ + : base_type(::boost::forward(outerAlloc) BOOST_MOVE_I##N BOOST_MOVE_ARG##N)\ + {}\ + // + BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_RELATED_ALLOCATOR_CONSTRUCTOR_CODE) + #undef BOOST_CONTAINER_SCOPED_ALLOCATOR_ADAPTOR_RELATED_ALLOCATOR_CONSTRUCTOR_CODE + + #endif // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Requires: OuterAlloc shall be constructible from OuterA2. + //! + //! Effects: initializes each allocator within the adaptor with the corresponding allocator from other. + template + scoped_allocator_adaptor(const scoped_allocator_adaptor &other) + : base_type(other.base()) + {} + + //! Requires: OuterAlloc shall be constructible from OuterA2. + //! + //! Effects: initializes each allocator within the adaptor with the corresponding allocator + //! rvalue from other. + template + scoped_allocator_adaptor(BOOST_RV_REF_BEG scoped_allocator_adaptor + BOOST_RV_REF_END other) + : base_type(::boost::move(other.base())) + {} + + scoped_allocator_adaptor &operator=(BOOST_COPY_ASSIGN_REF(scoped_allocator_adaptor) other) + { return static_cast(base_type::operator=(static_cast(other))); } + + scoped_allocator_adaptor &operator=(BOOST_RV_REF(scoped_allocator_adaptor) other) + { return static_cast(base_type::operator=(boost::move(other.base()))); } + + #ifdef BOOST_CONTAINER_DOXYGEN_INVOKED + //! Effects: swaps *this with r. + //! + void swap(scoped_allocator_adaptor &r); + + //! Effects: swaps *this with r. + //! + friend void swap(scoped_allocator_adaptor &l, scoped_allocator_adaptor &r); + + //! Returns: + //! static_cast(*this). + outer_allocator_type & outer_allocator() BOOST_NOEXCEPT_OR_NOTHROW; + + //! Returns: + //! static_cast(*this). + const outer_allocator_type &outer_allocator() const BOOST_NOEXCEPT_OR_NOTHROW; + + //! Returns: + //! *this if sizeof...(InnerAllocs) is zero; otherwise, inner. + inner_allocator_type& inner_allocator() BOOST_NOEXCEPT_OR_NOTHROW; + + //! Returns: + //! *this if sizeof...(InnerAllocs) is zero; otherwise, inner. + inner_allocator_type const& inner_allocator() const BOOST_NOEXCEPT_OR_NOTHROW; + + #endif //BOOST_CONTAINER_DOXYGEN_INVOKED + + //! Returns: + //! allocator_traits:: max_size(outer_allocator()). + size_type max_size() const BOOST_NOEXCEPT_OR_NOTHROW + { return outer_traits_type::max_size(this->outer_allocator()); } + + //! Effects: + //! calls OUTERMOST_ALLOC_TRAITS(*this):: destroy(OUTERMOST(*this), p). + template + void destroy(T* p) BOOST_NOEXCEPT_OR_NOTHROW + { + allocator_traits::type> + ::destroy(get_outermost_allocator(this->outer_allocator()), p); + } + + //! Returns: + //! allocator_traits::allocate(outer_allocator(), n). + pointer allocate(size_type n) + { return outer_traits_type::allocate(this->outer_allocator(), n); } + + //! Returns: + //! allocator_traits::allocate(outer_allocator(), n, hint). + pointer allocate(size_type n, const_void_pointer hint) + { return outer_traits_type::allocate(this->outer_allocator(), n, hint); } + + //! Effects: + //! allocator_traits::deallocate(outer_allocator(), p, n). + void deallocate(pointer p, size_type n) + { outer_traits_type::deallocate(this->outer_allocator(), p, n); } + + #ifdef BOOST_CONTAINER_DOXYGEN_INVOKED + //! Returns: A new scoped_allocator_adaptor object where each allocator + //! Allocator in the adaptor is initialized from the result of calling + //! allocator_traits::select_on_container_copy_construction() on + //! the corresponding allocator in *this. + scoped_allocator_adaptor select_on_container_copy_construction() const; + #endif //BOOST_CONTAINER_DOXYGEN_INVOKED + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + base_type &base() { return *this; } + + const base_type &base() const { return *this; } + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Effects: + //! 1) If uses_allocator::value is false calls + //! OUTERMOST_ALLOC_TRAITS(*this):: + //! construct(OUTERMOST(*this), p, std::forward(args)...). + //! + //! 2) Otherwise, if uses_allocator::value is true and + //! is_constructible:: value is true, calls + //! OUTERMOST_ALLOC_TRAITS(*this):: construct(OUTERMOST(*this), p, allocator_arg, + //! inner_allocator(), std::forward(args)...). + //! + //! [Note: In compilers without advanced decltype SFINAE support, is_constructible can't + //! be implemented so that condition will be replaced by + //! constructible_with_allocator_prefix::value. -end note] + //! + //! 3) Otherwise, if uses_allocator::value is true and + //! is_constructible:: value is true, calls + //! OUTERMOST_ALLOC_TRAITS(*this):: construct(OUTERMOST(*this), p, + //! std::forward(args)..., inner_allocator()). + //! + //! [Note: In compilers without advanced decltype SFINAE support, is_constructible can't be + //! implemented so that condition will be replaced by + //! constructible_with_allocator_suffix:: value. -end note] + //! + //! 4) Otherwise, the program is ill-formed. + //! + //! [Note: An error will result if uses_allocator evaluates + //! to true but the specific constructor does not take an allocator. This definition prevents a silent + //! failure to pass an inner allocator to a contained element. -end note] + template < typename T, class ...Args> + void construct(T* p, BOOST_FWD_REF(Args)...args) + { + container_detail::dispatch_uses_allocator + ( (get_outermost_allocator)(this->outer_allocator()) + , this->inner_allocator(), p, ::boost::forward(args)...); + } + + #else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //Disable this overload if the first argument is pair as some compilers have + //overload selection problems when the first parameter is a pair. + #define BOOST_CONTAINER_SCOPED_ALLOCATOR_CONSTRUCT_CODE(N) \ + template < typename T BOOST_MOVE_I##N BOOST_MOVE_CLASSQ##N >\ + void construct(T* p BOOST_MOVE_I##N BOOST_MOVE_UREFQ##N)\ + {\ + container_detail::dispatch_uses_allocator\ + ( (get_outermost_allocator)(this->outer_allocator())\ + , this->inner_allocator(), p BOOST_MOVE_I##N BOOST_MOVE_FWDQ##N);\ + }\ + // + BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_SCOPED_ALLOCATOR_CONSTRUCT_CODE) + #undef BOOST_CONTAINER_SCOPED_ALLOCATOR_CONSTRUCT_CODE + + #endif // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + public: + //Internal function + template + scoped_allocator_adaptor(internal_type_t, BOOST_FWD_REF(OuterA2) outer, const inner_allocator_type& inner) + : base_type(internal_type_t(), ::boost::forward(outer), inner) + {} + + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED +}; + +/// @cond + +template +struct scoped_allocator_operator_equal +{ + //Optimize equal outer allocator types with + //allocator_traits::equal which uses is_always_equal + template + static bool equal_outer(const IA &l, const IA &r) + { return allocator_traits::equal(l, r); } + + //Otherwise compare it normally + template + static bool equal_outer(const IA1 &l, const IA2 &r) + { return l == r; } + + //Otherwise compare it normally + template + static bool equal_inner(const IA &l, const IA &r) + { return allocator_traits::equal(l, r); } +}; + +template<> +struct scoped_allocator_operator_equal + : scoped_allocator_operator_equal +{ + //when inner allocator count is zero, + //inner_allocator_type is the same as outer_allocator_type + //so both types can be different in operator== + template + static bool equal_inner(const IA1 &, const IA2 &) + { return true; } +}; + +/// @endcond + +template +inline bool operator==(const scoped_allocator_adaptor& a + ,const scoped_allocator_adaptor& b) +{ + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + const bool has_zero_inner = sizeof...(InnerAllocs) == 0u; + #else + const bool has_zero_inner = boost::container::container_detail::is_same::value; + #endif + typedef scoped_allocator_operator_equal equal_t; + return equal_t::equal_outer(a.outer_allocator(), b.outer_allocator()) && + equal_t::equal_inner(a.inner_allocator(), b.inner_allocator()); +} + +template +inline bool operator!=(const scoped_allocator_adaptor& a + ,const scoped_allocator_adaptor& b) +{ return !(a == b); } + +}} // namespace boost { namespace container { + +#include + +#endif // BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator_fwd.hpp new file mode 100644 index 0000000..cddf7fa --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/scoped_allocator_fwd.hpp @@ -0,0 +1,71 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2015-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_FWD_HPP +#define BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_FWD_HPP + +//! \file +//! This header file forward declares boost::container::scoped_allocator_adaptor + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif + +namespace boost { namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + #if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + + template + class scoped_allocator_adaptor; + + #else // #if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + + template + class scoped_allocator_adaptor; + + template + class scoped_allocator_adaptor; + + #endif // #if !defined(BOOST_CONTAINER_UNIMPLEMENTED_PACK_EXPANSION_TO_FIXED_LIST) + +#else // #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + template + class scoped_allocator_adaptor; + +#endif + + +#else //BOOST_CONTAINER_DOXYGEN_INVOKED + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +}} // namespace boost { namespace container { + +#include + +#endif // BOOST_CONTAINER_ALLOCATOR_SCOPED_ALLOCATOR_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/throw_exception.hpp b/thirdparty/source/boost_1_61_0/boost/container/throw_exception.hpp new file mode 100644 index 0000000..118cc39 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/throw_exception.hpp @@ -0,0 +1,171 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2012-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_THROW_EXCEPTION_HPP +#define BOOST_CONTAINER_THROW_EXCEPTION_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS + #include //for std exception types + #include //for implicit std::string conversion + #include //for std::bad_alloc +#else + #include + #include //for std::abort +#endif + +namespace boost { +namespace container { + +#if defined(BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS) + //The user must provide definitions for the following functions + + void throw_bad_alloc(); + + void throw_out_of_range(const char* str); + + void throw_length_error(const char* str); + + void throw_logic_error(const char* str); + + void throw_runtime_error(const char* str); + +#elif defined(BOOST_NO_EXCEPTIONS) + + inline void throw_bad_alloc() + { + BOOST_ASSERT(!"boost::container bad_alloc thrown"); + std::abort(); + } + + inline void throw_out_of_range(const char* str) + { + BOOST_ASSERT_MSG(!"boost::container out_of_range thrown", str); + std::abort(); + } + + inline void throw_length_error(const char* str) + { + BOOST_ASSERT_MSG(!"boost::container length_error thrown", str); + std::abort(); + } + + inline void throw_logic_error(const char* str) + { + BOOST_ASSERT_MSG(!"boost::container logic_error thrown", str); + std::abort(); + } + + inline void throw_runtime_error(const char* str) + { + BOOST_ASSERT_MSG(!"boost::container runtime_error thrown", str); + std::abort(); + } + +#else //defined(BOOST_NO_EXCEPTIONS) + + //! Exception callback called by Boost.Container when fails to allocate the requested storage space. + //!
    + //!
  • If BOOST_NO_EXCEPTIONS is NOT defined std::bad_alloc() is thrown.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS is defined and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS + //! is NOT defined BOOST_ASSERT(!"boost::container bad_alloc thrown") is called + //! and std::abort() if the former returns.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS are defined + //! the user must provide an implementation and the function should not return.
  • + //!
+ inline void throw_bad_alloc() + { + throw std::bad_alloc(); + } + + //! Exception callback called by Boost.Container to signal arguments out of range. + //!
    + //!
  • If BOOST_NO_EXCEPTIONS is NOT defined std::out_of_range(str) is thrown.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS is defined and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS + //! is NOT defined BOOST_ASSERT_MSG(!"boost::container out_of_range thrown", str) is called + //! and std::abort() if the former returns.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS are defined + //! the user must provide an implementation and the function should not return.
  • + //!
+ inline void throw_out_of_range(const char* str) + { + throw std::out_of_range(str); + } + + //! Exception callback called by Boost.Container to signal errors resizing. + //!
    + //!
  • If BOOST_NO_EXCEPTIONS is NOT defined std::length_error(str) is thrown.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS is defined and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS + //! is NOT defined BOOST_ASSERT_MSG(!"boost::container length_error thrown", str) is called + //! and std::abort() if the former returns.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS are defined + //! the user must provide an implementation and the function should not return.
  • + //!
+ inline void throw_length_error(const char* str) + { + throw std::length_error(str); + } + + //! Exception callback called by Boost.Container to report errors in the internal logical + //! of the program, such as violation of logical preconditions or class invariants. + //!
    + //!
  • If BOOST_NO_EXCEPTIONS is NOT defined std::logic_error(str) is thrown.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS is defined and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS + //! is NOT defined BOOST_ASSERT_MSG(!"boost::container logic_error thrown", str) is called + //! and std::abort() if the former returns.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS are defined + //! the user must provide an implementation and the function should not return.
  • + //!
+ inline void throw_logic_error(const char* str) + { + throw std::logic_error(str); + } + + //! Exception callback called by Boost.Container to report errors that can only be detected during runtime. + //!
    + //!
  • If BOOST_NO_EXCEPTIONS is NOT defined std::runtime_error(str) is thrown.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS is defined and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS + //! is NOT defined BOOST_ASSERT_MSG(!"boost::container runtime_error thrown", str) is called + //! and std::abort() if the former returns.
  • + //! + //!
  • If BOOST_NO_EXCEPTIONS and BOOST_CONTAINER_USER_DEFINED_THROW_CALLBACKS are defined + //! the user must provide an implementation and the function should not return.
  • + //!
+ inline void throw_runtime_error(const char* str) + { + throw std::runtime_error(str); + } + +#endif + +}} //namespace boost { namespace container { + +#include + +#endif //#ifndef BOOST_CONTAINER_THROW_EXCEPTION_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/uses_allocator.hpp b/thirdparty/source/boost_1_61_0/boost/container/uses_allocator.hpp new file mode 100644 index 0000000..2bcc465 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/uses_allocator.hpp @@ -0,0 +1,169 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2011-2013. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_USES_ALLOCATOR_HPP +#define BOOST_CONTAINER_USES_ALLOCATOR_HPP + +#include +#include + +namespace boost { +namespace container { + +//! Remark: if a specialization constructible_with_allocator_suffix::value is true, indicates that T may be constructed +//! with an allocator as its last constructor argument. Ideally, all constructors of T (including the +//! copy and move constructors) should have a variant that accepts a final argument of +//! allocator_type. +//! +//! Requires: if a specialization constructible_with_allocator_suffix::value is true, T must have a nested type, +//! allocator_type and at least one constructor for which allocator_type is the last +//! parameter. If not all constructors of T can be called with a final allocator_type argument, +//! and if T is used in a context where a container must call such a constructor, then the program is +//! ill-formed. +//! +//! +//! template > +//! class Z { +//! public: +//! typedef Allocator allocator_type; +//! +//! // Default constructor with optional allocator suffix +//! Z(const allocator_type& a = allocator_type()); +//! +//! // Copy constructor and allocator-extended copy constructor +//! Z(const Z& zz); +//! Z(const Z& zz, const allocator_type& a); +//! }; +//! +//! // Specialize trait for class template Z +//! template > +//! struct constructible_with_allocator_suffix > +//! { static const bool value = true; }; +//! +//! +//! Note: This trait is a workaround inspired by "N2554: The Scoped A Model (Rev 2)" +//! (Pablo Halpern, 2008-02-29) to backport the scoped allocator model to C++03, as +//! in C++03 there is no mechanism to detect if a type can be constructed from arbitrary arguments. +//! Applications aiming portability with several compilers should always define this trait. +//! +//! In conforming C++11 compilers or compilers supporting SFINAE expressions +//! (when BOOST_NO_SFINAE_EXPR is NOT defined), this trait is ignored and C++11 rules will be used +//! to detect if a type should be constructed with suffix or prefix allocator arguments. +template +struct constructible_with_allocator_suffix +{ static const bool value = false; }; + +//! Remark: if a specialization constructible_with_allocator_prefix::value is true, indicates that T may be constructed +//! with allocator_arg and T::allocator_type as its first two constructor arguments. +//! Ideally, all constructors of T (including the copy and move constructors) should have a variant +//! that accepts these two initial arguments. +//! +//! Requires: specialization constructible_with_allocator_prefix::value is true, T must have a nested type, +//! allocator_type and at least one constructor for which allocator_arg_t is the first +//! parameter and allocator_type is the second parameter. If not all constructors of T can be +//! called with these initial arguments, and if T is used in a context where a container must call such +//! a constructor, then the program is ill-formed. +//! +//! +//! template > +//! class Y { +//! public: +//! typedef Allocator allocator_type; +//! +//! // Default constructor with and allocator-extended default constructor +//! Y(); +//! Y(allocator_arg_t, const allocator_type& a); +//! +//! // Copy constructor and allocator-extended copy constructor +//! Y(const Y& yy); +//! Y(allocator_arg_t, const allocator_type& a, const Y& yy); +//! +//! // Variadic constructor and allocator-extended variadic constructor +//! template Y(Args&& args...); +//! template +//! Y(allocator_arg_t, const allocator_type& a, BOOST_FWD_REF(Args)... args); +//! }; +//! +//! // Specialize trait for class template Y +//! template > +//! struct constructible_with_allocator_prefix > +//! { static const bool value = true; }; +//! +//! +//! +//! Note: This trait is a workaround inspired by "N2554: The Scoped Allocator Model (Rev 2)" +//! (Pablo Halpern, 2008-02-29) to backport the scoped allocator model to C++03, as +//! in C++03 there is no mechanism to detect if a type can be constructed from arbitrary arguments. +//! Applications aiming portability with several compilers should always define this trait. +//! +//! In conforming C++11 compilers or compilers supporting SFINAE expressions +//! (when BOOST_NO_SFINAE_EXPR is NOT defined), this trait is ignored and C++11 rules will be used +//! to detect if a type should be constructed with suffix or prefix allocator arguments. +template +struct constructible_with_allocator_prefix +{ static const bool value = false; }; + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +namespace container_detail { + +template +struct uses_allocator_imp +{ + // Use SFINAE (Substitution Failure Is Not An Error) to detect the + // presence of an 'allocator_type' nested type convertilble from Allocator. + private: + typedef char yes_type; + struct no_type{ char dummy[2]; }; + + // Match this function if T::allocator_type exists and is + // implicitly convertible from Allocator + template + static yes_type test(typename U::allocator_type); + + // Match this function if T::allocator_type exists and it's type is `erased_type`. + template + static typename container_detail::enable_if + < container_detail::is_same + , yes_type + >::type test(const V&); + + // Match this function if TypeT::allocator_type does not exist or is + // not convertible from Allocator. + template + static no_type test(...); + static Allocator alloc; // Declared but not defined + + public: + static const bool value = sizeof(test(alloc)) == sizeof(yes_type); +}; + +} //namespace container_detail { + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! Remark: Automatically detects whether T has a nested allocator_type that is convertible from +//! Allocator. Meets the BinaryTypeTrait requirements ([meta.rqmts] 20.4.1). A program may +//! specialize this type to define uses_allocator::value as true for a T of user-defined type if T does not +//! have a nested allocator_type but is nonetheless constructible using the specified Allocator where either: +//! the first argument of a constructor has type allocator_arg_t and the second argument has type Alloc or +//! the last argument of a constructor has type Alloc. +//! +//! Result: uses_allocator::value== true if a type T::allocator_type +//! exists and either is_convertible::value != false or T::allocator_type +//! is an alias `erased_type`. False otherwise. +template +struct uses_allocator + : container_detail::uses_allocator_imp +{}; + +}} //namespace boost::container + +#endif //BOOST_CONTAINER_USES_ALLOCATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/uses_allocator_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/container/uses_allocator_fwd.hpp new file mode 100644 index 0000000..d8fe67f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/uses_allocator_fwd.hpp @@ -0,0 +1,73 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2015-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_USES_ALLOCATOR_FWD_HPP +#define BOOST_CONTAINER_USES_ALLOCATOR_FWD_HPP + +#include +#include + +//! \file +//! This header forward declares boost::container::constructible_with_allocator_prefix, +//! boost::container::constructible_with_allocator_suffix and +//! boost::container::uses_allocator. Also defines the following types: + +namespace boost { +namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + template + struct std_allocator_arg_holder + { + static ::std::allocator_arg_t *dummy; + }; + + template + ::std::allocator_arg_t *std_allocator_arg_holder::dummy; + +typedef const std::allocator_arg_t & allocator_arg_t; + +#else + +//! The allocator_arg_t struct is an empty structure type used as a unique type to +//! disambiguate constructor and function overloading. Specifically, several types +//! have constructors with allocator_arg_t as the first argument, immediately followed +//! by an argument of a type that satisfies Allocator requirements +typedef unspecified allocator_arg_t; + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! The `erased_type` struct is an empty struct that serves as a placeholder for a type +//! T in situations where the actual type T is determined at runtime. For example, +//! the nested type, `allocator_type`, is an alias for `erased_type` in classes that +//! use type-erased allocators. +struct erased_type {}; + +//! A instance of type +//! allocator_arg_t +static allocator_arg_t allocator_arg = BOOST_CONTAINER_DOC1ST(unspecified, *std_allocator_arg_holder<>::dummy); + +// @cond + +template +struct constructible_with_allocator_suffix; + +template +struct constructible_with_allocator_prefix; + +template +struct uses_allocator; + +// @endcond + +}} // namespace boost { namespace container { + +#endif //BOOST_CONTAINER_USES_ALLOCATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/container/vector.hpp b/thirdparty/source/boost_1_61_0/boost/container/vector.hpp new file mode 100644 index 0000000..fabe92d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/container/vector.hpp @@ -0,0 +1,3398 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2005-2015. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONTAINER_CONTAINER_VECTOR_HPP +#define BOOST_CONTAINER_CONTAINER_VECTOR_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include + +// container +#include +#include +#include //new_allocator +#include +// container detail +#include +#include //equal() +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +// intrusive +#include +// move +#include +#include +#include +#include +// move/detail +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#include +#endif +#include +// other +#include +#include +#include + +//std +#if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) +#include //for std::initializer_list +#endif + +namespace boost { +namespace container { + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//#define BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +namespace container_detail { + +#ifndef BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +template +class vec_iterator +{ + public: + typedef std::random_access_iterator_tag iterator_category; + typedef typename boost::intrusive::pointer_traits::element_type value_type; + typedef typename boost::intrusive::pointer_traits::difference_type difference_type; + typedef typename if_c + < IsConst + , typename boost::intrusive::pointer_traits::template + rebind_pointer::type + , Pointer + >::type pointer; + typedef typename boost::intrusive::pointer_traits ptr_traits; + typedef typename ptr_traits::reference reference; + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + private: + Pointer m_ptr; + + public: + BOOST_CONTAINER_FORCEINLINE const Pointer &get_ptr() const BOOST_NOEXCEPT_OR_NOTHROW + { return m_ptr; } + + BOOST_CONTAINER_FORCEINLINE Pointer &get_ptr() BOOST_NOEXCEPT_OR_NOTHROW + { return m_ptr; } + + BOOST_CONTAINER_FORCEINLINE explicit vec_iterator(Pointer ptr) BOOST_NOEXCEPT_OR_NOTHROW + : m_ptr(ptr) + {} + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + public: + + //Constructors + BOOST_CONTAINER_FORCEINLINE vec_iterator() BOOST_NOEXCEPT_OR_NOTHROW + : m_ptr() //Value initialization to achieve "null iterators" (N3644) + {} + + BOOST_CONTAINER_FORCEINLINE vec_iterator(vec_iterator const& other) BOOST_NOEXCEPT_OR_NOTHROW + : m_ptr(other.get_ptr()) + {} + + //Pointer like operators + BOOST_CONTAINER_FORCEINLINE reference operator*() const BOOST_NOEXCEPT_OR_NOTHROW + { return *m_ptr; } + + BOOST_CONTAINER_FORCEINLINE pointer operator->() const BOOST_NOEXCEPT_OR_NOTHROW + { return ::boost::intrusive::pointer_traits::pointer_to(this->operator*()); } + + BOOST_CONTAINER_FORCEINLINE reference operator[](difference_type off) const BOOST_NOEXCEPT_OR_NOTHROW + { return m_ptr[off]; } + + //Increment / Decrement + BOOST_CONTAINER_FORCEINLINE vec_iterator& operator++() BOOST_NOEXCEPT_OR_NOTHROW + { ++m_ptr; return *this; } + + BOOST_CONTAINER_FORCEINLINE vec_iterator operator++(int) BOOST_NOEXCEPT_OR_NOTHROW + { return vec_iterator(m_ptr++); } + + BOOST_CONTAINER_FORCEINLINE vec_iterator& operator--() BOOST_NOEXCEPT_OR_NOTHROW + { --m_ptr; return *this; } + + BOOST_CONTAINER_FORCEINLINE vec_iterator operator--(int) BOOST_NOEXCEPT_OR_NOTHROW + { return vec_iterator(m_ptr--); } + + //Arithmetic + BOOST_CONTAINER_FORCEINLINE vec_iterator& operator+=(difference_type off) BOOST_NOEXCEPT_OR_NOTHROW + { m_ptr += off; return *this; } + + BOOST_CONTAINER_FORCEINLINE vec_iterator& operator-=(difference_type off) BOOST_NOEXCEPT_OR_NOTHROW + { m_ptr -= off; return *this; } + + BOOST_CONTAINER_FORCEINLINE friend vec_iterator operator+(const vec_iterator &x, difference_type off) BOOST_NOEXCEPT_OR_NOTHROW + { return vec_iterator(x.m_ptr+off); } + + BOOST_CONTAINER_FORCEINLINE friend vec_iterator operator+(difference_type off, vec_iterator right) BOOST_NOEXCEPT_OR_NOTHROW + { right.m_ptr += off; return right; } + + BOOST_CONTAINER_FORCEINLINE friend vec_iterator operator-(vec_iterator left, difference_type off) BOOST_NOEXCEPT_OR_NOTHROW + { left.m_ptr -= off; return left; } + + BOOST_CONTAINER_FORCEINLINE friend difference_type operator-(const vec_iterator &left, const vec_iterator& right) BOOST_NOEXCEPT_OR_NOTHROW + { return left.m_ptr - right.m_ptr; } + + //Comparison operators + BOOST_CONTAINER_FORCEINLINE friend bool operator== (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr == r.m_ptr; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator!= (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr != r.m_ptr; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator< (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr < r.m_ptr; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator<= (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr <= r.m_ptr; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator> (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr > r.m_ptr; } + + BOOST_CONTAINER_FORCEINLINE friend bool operator>= (const vec_iterator& l, const vec_iterator& r) BOOST_NOEXCEPT_OR_NOTHROW + { return l.m_ptr >= r.m_ptr; } +}; + +template +struct vector_insert_ordered_cursor +{ + typedef typename iterator_traits::value_type size_type; + typedef typename iterator_traits::reference reference; + + BOOST_CONTAINER_FORCEINLINE vector_insert_ordered_cursor(BiDirPosConstIt posit, BiDirValueIt valueit) + : last_position_it(posit), last_value_it(valueit) + {} + + void operator --() + { + --last_value_it; + --last_position_it; + while(this->get_pos() == size_type(-1)){ + --last_value_it; + --last_position_it; + } + } + + BOOST_CONTAINER_FORCEINLINE size_type get_pos() const + { return *last_position_it; } + + BOOST_CONTAINER_FORCEINLINE reference get_val() + { return *last_value_it; } + + BiDirPosConstIt last_position_it; + BiDirValueIt last_value_it; +}; + +template +struct vector_merge_cursor +{ + typedef SizeType size_type; + typedef typename iterator_traits::reference reference; + + BOOST_CONTAINER_FORCEINLINE vector_merge_cursor(T *pbeg, T *plast, BiDirValueIt valueit, Comp &cmp) + : m_pbeg(pbeg), m_pcur(--plast), m_valueit(valueit), m_cmp(cmp) + {} + + void operator --() + { + --m_valueit; + const T &t = *m_valueit; + while((m_pcur + 1) != m_pbeg){ + if(!m_cmp(t, *m_pcur)){ + break; + } + --m_pcur; + } + } + + BOOST_CONTAINER_FORCEINLINE size_type get_pos() const + { return static_cast((m_pcur + 1) - m_pbeg); } + + BOOST_CONTAINER_FORCEINLINE reference get_val() + { return *m_valueit; } + + T *const m_pbeg; + T *m_pcur; + BiDirValueIt m_valueit; + Comp &m_cmp; +}; + +} //namespace container_detail { + +template +BOOST_CONTAINER_FORCEINLINE const Pointer &vector_iterator_get_ptr(const container_detail::vec_iterator &it) BOOST_NOEXCEPT_OR_NOTHROW +{ return it.get_ptr(); } + +template +BOOST_CONTAINER_FORCEINLINE Pointer &get_ptr(container_detail::vec_iterator &it) BOOST_NOEXCEPT_OR_NOTHROW +{ return it.get_ptr(); } + +namespace container_detail { + +#else //ifndef BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +template< class MaybeConstPointer + , bool ElementTypeIsConst + = is_const< typename boost::intrusive::pointer_traits::element_type>::value > +struct vector_get_ptr_pointer_to_non_const +{ + typedef MaybeConstPointer const_pointer; + typedef boost::intrusive::pointer_traits pointer_traits_t; + typedef typename pointer_traits_t::element_type element_type; + typedef typename remove_const::type non_const_element_type; + typedef typename pointer_traits_t + ::template rebind_pointer::type return_type; + + BOOST_CONTAINER_FORCEINLINE static return_type get_ptr(const const_pointer &ptr) BOOST_NOEXCEPT_OR_NOTHROW + { return boost::intrusive::pointer_traits::const_cast_from(ptr); } +}; + +template +struct vector_get_ptr_pointer_to_non_const +{ + typedef const Pointer & return_type; + BOOST_CONTAINER_FORCEINLINE static return_type get_ptr(const Pointer &ptr) BOOST_NOEXCEPT_OR_NOTHROW + { return ptr; } +}; + +} //namespace container_detail { + +template +BOOST_CONTAINER_FORCEINLINE typename container_detail::vector_get_ptr_pointer_to_non_const::return_type + vector_iterator_get_ptr(const MaybeConstPointer &ptr) BOOST_NOEXCEPT_OR_NOTHROW +{ + return container_detail::vector_get_ptr_pointer_to_non_const::get_ptr(ptr); +} + +namespace container_detail { + +#endif //#ifndef BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + +struct uninitialized_size_t {}; +static const uninitialized_size_t uninitialized_size = uninitialized_size_t(); + +template +struct vector_value_traits_base +{ + static const bool trivial_dctr = is_trivially_destructible::value; + static const bool trivial_dctr_after_move = has_trivial_destructor_after_move::value; + static const bool trivial_copy = is_trivially_copy_constructible::value; + static const bool nothrow_copy = is_nothrow_copy_constructible::value || trivial_copy; + static const bool trivial_assign = is_trivially_copy_assignable::value; + static const bool nothrow_assign = is_nothrow_copy_assignable::value || trivial_assign; +}; + + +template +struct vector_value_traits + : public vector_value_traits_base +{ + typedef vector_value_traits_base base_t; + //This is the anti-exception array destructor + //to deallocate values already constructed + typedef typename container_detail::if_c + + ,container_detail::scoped_destructor_n + >::type ArrayDestructor; + //This is the anti-exception array deallocator + typedef container_detail::scoped_array_deallocator ArrayDeallocator; +}; + +//!This struct deallocates and allocated memory +template < class Allocator + , class AllocatorVersion = typename container_detail::version::type + > +struct vector_alloc_holder + : public Allocator +{ + private: + BOOST_MOVABLE_BUT_NOT_COPYABLE(vector_alloc_holder) + + public: + typedef Allocator allocator_type; + typedef boost::container::allocator_traits allocator_traits_type; + typedef typename allocator_traits_type::pointer pointer; + typedef typename allocator_traits_type::size_type size_type; + typedef typename allocator_traits_type::value_type value_type; + + static bool is_propagable_from(const allocator_type &from_alloc, pointer p, const allocator_type &to_alloc, bool const propagate_allocator) + { + (void)propagate_allocator; (void)p; (void)to_alloc; (void)from_alloc; + const bool all_storage_propagable = !allocator_traits_type::is_partially_propagable::value || + !allocator_traits_type::storage_is_unpropagable(from_alloc, p); + return all_storage_propagable && (propagate_allocator || allocator_traits_type::equal(from_alloc, to_alloc)); + } + + static bool are_swap_propagable(const allocator_type &l_a, pointer l_p, const allocator_type &r_a, pointer r_p, bool const propagate_allocator) + { + (void)propagate_allocator; (void)l_p; (void)r_p; (void)l_a; (void)r_a; + const bool all_storage_propagable = !allocator_traits_type::is_partially_propagable::value || + !(allocator_traits_type::storage_is_unpropagable(l_a, l_p) || allocator_traits_type::storage_is_unpropagable(r_a, r_p)); + return all_storage_propagable && (propagate_allocator || allocator_traits_type::equal(l_a, r_a)); + } + + //Constructor, does not throw + vector_alloc_holder() + BOOST_NOEXCEPT_IF(container_detail::is_nothrow_default_constructible::value) + : Allocator(), m_start(), m_size(), m_capacity() + {} + + //Constructor, does not throw + template + explicit vector_alloc_holder(BOOST_FWD_REF(AllocConvertible) a) BOOST_NOEXCEPT_OR_NOTHROW + : Allocator(boost::forward(a)), m_start(), m_size(), m_capacity() + {} + + //Constructor, does not throw + template + vector_alloc_holder(uninitialized_size_t, BOOST_FWD_REF(AllocConvertible) a, size_type initial_size) + : Allocator(boost::forward(a)) + , m_start() + , m_size(initial_size) //Size is initialized here so vector should only call uninitialized_xxx after this + , m_capacity() + { + if(initial_size){ + pointer reuse = 0; + m_start = this->allocation_command(allocate_new, initial_size, m_capacity = initial_size, reuse); + } + } + + //Constructor, does not throw + vector_alloc_holder(uninitialized_size_t, size_type initial_size) + : Allocator() + , m_start() + , m_size(initial_size) //Size is initialized here so vector should only call uninitialized_xxx after this + , m_capacity() + { + if(initial_size){ + pointer reuse = 0; + m_start = this->allocation_command(allocate_new, initial_size, m_capacity = initial_size, reuse); + } + } + + vector_alloc_holder(BOOST_RV_REF(vector_alloc_holder) holder) BOOST_NOEXCEPT_OR_NOTHROW + : Allocator(BOOST_MOVE_BASE(Allocator, holder)) + , m_start(holder.m_start) + , m_size(holder.m_size) + , m_capacity(holder.m_capacity) + { + holder.m_start = pointer(); + holder.m_size = holder.m_capacity = 0; + } + + vector_alloc_holder(pointer p, size_type capacity, BOOST_RV_REF(vector_alloc_holder) holder) + : Allocator(BOOST_MOVE_BASE(Allocator, holder)) + , m_start(p) + , m_size(holder.m_size) + , m_capacity(capacity) + { + allocator_type &this_alloc = this->alloc(); + allocator_type &x_alloc = holder.alloc(); + if(this->is_propagable_from(x_alloc, holder.start(), this_alloc, true)){ + if(this->m_capacity){ + this->alloc().deallocate(this->m_start, this->m_capacity); + } + m_start = holder.m_start; + m_capacity = holder.m_capacity; + holder.m_start = pointer(); + holder.m_capacity = holder.m_size = 0; + } + else if(this->m_capacity < holder.m_size){ + size_type const n = holder.m_size; + pointer reuse = pointer(); + m_start = this->allocation_command(allocate_new, n, m_capacity = n, reuse); + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + } + } + + vector_alloc_holder(pointer p, size_type n) + BOOST_NOEXCEPT_IF(container_detail::is_nothrow_default_constructible::value) + : Allocator() + , m_start(p) + , m_size() + , m_capacity(n) + {} + + template + vector_alloc_holder(pointer p, size_type n, BOOST_FWD_REF(AllocFwd) a) + : Allocator(::boost::forward(a)) + , m_start(p) + , m_size() + , m_capacity(n) + {} + + BOOST_CONTAINER_FORCEINLINE ~vector_alloc_holder() BOOST_NOEXCEPT_OR_NOTHROW + { + if(this->m_capacity){ + this->alloc().deallocate(this->m_start, this->m_capacity); + } + } + + BOOST_CONTAINER_FORCEINLINE pointer allocation_command(boost::container::allocation_type command, + size_type limit_size, size_type &prefer_in_recvd_out_size, pointer &reuse) + { + typedef typename container_detail::version::type alloc_version; + return this->priv_allocation_command(alloc_version(), command, limit_size, prefer_in_recvd_out_size, reuse); + } + + bool try_expand_fwd(size_type at_least) + { + //There is not enough memory, try to expand the old one + const size_type new_cap = this->capacity() + at_least; + size_type real_cap = new_cap; + pointer reuse = this->start(); + bool const success = !!this->allocation_command(expand_fwd, new_cap, real_cap, reuse); + //Check for forward expansion + if(success){ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_fwd; + #endif + this->capacity(real_cap); + } + return success; + } + + BOOST_CONTAINER_FORCEINLINE size_type next_capacity(size_type additional_objects) const + { + return next_capacity_calculator + ::get( allocator_traits_type::max_size(this->alloc()) + , this->m_capacity, additional_objects ); + } + + pointer m_start; + size_type m_size; + size_type m_capacity; + + void swap_resources(vector_alloc_holder &x) BOOST_NOEXCEPT_OR_NOTHROW + { + boost::adl_move_swap(this->m_start, x.m_start); + boost::adl_move_swap(this->m_size, x.m_size); + boost::adl_move_swap(this->m_capacity, x.m_capacity); + } + + void steal_resources(vector_alloc_holder &x) BOOST_NOEXCEPT_OR_NOTHROW + { + this->m_start = x.m_start; + this->m_size = x.m_size; + this->m_capacity = x.m_capacity; + x.m_start = pointer(); + x.m_size = x.m_capacity = 0; + } + + BOOST_CONTAINER_FORCEINLINE Allocator &alloc() BOOST_NOEXCEPT_OR_NOTHROW + { return *this; } + + BOOST_CONTAINER_FORCEINLINE const Allocator &alloc() const BOOST_NOEXCEPT_OR_NOTHROW + { return *this; } + + const pointer &start() const BOOST_NOEXCEPT_OR_NOTHROW { return m_start; } + const size_type &capacity() const BOOST_NOEXCEPT_OR_NOTHROW { return m_capacity; } + void start(const pointer &p) BOOST_NOEXCEPT_OR_NOTHROW { m_start = p; } + void capacity(const size_type &c) BOOST_NOEXCEPT_OR_NOTHROW { m_capacity = c; } + + private: + void priv_first_allocation(size_type cap) + { + if(cap){ + pointer reuse = 0; + m_start = this->allocation_command(allocate_new, cap, cap, reuse); + m_capacity = cap; + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + } + } + + BOOST_CONTAINER_FORCEINLINE pointer priv_allocation_command(version_1, boost::container::allocation_type command, + size_type , + size_type &prefer_in_recvd_out_size, + pointer &reuse) + { + (void)command; + BOOST_ASSERT( (command & allocate_new)); + BOOST_ASSERT(!(command & nothrow_allocation)); + pointer const p = allocator_traits_type::allocate(this->alloc(), prefer_in_recvd_out_size, reuse); + reuse = pointer(); + return p; + } + + pointer priv_allocation_command(version_2, boost::container::allocation_type command, + size_type limit_size, + size_type &prefer_in_recvd_out_size, + pointer &reuse) + { + return this->alloc().allocation_command(command, limit_size, prefer_in_recvd_out_size, reuse); + } +}; + +//!This struct deallocates and allocated memory +template +struct vector_alloc_holder + : public Allocator +{ + private: + BOOST_MOVABLE_BUT_NOT_COPYABLE(vector_alloc_holder) + + public: + typedef boost::container::allocator_traits allocator_traits_type; + typedef typename allocator_traits_type::pointer pointer; + typedef typename allocator_traits_type::size_type size_type; + typedef typename allocator_traits_type::value_type value_type; + + template + friend struct vector_alloc_holder; + + //Constructor, does not throw + vector_alloc_holder() + BOOST_NOEXCEPT_IF(container_detail::is_nothrow_default_constructible::value) + : Allocator(), m_size() + {} + + //Constructor, does not throw + template + explicit vector_alloc_holder(BOOST_FWD_REF(AllocConvertible) a) BOOST_NOEXCEPT_OR_NOTHROW + : Allocator(boost::forward(a)), m_size() + {} + + //Constructor, does not throw + template + vector_alloc_holder(uninitialized_size_t, BOOST_FWD_REF(AllocConvertible) a, size_type initial_size) + : Allocator(boost::forward(a)) + , m_size(initial_size) //Size is initialized here... + { + //... and capacity here, so vector, must call uninitialized_xxx in the derived constructor + this->priv_first_allocation(initial_size); + } + + //Constructor, does not throw + vector_alloc_holder(uninitialized_size_t, size_type initial_size) + : Allocator() + , m_size(initial_size) //Size is initialized here... + { + //... and capacity here, so vector, must call uninitialized_xxx in the derived constructor + this->priv_first_allocation(initial_size); + } + + vector_alloc_holder(BOOST_RV_REF(vector_alloc_holder) holder) + : Allocator(BOOST_MOVE_BASE(Allocator, holder)) + , m_size(holder.m_size) //Size is initialized here so vector should only call uninitialized_xxx after this + { + ::boost::container::uninitialized_move_alloc_n + (this->alloc(), container_detail::to_raw_pointer(holder.start()), m_size, container_detail::to_raw_pointer(this->start())); + } + + template + vector_alloc_holder(BOOST_RV_REF_BEG vector_alloc_holder BOOST_RV_REF_END holder) + : Allocator() + , m_size(holder.m_size) //Initialize it to m_size as first_allocation can only succeed or abort + { + //Different allocator type so we must check we have enough storage + const size_type n = holder.m_size; + this->priv_first_allocation(n); + ::boost::container::uninitialized_move_alloc_n + (this->alloc(), container_detail::to_raw_pointer(holder.start()), n, container_detail::to_raw_pointer(this->start())); + } + + BOOST_CONTAINER_FORCEINLINE void priv_first_allocation(size_type cap) + { + if(cap > Allocator::internal_capacity){ + throw_bad_alloc(); + } + } + + BOOST_CONTAINER_FORCEINLINE void deep_swap(vector_alloc_holder &x) + { + this->priv_deep_swap(x); + } + + template + void deep_swap(vector_alloc_holder &x) + { + if(this->m_size > OtherAllocator::internal_capacity || x.m_size > Allocator::internal_capacity){ + throw_bad_alloc(); + } + this->priv_deep_swap(x); + } + + BOOST_CONTAINER_FORCEINLINE void swap_resources(vector_alloc_holder &) BOOST_NOEXCEPT_OR_NOTHROW + { //Containers with version 0 allocators can't be moved without moving elements one by one + throw_bad_alloc(); + } + + + BOOST_CONTAINER_FORCEINLINE void steal_resources(vector_alloc_holder &) + { //Containers with version 0 allocators can't be moved without moving elements one by one + throw_bad_alloc(); + } + + BOOST_CONTAINER_FORCEINLINE Allocator &alloc() BOOST_NOEXCEPT_OR_NOTHROW + { return *this; } + + BOOST_CONTAINER_FORCEINLINE const Allocator &alloc() const BOOST_NOEXCEPT_OR_NOTHROW + { return *this; } + + BOOST_CONTAINER_FORCEINLINE bool try_expand_fwd(size_type at_least) + { return !at_least; } + + BOOST_CONTAINER_FORCEINLINE pointer start() const BOOST_NOEXCEPT_OR_NOTHROW { return Allocator::internal_storage(); } + BOOST_CONTAINER_FORCEINLINE size_type capacity() const BOOST_NOEXCEPT_OR_NOTHROW { return Allocator::internal_capacity; } + size_type m_size; + + private: + + template + void priv_deep_swap(vector_alloc_holder &x) + { + const size_type MaxTmpStorage = sizeof(value_type)*Allocator::internal_capacity; + value_type *const first_this = container_detail::to_raw_pointer(this->start()); + value_type *const first_x = container_detail::to_raw_pointer(x.start()); + + if(this->m_size < x.m_size){ + boost::container::deep_swap_alloc_n(this->alloc(), first_this, this->m_size, first_x, x.m_size); + } + else{ + boost::container::deep_swap_alloc_n(this->alloc(), first_x, x.m_size, first_this, this->m_size); + } + boost::adl_move_swap(this->m_size, x.m_size); + } +}; + +} //namespace container_detail { + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +//! A vector is a sequence that supports random access to elements, constant +//! time insertion and removal of elements at the end, and linear time insertion +//! and removal of elements at the beginning or in the middle. The number of +//! elements in a vector may vary dynamically; memory management is automatic. +//! +//! \tparam T The type of object that is stored in the vector +//! \tparam Allocator The allocator used for all internal memory management +template ) > +class vector +{ + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + struct value_less + { + typedef typename boost::container::allocator_traits::value_type value_type; + bool operator()(const value_type &a, const value_type &b) const + { return a < b; } + }; + + typedef typename container_detail::version::type alloc_version; + typedef boost::container::container_detail::vector_alloc_holder alloc_holder_t; + alloc_holder_t m_holder; + typedef allocator_traits allocator_traits_type; + template + friend class vector; + + typedef typename allocator_traits_type::pointer pointer_impl; + typedef container_detail::vec_iterator iterator_impl; + typedef container_detail::vec_iterator const_iterator_impl; + + protected: + static bool is_propagable_from(const Allocator &from_alloc, pointer_impl p, const Allocator &to_alloc, bool const propagate_allocator) + { return alloc_holder_t::is_propagable_from(from_alloc, p, to_alloc, propagate_allocator); } + + static bool are_swap_propagable( const Allocator &l_a, pointer_impl l_p + , const Allocator &r_a, pointer_impl r_p, bool const propagate_allocator) + { return alloc_holder_t::are_swap_propagable(l_a, l_p, r_a, r_p, propagate_allocator); } + + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + public: + ////////////////////////////////////////////// + // + // types + // + ////////////////////////////////////////////// + + typedef T value_type; + typedef typename ::boost::container::allocator_traits::pointer pointer; + typedef typename ::boost::container::allocator_traits::const_pointer const_pointer; + typedef typename ::boost::container::allocator_traits::reference reference; + typedef typename ::boost::container::allocator_traits::const_reference const_reference; + typedef typename ::boost::container::allocator_traits::size_type size_type; + typedef typename ::boost::container::allocator_traits::difference_type difference_type; + typedef Allocator allocator_type; + typedef Allocator stored_allocator_type; + #if defined BOOST_CONTAINER_VECTOR_ITERATOR_IS_POINTER + typedef BOOST_CONTAINER_IMPDEF(pointer) iterator; + typedef BOOST_CONTAINER_IMPDEF(const_pointer) const_iterator; + #else + typedef BOOST_CONTAINER_IMPDEF(iterator_impl) iterator; + typedef BOOST_CONTAINER_IMPDEF(const_iterator_impl) const_iterator; + #endif + typedef BOOST_CONTAINER_IMPDEF(boost::container::reverse_iterator) reverse_iterator; + typedef BOOST_CONTAINER_IMPDEF(boost::container::reverse_iterator) const_reverse_iterator; + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + private: + BOOST_COPYABLE_AND_MOVABLE(vector) + typedef container_detail::vector_value_traits value_traits; + typedef constant_iterator cvalue_iterator; + + protected: + + BOOST_CONTAINER_FORCEINLINE void steal_resources(vector &x) + { return this->m_holder.steal_resources(x.m_holder); } + + struct initial_capacity_t{}; + template + BOOST_CONTAINER_FORCEINLINE vector(initial_capacity_t, pointer initial_memory, size_type capacity, BOOST_FWD_REF(AllocFwd) a) + : m_holder(initial_memory, capacity, ::boost::forward(a)) + {} + + BOOST_CONTAINER_FORCEINLINE vector(initial_capacity_t, pointer initial_memory, size_type capacity) + : m_holder(initial_memory, capacity) + {} + + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + public: + ////////////////////////////////////////////// + // + // construct/copy/destroy + // + ////////////////////////////////////////////// + + //! Effects: Constructs a vector taking the allocator as parameter. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + vector() BOOST_NOEXCEPT_OR_NOTHROW + : m_holder() + {} + + //! Effects: Constructs a vector taking the allocator as parameter. + //! + //! Throws: Nothing + //! + //! Complexity: Constant. + explicit vector(const allocator_type& a) BOOST_NOEXCEPT_OR_NOTHROW + : m_holder(a) + {} + + //! Effects: Constructs a vector and inserts n value initialized values. + //! + //! Throws: If allocator_type's allocation + //! throws or T's value initialization throws. + //! + //! Complexity: Linear to n. + explicit vector(size_type n) + : m_holder(container_detail::uninitialized_size, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_value_init_alloc_n + (this->m_holder.alloc(), n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts n value initialized values. + //! + //! Throws: If allocator_type's allocation + //! throws or T's value initialization throws. + //! + //! Complexity: Linear to n. + explicit vector(size_type n, const allocator_type &a) + : m_holder(container_detail::uninitialized_size, a, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_value_init_alloc_n + (this->m_holder.alloc(), n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts n default initialized values. + //! + //! Throws: If allocator_type's allocation + //! throws or T's default initialization throws. + //! + //! Complexity: Linear to n. + //! + //! Note: Non-standard extension + vector(size_type n, default_init_t) + : m_holder(container_detail::uninitialized_size, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_default_init_alloc_n + (this->m_holder.alloc(), n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts n default initialized values. + //! + //! Throws: If allocator_type's allocation + //! throws or T's default initialization throws. + //! + //! Complexity: Linear to n. + //! + //! Note: Non-standard extension + vector(size_type n, default_init_t, const allocator_type &a) + : m_holder(container_detail::uninitialized_size, a, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_default_init_alloc_n + (this->m_holder.alloc(), n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector + //! and inserts n copies of value. + //! + //! Throws: If allocator_type's allocation + //! throws or T's copy constructor throws. + //! + //! Complexity: Linear to n. + vector(size_type n, const T& value) + : m_holder(container_detail::uninitialized_size, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_fill_alloc_n + (this->m_holder.alloc(), value, n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts n copies of value. + //! + //! Throws: If allocation + //! throws or T's copy constructor throws. + //! + //! Complexity: Linear to n. + vector(size_type n, const T& value, const allocator_type& a) + : m_holder(container_detail::uninitialized_size, a, n) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + boost::container::uninitialized_fill_alloc_n + (this->m_holder.alloc(), value, n, this->priv_raw_begin()); + } + + //! Effects: Constructs a vector + //! and inserts a copy of the range [first, last) in the vector. + //! + //! Throws: If allocator_type's allocation + //! throws or T's constructor taking a dereferenced InIt throws. + //! + //! Complexity: Linear to the range [first, last). + template + vector(InIt first, InIt last + BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename container_detail::disable_if_c + < container_detail::is_convertible::value + BOOST_MOVE_I container_detail::nat >::type * = 0) + ) + : m_holder() + { this->assign(first, last); } + + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts a copy of the range [first, last) in the vector. + //! + //! Throws: If allocator_type's allocation + //! throws or T's constructor taking a dereferenced InIt throws. + //! + //! Complexity: Linear to the range [first, last). + template + vector(InIt first, InIt last, const allocator_type& a + BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename container_detail::disable_if_c + < container_detail::is_convertible::value + BOOST_MOVE_I container_detail::nat >::type * = 0) + ) + : m_holder(a) + { this->assign(first, last); } + + //! Effects: Copy constructs a vector. + //! + //! Postcondition: x == *this. + //! + //! Throws: If allocator_type's allocation + //! throws or T's copy constructor throws. + //! + //! Complexity: Linear to the elements x contains. + vector(const vector &x) + : m_holder( container_detail::uninitialized_size + , allocator_traits_type::select_on_container_copy_construction(x.m_holder.alloc()) + , x.size()) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += x.size() != 0; + #endif + ::boost::container::uninitialized_copy_alloc_n + ( this->m_holder.alloc(), x.priv_raw_begin() + , x.size(), this->priv_raw_begin()); + } + + //! Effects: Move constructor. Moves x's resources to *this. + //! + //! Throws: Nothing + //! + //! Complexity: Constant. + vector(BOOST_RV_REF(vector) x) BOOST_NOEXCEPT_OR_NOTHROW + : m_holder(boost::move(x.m_holder)) + { BOOST_STATIC_ASSERT((!allocator_traits_type::is_partially_propagable::value)); } + + #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) + //! Effects: Constructs a vector that will use a copy of allocator a + //! and inserts a copy of the range [il.begin(), il.last()) in the vector + //! + //! Throws: If T's constructor taking a dereferenced initializer_list iterator throws. + //! + //! Complexity: Linear to the range [il.begin(), il.end()). + vector(std::initializer_list il, const allocator_type& a = allocator_type()) + : m_holder(a) + { + this->assign(il.begin(), il.end()); + } + #endif + + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Effects: Move constructor. Moves x's resources to *this. + //! + //! Throws: If T's move constructor or allocation throws + //! + //! Complexity: Linear. + //! + //! Note: Non-standard extension to support static_vector + template + vector(BOOST_RV_REF_BEG vector BOOST_RV_REF_END x + , typename container_detail::enable_if_c + < container_detail::is_version::value>::type * = 0 + ) + : m_holder(boost::move(x.m_holder)) + {} + + #endif //!defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Effects: Copy constructs a vector using the specified allocator. + //! + //! Postcondition: x == *this. + //! + //! Throws: If allocation + //! throws or T's copy constructor throws. + //! + //! Complexity: Linear to the elements x contains. + vector(const vector &x, const allocator_type &a) + : m_holder(container_detail::uninitialized_size, a, x.size()) + { + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += x.size() != 0; + #endif + ::boost::container::uninitialized_copy_alloc_n_source + ( this->m_holder.alloc(), x.priv_raw_begin() + , x.size(), this->priv_raw_begin()); + } + + //! Effects: Move constructor using the specified allocator. + //! Moves x's resources to *this if a == allocator_type(). + //! Otherwise copies values from x to *this. + //! + //! Throws: If allocation or T's copy constructor throws. + //! + //! Complexity: Constant if a == x.get_allocator(), linear otherwise. + vector(BOOST_RV_REF(vector) x, const allocator_type &a) + : m_holder( container_detail::uninitialized_size, a + , is_propagable_from(x.get_stored_allocator(), x.m_holder.start(), a, true) ? 0 : x.size() + ) + { + if(is_propagable_from(x.get_stored_allocator(), x.m_holder.start(), a, true)){ + this->m_holder.steal_resources(x.m_holder); + } + else{ + const size_type n = x.size(); + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + this->num_alloc += n != 0; + #endif + ::boost::container::uninitialized_move_alloc_n_source + ( this->m_holder.alloc(), x.priv_raw_begin() + , n, this->priv_raw_begin()); + } + } + + //! Effects: Destroys the vector. All stored values are destroyed + //! and used memory is deallocated. + //! + //! Throws: Nothing. + //! + //! Complexity: Linear to the number of elements. + ~vector() BOOST_NOEXCEPT_OR_NOTHROW + { + boost::container::destroy_alloc_n + (this->get_stored_allocator(), this->priv_raw_begin(), this->m_holder.m_size); + //vector_alloc_holder deallocates the data + } + + //! Effects: Makes *this contain the same elements as x. + //! + //! Postcondition: this->size() == x.size(). *this contains a copy + //! of each of x's elements. + //! + //! Throws: If memory allocation throws or T's copy/move constructor/assignment throws. + //! + //! Complexity: Linear to the number of elements in x. + BOOST_CONTAINER_FORCEINLINE vector& operator=(BOOST_COPY_ASSIGN_REF(vector) x) + { + if (&x != this){ + this->priv_copy_assign(x); + } + return *this; + } + + #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) + //! Effects: Make *this container contains elements from il. + //! + //! Complexity: Linear to the range [il.begin(), il.end()). + BOOST_CONTAINER_FORCEINLINE vector& operator=(std::initializer_list il) + { + this->assign(il.begin(), il.end()); + return *this; + } + #endif + + //! Effects: Move assignment. All x's values are transferred to *this. + //! + //! Postcondition: x.empty(). *this contains a the elements x had + //! before the function. + //! + //! Throws: If allocator_traits_type::propagate_on_container_move_assignment + //! is false and (allocation throws or value_type's move constructor throws) + //! + //! Complexity: Constant if allocator_traits_type:: + //! propagate_on_container_move_assignment is true or + //! this->get>allocator() == x.get_allocator(). Linear otherwise. + BOOST_CONTAINER_FORCEINLINE vector& operator=(BOOST_RV_REF(vector) x) + BOOST_NOEXCEPT_IF(allocator_traits_type::propagate_on_container_move_assignment::value + || allocator_traits_type::is_always_equal::value) + { + BOOST_ASSERT(&x != this); + this->priv_move_assign(boost::move(x)); + return *this; + } + + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + + //! Effects: Move assignment. All x's values are transferred to *this. + //! + //! Postcondition: x.empty(). *this contains a the elements x had + //! before the function. + //! + //! Throws: If move constructor/assignment of T throws or allocation throws + //! + //! Complexity: Linear. + //! + //! Note: Non-standard extension to support static_vector + template + BOOST_CONTAINER_FORCEINLINE typename container_detail::enable_if_and + < vector& + , container_detail::is_version + , container_detail::is_different + >::type + operator=(BOOST_RV_REF_BEG vector BOOST_RV_REF_END x) + { + this->priv_move_assign(boost::move(x)); + return *this; + } + + //! Effects: Copy assignment. All x's values are copied to *this. + //! + //! Postcondition: x.empty(). *this contains a the elements x had + //! before the function. + //! + //! Throws: If move constructor/assignment of T throws or allocation throws + //! + //! Complexity: Linear. + //! + //! Note: Non-standard extension to support static_vector + template + BOOST_CONTAINER_FORCEINLINE typename container_detail::enable_if_and + < vector& + , container_detail::is_version + , container_detail::is_different + >::type + operator=(const vector &x) + { + this->priv_copy_assign(x); + return *this; + } + + #endif + + //! Effects: Assigns the the range [first, last) to *this. + //! + //! Throws: If memory allocation throws or T's copy/move constructor/assignment or + //! T's constructor/assignment from dereferencing InpIt throws. + //! + //! Complexity: Linear to n. + template + void assign(InIt first, InIt last + BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename container_detail::disable_if_or + < void + BOOST_MOVE_I container_detail::is_convertible + BOOST_MOVE_I container_detail::and_ + < container_detail::is_different + BOOST_MOVE_I container_detail::is_not_input_iterator + > + >::type * = 0) + ) + { + //Overwrite all elements we can from [first, last) + iterator cur = this->begin(); + const iterator end_it = this->end(); + for ( ; first != last && cur != end_it; ++cur, ++first){ + *cur = *first; + } + + if (first == last){ + //There are no more elements in the sequence, erase remaining + T* const end_pos = this->priv_raw_end(); + const size_type n = static_cast(end_pos - container_detail::iterator_to_raw_pointer(cur)); + this->priv_destroy_last_n(n); + } + else{ + //There are more elements in the range, insert the remaining ones + this->insert(this->cend(), first, last); + } + } + + #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) + //! Effects: Assigns the the range [il.begin(), il.end()) to *this. + //! + //! Throws: If memory allocation throws or + //! T's constructor from dereferencing iniializer_list iterator throws. + //! + BOOST_CONTAINER_FORCEINLINE void assign(std::initializer_list il) + { + this->assign(il.begin(), il.end()); + } + #endif + + //! Effects: Assigns the the range [first, last) to *this. + //! + //! Throws: If memory allocation throws or T's copy/move constructor/assignment or + //! T's constructor/assignment from dereferencing InpIt throws. + //! + //! Complexity: Linear to n. + template + void assign(FwdIt first, FwdIt last + BOOST_CONTAINER_DOCIGN(BOOST_MOVE_I typename container_detail::disable_if_or + < void + BOOST_MOVE_I container_detail::is_same + BOOST_MOVE_I container_detail::is_convertible + BOOST_MOVE_I container_detail::is_input_iterator + >::type * = 0) + ) + { + //For Fwd iterators the standard only requires EmplaceConstructible and assignable from *first + //so we can't do any backwards allocation + const size_type input_sz = static_cast(boost::container::iterator_distance(first, last)); + const size_type old_capacity = this->capacity(); + if(input_sz > old_capacity){ //If input range is too big, we need to reallocate + size_type real_cap = 0; + pointer reuse(this->m_holder.start()); + pointer const ret(this->m_holder.allocation_command(allocate_new|expand_fwd, input_sz, real_cap = input_sz, reuse)); + if(!reuse){ //New allocation, just emplace new values + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + pointer const old_p = this->m_holder.start(); + if(old_p){ + this->priv_destroy_all(); + this->m_holder.alloc().deallocate(old_p, old_capacity); + } + this->m_holder.start(ret); + this->m_holder.capacity(real_cap); + this->m_holder.m_size = 0; + this->priv_uninitialized_construct_at_end(first, last); + return; + } + else{ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_fwd; + #endif + this->m_holder.capacity(real_cap); + //Forward expansion, use assignment + back deletion/construction that comes later + } + } + //Overwrite all elements we can from [first, last) + iterator cur = this->begin(); + const iterator end_it = this->end(); + for ( ; first != last && cur != end_it; ++cur, ++first){ + *cur = *first; + } + + if (first == last){ + //There are no more elements in the sequence, erase remaining + this->priv_destroy_last_n(this->size() - input_sz); + } + else{ + //Uninitialized construct at end the remaining range + this->priv_uninitialized_construct_at_end(first, last); + } + } + + //! Effects: Assigns the n copies of val to *this. + //! + //! Throws: If memory allocation throws or + //! T's copy/move constructor/assignment throws. + //! + //! Complexity: Linear to n. + BOOST_CONTAINER_FORCEINLINE void assign(size_type n, const value_type& val) + { this->assign(cvalue_iterator(val, n), cvalue_iterator()); } + + //! Effects: Returns a copy of the internal allocator. + //! + //! Throws: If allocator's copy constructor throws. + //! + //! Complexity: Constant. + allocator_type get_allocator() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_holder.alloc(); } + + //! Effects: Returns a reference to the internal allocator. + //! + //! Throws: Nothing + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension. + BOOST_CONTAINER_FORCEINLINE stored_allocator_type &get_stored_allocator() BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_holder.alloc(); } + + //! Effects: Returns a reference to the internal allocator. + //! + //! Throws: Nothing + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension. + BOOST_CONTAINER_FORCEINLINE const stored_allocator_type &get_stored_allocator() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_holder.alloc(); } + + ////////////////////////////////////////////// + // + // iterators + // + ////////////////////////////////////////////// + + //! Effects: Returns an iterator to the first element contained in the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE iterator begin() BOOST_NOEXCEPT_OR_NOTHROW + { return iterator(this->m_holder.start()); } + + //! Effects: Returns a const_iterator to the first element contained in the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_iterator begin() const BOOST_NOEXCEPT_OR_NOTHROW + { return const_iterator(this->m_holder.start()); } + + //! Effects: Returns an iterator to the end of the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE iterator end() BOOST_NOEXCEPT_OR_NOTHROW + { return iterator(this->m_holder.start() + this->m_holder.m_size); } + + //! Effects: Returns a const_iterator to the end of the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_iterator end() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->cend(); } + + //! Effects: Returns a reverse_iterator pointing to the beginning + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE reverse_iterator rbegin() BOOST_NOEXCEPT_OR_NOTHROW + { return reverse_iterator(this->end()); } + + //! Effects: Returns a const_reverse_iterator pointing to the beginning + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_reverse_iterator rbegin() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->crbegin(); } + + //! Effects: Returns a reverse_iterator pointing to the end + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE reverse_iterator rend() BOOST_NOEXCEPT_OR_NOTHROW + { return reverse_iterator(this->begin()); } + + //! Effects: Returns a const_reverse_iterator pointing to the end + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_reverse_iterator rend() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->crend(); } + + //! Effects: Returns a const_iterator to the first element contained in the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_iterator cbegin() const BOOST_NOEXCEPT_OR_NOTHROW + { return const_iterator(this->m_holder.start()); } + + //! Effects: Returns a const_iterator to the end of the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_iterator cend() const BOOST_NOEXCEPT_OR_NOTHROW + { return const_iterator(this->m_holder.start() + this->m_holder.m_size); } + + //! Effects: Returns a const_reverse_iterator pointing to the beginning + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_reverse_iterator crbegin() const BOOST_NOEXCEPT_OR_NOTHROW + { return const_reverse_iterator(this->end());} + + //! Effects: Returns a const_reverse_iterator pointing to the end + //! of the reversed vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE const_reverse_iterator crend() const BOOST_NOEXCEPT_OR_NOTHROW + { return const_reverse_iterator(this->begin()); } + + ////////////////////////////////////////////// + // + // capacity + // + ////////////////////////////////////////////// + + //! Effects: Returns true if the vector contains no elements. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE bool empty() const BOOST_NOEXCEPT_OR_NOTHROW + { return !this->m_holder.m_size; } + + //! Effects: Returns the number of the elements contained in the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE size_type size() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_holder.m_size; } + + //! Effects: Returns the largest possible size of the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE size_type max_size() const BOOST_NOEXCEPT_OR_NOTHROW + { return allocator_traits_type::max_size(this->m_holder.alloc()); } + + //! Effects: Inserts or erases elements at the end such that + //! the size becomes n. New elements are value initialized. + //! + //! Throws: If memory allocation throws, or T's copy/move or value initialization throws. + //! + //! Complexity: Linear to the difference between size() and new_size. + void resize(size_type new_size) + { this->priv_resize(new_size, value_init); } + + //! Effects: Inserts or erases elements at the end such that + //! the size becomes n. New elements are default initialized. + //! + //! Throws: If memory allocation throws, or T's copy/move or default initialization throws. + //! + //! Complexity: Linear to the difference between size() and new_size. + //! + //! Note: Non-standard extension + void resize(size_type new_size, default_init_t) + { this->priv_resize(new_size, default_init); } + + //! Effects: Inserts or erases elements at the end such that + //! the size becomes n. New elements are copy constructed from x. + //! + //! Throws: If memory allocation throws, or T's copy/move constructor throws. + //! + //! Complexity: Linear to the difference between size() and new_size. + void resize(size_type new_size, const T& x) + { this->priv_resize(new_size, x); } + + //! Effects: Number of elements for which memory has been allocated. + //! capacity() is always greater than or equal to size(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + BOOST_CONTAINER_FORCEINLINE size_type capacity() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->m_holder.capacity(); } + + //! Effects: If n is less than or equal to capacity(), this call has no + //! effect. Otherwise, it is a request for allocation of additional memory. + //! If the request is successful, then capacity() is greater than or equal to + //! n; otherwise, capacity() is unchanged. In either case, size() is unchanged. + //! + //! Throws: If memory allocation allocation throws or T's copy/move constructor throws. + BOOST_CONTAINER_FORCEINLINE void reserve(size_type new_cap) + { + if (this->capacity() < new_cap){ + this->priv_reserve_no_capacity(new_cap, alloc_version()); + } + } + + //! Effects: Tries to deallocate the excess of memory created + //! with previous allocations. The size of the vector is unchanged + //! + //! Throws: If memory allocation throws, or T's copy/move constructor throws. + //! + //! Complexity: Linear to size(). + BOOST_CONTAINER_FORCEINLINE void shrink_to_fit() + { this->priv_shrink_to_fit(alloc_version()); } + + ////////////////////////////////////////////// + // + // element access + // + ////////////////////////////////////////////// + + //! Requires: !empty() + //! + //! Effects: Returns a reference to the first + //! element of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + reference front() BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(!this->empty()); + return *this->m_holder.start(); + } + + //! Requires: !empty() + //! + //! Effects: Returns a const reference to the first + //! element of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + const_reference front() const BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(!this->empty()); + return *this->m_holder.start(); + } + + //! Requires: !empty() + //! + //! Effects: Returns a reference to the last + //! element of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + reference back() BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(!this->empty()); + return this->m_holder.start()[this->m_holder.m_size - 1]; + } + + //! Requires: !empty() + //! + //! Effects: Returns a const reference to the last + //! element of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + const_reference back() const BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(!this->empty()); + return this->m_holder.start()[this->m_holder.m_size - 1]; + } + + //! Requires: size() > n. + //! + //! Effects: Returns a reference to the nth element + //! from the beginning of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + reference operator[](size_type n) BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(this->m_holder.m_size > n); + return this->m_holder.start()[n]; + } + + //! Requires: size() > n. + //! + //! Effects: Returns a const reference to the nth element + //! from the beginning of the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + const_reference operator[](size_type n) const BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(this->m_holder.m_size > n); + return this->m_holder.start()[n]; + } + + //! Requires: size() >= n. + //! + //! Effects: Returns an iterator to the nth element + //! from the beginning of the container. Returns end() + //! if n == size(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension + iterator nth(size_type n) BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(this->m_holder.m_size >= n); + return iterator(this->m_holder.start()+n); + } + + //! Requires: size() >= n. + //! + //! Effects: Returns a const_iterator to the nth element + //! from the beginning of the container. Returns end() + //! if n == size(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension + const_iterator nth(size_type n) const BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(this->m_holder.m_size >= n); + return const_iterator(this->m_holder.start()+n); + } + + //! Requires: size() >= n. + //! + //! Effects: Returns an iterator to the nth element + //! from the beginning of the container. Returns end() + //! if n == size(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension + size_type index_of(iterator p) BOOST_NOEXCEPT_OR_NOTHROW + { + //Range check assert done in priv_index_of + return this->priv_index_of(vector_iterator_get_ptr(p)); + } + + //! Requires: begin() <= p <= end(). + //! + //! Effects: Returns the index of the element pointed by p + //! and size() if p == end(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + //! + //! Note: Non-standard extension + size_type index_of(const_iterator p) const BOOST_NOEXCEPT_OR_NOTHROW + { + //Range check assert done in priv_index_of + return this->priv_index_of(vector_iterator_get_ptr(p)); + } + + //! Requires: size() > n. + //! + //! Effects: Returns a reference to the nth element + //! from the beginning of the container. + //! + //! Throws: std::range_error if n >= size() + //! + //! Complexity: Constant. + reference at(size_type n) + { + this->priv_throw_if_out_of_range(n); + return this->m_holder.start()[n]; + } + + //! Requires: size() > n. + //! + //! Effects: Returns a const reference to the nth element + //! from the beginning of the container. + //! + //! Throws: std::range_error if n >= size() + //! + //! Complexity: Constant. + const_reference at(size_type n) const + { + this->priv_throw_if_out_of_range(n); + return this->m_holder.start()[n]; + } + + ////////////////////////////////////////////// + // + // data access + // + ////////////////////////////////////////////// + + //! Returns: A pointer such that [data(),data() + size()) is a valid range. + //! For a non-empty vector, data() == &front(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + T* data() BOOST_NOEXCEPT_OR_NOTHROW + { return this->priv_raw_begin(); } + + //! Returns: A pointer such that [data(),data() + size()) is a valid range. + //! For a non-empty vector, data() == &front(). + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + const T * data() const BOOST_NOEXCEPT_OR_NOTHROW + { return this->priv_raw_begin(); } + + ////////////////////////////////////////////// + // + // modifiers + // + ////////////////////////////////////////////// + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + //! Effects: Inserts an object of type T constructed with + //! std::forward(args)... in the end of the vector. + //! + //! Throws: If memory allocation throws or the in-place constructor throws or + //! T's copy/move constructor throws. + //! + //! Complexity: Amortized constant time. + template + BOOST_CONTAINER_FORCEINLINE void emplace_back(BOOST_FWD_REF(Args)...args) + { + if (BOOST_LIKELY(this->room_enough())){ + //There is more memory, just construct a new object at the end + allocator_traits_type::construct(this->m_holder.alloc(), this->priv_raw_end(), ::boost::forward(args)...); + ++this->m_holder.m_size; + } + else{ + typedef container_detail::insert_emplace_proxy type; + this->priv_forward_range_insert_no_capacity + (this->back_ptr(), 1, type(::boost::forward(args)...), alloc_version()); + } + } + + //! Effects: Inserts an object of type T constructed with + //! std::forward(args)... in the end of the vector. + //! + //! Throws: If the in-place constructor throws. + //! + //! Complexity: Constant time. + //! + //! Note: Non-standard extension. + template + BOOST_CONTAINER_FORCEINLINE bool stable_emplace_back(BOOST_FWD_REF(Args)...args) + { + const bool is_room_enough = this->room_enough() || (alloc_version::value == 2 && this->m_holder.try_expand_fwd(1u)); + if (BOOST_LIKELY(is_room_enough)){ + //There is more memory, just construct a new object at the end + allocator_traits_type::construct(this->m_holder.alloc(), this->priv_raw_end(), ::boost::forward(args)...); + ++this->m_holder.m_size; + } + return is_room_enough; + } + + //! Requires: position must be a valid iterator of *this. + //! + //! Effects: Inserts an object of type T constructed with + //! std::forward(args)... before position + //! + //! Throws: If memory allocation throws or the in-place constructor throws or + //! T's copy/move constructor/assignment throws. + //! + //! Complexity: If position is end(), amortized constant time + //! Linear time otherwise. + template + iterator emplace(const_iterator position, BOOST_FWD_REF(Args) ...args) + { + BOOST_ASSERT(this->priv_in_range_or_end(position)); + //Just call more general insert(pos, size, value) and return iterator + typedef container_detail::insert_emplace_proxy type; + return this->priv_forward_range_insert( vector_iterator_get_ptr(position), 1 + , type(::boost::forward(args)...)); + } + + #else // !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + #define BOOST_CONTAINER_VECTOR_EMPLACE_CODE(N) \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + BOOST_CONTAINER_FORCEINLINE void emplace_back(BOOST_MOVE_UREF##N)\ + {\ + if (BOOST_LIKELY(this->room_enough())){\ + allocator_traits_type::construct (this->m_holder.alloc()\ + , this->priv_raw_end() BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\ + ++this->m_holder.m_size;\ + }\ + else{\ + typedef container_detail::insert_emplace_proxy_arg##N type;\ + this->priv_forward_range_insert_no_capacity\ + ( this->back_ptr(), 1, type(BOOST_MOVE_FWD##N), alloc_version());\ + }\ + }\ + \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + BOOST_CONTAINER_FORCEINLINE bool stable_emplace_back(BOOST_MOVE_UREF##N)\ + {\ + const bool is_room_enough = this->room_enough() || (alloc_version::value == 2 && this->m_holder.try_expand_fwd(1u));\ + if (BOOST_LIKELY(is_room_enough)){\ + allocator_traits_type::construct (this->m_holder.alloc()\ + , this->priv_raw_end() BOOST_MOVE_I##N BOOST_MOVE_FWD##N);\ + ++this->m_holder.m_size;\ + }\ + return is_room_enough;\ + }\ + \ + BOOST_MOVE_TMPL_LT##N BOOST_MOVE_CLASS##N BOOST_MOVE_GT##N \ + iterator emplace(const_iterator pos BOOST_MOVE_I##N BOOST_MOVE_UREF##N)\ + {\ + BOOST_ASSERT(this->priv_in_range_or_end(pos));\ + typedef container_detail::insert_emplace_proxy_arg##N type;\ + return this->priv_forward_range_insert(vector_iterator_get_ptr(pos), 1, type(BOOST_MOVE_FWD##N));\ + }\ + // + BOOST_MOVE_ITERATE_0TO9(BOOST_CONTAINER_VECTOR_EMPLACE_CODE) + #undef BOOST_CONTAINER_VECTOR_EMPLACE_CODE + + #endif + + #if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + //! Effects: Inserts a copy of x at the end of the vector. + //! + //! Throws: If memory allocation throws or + //! T's copy/move constructor throws. + //! + //! Complexity: Amortized constant time. + void push_back(const T &x); + + //! Effects: Constructs a new element in the end of the vector + //! and moves the resources of x to this new element. + //! + //! Throws: If memory allocation throws or + //! T's copy/move constructor throws. + //! + //! Complexity: Amortized constant time. + void push_back(T &&x); + #else + BOOST_CONTAINER_FORCEINLINE BOOST_MOVE_CONVERSION_AWARE_CATCH(push_back, T, void, priv_push_back) + #endif + + #if defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + //! Requires: position must be a valid iterator of *this. + //! + //! Effects: Insert a copy of x before position. + //! + //! Throws: If memory allocation throws or T's copy/move constructor/assignment throws. + //! + //! Complexity: If position is end(), amortized constant time + //! Linear time otherwise. + iterator insert(const_iterator position, const T &x); + + //! Requires: position must be a valid iterator of *this. + //! + //! Effects: Insert a new element before position with x's resources. + //! + //! Throws: If memory allocation throws. + //! + //! Complexity: If position is end(), amortized constant time + //! Linear time otherwise. + iterator insert(const_iterator position, T &&x); + #else + BOOST_MOVE_CONVERSION_AWARE_CATCH_1ARG(insert, T, iterator, priv_insert, const_iterator, const_iterator) + #endif + + //! Requires: p must be a valid iterator of *this. + //! + //! Effects: Insert n copies of x before pos. + //! + //! Returns: an iterator to the first inserted element or p if n is 0. + //! + //! Throws: If memory allocation throws or T's copy/move constructor throws. + //! + //! Complexity: Linear to n. + iterator insert(const_iterator p, size_type n, const T& x) + { + BOOST_ASSERT(this->priv_in_range_or_end(p)); + container_detail::insert_n_copies_proxy proxy(x); + return this->priv_forward_range_insert(vector_iterator_get_ptr(p), n, proxy); + } + + //! Requires: p must be a valid iterator of *this. + //! + //! Effects: Insert a copy of the [first, last) range before pos. + //! + //! Returns: an iterator to the first inserted element or pos if first == last. + //! + //! Throws: If memory allocation throws, T's constructor from a + //! dereferenced InpIt throws or T's copy/move constructor/assignment throws. + //! + //! Complexity: Linear to boost::container::iterator_distance [first, last). + template + iterator insert(const_iterator pos, InIt first, InIt last + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + , typename container_detail::disable_if_or + < void + , container_detail::is_convertible + , container_detail::is_not_input_iterator + >::type * = 0 + #endif + ) + { + BOOST_ASSERT(this->priv_in_range_or_end(pos)); + const size_type n_pos = pos - this->cbegin(); + iterator it(vector_iterator_get_ptr(pos)); + for(;first != last; ++first){ + it = this->emplace(it, *first); + ++it; + } + return iterator(this->m_holder.start() + n_pos); + } + + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + template + iterator insert(const_iterator pos, FwdIt first, FwdIt last + , typename container_detail::disable_if_or + < void + , container_detail::is_convertible + , container_detail::is_input_iterator + >::type * = 0 + ) + { + BOOST_ASSERT(this->priv_in_range_or_end(pos)); + container_detail::insert_range_proxy proxy(first); + return this->priv_forward_range_insert(vector_iterator_get_ptr(pos), boost::container::iterator_distance(first, last), proxy); + } + #endif + + //! Requires: p must be a valid iterator of *this. num, must + //! be equal to boost::container::iterator_distance(first, last) + //! + //! Effects: Insert a copy of the [first, last) range before pos. + //! + //! Returns: an iterator to the first inserted element or pos if first == last. + //! + //! Throws: If memory allocation throws, T's constructor from a + //! dereferenced InpIt throws or T's copy/move constructor/assignment throws. + //! + //! Complexity: Linear to boost::container::iterator_distance [first, last). + //! + //! Note: This function avoids a linear operation to calculate boost::container::iterator_distance[first, last) + //! for forward and bidirectional iterators, and a one by one insertion for input iterators. This is a + //! a non-standard extension. + #if !defined(BOOST_CONTAINER_DOXYGEN_INVOKED) + template + iterator insert(const_iterator pos, size_type num, InIt first, InIt last) + { + BOOST_ASSERT(this->priv_in_range_or_end(pos)); + BOOST_ASSERT(container_detail::is_input_iterator::value || + num == static_cast(boost::container::iterator_distance(first, last))); + (void)last; + container_detail::insert_range_proxy proxy(first); + return this->priv_forward_range_insert(vector_iterator_get_ptr(pos), num, proxy); + } + #endif + + #if !defined(BOOST_NO_CXX11_HDR_INITIALIZER_LIST) + //! Requires: position must be a valid iterator of *this. + //! + //! Effects: Insert a copy of the [il.begin(), il.end()) range before position. + //! + //! Returns: an iterator to the first inserted element or position if first == last. + //! + //! Complexity: Linear to the range [il.begin(), il.end()). + iterator insert(const_iterator position, std::initializer_list il) + { + //Assertion done in insert() + return this->insert(position, il.begin(), il.end()); + } + #endif + + //! Effects: Removes the last element from the container. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant time. + void pop_back() BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(!this->empty()); + //Destroy last element + this->priv_destroy_last(); + } + + //! Effects: Erases the element at position pos. + //! + //! Throws: Nothing. + //! + //! Complexity: Linear to the elements between pos and the + //! last element. Constant if pos is the last element. + iterator erase(const_iterator position) + { + BOOST_ASSERT(this->priv_in_range(position)); + const pointer p = vector_iterator_get_ptr(position); + T *const pos_ptr = container_detail::to_raw_pointer(p); + T *const beg_ptr = this->priv_raw_begin(); + T *const new_end_ptr = ::boost::container::move(pos_ptr + 1, beg_ptr + this->m_holder.m_size, pos_ptr); + //Move elements forward and destroy last + this->priv_destroy_last(pos_ptr == new_end_ptr); + return iterator(p); + } + + //! Effects: Erases the elements pointed by [first, last). + //! + //! Throws: Nothing. + //! + //! Complexity: Linear to the distance between first and last + //! plus linear to the elements between pos and the last element. + iterator erase(const_iterator first, const_iterator last) + { + BOOST_ASSERT(first == last || + (first < last && this->priv_in_range(first) && this->priv_in_range_or_end(last))); + if (first != last){ + T* const old_end_ptr = this->priv_raw_end(); + T* const first_ptr = container_detail::to_raw_pointer(vector_iterator_get_ptr(first)); + T* const last_ptr = container_detail::to_raw_pointer(vector_iterator_get_ptr(last)); + T* const ptr = container_detail::to_raw_pointer(boost::container::move(last_ptr, old_end_ptr, first_ptr)); + this->priv_destroy_last_n(old_end_ptr - ptr); + } + return iterator(vector_iterator_get_ptr(first)); + } + + //! Effects: Swaps the contents of *this and x. + //! + //! Throws: Nothing. + //! + //! Complexity: Constant. + void swap(vector& x) + BOOST_NOEXCEPT_IF( ((allocator_traits_type::propagate_on_container_swap::value + || allocator_traits_type::is_always_equal::value) && + !container_detail::is_version::value)) + { + this->priv_swap(x, container_detail::bool_::value>()); + } + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + //! Effects: Swaps the contents of *this and x. + //! + //! Throws: Nothing. + //! + //! Complexity: Linear + //! + //! Note: Non-standard extension to support static_vector + template + void swap(vector & x + , typename container_detail::enable_if_and + < void + , container_detail::is_version + , container_detail::is_different + >::type * = 0 + ) + { this->m_holder.deep_swap(x.m_holder); } + + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + + //! Effects: Erases all the elements of the vector. + //! + //! Throws: Nothing. + //! + //! Complexity: Linear to the number of elements in the container. + void clear() BOOST_NOEXCEPT_OR_NOTHROW + { this->priv_destroy_all(); } + + //! Effects: Returns true if x and y are equal + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator==(const vector& x, const vector& y) + { return x.size() == y.size() && ::boost::container::algo_equal(x.begin(), x.end(), y.begin()); } + + //! Effects: Returns true if x and y are unequal + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator!=(const vector& x, const vector& y) + { return !(x == y); } + + //! Effects: Returns true if x is less than y + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator<(const vector& x, const vector& y) + { + const_iterator first1(x.cbegin()), first2(y.cbegin()); + const const_iterator last1(x.cend()), last2(y.cend()); + for ( ; (first1 != last1) && (first2 != last2); ++first1, ++first2 ) { + if (*first1 < *first2) return true; + if (*first2 < *first1) return false; + } + return (first1 == last1) && (first2 != last2); + } + + //! Effects: Returns true if x is greater than y + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator>(const vector& x, const vector& y) + { return y < x; } + + //! Effects: Returns true if x is equal or less than y + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator<=(const vector& x, const vector& y) + { return !(y < x); } + + //! Effects: Returns true if x is equal or greater than y + //! + //! Complexity: Linear to the number of elements in the container. + friend bool operator>=(const vector& x, const vector& y) + { return !(x < y); } + + //! Effects: x.swap(y) + //! + //! Complexity: Constant. + friend void swap(vector& x, vector& y) + { x.swap(y); } + + #ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + //! Effects: If n is less than or equal to capacity(), this call has no + //! effect. Otherwise, it is a request for allocation of additional memory + //! (memory expansion) that will not invalidate iterators. + //! If the request is successful, then capacity() is greater than or equal to + //! n; otherwise, capacity() is unchanged. In either case, size() is unchanged. + //! + //! Throws: If memory allocation allocation throws or T's copy/move constructor throws. + //! + //! Note: Non-standard extension. + bool stable_reserve(size_type new_cap) + { + const size_type cp = this->capacity(); + return cp >= new_cap || (alloc_version::value == 2 && this->m_holder.try_expand_fwd(new_cap - cp)); + } + + //Absolutely experimental. This function might change, disappear or simply crash! + template + void insert_ordered_at(const size_type element_count, BiDirPosConstIt last_position_it, BiDirValueIt last_value_it) + { + typedef container_detail::vector_insert_ordered_cursor inserter_t; + return this->priv_insert_ordered_at(element_count, inserter_t(last_position_it, last_value_it)); + } + + template + void merge(BidirIt first, BidirIt last) + { this->merge(first, last, value_less()); } + + template + void merge(BidirIt first, BidirIt last, Compare comp) + { this->priv_merge(container_detail::false_type(), first, last, comp); } + + template + void merge_unique(BidirIt first, BidirIt last) + { this->priv_merge(container_detail::true_type(), first, last, value_less()); } + + template + void merge_unique(BidirIt first, BidirIt last, Compare comp) + { this->priv_merge(container_detail::true_type(), first, last, comp); } + + private: + template + void priv_insert_ordered_at(const size_type element_count, PositionValue position_value) + { + const size_type old_size_pos = this->size(); + this->reserve(old_size_pos + element_count); + T* const begin_ptr = this->priv_raw_begin(); + size_type insertions_left = element_count; + size_type prev_pos = old_size_pos; + size_type old_hole_size = element_count; + + //Exception rollback. If any copy throws before the hole is filled, values + //already inserted/copied at the end of the buffer will be destroyed. + typename value_traits::ArrayDestructor past_hole_values_destroyer + (begin_ptr + old_size_pos + element_count, this->m_holder.alloc(), size_type(0u)); + //Loop for each insertion backwards, first moving the elements after the insertion point, + //then inserting the element. + while(insertions_left){ + --position_value; + size_type const pos = position_value.get_pos(); + BOOST_ASSERT(pos != size_type(-1) && pos <= old_size_pos && pos <= prev_pos); + //If needed shift the range after the insertion point and the previous insertion point. + //Function will take care if the shift crosses the size() boundary, using copy/move + //or uninitialized copy/move if necessary. + size_type new_hole_size = (pos != prev_pos) + ? priv_insert_ordered_at_shift_range(pos, prev_pos, this->size(), insertions_left) + : old_hole_size + ; + if(new_hole_size){ + //The hole was reduced by priv_insert_ordered_at_shift_range so expand exception rollback range backwards + past_hole_values_destroyer.increment_size_backwards(prev_pos - pos); + //Insert the new value in the hole + allocator_traits_type::construct(this->m_holder.alloc(), begin_ptr + pos + insertions_left - 1, position_value.get_val()); + if(--new_hole_size){ + //The hole was reduced by the new insertion by one + past_hole_values_destroyer.increment_size_backwards(size_type(1u)); + } + else{ + //Hole was just filled, disable exception rollback and change vector size + past_hole_values_destroyer.release(); + this->m_holder.m_size += element_count; + } + } + else{ + if(old_hole_size){ + //Hole was just filled by priv_insert_ordered_at_shift_range, disable exception rollback and change vector size + past_hole_values_destroyer.release(); + this->m_holder.m_size += element_count; + } + //Insert the new value in the already constructed range + begin_ptr[pos + insertions_left - 1] = position_value.get_val(); + } + --insertions_left; + old_hole_size = new_hole_size; + prev_pos = pos; + } + } + + template + void priv_merge(UniqueBool, BidirIt first, BidirIt last, Compare comp) + { + size_type const n = static_cast(boost::container::iterator_distance(first, last)); + size_type const s = this->size(); + if(BOOST_LIKELY(s)){ + size_type const c = this->capacity(); + size_type const free_c = (c - s); + //Use a new buffer if current one is too small for new elements, + //or there is no room for position indexes + if(free_c < n){ + size_type const new_size = s + n; + size_type new_cap = new_size; + pointer p = pointer(); + p = this->m_holder.allocation_command(allocate_new, new_size, new_cap, p); + this->priv_merge_in_new_buffer(UniqueBool(), first, n, comp, p, new_cap); + } + else if(!UniqueBool::value && free_c >= n){ + typedef container_detail::vector_merge_cursor inserter_t; + T* const pbeg = this->priv_raw_begin(); + return this->priv_insert_ordered_at(n, inserter_t(pbeg, pbeg + s, last, comp)); + } + else{ //UniqueBool::value == true and free_c >= n + std::size_t remaining = n; + static const std::size_t PosCount = 64u; + size_type positions[PosCount]; + size_type *indexes = 0; + while(remaining){ + //Query for room to store indexes in the remaining buffer + uintptr_t const szt_align_mask = container_detail::alignment_of::value - 1; + boost::uintptr_t const addr = boost::uintptr_t(this->priv_raw_begin() + s + n); + boost::uintptr_t const capaddr = boost::uintptr_t(this->priv_raw_begin() + c); + boost::uintptr_t const aligned_addr = (addr + szt_align_mask) & ~szt_align_mask; + indexes = reinterpret_cast(aligned_addr); + std::size_t index_capacity = (aligned_addr >= capaddr) ? 0u : (capaddr - addr)/sizeof(size_type); + + //Capacity is constant, we're not going to change it + if(index_capacity < PosCount){ + indexes = positions; + index_capacity = PosCount; + } + if(index_capacity > remaining) + index_capacity = remaining; + BidirIt limit = first; + boost::container::iterator_advance(limit, index_capacity); + this->priv_insert_ordered_range(UniqueBool(), index_capacity, first, limit, indexes, comp); + first = limit; + remaining -= index_capacity; + } + } + } + else{ + this->insert(this->cend(), n, first, last); + } + } + + template + void priv_insert_ordered_range + (UniqueBool, size_type const n, BidirIt first, BidirIt const last, size_type positions[], Compare comp) + { + //Linear: at most N + M -1 comparisons + //Log: MlogN + //Average + //Linear: N + M - 2 + //Log: MlogN + //N+M - 2 + //N + //(N+M)/2 < MlogN + //(N/M+1)/2 <= logN + //bool const linear = !s || !n || (s <= n) || ((s+n)/n/2 < logN); + size_type const s = this->size(); + size_type remaining = n; + T* const pbeg = this->priv_raw_begin(); + T* const pend = pbeg + s; + T* pcur = pbeg; + size_type *position = positions; + size_type added_in_middle = 0; + if(first != last && pcur != pend){ + while(1){ + //maintain stability moving external values only if they are strictly less + if(comp(*first, *pcur)) { + *position = static_cast(pcur - pbeg); + BOOST_ASSERT((position == positions) || (*(position-1) == size_type(-1)) || (*(position-1) <= *position)); + ++position; + ++added_in_middle; + --remaining; + if(++first == last) break; + } + else if(UniqueBool::value && !comp(*pcur, *first)){ + *position = size_type(-1); + ++position; + --remaining; + if(++first == last) break; + } + else{ + if(++pcur == pend) break; + } + } + } + this->insert_ordered_at(added_in_middle, position, first); + this->insert(this->cend(), remaining, first, last); + } + + template + void priv_merge_in_new_buffer + (UniqueBool, FwdIt first, size_type n, Compare comp, pointer new_storage, size_type const new_cap) + { + BOOST_ASSERT((new_cap >= this->size() ) && (new_cap - this->size()) >= n); + allocator_type &a = this->m_holder.alloc(); + typename value_traits::ArrayDeallocator new_buffer_deallocator(new_storage, a, new_cap); + typename value_traits::ArrayDestructor new_values_destroyer(new_storage, a, 0u); + T* pbeg = this->priv_raw_begin(); + size_type const old_size = this->size(); + T* const pend = pbeg + old_size; + T* d_first = container_detail::to_raw_pointer(new_storage); + size_type added = n; + //Merge in new buffer loop + while(1){ + if(!n) { + ::boost::container::uninitialized_move_alloc(this->m_holder.alloc(), pbeg, pend, d_first); + break; + } + else if(pbeg == pend) { + ::boost::container::uninitialized_move_alloc_n(this->m_holder.alloc(), first, n, d_first); + break; + } + //maintain stability moving external values only if they are strictly less + else if(comp(*first, *pbeg)) { + allocator_traits_type::construct( this->m_holder.alloc(), d_first, ::boost::move(*first) ); + new_values_destroyer.increment_size(1u); + ++first; + --n; + ++d_first; + } + else if(UniqueBool::value && !comp(*pbeg, *first)){ + ++first; + --n; + --added; + } + else{ + allocator_traits_type::construct( this->m_holder.alloc(), d_first, ::boost::move(*pbeg) ); + new_values_destroyer.increment_size(1u); + ++pbeg; + ++d_first; + } + } + + //Nothrow operations + pointer const old_p = this->m_holder.start(); + size_type const old_cap = this->m_holder.capacity(); + boost::container::destroy_alloc_n(a, container_detail::to_raw_pointer(old_p), old_size); + a.deallocate(old_p, old_cap); + this->m_holder.m_size = old_size + added; + this->m_holder.start(new_storage); + this->m_holder.capacity(new_cap); + new_buffer_deallocator.release(); + new_values_destroyer.release(); + } + + bool room_enough() const + { return this->m_holder.m_size < this->m_holder.capacity(); } + + pointer back_ptr() const + { return this->m_holder.start() + this->m_holder.m_size; } + + size_type priv_index_of(pointer p) const + { + BOOST_ASSERT(this->m_holder.start() <= p); + BOOST_ASSERT(p <= (this->m_holder.start()+this->size())); + return static_cast(p - this->m_holder.start()); + } + + template + void priv_move_assign(BOOST_RV_REF_BEG vector BOOST_RV_REF_END x + , typename container_detail::enable_if_c + < container_detail::is_version::value >::type * = 0) + { + if(!container_detail::is_same::value && + this->capacity() < x.size()){ + throw_bad_alloc(); + } + T* const this_start = this->priv_raw_begin(); + T* const other_start = x.priv_raw_begin(); + const size_type this_sz = m_holder.m_size; + const size_type other_sz = static_cast(x.m_holder.m_size); + boost::container::move_assign_range_alloc_n(this->m_holder.alloc(), other_start, other_sz, this_start, this_sz); + this->m_holder.m_size = other_sz; + } + + template + void priv_move_assign(BOOST_RV_REF_BEG vector BOOST_RV_REF_END x + , typename container_detail::disable_if_or + < void + , container_detail::is_version + , container_detail::is_different + >::type * = 0) + { + //for move assignment, no aliasing (&x != this) is assummed. + BOOST_ASSERT(this != &x); + allocator_type &this_alloc = this->m_holder.alloc(); + allocator_type &x_alloc = x.m_holder.alloc(); + const bool propagate_alloc = allocator_traits_type::propagate_on_container_move_assignment::value; + + const bool is_propagable_from_x = is_propagable_from(x_alloc, x.m_holder.start(), this_alloc, propagate_alloc); + const bool is_propagable_from_t = is_propagable_from(this_alloc, m_holder.start(), x_alloc, propagate_alloc); + const bool are_both_propagable = is_propagable_from_x && is_propagable_from_t; + + //Resources can be transferred if both allocators are + //going to be equal after this function (either propagated or already equal) + if(are_both_propagable){ + //Destroy objects but retain memory in case x reuses it in the future + this->clear(); + this->m_holder.swap_resources(x.m_holder); + } + else if(is_propagable_from_x){ + this->clear(); + this->m_holder.alloc().deallocate(this->m_holder.m_start, this->m_holder.m_capacity); + this->m_holder.steal_resources(x.m_holder); + } + //Else do a one by one move + else{ + this->assign( boost::make_move_iterator(container_detail::iterator_to_raw_pointer(x.begin())) + , boost::make_move_iterator(container_detail::iterator_to_raw_pointer(x.end() )) + ); + } + //Move allocator if needed + container_detail::move_alloc(this_alloc, x_alloc, container_detail::bool_()); + } + + template + void priv_copy_assign(const vector &x + , typename container_detail::enable_if_c + < container_detail::is_version::value >::type * = 0) + { + if(!container_detail::is_same::value && + this->capacity() < x.size()){ + throw_bad_alloc(); + } + T* const this_start = this->priv_raw_begin(); + T* const other_start = x.priv_raw_begin(); + const size_type this_sz = m_holder.m_size; + const size_type other_sz = static_cast(x.m_holder.m_size); + boost::container::copy_assign_range_alloc_n(this->m_holder.alloc(), other_start, other_sz, this_start, this_sz); + this->m_holder.m_size = other_sz; + } + + template + typename container_detail::disable_if_or + < void + , container_detail::is_version + , container_detail::is_different + >::type + priv_copy_assign(const vector &x) + { + allocator_type &this_alloc = this->m_holder.alloc(); + const allocator_type &x_alloc = x.m_holder.alloc(); + container_detail::bool_ flag; + if(flag && this_alloc != x_alloc){ + this->clear(); + this->shrink_to_fit(); + } + container_detail::assign_alloc(this_alloc, x_alloc, flag); + this->assign( x.priv_raw_begin(), x.priv_raw_end() ); + } + + template //Template it to avoid it in explicit instantiations + void priv_swap(Vector &x, container_detail::true_type) //version_0 + { this->m_holder.deep_swap(x.m_holder); } + + template //Template it to avoid it in explicit instantiations + void priv_swap(Vector &x, container_detail::false_type) //version_N + { + const bool propagate_alloc = allocator_traits_type::propagate_on_container_swap::value; + if(are_swap_propagable( this->get_stored_allocator(), this->m_holder.start() + , x.get_stored_allocator(), x.m_holder.start(), propagate_alloc)){ + //Just swap internals + this->m_holder.swap_resources(x.m_holder); + } + else{ + //Else swap element by element... + bool const t_smaller = this->size() < x.size(); + vector &sml = t_smaller ? *this : x; + vector &big = t_smaller ? x : *this; + + size_type const common_elements = sml.size(); + for(size_type i = 0; i != common_elements; ++i){ + boost::adl_move_swap(sml[i], big[i]); + } + //... and move-insert the remaining range + sml.insert( sml.cend() + , boost::make_move_iterator(container_detail::iterator_to_raw_pointer(big.nth(common_elements))) + , boost::make_move_iterator(container_detail::iterator_to_raw_pointer(big.end())) + ); + //Destroy remaining elements + big.erase(big.nth(common_elements), big.cend()); + } + //And now swap the allocator + container_detail::swap_alloc(this->m_holder.alloc(), x.m_holder.alloc(), container_detail::bool_()); + } + + void priv_reserve_no_capacity(size_type, version_0) + { throw_bad_alloc(); } + + container_detail::insert_range_proxy, T*> priv_dummy_empty_proxy() + { + return container_detail::insert_range_proxy, T*> + (::boost::make_move_iterator((T *)0)); + } + + void priv_reserve_no_capacity(size_type new_cap, version_1) + { + //There is not enough memory, allocate a new buffer + //Pass the hint so that allocators can take advantage of this. + pointer const p = allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start); + //We will reuse insert code, so create a dummy input iterator + this->priv_forward_range_insert_new_allocation + ( container_detail::to_raw_pointer(p), new_cap, this->priv_raw_end(), 0, this->priv_dummy_empty_proxy()); + } + + void priv_reserve_no_capacity(size_type new_cap, version_2) + { + //There is not enough memory, allocate a new + //buffer or expand the old one. + bool same_buffer_start; + size_type real_cap = 0; + pointer reuse = 0; + pointer const ret(this->m_holder.allocation_command(allocate_new | expand_fwd | expand_bwd, new_cap, real_cap = new_cap, reuse)); + + //Check for forward expansion + same_buffer_start = reuse && this->m_holder.start() == ret; + if(same_buffer_start){ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_fwd; + #endif + this->m_holder.capacity(real_cap); + } + else{ //If there is no forward expansion, move objects, we will reuse insertion code + T * const new_mem = container_detail::to_raw_pointer(ret); + T * const ins_pos = this->priv_raw_end(); + if(reuse){ //Backwards (and possibly forward) expansion + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_bwd; + #endif + this->priv_forward_range_insert_expand_backwards + ( new_mem , real_cap, ins_pos, 0, this->priv_dummy_empty_proxy()); + } + else{ //New buffer + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + this->priv_forward_range_insert_new_allocation + ( new_mem, real_cap, ins_pos, 0, this->priv_dummy_empty_proxy()); + } + } + } + + void priv_destroy_last(const bool moved = false) BOOST_NOEXCEPT_OR_NOTHROW + { + (void)moved; + if(!(value_traits::trivial_dctr || (value_traits::trivial_dctr_after_move && moved))){ + value_type* const p = this->priv_raw_end() - 1; + allocator_traits_type::destroy(this->get_stored_allocator(), p); + } + --this->m_holder.m_size; + } + + void priv_destroy_last_n(const size_type n) BOOST_NOEXCEPT_OR_NOTHROW + { + BOOST_ASSERT(n <= this->m_holder.m_size); + if(!value_traits::trivial_dctr){ + T* const destroy_pos = this->priv_raw_begin() + (this->m_holder.m_size-n); + boost::container::destroy_alloc_n(this->get_stored_allocator(), destroy_pos, n); + } + this->m_holder.m_size -= n; + } + + template + void priv_uninitialized_construct_at_end(InpIt first, InpIt last) + { + T* const old_end_pos = this->priv_raw_end(); + T* const new_end_pos = boost::container::uninitialized_copy_alloc(this->m_holder.alloc(), first, last, old_end_pos); + this->m_holder.m_size += new_end_pos - old_end_pos; + } + + void priv_destroy_all() BOOST_NOEXCEPT_OR_NOTHROW + { + boost::container::destroy_alloc_n + (this->get_stored_allocator(), this->priv_raw_begin(), this->m_holder.m_size); + this->m_holder.m_size = 0; + } + + template + iterator priv_insert(const const_iterator &p, BOOST_FWD_REF(U) x) + { + BOOST_ASSERT(this->priv_in_range_or_end(p)); + return this->priv_forward_range_insert + ( vector_iterator_get_ptr(p), 1, container_detail::get_insert_value_proxy(::boost::forward(x))); + } + + container_detail::insert_copy_proxy priv_single_insert_proxy(const T &x) + { return container_detail::insert_copy_proxy (x); } + + container_detail::insert_move_proxy priv_single_insert_proxy(BOOST_RV_REF(T) x) + { return container_detail::insert_move_proxy (x); } + + template + void priv_push_back(BOOST_FWD_REF(U) u) + { + if (BOOST_LIKELY(this->room_enough())){ + //There is more memory, just construct a new object at the end + allocator_traits_type::construct + ( this->m_holder.alloc(), this->priv_raw_end(), ::boost::forward(u) ); + ++this->m_holder.m_size; + } + else{ + this->priv_forward_range_insert_no_capacity + ( this->back_ptr(), 1 + , this->priv_single_insert_proxy(::boost::forward(u)), alloc_version()); + } + } + + container_detail::insert_n_copies_proxy priv_resize_proxy(const T &x) + { return container_detail::insert_n_copies_proxy(x); } + + container_detail::insert_default_initialized_n_proxy priv_resize_proxy(default_init_t) + { return container_detail::insert_default_initialized_n_proxy(); } + + container_detail::insert_value_initialized_n_proxy priv_resize_proxy(value_init_t) + { return container_detail::insert_value_initialized_n_proxy(); } + + template + void priv_resize(size_type new_size, const U& u) + { + const size_type sz = this->size(); + if (new_size < sz){ + //Destroy last elements + this->priv_destroy_last_n(sz - new_size); + } + else{ + const size_type n = new_size - this->size(); + this->priv_forward_range_insert_at_end(n, this->priv_resize_proxy(u), alloc_version()); + } + } + + void priv_shrink_to_fit(version_0) BOOST_NOEXCEPT_OR_NOTHROW + {} + + void priv_shrink_to_fit(version_1) + { + const size_type cp = this->m_holder.capacity(); + if(cp){ + const size_type sz = this->size(); + if(!sz){ + this->m_holder.alloc().deallocate(this->m_holder.m_start, cp); + this->m_holder.m_start = pointer(); + this->m_holder.m_capacity = 0; + } + else if(sz < cp){ + //Allocate a new buffer. + //Pass the hint so that allocators can take advantage of this. + pointer const p = allocator_traits_type::allocate(this->m_holder.alloc(), sz, this->m_holder.m_start); + + //We will reuse insert code, so create a dummy input iterator + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + this->priv_forward_range_insert_new_allocation + ( container_detail::to_raw_pointer(p), sz + , this->priv_raw_begin(), 0, this->priv_dummy_empty_proxy()); + } + } + } + + void priv_shrink_to_fit(version_2) BOOST_NOEXCEPT_OR_NOTHROW + { + const size_type cp = this->m_holder.capacity(); + if(cp){ + const size_type sz = this->size(); + if(!sz){ + this->m_holder.alloc().deallocate(this->m_holder.m_start, cp); + this->m_holder.m_start = pointer(); + this->m_holder.m_capacity = 0; + } + else{ + size_type received_size = sz; + pointer reuse(this->m_holder.start()); + if(this->m_holder.allocation_command + (shrink_in_place | nothrow_allocation, cp, received_size, reuse)){ + this->m_holder.capacity(received_size); + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_shrink; + #endif + } + } + } + } + + template + iterator priv_forward_range_insert_no_capacity + (const pointer &pos, const size_type, const InsertionProxy , version_0) + { + throw_bad_alloc(); + return iterator(pos); + } + + template + iterator priv_forward_range_insert_no_capacity + (const pointer &pos, const size_type n, const InsertionProxy insert_range_proxy, version_1) + { + //Check if we have enough memory or try to expand current memory + const size_type n_pos = pos - this->m_holder.start(); + T *const raw_pos = container_detail::to_raw_pointer(pos); + + const size_type new_cap = this->m_holder.next_capacity(n); + //Pass the hint so that allocators can take advantage of this. + T * const new_buf = container_detail::to_raw_pointer + (allocator_traits_type::allocate(this->m_holder.alloc(), new_cap, this->m_holder.m_start)); + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + this->priv_forward_range_insert_new_allocation + ( new_buf, new_cap, raw_pos, n, insert_range_proxy); + return iterator(this->m_holder.start() + n_pos); + } + + template + iterator priv_forward_range_insert_no_capacity + (const pointer &pos, const size_type n, const InsertionProxy insert_range_proxy, version_2) + { + //Check if we have enough memory or try to expand current memory + T *const raw_pos = container_detail::to_raw_pointer(pos); + const size_type n_pos = raw_pos - this->priv_raw_begin(); + + //There is not enough memory, allocate a new + //buffer or expand the old one. + size_type real_cap = this->m_holder.next_capacity(n); + pointer reuse(this->m_holder.start()); + pointer const ret (this->m_holder.allocation_command + (allocate_new | expand_fwd | expand_bwd, this->m_holder.m_size + n, real_cap, reuse)); + + //Buffer reallocated + if(reuse){ + //Forward expansion, delay insertion + if(this->m_holder.start() == ret){ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_fwd; + #endif + this->m_holder.capacity(real_cap); + //Expand forward + this->priv_forward_range_insert_expand_forward(raw_pos, n, insert_range_proxy); + } + //Backwards (and possibly forward) expansion + else{ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_expand_bwd; + #endif + this->priv_forward_range_insert_expand_backwards + (container_detail::to_raw_pointer(ret), real_cap, raw_pos, n, insert_range_proxy); + } + } + //New buffer + else{ + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + ++this->num_alloc; + #endif + this->priv_forward_range_insert_new_allocation + ( container_detail::to_raw_pointer(ret), real_cap, raw_pos, n, insert_range_proxy); + } + + return iterator(this->m_holder.start() + n_pos); + } + + template + iterator priv_forward_range_insert + (const pointer &pos, const size_type n, const InsertionProxy insert_range_proxy) + { + BOOST_ASSERT(this->m_holder.capacity() >= this->m_holder.m_size); + //Check if we have enough memory or try to expand current memory + const size_type remaining = this->m_holder.capacity() - this->m_holder.m_size; + + bool same_buffer_start = n <= remaining; + if (!same_buffer_start){ + return priv_forward_range_insert_no_capacity(pos, n, insert_range_proxy, alloc_version()); + } + else{ + //Expand forward + T *const raw_pos = container_detail::to_raw_pointer(pos); + const size_type n_pos = raw_pos - this->priv_raw_begin(); + this->priv_forward_range_insert_expand_forward(raw_pos, n, insert_range_proxy); + return iterator(this->m_holder.start() + n_pos); + } + } + + template + iterator priv_forward_range_insert_at_end + (const size_type n, const InsertionProxy insert_range_proxy, version_0) + { + //Check if we have enough memory or try to expand current memory + const size_type remaining = this->m_holder.capacity() - this->m_holder.m_size; + + if (n > remaining){ + //This will trigger an error + throw_bad_alloc(); + } + this->priv_forward_range_insert_at_end_expand_forward(n, insert_range_proxy); + return this->end(); + } + + template + iterator priv_forward_range_insert_at_end + (const size_type n, const InsertionProxy insert_range_proxy, AllocVersion) + { + return this->priv_forward_range_insert(this->back_ptr(), n, insert_range_proxy); + } + + //Takes the range pointed by [first_pos, last_pos) and shifts it to the right + //by 'shift_count'. 'limit_pos' marks the end of constructed elements. + // + //Precondition: first_pos <= last_pos <= limit_pos + // + //The shift operation might cross limit_pos so elements to moved beyond limit_pos + //are uninitialized_moved with an allocator. Other elements are moved. + // + //The shift operation might left uninitialized elements after limit_pos + //and the number of uninitialized elements is returned by the function. + // + //Old situation: + // first_pos last_pos old_limit + // | | | + // ____________V_______V__________________V_____________ + //| prefix | range | suffix |raw_mem ~ + //|____________|_______|__________________|_____________~ + // + //New situation in Case A (hole_size == 0): + // range is moved through move assignments + // + // first_pos last_pos limit_pos + // | | | + // ____________V_______V__________________V_____________ + //| prefix' | | | range |suffix'|raw_mem ~ + //|________________+______|___^___|_______|_____________~ + // | | + // |_>_>_>_>_>^ + // + // + //New situation in Case B (hole_size >= 0): + // range is moved through uninitialized moves + // + // first_pos last_pos limit_pos + // | | | + // ____________V_______V__________________V________________ + //| prefix' | | | [hole] | range | + //|_______________________________________|________|___^___| + // | | + // |_>_>_>_>_>_>_>_>_>_>_>_>_>_>_>_>_>_^ + // + //New situation in Case C (hole_size == 0): + // range is moved through move assignments and uninitialized moves + // + // first_pos last_pos limit_pos + // | | | + // ____________V_______V__________________V___ + //| prefix' | | | range | + //|___________________________________|___^___| + // | | + // |_>_>_>_>_>_>_>_>_>_>_>^ + size_type priv_insert_ordered_at_shift_range + (size_type first_pos, size_type last_pos, size_type limit_pos, size_type shift_count) + { + BOOST_ASSERT(first_pos <= last_pos); + BOOST_ASSERT(last_pos <= limit_pos); + // + T* const begin_ptr = this->priv_raw_begin(); + T* const first_ptr = begin_ptr + first_pos; + T* const last_ptr = begin_ptr + last_pos; + + size_type hole_size = 0; + //Case A: + if((last_pos + shift_count) <= limit_pos){ + //All move assigned + boost::container::move_backward(first_ptr, last_ptr, last_ptr + shift_count); + } + //Case B: + else if((first_pos + shift_count) >= limit_pos){ + //All uninitialized_moved + ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), first_ptr, last_ptr, first_ptr + shift_count); + hole_size = first_pos + shift_count - limit_pos; + } + //Case C: + else{ + //Some uninitialized_moved + T* const limit_ptr = begin_ptr + limit_pos; + T* const boundary_ptr = limit_ptr - shift_count; + ::boost::container::uninitialized_move_alloc(this->m_holder.alloc(), boundary_ptr, last_ptr, limit_ptr); + //The rest is move assigned + boost::container::move_backward(first_ptr, boundary_ptr, limit_ptr); + } + return hole_size; + } + + private: + T *priv_raw_begin() const + { return container_detail::to_raw_pointer(m_holder.start()); } + + T* priv_raw_end() const + { return this->priv_raw_begin() + this->m_holder.m_size; } + + template + void priv_forward_range_insert_at_end_expand_forward(const size_type n, InsertionProxy insert_range_proxy) + { + T* const old_finish = this->priv_raw_end(); + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), old_finish, n); + this->m_holder.m_size += n; + } + + template + void priv_forward_range_insert_expand_forward(T* const pos, const size_type n, InsertionProxy insert_range_proxy) + { + //n can't be 0, because there is nothing to do in that case + if(BOOST_UNLIKELY(!n)) return; + //There is enough memory + T* const old_finish = this->priv_raw_end(); + const size_type elems_after = old_finish - pos; + + if (!elems_after){ + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), old_finish, n); + this->m_holder.m_size += n; + } + else if (elems_after >= n){ + //New elements can be just copied. + //Move to uninitialized memory last objects + ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), old_finish - n, old_finish, old_finish); + this->m_holder.m_size += n; + //Copy previous to last objects to the initialized end + boost::container::move_backward(pos, old_finish - n, old_finish); + //Insert new objects in the pos + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), pos, n); + } + else { + //The new elements don't fit in the [pos, end()) range. + + //Copy old [pos, end()) elements to the uninitialized memory (a gap is created) + ::boost::container::uninitialized_move_alloc(this->m_holder.alloc(), pos, old_finish, pos + n); + BOOST_TRY{ + //Copy first new elements in pos (gap is still there) + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), pos, elems_after); + //Copy to the beginning of the unallocated zone the last new elements (the gap is closed). + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), old_finish, n - elems_after); + this->m_holder.m_size += n; + } + BOOST_CATCH(...){ + boost::container::destroy_alloc_n(this->get_stored_allocator(), pos + n, elems_after); + BOOST_RETHROW + } + BOOST_CATCH_END + } + } + + template + void priv_forward_range_insert_new_allocation + (T* const new_start, size_type new_cap, T* const pos, const size_type n, InsertionProxy insert_range_proxy) + { + //n can be zero, if we want to reallocate! + T *new_finish = new_start; + T *old_finish; + //Anti-exception rollbacks + typename value_traits::ArrayDeallocator new_buffer_deallocator(new_start, this->m_holder.alloc(), new_cap); + typename value_traits::ArrayDestructor new_values_destroyer(new_start, this->m_holder.alloc(), 0u); + + //Initialize with [begin(), pos) old buffer + //the start of the new buffer + T * const old_buffer = this->priv_raw_begin(); + if(old_buffer){ + new_finish = ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), this->priv_raw_begin(), pos, old_finish = new_finish); + new_values_destroyer.increment_size(new_finish - old_finish); + } + //Initialize new objects, starting from previous point + old_finish = new_finish; + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), old_finish, n); + new_finish += n; + new_values_destroyer.increment_size(new_finish - old_finish); + //Initialize from the rest of the old buffer, + //starting from previous point + if(old_buffer){ + new_finish = ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), pos, old_buffer + this->m_holder.m_size, new_finish); + //Destroy and deallocate old elements + //If there is allocated memory, destroy and deallocate + if(!value_traits::trivial_dctr_after_move) + boost::container::destroy_alloc_n(this->get_stored_allocator(), old_buffer, this->m_holder.m_size); + this->m_holder.alloc().deallocate(this->m_holder.start(), this->m_holder.capacity()); + } + this->m_holder.start(new_start); + this->m_holder.m_size = new_finish - new_start; + this->m_holder.capacity(new_cap); + //All construction successful, disable rollbacks + new_values_destroyer.release(); + new_buffer_deallocator.release(); + } + + template + void priv_forward_range_insert_expand_backwards + (T* const new_start, const size_type new_capacity, + T* const pos, const size_type n, InsertionProxy insert_range_proxy) + { + //n can be zero to just expand capacity + //Backup old data + T* const old_start = this->priv_raw_begin(); + const size_type old_size = this->m_holder.m_size; + T* const old_finish = old_start + old_size; + + //We can have 8 possibilities: + const size_type elemsbefore = static_cast(pos - old_start); + const size_type s_before = static_cast(old_start - new_start); + const size_type before_plus_new = elemsbefore + n; + + //Update the vector buffer information to a safe state + this->m_holder.start(new_start); + this->m_holder.capacity(new_capacity); + this->m_holder.m_size = 0; + + //If anything goes wrong, this object will destroy + //all the old objects to fulfill previous vector state + typename value_traits::ArrayDestructor old_values_destroyer(old_start, this->m_holder.alloc(), old_size); + //Check if s_before is big enough to hold the beginning of old data + new data + if(s_before >= before_plus_new){ + //Copy first old values before pos, after that the new objects + T *const new_elem_pos = + ::boost::container::uninitialized_move_alloc(this->m_holder.alloc(), old_start, pos, new_start); + this->m_holder.m_size = elemsbefore; + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), new_elem_pos, n); + this->m_holder.m_size = before_plus_new; + const size_type new_size = old_size + n; + //Check if s_before is so big that even copying the old data + new data + //there is a gap between the new data and the old data + if(s_before >= new_size){ + //Old situation: + // _________________________________________________________ + //| raw_mem | old_begin | old_end | + //| __________________________________|___________|_________| + // + //New situation: + // _________________________________________________________ + //| old_begin | new | old_end | raw_mem | + //|___________|__________|_________|________________________| + // + //Now initialize the rest of memory with the last old values + if(before_plus_new != new_size){ //Special case to avoid operations in back insertion + ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), pos, old_finish, new_start + before_plus_new); + //All new elements correctly constructed, avoid new element destruction + this->m_holder.m_size = new_size; + } + //Old values destroyed automatically with "old_values_destroyer" + //when "old_values_destroyer" goes out of scope unless the have trivial + //destructor after move. + if(value_traits::trivial_dctr_after_move) + old_values_destroyer.release(); + } + //s_before is so big that divides old_end + else{ + //Old situation: + // __________________________________________________ + //| raw_mem | old_begin | old_end | + //| ___________________________|___________|_________| + // + //New situation: + // __________________________________________________ + //| old_begin | new | old_end | raw_mem | + //|___________|__________|_________|_________________| + // + //Now initialize the rest of memory with the last old values + //All new elements correctly constructed, avoid new element destruction + const size_type raw_gap = s_before - before_plus_new; + if(!value_traits::trivial_dctr){ + //Now initialize the rest of s_before memory with the + //first of elements after new values + ::boost::container::uninitialized_move_alloc_n + (this->m_holder.alloc(), pos, raw_gap, new_start + before_plus_new); + //Now we have a contiguous buffer so program trailing element destruction + //and update size to the final size. + old_values_destroyer.shrink_forward(new_size-s_before); + this->m_holder.m_size = new_size; + //Now move remaining last objects in the old buffer begin + T * const remaining_pos = pos + raw_gap; + if(remaining_pos != old_start){ //Make sure data has to be moved + ::boost::container::move(remaining_pos, old_finish, old_start); + } + //Once moved, avoid calling the destructors if trivial after move + if(value_traits::trivial_dctr_after_move){ + old_values_destroyer.release(); + } + } + else{ //If trivial destructor, we can uninitialized copy + copy in a single uninitialized copy + ::boost::container::uninitialized_move_alloc_n + (this->m_holder.alloc(), pos, old_finish - pos, new_start + before_plus_new); + this->m_holder.m_size = new_size; + old_values_destroyer.release(); + } + } + } + else{ + //Check if we have to do the insertion in two phases + //since maybe s_before is not big enough and + //the buffer was expanded both sides + // + //Old situation: + // _________________________________________________ + //| raw_mem | old_begin + old_end | raw_mem | + //|_________|_____________________|_________________| + // + //New situation with do_after: + // _________________________________________________ + //| old_begin + new + old_end | raw_mem | + //|___________________________________|_____________| + // + //New without do_after: + // _________________________________________________ + //| old_begin + new + old_end | raw_mem | + //|____________________________|____________________| + // + const bool do_after = n > s_before; + + //Now we can have two situations: the raw_mem of the + //beginning divides the old_begin, or the new elements: + if (s_before <= elemsbefore) { + //The raw memory divides the old_begin group: + // + //If we need two phase construction (do_after) + //new group is divided in new = new_beg + new_end groups + //In this phase only new_beg will be inserted + // + //Old situation: + // _________________________________________________ + //| raw_mem | old_begin | old_end | raw_mem | + //|_________|___________|_________|_________________| + // + //New situation with do_after(1): + //This is not definitive situation, the second phase + //will include + // _________________________________________________ + //| old_begin | new_beg | old_end | raw_mem | + //|___________|_________|_________|_________________| + // + //New situation without do_after: + // _________________________________________________ + //| old_begin | new | old_end | raw_mem | + //|___________|_____|_________|_____________________| + // + //Copy the first part of old_begin to raw_mem + ::boost::container::uninitialized_move_alloc_n + (this->m_holder.alloc(), old_start, s_before, new_start); + //The buffer is all constructed until old_end, + //so program trailing destruction and assign final size + //if !do_after, s_before+n otherwise. + size_type new_1st_range; + if(do_after){ + new_1st_range = s_before; + //release destroyer and update size + old_values_destroyer.release(); + } + else{ + new_1st_range = n; + if(value_traits::trivial_dctr_after_move) + old_values_destroyer.release(); + else{ + old_values_destroyer.shrink_forward(old_size - (s_before - n)); + } + } + this->m_holder.m_size = old_size + new_1st_range; + //Now copy the second part of old_begin overwriting itself + T *const next = ::boost::container::move(old_start + s_before, pos, old_start); + //Now copy the new_beg elements + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), next, new_1st_range); + + //If there is no after work and the last old part needs to be moved to front, do it + if(!do_after && (n != s_before)){ + //Now displace old_end elements + ::boost::container::move(pos, old_finish, next + new_1st_range); + } + } + else { + //If we have to expand both sides, + //we will play if the first new values so + //calculate the upper bound of new values + + //The raw memory divides the new elements + // + //If we need two phase construction (do_after) + //new group is divided in new = new_beg + new_end groups + //In this phase only new_beg will be inserted + // + //Old situation: + // _______________________________________________________ + //| raw_mem | old_begin | old_end | raw_mem | + //|_______________|___________|_________|_________________| + // + //New situation with do_after(): + // ____________________________________________________ + //| old_begin | new_beg | old_end | raw_mem | + //|___________|_______________|_________|______________| + // + //New situation without do_after: + // ______________________________________________________ + //| old_begin | new | old_end | raw_mem | + //|___________|_____|_________|__________________________| + // + //First copy whole old_begin and part of new to raw_mem + T * const new_pos = ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), old_start, pos, new_start); + this->m_holder.m_size = elemsbefore; + const size_type mid_n = s_before - elemsbefore; + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), new_pos, mid_n); + //The buffer is all constructed until old_end, + //release destroyer + this->m_holder.m_size = old_size + s_before; + old_values_destroyer.release(); + + if(do_after){ + //Copy new_beg part + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), old_start, elemsbefore); + } + else{ + //Copy all new elements + const size_type rest_new = n - mid_n; + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), old_start, rest_new); + T* const move_start = old_start + rest_new; + //Displace old_end, but make sure data has to be moved + T* const move_end = move_start != pos ? ::boost::container::move(pos, old_finish, move_start) + : old_finish; + //Destroy remaining moved elements from old_end except if they + //have trivial destructor after being moved + size_type n_destroy = s_before - n; + if(!value_traits::trivial_dctr_after_move) + boost::container::destroy_alloc_n(this->get_stored_allocator(), move_end, n_destroy); + this->m_holder.m_size -= n_destroy; + } + } + + //This is only executed if two phase construction is needed + if(do_after){ + //The raw memory divides the new elements + // + //Old situation: + // ______________________________________________________ + //| raw_mem | old_begin | old_end | raw_mem | + //|______________|___________|____________|______________| + // + //New situation with do_after(1): + // _______________________________________________________ + //| old_begin + new_beg | new_end |old_end | raw_mem | + //|__________________________|_________|________|_________| + // + //New situation with do_after(2): + // ______________________________________________________ + //| old_begin + new | old_end |raw | + //|_______________________________________|_________|____| + // + const size_type n_after = n - s_before; + const size_type elemsafter = old_size - elemsbefore; + + //We can have two situations: + if (elemsafter >= n_after){ + //The raw_mem from end will divide displaced old_end + // + //Old situation: + // ______________________________________________________ + //| raw_mem | old_begin | old_end | raw_mem | + //|______________|___________|____________|______________| + // + //New situation with do_after(1): + // _______________________________________________________ + //| old_begin + new_beg | new_end |old_end | raw_mem | + //|__________________________|_________|________|_________| + // + //First copy the part of old_end raw_mem + T* finish_n = old_finish - n_after; + ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), finish_n, old_finish, old_finish); + this->m_holder.m_size += n_after; + //Displace the rest of old_end to the new position + boost::container::move_backward(pos, finish_n, old_finish); + //Now overwrite with new_end + //The new_end part is [first + (n - n_after), last) + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), pos, n_after); + } + else { + //The raw_mem from end will divide new_end part + // + //Old situation: + // _____________________________________________________________ + //| raw_mem | old_begin | old_end | raw_mem | + //|______________|___________|____________|_____________________| + // + //New situation with do_after(2): + // _____________________________________________________________ + //| old_begin + new_beg | new_end |old_end | raw_mem | + //|__________________________|_______________|________|_________| + // + + const size_type mid_last_dist = n_after - elemsafter; + //First initialize data in raw memory + + //Copy to the old_end part to the uninitialized zone leaving a gap. + ::boost::container::uninitialized_move_alloc + (this->m_holder.alloc(), pos, old_finish, old_finish + mid_last_dist); + + typename value_traits::ArrayDestructor old_end_destroyer + (old_finish + mid_last_dist, this->m_holder.alloc(), old_finish - pos); + + //Copy the first part to the already constructed old_end zone + insert_range_proxy.copy_n_and_update(this->m_holder.alloc(), pos, elemsafter); + //Copy the rest to the uninitialized zone filling the gap + insert_range_proxy.uninitialized_copy_n_and_update(this->m_holder.alloc(), old_finish, mid_last_dist); + this->m_holder.m_size += n_after; + old_end_destroyer.release(); + } + } + } + } + + void priv_throw_if_out_of_range(size_type n) const + { + //If n is out of range, throw an out_of_range exception + if (n >= this->size()){ + throw_out_of_range("vector::at out of range"); + } + } + + bool priv_in_range(const_iterator pos) const + { + return (this->begin() <= pos) && (pos < this->end()); + } + + bool priv_in_range_or_end(const_iterator pos) const + { + return (this->begin() <= pos) && (pos <= this->end()); + } + + #ifdef BOOST_CONTAINER_VECTOR_ALLOC_STATS + public: + unsigned int num_expand_fwd; + unsigned int num_expand_bwd; + unsigned int num_shrink; + unsigned int num_alloc; + void reset_alloc_stats() + { num_expand_fwd = num_expand_bwd = num_alloc = 0, num_shrink = 0; } + #endif + #endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED +}; + +}} //namespace boost::container + +#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +namespace boost { + +//!has_trivial_destructor_after_move<> == true_type +//!specialization for optimizations +template +struct has_trivial_destructor_after_move > +{ + typedef typename ::boost::container::allocator_traits::pointer pointer; + static const bool value = ::boost::has_trivial_destructor_after_move::value && + ::boost::has_trivial_destructor_after_move::value; +}; + +} + +#endif //#ifndef BOOST_CONTAINER_DOXYGEN_INVOKED + +#include + +#endif // #ifndef BOOST_CONTAINER_CONTAINER_VECTOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/addressof.hpp b/thirdparty/source/boost_1_61_0/boost/core/addressof.hpp new file mode 100644 index 0000000..889b582 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/addressof.hpp @@ -0,0 +1,162 @@ +// Copyright (C) 2002 Brad King (brad.king@kitware.com) +// Douglas Gregor (gregod@cs.rpi.edu) +// +// Copyright (C) 2002, 2008, 2013 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#ifndef BOOST_CORE_ADDRESSOF_HPP +#define BOOST_CORE_ADDRESSOF_HPP + +# include +# include +# include + +namespace boost +{ + +namespace detail +{ + +template struct addr_impl_ref +{ + T & v_; + + BOOST_FORCEINLINE addr_impl_ref( T & v ): v_( v ) {} + BOOST_FORCEINLINE operator T& () const { return v_; } + +private: + addr_impl_ref & operator=(const addr_impl_ref &); +}; + +template struct addressof_impl +{ + static BOOST_FORCEINLINE T * f( T & v, long ) + { + return reinterpret_cast( + &const_cast(reinterpret_cast(v))); + } + + static BOOST_FORCEINLINE T * f( T * v, int ) + { + return v; + } +}; + +#if !defined( BOOST_NO_CXX11_NULLPTR ) + +#if !defined( BOOST_NO_CXX11_DECLTYPE ) && ( ( defined( __clang__ ) && !defined( _LIBCPP_VERSION ) ) || defined( __INTEL_COMPILER ) ) + + typedef decltype(nullptr) addr_nullptr_t; + +#else + + typedef std::nullptr_t addr_nullptr_t; + +#endif + +template<> struct addressof_impl< addr_nullptr_t > +{ + typedef addr_nullptr_t T; + + static BOOST_FORCEINLINE T * f( T & v, int ) + { + return &v; + } +}; + +template<> struct addressof_impl< addr_nullptr_t const > +{ + typedef addr_nullptr_t const T; + + static BOOST_FORCEINLINE T * f( T & v, int ) + { + return &v; + } +}; + +template<> struct addressof_impl< addr_nullptr_t volatile > +{ + typedef addr_nullptr_t volatile T; + + static BOOST_FORCEINLINE T * f( T & v, int ) + { + return &v; + } +}; + +template<> struct addressof_impl< addr_nullptr_t const volatile > +{ + typedef addr_nullptr_t const volatile T; + + static BOOST_FORCEINLINE T * f( T & v, int ) + { + return &v; + } +}; + +#endif + +} // namespace detail + +template +BOOST_FORCEINLINE +T * addressof( T & v ) +{ +#if (defined( __BORLANDC__ ) && BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT( 0x610 ) ) ) || (defined(__SUNPRO_CC) && BOOST_WORKAROUND(__SUNPRO_CC, <= 0x5120)) + + return boost::detail::addressof_impl::f( v, 0 ); + +#else + + return boost::detail::addressof_impl::f( boost::detail::addr_impl_ref( v ), 0 ); + +#endif +} + +#if defined( __SUNPRO_CC ) && BOOST_WORKAROUND( __SUNPRO_CC, BOOST_TESTED_AT( 0x590 ) ) + +namespace detail +{ + +template struct addressof_addp +{ + typedef T * type; +}; + +} // namespace detail + +template< class T, std::size_t N > +BOOST_FORCEINLINE +typename detail::addressof_addp< T[N] >::type addressof( T (&t)[N] ) +{ + return &t; +} + +#endif + +// Borland doesn't like casting an array reference to a char reference +// but these overloads work around the problem. +#if defined( __BORLANDC__ ) && BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +template +BOOST_FORCEINLINE +T (*addressof(T (&t)[N]))[N] +{ + return reinterpret_cast(&t); +} + +template +BOOST_FORCEINLINE +const T (*addressof(const T (&t)[N]))[N] +{ + return reinterpret_cast(&t); +} +#endif + +} // namespace boost + +#endif // BOOST_CORE_ADDRESSOF_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/checked_delete.hpp b/thirdparty/source/boost_1_61_0/boost/core/checked_delete.hpp new file mode 100644 index 0000000..b086e03 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/checked_delete.hpp @@ -0,0 +1,69 @@ +#ifndef BOOST_CORE_CHECKED_DELETE_HPP +#define BOOST_CORE_CHECKED_DELETE_HPP + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// boost/checked_delete.hpp +// +// Copyright (c) 2002, 2003 Peter Dimov +// Copyright (c) 2003 Daniel Frey +// Copyright (c) 2003 Howard Hinnant +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/core/doc/html/core/checked_delete.html for documentation. +// + +namespace boost +{ + +// verify that types are complete for increased safety + +template inline void checked_delete(T * x) +{ + // intentionally complex - simplification causes regressions + typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]; + (void) sizeof(type_must_be_complete); + delete x; +} + +template inline void checked_array_delete(T * x) +{ + typedef char type_must_be_complete[ sizeof(T)? 1: -1 ]; + (void) sizeof(type_must_be_complete); + delete [] x; +} + +template struct checked_deleter +{ + typedef void result_type; + typedef T * argument_type; + + void operator()(T * x) const + { + // boost:: disables ADL + boost::checked_delete(x); + } +}; + +template struct checked_array_deleter +{ + typedef void result_type; + typedef T * argument_type; + + void operator()(T * x) const + { + boost::checked_array_delete(x); + } +}; + +} // namespace boost + +#endif // #ifndef BOOST_CORE_CHECKED_DELETE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/demangle.hpp b/thirdparty/source/boost_1_61_0/boost/core/demangle.hpp new file mode 100644 index 0000000..f13c26a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/demangle.hpp @@ -0,0 +1,131 @@ +#ifndef BOOST_CORE_DEMANGLE_HPP_INCLUDED +#define BOOST_CORE_DEMANGLE_HPP_INCLUDED + +// core::demangle +// +// Copyright 2014 Peter Dimov +// Copyright 2014 Andrey Semashev +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include +#include + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +// __has_include is currently supported by GCC and Clang. However GCC 4.9 may have issues and +// returns 1 for 'defined( __has_include )', while '__has_include' is actually not supported: +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=63662 +#if defined( __has_include ) && (!defined( BOOST_GCC ) || (__GNUC__ + 0) >= 5) +# if __has_include() +# define BOOST_CORE_HAS_CXXABI_H +# endif +#elif defined( __GLIBCXX__ ) || defined( __GLIBCPP__ ) +# define BOOST_CORE_HAS_CXXABI_H +#endif + +#if defined( BOOST_CORE_HAS_CXXABI_H ) +# include +// For some archtectures (mips, mips64, x86, x86_64) cxxabi.h in Android NDK is implemented by gabi++ library +// (https://android.googlesource.com/platform/ndk/+/master/sources/cxx-stl/gabi++/), which does not implement +// abi::__cxa_demangle(). We detect this implementation by checking the include guard here. +# if defined( __GABIXX_CXXABI_H__ ) +# undef BOOST_CORE_HAS_CXXABI_H +# else +# include +# include +# endif +#endif + +namespace boost +{ + +namespace core +{ + +inline char const * demangle_alloc( char const * name ) BOOST_NOEXCEPT; +inline void demangle_free( char const * name ) BOOST_NOEXCEPT; + +class scoped_demangled_name +{ +private: + char const * m_p; + +public: + explicit scoped_demangled_name( char const * name ) BOOST_NOEXCEPT : + m_p( demangle_alloc( name ) ) + { + } + + ~scoped_demangled_name() BOOST_NOEXCEPT + { + demangle_free( m_p ); + } + + char const * get() const BOOST_NOEXCEPT + { + return m_p; + } + + BOOST_DELETED_FUNCTION(scoped_demangled_name( scoped_demangled_name const& )) + BOOST_DELETED_FUNCTION(scoped_demangled_name& operator= ( scoped_demangled_name const& )) +}; + + +#if defined( BOOST_CORE_HAS_CXXABI_H ) + +inline char const * demangle_alloc( char const * name ) BOOST_NOEXCEPT +{ + int status = 0; + std::size_t size = 0; + return abi::__cxa_demangle( name, NULL, &size, &status ); +} + +inline void demangle_free( char const * name ) BOOST_NOEXCEPT +{ + std::free( const_cast< char* >( name ) ); +} + +inline std::string demangle( char const * name ) +{ + scoped_demangled_name demangled_name( name ); + char const * const p = demangled_name.get(); + if( p ) + { + return p; + } + else + { + return name; + } +} + +#else + +inline char const * demangle_alloc( char const * name ) BOOST_NOEXCEPT +{ + return name; +} + +inline void demangle_free( char const * ) BOOST_NOEXCEPT +{ +} + +inline std::string demangle( char const * name ) +{ + return name; +} + +#endif + +} // namespace core + +} // namespace boost + +#undef BOOST_CORE_HAS_CXXABI_H + +#endif // #ifndef BOOST_CORE_DEMANGLE_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/core/enable_if.hpp b/thirdparty/source/boost_1_61_0/boost/core/enable_if.hpp new file mode 100644 index 0000000..5dcef1e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/enable_if.hpp @@ -0,0 +1,128 @@ +// Boost enable_if library + +// Copyright 2003 (c) The Trustees of Indiana University. + +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Authors: Jaakko Jarvi (jajarvi at osl.iu.edu) +// Jeremiah Willcock (jewillco at osl.iu.edu) +// Andrew Lumsdaine (lums at osl.iu.edu) + + +#ifndef BOOST_CORE_ENABLE_IF_HPP +#define BOOST_CORE_ENABLE_IF_HPP + +#include "boost/config.hpp" + +// Even the definition of enable_if causes problems on some compilers, +// so it's macroed out for all compilers that do not support SFINAE + +#ifndef BOOST_NO_SFINAE + +namespace boost +{ + template + struct enable_if_has_type + { + typedef R type; + }; + + template + struct enable_if_c { + typedef T type; + }; + + template + struct enable_if_c {}; + + template + struct enable_if : public enable_if_c {}; + + template + struct lazy_enable_if_c { + typedef typename T::type type; + }; + + template + struct lazy_enable_if_c {}; + + template + struct lazy_enable_if : public lazy_enable_if_c {}; + + + template + struct disable_if_c { + typedef T type; + }; + + template + struct disable_if_c {}; + + template + struct disable_if : public disable_if_c {}; + + template + struct lazy_disable_if_c { + typedef typename T::type type; + }; + + template + struct lazy_disable_if_c {}; + + template + struct lazy_disable_if : public lazy_disable_if_c {}; + +} // namespace boost + +#else + +namespace boost { + + namespace detail { typedef void enable_if_default_T; } + + template + struct enable_if_does_not_work_on_this_compiler; + + template + struct enable_if_has_type : enable_if_does_not_work_on_this_compiler + { }; + + template + struct enable_if_c : enable_if_does_not_work_on_this_compiler + { }; + + template + struct disable_if_c : enable_if_does_not_work_on_this_compiler + { }; + + template + struct lazy_enable_if_c : enable_if_does_not_work_on_this_compiler + { }; + + template + struct lazy_disable_if_c : enable_if_does_not_work_on_this_compiler + { }; + + template + struct enable_if : enable_if_does_not_work_on_this_compiler + { }; + + template + struct disable_if : enable_if_does_not_work_on_this_compiler + { }; + + template + struct lazy_enable_if : enable_if_does_not_work_on_this_compiler + { }; + + template + struct lazy_disable_if : enable_if_does_not_work_on_this_compiler + { }; + +} // namespace boost + +#endif // BOOST_NO_SFINAE + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/core/explicit_operator_bool.hpp b/thirdparty/source/boost_1_61_0/boost/core/explicit_operator_bool.hpp new file mode 100644 index 0000000..a8936e2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/explicit_operator_bool.hpp @@ -0,0 +1,154 @@ +/* + * Copyright Andrey Semashev 2007 - 2013. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +/*! + * \file explicit_operator_bool.hpp + * \author Andrey Semashev + * \date 08.03.2009 + * + * This header defines a compatibility macro that implements an unspecified + * \c bool operator idiom, which is superseded with explicit conversion operators in + * C++11. + */ + +#ifndef BOOST_CORE_EXPLICIT_OPERATOR_BOOL_HPP +#define BOOST_CORE_EXPLICIT_OPERATOR_BOOL_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined(BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS) + +/*! + * \brief The macro defines an explicit operator of conversion to \c bool + * + * The macro should be used inside the definition of a class that has to + * support the conversion. The class should also implement operator!, + * in terms of which the conversion operator will be implemented. + */ +#define BOOST_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE explicit operator bool () const\ + {\ + return !this->operator! ();\ + } + +/*! + * \brief The macro defines a noexcept explicit operator of conversion to \c bool + * + * The macro should be used inside the definition of a class that has to + * support the conversion. The class should also implement operator!, + * in terms of which the conversion operator will be implemented. + */ +#define BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()\ + BOOST_FORCEINLINE explicit operator bool () const BOOST_NOEXCEPT\ + {\ + return !this->operator! ();\ + } + +/*! + * \brief The macro defines a constexpr explicit operator of conversion to \c bool + * + * The macro should be used inside the definition of a class that has to + * support the conversion. The class should also implement operator!, + * in terms of which the conversion operator will be implemented. + */ +#define BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE BOOST_CONSTEXPR explicit operator bool () const BOOST_NOEXCEPT\ + {\ + return !this->operator! ();\ + } + +#else // !defined(BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS) + +#if (defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x530)) && !defined(BOOST_NO_COMPILER_CONFIG) +// Sun C++ 5.3 can't handle the safe_bool idiom, so don't use it +#define BOOST_NO_UNSPECIFIED_BOOL +#endif // (defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x530)) && !defined(BOOST_NO_COMPILER_CONFIG) + +#if !defined(BOOST_NO_UNSPECIFIED_BOOL) + +namespace boost { + +namespace detail { + +#if !defined(_MSC_VER) && !defined(__IBMCPP__) + + struct unspecified_bool + { + // NOTE TO THE USER: If you see this in error messages then you tried + // to apply an unsupported operator on the object that supports + // explicit conversion to bool. + struct OPERATORS_NOT_ALLOWED; + static void true_value(OPERATORS_NOT_ALLOWED*) {} + }; + typedef void (*unspecified_bool_type)(unspecified_bool::OPERATORS_NOT_ALLOWED*); + +#else + + // MSVC and VACPP are too eager to convert pointer to function to void* even though they shouldn't + struct unspecified_bool + { + // NOTE TO THE USER: If you see this in error messages then you tried + // to apply an unsupported operator on the object that supports + // explicit conversion to bool. + struct OPERATORS_NOT_ALLOWED; + void true_value(OPERATORS_NOT_ALLOWED*) {} + }; + typedef void (unspecified_bool::*unspecified_bool_type)(unspecified_bool::OPERATORS_NOT_ALLOWED*); + +#endif + +} // namespace detail + +} // namespace boost + +#define BOOST_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE operator boost::detail::unspecified_bool_type () const\ + {\ + return (!this->operator! () ? &boost::detail::unspecified_bool::true_value : (boost::detail::unspecified_bool_type)0);\ + } + +#define BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()\ + BOOST_FORCEINLINE operator boost::detail::unspecified_bool_type () const BOOST_NOEXCEPT\ + {\ + return (!this->operator! () ? &boost::detail::unspecified_bool::true_value : (boost::detail::unspecified_bool_type)0);\ + } + +#define BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE BOOST_CONSTEXPR operator boost::detail::unspecified_bool_type () const BOOST_NOEXCEPT\ + {\ + return (!this->operator! () ? &boost::detail::unspecified_bool::true_value : (boost::detail::unspecified_bool_type)0);\ + } + +#else // !defined(BOOST_NO_UNSPECIFIED_BOOL) + +#define BOOST_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE operator bool () const\ + {\ + return !this->operator! ();\ + } + +#define BOOST_EXPLICIT_OPERATOR_BOOL_NOEXCEPT()\ + BOOST_FORCEINLINE operator bool () const BOOST_NOEXCEPT\ + {\ + return !this->operator! ();\ + } + +#define BOOST_CONSTEXPR_EXPLICIT_OPERATOR_BOOL()\ + BOOST_FORCEINLINE BOOST_CONSTEXPR operator bool () const BOOST_NOEXCEPT\ + {\ + return !this->operator! ();\ + } + +#endif // !defined(BOOST_NO_UNSPECIFIED_BOOL) + +#endif // !defined(BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS) + +#endif // BOOST_CORE_EXPLICIT_OPERATOR_BOOL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/ignore_unused.hpp b/thirdparty/source/boost_1_61_0/boost/core/ignore_unused.hpp new file mode 100644 index 0000000..994e5f6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/ignore_unused.hpp @@ -0,0 +1,70 @@ +// Copyright (c) 2014 Adam Wulkiewicz, Lodz, Poland. +// +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_CORE_IGNORE_UNUSED_HPP +#define BOOST_CORE_IGNORE_UNUSED_HPP + +#include + +namespace boost { + +#ifndef BOOST_NO_CXX11_VARIADIC_TEMPLATES + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(Ts const& ...) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +#else + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(T1 const&) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(T1 const&, T2 const&) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(T1 const&, T2 const&, T3 const&) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(T1 const&, T2 const&, T3 const&, T4 const&) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused(T1 const&, T2 const&, T3 const&, T4 const&, T5 const&) +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +template +BOOST_FORCEINLINE BOOST_CXX14_CONSTEXPR void ignore_unused() +{} + +#endif + +} // namespace boost + +#endif // BOOST_CORE_IGNORE_UNUSED_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/is_same.hpp b/thirdparty/source/boost_1_61_0/boost/core/is_same.hpp new file mode 100644 index 0000000..f373c65 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/is_same.hpp @@ -0,0 +1,40 @@ +#ifndef BOOST_CORE_IS_SAME_HPP_INCLUDED +#define BOOST_CORE_IS_SAME_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// is_same::value is true when T1 == T2 +// +// Copyright 2014 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include + +namespace boost +{ + +namespace core +{ + +template< class T1, class T2 > struct is_same +{ + BOOST_STATIC_CONSTANT( bool, value = false ); +}; + +template< class T > struct is_same< T, T > +{ + BOOST_STATIC_CONSTANT( bool, value = true ); +}; + +} // namespace core + +} // namespace boost + +#endif // #ifndef BOOST_CORE_IS_SAME_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/core/lightweight_test.hpp b/thirdparty/source/boost_1_61_0/boost/core/lightweight_test.hpp new file mode 100644 index 0000000..cdc8a72 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/lightweight_test.hpp @@ -0,0 +1,171 @@ +#ifndef BOOST_CORE_LIGHTWEIGHT_TEST_HPP +#define BOOST_CORE_LIGHTWEIGHT_TEST_HPP + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) +# pragma once +#endif + +// +// boost/core/lightweight_test.hpp - lightweight test library +// +// Copyright (c) 2002, 2009, 2014 Peter Dimov +// Copyright (2) Beman Dawes 2010, 2011 +// Copyright (3) Ion Gaztanaga 2013 +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include +#include +#include +#include + +// IDE's like Visual Studio perform better if output goes to std::cout or +// some other stream, so allow user to configure output stream: +#ifndef BOOST_LIGHTWEIGHT_TEST_OSTREAM +# define BOOST_LIGHTWEIGHT_TEST_OSTREAM std::cerr +#endif + +namespace boost +{ + +namespace detail +{ + +struct report_errors_reminder +{ + bool called_report_errors_function; + + report_errors_reminder() : called_report_errors_function(false) {} + + ~report_errors_reminder() + { + BOOST_ASSERT(called_report_errors_function); // verify report_errors() was called + } +}; + +inline report_errors_reminder& report_errors_remind() +{ + static report_errors_reminder r; + return r; +} + +inline int & test_errors() +{ + static int x = 0; + report_errors_remind(); + return x; +} + +inline void test_failed_impl(char const * expr, char const * file, int line, char const * function) +{ + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << file << "(" << line << "): test '" << expr << "' failed in function '" + << function << "'" << std::endl; + ++test_errors(); +} + +inline void error_impl(char const * msg, char const * file, int line, char const * function) +{ + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << file << "(" << line << "): " << msg << " in function '" + << function << "'" << std::endl; + ++test_errors(); +} + +inline void throw_failed_impl(char const * excep, char const * file, int line, char const * function) +{ + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << file << "(" << line << "): Exception '" << excep << "' not thrown in function '" + << function << "'" << std::endl; + ++test_errors(); +} + +template inline void test_eq_impl( char const * expr1, char const * expr2, + char const * file, int line, char const * function, T const & t, U const & u ) +{ + if( t == u ) + { + report_errors_remind(); + } + else + { + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << file << "(" << line << "): test '" << expr1 << " == " << expr2 + << "' failed in function '" << function << "': " + << "'" << t << "' != '" << u << "'" << std::endl; + ++test_errors(); + } +} + +template inline void test_ne_impl( char const * expr1, char const * expr2, + char const * file, int line, char const * function, T const & t, U const & u ) +{ + if( t != u ) + { + report_errors_remind(); + } + else + { + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << file << "(" << line << "): test '" << expr1 << " != " << expr2 + << "' failed in function '" << function << "': " + << "'" << t << "' == '" << u << "'" << std::endl; + ++test_errors(); + } +} + +} // namespace detail + +inline int report_errors() +{ + boost::detail::report_errors_remind().called_report_errors_function = true; + + int errors = boost::detail::test_errors(); + + if( errors == 0 ) + { + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << "No errors detected." << std::endl; + return 0; + } + else + { + BOOST_LIGHTWEIGHT_TEST_OSTREAM + << errors << " error" << (errors == 1? "": "s") << " detected." << std::endl; + return 1; + } +} + +} // namespace boost + +#define BOOST_TEST(expr) ((expr)? (void)0: ::boost::detail::test_failed_impl(#expr, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION)) + +#define BOOST_ERROR(msg) ( ::boost::detail::error_impl(msg, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION) ) + +#define BOOST_TEST_EQ(expr1,expr2) ( ::boost::detail::test_eq_impl(#expr1, #expr2, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, expr1, expr2) ) +#define BOOST_TEST_NE(expr1,expr2) ( ::boost::detail::test_ne_impl(#expr1, #expr2, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION, expr1, expr2) ) + +#ifndef BOOST_NO_EXCEPTIONS + #define BOOST_TEST_THROWS( EXPR, EXCEP ) \ + try { \ + EXPR; \ + ::boost::detail::throw_failed_impl \ + (#EXCEP, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION); \ + } \ + catch(EXCEP const&) { \ + } \ + catch(...) { \ + ::boost::detail::throw_failed_impl \ + (#EXCEP, __FILE__, __LINE__, BOOST_CURRENT_FUNCTION); \ + } \ + // +#else + #define BOOST_TEST_THROWS( EXPR, EXCEP ) +#endif + +#endif // #ifndef BOOST_CORE_LIGHTWEIGHT_TEST_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/no_exceptions_support.hpp b/thirdparty/source/boost_1_61_0/boost/core/no_exceptions_support.hpp new file mode 100644 index 0000000..a697f01 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/no_exceptions_support.hpp @@ -0,0 +1,44 @@ +#ifndef BOOST_CORE_NO_EXCEPTIONS_SUPPORT_HPP +#define BOOST_CORE_NO_EXCEPTIONS_SUPPORT_HPP + +#if defined(_MSC_VER) +# pragma once +#endif + +//---------------------------------------------------------------------- +// (C) Copyright 2004 Pavel Vozenilek. +// Use, modification and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// +// This file contains helper macros used when exception support may be +// disabled (as indicated by macro BOOST_NO_EXCEPTIONS). +// +// Before picking up these macros you may consider using RAII techniques +// to deal with exceptions - their syntax can be always the same with +// or without exception support enabled. +//---------------------------------------------------------------------- + +#include +#include + +#if !(defined BOOST_NO_EXCEPTIONS) +# define BOOST_TRY { try +# define BOOST_CATCH(x) catch(x) +# define BOOST_RETHROW throw; +# define BOOST_CATCH_END } +#else +# if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x564)) +# define BOOST_TRY { if ("") +# define BOOST_CATCH(x) else if (!"") +# else +# define BOOST_TRY { if (true) +# define BOOST_CATCH(x) else if (false) +# endif +# define BOOST_RETHROW +# define BOOST_CATCH_END } +#endif + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/core/noncopyable.hpp b/thirdparty/source/boost_1_61_0/boost/core/noncopyable.hpp new file mode 100644 index 0000000..6ae8c24 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/noncopyable.hpp @@ -0,0 +1,48 @@ +// Boost noncopyable.hpp header file --------------------------------------// + +// (C) Copyright Beman Dawes 1999-2003. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/utility for documentation. + +#ifndef BOOST_CORE_NONCOPYABLE_HPP +#define BOOST_CORE_NONCOPYABLE_HPP + +#include + +namespace boost { + +// Private copy constructor and copy assignment ensure classes derived from +// class noncopyable cannot be copied. + +// Contributed by Dave Abrahams + +namespace noncopyable_ // protection from unintended ADL +{ + class noncopyable + { + protected: +#if !defined(BOOST_NO_CXX11_DEFAULTED_FUNCTIONS) && !defined(BOOST_NO_CXX11_NON_PUBLIC_DEFAULTED_FUNCTIONS) + BOOST_CONSTEXPR noncopyable() = default; + ~noncopyable() = default; +#else + noncopyable() {} + ~noncopyable() {} +#endif +#if !defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) + noncopyable( const noncopyable& ) = delete; + noncopyable& operator=( const noncopyable& ) = delete; +#else + private: // emphasize the following members are private + noncopyable( const noncopyable& ); + noncopyable& operator=( const noncopyable& ); +#endif + }; +} + +typedef noncopyable_::noncopyable noncopyable; + +} // namespace boost + +#endif // BOOST_CORE_NONCOPYABLE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/null_deleter.hpp b/thirdparty/source/boost_1_61_0/boost/core/null_deleter.hpp new file mode 100644 index 0000000..f0af590 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/null_deleter.hpp @@ -0,0 +1,44 @@ +/* + * Copyright Andrey Semashev 2007 - 2014. + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ +/*! + * \file null_deleter.hpp + * \author Andrey Semashev + * \date 22.04.2007 + * + * This header contains a \c null_deleter implementation. This is an empty + * function object that receives a pointer and does nothing with it. + * Such empty deletion strategy may be convenient, for example, when + * constructing shared_ptrs that point to some object that should not be + * deleted (i.e. a variable on the stack or some global singleton, like std::cout). + */ + +#ifndef BOOST_CORE_NULL_DELETER_HPP +#define BOOST_CORE_NULL_DELETER_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost { + +//! A function object that does nothing and can be used as an empty deleter for \c shared_ptr +struct null_deleter +{ + //! Function object result type + typedef void result_type; + /*! + * Does nothing + */ + template< typename T > + void operator() (T*) const BOOST_NOEXCEPT {} +}; + +} // namespace boost + +#endif // BOOST_CORE_NULL_DELETER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/ref.hpp b/thirdparty/source/boost_1_61_0/boost/core/ref.hpp new file mode 100644 index 0000000..47dc858 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/ref.hpp @@ -0,0 +1,301 @@ +#ifndef BOOST_CORE_REF_HPP +#define BOOST_CORE_REF_HPP + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +#include +#include +#include + +// +// ref.hpp - ref/cref, useful helper functions +// +// Copyright (C) 1999, 2000 Jaakko Jarvi (jaakko.jarvi@cs.utu.fi) +// Copyright (C) 2001, 2002 Peter Dimov +// Copyright (C) 2002 David Abrahams +// +// Copyright (C) 2014 Glen Joseph Fernandes +// glenfe at live dot com +// Copyright (C) 2014 Agustin Berge +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/core/doc/html/core/ref.html for documentation. +// + +/** + @file +*/ + +/** + Boost namespace. +*/ +namespace boost +{ + +#if defined( BOOST_MSVC ) && BOOST_WORKAROUND( BOOST_MSVC, == 1600 ) + + struct ref_workaround_tag {}; + +#endif + +// reference_wrapper + +/** + @brief Contains a reference to an object of type `T`. + + `reference_wrapper` is primarily used to "feed" references to + function templates (algorithms) that take their parameter by + value. It provides an implicit conversion to `T&`, which + usually allows the function templates to work on references + unmodified. +*/ +template class reference_wrapper +{ +public: + /** + Type `T`. + */ + typedef T type; + + /** + Constructs a `reference_wrapper` object that stores a + reference to `t`. + + @remark Does not throw. + */ + BOOST_FORCEINLINE explicit reference_wrapper(T& t): t_(boost::addressof(t)) {} + +#if defined( BOOST_MSVC ) && BOOST_WORKAROUND( BOOST_MSVC, == 1600 ) + + BOOST_FORCEINLINE explicit reference_wrapper( T & t, ref_workaround_tag ): t_( boost::addressof( t ) ) {} + +#endif + +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) + /** + @remark Construction from a temporary object is disabled. + */ + BOOST_DELETED_FUNCTION(reference_wrapper(T&& t)) +public: +#endif + + /** + @return The stored reference. + @remark Does not throw. + */ + BOOST_FORCEINLINE operator T& () const { return *t_; } + + /** + @return The stored reference. + @remark Does not throw. + */ + BOOST_FORCEINLINE T& get() const { return *t_; } + + /** + @return A pointer to the object referenced by the stored + reference. + @remark Does not throw. + */ + BOOST_FORCEINLINE T* get_pointer() const { return t_; } + +private: + + T* t_; +}; + +// ref + +/** + @cond +*/ +#if defined( __BORLANDC__ ) && BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0x581) ) +# define BOOST_REF_CONST +#else +# define BOOST_REF_CONST const +#endif +/** + @endcond +*/ + +/** + @return `reference_wrapper(t)` + @remark Does not throw. +*/ +template BOOST_FORCEINLINE reference_wrapper BOOST_REF_CONST ref( T & t ) +{ +#if defined( BOOST_MSVC ) && BOOST_WORKAROUND( BOOST_MSVC, == 1600 ) + + return reference_wrapper( t, ref_workaround_tag() ); + +#else + + return reference_wrapper( t ); + +#endif +} + +// cref + +/** + @return `reference_wrapper(t)` + @remark Does not throw. +*/ +template BOOST_FORCEINLINE reference_wrapper BOOST_REF_CONST cref( T const & t ) +{ + return reference_wrapper(t); +} + +#undef BOOST_REF_CONST + +#if !defined(BOOST_NO_CXX11_RVALUE_REFERENCES) + +/** + @cond +*/ +#if defined(BOOST_NO_CXX11_DELETED_FUNCTIONS) +# define BOOST_REF_DELETE +#else +# define BOOST_REF_DELETE = delete +#endif +/** + @endcond +*/ + +/** + @remark Construction from a temporary object is disabled. +*/ +template void ref(T const&&) BOOST_REF_DELETE; + +/** + @remark Construction from a temporary object is disabled. +*/ +template void cref(T const&&) BOOST_REF_DELETE; + +#undef BOOST_REF_DELETE + +#endif + +// is_reference_wrapper + +/** + @brief Determine if a type `T` is an instantiation of + `reference_wrapper`. + + The value static constant will be true if the type `T` is a + specialization of `reference_wrapper`. +*/ +template struct is_reference_wrapper +{ + BOOST_STATIC_CONSTANT( bool, value = false ); +}; + +/** + @cond +*/ +template struct is_reference_wrapper< reference_wrapper > +{ + BOOST_STATIC_CONSTANT( bool, value = true ); +}; + +#if !defined(BOOST_NO_CV_SPECIALIZATIONS) + +template struct is_reference_wrapper< reference_wrapper const > +{ + BOOST_STATIC_CONSTANT( bool, value = true ); +}; + +template struct is_reference_wrapper< reference_wrapper volatile > +{ + BOOST_STATIC_CONSTANT( bool, value = true ); +}; + +template struct is_reference_wrapper< reference_wrapper const volatile > +{ + BOOST_STATIC_CONSTANT( bool, value = true ); +}; + +#endif // !defined(BOOST_NO_CV_SPECIALIZATIONS) + +/** + @endcond +*/ + + +// unwrap_reference + +/** + @brief Find the type in a `reference_wrapper`. + + The `typedef` type is `T::type` if `T` is a + `reference_wrapper`, `T` otherwise. +*/ +template struct unwrap_reference +{ + typedef T type; +}; + +/** + @cond +*/ +template struct unwrap_reference< reference_wrapper > +{ + typedef T type; +}; + +#if !defined(BOOST_NO_CV_SPECIALIZATIONS) + +template struct unwrap_reference< reference_wrapper const > +{ + typedef T type; +}; + +template struct unwrap_reference< reference_wrapper volatile > +{ + typedef T type; +}; + +template struct unwrap_reference< reference_wrapper const volatile > +{ + typedef T type; +}; + +#endif // !defined(BOOST_NO_CV_SPECIALIZATIONS) + +/** + @endcond +*/ + +// unwrap_ref + +/** + @return `unwrap_reference::type&(t)` + @remark Does not throw. +*/ +template BOOST_FORCEINLINE typename unwrap_reference::type& unwrap_ref( T & t ) +{ + return t; +} + +// get_pointer + +/** + @cond +*/ +template BOOST_FORCEINLINE T* get_pointer( reference_wrapper const & r ) +{ + return r.get_pointer(); +} +/** + @endcond +*/ + +} // namespace boost + +#endif // #ifndef BOOST_CORE_REF_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/scoped_enum.hpp b/thirdparty/source/boost_1_61_0/boost/core/scoped_enum.hpp new file mode 100644 index 0000000..56dd0ed --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/scoped_enum.hpp @@ -0,0 +1,194 @@ +// scoped_enum.hpp ---------------------------------------------------------// + +// Copyright Beman Dawes, 2009 +// Copyright (C) 2011-2012 Vicente J. Botet Escriba +// Copyright (C) 2012 Anthony Williams + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_CORE_SCOPED_ENUM_HPP +#define BOOST_CORE_SCOPED_ENUM_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +namespace boost +{ + +#ifdef BOOST_NO_CXX11_SCOPED_ENUMS + + /** + * Meta-function to get the native enum type associated to an enum class or its emulation. + */ + template + struct native_type + { + /** + * The member typedef type names the native enum type associated to the scoped enum, + * which is it self if the compiler supports scoped enums or EnumType::enum_type if it is an emulated scoped enum. + */ + typedef typename EnumType::enum_type type; + }; + + /** + * Casts a scoped enum to its underlying type. + * + * This function is useful when working with scoped enum classes, which doens't implicitly convert to the underlying type. + * @param v A scoped enum. + * @returns The underlying type. + * @throws No-throws. + */ + template + inline + BOOST_CONSTEXPR UnderlyingType underlying_cast(EnumType v) BOOST_NOEXCEPT + { + return v.get_underlying_value_(); + } + + /** + * Casts a scoped enum to its native enum type. + * + * This function is useful to make programs portable when the scoped enum emulation can not be use where native enums can. + * + * EnumType the scoped enum type + * + * @param v A scoped enum. + * @returns The native enum value. + * @throws No-throws. + */ + template + inline + BOOST_CONSTEXPR typename EnumType::enum_type native_value(EnumType e) BOOST_NOEXCEPT + { + return e.get_native_value_(); + } + +#else // BOOST_NO_CXX11_SCOPED_ENUMS + + template + struct native_type + { + typedef EnumType type; + }; + + template + inline + BOOST_CONSTEXPR UnderlyingType underlying_cast(EnumType v) BOOST_NOEXCEPT + { + return static_cast(v); + } + + template + inline + BOOST_CONSTEXPR EnumType native_value(EnumType e) BOOST_NOEXCEPT + { + return e; + } + +#endif // BOOST_NO_CXX11_SCOPED_ENUMS +} + + +#ifdef BOOST_NO_CXX11_SCOPED_ENUMS + +#ifndef BOOST_NO_CXX11_EXPLICIT_CONVERSION_OPERATORS + +#define BOOST_SCOPED_ENUM_UT_DECLARE_CONVERSION_OPERATOR \ + explicit BOOST_CONSTEXPR operator underlying_type() const BOOST_NOEXCEPT { return get_underlying_value_(); } + +#else + +#define BOOST_SCOPED_ENUM_UT_DECLARE_CONVERSION_OPERATOR + +#endif + +/** + * Start a declaration of a scoped enum. + * + * @param EnumType The new scoped enum. + * @param UnderlyingType The underlying type. + */ +#define BOOST_SCOPED_ENUM_UT_DECLARE_BEGIN(EnumType, UnderlyingType) \ + struct EnumType { \ + typedef void is_boost_scoped_enum_tag; \ + typedef UnderlyingType underlying_type; \ + EnumType() BOOST_NOEXCEPT {} \ + explicit BOOST_CONSTEXPR EnumType(underlying_type v) BOOST_NOEXCEPT : v_(v) {} \ + BOOST_CONSTEXPR underlying_type get_underlying_value_() const BOOST_NOEXCEPT { return v_; } \ + BOOST_SCOPED_ENUM_UT_DECLARE_CONVERSION_OPERATOR \ + private: \ + underlying_type v_; \ + typedef EnumType self_type; \ + public: \ + enum enum_type + +#define BOOST_SCOPED_ENUM_DECLARE_END2() \ + BOOST_CONSTEXPR enum_type get_native_value_() const BOOST_NOEXCEPT { return enum_type(v_); } \ + friend BOOST_CONSTEXPR bool operator ==(self_type lhs, self_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)==enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator ==(self_type lhs, enum_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)==rhs; } \ + friend BOOST_CONSTEXPR bool operator ==(enum_type lhs, self_type rhs) BOOST_NOEXCEPT { return lhs==enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator !=(self_type lhs, self_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)!=enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator !=(self_type lhs, enum_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)!=rhs; } \ + friend BOOST_CONSTEXPR bool operator !=(enum_type lhs, self_type rhs) BOOST_NOEXCEPT { return lhs!=enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator <(self_type lhs, self_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)(self_type lhs, self_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)>enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator >(self_type lhs, enum_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)>rhs; } \ + friend BOOST_CONSTEXPR bool operator >(enum_type lhs, self_type rhs) BOOST_NOEXCEPT { return lhs>enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator >=(self_type lhs, self_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)>=enum_type(rhs.v_); } \ + friend BOOST_CONSTEXPR bool operator >=(self_type lhs, enum_type rhs) BOOST_NOEXCEPT { return enum_type(lhs.v_)>=rhs; } \ + friend BOOST_CONSTEXPR bool operator >=(enum_type lhs, self_type rhs) BOOST_NOEXCEPT { return lhs>=enum_type(rhs.v_); } \ + }; + +#define BOOST_SCOPED_ENUM_DECLARE_END(EnumType) \ + ; \ + BOOST_CONSTEXPR EnumType(enum_type v) BOOST_NOEXCEPT : v_(v) {} \ + BOOST_SCOPED_ENUM_DECLARE_END2() + +/** + * Starts a declaration of a scoped enum with the default int underlying type. + * + * @param EnumType The new scoped enum. + */ +#define BOOST_SCOPED_ENUM_DECLARE_BEGIN(EnumType) \ + BOOST_SCOPED_ENUM_UT_DECLARE_BEGIN(EnumType,int) + +/** + * Name of the native enum type. + * + * @param EnumType The new scoped enum. + */ +#define BOOST_SCOPED_ENUM_NATIVE(EnumType) EnumType::enum_type +/** + * Forward declares an scoped enum. + * + * @param EnumType The scoped enum. + */ +#define BOOST_SCOPED_ENUM_FORWARD_DECLARE(EnumType) struct EnumType + +#else // BOOST_NO_CXX11_SCOPED_ENUMS + +#define BOOST_SCOPED_ENUM_UT_DECLARE_BEGIN(EnumType,UnderlyingType) enum class EnumType : UnderlyingType +#define BOOST_SCOPED_ENUM_DECLARE_BEGIN(EnumType) enum class EnumType +#define BOOST_SCOPED_ENUM_DECLARE_END2() +#define BOOST_SCOPED_ENUM_DECLARE_END(EnumType) ; + +#define BOOST_SCOPED_ENUM_NATIVE(EnumType) EnumType +#define BOOST_SCOPED_ENUM_FORWARD_DECLARE(EnumType) enum class EnumType + +#endif // BOOST_NO_CXX11_SCOPED_ENUMS + +// Deprecated macros +#define BOOST_SCOPED_ENUM_START(name) BOOST_SCOPED_ENUM_DECLARE_BEGIN(name) +#define BOOST_SCOPED_ENUM_END BOOST_SCOPED_ENUM_DECLARE_END2() +#define BOOST_SCOPED_ENUM(name) BOOST_SCOPED_ENUM_NATIVE(name) + +#endif // BOOST_CORE_SCOPED_ENUM_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/core/swap.hpp b/thirdparty/source/boost_1_61_0/boost/core/swap.hpp new file mode 100644 index 0000000..baa1be9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/swap.hpp @@ -0,0 +1,60 @@ +// Copyright (C) 2007, 2008 Steven Watanabe, Joseph Gauterin, Niels Dekker +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// For more information, see http://www.boost.org + + +#ifndef BOOST_CORE_SWAP_HPP +#define BOOST_CORE_SWAP_HPP + +// Note: the implementation of this utility contains various workarounds: +// - swap_impl is put outside the boost namespace, to avoid infinite +// recursion (causing stack overflow) when swapping objects of a primitive +// type. +// - swap_impl has a using-directive, rather than a using-declaration, +// because some compilers (including MSVC 7.1, Borland 5.9.3, and +// Intel 8.1) don't do argument-dependent lookup when it has a +// using-declaration instead. +// - boost::swap has two template arguments, instead of one, to +// avoid ambiguity when swapping objects of a Boost type that does +// not have its own boost::swap overload. + +#include //for std::swap (C++11) +#include //for std::swap (C++98) +#include //for std::size_t +#include + +namespace boost_swap_impl +{ + template + BOOST_GPU_ENABLED + void swap_impl(T& left, T& right) + { + using namespace std;//use std::swap if argument dependent lookup fails + swap(left,right); + } + + template + BOOST_GPU_ENABLED + void swap_impl(T (& left)[N], T (& right)[N]) + { + for (std::size_t i = 0; i < N; ++i) + { + ::boost_swap_impl::swap_impl(left[i], right[i]); + } + } +} + +namespace boost +{ + template + BOOST_GPU_ENABLED + void swap(T1& left, T2& right) + { + ::boost_swap_impl::swap_impl(left, right); + } +} + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/core/typeinfo.hpp b/thirdparty/source/boost_1_61_0/boost/core/typeinfo.hpp new file mode 100644 index 0000000..e67b4a3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/core/typeinfo.hpp @@ -0,0 +1,151 @@ +#ifndef BOOST_CORE_TYPEINFO_HPP_INCLUDED +#define BOOST_CORE_TYPEINFO_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// core::typeinfo, BOOST_CORE_TYPEID +// +// Copyright 2007, 2014 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include + +#if defined( BOOST_NO_TYPEID ) + +#include +#include + +namespace boost +{ + +namespace core +{ + +class typeinfo +{ +private: + + typeinfo( typeinfo const& ); + typeinfo& operator=( typeinfo const& ); + + char const * name_; + +public: + + explicit typeinfo( char const * name ): name_( name ) + { + } + + bool operator==( typeinfo const& rhs ) const + { + return this == &rhs; + } + + bool operator!=( typeinfo const& rhs ) const + { + return this != &rhs; + } + + bool before( typeinfo const& rhs ) const + { + return std::less< typeinfo const* >()( this, &rhs ); + } + + char const* name() const + { + return name_; + } +}; + +inline char const * demangled_name( core::typeinfo const & ti ) +{ + return ti.name(); +} + +} // namespace core + +namespace detail +{ + +template struct core_typeid_ +{ + static boost::core::typeinfo ti_; + + static char const * name() + { + return BOOST_CURRENT_FUNCTION; + } +}; + +#if defined(__SUNPRO_CC) +// see #4199, the Sun Studio compiler gets confused about static initialization +// constructor arguments. But an assignment works just fine. +template boost::core::typeinfo core_typeid_< T >::ti_ = core_typeid_< T >::name(); +#else +template boost::core::typeinfo core_typeid_< T >::ti_(core_typeid_< T >::name()); +#endif + +template struct core_typeid_< T & >: core_typeid_< T > +{ +}; + +template struct core_typeid_< T const >: core_typeid_< T > +{ +}; + +template struct core_typeid_< T volatile >: core_typeid_< T > +{ +}; + +template struct core_typeid_< T const volatile >: core_typeid_< T > +{ +}; + +} // namespace detail + +} // namespace boost + +#define BOOST_CORE_TYPEID(T) (boost::detail::core_typeid_::ti_) + +#else + +#include +#include + +namespace boost +{ + +namespace core +{ + +#if defined( BOOST_NO_STD_TYPEINFO ) + +typedef ::type_info typeinfo; + +#else + +typedef std::type_info typeinfo; + +#endif + +inline std::string demangled_name( core::typeinfo const & ti ) +{ + return core::demangle( ti.name() ); +} + +} // namespace core + +} // namespace boost + +#define BOOST_CORE_TYPEID(T) typeid(T) + +#endif + +#endif // #ifndef BOOST_CORE_TYPEINFO_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/cregex.hpp b/thirdparty/source/boost_1_61_0/boost/cregex.hpp new file mode 100644 index 0000000..b7a918e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/cregex.hpp @@ -0,0 +1,39 @@ +/* + * + * Copyright (c) 1998-2002 + * John Maddock + * + * Use, modification and distribution are subject to the + * Boost Software License, Version 1.0. (See accompanying file + * LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * + */ + + /* + * LOCATION: see http://www.boost.org/libs/regex for most recent version. + * FILE cregex.cpp + * VERSION see + * DESCRIPTION: Declares POSIX API functions + * + boost::RegEx high level wrapper. + */ + +#ifndef BOOST_RE_CREGEX_HPP +#define BOOST_RE_CREGEX_HPP + +#ifndef BOOST_REGEX_CONFIG_HPP +#include +#endif + +#include + +#endif /* include guard */ + + + + + + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/cstdint.hpp b/thirdparty/source/boost_1_61_0/boost/cstdint.hpp new file mode 100644 index 0000000..bf7097e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/cstdint.hpp @@ -0,0 +1,546 @@ +// boost cstdint.hpp header file ------------------------------------------// + +// (C) Copyright Beman Dawes 1999. +// (C) Copyright Jens Mauer 2001 +// (C) Copyright John Maddock 2001 +// Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/integer for documentation. + +// Revision History +// 31 Oct 01 use BOOST_HAS_LONG_LONG to check for "long long" (Jens M.) +// 16 Apr 01 check LONGLONG_MAX when looking for "long long" (Jens Maurer) +// 23 Jan 01 prefer "long" over "int" for int32_t and intmax_t (Jens Maurer) +// 12 Nov 00 Merged (Jens Maurer) +// 23 Sep 00 Added INTXX_C macro support (John Maddock). +// 22 Sep 00 Better 64-bit support (John Maddock) +// 29 Jun 00 Reimplement to avoid including stdint.h within namespace boost +// 8 Aug 99 Initial version (Beman Dawes) + + +#ifndef BOOST_CSTDINT_HPP +#define BOOST_CSTDINT_HPP + +// +// Since we always define the INT#_C macros as per C++0x, +// define __STDC_CONSTANT_MACROS so that does the right +// thing if possible, and so that the user knows that the macros +// are actually defined as per C99. +// +#ifndef __STDC_CONSTANT_MACROS +# define __STDC_CONSTANT_MACROS +#endif + +#include + +// +// Note that GLIBC is a bit inconsistent about whether int64_t is defined or not +// depending upon what headers happen to have been included first... +// so we disable use of stdint.h when GLIBC does not define __GLIBC_HAVE_LONG_LONG. +// See https://svn.boost.org/trac/boost/ticket/3548 and http://sources.redhat.com/bugzilla/show_bug.cgi?id=10990 +// +#if defined(BOOST_HAS_STDINT_H) \ + && (!defined(__GLIBC__) \ + || defined(__GLIBC_HAVE_LONG_LONG) \ + || (defined(__GLIBC__) && ((__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 17))))) + +// The following #include is an implementation artifact; not part of interface. +# ifdef __hpux +// HP-UX has a vaguely nice in a non-standard location +# include +# ifdef __STDC_32_MODE__ + // this is triggered with GCC, because it defines __cplusplus < 199707L +# define BOOST_NO_INT64_T +# endif +# elif defined(__FreeBSD__) || defined(__IBMCPP__) || defined(_AIX) +# include +# else +# include + +// There is a bug in Cygwin two _C macros +# if defined(__STDC_CONSTANT_MACROS) && defined(__CYGWIN__) +# undef INTMAX_C +# undef UINTMAX_C +# define INTMAX_C(c) c##LL +# define UINTMAX_C(c) c##ULL +# endif + +# endif + +#if defined(__QNX__) && defined(__EXT_QNX) + +// QNX (Dinkumware stdlib) defines these as non-standard names. +// Reflect to the standard names. + +typedef ::intleast8_t int_least8_t; +typedef ::intfast8_t int_fast8_t; +typedef ::uintleast8_t uint_least8_t; +typedef ::uintfast8_t uint_fast8_t; + +typedef ::intleast16_t int_least16_t; +typedef ::intfast16_t int_fast16_t; +typedef ::uintleast16_t uint_least16_t; +typedef ::uintfast16_t uint_fast16_t; + +typedef ::intleast32_t int_least32_t; +typedef ::intfast32_t int_fast32_t; +typedef ::uintleast32_t uint_least32_t; +typedef ::uintfast32_t uint_fast32_t; + +# ifndef BOOST_NO_INT64_T + +typedef ::intleast64_t int_least64_t; +typedef ::intfast64_t int_fast64_t; +typedef ::uintleast64_t uint_least64_t; +typedef ::uintfast64_t uint_fast64_t; + +# endif + +#endif + +namespace boost +{ + + using ::int8_t; + using ::int_least8_t; + using ::int_fast8_t; + using ::uint8_t; + using ::uint_least8_t; + using ::uint_fast8_t; + + using ::int16_t; + using ::int_least16_t; + using ::int_fast16_t; + using ::uint16_t; + using ::uint_least16_t; + using ::uint_fast16_t; + + using ::int32_t; + using ::int_least32_t; + using ::int_fast32_t; + using ::uint32_t; + using ::uint_least32_t; + using ::uint_fast32_t; + +# ifndef BOOST_NO_INT64_T + + using ::int64_t; + using ::int_least64_t; + using ::int_fast64_t; + using ::uint64_t; + using ::uint_least64_t; + using ::uint_fast64_t; + +# endif + + using ::intmax_t; + using ::uintmax_t; + +} // namespace boost + +#elif defined(__FreeBSD__) && (__FreeBSD__ <= 4) || defined(__osf__) || defined(__VMS) || defined(__SOLARIS9__) || defined(__NetBSD__) +// FreeBSD and Tru64 have an that contains much of what we need. +# include + +namespace boost { + + using ::int8_t; + typedef int8_t int_least8_t; + typedef int8_t int_fast8_t; + using ::uint8_t; + typedef uint8_t uint_least8_t; + typedef uint8_t uint_fast8_t; + + using ::int16_t; + typedef int16_t int_least16_t; + typedef int16_t int_fast16_t; + using ::uint16_t; + typedef uint16_t uint_least16_t; + typedef uint16_t uint_fast16_t; + + using ::int32_t; + typedef int32_t int_least32_t; + typedef int32_t int_fast32_t; + using ::uint32_t; + typedef uint32_t uint_least32_t; + typedef uint32_t uint_fast32_t; + +# ifndef BOOST_NO_INT64_T + + using ::int64_t; + typedef int64_t int_least64_t; + typedef int64_t int_fast64_t; + using ::uint64_t; + typedef uint64_t uint_least64_t; + typedef uint64_t uint_fast64_t; + + typedef int64_t intmax_t; + typedef uint64_t uintmax_t; + +# else + + typedef int32_t intmax_t; + typedef uint32_t uintmax_t; + +# endif + +} // namespace boost + +#else // BOOST_HAS_STDINT_H + +# include // implementation artifact; not part of interface +# include // needed for limits macros + + +namespace boost +{ + +// These are fairly safe guesses for some 16-bit, and most 32-bit and 64-bit +// platforms. For other systems, they will have to be hand tailored. +// +// Because the fast types are assumed to be the same as the undecorated types, +// it may be possible to hand tailor a more efficient implementation. Such +// an optimization may be illusionary; on the Intel x86-family 386 on, for +// example, byte arithmetic and load/stores are as fast as "int" sized ones. + +// 8-bit types ------------------------------------------------------------// + +# if UCHAR_MAX == 0xff + typedef signed char int8_t; + typedef signed char int_least8_t; + typedef signed char int_fast8_t; + typedef unsigned char uint8_t; + typedef unsigned char uint_least8_t; + typedef unsigned char uint_fast8_t; +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif + +// 16-bit types -----------------------------------------------------------// + +# if USHRT_MAX == 0xffff +# if defined(__crayx1) + // The Cray X1 has a 16-bit short, however it is not recommend + // for use in performance critical code. + typedef short int16_t; + typedef short int_least16_t; + typedef int int_fast16_t; + typedef unsigned short uint16_t; + typedef unsigned short uint_least16_t; + typedef unsigned int uint_fast16_t; +# else + typedef short int16_t; + typedef short int_least16_t; + typedef short int_fast16_t; + typedef unsigned short uint16_t; + typedef unsigned short uint_least16_t; + typedef unsigned short uint_fast16_t; +# endif +# elif (USHRT_MAX == 0xffffffff) && defined(__MTA__) + // On MTA / XMT short is 32 bits unless the -short16 compiler flag is specified + // MTA / XMT does support the following non-standard integer types + typedef __short16 int16_t; + typedef __short16 int_least16_t; + typedef __short16 int_fast16_t; + typedef unsigned __short16 uint16_t; + typedef unsigned __short16 uint_least16_t; + typedef unsigned __short16 uint_fast16_t; +# elif (USHRT_MAX == 0xffffffff) && defined(CRAY) + // no 16-bit types on Cray: + typedef short int_least16_t; + typedef short int_fast16_t; + typedef unsigned short uint_least16_t; + typedef unsigned short uint_fast16_t; +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif + +// 32-bit types -----------------------------------------------------------// + +# if UINT_MAX == 0xffffffff + typedef int int32_t; + typedef int int_least32_t; + typedef int int_fast32_t; + typedef unsigned int uint32_t; + typedef unsigned int uint_least32_t; + typedef unsigned int uint_fast32_t; +# elif (USHRT_MAX == 0xffffffff) + typedef short int32_t; + typedef short int_least32_t; + typedef short int_fast32_t; + typedef unsigned short uint32_t; + typedef unsigned short uint_least32_t; + typedef unsigned short uint_fast32_t; +# elif ULONG_MAX == 0xffffffff + typedef long int32_t; + typedef long int_least32_t; + typedef long int_fast32_t; + typedef unsigned long uint32_t; + typedef unsigned long uint_least32_t; + typedef unsigned long uint_fast32_t; +# elif (UINT_MAX == 0xffffffffffffffff) && defined(__MTA__) + // Integers are 64 bits on the MTA / XMT + typedef __int32 int32_t; + typedef __int32 int_least32_t; + typedef __int32 int_fast32_t; + typedef unsigned __int32 uint32_t; + typedef unsigned __int32 uint_least32_t; + typedef unsigned __int32 uint_fast32_t; +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif + +// 64-bit types + intmax_t and uintmax_t ----------------------------------// + +# if defined(BOOST_HAS_LONG_LONG) && \ + !defined(BOOST_MSVC) && !defined(__BORLANDC__) && \ + (!defined(__GLIBCPP__) || defined(_GLIBCPP_USE_LONG_LONG)) && \ + (defined(ULLONG_MAX) || defined(ULONG_LONG_MAX) || defined(ULONGLONG_MAX)) +# if defined(__hpux) + // HP-UX's value of ULONG_LONG_MAX is unusable in preprocessor expressions +# elif (defined(ULLONG_MAX) && ULLONG_MAX == 18446744073709551615ULL) || (defined(ULONG_LONG_MAX) && ULONG_LONG_MAX == 18446744073709551615ULL) || (defined(ULONGLONG_MAX) && ULONGLONG_MAX == 18446744073709551615ULL) + // 2**64 - 1 +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif + + typedef ::boost::long_long_type intmax_t; + typedef ::boost::ulong_long_type uintmax_t; + typedef ::boost::long_long_type int64_t; + typedef ::boost::long_long_type int_least64_t; + typedef ::boost::long_long_type int_fast64_t; + typedef ::boost::ulong_long_type uint64_t; + typedef ::boost::ulong_long_type uint_least64_t; + typedef ::boost::ulong_long_type uint_fast64_t; + +# elif ULONG_MAX != 0xffffffff + +# if ULONG_MAX == 18446744073709551615 // 2**64 - 1 + typedef long intmax_t; + typedef unsigned long uintmax_t; + typedef long int64_t; + typedef long int_least64_t; + typedef long int_fast64_t; + typedef unsigned long uint64_t; + typedef unsigned long uint_least64_t; + typedef unsigned long uint_fast64_t; +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif +# elif defined(__GNUC__) && defined(BOOST_HAS_LONG_LONG) + __extension__ typedef long long intmax_t; + __extension__ typedef unsigned long long uintmax_t; + __extension__ typedef long long int64_t; + __extension__ typedef long long int_least64_t; + __extension__ typedef long long int_fast64_t; + __extension__ typedef unsigned long long uint64_t; + __extension__ typedef unsigned long long uint_least64_t; + __extension__ typedef unsigned long long uint_fast64_t; +# elif defined(BOOST_HAS_MS_INT64) + // + // we have Borland/Intel/Microsoft __int64: + // + typedef __int64 intmax_t; + typedef unsigned __int64 uintmax_t; + typedef __int64 int64_t; + typedef __int64 int_least64_t; + typedef __int64 int_fast64_t; + typedef unsigned __int64 uint64_t; + typedef unsigned __int64 uint_least64_t; + typedef unsigned __int64 uint_fast64_t; +# else // assume no 64-bit integers +# define BOOST_NO_INT64_T + typedef int32_t intmax_t; + typedef uint32_t uintmax_t; +# endif + +} // namespace boost + + +#endif // BOOST_HAS_STDINT_H + +// intptr_t/uintptr_t are defined separately because they are optional and not universally available +#if defined(BOOST_WINDOWS) && !defined(_WIN32_WCE) && !defined(BOOST_HAS_STDINT_H) +// Older MSVC don't have stdint.h and have intptr_t/uintptr_t defined in stddef.h +#include +#endif + +// PGI seems to not support intptr_t/uintptr_t properly. BOOST_HAS_STDINT_H is not defined for this compiler by Boost.Config. +#if !defined(__PGIC__) + +#if (defined(BOOST_WINDOWS) && !defined(_WIN32_WCE)) \ + || (defined(_XOPEN_UNIX) && (_XOPEN_UNIX+0 > 0) && !defined(__UCLIBC__)) \ + || defined(__CYGWIN__) \ + || defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__) \ + || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) || defined(sun) + +namespace boost { + using ::intptr_t; + using ::uintptr_t; +} +#define BOOST_HAS_INTPTR_T + +// Clang pretends to be GCC, so it'll match this condition +#elif defined(__GNUC__) && defined(__INTPTR_TYPE__) && defined(__UINTPTR_TYPE__) + +namespace boost { + typedef __INTPTR_TYPE__ intptr_t; + typedef __UINTPTR_TYPE__ uintptr_t; +} +#define BOOST_HAS_INTPTR_T + +#endif + +#endif // !defined(__PGIC__) + +#endif // BOOST_CSTDINT_HPP + + +/**************************************************** + +Macro definition section: + +Added 23rd September 2000 (John Maddock). +Modified 11th September 2001 to be excluded when +BOOST_HAS_STDINT_H is defined (John Maddock). +Modified 11th Dec 2009 to always define the +INT#_C macros if they're not already defined (John Maddock). + +******************************************************/ + +#if !defined(BOOST__STDC_CONSTANT_MACROS_DEFINED) && \ + (!defined(INT8_C) || !defined(INT16_C) || !defined(INT32_C) || !defined(INT64_C)) +// +// For the following code we get several warnings along the lines of: +// +// boost/cstdint.hpp:428:35: error: use of C99 long long integer constant +// +// So we declare this a system header to suppress these warnings. +// +#if defined(__GNUC__) && (__GNUC__ >= 4) +#pragma GCC system_header +#endif + +#include +# define BOOST__STDC_CONSTANT_MACROS_DEFINED +# if defined(BOOST_HAS_MS_INT64) +// +// Borland/Intel/Microsoft compilers have width specific suffixes: +// +#ifndef INT8_C +# define INT8_C(value) value##i8 +#endif +#ifndef INT16_C +# define INT16_C(value) value##i16 +#endif +#ifndef INT32_C +# define INT32_C(value) value##i32 +#endif +#ifndef INT64_C +# define INT64_C(value) value##i64 +#endif +# ifdef __BORLANDC__ + // Borland bug: appending ui8 makes the type a signed char +# define UINT8_C(value) static_cast(value##u) +# else +# define UINT8_C(value) value##ui8 +# endif +#ifndef UINT16_C +# define UINT16_C(value) value##ui16 +#endif +#ifndef UINT32_C +# define UINT32_C(value) value##ui32 +#endif +#ifndef UINT64_C +# define UINT64_C(value) value##ui64 +#endif +#ifndef INTMAX_C +# define INTMAX_C(value) value##i64 +# define UINTMAX_C(value) value##ui64 +#endif + +# else +// do it the old fashioned way: + +// 8-bit types ------------------------------------------------------------// + +# if (UCHAR_MAX == 0xff) && !defined(INT8_C) +# define INT8_C(value) static_cast(value) +# define UINT8_C(value) static_cast(value##u) +# endif + +// 16-bit types -----------------------------------------------------------// + +# if (USHRT_MAX == 0xffff) && !defined(INT16_C) +# define INT16_C(value) static_cast(value) +# define UINT16_C(value) static_cast(value##u) +# endif + +// 32-bit types -----------------------------------------------------------// +#ifndef INT32_C +# if (UINT_MAX == 0xffffffff) +# define INT32_C(value) value +# define UINT32_C(value) value##u +# elif ULONG_MAX == 0xffffffff +# define INT32_C(value) value##L +# define UINT32_C(value) value##uL +# endif +#endif + +// 64-bit types + intmax_t and uintmax_t ----------------------------------// +#ifndef INT64_C +# if defined(BOOST_HAS_LONG_LONG) && \ + (defined(ULLONG_MAX) || defined(ULONG_LONG_MAX) || defined(ULONGLONG_MAX) || defined(_ULLONG_MAX) || defined(_LLONG_MAX)) + +# if defined(__hpux) + // HP-UX's value of ULONG_LONG_MAX is unusable in preprocessor expressions +# define INT64_C(value) value##LL +# define UINT64_C(value) value##uLL +# elif (defined(ULLONG_MAX) && ULLONG_MAX == 18446744073709551615ULL) || \ + (defined(ULONG_LONG_MAX) && ULONG_LONG_MAX == 18446744073709551615ULL) || \ + (defined(ULONGLONG_MAX) && ULONGLONG_MAX == 18446744073709551615ULL) || \ + (defined(_ULLONG_MAX) && _ULLONG_MAX == 18446744073709551615ULL) || \ + (defined(_LLONG_MAX) && _LLONG_MAX == 9223372036854775807LL) + +# define INT64_C(value) value##LL +# define UINT64_C(value) value##uLL +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif +# elif ULONG_MAX != 0xffffffff + +# if ULONG_MAX == 18446744073709551615U // 2**64 - 1 +# define INT64_C(value) value##L +# define UINT64_C(value) value##uL +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif +# elif defined(BOOST_HAS_LONG_LONG) + // Usual macros not defined, work things out for ourselves: +# if(~0uLL == 18446744073709551615ULL) +# define INT64_C(value) value##LL +# define UINT64_C(value) value##uLL +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif +# else +# error defaults not correct; you must hand modify boost/cstdint.hpp +# endif + +# ifdef BOOST_NO_INT64_T +# define INTMAX_C(value) INT32_C(value) +# define UINTMAX_C(value) UINT32_C(value) +# else +# define INTMAX_C(value) INT64_C(value) +# define UINTMAX_C(value) UINT64_C(value) +# endif +#endif +# endif // Borland/Microsoft specific width suffixes + +#endif // INT#_C macros. + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/cstdlib.hpp b/thirdparty/source/boost_1_61_0/boost/cstdlib.hpp new file mode 100644 index 0000000..6322146 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/cstdlib.hpp @@ -0,0 +1,41 @@ +// boost/cstdlib.hpp header ------------------------------------------------// + +// Copyright Beman Dawes 2001. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/utility/cstdlib.html for documentation. + +// Revision History +// 26 Feb 01 Initial version (Beman Dawes) + +#ifndef BOOST_CSTDLIB_HPP +#define BOOST_CSTDLIB_HPP + +#include + +namespace boost +{ + // The intent is to propose the following for addition to namespace std + // in the C++ Standard Library, and to then deprecate EXIT_SUCCESS and + // EXIT_FAILURE. As an implementation detail, this header defines the + // new constants in terms of EXIT_SUCCESS and EXIT_FAILURE. In a new + // standard, the constants would be implementation-defined, although it + // might be worthwhile to "suggest" (which a standard is allowed to do) + // values of 0 and 1 respectively. + + // Rationale for having multiple failure values: some environments may + // wish to distinguish between different classes of errors. + // Rationale for choice of values: programs often use values < 100 for + // their own error reporting. Values > 255 are sometimes reserved for + // system detected errors. 200/201 were suggested to minimize conflict. + + const int exit_success = EXIT_SUCCESS; // implementation-defined value + const int exit_failure = EXIT_FAILURE; // implementation-defined value + const int exit_exception_failure = 200; // otherwise uncaught exception + const int exit_test_failure = 201; // report_error or + // report_critical_error called. +} + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/current_function.hpp b/thirdparty/source/boost_1_61_0/boost/current_function.hpp new file mode 100644 index 0000000..5c113f8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/current_function.hpp @@ -0,0 +1,71 @@ +#ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED +#define BOOST_CURRENT_FUNCTION_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// boost/current_function.hpp - BOOST_CURRENT_FUNCTION +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// http://www.boost.org/libs/assert/current_function.html +// + +namespace boost +{ + +namespace detail +{ + +inline void current_function_helper() +{ + +#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__) + +# define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__ + +#elif defined(__DMC__) && (__DMC__ >= 0x810) + +# define BOOST_CURRENT_FUNCTION __PRETTY_FUNCTION__ + +#elif defined(__FUNCSIG__) + +# define BOOST_CURRENT_FUNCTION __FUNCSIG__ + +#elif (defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || (defined(__IBMCPP__) && (__IBMCPP__ >= 500)) + +# define BOOST_CURRENT_FUNCTION __FUNCTION__ + +#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550) + +# define BOOST_CURRENT_FUNCTION __FUNC__ + +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) + +# define BOOST_CURRENT_FUNCTION __func__ + +#elif defined(__cplusplus) && (__cplusplus >= 201103) + +# define BOOST_CURRENT_FUNCTION __func__ + +#else + +# define BOOST_CURRENT_FUNCTION "(unknown)" + +#endif + +} + +} // namespace detail + +} // namespace boost + +#endif // #ifndef BOOST_CURRENT_FUNCTION_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/adjust_functors.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/adjust_functors.hpp new file mode 100644 index 0000000..f6c5a04 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/adjust_functors.hpp @@ -0,0 +1,178 @@ +#ifndef _DATE_TIME_ADJUST_FUNCTORS_HPP___ +#define _DATE_TIME_ADJUST_FUNCTORS_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/date.hpp" +#include "boost/date_time/wrapping_int.hpp" + +namespace boost { +namespace date_time { + + + //! Functor to iterate a fixed number of days + template + class day_functor + { + public: + typedef typename date_type::duration_type duration_type; + day_functor(int f) : f_(f) {} + duration_type get_offset(const date_type& d) const + { + // why is 'd' a parameter??? + // fix compiler warnings + d.year(); + return duration_type(f_); + } + duration_type get_neg_offset(const date_type& d) const + { + // fix compiler warnings + d.year(); + return duration_type(-f_); + } + private: + int f_; + }; + + + //! Provides calculation to find next nth month given a date + /*! This adjustment function provides the logic for 'month-based' + * advancement on a ymd based calendar. The policy it uses + * to handle the non existant end of month days is to back + * up to the last day of the month. Also, if the starting + * date is the last day of a month, this functor will attempt + * to adjust to the end of the month. + + */ + template + class month_functor + { + public: + typedef typename date_type::duration_type duration_type; + typedef typename date_type::calendar_type cal_type; + typedef typename cal_type::ymd_type ymd_type; + typedef typename cal_type::day_type day_type; + + month_functor(int f) : f_(f), origDayOfMonth_(0) {} + duration_type get_offset(const date_type& d) const + { + ymd_type ymd(d.year_month_day()); + if (origDayOfMonth_ == 0) { + origDayOfMonth_ = ymd.day; + day_type endOfMonthDay(cal_type::end_of_month_day(ymd.year,ymd.month)); + if (endOfMonthDay == ymd.day) { + origDayOfMonth_ = -1; //force the value to the end of month + } + } + typedef date_time::wrapping_int2 wrap_int2; + typedef typename wrap_int2::int_type int_type; + wrap_int2 wi(ymd.month); + //calc the year wrap around, add() returns 0 or 1 if wrapped + int_type year = wi.add(static_cast(f_)); + year = static_cast(year + ymd.year); //calculate resulting year +// std::cout << "trace wi: " << wi.as_int() << std::endl; +// std::cout << "trace year: " << year << std::endl; + //find the last day for the new month + day_type resultingEndOfMonthDay(cal_type::end_of_month_day(year, wi.as_int())); + //original was the end of month -- force to last day of month + if (origDayOfMonth_ == -1) { + return date_type(year, wi.as_int(), resultingEndOfMonthDay) - d; + } + day_type dayOfMonth = origDayOfMonth_; + if (dayOfMonth > resultingEndOfMonthDay) { + dayOfMonth = resultingEndOfMonthDay; + } + return date_type(year, wi.as_int(), dayOfMonth) - d; + } + //! Returns a negative duration_type + duration_type get_neg_offset(const date_type& d) const + { + ymd_type ymd(d.year_month_day()); + if (origDayOfMonth_ == 0) { + origDayOfMonth_ = ymd.day; + day_type endOfMonthDay(cal_type::end_of_month_day(ymd.year,ymd.month)); + if (endOfMonthDay == ymd.day) { + origDayOfMonth_ = -1; //force the value to the end of month + } + } + typedef date_time::wrapping_int2 wrap_int2; + typedef typename wrap_int2::int_type int_type; + wrap_int2 wi(ymd.month); + //calc the year wrap around, add() returns 0 or 1 if wrapped + int_type year = wi.subtract(static_cast(f_)); + year = static_cast(year + ymd.year); //calculate resulting year + //find the last day for the new month + day_type resultingEndOfMonthDay(cal_type::end_of_month_day(year, wi.as_int())); + //original was the end of month -- force to last day of month + if (origDayOfMonth_ == -1) { + return date_type(year, wi.as_int(), resultingEndOfMonthDay) - d; + } + day_type dayOfMonth = origDayOfMonth_; + if (dayOfMonth > resultingEndOfMonthDay) { + dayOfMonth = resultingEndOfMonthDay; + } + return date_type(year, wi.as_int(), dayOfMonth) - d; + } + private: + int f_; + mutable short origDayOfMonth_; + }; + + + //! Functor to iterate a over weeks + template + class week_functor + { + public: + typedef typename date_type::duration_type duration_type; + typedef typename date_type::calendar_type calendar_type; + week_functor(int f) : f_(f) {} + duration_type get_offset(const date_type& d) const + { + // why is 'd' a parameter??? + // fix compiler warnings + d.year(); + return duration_type(f_*calendar_type::days_in_week()); + } + duration_type get_neg_offset(const date_type& d) const + { + // fix compiler warnings + d.year(); + return duration_type(-f_*calendar_type::days_in_week()); + } + private: + int f_; + }; + + //! Functor to iterate by a year adjusting for leap years + template + class year_functor + { + public: + //typedef typename date_type::year_type year_type; + typedef typename date_type::duration_type duration_type; + year_functor(int f) : _mf(f * 12) {} + duration_type get_offset(const date_type& d) const + { + return _mf.get_offset(d); + } + duration_type get_neg_offset(const date_type& d) const + { + return _mf.get_neg_offset(d); + } + private: + month_functor _mf; + }; + + +} }//namespace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/c_time.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/c_time.hpp new file mode 100644 index 0000000..5998908 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/c_time.hpp @@ -0,0 +1,123 @@ +#ifndef DATE_TIME_C_TIME_HPP___ +#define DATE_TIME_C_TIME_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +/*! @file c_time.hpp + Provide workarounds related to the ctime header +*/ + +#include +#include // to be able to convert from string literals to exceptions +#include +#include +#include + +//Work around libraries that don't put time_t and time in namespace std +#ifdef BOOST_NO_STDC_NAMESPACE +namespace std { using ::time_t; using ::time; using ::localtime; + using ::tm; using ::gmtime; } +#endif // BOOST_NO_STDC_NAMESPACE + +//The following is used to support high precision time clocks +#ifdef BOOST_HAS_GETTIMEOFDAY +#include +#endif + +#ifdef BOOST_HAS_FTIME +#include +#endif + +namespace boost { +namespace date_time { + //! Provides a uniform interface to some 'ctime' functions + /*! Provides a uniform interface to some ctime functions and + * their '_r' counterparts. The '_r' functions require a pointer to a + * user created std::tm struct whereas the regular functions use a + * staticly created struct and return a pointer to that. These wrapper + * functions require the user to create a std::tm struct and send in a + * pointer to it. This struct may be used to store the resulting time. + * The returned pointer may or may not point to this struct, however, + * it will point to the result of the corresponding function. + * All functions do proper checking of the C function results and throw + * exceptions on error. Therefore the functions will never return NULL. + */ + struct c_time { + public: +#if defined(BOOST_DATE_TIME_HAS_REENTRANT_STD_FUNCTIONS) + //! requires a pointer to a user created std::tm struct + inline + static std::tm* localtime(const std::time_t* t, std::tm* result) + { + // localtime_r() not in namespace std??? + #if defined(__VMS) && __INITIAL_POINTER_SIZE == 64 + std::tm tmp; + if(!localtime_r(t,&tmp)) + result = 0; + else + *result = tmp; + #else + result = localtime_r(t, result); + #endif + if (!result) + boost::throw_exception(std::runtime_error("could not convert calendar time to local time")); + return result; + } + //! requires a pointer to a user created std::tm struct + inline + static std::tm* gmtime(const std::time_t* t, std::tm* result) + { + // gmtime_r() not in namespace std??? + #if defined(__VMS) && __INITIAL_POINTER_SIZE == 64 + std::tm tmp; + if(!gmtime_r(t,&tmp)) + result = 0; + else + *result = tmp; + #else + result = gmtime_r(t, result); + #endif + if (!result) + boost::throw_exception(std::runtime_error("could not convert calendar time to UTC time")); + return result; + } +#else // BOOST_DATE_TIME_HAS_REENTRANT_STD_FUNCTIONS + +#if (defined(_MSC_VER) && (_MSC_VER >= 1400)) +#pragma warning(push) // preserve warning settings +#pragma warning(disable : 4996) // disable depricated localtime/gmtime warning on vc8 +#endif // _MSC_VER >= 1400 + //! requires a pointer to a user created std::tm struct + inline + static std::tm* localtime(const std::time_t* t, std::tm* result) + { + result = std::localtime(t); + if (!result) + boost::throw_exception(std::runtime_error("could not convert calendar time to local time")); + return result; + } + //! requires a pointer to a user created std::tm struct + inline + static std::tm* gmtime(const std::time_t* t, std::tm* result) + { + result = std::gmtime(t); + if (!result) + boost::throw_exception(std::runtime_error("could not convert calendar time to UTC time")); + return result; + } +#if (defined(_MSC_VER) && (_MSC_VER >= 1400)) +#pragma warning(pop) // restore warnings to previous state +#endif // _MSC_VER >= 1400 + +#endif // BOOST_DATE_TIME_HAS_REENTRANT_STD_FUNCTIONS + }; +}} // namespaces + +#endif // DATE_TIME_C_TIME_HPP___ diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/compiler_config.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/compiler_config.hpp new file mode 100644 index 0000000..e37d061 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/compiler_config.hpp @@ -0,0 +1,169 @@ +#ifndef DATE_TIME_COMPILER_CONFIG_HPP___ +#define DATE_TIME_COMPILER_CONFIG_HPP___ + +/* Copyright (c) 2002-2004 CrystalClear Software, Inc. + * Subject to the Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include + +// With boost release 1.33, date_time will be using a different, +// more flexible, IO system. This new system is not compatible with +// old compilers. The original date_time IO system remains for those +// compilers. They must define this macro to use the legacy IO. +// (defined(__BORLANDC__) && (__BORLANDC__ <= 0x0581) ) ) && + #if( BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0x581) ) \ + || BOOST_WORKAROUND( __GNUC__, < 3) \ + || (BOOST_WORKAROUND( _MSC_VER, <= 1300) ) \ + ) \ + && !defined(USE_DATE_TIME_PRE_1_33_FACET_IO) +# define USE_DATE_TIME_PRE_1_33_FACET_IO +#endif + + +// This file performs some local compiler configurations + +#include //set up locale configurations + +//Set up a configuration parameter for platforms that have +//GetTimeOfDay +#if defined(BOOST_HAS_GETTIMEOFDAY) || defined(BOOST_HAS_FTIME) +#define BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK +#endif + +// To Force no default constructors for date & ptime, un-comment following +//#define DATE_TIME_NO_DEFAULT_CONSTRUCTOR + +// Include extensions to date_duration - comment out to remove this feature +#define BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES +// these extensions are known to cause problems with gcc295 +#if defined(__GNUC__) && (__GNUC__ < 3) +#undef BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES +#endif + +#if (defined(BOOST_NO_INCLASS_MEMBER_INITIALIZATION) || BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0x581) ) ) +#define BOOST_DATE_TIME_NO_MEMBER_INIT +#endif + +// include these types before we try to re-define them +#include + +//Define INT64_C for compilers that don't have it +#if (!defined(INT64_C)) +#define INT64_C(value) int64_t(value) +#endif + + +/* Workaround for Borland iterator error. Error was "Cannot convert 'istream *' to 'wistream *' in function istream_iterator<>::istream_iterator() */ +#if defined(__BORLANDC__) && defined(BOOST_BCB_WITH_RW_LIB) +#define BOOST_DATE_TIME_NO_WISTREAM_ITERATOR +#endif + + +// Borland v5.64 does not have the following in std namespace; v5.5.1 does +#if defined(__BORLANDC__) && defined(BOOST_BCB_WITH_STLPORT) +#include +namespace std { + using stlport::tolower; + using stlport::ctype; + using stlport::use_facet; +} +#endif + +// workaround for errors associated with output for date classes +// modifications and input streaming for time classes. +// Compilers affected are: +// gcc295, msvc (neither with STLPort), any borland +// +#if (((defined(__GNUC__) && (__GNUC__ < 3)) || \ + (defined(_MSC_VER) && (_MSC_VER < 1300)) ) && \ + !defined(_STLP_OWN_IOSTREAMS) ) || \ + BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0x581) ) +#define BOOST_DATE_TIME_INCLUDE_LIMITED_HEADERS +#endif + +// The macro marks up places where compiler complains for missing return statement or +// uninitialized variables after calling to boost::throw_exception. +// BOOST_UNREACHABLE_RETURN doesn't work since even compilers that support +// unreachable statements detection emit such warnings. +#if defined(_MSC_VER) +// Use special MSVC extension to markup unreachable code +# define BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(x) __assume(false) +#elif !defined(BOOST_NO_UNREACHABLE_RETURN_DETECTION) +// Call to a non-returning function should suppress the warning +# if defined(BOOST_NO_STDC_NAMESPACE) +namespace std { + using ::abort; +} +# endif // defined(BOOST_NO_STDC_NAMESPACE) +# define BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(x) std::abort() +#else +// For other poor compilers the specified expression is compiled. Usually, this would be a return statement. +# define BOOST_DATE_TIME_UNREACHABLE_EXPRESSION(x) x +#endif + +/* The following handles the definition of the necessary macros + * for dll building on Win32 platforms. + * + * For code that will be placed in the date_time .dll, + * it must be properly prefixed with BOOST_DATE_TIME_DECL. + * The corresponding .cpp file must have BOOST_DATE_TIME_SOURCE + * defined before including its header. For examples see: + * greg_month.hpp & greg_month.cpp + * + */ + +// we need to import/export our code only if the user has specifically +// asked for it by defining either BOOST_ALL_DYN_LINK if they want all boost +// libraries to be dynamically linked, or BOOST_DATE_TIME_DYN_LINK +// if they want just this one to be dynamically liked: +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_DATE_TIME_DYN_LINK) + // export if this is our own source, otherwise import: +# ifdef BOOST_DATE_TIME_SOURCE +# define BOOST_DATE_TIME_DECL BOOST_SYMBOL_EXPORT +# else +# define BOOST_DATE_TIME_DECL BOOST_SYMBOL_IMPORT +# endif // BOOST_DATE_TIME_SOURCE +#endif // DYN_LINK +// +// if BOOST_WHATEVER_DECL isn't defined yet define it now: +#ifndef BOOST_DATE_TIME_DECL +# define BOOST_DATE_TIME_DECL +#endif + +// +// Automatically link to the correct build variant where possible. +// +#if !defined(BOOST_ALL_NO_LIB) && !defined(BOOST_DATE_TIME_NO_LIB) && !defined(BOOST_DATE_TIME_SOURCE) +// +// Set the name of our library, this will get undef'ed by auto_link.hpp +// once it's done with it: +// +#define BOOST_LIB_NAME boost_date_time +// +// If we're importing code from a dll, then tell auto_link.hpp about it: +// +#if defined(BOOST_ALL_DYN_LINK) || defined(BOOST_DATE_TIME_DYN_LINK) +# define BOOST_DYN_LINK +#endif +// +// And include the header that does the work: +// +#include +#endif // auto-linking disabled + +#if defined(BOOST_HAS_THREADS) +# if defined(_MSC_VER) || defined(__MWERKS__) || defined(__MINGW32__) || defined(__BORLANDC__) + //no reentrant posix functions (eg: localtime_r) +# elif (!defined(__hpux) || (defined(__hpux) && defined(_REENTRANT))) +# define BOOST_DATE_TIME_HAS_REENTRANT_STD_FUNCTIONS +# endif +#endif + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/constrained_value.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/constrained_value.hpp new file mode 100644 index 0000000..910e99a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/constrained_value.hpp @@ -0,0 +1,121 @@ +#ifndef CONSTRAINED_VALUE_HPP___ +#define CONSTRAINED_VALUE_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include +#include +#include +#include +#include +#include + +namespace boost { + +//! Namespace containing constrained_value template and types +namespace CV { + //! Represent a min or max violation type + enum violation_enum {min_violation, max_violation}; + + //! A template to specify a constrained basic value type + /*! This template provides a quick way to generate + * an integer type with a constrained range. The type + * provides for the ability to specify the min, max, and + * and error handling policy. + * + * value policies + * A class that provides the range limits via the min and + * max functions as well as a function on_error that + * determines how errors are handled. A common strategy + * would be to assert or throw and exception. The on_error + * is passed both the current value and the new value that + * is in error. + * + */ + template + class constrained_value { + public: + typedef typename value_policies::value_type value_type; + // typedef except_type exception_type; + constrained_value(value_type value) : value_((min)()) + { + assign(value); + } + constrained_value& operator=(value_type v) + { + assign(v); + return *this; + } + //! Return the max allowed value (traits method) + static value_type max BOOST_PREVENT_MACRO_SUBSTITUTION () {return (value_policies::max)();} + //! Return the min allowed value (traits method) + static value_type min BOOST_PREVENT_MACRO_SUBSTITUTION () {return (value_policies::min)();} + //! Coerce into the representation type + operator value_type() const {return value_;} + protected: + value_type value_; + private: + void assign(value_type value) + { + //adding 1 below gets rid of a compiler warning which occurs when the + //min_value is 0 and the type is unsigned.... + if (value+1 < (min)()+1) { + value_policies::on_error(value_, value, min_violation); + return; + } + if (value > (max)()) { + value_policies::on_error(value_, value, max_violation); + return; + } + value_ = value; + } +}; + + //! Template to shortcut the constrained_value policy creation process + template + class simple_exception_policy + { + struct exception_wrapper : public exception_type + { + // In order to support throw_exception mechanism in the BOOST_NO_EXCEPTIONS mode, + // we'll have to provide a way to acquire std::exception from the exception being thrown. + // However, we cannot derive from it, since it would make it interceptable by this class, + // which might not be what the user wanted. + operator std::out_of_range () const + { + // TODO: Make the message more descriptive by using arguments to on_error + return std::out_of_range("constrained value boundary has been violated"); + } + }; + + typedef typename mpl::if_< + is_base_of< std::exception, exception_type >, + exception_type, + exception_wrapper + >::type actual_exception_type; + + public: + typedef rep_type value_type; + static rep_type min BOOST_PREVENT_MACRO_SUBSTITUTION () { return min_value; } + static rep_type max BOOST_PREVENT_MACRO_SUBSTITUTION () { return max_value; } + static void on_error(rep_type, rep_type, violation_enum) + { + boost::throw_exception(actual_exception_type()); + } + }; + + + +} } //namespace CV + + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date.hpp new file mode 100644 index 0000000..b38db22 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date.hpp @@ -0,0 +1,208 @@ +#ifndef DATE_TIME_DATE_HPP___ +#define DATE_TIME_DATE_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include + +namespace boost { +namespace date_time { + + //!Representation of timepoint at the one day level resolution. + /*! + The date template represents an interface shell for a date class + that is based on a year-month-day system such as the gregorian + or iso systems. It provides basic operations to enable calculation + and comparisons. + + Theory + + This date representation fundamentally departs from the C tm struct + approach. The goal for this type is to provide efficient date + operations (add, subtract) and storage (minimize space to represent) + in a concrete class. Thus, the date uses a count internally to + represent a particular date. The calendar parameter defines + the policies for converting the the year-month-day and internal + counted form here. Applications that need to perform heavy + formatting of the same date repeatedly will perform better + by using the year-month-day representation. + + Internally the date uses a day number to represent the date. + This is a monotonic time representation. This representation + allows for fast comparison as well as simplifying + the creation of writing numeric operations. Essentially, the + internal day number is like adjusted julian day. The adjustment + is determined by the Epoch date which is represented as day 1 of + the calendar. Day 0 is reserved for negative infinity so that + any actual date is automatically greater than negative infinity. + When a date is constructed from a date or formatted for output, + the appropriate conversions are applied to create the year, month, + day representations. + */ + + + template + class date : private + boost::less_than_comparable > + { + public: + typedef T date_type; + typedef calendar calendar_type; + typedef typename calendar::date_traits_type traits_type; + typedef duration_type_ duration_type; + typedef typename calendar::year_type year_type; + typedef typename calendar::month_type month_type; + typedef typename calendar::day_type day_type; + typedef typename calendar::ymd_type ymd_type; + typedef typename calendar::date_rep_type date_rep_type; + typedef typename calendar::date_int_type date_int_type; + typedef typename calendar::day_of_week_type day_of_week_type; + date(year_type y, month_type m, day_type d) + : days_(calendar::day_number(ymd_type(y, m, d))) + {} + date(const ymd_type& ymd) + : days_(calendar::day_number(ymd)) + {} + //let the compiler write copy, assignment, and destructor + year_type year() const + { + ymd_type ymd = calendar::from_day_number(days_); + return ymd.year; + } + month_type month() const + { + ymd_type ymd = calendar::from_day_number(days_); + return ymd.month; + } + day_type day() const + { + ymd_type ymd = calendar::from_day_number(days_); + return ymd.day; + } + day_of_week_type day_of_week() const + { + ymd_type ymd = calendar::from_day_number(days_); + return calendar::day_of_week(ymd); + } + ymd_type year_month_day() const + { + return calendar::from_day_number(days_); + } + bool operator<(const date_type& rhs) const + { + return days_ < rhs.days_; + } + bool operator==(const date_type& rhs) const + { + return days_ == rhs.days_; + } + //! check to see if date is a special value + bool is_special()const + { + return(is_not_a_date() || is_infinity()); + } + //! check to see if date is not a value + bool is_not_a_date() const + { + return traits_type::is_not_a_number(days_); + } + //! check to see if date is one of the infinity values + bool is_infinity() const + { + return traits_type::is_inf(days_); + } + //! check to see if date is greater than all possible dates + bool is_pos_infinity() const + { + return traits_type::is_pos_inf(days_); + } + //! check to see if date is greater than all possible dates + bool is_neg_infinity() const + { + return traits_type::is_neg_inf(days_); + } + //! return as a special value or a not_special if a normal date + special_values as_special() const + { + return traits_type::to_special(days_); + } + duration_type operator-(const date_type& d) const + { + if (!this->is_special() && !d.is_special()) + { + // The duration underlying type may be wider than the date underlying type. + // Thus we calculate the difference in terms of two durations from some common fixed base date. + typedef typename duration_type::duration_rep_type duration_rep_type; + return duration_type(static_cast< duration_rep_type >(days_) - static_cast< duration_rep_type >(d.days_)); + } + else + { + // In this case the difference will be a special value, too + date_rep_type val = date_rep_type(days_) - date_rep_type(d.days_); + return duration_type(val.as_special()); + } + } + + date_type operator-(const duration_type& dd) const + { + if(dd.is_special()) + { + return date_type(date_rep_type(days_) - dd.get_rep()); + } + return date_type(date_rep_type(days_) - static_cast(dd.days())); + } + date_type operator-=(const duration_type& dd) + { + *this = *this - dd; + return date_type(days_); + } + date_rep_type day_count() const + { + return days_; + } + //allow internal access from operators + date_type operator+(const duration_type& dd) const + { + if(dd.is_special()) + { + return date_type(date_rep_type(days_) + dd.get_rep()); + } + return date_type(date_rep_type(days_) + static_cast(dd.days())); + } + date_type operator+=(const duration_type& dd) + { + *this = *this + dd; + return date_type(days_); + } + + //see reference + protected: + /*! This is a private constructor which allows for the creation of new + dates. It is not exposed to users since that would require class + users to understand the inner workings of the date class. + */ + explicit date(date_int_type days) : days_(days) {} + explicit date(date_rep_type days) : days_(days.as_number()) {} + date_int_type days_; + + }; + + + + +} } // namespace date_time + + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_clock_device.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_clock_device.hpp new file mode 100644 index 0000000..2145d65 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_clock_device.hpp @@ -0,0 +1,77 @@ +#ifndef DATE_CLOCK_DEVICE_HPP___ +#define DATE_CLOCK_DEVICE_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/c_time.hpp" + + +namespace boost { +namespace date_time { + + //! A clock providing day level services based on C time_t capabilities + /*! This clock uses Posix interfaces as its implementation and hence + * uses the timezone settings of the operating system. Incorrect + * user settings will result in incorrect results for the calls + * to local_day. + */ + template + class day_clock + { + public: + typedef typename date_type::ymd_type ymd_type; + //! Get the local day as a date type + static date_type local_day() + { + return date_type(local_day_ymd()); + } + //! Get the local day as a ymd_type + static typename date_type::ymd_type local_day_ymd() + { + ::std::tm result; + ::std::tm* curr = get_local_time(result); + return ymd_type(static_cast(curr->tm_year + 1900), + static_cast(curr->tm_mon + 1), + static_cast(curr->tm_mday)); + } + //! Get the current day in universal date as a ymd_type + static typename date_type::ymd_type universal_day_ymd() + { + ::std::tm result; + ::std::tm* curr = get_universal_time(result); + return ymd_type(static_cast(curr->tm_year + 1900), + static_cast(curr->tm_mon + 1), + static_cast(curr->tm_mday)); + } + //! Get the UTC day as a date type + static date_type universal_day() + { + return date_type(universal_day_ymd()); + } + + private: + static ::std::tm* get_local_time(std::tm& result) + { + ::std::time_t t; + ::std::time(&t); + return c_time::localtime(&t, &result); + } + static ::std::tm* get_universal_time(std::tm& result) + { + ::std::time_t t; + ::std::time(&t); + return c_time::gmtime(&t, &result); + } + + }; + +} } //namespace date_time + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_defs.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_defs.hpp new file mode 100644 index 0000000..6c80db3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_defs.hpp @@ -0,0 +1,26 @@ +#ifndef DATE_TIME_DATE_DEFS_HPP +#define DATE_TIME_DATE_DEFS_HPP + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + + +namespace boost { +namespace date_time { + + //! An enumeration of weekday names + enum weekdays {Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday}; + + //! Simple enum to allow for nice programming with Jan, Feb, etc + enum months_of_year {Jan=1,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec,NotAMonth,NumMonths}; + +} } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_duration.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_duration.hpp new file mode 100644 index 0000000..f5b4b08 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_duration.hpp @@ -0,0 +1,146 @@ +#ifndef DATE_TIME_DATE_DURATION__ +#define DATE_TIME_DATE_DURATION__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include +#include + +namespace boost { +namespace date_time { + + + //! Duration type with date level resolution + template + class date_duration : private + boost::less_than_comparable1< date_duration< duration_rep_traits > + , boost::equality_comparable1< date_duration< duration_rep_traits > + , boost::addable1< date_duration< duration_rep_traits > + , boost::subtractable1< date_duration< duration_rep_traits > + , boost::dividable2< date_duration< duration_rep_traits >, int + > > > > > + { + public: + typedef typename duration_rep_traits::int_type duration_rep_type; + typedef typename duration_rep_traits::impl_type duration_rep; + + //! Construct from a day count + explicit date_duration(duration_rep day_count) : days_(day_count) {} + + /*! construct from special_values - only works when + * instantiated with duration_traits_adapted */ + date_duration(special_values sv) : + days_(duration_rep::from_special(sv)) + {} + + // copy constructor required for addable<> & subtractable<> + //! Construct from another date_duration (Copy Constructor) + date_duration(const date_duration& other) : + days_(other.days_) + {} + + //! returns days_ as it's instantiated type - used for streaming + duration_rep get_rep()const + { + return days_; + } + bool is_special()const + { + return days_.is_special(); + } + //! returns days as value, not object. + duration_rep_type days() const + { + return duration_rep_traits::as_number(days_); + } + //! Returns the smallest duration -- used by to calculate 'end' + static date_duration unit() + { + return date_duration(1); + } + //! Equality + bool operator==(const date_duration& rhs) const + { + return days_ == rhs.days_; + } + //! Less + bool operator<(const date_duration& rhs) const + { + return days_ < rhs.days_; + } + + /* For shortcut operators (+=, -=, etc) simply using + * "days_ += days_" may not work. If instantiated with + * an int_adapter, shortcut operators are not present, + * so this will not compile */ + + //! Subtract another duration -- result is signed + date_duration& operator-=(const date_duration& rhs) + { + //days_ -= rhs.days_; + days_ = days_ - rhs.days_; + return *this; + } + //! Add a duration -- result is signed + date_duration& operator+=(const date_duration& rhs) + { + days_ = days_ + rhs.days_; + return *this; + } + + //! unary- Allows for dd = -date_duration(2); -> dd == -2 + date_duration operator-() const + { + return date_duration(get_rep() * (-1)); + } + //! Division operations on a duration with an integer. + date_duration& operator/=(int divisor) + { + days_ = days_ / divisor; + return *this; + } + + //! return sign information + bool is_negative() const + { + return days_ < 0; + } + + private: + duration_rep days_; + }; + + + /*! Struct for instantiating date_duration with NO special values + * functionality. Allows for transparent implementation of either + * date_duration or date_duration > */ + struct duration_traits_long + { + typedef long int_type; + typedef long impl_type; + static int_type as_number(impl_type i) { return i; } + }; + + /*! Struct for instantiating date_duration WITH special values + * functionality. Allows for transparent implementation of either + * date_duration or date_duration > */ + struct duration_traits_adapted + { + typedef long int_type; + typedef boost::date_time::int_adapter impl_type; + static int_type as_number(impl_type i) { return i.as_number(); } + }; + + +} } //namspace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_duration_types.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_duration_types.hpp new file mode 100644 index 0000000..8c0e986 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_duration_types.hpp @@ -0,0 +1,269 @@ +#ifndef DATE_DURATION_TYPES_HPP___ +#define DATE_DURATION_TYPES_HPP___ + +/* Copyright (c) 2004 CrystalClear Software, Inc. + * Subject to the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or + * http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include + +namespace boost { +namespace date_time { + + + //! Additional duration type that represents a number of n*7 days + template + class weeks_duration : public date_duration { + public: + weeks_duration(typename duration_config::impl_type w) + : date_duration(w * 7) {} + weeks_duration(special_values sv) + : date_duration(sv) {} + }; + + // predeclare + template + class years_duration; + + //! additional duration type that represents a logical month + /*! A logical month enables things like: "date(2002,Mar,2) + months(2) -> + * 2002-May2". If the date is a last day-of-the-month, the result will + * also be a last-day-of-the-month. + */ + template + class months_duration + { + private: + typedef typename base_config::int_rep int_rep; + typedef typename int_rep::int_type int_type; + typedef typename base_config::date_type date_type; + typedef typename date_type::duration_type duration_type; + typedef typename base_config::month_adjustor_type month_adjustor_type; + typedef months_duration months_type; + typedef years_duration years_type; + public: + months_duration(int_rep num) : _m(num) {} + months_duration(special_values sv) : _m(sv) + { + _m = int_rep::from_special(sv); + } + int_rep number_of_months() const { return _m; } + //! returns a negative duration + duration_type get_neg_offset(const date_type& d) const + { + month_adjustor_type m_adj(_m.as_number()); + return duration_type(m_adj.get_neg_offset(d)); + } + duration_type get_offset(const date_type& d) const + { + month_adjustor_type m_adj(_m.as_number()); + return duration_type(m_adj.get_offset(d)); + } + bool operator==(const months_type& rhs) const + { + return(_m == rhs._m); + } + bool operator!=(const months_type& rhs) const + { + return(_m != rhs._m); + } + months_type operator+(const months_type& rhs)const + { + return months_type(_m + rhs._m); + } + months_type& operator+=(const months_type& rhs) + { + _m = _m + rhs._m; + return *this; + } + months_type operator-(const months_type& rhs)const + { + return months_type(_m - rhs._m); + } + months_type& operator-=(const months_type& rhs) + { + _m = _m - rhs._m; + return *this; + } + months_type operator*(const int_type rhs)const + { + return months_type(_m * rhs); + } + months_type& operator*=(const int_type rhs) + { + _m = _m * rhs; + return *this; + } + months_type operator/(const int_type rhs)const + { + return months_type(_m / rhs); + } + months_type& operator/=(const int_type rhs) + { + _m = _m / rhs; + return *this; + } + months_type operator+(const years_type& y)const + { + return months_type(y.number_of_years() * 12 + _m); + } + months_type& operator+=(const years_type& y) + { + _m = y.number_of_years() * 12 + _m; + return *this; + } + months_type operator-(const years_type& y) const + { + return months_type(_m - y.number_of_years() * 12); + } + months_type& operator-=(const years_type& y) + { + _m = _m - y.number_of_years() * 12; + return *this; + } + + // + friend date_type operator+(const date_type& d, const months_type& m) + { + return d + m.get_offset(d); + } + friend date_type operator+=(date_type& d, const months_type& m) + { + return d += m.get_offset(d); + } + friend date_type operator-(const date_type& d, const months_type& m) + { + // get_neg_offset returns a negative duration, so we add + return d + m.get_neg_offset(d); + } + friend date_type operator-=(date_type& d, const months_type& m) + { + // get_neg_offset returns a negative duration, so we add + return d += m.get_neg_offset(d); + } + + private: + int_rep _m; + }; + + //! additional duration type that represents a logical year + /*! A logical year enables things like: "date(2002,Mar,2) + years(2) -> + * 2004-Mar-2". If the date is a last day-of-the-month, the result will + * also be a last-day-of-the-month (ie date(2001-Feb-28) + years(3) -> + * 2004-Feb-29). + */ + template + class years_duration + { + private: + typedef typename base_config::int_rep int_rep; + typedef typename int_rep::int_type int_type; + typedef typename base_config::date_type date_type; + typedef typename date_type::duration_type duration_type; + typedef typename base_config::month_adjustor_type month_adjustor_type; + typedef years_duration years_type; + typedef months_duration months_type; + public: + years_duration(int_rep num) : _y(num) {} + years_duration(special_values sv) : _y(sv) + { + _y = int_rep::from_special(sv); + } + int_rep number_of_years() const { return _y; } + //! returns a negative duration + duration_type get_neg_offset(const date_type& d) const + { + month_adjustor_type m_adj(_y.as_number() * 12); + return duration_type(m_adj.get_neg_offset(d)); + } + duration_type get_offset(const date_type& d) const + { + month_adjustor_type m_adj(_y.as_number() * 12); + return duration_type(m_adj.get_offset(d)); + } + bool operator==(const years_type& rhs) const + { + return(_y == rhs._y); + } + bool operator!=(const years_type& rhs) const + { + return(_y != rhs._y); + } + years_type operator+(const years_type& rhs)const + { + return years_type(_y + rhs._y); + } + years_type& operator+=(const years_type& rhs) + { + _y = _y + rhs._y; + return *this; + } + years_type operator-(const years_type& rhs)const + { + return years_type(_y - rhs._y); + } + years_type& operator-=(const years_type& rhs) + { + _y = _y - rhs._y; + return *this; + } + years_type operator*(const int_type rhs)const + { + return years_type(_y * rhs); + } + years_type& operator*=(const int_type rhs) + { + _y = _y * rhs; + return *this; + } + years_type operator/(const int_type rhs)const + { + return years_type(_y / rhs); + } + years_type& operator/=(const int_type rhs) + { + _y = _y / rhs; + return *this; + } + months_type operator+(const months_type& m) const + { + return(months_type(_y * 12 + m.number_of_months())); + } + months_type operator-(const months_type& m) const + { + return(months_type(_y * 12 - m.number_of_months())); + } + + // + friend date_type operator+(const date_type& d, const years_type& y) + { + return d + y.get_offset(d); + } + friend date_type operator+=(date_type& d, const years_type& y) + { + return d += y.get_offset(d); + } + friend date_type operator-(const date_type& d, const years_type& y) + { + // get_neg_offset returns a negative duration, so we add + return d + y.get_neg_offset(d); + } + friend date_type operator-=(date_type& d, const years_type& y) + { + // get_neg_offset returns a negative duration, so we add + return d += y.get_neg_offset(d); + } + + private: + int_rep _y; + }; + +}} // namespace boost::date_time + +#endif // DATE_DURATION_TYPES_HPP___ diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_format_simple.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_format_simple.hpp new file mode 100644 index 0000000..4529903 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_format_simple.hpp @@ -0,0 +1,159 @@ +#ifndef DATE_TIME_SIMPLE_FORMAT_HPP___ +#define DATE_TIME_SIMPLE_FORMAT_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/parse_format_base.hpp" + +namespace boost { +namespace date_time { + +//! Class to provide simple basic formatting rules +template +class simple_format { +public: + + //! String used printed is date is invalid + static const charT* not_a_date() + { + return "not-a-date-time"; + } + //! String used to for positive infinity value + static const charT* pos_infinity() + { + return "+infinity"; + } + //! String used to for positive infinity value + static const charT* neg_infinity() + { + return "-infinity"; + } + //! Describe month format + static month_format_spec month_format() + { + return month_as_short_string; + } + static ymd_order_spec date_order() + { + return ymd_order_iso; //YYYY-MM-DD + } + //! This format uses '-' to separate date elements + static bool has_date_sep_chars() + { + return true; + } + //! Char to sep? + static charT year_sep_char() + { + return '-'; + } + //! char between year-month + static charT month_sep_char() + { + return '-'; + } + //! Char to separate month-day + static charT day_sep_char() + { + return '-'; + } + //! char between date-hours + static charT hour_sep_char() + { + return ' '; + } + //! char between hour and minute + static charT minute_sep_char() + { + return ':'; + } + //! char for second + static charT second_sep_char() + { + return ':'; + } + +}; + +#ifndef BOOST_NO_STD_WSTRING + +//! Specialization of formmating rules for wchar_t +template<> +class simple_format { +public: + + //! String used printed is date is invalid + static const wchar_t* not_a_date() + { + return L"not-a-date-time"; + } + //! String used to for positive infinity value + static const wchar_t* pos_infinity() + { + return L"+infinity"; + } + //! String used to for positive infinity value + static const wchar_t* neg_infinity() + { + return L"-infinity"; + } + //! Describe month format + static month_format_spec month_format() + { + return month_as_short_string; + } + static ymd_order_spec date_order() + { + return ymd_order_iso; //YYYY-MM-DD + } + //! This format uses '-' to separate date elements + static bool has_date_sep_chars() + { + return true; + } + //! Char to sep? + static wchar_t year_sep_char() + { + return '-'; + } + //! char between year-month + static wchar_t month_sep_char() + { + return '-'; + } + //! Char to separate month-day + static wchar_t day_sep_char() + { + return '-'; + } + //! char between date-hours + static wchar_t hour_sep_char() + { + return ' '; + } + //! char between hour and minute + static wchar_t minute_sep_char() + { + return ':'; + } + //! char for second + static wchar_t second_sep_char() + { + return ':'; + } + +}; + +#endif // BOOST_NO_STD_WSTRING +} } //namespace date_time + + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting.hpp new file mode 100644 index 0000000..d4ca3dd --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting.hpp @@ -0,0 +1,135 @@ +#ifndef DATE_TIME_DATE_FORMATTING_HPP___ +#define DATE_TIME_DATE_FORMATTING_HPP___ + +/* Copyright (c) 2002-2004 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/iso_format.hpp" +#include "boost/date_time/compiler_config.hpp" +#include +#include +#include + +/* NOTE: "formatter" code for older compilers, ones that define + * BOOST_DATE_TIME_INCLUDE_LIMITED_HEADERS, is located in + * date_formatting_limited.hpp + */ + +namespace boost { +namespace date_time { + + //! Formats a month as as string into an ostream + template + class month_formatter + { + typedef std::basic_ostream ostream_type; + public: + //! Formats a month as as string into an ostream + /*! This function demands that month_type provide + * functions for converting to short and long strings + * if that capability is used. + */ + static ostream_type& format_month(const month_type& month, + ostream_type &os) + { + switch (format_type::month_format()) + { + case month_as_short_string: + { + os << month.as_short_string(); + break; + } + case month_as_long_string: + { + os << month.as_long_string(); + break; + } + case month_as_integer: + { + os << std::setw(2) << std::setfill(os.widen('0')) << month.as_number(); + break; + } + default: + break; + + } + return os; + } // format_month + }; + + + //! Convert ymd to a standard string formatting policies + template + class ymd_formatter + { + public: + //! Convert ymd to a standard string formatting policies + /*! This is standard code for handling date formatting with + * year-month-day based date information. This function + * uses the format_type to control whether the string will + * contain separator characters, and if so what the character + * will be. In addtion, it can format the month as either + * an integer or a string as controled by the formatting + * policy + */ + static std::basic_string ymd_to_string(ymd_type ymd) + { + typedef typename ymd_type::month_type month_type; + std::basic_ostringstream ss; + + // Temporarily switch to classic locale to prevent possible formatting + // of year with comma or other character (for example 2,008). + ss.imbue(std::locale::classic()); + ss << ymd.year; + ss.imbue(std::locale()); + + if (format_type::has_date_sep_chars()) { + ss << format_type::month_sep_char(); + } + //this name is a bit ugly, oh well.... + month_formatter::format_month(ymd.month, ss); + if (format_type::has_date_sep_chars()) { + ss << format_type::day_sep_char(); + } + ss << std::setw(2) << std::setfill(ss.widen('0')) + << ymd.day; + return ss.str(); + } + }; + + + //! Convert a date to string using format policies + template + class date_formatter + { + public: + typedef std::basic_string string_type; + //! Convert to a date to standard string using format policies + static string_type date_to_string(date_type d) + { + typedef typename date_type::ymd_type ymd_type; + if (d.is_not_a_date()) { + return string_type(format_type::not_a_date()); + } + if (d.is_neg_infinity()) { + return string_type(format_type::neg_infinity()); + } + if (d.is_pos_infinity()) { + return string_type(format_type::pos_infinity()); + } + ymd_type ymd = d.year_month_day(); + return ymd_formatter::ymd_to_string(ymd); + } + }; + + +} } //namespace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_limited.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_limited.hpp new file mode 100644 index 0000000..7c5c173 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_limited.hpp @@ -0,0 +1,121 @@ +#ifndef DATE_TIME_DATE_FORMATTING_LIMITED_HPP___ +#define DATE_TIME_DATE_FORMATTING_LIMITED_HPP___ + +/* Copyright (c) 2002-2004 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/iso_format.hpp" +#include "boost/date_time/compiler_config.hpp" +#include +#include +#include + + +namespace boost { +namespace date_time { + + //! Formats a month as as string into an ostream + template + class month_formatter + { + public: + //! Formats a month as as string into an ostream + /*! This function demands that month_type provide + * functions for converting to short and long strings + * if that capability is used. + */ + static std::ostream& format_month(const month_type& month, + std::ostream& os) + { + switch (format_type::month_format()) + { + case month_as_short_string: + { + os << month.as_short_string(); + break; + } + case month_as_long_string: + { + os << month.as_long_string(); + break; + } + case month_as_integer: + { + os << std::setw(2) << std::setfill('0') << month.as_number(); + break; + } + + } + return os; + } // format_month + }; + + + //! Convert ymd to a standard string formatting policies + template + class ymd_formatter + { + public: + //! Convert ymd to a standard string formatting policies + /*! This is standard code for handling date formatting with + * year-month-day based date information. This function + * uses the format_type to control whether the string will + * contain separator characters, and if so what the character + * will be. In addtion, it can format the month as either + * an integer or a string as controled by the formatting + * policy + */ + static std::string ymd_to_string(ymd_type ymd) + { + typedef typename ymd_type::month_type month_type; + std::ostringstream ss; + ss << ymd.year; + if (format_type::has_date_sep_chars()) { + ss << format_type::month_sep_char(); + } + //this name is a bit ugly, oh well.... + month_formatter::format_month(ymd.month, ss); + if (format_type::has_date_sep_chars()) { + ss << format_type::day_sep_char(); + } + ss << std::setw(2) << std::setfill('0') + << ymd.day; + return ss.str(); + } + }; + + + //! Convert a date to string using format policies + template + class date_formatter + { + public: + //! Convert to a date to standard string using format policies + static std::string date_to_string(date_type d) + { + typedef typename date_type::ymd_type ymd_type; + if (d.is_not_a_date()) { + return format_type::not_a_date(); + } + if (d.is_neg_infinity()) { + return format_type::neg_infinity(); + } + if (d.is_pos_infinity()) { + return format_type::pos_infinity(); + } + ymd_type ymd = d.year_month_day(); + return ymd_formatter::ymd_to_string(ymd); + } + }; + + +} } //namespace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_locales.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_locales.hpp new file mode 100644 index 0000000..2c17c05 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_formatting_locales.hpp @@ -0,0 +1,233 @@ +#ifndef DATE_TIME_DATE_FORMATTING_LOCALES_HPP___ +#define DATE_TIME_DATE_FORMATTING_LOCALES_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include "boost/date_time/locale_config.hpp" // set BOOST_DATE_TIME_NO_LOCALE + +#ifndef BOOST_DATE_TIME_NO_LOCALE + +#include "boost/date_time/iso_format.hpp" +#include "boost/date_time/date_names_put.hpp" +#include "boost/date_time/parse_format_base.hpp" +//#include +#include +#include + + +namespace boost { +namespace date_time { + + //! Formats a month as as string into an ostream + template + class ostream_month_formatter + { + public: + typedef typename facet_type::month_type month_type; + typedef std::basic_ostream ostream_type; + + //! Formats a month as as string into an output iterator + static void format_month(const month_type& month, + ostream_type& os, + const facet_type& f) + { + + switch (f.month_format()) + { + case month_as_short_string: + { + std::ostreambuf_iterator oitr(os); + f.put_month_short(oitr, month.as_enum()); + break; + } + case month_as_long_string: + { + std::ostreambuf_iterator oitr(os); + f.put_month_long(oitr, month.as_enum()); + break; + } + case month_as_integer: + { + charT fill_char = '0'; + os << std::setw(2) << std::setfill(fill_char) << month.as_number(); + break; + } + + } + } // format_month + + }; + + + //! Formats a weekday + template + class ostream_weekday_formatter + { + public: + typedef typename facet_type::month_type month_type; + typedef std::basic_ostream ostream_type; + + //! Formats a month as as string into an output iterator + static void format_weekday(const weekday_type& wd, + ostream_type& os, + const facet_type& f, + bool as_long_string) + { + + std::ostreambuf_iterator oitr(os); + if (as_long_string) { + f.put_weekday_long(oitr, wd.as_enum()); + } + else { + f.put_weekday_short(oitr, wd.as_enum()); + } + + } // format_weekday + + }; + + + //! Convert ymd to a standard string formatting policies + template + class ostream_ymd_formatter + { + public: + typedef typename ymd_type::month_type month_type; + typedef ostream_month_formatter month_formatter_type; + typedef std::basic_ostream ostream_type; + typedef std::basic_string foo_type; + + //! Convert ymd to a standard string formatting policies + /*! This is standard code for handling date formatting with + * year-month-day based date information. This function + * uses the format_type to control whether the string will + * contain separator characters, and if so what the character + * will be. In addtion, it can format the month as either + * an integer or a string as controled by the formatting + * policy + */ + // static string_type ymd_to_string(ymd_type ymd) +// { +// std::ostringstream ss; +// facet_type dnp; +// ymd_put(ymd, ss, dnp); +// return ss.str(); +// } + + + // Put ymd to ostream -- part of ostream refactor + static void ymd_put(ymd_type ymd, + ostream_type& os, + const facet_type& f) + { + std::ostreambuf_iterator oitr(os); + charT fill_char = '0'; + switch (f.date_order()) { + case ymd_order_iso: { + os << ymd.year; + if (f.has_date_sep_chars()) { + f.month_sep_char(oitr); + } + month_formatter_type::format_month(ymd.month, os, f); + if (f.has_date_sep_chars()) { + f.day_sep_char(oitr); + } + os << std::setw(2) << std::setfill(fill_char) + << ymd.day; + break; + } + case ymd_order_us: { + month_formatter_type::format_month(ymd.month, os, f); + if (f.has_date_sep_chars()) { + f.day_sep_char(oitr); + } + os << std::setw(2) << std::setfill(fill_char) + << ymd.day; + if (f.has_date_sep_chars()) { + f.month_sep_char(oitr); + } + os << ymd.year; + break; + } + case ymd_order_dmy: { + os << std::setw(2) << std::setfill(fill_char) + << ymd.day; + if (f.has_date_sep_chars()) { + f.day_sep_char(oitr); + } + month_formatter_type::format_month(ymd.month, os, f); + if (f.has_date_sep_chars()) { + f.month_sep_char(oitr); + } + os << ymd.year; + break; + } + } + } + }; + + + //! Convert a date to string using format policies + template + class ostream_date_formatter + { + public: + typedef std::basic_ostream ostream_type; + typedef typename date_type::ymd_type ymd_type; + + //! Put date into an ostream + static void date_put(const date_type& d, + ostream_type& os, + const facet_type& f) + { + special_values sv = d.as_special(); + if (sv == not_special) { + ymd_type ymd = d.year_month_day(); + ostream_ymd_formatter::ymd_put(ymd, os, f); + } + else { // output a special value + std::ostreambuf_iterator coi(os); + f.put_special_value(coi, sv); + } + } + + + //! Put date into an ostream + static void date_put(const date_type& d, + ostream_type& os) + { + //retrieve the local from the ostream + std::locale locale = os.getloc(); + if (std::has_facet(locale)) { + const facet_type& f = std::use_facet(locale); + date_put(d, os, f); + } + else { + //default to something sensible if no facet installed + facet_type default_facet; + date_put(d, os, default_facet); + } + } // date_to_ostream + }; //class date_formatter + + +} } //namespace date_time + +#endif + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_generators.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_generators.hpp new file mode 100644 index 0000000..274ce1f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_generators.hpp @@ -0,0 +1,509 @@ +#ifndef DATE_TIME_DATE_GENERATORS_HPP__ +#define DATE_TIME_DATE_GENERATORS_HPP__ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! @file date_generators.hpp + Definition and implementation of date algorithm templates +*/ + +#include +#include +#include +#include +#include + +namespace boost { +namespace date_time { + + //! Base class for all generators that take a year and produce a date. + /*! This class is a base class for polymorphic function objects that take + a year and produce a concrete date. + @param date_type The type representing a date. This type must + export a calender_type which defines a year_type. + */ + template + class year_based_generator + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::year_type year_type; + year_based_generator() {} + virtual ~year_based_generator() {} + virtual date_type get_date(year_type y) const = 0; + //! Returns a string for use in a POSIX time_zone string + virtual std::string to_string() const =0; + }; + + //! Generates a date by applying the year to the given month and day. + /*! + Example usage: + @code + partial_date pd(1, Jan); + partial_date pd2(70); + date d = pd.get_date(2002); //2002-Jan-01 + date d2 = pd2.get_date(2002); //2002-Mar-10 + @endcode + \ingroup date_alg + */ + template + class partial_date : public year_based_generator + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_type day_type; + typedef typename calendar_type::month_type month_type; + typedef typename calendar_type::year_type year_type; + typedef typename date_type::duration_type duration_type; + typedef typename duration_type::duration_rep duration_rep; + partial_date(day_type d, month_type m) : + day_(d), + month_(m) + {} + //! Partial date created from number of days into year. Range 1-366 + /*! Allowable values range from 1 to 366. 1=Jan1, 366=Dec31. If argument + * exceeds range, partial_date will be created with closest in-range value. + * 60 will always be Feb29, if get_date() is called with a non-leap year + * an exception will be thrown */ + partial_date(duration_rep days) : + day_(1), // default values + month_(1) + { + date_type d1(2000,1,1); + if(days > 1) { + if(days > 366) // prevents wrapping + { + days = 366; + } + days = days - 1; + duration_type dd(days); + d1 = d1 + dd; + } + day_ = d1.day(); + month_ = d1.month(); + } + //! Return a concrete date when provided with a year specific year. + /*! Will throw an 'invalid_argument' exception if a partial_date object, + * instantiated with Feb-29, has get_date called with a non-leap year. + * Example: + * @code + * partial_date pd(29, Feb); + * pd.get_date(2003); // throws invalid_argument exception + * pg.get_date(2000); // returns 2000-2-29 + * @endcode + */ + date_type get_date(year_type y) const + { + if((day_ == 29) && (month_ == 2) && !(calendar_type::is_leap_year(y))) { + std::ostringstream ss; + ss << "No Feb 29th in given year of " << y << "."; + boost::throw_exception(std::invalid_argument(ss.str())); + } + return date_type(y, month_, day_); + } + date_type operator()(year_type y) const + { + return get_date(y); + //return date_type(y, month_, day_); + } + bool operator==(const partial_date& rhs) const + { + return (month_ == rhs.month_) && (day_ == rhs.day_); + } + bool operator<(const partial_date& rhs) const + { + if (month_ < rhs.month_) return true; + if (month_ > rhs.month_) return false; + //months are equal + return (day_ < rhs.day_); + } + + // added for streaming purposes + month_type month() const + { + return month_; + } + day_type day() const + { + return day_; + } + + //! Returns string suitable for use in POSIX time zone string + /*! Returns string formatted with up to 3 digits: + * Jan-01 == "0" + * Feb-29 == "58" + * Dec-31 == "365" */ + virtual std::string to_string() const + { + std::ostringstream ss; + date_type d(2004, month_, day_); + unsigned short c = d.day_of_year(); + c--; // numbered 0-365 while day_of_year is 1 based... + ss << c; + return ss.str(); + } + private: + day_type day_; + month_type month_; + }; + + + //! Returns nth arg as string. 1 -> "first", 2 -> "second", max is 5. + BOOST_DATE_TIME_DECL const char* nth_as_str(int n); + + //! Useful generator functor for finding holidays + /*! Based on the idea in Cal. Calc. for finding holidays that are + * the 'first Monday of September'. When instantiated with + * 'fifth' kday of month, the result will be the last kday of month + * which can be the fourth or fifth depending on the structure of + * the month. + * + * The algorithm here basically guesses for the first + * day of the month. Then finds the first day of the correct + * type. That is, if the first of the month is a Tuesday + * and it needs Wenesday then we simply increment by a day + * and then we can add the length of a week until we get + * to the 'nth kday'. There are probably more efficient + * algorithms based on using a mod 7, but this one works + * reasonably well for basic applications. + * \ingroup date_alg + */ + template + class nth_kday_of_month : public year_based_generator + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_of_week_type day_of_week_type; + typedef typename calendar_type::month_type month_type; + typedef typename calendar_type::year_type year_type; + typedef typename date_type::duration_type duration_type; + enum week_num {first=1, second, third, fourth, fifth}; + nth_kday_of_month(week_num week_no, + day_of_week_type dow, + month_type m) : + month_(m), + wn_(week_no), + dow_(dow) + {} + //! Return a concrete date when provided with a year specific year. + date_type get_date(year_type y) const + { + date_type d(y, month_, 1); //first day of month + duration_type one_day(1); + duration_type one_week(7); + while (dow_ != d.day_of_week()) { + d = d + one_day; + } + int week = 1; + while (week < wn_) { + d = d + one_week; + week++; + } + // remove wrapping to next month behavior + if(d.month() != month_) { + d = d - one_week; + } + return d; + } + // added for streaming + month_type month() const + { + return month_; + } + week_num nth_week() const + { + return wn_; + } + day_of_week_type day_of_week() const + { + return dow_; + } + const char* nth_week_as_str() const + { + return nth_as_str(wn_); + } + //! Returns string suitable for use in POSIX time zone string + /*! Returns a string formatted as "M4.3.0" ==> 3rd Sunday in April. */ + virtual std::string to_string() const + { + std::ostringstream ss; + ss << 'M' + << static_cast(month_) << '.' + << static_cast(wn_) << '.' + << static_cast(dow_); + return ss.str(); + } + private: + month_type month_; + week_num wn_; + day_of_week_type dow_; + }; + + //! Useful generator functor for finding holidays and daylight savings + /*! Similar to nth_kday_of_month, but requires less paramters + * \ingroup date_alg + */ + template + class first_kday_of_month : public year_based_generator + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_of_week_type day_of_week_type; + typedef typename calendar_type::month_type month_type; + typedef typename calendar_type::year_type year_type; + typedef typename date_type::duration_type duration_type; + //!Specify the first 'Sunday' in 'April' spec + /*!@param dow The day of week, eg: Sunday, Monday, etc + * @param m The month of the year, eg: Jan, Feb, Mar, etc + */ + first_kday_of_month(day_of_week_type dow, month_type m) : + month_(m), + dow_(dow) + {} + //! Return a concrete date when provided with a year specific year. + date_type get_date(year_type year) const + { + date_type d(year, month_,1); + duration_type one_day(1); + while (dow_ != d.day_of_week()) { + d = d + one_day; + } + return d; + } + // added for streaming + month_type month() const + { + return month_; + } + day_of_week_type day_of_week() const + { + return dow_; + } + //! Returns string suitable for use in POSIX time zone string + /*! Returns a string formatted as "M4.1.0" ==> 1st Sunday in April. */ + virtual std::string to_string() const + { + std::ostringstream ss; + ss << 'M' + << static_cast(month_) << '.' + << 1 << '.' + << static_cast(dow_); + return ss.str(); + } + private: + month_type month_; + day_of_week_type dow_; + }; + + + + //! Calculate something like Last Sunday of January + /*! Useful generator functor for finding holidays and daylight savings + * Get the last day of the month and then calculate the difference + * to the last previous day. + * @param date_type A date class that exports day_of_week, month_type, etc. + * \ingroup date_alg + */ + template + class last_kday_of_month : public year_based_generator + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_of_week_type day_of_week_type; + typedef typename calendar_type::month_type month_type; + typedef typename calendar_type::year_type year_type; + typedef typename date_type::duration_type duration_type; + //!Specify the date spec like last 'Sunday' in 'April' spec + /*!@param dow The day of week, eg: Sunday, Monday, etc + * @param m The month of the year, eg: Jan, Feb, Mar, etc + */ + last_kday_of_month(day_of_week_type dow, month_type m) : + month_(m), + dow_(dow) + {} + //! Return a concrete date when provided with a year specific year. + date_type get_date(year_type year) const + { + date_type d(year, month_, calendar_type::end_of_month_day(year,month_)); + duration_type one_day(1); + while (dow_ != d.day_of_week()) { + d = d - one_day; + } + return d; + } + // added for streaming + month_type month() const + { + return month_; + } + day_of_week_type day_of_week() const + { + return dow_; + } + //! Returns string suitable for use in POSIX time zone string + /*! Returns a string formatted as "M4.5.0" ==> last Sunday in April. */ + virtual std::string to_string() const + { + std::ostringstream ss; + ss << 'M' + << static_cast(month_) << '.' + << 5 << '.' + << static_cast(dow_); + return ss.str(); + } + private: + month_type month_; + day_of_week_type dow_; + }; + + + //! Calculate something like "First Sunday after Jan 1,2002 + /*! Date generator that takes a date and finds kday after + *@code + typedef boost::date_time::first_kday_after firstkdayafter; + firstkdayafter fkaf(Monday); + fkaf.get_date(date(2002,Feb,1)); + @endcode + * \ingroup date_alg + */ + template + class first_kday_after + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_of_week_type day_of_week_type; + typedef typename date_type::duration_type duration_type; + first_kday_after(day_of_week_type dow) : + dow_(dow) + {} + //! Return next kday given. + date_type get_date(date_type start_day) const + { + duration_type one_day(1); + date_type d = start_day + one_day; + while (dow_ != d.day_of_week()) { + d = d + one_day; + } + return d; + } + // added for streaming + day_of_week_type day_of_week() const + { + return dow_; + } + private: + day_of_week_type dow_; + }; + + //! Calculate something like "First Sunday before Jan 1,2002 + /*! Date generator that takes a date and finds kday after + *@code + typedef boost::date_time::first_kday_before firstkdaybefore; + firstkdaybefore fkbf(Monday); + fkbf.get_date(date(2002,Feb,1)); + @endcode + * \ingroup date_alg + */ + template + class first_kday_before + { + public: + typedef typename date_type::calendar_type calendar_type; + typedef typename calendar_type::day_of_week_type day_of_week_type; + typedef typename date_type::duration_type duration_type; + first_kday_before(day_of_week_type dow) : + dow_(dow) + {} + //! Return next kday given. + date_type get_date(date_type start_day) const + { + duration_type one_day(1); + date_type d = start_day - one_day; + while (dow_ != d.day_of_week()) { + d = d - one_day; + } + return d; + } + // added for streaming + day_of_week_type day_of_week() const + { + return dow_; + } + private: + day_of_week_type dow_; + }; + + //! Calculates the number of days until the next weekday + /*! Calculates the number of days until the next weekday. + * If the date given falls on a Sunday and the given weekday + * is Tuesday the result will be 2 days */ + template + inline + typename date_type::duration_type days_until_weekday(const date_type& d, const weekday_type& wd) + { + typedef typename date_type::duration_type duration_type; + duration_type wks(0); + duration_type dd(wd.as_number() - d.day_of_week().as_number()); + if(dd.is_negative()){ + wks = duration_type(7); + } + return dd + wks; + } + + //! Calculates the number of days since the previous weekday + /*! Calculates the number of days since the previous weekday + * If the date given falls on a Sunday and the given weekday + * is Tuesday the result will be 5 days. The answer will be a positive + * number because Tuesday is 5 days before Sunday, not -5 days before. */ + template + inline + typename date_type::duration_type days_before_weekday(const date_type& d, const weekday_type& wd) + { + typedef typename date_type::duration_type duration_type; + duration_type wks(0); + duration_type dd(wd.as_number() - d.day_of_week().as_number()); + if(dd.days() > 0){ + wks = duration_type(7); + } + // we want a number of days, not an offset. The value returned must + // be zero or larger. + return (-dd + wks); + } + + //! Generates a date object representing the date of the following weekday from the given date + /*! Generates a date object representing the date of the following + * weekday from the given date. If the date given is 2004-May-9 + * (a Sunday) and the given weekday is Tuesday then the resulting date + * will be 2004-May-11. */ + template + inline + date_type next_weekday(const date_type& d, const weekday_type& wd) + { + return d + days_until_weekday(d, wd); + } + + //! Generates a date object representing the date of the previous weekday from the given date + /*! Generates a date object representing the date of the previous + * weekday from the given date. If the date given is 2004-May-9 + * (a Sunday) and the given weekday is Tuesday then the resulting date + * will be 2004-May-4. */ + template + inline + date_type previous_weekday(const date_type& d, const weekday_type& wd) + { + return d - days_before_weekday(d, wd); + } + +} } //namespace date_time + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_iterator.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_iterator.hpp new file mode 100644 index 0000000..3526ba1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_iterator.hpp @@ -0,0 +1,101 @@ +#ifndef DATE_ITERATOR_HPP___ +#define DATE_ITERATOR_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include + +namespace boost { +namespace date_time { + //! An iterator over dates with varying resolution (day, week, month, year, etc) + enum date_resolutions {day, week, months, year, decade, century, NumDateResolutions}; + + //! Base date iterator type + /*! This class provides the skeleton for the creation of iterators. + * New and interesting interators can be created by plugging in a new + * function that derives the next value from the current state. + * generation of various types of -based information. + * + * Template Parameters + * + * date_type + * + * The date_type is a concrete date_type. The date_type must + * define a duration_type and a calendar_type. + */ + template + class date_itr_base { + // works, but benefit unclear at the moment + // class date_itr_base : public std::iterator{ + public: + typedef typename date_type::duration_type duration_type; + typedef date_type value_type; + typedef std::input_iterator_tag iterator_category; + + date_itr_base(date_type d) : current_(d) {} + virtual ~date_itr_base() {} + date_itr_base& operator++() + { + current_ = current_ + get_offset(current_); + return *this; + } + date_itr_base& operator--() + { + current_ = current_ + get_neg_offset(current_); + return *this; + } + virtual duration_type get_offset(const date_type& current) const=0; + virtual duration_type get_neg_offset(const date_type& current) const=0; + date_type operator*() {return current_;} + date_type* operator->() {return ¤t_;} + bool operator< (const date_type& d) {return current_ < d;} + bool operator<= (const date_type& d) {return current_ <= d;} + bool operator> (const date_type& d) {return current_ > d;} + bool operator>= (const date_type& d) {return current_ >= d;} + bool operator== (const date_type& d) {return current_ == d;} + bool operator!= (const date_type& d) {return current_ != d;} + private: + date_type current_; + }; + + //! Overrides the base date iterator providing hook for functors + /* + * offset_functor + * + * The offset functor must define a get_offset function that takes the + * current point in time and calculates and offset. + * + */ + template + class date_itr : public date_itr_base { + public: + typedef typename date_type::duration_type duration_type; + date_itr(date_type d, int factor=1) : + date_itr_base(d), + of_(factor) + {} + private: + virtual duration_type get_offset(const date_type& current) const + { + return of_.get_offset(current); + } + virtual duration_type get_neg_offset(const date_type& current) const + { + return of_.get_neg_offset(current); + } + offset_functor of_; + }; + + + +} } //namespace date_time + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_names_put.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_names_put.hpp new file mode 100644 index 0000000..e055fa8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_names_put.hpp @@ -0,0 +1,320 @@ +#ifndef DATE_TIME_DATE_NAMES_PUT_HPP___ +#define DATE_TIME_DATE_NAMES_PUT_HPP___ + +/* Copyright (c) 2002-2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include "boost/date_time/locale_config.hpp" // set BOOST_DATE_TIME_NO_LOCALE + +#ifndef BOOST_DATE_TIME_NO_LOCALE + +#include "boost/date_time/special_defs.hpp" +#include "boost/date_time/date_defs.hpp" +#include "boost/date_time/parse_format_base.hpp" +#include "boost/lexical_cast.hpp" +#include + + +namespace boost { +namespace date_time { + + //! Output facet base class for gregorian dates. + /*! This class is a base class for date facets used to localize the + * names of months and the names of days in the week. + * + * Requirements of Config + * - define an enumeration month_enum that enumerates the months. + * The enumeration should be '1' based eg: Jan==1 + * - define as_short_string and as_long_string + * + * (see langer & kreft p334). + * + */ + template > + class date_names_put : public std::locale::facet + { + public: + date_names_put() {} + typedef OutputIterator iter_type; + typedef typename Config::month_type month_type; + typedef typename Config::month_enum month_enum; + typedef typename Config::weekday_enum weekday_enum; + typedef typename Config::special_value_enum special_value_enum; + //typedef typename Config::format_type format_type; + typedef std::basic_string string_type; + typedef charT char_type; + static const char_type default_special_value_names[3][17]; + static const char_type separator[2]; + + static std::locale::id id; + +#if defined (__SUNPRO_CC) && defined (_RWSTD_VER) + std::locale::id& __get_id (void) const { return id; } +#endif + + void put_special_value(iter_type& oitr, special_value_enum sv) const + { + do_put_special_value(oitr, sv); + } + void put_month_short(iter_type& oitr, month_enum moy) const + { + do_put_month_short(oitr, moy); + } + void put_month_long(iter_type& oitr, month_enum moy) const + { + do_put_month_long(oitr, moy); + } + void put_weekday_short(iter_type& oitr, weekday_enum wd) const + { + do_put_weekday_short(oitr, wd); + } + void put_weekday_long(iter_type& oitr, weekday_enum wd) const + { + do_put_weekday_long(oitr, wd); + } + bool has_date_sep_chars() const + { + return do_has_date_sep_chars(); + } + void year_sep_char(iter_type& oitr) const + { + do_year_sep_char(oitr); + } + //! char between year-month + void month_sep_char(iter_type& oitr) const + { + do_month_sep_char(oitr); + } + //! Char to separate month-day + void day_sep_char(iter_type& oitr) const + { + do_day_sep_char(oitr); + } + //! Determines the order to put the date elements + ymd_order_spec date_order() const + { + return do_date_order(); + } + //! Determines if month is displayed as integer, short or long string + month_format_spec month_format() const + { + return do_month_format(); + } + + protected: + //! Default facet implementation uses month_type defaults + virtual void do_put_month_short(iter_type& oitr, month_enum moy) const + { + month_type gm(moy); + charT c = '\0'; + put_string(oitr, gm.as_short_string(c)); + } + //! Default facet implementation uses month_type defaults + virtual void do_put_month_long(iter_type& oitr, + month_enum moy) const + { + month_type gm(moy); + charT c = '\0'; + put_string(oitr, gm.as_long_string(c)); + } + //! Default facet implementation for special value types + virtual void do_put_special_value(iter_type& oitr, special_value_enum sv) const + { + if(sv <= 2) { // only output not_a_date_time, neg_infin, or pos_infin + string_type s(default_special_value_names[sv]); + put_string(oitr, s); + } + } + virtual void do_put_weekday_short(iter_type&, weekday_enum) const + { + } + virtual void do_put_weekday_long(iter_type&, weekday_enum) const + { + } + virtual bool do_has_date_sep_chars() const + { + return true; + } + virtual void do_year_sep_char(iter_type& oitr) const + { + string_type s(separator); + put_string(oitr, s); + } + //! char between year-month + virtual void do_month_sep_char(iter_type& oitr) const + { + string_type s(separator); + put_string(oitr, s); + } + //! Char to separate month-day + virtual void do_day_sep_char(iter_type& oitr) const + { + string_type s(separator); //put in '-' + put_string(oitr, s); + } + //! Default for date order + virtual ymd_order_spec do_date_order() const + { + return ymd_order_iso; + } + //! Default month format + virtual month_format_spec do_month_format() const + { + return month_as_short_string; + } + void put_string(iter_type& oi, const charT* const s) const + { + string_type s1(boost::lexical_cast(s)); + typename string_type::iterator si,end; + for (si=s1.begin(), end=s1.end(); si!=end; si++, oi++) { + *oi = *si; + } + } + void put_string(iter_type& oi, const string_type& s1) const + { + typename string_type::const_iterator si,end; + for (si=s1.begin(), end=s1.end(); si!=end; si++, oi++) { + *oi = *si; + } + } + }; + + template + const typename date_names_put::char_type + date_names_put::default_special_value_names[3][17] = { + {'n','o','t','-','a','-','d','a','t','e','-','t','i','m','e'}, + {'-','i','n','f','i','n','i','t','y'}, + {'+','i','n','f','i','n','i','t','y'} }; + + template + const typename date_names_put::char_type + date_names_put::separator[2] = + {'-', '\0'} ; + + + //! Generate storage location for a std::locale::id + template + std::locale::id date_names_put::id; + + //! A date name output facet that takes an array of char* to define strings + template > + class all_date_names_put : public date_names_put + { + public: + all_date_names_put(const charT* const month_short_names[], + const charT* const month_long_names[], + const charT* const special_value_names[], + const charT* const weekday_short_names[], + const charT* const weekday_long_names[], + charT separator_char = '-', + ymd_order_spec order_spec = ymd_order_iso, + month_format_spec month_format = month_as_short_string) : + month_short_names_(month_short_names), + month_long_names_(month_long_names), + special_value_names_(special_value_names), + weekday_short_names_(weekday_short_names), + weekday_long_names_(weekday_long_names), + order_spec_(order_spec), + month_format_spec_(month_format) + { + separator_char_[0] = separator_char; + separator_char_[1] = '\0'; + + } + typedef OutputIterator iter_type; + typedef typename Config::month_enum month_enum; + typedef typename Config::weekday_enum weekday_enum; + typedef typename Config::special_value_enum special_value_enum; + + const charT* const* get_short_month_names() const + { + return month_short_names_; + } + const charT* const* get_long_month_names() const + { + return month_long_names_; + } + const charT* const* get_special_value_names() const + { + return special_value_names_; + } + const charT* const* get_short_weekday_names()const + { + return weekday_short_names_; + } + const charT* const* get_long_weekday_names()const + { + return weekday_long_names_; + } + + protected: + //! Generic facet that takes array of chars + virtual void do_put_month_short(iter_type& oitr, month_enum moy) const + { + this->put_string(oitr, month_short_names_[moy-1]); + } + //! Long month names + virtual void do_put_month_long(iter_type& oitr, month_enum moy) const + { + this->put_string(oitr, month_long_names_[moy-1]); + } + //! Special values names + virtual void do_put_special_value(iter_type& oitr, special_value_enum sv) const + { + this->put_string(oitr, special_value_names_[sv]); + } + virtual void do_put_weekday_short(iter_type& oitr, weekday_enum wd) const + { + this->put_string(oitr, weekday_short_names_[wd]); + } + virtual void do_put_weekday_long(iter_type& oitr, weekday_enum wd) const + { + this->put_string(oitr, weekday_long_names_[wd]); + } + //! char between year-month + virtual void do_month_sep_char(iter_type& oitr) const + { + this->put_string(oitr, separator_char_); + } + //! Char to separate month-day + virtual void do_day_sep_char(iter_type& oitr) const + { + this->put_string(oitr, separator_char_); + } + //! Set the date ordering + virtual ymd_order_spec do_date_order() const + { + return order_spec_; + } + //! Set the date ordering + virtual month_format_spec do_month_format() const + { + return month_format_spec_; + } + + private: + const charT* const* month_short_names_; + const charT* const* month_long_names_; + const charT* const* special_value_names_; + const charT* const* weekday_short_names_; + const charT* const* weekday_long_names_; + charT separator_char_[2]; + ymd_order_spec order_spec_; + month_format_spec month_format_spec_; + }; + +} } //namespace boost::date_time + +#endif //BOOST_NO_STD_LOCALE + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/date_parsing.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/date_parsing.hpp new file mode 100644 index 0000000..33c5366 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/date_parsing.hpp @@ -0,0 +1,316 @@ +#ifndef _DATE_TIME_DATE_PARSING_HPP___ +#define _DATE_TIME_DATE_PARSING_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_DATE_TIME_NO_LOCALE) +#include // ::tolower(int) +#else +#include // std::tolower(char, locale) +#endif + +namespace boost { +namespace date_time { + + //! A function to replace the std::transform( , , ,tolower) construct + /*! This function simply takes a string, and changes all the characters + * in that string to lowercase (according to the default system locale). + * In the event that a compiler does not support locales, the old + * C style tolower() is used. + */ + inline + std::string + convert_to_lower(std::string inp) + { +#if !defined(BOOST_DATE_TIME_NO_LOCALE) + const std::locale loc(std::locale::classic()); +#endif + std::string::size_type i = 0, n = inp.length(); + for (; i < n; ++i) { + inp[i] = +#if defined(BOOST_DATE_TIME_NO_LOCALE) + static_cast(std::tolower(inp[i])); +#else + // tolower and others were brought in to std for borland >= v564 + // in compiler_config.hpp + std::tolower(inp[i], loc); +#endif + } + return inp; + } + + //! Helper function for parse_date. + /* Used by-value parameter because we change the string and may + * want to preserve the original argument */ + template + inline unsigned short + month_str_to_ushort(std::string const& s) { + if((s.at(0) >= '0') && (s.at(0) <= '9')) { + return boost::lexical_cast(s); + } + else { + std::string str = convert_to_lower(s); + typename month_type::month_map_ptr_type ptr = month_type::get_month_map_ptr(); + typename month_type::month_map_type::iterator iter = ptr->find(str); + if(iter != ptr->end()) { // required for STLport + return iter->second; + } + } + return 13; // intentionally out of range - name not found + } + + //! Find index of a string in either of 2 arrays + /*! find_match searches both arrays for a match to 's'. Both arrays + * must contain 'size' elements. The index of the match is returned. + * If no match is found, 'size' is returned. + * Ex. "Jan" returns 0, "Dec" returns 11, "Tue" returns 2. + * 'size' can be sent in with: (greg_month::max)() (which 12), + * (greg_weekday::max)() + 1 (which is 7) or date_time::NumSpecialValues */ + template + short find_match(const charT* const* short_names, + const charT* const* long_names, + short size, + const std::basic_string& s) { + for(short i = 0; i < size; ++i){ + if(short_names[i] == s || long_names[i] == s){ + return i; + } + } + return size; // not-found, return a value out of range + } + + //! Generic function to parse a delimited date (eg: 2002-02-10) + /*! Accepted formats are: "2003-02-10" or " 2003-Feb-10" or + * "2003-Feburary-10" + * The order in which the Month, Day, & Year appear in the argument + * string can be accomodated by passing in the appropriate ymd_order_spec + */ + template + date_type + parse_date(const std::string& s, int order_spec = ymd_order_iso) { + std::string spec_str; + if(order_spec == ymd_order_iso) { + spec_str = "ymd"; + } + else if(order_spec == ymd_order_dmy) { + spec_str = "dmy"; + } + else { // (order_spec == ymd_order_us) + spec_str = "mdy"; + } + + typedef typename date_type::month_type month_type; + unsigned pos = 0; + unsigned short year(0), month(0), day(0); + typedef typename std::basic_string::traits_type traits_type; + typedef boost::char_separator char_separator_type; + typedef boost::tokenizer::const_iterator, + std::basic_string > tokenizer; + typedef boost::tokenizer::const_iterator, + std::basic_string >::iterator tokenizer_iterator; + // may need more delimiters, these work for the regression tests + const char sep_char[] = {',','-','.',' ','/','\0'}; + char_separator_type sep(sep_char); + tokenizer tok(s,sep); + for(tokenizer_iterator beg=tok.begin(); + beg!=tok.end() && pos < spec_str.size(); + ++beg, ++pos) { + switch(spec_str.at(pos)) { + case 'y': + { + year = boost::lexical_cast(*beg); + break; + } + case 'm': + { + month = month_str_to_ushort(*beg); + break; + } + case 'd': + { + day = boost::lexical_cast(*beg); + break; + } + default: break; + } //switch + } + return date_type(year, month, day); + } + + //! Generic function to parse undelimited date (eg: 20020201) + template + date_type + parse_undelimited_date(const std::string& s) { + int offsets[] = {4,2,2}; + int pos = 0; + //typename date_type::ymd_type ymd((year_type::min)(),1,1); + unsigned short y = 0, m = 0, d = 0; + + /* The two bool arguments state that parsing will not wrap + * (only the first 8 characters will be parsed) and partial + * strings will not be parsed. + * Ex: + * "2005121" will parse 2005 & 12, but not the "1" */ + boost::offset_separator osf(offsets, offsets+3, false, false); + + typedef typename boost::tokenizer::const_iterator, + std::basic_string > tokenizer_type; + tokenizer_type tok(s, osf); + for(typename tokenizer_type::iterator ti=tok.begin(); ti!=tok.end();++ti) { + unsigned short i = boost::lexical_cast(*ti); + switch(pos) { + case 0: y = i; break; + case 1: m = i; break; + case 2: d = i; break; + default: break; + } + pos++; + } + return date_type(y,m,d); + } + + //! Helper function for 'date gregorian::from_stream()' + /*! Creates a string from the iterators that reference the + * begining & end of a char[] or string. All elements are + * used in output string */ + template + inline + date_type + from_stream_type(iterator_type& beg, + iterator_type const& end, + char) + { + std::ostringstream ss; + while(beg != end) { + ss << *beg++; + } + return parse_date(ss.str()); + } + + //! Helper function for 'date gregorian::from_stream()' + /*! Returns the first string found in the stream referenced by the + * begining & end iterators */ + template + inline + date_type + from_stream_type(iterator_type& beg, + iterator_type const& /* end */, + std::string const&) + { + return parse_date(*beg); + } + + /* I believe the wchar stuff would be best elsewhere, perhaps in + * parse_date<>()? In the mean time this gets us started... */ + //! Helper function for 'date gregorian::from_stream()' + /*! Creates a string from the iterators that reference the + * begining & end of a wstring. All elements are + * used in output string */ + template + inline + date_type from_stream_type(iterator_type& beg, + iterator_type const& end, + wchar_t) + { + std::ostringstream ss; +#if !defined(BOOST_DATE_TIME_NO_LOCALE) + std::locale loc; + std::ctype const& fac = std::use_facet >(loc); + while(beg != end) { + ss << fac.narrow(*beg++, 'X'); // 'X' will cause exception to be thrown + } +#else + while(beg != end) { + char c = 'X'; // 'X' will cause exception to be thrown + const wchar_t wc = *beg++; + if (wc >= 0 && wc <= 127) + c = static_cast< char >(wc); + ss << c; + } +#endif + return parse_date(ss.str()); + } +#ifndef BOOST_NO_STD_WSTRING + //! Helper function for 'date gregorian::from_stream()' + /*! Creates a string from the first wstring found in the stream + * referenced by the begining & end iterators */ + template + inline + date_type + from_stream_type(iterator_type& beg, + iterator_type const& /* end */, + std::wstring const&) { + std::wstring ws = *beg; + std::ostringstream ss; + std::wstring::iterator wsb = ws.begin(), wse = ws.end(); +#if !defined(BOOST_DATE_TIME_NO_LOCALE) + std::locale loc; + std::ctype const& fac = std::use_facet >(loc); + while(wsb != wse) { + ss << fac.narrow(*wsb++, 'X'); // 'X' will cause exception to be thrown + } +#else + while(wsb != wse) { + char c = 'X'; // 'X' will cause exception to be thrown + const wchar_t wc = *wsb++; + if (wc >= 0 && wc <= 127) + c = static_cast< char >(wc); + ss << c; + } +#endif + return parse_date(ss.str()); + } +#endif // BOOST_NO_STD_WSTRING +#if (defined(BOOST_MSVC) && (_MSC_VER < 1300)) + // This function cannot be compiled with MSVC 6.0 due to internal compiler shorcomings +#else + //! function called by wrapper functions: date_period_from_(w)string() + template + period + from_simple_string_type(const std::basic_string& s){ + typedef typename std::basic_string::traits_type traits_type; + typedef typename boost::char_separator char_separator; + typedef typename boost::tokenizer::const_iterator, + std::basic_string > tokenizer; + const charT sep_list[4] = {'[','/',']','\0'}; + char_separator sep(sep_list); + tokenizer tokens(s, sep); + typename tokenizer::iterator tok_it = tokens.begin(); + std::basic_string date_string = *tok_it; + // get 2 string iterators and generate a date from them + typename std::basic_string::iterator date_string_start = date_string.begin(), + date_string_end = date_string.end(); + typedef typename std::iterator_traits::iterator>::value_type value_type; + date_type d1 = from_stream_type(date_string_start, date_string_end, value_type()); + date_string = *(++tok_it); // next token + date_string_start = date_string.begin(), date_string_end = date_string.end(); + date_type d2 = from_stream_type(date_string_start, date_string_end, value_type()); + return period(d1, d2); + } +#endif + +} } //namespace date_time + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/dst_rules.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/dst_rules.hpp new file mode 100644 index 0000000..3229019 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/dst_rules.hpp @@ -0,0 +1,391 @@ +#ifndef DATE_TIME_DST_RULES_HPP__ +#define DATE_TIME_DST_RULES_HPP__ + +/* Copyright (c) 2002,2003, 2007 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! @file dst_rules.hpp + Contains template class to provide static dst rule calculations +*/ + +#include "boost/date_time/date_generators.hpp" +#include "boost/date_time/period.hpp" +#include "boost/date_time/date_defs.hpp" +#include + +namespace boost { + namespace date_time { + + enum time_is_dst_result {is_not_in_dst, is_in_dst, + ambiguous, invalid_time_label}; + + + //! Dynamic class used to caluclate dst transition information + template + class dst_calculator + { + public: + typedef time_duration_type_ time_duration_type; + typedef date_type_ date_type; + + //! Check the local time offset when on dst start day + /*! On this dst transition, the time label between + * the transition boundary and the boudary + the offset + * are invalid times. If before the boundary then still + * not in dst. + *@param time_of_day Time offset in the day for the local time + *@param dst_start_offset_minutes Local day offset for start of dst + *@param dst_length_minutes Number of minutes to adjust clock forward + *@retval status of time label w.r.t. dst + */ + static time_is_dst_result + process_local_dst_start_day(const time_duration_type& time_of_day, + unsigned int dst_start_offset_minutes, + long dst_length_minutes) + { + //std::cout << "here" << std::endl; + if (time_of_day < time_duration_type(0,dst_start_offset_minutes,0)) { + return is_not_in_dst; + } + long offset = dst_start_offset_minutes + dst_length_minutes; + if (time_of_day >= time_duration_type(0,offset,0)) { + return is_in_dst; + } + return invalid_time_label; + } + + //! Check the local time offset when on the last day of dst + /*! This is the calculation for the DST end day. On that day times + * prior to the conversion time - dst_length (1 am in US) are still + * in dst. Times between the above and the switch time are + * ambiguous. Times after the start_offset are not in dst. + *@param time_of_day Time offset in the day for the local time + *@param dst_end_offset_minutes Local time of day for end of dst + *@retval status of time label w.r.t. dst + */ + static time_is_dst_result + process_local_dst_end_day(const time_duration_type& time_of_day, + unsigned int dst_end_offset_minutes, + long dst_length_minutes) + { + //in US this will be 60 so offset in day is 1,0,0 + int offset = dst_end_offset_minutes-dst_length_minutes; + if (time_of_day < time_duration_type(0,offset,0)) { + return is_in_dst; + } + if (time_of_day >= time_duration_type(0,dst_end_offset_minutes,0)) { + return is_not_in_dst; + } + return ambiguous; + } + + //! Calculates if the given local time is dst or not + /*! Determines if the time is really in DST or not. Also checks for + * invalid and ambiguous. + * @param current_day The day to check for dst + * @param time_of_day Time offset within the day to check + * @param dst_start_day Starting day of dst for the given locality + * @param dst_start_offset Time offset within day for dst boundary + * @param dst_end_day Ending day of dst for the given locality + * @param dst_end_offset Time offset within day given in dst for dst boundary + * @param dst_length lenght of dst adjusment + * @retval The time is either ambiguous, invalid, in dst, or not in dst + */ + static time_is_dst_result + local_is_dst(const date_type& current_day, + const time_duration_type& time_of_day, + const date_type& dst_start_day, + const time_duration_type& dst_start_offset, + const date_type& dst_end_day, + const time_duration_type& dst_end_offset, + const time_duration_type& dst_length_minutes) + { + unsigned int start_minutes = + dst_start_offset.hours() * 60 + dst_start_offset.minutes(); + unsigned int end_minutes = + dst_end_offset.hours() * 60 + dst_end_offset.minutes(); + long length_minutes = + dst_length_minutes.hours() * 60 + dst_length_minutes.minutes(); + + return local_is_dst(current_day, time_of_day, + dst_start_day, start_minutes, + dst_end_day, end_minutes, + length_minutes); + } + + //! Calculates if the given local time is dst or not + /*! Determines if the time is really in DST or not. Also checks for + * invalid and ambiguous. + * @param current_day The day to check for dst + * @param time_of_day Time offset within the day to check + * @param dst_start_day Starting day of dst for the given locality + * @param dst_start_offset_minutes Offset within day for dst + * boundary (eg 120 for US which is 02:00:00) + * @param dst_end_day Ending day of dst for the given locality + * @param dst_end_offset_minutes Offset within day given in dst for dst + * boundary (eg 120 for US which is 02:00:00) + * @param dst_length_minutes Length of dst adjusment (eg: 60 for US) + * @retval The time is either ambiguous, invalid, in dst, or not in dst + */ + static time_is_dst_result + local_is_dst(const date_type& current_day, + const time_duration_type& time_of_day, + const date_type& dst_start_day, + unsigned int dst_start_offset_minutes, + const date_type& dst_end_day, + unsigned int dst_end_offset_minutes, + long dst_length_minutes) + { + //in northern hemisphere dst is in the middle of the year + if (dst_start_day < dst_end_day) { + if ((current_day > dst_start_day) && (current_day < dst_end_day)) { + return is_in_dst; + } + if ((current_day < dst_start_day) || (current_day > dst_end_day)) { + return is_not_in_dst; + } + } + else {//southern hemisphere dst is at begining /end of year + if ((current_day < dst_start_day) && (current_day > dst_end_day)) { + return is_not_in_dst; + } + if ((current_day > dst_start_day) || (current_day < dst_end_day)) { + return is_in_dst; + } + } + + if (current_day == dst_start_day) { + return process_local_dst_start_day(time_of_day, + dst_start_offset_minutes, + dst_length_minutes); + } + + if (current_day == dst_end_day) { + return process_local_dst_end_day(time_of_day, + dst_end_offset_minutes, + dst_length_minutes); + } + //you should never reach this statement + return invalid_time_label; + } + + }; + + + //! Compile-time configurable daylight savings time calculation engine + /* This template provides the ability to configure a daylight savings + * calculation at compile time covering all the cases. Unfortunately + * because of the number of dimensions related to daylight savings + * calculation the number of parameters is high. In addition, the + * start and end transition rules are complex types that specify + * an algorithm for calculation of the starting day and ending + * day of daylight savings time including the month and day + * specifications (eg: last sunday in October). + * + * @param date_type A type that represents dates, typically gregorian::date + * @param time_duration_type Used for the offset in the day calculations + * @param dst_traits A set of traits that define the rules of dst + * calculation. The dst_trait must include the following: + * start_rule_functor - Rule to calculate the starting date of a + * dst transition (eg: last_kday_of_month). + * start_day - static function that returns month of dst start for + * start_rule_functor + * start_month -static function that returns day or day of week for + * dst start of dst + * end_rule_functor - Rule to calculate the end of dst day. + * end_day - static fucntion that returns end day for end_rule_functor + * end_month - static function that returns end month for end_rule_functor + * dst_start_offset_minutes - number of minutes from start of day to transition to dst -- 120 (or 2:00 am) is typical for the U.S. and E.U. + * dst_start_offset_minutes - number of minutes from start of day to transition off of dst -- 180 (or 3:00 am) is typical for E.U. + * dst_length_minutes - number of minutes that dst shifts clock + */ + template + class dst_calc_engine + { + public: + typedef typename date_type::year_type year_type; + typedef typename date_type::calendar_type calendar_type; + typedef dst_calculator dstcalc; + + //! Calculates if the given local time is dst or not + /*! Determines if the time is really in DST or not. Also checks for + * invalid and ambiguous. + * @retval The time is either ambiguous, invalid, in dst, or not in dst + */ + static time_is_dst_result local_is_dst(const date_type& d, + const time_duration_type& td) + { + + year_type y = d.year(); + date_type dst_start = local_dst_start_day(y); + date_type dst_end = local_dst_end_day(y); + return dstcalc::local_is_dst(d,td, + dst_start, + dst_traits::dst_start_offset_minutes(), + dst_end, + dst_traits::dst_end_offset_minutes(), + dst_traits::dst_shift_length_minutes()); + + } + + static bool is_dst_boundary_day(date_type d) + { + year_type y = d.year(); + return ((d == local_dst_start_day(y)) || + (d == local_dst_end_day(y))); + } + + //! The time of day for the dst transition (eg: typically 01:00:00 or 02:00:00) + static time_duration_type dst_offset() + { + return time_duration_type(0,dst_traits::dst_shift_length_minutes(),0); + } + + static date_type local_dst_start_day(year_type year) + { + return dst_traits::local_dst_start_day(year); + } + + static date_type local_dst_end_day(year_type year) + { + return dst_traits::local_dst_end_day(year); + } + + + }; + + //! Depricated: Class to calculate dst boundaries for US time zones + /* Use dst_calc_engine instead. + * In 2007 US/Canada DST rules changed + * (http://en.wikipedia.org/wiki/Energy_Policy_Act_of_2005#Change_to_daylight_saving_time). + */ + template //1 hour == 60 min in US + class us_dst_rules + { + public: + typedef time_duration_type_ time_duration_type; + typedef date_type_ date_type; + typedef typename date_type::year_type year_type; + typedef typename date_type::calendar_type calendar_type; + typedef date_time::last_kday_of_month lkday; + typedef date_time::first_kday_of_month fkday; + typedef date_time::nth_kday_of_month nkday; + typedef dst_calculator dstcalc; + + //! Calculates if the given local time is dst or not + /*! Determines if the time is really in DST or not. Also checks for + * invalid and ambiguous. + * @retval The time is either ambiguous, invalid, in dst, or not in dst + */ + static time_is_dst_result local_is_dst(const date_type& d, + const time_duration_type& td) + { + + year_type y = d.year(); + date_type dst_start = local_dst_start_day(y); + date_type dst_end = local_dst_end_day(y); + return dstcalc::local_is_dst(d,td, + dst_start,dst_start_offset_minutes, + dst_end, dst_start_offset_minutes, + dst_length_minutes); + + } + + + static bool is_dst_boundary_day(date_type d) + { + year_type y = d.year(); + return ((d == local_dst_start_day(y)) || + (d == local_dst_end_day(y))); + } + + static date_type local_dst_start_day(year_type year) + { + if (year >= year_type(2007)) { + //second sunday in march + nkday ssim(nkday::second, Sunday, gregorian::Mar); + return ssim.get_date(year); + } else { + //first sunday in april + fkday fsia(Sunday, gregorian::Apr); + return fsia.get_date(year); + } + } + + static date_type local_dst_end_day(year_type year) + { + if (year >= year_type(2007)) { + //first sunday in november + fkday fsin(Sunday, gregorian::Nov); + return fsin.get_date(year); + } else { + //last sunday in october + lkday lsio(Sunday, gregorian::Oct); + return lsio.get_date(year); + } + } + + static time_duration_type dst_offset() + { + return time_duration_type(0,dst_length_minutes,0); + } + + private: + + + }; + + //! Used for local time adjustments in places that don't use dst + template + class null_dst_rules + { + public: + typedef time_duration_type_ time_duration_type; + typedef date_type_ date_type; + + + //! Calculates if the given local time is dst or not + /*! @retval Always is_not_in_dst since this is for zones without dst + */ + static time_is_dst_result local_is_dst(const date_type&, + const time_duration_type&) + { + return is_not_in_dst; + } + + //! Calculates if the given utc time is in dst + static time_is_dst_result utc_is_dst(const date_type&, + const time_duration_type&) + { + return is_not_in_dst; + } + + static bool is_dst_boundary_day(date_type /*d*/) + { + return false; + } + + static time_duration_type dst_offset() + { + return time_duration_type(0,0,0); + } + + }; + + + } } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/filetime_functions.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/filetime_functions.hpp new file mode 100644 index 0000000..ca5a1ad --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/filetime_functions.hpp @@ -0,0 +1,170 @@ +#ifndef DATE_TIME_FILETIME_FUNCTIONS_HPP__ +#define DATE_TIME_FILETIME_FUNCTIONS_HPP__ + +/* Copyright (c) 2004 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! @file filetime_functions.hpp + * Function(s) for converting between a FILETIME structure and a + * time object. This file is only available on systems that have + * BOOST_HAS_FTIME defined. + */ + +#include + +#if defined(BOOST_HAS_FTIME) // skip this file if no FILETIME + +#if defined(BOOST_USE_WINDOWS_H) +# include +#endif + +#include +#include +#include + +namespace boost { + +namespace date_time { + +namespace winapi { + +#if !defined(BOOST_USE_WINDOWS_H) + + extern "C" { + + struct FILETIME + { + boost::uint32_t dwLowDateTime; + boost::uint32_t dwHighDateTime; + }; + struct SYSTEMTIME + { + boost::uint16_t wYear; + boost::uint16_t wMonth; + boost::uint16_t wDayOfWeek; + boost::uint16_t wDay; + boost::uint16_t wHour; + boost::uint16_t wMinute; + boost::uint16_t wSecond; + boost::uint16_t wMilliseconds; + }; + + __declspec(dllimport) void __stdcall GetSystemTimeAsFileTime(FILETIME* lpFileTime); + __declspec(dllimport) int __stdcall FileTimeToLocalFileTime(const FILETIME* lpFileTime, FILETIME* lpLocalFileTime); + __declspec(dllimport) void __stdcall GetSystemTime(SYSTEMTIME* lpSystemTime); + __declspec(dllimport) int __stdcall SystemTimeToFileTime(const SYSTEMTIME* lpSystemTime, FILETIME* lpFileTime); + + } // extern "C" + +#endif // defined(BOOST_USE_WINDOWS_H) + + typedef FILETIME file_time; + typedef SYSTEMTIME system_time; + + inline void get_system_time_as_file_time(file_time& ft) + { +#if BOOST_WORKAROUND(__MWERKS__, BOOST_TESTED_AT(0x3205)) + // Some runtime library implementations expect local times as the norm for ctime. + file_time ft_utc; + GetSystemTimeAsFileTime(&ft_utc); + FileTimeToLocalFileTime(&ft_utc, &ft); +#elif defined(BOOST_HAS_GETSYSTEMTIMEASFILETIME) + GetSystemTimeAsFileTime(&ft); +#else + system_time st; + GetSystemTime(&st); + SystemTimeToFileTime(&st, &ft); +#endif + } + + /*! + * The function converts file_time into number of microseconds elapsed since 1970-Jan-01 + * + * \note Only dates after 1970-Jan-01 are supported. Dates before will be wrapped. + * + * \note The function is templated on the FILETIME type, so that + * it can be used with both native FILETIME and the ad-hoc + * boost::date_time::winapi::file_time type. + */ + template< typename FileTimeT > + inline boost::uint64_t file_time_to_microseconds(FileTimeT const& ft) + { + /* shift is difference between 1970-Jan-01 & 1601-Jan-01 + * in 100-nanosecond intervals */ + const uint64_t shift = 116444736000000000ULL; // (27111902 << 32) + 3577643008 + + union { + FileTimeT as_file_time; + uint64_t as_integer; // 100-nanos since 1601-Jan-01 + } caster; + caster.as_file_time = ft; + + caster.as_integer -= shift; // filetime is now 100-nanos since 1970-Jan-01 + return (caster.as_integer / 10); // truncate to microseconds + } + +} // namespace winapi + +//! Create a time object from an initialized FILETIME struct. +/*! + * Create a time object from an initialized FILETIME struct. + * A FILETIME struct holds 100-nanosecond units (0.0000001). When + * built with microsecond resolution the file_time's sub second value + * will be truncated. Nanosecond resolution has no truncation. + * + * \note The function is templated on the FILETIME type, so that + * it can be used with both native FILETIME and the ad-hoc + * boost::date_time::winapi::file_time type. + */ +template< typename TimeT, typename FileTimeT > +inline +TimeT time_from_ftime(const FileTimeT& ft) +{ + typedef typename TimeT::date_type date_type; + typedef typename TimeT::date_duration_type date_duration_type; + typedef typename TimeT::time_duration_type time_duration_type; + + // https://svn.boost.org/trac/boost/ticket/2523 + // Since this function can be called with arbitrary times, including ones that + // are before 1970-Jan-01, we'll have to cast the time a bit differently, + // than it is done in the file_time_to_microseconds function. This allows to + // avoid integer wrapping for dates before 1970-Jan-01. + union { + FileTimeT as_file_time; + uint64_t as_integer; // 100-nanos since 1601-Jan-01 + } caster; + caster.as_file_time = ft; + + uint64_t sec = caster.as_integer / 10000000UL; + uint32_t sub_sec = (caster.as_integer % 10000000UL) // 100-nanoseconds since the last second +#if !defined(BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG) + / 10; // microseconds since the last second +#else + * 100; // nanoseconds since the last second +#endif + + // split sec into usable chunks: days, hours, minutes, & seconds + const uint32_t sec_per_day = 86400; // seconds per day + uint32_t days = static_cast< uint32_t >(sec / sec_per_day); + uint32_t tmp = static_cast< uint32_t >(sec % sec_per_day); + uint32_t hours = tmp / 3600; // sec_per_hour + tmp %= 3600; + uint32_t minutes = tmp / 60; // sec_per_min + tmp %= 60; + uint32_t seconds = tmp; // seconds + + date_duration_type dd(days); + date_type d = date_type(1601, Jan, 01) + dd; + return TimeT(d, time_duration_type(hours, minutes, seconds, sub_sec)); +} + +}} // boost::date_time + +#endif // BOOST_HAS_FTIME + +#endif // DATE_TIME_FILETIME_FUNCTIONS_HPP__ diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/conversion.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/conversion.hpp new file mode 100644 index 0000000..c844c4e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/conversion.hpp @@ -0,0 +1,68 @@ +#ifndef _GREGORIAN__CONVERSION_HPP___ +#define _GREGORIAN__CONVERSION_HPP___ + +/* Copyright (c) 2004-2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace boost { + +namespace gregorian { + + //! Converts a date to a tm struct. Throws out_of_range exception if date is a special value + inline + std::tm to_tm(const date& d) + { + if (d.is_special()) + { + std::string s = "tm unable to handle "; + switch (d.as_special()) + { + case date_time::not_a_date_time: + s += "not-a-date-time value"; break; + case date_time::neg_infin: + s += "-infinity date value"; break; + case date_time::pos_infin: + s += "+infinity date value"; break; + default: + s += "a special date value"; break; + } + boost::throw_exception(std::out_of_range(s)); + } + + std::tm datetm; + std::memset(&datetm, 0, sizeof(datetm)); + boost::gregorian::date::ymd_type ymd = d.year_month_day(); + datetm.tm_year = ymd.year - 1900; + datetm.tm_mon = ymd.month - 1; + datetm.tm_mday = ymd.day; + datetm.tm_wday = d.day_of_week(); + datetm.tm_yday = d.day_of_year() - 1; + datetm.tm_isdst = -1; // negative because not enough info to set tm_isdst + return datetm; + } + + //! Converts a tm structure into a date dropping the any time values. + inline + date date_from_tm(const std::tm& datetm) + { + return date(static_cast(datetm.tm_year+1900), + static_cast(datetm.tm_mon+1), + static_cast(datetm.tm_mday)); + } + +} } //namespace boost::gregorian + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters.hpp new file mode 100644 index 0000000..d486ef0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters.hpp @@ -0,0 +1,162 @@ +#ifndef GREGORIAN_FORMATTERS_HPP___ +#define GREGORIAN_FORMATTERS_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/compiler_config.hpp" +#include "boost/date_time/gregorian/gregorian_types.hpp" +#if defined(BOOST_DATE_TIME_INCLUDE_LIMITED_HEADERS) +#include "boost/date_time/date_formatting_limited.hpp" +#else +#include "boost/date_time/date_formatting.hpp" +#endif +#include "boost/date_time/iso_format.hpp" +#include "boost/date_time/date_format_simple.hpp" + +/* NOTE: "to_*_string" code for older compilers, ones that define + * BOOST_DATE_TIME_INCLUDE_LIMITED_HEADERS, is located in + * formatters_limited.hpp + */ + +namespace boost { +namespace gregorian { + + // wrapper function for to_simple_(w)string(date) + template + inline + std::basic_string to_simple_string_type(const date& d) { + return date_time::date_formatter,charT>::date_to_string(d); + } + //! To YYYY-mmm-DD string where mmm 3 char month name. Example: 2002-Jan-01 + /*!\ingroup date_format + */ + inline std::string to_simple_string(const date& d) { + return to_simple_string_type(d); + } + + + // wrapper function for to_simple_(w)string(date_period) + template + inline std::basic_string to_simple_string_type(const date_period& d) { + typedef std::basic_string string_type; + charT b = '[', m = '/', e=']'; + + string_type d1(date_time::date_formatter,charT>::date_to_string(d.begin())); + string_type d2(date_time::date_formatter,charT>::date_to_string(d.last())); + return string_type(b + d1 + m + d2 + e); + } + //! Convert date period to simple string. Example: [2002-Jan-01/2002-Jan-02] + /*!\ingroup date_format + */ + inline std::string to_simple_string(const date_period& d) { + return to_simple_string_type(d); + } + + // wrapper function for to_iso_(w)string(date_period) + template + inline std::basic_string to_iso_string_type(const date_period& d) { + charT sep = '/'; + std::basic_string s(date_time::date_formatter,charT>::date_to_string(d.begin())); + return s + sep + date_time::date_formatter,charT>::date_to_string(d.last()); + } + //! Date period to iso standard format CCYYMMDD/CCYYMMDD. Example: 20021225/20021231 + /*!\ingroup date_format + */ + inline std::string to_iso_string(const date_period& d) { + return to_iso_string_type(d); + } + + + // wrapper function for to_iso_extended_(w)string(date) + template + inline std::basic_string to_iso_extended_string_type(const date& d) { + return date_time::date_formatter,charT>::date_to_string(d); + } + //! Convert to iso extended format string CCYY-MM-DD. Example 2002-12-31 + /*!\ingroup date_format + */ + inline std::string to_iso_extended_string(const date& d) { + return to_iso_extended_string_type(d); + } + + // wrapper function for to_iso_(w)string(date) + template + inline std::basic_string to_iso_string_type(const date& d) { + return date_time::date_formatter,charT>::date_to_string(d); + } + //! Convert to iso standard string YYYYMMDD. Example: 20021231 + /*!\ingroup date_format + */ + inline std::string to_iso_string(const date& d) { + return to_iso_string_type(d); + } + + + + + // wrapper function for to_sql_(w)string(date) + template + inline std::basic_string to_sql_string_type(const date& d) + { + date::ymd_type ymd = d.year_month_day(); + std::basic_ostringstream ss; + ss << ymd.year << "-" + << std::setw(2) << std::setfill(ss.widen('0')) + << ymd.month.as_number() //solves problem with gcc 3.1 hanging + << "-" + << std::setw(2) << std::setfill(ss.widen('0')) + << ymd.day; + return ss.str(); + } + inline std::string to_sql_string(const date& d) { + return to_sql_string_type(d); + } + + +#if !defined(BOOST_NO_STD_WSTRING) + //! Convert date period to simple string. Example: [2002-Jan-01/2002-Jan-02] + /*!\ingroup date_format + */ + inline std::wstring to_simple_wstring(const date_period& d) { + return to_simple_string_type(d); + } + //! To YYYY-mmm-DD string where mmm 3 char month name. Example: 2002-Jan-01 + /*!\ingroup date_format + */ + inline std::wstring to_simple_wstring(const date& d) { + return to_simple_string_type(d); + } + //! Date period to iso standard format CCYYMMDD/CCYYMMDD. Example: 20021225/20021231 + /*!\ingroup date_format + */ + inline std::wstring to_iso_wstring(const date_period& d) { + return to_iso_string_type(d); + } + //! Convert to iso extended format string CCYY-MM-DD. Example 2002-12-31 + /*!\ingroup date_format + */ + inline std::wstring to_iso_extended_wstring(const date& d) { + return to_iso_extended_string_type(d); + } + //! Convert to iso standard string YYYYMMDD. Example: 20021231 + /*!\ingroup date_format + */ + inline std::wstring to_iso_wstring(const date& d) { + return to_iso_string_type(d); + } + inline std::wstring to_sql_wstring(const date& d) { + return to_sql_string_type(d); + } +#endif // BOOST_NO_STD_WSTRING + +} } //namespace gregorian + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters_limited.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters_limited.hpp new file mode 100644 index 0000000..755f5aa --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/formatters_limited.hpp @@ -0,0 +1,81 @@ +#ifndef GREGORIAN_FORMATTERS_LIMITED_HPP___ +#define GREGORIAN_FORMATTERS_LIMITED_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/gregorian/gregorian_types.hpp" +#include "boost/date_time/date_formatting_limited.hpp" +#include "boost/date_time/iso_format.hpp" +#include "boost/date_time/date_format_simple.hpp" +#include "boost/date_time/compiler_config.hpp" + +namespace boost { +namespace gregorian { + + //! To YYYY-mmm-DD string where mmm 3 char month name. Example: 2002-Jan-01 + /*!\ingroup date_format + */ + inline std::string to_simple_string(const date& d) { + return date_time::date_formatter >::date_to_string(d); + } + + //! Convert date period to simple string. Example: [2002-Jan-01/2002-Jan-02] + /*!\ingroup date_format + */ + inline std::string to_simple_string(const date_period& d) { + std::string s("["); + std::string d1(date_time::date_formatter >::date_to_string(d.begin())); + std::string d2(date_time::date_formatter >::date_to_string(d.last())); + return std::string("[" + d1 + "/" + d2 + "]"); + } + + //! Date period to iso standard format CCYYMMDD/CCYYMMDD. Example: 20021225/20021231 + /*!\ingroup date_format + */ + inline std::string to_iso_string(const date_period& d) { + std::string s(date_time::date_formatter >::date_to_string(d.begin())); + return s + "/" + date_time::date_formatter >::date_to_string(d.last()); + } + + + //! Convert to iso extended format string CCYY-MM-DD. Example 2002-12-31 + /*!\ingroup date_format + */ + inline std::string to_iso_extended_string(const date& d) { + return date_time::date_formatter >::date_to_string(d); + } + + //! Convert to iso standard string YYYYMMDD. Example: 20021231 + /*!\ingroup date_format + */ + inline std::string to_iso_string(const date& d) { + return date_time::date_formatter >::date_to_string(d); + } + + + + inline std::string to_sql_string(const date& d) + { + date::ymd_type ymd = d.year_month_day(); + std::ostringstream ss; + ss << ymd.year << "-" + << std::setw(2) << std::setfill('0') + << ymd.month.as_number() //solves problem with gcc 3.1 hanging + << "-" + << std::setw(2) << std::setfill('0') + << ymd.day; + return ss.str(); + } + + +} } //namespace gregorian + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_calendar.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_calendar.hpp new file mode 100644 index 0000000..34ce0ae --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_calendar.hpp @@ -0,0 +1,48 @@ +#ifndef GREGORIAN_GREGORIAN_CALENDAR_HPP__ +#define GREGORIAN_GREGORIAN_CALENDAR_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace gregorian { + + //!An internal date representation that includes infinities, not a date + typedef date_time::int_adapter fancy_date_rep; + + //! Gregorian calendar for this implementation, hard work in the base + class gregorian_calendar : + public date_time::gregorian_calendar_base { + public: + //! Type to hold a weekday (eg: Sunday, Monday,...) + typedef greg_weekday day_of_week_type; + //! Counter type from 1 to 366 for gregorian dates. + typedef greg_day_of_year_rep day_of_year_type; + //! Internal date representation that handles infinity, not a date + typedef fancy_date_rep date_rep_type; + //! Date rep implements the traits stuff as well + typedef fancy_date_rep date_traits_type; + + + private: + }; + +} } //namespace gregorian + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_date.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_date.hpp new file mode 100644 index 0000000..f7aa2fc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_date.hpp @@ -0,0 +1,136 @@ +#ifndef GREG_DATE_HPP___ +#define GREG_DATE_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include +#include +#include +#include +#include + +namespace boost { +namespace gregorian { + + //bring special enum values into the namespace + using date_time::special_values; + using date_time::not_special; + using date_time::neg_infin; + using date_time::pos_infin; + using date_time::not_a_date_time; + using date_time::max_date_time; + using date_time::min_date_time; + + //! A date type based on gregorian_calendar + /*! This class is the primary interface for programming with + greogorian dates. The is a lightweight type that can be + freely passed by value. All comparison operators are + supported. + \ingroup date_basics + */ + class date : public date_time::date + { + public: + typedef gregorian_calendar::year_type year_type; + typedef gregorian_calendar::month_type month_type; + typedef gregorian_calendar::day_type day_type; + typedef gregorian_calendar::day_of_year_type day_of_year_type; + typedef gregorian_calendar::ymd_type ymd_type; + typedef gregorian_calendar::date_rep_type date_rep_type; + typedef gregorian_calendar::date_int_type date_int_type; + typedef date_duration duration_type; +#if !defined(DATE_TIME_NO_DEFAULT_CONSTRUCTOR) + //! Default constructor constructs with not_a_date_time + date(): + date_time::date(date_rep_type::from_special(not_a_date_time)) + {} +#endif // DATE_TIME_NO_DEFAULT_CONSTRUCTOR + //! Main constructor with year, month, day + date(year_type y, month_type m, day_type d) + : date_time::date(y, m, d) + { + if (gregorian_calendar::end_of_month_day(y, m) < d) { + boost::throw_exception(bad_day_of_month(std::string("Day of month is not valid for year"))); + } + } + //! Constructor from a ymd_type structure + explicit date(const ymd_type& ymd) + : date_time::date(ymd) + {} + //! Needed copy constructor + explicit date(const date_int_type& rhs): + date_time::date(rhs) + {} + //! Needed copy constructor + explicit date(date_rep_type rhs): + date_time::date(rhs) + {} + //! Constructor for infinities, not a date, max and min date + explicit date(special_values sv): + date_time::date(date_rep_type::from_special(sv)) + { + if (sv == min_date_time) + { + *this = date(1400, 1, 1); + } + if (sv == max_date_time) + { + *this = date(9999, 12, 31); + } + + } + //!Return the Julian Day number for the date. + date_int_type julian_day() const + { + ymd_type ymd = year_month_day(); + return gregorian_calendar::julian_day_number(ymd); + } + //!Return the day of year 1..365 or 1..366 (for leap year) + day_of_year_type day_of_year() const + { + date start_of_year(year(), 1, 1); + unsigned short doy = static_cast((*this-start_of_year).days() + 1); + return day_of_year_type(doy); + } + //!Return the Modified Julian Day number for the date. + date_int_type modjulian_day() const + { + ymd_type ymd = year_month_day(); + return gregorian_calendar::modjulian_day_number(ymd); + } + //!Return the iso 8601 week number 1..53 + int week_number() const + { + ymd_type ymd = year_month_day(); + return gregorian_calendar::week_number(ymd); + } + //! Return the day number from the calendar + date_int_type day_number() const + { + return days_; + } + //! Return the last day of the current month + date end_of_month() const + { + ymd_type ymd = year_month_day(); + short eom_day = gregorian_calendar::end_of_month_day(ymd.year, ymd.month); + return date(ymd.year, ymd.month, eom_day); + } + + private: + + }; + + + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day.hpp new file mode 100644 index 0000000..4a2d5e7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day.hpp @@ -0,0 +1,57 @@ +#ifndef GREG_DAY_HPP___ +#define GREG_DAY_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/constrained_value.hpp" +#include +#include + +namespace boost { +namespace gregorian { + + //! Exception type for gregorian day of month (1..31) + struct bad_day_of_month : public std::out_of_range + { + bad_day_of_month() : + std::out_of_range(std::string("Day of month value is out of range 1..31")) + {} + //! Allow other classes to throw with unique string for bad day like Feb 29 + bad_day_of_month(const std::string& s) : + std::out_of_range(s) + {} + }; + //! Policy class that declares error handling and day of month ranges + typedef CV::simple_exception_policy greg_day_policies; + + //! Generated represetation for gregorian day of month + typedef CV::constrained_value greg_day_rep; + + //! Represent a day of the month (range 1 - 31) + /*! This small class allows for simple conversion an integer value into + a day of the month for a standard gregorian calendar. The type + is automatically range checked so values outside of the range 1-31 + will cause a bad_day_of_month exception + */ + class greg_day : public greg_day_rep { + public: + greg_day(unsigned short day_of_month) : greg_day_rep(day_of_month) {} + unsigned short as_number() const {return value_;} + operator unsigned short() const {return value_;} + private: + + }; + + + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day_of_year.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day_of_year.hpp new file mode 100644 index 0000000..abf0c9e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_day_of_year.hpp @@ -0,0 +1,38 @@ +#ifndef GREG_DAY_OF_YEAR_HPP___ +#define GREG_DAY_OF_YEAR_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/constrained_value.hpp" +#include +#include + +namespace boost { +namespace gregorian { + + //! Exception type for day of year (1..366) + struct bad_day_of_year : public std::out_of_range + { + bad_day_of_year() : + std::out_of_range(std::string("Day of year value is out of range 1..366")) + {} + }; + + //! A day of the year range (1..366) + typedef CV::simple_exception_policy greg_day_of_year_policies; + + //! Define a range representation type for the day of the year 1..366 + typedef CV::constrained_value greg_day_of_year_rep; + + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration.hpp new file mode 100644 index 0000000..dc6ad60 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration.hpp @@ -0,0 +1,134 @@ +#ifndef GREG_DURATION_HPP___ +#define GREG_DURATION_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include + +namespace boost { +namespace gregorian { + + //!An internal date representation that includes infinities, not a date + typedef boost::date_time::duration_traits_adapted date_duration_rep; + + //! Durations in days for gregorian system + /*! \ingroup date_basics + */ + class date_duration : + public boost::date_time::date_duration< date_duration_rep > + { + typedef boost::date_time::date_duration< date_duration_rep > base_type; + + public: + typedef base_type::duration_rep duration_rep; + + //! Construct from a day count + explicit date_duration(duration_rep day_count = 0) : base_type(day_count) {} + + //! construct from special_values + date_duration(date_time::special_values sv) : base_type(sv) {} + + //! Copy constructor + date_duration(const date_duration& other) : base_type(static_cast< base_type const& >(other)) + {} + + //! Construct from another date_duration + date_duration(const base_type& other) : base_type(other) + {} + + // Relational operators + // NOTE: Because of date_time::date_duration< T > design choice we don't use Boost.Operators here, + // because we need the class to be a direct base. Either lose EBO, or define operators by hand. + // The latter is more effecient. + bool operator== (const date_duration& rhs) const + { + return base_type::operator== (rhs); + } + bool operator!= (const date_duration& rhs) const + { + return !operator== (rhs); + } + bool operator< (const date_duration& rhs) const + { + return base_type::operator< (rhs); + } + bool operator> (const date_duration& rhs) const + { + return !(base_type::operator< (rhs) || base_type::operator== (rhs)); + } + bool operator<= (const date_duration& rhs) const + { + return (base_type::operator< (rhs) || base_type::operator== (rhs)); + } + bool operator>= (const date_duration& rhs) const + { + return !base_type::operator< (rhs); + } + + //! Subtract another duration -- result is signed + date_duration& operator-= (const date_duration& rhs) + { + base_type::operator-= (rhs); + return *this; + } + friend date_duration operator- (date_duration rhs, date_duration const& lhs) + { + rhs -= lhs; + return rhs; + } + + //! Add a duration -- result is signed + date_duration& operator+= (const date_duration& rhs) + { + base_type::operator+= (rhs); + return *this; + } + friend date_duration operator+ (date_duration rhs, date_duration const& lhs) + { + rhs += lhs; + return rhs; + } + + //! unary- Allows for dd = -date_duration(2); -> dd == -2 + date_duration operator- ()const + { + return date_duration(get_rep() * (-1)); + } + + //! Division operations on a duration with an integer. + date_duration& operator/= (int divisor) + { + base_type::operator/= (divisor); + return *this; + } + friend date_duration operator/ (date_duration rhs, int lhs) + { + rhs /= lhs; + return rhs; + } + + //! Returns the smallest duration -- used by to calculate 'end' + static date_duration unit() + { + return date_duration(base_type::unit().get_rep()); + } + }; + + //! Shorthand for date_duration + typedef date_duration days; + +} } //namespace gregorian + +#if defined(BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES) +#include +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration_types.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration_types.hpp new file mode 100644 index 0000000..d1f9a65 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_duration_types.hpp @@ -0,0 +1,43 @@ +#ifndef GREG_DURATION_TYPES_HPP___ +#define GREG_DURATION_TYPES_HPP___ + +/* Copyright (c) 2004 CrystalClear Software, Inc. + * Subject to Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include +#include +#include +#include +#include + +namespace boost { +namespace gregorian { + + //! config struct for additional duration types (ie months_duration<> & years_duration<>) + struct greg_durations_config { + typedef date date_type; + typedef date_time::int_adapter int_rep; + typedef date_time::month_functor month_adjustor_type; + }; + + typedef date_time::months_duration months; + typedef date_time::years_duration years; + + class weeks_duration : public date_duration { + public: + weeks_duration(duration_rep w) + : date_duration(w * 7) {} + weeks_duration(date_time::special_values sv) + : date_duration(sv) {} + }; + + typedef weeks_duration weeks; + +}} // namespace boost::gregorian + +#endif // GREG_DURATION_TYPES_HPP___ diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_facet.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_facet.hpp new file mode 100644 index 0000000..b8c6d57 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_facet.hpp @@ -0,0 +1,352 @@ +#ifndef GREGORIAN_FACET_HPP___ +#define GREGORIAN_FACET_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/gregorian/gregorian_types.hpp" +#include "boost/date_time/date_formatting_locales.hpp" // sets BOOST_DATE_TIME_NO_LOCALE +#include "boost/date_time/gregorian/parsers.hpp" + +//This file is basically commented out if locales are not supported +#ifndef BOOST_DATE_TIME_NO_LOCALE + +#include +#include +#include +#include +#include + +namespace boost { +namespace gregorian { + + //! Configuration of the output facet template + struct greg_facet_config + { + typedef boost::gregorian::greg_month month_type; + typedef boost::date_time::special_values special_value_enum; + typedef boost::gregorian::months_of_year month_enum; + typedef boost::date_time::weekdays weekday_enum; + }; + +#if defined(USE_DATE_TIME_PRE_1_33_FACET_IO) + //! Create the base facet type for gregorian::date + typedef boost::date_time::date_names_put greg_base_facet; + + //! ostream operator for gregorian::date + /*! Uses the date facet to determine various output parameters including: + * - string values for the month (eg: Jan, Feb, Mar) (default: English) + * - string values for special values (eg: not-a-date-time) (default: English) + * - selection of long, short strings, or numerical month representation (default: short string) + * - month day year order (default yyyy-mmm-dd) + */ + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const date& d) + { + typedef boost::date_time::date_names_put facet_def; + typedef boost::date_time::ostream_date_formatter greg_ostream_formatter; + greg_ostream_formatter::date_put(d, os); + return os; + } + + //! operator<< for gregorian::greg_month typically streaming: Jan, Feb, Mar... + /*! Uses the date facet to determine output string as well as selection of long or short strings. + * Default if no facet is installed is to output a 2 wide numeric value for the month + * eg: 01 == Jan, 02 == Feb, ... 12 == Dec. + */ + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const greg_month& m) + { + typedef boost::date_time::date_names_put facet_def; + typedef boost::date_time::ostream_month_formatter greg_month_formatter; + std::locale locale = os.getloc(); + if (std::has_facet(locale)) { + const facet_def& f = std::use_facet(locale); + greg_month_formatter::format_month(m, os, f); + + } + else { //default to numeric + charT fill_char = '0'; + os << std::setw(2) << std::setfill(fill_char) << m.as_number(); + } + + return os; + } + + //! operator<< for gregorian::greg_weekday typically streaming: Sun, Mon, Tue, ... + /*! Uses the date facet to determine output string as well as selection of long or short string. + * Default if no facet is installed is to output a 3 char english string for the + * day of the week. + */ + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const greg_weekday& wd) + { + typedef boost::date_time::date_names_put facet_def; + typedef boost::date_time::ostream_weekday_formatter greg_weekday_formatter; + std::locale locale = os.getloc(); + if (std::has_facet(locale)) { + const facet_def& f = std::use_facet(locale); + greg_weekday_formatter::format_weekday(wd, os, f, true); + } + else { //default to short English string eg: Sun, Mon, Tue, Wed... + os << wd.as_short_string(); + } + + return os; + } + + //! operator<< for gregorian::date_period typical output: [2002-Jan-01/2002-Jan-31] + /*! Uses the date facet to determine output string as well as selection of long + * or short string fr dates. + * Default if no facet is installed is to output a 3 char english string for the + * day of the week. + */ + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const date_period& dp) + { + os << '['; //TODO: facet or manipulator for periods? + os << dp.begin(); + os << '/'; //TODO: facet or manipulator for periods? + os << dp.last(); + os << ']'; + return os; + } + + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const date_duration& dd) + { + //os << dd.days(); + os << dd.get_rep(); + return os; + } + + //! operator<< for gregorian::partial_date. Output: "Jan 1" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const partial_date& pd) + { + os << std::setw(2) << std::setfill('0') << pd.day() << ' ' + << pd.month().as_short_string() ; + return os; + } + + //! operator<< for gregorian::nth_kday_of_month. Output: "first Mon of Jun" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, + const nth_kday_of_month& nkd) + { + os << nkd.nth_week_as_str() << ' ' + << nkd.day_of_week() << " of " + << nkd.month().as_short_string() ; + return os; + } + + //! operator<< for gregorian::first_kday_of_month. Output: "first Mon of Jun" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, + const first_kday_of_month& fkd) + { + os << "first " << fkd.day_of_week() << " of " + << fkd.month().as_short_string() ; + return os; + } + + //! operator<< for gregorian::last_kday_of_month. Output: "last Mon of Jun" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, + const last_kday_of_month& lkd) + { + os << "last " << lkd.day_of_week() << " of " + << lkd.month().as_short_string() ; + return os; + } + + //! operator<< for gregorian::first_kday_after. Output: "first Mon after" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, + const first_kday_after& fka) + { + os << fka.day_of_week() << " after"; + return os; + } + + //! operator<< for gregorian::first_kday_before. Output: "first Mon before" + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, + const first_kday_before& fkb) + { + os << fkb.day_of_week() << " before"; + return os; + } +#endif // USE_DATE_TIME_PRE_1_33_FACET_IO + /**************** Input Streaming ******************/ + +#if !defined(BOOST_NO_STD_ITERATOR_TRAITS) + //! operator>> for gregorian::date + template + inline + std::basic_istream& operator>>(std::basic_istream& is, date& d) + { + std::istream_iterator, charT> beg(is), eos; + d = from_stream(beg, eos); + return is; + } +#endif // BOOST_NO_STD_ITERATOR_TRAITS + + //! operator>> for gregorian::date_duration + template + inline + std::basic_istream& operator>>(std::basic_istream& is, + date_duration& dd) + { + long v; + is >> v; + dd = date_duration(v); + return is; + } + + //! operator>> for gregorian::date_period + template + inline + std::basic_istream& operator>>(std::basic_istream& is, + date_period& dp) + { + std::basic_string s; + is >> s; + dp = date_time::from_simple_string_type(s); + return is; + } + + //! generates a locale with the set of gregorian name-strings of type char* + BOOST_DATE_TIME_DECL std::locale generate_locale(std::locale& loc, char type); + + //! Returns a pointer to a facet with a default set of names (English) + /* Necessary in the event an exception is thrown from op>> for + * weekday or month. See comments in those functions for more info */ + BOOST_DATE_TIME_DECL boost::date_time::all_date_names_put* create_facet_def(char type); + +#ifndef BOOST_NO_STD_WSTRING + //! generates a locale with the set of gregorian name-strings of type wchar_t* + BOOST_DATE_TIME_DECL std::locale generate_locale(std::locale& loc, wchar_t type); + //! Returns a pointer to a facet with a default set of names (English) + /* Necessary in the event an exception is thrown from op>> for + * weekday or month. See comments in those functions for more info */ + BOOST_DATE_TIME_DECL boost::date_time::all_date_names_put* create_facet_def(wchar_t type); +#endif // BOOST_NO_STD_WSTRING + + //! operator>> for gregorian::greg_month - throws exception if invalid month given + template + inline + std::basic_istream& operator>>(std::basic_istream& is,greg_month& m) + { + typedef boost::date_time::all_date_names_put facet_def; + + std::basic_string s; + is >> s; + + if(!std::has_facet(is.getloc())) { + std::locale loc = is.getloc(); + charT a = '\0'; + is.imbue(generate_locale(loc, a)); + } + + short num = 0; + + try{ + const facet_def& f = std::use_facet(is.getloc()); + num = date_time::find_match(f.get_short_month_names(), + f.get_long_month_names(), + (greg_month::max)(), s); // greg_month spans 1..12, so max returns the array size, + // which is needed by find_match + } + /* bad_cast will be thrown if the desired facet is not accessible + * so we can generate the facet. This has the drawback of using english + * names as a default. */ + catch(std::bad_cast&){ + charT a = '\0'; + std::auto_ptr< const facet_def > f(create_facet_def(a)); + num = date_time::find_match(f->get_short_month_names(), + f->get_long_month_names(), + (greg_month::max)(), s); // greg_month spans 1..12, so max returns the array size, + // which is needed by find_match + } + + ++num; // months numbered 1-12 + m = greg_month(num); + + return is; + } + + //! operator>> for gregorian::greg_weekday - throws exception if invalid weekday given + template + inline + std::basic_istream& operator>>(std::basic_istream& is,greg_weekday& wd) + { + typedef boost::date_time::all_date_names_put facet_def; + + std::basic_string s; + is >> s; + + if(!std::has_facet(is.getloc())) { + std::locale loc = is.getloc(); + charT a = '\0'; + is.imbue(generate_locale(loc, a)); + } + + short num = 0; + try{ + const facet_def& f = std::use_facet(is.getloc()); + num = date_time::find_match(f.get_short_weekday_names(), + f.get_long_weekday_names(), + (greg_weekday::max)() + 1, s); // greg_weekday spans 0..6, so increment is needed + // to form the array size which is needed by find_match + } + /* bad_cast will be thrown if the desired facet is not accessible + * so we can generate the facet. This has the drawback of using english + * names as a default. */ + catch(std::bad_cast&){ + charT a = '\0'; + std::auto_ptr< const facet_def > f(create_facet_def(a)); + num = date_time::find_match(f->get_short_weekday_names(), + f->get_long_weekday_names(), + (greg_weekday::max)() + 1, s); // greg_weekday spans 0..6, so increment is needed + // to form the array size which is needed by find_match + } + + wd = greg_weekday(num); // weekdays numbered 0-6 + return is; + } + +} } //namespace gregorian + +#endif + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_month.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_month.hpp new file mode 100644 index 0000000..d483f77 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_month.hpp @@ -0,0 +1,105 @@ +#ifndef GREG_MONTH_HPP___ +#define GREG_MONTH_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/constrained_value.hpp" +#include "boost/date_time/date_defs.hpp" +#include "boost/shared_ptr.hpp" +#include "boost/date_time/compiler_config.hpp" +#include +#include +#include +#include +#include + +namespace boost { +namespace gregorian { + + typedef date_time::months_of_year months_of_year; + + //bring enum values into the namespace + using date_time::Jan; + using date_time::Feb; + using date_time::Mar; + using date_time::Apr; + using date_time::May; + using date_time::Jun; + using date_time::Jul; + using date_time::Aug; + using date_time::Sep; + using date_time::Oct; + using date_time::Nov; + using date_time::Dec; + using date_time::NotAMonth; + using date_time::NumMonths; + + //! Exception thrown if a greg_month is constructed with a value out of range + struct bad_month : public std::out_of_range + { + bad_month() : std::out_of_range(std::string("Month number is out of range 1..12")) {} + }; + //! Build a policy class for the greg_month_rep + typedef CV::simple_exception_policy greg_month_policies; + //! A constrained range that implements the gregorian_month rules + typedef CV::constrained_value greg_month_rep; + + + //! Wrapper class to represent months in gregorian based calendar + class BOOST_DATE_TIME_DECL greg_month : public greg_month_rep { + public: + typedef date_time::months_of_year month_enum; + typedef std::map month_map_type; + typedef boost::shared_ptr month_map_ptr_type; + //! Construct a month from the months_of_year enumeration + greg_month(month_enum theMonth) : + greg_month_rep(static_cast(theMonth)) {} + //! Construct from a short value + greg_month(unsigned short theMonth) : greg_month_rep(theMonth) {} + //! Convert the value back to a short + operator unsigned short() const {return value_;} + //! Returns month as number from 1 to 12 + unsigned short as_number() const {return value_;} + month_enum as_enum() const {return static_cast(value_);} + const char* as_short_string() const; + const char* as_long_string() const; +#ifndef BOOST_NO_STD_WSTRING + const wchar_t* as_short_wstring() const; + const wchar_t* as_long_wstring() const; +#endif // BOOST_NO_STD_WSTRING + //! Shared pointer to a map of Month strings (Names & Abbrev) & numbers + static month_map_ptr_type get_month_map_ptr(); + + /* parameterized as_*_string functions are intended to be called + * from a template function: "... as_short_string(charT c='\0');" */ + const char* as_short_string(char) const + { + return as_short_string(); + } + const char* as_long_string(char) const + { + return as_long_string(); + } +#ifndef BOOST_NO_STD_WSTRING + const wchar_t* as_short_string(wchar_t) const + { + return as_short_wstring(); + } + const wchar_t* as_long_string(wchar_t) const + { + return as_long_wstring(); + } +#endif // BOOST_NO_STD_WSTRING + }; + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_weekday.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_weekday.hpp new file mode 100644 index 0000000..60fec32 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_weekday.hpp @@ -0,0 +1,66 @@ +#ifndef GREG_WEEKDAY_HPP___ +#define GREG_WEEKDAY_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/constrained_value.hpp" +#include "boost/date_time/date_defs.hpp" +#include "boost/date_time/compiler_config.hpp" +#include +#include + +namespace boost { +namespace gregorian { + + //bring enum values into the namespace + using date_time::Sunday; + using date_time::Monday; + using date_time::Tuesday; + using date_time::Wednesday; + using date_time::Thursday; + using date_time::Friday; + using date_time::Saturday; + + + //! Exception that flags that a weekday number is incorrect + struct bad_weekday : public std::out_of_range + { + bad_weekday() : std::out_of_range(std::string("Weekday is out of range 0..6")) {} + }; + typedef CV::simple_exception_policy greg_weekday_policies; + typedef CV::constrained_value greg_weekday_rep; + + + //! Represent a day within a week (range 0==Sun to 6==Sat) + class BOOST_DATE_TIME_DECL greg_weekday : public greg_weekday_rep { + public: + typedef boost::date_time::weekdays weekday_enum; + greg_weekday(unsigned short day_of_week_num) : + greg_weekday_rep(day_of_week_num) + {} + + unsigned short as_number() const {return value_;} + const char* as_short_string() const; + const char* as_long_string() const; +#ifndef BOOST_NO_STD_WSTRING + const wchar_t* as_short_wstring() const; + const wchar_t* as_long_wstring() const; +#endif // BOOST_NO_STD_WSTRING + weekday_enum as_enum() const {return static_cast(value_);} + + + }; + + + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_year.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_year.hpp new file mode 100644 index 0000000..3d98fd0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_year.hpp @@ -0,0 +1,53 @@ +#ifndef GREG_YEAR_HPP___ +#define GREG_YEAR_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/constrained_value.hpp" +#include +#include + +namespace boost { +namespace gregorian { + + //! Exception type for gregorian year + struct bad_year : public std::out_of_range + { + bad_year() : + std::out_of_range(std::string("Year is out of valid range: 1400..10000")) + {} + }; + //! Policy class that declares error handling gregorian year type + typedef CV::simple_exception_policy greg_year_policies; + + //! Generated representation for gregorian year + typedef CV::constrained_value greg_year_rep; + + //! Represent a day of the month (range 1900 - 10000) + /*! This small class allows for simple conversion an integer value into + a year for the gregorian calendar. This currently only allows a + range of 1900 to 10000. Both ends of the range are a bit arbitrary + at the moment, but they are the limits of current testing of the + library. As such they may be increased in the future. + */ + class greg_year : public greg_year_rep { + public: + greg_year(unsigned short year) : greg_year_rep(year) {} + operator unsigned short() const {return value_;} + private: + + }; + + + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_ymd.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_ymd.hpp new file mode 100644 index 0000000..503666c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/greg_ymd.hpp @@ -0,0 +1,33 @@ +#ifndef DATE_TIME_GREG_YMD_HPP__ +#define DATE_TIME_GREG_YMD_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/year_month_day.hpp" +#include "boost/date_time/special_defs.hpp" +#include "boost/date_time/gregorian/greg_day.hpp" +#include "boost/date_time/gregorian/greg_year.hpp" +#include "boost/date_time/gregorian/greg_month.hpp" + +namespace boost { +namespace gregorian { + + typedef date_time::year_month_day_base greg_year_month_day; + + + +} } //namespace gregorian + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/gregorian_types.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/gregorian_types.hpp new file mode 100644 index 0000000..d50e9cc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/gregorian_types.hpp @@ -0,0 +1,109 @@ +#ifndef _GREGORIAN_TYPES_HPP__ +#define _GREGORIAN_TYPES_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! @file gregorian_types.hpp + Single file header that defines most of the types for the gregorian + date-time system. +*/ + +#include "boost/date_time/date.hpp" +#include "boost/date_time/period.hpp" +#include "boost/date_time/gregorian/greg_calendar.hpp" +#include "boost/date_time/gregorian/greg_duration.hpp" +#if defined(BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES) +#include "boost/date_time/gregorian/greg_duration_types.hpp" +#endif +#include "boost/date_time/gregorian/greg_date.hpp" +#include "boost/date_time/date_generators.hpp" +#include "boost/date_time/date_clock_device.hpp" +#include "boost/date_time/date_iterator.hpp" +#include "boost/date_time/adjust_functors.hpp" + +namespace boost { + +//! Gregorian date system based on date_time components +/*! This date system defines a full complement of types including + * a date, date_duration, date_period, day_clock, and a + * day_iterator. + */ +namespace gregorian { + //! Date periods for the gregorian system + /*!\ingroup date_basics + */ + typedef date_time::period date_period; + + //! A unifying date_generator base type + /*! A unifying date_generator base type for: + * partial_date, nth_day_of_the_week_in_month, + * first_day_of_the_week_in_month, and last_day_of_the_week_in_month + */ + typedef date_time::year_based_generator year_based_generator; + + //! A date generation object type + typedef date_time::partial_date partial_date; + + typedef date_time::nth_kday_of_month nth_kday_of_month; + typedef nth_kday_of_month nth_day_of_the_week_in_month; + + typedef date_time::first_kday_of_month first_kday_of_month; + typedef first_kday_of_month first_day_of_the_week_in_month; + + typedef date_time::last_kday_of_month last_kday_of_month; + typedef last_kday_of_month last_day_of_the_week_in_month; + + typedef date_time::first_kday_after first_kday_after; + typedef first_kday_after first_day_of_the_week_after; + + typedef date_time::first_kday_before first_kday_before; + typedef first_kday_before first_day_of_the_week_before; + + //! A clock to get the current day from the local computer + /*!\ingroup date_basics + */ + typedef date_time::day_clock day_clock; + + //! Base date_iterator type for gregorian types. + /*!\ingroup date_basics + */ + typedef date_time::date_itr_base date_iterator; + + //! A day level iterator + /*!\ingroup date_basics + */ + typedef date_time::date_itr, + date> day_iterator; + //! A week level iterator + /*!\ingroup date_basics + */ + typedef date_time::date_itr, + date> week_iterator; + //! A month level iterator + /*!\ingroup date_basics + */ + typedef date_time::date_itr, + date> month_iterator; + //! A year level iterator + /*!\ingroup date_basics + */ + typedef date_time::date_itr, + date> year_iterator; + + // bring in these date_generator functions from date_time namespace + using date_time::days_until_weekday; + using date_time::days_before_weekday; + using date_time::next_weekday; + using date_time::previous_weekday; + +} } //namespace gregorian + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/parsers.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/parsers.hpp new file mode 100644 index 0000000..afc6537 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian/parsers.hpp @@ -0,0 +1,91 @@ +#ifndef GREGORIAN_PARSERS_HPP___ +#define GREGORIAN_PARSERS_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/gregorian/gregorian_types.hpp" +#include "boost/date_time/date_parsing.hpp" +#include "boost/date_time/compiler_config.hpp" +#include "boost/date_time/parse_format_base.hpp" +#include +#include + +namespace boost { +namespace gregorian { + + //! Return special_value from string argument + /*! Return special_value from string argument. If argument is + * not one of the special value names (defined in src/gregorian/names.hpp), + * return 'not_special' */ + BOOST_DATE_TIME_DECL special_values special_value_from_string(const std::string& s); + + //! Deprecated: Use from_simple_string + inline date from_string(std::string s) { + return date_time::parse_date(s); + } + + //! From delimited date string where with order year-month-day eg: 2002-1-25 or 2003-Jan-25 (full month name is also accepted) + inline date from_simple_string(std::string s) { + return date_time::parse_date(s, date_time::ymd_order_iso); + } + + //! From delimited date string where with order year-month-day eg: 1-25-2003 or Jan-25-2003 (full month name is also accepted) + inline date from_us_string(std::string s) { + return date_time::parse_date(s, date_time::ymd_order_us); + } + + //! From delimited date string where with order day-month-year eg: 25-1-2002 or 25-Jan-2003 (full month name is also accepted) + inline date from_uk_string(std::string s) { + return date_time::parse_date(s, date_time::ymd_order_dmy); + } + + //! From iso type date string where with order year-month-day eg: 20020125 + inline date from_undelimited_string(std::string s) { + return date_time::parse_undelimited_date(s); + } + + //! From iso type date string where with order year-month-day eg: 20020125 + inline date date_from_iso_string(const std::string& s) { + return date_time::parse_undelimited_date(s); + } + +#if !(defined(BOOST_NO_STD_ITERATOR_TRAITS)) + //! Stream should hold a date in the form of: 2002-1-25. Month number, abbrev, or name are accepted + /* Arguments passed in by-value for convertability of char[] + * to iterator_type. Calls to from_stream_type are by-reference + * since conversion is already done */ + template + inline date from_stream(iterator_type beg, iterator_type end) { + if(beg == end) + { + return date(not_a_date_time); + } + typedef typename std::iterator_traits::value_type value_type; + return date_time::from_stream_type(beg, end, value_type()); + } +#endif //BOOST_NO_STD_ITERATOR_TRAITS + +#if (defined(_MSC_VER) && (_MSC_VER < 1300)) + // This function cannot be compiled with MSVC 6.0 due to internal compiler shorcomings +#else + //! Function to parse a date_period from a string (eg: [2003-Oct-31/2003-Dec-25]) + inline date_period date_period_from_string(const std::string& s){ + return date_time::from_simple_string_type(s); + } +# if !defined(BOOST_NO_STD_WSTRING) + //! Function to parse a date_period from a wstring (eg: [2003-Oct-31/2003-Dec-25]) + inline date_period date_period_from_wstring(const std::wstring& s){ + return date_time::from_simple_string_type(s); + } +# endif // BOOST_NO_STD_WSTRING +#endif + +} } //namespace gregorian + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.hpp new file mode 100644 index 0000000..dfe3771 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.hpp @@ -0,0 +1,70 @@ +#ifndef DATE_TIME_GREGORIAN_CALENDAR_HPP__ +#define DATE_TIME_GREGORIAN_CALENDAR_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + + +namespace boost { +namespace date_time { + + + //! An implementation of the Gregorian calendar + /*! This is a parameterized implementation of a proleptic Gregorian Calendar that + can be used in the creation of date systems or just to perform calculations. + All the methods of this class are static functions, so the intent is to + never create instances of this class. + @param ymd_type_ Struct type representing the year, month, day. The ymd_type must + define a of types for the year, month, and day. These types need to be + arithmetic types. + @param date_int_type_ Underlying type for the date count. Must be an arithmetic type. + */ + template + class gregorian_calendar_base { + public: + //! define a type a date split into components + typedef ymd_type_ ymd_type; + //! define a type for representing months + typedef typename ymd_type::month_type month_type; + //! define a type for representing days + typedef typename ymd_type::day_type day_type; + //! Type to hold a stand alone year value (eg: 2002) + typedef typename ymd_type::year_type year_type; + //! Define the integer type to use for internal calculations + typedef date_int_type_ date_int_type; + + + static unsigned short day_of_week(const ymd_type& ymd); + static int week_number(const ymd_type&ymd); + //static unsigned short day_of_year(date_int_type); + static date_int_type day_number(const ymd_type& ymd); + static date_int_type julian_day_number(const ymd_type& ymd); + static date_int_type modjulian_day_number(const ymd_type& ymd); + static ymd_type from_day_number(date_int_type); + static ymd_type from_julian_day_number(date_int_type); + static ymd_type from_modjulian_day_number(date_int_type); + static bool is_leap_year(year_type); + static unsigned short end_of_month_day(year_type y, month_type m); + static ymd_type epoch(); + static unsigned short days_in_week(); + + }; + + + +} } //namespace + +#ifndef NO_BOOST_DATE_TIME_INLINE +#include "boost/date_time/gregorian_calendar.ipp" +#endif + + + +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.ipp b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.ipp new file mode 100644 index 0000000..7b43ea8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/gregorian_calendar.ipp @@ -0,0 +1,219 @@ +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#ifndef NO_BOOST_DATE_TIME_INLINE + #undef BOOST_DATE_TIME_INLINE + #define BOOST_DATE_TIME_INLINE inline +#endif + +namespace boost { +namespace date_time { + //! Return the day of the week (0==Sunday, 1==Monday, etc) + /*! Converts a year-month-day into a day of the week number + */ + template + BOOST_DATE_TIME_INLINE + unsigned short + gregorian_calendar_base::day_of_week(const ymd_type& ymd) { + unsigned short a = static_cast((14-ymd.month)/12); + unsigned short y = static_cast(ymd.year - a); + unsigned short m = static_cast(ymd.month + 12*a - 2); + unsigned short d = static_cast((ymd.day + y + (y/4) - (y/100) + (y/400) + (31*m)/12) % 7); + //std::cout << year << "-" << month << "-" << day << " is day: " << d << "\n"; + return d; + } + + //!Return the iso week number for the date + /*!Implements the rules associated with the iso 8601 week number. + Basically the rule is that Week 1 of the year is the week that contains + January 4th or the week that contains the first Thursday in January. + Reference for this algorithm is the Calendar FAQ by Claus Tondering, April 2000. + */ + template + BOOST_DATE_TIME_INLINE + int + gregorian_calendar_base::week_number(const ymd_type& ymd) { + unsigned long julianbegin = julian_day_number(ymd_type(ymd.year,1,1)); + unsigned long juliantoday = julian_day_number(ymd); + unsigned long day = (julianbegin + 3) % 7; + unsigned long week = (juliantoday + day - julianbegin + 4)/7; + + if ((week >= 1) && (week <= 52)) { + return static_cast(week); + } + + if (week == 53) { + if((day==6) ||(day == 5 && is_leap_year(ymd.year))) { + return static_cast(week); //under these circumstances week == 53. + } else { + return 1; //monday - wednesday is in week 1 of next year + } + } + //if the week is not in current year recalculate using the previous year as the beginning year + else if (week == 0) { + julianbegin = julian_day_number(ymd_type(static_cast(ymd.year-1),1,1)); + juliantoday = julian_day_number(ymd); + day = (julianbegin + 3) % 7; + week = (juliantoday + day - julianbegin + 4)/7; + return static_cast(week); + } + + return static_cast(week); //not reachable -- well except if day == 5 and is_leap_year != true + + } + + //! Convert a ymd_type into a day number + /*! The day number is an absolute number of days since the start of count + */ + template + BOOST_DATE_TIME_INLINE + date_int_type_ + gregorian_calendar_base::day_number(const ymd_type& ymd) + { + unsigned short a = static_cast((14-ymd.month)/12); + unsigned short y = static_cast(ymd.year + 4800 - a); + unsigned short m = static_cast(ymd.month + 12*a - 3); + unsigned long d = ymd.day + ((153*m + 2)/5) + 365*y + (y/4) - (y/100) + (y/400) - 32045; + return static_cast(d); + } + + //! Convert a year-month-day into the julian day number + /*! Since this implementation uses julian day internally, this is the same as the day_number. + */ + template + BOOST_DATE_TIME_INLINE + date_int_type_ + gregorian_calendar_base::julian_day_number(const ymd_type& ymd) + { + return day_number(ymd); + } + + //! Convert year-month-day into a modified julian day number + /*! The day number is an absolute number of days. + * MJD 0 thus started on 17 Nov 1858(Gregorian) at 00:00:00 UTC + */ + template + BOOST_DATE_TIME_INLINE + date_int_type_ + gregorian_calendar_base::modjulian_day_number(const ymd_type& ymd) + { + return julian_day_number(ymd)-2400001; //prerounded + } + + //! Change a day number into a year-month-day + template + BOOST_DATE_TIME_INLINE + ymd_type_ + gregorian_calendar_base::from_day_number(date_int_type dayNumber) + { + date_int_type a = dayNumber + 32044; + date_int_type b = (4*a + 3)/146097; + date_int_type c = a-((146097*b)/4); + date_int_type d = (4*c + 3)/1461; + date_int_type e = c - (1461*d)/4; + date_int_type m = (5*e + 2)/153; + unsigned short day = static_cast(e - ((153*m + 2)/5) + 1); + unsigned short month = static_cast(m + 3 - 12 * (m/10)); + year_type year = static_cast(100*b + d - 4800 + (m/10)); + //std::cout << year << "-" << month << "-" << day << "\n"; + + return ymd_type(static_cast(year),month,day); + } + + //! Change a day number into a year-month-day + template + BOOST_DATE_TIME_INLINE + ymd_type_ + gregorian_calendar_base::from_julian_day_number(date_int_type dayNumber) + { + date_int_type a = dayNumber + 32044; + date_int_type b = (4*a+3)/146097; + date_int_type c = a - ((146097*b)/4); + date_int_type d = (4*c + 3)/1461; + date_int_type e = c - ((1461*d)/4); + date_int_type m = (5*e + 2)/153; + unsigned short day = static_cast(e - ((153*m + 2)/5) + 1); + unsigned short month = static_cast(m + 3 - 12 * (m/10)); + year_type year = static_cast(100*b + d - 4800 + (m/10)); + //std::cout << year << "-" << month << "-" << day << "\n"; + + return ymd_type(year,month,day); + } + + //! Change a modified julian day number into a year-month-day + template + BOOST_DATE_TIME_INLINE + ymd_type_ + gregorian_calendar_base::from_modjulian_day_number(date_int_type dayNumber) { + date_int_type jd = dayNumber + 2400001; //is 2400000.5 prerounded + return from_julian_day_number(jd); + } + + //! Determine if the provided year is a leap year + /*! + *@return true if year is a leap year, false otherwise + */ + template + BOOST_DATE_TIME_INLINE + bool + gregorian_calendar_base::is_leap_year(year_type year) + { + //divisible by 4, not if divisible by 100, but true if divisible by 400 + return (!(year % 4)) && ((year % 100) || (!(year % 400))); + } + + //! Calculate the last day of the month + /*! Find the day which is the end of the month given year and month + * No error checking is performed. + */ + template + BOOST_DATE_TIME_INLINE + unsigned short + gregorian_calendar_base::end_of_month_day(year_type year, + month_type month) + { + switch (month) { + case 2: + if (is_leap_year(year)) { + return 29; + } else { + return 28; + }; + case 4: + case 6: + case 9: + case 11: + return 30; + default: + return 31; + }; + + } + + //! Provide the ymd_type specification for the calandar start + template + BOOST_DATE_TIME_INLINE + ymd_type_ + gregorian_calendar_base::epoch() + { + return ymd_type(1400,1,1); + } + + //! Defines length of a week for week calculations + template + BOOST_DATE_TIME_INLINE + unsigned short + gregorian_calendar_base::days_in_week() + { + return 7; + } + + +} } //namespace gregorian + + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/int_adapter.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/int_adapter.hpp new file mode 100644 index 0000000..3921086 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/int_adapter.hpp @@ -0,0 +1,509 @@ +#ifndef _DATE_TIME_INT_ADAPTER_HPP__ +#define _DATE_TIME_INT_ADAPTER_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include "boost/config.hpp" +#include "boost/limits.hpp" //work around compilers without limits +#include "boost/date_time/special_defs.hpp" +#include "boost/date_time/locale_config.hpp" +#ifndef BOOST_DATE_TIME_NO_LOCALE +# include +#endif + +namespace boost { +namespace date_time { + + +//! Adapter to create integer types with +-infinity, and not a value +/*! This class is used internally in counted date/time representations. + * It adds the floating point like features of infinities and + * not a number. It also provides mathmatical operations with + * consideration to special values following these rules: + *@code + * +infinity - infinity == Not A Number (NAN) + * infinity * non-zero == infinity + * infinity * zero == NAN + * +infinity * -integer == -infinity + * infinity / infinity == NAN + * infinity * infinity == infinity + *@endcode + */ +template +class int_adapter { +public: + typedef int_type_ int_type; + int_adapter(int_type v) : + value_(v) + {} + static bool has_infinity() + { + return true; + } + static const int_adapter pos_infinity() + { + return (::std::numeric_limits::max)(); + } + static const int_adapter neg_infinity() + { + return (::std::numeric_limits::min)(); + } + static const int_adapter not_a_number() + { + return (::std::numeric_limits::max)()-1; + } + static int_adapter max BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return (::std::numeric_limits::max)()-2; + } + static int_adapter min BOOST_PREVENT_MACRO_SUBSTITUTION () + { + return (::std::numeric_limits::min)()+1; + } + static int_adapter from_special(special_values sv) + { + switch (sv) { + case not_a_date_time: return not_a_number(); + case neg_infin: return neg_infinity(); + case pos_infin: return pos_infinity(); + case max_date_time: return (max)(); + case min_date_time: return (min)(); + default: return not_a_number(); + } + } + static bool is_inf(int_type v) + { + return (v == neg_infinity().as_number() || + v == pos_infinity().as_number()); + } + static bool is_neg_inf(int_type v) + { + return (v == neg_infinity().as_number()); + } + static bool is_pos_inf(int_type v) + { + return (v == pos_infinity().as_number()); + } + static bool is_not_a_number(int_type v) + { + return (v == not_a_number().as_number()); + } + //! Returns either special value type or is_not_special + static special_values to_special(int_type v) + { + if (is_not_a_number(v)) return not_a_date_time; + if (is_neg_inf(v)) return neg_infin; + if (is_pos_inf(v)) return pos_infin; + return not_special; + } + + //-3 leaves room for representations of infinity and not a date + static int_type maxcount() + { + return (::std::numeric_limits::max)()-3; + } + bool is_infinity() const + { + return (value_ == neg_infinity().as_number() || + value_ == pos_infinity().as_number()); + } + bool is_pos_infinity()const + { + return(value_ == pos_infinity().as_number()); + } + bool is_neg_infinity()const + { + return(value_ == neg_infinity().as_number()); + } + bool is_nan() const + { + return (value_ == not_a_number().as_number()); + } + bool is_special() const + { + return(is_infinity() || is_nan()); + } + bool operator==(const int_adapter& rhs) const + { + return (compare(rhs) == 0); + } + bool operator==(const int& rhs) const + { + // quiets compiler warnings + bool is_signed = std::numeric_limits::is_signed; + if(!is_signed) + { + if(is_neg_inf(value_) && rhs == 0) + { + return false; + } + } + return (compare(rhs) == 0); + } + bool operator!=(const int_adapter& rhs) const + { + return (compare(rhs) != 0); + } + bool operator!=(const int& rhs) const + { + // quiets compiler warnings + bool is_signed = std::numeric_limits::is_signed; + if(!is_signed) + { + if(is_neg_inf(value_) && rhs == 0) + { + return true; + } + } + return (compare(rhs) != 0); + } + bool operator<(const int_adapter& rhs) const + { + return (compare(rhs) == -1); + } + bool operator<(const int& rhs) const + { + // quiets compiler warnings + bool is_signed = std::numeric_limits::is_signed; + if(!is_signed) + { + if(is_neg_inf(value_) && rhs == 0) + { + return true; + } + } + return (compare(rhs) == -1); + } + bool operator>(const int_adapter& rhs) const + { + return (compare(rhs) == 1); + } + int_type as_number() const + { + return value_; + } + //! Returns either special value type or is_not_special + special_values as_special() const + { + return int_adapter::to_special(value_); + } + //creates nasty ambiguities +// operator int_type() const +// { +// return value_; +// } + + /*! Operator allows for adding dissimilar int_adapter types. + * The return type will match that of the the calling object's type */ + template + inline + int_adapter operator+(const int_adapter& rhs) const + { + if(is_special() || rhs.is_special()) + { + if (is_nan() || rhs.is_nan()) + { + return int_adapter::not_a_number(); + } + if((is_pos_inf(value_) && rhs.is_neg_inf(rhs.as_number())) || + (is_neg_inf(value_) && rhs.is_pos_inf(rhs.as_number())) ) + { + return int_adapter::not_a_number(); + } + if (is_infinity()) + { + return *this; + } + if (rhs.is_pos_inf(rhs.as_number())) + { + return int_adapter::pos_infinity(); + } + if (rhs.is_neg_inf(rhs.as_number())) + { + return int_adapter::neg_infinity(); + } + } + return int_adapter(value_ + static_cast(rhs.as_number())); + } + + int_adapter operator+(const int_type rhs) const + { + if(is_special()) + { + if (is_nan()) + { + return int_adapter(not_a_number()); + } + if (is_infinity()) + { + return *this; + } + } + return int_adapter(value_ + rhs); + } + + /*! Operator allows for subtracting dissimilar int_adapter types. + * The return type will match that of the the calling object's type */ + template + inline + int_adapter operator-(const int_adapter& rhs)const + { + if(is_special() || rhs.is_special()) + { + if (is_nan() || rhs.is_nan()) + { + return int_adapter::not_a_number(); + } + if((is_pos_inf(value_) && rhs.is_pos_inf(rhs.as_number())) || + (is_neg_inf(value_) && rhs.is_neg_inf(rhs.as_number())) ) + { + return int_adapter::not_a_number(); + } + if (is_infinity()) + { + return *this; + } + if (rhs.is_pos_inf(rhs.as_number())) + { + return int_adapter::neg_infinity(); + } + if (rhs.is_neg_inf(rhs.as_number())) + { + return int_adapter::pos_infinity(); + } + } + return int_adapter(value_ - static_cast(rhs.as_number())); + } + int_adapter operator-(const int_type rhs) const + { + if(is_special()) + { + if (is_nan()) + { + return int_adapter(not_a_number()); + } + if (is_infinity()) + { + return *this; + } + } + return int_adapter(value_ - rhs); + } + + // should templatize this to be consistant with op +- + int_adapter operator*(const int_adapter& rhs)const + { + if(this->is_special() || rhs.is_special()) + { + return mult_div_specials(rhs); + } + return int_adapter(value_ * rhs.value_); + } + /*! Provided for cases when automatic conversion from + * 'int' to 'int_adapter' causes incorrect results. */ + int_adapter operator*(const int rhs) const + { + if(is_special()) + { + return mult_div_specials(rhs); + } + return int_adapter(value_ * rhs); + } + + // should templatize this to be consistant with op +- + int_adapter operator/(const int_adapter& rhs)const + { + if(this->is_special() || rhs.is_special()) + { + if(is_infinity() && rhs.is_infinity()) + { + return int_adapter(not_a_number()); + } + if(rhs != 0) + { + return mult_div_specials(rhs); + } + else { // let divide by zero blow itself up + return int_adapter(value_ / rhs.value_); + } + } + return int_adapter(value_ / rhs.value_); + } + /*! Provided for cases when automatic conversion from + * 'int' to 'int_adapter' causes incorrect results. */ + int_adapter operator/(const int rhs) const + { + if(is_special() && rhs != 0) + { + return mult_div_specials(rhs); + } + return int_adapter(value_ / rhs); + } + + // should templatize this to be consistant with op +- + int_adapter operator%(const int_adapter& rhs)const + { + if(this->is_special() || rhs.is_special()) + { + if(is_infinity() && rhs.is_infinity()) + { + return int_adapter(not_a_number()); + } + if(rhs != 0) + { + return mult_div_specials(rhs); + } + else { // let divide by zero blow itself up + return int_adapter(value_ % rhs.value_); + } + } + return int_adapter(value_ % rhs.value_); + } + /*! Provided for cases when automatic conversion from + * 'int' to 'int_adapter' causes incorrect results. */ + int_adapter operator%(const int rhs) const + { + if(is_special() && rhs != 0) + { + return mult_div_specials(rhs); + } + return int_adapter(value_ % rhs); + } +private: + int_type value_; + + //! returns -1, 0, 1, or 2 if 'this' is <, ==, >, or 'nan comparison' rhs + int compare(const int_adapter& rhs)const + { + if(this->is_special() || rhs.is_special()) + { + if(this->is_nan() || rhs.is_nan()) { + if(this->is_nan() && rhs.is_nan()) { + return 0; // equal + } + else { + return 2; // nan + } + } + if((is_neg_inf(value_) && !is_neg_inf(rhs.value_)) || + (is_pos_inf(rhs.value_) && !is_pos_inf(value_)) ) + { + return -1; // less than + } + if((is_pos_inf(value_) && !is_pos_inf(rhs.value_)) || + (is_neg_inf(rhs.value_) && !is_neg_inf(value_)) ) { + return 1; // greater than + } + } + if(value_ < rhs.value_) return -1; + if(value_ > rhs.value_) return 1; + // implied-> if(value_ == rhs.value_) + return 0; + } + /* When multiplying and dividing with at least 1 special value + * very simmilar rules apply. In those cases where the rules + * are different, they are handled in the respective operator + * function. */ + //! Assumes at least 'this' or 'rhs' is a special value + int_adapter mult_div_specials(const int_adapter& rhs)const + { + int min_value; + // quiets compiler warnings + bool is_signed = std::numeric_limits::is_signed; + if(is_signed) { + min_value = 0; + } + else { + min_value = 1;// there is no zero with unsigned + } + if(this->is_nan() || rhs.is_nan()) { + return int_adapter(not_a_number()); + } + if((*this > 0 && rhs > 0) || (*this < min_value && rhs < min_value)) { + return int_adapter(pos_infinity()); + } + if((*this > 0 && rhs < min_value) || (*this < min_value && rhs > 0)) { + return int_adapter(neg_infinity()); + } + //implied -> if(this->value_ == 0 || rhs.value_ == 0) + return int_adapter(not_a_number()); + } + /* Overloaded function necessary because of special + * situation where int_adapter is instantiated with + * 'unsigned' and func is called with negative int. + * It would produce incorrect results since 'unsigned' + * wraps around when initialized with a negative value */ + //! Assumes 'this' is a special value + int_adapter mult_div_specials(const int& rhs) const + { + int min_value; + // quiets compiler warnings + bool is_signed = std::numeric_limits::is_signed; + if(is_signed) { + min_value = 0; + } + else { + min_value = 1;// there is no zero with unsigned + } + if(this->is_nan()) { + return int_adapter(not_a_number()); + } + if((*this > 0 && rhs > 0) || (*this < min_value && rhs < 0)) { + return int_adapter(pos_infinity()); + } + if((*this > 0 && rhs < 0) || (*this < min_value && rhs > 0)) { + return int_adapter(neg_infinity()); + } + //implied -> if(this->value_ == 0 || rhs.value_ == 0) + return int_adapter(not_a_number()); + } + +}; + +#ifndef BOOST_DATE_TIME_NO_LOCALE + /*! Expected output is either a numeric representation + * or a special values representation.
+ * Ex. "12", "+infinity", "not-a-number", etc. */ + //template, typename int_type> + template + inline + std::basic_ostream& + operator<<(std::basic_ostream& os, const int_adapter& ia) + { + if(ia.is_special()) { + // switch copied from date_names_put.hpp + switch(ia.as_special()) + { + case not_a_date_time: + os << "not-a-number"; + break; + case pos_infin: + os << "+infinity"; + break; + case neg_infin: + os << "-infinity"; + break; + default: + os << ""; + } + } + else { + os << ia.as_number(); + } + return os; + } +#endif + + +} } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/iso_format.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/iso_format.hpp new file mode 100644 index 0000000..2e7942d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/iso_format.hpp @@ -0,0 +1,303 @@ +#ifndef ISO_FORMAT_HPP___ +#define ISO_FORMAT_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/parse_format_base.hpp" + +namespace boost { +namespace date_time { + +//! Class to provide common iso formatting spec +template +class iso_format_base { +public: + //! Describe month format -- its an integer in iso format + static month_format_spec month_format() + { + return month_as_integer; + } + + //! String used printed is date is invalid + static const charT* not_a_date() + { + return "not-a-date-time"; + } + //! String used to for positive infinity value + static const charT* pos_infinity() + { + return "+infinity"; + } + //! String used to for positive infinity value + static const charT* neg_infinity() + { + return "-infinity"; + } + + //! ISO char for a year -- used in durations + static charT year_sep_char() + { + return 'Y'; + } + //! ISO char for a month + static charT month_sep_char() + { + return '-'; + } + //! ISO char for a day + static charT day_sep_char() + { + return '-'; + } + //! char for minute + static charT hour_sep_char() + { + return ':'; + } + //! char for minute + static charT minute_sep_char() + { + return ':'; + } + //! char for second + static charT second_sep_char() + { + return ':'; + } + //! ISO char for a period + static charT period_start_char() + { + return 'P'; + } + //! Used in time in mixed strings to set start of time + static charT time_start_char() + { + return 'T'; + } + + //! Used in mixed strings to identify start of a week number + static charT week_start_char() + { + return 'W'; + } + + //! Separators for periods + static charT period_sep_char() + { + return '/'; + } + //! Separator for hh:mm:ss + static charT time_sep_char() + { + return ':'; + } + //! Preferred Separator for hh:mm:ss,decimal_fraction + static charT fractional_time_sep_char() + { + return ','; + } + + static bool is_component_sep(charT sep) + { + switch(sep) { + case 'H': + case 'M': + case 'S': + case 'W': + case 'T': + case 'Y': + case 'D':return true; + default: + return false; + } + } + + static bool is_fractional_time_sep(charT sep) + { + switch(sep) { + case ',': + case '.': return true; + default: return false; + } + } + static bool is_timezone_sep(charT sep) + { + switch(sep) { + case '+': + case '-': return true; + default: return false; + } + } + static charT element_sep_char() + { + return '-'; + } + +}; + +#ifndef BOOST_NO_STD_WSTRING + +//! Class to provide common iso formatting spec +template<> +class iso_format_base { +public: + //! Describe month format -- its an integer in iso format + static month_format_spec month_format() + { + return month_as_integer; + } + + //! String used printed is date is invalid + static const wchar_t* not_a_date() + { + return L"not-a-date-time"; + } + //! String used to for positive infinity value + static const wchar_t* pos_infinity() + { + return L"+infinity"; + } + //! String used to for positive infinity value + static const wchar_t* neg_infinity() + { + return L"-infinity"; + } + + //! ISO char for a year -- used in durations + static wchar_t year_sep_char() + { + return 'Y'; + } + //! ISO char for a month + static wchar_t month_sep_char() + { + return '-'; + } + //! ISO char for a day + static wchar_t day_sep_char() + { + return '-'; + } + //! char for minute + static wchar_t hour_sep_char() + { + return ':'; + } + //! char for minute + static wchar_t minute_sep_char() + { + return ':'; + } + //! char for second + static wchar_t second_sep_char() + { + return ':'; + } + //! ISO char for a period + static wchar_t period_start_char() + { + return 'P'; + } + //! Used in time in mixed strings to set start of time + static wchar_t time_start_char() + { + return 'T'; + } + + //! Used in mixed strings to identify start of a week number + static wchar_t week_start_char() + { + return 'W'; + } + + //! Separators for periods + static wchar_t period_sep_char() + { + return '/'; + } + //! Separator for hh:mm:ss + static wchar_t time_sep_char() + { + return ':'; + } + //! Preferred Separator for hh:mm:ss,decimal_fraction + static wchar_t fractional_time_sep_char() + { + return ','; + } + + static bool is_component_sep(wchar_t sep) + { + switch(sep) { + case 'H': + case 'M': + case 'S': + case 'W': + case 'T': + case 'Y': + case 'D':return true; + default: + return false; + } + } + + static bool is_fractional_time_sep(wchar_t sep) + { + switch(sep) { + case ',': + case '.': return true; + default: return false; + } + } + static bool is_timezone_sep(wchar_t sep) + { + switch(sep) { + case '+': + case '-': return true; + default: return false; + } + } + static wchar_t element_sep_char() + { + return '-'; + } + +}; + +#endif // BOOST_NO_STD_WSTRING + +//! Format description for iso normal YYYYMMDD +template +class iso_format : public iso_format_base { +public: + //! The ios standard format doesn't use char separators + static bool has_date_sep_chars() + { + return false; + } +}; + +//! Extended format uses seperators YYYY-MM-DD +template +class iso_extended_format : public iso_format_base { +public: + //! Extended format needs char separators + static bool has_date_sep_chars() + { + return true; + } + +}; + +} } //namespace date_time + + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/locale_config.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/locale_config.hpp new file mode 100644 index 0000000..42a2b73 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/locale_config.hpp @@ -0,0 +1,31 @@ +#ifndef DATE_TIME_LOCALE_CONFIG_HPP___ +#define DATE_TIME_LOCALE_CONFIG_HPP___ + +/* Copyright (c) 2002-2006 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +// This file configures whether the library will support locales and hence +// iostream based i/o. Even if a compiler has some support for locales, +// any failure to be compatible gets the compiler on the exclusion list. +// +// At the moment this is defined for MSVC 6 and any compiler that +// defines BOOST_NO_STD_LOCALE (gcc 2.95.x) + +#include "boost/config.hpp" //sets BOOST_NO_STD_LOCALE +#include "boost/detail/workaround.hpp" + +//This file basically becomes a noop if locales are not properly supported +#if (defined(BOOST_NO_STD_LOCALE) \ + || (BOOST_WORKAROUND( BOOST_MSVC, < 1300)) \ + || (BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT( 0x581 )) ) ) +#define BOOST_DATE_TIME_NO_LOCALE +#endif + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/microsec_time_clock.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/microsec_time_clock.hpp new file mode 100644 index 0000000..1dd08ff --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/microsec_time_clock.hpp @@ -0,0 +1,127 @@ +#ifndef DATE_TIME_HIGHRES_TIME_CLOCK_HPP___ +#define DATE_TIME_HIGHRES_TIME_CLOCK_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +/*! @file microsec_time_clock.hpp + This file contains a high resolution time clock implementation. +*/ + +#include +#include +#include +#include +#include +#include +#include + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK + +namespace boost { +namespace date_time { + + //! A clock providing microsecond level resolution + /*! A high precision clock that measures the local time + * at a resolution up to microseconds and adjusts to the + * resolution of the time system. For example, for the + * a library configuration with nano second resolution, + * the last 3 places of the fractional seconds will always + * be 000 since there are 1000 nano-seconds in a micro second. + */ + template + class microsec_clock + { + private: + //! Type for the function used to convert time_t to tm + typedef std::tm* (*time_converter)(const std::time_t*, std::tm*); + + public: + typedef typename time_type::date_type date_type; + typedef typename time_type::time_duration_type time_duration_type; + typedef typename time_duration_type::rep_type resolution_traits_type; + + //! return a local time object for the given zone, based on computer clock + //JKG -- looks like we could rewrite this against universal_time + template + static time_type local_time(shared_ptr tz_ptr) + { + typedef typename time_type::utc_time_type utc_time_type; + typedef second_clock second_clock; + // we'll need to know the utc_offset this machine has + // in order to get a utc_time_type set to utc + utc_time_type utc_time = second_clock::universal_time(); + time_duration_type utc_offset = second_clock::local_time() - utc_time; + // use micro clock to get a local time with sub seconds + // and adjust it to get a true utc time reading with sub seconds + utc_time = microsec_clock::local_time() - utc_offset; + return time_type(utc_time, tz_ptr); + } + + //! Returns the local time based on computer clock settings + static time_type local_time() + { + return create_time(&c_time::localtime); + } + + //! Returns the UTC time based on computer settings + static time_type universal_time() + { + return create_time(&c_time::gmtime); + } + + private: + static time_type create_time(time_converter converter) + { +#ifdef BOOST_HAS_GETTIMEOFDAY + timeval tv; + gettimeofday(&tv, 0); //gettimeofday does not support TZ adjust on Linux. + std::time_t t = tv.tv_sec; + boost::uint32_t sub_sec = tv.tv_usec; +#elif defined(BOOST_HAS_FTIME) + winapi::file_time ft; + winapi::get_system_time_as_file_time(ft); + uint64_t micros = winapi::file_time_to_microseconds(ft); // it will not wrap, since ft is the current time + // and cannot be before 1970-Jan-01 + std::time_t t = static_cast(micros / 1000000UL); // seconds since epoch + // microseconds -- static casts suppress warnings + boost::uint32_t sub_sec = static_cast(micros % 1000000UL); +#else +#error Internal Boost.DateTime error: BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK is defined, however neither gettimeofday nor FILETIME support is detected. +#endif + + std::tm curr; + std::tm* curr_ptr = converter(&t, &curr); + date_type d(static_cast< typename date_type::year_type::value_type >(curr_ptr->tm_year + 1900), + static_cast< typename date_type::month_type::value_type >(curr_ptr->tm_mon + 1), + static_cast< typename date_type::day_type::value_type >(curr_ptr->tm_mday)); + + //The following line will adjust the fractional second tick in terms + //of the current time system. For example, if the time system + //doesn't support fractional seconds then res_adjust returns 0 + //and all the fractional seconds return 0. + int adjust = static_cast< int >(resolution_traits_type::res_adjust() / 1000000); + + time_duration_type td(static_cast< typename time_duration_type::hour_type >(curr_ptr->tm_hour), + static_cast< typename time_duration_type::min_type >(curr_ptr->tm_min), + static_cast< typename time_duration_type::sec_type >(curr_ptr->tm_sec), + sub_sec * adjust); + + return time_type(d,td); + } + }; + + +} } //namespace date_time + +#endif //BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/parse_format_base.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/parse_format_base.hpp new file mode 100644 index 0000000..d4b2f59 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/parse_format_base.hpp @@ -0,0 +1,29 @@ +#ifndef DATE_TIME_PARSE_FORMAT_BASE__ +#define DATE_TIME_PARSE_FORMAT_BASE__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +namespace boost { +namespace date_time { + + //! Enum for distinguishing parsing and formatting options + enum month_format_spec {month_as_integer, month_as_short_string, + month_as_long_string}; + + //! Enum for distinguishing the order of Month, Day, & Year. + /*! Enum for distinguishing the order in which Month, Day, & Year + * will appear in a date string */ + enum ymd_order_spec {ymd_order_iso, //order is year-month-day + ymd_order_dmy, //day-month-year + ymd_order_us}; //order is month-day-year + + +} }//namespace date_time + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/period.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/period.hpp new file mode 100644 index 0000000..1a88209 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/period.hpp @@ -0,0 +1,377 @@ +#ifndef DATE_TIME_PERIOD_HPP___ +#define DATE_TIME_PERIOD_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! \file period.hpp + This file contain the implementation of the period abstraction. This is + basically the same idea as a range. Although this class is intended for + use in the time library, it is pretty close to general enough for other + numeric uses. + +*/ + +#include "boost/operators.hpp" + + +namespace boost { +namespace date_time { + //!Provides generalized period type useful in date-time systems + /*!This template uses a class to represent a time point within the period + and another class to represent a duration. As a result, this class is + not appropriate for use when the number and duration representation + are the same (eg: in the regular number domain). + + A period can be specified by providing either the begining point and + a duration or the begining point and the end point( end is NOT part + of the period but 1 unit past it. A period will be "invalid" if either + end_point <= begin_point or the given duration is <= 0. Any valid period + will return false for is_null(). + + Zero length periods are also considered invalid. Zero length periods are + periods where the begining and end points are the same, or, the given + duration is zero. For a zero length period, the last point will be one + unit less than the begining point. + + In the case that the begin and last are the same, the period has a + length of one unit. + + The best way to handle periods is usually to provide a begining point and + a duration. So, day1 + 7 days is a week period which includes all of the + first day and 6 more days (eg: Sun to Sat). + + */ + template + class period : private + boost::less_than_comparable + , boost::equality_comparable< period + > > + { + public: + typedef point_rep point_type; + typedef duration_rep duration_type; + + period(point_rep first_point, point_rep end_point); + period(point_rep first_point, duration_rep len); + point_rep begin() const; + point_rep end() const; + point_rep last() const; + duration_rep length() const; + bool is_null() const; + bool operator==(const period& rhs) const; + bool operator<(const period& rhs) const; + void shift(const duration_rep& d); + void expand(const duration_rep& d); + bool contains(const point_rep& point) const; + bool contains(const period& other) const; + bool intersects(const period& other) const; + bool is_adjacent(const period& other) const; + bool is_before(const point_rep& point) const; + bool is_after(const point_rep& point) const; + period intersection(const period& other) const; + period merge(const period& other) const; + period span(const period& other) const; + private: + point_rep begin_; + point_rep last_; + }; + + //! create a period from begin to last eg: [begin,end) + /*! If end <= begin then the period will be invalid + */ + template + inline + period::period(point_rep first_point, + point_rep end_point) : + begin_(first_point), + last_(end_point - duration_rep::unit()) + {} + + //! create a period as [begin, begin+len) + /*! If len is <= 0 then the period will be invalid + */ + template + inline + period::period(point_rep first_point, duration_rep len) : + begin_(first_point), + last_(first_point + len-duration_rep::unit()) + { } + + + //! Return the first element in the period + template + inline + point_rep period::begin() const + { + return begin_; + } + + //! Return one past the last element + template + inline + point_rep period::end() const + { + return last_ + duration_rep::unit(); + } + + //! Return the last item in the period + template + inline + point_rep period::last() const + { + return last_; + } + + //! True if period is ill formed (length is zero or less) + template + inline + bool period::is_null() const + { + return end() <= begin_; + } + + //! Return the length of the period + template + inline + duration_rep period::length() const + { + if(last_ < begin_){ // invalid period + return last_+duration_rep::unit() - begin_; + } + else{ + return end() - begin_; // normal case + } + } + + //! Equality operator + template + inline + bool period::operator==(const period& rhs) const + { + return ((begin_ == rhs.begin_) && + (last_ == rhs.last_)); + } + + //! Strict as defined by rhs.last <= lhs.last + template + inline + bool period::operator<(const period& rhs) const + { + return (last_ < rhs.begin_); + } + + + //! Shift the start and end by the specified amount + template + inline + void period::shift(const duration_rep& d) + { + begin_ = begin_ + d; + last_ = last_ + d; + } + + /** Expands the size of the period by the duration on both ends. + * + *So before expand + *@code + * + * [-------] + * ^ ^ ^ ^ ^ ^ ^ + * 1 2 3 4 5 6 7 + * + *@endcode + * After expand(2) + *@code + * + * [----------------------] + * ^ ^ ^ ^ ^ ^ ^ + * 1 2 3 4 5 6 7 + * + *@endcode + */ + template + inline + void period::expand(const duration_rep& d) + { + begin_ = begin_ - d; + last_ = last_ + d; + } + + //! True if the point is inside the period, zero length periods contain no points + template + inline + bool period::contains(const point_rep& point) const + { + return ((point >= begin_) && + (point <= last_)); + } + + + //! True if this period fully contains (or equals) the other period + template + inline + bool period::contains(const period& other) const + { + return ((begin_ <= other.begin_) && (last_ >= other.last_)); + } + + + //! True if periods are next to each other without a gap. + /* In the example below, p1 and p2 are adjacent, but p3 is not adjacent + * with either of p1 or p2. + *@code + * [-p1-) + * [-p2-) + * [-p3-) + *@endcode + */ + template + inline + bool + period::is_adjacent(const period& other) const + { + return (other.begin() == end() || + begin_ == other.end()); + } + + + //! True if all of the period is prior or t < start + /* In the example below only point 1 would evaluate to true. + *@code + * [---------]) + * ^ ^ ^ ^ ^ + * 1 2 3 4 5 + * + *@endcode + */ + template + inline + bool + period::is_after(const point_rep& t) const + { + if (is_null()) + { + return false; //null period isn't after + } + + return t < begin_; + } + + //! True if all of the period is prior to the passed point or end <= t + /* In the example below points 4 and 5 return true. + *@code + * [---------]) + * ^ ^ ^ ^ ^ + * 1 2 3 4 5 + * + *@endcode + */ + template + inline + bool + period::is_before(const point_rep& t) const + { + if (is_null()) + { + return false; //null period isn't before anything + } + + return last_ < t; + } + + + //! True if the periods overlap in any way + /* In the example below p1 intersects with p2, p4, and p6. + *@code + * [---p1---) + * [---p2---) + * [---p3---) + * [---p4---) + * [-p5-) + * [-p6-) + *@endcode + */ + template + inline + bool period::intersects(const period& other) const + { + return ( contains(other.begin_) || + other.contains(begin_) || + ((other.begin_ < begin_) && (other.last_ >= begin_))); + } + + //! Returns the period of intersection or invalid range no intersection + template + inline + period + period::intersection(const period& other) const + { + if (begin_ > other.begin_) { + if (last_ <= other.last_) { //case2 + return *this; + } + //case 1 + return period(begin_, other.end()); + } + else { + if (last_ <= other.last_) { //case3 + return period(other.begin_, this->end()); + } + //case4 + return other; + } + //unreachable + } + + //! Returns the union of intersecting periods -- or null period + /*! + */ + template + inline + period + period::merge(const period& other) const + { + if (this->intersects(other)) { + if (begin_ < other.begin_) { + return period(begin_, last_ > other.last_ ? this->end() : other.end()); + } + + return period(other.begin_, last_ > other.last_ ? this->end() : other.end()); + + } + return period(begin_,begin_); // no intersect return null + } + + //! Combine two periods with earliest start and latest end. + /*! Combines two periods and any gap between them such that + * start = min(p1.start, p2.start) + * end = max(p1.end , p2.end) + *@code + * [---p1---) + * [---p2---) + * result: + * [-----------p3----------) + *@endcode + */ + template + inline + period + period::span(const period& other) const + { + point_rep start((begin_ < other.begin_) ? begin() : other.begin()); + point_rep newend((last_ < other.last_) ? other.end() : this->end()); + return period(start, newend); + } + + +} } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/conversion.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/conversion.hpp new file mode 100644 index 0000000..ed3d486 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/conversion.hpp @@ -0,0 +1,102 @@ +#ifndef POSIX_TIME_CONVERSION_HPP___ +#define POSIX_TIME_CONVERSION_HPP___ + +/* Copyright (c) 2002-2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include +#include +#include +#include // absolute_value +#include + +namespace boost { + +namespace posix_time { + + + //! Function that converts a time_t into a ptime. + inline + ptime from_time_t(std::time_t t) + { + ptime start(gregorian::date(1970,1,1)); + return start + seconds(static_cast(t)); + } + + //! Function that converts a ptime into a time_t + inline + std::time_t to_time_t(ptime pt) + { + time_duration dur = pt - ptime(gregorian::date(1970,1,1)); + return std::time_t(dur.total_seconds()); + } + + //! Convert a time to a tm structure truncating any fractional seconds + inline + std::tm to_tm(const boost::posix_time::ptime& t) { + std::tm timetm = boost::gregorian::to_tm(t.date()); + boost::posix_time::time_duration td = t.time_of_day(); + timetm.tm_hour = td.hours(); + timetm.tm_min = td.minutes(); + timetm.tm_sec = td.seconds(); + timetm.tm_isdst = -1; // -1 used when dst info is unknown + return timetm; + } + //! Convert a time_duration to a tm structure truncating any fractional seconds and zeroing fields for date components + inline + std::tm to_tm(const boost::posix_time::time_duration& td) { + std::tm timetm; + std::memset(&timetm, 0, sizeof(timetm)); + timetm.tm_hour = date_time::absolute_value(td.hours()); + timetm.tm_min = date_time::absolute_value(td.minutes()); + timetm.tm_sec = date_time::absolute_value(td.seconds()); + timetm.tm_isdst = -1; // -1 used when dst info is unknown + return timetm; + } + + //! Convert a tm struct to a ptime ignoring is_dst flag + inline + ptime ptime_from_tm(const std::tm& timetm) { + boost::gregorian::date d = boost::gregorian::date_from_tm(timetm); + return ptime(d, time_duration(timetm.tm_hour, timetm.tm_min, timetm.tm_sec)); + } + + +#if defined(BOOST_HAS_FTIME) + + //! Function to create a time object from an initialized FILETIME struct. + /*! Function to create a time object from an initialized FILETIME struct. + * A FILETIME struct holds 100-nanosecond units (0.0000001). When + * built with microsecond resolution the FILETIME's sub second value + * will be truncated. Nanosecond resolution has no truncation. + * + * \note FILETIME is part of the Win32 API, so it is not portable to non-windows + * platforms. + * + * \note The function is templated on the FILETIME type, so that + * it can be used with both native FILETIME and the ad-hoc + * boost::date_time::winapi::file_time type. + */ + template< typename TimeT, typename FileTimeT > + inline + TimeT from_ftime(const FileTimeT& ft) + { + return boost::date_time::time_from_ftime(ft); + } + +#endif // BOOST_HAS_FTIME + +} } //namespace boost::posix_time + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/date_duration_operators.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/date_duration_operators.hpp new file mode 100644 index 0000000..60821f0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/date_duration_operators.hpp @@ -0,0 +1,114 @@ +#ifndef DATE_DURATION_OPERATORS_HPP___ +#define DATE_DURATION_OPERATORS_HPP___ + +/* Copyright (c) 2004 CrystalClear Software, Inc. + * Subject to the Boost Software License, Version 1.0. + * (See accompanying file LICENSE_1_0.txt or + * http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include "boost/date_time/gregorian/greg_duration_types.hpp" +#include "boost/date_time/posix_time/ptime.hpp" + +namespace boost { +namespace posix_time { + + /*!@file date_duration_operators.hpp Operators for ptime and + * optional gregorian types. Operators use snap-to-end-of-month behavior. + * Further details on this behavior can be found in reference for + * date_time/date_duration_types.hpp and documentation for + * month and year iterators. + */ + + + /*! Adds a months object and a ptime. Result will be same + * day-of-month as ptime unless original day was the last day of month. + * see date_time::months_duration for more details */ + inline + ptime + operator+(const ptime& t, const boost::gregorian::months& m) + { + return t + m.get_offset(t.date()); + } + + /*! Adds a months object to a ptime. Result will be same + * day-of-month as ptime unless original day was the last day of month. + * see date_time::months_duration for more details */ + inline + ptime + operator+=(ptime& t, const boost::gregorian::months& m) + { + // get_neg_offset returns a negative duration, so we add + return t += m.get_offset(t.date()); + } + + /*! Subtracts a months object and a ptime. Result will be same + * day-of-month as ptime unless original day was the last day of month. + * see date_time::months_duration for more details */ + inline + ptime + operator-(const ptime& t, const boost::gregorian::months& m) + { + // get_neg_offset returns a negative duration, so we add + return t + m.get_neg_offset(t.date()); + } + + /*! Subtracts a months object from a ptime. Result will be same + * day-of-month as ptime unless original day was the last day of month. + * see date_time::months_duration for more details */ + inline + ptime + operator-=(ptime& t, const boost::gregorian::months& m) + { + return t += m.get_neg_offset(t.date()); + } + + // ptime & years + + /*! Adds a years object and a ptime. Result will be same + * month and day-of-month as ptime unless original day was the + * last day of month. see date_time::years_duration for more details */ + inline + ptime + operator+(const ptime& t, const boost::gregorian::years& y) + { + return t + y.get_offset(t.date()); + } + + /*! Adds a years object to a ptime. Result will be same + * month and day-of-month as ptime unless original day was the + * last day of month. see date_time::years_duration for more details */ + inline + ptime + operator+=(ptime& t, const boost::gregorian::years& y) + { + return t += y.get_offset(t.date()); + } + + /*! Subtracts a years object and a ptime. Result will be same + * month and day-of-month as ptime unless original day was the + * last day of month. see date_time::years_duration for more details */ + inline + ptime + operator-(const ptime& t, const boost::gregorian::years& y) + { + // get_neg_offset returns a negative duration, so we add + return t + y.get_neg_offset(t.date()); + } + + /*! Subtracts a years object from a ptime. Result will be same + * month and day-of-month as ptime unless original day was the + * last day of month. see date_time::years_duration for more details */ + inline + ptime + operator-=(ptime& t, const boost::gregorian::years& y) + { + // get_neg_offset returns a negative duration, so we add + return t += y.get_neg_offset(t.date()); + } + +}} // namespaces + +#endif // DATE_DURATION_OPERATORS_HPP___ diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_config.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_config.hpp new file mode 100644 index 0000000..60b3468 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_config.hpp @@ -0,0 +1,178 @@ +#ifndef POSIX_TIME_CONFIG_HPP___ +#define POSIX_TIME_CONFIG_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include //for MCW 7.2 std::abs(long long) +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace posix_time { + +//Remove the following line if you want 64 bit millisecond resolution time +//#define BOOST_GDTL_POSIX_TIME_STD_CONFIG + +#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG + // set up conditional test compilations +#define BOOST_DATE_TIME_HAS_MILLISECONDS +#define BOOST_DATE_TIME_HAS_MICROSECONDS +#define BOOST_DATE_TIME_HAS_NANOSECONDS + typedef date_time::time_resolution_traits time_res_traits; +#else + // set up conditional test compilations +#define BOOST_DATE_TIME_HAS_MILLISECONDS +#define BOOST_DATE_TIME_HAS_MICROSECONDS +#undef BOOST_DATE_TIME_HAS_NANOSECONDS + typedef date_time::time_resolution_traits< + boost::date_time::time_resolution_traits_adapted64_impl, boost::date_time::micro, + 1000000, 6 > time_res_traits; + + +// #undef BOOST_DATE_TIME_HAS_MILLISECONDS +// #undef BOOST_DATE_TIME_HAS_MICROSECONDS +// #undef BOOST_DATE_TIME_HAS_NANOSECONDS +// typedef date_time::time_resolution_traits time_res_traits; + +#endif + + + //! Base time duration type + /*! \ingroup time_basics + */ + class time_duration : + public date_time::time_duration + { + public: + typedef time_res_traits rep_type; + typedef time_res_traits::day_type day_type; + typedef time_res_traits::hour_type hour_type; + typedef time_res_traits::min_type min_type; + typedef time_res_traits::sec_type sec_type; + typedef time_res_traits::fractional_seconds_type fractional_seconds_type; + typedef time_res_traits::tick_type tick_type; + typedef time_res_traits::impl_type impl_type; + time_duration(hour_type hour, + min_type min, + sec_type sec, + fractional_seconds_type fs=0) : + date_time::time_duration(hour,min,sec,fs) + {} + time_duration() : + date_time::time_duration(0,0,0) + {} + //! Construct from special_values + time_duration(boost::date_time::special_values sv) : + date_time::time_duration(sv) + {} + //Give duration access to ticks constructor -- hide from users + friend class date_time::time_duration; + protected: + explicit time_duration(impl_type tick_count) : + date_time::time_duration(tick_count) + {} + }; + +#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG + + //! Simple implementation for the time rep + struct simple_time_rep + { + typedef gregorian::date date_type; + typedef time_duration time_duration_type; + simple_time_rep(date_type d, time_duration_type tod) : + day(d), + time_of_day(tod) + { + // make sure we have sane values for date & time + if(!day.is_special() && !time_of_day.is_special()){ + if(time_of_day >= time_duration_type(24,0,0)) { + while(time_of_day >= time_duration_type(24,0,0)) { + day += date_type::duration_type(1); + time_of_day -= time_duration_type(24,0,0); + } + } + else if(time_of_day.is_negative()) { + while(time_of_day.is_negative()) { + day -= date_type::duration_type(1); + time_of_day += time_duration_type(24,0,0); + } + } + } + } + date_type day; + time_duration_type time_of_day; + bool is_special()const + { + return(is_pos_infinity() || is_neg_infinity() || is_not_a_date_time()); + } + bool is_pos_infinity()const + { + return(day.is_pos_infinity() || time_of_day.is_pos_infinity()); + } + bool is_neg_infinity()const + { + return(day.is_neg_infinity() || time_of_day.is_neg_infinity()); + } + bool is_not_a_date_time()const + { + return(day.is_not_a_date() || time_of_day.is_not_a_date_time()); + } + }; + + class posix_time_system_config + { + public: + typedef simple_time_rep time_rep_type; + typedef gregorian::date date_type; + typedef gregorian::date_duration date_duration_type; + typedef time_duration time_duration_type; + typedef time_res_traits::tick_type int_type; + typedef time_res_traits resolution_traits; +#if (defined(BOOST_DATE_TIME_NO_MEMBER_INIT)) //help bad compilers +#else + BOOST_STATIC_CONSTANT(boost::int64_t, tick_per_second = 1000000000); +#endif + }; + +#else + + class millisec_posix_time_system_config + { + public: + typedef boost::int64_t time_rep_type; + //typedef time_res_traits::tick_type time_rep_type; + typedef gregorian::date date_type; + typedef gregorian::date_duration date_duration_type; + typedef time_duration time_duration_type; + typedef time_res_traits::tick_type int_type; + typedef time_res_traits::impl_type impl_type; + typedef time_res_traits resolution_traits; +#if (defined(BOOST_DATE_TIME_NO_MEMBER_INIT)) //help bad compilers +#else + BOOST_STATIC_CONSTANT(boost::int64_t, tick_per_second = 1000000); +#endif + }; + +#endif + +} }//namespace posix_time + + +#endif + + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_duration.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_duration.hpp new file mode 100644 index 0000000..34380de --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_duration.hpp @@ -0,0 +1,82 @@ +#ifndef POSIX_TIME_DURATION_HPP___ +#define POSIX_TIME_DURATION_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/posix_time/posix_time_config.hpp" + +namespace boost { +namespace posix_time { + + //! Allows expression of durations as an hour count + /*! \ingroup time_basics + */ + class hours : public time_duration + { + public: + explicit hours(long h) : + time_duration(static_cast(h),0,0) + {} + }; + + //! Allows expression of durations as a minute count + /*! \ingroup time_basics + */ + class minutes : public time_duration + { + public: + explicit minutes(long m) : + time_duration(0,static_cast(m),0) + {} + }; + + //! Allows expression of durations as a seconds count + /*! \ingroup time_basics + */ + class seconds : public time_duration + { + public: + explicit seconds(long s) : + time_duration(0,0,static_cast(s)) + {} + }; + + + //! Allows expression of durations as milli seconds + /*! \ingroup time_basics + */ + typedef date_time::subsecond_duration millisec; + typedef date_time::subsecond_duration milliseconds; + + //! Allows expression of durations as micro seconds + /*! \ingroup time_basics + */ + typedef date_time::subsecond_duration microsec; + typedef date_time::subsecond_duration microseconds; + + //This is probably not needed anymore... +#if defined(BOOST_DATE_TIME_HAS_NANOSECONDS) + + //! Allows expression of durations as nano seconds + /*! \ingroup time_basics + */ + typedef date_time::subsecond_duration nanosec; + typedef date_time::subsecond_duration nanoseconds; + + +#endif + + + + +} }//namespace posix_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_system.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_system.hpp new file mode 100644 index 0000000..84c21ca --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_system.hpp @@ -0,0 +1,68 @@ +#ifndef POSIX_TIME_SYSTEM_HPP___ +#define POSIX_TIME_SYSTEM_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + + +#include "boost/date_time/posix_time/posix_time_config.hpp" +#include "boost/date_time/time_system_split.hpp" +#include "boost/date_time/time_system_counted.hpp" +#include "boost/date_time/compiler_config.hpp" + + +namespace boost { +namespace posix_time { + +#ifdef BOOST_DATE_TIME_POSIX_TIME_STD_CONFIG + +#if (defined(BOOST_DATE_TIME_NO_MEMBER_INIT)) //help bad compilers + typedef date_time::split_timedate_system posix_time_system; +#else + typedef date_time::split_timedate_system posix_time_system; +#endif + +#else + + typedef date_time::counted_time_rep int64_time_rep; + typedef date_time::counted_time_system posix_time_system; + +#endif + +} }//namespace posix_time + + +#endif + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_types.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_types.hpp new file mode 100644 index 0000000..f2488f8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/posix_time_types.hpp @@ -0,0 +1,55 @@ +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + */ +#ifndef POSIX_TIME_TYPES_HPP___ +#define POSIX_TIME_TYPES_HPP___ + +#include "boost/date_time/time_clock.hpp" +#include "boost/date_time/microsec_time_clock.hpp" +#include "boost/date_time/posix_time/ptime.hpp" +#if defined(BOOST_DATE_TIME_OPTIONAL_GREGORIAN_TYPES) +#include "boost/date_time/posix_time/date_duration_operators.hpp" +#endif +#include "boost/date_time/posix_time/posix_time_duration.hpp" +#include "boost/date_time/posix_time/posix_time_system.hpp" +#include "boost/date_time/posix_time/time_period.hpp" +#include "boost/date_time/time_iterator.hpp" +#include "boost/date_time/dst_rules.hpp" + +namespace boost { + +//!Defines a non-adjusted time system with nano-second resolution and stable calculation properties +namespace posix_time { + + //! Iterator over a defined time duration + /*! \ingroup time_basics + */ + typedef date_time::time_itr time_iterator; + //! A time clock that has a resolution of one second + /*! \ingroup time_basics + */ + typedef date_time::second_clock second_clock; + +#ifdef BOOST_DATE_TIME_HAS_HIGH_PRECISION_CLOCK + //! A time clock that has a resolution of one microsecond + /*! \ingroup time_basics + */ + typedef date_time::microsec_clock microsec_clock; +#endif + + //! Define a dst null dst rule for the posix_time system + typedef date_time::null_dst_rules no_dst; + //! Define US dst rule calculator for the posix_time system + typedef date_time::us_dst_rules us_dst; + + +} } //namespace posix_time + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/ptime.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/ptime.hpp new file mode 100644 index 0000000..e4f9d02 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/ptime.hpp @@ -0,0 +1,65 @@ +#ifndef POSIX_PTIME_HPP___ +#define POSIX_PTIME_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/posix_time/posix_time_system.hpp" +#include "boost/date_time/time.hpp" + +namespace boost { + +namespace posix_time { + + //bring special enum values into the namespace + using date_time::special_values; + using date_time::not_special; + using date_time::neg_infin; + using date_time::pos_infin; + using date_time::not_a_date_time; + using date_time::max_date_time; + using date_time::min_date_time; + + //! Time type with no timezone or other adjustments + /*! \ingroup time_basics + */ + class ptime : public date_time::base_time + { + public: + typedef posix_time_system time_system_type; + typedef time_system_type::time_rep_type time_rep_type; + typedef time_system_type::time_duration_type time_duration_type; + typedef ptime time_type; + //! Construct with date and offset in day + ptime(gregorian::date d,time_duration_type td) : date_time::base_time(d,td) + {} + //! Construct a time at start of the given day (midnight) + explicit ptime(gregorian::date d) : date_time::base_time(d,time_duration_type(0,0,0)) + {} + //! Copy from time_rep + ptime(const time_rep_type& rhs): + date_time::base_time(rhs) + {} + //! Construct from special value + ptime(const special_values sv) : date_time::base_time(sv) + {} +#if !defined(DATE_TIME_NO_DEFAULT_CONSTRUCTOR) + // Default constructor constructs to not_a_date_time + ptime() : date_time::base_time(gregorian::date(not_a_date_time), time_duration_type(not_a_date_time)) + {} +#endif // DATE_TIME_NO_DEFAULT_CONSTRUCTOR + + }; + + + +} }//namespace posix_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/time_period.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/time_period.hpp new file mode 100644 index 0000000..7c6095b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/posix_time/time_period.hpp @@ -0,0 +1,29 @@ +#ifndef POSIX_TIME_PERIOD_HPP___ +#define POSIX_TIME_PERIOD_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +#include "boost/date_time/period.hpp" +#include "boost/date_time/posix_time/posix_time_duration.hpp" +#include "boost/date_time/posix_time/ptime.hpp" + +namespace boost { +namespace posix_time { + + //! Time period type + /*! \ingroup time_basics + */ + typedef date_time::period time_period; + + +} }//namespace posix_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/special_defs.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/special_defs.hpp new file mode 100644 index 0000000..5a757be --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/special_defs.hpp @@ -0,0 +1,25 @@ +#ifndef DATE_TIME_SPECIAL_DEFS_HPP__ +#define DATE_TIME_SPECIAL_DEFS_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +namespace boost { +namespace date_time { + + enum special_values {not_a_date_time, + neg_infin, pos_infin, + min_date_time, max_date_time, + not_special, NumSpecialValues}; + + +} } //namespace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time.hpp new file mode 100644 index 0000000..632f10d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time.hpp @@ -0,0 +1,193 @@ +#ifndef DATE_TIME_TIME_HPP___ +#define DATE_TIME_TIME_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +/*! @file time.hpp + This file contains the interface for the time associated classes. +*/ +#include +#include +#include +#include + +namespace boost { +namespace date_time { + + //! Representation of a precise moment in time, including the date. + /*! + This class is a skeleton for the interface of a temporal type + with a resolution that is higher than a day. It is intended that + this class be the base class and that the actual time + class be derived using the BN pattern. In this way, the derived + class can make decisions such as 'should there be a default constructor' + and what should it set its value to, should there be optional constructors + say allowing only an time_durations that generate a time from a clock,etc. + So, in fact multiple time types can be created for a time_system with + different construction policies, and all of them can perform basic + operations by only writing a copy constructor. Finally, compiler + errors are also shorter. + + The real behavior of the time class is provided by the time_system + template parameter. This class must provide all the logic + for addition, subtraction, as well as define all the interface + types. + + */ + + template + class base_time : private + boost::less_than_comparable > + { + public: + // A tag for type categorization. Can be used to detect Boost.DateTime time points in generic code. + typedef void _is_boost_date_time_time_point; + typedef T time_type; + typedef typename time_system::time_rep_type time_rep_type; + typedef typename time_system::date_type date_type; + typedef typename time_system::date_duration_type date_duration_type; + typedef typename time_system::time_duration_type time_duration_type; + //typedef typename time_system::hms_type hms_type; + + base_time(const date_type& day, + const time_duration_type& td, + dst_flags dst=not_dst) : + time_(time_system::get_time_rep(day, td, dst)) + {} + base_time(special_values sv) : + time_(time_system::get_time_rep(sv)) + {} + base_time(const time_rep_type& rhs) : + time_(rhs) + {} + date_type date() const + { + return time_system::get_date(time_); + } + time_duration_type time_of_day() const + { + return time_system::get_time_of_day(time_); + } + /*! Optional bool parameter will return time zone as an offset + * (ie "+07:00"). Empty string is returned for classes that do + * not use a time_zone */ + std::string zone_name(bool /*as_offset*/=false) const + { + return time_system::zone_name(time_); + } + /*! Optional bool parameter will return time zone as an offset + * (ie "+07:00"). Empty string is returned for classes that do + * not use a time_zone */ + std::string zone_abbrev(bool /*as_offset*/=false) const + { + return time_system::zone_name(time_); + } + //! An empty string is returned for classes that do not use a time_zone + std::string zone_as_posix_string() const + { + return std::string(); + } + + //! check to see if date is not a value + bool is_not_a_date_time() const + { + return time_.is_not_a_date_time(); + } + //! check to see if date is one of the infinity values + bool is_infinity() const + { + return (is_pos_infinity() || is_neg_infinity()); + } + //! check to see if date is greater than all possible dates + bool is_pos_infinity() const + { + return time_.is_pos_infinity(); + } + //! check to see if date is greater than all possible dates + bool is_neg_infinity() const + { + return time_.is_neg_infinity(); + } + //! check to see if time is a special value + bool is_special() const + { + return(is_not_a_date_time() || is_infinity()); + } + //!Equality operator -- others generated by boost::equality_comparable + bool operator==(const time_type& rhs) const + { + return time_system::is_equal(time_,rhs.time_); + } + //!Equality operator -- others generated by boost::less_than_comparable + bool operator<(const time_type& rhs) const + { + return time_system::is_less(time_,rhs.time_); + } + //! difference between two times + time_duration_type operator-(const time_type& rhs) const + { + return time_system::subtract_times(time_, rhs.time_); + } + //! add date durations + time_type operator+(const date_duration_type& dd) const + { + return time_system::add_days(time_, dd); + } + time_type operator+=(const date_duration_type& dd) + { + time_ = (time_system::get_time_rep(date() + dd, time_of_day())); + return time_type(time_); + } + //! subtract date durations + time_type operator-(const date_duration_type& dd) const + { + return time_system::subtract_days(time_, dd); + } + time_type operator-=(const date_duration_type& dd) + { + time_ = (time_system::get_time_rep(date() - dd, time_of_day())); + return time_type(time_); + } + //! add time durations + time_type operator+(const time_duration_type& td) const + { + return time_type(time_system::add_time_duration(time_, td)); + } + time_type operator+=(const time_duration_type& td) + { + time_ = (time_system::get_time_rep(date(), time_of_day() + td)); + return time_type(time_); + } + //! subtract time durations + time_type operator-(const time_duration_type& rhs) const + { + return time_system::subtract_time_duration(time_, rhs); + } + time_type operator-=(const time_duration_type& td) + { + time_ = (time_system::get_time_rep(date(), time_of_day() - td)); + return time_type(time_); + } + + protected: + time_rep_type time_; + }; + + + + + +} } //namespace date_time::boost + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_clock.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_clock.hpp new file mode 100644 index 0000000..a64a5b8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_clock.hpp @@ -0,0 +1,83 @@ +#ifndef DATE_TIME_TIME_CLOCK_HPP___ +#define DATE_TIME_TIME_CLOCK_HPP___ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +/*! @file time_clock.hpp + This file contains the interface for clock devices. +*/ + +#include "boost/date_time/c_time.hpp" +#include "boost/shared_ptr.hpp" + +namespace boost { +namespace date_time { + + + //! A clock providing time level services based on C time_t capabilities + /*! This clock provides resolution to the 1 second level + */ + template + class second_clock + { + public: + typedef typename time_type::date_type date_type; + typedef typename time_type::time_duration_type time_duration_type; + + static time_type local_time() + { + ::std::time_t t; + ::std::time(&t); + ::std::tm curr, *curr_ptr; + //curr_ptr = ::std::localtime(&t); + curr_ptr = c_time::localtime(&t, &curr); + return create_time(curr_ptr); + } + + + //! Get the current day in universal date as a ymd_type + static time_type universal_time() + { + + ::std::time_t t; + ::std::time(&t); + ::std::tm curr, *curr_ptr; + //curr_ptr = ::std::gmtime(&t); + curr_ptr = c_time::gmtime(&t, &curr); + return create_time(curr_ptr); + } + + template + static time_type local_time(boost::shared_ptr tz_ptr) + { + typedef typename time_type::utc_time_type utc_time_type; + utc_time_type utc_time = second_clock::universal_time(); + return time_type(utc_time, tz_ptr); + } + + + private: + static time_type create_time(::std::tm* current) + { + date_type d(static_cast(current->tm_year + 1900), + static_cast(current->tm_mon + 1), + static_cast(current->tm_mday)); + time_duration_type td(current->tm_hour, + current->tm_min, + current->tm_sec); + return time_type(d,td); + } + + }; + + +} } //namespace date_time + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_defs.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_defs.hpp new file mode 100644 index 0000000..852207e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_defs.hpp @@ -0,0 +1,43 @@ +#ifndef DATE_TIME_TIME_PRECISION_LIMITS_HPP +#define DATE_TIME_TIME_PRECISION_LIMITS_HPP + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + + + +/*! \file time_defs.hpp + This file contains nice definitions for handling the resoluion of various time + reprsentations. +*/ + +namespace boost { +namespace date_time { + + //!Defines some nice types for handling time level resolutions + enum time_resolutions { + sec, + tenth, + hundreth, // deprecated misspelled version of hundredth + hundredth = hundreth, + milli, + ten_thousandth, + micro, + nano, + NumResolutions + }; + + //! Flags for daylight savings or summer time + enum dst_flags {not_dst, is_dst, calculate}; + + +} } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_duration.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_duration.hpp new file mode 100644 index 0000000..58768bc --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_duration.hpp @@ -0,0 +1,295 @@ +#ifndef DATE_TIME_TIME_DURATION_HPP___ +#define DATE_TIME_TIME_DURATION_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace date_time { + + + //! Represents some amount of elapsed time measure to a given resolution + /*! This class represents a standard set of capabilities for all + counted time durations. Time duration implementations should derive + from this class passing their type as the first template parameter. + This design allows the subclass duration types to provide custom + construction policies or other custom features not provided here. + + @param T The subclass type + @param rep_type The time resolution traits for this duration type. + */ + template + class time_duration : private + boost::less_than_comparable > + /* dividable, addable, and subtractable operator templates + * won't work with this class (MSVC++ 6.0). return type + * from '+=' is different than expected return type + * from '+'. multipliable probably wont work + * either (haven't tried) */ + { + public: + // A tag for type categorization. Can be used to detect Boost.DateTime duration types in generic code. + typedef void _is_boost_date_time_duration; + typedef T duration_type; //the subclass + typedef rep_type traits_type; + typedef typename rep_type::day_type day_type; + typedef typename rep_type::hour_type hour_type; + typedef typename rep_type::min_type min_type; + typedef typename rep_type::sec_type sec_type; + typedef typename rep_type::fractional_seconds_type fractional_seconds_type; + typedef typename rep_type::tick_type tick_type; + typedef typename rep_type::impl_type impl_type; + + time_duration() : ticks_(0) {} + time_duration(hour_type hours_in, + min_type minutes_in, + sec_type seconds_in=0, + fractional_seconds_type frac_sec_in = 0) : + ticks_(rep_type::to_tick_count(hours_in,minutes_in,seconds_in,frac_sec_in)) + {} + // copy constructor required for dividable<> + //! Construct from another time_duration (Copy constructor) + time_duration(const time_duration& other) + : ticks_(other.ticks_) + {} + //! Construct from special_values + time_duration(special_values sv) : ticks_(impl_type::from_special(sv)) + {} + //! Returns smallest representable duration + static duration_type unit() + { + return duration_type(0,0,0,1); + } + //! Return the number of ticks in a second + static tick_type ticks_per_second() + { + return rep_type::res_adjust(); + } + //! Provide the resolution of this duration type + static time_resolutions resolution() + { + return rep_type::resolution(); + } + //! Returns number of hours in the duration + hour_type hours() const + { + return static_cast(ticks() / (3600*ticks_per_second())); + } + //! Returns normalized number of minutes + min_type minutes() const + { + return static_cast((ticks() / (60*ticks_per_second())) % 60); + } + //! Returns normalized number of seconds (0..60) + sec_type seconds() const + { + return static_cast((ticks()/ticks_per_second()) % 60); + } + //! Returns total number of seconds truncating any fractional seconds + sec_type total_seconds() const + { + return static_cast(ticks() / ticks_per_second()); + } + //! Returns total number of milliseconds truncating any fractional seconds + tick_type total_milliseconds() const + { + if (ticks_per_second() < 1000) { + return ticks() * (static_cast(1000) / ticks_per_second()); + } + return ticks() / (ticks_per_second() / static_cast(1000)) ; + } + //! Returns total number of nanoseconds truncating any sub millisecond values + tick_type total_nanoseconds() const + { + if (ticks_per_second() < 1000000000) { + return ticks() * (static_cast(1000000000) / ticks_per_second()); + } + return ticks() / (ticks_per_second() / static_cast(1000000000)) ; + } + //! Returns total number of microseconds truncating any sub microsecond values + tick_type total_microseconds() const + { + if (ticks_per_second() < 1000000) { + return ticks() * (static_cast(1000000) / ticks_per_second()); + } + return ticks() / (ticks_per_second() / static_cast(1000000)) ; + } + //! Returns count of fractional seconds at given resolution + fractional_seconds_type fractional_seconds() const + { + return (ticks() % ticks_per_second()); + } + //! Returns number of possible digits in fractional seconds + static unsigned short num_fractional_digits() + { + return rep_type::num_fractional_digits(); + } + duration_type invert_sign() const + { + return duration_type(ticks_ * (-1)); + } + bool is_negative() const + { + return ticks_ < 0; + } + bool operator<(const time_duration& rhs) const + { + return ticks_ < rhs.ticks_; + } + bool operator==(const time_duration& rhs) const + { + return ticks_ == rhs.ticks_; + } + //! unary- Allows for time_duration td = -td1 + duration_type operator-()const + { + return duration_type(ticks_ * (-1)); + } + duration_type operator-(const duration_type& d) const + { + return duration_type(ticks_ - d.ticks_); + } + duration_type operator+(const duration_type& d) const + { + return duration_type(ticks_ + d.ticks_); + } + duration_type operator/(int divisor) const + { + return duration_type(ticks_ / divisor); + } + duration_type operator-=(const duration_type& d) + { + ticks_ = ticks_ - d.ticks_; + return duration_type(ticks_); + } + duration_type operator+=(const duration_type& d) + { + ticks_ = ticks_ + d.ticks_; + return duration_type(ticks_); + } + //! Division operations on a duration with an integer. + duration_type operator/=(int divisor) + { + ticks_ = ticks_ / divisor; + return duration_type(ticks_); + } + //! Multiplication operations an a duration with an integer + duration_type operator*(int rhs) const + { + return duration_type(ticks_ * rhs); + } + duration_type operator*=(int divisor) + { + ticks_ = ticks_ * divisor; + return duration_type(ticks_); + } + tick_type ticks() const + { + return traits_type::as_number(ticks_); + } + + //! Is ticks_ a special value? + bool is_special()const + { + if(traits_type::is_adapted()) + { + return ticks_.is_special(); + } + else{ + return false; + } + } + //! Is duration pos-infinity + bool is_pos_infinity()const + { + if(traits_type::is_adapted()) + { + return ticks_.is_pos_infinity(); + } + else{ + return false; + } + } + //! Is duration neg-infinity + bool is_neg_infinity()const + { + if(traits_type::is_adapted()) + { + return ticks_.is_neg_infinity(); + } + else{ + return false; + } + } + //! Is duration not-a-date-time + bool is_not_a_date_time()const + { + if(traits_type::is_adapted()) + { + return ticks_.is_nan(); + } + else{ + return false; + } + } + + //! Used for special_values output + impl_type get_rep()const + { + return ticks_; + } + + protected: + explicit time_duration(impl_type in) : ticks_(in) {} + impl_type ticks_; + }; + + + + //! Template for instantiating derived adjusting durations + /* These templates are designed to work with multiples of + * 10 for frac_of_second and resoultion adjustment + */ + template + class subsecond_duration : public base_duration + { + public: + typedef typename base_duration::impl_type impl_type; + typedef typename base_duration::traits_type traits_type; + + private: + // To avoid integer overflow we precompute the duration resolution conversion coefficient (ticket #3471) + BOOST_STATIC_ASSERT_MSG((traits_type::ticks_per_second >= frac_of_second ? traits_type::ticks_per_second % frac_of_second : frac_of_second % traits_type::ticks_per_second) == 0,\ + "The base duration resolution must be a multiple of the subsecond duration resolution"); + BOOST_STATIC_CONSTANT(boost::int64_t, adjustment_ratio = (traits_type::ticks_per_second >= frac_of_second ? traits_type::ticks_per_second / frac_of_second : frac_of_second / traits_type::ticks_per_second)); + + public: + explicit subsecond_duration(boost::int64_t ss) : + base_duration(impl_type(traits_type::ticks_per_second >= frac_of_second ? ss * adjustment_ratio : ss / adjustment_ratio)) + { + } + }; + + + +} } //namespace date_time + + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_iterator.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_iterator.hpp new file mode 100644 index 0000000..6443936 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_iterator.hpp @@ -0,0 +1,52 @@ +#ifndef DATE_TIME_TIME_ITERATOR_HPP___ +#define DATE_TIME_TIME_ITERATOR_HPP___ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +namespace boost { +namespace date_time { + + + //! Simple time iterator skeleton class + template + class time_itr { + public: + typedef typename time_type::time_duration_type time_duration_type; + time_itr(time_type t, time_duration_type d) : current_(t), offset_(d) {} + time_itr& operator++() + { + current_ = current_ + offset_; + return *this; + } + time_itr& operator--() + { + current_ = current_ - offset_; + return *this; + } + time_type operator*() {return current_;} + time_type* operator->() {return ¤t_;} + bool operator< (const time_type& t) {return current_ < t;} + bool operator<= (const time_type& t) {return current_ <= t;} + bool operator!= (const time_type& t) {return current_ != t;} + bool operator== (const time_type& t) {return current_ == t;} + bool operator> (const time_type& t) {return current_ > t;} + bool operator>= (const time_type& t) {return current_ >= t;} + + private: + time_type current_; + time_duration_type offset_; + }; + + + +} }//namespace date_time + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_resolution_traits.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_resolution_traits.hpp new file mode 100644 index 0000000..3b6134c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_resolution_traits.hpp @@ -0,0 +1,144 @@ +#ifndef DATE_TIME_TIME_RESOLUTION_TRAITS_HPP +#define DATE_TIME_TIME_RESOLUTION_TRAITS_HPP + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include +#include +#include +#include + +namespace boost { +namespace date_time { + + //! Simple function to calculate absolute value of a numeric type + template + // JDG [7/6/02 made a template], + // moved here from time_duration.hpp 2003-Sept-4. + inline T absolute_value(T x) + { + return x < 0 ? -x : x; + } + + //! traits struct for time_resolution_traits implementation type + struct time_resolution_traits_bi32_impl { + typedef boost::int32_t int_type; + typedef boost::int32_t impl_type; + static int_type as_number(impl_type i){ return i;} + //! Used to determine if implemented type is int_adapter or int + static bool is_adapted() { return false;} + }; + //! traits struct for time_resolution_traits implementation type + struct time_resolution_traits_adapted32_impl { + typedef boost::int32_t int_type; + typedef boost::date_time::int_adapter impl_type; + static int_type as_number(impl_type i){ return i.as_number();} + //! Used to determine if implemented type is int_adapter or int + static bool is_adapted() { return true;} + }; + //! traits struct for time_resolution_traits implementation type + struct time_resolution_traits_bi64_impl { + typedef boost::int64_t int_type; + typedef boost::int64_t impl_type; + static int_type as_number(impl_type i){ return i;} + //! Used to determine if implemented type is int_adapter or int + static bool is_adapted() { return false;} + }; + //! traits struct for time_resolution_traits implementation type + struct time_resolution_traits_adapted64_impl { + typedef boost::int64_t int_type; + typedef boost::date_time::int_adapter impl_type; + static int_type as_number(impl_type i){ return i.as_number();} + //! Used to determine if implemented type is int_adapter or int + static bool is_adapted() { return true;} + }; + + template + class time_resolution_traits { + public: + typedef typename frac_sec_type::int_type fractional_seconds_type; + typedef typename frac_sec_type::int_type tick_type; + typedef typename frac_sec_type::impl_type impl_type; + typedef var_type day_type; + typedef var_type hour_type; + typedef var_type min_type; + typedef var_type sec_type; + + // bring in function from frac_sec_type traits structs + static fractional_seconds_type as_number(impl_type i) + { + return frac_sec_type::as_number(i); + } + static bool is_adapted() + { + return frac_sec_type::is_adapted(); + } + + //Would like this to be frac_sec_type, but some compilers complain +#if (defined(BOOST_MSVC) && (_MSC_VER < 1300)) + BOOST_STATIC_CONSTANT(boost::int64_t, ticks_per_second = resolution_adjust); +#else + BOOST_STATIC_CONSTANT(fractional_seconds_type, ticks_per_second = resolution_adjust); +#endif + + static time_resolutions resolution() + { + return res; + } + static unsigned short num_fractional_digits() + { + return frac_digits; + } + static fractional_seconds_type res_adjust() + { + return resolution_adjust; + } + //! Any negative argument results in a negative tick_count + static tick_type to_tick_count(hour_type hours, + min_type minutes, + sec_type seconds, + fractional_seconds_type fs) + { + if(hours < 0 || minutes < 0 || seconds < 0 || fs < 0) + { + hours = absolute_value(hours); + minutes = absolute_value(minutes); + seconds = absolute_value(seconds); + fs = absolute_value(fs); + return (((((fractional_seconds_type(hours)*3600) + + (fractional_seconds_type(minutes)*60) + + seconds)*res_adjust()) + fs) * -1); + } + + return (((fractional_seconds_type(hours)*3600) + + (fractional_seconds_type(minutes)*60) + + seconds)*res_adjust()) + fs; + } + + }; + + typedef time_resolution_traits milli_res; + typedef time_resolution_traits micro_res; + typedef time_resolution_traits nano_res; + + +} } //namespace date_time + + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_system_counted.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_system_counted.hpp new file mode 100644 index 0000000..af27aad --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_system_counted.hpp @@ -0,0 +1,254 @@ +#ifndef DATE_TIME_TIME_SYSTEM_COUNTED_HPP +#define DATE_TIME_TIME_SYSTEM_COUNTED_HPP + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + + +#include "boost/date_time/time_defs.hpp" +#include + + +namespace boost { +namespace date_time { + + //! Time representation that uses a single integer count + template + struct counted_time_rep + { + typedef typename config::int_type int_type; + typedef typename config::date_type date_type; + typedef typename config::impl_type impl_type; + typedef typename date_type::duration_type date_duration_type; + typedef typename date_type::calendar_type calendar_type; + typedef typename date_type::ymd_type ymd_type; + typedef typename config::time_duration_type time_duration_type; + typedef typename config::resolution_traits resolution_traits; + + counted_time_rep(const date_type& d, const time_duration_type& time_of_day) + : time_count_(1) + { + if(d.is_infinity() || d.is_not_a_date() || time_of_day.is_special()) { + time_count_ = time_of_day.get_rep() + d.day_count(); + //std::cout << time_count_ << std::endl; + } + else { + time_count_ = (d.day_number() * frac_sec_per_day()) + time_of_day.ticks(); + } + } + explicit counted_time_rep(int_type count) : + time_count_(count) + {} + explicit counted_time_rep(impl_type count) : + time_count_(count) + {} + date_type date() const + { + if(time_count_.is_special()) { + return date_type(time_count_.as_special()); + } + else { + typename calendar_type::date_int_type dc = static_cast(day_count()); + //std::cout << "time_rep here:" << dc << std::endl; + ymd_type ymd = calendar_type::from_day_number(dc); + return date_type(ymd); + } + } + //int_type day_count() const + unsigned long day_count() const + { + /* resolution_traits::as_number returns a boost::int64_t & + * frac_sec_per_day is also a boost::int64_t so, naturally, + * the division operation returns a boost::int64_t. + * The static_cast to an unsigned long is ok (results in no data loss) + * because frac_sec_per_day is either the number of + * microseconds per day, or the number of nanoseconds per day. + * Worst case scenario: resolution_traits::as_number returns the + * maximum value an int64_t can hold and frac_sec_per_day + * is microseconds per day (lowest possible value). + * The division operation will then return a value of 106751991 - + * easily fitting in an unsigned long. + */ + return static_cast(resolution_traits::as_number(time_count_) / frac_sec_per_day()); + } + int_type time_count() const + { + return resolution_traits::as_number(time_count_); + } + int_type tod() const + { + return resolution_traits::as_number(time_count_) % frac_sec_per_day(); + } + static int_type frac_sec_per_day() + { + int_type seconds_per_day = 60*60*24; + int_type fractional_sec_per_sec(resolution_traits::res_adjust()); + return seconds_per_day*fractional_sec_per_sec; + } + bool is_pos_infinity()const + { + return impl_type::is_pos_inf(time_count_.as_number()); + } + bool is_neg_infinity()const + { + return impl_type::is_neg_inf(time_count_.as_number()); + } + bool is_not_a_date_time()const + { + return impl_type::is_not_a_number(time_count_.as_number()); + } + bool is_special()const + { + return time_count_.is_special(); + } + impl_type get_rep()const + { + return time_count_; + } + private: + impl_type time_count_; + }; + + //! An unadjusted time system implementation. + template + class counted_time_system + { + public: + typedef time_rep time_rep_type; + typedef typename time_rep_type::impl_type impl_type; + typedef typename time_rep_type::time_duration_type time_duration_type; + typedef typename time_duration_type::fractional_seconds_type fractional_seconds_type; + typedef typename time_rep_type::date_type date_type; + typedef typename time_rep_type::date_duration_type date_duration_type; + + + template static void unused_var(const T&) {} + + static time_rep_type get_time_rep(const date_type& day, + const time_duration_type& tod, + date_time::dst_flags dst=not_dst) + { + unused_var(dst); + return time_rep_type(day, tod); + } + + static time_rep_type get_time_rep(special_values sv) + { + switch (sv) { + case not_a_date_time: + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + case pos_infin: + return time_rep_type(date_type(pos_infin), + time_duration_type(pos_infin)); + case neg_infin: + return time_rep_type(date_type(neg_infin), + time_duration_type(neg_infin)); + case max_date_time: { + time_duration_type td = time_duration_type(24,0,0,0) - time_duration_type(0,0,0,1); + return time_rep_type(date_type(max_date_time), td); + } + case min_date_time: + return time_rep_type(date_type(min_date_time), time_duration_type(0,0,0,0)); + + default: + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + + } + + } + + static date_type get_date(const time_rep_type& val) + { + return val.date(); + } + static time_duration_type get_time_of_day(const time_rep_type& val) + { + if(val.is_special()) { + return time_duration_type(val.get_rep().as_special()); + } + else{ + return time_duration_type(0,0,0,val.tod()); + } + } + static std::string zone_name(const time_rep_type&) + { + return ""; + } + static bool is_equal(const time_rep_type& lhs, const time_rep_type& rhs) + { + return (lhs.time_count() == rhs.time_count()); + } + static bool is_less(const time_rep_type& lhs, const time_rep_type& rhs) + { + return (lhs.time_count() < rhs.time_count()); + } + static time_rep_type add_days(const time_rep_type& base, + const date_duration_type& dd) + { + if(base.is_special() || dd.is_special()) { + return(time_rep_type(base.get_rep() + dd.get_rep())); + } + else { + return time_rep_type(base.time_count() + (dd.days() * time_rep_type::frac_sec_per_day())); + } + } + static time_rep_type subtract_days(const time_rep_type& base, + const date_duration_type& dd) + { + if(base.is_special() || dd.is_special()) { + return(time_rep_type(base.get_rep() - dd.get_rep())); + } + else{ + return time_rep_type(base.time_count() - (dd.days() * time_rep_type::frac_sec_per_day())); + } + } + static time_rep_type subtract_time_duration(const time_rep_type& base, + const time_duration_type& td) + { + if(base.is_special() || td.is_special()) { + return(time_rep_type(base.get_rep() - td.get_rep())); + } + else { + return time_rep_type(base.time_count() - td.ticks()); + } + } + static time_rep_type add_time_duration(const time_rep_type& base, + time_duration_type td) + { + if(base.is_special() || td.is_special()) { + return(time_rep_type(base.get_rep() + td.get_rep())); + } + else { + return time_rep_type(base.time_count() + td.ticks()); + } + } + static time_duration_type subtract_times(const time_rep_type& lhs, + const time_rep_type& rhs) + { + if(lhs.is_special() || rhs.is_special()) { + return(time_duration_type( + impl_type::to_special((lhs.get_rep() - rhs.get_rep()).as_number()))); + } + else { + fractional_seconds_type fs = lhs.time_count() - rhs.time_count(); + return time_duration_type(0,0,0,fs); + } + } + + }; + + +} } //namespace date_time + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/time_system_split.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/time_system_split.hpp new file mode 100644 index 0000000..cf5931a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/time_system_split.hpp @@ -0,0 +1,207 @@ +#ifndef DATE_TIME_TIME_SYSTEM_SPLIT_HPP +#define DATE_TIME_TIME_SYSTEM_SPLIT_HPP + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +#include +#include "boost/date_time/compiler_config.hpp" +#include "boost/date_time/special_defs.hpp" + +namespace boost { +namespace date_time { + + //! An unadjusted time system implementation. +#if (defined(BOOST_DATE_TIME_NO_MEMBER_INIT)) + template +#else + template +#endif + class split_timedate_system + { + public: + typedef typename config::time_rep_type time_rep_type; + typedef typename config::date_type date_type; + typedef typename config::time_duration_type time_duration_type; + typedef typename config::date_duration_type date_duration_type; + typedef typename config::int_type int_type; + typedef typename config::resolution_traits resolution_traits; + + //86400 is number of seconds in a day... +#if (defined(BOOST_DATE_TIME_NO_MEMBER_INIT)) + typedef date_time::wrapping_int wrap_int_type; +#else + private: + BOOST_STATIC_CONSTANT(int_type, ticks_per_day = INT64_C(86400) * config::tick_per_second); + public: +# if BOOST_WORKAROUND( __BORLANDC__, BOOST_TESTED_AT(0X581) ) + typedef date_time::wrapping_int< split_timedate_system::int_type, split_timedate_system::ticks_per_day> wrap_int_type; +# else + typedef date_time::wrapping_int wrap_int_type; +#endif +#endif + + static time_rep_type get_time_rep(special_values sv) + { + switch (sv) { + case not_a_date_time: + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + case pos_infin: + return time_rep_type(date_type(pos_infin), + time_duration_type(pos_infin)); + case neg_infin: + return time_rep_type(date_type(neg_infin), + time_duration_type(neg_infin)); + case max_date_time: { + time_duration_type td = time_duration_type(24,0,0,0) - time_duration_type(0,0,0,1); + return time_rep_type(date_type(max_date_time), td); + } + case min_date_time: + return time_rep_type(date_type(min_date_time), time_duration_type(0,0,0,0)); + + default: + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + + } + + } + + static time_rep_type get_time_rep(const date_type& day, + const time_duration_type& tod, + date_time::dst_flags /* dst */ = not_dst) + { + if(day.is_special() || tod.is_special()) { + if(day.is_not_a_date() || tod.is_not_a_date_time()) { + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + } + else if(day.is_pos_infinity()) { + if(tod.is_neg_infinity()) { + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + } + else { + return time_rep_type(day, time_duration_type(pos_infin)); + } + } + else if(day.is_neg_infinity()) { + if(tod.is_pos_infinity()) { + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + } + else { + return time_rep_type(day, time_duration_type(neg_infin)); + } + } + else if(tod.is_pos_infinity()) { + if(day.is_neg_infinity()) { + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + } + else { + return time_rep_type(date_type(pos_infin), tod); + } + } + else if(tod.is_neg_infinity()) { + if(day.is_pos_infinity()) { + return time_rep_type(date_type(not_a_date_time), + time_duration_type(not_a_date_time)); + } + else { + return time_rep_type(date_type(neg_infin), tod); + } + } + } + return time_rep_type(day, tod); + } + static date_type get_date(const time_rep_type& val) + { + return date_type(val.day); + } + static time_duration_type get_time_of_day(const time_rep_type& val) + { + return time_duration_type(val.time_of_day); + } + static std::string zone_name(const time_rep_type&) + { + return std::string(); + } + static bool is_equal(const time_rep_type& lhs, const time_rep_type& rhs) + { + return ((lhs.day == rhs.day) && (lhs.time_of_day == rhs.time_of_day)); + } + static bool is_less(const time_rep_type& lhs, const time_rep_type& rhs) + { + if (lhs.day < rhs.day) return true; + if (lhs.day > rhs.day) return false; + return (lhs.time_of_day < rhs.time_of_day); + } + static time_rep_type add_days(const time_rep_type& base, + const date_duration_type& dd) + { + return time_rep_type(base.day+dd, base.time_of_day); + } + static time_rep_type subtract_days(const time_rep_type& base, + const date_duration_type& dd) + { + return split_timedate_system::get_time_rep(base.day-dd, base.time_of_day); + } + static time_rep_type subtract_time_duration(const time_rep_type& base, + const time_duration_type& td) + { + if(base.day.is_special() || td.is_special()) + { + return split_timedate_system::get_time_rep(base.day, -td); + } + if (td.is_negative()) { + time_duration_type td1 = td.invert_sign(); + return add_time_duration(base,td1); + } + + wrap_int_type day_offset(base.time_of_day.ticks()); + date_duration_type day_overflow(static_cast(day_offset.subtract(td.ticks()))); + + return time_rep_type(base.day-day_overflow, + time_duration_type(0,0,0,day_offset.as_int())); + } + static time_rep_type add_time_duration(const time_rep_type& base, + time_duration_type td) + { + if(base.day.is_special() || td.is_special()) { + return split_timedate_system::get_time_rep(base.day, td); + } + if (td.is_negative()) { + time_duration_type td1 = td.invert_sign(); + return subtract_time_duration(base,td1); + } + + wrap_int_type day_offset(base.time_of_day.ticks()); + date_duration_type day_overflow(static_cast< typename date_duration_type::duration_rep_type >(day_offset.add(td.ticks()))); + + return time_rep_type(base.day+day_overflow, + time_duration_type(0,0,0,day_offset.as_int())); + } + static time_duration_type subtract_times(const time_rep_type& lhs, + const time_rep_type& rhs) + { + date_duration_type dd = lhs.day - rhs.day; + time_duration_type td(dd.days()*24,0,0); //days * 24 hours + time_duration_type td2 = lhs.time_of_day - rhs.time_of_day; + return td+td2; + // return time_rep_type(base.day-dd, base.time_of_day); + } + + }; + +} } //namespace date_time + + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/wrapping_int.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/wrapping_int.hpp new file mode 100644 index 0000000..6f869d3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/wrapping_int.hpp @@ -0,0 +1,169 @@ +#ifndef _DATE_TIME_WRAPPING_INT_HPP__ +#define _DATE_TIME_WRAPPING_INT_HPP__ + +/* Copyright (c) 2002,2003,2005 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland, Bart Garst + * $Date$ + */ + + +namespace boost { +namespace date_time { + +//! A wrapping integer used to support time durations (WARNING: only instantiate with a signed type) +/*! In composite date and time types this type is used to + * wrap at the day boundary. + * Ex: + * A wrapping_int will roll over after nine, and + * roll under below zero. This gives a range of [0,9] + * + * NOTE: it is strongly recommended that wrapping_int2 be used + * instead of wrapping_int as wrapping_int is to be depricated + * at some point soon. + * + * Also Note that warnings will occur if instantiated with an + * unsigned type. Only a signed type should be used! + */ +template +class wrapping_int { +public: + typedef int_type_ int_type; + //typedef overflow_type_ overflow_type; + static int_type wrap_value() {return wrap_val;} + //!Add, return true if wrapped + wrapping_int(int_type v) : value_(v) {} + //! Explicit converion method + int_type as_int() const {return value_;} + operator int_type() const {return value_;} + //!Add, return number of wraps performed + /*! The sign of the returned value will indicate which direction the + * wraps went. Ex: add a negative number and wrapping under could occur, + * this would be indicated by a negative return value. If wrapping over + * took place, a positive value would be returned */ + template< typename IntT > + IntT add(IntT v) + { + int_type remainder = static_cast(v % (wrap_val)); + IntT overflow = static_cast(v / (wrap_val)); + value_ = static_cast(value_ + remainder); + return calculate_wrap(overflow); + } + //! Subtract will return '+d' if wrapping under took place ('d' is the number of wraps) + /*! The sign of the returned value will indicate which direction the + * wraps went (positive indicates wrap under, negative indicates wrap over). + * Ex: subtract a negative number and wrapping over could + * occur, this would be indicated by a negative return value. If + * wrapping under took place, a positive value would be returned. */ + template< typename IntT > + IntT subtract(IntT v) + { + int_type remainder = static_cast(v % (wrap_val)); + IntT underflow = static_cast(-(v / (wrap_val))); + value_ = static_cast(value_ - remainder); + return calculate_wrap(underflow) * -1; + } +private: + int_type value_; + + template< typename IntT > + IntT calculate_wrap(IntT wrap) + { + if ((value_) >= wrap_val) + { + ++wrap; + value_ -= (wrap_val); + } + else if(value_ < 0) + { + --wrap; + value_ += (wrap_val); + } + return wrap; + } + +}; + + +//! A wrapping integer used to wrap around at the top (WARNING: only instantiate with a signed type) +/*! Bad name, quick impl to fix a bug -- fix later!! + * This allows the wrap to restart at a value other than 0. + */ +template +class wrapping_int2 { +public: + typedef int_type_ int_type; + static int_type wrap_value() {return wrap_max;} + static int_type min_value() {return wrap_min;} + /*! If initializing value is out of range of [wrap_min, wrap_max], + * value will be initialized to closest of min or max */ + wrapping_int2(int_type v) : value_(v) { + if(value_ < wrap_min) + { + value_ = wrap_min; + } + if(value_ > wrap_max) + { + value_ = wrap_max; + } + } + //! Explicit converion method + int_type as_int() const {return value_;} + operator int_type() const {return value_;} + //!Add, return number of wraps performed + /*! The sign of the returned value will indicate which direction the + * wraps went. Ex: add a negative number and wrapping under could occur, + * this would be indicated by a negative return value. If wrapping over + * took place, a positive value would be returned */ + template< typename IntT > + IntT add(IntT v) + { + int_type remainder = static_cast(v % (wrap_max - wrap_min + 1)); + IntT overflow = static_cast(v / (wrap_max - wrap_min + 1)); + value_ = static_cast(value_ + remainder); + return calculate_wrap(overflow); + } + //! Subtract will return '-d' if wrapping under took place ('d' is the number of wraps) + /*! The sign of the returned value will indicate which direction the + * wraps went. Ex: subtract a negative number and wrapping over could + * occur, this would be indicated by a positive return value. If + * wrapping under took place, a negative value would be returned */ + template< typename IntT > + IntT subtract(IntT v) + { + int_type remainder = static_cast(v % (wrap_max - wrap_min + 1)); + IntT underflow = static_cast(-(v / (wrap_max - wrap_min + 1))); + value_ = static_cast(value_ - remainder); + return calculate_wrap(underflow); + } + +private: + int_type value_; + + template< typename IntT > + IntT calculate_wrap(IntT wrap) + { + if ((value_) > wrap_max) + { + ++wrap; + value_ -= (wrap_max - wrap_min + 1); + } + else if((value_) < wrap_min) + { + --wrap; + value_ += (wrap_max - wrap_min + 1); + } + return wrap; + } +}; + + + +} } //namespace date_time + + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/date_time/year_month_day.hpp b/thirdparty/source/boost_1_61_0/boost/date_time/year_month_day.hpp new file mode 100644 index 0000000..e1bf2c7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/date_time/year_month_day.hpp @@ -0,0 +1,45 @@ +#ifndef YearMonthDayBase_HPP__ +#define YearMonthDayBase_HPP__ + +/* Copyright (c) 2002,2003 CrystalClear Software, Inc. + * Use, modification and distribution is subject to the + * Boost Software License, Version 1.0. (See accompanying + * file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt) + * Author: Jeff Garland + * $Date$ + */ + +namespace boost { +namespace date_time { + + //! Allow rapid creation of ymd triples of different types + template + struct year_month_day_base { + year_month_day_base(YearType year, + MonthType month, + DayType day); + YearType year; + MonthType month; + DayType day; + typedef YearType year_type; + typedef MonthType month_type; + typedef DayType day_type; + }; + + + //! A basic constructor + template + inline + year_month_day_base::year_month_day_base(YearType y, + MonthType m, + DayType d) : + year(y), + month(m), + day(d) + {} + +} }//namespace date_time + + +#endif + diff --git a/thirdparty/source/boost_1_61_0/boost/detail/atomic_count.hpp b/thirdparty/source/boost_1_61_0/boost/detail/atomic_count.hpp new file mode 100644 index 0000000..5411c7a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/atomic_count.hpp @@ -0,0 +1,21 @@ +#ifndef BOOST_DETAIL_ATOMIC_COUNT_HPP_INCLUDED +#define BOOST_DETAIL_ATOMIC_COUNT_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// boost/detail/atomic_count.hpp - thread/SMP safe reference counter +// +// Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include + +#endif // #ifndef BOOST_DETAIL_ATOMIC_COUNT_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/atomic_redef_macros.hpp b/thirdparty/source/boost_1_61_0/boost/detail/atomic_redef_macros.hpp new file mode 100644 index 0000000..dfd15f5 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/atomic_redef_macros.hpp @@ -0,0 +1,19 @@ +// Copyright (C) 2013 Vicente J. Botet Escriba +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +#if defined(BOOST_INTEL) + +#pragma pop_macro("atomic_compare_exchange") +#pragma pop_macro("atomic_compare_exchange_explicit") +#pragma pop_macro("atomic_exchange") +#pragma pop_macro("atomic_exchange_explicit") +#pragma pop_macro("atomic_is_lock_free") +#pragma pop_macro("atomic_load") +#pragma pop_macro("atomic_load_explicit") +#pragma pop_macro("atomic_store") +#pragma pop_macro("atomic_store_explicit") + +#endif // #if defined(BOOST_INTEL) diff --git a/thirdparty/source/boost_1_61_0/boost/detail/atomic_undef_macros.hpp b/thirdparty/source/boost_1_61_0/boost/detail/atomic_undef_macros.hpp new file mode 100644 index 0000000..18d840a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/atomic_undef_macros.hpp @@ -0,0 +1,39 @@ +// Copyright (C) 2013 Vicente J. Botet Escriba +// +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + + +#if defined(BOOST_INTEL) + +#pragma push_macro("atomic_compare_exchange") +#undef atomic_compare_exchange + +#pragma push_macro("atomic_compare_exchange_explicit") +#undef atomic_compare_exchange_explicit + +#pragma push_macro("atomic_exchange") +#undef atomic_exchange + +#pragma push_macro("atomic_exchange_explicit") +#undef atomic_exchange_explicit + +#pragma push_macro("atomic_is_lock_free") +#undef atomic_is_lock_free + +#pragma push_macro("atomic_load") +#undef atomic_load + +#pragma push_macro("atomic_load_explicit") +#undef atomic_load_explicit + +#pragma push_macro("atomic_store") +#undef atomic_store + +#pragma push_macro("atomic_store_explicit") +#undef atomic_store_explicit + + +#endif // #if defined(BOOST_INTEL) + + diff --git a/thirdparty/source/boost_1_61_0/boost/detail/basic_pointerbuf.hpp b/thirdparty/source/boost_1_61_0/boost/detail/basic_pointerbuf.hpp new file mode 100644 index 0000000..1d8cf37 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/basic_pointerbuf.hpp @@ -0,0 +1,139 @@ +//----------------------------------------------------------------------------- +// boost detail/templated_streams.hpp header file +// See http://www.boost.org for updates, documentation, and revision history. +//----------------------------------------------------------------------------- +// +// Copyright (c) 2013 John Maddock, Antony Polukhin +// +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_DETAIL_BASIC_POINTERBUF_HPP +#define BOOST_DETAIL_BASIC_POINTERBUF_HPP + +// MS compatible compilers support #pragma once +#if defined(_MSC_VER) +# pragma once +#endif + +#include "boost/config.hpp" +#include + +namespace boost { namespace detail { + +// +// class basic_pointerbuf: +// acts as a stream buffer which wraps around a pair of pointers: +// +template +class basic_pointerbuf : public BufferT { +protected: + typedef BufferT base_type; + typedef basic_pointerbuf this_type; + typedef typename base_type::int_type int_type; + typedef typename base_type::char_type char_type; + typedef typename base_type::pos_type pos_type; + typedef ::std::streamsize streamsize; + typedef typename base_type::off_type off_type; + +public: + basic_pointerbuf() : base_type() { setbuf(0, 0); } + const charT* getnext() { return this->gptr(); } + +#ifndef BOOST_NO_USING_TEMPLATE + using base_type::pptr; + using base_type::pbase; +#else + charT* pptr() const { return base_type::pptr(); } + charT* pbase() const { return base_type::pbase(); } +#endif + +protected: + // VC mistakenly assumes that `setbuf` and other functions are not referenced. + // Marking those functions with `inline` suppresses the warnings. + // There must be no harm from marking virtual functions as inline: inline virtual + // call can be inlined ONLY when the compiler knows the "exact class". + inline base_type* setbuf(char_type* s, streamsize n); + inline typename this_type::pos_type seekpos(pos_type sp, ::std::ios_base::openmode which); + inline typename this_type::pos_type seekoff(off_type off, ::std::ios_base::seekdir way, ::std::ios_base::openmode which); + +private: + basic_pointerbuf& operator=(const basic_pointerbuf&); + basic_pointerbuf(const basic_pointerbuf&); +}; + +template +BufferT* +basic_pointerbuf::setbuf(char_type* s, streamsize n) +{ + this->setg(s, s, s + n); + return this; +} + +template +typename basic_pointerbuf::pos_type +basic_pointerbuf::seekoff(off_type off, ::std::ios_base::seekdir way, ::std::ios_base::openmode which) +{ + typedef typename boost::int_t::least cast_type; + + if(which & ::std::ios_base::out) + return pos_type(off_type(-1)); + std::ptrdiff_t size = this->egptr() - this->eback(); + std::ptrdiff_t pos = this->gptr() - this->eback(); + charT* g = this->eback(); + switch(static_cast(way)) + { + case ::std::ios_base::beg: + if((off < 0) || (off > size)) + return pos_type(off_type(-1)); + else + this->setg(g, g + off, g + size); + break; + case ::std::ios_base::end: + if((off < 0) || (off > size)) + return pos_type(off_type(-1)); + else + this->setg(g, g + size - off, g + size); + break; + case ::std::ios_base::cur: + { + std::ptrdiff_t newpos = static_cast(pos + off); + if((newpos < 0) || (newpos > size)) + return pos_type(off_type(-1)); + else + this->setg(g, g + newpos, g + size); + break; + } + default: ; + } +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable:4244) +#endif + return static_cast(this->gptr() - this->eback()); +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif +} + +template +typename basic_pointerbuf::pos_type +basic_pointerbuf::seekpos(pos_type sp, ::std::ios_base::openmode which) +{ + if(which & ::std::ios_base::out) + return pos_type(off_type(-1)); + off_type size = static_cast(this->egptr() - this->eback()); + charT* g = this->eback(); + if(off_type(sp) <= size) + { + this->setg(g, g + off_type(sp), g + size); + } + return pos_type(off_type(-1)); +} + +}} // namespace boost::detail + +#endif // BOOST_DETAIL_BASIC_POINTERBUF_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/detail/call_traits.hpp b/thirdparty/source/boost_1_61_0/boost/detail/call_traits.hpp new file mode 100644 index 0000000..36dea00 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/call_traits.hpp @@ -0,0 +1,172 @@ +// (C) Copyright Steve Cleary, Beman Dawes, Howard Hinnant & John Maddock 2000. +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). +// +// See http://www.boost.org/libs/utility for most recent version including documentation. + +// call_traits: defines typedefs for function usage +// (see libs/utility/call_traits.htm) + +/* Release notes: + 23rd July 2000: + Fixed array specialization. (JM) + Added Borland specific fixes for reference types + (issue raised by Steve Cleary). +*/ + +#ifndef BOOST_DETAIL_CALL_TRAITS_HPP +#define BOOST_DETAIL_CALL_TRAITS_HPP + +#ifndef BOOST_CONFIG_HPP +#include +#endif +#include + +#include +#include +#include +#include + +namespace boost{ + +namespace detail{ + +template +struct ct_imp2 +{ + typedef const T& param_type; +}; + +template +struct ct_imp2 +{ + typedef const T param_type; +}; + +template +struct ct_imp +{ + typedef const T& param_type; +}; + +template +struct ct_imp +{ + typedef typename ct_imp2::param_type param_type; +}; + +template +struct ct_imp +{ + typedef typename ct_imp2::param_type param_type; +}; + +template +struct ct_imp +{ + typedef const T param_type; +}; + +} + +template +struct call_traits +{ +public: + typedef T value_type; + typedef T& reference; + typedef const T& const_reference; + // + // C++ Builder workaround: we should be able to define a compile time + // constant and pass that as a single template parameter to ct_imp, + // however compiler bugs prevent this - instead pass three bool's to + // ct_imp and add an extra partial specialisation + // of ct_imp to handle the logic. (JM) + typedef typename boost::detail::ct_imp< + T, + ::boost::is_pointer::value, + ::boost::is_arithmetic::value, + ::boost::is_enum::value + >::param_type param_type; +}; + +template +struct call_traits +{ + typedef T& value_type; + typedef T& reference; + typedef const T& const_reference; + typedef T& param_type; // hh removed const +}; + +#if BOOST_WORKAROUND( __BORLANDC__, < 0x5A0 ) +// these are illegal specialisations; cv-qualifies applied to +// references have no effect according to [8.3.2p1], +// C++ Builder requires them though as it treats cv-qualified +// references as distinct types... +template +struct call_traits +{ + typedef T& value_type; + typedef T& reference; + typedef const T& const_reference; + typedef T& param_type; // hh removed const +}; +template +struct call_traits +{ + typedef T& value_type; + typedef T& reference; + typedef const T& const_reference; + typedef T& param_type; // hh removed const +}; +template +struct call_traits +{ + typedef T& value_type; + typedef T& reference; + typedef const T& const_reference; + typedef T& param_type; // hh removed const +}; + +template +struct call_traits< T * > +{ + typedef T * value_type; + typedef T * & reference; + typedef T * const & const_reference; + typedef T * const param_type; // hh removed const +}; +#endif +#if !defined(BOOST_NO_ARRAY_TYPE_SPECIALIZATIONS) +template +struct call_traits +{ +private: + typedef T array_type[N]; +public: + // degrades array to pointer: + typedef const T* value_type; + typedef array_type& reference; + typedef const array_type& const_reference; + typedef const T* const param_type; +}; + +template +struct call_traits +{ +private: + typedef const T array_type[N]; +public: + // degrades array to pointer: + typedef const T* value_type; + typedef array_type& reference; + typedef const array_type& const_reference; + typedef const T* const param_type; +}; +#endif + +} + +#endif // BOOST_DETAIL_CALL_TRAITS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/compressed_pair.hpp b/thirdparty/source/boost_1_61_0/boost/detail/compressed_pair.hpp new file mode 100644 index 0000000..5dc21e2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/compressed_pair.hpp @@ -0,0 +1,443 @@ +// (C) Copyright Steve Cleary, Beman Dawes, Howard Hinnant & John Maddock 2000. +// Use, modification and distribution are subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt). +// +// See http://www.boost.org/libs/utility for most recent version including documentation. + +// compressed_pair: pair that "compresses" empty members +// (see libs/utility/doc/html/compressed_pair.html) +// +// JM changes 25 Jan 2004: +// For the case where T1 == T2 and both are empty, then first() and second() +// should return different objects. +// JM changes 25 Jan 2000: +// Removed default arguments from compressed_pair_switch to get +// C++ Builder 4 to accept them +// rewriten swap to get gcc and C++ builder to compile. +// added partial specialisations for case T1 == T2 to avoid duplicate constructor defs. + +#ifndef BOOST_DETAIL_COMPRESSED_PAIR_HPP +#define BOOST_DETAIL_COMPRESSED_PAIR_HPP + +#include + +#include +#include +#include +#include + +#ifdef BOOST_MSVC +# pragma warning(push) +# pragma warning(disable:4512) +#endif +namespace boost +{ + +template +class compressed_pair; + + +// compressed_pair + +namespace details +{ + // JM altered 26 Jan 2000: + template + struct compressed_pair_switch; + + template + struct compressed_pair_switch + {static const int value = 0;}; + + template + struct compressed_pair_switch + {static const int value = 3;}; + + template + struct compressed_pair_switch + {static const int value = 1;}; + + template + struct compressed_pair_switch + {static const int value = 2;}; + + template + struct compressed_pair_switch + {static const int value = 4;}; + + template + struct compressed_pair_switch + {static const int value = 5;}; + + template class compressed_pair_imp; + +#ifdef __GNUC__ + // workaround for GCC (JM): + using std::swap; +#endif + // + // can't call unqualified swap from within classname::swap + // as Koenig lookup rules will find only the classname::swap + // member function not the global declaration, so use cp_swap + // as a forwarding function (JM): + template + inline void cp_swap(T& t1, T& t2) + { +#ifndef __GNUC__ + using std::swap; +#endif + swap(t1, t2); + } + + // 0 derive from neither + + template + class compressed_pair_imp + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : first_(x), second_(y) {} + + compressed_pair_imp(first_param_type x) + : first_(x) {} + + compressed_pair_imp(second_param_type y) + : second_(y) {} + + first_reference first() {return first_;} + first_const_reference first() const {return first_;} + + second_reference second() {return second_;} + second_const_reference second() const {return second_;} + + void swap(::boost::compressed_pair& y) + { + cp_swap(first_, y.first()); + cp_swap(second_, y.second()); + } + private: + first_type first_; + second_type second_; + }; + + // 1 derive from T1 + + template + class compressed_pair_imp + : protected ::boost::remove_cv::type + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : first_type(x), second_(y) {} + + compressed_pair_imp(first_param_type x) + : first_type(x) {} + + compressed_pair_imp(second_param_type y) + : second_(y) {} + + first_reference first() {return *this;} + first_const_reference first() const {return *this;} + + second_reference second() {return second_;} + second_const_reference second() const {return second_;} + + void swap(::boost::compressed_pair& y) + { + // no need to swap empty base class: + cp_swap(second_, y.second()); + } + private: + second_type second_; + }; + + // 2 derive from T2 + + template + class compressed_pair_imp + : protected ::boost::remove_cv::type + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : second_type(y), first_(x) {} + + compressed_pair_imp(first_param_type x) + : first_(x) {} + + compressed_pair_imp(second_param_type y) + : second_type(y) {} + + first_reference first() {return first_;} + first_const_reference first() const {return first_;} + + second_reference second() {return *this;} + second_const_reference second() const {return *this;} + + void swap(::boost::compressed_pair& y) + { + // no need to swap empty base class: + cp_swap(first_, y.first()); + } + + private: + first_type first_; + }; + + // 3 derive from T1 and T2 + + template + class compressed_pair_imp + : protected ::boost::remove_cv::type, + protected ::boost::remove_cv::type + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : first_type(x), second_type(y) {} + + compressed_pair_imp(first_param_type x) + : first_type(x) {} + + compressed_pair_imp(second_param_type y) + : second_type(y) {} + + first_reference first() {return *this;} + first_const_reference first() const {return *this;} + + second_reference second() {return *this;} + second_const_reference second() const {return *this;} + // + // no need to swap empty bases: + void swap(::boost::compressed_pair&) {} + }; + + // JM + // 4 T1 == T2, T1 and T2 both empty + // Originally this did not store an instance of T2 at all + // but that led to problems beause it meant &x.first() == &x.second() + // which is not true for any other kind of pair, so now we store an instance + // of T2 just in case the user is relying on first() and second() returning + // different objects (albeit both empty). + template + class compressed_pair_imp + : protected ::boost::remove_cv::type + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : first_type(x), m_second(y) {} + + compressed_pair_imp(first_param_type x) + : first_type(x), m_second(x) {} + + first_reference first() {return *this;} + first_const_reference first() const {return *this;} + + second_reference second() {return m_second;} + second_const_reference second() const {return m_second;} + + void swap(::boost::compressed_pair&) {} + private: + T2 m_second; + }; + + // 5 T1 == T2 and are not empty: //JM + + template + class compressed_pair_imp + { + public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair_imp() {} + + compressed_pair_imp(first_param_type x, second_param_type y) + : first_(x), second_(y) {} + + compressed_pair_imp(first_param_type x) + : first_(x), second_(x) {} + + first_reference first() {return first_;} + first_const_reference first() const {return first_;} + + second_reference second() {return second_;} + second_const_reference second() const {return second_;} + + void swap(::boost::compressed_pair& y) + { + cp_swap(first_, y.first()); + cp_swap(second_, y.second()); + } + private: + first_type first_; + second_type second_; + }; + +} // details + +template +class compressed_pair + : private ::boost::details::compressed_pair_imp::type, typename remove_cv::type>::value, + ::boost::is_empty::value, + ::boost::is_empty::value>::value> +{ +private: + typedef details::compressed_pair_imp::type, typename remove_cv::type>::value, + ::boost::is_empty::value, + ::boost::is_empty::value>::value> base; +public: + typedef T1 first_type; + typedef T2 second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair() : base() {} + compressed_pair(first_param_type x, second_param_type y) : base(x, y) {} + explicit compressed_pair(first_param_type x) : base(x) {} + explicit compressed_pair(second_param_type y) : base(y) {} + + first_reference first() {return base::first();} + first_const_reference first() const {return base::first();} + + second_reference second() {return base::second();} + second_const_reference second() const {return base::second();} + + void swap(compressed_pair& y) { base::swap(y); } +}; + +// JM +// Partial specialisation for case where T1 == T2: +// +template +class compressed_pair + : private details::compressed_pair_imp::type, typename remove_cv::type>::value, + ::boost::is_empty::value, + ::boost::is_empty::value>::value> +{ +private: + typedef details::compressed_pair_imp::type, typename remove_cv::type>::value, + ::boost::is_empty::value, + ::boost::is_empty::value>::value> base; +public: + typedef T first_type; + typedef T second_type; + typedef typename call_traits::param_type first_param_type; + typedef typename call_traits::param_type second_param_type; + typedef typename call_traits::reference first_reference; + typedef typename call_traits::reference second_reference; + typedef typename call_traits::const_reference first_const_reference; + typedef typename call_traits::const_reference second_const_reference; + + compressed_pair() : base() {} + compressed_pair(first_param_type x, second_param_type y) : base(x, y) {} +#if !(defined(__SUNPRO_CC) && (__SUNPRO_CC <= 0x530)) + explicit +#endif + compressed_pair(first_param_type x) : base(x) {} + + first_reference first() {return base::first();} + first_const_reference first() const {return base::first();} + + second_reference second() {return base::second();} + second_const_reference second() const {return base::second();} + + void swap(::boost::compressed_pair& y) { base::swap(y); } +}; + +template +inline +void +swap(compressed_pair& x, compressed_pair& y) +{ + x.swap(y); +} + +} // boost + +#ifdef BOOST_MSVC +# pragma warning(pop) +#endif + +#endif // BOOST_DETAIL_COMPRESSED_PAIR_HPP + diff --git a/thirdparty/source/boost_1_61_0/boost/detail/container_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/detail/container_fwd.hpp new file mode 100644 index 0000000..04ce972 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/container_fwd.hpp @@ -0,0 +1,157 @@ + +// Copyright 2005-2011 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Note: if you change this include guard, you also need to change +// container_fwd_compile_fail.cpp +#if !defined(BOOST_DETAIL_CONTAINER_FWD_HPP) +#define BOOST_DETAIL_CONTAINER_FWD_HPP + +#if defined(_MSC_VER) && \ + !defined(BOOST_DETAIL_TEST_CONFIG_ONLY) +# pragma once +#endif + +#include +#include + +//////////////////////////////////////////////////////////////////////////////// +// // +// Define BOOST_DETAIL_NO_CONTAINER_FWD if you don't want this header to // +// forward declare standard containers. // +// // +// BOOST_DETAIL_CONTAINER_FWD to make it foward declare containers even if it // +// normally doesn't. // +// // +// BOOST_DETAIL_NO_CONTAINER_FWD overrides BOOST_DETAIL_CONTAINER_FWD. // +// // +//////////////////////////////////////////////////////////////////////////////// + +#if !defined(BOOST_DETAIL_NO_CONTAINER_FWD) +# if defined(BOOST_DETAIL_CONTAINER_FWD) + // Force forward declarations. +# elif defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) + // STLport +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif defined(__LIBCOMO__) + // Comeau STL: +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) + // Rogue Wave library: +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif defined(_LIBCPP_VERSION) + // libc++ +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif defined(__GLIBCPP__) || defined(__GLIBCXX__) + // GNU libstdc++ 3 + // + // Disable forwarding for all recent versions, as the library has a + // versioned namespace mode, and I don't know how to detect it. +# if __GLIBCXX__ >= 20070513 \ + || defined(_GLIBCXX_DEBUG) \ + || defined(_GLIBCXX_PARALLEL) \ + || defined(_GLIBCXX_PROFILE) +# define BOOST_DETAIL_NO_CONTAINER_FWD +# else +# if defined(__GLIBCXX__) && __GLIBCXX__ >= 20040530 +# define BOOST_CONTAINER_FWD_COMPLEX_STRUCT +# endif +# endif +# elif defined(__STL_CONFIG_H) + // generic SGI STL + // + // Forward declaration seems to be okay, but it has a couple of odd + // implementations. +# define BOOST_CONTAINER_FWD_BAD_BITSET +# if !defined(__STL_NON_TYPE_TMPL_PARAM_BUG) +# define BOOST_CONTAINER_FWD_BAD_DEQUE +# endif +# elif defined(__MSL_CPP__) + // MSL standard lib: +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif defined(__IBMCPP__) + // The default VACPP std lib, forward declaration seems to be fine. +# elif defined(MSIPL_COMPILE_H) + // Modena C++ standard library +# define BOOST_DETAIL_NO_CONTAINER_FWD +# elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) + // Dinkumware Library (this has to appear after any possible replacement + // libraries) +# else +# define BOOST_DETAIL_NO_CONTAINER_FWD +# endif +#endif + +#if !defined(BOOST_DETAIL_TEST_CONFIG_ONLY) + +#if defined(BOOST_DETAIL_NO_CONTAINER_FWD) && \ + !defined(BOOST_DETAIL_TEST_FORCE_CONTAINER_FWD) + +#include +#include +#include +#include +#include +#include +#include +#include + +#else + +#include + +#if defined(BOOST_CONTAINER_FWD_BAD_DEQUE) +#include +#endif + +#if defined(BOOST_CONTAINER_FWD_BAD_BITSET) +#include +#endif + +#if defined(BOOST_MSVC) +#pragma warning(push) +#pragma warning(disable:4099) // struct/class mismatch in fwd declarations +#endif + +namespace std +{ + template class allocator; + template class basic_string; + + template struct char_traits; + +#if defined(BOOST_CONTAINER_FWD_COMPLEX_STRUCT) + template struct complex; +#else + template class complex; +#endif + +#if !defined(BOOST_CONTAINER_FWD_BAD_DEQUE) + template class deque; +#endif + + template class list; + template class vector; + template class map; + template + class multimap; + template class set; + template class multiset; + +#if !defined(BOOST_CONTAINER_FWD_BAD_BITSET) + template class bitset; +#endif + template struct pair; +} + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif // BOOST_DETAIL_NO_CONTAINER_FWD && + // !defined(BOOST_DETAIL_TEST_FORCE_CONTAINER_FWD) + +#endif // BOOST_DETAIL_TEST_CONFIG_ONLY + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/detail/endian.hpp b/thirdparty/source/boost_1_61_0/boost/detail/endian.hpp new file mode 100644 index 0000000..f576c26 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/endian.hpp @@ -0,0 +1,11 @@ +// Copyright 2013 Rene Rivera +// Distributed under the Boost Software License, Version 1.0. (See accompany- +// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_DETAIL_ENDIAN_HPP +#define BOOST_DETAIL_ENDIAN_HPP + +// Use the Predef library for the detection of endianess. +#include + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/detail/fenv.hpp b/thirdparty/source/boost_1_61_0/boost/detail/fenv.hpp new file mode 100644 index 0000000..b268f5c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/fenv.hpp @@ -0,0 +1,101 @@ +/*============================================================================= + Copyright (c) 2010 Bryce Lelbach + + Distributed under the Boost Software License, Version 1.0. (See accompanying + file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +=============================================================================*/ + +#include + +#if defined(BOOST_NO_FENV_H) + #error This platform does not have a floating point environment +#endif + +#if !defined(BOOST_DETAIL_FENV_HPP) +#define BOOST_DETAIL_FENV_HPP + +/* If we're using clang + glibc, we have to get hacky. + * See http://llvm.org/bugs/show_bug.cgi?id=6907 */ +#if defined(__clang__) && (__clang_major__ < 3) && \ + defined(__GNU_LIBRARY__) && /* up to version 5 */ \ + defined(__GLIBC__) && /* version 6 + */ \ + !defined(_FENV_H) + #define _FENV_H + + #include + #include + + extern "C" { + extern int fegetexceptflag (fexcept_t*, int) __THROW; + extern int fesetexceptflag (__const fexcept_t*, int) __THROW; + extern int feclearexcept (int) __THROW; + extern int feraiseexcept (int) __THROW; + extern int fetestexcept (int) __THROW; + extern int fegetround (void) __THROW; + extern int fesetround (int) __THROW; + extern int fegetenv (fenv_t*) __THROW; + extern int fesetenv (__const fenv_t*) __THROW; + extern int feupdateenv (__const fenv_t*) __THROW; + extern int feholdexcept (fenv_t*) __THROW; + + #ifdef __USE_GNU + extern int feenableexcept (int) __THROW; + extern int fedisableexcept (int) __THROW; + extern int fegetexcept (void) __THROW; + #endif + } + + namespace std { namespace tr1 { + using ::fenv_t; + using ::fexcept_t; + using ::fegetexceptflag; + using ::fesetexceptflag; + using ::feclearexcept; + using ::feraiseexcept; + using ::fetestexcept; + using ::fegetround; + using ::fesetround; + using ::fegetenv; + using ::fesetenv; + using ::feupdateenv; + using ::feholdexcept; + } } + +#elif defined(__MINGW32__) && defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) >= 408 + + // MinGW (32-bit) has a bug in mingw32/bits/c++config.h, it does not define _GLIBCXX_HAVE_FENV_H, + // which prevents the C fenv.h header contents to be included in the C++ wrapper header fenv.h. This is at least + // the case with gcc 4.8.1 packages tested so far, up to 4.8.1-4. Note that there is no issue with + // MinGW-w64. + // To work around the bug we avoid including the C++ wrapper header and include the C header directly + // and import all relevant symbols into std:: ourselves. + + #include <../include/fenv.h> + + namespace std { + using ::fenv_t; + using ::fexcept_t; + using ::fegetexceptflag; + using ::fesetexceptflag; + using ::feclearexcept; + using ::feraiseexcept; + using ::fetestexcept; + using ::fegetround; + using ::fesetround; + using ::fegetenv; + using ::fesetenv; + using ::feupdateenv; + using ::feholdexcept; + } + +#else /* if we're not using GNU's C stdlib, fenv.h should work with clang */ + + #if defined(__SUNPRO_CC) /* lol suncc */ + #include + #endif + + #include + +#endif + +#endif /* BOOST_DETAIL_FENV_HPP */ diff --git a/thirdparty/source/boost_1_61_0/boost/detail/indirect_traits.hpp b/thirdparty/source/boost_1_61_0/boost/detail/indirect_traits.hpp new file mode 100644 index 0000000..6294e40 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/indirect_traits.hpp @@ -0,0 +1,204 @@ +// Copyright David Abrahams 2002. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#ifndef INDIRECT_TRAITS_DWA2002131_HPP +# define INDIRECT_TRAITS_DWA2002131_HPP +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# include + +# include +# include +# include +# include +# include +# include + + +namespace boost { namespace detail { + +namespace indirect_traits { + +template +struct is_reference_to_const : mpl::false_ +{ +}; + +template +struct is_reference_to_const : mpl::true_ +{ +}; + +# if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround +template +struct is_reference_to_const : mpl::true_ +{ +}; +# endif + +template +struct is_reference_to_function : mpl::false_ +{ +}; + +template +struct is_reference_to_function : is_function +{ +}; + +template +struct is_pointer_to_function : mpl::false_ +{ +}; + +// There's no such thing as a pointer-to-cv-function, so we don't need +// specializations for those +template +struct is_pointer_to_function : is_function +{ +}; + +template +struct is_reference_to_member_function_pointer_impl : mpl::false_ +{ +}; + +template +struct is_reference_to_member_function_pointer_impl + : is_member_function_pointer::type> +{ +}; + + +template +struct is_reference_to_member_function_pointer + : is_reference_to_member_function_pointer_impl +{ + BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_reference_to_member_function_pointer,(T)) +}; + +template +struct is_reference_to_function_pointer_aux + : mpl::and_< + is_reference + , is_pointer_to_function< + typename remove_cv< + typename remove_reference::type + >::type + > + > +{ + // There's no such thing as a pointer-to-cv-function, so we don't need specializations for those +}; + +template +struct is_reference_to_function_pointer + : mpl::if_< + is_reference_to_function + , mpl::false_ + , is_reference_to_function_pointer_aux + >::type +{ +}; + +template +struct is_reference_to_non_const + : mpl::and_< + is_reference + , mpl::not_< + is_reference_to_const + > + > +{ +}; + +template +struct is_reference_to_volatile : mpl::false_ +{ +}; + +template +struct is_reference_to_volatile : mpl::true_ +{ +}; + +# if defined(BOOST_MSVC) && _MSC_FULL_VER <= 13102140 // vc7.01 alpha workaround +template +struct is_reference_to_volatile : mpl::true_ +{ +}; +# endif + + +template +struct is_reference_to_pointer : mpl::false_ +{ +}; + +template +struct is_reference_to_pointer : mpl::true_ +{ +}; + +template +struct is_reference_to_pointer : mpl::true_ +{ +}; + +template +struct is_reference_to_pointer : mpl::true_ +{ +}; + +template +struct is_reference_to_pointer : mpl::true_ +{ +}; + +template +struct is_reference_to_class + : mpl::and_< + is_reference + , is_class< + typename remove_cv< + typename remove_reference::type + >::type + > + > +{ + BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_reference_to_class,(T)) +}; + +template +struct is_pointer_to_class + : mpl::and_< + is_pointer + , is_class< + typename remove_cv< + typename remove_pointer::type + >::type + > + > +{ + BOOST_MPL_AUX_LAMBDA_SUPPORT(1,is_pointer_to_class,(T)) +}; + + +} + +using namespace indirect_traits; + +}} // namespace boost::python::detail + +#endif // INDIRECT_TRAITS_DWA2002131_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/interlocked.hpp b/thirdparty/source/boost_1_61_0/boost/detail/interlocked.hpp new file mode 100644 index 0000000..2c91ce2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/interlocked.hpp @@ -0,0 +1,218 @@ +#ifndef BOOST_DETAIL_INTERLOCKED_HPP_INCLUDED +#define BOOST_DETAIL_INTERLOCKED_HPP_INCLUDED + +// +// boost/detail/interlocked.hpp +// +// Copyright 2005 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// + +#include + +// MS compatible compilers support #pragma once +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined( BOOST_USE_WINDOWS_H ) + +# include + +# define BOOST_INTERLOCKED_INCREMENT InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD InterlockedExchangeAdd +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER InterlockedCompareExchangePointer +# define BOOST_INTERLOCKED_EXCHANGE_POINTER InterlockedExchangePointer + +#elif defined( BOOST_USE_INTRIN_H ) + +#include + +# define BOOST_INTERLOCKED_INCREMENT _InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT _InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE _InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE _InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD _InterlockedExchangeAdd + +# if defined(_M_IA64) || defined(_M_AMD64) || defined(__x86_64__) || defined(__x86_64) + +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER _InterlockedCompareExchangePointer +# define BOOST_INTERLOCKED_EXCHANGE_POINTER _InterlockedExchangePointer + +# else + +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest,exchange,compare) \ + ((void*)BOOST_INTERLOCKED_COMPARE_EXCHANGE((long volatile*)(dest),(long)(exchange),(long)(compare))) +# define BOOST_INTERLOCKED_EXCHANGE_POINTER(dest,exchange) \ + ((void*)BOOST_INTERLOCKED_EXCHANGE((long volatile*)(dest),(long)(exchange))) + +# endif + +#elif defined(_WIN32_WCE) + +#if _WIN32_WCE >= 0x600 + +extern "C" long __cdecl _InterlockedIncrement( long volatile * ); +extern "C" long __cdecl _InterlockedDecrement( long volatile * ); +extern "C" long __cdecl _InterlockedCompareExchange( long volatile *, long, long ); +extern "C" long __cdecl _InterlockedExchange( long volatile *, long ); +extern "C" long __cdecl _InterlockedExchangeAdd( long volatile *, long ); + +# define BOOST_INTERLOCKED_INCREMENT _InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT _InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE _InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE _InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD _InterlockedExchangeAdd + +#else +// under Windows CE we still have old-style Interlocked* functions + +extern "C" long __cdecl InterlockedIncrement( long* ); +extern "C" long __cdecl InterlockedDecrement( long* ); +extern "C" long __cdecl InterlockedCompareExchange( long*, long, long ); +extern "C" long __cdecl InterlockedExchange( long*, long ); +extern "C" long __cdecl InterlockedExchangeAdd( long*, long ); + +# define BOOST_INTERLOCKED_INCREMENT InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD InterlockedExchangeAdd + +#endif + +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest,exchange,compare) \ + ((void*)BOOST_INTERLOCKED_COMPARE_EXCHANGE((long*)(dest),(long)(exchange),(long)(compare))) +# define BOOST_INTERLOCKED_EXCHANGE_POINTER(dest,exchange) \ + ((void*)BOOST_INTERLOCKED_EXCHANGE((long*)(dest),(long)(exchange))) + +#elif defined( BOOST_MSVC ) || defined( BOOST_INTEL_WIN ) + +#if defined( BOOST_MSVC ) && BOOST_MSVC >= 1400 + +#include + +#else + +# if defined( __CLRCALL_PURE_OR_CDECL ) +# define BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL __CLRCALL_PURE_OR_CDECL +# else +# define BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL __cdecl +# endif + +extern "C" long BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL _InterlockedIncrement( long volatile * ); +extern "C" long BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL _InterlockedDecrement( long volatile * ); +extern "C" long BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL _InterlockedCompareExchange( long volatile *, long, long ); +extern "C" long BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL _InterlockedExchange( long volatile *, long ); +extern "C" long BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL _InterlockedExchangeAdd( long volatile *, long ); + +# undef BOOST_INTERLOCKED_CLRCALL_PURE_OR_CDECL + +# if defined( BOOST_MSVC ) && BOOST_MSVC >= 1310 +# pragma intrinsic( _InterlockedIncrement ) +# pragma intrinsic( _InterlockedDecrement ) +# pragma intrinsic( _InterlockedCompareExchange ) +# pragma intrinsic( _InterlockedExchange ) +# pragma intrinsic( _InterlockedExchangeAdd ) +# endif + +#endif + +# if defined(_M_IA64) || defined(_M_AMD64) + +extern "C" void* __cdecl _InterlockedCompareExchangePointer( void* volatile *, void*, void* ); +extern "C" void* __cdecl _InterlockedExchangePointer( void* volatile *, void* ); + +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER _InterlockedCompareExchangePointer +# define BOOST_INTERLOCKED_EXCHANGE_POINTER _InterlockedExchangePointer + +# else + +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest,exchange,compare) \ + ((void*)BOOST_INTERLOCKED_COMPARE_EXCHANGE((long volatile*)(dest),(long)(exchange),(long)(compare))) +# define BOOST_INTERLOCKED_EXCHANGE_POINTER(dest,exchange) \ + ((void*)BOOST_INTERLOCKED_EXCHANGE((long volatile*)(dest),(long)(exchange))) + +# endif + +# define BOOST_INTERLOCKED_INCREMENT _InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT _InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE _InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE _InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD _InterlockedExchangeAdd + +// Unlike __MINGW64__, __MINGW64_VERSION_MAJOR is defined by MinGW-w64 for both 32 and 64-bit targets. +#elif defined(__MINGW64_VERSION_MAJOR) + +// MinGW-w64 provides intrin.h for both 32 and 64-bit targets. +#include + +# define BOOST_INTERLOCKED_INCREMENT _InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT _InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE _InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE _InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD _InterlockedExchangeAdd +# if defined(__x86_64__) || defined(__x86_64) +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER _InterlockedCompareExchangePointer +# define BOOST_INTERLOCKED_EXCHANGE_POINTER _InterlockedExchangePointer +# else +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest,exchange,compare) \ + ((void*)BOOST_INTERLOCKED_COMPARE_EXCHANGE((long volatile*)(dest),(long)(exchange),(long)(compare))) +# define BOOST_INTERLOCKED_EXCHANGE_POINTER(dest,exchange) \ + ((void*)BOOST_INTERLOCKED_EXCHANGE((long volatile*)(dest),(long)(exchange))) +# endif + +#elif defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) || defined( __CYGWIN__ ) + +#define BOOST_INTERLOCKED_IMPORT __declspec(dllimport) + +namespace boost +{ + +namespace detail +{ + +extern "C" BOOST_INTERLOCKED_IMPORT long __stdcall InterlockedIncrement( long volatile * ); +extern "C" BOOST_INTERLOCKED_IMPORT long __stdcall InterlockedDecrement( long volatile * ); +extern "C" BOOST_INTERLOCKED_IMPORT long __stdcall InterlockedCompareExchange( long volatile *, long, long ); +extern "C" BOOST_INTERLOCKED_IMPORT long __stdcall InterlockedExchange( long volatile *, long ); +extern "C" BOOST_INTERLOCKED_IMPORT long __stdcall InterlockedExchangeAdd( long volatile *, long ); + +# if defined(_M_IA64) || defined(_M_AMD64) +extern "C" BOOST_INTERLOCKED_IMPORT void* __stdcall InterlockedCompareExchangePointer( void* volatile *, void*, void* ); +extern "C" BOOST_INTERLOCKED_IMPORT void* __stdcall InterlockedExchangePointer( void* volatile *, void* ); +# endif + +} // namespace detail + +} // namespace boost + +# define BOOST_INTERLOCKED_INCREMENT ::boost::detail::InterlockedIncrement +# define BOOST_INTERLOCKED_DECREMENT ::boost::detail::InterlockedDecrement +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE ::boost::detail::InterlockedCompareExchange +# define BOOST_INTERLOCKED_EXCHANGE ::boost::detail::InterlockedExchange +# define BOOST_INTERLOCKED_EXCHANGE_ADD ::boost::detail::InterlockedExchangeAdd + +# if defined(_M_IA64) || defined(_M_AMD64) +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER ::boost::detail::InterlockedCompareExchangePointer +# define BOOST_INTERLOCKED_EXCHANGE_POINTER ::boost::detail::InterlockedExchangePointer +# else +# define BOOST_INTERLOCKED_COMPARE_EXCHANGE_POINTER(dest,exchange,compare) \ + ((void*)BOOST_INTERLOCKED_COMPARE_EXCHANGE((long volatile*)(dest),(long)(exchange),(long)(compare))) +# define BOOST_INTERLOCKED_EXCHANGE_POINTER(dest,exchange) \ + ((void*)BOOST_INTERLOCKED_EXCHANGE((long volatile*)(dest),(long)(exchange))) +# endif + +#else + +# error "Interlocked intrinsics not available" + +#endif + +#endif // #ifndef BOOST_DETAIL_INTERLOCKED_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/iterator.hpp b/thirdparty/source/boost_1_61_0/boost/detail/iterator.hpp new file mode 100644 index 0000000..c2e8f1e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/iterator.hpp @@ -0,0 +1,26 @@ +// (C) Copyright David Abrahams 2002. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef ITERATOR_DWA122600_HPP_ +#define ITERATOR_DWA122600_HPP_ + +// This header is obsolete and will be deprecated. + +#include + +namespace boost +{ + +namespace detail +{ + +using std::iterator_traits; +using std::distance; + +} // namespace detail + +} // namespace boost + +#endif // ITERATOR_DWA122600_HPP_ diff --git a/thirdparty/source/boost_1_61_0/boost/detail/lcast_precision.hpp b/thirdparty/source/boost_1_61_0/boost/detail/lcast_precision.hpp new file mode 100644 index 0000000..93abce1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/lcast_precision.hpp @@ -0,0 +1,184 @@ +// Copyright Alexander Nasonov & Paul A. Bristow 2006. + +// Use, modification and distribution are subject to the +// Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_DETAIL_LCAST_PRECISION_HPP_INCLUDED +#define BOOST_DETAIL_LCAST_PRECISION_HPP_INCLUDED + +#include +#include +#include + +#include +#include + +#ifndef BOOST_NO_IS_ABSTRACT +// Fix for SF:1358600 - lexical_cast & pure virtual functions & VC 8 STL +#include +#include +#endif + +#if defined(BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS) || \ + (defined(BOOST_MSVC) && (BOOST_MSVC<1310)) + +#define BOOST_LCAST_NO_COMPILE_TIME_PRECISION +#endif + +#ifdef BOOST_LCAST_NO_COMPILE_TIME_PRECISION +#include +#else +#include +#endif + +namespace boost { namespace detail { + +class lcast_abstract_stub {}; + +#ifndef BOOST_LCAST_NO_COMPILE_TIME_PRECISION +// Calculate an argument to pass to std::ios_base::precision from +// lexical_cast. See alternative implementation for broken standard +// libraries in lcast_get_precision below. Keep them in sync, please. +template +struct lcast_precision +{ +#ifdef BOOST_NO_IS_ABSTRACT + typedef std::numeric_limits limits; // No fix for SF:1358600. +#else + typedef BOOST_DEDUCED_TYPENAME boost::mpl::if_< + boost::is_abstract + , std::numeric_limits + , std::numeric_limits + >::type limits; +#endif + + BOOST_STATIC_CONSTANT(bool, use_default_precision = + !limits::is_specialized || limits::is_exact + ); + + BOOST_STATIC_CONSTANT(bool, is_specialized_bin = + !use_default_precision && + limits::radix == 2 && limits::digits > 0 + ); + + BOOST_STATIC_CONSTANT(bool, is_specialized_dec = + !use_default_precision && + limits::radix == 10 && limits::digits10 > 0 + ); + + BOOST_STATIC_CONSTANT(std::streamsize, streamsize_max = + boost::integer_traits::const_max + ); + + BOOST_STATIC_CONSTANT(unsigned int, precision_dec = limits::digits10 + 1U); + + BOOST_STATIC_ASSERT(!is_specialized_dec || + precision_dec <= streamsize_max + 0UL + ); + + BOOST_STATIC_CONSTANT(unsigned long, precision_bin = + 2UL + limits::digits * 30103UL / 100000UL + ); + + BOOST_STATIC_ASSERT(!is_specialized_bin || + (limits::digits + 0UL < ULONG_MAX / 30103UL && + precision_bin > limits::digits10 + 0UL && + precision_bin <= streamsize_max + 0UL) + ); + + BOOST_STATIC_CONSTANT(std::streamsize, value = + is_specialized_bin ? precision_bin + : is_specialized_dec ? precision_dec : 6 + ); +}; +#endif + +template +inline std::streamsize lcast_get_precision(T* = 0) +{ +#ifndef BOOST_LCAST_NO_COMPILE_TIME_PRECISION + return lcast_precision::value; +#else // Follow lcast_precision algorithm at run-time: + +#ifdef BOOST_NO_IS_ABSTRACT + typedef std::numeric_limits limits; // No fix for SF:1358600. +#else + typedef BOOST_DEDUCED_TYPENAME boost::mpl::if_< + boost::is_abstract + , std::numeric_limits + , std::numeric_limits + >::type limits; +#endif + + bool const use_default_precision = + !limits::is_specialized || limits::is_exact; + + if(!use_default_precision) + { // Includes all built-in floating-point types, float, double ... + // and UDT types for which digits (significand bits) is defined (not zero) + + bool const is_specialized_bin = + limits::radix == 2 && limits::digits > 0; + bool const is_specialized_dec = + limits::radix == 10 && limits::digits10 > 0; + std::streamsize const streamsize_max = + (boost::integer_traits::max)(); + + if(is_specialized_bin) + { // Floating-point types with + // limits::digits defined by the specialization. + + unsigned long const digits = limits::digits; + unsigned long const precision = 2UL + digits * 30103UL / 100000UL; + // unsigned long is selected because it is at least 32-bits + // and thus ULONG_MAX / 30103UL is big enough for all types. + BOOST_ASSERT( + digits < ULONG_MAX / 30103UL && + precision > limits::digits10 + 0UL && + precision <= streamsize_max + 0UL + ); + return precision; + } + else if(is_specialized_dec) + { // Decimal Floating-point type, most likely a User Defined Type + // rather than a real floating-point hardware type. + unsigned int const precision = limits::digits10 + 1U; + BOOST_ASSERT(precision <= streamsize_max + 0UL); + return precision; + } + } + + // Integral type (for which precision has no effect) + // or type T for which limits is NOT specialized, + // so assume stream precision remains the default 6 decimal digits. + // Warning: if your User-defined Floating-point type T is NOT specialized, + // then you may lose accuracy by only using 6 decimal digits. + // To avoid this, you need to specialize T with either + // radix == 2 and digits == the number of significand bits, + // OR + // radix = 10 and digits10 == the number of decimal digits. + + return 6; +#endif +} + +template +inline void lcast_set_precision(std::ios_base& stream, T*) +{ + stream.precision(lcast_get_precision()); +} + +template +inline void lcast_set_precision(std::ios_base& stream, Source*, Target*) +{ + std::streamsize const s = lcast_get_precision(static_cast(0)); + std::streamsize const t = lcast_get_precision(static_cast(0)); + stream.precision(s > t ? s : t); +} + +}} + +#endif // BOOST_DETAIL_LCAST_PRECISION_HPP_INCLUDED + diff --git a/thirdparty/source/boost_1_61_0/boost/detail/lightweight_mutex.hpp b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_mutex.hpp new file mode 100644 index 0000000..b7a7f6d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_mutex.hpp @@ -0,0 +1,22 @@ +#ifndef BOOST_DETAIL_LIGHTWEIGHT_MUTEX_HPP_INCLUDED +#define BOOST_DETAIL_LIGHTWEIGHT_MUTEX_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// boost/detail/lightweight_mutex.hpp - lightweight mutex +// +// Copyright (c) 2002, 2003 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#endif // #ifndef BOOST_DETAIL_LIGHTWEIGHT_MUTEX_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/lightweight_test.hpp b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_test.hpp new file mode 100644 index 0000000..9fece8a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_test.hpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 Glen Fernandes + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_DETAIL_LIGHTWEIGHT_TEST_HPP +#define BOOST_DETAIL_LIGHTWEIGHT_TEST_HPP + +// The header file at this path is deprecated; +// use boost/core/lightweight_test.hpp instead. + +#include + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/detail/lightweight_thread.hpp b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_thread.hpp new file mode 100644 index 0000000..6fe70a6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/lightweight_thread.hpp @@ -0,0 +1,135 @@ +#ifndef BOOST_DETAIL_LIGHTWEIGHT_THREAD_HPP_INCLUDED +#define BOOST_DETAIL_LIGHTWEIGHT_THREAD_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// boost/detail/lightweight_thread.hpp +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2008 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt + +#include +#include +#include + +// pthread_create, pthread_join + +#if defined( BOOST_HAS_PTHREADS ) + +#include + +#else + +#include +#include + +typedef HANDLE pthread_t; + +int pthread_create( pthread_t * thread, void const *, unsigned (__stdcall * start_routine) (void*), void* arg ) +{ + HANDLE h = (HANDLE)_beginthreadex( 0, 0, start_routine, arg, 0, 0 ); + + if( h != 0 ) + { + *thread = h; + return 0; + } + else + { + return EAGAIN; + } +} + +int pthread_join( pthread_t thread, void ** /*value_ptr*/ ) +{ + ::WaitForSingleObject( thread, INFINITE ); + ::CloseHandle( thread ); + return 0; +} + +#endif + +// template int lw_thread_create( pthread_t & pt, F f ); + +namespace boost +{ + +namespace detail +{ + +class lw_abstract_thread +{ +public: + + virtual ~lw_abstract_thread() {} + virtual void run() = 0; +}; + +#if defined( BOOST_HAS_PTHREADS ) + +extern "C" void * lw_thread_routine( void * pv ) +{ + std::auto_ptr pt( static_cast( pv ) ); + + pt->run(); + + return 0; +} + +#else + +unsigned __stdcall lw_thread_routine( void * pv ) +{ + std::auto_ptr pt( static_cast( pv ) ); + + pt->run(); + + return 0; +} + +#endif + +template class lw_thread_impl: public lw_abstract_thread +{ +public: + + explicit lw_thread_impl( F f ): f_( f ) + { + } + + void run() + { + f_(); + } + +private: + + F f_; +}; + +template int lw_thread_create( pthread_t & pt, F f ) +{ + std::auto_ptr p( new lw_thread_impl( f ) ); + + int r = pthread_create( &pt, 0, lw_thread_routine, p.get() ); + + if( r == 0 ) + { + p.release(); + } + + return r; +} + +} // namespace detail +} // namespace boost + +#endif // #ifndef BOOST_DETAIL_LIGHTWEIGHT_THREAD_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/no_exceptions_support.hpp b/thirdparty/source/boost_1_61_0/boost/detail/no_exceptions_support.hpp new file mode 100644 index 0000000..7d17454 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/no_exceptions_support.hpp @@ -0,0 +1,17 @@ +/* + * Copyright (c) 2014 Glen Fernandes + * + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + */ + +#ifndef BOOST_DETAIL_NO_EXCEPTIONS_SUPPORT_HPP +#define BOOST_DETAIL_NO_EXCEPTIONS_SUPPORT_HPP + +// The header file at this path is deprecated; +// use boost/core/no_exceptions_support.hpp instead. + +#include + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/detail/numeric_traits.hpp b/thirdparty/source/boost_1_61_0/boost/detail/numeric_traits.hpp new file mode 100644 index 0000000..2f97ebf --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/numeric_traits.hpp @@ -0,0 +1,182 @@ +// (C) Copyright David Abrahams 2001, Howard Hinnant 2001. +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Template class numeric_traits -- +// +// Supplies: +// +// typedef difference_type -- a type used to represent the difference +// between any two values of Number. +// +// Support: +// 1. Not all specializations are supplied +// +// 2. Use of specializations that are not supplied will cause a +// compile-time error +// +// 3. Users are free to specialize numeric_traits for any type. +// +// 4. Right now, specializations are only supplied for integer types. +// +// 5. On implementations which do not supply compile-time constants in +// std::numeric_limits<>, only specializations for built-in integer types +// are supplied. +// +// 6. Handling of numbers whose range of representation is at least as +// great as boost::intmax_t can cause some differences to be +// unrepresentable in difference_type: +// +// Number difference_type +// ------ --------------- +// signed Number +// unsigned intmax_t +// +// template typename numeric_traits::difference_type +// numeric_distance(Number x, Number y) +// computes (y - x), attempting to avoid overflows. +// + +// See http://www.boost.org for most recent version including documentation. + +// Revision History +// 11 Feb 2001 - Use BOOST_STATIC_CONSTANT (David Abrahams) +// 11 Feb 2001 - Rolled back ineffective Borland-specific code +// (David Abrahams) +// 10 Feb 2001 - Rolled in supposed Borland fixes from John Maddock, but +// not seeing any improvement yet (David Abrahams) +// 06 Feb 2001 - Factored if_true out into boost/detail/select_type.hpp +// (David Abrahams) +// 23 Jan 2001 - Fixed logic of difference_type selection, which was +// completely wack. In the process, added digit_traits<> +// to compute the number of digits in intmax_t even when +// not supplied by numeric_limits<>. (David Abrahams) +// 21 Jan 2001 - Created (David Abrahams) + +#ifndef BOOST_NUMERIC_TRAITS_HPP_DWA20001901 +# define BOOST_NUMERIC_TRAITS_HPP_DWA20001901 + +# include +# include +# include +# include +# include +# include + +namespace boost { namespace detail { + + // Template class is_signed -- determine whether a numeric type is signed + // Requires that T is constructable from the literals -1 and 0. Compile-time + // error results if that requirement is not met (and thus signedness is not + // likely to have meaning for that type). + template + struct is_signed + { +#if defined(BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS) + BOOST_STATIC_CONSTANT(bool, value = (Number(-1) < Number(0))); +#else + BOOST_STATIC_CONSTANT(bool, value = std::numeric_limits::is_signed); +#endif + }; + +# ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + // digit_traits - compute the number of digits in a built-in integer + // type. Needed for implementations on which numeric_limits is not specialized + // for intmax_t (e.g. VC6). + template struct digit_traits_select; + + // numeric_limits is specialized; just select that version of digits + template <> struct digit_traits_select + { + template struct traits + { + BOOST_STATIC_CONSTANT(int, digits = std::numeric_limits::digits); + }; + }; + + // numeric_limits is not specialized; compute digits from sizeof(T) + template <> struct digit_traits_select + { + template struct traits + { + BOOST_STATIC_CONSTANT(int, digits = ( + sizeof(T) * std::numeric_limits::digits + - (is_signed::value ? 1 : 0)) + ); + }; + }; + + // here's the "usable" template + template struct digit_traits + { + typedef digit_traits_select< + ::std::numeric_limits::is_specialized> selector; + typedef typename selector::template traits traits; + BOOST_STATIC_CONSTANT(int, digits = traits::digits); + }; +#endif + + // Template class integer_traits -- traits of various integer types + // This should probably be rolled into boost::integer_traits one day, but I + // need it to work without + template + struct integer_traits + { +# ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + private: + typedef Integer integer_type; + typedef std::numeric_limits x; + public: + typedef typename + if_true<(int(x::is_signed) + && (!int(x::is_bounded) + // digits is the number of no-sign bits + || (int(x::digits) + 1 >= digit_traits::digits)))>::template then< + Integer, + + typename if_true<(int(x::digits) + 1 < digit_traits::digits)>::template then< + signed int, + + typename if_true<(int(x::digits) + 1 < digit_traits::digits)>::template then< + signed long, + + // else + intmax_t + >::type>::type>::type difference_type; +#else + BOOST_STATIC_ASSERT(boost::is_integral::value); + + typedef typename + if_true<(sizeof(Integer) >= sizeof(intmax_t))>::template then< + + typename if_true<(is_signed::value)>::template then< + Integer, + intmax_t + >::type, + + typename if_true<(sizeof(Integer) < sizeof(std::ptrdiff_t))>::template then< + std::ptrdiff_t, + intmax_t + >::type + >::type difference_type; +# endif + }; + + // Right now, only supports integers, but should be expanded. + template + struct numeric_traits + { + typedef typename integer_traits::difference_type difference_type; + }; + + template + typename numeric_traits::difference_type numeric_distance(Number x, Number y) + { + typedef typename numeric_traits::difference_type difference_type; + return difference_type(y) - difference_type(x); + } +}} + +#endif // BOOST_NUMERIC_TRAITS_HPP_DWA20001901 diff --git a/thirdparty/source/boost_1_61_0/boost/detail/quick_allocator.hpp b/thirdparty/source/boost_1_61_0/boost/detail/quick_allocator.hpp new file mode 100644 index 0000000..d54b3a7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/quick_allocator.hpp @@ -0,0 +1,23 @@ +#ifndef BOOST_DETAIL_QUICK_ALLOCATOR_HPP_INCLUDED +#define BOOST_DETAIL_QUICK_ALLOCATOR_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// +// detail/quick_allocator.hpp +// +// Copyright (c) 2003 David Abrahams +// Copyright (c) 2003 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// + +#include + +#endif // #ifndef BOOST_DETAIL_QUICK_ALLOCATOR_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/reference_content.hpp b/thirdparty/source/boost_1_61_0/boost/detail/reference_content.hpp new file mode 100644 index 0000000..36b80d2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/reference_content.hpp @@ -0,0 +1,120 @@ +//----------------------------------------------------------------------------- +// boost detail/reference_content.hpp header file +// See http://www.boost.org for updates, documentation, and revision history. +//----------------------------------------------------------------------------- +// +// Copyright (c) 2003 +// Eric Friedman +// +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef BOOST_DETAIL_REFERENCE_CONTENT_HPP +#define BOOST_DETAIL_REFERENCE_CONTENT_HPP + +#include "boost/config.hpp" + +# include "boost/mpl/bool.hpp" +# include "boost/type_traits/has_nothrow_copy.hpp" + +#include "boost/mpl/void.hpp" + +namespace boost { + +namespace detail { + +/////////////////////////////////////////////////////////////////////////////// +// (detail) class template reference_content +// +// Non-Assignable wrapper for references. +// +template +class reference_content +{ +private: // representation + + RefT content_; + +public: // structors + + ~reference_content() + { + } + + reference_content(RefT r) + : content_( r ) + { + } + + reference_content(const reference_content& operand) + : content_( operand.content_ ) + { + } + +private: // non-Assignable + + reference_content& operator=(const reference_content&); + +public: // queries + + RefT get() const + { + return content_; + } + +}; + +/////////////////////////////////////////////////////////////////////////////// +// (detail) metafunction make_reference_content +// +// Wraps with reference_content if specified type is reference. +// + +template struct make_reference_content; + + +template +struct make_reference_content +{ + typedef T type; +}; + +template +struct make_reference_content< T& > +{ + typedef reference_content type; +}; + + +template <> +struct make_reference_content< mpl::void_ > +{ + template + struct apply + : make_reference_content + { + }; + + typedef mpl::void_ type; +}; + +} // namespace detail + +/////////////////////////////////////////////////////////////////////////////// +// reference_content type traits specializations +// + + +template +struct has_nothrow_copy< + ::boost::detail::reference_content< T& > + > + : mpl::true_ +{ +}; + + +} // namespace boost + +#endif // BOOST_DETAIL_REFERENCE_CONTENT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/select_type.hpp b/thirdparty/source/boost_1_61_0/boost/detail/select_type.hpp new file mode 100644 index 0000000..c13946f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/select_type.hpp @@ -0,0 +1,36 @@ +// (C) Copyright David Abrahams 2001. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org for most recent version including documentation. + +// Revision History +// 09 Feb 01 Applied John Maddock's Borland patch Moving +// specialization to unspecialized template (David Abrahams) +// 06 Feb 01 Created (David Abrahams) + +#ifndef SELECT_TYPE_DWA20010206_HPP +# define SELECT_TYPE_DWA20010206_HPP + +namespace boost { namespace detail { + + // Template class if_true -- select among 2 types based on a bool constant expression + // Usage: + // typename if_true<(bool_const_expression)>::template then::type + + // HP aCC cannot deal with missing names for template value parameters + template struct if_true + { + template + struct then { typedef T type; }; + }; + + template <> + struct if_true + { + template + struct then { typedef F type; }; + }; +}} +#endif // SELECT_TYPE_DWA20010206_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/sp_typeinfo.hpp b/thirdparty/source/boost_1_61_0/boost/detail/sp_typeinfo.hpp new file mode 100644 index 0000000..4e4de55 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/sp_typeinfo.hpp @@ -0,0 +1,36 @@ +#ifndef BOOST_DETAIL_SP_TYPEINFO_HPP_INCLUDED +#define BOOST_DETAIL_SP_TYPEINFO_HPP_INCLUDED + +// MS compatible compilers support #pragma once + +#if defined(_MSC_VER) && (_MSC_VER >= 1020) +# pragma once +#endif + +// detail/sp_typeinfo.hpp +// +// Deprecated, please use boost/core/typeinfo.hpp +// +// Copyright 2007 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include + +namespace boost +{ + +namespace detail +{ + +typedef boost::core::typeinfo sp_typeinfo; + +} // namespace detail + +} // namespace boost + +#define BOOST_SP_TYPEID(T) BOOST_CORE_TYPEID(T) + +#endif // #ifndef BOOST_DETAIL_SP_TYPEINFO_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentProcess.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentProcess.hpp new file mode 100644 index 0000000..14d5186 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentProcess.hpp @@ -0,0 +1,25 @@ +// GetCurrentProcess.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GETCURRENTPROCESS_HPP +#define BOOST_DETAIL_WINAPI_GETCURRENTPROCESS_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__GNUC__) +#pragma message "This header is deprecated, use boost/detail/winapi/get_current_process.hpp instead." +#elif defined(_MSC_VER) +#pragma message("This header is deprecated, use boost/detail/winapi/get_current_process.hpp instead.") +#endif + +#endif // BOOST_DETAIL_WINAPI_GETCURRENTPROCESS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentThread.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentThread.hpp new file mode 100644 index 0000000..047add8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetCurrentThread.hpp @@ -0,0 +1,25 @@ +// GetCurrentThread.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GETCURRENTTHREAD_HPP +#define BOOST_DETAIL_WINAPI_GETCURRENTTHREAD_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__GNUC__) +#pragma message "This header is deprecated, use boost/detail/winapi/get_current_thread.hpp instead." +#elif defined(_MSC_VER) +#pragma message("This header is deprecated, use boost/detail/winapi/get_current_thread.hpp instead.") +#endif + +#endif // BOOST_DETAIL_WINAPI_GETCURRENTTHREAD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetLastError.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetLastError.hpp new file mode 100644 index 0000000..7fda753 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetLastError.hpp @@ -0,0 +1,25 @@ +// GetLastError.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GETLASTERROR_HPP +#define BOOST_DETAIL_WINAPI_GETLASTERROR_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__GNUC__) +#pragma message "This header is deprecated, use boost/detail/winapi/get_last_error.hpp instead." +#elif defined(_MSC_VER) +#pragma message("This header is deprecated, use boost/detail/winapi/get_last_error.hpp instead.") +#endif + +#endif // BOOST_DETAIL_WINAPI_GETLASTERROR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetProcessTimes.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetProcessTimes.hpp new file mode 100644 index 0000000..7b2b969 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetProcessTimes.hpp @@ -0,0 +1,24 @@ +// GetProcessTimes.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GETPROCESSTIMES_HPP +#define BOOST_DETAIL_WINAPI_GETPROCESSTIMES_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__GNUC__) +#pragma message "This header is deprecated, use boost/detail/winapi/get_process_times.hpp instead." +#elif defined(_MSC_VER) +#pragma message("This header is deprecated, use boost/detail/winapi/get_process_times.hpp instead.") +#endif + +#endif // BOOST_DETAIL_WINAPI_GETPROCESSTIMES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetThreadTimes.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetThreadTimes.hpp new file mode 100644 index 0000000..d69c410 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/GetThreadTimes.hpp @@ -0,0 +1,25 @@ +// GetThreadTimes.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GETTHREADTIMES_HPP +#define BOOST_DETAIL_WINAPI_GETTHREADTIMES_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined(__GNUC__) +#pragma message "This header is deprecated, use boost/detail/winapi/get_thread_times.hpp instead." +#elif defined(_MSC_VER) +#pragma message("This header is deprecated, use boost/detail/winapi/get_thread_times.hpp instead.") +#endif + +#endif // BOOST_DETAIL_WINAPI_GETTHREADTIMES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/basic_types.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/basic_types.hpp new file mode 100644 index 0000000..30df135 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/basic_types.hpp @@ -0,0 +1,233 @@ +// basic_types.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + +#ifndef BOOST_DETAIL_WINAPI_BASIC_TYPES_HPP +#define BOOST_DETAIL_WINAPI_BASIC_TYPES_HPP + +#include +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if defined( BOOST_USE_WINDOWS_H ) +# include +#elif defined( WIN32 ) || defined( _WIN32 ) || defined( __WIN32__ ) || defined(__CYGWIN__) +# include +# ifdef UNDER_CE +# ifndef WINAPI +# ifndef _WIN32_WCE_EMULATION +# define WINAPI __cdecl // Note this doesn't match the desktop definition +# else +# define WINAPI __stdcall +# endif +# endif +// Windows CE defines a few functions as inline functions in kfuncs.h +typedef int BOOL; +typedef unsigned long DWORD; +typedef void* HANDLE; +# include +# else +# ifndef WINAPI +# define WINAPI __stdcall +# endif +# endif +# ifndef NTAPI +# define NTAPI __stdcall +# endif +#else +# error "Win32 functions not available" +#endif + +#ifndef NO_STRICT +#ifndef STRICT +#define STRICT 1 +#endif +#endif + +#if defined(STRICT) +#define BOOST_DETAIL_WINAPI_DECLARE_HANDLE(x) struct x##__; typedef struct x##__ *x +#else +#define BOOST_DETAIL_WINAPI_DECLARE_HANDLE(x) typedef void* x +#endif + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +union _LARGE_INTEGER; +struct _SECURITY_ATTRIBUTES; +BOOST_DETAIL_WINAPI_DECLARE_HANDLE(HINSTANCE); +typedef HINSTANCE HMODULE; +} +#endif + +#if defined(__GNUC__) +#define BOOST_DETAIL_WINAPI_MAY_ALIAS __attribute__ ((__may_alias__)) +#else +#define BOOST_DETAIL_WINAPI_MAY_ALIAS +#endif + +// MinGW64 gcc 4.8.2 fails to compile function declarations with boost::detail::winapi::VOID_ arguments even though +// the typedef expands to void. In Windows SDK, VOID is a macro which unfolds to void. We use our own macro in such cases. +#define BOOST_DETAIL_WINAPI_VOID void + +namespace boost { +namespace detail { +namespace winapi { +#if defined( BOOST_USE_WINDOWS_H ) + +typedef ::BOOL BOOL_; +typedef ::PBOOL PBOOL_; +typedef ::LPBOOL LPBOOL_; +typedef ::BOOLEAN BOOLEAN_; +typedef ::PBOOLEAN PBOOLEAN_; +typedef ::BYTE BYTE_; +typedef ::PBYTE PBYTE_; +typedef ::LPBYTE LPBYTE_; +typedef ::WORD WORD_; +typedef ::PWORD PWORD_; +typedef ::LPWORD LPWORD_; +typedef ::DWORD DWORD_; +typedef ::PDWORD PDWORD_; +typedef ::LPDWORD LPDWORD_; +typedef ::HANDLE HANDLE_; +typedef ::PHANDLE PHANDLE_; +typedef ::SHORT SHORT_; +typedef ::PSHORT PSHORT_; +typedef ::USHORT USHORT_; +typedef ::PUSHORT PUSHORT_; +typedef ::INT INT_; +typedef ::PINT PINT_; +typedef ::LPINT LPINT_; +typedef ::UINT UINT_; +typedef ::PUINT PUINT_; +typedef ::LONG LONG_; +typedef ::PLONG PLONG_; +typedef ::LPLONG LPLONG_; +typedef ::ULONG ULONG_; +typedef ::PULONG PULONG_; +typedef ::LONGLONG LONGLONG_; +typedef ::ULONGLONG ULONGLONG_; +typedef ::INT_PTR INT_PTR_; +typedef ::UINT_PTR UINT_PTR_; +typedef ::LONG_PTR LONG_PTR_; +typedef ::ULONG_PTR ULONG_PTR_; +typedef ::DWORD_PTR DWORD_PTR_; +typedef ::PDWORD_PTR PDWORD_PTR_; +typedef ::SIZE_T SIZE_T_; +typedef ::PSIZE_T PSIZE_T_; +typedef ::SSIZE_T SSIZE_T_; +typedef ::PSSIZE_T PSSIZE_T_; +typedef VOID VOID_; // VOID is a macro +typedef ::PVOID PVOID_; +typedef ::LPVOID LPVOID_; +typedef ::LPCVOID LPCVOID_; +typedef ::CHAR CHAR_; +typedef ::LPSTR LPSTR_; +typedef ::LPCSTR LPCSTR_; +typedef ::WCHAR WCHAR_; +typedef ::LPWSTR LPWSTR_; +typedef ::LPCWSTR LPCWSTR_; + +#else // defined( BOOST_USE_WINDOWS_H ) + +typedef int BOOL_; +typedef BOOL_* PBOOL_; +typedef BOOL_* LPBOOL_; +typedef unsigned char BYTE_; +typedef BYTE_* PBYTE_; +typedef BYTE_* LPBYTE_; +typedef BYTE_ BOOLEAN_; +typedef BOOLEAN_* PBOOLEAN_; +typedef unsigned short WORD_; +typedef WORD_* PWORD_; +typedef WORD_* LPWORD_; +typedef unsigned long DWORD_; +typedef DWORD_* PDWORD_; +typedef DWORD_* LPDWORD_; +typedef void* HANDLE_; +typedef void** PHANDLE_; + +typedef short SHORT_; +typedef SHORT_* PSHORT_; +typedef unsigned short USHORT_; +typedef USHORT_* PUSHORT_; +typedef int INT_; +typedef INT_* PINT_; +typedef INT_* LPINT_; +typedef unsigned int UINT_; +typedef UINT_* PUINT_; +typedef long LONG_; +typedef LONG_* PLONG_; +typedef LONG_* LPLONG_; +typedef unsigned long ULONG_; +typedef ULONG_* PULONG_; + +typedef boost::int64_t LONGLONG_; +typedef boost::uint64_t ULONGLONG_; + +# ifdef _WIN64 +# if defined(__CYGWIN__) +typedef long INT_PTR_; +typedef unsigned long UINT_PTR_; +typedef long LONG_PTR_; +typedef unsigned long ULONG_PTR_; +# else +typedef __int64 INT_PTR_; +typedef unsigned __int64 UINT_PTR_; +typedef __int64 LONG_PTR_; +typedef unsigned __int64 ULONG_PTR_; +# endif +# else +typedef int INT_PTR_; +typedef unsigned int UINT_PTR_; +typedef long LONG_PTR_; +typedef unsigned long ULONG_PTR_; +# endif + +typedef ULONG_PTR_ DWORD_PTR_, *PDWORD_PTR_; +typedef ULONG_PTR_ SIZE_T_, *PSIZE_T_; +typedef LONG_PTR_ SSIZE_T_, *PSSIZE_T_; + +typedef void VOID_; +typedef void *PVOID_; +typedef void *LPVOID_; +typedef const void *LPCVOID_; + +typedef char CHAR_; +typedef CHAR_ *LPSTR_; +typedef const CHAR_ *LPCSTR_; + +typedef wchar_t WCHAR_; +typedef WCHAR_ *LPWSTR_; +typedef const WCHAR_ *LPCWSTR_; + +#endif // defined( BOOST_USE_WINDOWS_H ) + +typedef ::HMODULE HMODULE_; + +typedef union BOOST_DETAIL_WINAPI_MAY_ALIAS _LARGE_INTEGER { + struct { + DWORD_ LowPart; + LONG_ HighPart; + } u; + LONGLONG_ QuadPart; +} LARGE_INTEGER_, *PLARGE_INTEGER_; + +typedef struct BOOST_DETAIL_WINAPI_MAY_ALIAS _SECURITY_ATTRIBUTES { + DWORD_ nLength; + LPVOID_ lpSecurityDescriptor; + BOOL_ bInheritHandle; +} SECURITY_ATTRIBUTES_, *PSECURITY_ATTRIBUTES_, *LPSECURITY_ATTRIBUTES_; + +} +} +} + +#endif // BOOST_DETAIL_WINAPI_BASIC_TYPES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/config.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/config.hpp new file mode 100644 index 0000000..1f08c2a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/config.hpp @@ -0,0 +1,73 @@ +// config.hpp --------------------------------------------------------------// + +// Copyright 2013 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_CONFIG_HPP_INCLUDED_ +#define BOOST_DETAIL_WINAPI_CONFIG_HPP_INCLUDED_ + +#if defined __MINGW32__ +#include <_mingw.h> +#endif + +// BOOST_WINAPI_IS_MINGW indicates that the target Windows SDK is provided by MinGW (http://mingw.org/). +// BOOST_WINAPI_IS_MINGW_W64 indicates that the target Windows SDK is provided by MinGW-w64 (http://mingw-w64.org). +#if defined __MINGW32__ +#if defined __MINGW64_VERSION_MAJOR +#define BOOST_WINAPI_IS_MINGW_W64 +#else +#define BOOST_WINAPI_IS_MINGW +#endif +#endif + +// These constants reflect _WIN32_WINNT_* macros from sdkddkver.h +// See also: http://msdn.microsoft.com/en-us/library/windows/desktop/aa383745%28v=vs.85%29.aspx#setting_winver_or__win32_winnt +#define BOOST_WINAPI_VERSION_NT4 0x0400 +#define BOOST_WINAPI_VERSION_WIN2K 0x0500 +#define BOOST_WINAPI_VERSION_WINXP 0x0501 +#define BOOST_WINAPI_VERSION_WS03 0x0502 +#define BOOST_WINAPI_VERSION_WIN6 0x0600 +#define BOOST_WINAPI_VERSION_VISTA 0x0600 +#define BOOST_WINAPI_VERSION_WS08 0x0600 +#define BOOST_WINAPI_VERSION_LONGHORN 0x0600 +#define BOOST_WINAPI_VERSION_WIN7 0x0601 +#define BOOST_WINAPI_VERSION_WIN8 0x0602 +#define BOOST_WINAPI_VERSION_WINBLUE 0x0603 +#define BOOST_WINAPI_VERSION_WINTHRESHOLD 0x0A00 +#define BOOST_WINAPI_VERSION_WIN10 0x0A00 + +#if !defined(BOOST_USE_WINAPI_VERSION) +#if defined(_WIN32_WINNT) +#define BOOST_USE_WINAPI_VERSION _WIN32_WINNT +#elif defined(WINVER) +#define BOOST_USE_WINAPI_VERSION WINVER +#else +// By default use Windows Vista API on compilers that support it and XP on the others +#if (defined(_MSC_VER) && _MSC_VER < 1500) || defined(BOOST_WINAPI_IS_MINGW) +#define BOOST_USE_WINAPI_VERSION BOOST_WINAPI_VERSION_WINXP +#else +#define BOOST_USE_WINAPI_VERSION BOOST_WINAPI_VERSION_WIN6 +#endif +#endif +#endif + +#if defined(BOOST_USE_WINDOWS_H) +// We have to define the version macros so that windows.h provides the necessary symbols +#if !defined(_WIN32_WINNT) +#define _WIN32_WINNT BOOST_USE_WINAPI_VERSION +#endif +#if !defined(WINVER) +#define WINVER BOOST_USE_WINAPI_VERSION +#endif +#endif + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#endif // BOOST_DETAIL_WINAPI_CONFIG_HPP_INCLUDED_ diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_process.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_process.hpp new file mode 100644 index 0000000..e5f4f2a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_process.hpp @@ -0,0 +1,34 @@ +// get_current_process.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GET_CURRENT_PROCESS_HPP +#define BOOST_DETAIL_WINAPI_GET_CURRENT_PROCESS_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +// Windows CE define GetCurrentProcess as an inline function in kfuncs.h +#if !defined( BOOST_USE_WINDOWS_H ) && !defined( UNDER_CE ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::HANDLE_ WINAPI GetCurrentProcess(BOOST_DETAIL_WINAPI_VOID); +} +#endif + +namespace boost { +namespace detail { +namespace winapi { +using ::GetCurrentProcess; +} +} +} + +#endif // BOOST_DETAIL_WINAPI_GET_CURRENT_PROCESS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_thread.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_thread.hpp new file mode 100644 index 0000000..b52c3a8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_current_thread.hpp @@ -0,0 +1,34 @@ +// get_current_thread.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GET_CURRENT_THREAD_HPP +#define BOOST_DETAIL_WINAPI_GET_CURRENT_THREAD_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +// Windows CE define GetCurrentThread as an inline function in kfuncs.h +#if !defined( BOOST_USE_WINDOWS_H ) && !defined( UNDER_CE ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::HANDLE_ WINAPI GetCurrentThread(BOOST_DETAIL_WINAPI_VOID); +} +#endif + +namespace boost { +namespace detail { +namespace winapi { +using ::GetCurrentThread; +} +} +} + +#endif // BOOST_DETAIL_WINAPI_GET_CURRENT_THREAD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_last_error.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_last_error.hpp new file mode 100644 index 0000000..543efaf --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_last_error.hpp @@ -0,0 +1,33 @@ +// get_last_error.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GET_LAST_ERROR_HPP +#define BOOST_DETAIL_WINAPI_GET_LAST_ERROR_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::DWORD_ WINAPI GetLastError(BOOST_DETAIL_WINAPI_VOID); +} +#endif + +namespace boost { +namespace detail { +namespace winapi { +using ::GetLastError; +} +} +} + +#endif // BOOST_DETAIL_WINAPI_GET_LAST_ERROR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_process_times.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_process_times.hpp new file mode 100644 index 0000000..a79eeb0 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_process_times.hpp @@ -0,0 +1,60 @@ +// get_process_times.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GET_PROCESS_TIMES_HPP +#define BOOST_DETAIL_WINAPI_GET_PROCESS_TIMES_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +// Windows CE does not define GetProcessTimes +#if !defined( UNDER_CE ) + +#include +#include + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +GetProcessTimes( + boost::detail::winapi::HANDLE_ hProcess, + ::_FILETIME* lpCreationTime, + ::_FILETIME* lpExitTime, + ::_FILETIME* lpKernelTime, + ::_FILETIME* lpUserTime); +} +#endif + +namespace boost { +namespace detail { +namespace winapi { + +BOOST_FORCEINLINE BOOL_ GetProcessTimes( + HANDLE_ hProcess, + LPFILETIME_ lpCreationTime, + LPFILETIME_ lpExitTime, + LPFILETIME_ lpKernelTime, + LPFILETIME_ lpUserTime) +{ + return ::GetProcessTimes( + hProcess, + reinterpret_cast< ::_FILETIME* >(lpCreationTime), + reinterpret_cast< ::_FILETIME* >(lpExitTime), + reinterpret_cast< ::_FILETIME* >(lpKernelTime), + reinterpret_cast< ::_FILETIME* >(lpUserTime)); +} + +} +} +} + +#endif // !defined( UNDER_CE ) +#endif // BOOST_DETAIL_WINAPI_GET_PROCESS_TIMES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_thread_times.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_thread_times.hpp new file mode 100644 index 0000000..13308d8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/get_thread_times.hpp @@ -0,0 +1,55 @@ +// get_thread_times.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_GET_THREAD_TIMES_HPP +#define BOOST_DETAIL_WINAPI_GET_THREAD_TIMES_HPP + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +GetThreadTimes( + boost::detail::winapi::HANDLE_ hThread, + ::_FILETIME* lpCreationTime, + ::_FILETIME* lpExitTime, + ::_FILETIME* lpKernelTime, + ::_FILETIME* lpUserTime); +} +#endif + +namespace boost { +namespace detail { +namespace winapi { + +BOOST_FORCEINLINE BOOL_ GetThreadTimes( + HANDLE_ hThread, + LPFILETIME_ lpCreationTime, + LPFILETIME_ lpExitTime, + LPFILETIME_ lpKernelTime, + LPFILETIME_ lpUserTime) +{ + return ::GetThreadTimes( + hThread, + reinterpret_cast< ::_FILETIME* >(lpCreationTime), + reinterpret_cast< ::_FILETIME* >(lpExitTime), + reinterpret_cast< ::_FILETIME* >(lpKernelTime), + reinterpret_cast< ::_FILETIME* >(lpUserTime)); +} + +} +} +} + +#endif // BOOST_DETAIL_WINAPI_GET_THREAD_TIMES_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/time.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/time.hpp new file mode 100644 index 0000000..0f69f47 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/time.hpp @@ -0,0 +1,139 @@ +// time.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright (c) Microsoft Corporation 2014 +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_TIME_HPP +#define BOOST_DETAIL_WINAPI_TIME_HPP + +#include +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +struct _FILETIME; +struct _SYSTEMTIME; + +BOOST_SYMBOL_IMPORT boost::detail::winapi::VOID_ WINAPI +GetSystemTime(::_SYSTEMTIME* lpSystemTime); + +#ifdef BOOST_HAS_GETSYSTEMTIMEASFILETIME // Windows CE does not define GetSystemTimeAsFileTime +BOOST_SYMBOL_IMPORT boost::detail::winapi::VOID_ WINAPI +GetSystemTimeAsFileTime(::_FILETIME* lpSystemTimeAsFileTime); +#endif + +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +SystemTimeToFileTime( + const ::_SYSTEMTIME* lpSystemTime, + ::_FILETIME* lpFileTime); + +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +FileTimeToSystemTime( + const ::_FILETIME* lpFileTime, + ::_SYSTEMTIME* lpSystemTime); + +#if BOOST_PLAT_WINDOWS_DESKTOP +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +FileTimeToLocalFileTime( + const ::_FILETIME* lpFileTime, + ::_FILETIME* lpLocalFileTime); + +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +LocalFileTimeToFileTime( + const ::_FILETIME* lpLocalFileTime, + ::_FILETIME* lpFileTime); + +BOOST_SYMBOL_IMPORT boost::detail::winapi::DWORD_ WINAPI +GetTickCount(BOOST_DETAIL_WINAPI_VOID); +#endif + +#if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6 +BOOST_SYMBOL_IMPORT boost::detail::winapi::ULONGLONG_ WINAPI +GetTickCount64(BOOST_DETAIL_WINAPI_VOID); +#endif +} +#endif + +namespace boost { +namespace detail { +namespace winapi { + +typedef struct BOOST_DETAIL_WINAPI_MAY_ALIAS _FILETIME { + DWORD_ dwLowDateTime; + DWORD_ dwHighDateTime; +} FILETIME_, *PFILETIME_, *LPFILETIME_; + +typedef struct BOOST_DETAIL_WINAPI_MAY_ALIAS _SYSTEMTIME { + WORD_ wYear; + WORD_ wMonth; + WORD_ wDayOfWeek; + WORD_ wDay; + WORD_ wHour; + WORD_ wMinute; + WORD_ wSecond; + WORD_ wMilliseconds; +} SYSTEMTIME_, *PSYSTEMTIME_, *LPSYSTEMTIME_; + +#if BOOST_PLAT_WINDOWS_DESKTOP +using ::GetTickCount; +#endif +#if BOOST_USE_WINAPI_VERSION >= BOOST_WINAPI_VERSION_WIN6 +using ::GetTickCount64; +#endif + +BOOST_FORCEINLINE VOID_ GetSystemTime(LPSYSTEMTIME_ lpSystemTime) +{ + ::GetSystemTime(reinterpret_cast< ::_SYSTEMTIME* >(lpSystemTime)); +} + +#if defined( BOOST_HAS_GETSYSTEMTIMEASFILETIME ) +BOOST_FORCEINLINE VOID_ GetSystemTimeAsFileTime(LPFILETIME_ lpSystemTimeAsFileTime) +{ + ::GetSystemTimeAsFileTime(reinterpret_cast< ::_FILETIME* >(lpSystemTimeAsFileTime)); +} +#else +// Windows CE does not define GetSystemTimeAsFileTime +BOOST_FORCEINLINE VOID_ GetSystemTimeAsFileTime(FILETIME_* lpFileTime) +{ + boost::detail::winapi::SYSTEMTIME_ st; + boost::detail::winapi::GetSystemTime(&st); + boost::detail::winapi::SystemTimeToFileTime(&st, lpFileTime); +} +#endif + +BOOST_FORCEINLINE BOOL_ SystemTimeToFileTime(const SYSTEMTIME_* lpSystemTime, FILETIME_* lpFileTime) +{ + return ::SystemTimeToFileTime(reinterpret_cast< const ::_SYSTEMTIME* >(lpSystemTime), reinterpret_cast< ::_FILETIME* >(lpFileTime)); +} + +BOOST_FORCEINLINE BOOL_ FileTimeToSystemTime(const FILETIME_* lpFileTime, SYSTEMTIME_* lpSystemTime) +{ + return ::FileTimeToSystemTime(reinterpret_cast< const ::_FILETIME* >(lpFileTime), reinterpret_cast< ::_SYSTEMTIME* >(lpSystemTime)); +} + +#if BOOST_PLAT_WINDOWS_DESKTOP +BOOST_FORCEINLINE BOOL_ FileTimeToLocalFileTime(const FILETIME_* lpFileTime, FILETIME_* lpLocalFileTime) +{ + return ::FileTimeToLocalFileTime(reinterpret_cast< const ::_FILETIME* >(lpFileTime), reinterpret_cast< ::_FILETIME* >(lpLocalFileTime)); +} + +BOOST_FORCEINLINE BOOL_ LocalFileTimeToFileTime(const FILETIME_* lpLocalFileTime, FILETIME_* lpFileTime) +{ + return ::LocalFileTimeToFileTime(reinterpret_cast< const ::_FILETIME* >(lpLocalFileTime), reinterpret_cast< ::_FILETIME* >(lpFileTime)); +} +#endif + +} +} +} + +#endif // BOOST_DETAIL_WINAPI_TIME_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/winapi/timers.hpp b/thirdparty/source/boost_1_61_0/boost/detail/winapi/timers.hpp new file mode 100644 index 0000000..c3bf826 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/winapi/timers.hpp @@ -0,0 +1,48 @@ +// timers.hpp --------------------------------------------------------------// + +// Copyright 2010 Vicente J. Botet Escriba +// Copyright 2015 Andrey Semashev + +// Distributed under the Boost Software License, Version 1.0. +// See http://www.boost.org/LICENSE_1_0.txt + + +#ifndef BOOST_DETAIL_WINAPI_TIMERS_HPP +#define BOOST_DETAIL_WINAPI_TIMERS_HPP + +#include + +#ifdef BOOST_HAS_PRAGMA_ONCE +#pragma once +#endif + +#if !defined( BOOST_USE_WINDOWS_H ) +extern "C" { +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +QueryPerformanceCounter(::_LARGE_INTEGER* lpPerformanceCount); + +BOOST_SYMBOL_IMPORT boost::detail::winapi::BOOL_ WINAPI +QueryPerformanceFrequency(::_LARGE_INTEGER* lpFrequency); +} +#endif + + +namespace boost { +namespace detail { +namespace winapi { + +BOOST_FORCEINLINE BOOL_ QueryPerformanceCounter(LARGE_INTEGER_* lpPerformanceCount) +{ + return ::QueryPerformanceCounter(reinterpret_cast< ::_LARGE_INTEGER* >(lpPerformanceCount)); +} + +BOOST_FORCEINLINE BOOL_ QueryPerformanceFrequency(LARGE_INTEGER_* lpFrequency) +{ + return ::QueryPerformanceFrequency(reinterpret_cast< ::_LARGE_INTEGER* >(lpFrequency)); +} + +} +} +} + +#endif // BOOST_DETAIL_WINAPI_TIMERS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/detail/workaround.hpp b/thirdparty/source/boost_1_61_0/boost/detail/workaround.hpp new file mode 100644 index 0000000..40b3423 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/detail/workaround.hpp @@ -0,0 +1,267 @@ +// Copyright David Abrahams 2002. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#ifndef WORKAROUND_DWA2002126_HPP +# define WORKAROUND_DWA2002126_HPP + +// Compiler/library version workaround macro +// +// Usage: +// +// #if BOOST_WORKAROUND(BOOST_MSVC, < 1300) +// // workaround for eVC4 and VC6 +// ... // workaround code here +// #endif +// +// When BOOST_STRICT_CONFIG is defined, expands to 0. Otherwise, the +// first argument must be undefined or expand to a numeric +// value. The above expands to: +// +// (BOOST_MSVC) != 0 && (BOOST_MSVC) < 1300 +// +// When used for workarounds that apply to the latest known version +// and all earlier versions of a compiler, the following convention +// should be observed: +// +// #if BOOST_WORKAROUND(BOOST_MSVC, BOOST_TESTED_AT(1301)) +// +// The version number in this case corresponds to the last version in +// which the workaround was known to have been required. When +// BOOST_DETECT_OUTDATED_WORKAROUNDS is not the defined, the macro +// BOOST_TESTED_AT(x) expands to "!= 0", which effectively activates +// the workaround for any version of the compiler. When +// BOOST_DETECT_OUTDATED_WORKAROUNDS is defined, a compiler warning or +// error will be issued if the compiler version exceeds the argument +// to BOOST_TESTED_AT(). This can be used to locate workarounds which +// may be obsoleted by newer versions. + +# ifndef BOOST_STRICT_CONFIG + +#include + +#ifndef __BORLANDC__ +#define __BORLANDC___WORKAROUND_GUARD 1 +#else +#define __BORLANDC___WORKAROUND_GUARD 0 +#endif +#ifndef __CODEGEARC__ +#define __CODEGEARC___WORKAROUND_GUARD 1 +#else +#define __CODEGEARC___WORKAROUND_GUARD 0 +#endif +#ifndef _MSC_VER +#define _MSC_VER_WORKAROUND_GUARD 1 +#else +#define _MSC_VER_WORKAROUND_GUARD 0 +#endif +#ifndef _MSC_FULL_VER +#define _MSC_FULL_VER_WORKAROUND_GUARD 1 +#else +#define _MSC_FULL_VER_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_MSVC +#define BOOST_MSVC_WORKAROUND_GUARD 1 +#else +#define BOOST_MSVC_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_MSVC_FULL_VER +#define BOOST_MSVC_FULL_VER_WORKAROUND_GUARD 1 +#else +#define BOOST_MSVC_FULL_VER_WORKAROUND_GUARD 0 +#endif +#ifndef __GNUC__ +#define __GNUC___WORKAROUND_GUARD 1 +#else +#define __GNUC___WORKAROUND_GUARD 0 +#endif +#ifndef __GNUC_MINOR__ +#define __GNUC_MINOR___WORKAROUND_GUARD 1 +#else +#define __GNUC_MINOR___WORKAROUND_GUARD 0 +#endif +#ifndef __GNUC_PATCHLEVEL__ +#define __GNUC_PATCHLEVEL___WORKAROUND_GUARD 1 +#else +#define __GNUC_PATCHLEVEL___WORKAROUND_GUARD 0 +#endif +#ifndef __IBMCPP__ +#define __IBMCPP___WORKAROUND_GUARD 1 +#else +#define __IBMCPP___WORKAROUND_GUARD 0 +#endif +#ifndef __SUNPRO_CC +#define __SUNPRO_CC_WORKAROUND_GUARD 1 +#else +#define __SUNPRO_CC_WORKAROUND_GUARD 0 +#endif +#ifndef __DECCXX_VER +#define __DECCXX_VER_WORKAROUND_GUARD 1 +#else +#define __DECCXX_VER_WORKAROUND_GUARD 0 +#endif +#ifndef __MWERKS__ +#define __MWERKS___WORKAROUND_GUARD 1 +#else +#define __MWERKS___WORKAROUND_GUARD 0 +#endif +#ifndef __EDG__ +#define __EDG___WORKAROUND_GUARD 1 +#else +#define __EDG___WORKAROUND_GUARD 0 +#endif +#ifndef __EDG_VERSION__ +#define __EDG_VERSION___WORKAROUND_GUARD 1 +#else +#define __EDG_VERSION___WORKAROUND_GUARD 0 +#endif +#ifndef __HP_aCC +#define __HP_aCC_WORKAROUND_GUARD 1 +#else +#define __HP_aCC_WORKAROUND_GUARD 0 +#endif +#ifndef __hpxstd98 +#define __hpxstd98_WORKAROUND_GUARD 1 +#else +#define __hpxstd98_WORKAROUND_GUARD 0 +#endif +#ifndef _CRAYC +#define _CRAYC_WORKAROUND_GUARD 1 +#else +#define _CRAYC_WORKAROUND_GUARD 0 +#endif +#ifndef __DMC__ +#define __DMC___WORKAROUND_GUARD 1 +#else +#define __DMC___WORKAROUND_GUARD 0 +#endif +#ifndef MPW_CPLUS +#define MPW_CPLUS_WORKAROUND_GUARD 1 +#else +#define MPW_CPLUS_WORKAROUND_GUARD 0 +#endif +#ifndef __COMO__ +#define __COMO___WORKAROUND_GUARD 1 +#else +#define __COMO___WORKAROUND_GUARD 0 +#endif +#ifndef __COMO_VERSION__ +#define __COMO_VERSION___WORKAROUND_GUARD 1 +#else +#define __COMO_VERSION___WORKAROUND_GUARD 0 +#endif +#ifndef __INTEL_COMPILER +#define __INTEL_COMPILER_WORKAROUND_GUARD 1 +#else +#define __INTEL_COMPILER_WORKAROUND_GUARD 0 +#endif +#ifndef __ICL +#define __ICL_WORKAROUND_GUARD 1 +#else +#define __ICL_WORKAROUND_GUARD 0 +#endif +#ifndef _COMPILER_VERSION +#define _COMPILER_VERSION_WORKAROUND_GUARD 1 +#else +#define _COMPILER_VERSION_WORKAROUND_GUARD 0 +#endif + +#ifndef _RWSTD_VER +#define _RWSTD_VER_WORKAROUND_GUARD 1 +#else +#define _RWSTD_VER_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_RWSTD_VER +#define BOOST_RWSTD_VER_WORKAROUND_GUARD 1 +#else +#define BOOST_RWSTD_VER_WORKAROUND_GUARD 0 +#endif +#ifndef __GLIBCPP__ +#define __GLIBCPP___WORKAROUND_GUARD 1 +#else +#define __GLIBCPP___WORKAROUND_GUARD 0 +#endif +#ifndef _GLIBCXX_USE_C99_FP_MACROS_DYNAMIC +#define _GLIBCXX_USE_C99_FP_MACROS_DYNAMIC_WORKAROUND_GUARD 1 +#else +#define _GLIBCXX_USE_C99_FP_MACROS_DYNAMIC_WORKAROUND_GUARD 0 +#endif +#ifndef __SGI_STL_PORT +#define __SGI_STL_PORT_WORKAROUND_GUARD 1 +#else +#define __SGI_STL_PORT_WORKAROUND_GUARD 0 +#endif +#ifndef _STLPORT_VERSION +#define _STLPORT_VERSION_WORKAROUND_GUARD 1 +#else +#define _STLPORT_VERSION_WORKAROUND_GUARD 0 +#endif +#ifndef __LIBCOMO_VERSION__ +#define __LIBCOMO_VERSION___WORKAROUND_GUARD 1 +#else +#define __LIBCOMO_VERSION___WORKAROUND_GUARD 0 +#endif +#ifndef _CPPLIB_VER +#define _CPPLIB_VER_WORKAROUND_GUARD 1 +#else +#define _CPPLIB_VER_WORKAROUND_GUARD 0 +#endif + +#ifndef BOOST_INTEL_CXX_VERSION +#define BOOST_INTEL_CXX_VERSION_WORKAROUND_GUARD 1 +#else +#define BOOST_INTEL_CXX_VERSION_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_INTEL_WIN +#define BOOST_INTEL_WIN_WORKAROUND_GUARD 1 +#else +#define BOOST_INTEL_WIN_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_DINKUMWARE_STDLIB +#define BOOST_DINKUMWARE_STDLIB_WORKAROUND_GUARD 1 +#else +#define BOOST_DINKUMWARE_STDLIB_WORKAROUND_GUARD 0 +#endif +#ifndef BOOST_INTEL +#define BOOST_INTEL_WORKAROUND_GUARD 1 +#else +#define BOOST_INTEL_WORKAROUND_GUARD 0 +#endif +// Always define to zero, if it's used it'll be defined my MPL: +#define BOOST_MPL_CFG_GCC_WORKAROUND_GUARD 0 + +# define BOOST_WORKAROUND(symbol, test) \ + ((symbol ## _WORKAROUND_GUARD + 0 == 0) && \ + (symbol != 0) && (1 % (( (symbol test) ) + 1))) +// ^ ^ ^ ^ +// The extra level of parenthesis nesting above, along with the +// BOOST_OPEN_PAREN indirection below, is required to satisfy the +// broken preprocessor in MWCW 8.3 and earlier. +// +// The basic mechanism works as follows: +// (symbol test) + 1 => if (symbol test) then 2 else 1 +// 1 % ((symbol test) + 1) => if (symbol test) then 1 else 0 +// +// The complication with % is for cooperation with BOOST_TESTED_AT(). +// When "test" is BOOST_TESTED_AT(x) and +// BOOST_DETECT_OUTDATED_WORKAROUNDS is #defined, +// +// symbol test => if (symbol <= x) then 1 else -1 +// (symbol test) + 1 => if (symbol <= x) then 2 else 0 +// 1 % ((symbol test) + 1) => if (symbol <= x) then 1 else divide-by-zero +// + +# ifdef BOOST_DETECT_OUTDATED_WORKAROUNDS +# define BOOST_OPEN_PAREN ( +# define BOOST_TESTED_AT(value) > value) ?(-1): BOOST_OPEN_PAREN 1 +# else +# define BOOST_TESTED_AT(value) != ((value)-(value)) +# endif + +# else + +# define BOOST_WORKAROUND(symbol, test) 0 + +# endif + +#endif // WORKAROUND_DWA2002126_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/enable_shared_from_this.hpp b/thirdparty/source/boost_1_61_0/boost/enable_shared_from_this.hpp new file mode 100644 index 0000000..b1bb63d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/enable_shared_from_this.hpp @@ -0,0 +1,18 @@ +#ifndef BOOST_ENABLE_SHARED_FROM_THIS_HPP_INCLUDED +#define BOOST_ENABLE_SHARED_FROM_THIS_HPP_INCLUDED + +// +// enable_shared_from_this.hpp +// +// Copyright (c) 2002 Peter Dimov +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// http://www.boost.org/libs/smart_ptr/enable_shared_from_this.html +// + +#include + +#endif // #ifndef BOOST_ENABLE_SHARED_FROM_THIS_HPP_INCLUDED diff --git a/thirdparty/source/boost_1_61_0/boost/exception/current_exception_cast.hpp b/thirdparty/source/boost_1_61_0/boost/exception/current_exception_cast.hpp new file mode 100644 index 0000000..5d81f00 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/current_exception_cast.hpp @@ -0,0 +1,43 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_7E83C166200811DE885E826156D89593 +#define UUID_7E83C166200811DE885E826156D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +namespace +boost + { + template + inline + E * + current_exception_cast() + { + try + { + throw; + } + catch( + E & e ) + { + return &e; + } + catch( + ...) + { + return 0; + } + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/clone_current_exception.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/clone_current_exception.hpp new file mode 100644 index 0000000..6fc1374 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/clone_current_exception.hpp @@ -0,0 +1,56 @@ +//Copyright (c) 2006-2013 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_81522C0EB56511DFAB613DB0DFD72085 +#define UUID_81522C0EB56511DFAB613DB0DFD72085 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#ifdef BOOST_NO_EXCEPTIONS +# error This header requires exception handling to be enabled. +#endif + +namespace +boost + { + namespace + exception_detail + { + class clone_base; + +#ifdef BOOST_ENABLE_NON_INTRUSIVE_EXCEPTION_PTR + int clone_current_exception_non_intrusive( clone_base const * & cloned ); +#endif + + namespace + clone_current_exception_result + { + int const success=0; + int const bad_alloc=1; + int const bad_exception=2; + int const not_supported=3; + } + + inline + int + clone_current_exception( clone_base const * & cloned ) + { +#ifdef BOOST_ENABLE_NON_INTRUSIVE_EXCEPTION_PTR + return clone_current_exception_non_intrusive(cloned); +#else + return clone_current_exception_result::not_supported; +#endif + } + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/error_info_impl.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/error_info_impl.hpp new file mode 100644 index 0000000..12e601b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/error_info_impl.hpp @@ -0,0 +1,74 @@ +//Copyright (c) 2006-2010 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_CE6983AC753411DDA764247956D89593 +#define UUID_CE6983AC753411DDA764247956D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include + +namespace +boost + { + namespace + exception_detail + { + class + error_info_base + { + public: + + virtual std::string name_value_string() const = 0; + + protected: + + virtual + ~error_info_base() throw() + { + } + }; + } + + template + class + error_info: + public exception_detail::error_info_base + { + public: + + typedef T value_type; + + error_info( value_type const & value ); + ~error_info() throw(); + + value_type const & + value() const + { + return value_; + } + + value_type & + value() + { + return value_; + } + + private: + + std::string name_value_string() const; + + value_type value_; + }; + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/exception_ptr.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/exception_ptr.hpp new file mode 100644 index 0000000..cac64e6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/exception_ptr.hpp @@ -0,0 +1,513 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_618474C2DE1511DEB74A388C56D89593 +#define UUID_618474C2DE1511DEB74A388C56D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#ifdef BOOST_NO_EXCEPTIONS +#error This header requires exception handling to be enabled. +#endif +#include +#include +#include +#include +#include +#ifndef BOOST_NO_RTTI +#include +#endif +#include +#include +#include +#include +#include + +namespace +boost + { + class exception_ptr; + BOOST_NORETURN void rethrow_exception( exception_ptr const & ); + exception_ptr current_exception(); + + class + exception_ptr + { + typedef boost::shared_ptr impl; + impl ptr_; + friend void rethrow_exception( exception_ptr const & ); + typedef exception_detail::clone_base const * (impl::*unspecified_bool_type)() const; + public: + exception_ptr() + { + } + explicit + exception_ptr( impl const & ptr ): + ptr_(ptr) + { + } + bool + operator==( exception_ptr const & other ) const + { + return ptr_==other.ptr_; + } + bool + operator!=( exception_ptr const & other ) const + { + return ptr_!=other.ptr_; + } + operator unspecified_bool_type() const + { + return ptr_?&impl::get:0; + } + }; + + template + inline + exception_ptr + copy_exception( T const & e ) + { + try + { + throw enable_current_exception(e); + } + catch( + ... ) + { + return current_exception(); + } + } + +#ifndef BOOST_NO_RTTI + typedef error_info original_exception_type; + + inline + std::string + to_string( original_exception_type const & x ) + { + return core::demangle(x.value()->name()); + } +#endif + + namespace + exception_detail + { + struct + bad_alloc_: + boost::exception, + std::bad_alloc + { + ~bad_alloc_() throw() { } + }; + + struct + bad_exception_: + boost::exception, + std::bad_exception + { + ~bad_exception_() throw() { } + }; + + template + exception_ptr + get_static_exception_object() + { + Exception ba; + exception_detail::clone_impl c(ba); +#ifndef BOOST_EXCEPTION_DISABLE + c << + throw_function(BOOST_CURRENT_FUNCTION) << + throw_file(__FILE__) << + throw_line(__LINE__); +#endif + static exception_ptr ep(shared_ptr(new exception_detail::clone_impl(c))); + return ep; + } + + template + struct + exception_ptr_static_exception_object + { + static exception_ptr const e; + }; + + template + exception_ptr const + exception_ptr_static_exception_object:: + e = get_static_exception_object(); + } + +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + class + unknown_exception: + public boost::exception, + public std::exception + { + public: + + unknown_exception() + { + } + + explicit + unknown_exception( std::exception const & e ) + { + add_original_type(e); + } + + explicit + unknown_exception( boost::exception const & e ): + boost::exception(e) + { + add_original_type(e); + } + + ~unknown_exception() throw() + { + } + + private: + + template + void + add_original_type( E const & e ) + { +#ifndef BOOST_NO_RTTI + (*this) << original_exception_type(&typeid(e)); +#endif + } + }; +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + namespace + exception_detail + { + template + class + current_exception_std_exception_wrapper: + public T, + public boost::exception + { + public: + + explicit + current_exception_std_exception_wrapper( T const & e1 ): + T(e1) + { + add_original_type(e1); + } + + current_exception_std_exception_wrapper( T const & e1, boost::exception const & e2 ): + T(e1), + boost::exception(e2) + { + add_original_type(e1); + } + + ~current_exception_std_exception_wrapper() throw() + { + } + + private: + + template + void + add_original_type( E const & e ) + { +#ifndef BOOST_NO_RTTI + (*this) << original_exception_type(&typeid(e)); +#endif + } + }; + +#ifdef BOOST_NO_RTTI + template + boost::exception const * + get_boost_exception( T const * ) + { + try + { + throw; + } + catch( + boost::exception & x ) + { + return &x; + } + catch(...) + { + return 0; + } + } +#else + template + boost::exception const * + get_boost_exception( T const * x ) + { + return dynamic_cast(x); + } +#endif + + template + inline + exception_ptr + current_exception_std_exception( T const & e1 ) + { + if( boost::exception const * e2 = get_boost_exception(&e1) ) + return boost::copy_exception(current_exception_std_exception_wrapper(e1,*e2)); + else + return boost::copy_exception(current_exception_std_exception_wrapper(e1)); + } + + inline + exception_ptr + current_exception_unknown_exception() + { + return boost::copy_exception(unknown_exception()); + } + + inline + exception_ptr + current_exception_unknown_boost_exception( boost::exception const & e ) + { + return boost::copy_exception(unknown_exception(e)); + } + + inline + exception_ptr + current_exception_unknown_std_exception( std::exception const & e ) + { + if( boost::exception const * be = get_boost_exception(&e) ) + return current_exception_unknown_boost_exception(*be); + else + return boost::copy_exception(unknown_exception(e)); + } + + inline + exception_ptr + current_exception_impl() + { + exception_detail::clone_base const * e=0; + switch( + exception_detail::clone_current_exception(e) ) + { + case exception_detail::clone_current_exception_result:: + success: + { + BOOST_ASSERT(e!=0); + return exception_ptr(shared_ptr(e)); + } + case exception_detail::clone_current_exception_result:: + bad_alloc: + { + BOOST_ASSERT(!e); + return exception_detail::exception_ptr_static_exception_object::e; + } + case exception_detail::clone_current_exception_result:: + bad_exception: + { + BOOST_ASSERT(!e); + return exception_detail::exception_ptr_static_exception_object::e; + } + default: + BOOST_ASSERT(0); + case exception_detail::clone_current_exception_result:: + not_supported: + { + BOOST_ASSERT(!e); + try + { + throw; + } + catch( + exception_detail::clone_base & e ) + { + return exception_ptr(shared_ptr(e.clone())); + } + catch( + std::domain_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::invalid_argument & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::length_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::out_of_range & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::logic_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::range_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::overflow_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::underflow_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::ios_base::failure & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::runtime_error & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::bad_alloc & e ) + { + return exception_detail::current_exception_std_exception(e); + } +#ifndef BOOST_NO_TYPEID + catch( + std::bad_cast & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::bad_typeid & e ) + { + return exception_detail::current_exception_std_exception(e); + } +#endif + catch( + std::bad_exception & e ) + { + return exception_detail::current_exception_std_exception(e); + } + catch( + std::exception & e ) + { + return exception_detail::current_exception_unknown_std_exception(e); + } + catch( + boost::exception & e ) + { + return exception_detail::current_exception_unknown_boost_exception(e); + } + catch( + ... ) + { + return exception_detail::current_exception_unknown_exception(); + } + } + } + } + } + + inline + exception_ptr + current_exception() + { + exception_ptr ret; + try + { + ret=exception_detail::current_exception_impl(); + } + catch( + std::bad_alloc & ) + { + ret=exception_detail::exception_ptr_static_exception_object::e; + } + catch( + ... ) + { + ret=exception_detail::exception_ptr_static_exception_object::e; + } + BOOST_ASSERT(ret); + return ret; + } + + BOOST_NORETURN + inline + void + rethrow_exception( exception_ptr const & p ) + { + BOOST_ASSERT(p); + p.ptr_->rethrow(); + BOOST_ASSERT(0); + #if defined(UNDER_CE) + // some CE platforms don't define ::abort() + exit(-1); + #else + abort(); + #endif + } + + inline + std::string + diagnostic_information( exception_ptr const & p, bool verbose=true ) + { + if( p ) + try + { + rethrow_exception(p); + } + catch( + ... ) + { + return current_exception_diagnostic_information(verbose); + } + return ""; + } + + inline + std::string + to_string( exception_ptr const & p ) + { + std::string s='\n'+diagnostic_information(p); + std::string padding(" "); + std::string r; + bool f=false; + for( std::string::const_iterator i=s.begin(),e=s.end(); i!=e; ++i ) + { + if( f ) + r+=padding; + char c=*i; + r+=c; + f=(c=='\n'); + } + return r; + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/is_output_streamable.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/is_output_streamable.hpp new file mode 100644 index 0000000..847f348 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/is_output_streamable.hpp @@ -0,0 +1,60 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_898984B4076411DD973EDFA055D89593 +#define UUID_898984B4076411DD973EDFA055D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include + +namespace +boost + { + namespace + to_string_detail + { + struct + partial_ordering_helper1 + { + template + partial_ordering_helper1( std::basic_ostream & ); + }; + + struct + partial_ordering_helper2 + { + template + partial_ordering_helper2( T const & ); + }; + + char operator<<( partial_ordering_helper1, partial_ordering_helper2 ); + + template + struct + is_output_streamable_impl + { + static std::basic_ostream & f(); + static T const & g(); + enum e { value=1!=(sizeof(f()< > + struct + is_output_streamable + { + enum e { value=to_string_detail::is_output_streamable_impl::value }; + }; + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/object_hex_dump.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/object_hex_dump.hpp new file mode 100644 index 0000000..53c8bf6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/object_hex_dump.hpp @@ -0,0 +1,50 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_6F463AC838DF11DDA3E6909F56D89593 +#define UUID_6F463AC838DF11DDA3E6909F56D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include +#include +#include +#include + +namespace +boost + { + namespace + exception_detail + { + template + inline + std::string + object_hex_dump( T const & x, std::size_t max_size=16 ) + { + std::ostringstream s; + s << "type: " << type_name() << ", size: " << sizeof(T) << ", dump: "; + std::size_t n=sizeof(T)>max_size?max_size:sizeof(T); + s.fill('0'); + s.width(2); + unsigned char const * b=reinterpret_cast(&x); + s << std::setw(2) << std::hex << (unsigned int)*b; + for( unsigned char const * e=b+n; ++b!=e; ) + s << " " << std::setw(2) << std::hex << (unsigned int)*b; + return s.str(); + } + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/detail/type_info.hpp b/thirdparty/source/boost_1_61_0/boost/exception/detail/type_info.hpp new file mode 100644 index 0000000..b8c7d48 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/detail/type_info.hpp @@ -0,0 +1,81 @@ +//Copyright (c) 2006-2010 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_C3E1741C754311DDB2834CCA55D89593 +#define UUID_C3E1741C754311DDB2834CCA55D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include +#include +#include + +namespace +boost + { + template + inline + std::string + tag_type_name() + { +#ifdef BOOST_NO_TYPEID + return BOOST_CURRENT_FUNCTION; +#else + return core::demangle(typeid(T*).name()); +#endif + } + + template + inline + std::string + type_name() + { +#ifdef BOOST_NO_TYPEID + return BOOST_CURRENT_FUNCTION; +#else + return core::demangle(typeid(T).name()); +#endif + } + + namespace + exception_detail + { + struct + type_info_ + { + core::typeinfo const * type_; + + explicit + type_info_( core::typeinfo const & type ): + type_(&type) + { + } + + friend + bool + operator<( type_info_ const & a, type_info_ const & b ) + { + return 0!=(a.type_->before(*b.type_)); + } + }; + } + } + +#define BOOST_EXCEPTION_STATIC_TYPEID(T) ::boost::exception_detail::type_info_(BOOST_CORE_TYPEID(T)) + +#ifndef BOOST_NO_RTTI +#define BOOST_EXCEPTION_DYNAMIC_TYPEID(x) ::boost::exception_detail::type_info_(typeid(x)) +#endif + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/diagnostic_information.hpp b/thirdparty/source/boost_1_61_0/boost/exception/diagnostic_information.hpp new file mode 100644 index 0000000..305e8ed --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/diagnostic_information.hpp @@ -0,0 +1,201 @@ +//Copyright (c) 2006-2010 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_0552D49838DD11DD90146B8956D89593 +#define UUID_0552D49838DD11DD90146B8956D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include +#include +#ifndef BOOST_NO_RTTI +#include +#endif +#include +#include +#include + +#ifndef BOOST_NO_EXCEPTIONS +#include +namespace +boost + { + namespace + exception_detail + { + std::string diagnostic_information_impl( boost::exception const *, std::exception const *, bool, bool ); + } + + inline + std::string + current_exception_diagnostic_information( bool verbose=true) + { + boost::exception const * be=current_exception_cast(); + std::exception const * se=current_exception_cast(); + if( be || se ) + return exception_detail::diagnostic_information_impl(be,se,true,verbose); + else + return "No diagnostic information available."; + } + } +#endif + +namespace +boost + { + namespace + exception_detail + { + inline + exception const * + get_boost_exception( exception const * e ) + { + return e; + } + + inline + exception const * + get_boost_exception( ... ) + { + return 0; + } + + inline + std::exception const * + get_std_exception( std::exception const * e ) + { + return e; + } + + inline + std::exception const * + get_std_exception( ... ) + { + return 0; + } + + inline + char const * + get_diagnostic_information( exception const & x, char const * header ) + { +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + error_info_container * c=x.data_.get(); + if( !c ) + x.data_.adopt(c=new exception_detail::error_info_container_impl); + char const * di=c->diagnostic_information(header); + BOOST_ASSERT(di!=0); + return di; +#ifndef BOOST_NO_EXCEPTIONS + } + catch(...) + { + return 0; + } +#endif + } + + inline + std::string + diagnostic_information_impl( boost::exception const * be, std::exception const * se, bool with_what, bool verbose ) + { + if( !be && !se ) + return "Unknown exception."; +#ifndef BOOST_NO_RTTI + if( !be ) + be=dynamic_cast(se); + if( !se ) + se=dynamic_cast(be); +#endif + char const * wh=0; + if( with_what && se ) + { + wh=se->what(); + if( be && exception_detail::get_diagnostic_information(*be,0)==wh ) + return wh; + } + std::ostringstream tmp; + if( be && verbose ) + { + char const * const * f=get_error_info(*be); + int const * l=get_error_info(*be); + char const * const * fn=get_error_info(*be); + if( !f && !l && !fn ) + tmp << "Throw location unknown (consider using BOOST_THROW_EXCEPTION)\n"; + else + { + if( f ) + { + tmp << *f; + if( int const * l=get_error_info(*be) ) + tmp << '(' << *l << "): "; + } + tmp << "Throw in function "; + if( char const * const * fn=get_error_info(*be) ) + tmp << *fn; + else + tmp << "(unknown)"; + tmp << '\n'; + } + } +#ifndef BOOST_NO_RTTI + if ( verbose ) + tmp << std::string("Dynamic exception type: ") << + core::demangle((be?(BOOST_EXCEPTION_DYNAMIC_TYPEID(*be)):(BOOST_EXCEPTION_DYNAMIC_TYPEID(*se))).type_->name()) << '\n'; +#endif + if( with_what && se && verbose ) + tmp << "std::exception::what: " << wh << '\n'; + if( be ) + if( char const * s=exception_detail::get_diagnostic_information(*be,tmp.str().c_str()) ) + if( *s ) + return std::string(s); + return tmp.str(); + } + } + + template + std::string + diagnostic_information( T const & e, bool verbose=true ) + { + return exception_detail::diagnostic_information_impl(exception_detail::get_boost_exception(&e),exception_detail::get_std_exception(&e),true,verbose); + } + + inline + char const * + diagnostic_information_what( exception const & e, bool verbose=true ) throw() + { + char const * w=0; +#ifndef BOOST_NO_EXCEPTIONS + try + { +#endif + (void) exception_detail::diagnostic_information_impl(&e,0,false,verbose); + if( char const * di=exception_detail::get_diagnostic_information(e,0) ) + return di; + else + return "Failed to produce boost::diagnostic_information_what()"; +#ifndef BOOST_NO_EXCEPTIONS + } + catch( + ... ) + { + } +#endif + return w; + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/exception.hpp b/thirdparty/source/boost_1_61_0/boost/exception/exception.hpp new file mode 100644 index 0000000..1f2bd9c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/exception.hpp @@ -0,0 +1,499 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_274DA366004E11DCB1DDFE2E56D89593 +#define UUID_274DA366004E11DCB1DDFE2E56D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +namespace +boost + { + namespace + exception_detail + { + template + class + refcount_ptr + { + public: + + refcount_ptr(): + px_(0) + { + } + + ~refcount_ptr() + { + release(); + } + + refcount_ptr( refcount_ptr const & x ): + px_(x.px_) + { + add_ref(); + } + + refcount_ptr & + operator=( refcount_ptr const & x ) + { + adopt(x.px_); + return *this; + } + + void + adopt( T * px ) + { + release(); + px_=px; + add_ref(); + } + + T * + get() const + { + return px_; + } + + private: + + T * px_; + + void + add_ref() + { + if( px_ ) + px_->add_ref(); + } + + void + release() + { + if( px_ && px_->release() ) + px_=0; + } + }; + } + + //////////////////////////////////////////////////////////////////////// + + template + class error_info; + + typedef error_info throw_function; + typedef error_info throw_file; + typedef error_info throw_line; + + template <> + class + error_info + { + public: + typedef char const * value_type; + value_type v_; + explicit + error_info( value_type v ): + v_(v) + { + } + }; + + template <> + class + error_info + { + public: + typedef char const * value_type; + value_type v_; + explicit + error_info( value_type v ): + v_(v) + { + } + }; + + template <> + class + error_info + { + public: + typedef int value_type; + value_type v_; + explicit + error_info( value_type v ): + v_(v) + { + } + }; + +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + class exception; +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + template + class shared_ptr; + + namespace + exception_detail + { + class error_info_base; + struct type_info_; + + struct + error_info_container + { + virtual char const * diagnostic_information( char const * ) const = 0; + virtual shared_ptr get( type_info_ const & ) const = 0; + virtual void set( shared_ptr const &, type_info_ const & ) = 0; + virtual void add_ref() const = 0; + virtual bool release() const = 0; + virtual refcount_ptr clone() const = 0; + + protected: + + ~error_info_container() throw() + { + } + }; + + template + struct get_info; + + template <> + struct get_info; + + template <> + struct get_info; + + template <> + struct get_info; + + char const * get_diagnostic_information( exception const &, char const * ); + + void copy_boost_exception( exception *, exception const * ); + + template + E const & set_info( E const &, error_info const & ); + + template + E const & set_info( E const &, throw_function const & ); + + template + E const & set_info( E const &, throw_file const & ); + + template + E const & set_info( E const &, throw_line const & ); + } + +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + class + exception + { + // + public: + template void set( typename Tag::type const & ); + template typename Tag::type const * get() const; + // + + protected: + + exception(): + throw_function_(0), + throw_file_(0), + throw_line_(-1) + { + } + +#ifdef __HP_aCC + //On HP aCC, this protected copy constructor prevents throwing boost::exception. + //On all other platforms, the same effect is achieved by the pure virtual destructor. + exception( exception const & x ) throw(): + data_(x.data_), + throw_function_(x.throw_function_), + throw_file_(x.throw_file_), + throw_line_(x.throw_line_) + { + } +#endif + + virtual ~exception() throw() +#ifndef __HP_aCC + = 0 //Workaround for HP aCC, =0 incorrectly leads to link errors. +#endif + ; + +#if (defined(__MWERKS__) && __MWERKS__<=0x3207) || (defined(_MSC_VER) && _MSC_VER<=1310) + public: +#else + private: + + template + friend E const & exception_detail::set_info( E const &, throw_function const & ); + + template + friend E const & exception_detail::set_info( E const &, throw_file const & ); + + template + friend E const & exception_detail::set_info( E const &, throw_line const & ); + + template + friend E const & exception_detail::set_info( E const &, error_info const & ); + + friend char const * exception_detail::get_diagnostic_information( exception const &, char const * ); + + template + friend struct exception_detail::get_info; + friend struct exception_detail::get_info; + friend struct exception_detail::get_info; + friend struct exception_detail::get_info; + friend void exception_detail::copy_boost_exception( exception *, exception const * ); +#endif + mutable exception_detail::refcount_ptr data_; + mutable char const * throw_function_; + mutable char const * throw_file_; + mutable int throw_line_; + }; +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + inline + exception:: + ~exception() throw() + { + } + + namespace + exception_detail + { + template + E const & + set_info( E const & x, throw_function const & y ) + { + x.throw_function_=y.v_; + return x; + } + + template + E const & + set_info( E const & x, throw_file const & y ) + { + x.throw_file_=y.v_; + return x; + } + + template + E const & + set_info( E const & x, throw_line const & y ) + { + x.throw_line_=y.v_; + return x; + } + } + + //////////////////////////////////////////////////////////////////////// + + namespace + exception_detail + { +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + template + struct + error_info_injector: + public T, + public exception + { + explicit + error_info_injector( T const & x ): + T(x) + { + } + + ~error_info_injector() throw() + { + } + }; +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + struct large_size { char c[256]; }; + large_size dispatch_boost_exception( exception const * ); + + struct small_size { }; + small_size dispatch_boost_exception( void const * ); + + template + struct enable_error_info_helper; + + template + struct + enable_error_info_helper + { + typedef T type; + }; + + template + struct + enable_error_info_helper + { + typedef error_info_injector type; + }; + + template + struct + enable_error_info_return_type + { + typedef typename enable_error_info_helper(0)))>::type type; + }; + } + + template + inline + typename + exception_detail::enable_error_info_return_type::type + enable_error_info( T const & x ) + { + typedef typename exception_detail::enable_error_info_return_type::type rt; + return rt(x); + } + + //////////////////////////////////////////////////////////////////////// + + namespace + exception_detail + { +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + class + clone_base + { + public: + + virtual clone_base const * clone() const = 0; + virtual void rethrow() const = 0; + + virtual + ~clone_base() throw() + { + } + }; +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + inline + void + copy_boost_exception( exception * a, exception const * b ) + { + refcount_ptr data; + if( error_info_container * d=b->data_.get() ) + data = d->clone(); + a->throw_file_ = b->throw_file_; + a->throw_line_ = b->throw_line_; + a->throw_function_ = b->throw_function_; + a->data_ = data; + } + + inline + void + copy_boost_exception( void *, void const * ) + { + } + +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility push (default) +# endif +#endif + template + class + clone_impl: + public T, + public virtual clone_base + { + struct clone_tag { }; + clone_impl( clone_impl const & x, clone_tag ): + T(x) + { + copy_boost_exception(this,&x); + } + + public: + + explicit + clone_impl( T const & x ): + T(x) + { + copy_boost_exception(this,&x); + } + + ~clone_impl() throw() + { + } + + private: + + clone_base const * + clone() const + { + return new clone_impl(*this,clone_tag()); + } + + void + rethrow() const + { + throw*this; + } + }; + } +#if defined(__GNUC__) +# if (__GNUC__ == 4 && __GNUC_MINOR__ >= 1) || (__GNUC__ > 4) +# pragma GCC visibility pop +# endif +#endif + + template + inline + exception_detail::clone_impl + enable_current_exception( T const & x ) + { + return exception_detail::clone_impl(x); + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/get_error_info.hpp b/thirdparty/source/boost_1_61_0/boost/exception/get_error_info.hpp new file mode 100644 index 0000000..96be763 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/get_error_info.hpp @@ -0,0 +1,130 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_1A590226753311DD9E4CCF6156D89593 +#define UUID_1A590226753311DD9E4CCF6156D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include +#include + +namespace +boost + { + namespace + exception_detail + { + template + struct + get_info + { + static + typename ErrorInfo::value_type * + get( exception const & x ) + { + if( exception_detail::error_info_container * c=x.data_.get() ) + if( shared_ptr eib = c->get(BOOST_EXCEPTION_STATIC_TYPEID(ErrorInfo)) ) + { +#ifndef BOOST_NO_RTTI + BOOST_ASSERT( 0!=dynamic_cast(eib.get()) ); +#endif + ErrorInfo * w = static_cast(eib.get()); + return &w->value(); + } + return 0; + } + }; + + template <> + struct + get_info + { + static + char const * * + get( exception const & x ) + { + return x.throw_function_ ? &x.throw_function_ : 0; + } + }; + + template <> + struct + get_info + { + static + char const * * + get( exception const & x ) + { + return x.throw_file_ ? &x.throw_file_ : 0; + } + }; + + template <> + struct + get_info + { + static + int * + get( exception const & x ) + { + return x.throw_line_!=-1 ? &x.throw_line_ : 0; + } + }; + + template + struct + get_error_info_return_type + { + typedef R * type; + }; + + template + struct + get_error_info_return_type + { + typedef R const * type; + }; + } + +#ifdef BOOST_NO_RTTI + template + inline + typename ErrorInfo::value_type const * + get_error_info( boost::exception const & x ) + { + return exception_detail::get_info::get(x); + } + template + inline + typename ErrorInfo::value_type * + get_error_info( boost::exception & x ) + { + return exception_detail::get_info::get(x); + } +#else + template + inline + typename exception_detail::get_error_info_return_type::type + get_error_info( E & some_exception ) + { + if( exception const * x = dynamic_cast(&some_exception) ) + return exception_detail::get_info::get(*x); + else + return 0; + } +#endif + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/info.hpp b/thirdparty/source/boost_1_61_0/boost/exception/info.hpp new file mode 100644 index 0000000..762a950 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/info.hpp @@ -0,0 +1,198 @@ +//Copyright (c) 2006-2010 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_8D22C4CA9CC811DCAA9133D256D89593 +#define UUID_8D22C4CA9CC811DCAA9133D256D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include +#include +#include +#include + +namespace +boost + { + template + inline + std::string + error_info_name( error_info const & x ) + { + return tag_type_name(); + } + + template + inline + std::string + to_string( error_info const & x ) + { + return '[' + error_info_name(x) + "] = " + to_string_stub(x.value()) + '\n'; + } + + template + inline + error_info:: + error_info( value_type const & value ): + value_(value) + { + } + + template + inline + error_info:: + ~error_info() throw() + { + } + + template + inline + std::string + error_info:: + name_value_string() const + { + return to_string_stub(*this); + } + + namespace + exception_detail + { + class + error_info_container_impl: + public error_info_container + { + public: + + error_info_container_impl(): + count_(0) + { + } + + ~error_info_container_impl() throw() + { + } + + void + set( shared_ptr const & x, type_info_ const & typeid_ ) + { + BOOST_ASSERT(x); + info_[typeid_] = x; + diagnostic_info_str_.clear(); + } + + shared_ptr + get( type_info_ const & ti ) const + { + error_info_map::const_iterator i=info_.find(ti); + if( info_.end()!=i ) + { + shared_ptr const & p = i->second; +#ifndef BOOST_NO_RTTI + BOOST_ASSERT( *BOOST_EXCEPTION_DYNAMIC_TYPEID(*p).type_==*ti.type_ ); +#endif + return p; + } + return shared_ptr(); + } + + char const * + diagnostic_information( char const * header ) const + { + if( header ) + { + std::ostringstream tmp; + tmp << header; + for( error_info_map::const_iterator i=info_.begin(),end=info_.end(); i!=end; ++i ) + { + error_info_base const & x = *i->second; + tmp << x.name_value_string(); + } + tmp.str().swap(diagnostic_info_str_); + } + return diagnostic_info_str_.c_str(); + } + + private: + + friend class boost::exception; + + typedef std::map< type_info_, shared_ptr > error_info_map; + error_info_map info_; + mutable std::string diagnostic_info_str_; + mutable int count_; + + error_info_container_impl( error_info_container_impl const & ); + error_info_container_impl & operator=( error_info_container const & ); + + void + add_ref() const + { + ++count_; + } + + bool + release() const + { + if( --count_ ) + return false; + else + { + delete this; + return true; + } + } + + refcount_ptr + clone() const + { + refcount_ptr p; + error_info_container_impl * c=new error_info_container_impl; + p.adopt(c); + c->info_ = info_; + return p; + } + }; + + template + inline + E const & + set_info( E const & x, error_info const & v ) + { + typedef error_info error_info_tag_t; + shared_ptr p( new error_info_tag_t(v) ); + exception_detail::error_info_container * c=x.data_.get(); + if( !c ) + x.data_.adopt(c=new exception_detail::error_info_container_impl); + c->set(p,BOOST_EXCEPTION_STATIC_TYPEID(error_info_tag_t)); + return x; + } + + template + struct + derives_boost_exception + { + enum e { value = (sizeof(dispatch_boost_exception((T*)0))==sizeof(large_size)) }; + }; + } + + template + inline + typename enable_if,E const &>::type + operator<<( E const & x, error_info const & v ) + { + return exception_detail::set_info(x,v); + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/to_string.hpp b/thirdparty/source/boost_1_61_0/boost/exception/to_string.hpp new file mode 100644 index 0000000..68541d2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/to_string.hpp @@ -0,0 +1,88 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_7E48761AD92811DC9011477D56D89593 +#define UUID_7E48761AD92811DC9011477D56D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include + +namespace +boost + { + template + std::string to_string( std::pair const & ); + std::string to_string( std::exception const & ); + + namespace + to_string_detail + { + template + typename disable_if,char>::type to_string( T const & ); + using boost::to_string; + + template + struct has_to_string_impl; + + template + struct + has_to_string_impl + { + enum e { value=1 }; + }; + + template + struct + has_to_string_impl + { + static T const & f(); + enum e { value=1!=sizeof(to_string(f())) }; + }; + } + + template + inline + typename enable_if,std::string>::type + to_string( T const & x ) + { + std::ostringstream out; + out << x; + return out.str(); + } + + template + struct + has_to_string + { + enum e { value=to_string_detail::has_to_string_impl::value>::value }; + }; + + template + inline + std::string + to_string( std::pair const & x ) + { + return std::string("(") + to_string(x.first) + ',' + to_string(x.second) + ')'; + } + + inline + std::string + to_string( std::exception const & x ) + { + return x.what(); + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception/to_string_stub.hpp b/thirdparty/source/boost_1_61_0/boost/exception/to_string_stub.hpp new file mode 100644 index 0000000..b6ab31c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception/to_string_stub.hpp @@ -0,0 +1,117 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_E788439ED9F011DCB181F25B55D89593 +#define UUID_E788439ED9F011DCB181F25B55D89593 +#if (__GNUC__*100+__GNUC_MINOR__>301) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma GCC system_header +#endif +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(push,1) +#endif + +#include +#include +#include + +namespace +boost + { + namespace + exception_detail + { + template + struct + to_string_dispatcher + { + template + static + std::string + convert( T const & x, Stub ) + { + return to_string(x); + } + }; + + template <> + struct + to_string_dispatcher + { + template + static + std::string + convert( T const & x, Stub s ) + { + return s(x); + } + + template + static + std::string + convert( T const & x, std::string s ) + { + return s; + } + + template + static + std::string + convert( T const & x, char const * s ) + { + BOOST_ASSERT(s!=0); + return s; + } + }; + + namespace + to_string_dispatch + { + template + inline + std::string + dispatch( T const & x, Stub s ) + { + return to_string_dispatcher::value>::convert(x,s); + } + } + + template + inline + std::string + string_stub_dump( T const & x ) + { + return "[ " + exception_detail::object_hex_dump(x) + " ]"; + } + } + + template + inline + std::string + to_string_stub( T const & x ) + { + return exception_detail::to_string_dispatch::dispatch(x,&exception_detail::string_stub_dump); + } + + template + inline + std::string + to_string_stub( T const & x, Stub s ) + { + return exception_detail::to_string_dispatch::dispatch(x,s); + } + + template + inline + std::string + to_string_stub( std::pair const & x, Stub s ) + { + return std::string("(") + to_string_stub(x.first,s) + ',' + to_string_stub(x.second,s) + ')'; + } + } + +#if defined(_MSC_VER) && !defined(BOOST_EXCEPTION_ENABLE_WARNINGS) +#pragma warning(pop) +#endif +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/exception_ptr.hpp b/thirdparty/source/boost_1_61_0/boost/exception_ptr.hpp new file mode 100644 index 0000000..d48cce9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/exception_ptr.hpp @@ -0,0 +1,11 @@ +//Copyright (c) 2006-2009 Emil Dotchevski and Reverge Studios, Inc. + +//Distributed under the Boost Software License, Version 1.0. (See accompanying +//file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef UUID_FA5836A2CADA11DC8CD47C8555D89593 +#define UUID_FA5836A2CADA11DC8CD47C8555D89593 + +#include + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/format.hpp b/thirdparty/source/boost_1_61_0/boost/format.hpp new file mode 100644 index 0000000..73464a8 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format.hpp @@ -0,0 +1,59 @@ +// ---------------------------------------------------------------------------- +// format.hpp : primary header +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_HPP +#define BOOST_FORMAT_HPP + +#include +#include +#include +#include + +#ifndef BOOST_NO_STD_LOCALE +#include +#endif + +// *** Compatibility framework +#include + +#ifdef BOOST_NO_LOCALE_ISIDIGIT +#include // we'll use the non-locale 's std::isdigit(int) +#endif + +// **** Forward declarations ---------------------------------- +#include // basic_format, and other frontends +#include // misc forward declarations for internal use + +// **** Auxiliary structs (stream_format_state , and format_item ) +#include + +// **** Format class interface -------------------------------- +#include + +// **** Exceptions ----------------------------------------------- +#include + +// **** Implementation ------------------------------------------- +#include // member functions +#include // class for grouping arguments +#include // argument-feeding functions +#include // format-string parsing (member-)functions + +// **** Implementation of the free functions ---------------------- +#include + + +// *** Undefine 'local' macros : +#include + +#endif // BOOST_FORMAT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/alt_sstream.hpp b/thirdparty/source/boost_1_61_0/boost/format/alt_sstream.hpp new file mode 100644 index 0000000..e236be3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/alt_sstream.hpp @@ -0,0 +1,176 @@ +// ---------------------------------------------------------------------------- +// alt_sstream.hpp : alternative stringstream +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + + + +#ifndef BOOST_SK_ALT_SSTREAM_HPP +#define BOOST_SK_ALT_SSTREAM_HPP + +#include +#include +#include +#include +#include + +namespace boost { + namespace io { + + template, + class Alloc=::std::allocator > + class basic_altstringbuf; + + template, + class Alloc=::std::allocator > + class basic_oaltstringstream; + + + template + class basic_altstringbuf + : public ::std::basic_streambuf + { + typedef ::std::basic_streambuf streambuf_t; + typedef typename CompatAlloc::compatible_type compat_allocator_type; + typedef typename CompatTraits::compatible_type compat_traits_type; + public: + typedef Ch char_type; + typedef Tr traits_type; + typedef typename compat_traits_type::int_type int_type; + typedef typename compat_traits_type::pos_type pos_type; + typedef typename compat_traits_type::off_type off_type; + typedef Alloc allocator_type; + typedef ::std::basic_string string_type; + typedef typename string_type::size_type size_type; + + typedef ::std::streamsize streamsize; + + + explicit basic_altstringbuf(std::ios_base::openmode mode + = std::ios_base::in | std::ios_base::out) + : putend_(NULL), is_allocated_(false), mode_(mode) + {} + explicit basic_altstringbuf(const string_type& s, + ::std::ios_base::openmode mode + = ::std::ios_base::in | ::std::ios_base::out) + : putend_(NULL), is_allocated_(false), mode_(mode) + { dealloc(); str(s); } + virtual ~basic_altstringbuf() + { dealloc(); } + using streambuf_t::pbase; + using streambuf_t::pptr; + using streambuf_t::epptr; + using streambuf_t::eback; + using streambuf_t::gptr; + using streambuf_t::egptr; + + void clear_buffer(); + void str(const string_type& s); + + // 0-copy access : + Ch * begin() const; + size_type size() const; + size_type cur_size() const; // stop at current pointer + Ch * pend() const // the highest position reached by pptr() since creation + { return ((putend_ < pptr()) ? pptr() : putend_); } + size_type pcount() const + { return static_cast( pptr() - pbase()) ;} + + // copy buffer to string : + string_type str() const + { return string_type(begin(), size()); } + string_type cur_str() const + { return string_type(begin(), cur_size()); } + protected: + explicit basic_altstringbuf (basic_altstringbuf * s, + ::std::ios_base::openmode mode + = ::std::ios_base::in | ::std::ios_base::out) + : putend_(NULL), is_allocated_(false), mode_(mode) + { dealloc(); str(s); } + + virtual pos_type seekoff(off_type off, ::std::ios_base::seekdir way, + ::std::ios_base::openmode which + = ::std::ios_base::in | ::std::ios_base::out); + virtual pos_type seekpos (pos_type pos, + ::std::ios_base::openmode which + = ::std::ios_base::in | ::std::ios_base::out); + virtual int_type underflow(); + virtual int_type pbackfail(int_type meta = compat_traits_type::eof()); + virtual int_type overflow(int_type meta = compat_traits_type::eof()); + void dealloc(); + private: + enum { alloc_min = 256}; // minimum size of allocations + + Ch *putend_; // remembers (over seeks) the highest value of pptr() + bool is_allocated_; + ::std::ios_base::openmode mode_; + compat_allocator_type alloc_; // the allocator object + }; + + +// --- class basic_oaltstringstream ---------------------------------------- + template + class basic_oaltstringstream + : private base_from_member< shared_ptr< basic_altstringbuf< Ch, Tr, Alloc> > >, + public ::std::basic_ostream + { + class No_Op { + // used as no-op deleter for (not-owner) shared_pointers + public: + template + const T & operator()(const T & arg) { return arg; } + }; + typedef ::std::basic_ostream stream_t; + typedef boost::base_from_member > > + pbase_type; + typedef ::std::basic_string string_type; + typedef typename string_type::size_type size_type; + typedef basic_altstringbuf stringbuf_t; + public: + typedef Alloc allocator_type; + basic_oaltstringstream() + : pbase_type(new stringbuf_t), stream_t(rdbuf()) + { } + basic_oaltstringstream(::boost::shared_ptr buf) + : pbase_type(buf), stream_t(rdbuf()) + { } + basic_oaltstringstream(stringbuf_t * buf) + : pbase_type(buf, No_Op() ), stream_t(rdbuf()) + { } + stringbuf_t * rdbuf() const + { return pbase_type::member.get(); } + void clear_buffer() + { rdbuf()->clear_buffer(); } + + // 0-copy access : + Ch * begin() const + { return rdbuf()->begin(); } + size_type size() const + { return rdbuf()->size(); } + size_type cur_size() const // stops at current position + { return rdbuf()->cur_size(); } + + // copy buffer to string : + string_type str() const // [pbase, epptr[ + { return rdbuf()->str(); } + string_type cur_str() const // [pbase, pptr[ + { return rdbuf()->cur_str(); } + void str(const string_type& s) + { rdbuf()->str(s); } + }; + + } // N.S. io +} // N.S. boost + +#include + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/format/alt_sstream_impl.hpp b/thirdparty/source/boost_1_61_0/boost/format/alt_sstream_impl.hpp new file mode 100644 index 0000000..9975e4f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/alt_sstream_impl.hpp @@ -0,0 +1,313 @@ +// ---------------------------------------------------------------------------- +// alt_sstream_impl.hpp : alternative stringstream, templates implementation +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_SK_ALT_SSTREAM_IMPL_HPP +#define BOOST_SK_ALT_SSTREAM_IMPL_HPP + +namespace boost { + namespace io { +// --- Implementation ------------------------------------------------------// + + template + void basic_altstringbuf:: + clear_buffer () { + const Ch * p = pptr(); + const Ch * b = pbase(); + if(p != NULL && p != b) { + seekpos(0, ::std::ios_base::out); + } + p = gptr(); + b = eback(); + if(p != NULL && p != b) { + seekpos(0, ::std::ios_base::in); + } + } + + template + void basic_altstringbuf:: + str (const string_type& s) { + size_type sz=s.size(); + if(sz != 0 && mode_ & (::std::ios_base::in | ::std::ios_base::out) ) { +#ifdef _RWSTD_NO_CLASS_PARTIAL_SPEC + void *vd_ptr = alloc_.allocate(sz, is_allocated_? eback() : 0); + Ch *new_ptr = static_cast(vd_ptr); +#else + Ch *new_ptr = alloc_.allocate(sz, is_allocated_? eback() : 0); +#endif + // if this didnt throw, we're safe, update the buffer + dealloc(); + sz = s.copy(new_ptr, sz); + putend_ = new_ptr + sz; + if(mode_ & ::std::ios_base::in) + streambuf_t::setg(new_ptr, new_ptr, new_ptr + sz); + if(mode_ & ::std::ios_base::out) { + streambuf_t::setp(new_ptr, new_ptr + sz); + if(mode_ & (::std::ios_base::app | ::std::ios_base::ate)) + streambuf_t::pbump(static_cast(sz)); + if(gptr() == NULL) + streambuf_t::setg(new_ptr, NULL, new_ptr); + } + is_allocated_ = true; + } + else + dealloc(); + } + template + Ch* basic_altstringbuf:: + begin () const { + if(mode_ & ::std::ios_base::out && pptr() != NULL) + return pbase(); + else if(mode_ & ::std::ios_base::in && gptr() != NULL) + return eback(); + return NULL; + } + + template + typename std::basic_string::size_type + basic_altstringbuf:: + size () const { + if(mode_ & ::std::ios_base::out && pptr()) + return static_cast(pend() - pbase()); + else if(mode_ & ::std::ios_base::in && gptr()) + return static_cast(egptr() - eback()); + else + return 0; + } + + template + typename std::basic_string::size_type + basic_altstringbuf:: + cur_size () const { + if(mode_ & ::std::ios_base::out && pptr()) + return static_cast( pptr() - pbase()); + else if(mode_ & ::std::ios_base::in && gptr()) + return static_cast( gptr() - eback()); + else + return 0; + } + + template + typename basic_altstringbuf::pos_type + basic_altstringbuf:: + seekoff (off_type off, ::std::ios_base::seekdir way, ::std::ios_base::openmode which) { + if(pptr() != NULL && putend_ < pptr()) + putend_ = pptr(); + if(which & ::std::ios_base::in && gptr() != NULL) { + // get area + if(way == ::std::ios_base::end) + off += static_cast(putend_ - gptr()); + else if(way == ::std::ios_base::beg) + off += static_cast(eback() - gptr()); + else if(way != ::std::ios_base::cur || (which & ::std::ios_base::out) ) + // (altering in&out is only supported if way is beg or end, not cur) + return pos_type(off_type(-1)); + if(eback() <= off+gptr() && off+gptr() <= putend_ ) { + // set gptr + streambuf_t::gbump(static_cast(off)); + if(which & ::std::ios_base::out && pptr() != NULL) + // update pptr to match gptr + streambuf_t::pbump(static_cast(gptr()-pptr())); + } + else + off = off_type(-1); + } + else if(which & ::std::ios_base::out && pptr() != NULL) { + // put area + if(way == ::std::ios_base::end) + off += static_cast(putend_ - pptr()); + else if(way == ::std::ios_base::beg) + off += static_cast(pbase() - pptr()); + else if(way != ::std::ios_base::beg) + return pos_type(off_type(-1)); + if(pbase() <= off+pptr() && off+pptr() <= putend_) + // set pptr + streambuf_t::pbump(static_cast(off)); + else + off = off_type(-1); + } + else // neither in nor out + off = off_type(-1); + return (pos_type(off)); + } + //- end seekoff(..) + + + template + typename basic_altstringbuf::pos_type + basic_altstringbuf:: + seekpos (pos_type pos, ::std::ios_base::openmode which) { + off_type off = off_type(pos); // operation guaranteed by 27.4.3.2 table 88 + if(pptr() != NULL && putend_ < pptr()) + putend_ = pptr(); + if(off != off_type(-1)) { + if(which & ::std::ios_base::in && gptr() != NULL) { + // get area + if(0 <= off && off <= putend_ - eback()) { + streambuf_t::gbump(static_cast(eback() - gptr() + off)); + if(which & ::std::ios_base::out && pptr() != NULL) { + // update pptr to match gptr + streambuf_t::pbump(static_cast(gptr()-pptr())); + } + } + else + off = off_type(-1); + } + else if(which & ::std::ios_base::out && pptr() != NULL) { + // put area + if(0 <= off && off <= putend_ - eback()) + streambuf_t::pbump(static_cast(eback() - pptr() + off)); + else + off = off_type(-1); + } + else // neither in nor out + off = off_type(-1); + return (pos_type(off)); + } + else { + BOOST_ASSERT(0); // §27.4.3.2 allows undefined-behaviour here + return pos_type(off_type(-1)); + } + } + // -end seekpos(..) + + + template + typename basic_altstringbuf::int_type + basic_altstringbuf:: + underflow () { + if(gptr() == NULL) // no get area -> nothing to get. + return (compat_traits_type::eof()); + else if(gptr() < egptr()) // ok, in buffer + return (compat_traits_type::to_int_type(*gptr())); + else if(mode_ & ::std::ios_base::in && pptr() != NULL + && (gptr() < pptr() || gptr() < putend_) ) + { // expand get area + if(putend_ < pptr()) + putend_ = pptr(); // remember pptr reached this far + streambuf_t::setg(eback(), gptr(), putend_); + return (compat_traits_type::to_int_type(*gptr())); + } + else // couldnt get anything. EOF. + return (compat_traits_type::eof()); + } + // -end underflow(..) + + + template + typename basic_altstringbuf::int_type + basic_altstringbuf:: + pbackfail (int_type meta) { + if(gptr() != NULL && (eback() < gptr()) + && (mode_ & (::std::ios_base::out) + || compat_traits_type::eq_int_type(compat_traits_type::eof(), meta) + || compat_traits_type::eq(compat_traits_type::to_char_type(meta), gptr()[-1]) ) ) { + streambuf_t::gbump(-1); // back one character + if(!compat_traits_type::eq_int_type(compat_traits_type::eof(), meta)) + // put-back meta into get area + *gptr() = compat_traits_type::to_char_type(meta); + return (compat_traits_type::not_eof(meta)); + } + else + return (compat_traits_type::eof()); // failed putback + } + // -end pbackfail(..) + + + template + typename basic_altstringbuf::int_type + basic_altstringbuf:: + overflow (int_type meta) { +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable:4996) +#endif + if(compat_traits_type::eq_int_type(compat_traits_type::eof(), meta)) + return compat_traits_type::not_eof(meta); // nothing to do + else if(pptr() != NULL && pptr() < epptr()) { + streambuf_t::sputc(compat_traits_type::to_char_type(meta)); + return meta; + } + else if(! (mode_ & ::std::ios_base::out)) + // no write position, and cant make one + return compat_traits_type::eof(); + else { // make a write position available + std::size_t prev_size = pptr() == NULL ? 0 : epptr() - eback(); + std::size_t new_size = prev_size; + // exponential growth : size *= 1.5 + std::size_t add_size = new_size / 2; + if(add_size < alloc_min) + add_size = alloc_min; + Ch * newptr = NULL, *oldptr = eback(); + + // make sure adding add_size wont overflow size_t + while (0 < add_size && ((std::numeric_limits::max)() + - add_size < new_size) ) + add_size /= 2; + if(0 < add_size) { + new_size += add_size; +#ifdef _RWSTD_NO_CLASS_PARTIAL_SPEC + void *vdptr = alloc_.allocate(new_size, is_allocated_? oldptr : 0); + newptr = static_cast(vdptr); +#else + newptr = alloc_.allocate(new_size, is_allocated_? oldptr : 0); +#endif + } + + if(0 < prev_size) + compat_traits_type::copy(newptr, oldptr, prev_size); + if(is_allocated_) + alloc_.deallocate(oldptr, prev_size); + is_allocated_=true; + + if(prev_size == 0) { // first allocation + putend_ = newptr; + streambuf_t::setp(newptr, newptr + new_size); + if(mode_ & ::std::ios_base::in) + streambuf_t::setg(newptr, newptr, newptr + 1); + else + streambuf_t::setg(newptr, 0, newptr); + } + else { // update pointers + putend_ = putend_ - oldptr + newptr; + int pptr_count = static_cast(pptr()-pbase()); + int gptr_count = static_cast(gptr()-eback()); + streambuf_t::setp(pbase() - oldptr + newptr, newptr + new_size); + streambuf_t::pbump(pptr_count); + if(mode_ & ::std::ios_base::in) + streambuf_t::setg(newptr, newptr + gptr_count, pptr() + 1); + else + streambuf_t::setg(newptr, 0, newptr); + } + streambuf_t::sputc(compat_traits_type::to_char_type(meta)); + return meta; + } +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + } + // -end overflow(..) + + template + void basic_altstringbuf:: dealloc() { + if(is_allocated_) + alloc_.deallocate(eback(), (pptr() != NULL ? epptr() : egptr()) - eback()); + is_allocated_ = false; + streambuf_t::setg(0, 0, 0); + streambuf_t::setp(0, 0); + putend_ = NULL; + } + + }// N.S. io +} // N.S. boost + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/compat_workarounds.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/compat_workarounds.hpp new file mode 100644 index 0000000..8e51514 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/compat_workarounds.hpp @@ -0,0 +1,86 @@ +// ---------------------------------------------------------------------------- +// compat_workarounds : general framework for non-conformance workarounds +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// see http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + + +// this file defines wrapper classes to hide non-conforming +// std::char_traits<> and std::allocator<> traits +// and Includes : config_macros.hpp (defines config macros +// and compiler-specific switches) + +// Non-conformant Std-libs fail to supply conformant traits (std::char_traits, +// std::allocator) and/or the std::string doesnt support them. +// We don't want to have hundreds of #ifdef workarounds, so we define +// replacement traits. +// But both char_traits and allocator traits are visible in the interface, +// (inside the final string type), thus we need to keep both +// the replacement type (typedefed to 'compatible_type') for real use, +// and the original stdlib type (typedef to 'type_for_string') for interface +// visibility. This is what Compat* classes do (as well as be transparent +// when good allocator and char traits are present) + +#ifndef BOOST_FORMAT_COMPAT_WORKAROUNDS_HPP +#define BOOST_FORMAT_COMPAT_WORKAROUNDS_HPP + +namespace boost { + namespace io { + + // gcc-2.95 char traits (non-conformantly named string_char_traits) + // lack several functions so we extend them in a replacement class. + template + class CompatTraits; + + // std::allocator in gcc-2.95 is ok, but basic_string only works + // with plain 'std::alloc' still, alt_stringbuf requires a functionnal + // alloc template argument, so we need a replacement allocator + template + class CompatAlloc; + } // N.S. io +}// N.S. boost + + +#include + // sets-up macros and load compiler-specific workarounds headers. + +#if !defined(BOOST_FORMAT_STREAMBUF_DEFINED) +// workarounds-gcc-2.95 might have defined own streambuf +#include +#endif + +#if !defined(BOOST_FORMAT_OSTREAM_DEFINED) +// workarounds-gcc-2.95 might already have included +#include +#endif + + + +namespace boost { + namespace io { + + // **** CompatTraits general definitions : ---------------------------- + template + class CompatTraits + { // general case : be transparent + public: + typedef Tr compatible_type; + }; + + // **** CompatAlloc general definitions : ----------------------------- + template + class CompatAlloc + { // general case : be transparent + public: + typedef Alloc compatible_type; + }; + + } //N.S. io +} // N.S. boost +#endif // include guard diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/config_macros.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/config_macros.hpp new file mode 100644 index 0000000..44d1e86 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/config_macros.hpp @@ -0,0 +1,95 @@ +// -*- C++ -*- +// ---------------------------------------------------------------------------- +// config_macros.hpp : configuration macros for the format library +// only BOOST_IO_STD is absolutely needed (it should be 'std::' in general) +// others are compiler-specific workaround macros used in #ifdef switches +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// see http://www.boost.org/libs/format for library home page + + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_CONFIG_MACROS_HPP +#define BOOST_FORMAT_CONFIG_MACROS_HPP + +#include +#include + +// make sure our local macros wont override something : +#if defined(BOOST_NO_LOCALE_ISDIGIT) || defined(BOOST_OVERLOAD_FOR_NON_CONST) \ + || defined(BOOST_IO_STD) || defined( BOOST_IO_NEEDS_USING_DECLARATION ) \ + || defined(BOOST_NO_TEMPLATE_STD_STREAM) \ + || defined(BOOST_FORMAT_STREAMBUF_DEFINED) || defined(BOOST_FORMAT_OSTREAM_DEFINED) +#error "boost::format uses a local macro that is already defined." +#endif + +// specific workarounds. each header can define BOOS_IO_STD if it +// needs. (e.g. because of IO_NEEDS_USING_DECLARATION) +#include +#include + +#ifndef BOOST_IO_STD +# define BOOST_IO_STD ::std:: +#endif + +#if defined(BOOST_NO_STD_LOCALE) || \ + ( BOOST_WORKAROUND(__BORLANDC__, <= 0x564) \ + || BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT( 0x570 ) ) ) +// some future __BORLANDC__ >0x564 versions might not need this +// 0x570 is Borland's kylix branch +#define BOOST_NO_LOCALE_ISDIGIT +#endif + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x570) ) || BOOST_WORKAROUND( BOOST_MSVC, BOOST_TESTED_AT(1300)) +#define BOOST_NO_OVERLOAD_FOR_NON_CONST +#endif + +// **** Workaround for io streams, stlport and msvc. +#ifdef BOOST_IO_NEEDS_USING_DECLARATION +namespace boost { + using std::char_traits; + using std::basic_ostream; + namespace io { + using std::basic_ostream; + namespace detail { + using std::basic_ios; + using std::basic_ostream; + } + } +#if ! defined(BOOST_NO_STD_LOCALE) + using std::locale; + namespace io { + using std::locale; + namespace detail { + using std::locale; + } + } +#endif // locale +} + // -end N.S. boost +#endif // needs_using_declaration + +#if ! defined(BOOST_NO_STD_LOCALE) +#include +#endif + + +// *** hide std::locale if it doesnt exist. +// this typedef is either std::locale or int, avoids placing ifdefs everywhere +namespace boost { namespace io { namespace detail { +#if ! defined(BOOST_NO_STD_LOCALE) + typedef BOOST_IO_STD locale locale_t; +#else + typedef int locale_t; +#endif +} } } + + +// ---------------------------------------------------------------------------- + +#endif // BOOST_FORMAT_MACROS_DEFAULT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/msvc_disambiguater.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/msvc_disambiguater.hpp new file mode 100644 index 0000000..c2692c4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/msvc_disambiguater.hpp @@ -0,0 +1,54 @@ +// ---------------------------------------------------------------------------- +// msvc_disambiguater.hpp : msvc workarounds. (for put_{head|last} overloads) +// the trick was described in boost's list by Aleksey Gurtovoy +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// see http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_MSVC_DISAMBIGUATER_HPP +#define BOOST_MSVC_DISAMBIGUATER_HPP + +#if BOOST_WORKAROUND(__DECCXX_VER, BOOST_TESTED_AT(60590042)) + +#include +#include + +namespace boost { +namespace io { +namespace detail { + +template< class Ch, class Tr, class T > +struct disambiguater +{ + template< typename U > + static void put_head(BOOST_IO_STD basic_ostream& os, group1 const& x, long) + { + os << group_head(x.a1_); + } + static void put_head(BOOST_IO_STD basic_ostream& os, T const& x, int) + { + } + template< typename U > + static void put_last(BOOST_IO_STD basic_ostream& os, group1 const& x, long) + { + os << group_last(x.a1_); + } + static void put_last(BOOST_IO_STD basic_ostream& os, T const& x, int) + { + os << x; + } +}; + +} // namespace detail +} // namespace io +} // namespace boost + +#endif // -__DECCXX_VER + +#endif // -BOOST_MSVC_DISAMBIGUATER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/unset_macros.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/unset_macros.hpp new file mode 100644 index 0000000..b3ac47b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/unset_macros.hpp @@ -0,0 +1,34 @@ +// ---------------------------------------------------------------------------- +// unset_macros.hpp +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +// *** Undefine 'local' macros : +#ifdef BOOST_NO_OVERLOAD_FOR_NON_CONST +#undef BOOST_NO_OVERLOAD_FOR_NON_CONST +#endif +#ifdef BOOST_NO_LOCALE_ISDIGIT +#undef BOOST_NO_LOCALE_ISDIGIT +#endif +#ifdef BOOST_IO_STD +#undef BOOST_IO_STD +#endif +#ifdef BOOST_IO_NEEDS_USING_DECLARATION +#undef BOOST_IO_NEEDS_USING_DECLARATION +#endif +#ifdef BOOST_NO_TEMPLATE_STD_STREAM +#undef BOOST_NO_TEMPLATE_STD_STREAM +#endif +#ifdef BOOST_FORMAT_STREAMBUF_DEFINED +#undef BOOST_FORMAT_STREAMBUF_DEFINED +#endif +#ifdef BOOST_FORMAT_OSTREAM_DEFINED +#undef BOOST_FORMAT_OSTREAM_DEFINED +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_gcc-2_95.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_gcc-2_95.hpp new file mode 100644 index 0000000..8c49d42 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_gcc-2_95.hpp @@ -0,0 +1,162 @@ +// ---------------------------------------------------------------------------- +// workarounds for gcc < 3.0. +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + + +// ---------------------------------------------------------------------------- + +// There's a lot to do, the stdlib shipped with gcc prior to 3.x +// was terribly non-conforming. +// . defines macros switches +// . supplies template classes basic_foo where gcc only supplies foo. +// i.e : +// - basic_ios from ios +// - basic_ostream from ostream +// - basic_srteambuf from streambuf +// these can be used transparently. (it obviously does not work for wchar_t) +// . specialise CompatAlloc and CompatTraits to wrap gcc-2.95's +// string_char_traits and std::alloc + +#if BOOST_WORKAROUND(__GNUC__, < 3) && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) + // only for gcc-2.95's native stdlib + +#ifndef BOOST_FORMAT_WORKAROUNDS_GCC295_H +#define BOOST_FORMAT_WORKAROUNDS_GCC295_H + +// SGI STL doesnt have and others, so we need iostream. +#include +#define BOOST_FORMAT_OSTREAM_DEFINED + +#include +#define BOOST_FORMAT_STREAMBUF_DEFINED + +#define BOOST_NO_TEMPLATE_STD_STREAM + +#ifndef BOOST_IO_STD +# define BOOST_IO_STD std:: +#endif + + + +// *** +// gcc's simple classes turned into standard-like template classes : + +namespace std { + + + // gcc has string_char_traits, it's incomplete. + // we declare a std::char_traits, and specialize CompatTraits<..> on it + // to do what is required + template + class char_traits; // no definition here, we will just use it as a tag. + + template + class basic_streambuf; + + template + class basic_streambuf : public streambuf { + }; + + template > + class basic_ios; + + template + class basic_ios : public ostream { + public: + basic_ios(streambuf * p) : ostream(p) {}; + char fill() const { return ios::fill(); } // gcc returns wchar.. + char fill(char c) { return ios::fill(c); } // gcc takes wchar.. + char widen(char c) { return c; } + char narrow(char c, char def) { return c; } + basic_ios& copyfmt(const ios& right) { + fill(right.fill()); + flags(right.flags() ); + exceptions(right.exceptions()); + width(right.width()); + precision(right.precision()); + return *this; + } + }; + + + typedef ios ios_base; + + template + class basic_ostream; + + template + class basic_ostream : public basic_ios + { + public: + basic_ostream(streambuf * p) : basic_ios (p) {} + }; + +} // namespace std + + +namespace boost { + namespace io { + + + // ** CompatTraits gcc2.95 specialisations ---------------------------- + template + class CompatTraits< ::std::string_char_traits > + : public ::std::string_char_traits + { + public: + typedef CompatTraits compatible_type; + + typedef Ch char_type; + typedef int int_type; + typedef ::std::streampos pos_type; + typedef ::std::streamoff off_type; + + static char_type + to_char_type(const int_type& meta) { + return static_cast(meta); } + static int_type + to_int_type(const char_type& ch) { + return static_cast(static_cast(ch) );} + static bool + eq_int_type(const int_type& left, const int_type& right) { + return left == right; } + static int_type + eof() { + return static_cast(EOF); + } + static int_type + not_eof(const int_type& meta) { + return (meta == eof()) ? 0 : meta; + } + }; + + template + class CompatTraits< ::std::char_traits > { + public: + typedef CompatTraits< ::std::string_char_traits > compatible_type; + }; + + // ** CompatAlloc gcc-2.95 specialisations --------------------------- + template<> + class CompatAlloc< ::std::alloc> + { + public: + typedef ::std::allocator compatible_type; + }; + + } // N.S. io +} // N.S. boost + + + + + +#endif // include guard + +#endif // if workaround diff --git a/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_stlport.hpp b/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_stlport.hpp new file mode 100644 index 0000000..5d435b9 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/detail/workarounds_stlport.hpp @@ -0,0 +1,36 @@ +// ---------------------------------------------------------------------------- +// workarounds_stlport.hpp : workaround STLport issues +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// see http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_MACROS_STLPORT_HPP +#define BOOST_MACROS_STLPORT_HPP + +// *** This should go to "boost/config/stdlib/stlport.hpp". + +// If the streams are not native and there are problems with using templates +// accross namespaces, we define some macros to enable a workaround for this. + +// STLport 4.5 +#if !defined(_STLP_OWN_IOSTREAMS) && defined(_STLP_USE_NAMESPACES) && defined(BOOST_NO_USING_TEMPLATE) +# define BOOST_IO_STD +# define BOOST_IO_NEEDS_USING_DECLARATION +#endif + +// STLport 4.0 +#if !defined(__SGI_STL_OWN_IOSTREAMS) && defined(__STL_USE_OWN_NAMESPACE) && defined(BOOST_NO_USING_TEMPLATE) +# define BOOST_IO_STD +# define BOOST_IO_NEEDS_USING_DECLARATION +#endif + + +// ---------------------------------------------------------------------------- + +#endif // BOOST_MACROS_STLPORT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/exceptions.hpp b/thirdparty/source/boost_1_61_0/boost/format/exceptions.hpp new file mode 100644 index 0000000..56ee30d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/exceptions.hpp @@ -0,0 +1,103 @@ +// ---------------------------------------------------------------------------- +// boost/format/exceptions.hpp +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// +// See http://www.boost.org/libs/format/ for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_EXCEPTIONS_HPP +#define BOOST_FORMAT_EXCEPTIONS_HPP + + +#include + + +namespace boost { + + namespace io { + +// **** exceptions ----------------------------------------------- + + class format_error : public std::exception + { + public: + format_error() {} + virtual const char *what() const throw() { + return "boost::format_error: " + "format generic failure"; + } + }; + + class bad_format_string : public format_error + { + std::size_t pos_, next_; + public: + bad_format_string(std::size_t pos, std::size_t size) + : pos_(pos), next_(size) {} + std::size_t get_pos() const { return pos_; } + std::size_t get_next() const { return next_; } + virtual const char *what() const throw() { + return "boost::bad_format_string: format-string is ill-formed"; + } + }; + + class too_few_args : public format_error + { + std::size_t cur_, expected_; + public: + too_few_args(std::size_t cur, std::size_t expected) + : cur_(cur), expected_(expected) {} + std::size_t get_cur() const { return cur_; } + std::size_t get_expected() const { return expected_; } + virtual const char *what() const throw() { + return "boost::too_few_args: " + "format-string referred to more arguments than were passed"; + } + }; + + class too_many_args : public format_error + { + std::size_t cur_, expected_; + public: + too_many_args(std::size_t cur, std::size_t expected) + : cur_(cur), expected_(expected) {} + std::size_t get_cur() const { return cur_; } + std::size_t get_expected() const { return expected_; } + virtual const char *what() const throw() { + return "boost::too_many_args: " + "format-string referred to fewer arguments than were passed"; + } + }; + + + class out_of_range : public format_error + { + int index_, beg_, end_; // range is [ beg, end [ + public: + out_of_range(int index, int beg, int end) + : index_(index), beg_(beg), end_(end) {} + int get_index() const { return index_; } + int get_beg() const { return beg_; } + int get_end() const { return end_; } + virtual const char *what() const throw() { + return "boost::out_of_range: " + "tried to refer to an argument (or item) number which" + " is out of range, according to the format string"; + } + }; + + + } // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_EXCEPTIONS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/feed_args.hpp b/thirdparty/source/boost_1_61_0/boost/format/feed_args.hpp new file mode 100644 index 0000000..fa45d21 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/feed_args.hpp @@ -0,0 +1,319 @@ +// ---------------------------------------------------------------------------- +// feed_args.hpp : functions for processing each argument +// (feed, feed_manip, and distribute) +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_FEED_ARGS_HPP +#define BOOST_FORMAT_FEED_ARGS_HPP + +#include +#include +#include + +#include +#include +#include + +namespace boost { +namespace io { +namespace detail { + + template + void mk_str( std::basic_string & res, + const Ch * beg, + typename std::basic_string::size_type size, + std::streamsize w, + const Ch fill_char, + std::ios_base::fmtflags f, + const Ch prefix_space, // 0 if no space-padding + bool center) + // applies centered/left/right padding to the string [beg, beg+size[ + // Effects : the result is placed in res. + { + typedef typename std::basic_string::size_type size_type; + res.resize(0); + if(w<=0 || static_cast(w) <=size) { + // no need to pad. + res.reserve(size + !!prefix_space); + if(prefix_space) + res.append(1, prefix_space); + if (size) + res.append(beg, size); + } + else { + std::streamsize n=static_cast(w-size-!!prefix_space); + std::streamsize n_after = 0, n_before = 0; + res.reserve(static_cast(w)); // allocate once for the 2 inserts + if(center) + n_after = n/2, n_before = n - n_after; + else + if(f & std::ios_base::left) + n_after = n; + else + n_before = n; + // now make the res string : + if(n_before) res.append(static_cast(n_before), fill_char); + if(prefix_space) + res.append(1, prefix_space); + if (size) + res.append(beg, size); + if(n_after) res.append(static_cast(n_after), fill_char); + } + } // -mk_str(..) + + +#if BOOST_WORKAROUND(__DECCXX_VER, BOOST_TESTED_AT(60590042)) +// __DECCXX needs to be tricked to disambiguate this simple overload.. +// the trick is in "boost/format/msvc_disambiguater.hpp" + + template< class Ch, class Tr, class T> inline + void put_head (BOOST_IO_STD basic_ostream & os, const T& x ) { + disambiguater::put_head(os, x, 1L); + } + template< class Ch, class Tr, class T> inline + void put_last (BOOST_IO_STD basic_ostream & os, const T& x ) { + disambiguater::put_last(os, x, 1L); + } + +#else + + template< class Ch, class Tr, class T> inline + void put_head (BOOST_IO_STD basic_ostream &, const T& ) { + } + + template< class Ch, class Tr, class T> inline + void put_head( BOOST_IO_STD basic_ostream & os, const group1& x ) { + os << group_head(x.a1_); // send the first N-1 items, not the last + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream & os, const T& x ) { + os << x ; + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream & os, const group1& x ) { + os << group_last(x.a1_); // this selects the last element + } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template< class Ch, class Tr, class T> inline + void put_head( BOOST_IO_STD basic_ostream &, T& ) { + } + + template< class Ch, class Tr, class T> inline + void put_last( BOOST_IO_STD basic_ostream & os, T& x) { + os << x ; + } +#endif +#endif // -__DECCXX workaround + + template< class Ch, class Tr, class T> + void call_put_head(BOOST_IO_STD basic_ostream & os, const void* x) { + put_head(os, *(typename ::boost::remove_reference::type*)x); + } + + template< class Ch, class Tr, class T> + void call_put_last(BOOST_IO_STD basic_ostream & os, const void* x) { + put_last(os, *(T*)x); + } + + template< class Ch, class Tr> + struct put_holder { + template + put_holder(T& t) + : arg(&t), + put_head(&call_put_head), + put_last(&call_put_last) + {} + const void* arg; + void (*put_head)(BOOST_IO_STD basic_ostream & os, const void* x); + void (*put_last)(BOOST_IO_STD basic_ostream & os, const void* x); + }; + + template< class Ch, class Tr> inline + void put_head( BOOST_IO_STD basic_ostream & os, const put_holder& t) { + t.put_head(os, t.arg); + } + + template< class Ch, class Tr> inline + void put_last( BOOST_IO_STD basic_ostream & os, const put_holder& t) { + t.put_last(os, t.arg); + } + + + template< class Ch, class Tr, class Alloc, class T> + void put( T x, + const format_item& specs, + typename basic_format::string_type& res, + typename basic_format::internal_streambuf_t & buf, + io::detail::locale_t *loc_p = NULL) + { +#ifdef BOOST_MSVC + // If std::min or std::max are already instantiated + // at this point then we get a blizzard of warning messages when we call + // those templates with std::size_t as arguments. Weird and very annoyning... +#pragma warning(push) +#pragma warning(disable:4267) +#endif + // does the actual conversion of x, with given params, into a string + // using the supplied stringbuf. + + typedef typename basic_format::string_type string_type; + typedef typename basic_format::format_item_t format_item_t; + typedef typename string_type::size_type size_type; + + basic_oaltstringstream oss( &buf); + + if(loc_p != NULL) + oss.imbue(*loc_p); + + specs.fmtstate_.apply_on(oss, loc_p); + + // the stream format state can be modified by manipulators in the argument : + put_head( oss, x ); + // in case x is a group, apply the manip part of it, + // in order to find width + + const std::ios_base::fmtflags fl=oss.flags(); + const bool internal = (fl & std::ios_base::internal) != 0; + const std::streamsize w = oss.width(); + const bool two_stepped_padding= internal && (w!=0); + + res.resize(0); + if(! two_stepped_padding) { + if(w>0) // handle padding via mk_str, not natively in stream + oss.width(0); + put_last( oss, x); + const Ch * res_beg = buf.pbase(); + Ch prefix_space = 0; + if(specs.pad_scheme_ & format_item_t::spacepad) + if(buf.pcount()== 0 || + (res_beg[0] !=oss.widen('+') && res_beg[0] !=oss.widen('-') )) + prefix_space = oss.widen(' '); + size_type res_size = (std::min)( + static_cast(specs.truncate_ - !!prefix_space), + buf.pcount() ); + mk_str(res, res_beg, res_size, w, oss.fill(), fl, + prefix_space, (specs.pad_scheme_ & format_item_t::centered) !=0 ); + } + else { // 2-stepped padding + // internal can be implied by zeropad, or user-set. + // left, right, and centered alignment overrule internal, + // but spacepad or truncate might be mixed with internal (using manipulator) + put_last( oss, x); // may pad + const Ch * res_beg = buf.pbase(); + size_type res_size = buf.pcount(); + bool prefix_space=false; + if(specs.pad_scheme_ & format_item_t::spacepad) + if(buf.pcount()== 0 || + (res_beg[0] !=oss.widen('+') && res_beg[0] !=oss.widen('-') )) + prefix_space = true; + if(res_size == static_cast(w) && w<=specs.truncate_ && !prefix_space) { + // okay, only one thing was printed and padded, so res is fine + res.assign(res_beg, res_size); + } + else { // length w exceeded + // either it was multi-output with first output padding up all width.. + // either it was one big arg and we are fine. + // Note that res_size oss2( &buf); + specs.fmtstate_.apply_on(oss2, loc_p); + put_head( oss2, x ); + + oss2.width(0); + if(prefix_space) + oss2 << ' '; + put_last(oss2, x ); + if(buf.pcount()==0 && specs.pad_scheme_ & format_item_t::spacepad) { + prefix_space =true; + oss2 << ' '; + } + // we now have the minimal-length output + const Ch * tmp_beg = buf.pbase(); + size_type tmp_size = (std::min)(static_cast(specs.truncate_), + buf.pcount() ); + + + if(static_cast(w) <= tmp_size) { + // minimal length is already >= w, so no padding (cool!) + res.assign(tmp_beg, tmp_size); + } + else { // hum.. we need to pad (multi_output, or spacepad present) + //find where we should pad + size_type sz = (std::min)(res_size + (prefix_space ? 1 : 0), tmp_size); + size_type i = prefix_space; + for(; i=tmp_size) i=prefix_space; + res.assign(tmp_beg, i); + std::streamsize d = w - static_cast(tmp_size); + BOOST_ASSERT(d>0); + res.append(static_cast( d ), oss2.fill()); + res.append(tmp_beg+i, tmp_size-i); + BOOST_ASSERT(i+(tmp_size-i)+(std::max)(d,(std::streamsize)0) + == static_cast(w)); + BOOST_ASSERT(res.size() == static_cast(w)); + } + } + } + buf.clear_buffer(); +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + } // end- put(..) + + + template< class Ch, class Tr, class Alloc, class T> + void distribute (basic_format& self, T x) { + // call put(x, ..) on every occurrence of the current argument : + if(self.cur_arg_ >= self.num_args_) { + if( self.exceptions() & too_many_args_bit ) + boost::throw_exception(too_many_args(self.cur_arg_, self.num_args_)); + else return; + } + for(unsigned long i=0; i < self.items_.size(); ++i) { + if(self.items_[i].argN_ == self.cur_arg_) { + put (x, self.items_[i], self.items_[i].res_, + self.buf_, boost::get_pointer(self.loc_) ); + } + } + } + + template + basic_format& + feed_impl (basic_format& self, T x) { + if(self.dumped_) self.clear(); + distribute (self, x); + ++self.cur_arg_; + if(self.bound_.size() != 0) { + while( self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_] ) + ++self.cur_arg_; + } + return self; + } + + template inline + basic_format& + feed (basic_format& self, T x) { + return feed_impl&>(self, put_holder(x)); + } + +} // namespace detail +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_FEED_ARGS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/format_class.hpp b/thirdparty/source/boost_1_61_0/boost/format/format_class.hpp new file mode 100644 index 0000000..2ac59ef --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/format_class.hpp @@ -0,0 +1,168 @@ +// ---------------------------------------------------------------------------- +// format_class.hpp : class interface +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_CLASS_HPP +#define BOOST_FORMAT_CLASS_HPP + + +#include +#include + +#include // to store locale when needed + +#include +#include +#include +#include + +namespace boost { + + template + class basic_format + { + typedef typename io::CompatTraits::compatible_type compat_traits; + public: + typedef Ch CharT; // borland fails in operator% if we use Ch and Tr directly + typedef std::basic_string string_type; + typedef typename string_type::size_type size_type; + typedef io::detail::format_item format_item_t; + typedef io::basic_altstringbuf internal_streambuf_t; + + + explicit basic_format(const Ch* str=NULL); + explicit basic_format(const string_type& s); + basic_format(const basic_format& x); + basic_format& operator= (const basic_format& x); + void swap(basic_format& x); + +#if !defined(BOOST_NO_STD_LOCALE) + explicit basic_format(const Ch* str, const std::locale & loc); + explicit basic_format(const string_type& s, const std::locale & loc); +#endif + io::detail::locale_t getloc() const; + + basic_format& clear(); // empty all converted string buffers (except bound items) + basic_format& clear_binds(); // unbind all bound items, and call clear() + basic_format& parse(const string_type&); // resets buffers and parse a new format string + + // ** formatted result ** // + size_type size() const; // sum of the current string pieces sizes + string_type str() const; // final string + + // ** arguments passing ** // + template + basic_format& operator%(const T& x) + { return io::detail::feed(*this,x); } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + template basic_format& operator%(T& x) + { return io::detail::feed(*this,x); } +#endif + +#if defined(__GNUC__) + // GCC can't handle anonymous enums without some help + // ** arguments passing ** // + basic_format& operator%(const int& x) + { return io::detail::feed(*this,x); } + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + basic_format& operator%(int& x) + { return io::detail::feed(*this,x); } +#endif +#endif + + // The total number of arguments expected to be passed to the format objectt + int expected_args() const + { return num_args_; } + // The number of arguments currently bound (see bind_arg(..) ) + int bound_args() const; + // The number of arguments currently fed to the format object + int fed_args() const; + // The index (1-based) of the current argument (i.e. next to be formatted) + int cur_arg() const; + // The number of arguments still required to be fed + int remaining_args() const; // same as expected_args() - bound_args() - fed_args() + + + // ** object modifying **// + template + basic_format& bind_arg(int argN, const T& val) + { return io::detail::bind_arg_body(*this, argN, val); } + basic_format& clear_bind(int argN); + template + basic_format& modify_item(int itemN, T manipulator) + { return io::detail::modify_item_body (*this, itemN, manipulator);} + + // Choosing which errors will throw exceptions : + unsigned char exceptions() const; + unsigned char exceptions(unsigned char newexcept); + +#if !defined( BOOST_NO_MEMBER_TEMPLATE_FRIENDS ) \ + && !BOOST_WORKAROUND(__BORLANDC__, <= 0x570) \ + && !BOOST_WORKAROUND( _CRAYC, != 0) \ + && !BOOST_WORKAROUND(__DECCXX_VER, BOOST_TESTED_AT(60590042)) + // use friend templates and private members only if supported + +#ifndef BOOST_NO_TEMPLATE_STD_STREAM + template + friend std::basic_ostream & + operator<<( std::basic_ostream & , + const basic_format& ); +#else + template + friend std::ostream & + operator<<( std::ostream & , + const basic_format& ); +#endif + + template + friend basic_format& + io::detail::feed_impl (basic_format&, T); + + template friend + void io::detail::distribute (basic_format&, T); + + template friend + basic_format& + io::detail::modify_item_body (basic_format&, int, T); + + template friend + basic_format& + io::detail::bind_arg_body (basic_format&, int, const T&); + + private: +#endif + typedef io::detail::stream_format_state stream_format_state; + // flag bits, used for style_ + enum style_values { ordered = 1, // set only if all directives are positional + special_needs = 4 }; + + void make_or_reuse_data(std::size_t nbitems);// used for (re-)initialisation + + // member data --------------------------------------------// + std::vector items_; // each '%..' directive leads to a format_item + std::vector bound_; // stores which arguments were bound. size() == 0 || num_args + + int style_; // style of format-string : positional or not, etc + int cur_arg_; // keep track of wich argument is current + int num_args_; // number of expected arguments + mutable bool dumped_; // true only after call to str() or << + string_type prefix_; // piece of string to insert before first item + unsigned char exceptions_; + internal_streambuf_t buf_; // the internal stream buffer. + boost::optional loc_; + }; // class basic_format + +} // namespace boost + + +#endif // BOOST_FORMAT_CLASS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/format_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/format/format_fwd.hpp new file mode 100644 index 0000000..16b8565 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/format_fwd.hpp @@ -0,0 +1,43 @@ +// ---------------------------------------------------------------------------- +// format_fwd.hpp : forward declarations +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_FWD_HPP +#define BOOST_FORMAT_FWD_HPP + +#include +#include + +#include + +namespace boost { + + template , class Alloc = std::allocator > + class basic_format; + + typedef basic_format format; + +#if !defined(BOOST_NO_STD_WSTRING) && !defined(BOOST_NO_STD_WSTREAMBUF) + typedef basic_format wformat; +#endif + + namespace io { + enum format_error_bits { bad_format_string_bit = 1, + too_few_args_bit = 2, too_many_args_bit = 4, + out_of_range_bit = 8, + all_error_bits = 255, no_error_bits=0 }; + + } // namespace io + +} // namespace boost + +#endif // BOOST_FORMAT_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/format_implementation.hpp b/thirdparty/source/boost_1_61_0/boost/format/format_implementation.hpp new file mode 100644 index 0000000..2abb5c4 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/format_implementation.hpp @@ -0,0 +1,329 @@ +// ---------------------------------------------------------------------------- +// format_implementation.hpp Implementation of the basic_format class +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_IMPLEMENTATION_HPP +#define BOOST_FORMAT_IMPLEMENTATION_HPP + +#include +#include +#include +#include +#include // std::swap + +namespace boost { + +// --- basic_format implementation -----------------------------------------// + + template< class Ch, class Tr, class Alloc> + basic_format:: basic_format(const Ch* s) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + exceptions_(io::all_error_bits) + { + if( s) + parse( s ); + } + +#if !defined(BOOST_NO_STD_LOCALE) + template< class Ch, class Tr, class Alloc> + basic_format:: basic_format(const Ch* s, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + exceptions_(io::all_error_bits), loc_(loc) + { + if(s) parse( s ); + } + + template< class Ch, class Tr, class Alloc> + basic_format:: basic_format(const string_type& s, const std::locale & loc) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + exceptions_(io::all_error_bits), loc_(loc) + { + parse(s); + } +#endif // ! BOOST_NO_STD_LOCALE + template< class Ch, class Tr, class Alloc> + io::detail::locale_t basic_format:: + getloc() const { + return loc_ ? loc_.get() : io::detail::locale_t(); + } + + template< class Ch, class Tr, class Alloc> + basic_format:: basic_format(const string_type& s) + : style_(0), cur_arg_(0), num_args_(0), dumped_(false), + exceptions_(io::all_error_bits) + { + parse(s); + } + + template< class Ch, class Tr, class Alloc> // just don't copy the buf_ member + basic_format:: basic_format(const basic_format& x) + : items_(x.items_), bound_(x.bound_), style_(x.style_), + cur_arg_(x.cur_arg_), num_args_(x.num_args_), dumped_(x.dumped_), + prefix_(x.prefix_), exceptions_(x.exceptions_), loc_(x.loc_) + { + } + + template< class Ch, class Tr, class Alloc> // just don't copy the buf_ member + basic_format& basic_format:: + operator= (const basic_format& x) { + if(this == &x) + return *this; + (basic_format(x)).swap(*this); + return *this; + } + template< class Ch, class Tr, class Alloc> + void basic_format:: + swap (basic_format & x) { + std::swap(exceptions_, x.exceptions_); + std::swap(style_, x.style_); + std::swap(cur_arg_, x.cur_arg_); + std::swap(num_args_, x.num_args_); + std::swap(dumped_, x.dumped_); + + items_.swap(x.items_); + prefix_.swap(x.prefix_); + bound_.swap(x.bound_); + } + + template< class Ch, class Tr, class Alloc> + unsigned char basic_format:: exceptions() const { + return exceptions_; + } + + template< class Ch, class Tr, class Alloc> + unsigned char basic_format:: exceptions(unsigned char newexcept) { + unsigned char swp = exceptions_; + exceptions_ = newexcept; + return swp; + } + + template + void basic_format:: + make_or_reuse_data (std::size_t nbitems) { +#if !defined(BOOST_NO_STD_LOCALE) + Ch fill = ( BOOST_USE_FACET(std::ctype, getloc()) ). widen(' '); +#else + Ch fill = ' '; +#endif + if(items_.size() == 0) + items_.assign( nbitems, format_item_t(fill) ); + else { + if(nbitems>items_.size()) + items_.resize(nbitems, format_item_t(fill)); + bound_.resize(0); + for(std::size_t i=0; i < nbitems; ++i) + items_[i].reset(fill); // strings are resized, instead of reallocated + } + prefix_.resize(0); + } + + template< class Ch, class Tr, class Alloc> + basic_format& basic_format:: + clear () { + // empty the string buffers (except bound arguments) + // and make the format object ready for formatting a new set of arguments + + BOOST_ASSERT( bound_.size()==0 || num_args_ == static_cast(bound_.size()) ); + + for(unsigned long i=0; i + basic_format& basic_format:: + clear_binds () { + // remove all binds, then clear() + bound_.resize(0); + clear(); + return *this; + } + + template< class Ch, class Tr, class Alloc> + basic_format& basic_format:: + clear_bind (int argN) { + // remove the bind of ONE argument then clear() + if(argN<1 || argN > num_args_ || bound_.size()==0 || !bound_[argN-1] ) { + if( exceptions() & io::out_of_range_bit) + boost::throw_exception(io::out_of_range(argN, 1, num_args_+1 ) ); + else return *this; + } + bound_[argN-1]=false; + clear(); + return *this; + } + + template< class Ch, class Tr, class Alloc> + int basic_format:: + bound_args() const { + if(bound_.size()==0) + return 0; + int n=0; + for(int i=0; i + int basic_format:: + fed_args() const { + if(bound_.size()==0) + return cur_arg_; + int n=0; + for(int i=0; i + int basic_format:: + cur_arg() const { + return cur_arg_+1; } + + template< class Ch, class Tr, class Alloc> + int basic_format:: + remaining_args() const { + if(bound_.size()==0) + return num_args_-cur_arg_; + int n=0; + for(int i=cur_arg_; i + typename basic_format::string_type + basic_format:: + str () const { + if(items_.size()==0) + return prefix_; + if( cur_arg_ < num_args_) + if( exceptions() & io::too_few_args_bit ) + // not enough variables supplied + boost::throw_exception(io::too_few_args(cur_arg_, num_args_)); + + unsigned long i; + string_type res; + res.reserve(size()); + res += prefix_; + for(i=0; i < items_.size(); ++i) { + const format_item_t& item = items_[i]; + res += item.res_; + if( item.argN_ == format_item_t::argN_tabulation) { + BOOST_ASSERT( item.pad_scheme_ & format_item_t::tabulation); + if( static_cast(item.fmtstate_.width_) > res.size() ) + res.append( static_cast(item.fmtstate_.width_) - res.size(), + item.fmtstate_.fill_ ); + } + res += item.appendix_; + } + dumped_=true; + return res; + } + template< class Ch, class Tr, class Alloc> + typename std::basic_string::size_type basic_format:: + size () const { +#ifdef BOOST_MSVC + // If std::min or std::max are already instantiated + // at this point then we get a blizzard of warning messages when we call + // those templates with std::size_t as arguments. Weird and very annoyning... +#pragma warning(push) +#pragma warning(disable:4267) +#endif + BOOST_USING_STD_MAX(); + size_type sz = prefix_.size(); + unsigned long i; + for(i=0; i < items_.size(); ++i) { + const format_item_t& item = items_[i]; + sz += item.res_.size(); + if( item.argN_ == format_item_t::argN_tabulation) + sz = max BOOST_PREVENT_MACRO_SUBSTITUTION (sz, + static_cast(item.fmtstate_.width_) ); + sz += item.appendix_.size(); + } + return sz; +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + } + +namespace io { +namespace detail { + + template + basic_format& + bind_arg_body (basic_format& self, int argN, const T& val) { + // bind one argument to a fixed value + // this is persistent over clear() calls, thus also over str() and << + if(self.dumped_) + self.clear(); // needed because we will modify cur_arg_ + if(argN<1 || argN > self.num_args_) { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range(argN, 1, self.num_args_+1 ) ); + else return self; + } + if(self.bound_.size()==0) + self.bound_.assign(self.num_args_,false); + else + BOOST_ASSERT( self.num_args_ == static_cast(self.bound_.size()) ); + int o_cur_arg = self.cur_arg_; + self.cur_arg_ = argN-1; // arrays begin at 0 + + self.bound_[self.cur_arg_]=false; // if already set, we unset and re-sets.. + self.operator%(val); // put val at the right place, because cur_arg is set + + + // Now re-position cur_arg before leaving : + self.cur_arg_ = o_cur_arg; + self.bound_[argN-1]=true; + if(self.cur_arg_ == argN-1 ) { + // hum, now this arg is bound, so move to next free arg + while(self.cur_arg_ < self.num_args_ && self.bound_[self.cur_arg_]) + ++self.cur_arg_; + } + // In any case, we either have all args, or are on an unbound arg : + BOOST_ASSERT( self.cur_arg_ >= self.num_args_ || ! self.bound_[self.cur_arg_]); + return self; + } + + template basic_format& + modify_item_body (basic_format& self, int itemN, T manipulator) { + // applies a manipulator to the format_item describing a given directive. + // this is a permanent change, clear or reset won't cancel that. + if(itemN<1 || itemN > static_cast(self.items_.size() )) { + if( self.exceptions() & io::out_of_range_bit ) + boost::throw_exception(io::out_of_range(itemN, 1, static_cast(self.items_.size()) )); + else return self; + } + self.items_[itemN-1].fmtstate_. template apply_manip ( manipulator ); + return self; + } + +} // namespace detail +} // namespace io +} // namespace boost + + + +#endif // BOOST_FORMAT_IMPLEMENTATION_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/free_funcs.hpp b/thirdparty/source/boost_1_61_0/boost/format/free_funcs.hpp new file mode 100644 index 0000000..3a51545 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/free_funcs.hpp @@ -0,0 +1,70 @@ +// ---------------------------------------------------------------------------- +// free_funcs.hpp : implementation of the free functions of boost::format +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_FUNCS_HPP +#define BOOST_FORMAT_FUNCS_HPP + +#include +#include + +namespace boost { + + template inline + std::basic_string str(const basic_format& f) { + // adds up all pieces of strings and converted items, and return the formatted string + return f.str(); + } + namespace io { + using ::boost::str; // keep compatibility with when it was defined in this N.S. + } // - namespace io + +#ifndef BOOST_NO_TEMPLATE_STD_STREAM + template + std::basic_ostream & + operator<<( std::basic_ostream & os, + const basic_format& f) +#else + template + std::ostream & + operator<<( std::ostream & os, + const basic_format& f) +#endif + // effect: "return os << str(f);" but we can do it faster + { + typedef boost::basic_format format_t; + if(f.items_.size()==0) + os << f.prefix_; + else { + if(f.cur_arg_ < f.num_args_) + if( f.exceptions() & io::too_few_args_bit ) + // not enough variables supplied + boost::throw_exception(io::too_few_args(f.cur_arg_, f.num_args_)); + if(f.style_ & format_t::special_needs) + os << f.str(); + else { + // else we dont have to count chars output, so we dump directly to os : + os << f.prefix_; + for(unsigned long i=0; i + + +namespace boost { +namespace io { + + +namespace detail { + + +// empty group, but useful even though. +struct group0 +{ + group0() {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << ( BOOST_IO_STD basic_ostream& os, + const group0& ) +{ + return os; +} + +template +struct group1 +{ + T1 a1_; + group1(T1 a1) + : a1_(a1) + {} +private: + group1& operator=(const group1&); +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group1& x) +{ + os << x.a1_; + return os; +} + + + + +template +struct group2 +{ + T1 a1_; + T2 a2_; + group2(T1 a1,T2 a2) + : a1_(a1),a2_(a2) + {} +private: + group2& operator=(const group2&); +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group2& x) +{ + os << x.a1_<< x.a2_; + return os; +} + +template +struct group3 +{ + T1 a1_; + T2 a2_; + T3 a3_; + group3(T1 a1,T2 a2,T3 a3) + : a1_(a1),a2_(a2),a3_(a3) + {} +private: + group3& operator=(const group3&); +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group3& x) +{ + os << x.a1_<< x.a2_<< x.a3_; + return os; +} + +template +struct group4 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + group4(T1 a1,T2 a2,T3 a3,T4 a4) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4) + {} +private: + group4& operator=(const group4&); +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group4& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_; + return os; +} + +template +struct group5 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + group5(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group5& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_; + return os; +} + +template +struct group6 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + group6(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group6& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_; + return os; +} + +template +struct group7 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + group7(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group7& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_; + return os; +} + +template +struct group8 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + group8(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group8& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_; + return os; +} + +template +struct group9 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + group9(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group9& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_; + return os; +} + +template +struct group10 +{ + T1 a1_; + T2 a2_; + T3 a3_; + T4 a4_; + T5 a5_; + T6 a6_; + T7 a7_; + T8 a8_; + T9 a9_; + T10 a10_; + group10(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9,T10 a10) + : a1_(a1),a2_(a2),a3_(a3),a4_(a4),a5_(a5),a6_(a6),a7_(a7),a8_(a8),a9_(a9),a10_(a10) + {} +}; + +template +inline +BOOST_IO_STD basic_ostream& +operator << (BOOST_IO_STD basic_ostream& os, + const group10& x) +{ + os << x.a1_<< x.a2_<< x.a3_<< x.a4_<< x.a5_<< x.a6_<< x.a7_<< x.a8_<< x.a9_<< x.a10_; + return os; +} + + + + +template +inline +group1 +group_head( group2 const& x) +{ + return group1 (x.a1_); +} + +template +inline +group1 +group_last( group2 const& x) +{ + return group1 (x.a2_); +} + + + +template +inline +group2 +group_head( group3 const& x) +{ + return group2 (x.a1_,x.a2_); +} + +template +inline +group1 +group_last( group3 const& x) +{ + return group1 (x.a3_); +} + + + +template +inline +group3 +group_head( group4 const& x) +{ + return group3 (x.a1_,x.a2_,x.a3_); +} + +template +inline +group1 +group_last( group4 const& x) +{ + return group1 (x.a4_); +} + + + +template +inline +group4 +group_head( group5 const& x) +{ + return group4 (x.a1_,x.a2_,x.a3_,x.a4_); +} + +template +inline +group1 +group_last( group5 const& x) +{ + return group1 (x.a5_); +} + + + +template +inline +group5 +group_head( group6 const& x) +{ + return group5 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_); +} + +template +inline +group1 +group_last( group6 const& x) +{ + return group1 (x.a6_); +} + + + +template +inline +group6 +group_head( group7 const& x) +{ + return group6 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_); +} + +template +inline +group1 +group_last( group7 const& x) +{ + return group1 (x.a7_); +} + + + +template +inline +group7 +group_head( group8 const& x) +{ + return group7 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_); +} + +template +inline +group1 +group_last( group8 const& x) +{ + return group1 (x.a8_); +} + + + +template +inline +group8 +group_head( group9 const& x) +{ + return group8 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_); +} + +template +inline +group1 +group_last( group9 const& x) +{ + return group1 (x.a9_); +} + + + +template +inline +group9 +group_head( group10 const& x) +{ + return group9 (x.a1_,x.a2_,x.a3_,x.a4_,x.a5_,x.a6_,x.a7_,x.a8_,x.a9_); +} + +template +inline +group1 +group_last( group10 const& x) +{ + return group1 (x.a10_); +} + + + + + +} // namespace detail + + + +// helper functions + + +inline detail::group1< detail::group0 > +group() { return detail::group1< detail::group0 > ( detail::group0() ); } + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var const& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var const& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var const& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var const& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var const& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var const& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var const& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var const& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var const& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#ifndef BOOST_NO_OVERLOAD_FOR_NON_CONST + +template +inline +detail::group1< detail::group2 > + group(T1 a1, Var& var) +{ + return detail::group1< detail::group2 > + ( detail::group2 + (a1, var) + ); +} + +template +inline +detail::group1< detail::group3 > + group(T1 a1,T2 a2, Var& var) +{ + return detail::group1< detail::group3 > + ( detail::group3 + (a1,a2, var) + ); +} + +template +inline +detail::group1< detail::group4 > + group(T1 a1,T2 a2,T3 a3, Var& var) +{ + return detail::group1< detail::group4 > + ( detail::group4 + (a1,a2,a3, var) + ); +} + +template +inline +detail::group1< detail::group5 > + group(T1 a1,T2 a2,T3 a3,T4 a4, Var& var) +{ + return detail::group1< detail::group5 > + ( detail::group5 + (a1,a2,a3,a4, var) + ); +} + +template +inline +detail::group1< detail::group6 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5, Var& var) +{ + return detail::group1< detail::group6 > + ( detail::group6 + (a1,a2,a3,a4,a5, var) + ); +} + +template +inline +detail::group1< detail::group7 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6, Var& var) +{ + return detail::group1< detail::group7 > + ( detail::group7 + (a1,a2,a3,a4,a5,a6, var) + ); +} + +template +inline +detail::group1< detail::group8 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7, Var& var) +{ + return detail::group1< detail::group8 > + ( detail::group8 + (a1,a2,a3,a4,a5,a6,a7, var) + ); +} + +template +inline +detail::group1< detail::group9 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8, Var& var) +{ + return detail::group1< detail::group9 > + ( detail::group9 + (a1,a2,a3,a4,a5,a6,a7,a8, var) + ); +} + +template +inline +detail::group1< detail::group10 > + group(T1 a1,T2 a2,T3 a3,T4 a4,T5 a5,T6 a6,T7 a7,T8 a8,T9 a9, Var& var) +{ + return detail::group1< detail::group10 > + ( detail::group10 + (a1,a2,a3,a4,a5,a6,a7,a8,a9, var) + ); +} + + +#endif // - BOOST_NO_OVERLOAD_FOR_NON_CONST + + +} // namespace io + +} // namespace boost + + +#endif // BOOST_FORMAT_GROUP_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/internals.hpp b/thirdparty/source/boost_1_61_0/boost/format/internals.hpp new file mode 100644 index 0000000..1c67006 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/internals.hpp @@ -0,0 +1,202 @@ +// ---------------------------------------------------------------------------- +// internals.hpp : internal structs : stream_format_state, format_item. +// included by format.hpp +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_INTERNALS_HPP +#define BOOST_FORMAT_INTERNALS_HPP + + +#include +#include +#include +#include +#include +#include // used as a dummy stream + +namespace boost { +namespace io { +namespace detail { + + +//---- stream_format_state --------------------------------------------------// + +// set of params that define the format state of a stream + template + struct stream_format_state + { + typedef BOOST_IO_STD basic_ios basic_ios; + + stream_format_state(Ch fill) { reset(fill); } +// stream_format_state(const basic_ios& os) { set_by_stream(os); } + + void reset(Ch fill); //- sets to default state. + void set_by_stream(const basic_ios& os); //- sets to os's state. + void apply_on(basic_ios & os, //- applies format_state to the stream + boost::io::detail::locale_t * loc_default = 0) const; + template + void apply_manip(T manipulator) //- modifies state by applying manipulator + { apply_manip_body( *this, manipulator) ; } + + // --- data --- + std::streamsize width_; + std::streamsize precision_; + Ch fill_; + std::ios_base::fmtflags flags_; + std::ios_base::iostate rdstate_; + std::ios_base::iostate exceptions_; + boost::optional loc_; + }; + + +//---- format_item ---------------------------------------------------------// + +// stores all parameters that can be specified in format strings + template + struct format_item + { + enum pad_values { zeropad = 1, spacepad =2, centered=4, tabulation = 8 }; + // 1. if zeropad is set, all other bits are not, + // 2. if tabulation is set, all others are not. + // centered and spacepad can be mixed freely. + enum arg_values { argN_no_posit = -1, // non-positional directive. will set argN later + argN_tabulation = -2, // tabulation directive. (no argument read) + argN_ignored = -3 // ignored directive. (no argument read) + }; + typedef BOOST_IO_STD basic_ios basic_ios; + typedef detail::stream_format_state stream_format_state; + typedef ::std::basic_string string_type; + + format_item(Ch fill) :argN_(argN_no_posit), fmtstate_(fill), + truncate_(max_streamsize()), pad_scheme_(0) {} + void reset(Ch fill); + void compute_states(); // sets states according to truncate and pad_scheme. + + static std::streamsize max_streamsize() { + return (std::numeric_limits::max)(); + } + + // --- data --- + int argN_; //- argument number (starts at 0, eg : %1 => argN=0) + // negative values for items that don't process an argument + string_type res_; //- result of the formatting of this item + string_type appendix_; //- piece of string between this item and the next + + stream_format_state fmtstate_;// set by parsing, is only affected by modify_item + + std::streamsize truncate_;//- is set for directives like %.5s that ask truncation + unsigned int pad_scheme_;//- several possible padding schemes can mix. see pad_values + }; + + + +//--- Definitions ------------------------------------------------------------ + +// - stream_format_state:: ------------------------------------------------- + template + void stream_format_state:: apply_on (basic_ios & os, + boost::io::detail::locale_t * loc_default) const { + // If a locale is available, set it first. "os.fill(fill_);" may chrash otherwise. +#if !defined(BOOST_NO_STD_LOCALE) + if(loc_) + os.imbue(loc_.get()); + else if(loc_default) + os.imbue(*loc_default); +#else + (void) loc_default; // keep compiler quiet if we don't support locales +#endif + // set the state of this stream according to our params + if(width_ != -1) + os.width(width_); + if(precision_ != -1) + os.precision(precision_); + if(fill_ != 0) + os.fill(fill_); + os.flags(flags_); + os.clear(rdstate_); + os.exceptions(exceptions_); + } + + template + void stream_format_state:: set_by_stream(const basic_ios& os) { + // set our params according to the state of this stream + flags_ = os.flags(); + width_ = os.width(); + precision_ = os.precision(); + fill_ = os.fill(); + rdstate_ = os.rdstate(); + exceptions_ = os.exceptions(); + } + + + template + void apply_manip_body( stream_format_state& self, + T manipulator) { + // modify our params according to the manipulator + basic_oaltstringstream ss; + self.apply_on( ss ); + ss << manipulator; + self.set_by_stream( ss ); + } + + template inline + void stream_format_state:: reset(Ch fill) { + // set our params to standard's default state. cf 27.4.4.1 of the C++ norm + width_=0; precision_=6; + fill_=fill; // default is widen(' '), but we cant compute it without the locale + flags_ = std::ios_base::dec | std::ios_base::skipws; + // the adjust_field part is left equal to 0, which means right. + exceptions_ = std::ios_base::goodbit; + rdstate_ = std::ios_base::goodbit; + } + + +// --- format_item:: -------------------------------------------------------- + + template + void format_item:: + reset (Ch fill) { + argN_=argN_no_posit; truncate_ = max_streamsize(); pad_scheme_ =0; + res_.resize(0); appendix_.resize(0); + fmtstate_.reset(fill); + } + + template + void format_item:: + compute_states() { + // reflect pad_scheme_ on fmt_state_ + // because some pad_schemes has complex consequences on several state params. + if(pad_scheme_ & zeropad) { + // ignore zeropad in left alignment : + if(fmtstate_.flags_ & std::ios_base::left) { + BOOST_ASSERT(!(fmtstate_.flags_ &(std::ios_base::adjustfield ^std::ios_base::left))); + // only left bit might be set. (not right, nor internal) + pad_scheme_ = pad_scheme_ & (~zeropad); + } + else { + pad_scheme_ &= ~spacepad; // printf ignores spacepad when zeropadding + fmtstate_.fill_='0'; + fmtstate_.flags_ = (fmtstate_.flags_ & ~std::ios_base::adjustfield) + | std::ios_base::internal; + // removes all adjustfield bits, and adds internal. + } + } + if(pad_scheme_ & spacepad) { + if(fmtstate_.flags_ & std::ios_base::showpos) + pad_scheme_ &= ~spacepad; + } + } + + +} } } // namespaces boost :: io :: detail + + +#endif // BOOST_FORMAT_INTERNALS_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/internals_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/format/internals_fwd.hpp new file mode 100644 index 0000000..18cf122 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/internals_fwd.hpp @@ -0,0 +1,64 @@ +// ---------------------------------------------------------------------------- +// internals_fwd.hpp : forward declarations, for internal headers +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_INTERNAL_FWD_HPP +#define BOOST_FORMAT_INTERNAL_FWD_HPP + +#include +#include + + +namespace boost { +namespace io { + +namespace detail { + template struct stream_format_state; + template struct format_item; + + + // these functions were intended as methods, + // but MSVC have problems with template member functions : + // defined in format_implementation.hpp : + template + basic_format& + modify_item_body (basic_format& self, + int itemN, T manipulator); + + template + basic_format& + bind_arg_body (basic_format& self, + int argN, const T& val); + + // in internals.hpp : + template + void apply_manip_body (stream_format_state& self, + T manipulator); + + // argument feeding (defined in feed_args.hpp ) : + template + void distribute (basic_format& self, T x); + + template + basic_format& + feed (basic_format& self, T x); + + template + basic_format& + feed_impl (basic_format& self, T x); + +} // namespace detail + +} // namespace io +} // namespace boost + + +#endif // BOOST_FORMAT_INTERNAL_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/format/parsing.hpp b/thirdparty/source/boost_1_61_0/boost/format/parsing.hpp new file mode 100644 index 0000000..04ddf36 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/format/parsing.hpp @@ -0,0 +1,501 @@ +// ---------------------------------------------------------------------------- +// parsing.hpp : implementation of the parsing member functions +// ( parse, parse_printf_directive) +// ---------------------------------------------------------------------------- + +// Copyright Samuel Krempp 2003. Use, modification, and distribution are +// subject to the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// see http://www.boost.org/libs/format for library home page + +// ---------------------------------------------------------------------------- + +#ifndef BOOST_FORMAT_PARSING_HPP +#define BOOST_FORMAT_PARSING_HPP + + +#include +#include +#include +#include + + +namespace boost { +namespace io { +namespace detail { + +#if defined(BOOST_NO_STD_LOCALE) + // streams will be used for narrow / widen. but these methods are not const + template + T& const_or_not(const T& x) { + return const_cast (x); + } +#else + template + const T& const_or_not(const T& x) { + return x; + } +#endif + + template inline + char wrap_narrow(const Facet& fac, Ch c, char deflt) { + return const_or_not(fac).narrow(c, deflt); + } + + template inline + bool wrap_isdigit(const Facet& fac, Ch c) { +#if ! defined( BOOST_NO_LOCALE_ISDIGIT ) + return fac.is(std::ctype::digit, c); +# else + (void) fac; // remove "unused parameter" warning + using namespace std; + return isdigit(c) != 0; +#endif + } + + template + Iter wrap_scan_notdigit(const Facet & fac, Iter beg, Iter end) { + using namespace std; + for( ; beg!=end && wrap_isdigit(fac, *beg); ++beg) ; + return beg; + } + + + // Input : [start, last) iterators range and a + // a Facet to use its widen/narrow member function + // Effects : read sequence and convert digits into integral n, of type Res + // Returns : n + template + Iter str2int (const Iter & start, const Iter & last, Res & res, + const Facet& fac) + { + using namespace std; + Iter it; + res=0; + for(it=start; it != last && wrap_isdigit(fac, *it); ++it ) { + char cur_ch = wrap_narrow(fac, *it, 0); // cant fail. + res *= 10; + res += cur_ch - '0'; // 22.2.1.1.2.13 of the C++ standard + } + return it; + } + + // skip printf's "asterisk-fields" directives in the format-string buf + // Input : char string, with starting index *pos_p + // a Facet merely to use its widen/narrow member function + // Effects : advance *pos_p by skipping printf's asterisk fields. + // Returns : nothing + template + Iter skip_asterisk(Iter start, Iter last, const Facet& fac) + { + using namespace std; + ++ start; + start = wrap_scan_notdigit(fac, start, last); + if(start!=last && *start== const_or_not(fac).widen( '$') ) + ++start; + return start; + } + + + // auxiliary func called by parse_printf_directive + // for centralising error handling + // it either throws if user sets the corresponding flag, or does nothing. + inline void maybe_throw_exception(unsigned char exceptions, + std::size_t pos, std::size_t size) + { + if(exceptions & io::bad_format_string_bit) + boost::throw_exception(io::bad_format_string(pos, size) ); + } + + + // Input: the position of a printf-directive in the format-string + // a basic_ios& merely to use its widen/narrow member function + // a bitset'exceptions' telling whether to throw exceptions on errors. + // Returns: + // true if parse succeeded (ignore some errors if exceptions disabled) + // false if it failed so bad that the directive should be printed verbatim + // Effects: + // start is incremented so that *start is the first char after + // this directive + // *fpar is set with the parameters read in the directive + template + bool parse_printf_directive(Iter & start, const Iter& last, + detail::format_item * fpar, + const Facet& fac, + std::size_t offset, unsigned char exceptions) + { + typedef typename basic_format::format_item_t format_item_t; + + fpar->argN_ = format_item_t::argN_no_posit; // if no positional-directive + bool precision_set = false; + bool in_brackets=false; + Iter start0 = start; + std::size_t fstring_size = last-start0+offset; + + if(start>= last) { // empty directive : this is a trailing % + maybe_throw_exception(exceptions, start-start0 + offset, fstring_size); + return false; + } + + if(*start== const_or_not(fac).widen( '|')) { + in_brackets=true; + if( ++start >= last ) { + maybe_throw_exception(exceptions, start-start0 + offset, fstring_size); + return false; + } + } + + // the flag '0' would be picked as a digit for argument order, but here it's a flag : + if(*start== const_or_not(fac).widen( '0')) + goto parse_flags; + + // handle argument order (%2$d) or possibly width specification: %2d + if(wrap_isdigit(fac, *start)) { + int n; + start = str2int(start, last, n, fac); + if( start >= last ) { + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + return false; + } + + // %N% case : this is already the end of the directive + if( *start == const_or_not(fac).widen( '%') ) { + fpar->argN_ = n-1; + ++start; + if( in_brackets) + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + // but don't return. maybe "%" was used in lieu of '$', so we go on. + else + return true; + } + + if ( *start== const_or_not(fac).widen( '$') ) { + fpar->argN_ = n-1; + ++start; + } + else { + // non-positionnal directive + fpar->fmtstate_.width_ = n; + fpar->argN_ = format_item_t::argN_no_posit; + goto parse_precision; + } + } + + parse_flags: + // handle flags + while ( start != last) { // as long as char is one of + - = _ # 0 l h or ' ' + // misc switches + switch ( wrap_narrow(fac, *start, 0)) { + case '\'' : break; // no effect yet. (painful to implement) + case 'l': + case 'h': // short/long modifier : for printf-comaptibility (no action needed) + break; + case '-': + fpar->fmtstate_.flags_ |= std::ios_base::left; + break; + case '=': + fpar->pad_scheme_ |= format_item_t::centered; + break; + case '_': + fpar->fmtstate_.flags_ |= std::ios_base::internal; + break; + case ' ': + fpar->pad_scheme_ |= format_item_t::spacepad; + break; + case '+': + fpar->fmtstate_.flags_ |= std::ios_base::showpos; + break; + case '0': + fpar->pad_scheme_ |= format_item_t::zeropad; + // need to know alignment before really setting flags, + // so just add 'zeropad' flag for now, it will be processed later. + break; + case '#': + fpar->fmtstate_.flags_ |= std::ios_base::showpoint | std::ios_base::showbase; + break; + default: + goto parse_width; + } + ++start; + } // loop on flag. + + if( start>=last) { + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + return true; + } + parse_width: + // handle width spec + // first skip 'asterisk fields' : *, or *N$ + if(*start == const_or_not(fac).widen( '*') ) + start = skip_asterisk(start, last, fac); + if(start!=last && wrap_isdigit(fac, *start)) + start = str2int(start, last, fpar->fmtstate_.width_, fac); + + parse_precision: + if( start>= last) { + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + return true; + } + // handle precision spec + if (*start== const_or_not(fac).widen( '.')) { + ++start; + if(start != last && *start == const_or_not(fac).widen( '*') ) + start = skip_asterisk(start, last, fac); + if(start != last && wrap_isdigit(fac, *start)) { + start = str2int(start, last, fpar->fmtstate_.precision_, fac); + precision_set = true; + } + else + fpar->fmtstate_.precision_ =0; + } + + // handle formatting-type flags : + while( start != last && ( *start== const_or_not(fac).widen( 'l') + || *start== const_or_not(fac).widen( 'L') + || *start== const_or_not(fac).widen( 'h')) ) + ++start; + if( start>=last) { + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + return true; + } + + if( in_brackets && *start== const_or_not(fac).widen( '|') ) { + ++start; + return true; + } + switch ( wrap_narrow(fac, *start, 0) ) { + case 'X': + fpar->fmtstate_.flags_ |= std::ios_base::uppercase; + case 'p': // pointer => set hex. + case 'x': + fpar->fmtstate_.flags_ &= ~std::ios_base::basefield; + fpar->fmtstate_.flags_ |= std::ios_base::hex; + break; + + case 'o': + fpar->fmtstate_.flags_ &= ~std::ios_base::basefield; + fpar->fmtstate_.flags_ |= std::ios_base::oct; + break; + + case 'E': + fpar->fmtstate_.flags_ |= std::ios_base::uppercase; + case 'e': + fpar->fmtstate_.flags_ &= ~std::ios_base::floatfield; + fpar->fmtstate_.flags_ |= std::ios_base::scientific; + + fpar->fmtstate_.flags_ &= ~std::ios_base::basefield; + fpar->fmtstate_.flags_ |= std::ios_base::dec; + break; + + case 'f': + fpar->fmtstate_.flags_ &= ~std::ios_base::floatfield; + fpar->fmtstate_.flags_ |= std::ios_base::fixed; + case 'u': + case 'd': + case 'i': + fpar->fmtstate_.flags_ &= ~std::ios_base::basefield; + fpar->fmtstate_.flags_ |= std::ios_base::dec; + break; + + case 'T': + ++start; + if( start >= last) + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + else + fpar->fmtstate_.fill_ = *start; + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + case 't': + fpar->fmtstate_.fill_ = const_or_not(fac).widen( ' '); + fpar->pad_scheme_ |= format_item_t::tabulation; + fpar->argN_ = format_item_t::argN_tabulation; + break; + + case 'G': + fpar->fmtstate_.flags_ |= std::ios_base::uppercase; + break; + case 'g': // 'g' conversion is default for floats. + fpar->fmtstate_.flags_ &= ~std::ios_base::basefield; + fpar->fmtstate_.flags_ |= std::ios_base::dec; + + // CLEAR all floatield flags, so stream will CHOOSE + fpar->fmtstate_.flags_ &= ~std::ios_base::floatfield; + break; + + case 'C': + case 'c': + fpar->truncate_ = 1; + break; + case 'S': + case 's': + if(precision_set) // handle truncation manually, with own parameter. + fpar->truncate_ = fpar->fmtstate_.precision_; + fpar->fmtstate_.precision_ = 6; // default stream precision. + break; + case 'n' : + fpar->argN_ = format_item_t::argN_ignored; + break; + default: + maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + } + ++start; + + if( in_brackets ) { + if( start != last && *start== const_or_not(fac).widen( '|') ) { + ++start; + return true; + } + else maybe_throw_exception(exceptions, start-start0+offset, fstring_size); + } + return true; + } + // -end parse_printf_directive() + + template + int upper_bound_from_fstring(const String& buf, + const typename String::value_type arg_mark, + const Facet& fac, + unsigned char exceptions) + { + // quick-parsing of the format-string to count arguments mark (arg_mark, '%') + // returns : upper bound on the number of format items in the format strings + using namespace boost::io; + typename String::size_type i1=0; + int num_items=0; + while( (i1=buf.find(arg_mark,i1)) != String::npos ) { + if( i1+1 >= buf.size() ) { + if(exceptions & bad_format_string_bit) + boost::throw_exception(bad_format_string(i1, buf.size() )); // must not end in ".. %" + else { + ++num_items; + break; + } + } + if(buf[i1+1] == buf[i1] ) {// escaped "%%" + i1+=2; continue; + } + + ++i1; + // in case of %N% directives, dont count it double (wastes allocations..) : + i1 = detail::wrap_scan_notdigit(fac, buf.begin()+i1, buf.end()) - buf.begin(); + if( i1 < buf.size() && buf[i1] == arg_mark ) + ++i1; + ++num_items; + } + return num_items; + } + template inline + void append_string(String& dst, const String& src, + const typename String::size_type beg, + const typename String::size_type end) { + dst.append(src.begin()+beg, src.begin()+end); + } + +} // detail namespace +} // io namespace + + + +// ----------------------------------------------- +// format :: parse(..) + + template + basic_format& basic_format:: + parse (const string_type& buf) { + // parse the format-string + using namespace std; +#if !defined(BOOST_NO_STD_LOCALE) + const std::ctype & fac = BOOST_USE_FACET( std::ctype, getloc()); +#else + io::basic_oaltstringstream fac; + //has widen and narrow even on compilers without locale +#endif + + const Ch arg_mark = io::detail::const_or_not(fac).widen( '%'); + bool ordered_args=true; + int max_argN=-1; + + // A: find upper_bound on num_items and allocates arrays + int num_items = io::detail::upper_bound_from_fstring(buf, arg_mark, fac, exceptions()); + make_or_reuse_data(num_items); + + // B: Now the real parsing of the format string : + num_items=0; + typename string_type::size_type i0=0, i1=0; + typename string_type::const_iterator it; + bool special_things=false; + int cur_item=0; + while( (i1=buf.find(arg_mark,i1)) != string_type::npos ) { + string_type & piece = (cur_item==0) ? prefix_ : items_[cur_item-1].appendix_; + if( buf[i1+1] == buf[i1] ) { // escaped mark, '%%' + io::detail::append_string(piece, buf, i0, i1+1); + i1+=2; i0=i1; + continue; + } + BOOST_ASSERT( static_cast(cur_item) < items_.size() || cur_item==0); + + if(i1!=i0) { + io::detail::append_string(piece, buf, i0, i1); + i0=i1; + } + ++i1; + it = buf.begin()+i1; + bool parse_ok = io::detail::parse_printf_directive( + it, buf.end(), &items_[cur_item], fac, i1, exceptions()); + i1 = it - buf.begin(); + if( ! parse_ok ) // the directive will be printed verbatim + continue; + i0=i1; + items_[cur_item].compute_states(); // process complex options, like zeropad, into params + + int argN=items_[cur_item].argN_; + if(argN == format_item_t::argN_ignored) + continue; + if(argN ==format_item_t::argN_no_posit) + ordered_args=false; + else if(argN == format_item_t::argN_tabulation) special_things=true; + else if(argN > max_argN) max_argN = argN; + ++num_items; + ++cur_item; + } // loop on %'s + BOOST_ASSERT(cur_item == num_items); + + // store the final piece of string + { + string_type & piece = (cur_item==0) ? prefix_ : items_[cur_item-1].appendix_; + io::detail::append_string(piece, buf, i0, buf.size()); + } + + if( !ordered_args) { + if(max_argN >= 0 ) { // dont mix positional with non-positionnal directives + if(exceptions() & io::bad_format_string_bit) + boost::throw_exception( + io::bad_format_string(static_cast(max_argN), 0)); + // else do nothing. => positionnal arguments are processed as non-positionnal + } + // set things like it would have been with positional directives : + int non_ordered_items = 0; + for(int i=0; i< num_items; ++i) + if(items_[i].argN_ == format_item_t::argN_no_posit) { + items_[i].argN_ = non_ordered_items; + ++non_ordered_items; + } + max_argN = non_ordered_items-1; + } + + // C: set some member data : + items_.resize(num_items, format_item_t(io::detail::const_or_not(fac).widen( ' ')) ); + + if(special_things) style_ |= special_needs; + num_args_ = max_argN + 1; + if(ordered_args) style_ |= ordered; + else style_ &= ~ordered; + return *this; + } + +} // namespace boost + + +#endif // BOOST_FORMAT_PARSING_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/function.hpp b/thirdparty/source/boost_1_61_0/boost/function.hpp new file mode 100644 index 0000000..b72842b --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function.hpp @@ -0,0 +1,66 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2001-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org/libs/function + +// William Kempf, Jesse Jones and Karl Nelson were all very helpful in the +// design of this library. + +#include // unary_function, binary_function + +#include +#include + +#ifndef BOOST_FUNCTION_MAX_ARGS +# define BOOST_FUNCTION_MAX_ARGS 10 +#endif // BOOST_FUNCTION_MAX_ARGS + +// Include the prologue here so that the use of file-level iteration +// in anything that may be included by function_template.hpp doesn't break +#include + +// Older Visual Age C++ version do not handle the file iteration well +#if BOOST_WORKAROUND(__IBMCPP__, >= 500) && BOOST_WORKAROUND(__IBMCPP__, < 800) +# if BOOST_FUNCTION_MAX_ARGS >= 0 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 1 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 2 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 3 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 4 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 5 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 6 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 7 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 8 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 9 +# include +# endif +# if BOOST_FUNCTION_MAX_ARGS >= 10 +# include +# endif +#else +// What is the '3' for? +# define BOOST_PP_ITERATION_PARAMS_1 (3,(0,BOOST_FUNCTION_MAX_ARGS,)) +# include BOOST_PP_ITERATE() +# undef BOOST_PP_ITERATION_PARAMS_1 +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/function/detail/function_iterate.hpp b/thirdparty/source/boost_1_61_0/boost/function/detail/function_iterate.hpp new file mode 100644 index 0000000..5370b36 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/detail/function_iterate.hpp @@ -0,0 +1,16 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org +#if !defined(BOOST_PP_IS_ITERATING) +# error Boost.Function - do not include this file! +#endif + +#define BOOST_FUNCTION_NUM_ARGS BOOST_PP_ITERATION() +#include +#undef BOOST_FUNCTION_NUM_ARGS + diff --git a/thirdparty/source/boost_1_61_0/boost/function/detail/gen_maybe_include.pl b/thirdparty/source/boost_1_61_0/boost/function/detail/gen_maybe_include.pl new file mode 100644 index 0000000..d062920 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/detail/gen_maybe_include.pl @@ -0,0 +1,37 @@ +#!/usr/bin/perl -w +# +# Boost.Function library +# +# Copyright (C) 2001-2003 Douglas Gregor (gregod@cs.rpi.edu) +# +# Permission to copy, use, sell and distribute this software is granted +# provided this copyright notice appears in all copies. +# Permission to modify the code and to distribute modified code is granted +# provided this copyright notice appears in all copies, and a notice +# that the code was modified is included with the copyright notice. +# +# This software is provided "as is" without express or implied warranty, +# and with no claim as to its suitability for any purpose. +# +# For more information, see http://www.boost.org +use English; + +$max_args = $ARGV[0]; + +open (OUT, ">maybe_include.hpp") or die("Cannot write to maybe_include.hpp"); +for($on_arg = 0; $on_arg <= $max_args; ++$on_arg) { + if ($on_arg == 0) { + print OUT "#if"; + } + else { + print OUT "#elif"; + } + print OUT " BOOST_FUNCTION_NUM_ARGS == $on_arg\n"; + print OUT "# ifndef BOOST_FUNCTION_$on_arg\n"; + print OUT "# define BOOST_FUNCTION_$on_arg\n"; + print OUT "# include \n"; + print OUT "# endif\n"; +} +print OUT "#else\n"; +print OUT "# error Cannot handle Boost.Function objects that accept more than $max_args arguments!\n"; +print OUT "#endif\n"; diff --git a/thirdparty/source/boost_1_61_0/boost/function/detail/maybe_include.hpp b/thirdparty/source/boost_1_61_0/boost/function/detail/maybe_include.hpp new file mode 100644 index 0000000..92f71bb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/detail/maybe_include.hpp @@ -0,0 +1,267 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#if BOOST_FUNCTION_NUM_ARGS == 0 +# ifndef BOOST_FUNCTION_0 +# define BOOST_FUNCTION_0 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 1 +# ifndef BOOST_FUNCTION_1 +# define BOOST_FUNCTION_1 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 2 +# ifndef BOOST_FUNCTION_2 +# define BOOST_FUNCTION_2 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 3 +# ifndef BOOST_FUNCTION_3 +# define BOOST_FUNCTION_3 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 4 +# ifndef BOOST_FUNCTION_4 +# define BOOST_FUNCTION_4 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 5 +# ifndef BOOST_FUNCTION_5 +# define BOOST_FUNCTION_5 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 6 +# ifndef BOOST_FUNCTION_6 +# define BOOST_FUNCTION_6 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 7 +# ifndef BOOST_FUNCTION_7 +# define BOOST_FUNCTION_7 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 8 +# ifndef BOOST_FUNCTION_8 +# define BOOST_FUNCTION_8 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 9 +# ifndef BOOST_FUNCTION_9 +# define BOOST_FUNCTION_9 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 10 +# ifndef BOOST_FUNCTION_10 +# define BOOST_FUNCTION_10 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 11 +# ifndef BOOST_FUNCTION_11 +# define BOOST_FUNCTION_11 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 12 +# ifndef BOOST_FUNCTION_12 +# define BOOST_FUNCTION_12 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 13 +# ifndef BOOST_FUNCTION_13 +# define BOOST_FUNCTION_13 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 14 +# ifndef BOOST_FUNCTION_14 +# define BOOST_FUNCTION_14 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 15 +# ifndef BOOST_FUNCTION_15 +# define BOOST_FUNCTION_15 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 16 +# ifndef BOOST_FUNCTION_16 +# define BOOST_FUNCTION_16 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 17 +# ifndef BOOST_FUNCTION_17 +# define BOOST_FUNCTION_17 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 18 +# ifndef BOOST_FUNCTION_18 +# define BOOST_FUNCTION_18 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 19 +# ifndef BOOST_FUNCTION_19 +# define BOOST_FUNCTION_19 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 20 +# ifndef BOOST_FUNCTION_20 +# define BOOST_FUNCTION_20 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 21 +# ifndef BOOST_FUNCTION_21 +# define BOOST_FUNCTION_21 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 22 +# ifndef BOOST_FUNCTION_22 +# define BOOST_FUNCTION_22 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 23 +# ifndef BOOST_FUNCTION_23 +# define BOOST_FUNCTION_23 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 24 +# ifndef BOOST_FUNCTION_24 +# define BOOST_FUNCTION_24 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 25 +# ifndef BOOST_FUNCTION_25 +# define BOOST_FUNCTION_25 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 26 +# ifndef BOOST_FUNCTION_26 +# define BOOST_FUNCTION_26 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 27 +# ifndef BOOST_FUNCTION_27 +# define BOOST_FUNCTION_27 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 28 +# ifndef BOOST_FUNCTION_28 +# define BOOST_FUNCTION_28 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 29 +# ifndef BOOST_FUNCTION_29 +# define BOOST_FUNCTION_29 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 30 +# ifndef BOOST_FUNCTION_30 +# define BOOST_FUNCTION_30 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 31 +# ifndef BOOST_FUNCTION_31 +# define BOOST_FUNCTION_31 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 32 +# ifndef BOOST_FUNCTION_32 +# define BOOST_FUNCTION_32 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 33 +# ifndef BOOST_FUNCTION_33 +# define BOOST_FUNCTION_33 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 34 +# ifndef BOOST_FUNCTION_34 +# define BOOST_FUNCTION_34 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 35 +# ifndef BOOST_FUNCTION_35 +# define BOOST_FUNCTION_35 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 36 +# ifndef BOOST_FUNCTION_36 +# define BOOST_FUNCTION_36 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 37 +# ifndef BOOST_FUNCTION_37 +# define BOOST_FUNCTION_37 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 38 +# ifndef BOOST_FUNCTION_38 +# define BOOST_FUNCTION_38 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 39 +# ifndef BOOST_FUNCTION_39 +# define BOOST_FUNCTION_39 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 40 +# ifndef BOOST_FUNCTION_40 +# define BOOST_FUNCTION_40 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 41 +# ifndef BOOST_FUNCTION_41 +# define BOOST_FUNCTION_41 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 42 +# ifndef BOOST_FUNCTION_42 +# define BOOST_FUNCTION_42 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 43 +# ifndef BOOST_FUNCTION_43 +# define BOOST_FUNCTION_43 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 44 +# ifndef BOOST_FUNCTION_44 +# define BOOST_FUNCTION_44 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 45 +# ifndef BOOST_FUNCTION_45 +# define BOOST_FUNCTION_45 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 46 +# ifndef BOOST_FUNCTION_46 +# define BOOST_FUNCTION_46 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 47 +# ifndef BOOST_FUNCTION_47 +# define BOOST_FUNCTION_47 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 48 +# ifndef BOOST_FUNCTION_48 +# define BOOST_FUNCTION_48 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 49 +# ifndef BOOST_FUNCTION_49 +# define BOOST_FUNCTION_49 +# include +# endif +#elif BOOST_FUNCTION_NUM_ARGS == 50 +# ifndef BOOST_FUNCTION_50 +# define BOOST_FUNCTION_50 +# include +# endif +#else +# error Cannot handle Boost.Function objects that accept more than 50 arguments! +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/function/detail/prologue.hpp b/thirdparty/source/boost_1_61_0/boost/function/detail/prologue.hpp new file mode 100644 index 0000000..53d0f05 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/detail/prologue.hpp @@ -0,0 +1,26 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#ifndef BOOST_FUNCTION_PROLOGUE_HPP +#define BOOST_FUNCTION_PROLOGUE_HPP +# include +# include +# include // unary_function, binary_function +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +#endif // BOOST_FUNCTION_PROLOGUE_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/function/function0.hpp b/thirdparty/source/boost_1_61_0/boost/function/function0.hpp new file mode 100644 index 0000000..65a02e5 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function0.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 0 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function1.hpp b/thirdparty/source/boost_1_61_0/boost/function/function1.hpp new file mode 100644 index 0000000..9089715 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function1.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 1 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function10.hpp b/thirdparty/source/boost_1_61_0/boost/function/function10.hpp new file mode 100644 index 0000000..6562724 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function10.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 10 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function2.hpp b/thirdparty/source/boost_1_61_0/boost/function/function2.hpp new file mode 100644 index 0000000..dc8bf97 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function2.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 2 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function3.hpp b/thirdparty/source/boost_1_61_0/boost/function/function3.hpp new file mode 100644 index 0000000..19d1a49 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function3.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 3 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function4.hpp b/thirdparty/source/boost_1_61_0/boost/function/function4.hpp new file mode 100644 index 0000000..f3349e2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function4.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 4 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function5.hpp b/thirdparty/source/boost_1_61_0/boost/function/function5.hpp new file mode 100644 index 0000000..a1305eb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function5.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 5 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function6.hpp b/thirdparty/source/boost_1_61_0/boost/function/function6.hpp new file mode 100644 index 0000000..1f60914 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function6.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 6 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function7.hpp b/thirdparty/source/boost_1_61_0/boost/function/function7.hpp new file mode 100644 index 0000000..68542ed --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function7.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 7 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function8.hpp b/thirdparty/source/boost_1_61_0/boost/function/function8.hpp new file mode 100644 index 0000000..cf2c376 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function8.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 8 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function9.hpp b/thirdparty/source/boost_1_61_0/boost/function/function9.hpp new file mode 100644 index 0000000..590e088 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function9.hpp @@ -0,0 +1,12 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2002-2003. Use, modification and +// distribution is subject to the Boost Software License, Version +// 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#define BOOST_FUNCTION_NUM_ARGS 9 +#include +#undef BOOST_FUNCTION_NUM_ARGS diff --git a/thirdparty/source/boost_1_61_0/boost/function/function_base.hpp b/thirdparty/source/boost_1_61_0/boost/function/function_base.hpp new file mode 100644 index 0000000..35c1995 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function_base.hpp @@ -0,0 +1,892 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2001-2006 +// Copyright Emil Dotchevski 2007 +// Use, modification and distribution is subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +#ifndef BOOST_FUNCTION_BASE_HEADER +#define BOOST_FUNCTION_BASE_HEADER + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef BOOST_NO_SFINAE +# include "boost/utility/enable_if.hpp" +#else +# include "boost/mpl/bool.hpp" +#endif +#include +#include + +#if defined(BOOST_MSVC) +# pragma warning( push ) +# pragma warning( disable : 4793 ) // complaint about native code generation +# pragma warning( disable : 4127 ) // "conditional expression is constant" +#endif + +// Define BOOST_FUNCTION_STD_NS to the namespace that contains type_info. +#ifdef BOOST_NO_STD_TYPEINFO +// Embedded VC++ does not have type_info in namespace std +# define BOOST_FUNCTION_STD_NS +#else +# define BOOST_FUNCTION_STD_NS std +#endif + +// Borrowed from Boost.Python library: determines the cases where we +// need to use std::type_info::name to compare instead of operator==. +#if defined( BOOST_NO_TYPEID ) +# define BOOST_FUNCTION_COMPARE_TYPE_ID(X,Y) ((X)==(Y)) +#elif defined(__GNUC__) \ + || defined(_AIX) \ + || ( defined(__sgi) && defined(__host_mips)) +# include +# define BOOST_FUNCTION_COMPARE_TYPE_ID(X,Y) \ + (std::strcmp((X).name(),(Y).name()) == 0) +# else +# define BOOST_FUNCTION_COMPARE_TYPE_ID(X,Y) ((X)==(Y)) +#endif + +#if defined(__ICL) && __ICL <= 600 || defined(__MWERKS__) && __MWERKS__ < 0x2406 && !defined(BOOST_STRICT_CONFIG) +# define BOOST_FUNCTION_TARGET_FIX(x) x +#else +# define BOOST_FUNCTION_TARGET_FIX(x) +#endif // __ICL etc + +# define BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor,Type) \ + typename ::boost::enable_if_c< \ + !(::boost::is_integral::value), \ + Type>::type + +namespace boost { + namespace detail { + namespace function { + class X; + + /** + * A buffer used to store small function objects in + * boost::function. It is a union containing function pointers, + * object pointers, and a structure that resembles a bound + * member function pointer. + */ + union function_buffer + { + // For pointers to function objects + mutable void* obj_ptr; + + // For pointers to std::type_info objects + struct type_t { + // (get_functor_type_tag, check_functor_type_tag). + const detail::sp_typeinfo* type; + + // Whether the type is const-qualified. + bool const_qualified; + // Whether the type is volatile-qualified. + bool volatile_qualified; + } type; + + // For function pointers of all kinds + mutable void (*func_ptr)(); + + // For bound member pointers + struct bound_memfunc_ptr_t { + void (X::*memfunc_ptr)(int); + void* obj_ptr; + } bound_memfunc_ptr; + + // For references to function objects. We explicitly keep + // track of the cv-qualifiers on the object referenced. + struct obj_ref_t { + mutable void* obj_ptr; + bool is_const_qualified; + bool is_volatile_qualified; + } obj_ref; + + // To relax aliasing constraints + mutable char data; + }; + + /** + * The unusable class is a placeholder for unused function arguments + * It is also completely unusable except that it constructable from + * anything. This helps compilers without partial specialization to + * handle Boost.Function objects returning void. + */ + struct unusable + { + unusable() {} + template unusable(const T&) {} + }; + + /* Determine the return type. This supports compilers that do not support + * void returns or partial specialization by silently changing the return + * type to "unusable". + */ + template struct function_return_type { typedef T type; }; + + template<> + struct function_return_type + { + typedef unusable type; + }; + + // The operation type to perform on the given functor/function pointer + enum functor_manager_operation_type { + clone_functor_tag, + move_functor_tag, + destroy_functor_tag, + check_functor_type_tag, + get_functor_type_tag + }; + + // Tags used to decide between different types of functions + struct function_ptr_tag {}; + struct function_obj_tag {}; + struct member_ptr_tag {}; + struct function_obj_ref_tag {}; + + template + class get_function_tag + { + typedef typename mpl::if_c<(is_pointer::value), + function_ptr_tag, + function_obj_tag>::type ptr_or_obj_tag; + + typedef typename mpl::if_c<(is_member_pointer::value), + member_ptr_tag, + ptr_or_obj_tag>::type ptr_or_obj_or_mem_tag; + + typedef typename mpl::if_c<(is_reference_wrapper::value), + function_obj_ref_tag, + ptr_or_obj_or_mem_tag>::type or_ref_tag; + + public: + typedef or_ref_tag type; + }; + + // The trivial manager does nothing but return the same pointer (if we + // are cloning) or return the null pointer (if we are deleting). + template + struct reference_manager + { + static inline void + manage(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op) + { + switch (op) { + case clone_functor_tag: + out_buffer.obj_ref = in_buffer.obj_ref; + return; + + case move_functor_tag: + out_buffer.obj_ref = in_buffer.obj_ref; + in_buffer.obj_ref.obj_ptr = 0; + return; + + case destroy_functor_tag: + out_buffer.obj_ref.obj_ptr = 0; + return; + + case check_functor_type_tag: + { + const detail::sp_typeinfo& check_type + = *out_buffer.type.type; + + // Check whether we have the same type. We can add + // cv-qualifiers, but we can't take them away. + if (BOOST_FUNCTION_COMPARE_TYPE_ID(check_type, BOOST_SP_TYPEID(F)) + && (!in_buffer.obj_ref.is_const_qualified + || out_buffer.type.const_qualified) + && (!in_buffer.obj_ref.is_volatile_qualified + || out_buffer.type.volatile_qualified)) + out_buffer.obj_ptr = in_buffer.obj_ref.obj_ptr; + else + out_buffer.obj_ptr = 0; + } + return; + + case get_functor_type_tag: + out_buffer.type.type = &BOOST_SP_TYPEID(F); + out_buffer.type.const_qualified = in_buffer.obj_ref.is_const_qualified; + out_buffer.type.volatile_qualified = in_buffer.obj_ref.is_volatile_qualified; + return; + } + } + }; + + /** + * Determine if boost::function can use the small-object + * optimization with the function object type F. + */ + template + struct function_allows_small_object_optimization + { + BOOST_STATIC_CONSTANT + (bool, + value = ((sizeof(F) <= sizeof(function_buffer) && + (alignment_of::value + % alignment_of::value == 0)))); + }; + + template + struct functor_wrapper: public F, public A + { + functor_wrapper( F f, A a ): + F(f), + A(a) + { + } + + functor_wrapper(const functor_wrapper& f) : + F(static_cast(f)), + A(static_cast(f)) + { + } + }; + + /** + * The functor_manager class contains a static function "manage" which + * can clone or destroy the given function/function object pointer. + */ + template + struct functor_manager_common + { + typedef Functor functor_type; + + // Function pointers + static inline void + manage_ptr(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op) + { + if (op == clone_functor_tag) + out_buffer.func_ptr = in_buffer.func_ptr; + else if (op == move_functor_tag) { + out_buffer.func_ptr = in_buffer.func_ptr; + in_buffer.func_ptr = 0; + } else if (op == destroy_functor_tag) + out_buffer.func_ptr = 0; + else if (op == check_functor_type_tag) { + const boost::detail::sp_typeinfo& check_type + = *out_buffer.type.type; + if (BOOST_FUNCTION_COMPARE_TYPE_ID(check_type, BOOST_SP_TYPEID(Functor))) + out_buffer.obj_ptr = &in_buffer.func_ptr; + else + out_buffer.obj_ptr = 0; + } else /* op == get_functor_type_tag */ { + out_buffer.type.type = &BOOST_SP_TYPEID(Functor); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + } + } + + // Function objects that fit in the small-object buffer. + static inline void + manage_small(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op) + { + if (op == clone_functor_tag || op == move_functor_tag) { + const functor_type* in_functor = + reinterpret_cast(&in_buffer.data); + new (reinterpret_cast(&out_buffer.data)) functor_type(*in_functor); + + if (op == move_functor_tag) { + functor_type* f = reinterpret_cast(&in_buffer.data); + (void)f; // suppress warning about the value of f not being used (MSVC) + f->~Functor(); + } + } else if (op == destroy_functor_tag) { + // Some compilers (Borland, vc6, ...) are unhappy with ~functor_type. + functor_type* f = reinterpret_cast(&out_buffer.data); + (void)f; // suppress warning about the value of f not being used (MSVC) + f->~Functor(); + } else if (op == check_functor_type_tag) { + const detail::sp_typeinfo& check_type + = *out_buffer.type.type; + if (BOOST_FUNCTION_COMPARE_TYPE_ID(check_type, BOOST_SP_TYPEID(Functor))) + out_buffer.obj_ptr = &in_buffer.data; + else + out_buffer.obj_ptr = 0; + } else /* op == get_functor_type_tag */ { + out_buffer.type.type = &BOOST_SP_TYPEID(Functor); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + } + } + }; + + template + struct functor_manager + { + private: + typedef Functor functor_type; + + // Function pointers + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, function_ptr_tag) + { + functor_manager_common::manage_ptr(in_buffer,out_buffer,op); + } + + // Function objects that fit in the small-object buffer. + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, mpl::true_) + { + functor_manager_common::manage_small(in_buffer,out_buffer,op); + } + + // Function objects that require heap allocation + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, mpl::false_) + { + if (op == clone_functor_tag) { + // Clone the functor + // GCC 2.95.3 gets the CV qualifiers wrong here, so we + // can't do the static_cast that we should do. + // jewillco: Changing this to static_cast because GCC 2.95.3 is + // obsolete. + const functor_type* f = + static_cast(in_buffer.obj_ptr); + functor_type* new_f = new functor_type(*f); + out_buffer.obj_ptr = new_f; + } else if (op == move_functor_tag) { + out_buffer.obj_ptr = in_buffer.obj_ptr; + in_buffer.obj_ptr = 0; + } else if (op == destroy_functor_tag) { + /* Cast from the void pointer to the functor pointer type */ + functor_type* f = + static_cast(out_buffer.obj_ptr); + delete f; + out_buffer.obj_ptr = 0; + } else if (op == check_functor_type_tag) { + const detail::sp_typeinfo& check_type + = *out_buffer.type.type; + if (BOOST_FUNCTION_COMPARE_TYPE_ID(check_type, BOOST_SP_TYPEID(Functor))) + out_buffer.obj_ptr = in_buffer.obj_ptr; + else + out_buffer.obj_ptr = 0; + } else /* op == get_functor_type_tag */ { + out_buffer.type.type = &BOOST_SP_TYPEID(Functor); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + } + } + + // For function objects, we determine whether the function + // object can use the small-object optimization buffer or + // whether we need to allocate it on the heap. + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, function_obj_tag) + { + manager(in_buffer, out_buffer, op, + mpl::bool_<(function_allows_small_object_optimization::value)>()); + } + + // For member pointers, we use the small-object optimization buffer. + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, member_ptr_tag) + { + manager(in_buffer, out_buffer, op, mpl::true_()); + } + + public: + /* Dispatch to an appropriate manager based on whether we have a + function pointer or a function object pointer. */ + static inline void + manage(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op) + { + typedef typename get_function_tag::type tag_type; + switch (op) { + case get_functor_type_tag: + out_buffer.type.type = &BOOST_SP_TYPEID(functor_type); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + return; + + default: + manager(in_buffer, out_buffer, op, tag_type()); + return; + } + } + }; + + template + struct functor_manager_a + { + private: + typedef Functor functor_type; + + // Function pointers + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, function_ptr_tag) + { + functor_manager_common::manage_ptr(in_buffer,out_buffer,op); + } + + // Function objects that fit in the small-object buffer. + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, mpl::true_) + { + functor_manager_common::manage_small(in_buffer,out_buffer,op); + } + + // Function objects that require heap allocation + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, mpl::false_) + { + typedef functor_wrapper functor_wrapper_type; + typedef typename Allocator::template rebind::other + wrapper_allocator_type; + typedef typename wrapper_allocator_type::pointer wrapper_allocator_pointer_type; + + if (op == clone_functor_tag) { + // Clone the functor + // GCC 2.95.3 gets the CV qualifiers wrong here, so we + // can't do the static_cast that we should do. + const functor_wrapper_type* f = + static_cast(in_buffer.obj_ptr); + wrapper_allocator_type wrapper_allocator(static_cast(*f)); + wrapper_allocator_pointer_type copy = wrapper_allocator.allocate(1); + wrapper_allocator.construct(copy, *f); + + // Get back to the original pointer type + functor_wrapper_type* new_f = static_cast(copy); + out_buffer.obj_ptr = new_f; + } else if (op == move_functor_tag) { + out_buffer.obj_ptr = in_buffer.obj_ptr; + in_buffer.obj_ptr = 0; + } else if (op == destroy_functor_tag) { + /* Cast from the void pointer to the functor_wrapper_type */ + functor_wrapper_type* victim = + static_cast(in_buffer.obj_ptr); + wrapper_allocator_type wrapper_allocator(static_cast(*victim)); + wrapper_allocator.destroy(victim); + wrapper_allocator.deallocate(victim,1); + out_buffer.obj_ptr = 0; + } else if (op == check_functor_type_tag) { + const detail::sp_typeinfo& check_type + = *out_buffer.type.type; + if (BOOST_FUNCTION_COMPARE_TYPE_ID(check_type, BOOST_SP_TYPEID(Functor))) + out_buffer.obj_ptr = in_buffer.obj_ptr; + else + out_buffer.obj_ptr = 0; + } else /* op == get_functor_type_tag */ { + out_buffer.type.type = &BOOST_SP_TYPEID(Functor); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + } + } + + // For function objects, we determine whether the function + // object can use the small-object optimization buffer or + // whether we need to allocate it on the heap. + static inline void + manager(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op, function_obj_tag) + { + manager(in_buffer, out_buffer, op, + mpl::bool_<(function_allows_small_object_optimization::value)>()); + } + + public: + /* Dispatch to an appropriate manager based on whether we have a + function pointer or a function object pointer. */ + static inline void + manage(const function_buffer& in_buffer, function_buffer& out_buffer, + functor_manager_operation_type op) + { + typedef typename get_function_tag::type tag_type; + switch (op) { + case get_functor_type_tag: + out_buffer.type.type = &BOOST_SP_TYPEID(functor_type); + out_buffer.type.const_qualified = false; + out_buffer.type.volatile_qualified = false; + return; + + default: + manager(in_buffer, out_buffer, op, tag_type()); + return; + } + } + }; + + // A type that is only used for comparisons against zero + struct useless_clear_type {}; + +#ifdef BOOST_NO_SFINAE + // These routines perform comparisons between a Boost.Function + // object and an arbitrary function object (when the last + // parameter is mpl::bool_) or against zero (when the + // last parameter is mpl::bool_). They are only necessary + // for compilers that don't support SFINAE. + template + bool + compare_equal(const Function& f, const Functor&, int, mpl::bool_) + { return f.empty(); } + + template + bool + compare_not_equal(const Function& f, const Functor&, int, + mpl::bool_) + { return !f.empty(); } + + template + bool + compare_equal(const Function& f, const Functor& g, long, + mpl::bool_) + { + if (const Functor* fp = f.template target()) + return function_equal(*fp, g); + else return false; + } + + template + bool + compare_equal(const Function& f, const reference_wrapper& g, + int, mpl::bool_) + { + if (const Functor* fp = f.template target()) + return fp == g.get_pointer(); + else return false; + } + + template + bool + compare_not_equal(const Function& f, const Functor& g, long, + mpl::bool_) + { + if (const Functor* fp = f.template target()) + return !function_equal(*fp, g); + else return true; + } + + template + bool + compare_not_equal(const Function& f, + const reference_wrapper& g, int, + mpl::bool_) + { + if (const Functor* fp = f.template target()) + return fp != g.get_pointer(); + else return true; + } +#endif // BOOST_NO_SFINAE + + /** + * Stores the "manager" portion of the vtable for a + * boost::function object. + */ + struct vtable_base + { + void (*manager)(const function_buffer& in_buffer, + function_buffer& out_buffer, + functor_manager_operation_type op); + }; + } // end namespace function + } // end namespace detail + +/** + * The function_base class contains the basic elements needed for the + * function1, function2, function3, etc. classes. It is common to all + * functions (and as such can be used to tell if we have one of the + * functionN objects). + */ +class function_base +{ +public: + function_base() : vtable(0) { } + + /** Determine if the function is empty (i.e., has no target). */ + bool empty() const { return !vtable; } + + /** Retrieve the type of the stored function object, or BOOST_SP_TYPEID(void) + if this is empty. */ + const detail::sp_typeinfo& target_type() const + { + if (!vtable) return BOOST_SP_TYPEID(void); + + detail::function::function_buffer type; + get_vtable()->manager(functor, type, detail::function::get_functor_type_tag); + return *type.type.type; + } + + template + Functor* target() + { + if (!vtable) return 0; + + detail::function::function_buffer type_result; + type_result.type.type = &BOOST_SP_TYPEID(Functor); + type_result.type.const_qualified = is_const::value; + type_result.type.volatile_qualified = is_volatile::value; + get_vtable()->manager(functor, type_result, + detail::function::check_functor_type_tag); + return static_cast(type_result.obj_ptr); + } + + template + const Functor* target() const + { + if (!vtable) return 0; + + detail::function::function_buffer type_result; + type_result.type.type = &BOOST_SP_TYPEID(Functor); + type_result.type.const_qualified = true; + type_result.type.volatile_qualified = is_volatile::value; + get_vtable()->manager(functor, type_result, + detail::function::check_functor_type_tag); + // GCC 2.95.3 gets the CV qualifiers wrong here, so we + // can't do the static_cast that we should do. + return static_cast(type_result.obj_ptr); + } + + template + bool contains(const F& f) const + { + if (const F* fp = this->template target()) + { + return function_equal(*fp, f); + } else { + return false; + } + } + +#if defined(__GNUC__) && __GNUC__ == 3 && __GNUC_MINOR__ <= 3 + // GCC 3.3 and newer cannot copy with the global operator==, due to + // problems with instantiation of function return types before it + // has been verified that the argument types match up. + template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator==(Functor g) const + { + if (const Functor* fp = target()) + return function_equal(*fp, g); + else return false; + } + + template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator!=(Functor g) const + { + if (const Functor* fp = target()) + return !function_equal(*fp, g); + else return true; + } +#endif + +public: // should be protected, but GCC 2.95.3 will fail to allow access + detail::function::vtable_base* get_vtable() const { + return reinterpret_cast( + reinterpret_cast(vtable) & ~static_cast(0x01)); + } + + bool has_trivial_copy_and_destroy() const { + return reinterpret_cast(vtable) & 0x01; + } + + detail::function::vtable_base* vtable; + mutable detail::function::function_buffer functor; +}; + +/** + * The bad_function_call exception class is thrown when a boost::function + * object is invoked + */ +class bad_function_call : public std::runtime_error +{ +public: + bad_function_call() : std::runtime_error("call to empty boost::function") {} +}; + +#ifndef BOOST_NO_SFINAE +inline bool operator==(const function_base& f, + detail::function::useless_clear_type*) +{ + return f.empty(); +} + +inline bool operator!=(const function_base& f, + detail::function::useless_clear_type*) +{ + return !f.empty(); +} + +inline bool operator==(detail::function::useless_clear_type*, + const function_base& f) +{ + return f.empty(); +} + +inline bool operator!=(detail::function::useless_clear_type*, + const function_base& f) +{ + return !f.empty(); +} +#endif + +#ifdef BOOST_NO_SFINAE +// Comparisons between boost::function objects and arbitrary function objects +template + inline bool operator==(const function_base& f, Functor g) + { + typedef mpl::bool_<(is_integral::value)> integral; + return detail::function::compare_equal(f, g, 0, integral()); + } + +template + inline bool operator==(Functor g, const function_base& f) + { + typedef mpl::bool_<(is_integral::value)> integral; + return detail::function::compare_equal(f, g, 0, integral()); + } + +template + inline bool operator!=(const function_base& f, Functor g) + { + typedef mpl::bool_<(is_integral::value)> integral; + return detail::function::compare_not_equal(f, g, 0, integral()); + } + +template + inline bool operator!=(Functor g, const function_base& f) + { + typedef mpl::bool_<(is_integral::value)> integral; + return detail::function::compare_not_equal(f, g, 0, integral()); + } +#else + +# if !(defined(__GNUC__) && __GNUC__ == 3 && __GNUC_MINOR__ <= 3) +// Comparisons between boost::function objects and arbitrary function +// objects. GCC 3.3 and before has an obnoxious bug that prevents this +// from working. +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator==(const function_base& f, Functor g) + { + if (const Functor* fp = f.template target()) + return function_equal(*fp, g); + else return false; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator==(Functor g, const function_base& f) + { + if (const Functor* fp = f.template target()) + return function_equal(g, *fp); + else return false; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator!=(const function_base& f, Functor g) + { + if (const Functor* fp = f.template target()) + return !function_equal(*fp, g); + else return true; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator!=(Functor g, const function_base& f) + { + if (const Functor* fp = f.template target()) + return !function_equal(g, *fp); + else return true; + } +# endif + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator==(const function_base& f, reference_wrapper g) + { + if (const Functor* fp = f.template target()) + return fp == g.get_pointer(); + else return false; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator==(reference_wrapper g, const function_base& f) + { + if (const Functor* fp = f.template target()) + return g.get_pointer() == fp; + else return false; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator!=(const function_base& f, reference_wrapper g) + { + if (const Functor* fp = f.template target()) + return fp != g.get_pointer(); + else return true; + } + +template + BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL(Functor, bool) + operator!=(reference_wrapper g, const function_base& f) + { + if (const Functor* fp = f.template target()) + return g.get_pointer() != fp; + else return true; + } + +#endif // Compiler supporting SFINAE + +namespace detail { + namespace function { + inline bool has_empty_target(const function_base* f) + { + return f->empty(); + } + +#if BOOST_WORKAROUND(BOOST_MSVC, <= 1310) + inline bool has_empty_target(const void*) + { + return false; + } +#else + inline bool has_empty_target(...) + { + return false; + } +#endif + } // end namespace function +} // end namespace detail +} // end namespace boost + +#undef BOOST_FUNCTION_ENABLE_IF_NOT_INTEGRAL +#undef BOOST_FUNCTION_COMPARE_TYPE_ID + +#if defined(BOOST_MSVC) +# pragma warning( pop ) +#endif + +#endif // BOOST_FUNCTION_BASE_HEADER diff --git a/thirdparty/source/boost_1_61_0/boost/function/function_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/function/function_fwd.hpp new file mode 100644 index 0000000..e79b504 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function_fwd.hpp @@ -0,0 +1,69 @@ +// Boost.Function library +// Copyright (C) Douglas Gregor 2008 +// +// Use, modification and distribution is subject to the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// For more information, see http://www.boost.org +#ifndef BOOST_FUNCTION_FWD_HPP +#define BOOST_FUNCTION_FWD_HPP +#include + +#if defined(__sgi) && defined(_COMPILER_VERSION) && _COMPILER_VERSION <= 730 && !defined(BOOST_STRICT_CONFIG) +// Work around a compiler bug. +// boost::python::objects::function has to be seen by the compiler before the +// boost::function class template. +namespace boost { namespace python { namespace objects { + class function; +}}} +#endif + +#if defined(BOOST_BCB_PARTIAL_SPECIALIZATION_BUG) \ + || !(defined(BOOST_STRICT_CONFIG) || !defined(__SUNPRO_CC) || __SUNPRO_CC > 0x540) +# define BOOST_FUNCTION_NO_FUNCTION_TYPE_SYNTAX +#endif + +namespace boost { + class bad_function_call; + +#if !defined(BOOST_FUNCTION_NO_FUNCTION_TYPE_SYNTAX) + // Preferred syntax + template class function; + + template + inline void swap(function& f1, function& f2) + { + f1.swap(f2); + } +#endif // have partial specialization + + // Portable syntax + template class function0; + template class function1; + template class function2; + template class function3; + template + class function4; + template + class function5; + template + class function6; + template + class function7; + template + class function8; + template + class function9; + template + class function10; +} + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/function/function_template.hpp b/thirdparty/source/boost_1_61_0/boost/function/function_template.hpp new file mode 100644 index 0000000..211b81d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function/function_template.hpp @@ -0,0 +1,1190 @@ +// Boost.Function library + +// Copyright Douglas Gregor 2001-2006 +// Copyright Emil Dotchevski 2007 +// Use, modification and distribution is subject to the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org + +// Note: this header is a header template and must NOT have multiple-inclusion +// protection. +#include +#include + +#if defined(BOOST_MSVC) +# pragma warning( push ) +# pragma warning( disable : 4127 ) // "conditional expression is constant" +#endif + +#define BOOST_FUNCTION_TEMPLATE_PARMS BOOST_PP_ENUM_PARAMS(BOOST_FUNCTION_NUM_ARGS, typename T) + +#define BOOST_FUNCTION_TEMPLATE_ARGS BOOST_PP_ENUM_PARAMS(BOOST_FUNCTION_NUM_ARGS, T) + +#define BOOST_FUNCTION_PARM(J,I,D) BOOST_PP_CAT(T,I) BOOST_PP_CAT(a,I) + +#define BOOST_FUNCTION_PARMS BOOST_PP_ENUM(BOOST_FUNCTION_NUM_ARGS,BOOST_FUNCTION_PARM,BOOST_PP_EMPTY) + +#ifdef BOOST_NO_CXX11_RVALUE_REFERENCES +# define BOOST_FUNCTION_ARGS BOOST_PP_ENUM_PARAMS(BOOST_FUNCTION_NUM_ARGS, a) +#else +# include +# define BOOST_FUNCTION_ARG(J,I,D) ::boost::forward< BOOST_PP_CAT(T,I) >(BOOST_PP_CAT(a,I)) +# define BOOST_FUNCTION_ARGS BOOST_PP_ENUM(BOOST_FUNCTION_NUM_ARGS,BOOST_FUNCTION_ARG,BOOST_PP_EMPTY) +#endif + +#define BOOST_FUNCTION_ARG_TYPE(J,I,D) \ + typedef BOOST_PP_CAT(T,I) BOOST_PP_CAT(BOOST_PP_CAT(arg, BOOST_PP_INC(I)),_type); + +#define BOOST_FUNCTION_ARG_TYPES BOOST_PP_REPEAT(BOOST_FUNCTION_NUM_ARGS,BOOST_FUNCTION_ARG_TYPE,BOOST_PP_EMPTY) + +// Comma if nonzero number of arguments +#if BOOST_FUNCTION_NUM_ARGS == 0 +# define BOOST_FUNCTION_COMMA +#else +# define BOOST_FUNCTION_COMMA , +#endif // BOOST_FUNCTION_NUM_ARGS > 0 + +// Class names used in this version of the code +#define BOOST_FUNCTION_FUNCTION BOOST_JOIN(function,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_FUNCTION_INVOKER \ + BOOST_JOIN(function_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_VOID_FUNCTION_INVOKER \ + BOOST_JOIN(void_function_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_FUNCTION_OBJ_INVOKER \ + BOOST_JOIN(function_obj_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_VOID_FUNCTION_OBJ_INVOKER \ + BOOST_JOIN(void_function_obj_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_FUNCTION_REF_INVOKER \ + BOOST_JOIN(function_ref_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_VOID_FUNCTION_REF_INVOKER \ + BOOST_JOIN(void_function_ref_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_MEMBER_INVOKER \ + BOOST_JOIN(function_mem_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_VOID_MEMBER_INVOKER \ + BOOST_JOIN(function_void_mem_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_GET_FUNCTION_INVOKER \ + BOOST_JOIN(get_function_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER \ + BOOST_JOIN(get_function_obj_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER \ + BOOST_JOIN(get_function_ref_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_GET_MEMBER_INVOKER \ + BOOST_JOIN(get_member_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_GET_INVOKER \ + BOOST_JOIN(get_invoker,BOOST_FUNCTION_NUM_ARGS) +#define BOOST_FUNCTION_VTABLE BOOST_JOIN(basic_vtable,BOOST_FUNCTION_NUM_ARGS) + +#ifndef BOOST_NO_VOID_RETURNS +# define BOOST_FUNCTION_VOID_RETURN_TYPE void +# define BOOST_FUNCTION_RETURN(X) X +#else +# define BOOST_FUNCTION_VOID_RETURN_TYPE boost::detail::function::unusable +# define BOOST_FUNCTION_RETURN(X) X; return BOOST_FUNCTION_VOID_RETURN_TYPE () +#endif + +namespace boost { + namespace detail { + namespace function { + template< + typename FunctionPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_FUNCTION_INVOKER + { + static R invoke(function_buffer& function_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + { + FunctionPtr f = reinterpret_cast(function_ptr.func_ptr); + return f(BOOST_FUNCTION_ARGS); + } + }; + + template< + typename FunctionPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_VOID_FUNCTION_INVOKER + { + static BOOST_FUNCTION_VOID_RETURN_TYPE + invoke(function_buffer& function_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + FunctionPtr f = reinterpret_cast(function_ptr.func_ptr); + BOOST_FUNCTION_RETURN(f(BOOST_FUNCTION_ARGS)); + } + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_FUNCTION_OBJ_INVOKER + { + static R invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + FunctionObj* f; + if (function_allows_small_object_optimization::value) + f = reinterpret_cast(&function_obj_ptr.data); + else + f = reinterpret_cast(function_obj_ptr.obj_ptr); + return (*f)(BOOST_FUNCTION_ARGS); + } + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_VOID_FUNCTION_OBJ_INVOKER + { + static BOOST_FUNCTION_VOID_RETURN_TYPE + invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + FunctionObj* f; + if (function_allows_small_object_optimization::value) + f = reinterpret_cast(&function_obj_ptr.data); + else + f = reinterpret_cast(function_obj_ptr.obj_ptr); + BOOST_FUNCTION_RETURN((*f)(BOOST_FUNCTION_ARGS)); + } + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_FUNCTION_REF_INVOKER + { + static R invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + FunctionObj* f = + reinterpret_cast(function_obj_ptr.obj_ptr); + return (*f)(BOOST_FUNCTION_ARGS); + } + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_VOID_FUNCTION_REF_INVOKER + { + static BOOST_FUNCTION_VOID_RETURN_TYPE + invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + FunctionObj* f = + reinterpret_cast(function_obj_ptr.obj_ptr); + BOOST_FUNCTION_RETURN((*f)(BOOST_FUNCTION_ARGS)); + } + }; + +#if BOOST_FUNCTION_NUM_ARGS > 0 + /* Handle invocation of member pointers. */ + template< + typename MemberPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_MEMBER_INVOKER + { + static R invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + MemberPtr* f = + reinterpret_cast(&function_obj_ptr.data); + return boost::mem_fn(*f)(BOOST_FUNCTION_ARGS); + } + }; + + template< + typename MemberPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_VOID_MEMBER_INVOKER + { + static BOOST_FUNCTION_VOID_RETURN_TYPE + invoke(function_buffer& function_obj_ptr BOOST_FUNCTION_COMMA + BOOST_FUNCTION_PARMS) + + { + MemberPtr* f = + reinterpret_cast(&function_obj_ptr.data); + BOOST_FUNCTION_RETURN(boost::mem_fn(*f)(BOOST_FUNCTION_ARGS)); + } + }; +#endif + + template< + typename FunctionPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_GET_FUNCTION_INVOKER + { + typedef typename mpl::if_c<(is_void::value), + BOOST_FUNCTION_VOID_FUNCTION_INVOKER< + FunctionPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >, + BOOST_FUNCTION_FUNCTION_INVOKER< + FunctionPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + > + >::type type; + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER + { + typedef typename mpl::if_c<(is_void::value), + BOOST_FUNCTION_VOID_FUNCTION_OBJ_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >, + BOOST_FUNCTION_FUNCTION_OBJ_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + > + >::type type; + }; + + template< + typename FunctionObj, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER + { + typedef typename mpl::if_c<(is_void::value), + BOOST_FUNCTION_VOID_FUNCTION_REF_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >, + BOOST_FUNCTION_FUNCTION_REF_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + > + >::type type; + }; + +#if BOOST_FUNCTION_NUM_ARGS > 0 + /* Retrieve the appropriate invoker for a member pointer. */ + template< + typename MemberPtr, + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + struct BOOST_FUNCTION_GET_MEMBER_INVOKER + { + typedef typename mpl::if_c<(is_void::value), + BOOST_FUNCTION_VOID_MEMBER_INVOKER< + MemberPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >, + BOOST_FUNCTION_MEMBER_INVOKER< + MemberPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + > + >::type type; + }; +#endif + + /* Given the tag returned by get_function_tag, retrieve the + actual invoker that will be used for the given function + object. + + Each specialization contains an "apply" nested class template + that accepts the function object, return type, function + argument types, and allocator. The resulting "apply" class + contains two typedefs, "invoker_type" and "manager_type", + which correspond to the invoker and manager types. */ + template + struct BOOST_FUNCTION_GET_INVOKER { }; + + /* Retrieve the invoker for a function pointer. */ + template<> + struct BOOST_FUNCTION_GET_INVOKER + { + template + struct apply + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_INVOKER< + FunctionPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager manager_type; + }; + + template + struct apply_a + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_INVOKER< + FunctionPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager manager_type; + }; + }; + +#if BOOST_FUNCTION_NUM_ARGS > 0 + /* Retrieve the invoker for a member pointer. */ + template<> + struct BOOST_FUNCTION_GET_INVOKER + { + template + struct apply + { + typedef typename BOOST_FUNCTION_GET_MEMBER_INVOKER< + MemberPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager manager_type; + }; + + template + struct apply_a + { + typedef typename BOOST_FUNCTION_GET_MEMBER_INVOKER< + MemberPtr, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager manager_type; + }; + }; +#endif + + /* Retrieve the invoker for a function object. */ + template<> + struct BOOST_FUNCTION_GET_INVOKER + { + template + struct apply + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager manager_type; + }; + + template + struct apply_a + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER< + FunctionObj, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef functor_manager_a manager_type; + }; + }; + + /* Retrieve the invoker for a reference to a function object. */ + template<> + struct BOOST_FUNCTION_GET_INVOKER + { + template + struct apply + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER< + typename RefWrapper::type, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef reference_manager manager_type; + }; + + template + struct apply_a + { + typedef typename BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER< + typename RefWrapper::type, + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >::type + invoker_type; + + typedef reference_manager manager_type; + }; + }; + + + /** + * vtable for a specific boost::function instance. This + * structure must be an aggregate so that we can use static + * initialization in boost::function's assign_to and assign_to_a + * members. It therefore cannot have any constructors, + * destructors, base classes, etc. + */ + template + struct BOOST_FUNCTION_VTABLE + { +#ifndef BOOST_NO_VOID_RETURNS + typedef R result_type; +#else + typedef typename function_return_type::type result_type; +#endif // BOOST_NO_VOID_RETURNS + + typedef result_type (*invoker_type)(function_buffer& + BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS); + + template + bool assign_to(F f, function_buffer& functor) const + { + typedef typename get_function_tag::type tag; + return assign_to(f, functor, tag()); + } + template + bool assign_to_a(F f, function_buffer& functor, Allocator a) const + { + typedef typename get_function_tag::type tag; + return assign_to_a(f, functor, a, tag()); + } + + void clear(function_buffer& functor) const + { + if (base.manager) + base.manager(functor, functor, destroy_functor_tag); + } + + private: + // Function pointers + template + bool + assign_to(FunctionPtr f, function_buffer& functor, function_ptr_tag) const + { + this->clear(functor); + if (f) { + // should be a reinterpret cast, but some compilers insist + // on giving cv-qualifiers to free functions + functor.func_ptr = reinterpret_cast(f); + return true; + } else { + return false; + } + } + template + bool + assign_to_a(FunctionPtr f, function_buffer& functor, Allocator, function_ptr_tag) const + { + return assign_to(f,functor,function_ptr_tag()); + } + + // Member pointers +#if BOOST_FUNCTION_NUM_ARGS > 0 + template + bool assign_to(MemberPtr f, function_buffer& functor, member_ptr_tag) const + { + // DPG TBD: Add explicit support for member function + // objects, so we invoke through mem_fn() but we retain the + // right target_type() values. + if (f) { + this->assign_to(boost::mem_fn(f), functor); + return true; + } else { + return false; + } + } + template + bool assign_to_a(MemberPtr f, function_buffer& functor, Allocator a, member_ptr_tag) const + { + // DPG TBD: Add explicit support for member function + // objects, so we invoke through mem_fn() but we retain the + // right target_type() values. + if (f) { + this->assign_to_a(boost::mem_fn(f), functor, a); + return true; + } else { + return false; + } + } +#endif // BOOST_FUNCTION_NUM_ARGS > 0 + + // Function objects + // Assign to a function object using the small object optimization + template + void + assign_functor(FunctionObj f, function_buffer& functor, mpl::true_) const + { + new (reinterpret_cast(&functor.data)) FunctionObj(f); + } + template + void + assign_functor_a(FunctionObj f, function_buffer& functor, Allocator, mpl::true_) const + { + assign_functor(f,functor,mpl::true_()); + } + + // Assign to a function object allocated on the heap. + template + void + assign_functor(FunctionObj f, function_buffer& functor, mpl::false_) const + { + functor.obj_ptr = new FunctionObj(f); + } + template + void + assign_functor_a(FunctionObj f, function_buffer& functor, Allocator a, mpl::false_) const + { + typedef functor_wrapper functor_wrapper_type; + typedef typename Allocator::template rebind::other + wrapper_allocator_type; + typedef typename wrapper_allocator_type::pointer wrapper_allocator_pointer_type; + wrapper_allocator_type wrapper_allocator(a); + wrapper_allocator_pointer_type copy = wrapper_allocator.allocate(1); + wrapper_allocator.construct(copy, functor_wrapper_type(f,a)); + functor_wrapper_type* new_f = static_cast(copy); + functor.obj_ptr = new_f; + } + + template + bool + assign_to(FunctionObj f, function_buffer& functor, function_obj_tag) const + { + if (!boost::detail::function::has_empty_target(boost::addressof(f))) { + assign_functor(f, functor, + mpl::bool_<(function_allows_small_object_optimization::value)>()); + return true; + } else { + return false; + } + } + template + bool + assign_to_a(FunctionObj f, function_buffer& functor, Allocator a, function_obj_tag) const + { + if (!boost::detail::function::has_empty_target(boost::addressof(f))) { + assign_functor_a(f, functor, a, + mpl::bool_<(function_allows_small_object_optimization::value)>()); + return true; + } else { + return false; + } + } + + // Reference to a function object + template + bool + assign_to(const reference_wrapper& f, + function_buffer& functor, function_obj_ref_tag) const + { + functor.obj_ref.obj_ptr = (void *)(f.get_pointer()); + functor.obj_ref.is_const_qualified = is_const::value; + functor.obj_ref.is_volatile_qualified = is_volatile::value; + return true; + } + template + bool + assign_to_a(const reference_wrapper& f, + function_buffer& functor, Allocator, function_obj_ref_tag) const + { + return assign_to(f,functor,function_obj_ref_tag()); + } + + public: + vtable_base base; + invoker_type invoker; + }; + } // end namespace function + } // end namespace detail + + template< + typename R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_PARMS + > + class BOOST_FUNCTION_FUNCTION : public function_base + +#if BOOST_FUNCTION_NUM_ARGS == 1 + + , public std::unary_function + +#elif BOOST_FUNCTION_NUM_ARGS == 2 + + , public std::binary_function + +#endif + + { + public: +#ifndef BOOST_NO_VOID_RETURNS + typedef R result_type; +#else + typedef typename boost::detail::function::function_return_type::type + result_type; +#endif // BOOST_NO_VOID_RETURNS + + private: + typedef boost::detail::function::BOOST_FUNCTION_VTABLE< + R BOOST_FUNCTION_COMMA BOOST_FUNCTION_TEMPLATE_ARGS> + vtable_type; + + vtable_type* get_vtable() const { + return reinterpret_cast( + reinterpret_cast(vtable) & ~static_cast(0x01)); + } + + struct clear_type {}; + + public: + BOOST_STATIC_CONSTANT(int, args = BOOST_FUNCTION_NUM_ARGS); + + // add signature for boost::lambda + template + struct sig + { + typedef result_type type; + }; + +#if BOOST_FUNCTION_NUM_ARGS == 1 + typedef T0 argument_type; +#elif BOOST_FUNCTION_NUM_ARGS == 2 + typedef T0 first_argument_type; + typedef T1 second_argument_type; +#endif + + BOOST_STATIC_CONSTANT(int, arity = BOOST_FUNCTION_NUM_ARGS); + BOOST_FUNCTION_ARG_TYPES + + typedef BOOST_FUNCTION_FUNCTION self_type; + + BOOST_FUNCTION_FUNCTION() : function_base() { } + + // MSVC chokes if the following two constructors are collapsed into + // one with a default parameter. + template + BOOST_FUNCTION_FUNCTION(Functor BOOST_FUNCTION_TARGET_FIX(const &) f +#ifndef BOOST_NO_SFINAE + ,typename boost::enable_if_c< + !(is_integral::value), + int>::type = 0 +#endif // BOOST_NO_SFINAE + ) : + function_base() + { + this->assign_to(f); + } + template + BOOST_FUNCTION_FUNCTION(Functor BOOST_FUNCTION_TARGET_FIX(const &) f, Allocator a +#ifndef BOOST_NO_SFINAE + ,typename boost::enable_if_c< + !(is_integral::value), + int>::type = 0 +#endif // BOOST_NO_SFINAE + ) : + function_base() + { + this->assign_to_a(f,a); + } + +#ifndef BOOST_NO_SFINAE + BOOST_FUNCTION_FUNCTION(clear_type*) : function_base() { } +#else + BOOST_FUNCTION_FUNCTION(int zero) : function_base() + { + BOOST_ASSERT(zero == 0); + } +#endif + + BOOST_FUNCTION_FUNCTION(const BOOST_FUNCTION_FUNCTION& f) : function_base() + { + this->assign_to_own(f); + } + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + BOOST_FUNCTION_FUNCTION(BOOST_FUNCTION_FUNCTION&& f) : function_base() + { + this->move_assign(f); + } +#endif + + ~BOOST_FUNCTION_FUNCTION() { clear(); } + + result_type operator()(BOOST_FUNCTION_PARMS) const + { + if (this->empty()) + boost::throw_exception(bad_function_call()); + + return get_vtable()->invoker + (this->functor BOOST_FUNCTION_COMMA BOOST_FUNCTION_ARGS); + } + + // The distinction between when to use BOOST_FUNCTION_FUNCTION and + // when to use self_type is obnoxious. MSVC cannot handle self_type as + // the return type of these assignment operators, but Borland C++ cannot + // handle BOOST_FUNCTION_FUNCTION as the type of the temporary to + // construct. + template +#ifndef BOOST_NO_SFINAE + typename boost::enable_if_c< + !(is_integral::value), + BOOST_FUNCTION_FUNCTION&>::type +#else + BOOST_FUNCTION_FUNCTION& +#endif + operator=(Functor BOOST_FUNCTION_TARGET_FIX(const &) f) + { + this->clear(); + BOOST_TRY { + this->assign_to(f); + } BOOST_CATCH (...) { + vtable = 0; + BOOST_RETHROW; + } + BOOST_CATCH_END + return *this; + } + template + void assign(Functor BOOST_FUNCTION_TARGET_FIX(const &) f, Allocator a) + { + this->clear(); + BOOST_TRY{ + this->assign_to_a(f,a); + } BOOST_CATCH (...) { + vtable = 0; + BOOST_RETHROW; + } + BOOST_CATCH_END + } + +#ifndef BOOST_NO_SFINAE + BOOST_FUNCTION_FUNCTION& operator=(clear_type*) + { + this->clear(); + return *this; + } +#else + BOOST_FUNCTION_FUNCTION& operator=(int zero) + { + BOOST_ASSERT(zero == 0); + this->clear(); + return *this; + } +#endif + + // Assignment from another BOOST_FUNCTION_FUNCTION + BOOST_FUNCTION_FUNCTION& operator=(const BOOST_FUNCTION_FUNCTION& f) + { + if (&f == this) + return *this; + + this->clear(); + BOOST_TRY { + this->assign_to_own(f); + } BOOST_CATCH (...) { + vtable = 0; + BOOST_RETHROW; + } + BOOST_CATCH_END + return *this; + } + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + // Move assignment from another BOOST_FUNCTION_FUNCTION + BOOST_FUNCTION_FUNCTION& operator=(BOOST_FUNCTION_FUNCTION&& f) + { + + if (&f == this) + return *this; + + this->clear(); + BOOST_TRY { + this->move_assign(f); + } BOOST_CATCH (...) { + vtable = 0; + BOOST_RETHROW; + } + BOOST_CATCH_END + return *this; + } +#endif + + void swap(BOOST_FUNCTION_FUNCTION& other) + { + if (&other == this) + return; + + BOOST_FUNCTION_FUNCTION tmp; + tmp.move_assign(*this); + this->move_assign(other); + other.move_assign(tmp); + } + + // Clear out a target, if there is one + void clear() + { + if (vtable) { + if (!this->has_trivial_copy_and_destroy()) + get_vtable()->clear(this->functor); + vtable = 0; + } + } + +#if (defined __SUNPRO_CC) && (__SUNPRO_CC <= 0x530) && !(defined BOOST_NO_COMPILER_CONFIG) + // Sun C++ 5.3 can't handle the safe_bool idiom, so don't use it + operator bool () const { return !this->empty(); } +#else + private: + struct dummy { + void nonnull() {} + }; + + typedef void (dummy::*safe_bool)(); + + public: + operator safe_bool () const + { return (this->empty())? 0 : &dummy::nonnull; } + + bool operator!() const + { return this->empty(); } +#endif + + private: + void assign_to_own(const BOOST_FUNCTION_FUNCTION& f) + { + if (!f.empty()) { + this->vtable = f.vtable; + if (this->has_trivial_copy_and_destroy()) + this->functor = f.functor; + else + get_vtable()->base.manager(f.functor, this->functor, + boost::detail::function::clone_functor_tag); + } + } + + template + void assign_to(Functor f) + { + using boost::detail::function::vtable_base; + + typedef typename boost::detail::function::get_function_tag::type tag; + typedef boost::detail::function::BOOST_FUNCTION_GET_INVOKER get_invoker; + typedef typename get_invoker:: + template apply + handler_type; + + typedef typename handler_type::invoker_type invoker_type; + typedef typename handler_type::manager_type manager_type; + + // Note: it is extremely important that this initialization use + // static initialization. Otherwise, we will have a race + // condition here in multi-threaded code. See + // http://thread.gmane.org/gmane.comp.lib.boost.devel/164902/. + static const vtable_type stored_vtable = + { { &manager_type::manage }, &invoker_type::invoke }; + + if (stored_vtable.assign_to(f, functor)) { + std::size_t value = reinterpret_cast(&stored_vtable.base); + // coverity[pointless_expression]: suppress coverity warnings on apparant if(const). + if (boost::has_trivial_copy_constructor::value && + boost::has_trivial_destructor::value && + boost::detail::function::function_allows_small_object_optimization::value) + value |= static_cast(0x01); + vtable = reinterpret_cast(value); + } else + vtable = 0; + } + + template + void assign_to_a(Functor f,Allocator a) + { + using boost::detail::function::vtable_base; + + typedef typename boost::detail::function::get_function_tag::type tag; + typedef boost::detail::function::BOOST_FUNCTION_GET_INVOKER get_invoker; + typedef typename get_invoker:: + template apply_a + handler_type; + + typedef typename handler_type::invoker_type invoker_type; + typedef typename handler_type::manager_type manager_type; + + // Note: it is extremely important that this initialization use + // static initialization. Otherwise, we will have a race + // condition here in multi-threaded code. See + // http://thread.gmane.org/gmane.comp.lib.boost.devel/164902/. + static const vtable_type stored_vtable = + { { &manager_type::manage }, &invoker_type::invoke }; + + if (stored_vtable.assign_to_a(f, functor, a)) { + std::size_t value = reinterpret_cast(&stored_vtable.base); + // coverity[pointless_expression]: suppress coverity warnings on apparant if(const). + if (boost::has_trivial_copy_constructor::value && + boost::has_trivial_destructor::value && + boost::detail::function::function_allows_small_object_optimization::value) + value |= static_cast(0x01); + vtable = reinterpret_cast(value); + } else + vtable = 0; + } + + // Moves the value from the specified argument to *this. If the argument + // has its function object allocated on the heap, move_assign will pass + // its buffer to *this, and set the argument's buffer pointer to NULL. + void move_assign(BOOST_FUNCTION_FUNCTION& f) + { + if (&f == this) + return; + + BOOST_TRY { + if (!f.empty()) { + this->vtable = f.vtable; + if (this->has_trivial_copy_and_destroy()) + this->functor = f.functor; + else + get_vtable()->base.manager(f.functor, this->functor, + boost::detail::function::move_functor_tag); + f.vtable = 0; + } else { + clear(); + } + } BOOST_CATCH (...) { + vtable = 0; + BOOST_RETHROW; + } + BOOST_CATCH_END + } + }; + + template + inline void swap(BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >& f1, + BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS + >& f2) + { + f1.swap(f2); + } + +// Poison comparisons between boost::function objects of the same type. +template + void operator==(const BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS>&, + const BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS>&); +template + void operator!=(const BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS>&, + const BOOST_FUNCTION_FUNCTION< + R BOOST_FUNCTION_COMMA + BOOST_FUNCTION_TEMPLATE_ARGS>& ); + +#if !defined(BOOST_FUNCTION_NO_FUNCTION_TYPE_SYNTAX) + +#if BOOST_FUNCTION_NUM_ARGS == 0 +#define BOOST_FUNCTION_PARTIAL_SPEC R (void) +#else +#define BOOST_FUNCTION_PARTIAL_SPEC R (BOOST_PP_ENUM_PARAMS(BOOST_FUNCTION_NUM_ARGS,T)) +#endif + +template +class function + : public BOOST_FUNCTION_FUNCTION +{ + typedef BOOST_FUNCTION_FUNCTION base_type; + typedef function self_type; + + struct clear_type {}; + +public: + + function() : base_type() {} + + template + function(Functor f +#ifndef BOOST_NO_SFINAE + ,typename boost::enable_if_c< + !(is_integral::value), + int>::type = 0 +#endif + ) : + base_type(f) + { + } + template + function(Functor f, Allocator a +#ifndef BOOST_NO_SFINAE + ,typename boost::enable_if_c< + !(is_integral::value), + int>::type = 0 +#endif + ) : + base_type(f,a) + { + } + +#ifndef BOOST_NO_SFINAE + function(clear_type*) : base_type() {} +#endif + + function(const self_type& f) : base_type(static_cast(f)){} + + function(const base_type& f) : base_type(static_cast(f)){} + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + // Move constructors + function(self_type&& f): base_type(static_cast(f)){} + function(base_type&& f): base_type(static_cast(f)){} +#endif + + self_type& operator=(const self_type& f) + { + self_type(f).swap(*this); + return *this; + } + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + self_type& operator=(self_type&& f) + { + self_type(static_cast(f)).swap(*this); + return *this; + } +#endif + + template +#ifndef BOOST_NO_SFINAE + typename boost::enable_if_c< + !(is_integral::value), + self_type&>::type +#else + self_type& +#endif + operator=(Functor f) + { + self_type(f).swap(*this); + return *this; + } + +#ifndef BOOST_NO_SFINAE + self_type& operator=(clear_type*) + { + this->clear(); + return *this; + } +#endif + + self_type& operator=(const base_type& f) + { + self_type(f).swap(*this); + return *this; + } + +#ifndef BOOST_NO_CXX11_RVALUE_REFERENCES + self_type& operator=(base_type&& f) + { + self_type(static_cast(f)).swap(*this); + return *this; + } +#endif +}; + +#undef BOOST_FUNCTION_PARTIAL_SPEC +#endif // have partial specialization + +} // end namespace boost + +// Cleanup after ourselves... +#undef BOOST_FUNCTION_VTABLE +#undef BOOST_FUNCTION_COMMA +#undef BOOST_FUNCTION_FUNCTION +#undef BOOST_FUNCTION_FUNCTION_INVOKER +#undef BOOST_FUNCTION_VOID_FUNCTION_INVOKER +#undef BOOST_FUNCTION_FUNCTION_OBJ_INVOKER +#undef BOOST_FUNCTION_VOID_FUNCTION_OBJ_INVOKER +#undef BOOST_FUNCTION_FUNCTION_REF_INVOKER +#undef BOOST_FUNCTION_VOID_FUNCTION_REF_INVOKER +#undef BOOST_FUNCTION_MEMBER_INVOKER +#undef BOOST_FUNCTION_VOID_MEMBER_INVOKER +#undef BOOST_FUNCTION_GET_FUNCTION_INVOKER +#undef BOOST_FUNCTION_GET_FUNCTION_OBJ_INVOKER +#undef BOOST_FUNCTION_GET_FUNCTION_REF_INVOKER +#undef BOOST_FUNCTION_GET_MEM_FUNCTION_INVOKER +#undef BOOST_FUNCTION_GET_INVOKER +#undef BOOST_FUNCTION_TEMPLATE_PARMS +#undef BOOST_FUNCTION_TEMPLATE_ARGS +#undef BOOST_FUNCTION_PARMS +#undef BOOST_FUNCTION_PARM +#ifdef BOOST_FUNCTION_ARG +# undef BOOST_FUNCTION_ARG +#endif +#undef BOOST_FUNCTION_ARGS +#undef BOOST_FUNCTION_ARG_TYPE +#undef BOOST_FUNCTION_ARG_TYPES +#undef BOOST_FUNCTION_VOID_RETURN_TYPE +#undef BOOST_FUNCTION_RETURN + +#if defined(BOOST_MSVC) +# pragma warning( pop ) +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/function_equal.hpp b/thirdparty/source/boost_1_61_0/boost/function_equal.hpp new file mode 100644 index 0000000..2d76c75 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/function_equal.hpp @@ -0,0 +1,28 @@ +// Copyright Douglas Gregor 2004. +// Copyright 2005 Peter Dimov + +// Use, modification and distribution is subject to +// the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// For more information, see http://www.boost.org +#ifndef BOOST_FUNCTION_EQUAL_HPP +#define BOOST_FUNCTION_EQUAL_HPP + +namespace boost { + +template + bool function_equal_impl(const F& f, const G& g, long) + { return f == g; } + +// function_equal_impl needs to be unqualified to pick +// user overloads on two-phase compilers + +template + bool function_equal(const F& f, const G& g) + { return function_equal_impl(f, g, 0); } + +} // end namespace boost + +#endif // BOOST_FUNCTION_EQUAL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/functional/factory.hpp b/thirdparty/source/boost_1_61_0/boost/functional/factory.hpp new file mode 100644 index 0000000..67fee71 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/factory.hpp @@ -0,0 +1,179 @@ +/*============================================================================= + Copyright (c) 2007 Tobias Schwinger + + Use modification and distribution are subject to the Boost Software + License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt). +==============================================================================*/ + +#ifndef BOOST_FUNCTIONAL_FACTORY_HPP_INCLUDED +# ifndef BOOST_PP_IS_ITERATING + +# include +# include +# include + +# include +# include +# include +# include +# include + +# if defined(BOOST_FUNCTIONAL_FACTORY_SUPPORT_NONE_T) +# include +# endif + +# ifndef BOOST_FUNCTIONAL_FACTORY_MAX_ARITY +# define BOOST_FUNCTIONAL_FACTORY_MAX_ARITY 10 +# elif BOOST_FUNCTIONAL_FACTORY_MAX_ARITY < 3 +# undef BOOST_FUNCTIONAL_FACTORY_MAX_ARITY +# define BOOST_FUNCTIONAL_FACTORY_MAX_ARITY 3 +# endif + +namespace boost +{ + enum factory_alloc_propagation + { + factory_alloc_for_pointee_and_deleter, + factory_passes_alloc_to_smart_pointer + }; + +#if defined(BOOST_FUNCTIONAL_FACTORY_SUPPORT_NONE_T) + template< typename Pointer, class Allocator = boost::none_t, + factory_alloc_propagation AP = factory_alloc_for_pointee_and_deleter > + class factory; +#else + template< typename Pointer, class Allocator = void, + factory_alloc_propagation AP = factory_alloc_for_pointee_and_deleter > + class factory; +#endif + + //----- ---- --- -- - - - - + + template< typename Pointer, factory_alloc_propagation AP > + class factory + { + public: + typedef typename boost::remove_cv::type result_type; + typedef typename boost::pointee::type value_type; + + factory() + { } + +# define BOOST_PP_FILENAME_1 +# define BOOST_PP_ITERATION_LIMITS (0,BOOST_FUNCTIONAL_FACTORY_MAX_ARITY) +# include BOOST_PP_ITERATE() + }; + +#if defined(BOOST_FUNCTIONAL_FACTORY_SUPPORT_NONE_T) + template< typename Pointer, factory_alloc_propagation AP > + class factory + : public factory + {}; +#endif + + template< class Pointer, class Allocator, factory_alloc_propagation AP > + class factory + : private Allocator::template rebind< typename boost::pointee< + typename boost::remove_cv::type >::type >::other + { + public: + typedef typename boost::remove_cv::type result_type; + typedef typename boost::pointee::type value_type; + + typedef typename Allocator::template rebind::other + allocator_type; + + explicit factory(allocator_type const & a = allocator_type()) + : allocator_type(a) + { } + + private: + + struct deleter + : allocator_type + { + inline deleter(allocator_type const& that) + : allocator_type(that) + { } + + allocator_type& get_allocator() const + { + return *const_cast( + static_cast(this)); + } + + void operator()(value_type* ptr) const + { + if (!! ptr) ptr->~value_type(); + const_cast(static_cast( + this))->deallocate(ptr,1); + } + }; + + inline allocator_type& get_allocator() const + { + return *const_cast( + static_cast(this)); + } + + inline result_type make_pointer(value_type* ptr, boost::non_type< + factory_alloc_propagation,factory_passes_alloc_to_smart_pointer>) + const + { + return result_type(ptr,deleter(this->get_allocator())); + } + inline result_type make_pointer(value_type* ptr, boost::non_type< + factory_alloc_propagation,factory_alloc_for_pointee_and_deleter>) + const + { + return result_type(ptr,deleter(this->get_allocator()), + this->get_allocator()); + } + + public: + +# define BOOST_TMP_MACRO +# define BOOST_PP_FILENAME_1 +# define BOOST_PP_ITERATION_LIMITS (0,BOOST_FUNCTIONAL_FACTORY_MAX_ARITY) +# include BOOST_PP_ITERATE() +# undef BOOST_TMP_MACRO + }; + + template< typename Pointer, class Allocator, factory_alloc_propagation AP > + class factory; + // forbidden, would create a dangling reference +} + +# define BOOST_FUNCTIONAL_FACTORY_HPP_INCLUDED +# else // defined(BOOST_PP_IS_ITERATING) +# define N BOOST_PP_ITERATION() +# if !defined(BOOST_TMP_MACRO) +# if N > 0 + template< BOOST_PP_ENUM_PARAMS(N, typename T) > +# endif + inline result_type operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,& a)) const + { + return result_type( new value_type(BOOST_PP_ENUM_PARAMS(N,a)) ); + } +# else // defined(BOOST_TMP_MACRO) +# if N > 0 + template< BOOST_PP_ENUM_PARAMS(N, typename T) > +# endif + inline result_type operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,& a)) const + { + value_type* memory = this->get_allocator().allocate(1); + try + { + return make_pointer( + new(memory) value_type(BOOST_PP_ENUM_PARAMS(N,a)), + boost::non_type() ); + } + catch (...) { this->get_allocator().deallocate(memory,1); throw; } + } +# endif +# undef N +# endif // defined(BOOST_PP_IS_ITERATING) + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/forward_adapter.hpp b/thirdparty/source/boost_1_61_0/boost/functional/forward_adapter.hpp new file mode 100644 index 0000000..796abd2 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/forward_adapter.hpp @@ -0,0 +1,472 @@ +/*============================================================================= + Copyright (c) 2007-2008 Tobias Schwinger + + Use modification and distribution are subject to the Boost Software + License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt). +==============================================================================*/ + +#ifndef BOOST_FUNCTIONAL_FORWARD_ADAPTER_HPP_INCLUDED +# ifndef BOOST_PP_IS_ITERATING + +# include +# include + +# include +# include +# include +# include +# include + +# include + +# ifndef BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY +# define BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY 6 +# elif BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY < 3 +# undef BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY +# define BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY 3 +# endif + + +namespace boost +{ + template< typename Function, int Arity_Or_MinArity = -1, int MaxArity = -1 > + class forward_adapter; + + //----- ---- --- -- - - - - + + namespace detail + { + template< class MostDerived, typename Function, typename FunctionConst, + int Arity, int MinArity > + struct forward_adapter_impl; + + struct forward_adapter_result + { + template< typename Sig > struct apply; + + // Utility metafunction for qualification adjustment on arguments + template< typename T > struct q { typedef T const t; }; + template< typename T > struct q { typedef T const t; }; + template< typename T > struct q { typedef T t; }; + + // Utility metafunction to choose target function qualification + template< typename T > struct c + { typedef typename T::target_function_t t; }; + template< typename T > struct c + { typedef typename T::target_function_t t; }; + template< typename T > struct c + { typedef typename T::target_function_const_t t; }; + template< typename T > struct c + { typedef typename T::target_function_const_t t; }; + }; + } + +# define BOOST_TMP_MACRO(f,fn,fc) \ + boost::detail::forward_adapter_impl< \ + forward_adapter, fn, fc, \ + (MaxArity!=-1? MaxArity :Arity_Or_MinArity!=-1? Arity_Or_MinArity \ + :BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY), \ + (Arity_Or_MinArity!=-1? Arity_Or_MinArity : 0) > + + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class forward_adapter + : public BOOST_TMP_MACRO(Function,Function,Function const) + , private Function + { + public: + forward_adapter(Function const& f = Function()) + : Function(f) + { } + + typedef Function target_function_t; + typedef Function const target_function_const_t; + + Function & target_function() { return *this; } + Function const & target_function() const { return *this; } + + template< typename Sig > struct result + : detail::forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function,Function, Function const)::operator(); + }; + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class forward_adapter< Function const, Arity_Or_MinArity, MaxArity > + : public BOOST_TMP_MACRO(Function const, Function const, Function const) + , private Function + { + public: + forward_adapter(Function const& f = Function()) + : Function(f) + { } + + typedef Function const target_function_t; + typedef Function const target_function_const_t; + + Function const & target_function() const { return *this; } + + template< typename Sig > struct result + : detail::forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function const,Function const, Function const) + ::operator(); + }; + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class forward_adapter< Function &, Arity_Or_MinArity, MaxArity > + : public BOOST_TMP_MACRO(Function&, Function, Function) + { + Function& ref_function; + public: + forward_adapter(Function& f) + : ref_function(f) + { } + + typedef Function target_function_t; + typedef Function target_function_const_t; + + Function & target_function() const { return this->ref_function; } + + template< typename Sig > struct result + : detail::forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function&, Function, Function)::operator(); + }; + + #undef BOOST_TMP_MACRO + + namespace detail + { + template< class Self > + struct forward_adapter_result::apply< Self() > + : boost::result_of< BOOST_DEDUCED_TYPENAME c::t() > + { }; + + template< class MD, class F, class FC > + struct forward_adapter_impl + { + inline typename boost::result_of< FC() >::type + operator()() const + { + return static_cast(this)->target_function()(); + } + + inline typename boost::result_of< F() >::type + operator()() + { + return static_cast(this)->target_function()(); + } + + // closing brace gets generated by preprocessing code, below + +# define BOOST_TMP_MACRO(tpl_params,arg_types,params,args) \ + template< tpl_params > \ + inline typename boost::result_of< FC(arg_types) >::type \ + operator()(params) const \ + { \ + return static_cast(this)->target_function()(args); \ + } \ + template< tpl_params > \ + inline typename boost::result_of< F(arg_types)>::type \ + operator()(params) \ + { \ + return static_cast(this)->target_function()(args); \ + } + +# // This is the total number of iterations we need +# define count ((1 << BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY+1)-2) + +# // Chain file iteration to virtually one loop +# if BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY <= 7 +# define limit1 count +# define limit2 0 +# define limit3 0 +# else +# if BOOST_FUNCTIONAL_FORWARD_ADAPTER_MAX_ARITY <= 15 +# define limit1 (count >> 8) +# define limit2 255 +# define limit3 0 +# else +# define limit1 (count >> 16) +# define limit2 255 +# define limit3 255 +# endif +# endif + +# define N 0 + +# define BOOST_PP_FILENAME_1 +# define BOOST_PP_ITERATION_LIMITS (0,limit1) +# include BOOST_PP_ITERATE() + +# undef N +# undef limit3 +# undef limit2 +# undef limit1 +# undef count +# undef BOOST_TMP_MACRO + + }; + + } // namespace detail + + template + struct result_of const ()> + : boost::detail::forward_adapter_result::template apply< + boost::forward_adapter const () > + { }; + template + struct result_of()> + : boost::detail::forward_adapter_result::template apply< + boost::forward_adapter() > + { }; + template + struct result_of const& ()> + : boost::detail::forward_adapter_result::template apply< + boost::forward_adapter const () > + { }; + template + struct result_of& ()> + : boost::detail::forward_adapter_result::template apply< + boost::forward_adapter() > + { }; +} + +# define BOOST_FUNCTIONAL_FORWARD_ADAPTER_HPP_INCLUDED + +# elif BOOST_PP_ITERATION_DEPTH() == 1 && limit2 +# define BOOST_PP_FILENAME_2 +# define BOOST_PP_ITERATION_LIMITS (0,limit2) +# include BOOST_PP_ITERATE() +# elif BOOST_PP_ITERATION_DEPTH() == 2 && limit3 +# define BOOST_PP_FILENAME_3 +# define BOOST_PP_ITERATION_LIMITS (0,limit3) +# include BOOST_PP_ITERATE() + +# else + +# // I is the loop counter +# if limit2 && limit3 +# define I (BOOST_PP_ITERATION_1 << 16 | BOOST_PP_ITERATION_2 << 8 | \ + BOOST_PP_ITERATION_3) +# elif limit2 +# define I (BOOST_PP_ITERATION_1 << 8 | BOOST_PP_ITERATION_2) +# else +# define I BOOST_PP_ITERATION_1 +# endif + +# if I < count + +# // Done for this arity? Increment N +# if (I+2 >> N+1) +# if N == 0 +# undef N +# define N 1 +# elif N == 1 +# undef N +# define N 2 +# elif N == 2 +# undef N +# define N 3 +# elif N == 3 +# undef N +# define N 4 +# elif N == 4 +# undef N +# define N 5 +# elif N == 5 +# undef N +# define N 6 +# elif N == 6 +# undef N +# define N 7 +# elif N == 7 +# undef N +# define N 8 +# elif N == 8 +# undef N +# define N 9 +# elif N == 9 +# undef N +# define N 10 +# elif N == 10 +# undef N +# define N 11 +# elif N == 11 +# undef N +# define N 12 +# elif N == 12 +# undef N +# define N 13 +# elif N == 13 +# undef N +# define N 14 +# elif N == 14 +# undef N +# define N 15 +# elif N == 15 +# undef N +# define N 16 +# endif + + }; + + template< class Self, BOOST_PP_ENUM_PARAMS(N,typename T) > + struct forward_adapter_result::apply< Self(BOOST_PP_ENUM_PARAMS(N,T)) > + : boost::result_of< + BOOST_DEDUCED_TYPENAME c::t(BOOST_PP_ENUM_BINARY_PARAMS(N, + typename q::t& BOOST_PP_INTERCEPT)) > + { }; + + template< class MD, class F, class FC > + struct forward_adapter_impl + { + template< BOOST_PP_ENUM_PARAMS(N,typename T) > + inline typename boost::result_of< F( + BOOST_PP_ENUM_BINARY_PARAMS(N,T,& BOOST_PP_INTERCEPT)) >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,& BOOST_PP_INTERCEPT)); + }; + + template< class MD, class F, class FC, int MinArity > + struct forward_adapter_impl + : forward_adapter_impl + { + using forward_adapter_impl::operator(); + +# endif + +# // Zero based count for each arity would be I-(1< + inline typename boost::result_of< FC(BOOST_PP_ENUM_PARAMS(N,PT)) + >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,PT,a)) const + { + return static_cast(this) + ->target_function()(BOOST_PP_ENUM_PARAMS(N,a)); + } + template< BOOST_PP_ENUM_PARAMS(N,typename T) > + inline typename boost::result_of< F(BOOST_PP_ENUM_PARAMS(N,PT)) + >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,PT,a)) + { + return static_cast(this) + ->target_function()(BOOST_PP_ENUM_PARAMS(N,a)); + } +# else + BOOST_TMP_MACRO(BOOST_PP_ENUM_PARAMS(N,typename T), + BOOST_PP_ENUM_PARAMS(N,PT), BOOST_PP_ENUM_BINARY_PARAMS(N,PT,a), + BOOST_PP_ENUM_PARAMS(N,a) ) + // ...generates uglier code but is faster - it caches ENUM_* +# endif + +# undef PT0 +# undef PT1 +# undef PT2 +# undef PT3 +# undef PT4 +# undef PT5 +# undef PT6 +# undef PT7 +# undef PT8 +# undef PT9 +# undef PT10 +# undef PT11 +# undef PT12 +# undef PT13 +# undef PT14 +# undef PT15 + +# endif // I < count + +# undef I +# endif // defined(BOOST_PP_IS_ITERATING) + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash.hpp new file mode 100644 index 0000000..44983f1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash.hpp @@ -0,0 +1,7 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/float_functions.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/float_functions.hpp new file mode 100644 index 0000000..f3db52f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/float_functions.hpp @@ -0,0 +1,336 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(BOOST_FUNCTIONAL_HASH_DETAIL_FLOAT_FUNCTIONS_HPP) +#define BOOST_FUNCTIONAL_HASH_DETAIL_FLOAT_FUNCTIONS_HPP + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include + +// Set BOOST_HASH_CONFORMANT_FLOATS to 1 for libraries known to have +// sufficiently good floating point support to not require any +// workarounds. +// +// When set to 0, the library tries to automatically +// use the best available implementation. This normally works well, but +// breaks when ambiguities are created by odd namespacing of the functions. +// +// Note that if this is set to 0, the library should still take full +// advantage of the platform's floating point support. + +#if defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__LIBCOMO__) +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__STD_RWCOMPILER_H__) || defined(_RWSTD_VER) +// Rogue Wave library: +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(_LIBCPP_VERSION) +// libc++ +# define BOOST_HASH_CONFORMANT_FLOATS 1 +#elif defined(__GLIBCPP__) || defined(__GLIBCXX__) +// GNU libstdc++ 3 +# if defined(__GNUC__) && __GNUC__ >= 4 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#elif defined(__STL_CONFIG_H) +// generic SGI STL +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__MSL_CPP__) +// MSL standard lib: +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif defined(__IBMCPP__) +// VACPP std lib (probably conformant for much earlier version). +# if __IBMCPP__ >= 1210 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#elif defined(MSIPL_COMPILE_H) +// Modena C++ standard library +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#elif (defined(_YVALS) && !defined(__IBMCPP__)) || defined(_CPPLIB_VER) +// Dinkumware Library (this has to appear after any possible replacement libraries): +# if _CPPLIB_VER >= 405 +# define BOOST_HASH_CONFORMANT_FLOATS 1 +# else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +# endif +#else +# define BOOST_HASH_CONFORMANT_FLOATS 0 +#endif + +#if BOOST_HASH_CONFORMANT_FLOATS + +// The standard library is known to be compliant, so don't use the +// configuration mechanism. + +namespace boost { + namespace hash_detail { + template + struct call_ldexp { + typedef Float float_type; + inline Float operator()(Float x, int y) const { + return std::ldexp(x, y); + } + }; + + template + struct call_frexp { + typedef Float float_type; + inline Float operator()(Float x, int* y) const { + return std::frexp(x, y); + } + }; + + template + struct select_hash_type + { + typedef Float type; + }; + } +} + +#else // BOOST_HASH_CONFORMANT_FLOATS == 0 + +// The C++ standard requires that the C float functions are overloarded +// for float, double and long double in the std namespace, but some of the older +// library implementations don't support this. On some that don't, the C99 +// float functions (frexpf, frexpl, etc.) are available. +// +// The following tries to automatically detect which are available. + +namespace boost { + namespace hash_detail { + + // Returned by dummy versions of the float functions. + + struct not_found { + // Implicitly convertible to float and long double in order to avoid + // a compile error when the dummy float functions are used. + + inline operator float() const { return 0; } + inline operator long double() const { return 0; } + }; + + // A type for detecting the return type of functions. + + template struct is; + template <> struct is { char x[10]; }; + template <> struct is { char x[20]; }; + template <> struct is { char x[30]; }; + template <> struct is { char x[40]; }; + + // Used to convert the return type of a function to a type for sizeof. + + template is float_type(T); + + // call_ldexp + // + // This will get specialized for float and long double + + template struct call_ldexp + { + typedef double float_type; + + inline double operator()(double a, int b) const + { + using namespace std; + return ldexp(a, b); + } + }; + + // call_frexp + // + // This will get specialized for float and long double + + template struct call_frexp + { + typedef double float_type; + + inline double operator()(double a, int* b) const + { + using namespace std; + return frexp(a, b); + } + }; + } +} + +// A namespace for dummy functions to detect when the actual function we want +// isn't available. ldexpl, ldexpf etc. might be added tby the macros below. +// +// AFAICT these have to be outside of the boost namespace, as if they're in +// the boost namespace they'll always be preferable to any other function +// (since the arguments are built in types, ADL can't be used). + +namespace boost_hash_detect_float_functions { + template boost::hash_detail::not_found ldexp(Float, int); + template boost::hash_detail::not_found frexp(Float, int*); +} + +// Macros for generating specializations of call_ldexp and call_frexp. +// +// check_cpp and check_c99 check if the C++ or C99 functions are available. +// +// Then the call_* functions select an appropriate implementation. +// +// I used c99_func in a few places just to get a unique name. +// +// Important: when using 'using namespace' at namespace level, include as +// little as possible in that namespace, as Visual C++ has an odd bug which +// can cause the namespace to be imported at the global level. This seems to +// happen mainly when there's a template in the same namesapce. + +#define BOOST_HASH_CALL_FLOAT_FUNC(cpp_func, c99_func, type1, type2) \ +namespace boost_hash_detect_float_functions { \ + template \ + boost::hash_detail::not_found c99_func(Float, type2); \ +} \ + \ +namespace boost { \ + namespace hash_detail { \ + namespace c99_func##_detect { \ + using namespace std; \ + using namespace boost_hash_detect_float_functions; \ + \ + struct check { \ + static type1 x; \ + static type2 y; \ + BOOST_STATIC_CONSTANT(bool, cpp = \ + sizeof(float_type(cpp_func(x,y))) \ + == sizeof(is)); \ + BOOST_STATIC_CONSTANT(bool, c99 = \ + sizeof(float_type(c99_func(x,y))) \ + == sizeof(is)); \ + }; \ + } \ + \ + template \ + struct call_c99_##c99_func : \ + boost::hash_detail::call_##cpp_func {}; \ + \ + template <> \ + struct call_c99_##c99_func { \ + typedef type1 float_type; \ + \ + template \ + inline type1 operator()(type1 a, T b) const \ + { \ + using namespace std; \ + return c99_func(a, b); \ + } \ + }; \ + \ + template \ + struct call_cpp_##c99_func : \ + call_c99_##c99_func< \ + ::boost::hash_detail::c99_func##_detect::check::c99 \ + > {}; \ + \ + template <> \ + struct call_cpp_##c99_func { \ + typedef type1 float_type; \ + \ + template \ + inline type1 operator()(type1 a, T b) const \ + { \ + using namespace std; \ + return cpp_func(a, b); \ + } \ + }; \ + \ + template <> \ + struct call_##cpp_func : \ + call_cpp_##c99_func< \ + ::boost::hash_detail::c99_func##_detect::check::cpp \ + > {}; \ + } \ +} + +#define BOOST_HASH_CALL_FLOAT_MACRO(cpp_func, c99_func, type1, type2) \ +namespace boost { \ + namespace hash_detail { \ + \ + template <> \ + struct call_##cpp_func { \ + typedef type1 float_type; \ + inline type1 operator()(type1 x, type2 y) const { \ + return c99_func(x, y); \ + } \ + }; \ + } \ +} + +#if defined(ldexpf) +BOOST_HASH_CALL_FLOAT_MACRO(ldexp, ldexpf, float, int) +#else +BOOST_HASH_CALL_FLOAT_FUNC(ldexp, ldexpf, float, int) +#endif + +#if defined(ldexpl) +BOOST_HASH_CALL_FLOAT_MACRO(ldexp, ldexpl, long double, int) +#else +BOOST_HASH_CALL_FLOAT_FUNC(ldexp, ldexpl, long double, int) +#endif + +#if defined(frexpf) +BOOST_HASH_CALL_FLOAT_MACRO(frexp, frexpf, float, int*) +#else +BOOST_HASH_CALL_FLOAT_FUNC(frexp, frexpf, float, int*) +#endif + +#if defined(frexpl) +BOOST_HASH_CALL_FLOAT_MACRO(frexp, frexpl, long double, int*) +#else +BOOST_HASH_CALL_FLOAT_FUNC(frexp, frexpl, long double, int*) +#endif + +#undef BOOST_HASH_CALL_FLOAT_MACRO +#undef BOOST_HASH_CALL_FLOAT_FUNC + + +namespace boost +{ + namespace hash_detail + { + template + struct select_hash_type_impl { + typedef double type; + }; + + template <> + struct select_hash_type_impl { + typedef float type; + }; + + template <> + struct select_hash_type_impl { + typedef long double type; + }; + + + // select_hash_type + // + // If there is support for a particular floating point type, use that + // otherwise use double (there's always support for double). + + template + struct select_hash_type : select_hash_type_impl< + BOOST_DEDUCED_TYPENAME call_ldexp::float_type, + BOOST_DEDUCED_TYPENAME call_frexp::float_type + > {}; + } +} + +#endif // BOOST_HASH_CONFORMANT_FLOATS + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/hash_float.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/hash_float.hpp new file mode 100644 index 0000000..eb9264f --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/hash_float.hpp @@ -0,0 +1,271 @@ + +// Copyright 2005-2012 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#if !defined(BOOST_FUNCTIONAL_HASH_DETAIL_HASH_FLOAT_HEADER) +#define BOOST_FUNCTIONAL_HASH_DETAIL_HASH_FLOAT_HEADER + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_MSVC) +#pragma warning(push) +#if BOOST_MSVC >= 1400 +#pragma warning(disable:6294) // Ill-defined for-loop: initial condition does + // not satisfy test. Loop body not executed +#endif +#endif + +// Can we use fpclassify? + +// STLport +#if defined(__SGI_STL_PORT) || defined(_STLPORT_VERSION) +#define BOOST_HASH_USE_FPCLASSIFY 0 + +// GNU libstdc++ 3 +#elif defined(__GLIBCPP__) || defined(__GLIBCXX__) +# if (defined(__USE_ISOC99) || defined(_GLIBCXX_USE_C99_MATH)) && \ + !(defined(macintosh) || defined(__APPLE__) || defined(__APPLE_CC__)) +# define BOOST_HASH_USE_FPCLASSIFY 1 +# else +# define BOOST_HASH_USE_FPCLASSIFY 0 +# endif + +// Everything else +#else +# define BOOST_HASH_USE_FPCLASSIFY 0 +#endif + +namespace boost +{ + namespace hash_detail + { + inline void hash_float_combine(std::size_t& seed, std::size_t value) + { + seed ^= value + (seed<<6) + (seed>>2); + } + + //////////////////////////////////////////////////////////////////////// + // Binary hash function + // + // Only used for floats with known iec559 floats, and certain values in + // numeric_limits + + inline std::size_t hash_binary(char* ptr, std::size_t length) + { + std::size_t seed = 0; + + if (length >= sizeof(std::size_t)) { + std::memcpy(&seed, ptr, sizeof(std::size_t)); + length -= sizeof(std::size_t); + ptr += sizeof(std::size_t); + + while(length >= sizeof(std::size_t)) { + std::size_t buffer = 0; + std::memcpy(&buffer, ptr, sizeof(std::size_t)); + hash_float_combine(seed, buffer); + length -= sizeof(std::size_t); + ptr += sizeof(std::size_t); + } + } + + if (length > 0) { + std::size_t buffer = 0; + std::memcpy(&buffer, ptr, length); + hash_float_combine(seed, buffer); + } + + return seed; + } + + template + struct enable_binary_hash + { + BOOST_STATIC_CONSTANT(bool, value = + std::numeric_limits::is_iec559 && + std::numeric_limits::digits == digits && + std::numeric_limits::radix == 2 && + std::numeric_limits::max_exponent == max_exponent); + }; + + template + inline std::size_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + std::size_t>::type) + { + return hash_binary((char*) &v, 4); + } + + + template + inline std::size_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + std::size_t>::type) + { + return hash_binary((char*) &v, 8); + } + + template + inline std::size_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + std::size_t>::type) + { + return hash_binary((char*) &v, 10); + } + + template + inline std::size_t float_hash_impl(Float v, + BOOST_DEDUCED_TYPENAME boost::enable_if_c< + enable_binary_hash::value, + std::size_t>::type) + { + return hash_binary((char*) &v, 16); + } + + //////////////////////////////////////////////////////////////////////// + // Portable hash function + // + // Used as a fallback when the binary hash function isn't supported. + + template + inline std::size_t float_hash_impl2(T v) + { + boost::hash_detail::call_frexp frexp; + boost::hash_detail::call_ldexp ldexp; + + int exp = 0; + + v = frexp(v, &exp); + + // A postive value is easier to hash, so combine the + // sign with the exponent and use the absolute value. + if(v < 0) { + v = -v; + exp += limits::max_exponent - + limits::min_exponent; + } + + v = ldexp(v, limits::digits); + std::size_t seed = static_cast(v); + v -= static_cast(seed); + + // ceiling(digits(T) * log2(radix(T))/ digits(size_t)) - 1; + std::size_t const length + = (limits::digits * + boost::static_log2::radix>::value + + limits::digits - 1) + / limits::digits; + + for(std::size_t i = 0; i != length; ++i) + { + v = ldexp(v, limits::digits); + std::size_t part = static_cast(v); + v -= static_cast(part); + hash_float_combine(seed, part); + } + + hash_float_combine(seed, exp); + + return seed; + } + +#if !defined(BOOST_HASH_DETAIL_TEST_WITHOUT_GENERIC) + template + inline std::size_t float_hash_impl(T v, ...) + { + typedef BOOST_DEDUCED_TYPENAME select_hash_type::type type; + return float_hash_impl2(static_cast(v)); + } +#endif + } +} + +#if BOOST_HASH_USE_FPCLASSIFY + +#include + +namespace boost +{ + namespace hash_detail + { + template + inline std::size_t float_hash_value(T v) + { +#if defined(fpclassify) + switch (fpclassify(v)) +#elif BOOST_HASH_CONFORMANT_FLOATS + switch (std::fpclassify(v)) +#else + using namespace std; + switch (fpclassify(v)) +#endif + { + case FP_ZERO: + return 0; + case FP_INFINITE: + return (std::size_t)(v > 0 ? -1 : -2); + case FP_NAN: + return (std::size_t)(-3); + case FP_NORMAL: + case FP_SUBNORMAL: + return float_hash_impl(v, 0); + default: + BOOST_ASSERT(0); + return 0; + } + } + } +} + +#else // !BOOST_HASH_USE_FPCLASSIFY + +namespace boost +{ + namespace hash_detail + { + template + inline bool is_zero(T v) + { +#if !defined(__GNUC__) + return v == 0; +#else + // GCC's '-Wfloat-equal' will complain about comparing + // v to 0, but because it disables warnings for system + // headers it won't complain if you use std::equal_to to + // compare with 0. Resulting in this silliness: + return std::equal_to()(v, 0); +#endif + } + + template + inline std::size_t float_hash_value(T v) + { + return boost::hash_detail::is_zero(v) ? 0 : float_hash_impl(v, 0); + } + } +} + +#endif // BOOST_HASH_USE_FPCLASSIFY + +#undef BOOST_HASH_USE_FPCLASSIFY + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/limits.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/limits.hpp new file mode 100644 index 0000000..4a971a6 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/detail/limits.hpp @@ -0,0 +1,62 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// On some platforms std::limits gives incorrect values for long double. +// This tries to work around them. + +#if !defined(BOOST_FUNCTIONAL_HASH_DETAIL_LIMITS_HEADER) +#define BOOST_FUNCTIONAL_HASH_DETAIL_LIMITS_HEADER + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include + +// On OpenBSD, numeric_limits is not reliable for long doubles, but +// the macros defined in are and support long double when STLport +// doesn't. + +#if defined(__OpenBSD__) || defined(_STLP_NO_LONG_DOUBLE) +#include +#endif + +namespace boost +{ + namespace hash_detail + { + template + struct limits : std::numeric_limits {}; + +#if defined(__OpenBSD__) || defined(_STLP_NO_LONG_DOUBLE) + template <> + struct limits + : std::numeric_limits + { + static long double epsilon() { + return LDBL_EPSILON; + } + + static long double (max)() { + return LDBL_MAX; + } + + static long double (min)() { + return LDBL_MIN; + } + + BOOST_STATIC_CONSTANT(int, digits = LDBL_MANT_DIG); + BOOST_STATIC_CONSTANT(int, max_exponent = LDBL_MAX_EXP); + BOOST_STATIC_CONSTANT(int, min_exponent = LDBL_MIN_EXP); +#if defined(_STLP_NO_LONG_DOUBLE) + BOOST_STATIC_CONSTANT(int, radix = FLT_RADIX); +#endif + }; +#endif // __OpenBSD__ + } +} + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/extensions.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/extensions.hpp new file mode 100644 index 0000000..eafaefe --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/extensions.hpp @@ -0,0 +1,318 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. + +// This implements the extensions to the standard. +// It's undocumented, so you shouldn't use it.... + +#if !defined(BOOST_FUNCTIONAL_HASH_EXTENSIONS_HPP) +#define BOOST_FUNCTIONAL_HASH_EXTENSIONS_HPP + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include +#include +#include +#include +#include + +#if !defined(BOOST_NO_CXX11_HDR_ARRAY) +# include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TUPLE) +# include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_MEMORY) +# include +#endif + +#if defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) +#include +#endif + +namespace boost +{ + template + std::size_t hash_value(std::pair const&); + template + std::size_t hash_value(std::vector const&); + template + std::size_t hash_value(std::list const& v); + template + std::size_t hash_value(std::deque const& v); + template + std::size_t hash_value(std::set const& v); + template + std::size_t hash_value(std::multiset const& v); + template + std::size_t hash_value(std::map const& v); + template + std::size_t hash_value(std::multimap const& v); + + template + std::size_t hash_value(std::complex const&); + + template + std::size_t hash_value(std::pair const& v) + { + std::size_t seed = 0; + boost::hash_combine(seed, v.first); + boost::hash_combine(seed, v.second); + return seed; + } + + template + std::size_t hash_value(std::vector const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::list const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::deque const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::set const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::multiset const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::map const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::multimap const& v) + { + return boost::hash_range(v.begin(), v.end()); + } + + template + std::size_t hash_value(std::complex const& v) + { + boost::hash hasher; + std::size_t seed = hasher(v.imag()); + seed ^= hasher(v.real()) + (seed<<6) + (seed>>2); + return seed; + } + +#if !defined(BOOST_NO_CXX11_HDR_ARRAY) + template + std::size_t hash_value(std::array const& v) + { + return boost::hash_range(v.begin(), v.end()); + } +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TUPLE) + namespace hash_detail { + template + inline typename boost::enable_if_c<(I == std::tuple_size::value), + void>::type + hash_combine_tuple(std::size_t&, T const&) + { + } + + template + inline typename boost::enable_if_c<(I < std::tuple_size::value), + void>::type + hash_combine_tuple(std::size_t& seed, T const& v) + { + boost::hash_combine(seed, std::get(v)); + boost::hash_detail::hash_combine_tuple(seed, v); + } + + template + inline std::size_t hash_tuple(T const& v) + { + std::size_t seed = 0; + boost::hash_detail::hash_combine_tuple<0>(seed, v); + return seed; + } + } + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + template + inline std::size_t hash_value(std::tuple const& v) + { + return boost::hash_detail::hash_tuple(v); + } +#else + + inline std::size_t hash_value(std::tuple<> const& v) + { + return boost::hash_detail::hash_tuple(v); + } + +# define BOOST_HASH_TUPLE_F(z, n, _) \ + template< \ + BOOST_PP_ENUM_PARAMS_Z(z, n, typename A) \ + > \ + inline std::size_t hash_value(std::tuple< \ + BOOST_PP_ENUM_PARAMS_Z(z, n, A) \ + > const& v) \ + { \ + return boost::hash_detail::hash_tuple(v); \ + } + + BOOST_PP_REPEAT_FROM_TO(1, 11, BOOST_HASH_TUPLE_F, _) +# undef BOOST_HASH_TUPLE_F +#endif + +#endif + +#if !defined(BOOST_NO_CXX11_SMART_PTR) + template + inline std::size_t hash_value(std::shared_ptr const& x) { + return boost::hash_value(x.get()); + } + + template + inline std::size_t hash_value(std::unique_ptr const& x) { + return boost::hash_value(x.get()); + } +#endif + + // + // call_hash_impl + // + + // On compilers without function template ordering, this deals with arrays. + +#if defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + namespace hash_detail + { + template + struct call_hash_impl + { + template + struct inner + { + static std::size_t call(T const& v) + { + using namespace boost; + return hash_value(v); + } + }; + }; + + template <> + struct call_hash_impl + { + template + struct inner + { + static std::size_t call(Array const& v) + { + const int size = sizeof(v) / sizeof(*v); + return boost::hash_range(v, v + size); + } + }; + }; + + template + struct call_hash + : public call_hash_impl::value> + ::BOOST_NESTED_TEMPLATE inner + { + }; + } +#endif // BOOST_NO_FUNCTION_TEMPLATE_ORDERING + + // + // boost::hash + // + + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) + + template struct hash + : std::unary_function + { +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + std::size_t operator()(T const& val) const + { + return hash_value(val); + } +#else + std::size_t operator()(T const& val) const + { + return hash_detail::call_hash::call(val); + } +#endif + }; + +#if BOOST_WORKAROUND(__DMC__, <= 0x848) + template struct hash + : std::unary_function + { + std::size_t operator()(const T* val) const + { + return boost::hash_range(val, val+n); + } + }; +#endif + +#else // BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION + + // On compilers without partial specialization, boost::hash + // has already been declared to deal with pointers, so just + // need to supply the non-pointer version of hash_impl. + + namespace hash_detail + { + template + struct hash_impl; + + template <> + struct hash_impl + { + template + struct inner + : std::unary_function + { +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + std::size_t operator()(T const& val) const + { + return hash_value(val); + } +#else + std::size_t operator()(T const& val) const + { + return hash_detail::call_hash::call(val); + } +#endif + }; + }; + } +#endif // BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION +} + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/hash.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/hash.hpp new file mode 100644 index 0000000..2fb9f21 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/hash.hpp @@ -0,0 +1,559 @@ + +// Copyright 2005-2014 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. +// +// This also contains public domain code from MurmurHash. From the +// MurmurHash header: + +// MurmurHash3 was written by Austin Appleby, and is placed in the public +// domain. The author hereby disclaims copyright to this source code. + +#if !defined(BOOST_FUNCTIONAL_HASH_HASH_HPP) +#define BOOST_FUNCTIONAL_HASH_HASH_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) +#include +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) +#include +#endif + +#if defined(BOOST_MSVC) +#pragma warning(push) + +#if BOOST_MSVC >= 1400 +#pragma warning(disable:6295) // Ill-defined for-loop : 'unsigned int' values + // are always of range '0' to '4294967295'. + // Loop executes infinitely. +#endif + +#endif + +#if BOOST_WORKAROUND(__GNUC__, < 3) \ + && !defined(__SGI_STL_PORT) && !defined(_STLPORT_VERSION) +#define BOOST_HASH_CHAR_TRAITS string_char_traits +#else +#define BOOST_HASH_CHAR_TRAITS char_traits +#endif + +#if defined(_MSC_VER) +# define BOOST_FUNCTIONAL_HASH_ROTL32(x, r) _rotl(x,r) +#else +# define BOOST_FUNCTIONAL_HASH_ROTL32(x, r) (x << r) | (x >> (32 - r)) +#endif + +namespace boost +{ + namespace hash_detail + { + struct enable_hash_value { typedef std::size_t type; }; + + template struct basic_numbers {}; + template struct long_numbers; + template struct ulong_numbers; + template struct float_numbers {}; + + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; + +#if !defined(BOOST_NO_INTRINSIC_WCHAR_T) + template <> struct basic_numbers : + boost::hash_detail::enable_hash_value {}; +#endif + + // long_numbers is defined like this to allow for separate + // specialization for long_long and int128_type, in case + // they conflict. + template struct long_numbers2 {}; + template struct ulong_numbers2 {}; + template struct long_numbers : long_numbers2 {}; + template struct ulong_numbers : ulong_numbers2 {}; + +#if !defined(BOOST_NO_LONG_LONG) + template <> struct long_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct ulong_numbers : + boost::hash_detail::enable_hash_value {}; +#endif + +#if defined(BOOST_HAS_INT128) + template <> struct long_numbers2 : + boost::hash_detail::enable_hash_value {}; + template <> struct ulong_numbers2 : + boost::hash_detail::enable_hash_value {}; +#endif + + template <> struct float_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct float_numbers : + boost::hash_detail::enable_hash_value {}; + template <> struct float_numbers : + boost::hash_detail::enable_hash_value {}; + } + + template + typename boost::hash_detail::basic_numbers::type hash_value(T); + template + typename boost::hash_detail::long_numbers::type hash_value(T); + template + typename boost::hash_detail::ulong_numbers::type hash_value(T); + + template + typename boost::enable_if, std::size_t>::type + hash_value(T); + +#if !BOOST_WORKAROUND(__DMC__, <= 0x848) + template std::size_t hash_value(T* const&); +#else + template std::size_t hash_value(T*); +#endif + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + template< class T, unsigned N > + std::size_t hash_value(const T (&x)[N]); + + template< class T, unsigned N > + std::size_t hash_value(T (&x)[N]); +#endif + + template + std::size_t hash_value( + std::basic_string, A> const&); + + template + typename boost::hash_detail::float_numbers::type hash_value(T); + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + std::size_t hash_value(std::type_index); +#endif + + // Implementation + + namespace hash_detail + { + template + inline std::size_t hash_value_signed(T val) + { + const int size_t_bits = std::numeric_limits::digits; + // ceiling(std::numeric_limits::digits / size_t_bits) - 1 + const int length = (std::numeric_limits::digits - 1) + / size_t_bits; + + std::size_t seed = 0; + T positive = val < 0 ? -1 - val : val; + + // Hopefully, this loop can be unrolled. + for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) + { + seed ^= (std::size_t) (positive >> i) + (seed<<6) + (seed>>2); + } + seed ^= (std::size_t) val + (seed<<6) + (seed>>2); + + return seed; + } + + template + inline std::size_t hash_value_unsigned(T val) + { + const int size_t_bits = std::numeric_limits::digits; + // ceiling(std::numeric_limits::digits / size_t_bits) - 1 + const int length = (std::numeric_limits::digits - 1) + / size_t_bits; + + std::size_t seed = 0; + + // Hopefully, this loop can be unrolled. + for(unsigned int i = length * size_t_bits; i > 0; i -= size_t_bits) + { + seed ^= (std::size_t) (val >> i) + (seed<<6) + (seed>>2); + } + seed ^= (std::size_t) val + (seed<<6) + (seed>>2); + + return seed; + } + + template + inline void hash_combine_impl(SizeT& seed, SizeT value) + { + seed ^= value + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + template + inline void hash_combine_impl(boost::uint32_t& h1, + boost::uint32_t k1) + { + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + k1 *= c1; + k1 = BOOST_FUNCTIONAL_HASH_ROTL32(k1,15); + k1 *= c2; + + h1 ^= k1; + h1 = BOOST_FUNCTIONAL_HASH_ROTL32(h1,13); + h1 = h1*5+0xe6546b64; + } + + +// Don't define 64-bit hash combine on platforms with 64 bit integers, +// and also not for 32-bit gcc as it warns about the 64-bit constant. +#if !defined(BOOST_NO_INT64_T) && \ + !(defined(__GNUC__) && ULONG_MAX == 0xffffffff) + + template + inline void hash_combine_impl(boost::uint64_t& h, + boost::uint64_t k) + { + const uint64_t m = UINT64_C(0xc6a4a7935bd1e995); + const int r = 47; + + k *= m; + k ^= k >> r; + k *= m; + + h ^= k; + h *= m; + } + +#endif // BOOST_NO_INT64_T + } + + template + typename boost::hash_detail::basic_numbers::type hash_value(T v) + { + return static_cast(v); + } + + template + typename boost::hash_detail::long_numbers::type hash_value(T v) + { + return hash_detail::hash_value_signed(v); + } + + template + typename boost::hash_detail::ulong_numbers::type hash_value(T v) + { + return hash_detail::hash_value_unsigned(v); + } + + template + typename boost::enable_if, std::size_t>::type + hash_value(T v) + { + return static_cast(v); + } + + // Implementation by Alberto Barbati and Dave Harris. +#if !BOOST_WORKAROUND(__DMC__, <= 0x848) + template std::size_t hash_value(T* const& v) +#else + template std::size_t hash_value(T* v) +#endif + { +#if defined(__VMS) && __INITIAL_POINTER_SIZE == 64 + // for some reason ptrdiff_t on OpenVMS compiler with + // 64 bit is not 64 bit !!! + std::size_t x = static_cast( + reinterpret_cast(v)); +#else + std::size_t x = static_cast( + reinterpret_cast(v)); +#endif + return x + (x >> 3); + } + +#if defined(BOOST_MSVC) +#pragma warning(push) +#if BOOST_MSVC <= 1400 +#pragma warning(disable:4267) // 'argument' : conversion from 'size_t' to + // 'unsigned int', possible loss of data + // A misguided attempt to detect 64-bit + // incompatability. +#endif +#endif + + template + inline void hash_combine(std::size_t& seed, T const& v) + { + boost::hash hasher; + return boost::hash_detail::hash_combine_impl(seed, hasher(v)); + } + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + + template + inline std::size_t hash_range(It first, It last) + { + std::size_t seed = 0; + + for(; first != last; ++first) + { + hash_combine(seed, *first); + } + + return seed; + } + + template + inline void hash_range(std::size_t& seed, It first, It last) + { + for(; first != last; ++first) + { + hash_combine(seed, *first); + } + } + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x551)) + template + inline std::size_t hash_range(T* first, T* last) + { + std::size_t seed = 0; + + for(; first != last; ++first) + { + boost::hash hasher; + seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + + return seed; + } + + template + inline void hash_range(std::size_t& seed, T* first, T* last) + { + for(; first != last; ++first) + { + boost::hash hasher; + seed ^= hasher(*first) + 0x9e3779b9 + (seed<<6) + (seed>>2); + } + } +#endif + +#if !defined(BOOST_NO_FUNCTION_TEMPLATE_ORDERING) + template< class T, unsigned N > + inline std::size_t hash_value(const T (&x)[N]) + { + return hash_range(x, x + N); + } + + template< class T, unsigned N > + inline std::size_t hash_value(T (&x)[N]) + { + return hash_range(x, x + N); + } +#endif + + template + inline std::size_t hash_value( + std::basic_string, A> const& v) + { + return hash_range(v.begin(), v.end()); + } + + template + typename boost::hash_detail::float_numbers::type hash_value(T v) + { + return boost::hash_detail::float_hash_value(v); + } + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + inline std::size_t hash_value(std::type_index v) + { + return v.hash_code(); + } +#endif + + // + // boost::hash + // + + // Define the specializations required by the standard. The general purpose + // boost::hash is defined later in extensions.hpp if + // BOOST_HASH_NO_EXTENSIONS is not defined. + + // BOOST_HASH_SPECIALIZE - define a specialization for a type which is + // passed by copy. + // + // BOOST_HASH_SPECIALIZE_REF - define a specialization for a type which is + // passed by const reference. + // + // These are undefined later. + +#define BOOST_HASH_SPECIALIZE(type) \ + template <> struct hash \ + : public std::unary_function \ + { \ + std::size_t operator()(type v) const \ + { \ + return boost::hash_value(v); \ + } \ + }; + +#define BOOST_HASH_SPECIALIZE_REF(type) \ + template <> struct hash \ + : public std::unary_function \ + { \ + std::size_t operator()(type const& v) const \ + { \ + return boost::hash_value(v); \ + } \ + }; + + BOOST_HASH_SPECIALIZE(bool) + BOOST_HASH_SPECIALIZE(char) + BOOST_HASH_SPECIALIZE(signed char) + BOOST_HASH_SPECIALIZE(unsigned char) +#if !defined(BOOST_NO_INTRINSIC_WCHAR_T) + BOOST_HASH_SPECIALIZE(wchar_t) +#endif + BOOST_HASH_SPECIALIZE(short) + BOOST_HASH_SPECIALIZE(unsigned short) + BOOST_HASH_SPECIALIZE(int) + BOOST_HASH_SPECIALIZE(unsigned int) + BOOST_HASH_SPECIALIZE(long) + BOOST_HASH_SPECIALIZE(unsigned long) + + BOOST_HASH_SPECIALIZE(float) + BOOST_HASH_SPECIALIZE(double) + BOOST_HASH_SPECIALIZE(long double) + + BOOST_HASH_SPECIALIZE_REF(std::string) +#if !defined(BOOST_NO_STD_WSTRING) + BOOST_HASH_SPECIALIZE_REF(std::wstring) +#endif + +#if !defined(BOOST_NO_LONG_LONG) + BOOST_HASH_SPECIALIZE(boost::long_long_type) + BOOST_HASH_SPECIALIZE(boost::ulong_long_type) +#endif + +#if defined(BOOST_HAS_INT128) + BOOST_HASH_SPECIALIZE(boost::int128_type) + BOOST_HASH_SPECIALIZE(boost::uint128_type) +#endif + +#if !defined(BOOST_NO_CXX11_HDR_TYPEINDEX) + BOOST_HASH_SPECIALIZE(std::type_index) +#endif + +#undef BOOST_HASH_SPECIALIZE +#undef BOOST_HASH_SPECIALIZE_REF + +// Specializing boost::hash for pointers. + +#if !defined(BOOST_NO_TEMPLATE_PARTIAL_SPECIALIZATION) + + template + struct hash + : public std::unary_function + { + std::size_t operator()(T* v) const + { +#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 0x590) + return boost::hash_value(v); +#else + std::size_t x = static_cast( + reinterpret_cast(v)); + + return x + (x >> 3); +#endif + } + }; + +#else + + // For compilers without partial specialization, we define a + // boost::hash for all remaining types. But hash_impl is only defined + // for pointers in 'extensions.hpp' - so when BOOST_HASH_NO_EXTENSIONS + // is defined there will still be a compile error for types not supported + // in the standard. + + namespace hash_detail + { + template + struct hash_impl; + + template <> + struct hash_impl + { + template + struct inner + : public std::unary_function + { + std::size_t operator()(T val) const + { +#if !BOOST_WORKAROUND(__SUNPRO_CC, <= 590) + return boost::hash_value(val); +#else + std::size_t x = static_cast( + reinterpret_cast(val)); + + return x + (x >> 3); +#endif + } + }; + }; + } + + template struct hash + : public boost::hash_detail::hash_impl::value> + ::BOOST_NESTED_TEMPLATE inner + { + }; + +#endif +} + +#undef BOOST_HASH_CHAR_TRAITS +#undef BOOST_FUNCTIONAL_HASH_ROTL32 + +#if defined(BOOST_MSVC) +#pragma warning(pop) +#endif + +#endif // BOOST_FUNCTIONAL_HASH_HASH_HPP + +// Include this outside of the include guards in case the file is included +// twice - once with BOOST_HASH_NO_EXTENSIONS defined, and then with it +// undefined. + +#if !defined(BOOST_HASH_NO_EXTENSIONS) \ + && !defined(BOOST_FUNCTIONAL_HASH_EXTENSIONS_HPP) +#include +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash/hash_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash/hash_fwd.hpp new file mode 100644 index 0000000..01fe012 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash/hash_fwd.hpp @@ -0,0 +1,36 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// Based on Peter Dimov's proposal +// http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2005/n1756.pdf +// issue 6.18. + +#if !defined(BOOST_FUNCTIONAL_HASH_FWD_HPP) +#define BOOST_FUNCTIONAL_HASH_FWD_HPP + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include +#include + +namespace boost +{ + template struct hash; + + template void hash_combine(std::size_t& seed, T const& v); + + template std::size_t hash_range(It, It); + template void hash_range(std::size_t&, It, It); + +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x551)) + template inline std::size_t hash_range(T*, T*); + template inline void hash_range(std::size_t&, T*, T*); +#endif +} + +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/functional/hash_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/functional/hash_fwd.hpp new file mode 100644 index 0000000..eea9073 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/hash_fwd.hpp @@ -0,0 +1,11 @@ + +// Copyright 2005-2009 Daniel James. +// Distributed under the Boost Software License, Version 1.0. (See accompanying +// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#if defined(BOOST_HAS_PRAGMA_ONCE) +#pragma once +#endif + +#include diff --git a/thirdparty/source/boost_1_61_0/boost/functional/lightweight_forward_adapter.hpp b/thirdparty/source/boost_1_61_0/boost/functional/lightweight_forward_adapter.hpp new file mode 100644 index 0000000..637aa9e --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/lightweight_forward_adapter.hpp @@ -0,0 +1,259 @@ +/*============================================================================= + Copyright (c) 2007 Tobias Schwinger + + Use modification and distribution are subject to the Boost Software + License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt). +==============================================================================*/ + +#ifndef BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_HPP_INCLUDED +# ifndef BOOST_PP_IS_ITERATING + +# include +# include + +# include +# include +# include +# include +# include +# include + +# include +# include + +# ifndef BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY +# define BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY 10 +# elif BOOST_FUNCTIONAL_FORDWARD_ADAPTER_MAX_ARITY < 3 +# undef BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY +# define BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY 3 +# endif + +namespace boost +{ + template< typename Function, int Arity_Or_MinArity = -1, int MaxArity = -1 > + class lightweight_forward_adapter; + + //----- ---- --- -- - - - - + + namespace detail + { + template< class MostDerived, typename Function, typename FunctionConst, + int Arity, int MinArity > + struct lightweight_forward_adapter_impl; + + struct lightweight_forward_adapter_result + { + template< typename Sig > struct apply; + + // Utility metafunction for argument transform + template< typename T > struct x { typedef T const& t; }; + template< typename T > struct x< boost::reference_wrapper > + { typedef T& t; }; + template< typename T > struct x : x { }; + template< typename T > struct x : x { }; + template< typename T > struct x : x { }; + + // Utility metafunction to choose target function qualification + template< typename T > struct c + { typedef typename T::target_function_t t; }; + template< typename T > struct c + { typedef typename T::target_function_t t; }; + template< typename T > struct c + { typedef typename T::target_function_const_t t; }; + template< typename T > struct c + { typedef typename T::target_function_const_t t; }; + }; + } + +# define BOOST_TMP_MACRO(f,fn,fc) \ + boost::detail::lightweight_forward_adapter_impl< \ + lightweight_forward_adapter, fn, fc, \ + (MaxArity!=-1? MaxArity :Arity_Or_MinArity!=-1? Arity_Or_MinArity \ + :BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY), \ + (Arity_Or_MinArity!=-1? Arity_Or_MinArity : 0) > + + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class lightweight_forward_adapter + : public BOOST_TMP_MACRO(Function,Function,Function const) + , private Function + { + public: + lightweight_forward_adapter(Function const& f = Function()) + : Function(f) + { } + + typedef Function target_function_t; + typedef Function const target_function_const_t; + + Function & target_function() { return *this; } + Function const & target_function() const { return *this; } + + template< typename Sig > struct result + : detail::lightweight_forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function,Function, Function const)::operator(); + }; + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class lightweight_forward_adapter< Function const, Arity_Or_MinArity, + MaxArity > + : public BOOST_TMP_MACRO(Function const, Function const, Function const) + , private Function + { + public: + lightweight_forward_adapter(Function const& f = Function()) + : Function(f) + { } + + typedef Function const target_function_t; + typedef Function const target_function_const_t; + + Function const & target_function() const { return *this; } + + template< typename Sig > struct result + : detail::lightweight_forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function const,Function const, Function const) + ::operator(); + }; + template< typename Function, int Arity_Or_MinArity, int MaxArity > + class lightweight_forward_adapter< Function &, Arity_Or_MinArity, MaxArity > + : public BOOST_TMP_MACRO(Function&, Function, Function) + { + Function& ref_function; + public: + lightweight_forward_adapter(Function& f) + : ref_function(f) + { } + + typedef Function target_function_t; + typedef Function target_function_const_t; + + Function & target_function() const { return this->ref_function; } + + template< typename Sig > struct result + : detail::lightweight_forward_adapter_result::template apply + { }; + + using BOOST_TMP_MACRO(Function&, Function, Function)::operator(); + }; + + #undef BOOST_TMP_MACRO + + namespace detail + { + template< class Self > + struct lightweight_forward_adapter_result::apply< Self() > + : boost::result_of< BOOST_DEDUCED_TYPENAME c::t() > + { }; + + template< class MD, class F, class FC > + struct lightweight_forward_adapter_impl + : lightweight_forward_adapter_result + { + inline typename boost::result_of< FC() >::type + operator()() const + { + return static_cast(this)->target_function()(); + } + + inline typename boost::result_of< F() >::type + operator()() + { + return static_cast(this)->target_function()(); + } + }; + +# define BOOST_PP_FILENAME_1 \ + +# define BOOST_PP_ITERATION_LIMITS \ + (1,BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_MAX_ARITY) +# include BOOST_PP_ITERATE() + + } // namespace detail + + template + struct result_of const ()> + : boost::detail::lightweight_forward_adapter_result::template apply< + boost::lightweight_forward_adapter const () > + { }; + template + struct result_of()> + : boost::detail::lightweight_forward_adapter_result::template apply< + boost::lightweight_forward_adapter() > + { }; + template + struct result_of const& ()> + : boost::detail::lightweight_forward_adapter_result::template apply< + boost::lightweight_forward_adapter const () > + { }; + template + struct result_of& ()> + : boost::detail::lightweight_forward_adapter_result::template apply< + boost::lightweight_forward_adapter() > + { }; +} + +# define BOOST_FUNCTIONAL_LIGHTWEIGHT_FORWARD_ADAPTER_HPP_INCLUDED + +# else // defined(BOOST_PP_IS_ITERATING) +# define N BOOST_PP_ITERATION() + + template< class Self, BOOST_PP_ENUM_PARAMS(N,typename T) > + struct lightweight_forward_adapter_result::apply< + Self (BOOST_PP_ENUM_PARAMS(N,T)) > + : boost::result_of< + BOOST_DEDUCED_TYPENAME c::t (BOOST_PP_ENUM_BINARY_PARAMS(N, + typename x::t BOOST_PP_INTERCEPT)) > + { }; + + template< class MD, class F, class FC > + struct lightweight_forward_adapter_impl + : lightweight_forward_adapter_result + { + template< BOOST_PP_ENUM_PARAMS(N,typename T) > + inline typename boost::result_of< F(BOOST_PP_ENUM_BINARY_PARAMS(N, + T,const& BOOST_PP_INTERCEPT)) >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,& BOOST_PP_INTERCEPT)); + }; + + template< class MD, class F, class FC, int MinArity > + struct lightweight_forward_adapter_impl + : lightweight_forward_adapter_impl + { + using lightweight_forward_adapter_impl::operator(); + +# define M(z,i,d) \ + static_cast::t>(a##i) + + template< BOOST_PP_ENUM_PARAMS(N,typename T) > + inline typename lightweight_forward_adapter_result::template apply< + MD const (BOOST_PP_ENUM_BINARY_PARAMS(N, + T,const& BOOST_PP_INTERCEPT)) >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,const& a)) const + { + typedef lightweight_forward_adapter_result _; + return static_cast(this)->target_function()( + BOOST_PP_ENUM(N,M,_)); + } + template< BOOST_PP_ENUM_PARAMS(N,typename T) > + inline typename lightweight_forward_adapter_result::template apply< + MD (BOOST_PP_ENUM_BINARY_PARAMS(N, + T,const& BOOST_PP_INTERCEPT)) >::type + operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,const& a)) + { + typedef lightweight_forward_adapter_result _; + return static_cast(this)->target_function()( + BOOST_PP_ENUM(N,M,_)); + } +# undef M + }; + +# undef N +# endif // defined(BOOST_PP_IS_ITERATING) + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function.hpp b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function.hpp new file mode 100644 index 0000000..83fe4b3 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function.hpp @@ -0,0 +1,311 @@ + +// Copyright (C) 2009-2012 Lorenzo Caminiti +// Distributed under the Boost Software License, Version 1.0 +// (see accompanying file LICENSE_1_0.txt or a copy at +// http://www.boost.org/LICENSE_1_0.txt) +// Home at http://www.boost.org/libs/functional/overloaded_function + +#ifndef DOXYGEN // Doxygen documentation only. + +#if !BOOST_PP_IS_ITERATING +# ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_HPP_ +# define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_HPP_ + +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +#define BOOST_FUNCTIONAL_f_type(z, n, unused) \ + BOOST_PP_CAT(F, n) + +#define BOOST_FUNCTIONAL_f_arg(z, n, unused) \ + BOOST_PP_CAT(f, n) + +#define BOOST_FUNCTIONAL_f_tparam(z, n, unused) \ + typename BOOST_FUNCTIONAL_f_type(z, n, ~) \ + +#define BOOST_FUNCTIONAL_f_tparam_dflt(z, n, is_tspec) \ + BOOST_FUNCTIONAL_f_tparam(z, n, ~) \ + /* overload requires at least 2 functors so F0 and F1 not optional */ \ + BOOST_PP_EXPR_IIF(BOOST_PP_AND(BOOST_PP_NOT(is_tspec), \ + BOOST_PP_GREATER(n, 1)), \ + = void \ + ) + +#define BOOST_FUNCTIONAL_f_arg_decl(z, n, unused) \ + BOOST_FUNCTIONAL_f_type(z, n, ~) /* no qualifier to deduce tparam */ \ + BOOST_FUNCTIONAL_f_arg(z, n, ~) + +#define BOOST_FUNCTIONAL_g_type(z, n, unused) \ + BOOST_PP_CAT(G, n) + +#define BOOST_FUNCTIONAL_g_arg(z, n, unused) \ + BOOST_PP_CAT(g, n) + +#define BOOST_FUNCTIONAL_g_tparam(z, n, unused) \ + typename BOOST_FUNCTIONAL_g_type(z, n, ~) + +#define BOOST_FUNCTIONAL_g_arg_decl(z, n, unused) \ + BOOST_FUNCTIONAL_g_type(z, n, ~) /* no qualifier to deduce tparam */ \ + BOOST_FUNCTIONAL_g_arg(z, n, ~) + +#define BOOST_FUNCTIONAL_base(z, n, unused) \ + ::boost::overloaded_function_detail::base< \ + BOOST_FUNCTIONAL_f_type(z, n, ~) \ + > + +#define BOOST_FUNCTIONAL_inherit(z, n, unused) \ + public BOOST_FUNCTIONAL_base(z, n, ~) + +#define BOOST_FUNCTIONAL_base_init(z, n, unused) \ + BOOST_FUNCTIONAL_base(z, n, ~)(BOOST_FUNCTIONAL_g_arg(z, n, ~)) + +#define BOOST_FUNCTIONAL_using_operator_call(z, n, unused) \ + using BOOST_FUNCTIONAL_base(z, n, ~)::operator(); + +#define BOOST_FUNCTIONAL_function_type(z, n, unused) \ + typename ::boost::overloaded_function_detail::function_type< \ + BOOST_FUNCTIONAL_f_type(z, n, ~) \ + >::type + +# define BOOST_PP_ITERATION_PARAMS_1 \ + /* at least 2 func to overload so start from 2 to MAX */ \ + /* (cannot iterate [0, MAX-2) because error on Sun) */ \ + (3, (2, BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX, \ + "boost/functional/overloaded_function.hpp")) +# include BOOST_PP_ITERATE() // Iterate over function arity. + +#undef BOOST_FUNCTIONAL_f_type +#undef BOOST_FUNCTIONAL_f_arg +#undef BOOST_FUNCTIONAL_f_tparam +#undef BOOST_FUNCTIONAL_f_arg_decl +#undef BOOST_FUNCTIONAL_f_tparam_dflt +#undef BOOST_FUNCTIONAL_g_type +#undef BOOST_FUNCTIONAL_g_arg +#undef BOOST_FUNCTIONAL_g_tparam +#undef BOOST_FUNCTIONAL_g_arg_decl +#undef BOOST_FUNCTIONAL_base +#undef BOOST_FUNCTIONAL_inherit +#undef BOOST_FUNCTIONAL_base_init +#undef BOOST_FUNCTIONAL_using_operator_call +#undef BOOST_FUNCTIONAL_function_type + +# endif // #include guard + +#elif BOOST_PP_ITERATION_DEPTH() == 1 +# define BOOST_FUNCTIONAL_overloads \ + /* iterate as OVERLOADS, OVERLOADS-1, OVERLOADS-2, ... */ \ + /* (add 2 because iteration started from 2 to MAX) */ \ + BOOST_PP_ADD(2, BOOST_PP_SUB( \ + BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX, \ + BOOST_PP_FRAME_ITERATION(1))) +# define BOOST_FUNCTIONAL_is_tspec \ + /* if template specialization */ \ + BOOST_PP_LESS(BOOST_FUNCTIONAL_overloads, \ + BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX) + +// For type-of emulation: This must be included at this pp iteration level. +# include BOOST_TYPEOF_INCREMENT_REGISTRATION_GROUP() + +namespace boost { + +template< + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_f_tparam_dflt, + BOOST_FUNCTIONAL_is_tspec) +> +class overloaded_function + // Template specialization. + BOOST_PP_EXPR_IIF(BOOST_PP_EXPAND(BOOST_FUNCTIONAL_is_tspec), <) + BOOST_PP_IIF(BOOST_FUNCTIONAL_is_tspec, + BOOST_PP_ENUM + , + BOOST_PP_TUPLE_EAT(3) + )(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_f_type, ~) + BOOST_PP_EXPR_IIF(BOOST_PP_EXPAND(BOOST_FUNCTIONAL_is_tspec), >) + // Bases (overloads >= 2 so always at least 2 bases). + : BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, + BOOST_FUNCTIONAL_inherit, ~) +{ +public: + template< + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_g_tparam, ~) + > /* implicit */ inline overloaded_function( + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, + BOOST_FUNCTIONAL_g_arg_decl, ~)) + // Overloads >= 2 so always at least 2 bases to initialize. + : BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, + BOOST_FUNCTIONAL_base_init, ~) + {} + + BOOST_PP_REPEAT(BOOST_FUNCTIONAL_overloads, + BOOST_FUNCTIONAL_using_operator_call, ~) +}; + +template< + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_f_tparam, ~) +> +overloaded_function< + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_function_type, ~) +> make_overloaded_function( + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_f_arg_decl, ~) +) { + return overloaded_function< + BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, + BOOST_FUNCTIONAL_function_type, ~) + >(BOOST_PP_ENUM(BOOST_FUNCTIONAL_overloads, BOOST_FUNCTIONAL_f_arg, ~)); +} + +} // namespace + +// For type-of emulation: Register overloaded function type (for _AUTO, etc). +BOOST_TYPEOF_REGISTER_TEMPLATE(boost::overloaded_function, + BOOST_FUNCTIONAL_overloads) + +# undef BOOST_FUNCTIONAL_overloads +# undef BOOST_FUNCTIONAL_is_tspec +#endif // iteration + +// DOCUMENTATION // + +#else // DOXYGEN + +/** @file +@brief Overload distinct function pointers, function references, and +monomorphic function objects into a single function object. +*/ + +namespace boost { + +/** +@brief Function object to overload functions with distinct signatures. + +This function object aggregates together calls to functions of all the +specified function types F1, F2, etc which must have distinct +function signatures from one another. + +@Params +@Param{Fi, +Each function type must be specified using the following syntax (which is +Boost.Function's preferred syntax): +@code + result_type (argument1_type\, argumgnet2_type\, ...) +@endcode +} +@EndParams + +In some cases, the @RefFunc{make_overloaded_function} function template can be +useful to construct an overloaded function object without explicitly +specifying the function types. + +At least two distinct function types must be specified (because there is +nothing to overload between one or zero functions). +The maximum number of functions to overload is given by the +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX} +configuration macro. +The maximum number of function parameters for each of the specified function +types is given by the +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_ARITY_MAX} +configuration macro. + +@See @RefSect{tutorial, Tutorial} section, @RefFunc{make_overloaded_function}, +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX}, +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_ARITY_MAX}, +Boost.Function. +*/ +template +class overloaded_function { +public: + /** + @brief Construct the overloaded function object. + + Any function pointer, function reference, and monomorphic function object + that can be converted to a boost::function function object can be + specified as parameter. + + @Note Unfortunately, it is not possible to support polymorphic function + objects (as explained here). + */ + overloaded_function(const boost::function&, + const boost::function&, ...); + + /** + @brief Call operator matching the signature of the function type specified + as 1st template parameter. + + This will in turn invoke the call operator of the 1st function passed to + the constructor. + */ + typename boost::function_traits::result_type operator()( + typename boost::function_traits::arg1_type, + typename boost::function_traits::arg2_type, + ...) const; + + /** + @brief Call operator matching the signature of the function type specified + as 2nd template parameter. + + This will in turn invoke the call operator of the 2nd function passed to + the constructor. + + @Note Similar call operators are present for all specified function types + F1, F2, etc (even if not exhaustively listed by this + documentation). + */ + typename boost::function_traits::result_type operator()( + typename boost::function_traits::arg1_type, + typename boost::function_traits::arg2_type, + ...) const; +}; + +/** +@brief Make an overloaded function object without explicitly specifying the +function types. + +This function template creates and returns an @RefClass{overloaded_function} +object that overloads all the specified functions f1, f2, etc. + +The function types are internally determined from the template parameter types +so they do not need to be explicitly specified. +Therefore, this function template usually has a more concise syntax when +compared with @RefClass{overloaded_function}. +This is especially useful when the explicit type of the returned +@RefClass{overloaded_function} object does not need to be known (e.g., when +used with Boost.Typeof's BOOST_AUTO, C++11 auto, or when the +overloaded function object is handled using a function template parameter, see +the @RefSect{tutorial, Tutorial} section). + +The maximum number of functions to overload is given by the +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX} +configuration macro. + +@Note In this documentation, __function_type__ is a placeholder for a +symbol that is specific to the implementation of this library. + +@See @RefSect{tutorial, Tutorial} section, @RefClass{overloaded_function}, +@RefMacro{BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX}. +*/ +template +overloaded_function< + __function_type__, __function_type__, ... +> make_overloaded_function(F1 f1, F2 f2, ...); + +} // namespace + +#endif // DOXYGEN + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/config.hpp b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/config.hpp new file mode 100644 index 0000000..2f5d9e1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/config.hpp @@ -0,0 +1,50 @@ + +// Copyright (C) 2009-2012 Lorenzo Caminiti +// Distributed under the Boost Software License, Version 1.0 +// (see accompanying file LICENSE_1_0.txt or a copy at +// http://www.boost.org/LICENSE_1_0.txt) +// Home at http://www.boost.org/libs/functional/overloaded_function + +#ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_HPP_ +#define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_HPP_ + +/** @file +@brief Change the compile-time configuration of this library. +*/ + +/** +@brief Specify the maximum number of arguments of the functions being +overloaded. + +If this macro is left undefined by the user, it has a default value of 5 +(increasing this number might increase compilation time). +When specified by the user, this macro must be a non-negative integer number. + +@See @RefSect{getting_started, Getting Started}, +@RefClass{boost::overloaded_function}. +*/ +#ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_ARITY_MAX +# define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_ARITY_MAX 5 +#endif + +/** +@brief Specify the maximum number of functions that can be overloaded. + +If this macro is left undefined by the user, it has a default value of 5 +(increasing this number might increase compilation time). +When defined by the user, this macro must be an integer number greater or +equal than 2 (because at least two distinct functions need to be specified in +order to define an overload). + +@See @RefSect{getting_started, Getting Started}, +@RefClass{boost::overloaded_function}. +*/ +#ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX +# define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX 5 +#endif +#if BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_OVERLOAD_MAX < 2 +# error "maximum overload macro cannot be less than 2" +#endif + +#endif // #include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/base.hpp b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/base.hpp new file mode 100644 index 0000000..8fd9a0a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/base.hpp @@ -0,0 +1,86 @@ + +// Copyright (C) 2009-2012 Lorenzo Caminiti +// Distributed under the Boost Software License, Version 1.0 +// (see accompanying file LICENSE_1_0.txt or a copy at +// http://www.boost.org/LICENSE_1_0.txt) +// Home at http://www.boost.org/libs/functional/overloaded_function + +#if !BOOST_PP_IS_ITERATING +# ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_DETAIL_BASE_HPP_ +# define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_DETAIL_BASE_HPP_ + +# include +# include +# include +# include +# include +# include + +#define BOOST_FUNCTIONAL_DETAIL_arg_type(z, n, unused) \ + BOOST_PP_CAT(A, n) + +#define BOOST_FUNCTIONAL_DETAIL_arg_name(z, n, unused) \ + BOOST_PP_CAT(a, n) + +#define BOOST_FUNCTIONAL_DETAIL_arg_tparam(z, n, unused) \ + typename BOOST_FUNCTIONAL_DETAIL_arg_type(z, n, unused) + +#define BOOST_FUNCTIONAL_DETAIL_arg(z, n, unused) \ + BOOST_FUNCTIONAL_DETAIL_arg_type(z, n, unused) \ + BOOST_FUNCTIONAL_DETAIL_arg_name(z, n, unused) + +#define BOOST_FUNCTIONAL_DETAIL_f \ + R (BOOST_PP_ENUM(BOOST_FUNCTIONAL_DETAIL_arity, \ + BOOST_FUNCTIONAL_DETAIL_arg_type, ~)) + +// Do not use namespace ::detail because overloaded_function is already a class. +namespace boost { namespace overloaded_function_detail { + +template +class base {}; // Empty template cannot be used directly (only its spec). + +# define BOOST_PP_ITERATION_PARAMS_1 \ + (3, (0, BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_CONFIG_ARITY_MAX, \ + "boost/functional/overloaded_function/detail/base.hpp")) +# include BOOST_PP_ITERATE() // Iterate over funciton arity. + +} } // namespace + +#undef BOOST_FUNCTIONAL_DETAIL_arg_type +#undef BOOST_FUNCTIONAL_DETAIL_arg_name +#undef BOOST_FUNCTIONAL_DETAIL_arg_tparam +#undef BOOST_FUNCTIONAL_DETAIL_arg +#undef BOOST_FUNCTIONAL_DETAIL_f + +# endif // #include guard + +#elif BOOST_PP_ITERATION_DEPTH() == 1 +# define BOOST_FUNCTIONAL_DETAIL_arity BOOST_PP_FRAME_ITERATION(1) + +template< + typename R + BOOST_PP_COMMA_IF(BOOST_FUNCTIONAL_DETAIL_arity) + BOOST_PP_ENUM(BOOST_FUNCTIONAL_DETAIL_arity, + BOOST_FUNCTIONAL_DETAIL_arg_tparam, ~) +> +class base< BOOST_FUNCTIONAL_DETAIL_f > { +public: + /* implicit */ inline base( + // This requires specified type to be implicitly convertible to + // a boost::function<> functor. + boost::function< BOOST_FUNCTIONAL_DETAIL_f > const& f): f_(f) + {} + + inline R operator()(BOOST_PP_ENUM(BOOST_FUNCTIONAL_DETAIL_arity, + BOOST_FUNCTIONAL_DETAIL_arg, ~)) const { + return f_(BOOST_PP_ENUM(BOOST_FUNCTIONAL_DETAIL_arity, + BOOST_FUNCTIONAL_DETAIL_arg_name, ~)); + } + +private: + boost::function< BOOST_FUNCTIONAL_DETAIL_f > const f_; +}; + +# undef BOOST_FUNCTIONAL_DETAIL_arity +#endif // iteration + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/function_type.hpp b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/function_type.hpp new file mode 100644 index 0000000..0c28607 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/overloaded_function/detail/function_type.hpp @@ -0,0 +1,85 @@ + +// Copyright (C) 2009-2012 Lorenzo Caminiti +// Distributed under the Boost Software License, Version 1.0 +// (see accompanying file LICENSE_1_0.txt or a copy at +// http://www.boost.org/LICENSE_1_0.txt) +// Home at http://www.boost.org/libs/functional/overloaded_function + +#ifndef BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_DETAIL_FUNCTION_TYPE_HPP_ +#define BOOST_FUNCTIONAL_OVERLOADED_FUNCTION_DETAIL_FUNCTION_TYPE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Do not use namespace ::detail because overloaded_function is already a class. +namespace boost { namespace overloaded_function_detail { + +// Requires: F is a monomorphic functor (i.e., has non-template `operator()`). +// Returns: F's function type `result_type (arg1_type, arg2_type, ...)`. +// It does not assume F typedef result_type, arg1_type, ... but needs typeof. +template +class functor_type { + // NOTE: clang does not accept extra parenthesis `&(...)`. + typedef BOOST_TYPEOF_TPL(&F::operator()) call_ptr; +public: + typedef + typename boost::function_types::function_type< + typename boost::mpl::push_front< + typename boost::mpl::pop_front< // Remove functor type (1st). + typename boost::function_types::parameter_types< + call_ptr>::type + >::type + , typename boost::function_types::result_type::type + >::type + >::type + type; +}; + +// NOTE: When using boost::function in Boost.Typeof emulation mode, the user +// has to register boost::functionN instead of boost::function in oder to +// do TYPEOF(F::operator()). That is confusing, so boost::function is handled +// separately so it does not require any Boost.Typeof registration at all. +template +struct functor_type< boost::function > { + typedef F type; +}; + +// Requires: F is a function type, pointer, reference, or monomorphic functor. +// Returns: F's function type `result_type (arg1_type, arg2_type, ...)`. +template +struct function_type { + typedef + typename boost::mpl::if_, + boost::mpl::identity + , + typename boost::mpl::if_, + boost::remove_pointer + , + typename boost::mpl::if_, + boost::remove_reference + , // Else, requires that F is a functor. + functor_type + >::type + >::type + >::type + ::type type; +}; + +} } // namespace + +#endif // #include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/functional/value_factory.hpp b/thirdparty/source/boost_1_61_0/boost/functional/value_factory.hpp new file mode 100644 index 0000000..ba94c2a --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/functional/value_factory.hpp @@ -0,0 +1,69 @@ +/*============================================================================= + Copyright (c) 2007 Tobias Schwinger + + Use modification and distribution are subject to the Boost Software + License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at + http://www.boost.org/LICENSE_1_0.txt). +==============================================================================*/ + +#ifndef BOOST_FUNCTIONAL_VALUE_FACTORY_HPP_INCLUDED +# ifndef BOOST_PP_IS_ITERATING + +# include +# include +# include + +# include +# include +# include +# include +# include + +# ifndef BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY +# define BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY 10 +# elif BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY < 3 +# undef BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY +# define BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY 3 +# endif + +namespace boost +{ + template< typename T > + class value_factory; + + //----- ---- --- -- - - - - + + template< typename T > + class value_factory + { + public: + typedef T result_type; + + value_factory() + { } + +# define BOOST_PP_FILENAME_1 +# define BOOST_PP_ITERATION_LIMITS (0,BOOST_FUNCTIONAL_VALUE_FACTORY_MAX_ARITY) +# include BOOST_PP_ITERATE() + }; + + template< typename T > class value_factory; + // forbidden, would create a dangling reference +} +# define BOOST_FUNCTIONAL_VALUE_FACTORY_HPP_INCLUDED +# else // defined(BOOST_PP_IS_ITERATING) + +# define N BOOST_PP_ITERATION() +# if N > 0 + template< BOOST_PP_ENUM_PARAMS(N, typename T) > +# endif + inline result_type operator()(BOOST_PP_ENUM_BINARY_PARAMS(N,T,& a)) const + { + return result_type(BOOST_PP_ENUM_PARAMS(N,a)); + } +# undef N + +# endif // defined(BOOST_PP_IS_ITERATING) + +#endif // include guard + diff --git a/thirdparty/source/boost_1_61_0/boost/generator_iterator.hpp b/thirdparty/source/boost_1_61_0/boost/generator_iterator.hpp new file mode 100644 index 0000000..0fe1569 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/generator_iterator.hpp @@ -0,0 +1,85 @@ +// (C) Copyright Jens Maurer 2001. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Revision History: + +// 15 Nov 2001 Jens Maurer +// created. + +// See http://www.boost.org/libs/utility/iterator_adaptors.htm for documentation. + +#ifndef BOOST_ITERATOR_ADAPTOR_GENERATOR_ITERATOR_HPP +#define BOOST_ITERATOR_ADAPTOR_GENERATOR_ITERATOR_HPP + +#include +#include + +namespace boost { +namespace iterators { + +template +class generator_iterator + : public iterator_facade< + generator_iterator + , typename Generator::result_type + , single_pass_traversal_tag + , typename Generator::result_type const& + > +{ + typedef iterator_facade< + generator_iterator + , typename Generator::result_type + , single_pass_traversal_tag + , typename Generator::result_type const& + > super_t; + + public: + generator_iterator() {} + generator_iterator(Generator* g) : m_g(g), m_value((*m_g)()) {} + + void increment() + { + m_value = (*m_g)(); + } + + const typename Generator::result_type& + dereference() const + { + return m_value; + } + + bool equal(generator_iterator const& y) const + { + return this->m_g == y.m_g && this->m_value == y.m_value; + } + + private: + Generator* m_g; + typename Generator::result_type m_value; +}; + +template +struct generator_iterator_generator +{ + typedef generator_iterator type; +}; + +template +inline generator_iterator +make_generator_iterator(Generator & gen) +{ + typedef generator_iterator result_t; + return result_t(&gen); +} + +} // namespace iterators + +using iterators::generator_iterator; +using iterators::generator_iterator_generator; +using iterators::make_generator_iterator; + +} // namespace boost + +#endif // BOOST_ITERATOR_ADAPTOR_GENERATOR_ITERATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/get_pointer.hpp b/thirdparty/source/boost_1_61_0/boost/get_pointer.hpp new file mode 100644 index 0000000..36e2cd7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/get_pointer.hpp @@ -0,0 +1,76 @@ +// Copyright Peter Dimov and David Abrahams 2002. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +#ifndef GET_POINTER_DWA20021219_HPP +#define GET_POINTER_DWA20021219_HPP + +#include + +// In order to avoid circular dependencies with Boost.TR1 +// we make sure that our include of doesn't try to +// pull in the TR1 headers: that's why we use this header +// rather than including directly: +#include // std::auto_ptr + +namespace boost { + +// get_pointer(p) extracts a ->* capable pointer from p + +template T * get_pointer(T * p) +{ + return p; +} + +// get_pointer(shared_ptr const & p) has been moved to shared_ptr.hpp + +#if !defined( BOOST_NO_AUTO_PTR ) + +#if defined( __GNUC__ ) && (defined( __GXX_EXPERIMENTAL_CXX0X__ ) || (__cplusplus >= 201103L)) +#if defined( BOOST_GCC ) +#if BOOST_GCC >= 40600 +#define BOOST_CORE_DETAIL_DISABLE_LIBSTDCXX_DEPRECATED_WARNINGS +#endif // BOOST_GCC >= 40600 +#elif defined( __clang__ ) && defined( __has_warning ) +#if __has_warning("-Wdeprecated-declarations") +#define BOOST_CORE_DETAIL_DISABLE_LIBSTDCXX_DEPRECATED_WARNINGS +#endif // __has_warning("-Wdeprecated-declarations") +#endif +#endif // defined( __GNUC__ ) && (defined( __GXX_EXPERIMENTAL_CXX0X__ ) || (__cplusplus >= 201103L)) + +#if defined( BOOST_CORE_DETAIL_DISABLE_LIBSTDCXX_DEPRECATED_WARNINGS ) +// Disable libstdc++ warnings about std::auto_ptr being deprecated in C++11 mode +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#define BOOST_CORE_DETAIL_DISABLED_DEPRECATED_WARNINGS +#endif + +template T * get_pointer(std::auto_ptr const& p) +{ + return p.get(); +} + +#if defined( BOOST_CORE_DETAIL_DISABLE_LIBSTDCXX_DEPRECATED_WARNINGS ) +#pragma GCC diagnostic pop +#undef BOOST_CORE_DETAIL_DISABLE_LIBSTDCXX_DEPRECATED_WARNINGS +#endif + +#endif // !defined( BOOST_NO_AUTO_PTR ) + +#if !defined( BOOST_NO_CXX11_SMART_PTR ) + +template T * get_pointer( std::unique_ptr const& p ) +{ + return p.get(); +} + +template T * get_pointer( std::shared_ptr const& p ) +{ + return p.get(); +} + +#endif + +} // namespace boost + +#endif // GET_POINTER_DWA20021219_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/integer.hpp b/thirdparty/source/boost_1_61_0/boost/integer.hpp new file mode 100644 index 0000000..9fa0019 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer.hpp @@ -0,0 +1,262 @@ +// boost integer.hpp header file -------------------------------------------// + +// Copyright Beman Dawes and Daryle Walker 1999. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/integer for documentation. + +// Revision History +// 22 Sep 01 Added value-based integer templates. (Daryle Walker) +// 01 Apr 01 Modified to use new header. (John Maddock) +// 30 Jul 00 Add typename syntax fix (Jens Maurer) +// 28 Aug 99 Initial version + +#ifndef BOOST_INTEGER_HPP +#define BOOST_INTEGER_HPP + +#include // self include + +#include // for boost::::boost::integer_traits +#include // for ::std::numeric_limits +#include // for boost::int64_t and BOOST_NO_INTEGRAL_INT64_T +#include + +// +// We simply cannot include this header on gcc without getting copious warnings of the kind: +// +// boost/integer.hpp:77:30: warning: use of C99 long long integer constant +// +// And yet there is no other reasonable implementation, so we declare this a system header +// to suppress these warnings. +// +#if defined(__GNUC__) && (__GNUC__ >= 4) +#pragma GCC system_header +#endif + +namespace boost +{ + + // Helper templates ------------------------------------------------------// + + // fast integers from least integers + // int_fast_t<> works correctly for unsigned too, in spite of the name. + template< typename LeastInt > + struct int_fast_t + { + typedef LeastInt fast; + typedef fast type; + }; // imps may specialize + + namespace detail{ + + // convert category to type + template< int Category > struct int_least_helper {}; // default is empty + template< int Category > struct uint_least_helper {}; // default is empty + + // specializatons: 1=long, 2=int, 3=short, 4=signed char, + // 6=unsigned long, 7=unsigned int, 8=unsigned short, 9=unsigned char + // no specializations for 0 and 5: requests for a type > long are in error +#ifdef BOOST_HAS_LONG_LONG + template<> struct int_least_helper<1> { typedef boost::long_long_type least; }; +#elif defined(BOOST_HAS_MS_INT64) + template<> struct int_least_helper<1> { typedef __int64 least; }; +#endif + template<> struct int_least_helper<2> { typedef long least; }; + template<> struct int_least_helper<3> { typedef int least; }; + template<> struct int_least_helper<4> { typedef short least; }; + template<> struct int_least_helper<5> { typedef signed char least; }; +#ifdef BOOST_HAS_LONG_LONG + template<> struct uint_least_helper<1> { typedef boost::ulong_long_type least; }; +#elif defined(BOOST_HAS_MS_INT64) + template<> struct uint_least_helper<1> { typedef unsigned __int64 least; }; +#endif + template<> struct uint_least_helper<2> { typedef unsigned long least; }; + template<> struct uint_least_helper<3> { typedef unsigned int least; }; + template<> struct uint_least_helper<4> { typedef unsigned short least; }; + template<> struct uint_least_helper<5> { typedef unsigned char least; }; + + template + struct exact_signed_base_helper{}; + template + struct exact_unsigned_base_helper{}; + + template <> struct exact_signed_base_helper { typedef signed char exact; }; + template <> struct exact_unsigned_base_helper { typedef unsigned char exact; }; +#if USHRT_MAX != UCHAR_MAX + template <> struct exact_signed_base_helper { typedef short exact; }; + template <> struct exact_unsigned_base_helper { typedef unsigned short exact; }; +#endif +#if UINT_MAX != USHRT_MAX + template <> struct exact_signed_base_helper { typedef int exact; }; + template <> struct exact_unsigned_base_helper { typedef unsigned int exact; }; +#endif +#if ULONG_MAX != UINT_MAX && ( !defined __TI_COMPILER_VERSION__ || \ + ( __TI_COMPILER_VERSION__ >= 7000000 && !defined __TI_40BIT_LONG__ ) ) + template <> struct exact_signed_base_helper { typedef long exact; }; + template <> struct exact_unsigned_base_helper { typedef unsigned long exact; }; +#endif +#if defined(BOOST_HAS_LONG_LONG) &&\ + ((defined(ULLONG_MAX) && (ULLONG_MAX != ULONG_MAX)) ||\ + (defined(ULONG_LONG_MAX) && (ULONG_LONG_MAX != ULONG_MAX)) ||\ + (defined(ULONGLONG_MAX) && (ULONGLONG_MAX != ULONG_MAX)) ||\ + (defined(_ULLONG_MAX) && (_ULLONG_MAX != ULONG_MAX))) + template <> struct exact_signed_base_helper { typedef boost::long_long_type exact; }; + template <> struct exact_unsigned_base_helper { typedef boost::ulong_long_type exact; }; +#endif + + + } // namespace detail + + // integer templates specifying number of bits ---------------------------// + + // signed + template< int Bits > // bits (including sign) required + struct int_t : public boost::detail::exact_signed_base_helper + { + BOOST_STATIC_ASSERT_MSG(Bits <= (int)(sizeof(boost::intmax_t) * CHAR_BIT), + "No suitable signed integer type with the requested number of bits is available."); + typedef typename boost::detail::int_least_helper + < +#ifdef BOOST_HAS_LONG_LONG + (Bits <= (int)(sizeof(boost::long_long_type) * CHAR_BIT)) + +#else + 1 + +#endif + (Bits-1 <= ::std::numeric_limits::digits) + + (Bits-1 <= ::std::numeric_limits::digits) + + (Bits-1 <= ::std::numeric_limits::digits) + + (Bits-1 <= ::std::numeric_limits::digits) + >::least least; + typedef typename int_fast_t::type fast; + }; + + // unsigned + template< int Bits > // bits required + struct uint_t : public boost::detail::exact_unsigned_base_helper + { + BOOST_STATIC_ASSERT_MSG(Bits <= (int)(sizeof(boost::uintmax_t) * CHAR_BIT), + "No suitable unsigned integer type with the requested number of bits is available."); +#if (defined(__BORLANDC__) || defined(__CODEGEAR__)) && defined(BOOST_NO_INTEGRAL_INT64_T) + // It's really not clear why this workaround should be needed... shrug I guess! JM + BOOST_STATIC_CONSTANT(int, s = + 6 + + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits)); + typedef typename detail::int_least_helper< ::boost::uint_t::s>::least least; +#else + typedef typename boost::detail::uint_least_helper + < +#ifdef BOOST_HAS_LONG_LONG + (Bits <= (int)(sizeof(boost::long_long_type) * CHAR_BIT)) + +#else + 1 + +#endif + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits) + + (Bits <= ::std::numeric_limits::digits) + >::least least; +#endif + typedef typename int_fast_t::type fast; + // int_fast_t<> works correctly for unsigned too, in spite of the name. + }; + + // integer templates specifying extreme value ----------------------------// + + // signed +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::long_long_type MaxValue > // maximum value to require support +#else + template< long MaxValue > // maximum value to require support +#endif + struct int_max_value_t + { + typedef typename boost::detail::int_least_helper + < +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_LONG_LONG) + (MaxValue <= ::boost::integer_traits::const_max) + +#else + 1 + +#endif + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + >::least least; + typedef typename int_fast_t::type fast; + }; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::long_long_type MinValue > // minimum value to require support +#else + template< long MinValue > // minimum value to require support +#endif + struct int_min_value_t + { + typedef typename boost::detail::int_least_helper + < +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_LONG_LONG) + (MinValue >= ::boost::integer_traits::const_min) + +#else + 1 + +#endif + (MinValue >= ::boost::integer_traits::const_min) + + (MinValue >= ::boost::integer_traits::const_min) + + (MinValue >= ::boost::integer_traits::const_min) + + (MinValue >= ::boost::integer_traits::const_min) + >::least least; + typedef typename int_fast_t::type fast; + }; + + // unsigned +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::ulong_long_type MaxValue > // minimum value to require support +#else + template< unsigned long MaxValue > // minimum value to require support +#endif + struct uint_value_t + { +#if (defined(__BORLANDC__) || defined(__CODEGEAR__)) + // It's really not clear why this workaround should be needed... shrug I guess! JM +#if defined(BOOST_NO_INTEGRAL_INT64_T) + BOOST_STATIC_CONSTANT(unsigned, which = + 1 + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max)); + typedef typename detail::int_least_helper< ::boost::uint_value_t::which>::least least; +#else // BOOST_NO_INTEGRAL_INT64_T + BOOST_STATIC_CONSTANT(unsigned, which = + 1 + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max)); + typedef typename detail::uint_least_helper< ::boost::uint_value_t::which>::least least; +#endif // BOOST_NO_INTEGRAL_INT64_T +#else + typedef typename boost::detail::uint_least_helper + < +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && defined(BOOST_HAS_LONG_LONG) + (MaxValue <= ::boost::integer_traits::const_max) + +#else + 1 + +#endif + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + + (MaxValue <= ::boost::integer_traits::const_max) + >::least least; +#endif + typedef typename int_fast_t::type fast; + }; + + +} // namespace boost + +#endif // BOOST_INTEGER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/integer/common_factor_rt.hpp b/thirdparty/source/boost_1_61_0/boost/integer/common_factor_rt.hpp new file mode 100644 index 0000000..c2b54db --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer/common_factor_rt.hpp @@ -0,0 +1,460 @@ +// Boost common_factor_rt.hpp header file ----------------------------------// + +// (C) Copyright Daryle Walker and Paul Moore 2001-2002. Permission to copy, +// use, modify, sell and distribute this software is granted provided this +// copyright notice appears in all copies. This software is provided "as is" +// without express or implied warranty, and with no claim as to its suitability +// for any purpose. + +// boostinspect:nolicense (don't complain about the lack of a Boost license) +// (Paul Moore hasn't been in contact for years, so there's no way to change the +// license.) + +// See http://www.boost.org for updates, documentation, and revision history. + +#ifndef BOOST_INTEGER_COMMON_FACTOR_RT_HPP +#define BOOST_INTEGER_COMMON_FACTOR_RT_HPP + +#include // self include + +#include // for BOOST_NESTED_TEMPLATE, etc. +#include // for std::numeric_limits +#include // for CHAR_MIN +#include + +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable:4127 4244) // Conditional expression is constant +#endif + +namespace boost +{ +namespace integer +{ + + +// Forward declarations for function templates -----------------------------// + +template < typename IntegerType > + IntegerType gcd( IntegerType const &a, IntegerType const &b ); + +template < typename IntegerType > + IntegerType lcm( IntegerType const &a, IntegerType const &b ); + + +// Greatest common divisor evaluator class declaration ---------------------// + +template < typename IntegerType > +class gcd_evaluator +{ +public: + // Types + typedef IntegerType result_type, first_argument_type, second_argument_type; + + // Function object interface + result_type operator ()( first_argument_type const &a, + second_argument_type const &b ) const; + +}; // boost::integer::gcd_evaluator + + +// Least common multiple evaluator class declaration -----------------------// + +template < typename IntegerType > +class lcm_evaluator +{ +public: + // Types + typedef IntegerType result_type, first_argument_type, second_argument_type; + + // Function object interface + result_type operator ()( first_argument_type const &a, + second_argument_type const &b ) const; + +}; // boost::integer::lcm_evaluator + + +// Implementation details --------------------------------------------------// + +namespace detail +{ + // Greatest common divisor for rings (including unsigned integers) + template < typename RingType > + RingType + gcd_euclidean + ( + RingType a, + RingType b + ) + { + // Avoid repeated construction + #ifndef __BORLANDC__ + RingType const zero = static_cast( 0 ); + #else + RingType zero = static_cast( 0 ); + #endif + + // Reduce by GCD-remainder property [GCD(a,b) == GCD(b,a MOD b)] + while ( true ) + { + if ( a == zero ) + return b; + b %= a; + + if ( b == zero ) + return a; + a %= b; + } + } + + // Greatest common divisor for (signed) integers + template < typename IntegerType > + inline + IntegerType + gcd_integer + ( + IntegerType const & a, + IntegerType const & b + ) + { + // Avoid repeated construction + IntegerType const zero = static_cast( 0 ); + IntegerType const result = gcd_euclidean( a, b ); + + return ( result < zero ) ? static_cast(-result) : result; + } + + // Greatest common divisor for unsigned binary integers + template < typename BuiltInUnsigned > + BuiltInUnsigned + gcd_binary + ( + BuiltInUnsigned u, + BuiltInUnsigned v + ) + { + if ( u && v ) + { + // Shift out common factors of 2 + unsigned shifts = 0; + + while ( !(u & 1u) && !(v & 1u) ) + { + ++shifts; + u >>= 1; + v >>= 1; + } + + // Start with the still-even one, if any + BuiltInUnsigned r[] = { u, v }; + unsigned which = static_cast( u & 1u ); + + // Whittle down the values via their differences + do + { +#if BOOST_WORKAROUND(__BORLANDC__, BOOST_TESTED_AT(0x582)) + while ( !(r[ which ] & 1u) ) + { + r[ which ] = (r[which] >> 1); + } +#else + // Remove factors of two from the even one + while ( !(r[ which ] & 1u) ) + { + r[ which ] >>= 1; + } +#endif + + // Replace the larger of the two with their difference + if ( r[!which] > r[which] ) + { + which ^= 1u; + } + + r[ which ] -= r[ !which ]; + } + while ( r[which] ); + + // Shift-in the common factor of 2 to the residues' GCD + return r[ !which ] << shifts; + } + else + { + // At least one input is zero, return the other + // (adding since zero is the additive identity) + // or zero if both are zero. + return u + v; + } + } + + // Least common multiple for rings (including unsigned integers) + template < typename RingType > + inline + RingType + lcm_euclidean + ( + RingType const & a, + RingType const & b + ) + { + RingType const zero = static_cast( 0 ); + RingType const temp = gcd_euclidean( a, b ); + + return ( temp != zero ) ? ( a / temp * b ) : zero; + } + + // Least common multiple for (signed) integers + template < typename IntegerType > + inline + IntegerType + lcm_integer + ( + IntegerType const & a, + IntegerType const & b + ) + { + // Avoid repeated construction + IntegerType const zero = static_cast( 0 ); + IntegerType const result = lcm_euclidean( a, b ); + + return ( result < zero ) ? static_cast(-result) : result; + } + + // Function objects to find the best way of computing GCD or LCM +#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + template < typename T, bool IsSpecialized, bool IsSigned > + struct gcd_optimal_evaluator_helper_t + { + T operator ()( T const &a, T const &b ) + { + return gcd_euclidean( a, b ); + } + }; + + template < typename T > + struct gcd_optimal_evaluator_helper_t< T, true, true > + { + T operator ()( T const &a, T const &b ) + { + return gcd_integer( a, b ); + } + }; + + template < typename T > + struct gcd_optimal_evaluator + { + T operator ()( T const &a, T const &b ) + { + typedef ::std::numeric_limits limits_type; + + typedef gcd_optimal_evaluator_helper_t helper_type; + + helper_type solver; + + return solver( a, b ); + } + }; +#else // BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + template < typename T > + struct gcd_optimal_evaluator + { + T operator ()( T const &a, T const &b ) + { + return gcd_integer( a, b ); + } + }; +#endif + + // Specialize for the built-in integers +#define BOOST_PRIVATE_GCD_UF( Ut ) \ + template < > struct gcd_optimal_evaluator \ + { Ut operator ()( Ut a, Ut b ) const { return gcd_binary( a, b ); } } + + BOOST_PRIVATE_GCD_UF( unsigned char ); + BOOST_PRIVATE_GCD_UF( unsigned short ); + BOOST_PRIVATE_GCD_UF( unsigned ); + BOOST_PRIVATE_GCD_UF( unsigned long ); + +#ifdef BOOST_HAS_LONG_LONG + BOOST_PRIVATE_GCD_UF( boost::ulong_long_type ); +#elif defined(BOOST_HAS_MS_INT64) + BOOST_PRIVATE_GCD_UF( unsigned __int64 ); +#endif + +#if CHAR_MIN == 0 + BOOST_PRIVATE_GCD_UF( char ); // char is unsigned +#endif + +#undef BOOST_PRIVATE_GCD_UF + +#define BOOST_PRIVATE_GCD_SF( St, Ut ) \ + template < > struct gcd_optimal_evaluator \ + { St operator ()( St a, St b ) const { Ut const a_abs = \ + static_cast( a < 0 ? -a : +a ), b_abs = static_cast( \ + b < 0 ? -b : +b ); return static_cast( \ + gcd_optimal_evaluator()(a_abs, b_abs) ); } } + + BOOST_PRIVATE_GCD_SF( signed char, unsigned char ); + BOOST_PRIVATE_GCD_SF( short, unsigned short ); + BOOST_PRIVATE_GCD_SF( int, unsigned ); + BOOST_PRIVATE_GCD_SF( long, unsigned long ); + +#if CHAR_MIN < 0 + BOOST_PRIVATE_GCD_SF( char, unsigned char ); // char is signed +#endif + +#ifdef BOOST_HAS_LONG_LONG + BOOST_PRIVATE_GCD_SF( boost::long_long_type, boost::ulong_long_type ); +#elif defined(BOOST_HAS_MS_INT64) + BOOST_PRIVATE_GCD_SF( __int64, unsigned __int64 ); +#endif + +#undef BOOST_PRIVATE_GCD_SF + +#ifndef BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + template < typename T, bool IsSpecialized, bool IsSigned > + struct lcm_optimal_evaluator_helper_t + { + T operator ()( T const &a, T const &b ) + { + return lcm_euclidean( a, b ); + } + }; + + template < typename T > + struct lcm_optimal_evaluator_helper_t< T, true, true > + { + T operator ()( T const &a, T const &b ) + { + return lcm_integer( a, b ); + } + }; + + template < typename T > + struct lcm_optimal_evaluator + { + T operator ()( T const &a, T const &b ) + { + typedef ::std::numeric_limits limits_type; + + typedef lcm_optimal_evaluator_helper_t helper_type; + + helper_type solver; + + return solver( a, b ); + } + }; +#else // BOOST_NO_LIMITS_COMPILE_TIME_CONSTANTS + template < typename T > + struct lcm_optimal_evaluator + { + T operator ()( T const &a, T const &b ) + { + return lcm_integer( a, b ); + } + }; +#endif + + // Functions to find the GCD or LCM in the best way + template < typename T > + inline + T + gcd_optimal + ( + T const & a, + T const & b + ) + { + gcd_optimal_evaluator solver; + + return solver( a, b ); + } + + template < typename T > + inline + T + lcm_optimal + ( + T const & a, + T const & b + ) + { + lcm_optimal_evaluator solver; + + return solver( a, b ); + } + +} // namespace detail + + +// Greatest common divisor evaluator member function definition ------------// + +template < typename IntegerType > +inline +typename gcd_evaluator::result_type +gcd_evaluator::operator () +( + first_argument_type const & a, + second_argument_type const & b +) const +{ + return detail::gcd_optimal( a, b ); +} + + +// Least common multiple evaluator member function definition --------------// + +template < typename IntegerType > +inline +typename lcm_evaluator::result_type +lcm_evaluator::operator () +( + first_argument_type const & a, + second_argument_type const & b +) const +{ + return detail::lcm_optimal( a, b ); +} + + +// Greatest common divisor and least common multiple function definitions --// + +template < typename IntegerType > +inline +IntegerType +gcd +( + IntegerType const & a, + IntegerType const & b +) +{ + gcd_evaluator solver; + + return solver( a, b ); +} + +template < typename IntegerType > +inline +IntegerType +lcm +( + IntegerType const & a, + IntegerType const & b +) +{ + lcm_evaluator solver; + + return solver( a, b ); +} + + +} // namespace integer +} // namespace boost + +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + +#endif // BOOST_INTEGER_COMMON_FACTOR_RT_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/integer/integer_log2.hpp b/thirdparty/source/boost_1_61_0/boost/integer/integer_log2.hpp new file mode 100644 index 0000000..8b34ce7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer/integer_log2.hpp @@ -0,0 +1,112 @@ +// ----------------------------------------------------------- +// integer_log2.hpp +// +// Gives the integer part of the logarithm, in base 2, of a +// given number. Behavior is undefined if the argument is <= 0. +// +// Copyright (c) 2003-2004, 2008 Gennaro Prota +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// ----------------------------------------------------------- + +#ifndef BOOST_INTEGER_INTEGER_LOG2_HPP +#define BOOST_INTEGER_INTEGER_LOG2_HPP + +#include +#ifdef __BORLANDC__ +#include +#endif +#include +#include + + +namespace boost { + namespace detail { + + template + int integer_log2_impl(T x, int n) { + + int result = 0; + + while (x != 1) { + + const T t = static_cast(x >> n); + if (t) { + result += n; + x = t; + } + n /= 2; + + } + + return result; + } + + + + // helper to find the maximum power of two + // less than p (more involved than necessary, + // to avoid PTS) + // + template + struct max_pow2_less { + + enum { c = 2*n < p }; + + BOOST_STATIC_CONSTANT(int, value = + c ? (max_pow2_less< c*p, 2*c*n>::value) : n); + + }; + + template <> + struct max_pow2_less<0, 0> { + + BOOST_STATIC_CONSTANT(int, value = 0); + }; + + // this template is here just for Borland :( + // we could simply rely on numeric_limits but sometimes + // Borland tries to use numeric_limits, because + // of its usual const-related problems in argument deduction + // - gps + template + struct width { + +#ifdef __BORLANDC__ + BOOST_STATIC_CONSTANT(int, value = sizeof(T) * CHAR_BIT); +#else + BOOST_STATIC_CONSTANT(int, value = (std::numeric_limits::digits)); +#endif + + }; + + } // detail + + + // --------- + // integer_log2 + // --------------- + // + template + int integer_log2(T x) { + + assert(x > 0); + + const int n = detail::max_pow2_less< + detail::width :: value, 4 + > :: value; + + return detail::integer_log2_impl(x, n); + + } + + + +} + + + +#endif // include guard diff --git a/thirdparty/source/boost_1_61_0/boost/integer/integer_mask.hpp b/thirdparty/source/boost_1_61_0/boost/integer/integer_mask.hpp new file mode 100644 index 0000000..2acf7f7 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer/integer_mask.hpp @@ -0,0 +1,126 @@ +// Boost integer/integer_mask.hpp header file ------------------------------// + +// (C) Copyright Daryle Walker 2001. +// Distributed under the Boost Software License, Version 1.0. (See +// accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org for updates, documentation, and revision history. + +#ifndef BOOST_INTEGER_INTEGER_MASK_HPP +#define BOOST_INTEGER_INTEGER_MASK_HPP + +#include // self include + +#include // for BOOST_STATIC_CONSTANT +#include // for boost::uint_t + +#include // for UCHAR_MAX, etc. +#include // for std::size_t + +#include // for std::numeric_limits + +// +// We simply cannot include this header on gcc without getting copious warnings of the kind: +// +// boost/integer/integer_mask.hpp:93:35: warning: use of C99 long long integer constant +// +// And yet there is no other reasonable implementation, so we declare this a system header +// to suppress these warnings. +// +#if defined(__GNUC__) && (__GNUC__ >= 4) +#pragma GCC system_header +#endif + +namespace boost +{ + + +// Specified single-bit mask class declaration -----------------------------// +// (Lowest bit starts counting at 0.) + +template < std::size_t Bit > +struct high_bit_mask_t +{ + typedef typename uint_t<(Bit + 1)>::least least; + typedef typename uint_t<(Bit + 1)>::fast fast; + + BOOST_STATIC_CONSTANT( least, high_bit = (least( 1u ) << Bit) ); + BOOST_STATIC_CONSTANT( fast, high_bit_fast = (fast( 1u ) << Bit) ); + + BOOST_STATIC_CONSTANT( std::size_t, bit_position = Bit ); + +}; // boost::high_bit_mask_t + + +// Specified bit-block mask class declaration ------------------------------// +// Makes masks for the lowest N bits +// (Specializations are needed when N fills up a type.) + +template < std::size_t Bits > +struct low_bits_mask_t +{ + typedef typename uint_t::least least; + typedef typename uint_t::fast fast; + + BOOST_STATIC_CONSTANT( least, sig_bits = (~( ~(least( 0u )) << Bits )) ); + BOOST_STATIC_CONSTANT( fast, sig_bits_fast = fast(sig_bits) ); + + BOOST_STATIC_CONSTANT( std::size_t, bit_count = Bits ); + +}; // boost::low_bits_mask_t + + +#define BOOST_LOW_BITS_MASK_SPECIALIZE( Type ) \ + template < > struct low_bits_mask_t< std::numeric_limits::digits > { \ + typedef std::numeric_limits limits_type; \ + typedef uint_t::least least; \ + typedef uint_t::fast fast; \ + BOOST_STATIC_CONSTANT( least, sig_bits = (~( least(0u) )) ); \ + BOOST_STATIC_CONSTANT( fast, sig_bits_fast = fast(sig_bits) ); \ + BOOST_STATIC_CONSTANT( std::size_t, bit_count = limits_type::digits ); \ + } + +#ifdef BOOST_MSVC +#pragma warning(push) +#pragma warning(disable:4245) // 'initializing' : conversion from 'int' to 'const boost::low_bits_mask_t<8>::least', signed/unsigned mismatch +#endif + +BOOST_LOW_BITS_MASK_SPECIALIZE( unsigned char ); + +#if USHRT_MAX > UCHAR_MAX +BOOST_LOW_BITS_MASK_SPECIALIZE( unsigned short ); +#endif + +#if UINT_MAX > USHRT_MAX +BOOST_LOW_BITS_MASK_SPECIALIZE( unsigned int ); +#endif + +#if ULONG_MAX > UINT_MAX +BOOST_LOW_BITS_MASK_SPECIALIZE( unsigned long ); +#endif + +#if defined(BOOST_HAS_LONG_LONG) + #if ((defined(ULLONG_MAX) && (ULLONG_MAX > ULONG_MAX)) ||\ + (defined(ULONG_LONG_MAX) && (ULONG_LONG_MAX > ULONG_MAX)) ||\ + (defined(ULONGLONG_MAX) && (ULONGLONG_MAX > ULONG_MAX)) ||\ + (defined(_ULLONG_MAX) && (_ULLONG_MAX > ULONG_MAX))) + BOOST_LOW_BITS_MASK_SPECIALIZE( boost::ulong_long_type ); + #endif +#elif defined(BOOST_HAS_MS_INT64) + #if 18446744073709551615ui64 > ULONG_MAX + BOOST_LOW_BITS_MASK_SPECIALIZE( unsigned __int64 ); + #endif +#endif + +#ifdef BOOST_MSVC +#pragma warning(pop) +#endif + +#undef BOOST_LOW_BITS_MASK_SPECIALIZE + + +} // namespace boost + + +#endif // BOOST_INTEGER_INTEGER_MASK_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/integer/static_log2.hpp b/thirdparty/source/boost_1_61_0/boost/integer/static_log2.hpp new file mode 100644 index 0000000..56c7a00 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer/static_log2.hpp @@ -0,0 +1,127 @@ +// -------------- Boost static_log2.hpp header file ----------------------- // +// +// Copyright (C) 2001 Daryle Walker. +// Copyright (C) 2003 Vesa Karvonen. +// Copyright (C) 2003 Gennaro Prota. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// --------------------------------------------------- +// See http://www.boost.org/libs/integer for documentation. +// ------------------------------------------------------------------------- // + + +#ifndef BOOST_INTEGER_STATIC_LOG2_HPP +#define BOOST_INTEGER_STATIC_LOG2_HPP + +#include "boost/integer_fwd.hpp" // for boost::intmax_t + +namespace boost { + + namespace detail { + + namespace static_log2_impl { + + // choose_initial_n<> + // + // Recursively doubles its integer argument, until it + // becomes >= of the "width" (C99, 6.2.6.2p4) of + // static_log2_argument_type. + // + // Used to get the maximum power of two less then the width. + // + // Example: if on your platform argument_type has 48 value + // bits it yields n=32. + // + // It's easy to prove that, starting from such a value + // of n, the core algorithm works correctly for any width + // of static_log2_argument_type and that recursion always + // terminates with x = 1 and n = 0 (see the algorithm's + // invariant). + + typedef boost::static_log2_argument_type argument_type; + typedef boost::static_log2_result_type result_type; + + template + struct choose_initial_n { + + BOOST_STATIC_CONSTANT(bool, c = (argument_type(1) << n << n) != 0); + BOOST_STATIC_CONSTANT( + result_type, + value = !c*n + choose_initial_n<2*c*n>::value + ); + + }; + + template <> + struct choose_initial_n<0> { + BOOST_STATIC_CONSTANT(result_type, value = 0); + }; + + + + // start computing from n_zero - must be a power of two + const result_type n_zero = 16; + const result_type initial_n = choose_initial_n::value; + + // static_log2_impl<> + // + // * Invariant: + // 2n + // 1 <= x && x < 2 at the start of each recursion + // (see also choose_initial_n<>) + // + // * Type requirements: + // + // argument_type maybe any unsigned type with at least n_zero + 1 + // value bits. (Note: If larger types will be standardized -e.g. + // unsigned long long- then the argument_type typedef can be + // changed without affecting the rest of the code.) + // + + template + struct static_log2_impl { + + BOOST_STATIC_CONSTANT(bool, c = (x >> n) > 0); // x >= 2**n ? + BOOST_STATIC_CONSTANT( + result_type, + value = c*n + (static_log2_impl< (x>>c*n), n/2 >::value) + ); + + }; + + template <> + struct static_log2_impl<1, 0> { + BOOST_STATIC_CONSTANT(result_type, value = 0); + }; + + } + } // detail + + + + // -------------------------------------- + // static_log2 + // ---------------------------------------- + + template + struct static_log2 { + + BOOST_STATIC_CONSTANT( + static_log2_result_type, + value = detail::static_log2_impl::static_log2_impl::value + ); + + }; + + + template <> + struct static_log2<0> { }; + +} + + + +#endif // include guard diff --git a/thirdparty/source/boost_1_61_0/boost/integer_fwd.hpp b/thirdparty/source/boost_1_61_0/boost/integer_fwd.hpp new file mode 100644 index 0000000..10577ae --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer_fwd.hpp @@ -0,0 +1,187 @@ +// Boost integer_fwd.hpp header file ---------------------------------------// + +// (C) Copyright Dave Abrahams and Daryle Walker 2001. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See http://www.boost.org/libs/integer for documentation. + +#ifndef BOOST_INTEGER_FWD_HPP +#define BOOST_INTEGER_FWD_HPP + +#include // for UCHAR_MAX, etc. +#include // for std::size_t + +#include // for BOOST_NO_INTRINSIC_WCHAR_T +#include // for std::numeric_limits +#include // For intmax_t + + +namespace boost +{ + +#ifdef BOOST_NO_INTEGRAL_INT64_T + typedef unsigned long static_log2_argument_type; + typedef int static_log2_result_type; + typedef long static_min_max_signed_type; + typedef unsigned long static_min_max_unsigned_type; +#else + typedef boost::uintmax_t static_min_max_unsigned_type; + typedef boost::intmax_t static_min_max_signed_type; + typedef boost::uintmax_t static_log2_argument_type; + typedef int static_log2_result_type; +#endif + +// From ------------------------------------------------// + +// Only has typedefs or using statements, with #conditionals + + +// From -----------------------------------------// + +template < class T > + class integer_traits; + +template < > + class integer_traits< bool >; + +template < > + class integer_traits< char >; + +template < > + class integer_traits< signed char >; + +template < > + class integer_traits< unsigned char >; + +#ifndef BOOST_NO_INTRINSIC_WCHAR_T +template < > + class integer_traits< wchar_t >; +#endif + +template < > + class integer_traits< short >; + +template < > + class integer_traits< unsigned short >; + +template < > + class integer_traits< int >; + +template < > + class integer_traits< unsigned int >; + +template < > + class integer_traits< long >; + +template < > + class integer_traits< unsigned long >; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_LONG_LONG) +template < > +class integer_traits< ::boost::long_long_type>; + +template < > +class integer_traits< ::boost::ulong_long_type >; +#elif !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) && defined(BOOST_HAS_MS_INT64) +template < > +class integer_traits<__int64>; + +template < > +class integer_traits; +#endif + + +// From ------------------------------------------------// + +template < typename LeastInt > + struct int_fast_t; + +template< int Bits > + struct int_t; + +template< int Bits > + struct uint_t; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::long_long_type MaxValue > // maximum value to require support +#else + template< long MaxValue > // maximum value to require support +#endif + struct int_max_value_t; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::long_long_type MinValue > // minimum value to require support +#else + template< long MinValue > // minimum value to require support +#endif + struct int_min_value_t; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && defined(BOOST_HAS_LONG_LONG) + template< boost::ulong_long_type MaxValue > // maximum value to require support +#else + template< unsigned long MaxValue > // maximum value to require support +#endif + struct uint_value_t; + + +// From -----------------------------------// + +template < std::size_t Bit > + struct high_bit_mask_t; + +template < std::size_t Bits > + struct low_bits_mask_t; + +template < > + struct low_bits_mask_t< ::std::numeric_limits::digits >; + +// From ------------------------------------// + +template + struct static_log2; + +template <> struct static_log2<0u>; + + +// From ---------------------------------// + +template + struct static_signed_min; + +template + struct static_signed_max; + +template + struct static_unsigned_min; + +template + struct static_unsigned_max; + + +// From + +#ifdef BOOST_NO_INTEGRAL_INT64_T + typedef unsigned long static_gcd_type; +#else + typedef boost::uintmax_t static_gcd_type; +#endif + +template < static_gcd_type Value1, static_gcd_type Value2 > + struct static_gcd; +template < static_gcd_type Value1, static_gcd_type Value2 > + struct static_lcm; + + +// From + +template < typename IntegerType > + class gcd_evaluator; +template < typename IntegerType > + class lcm_evaluator; + + +} // namespace boost + + +#endif // BOOST_INTEGER_FWD_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/integer_traits.hpp b/thirdparty/source/boost_1_61_0/boost/integer_traits.hpp new file mode 100644 index 0000000..94eb00d --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/integer_traits.hpp @@ -0,0 +1,256 @@ +/* boost integer_traits.hpp header file + * + * Copyright Jens Maurer 2000 + * Distributed under the Boost Software License, Version 1.0. (See + * accompanying file LICENSE_1_0.txt or copy at + * http://www.boost.org/LICENSE_1_0.txt) + * + * $Id$ + * + * Idea by Beman Dawes, Ed Brey, Steve Cleary, and Nathan Myers + */ + +// See http://www.boost.org/libs/integer for documentation. + + +#ifndef BOOST_INTEGER_TRAITS_HPP +#define BOOST_INTEGER_TRAITS_HPP + +#include +#include + +// These are an implementation detail and not part of the interface +#include +// we need wchar.h for WCHAR_MAX/MIN but not all platforms provide it, +// and some may have but not ... +#if !defined(BOOST_NO_INTRINSIC_WCHAR_T) && (!defined(BOOST_NO_CWCHAR) || defined(sun) || defined(__sun) || defined(__QNX__)) +#include +#endif + +// +// We simply cannot include this header on gcc without getting copious warnings of the kind: +// +// ../../../boost/integer_traits.hpp:164:66: warning: use of C99 long long integer constant +// +// And yet there is no other reasonable implementation, so we declare this a system header +// to suppress these warnings. +// +#if defined(__GNUC__) && (__GNUC__ >= 4) +#pragma GCC system_header +#endif + +namespace boost { +template +class integer_traits : public std::numeric_limits +{ +public: + BOOST_STATIC_CONSTANT(bool, is_integral = false); +}; + +namespace detail { +template +class integer_traits_base +{ +public: + BOOST_STATIC_CONSTANT(bool, is_integral = true); + BOOST_STATIC_CONSTANT(T, const_min = min_val); + BOOST_STATIC_CONSTANT(T, const_max = max_val); +}; + +#ifndef BOOST_NO_INCLASS_MEMBER_INITIALIZATION +// A definition is required even for integral static constants +template +const bool integer_traits_base::is_integral; + +template +const T integer_traits_base::const_min; + +template +const T integer_traits_base::const_max; +#endif + +} // namespace detail + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +#ifndef BOOST_NO_INTRINSIC_WCHAR_T +template<> +class integer_traits + : public std::numeric_limits, + // Don't trust WCHAR_MIN and WCHAR_MAX with Mac OS X's native + // library: they are wrong! +#if defined(WCHAR_MIN) && defined(WCHAR_MAX) && !defined(__APPLE__) + public detail::integer_traits_base +#elif defined(__BORLANDC__) || defined(__CYGWIN__) || defined(__MINGW32__) || (defined(__BEOS__) && defined(__GNUC__)) + // No WCHAR_MIN and WCHAR_MAX, whar_t is short and unsigned: + public detail::integer_traits_base +#elif (defined(__sgi) && (!defined(__SGI_STL_PORT) || __SGI_STL_PORT < 0x400))\ + || (defined __APPLE__)\ + || (defined(__OpenBSD__) && defined(__GNUC__))\ + || (defined(__NetBSD__) && defined(__GNUC__))\ + || (defined(__FreeBSD__) && defined(__GNUC__))\ + || (defined(__DragonFly__) && defined(__GNUC__))\ + || (defined(__hpux) && defined(__GNUC__) && (__GNUC__ == 3) && !defined(__SGI_STL_PORT)) + // No WCHAR_MIN and WCHAR_MAX, wchar_t has the same range as int. + // - SGI MIPSpro with native library + // - gcc 3.x on HP-UX + // - Mac OS X with native library + // - gcc on FreeBSD, OpenBSD and NetBSD + public detail::integer_traits_base +#else +#error No WCHAR_MIN and WCHAR_MAX present, please adjust integer_traits<> for your compiler. +#endif +{ }; +#endif // BOOST_NO_INTRINSIC_WCHAR_T + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +template<> +class integer_traits + : public std::numeric_limits, + public detail::integer_traits_base +{ }; + +#if !defined(BOOST_NO_INTEGRAL_INT64_T) && !defined(BOOST_NO_INT64_T) +#if defined(ULLONG_MAX) && defined(BOOST_HAS_LONG_LONG) + +template<> +class integer_traits< ::boost::long_long_type> + : public std::numeric_limits< ::boost::long_long_type>, + public detail::integer_traits_base< ::boost::long_long_type, LLONG_MIN, LLONG_MAX> +{ }; + +template<> +class integer_traits< ::boost::ulong_long_type> + : public std::numeric_limits< ::boost::ulong_long_type>, + public detail::integer_traits_base< ::boost::ulong_long_type, 0, ULLONG_MAX> +{ }; + +#elif defined(ULONG_LONG_MAX) && defined(BOOST_HAS_LONG_LONG) + +template<> +class integer_traits< ::boost::long_long_type> : public std::numeric_limits< ::boost::long_long_type>, public detail::integer_traits_base< ::boost::long_long_type, LONG_LONG_MIN, LONG_LONG_MAX>{ }; +template<> +class integer_traits< ::boost::ulong_long_type> + : public std::numeric_limits< ::boost::ulong_long_type>, + public detail::integer_traits_base< ::boost::ulong_long_type, 0, ULONG_LONG_MAX> +{ }; + +#elif defined(ULONGLONG_MAX) && defined(BOOST_HAS_LONG_LONG) + +template<> +class integer_traits< ::boost::long_long_type> + : public std::numeric_limits< ::boost::long_long_type>, + public detail::integer_traits_base< ::boost::long_long_type, LONGLONG_MIN, LONGLONG_MAX> +{ }; + +template<> +class integer_traits< ::boost::ulong_long_type> + : public std::numeric_limits< ::boost::ulong_long_type>, + public detail::integer_traits_base< ::boost::ulong_long_type, 0, ULONGLONG_MAX> +{ }; + +#elif defined(_LLONG_MAX) && defined(_C2) && defined(BOOST_HAS_LONG_LONG) + +template<> +class integer_traits< ::boost::long_long_type> + : public std::numeric_limits< ::boost::long_long_type>, + public detail::integer_traits_base< ::boost::long_long_type, -_LLONG_MAX - _C2, _LLONG_MAX> +{ }; + +template<> +class integer_traits< ::boost::ulong_long_type> + : public std::numeric_limits< ::boost::ulong_long_type>, + public detail::integer_traits_base< ::boost::ulong_long_type, 0, _ULLONG_MAX> +{ }; + +#elif defined(BOOST_HAS_LONG_LONG) +// +// we have long long but no constants, this happens for example with gcc in -ansi mode, +// we'll just have to work out the values for ourselves (assumes 2's compliment representation): +// +template<> +class integer_traits< ::boost::long_long_type> + : public std::numeric_limits< ::boost::long_long_type>, + public detail::integer_traits_base< ::boost::long_long_type, (1LL << (sizeof(::boost::long_long_type) * CHAR_BIT - 1)), ~(1LL << (sizeof(::boost::long_long_type) * CHAR_BIT - 1))> +{ }; + +template<> +class integer_traits< ::boost::ulong_long_type> + : public std::numeric_limits< ::boost::ulong_long_type>, + public detail::integer_traits_base< ::boost::ulong_long_type, 0, ~0uLL> +{ }; + +#elif defined(BOOST_HAS_MS_INT64) + +template<> +class integer_traits< __int64> + : public std::numeric_limits< __int64>, + public detail::integer_traits_base< __int64, _I64_MIN, _I64_MAX> +{ }; + +template<> +class integer_traits< unsigned __int64> + : public std::numeric_limits< unsigned __int64>, + public detail::integer_traits_base< unsigned __int64, 0, _UI64_MAX> +{ }; + +#endif +#endif + +} // namespace boost + +#endif /* BOOST_INTEGER_TRAITS_HPP */ + + + diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/algorithm.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/algorithm.hpp new file mode 100644 index 0000000..d2421ff --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/algorithm.hpp @@ -0,0 +1,90 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_INTRUSIVE_DETAIL_ALGORITHM_HPP +#define BOOST_INTRUSIVE_DETAIL_ALGORITHM_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +namespace boost { +namespace intrusive { + +struct algo_pred_equal +{ + template + bool operator()(const T &x, const T &y) const + { return x == y; } +}; + +struct algo_pred_less +{ + template + bool operator()(const T &x, const T &y) const + { return x < y; } +}; + +template +bool algo_equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, BinaryPredicate p) +{ + for (; first1 != last1; ++first1, ++first2) { + if (!p(*first1, *first2)) { + return false; + } + } + return true; +} + +template +bool algo_equal(InputIt1 first1, InputIt1 last1, InputIt2 first2) +{ return (algo_equal)(first1, last1, first2, algo_pred_equal()); } + +template +bool algo_equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2, BinaryPredicate pred) +{ + for (; first1 != last1 && first2 != last2; ++first1, ++first2) + if (!pred(*first1, *first2)) + return false; + return first1 == last1 && first2 == last2; +} + +template +bool algo_equal(InputIt1 first1, InputIt1 last1, InputIt2 first2, InputIt2 last2) +{ return (algo_equal)(first1, last1, first2, last2, algo_pred_equal()); } + +template + bool algo_lexicographical_compare (InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2, + BinaryPredicate pred) +{ + while (first1 != last1){ + if (first2 == last2 || *first2 < *first1) return false; + else if (pred(*first1, *first2)) return true; + ++first1; ++first2; + } + return (first2 != last2); +} + +template + bool algo_lexicographical_compare (InputIterator1 first1, InputIterator1 last1, + InputIterator2 first2, InputIterator2 last2) +{ return (algo_lexicographical_compare)(first1, last1, first2, last2, algo_pred_less()); } + +} //namespace intrusive { +} //namespace boost { + +#endif //#ifndef BOOST_INTRUSIVE_DETAIL_ALGORITHM_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_begin.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_begin.hpp new file mode 100644 index 0000000..cef8616 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_begin.hpp @@ -0,0 +1,56 @@ +///////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2006-2013 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_CONFIG_HPP +#include +#endif + +#ifdef BOOST_MSVC + + #pragma warning (push) + // + //'function' : resolved overload was found by argument-dependent lookup + //A function found by argument-dependent lookup (Koenig lookup) was eventually + //chosen by overload resolution. + // + //In Visual C++ .NET and earlier compilers, a different function would have + //been called. To pick the original function, use an explicitly qualified name. + // + + //warning C4275: non dll-interface class 'x' used as base for + //dll-interface class 'Y' + #pragma warning (disable : 4275) + //warning C4251: 'x' : class 'y' needs to have dll-interface to + //be used by clients of class 'z' + #pragma warning (disable : 4251) + #pragma warning (disable : 4675) + #pragma warning (disable : 4996) + #pragma warning (disable : 4503) + #pragma warning (disable : 4284) // odd return type for operator-> + #pragma warning (disable : 4244) // possible loss of data + #pragma warning (disable : 4521) ////Disable "multiple copy constructors specified" + #pragma warning (disable : 4127) //conditional expression is constant + #pragma warning (disable : 4146) + #pragma warning (disable : 4267) //conversion from 'X' to 'Y', possible loss of data + #pragma warning (disable : 4541) //'typeid' used on polymorphic type 'boost::exception' with /GR- + #pragma warning (disable : 4512) //'typeid' used on polymorphic type 'boost::exception' with /GR- + #pragma warning (disable : 4522) + #pragma warning (disable : 4706) //assignment within conditional expression + #pragma warning (disable : 4710) // function not inlined + #pragma warning (disable : 4714) // "function": marked as __forceinline not inlined + #pragma warning (disable : 4711) // function selected for automatic inline expansion + #pragma warning (disable : 4786) // identifier truncated in debug info + #pragma warning (disable : 4996) // "function": was declared deprecated +#endif + +//#define BOOST_INTRUSIVE_USE_ITERATOR_FACADE +//#define BOOST_INTRUSIVE_USE_ITERATOR_ENABLE_IF_CONVERTIBLE diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_end.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_end.hpp new file mode 100644 index 0000000..a081443 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/config_end.hpp @@ -0,0 +1,15 @@ +///////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2006-2013 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +///////////////////////////////////////////////////////////////////////////// + +#if defined BOOST_MSVC + #pragma warning (pop) +#endif diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/has_member_function_callable_with.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/has_member_function_callable_with.hpp new file mode 100644 index 0000000..2e73305 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/has_member_function_callable_with.hpp @@ -0,0 +1,341 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/container for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_CALLABLE_WITH_HPP +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_CALLABLE_WITH_HPP + +//Mark that we don't support 0 arg calls due to compiler ICE in GCC 3.4/4.0/4.1 and +//wrong SFINAE for GCC 4.2/4.3 +#if defined(__GNUC__) && !defined(__clang__) && ((__GNUC__*100 + __GNUC_MINOR__*10) >= 340) && ((__GNUC__*100 + __GNUC_MINOR__*10) <= 430) + #define BOOST_INTRUSIVE_DETAIL_HAS_MEMBER_FUNCTION_CALLABLE_WITH_0_ARGS_UNSUPPORTED +#elif defined(BOOST_INTEL) && (BOOST_INTEL < 1200 ) + #define BOOST_INTRUSIVE_DETAIL_HAS_MEMBER_FUNCTION_CALLABLE_WITH_0_ARGS_UNSUPPORTED +#endif +#include +#include +#include + +namespace boost_intrusive_hmfcw { + +typedef char yes_type; +struct no_type{ char dummy[2]; }; + +#if defined(BOOST_NO_CXX11_DECLTYPE) + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template +struct make_dontcare +{ + typedef dont_care type; +}; + +#endif + +struct dont_care +{ + dont_care(...); +}; + +struct private_type +{ + static private_type p; + private_type const &operator,(int) const; +}; + +template +no_type is_private_type(T const &); +yes_type is_private_type(private_type const &); + +#endif //#if defined(BOOST_NO_CXX11_DECLTYPE) + +#if defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + +template struct remove_cv { typedef T type; }; +template struct remove_cv { typedef T type; }; +template struct remove_cv { typedef T type; }; +template struct remove_cv { typedef T type; }; + +#endif + +} //namespace boost_intrusive_hmfcw { + +#endif //BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_CALLABLE_WITH_HPP + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME + #error "You MUST define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME before including this header!" +#endif + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN + #error "You MUST define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN before including this header!" +#endif + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX + #error "You MUST define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX before including this header!" +#endif + +#if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX < BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN + #error "BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX value MUST be greater or equal than BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN!" +#endif + +#if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX == 0 + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_COMMA_IF +#else + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_COMMA_IF , +#endif + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG + #error "BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG not defined!" +#endif + +#ifndef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END + #error "BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END not defined!" +#endif + +BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) && !defined(BOOST_NO_CXX11_DECLTYPE) + //With decltype and variadic templaes, things are pretty easy + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_,BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { + template + static decltype(boost::move_detail::declval(). + BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(::boost::move_detail::declval()...) + , boost_intrusive_hmfcw::yes_type()) Test(U* f); + template + static boost_intrusive_hmfcw::no_type Test(...); + static const bool value = sizeof(Test((Fun*)0)) == sizeof(boost_intrusive_hmfcw::yes_type); + }; + +#else //defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) || defined(BOOST_NO_CXX11_DECLTYPE) + + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + // + // has_member_function_callable_with_impl_XXX + // declaration, special case and 0 arg specializaton + // + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + + #if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + // + // has_member_function_callable_with_impl_XXX for 1 to N arguments + // + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + + //defined(BOOST_NO_CXX11_DECLTYPE) must be true + template + struct FunWrapTmpl : Fun + { + using Fun::BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME; + boost_intrusive_hmfcw::private_type BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(DontCares...) const; + }; + + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_,BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { + typedef FunWrapTmpl::type...> FunWrap; + + static bool const value = (sizeof(boost_intrusive_hmfcw::no_type) == + sizeof(boost_intrusive_hmfcw::is_private_type + ( (::boost::move_detail::declval< FunWrap >(). + BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(::boost::move_detail::declval()...), 0) ) + ) + ); + }; + #else //defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + //Preprocessor must be used to generate specializations instead of variadic templates + + template + class BOOST_MOVE_CAT(has_member_function_named_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { + struct BaseMixin + { + void BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME() + {} //Some compilers require the definition or linker errors happen + }; + + struct Base + : public boost_intrusive_hmfcw::remove_cv::type, public BaseMixin + { //Declare the unneeded default constructor as some old compilers wrongly require it with is_convertible + Base(){} + }; + template class Helper{}; + + template + static boost_intrusive_hmfcw::no_type deduce + (U*, Helper* = 0); + static boost_intrusive_hmfcw::yes_type deduce(...); + + public: + static const bool value = sizeof(boost_intrusive_hmfcw::yes_type) == sizeof(deduce((Base*)0)); + }; + + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + // + // has_member_function_callable_with_impl_XXX specializations + // + ///////////////////////////////////////////////////////// + + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME); + + //No BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME member specialization + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + + { + static const bool value = false; + }; + + #if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN == 0 + //0 arg specialization when BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME is present + #if !defined(BOOST_NO_CXX11_DECLTYPE) + + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { + template + static decltype(boost::move_detail::declval().BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME() + , boost_intrusive_hmfcw::yes_type()) Test(U* f); + + template + static boost_intrusive_hmfcw::no_type Test(...); + static const bool value = sizeof(Test((Fun*)0)) == sizeof(boost_intrusive_hmfcw::yes_type); + }; + + #else //defined(BOOST_NO_CXX11_DECLTYPE) + + #if !defined(BOOST_INTRUSIVE_DETAIL_HAS_MEMBER_FUNCTION_CALLABLE_WITH_0_ARGS_UNSUPPORTED) + + template().BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(), 0)> + struct BOOST_MOVE_CAT(zeroarg_checker_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { boost_intrusive_hmfcw::yes_type dummy[N ? 1 : 2]; }; + + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + { + template static BOOST_MOVE_CAT(zeroarg_checker_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + Test(BOOST_MOVE_CAT(zeroarg_checker_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME)*); + template static boost_intrusive_hmfcw::no_type Test(...); + static const bool value = sizeof(Test< Fun >(0)) == sizeof(boost_intrusive_hmfcw::yes_type); + }; + + #else //defined(BOOST_INTRUSIVE_DETAIL_HAS_MEMBER_FUNCTION_CALLABLE_WITH_0_ARGS_UNSUPPORTED) + + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + {//GCC [3.4-4.3) gives ICE when instantiating the 0 arg version so it is not supported. + static const bool value = true; + }; + + #endif//!defined(BOOST_INTRUSIVE_DETAIL_HAS_MEMBER_FUNCTION_CALLABLE_WITH_0_ARGS_UNSUPPORTED) + #endif //!defined(BOOST_NO_CXX11_DECLTYPE) + #endif //#if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN == 0 + + #if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX > 0 + //1 to N arg specialization when BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME is present + //Declare some unneeded default constructor as some old compilers wrongly require it with is_convertible + #if defined(BOOST_NO_CXX11_DECLTYPE) + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATION(N)\ + \ + template\ + struct BOOST_MOVE_CAT(FunWrap##N, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME)\ + : Fun\ + {\ + using Fun::BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME;\ + BOOST_MOVE_CAT(FunWrap##N, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME)();\ + boost_intrusive_hmfcw::private_type BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME\ + (BOOST_MOVE_REPEAT##N(boost_intrusive_hmfcw::dont_care)) const;\ + };\ + \ + template\ + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME)\ + {\ + static bool const value = (sizeof(boost_intrusive_hmfcw::no_type) == sizeof(boost_intrusive_hmfcw::is_private_type\ + ( (::boost::move_detail::declval\ + < BOOST_MOVE_CAT(FunWrap##N, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) >().\ + BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(BOOST_MOVE_DECLVAL##N), 0) )\ + )\ + );\ + };\ + // + #else + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATION(N)\ + template\ + struct BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME)\ + \ + {\ + template\ + static decltype(boost::move_detail::declval().\ + BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME(BOOST_MOVE_DECLVAL##N)\ + , boost_intrusive_hmfcw::yes_type()) Test(U* f);\ + template\ + static boost_intrusive_hmfcw::no_type Test(...);\ + static const bool value = sizeof(Test((Fun*)0)) == sizeof(boost_intrusive_hmfcw::yes_type);\ + };\ + // + #endif + //////////////////////////////////// + // Build and invoke BOOST_MOVE_ITERATE_NTOM macrofunction, note that N has to be at least 1 + //////////////////////////////////// + #if BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN == 0 + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATE_MIN 1 + #else + #define BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATE_MIN BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN + #endif + BOOST_MOVE_CAT + (BOOST_MOVE_CAT(BOOST_MOVE_CAT(BOOST_MOVE_ITERATE_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATE_MIN), TO) + ,BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX) + (BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATION) + #undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATION + #undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_ITERATE_MIN + //////////////////////////////////// + // End of BOOST_MOVE_ITERATE_NTOM + //////////////////////////////////// + #endif //BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX > 0 + + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + // + // has_member_function_callable_with_FUNC + // + ///////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////// + + //Otherwise use the preprocessor + template + struct BOOST_MOVE_CAT(has_member_function_callable_with_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + : public BOOST_MOVE_CAT(has_member_function_callable_with_impl_, BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME) + ::value + BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_COMMA_IF BOOST_MOVE_CAT(BOOST_MOVE_TARG,BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX)> + {}; + #endif //defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) +#endif + +BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END + +//Undef local macros +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_COMMA_IF + +//Undef user defined macros +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_FUNCNAME +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MIN +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_MAX +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_BEG +#undef BOOST_INTRUSIVE_HAS_MEMBER_FUNCTION_CALLABLE_WITH_NS_END diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/iterator.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/iterator.hpp new file mode 100644 index 0000000..2ae6abb --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/iterator.hpp @@ -0,0 +1,156 @@ +///////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_INTRUSIVE_DETAIL_ITERATOR_HPP +#define BOOST_INTRUSIVE_DETAIL_ITERATOR_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include +#include +#include + +namespace boost { +namespace intrusive { + +using boost::movelib::iterator_traits; + +//////////////////// +// iterator +//////////////////// +template +struct iterator +{ + typedef Category iterator_category; + typedef T value_type; + typedef Difference difference_type; + typedef Pointer pointer; + typedef Reference reference; +}; + +//////////////////////////////////////// +// iterator_[dis|en]able_if_tag +//////////////////////////////////////// +template +struct iterator_enable_if_tag + : ::boost::move_detail::enable_if_c + < ::boost::move_detail::is_same + < typename boost::intrusive::iterator_traits::iterator_category + , Tag + >::value + , R> +{}; + +template +struct iterator_disable_if_tag + : ::boost::move_detail::enable_if_c + < !::boost::move_detail::is_same + < typename boost::intrusive::iterator_traits::iterator_category + , Tag + >::value + , R> +{}; + +//////////////////////////////////////// +// iterator_[dis|en]able_if_tag_difference_type +//////////////////////////////////////// +template +struct iterator_enable_if_tag_difference_type + : iterator_enable_if_tag::difference_type> +{}; + +template +struct iterator_disable_if_tag_difference_type + : iterator_disable_if_tag::difference_type> +{}; + +//////////////////// +// advance +//////////////////// +template +typename iterator_enable_if_tag::type + iterator_advance(InputIt& it, Distance n) +{ + while(n--) + ++it; +} + +template +typename iterator_enable_if_tag::type + iterator_advance(InputIt& it, Distance n) +{ + while(n--) + ++it; +} + +template +typename iterator_enable_if_tag::type + iterator_advance(InputIt& it, Distance n) +{ + for (; 0 < n; --n) + ++it; + for (; n < 0; ++n) + --it; +} + +template +BOOST_INTRUSIVE_FORCEINLINE typename iterator_enable_if_tag::type + iterator_advance(InputIt& it, Distance n) +{ + it += n; +} + +//////////////////// +// distance +//////////////////// +template inline +typename iterator_disable_if_tag_difference_type + ::type + iterator_distance(InputIt first, InputIt last) +{ + typename iterator_traits::difference_type off = 0; + while(first != last){ + ++off; + ++first; + } + return off; +} + +template +BOOST_INTRUSIVE_FORCEINLINE typename iterator_enable_if_tag_difference_type + ::type + iterator_distance(InputIt first, InputIt last) +{ + typename iterator_traits::difference_type off = last - first; + return off; +} + +template +BOOST_INTRUSIVE_FORCEINLINE typename iterator_traits::pointer iterator_arrow_result(const I &i) +{ return i.operator->(); } + +template +BOOST_INTRUSIVE_FORCEINLINE T * iterator_arrow_result(T *p) +{ return p; } + +} //namespace intrusive +} //namespace boost + +#endif //BOOST_INTRUSIVE_DETAIL_ITERATOR_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/minimal_pair_header.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/minimal_pair_header.hpp new file mode 100644 index 0000000..1358a08 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/minimal_pair_header.hpp @@ -0,0 +1,30 @@ +///////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2015 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +///////////////////////////////////////////////////////////////////////////// +#ifndef BOOST_INTRUSIVE_DETAIL_MINIMAL_PAIR_HEADER_HPP +#define BOOST_INTRUSIVE_DETAIL_MINIMAL_PAIR_HEADER_HPP +# +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif +# +#ifndef BOOST_CONFIG_HPP +# include +#endif +# +#//Try to avoid including , as it's quite big in C++11 +#if defined(BOOST_GNU_STDLIB) +# include +#else +# include //Fallback +#endif +# +#endif //BOOST_INTRUSIVE_DETAIL_MINIMAL_PAIR_HEADER_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/mpl.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/mpl.hpp new file mode 100644 index 0000000..8d227a1 --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/mpl.hpp @@ -0,0 +1,206 @@ +///////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2006-2014 +// (C) Copyright Microsoft Corporation 2014 +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +///////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_INTRUSIVE_DETAIL_MPL_HPP +#define BOOST_INTRUSIVE_DETAIL_MPL_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#include +#include +#include + +namespace boost { +namespace intrusive { +namespace detail { + +using boost::move_detail::is_same; +using boost::move_detail::add_const; +using boost::move_detail::remove_const; +using boost::move_detail::remove_cv; +using boost::move_detail::remove_reference; +using boost::move_detail::add_reference; +using boost::move_detail::remove_pointer; +using boost::move_detail::add_pointer; +using boost::move_detail::true_type; +using boost::move_detail::false_type; +using boost::move_detail::enable_if_c; +using boost::move_detail::enable_if; +using boost::move_detail::disable_if_c; +using boost::move_detail::disable_if; +using boost::move_detail::is_convertible; +using boost::move_detail::if_c; +using boost::move_detail::if_; +using boost::move_detail::is_const; +using boost::move_detail::identity; +using boost::move_detail::alignment_of; +using boost::move_detail::is_empty; +using boost::move_detail::addressof; +using boost::move_detail::integral_constant; +using boost::move_detail::enable_if_convertible; +using boost::move_detail::disable_if_convertible; +using boost::move_detail::bool_; +using boost::move_detail::true_; +using boost::move_detail::false_; +using boost::move_detail::yes_type; +using boost::move_detail::no_type; +using boost::move_detail::apply; +using boost::move_detail::eval_if_c; +using boost::move_detail::eval_if; +using boost::move_detail::unvoid_ref; +using boost::move_detail::add_const_if_c; + +template +struct ls_zeros +{ + static const std::size_t value = (S & std::size_t(1)) ? 0 : (1 + ls_zeros<(S>>1u)>::value); +}; + +template<> +struct ls_zeros<0> +{ + static const std::size_t value = 0; +}; + +template<> +struct ls_zeros<1> +{ + static const std::size_t value = 0; +}; + +// Infrastructure for providing a default type for T::TNAME if absent. +#define BOOST_INTRUSIVE_INSTANTIATE_DEFAULT_TYPE_TMPLT(TNAME) \ + template \ + struct boost_intrusive_default_type_ ## TNAME \ + { \ + template \ + static char test(int, typename X::TNAME*); \ + \ + template \ + static int test(...); \ + \ + struct DefaultWrap { typedef DefaultType TNAME; }; \ + \ + static const bool value = (1 == sizeof(test(0, 0))); \ + \ + typedef typename \ + ::boost::intrusive::detail::if_c \ + ::type::TNAME type; \ + }; \ + // + +#define BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_DEFAULT(INSTANTIATION_NS_PREFIX, T, TNAME, TIMPL) \ + typename INSTANTIATION_NS_PREFIX \ + boost_intrusive_default_type_ ## TNAME< T, TIMPL >::type \ +// + +#define BOOST_INTRUSIVE_INSTANTIATE_EVAL_DEFAULT_TYPE_TMPLT(TNAME)\ + template \ + struct boost_intrusive_eval_default_type_ ## TNAME \ + { \ + template \ + static char test(int, typename X::TNAME*); \ + \ + template \ + static int test(...); \ + \ + struct DefaultWrap \ + { typedef typename DefaultType::type TNAME; }; \ + \ + static const bool value = (1 == sizeof(test(0, 0))); \ + \ + typedef typename \ + ::boost::intrusive::detail::eval_if_c \ + < value \ + , ::boost::intrusive::detail::identity \ + , ::boost::intrusive::detail::identity \ + >::type::TNAME type; \ + }; \ +// + +#define BOOST_INTRUSIVE_OBTAIN_TYPE_WITH_EVAL_DEFAULT(INSTANTIATION_NS_PREFIX, T, TNAME, TIMPL) \ + typename INSTANTIATION_NS_PREFIX \ + boost_intrusive_eval_default_type_ ## TNAME< T, TIMPL >::type \ +// + +#define BOOST_INTRUSIVE_INTERNAL_STATIC_BOOL_IS_TRUE(TRAITS_PREFIX, TYPEDEF_TO_FIND) \ +template \ +struct TRAITS_PREFIX##_bool\ +{\ + template\ + struct two_or_three {yes_type _[2 + Add];};\ + template static yes_type test(...);\ + template static two_or_three test (int);\ + static const std::size_t value = sizeof(test(0));\ +};\ +\ +template \ +struct TRAITS_PREFIX##_bool_is_true\ +{\ + static const bool value = TRAITS_PREFIX##_bool::value > sizeof(yes_type)*2;\ +};\ +// + +#define BOOST_INTRUSIVE_HAS_STATIC_MEMBER_FUNC_SIGNATURE(TRAITS_NAME, FUNC_NAME) \ + template \ + class TRAITS_NAME \ + { \ + private: \ + template struct helper;\ + template \ + static ::boost::intrusive::detail::yes_type test(helper<&T::FUNC_NAME>*); \ + template static ::boost::intrusive::detail::no_type test(...); \ + public: \ + static const bool value = sizeof(test(0)) == sizeof(::boost::intrusive::detail::yes_type); \ + }; \ +// + +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNC_CALLED(TRAITS_NAME, FUNC_NAME) \ +template \ +struct TRAITS_NAME \ +{ \ + struct BaseMixin \ + { \ + void FUNC_NAME(); \ + }; \ + struct Base : public Type, public BaseMixin { Base(); }; \ + template class Helper{}; \ + template \ + static ::boost::intrusive::detail::no_type test(U*, Helper* = 0); \ + static ::boost::intrusive::detail::yes_type test(...); \ + static const bool value = sizeof(::boost::intrusive::detail::yes_type) == sizeof(test((Base*)(0))); \ +};\ +// + +#define BOOST_INTRUSIVE_HAS_MEMBER_FUNC_CALLED_IGNORE_SIGNATURE(TRAITS_NAME, FUNC_NAME) \ +BOOST_INTRUSIVE_HAS_MEMBER_FUNC_CALLED(TRAITS_NAME##_ignore_signature, FUNC_NAME) \ +\ +template \ +struct TRAITS_NAME \ + : public TRAITS_NAME##_ignore_signature \ +{};\ +// + +} //namespace detail +} //namespace intrusive +} //namespace boost + +#include + +#endif //BOOST_INTRUSIVE_DETAIL_MPL_HPP diff --git a/thirdparty/source/boost_1_61_0/boost/intrusive/detail/pointer_element.hpp b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/pointer_element.hpp new file mode 100644 index 0000000..dd26e3c --- /dev/null +++ b/thirdparty/source/boost_1_61_0/boost/intrusive/detail/pointer_element.hpp @@ -0,0 +1,168 @@ +////////////////////////////////////////////////////////////////////////////// +// +// (C) Copyright Ion Gaztanaga 2014-2014. Distributed under the Boost +// Software License, Version 1.0. (See accompanying file +// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) +// +// See http://www.boost.org/libs/intrusive for documentation. +// +////////////////////////////////////////////////////////////////////////////// + +#ifndef BOOST_INTRUSIVE_DETAIL_POINTER_ELEMENT_HPP +#define BOOST_INTRUSIVE_DETAIL_POINTER_ELEMENT_HPP + +#ifndef BOOST_CONFIG_HPP +# include +#endif + +#if defined(BOOST_HAS_PRAGMA_ONCE) +# pragma once +#endif + +#ifndef BOOST_INTRUSIVE_DETAIL_WORKAROUND_HPP +#include +#endif //BOOST_INTRUSIVE_DETAIL_WORKAROUND_HPP + +namespace boost { +namespace intrusive { +namespace detail{ + +////////////////////// +//struct first_param +////////////////////// + +template struct first_param +{ typedef void type; }; + +#if !defined(BOOST_NO_CXX11_VARIADIC_TEMPLATES) + + template